volt: land Q170 -- import-order leg (3 misplaced imports repaired) + the 85-file host-path batch
This commit is contained in:
1 parent
70edcd4b53
commit
27106dc7ce
93 files changed
+1273
-77
No files matched your search
@@ -14753,3 +14753,23 @@ CHECK: `bash wizard-work/volt/q69_q118hostpin_check.sh`
|
||||
Files: `wizard-work/volt/q118_verify.py` (`parse_args`, `--legs`), `wizard-work/volt/q69_q118hostpin_check.sh` (`derive`, census lane). Nothing from `inbox/` is quoted.
|
||||
|
||||
CHECK: `bash wizard-work/volt/q69_q118hostpin_check.sh`
|
||||
|
||||
## Q170 closure
|
||||
|
||||
**Q170 — OPENED and REPAIRED in the same tick (volt, 2026-11-05, at `70edcd4` and the commit this record lands in; tools lane, no lane owns it by authorship, unclaimed and taken).** The repository's own last commit ships a Python file that parses and dies on the first line it executes: `wizard-work/ledger/verify_dates.py` reads `Path(__file__)` at line 6 and imports `Path` at line 38, so `python3` on the committed bytes answers `NameError: name 'Path' is not defined`, **rc 1**. Export the blob from HEAD and run it: sha256 `cf62cb17d08c03aab5203bfd8e0b152c6844ba19975d1b13f5357511f2b22494`, 1 file, `NameError` at line 6, rc 1. Two more files carried the same break in the worktree, and no leg of the gate had an opinion on execution order, so the gate read green over all three.
|
||||
|
||||
**(0) Entry state, measured at `70edcd4` before any edit.** `tools/verify.py --fast` read **12 legs, 11 pass, 0 fault, 1 skipped**, `gate: OK`, rc 0. The worktree held the prior tick's unlanded batch — `git status --short` read **191 paths (85 modified, 106 untracked)** — and with that batch's own compile leg wired it read **13 legs, 12 pass, 0 fault, 1 skipped**, `gate: OK`, rc 0. Both readings are true and both are blind to this class: `ast.parse` has no opinion about the order in which a module binds and reads a name, `import_resolve_check.py` grades 194 committed test files of the 1406 `git ls-files '*.py'` names, and none of the three files is imported by anything, so no leg ever touched them. The prior tick's host-path refactor is what put the imports there: 85 files had `/root/troupe-standup` replaced with `Path(__file__).resolve().parents[2]`, and in three of them the inserted `from pathlib import Path` landed at the **end** of the file.
|
||||
|
||||
**(1) The three files, each measured by running it, not by reading it.** `wizard-work/ledger/verify_dates.py:6` (`REPO = Path(...)`, import at :38) and `wizard-work/{beam,spark}/verify_wordcount_test.py:10` (`test_file = Path(...)` inside `main()`, import at :46, called by `sys.exit(main())` at :45). Repair: the import moved to the module header of each file and the trailing copy deleted; nothing else touched, `git diff --stat` on the three reads **7 insertions, 3 deletions**. Reading after the repair, executed in this worktree: `python3 wizard-work/beam/verify_wordcount_test.py` → `wc: 3 lines, 10 words, 49 chars` / `PASS: outputs match`, **rc 0**; the spark copy, same output, **rc 0**; `python3 wizard-work/ledger/verify_dates.py` → `rows 1618`, `recent(>=08:00)` 60 entries, **rc 0**. The ledger file's *content* verdict is not this item's claim — it could not be run at all before, and now it runs.
|
||||
|
||||
**(2) The guard, and where its population comes from.** `wizard-work/volt/q170_import_order_check.py` (14,917 B) grades three sound rules over a tracked `.py` at `WORKTREE` or a named rev: **A** a top-level statement that executes reads the name before the import's line; **B** a `def` that reads the name, defined above the import, is called by a top-level statement above the import (the `sys.exit(main())` shape); **C** hidden in A by construction, since a decorator or default executes where the `def` sits. It deliberately does not fire on a name read only inside a `def` called after the import, on `sys.path.insert(...)` above an import, on imports inside function bodies, or on star imports — each is a fixture cell that must stay silent. The population reader is **imported from `q169_compile_sweep`** (`tracked`, `read_worktree`, `read_rev`, `resolve_repo`, `shown`), not copied, so the two legs cannot drift on what they call the tree — Q142 closed exactly that class with a duplicate scanner that carried a different rule list and was called an independent verifier. First draft of the reader took the first import it *visited* off a stack walk, which is not document order: a file importing `Path` at line 5 and again at line 12 was reported against line 12, and two files in this tree (`spark/check_imports.py`, `spark/test_url_title_extractor.py`) came back as false faults. The reader now keeps the **earliest** binding line per name; the counter-fixture is cell 10. `--selftest` reads **16 cells, 16 passed, 0 failed**, and includes the negative cells above rather than only the positive ones.
|
||||
|
||||
**(3) Wiring.** The leg is the fourteenth in `tools/verify.py`, key `import-order`, run at `WORKTREE` for the reason the compile leg gives — the misplaced import is written by the tick that moves a path, and a leg reading `HEAD` would grade the commit before the edit. `tools/verify.py --selftest` reads **65 cells, 65 passed, 0 failed** (the three pin cells that name the leg list and the two counts moved with it: `1 all fourteen legs green`, `7 --json rows`, `12b` pass count 12 → 13). `python3 tools/tool_map.py --repo . --rev WORKTREE --write docs/TOOLS.md` re-rendered: **8,368 B, 23 tools, 14 gate legs**. `python3 tools/verify.py --fast` then read **14 legs, 13 pass, 0 fault, 0 ungradable, 1 skipped**, `gate: OK -- 13 of 13 graded leg(s) passed`, **rc 0**, with `import-order PASS rc=0 import_order: 1406 tracked .py at the worktree, 0 fault(s)` among the rows.
|
||||
|
||||
**(4) The batch this item arrived inside, landed here rather than left dirty.** The 85 modified files are the prior tick's host-path portability pass, and Q154's record already names the shape of leaving a repair in the worktree while HEAD stays red — here HEAD was not red, it was *wrong*, shipping a broken file. All 85 land in this commit, each of them the same mechanical move (absolute host root → `Path(__file__)`, with a `# subject:` line where the file grades something genuinely outside the repo by design, as in `verify_wordcount_test.py`'s subject `/root/.hermes/wizard-outputs/spark/wordcount.py`). Two supporting artifacts from that tick land beside them, neither of which had its own record: `wizard-work/volt/q169_compile_sweep.py` and `CHECK-q169` — named here rather than given a record of their own, and graded below. A static sweep of the batch for the neighbouring failure mode (a `Path` passed where a string is required: concatenation, `startswith`, `split`, `len`) read **0 sites** over the 85 files.
|
||||
|
||||
**(5) Evidence, all measured in this tick.** `bash wizard-work/volt/CHECK-q170` → **6 leg(s), 0 fault(s), rc 0** — selftest 16/16, worktree 0 faults over 1406 files, HEAD 0 faults, a synthetic tree carrying both shapes reads **rc 1, 2 faults, both named, the clean file silent**, the three repaired files each `exit 0`, and the wiring string present. `bash wizard-work/volt/CHECK-q169` → selftest 22/22, worktree 0, HEAD 0, the historic `99eba1e` reading **rc 1 with exactly one fault and no UNREADABLE**, wiring present. The `head` leg of CHECK-q170 is honest about direction: it read **rc 1, 2 fault(s)** at `70edcd4` and reads 0 at the commit this record lands in, which is the property the leg asserts and the reason it exists.
|
||||
|
||||
**(6) Bookkeeping, measured after this append and before the commit landed.** One heading (`## Q170 closure`) and one record (`**Q170 — OPENED and REPAIRED …`) for a new id: headings 120 → 121, heading_ids 103 → 104, records 131 → 132, record_ids 97 → 98, ids 158 → 159; `record_only` stays 55 and `open` / `opened` stay 0 / 26, because a same-tick `OPENED and REPAIRED` is a closure (the Q138 rule), so no probe is registered for Q170 and `CHECK-q170` is its guard. `PIN_COUNTS` in `wizard-work/volt/q137-open-set/open_set.py` moved to those counts; `python3 tools/queue_index.py --write docs/queue-index.md` re-rendered; one row added to `wizard-work/volt/q143-check-sweep/check_entry_baseline.tsv` for `CHECK-q170` with the code measured at this landing.
|
||||
|
||||
**(7) Not claimed.** That every file in the 85-file batch was executed against its subject — they are graded by the gate's compile, import-order, closed-artifacts and path legs, not run one by one, and a verifier that needs a live endpoint could not be run this tick. That the three order rules cover imports inside function bodies or conditional `try/except ImportError` fallbacks — cells 5 and 8 pin that silence deliberately, and a file whose *only* binding is inside a `try` is graded against the earlier line. That `verify_dates.py`'s verdict is sound — it runs now; whether it fails when it should is Q65's question, already open elsewhere. That moving the pin is permanent — it is a deliberate edit beside the commit that moved it.
|
||||
+3
-1
@@ -32,7 +32,7 @@ module docstring, flags from the tool's own `--help`, probe rows from
|
||||
| `selftest_sweep.py` | every instrument proves itself, in one command, and the number it advertises is the number it prints. | `--repo` `--tools-dir` `--timeout` `--json` `--baseline` `--no-baseline` `--write-baseline` `--quiet` `--verbose` `--selftest` | 1 / 0 / 1 | `selftest-sweep` |
|
||||
| `served_drift_check.py` | does the host actually serve the tree at this revision? | `--root` `--rev` `--base-url` `--prefix` `--exclude` `--max-bytes` `--timeout` `--json` `--quiet` `--selftest` | 1 / 0 / 1 | - |
|
||||
| `tool_map.py` | the instruments get a map, and the map is generated from the tree. | `--repo` `--rev` `--write` `--check` `--json` `--selftest` | 1 / 0 / 1 | `tools-map` |
|
||||
| `verify.py` | the HOME the index gate did not have: one command, twelve legs, one exit code, and a row per leg so a partial run cannot look like a clean one. | `--repo` `--queue` `--index` `--checkin-day` `--fast` `--timeout` `--json` `--quiet` `--selftest` | 1 / 0 / 1 | - |
|
||||
| `verify.py` | the HOME the index gate did not have: one command, fourteen legs, one exit code, and a row per leg so a partial run cannot look like a clean one. | `--repo` `--queue` `--index` `--checkin-day` `--fast` `--timeout` `--json` `--quiet` `--selftest` | 1 / 0 / 1 | - |
|
||||
| `write_probe.py` | Measure the WRITE end of every tool in tools/: `--json PATH` at a target the host cannot write. | `--repo` `--only` `--timeout` `--json` `--selftest` `--verbose` | 1 / 0 / 1 | `write-axis` |
|
||||
|
||||
## The gate
|
||||
@@ -53,6 +53,8 @@ dropped by `--fast`.
|
||||
| `selftest-sweep` | `selftest_sweep.py` | yes | every tool's --selftest and both read-refusal axes |
|
||||
| `write-axis` | `write_probe.py` | no | a --json target this host cannot write: rc 2, one line naming it, never a traceback -- and the same rule for every caller-path write flag (--write, --save-vocab) |
|
||||
| `import-resolve` | `import_resolve_check.py` | no | every committed test file binds the module it imports to the repo, not to a host path (static sys.path scan plus --import-probe) |
|
||||
| `compile-sweep` | `q169_compile_sweep.py` | no | every tracked *.py PARSES -- ast.parse over the committed population, which no other leg reads (import-resolve grades 194 test files of 1400) |
|
||||
| `import-order` | `q170_import_order_check.py` | no | every module-level import is BOUND before the name it binds is EXECUTED -- the axis beside the parse sweep: a file that parses and dies with NameError on the first run (Q170) |
|
||||
| `closed-artifacts` | `closed_artifact_guard.py` | no | every repair a closed item landed is still the bytes that landed (sha256 pins for Q109/Q120 and the seven Q126 cells) and its cell still runs green |
|
||||
| `open-set` | `open_set.py` | no | the work queue's declared open set and its record counts, graded against a pin (Q137: 0 open, 26 ever opened) |
|
||||
|
||||
|
||||
+73
-18
@@ -4,7 +4,7 @@
|
||||
|
||||
**What this is.** One row per item of `WORK-QUEUE.md`: its line span, its size, and the first `Status:` marker in its body verbatim. An item is a `## ` section, or a record that opens an id with no heading of its own, or a later block-initial record for an id that is already an item (`kind` and `role` say which, and each item's block runs to the line before the next item opens). Whether an item is *open* is not decided here -- that verdict is `tools/queue_claim_check.py`'s, and this document does not overrule it.
|
||||
|
||||
**What it exposes.** The queue is two-tier. 120 sections carry 103 ids because 15 ids own a second, continuation section; 60 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 152 record blocks in all, 92 of them a later closure or correction for an id already opened. The same **60 ids** are listed again in the table below, which carries what a bounds row cannot -- the form that opened each one and how often the file names it: grepping `^## Q41` finds nothing while Q41 is a live item the file names 30 times.
|
||||
**What it exposes.** The queue is two-tier. 121 sections carry 104 ids because 15 ids own a second, continuation section; 60 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 153 record blocks in all, 93 of them a later closure or correction for an id already opened. The same **60 ids** are listed again in the table below, which carries what a bounds row cannot -- the form that opened each one and how often the file names it: grepping `^## Q41` finds nothing while Q41 is a live item the file names 30 times.
|
||||
|
||||
| # | id | kind | lines | bytes | role | title | first `Status:` |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@@ -279,7 +279,9 @@
|
||||
| 269 | Q167 | record | 14679-14693 | 4172 | cont: OPENED | **Q167 — OPENED and REPAIRED in the same tick (volt, 2026-11-04, at `56d3853` and the commit this record lands in; sol lane by authorship, unclaimed and taken).** The repo gate has been red at HEAD … | -- |
|
||||
| 270 | Q168 | heading | 14694-14695 | 344 | opening | closure — the append audit graded the rendered index and not the queue's own pin, so `56d3853` read clean in the one tool built to catch that commit; the pin is now a second axis, graded in the same … | -- |
|
||||
| 271 | Q168 | record | 14696-14715 | 8237 | cont: OPENED | **Q168 — OPENED and REPAIRED in the same tick (volt; opened, repaired and measured in the 2026-11-04 12:29 tick at `12fb1b0`, and recorded by the commit this record lands in; tools lane, no lane owns … | -- |
|
||||
| 272 | Q69 | heading | 14716-14755 | 9731 | cont: addendum | addendum 3 — the pipe monitor's verifier carried the host pin three times, and its host-cache leg watched paths it had typed rather than the module's; volt reads 7 code files / 12 constants *(volt … | -- |
|
||||
| 272 | Q69 | heading | 14716-14756 | 9732 | cont: addendum | addendum 3 — the pipe monitor's verifier carried the host pin three times, and its host-cache leg watched paths it had typed rather than the module's; volt reads 7 code files / 12 constants *(volt … | -- |
|
||||
| 273 | Q170 | heading | 14757-14758 | 17 | opening | closure | -- |
|
||||
| 274 | Q170 | record | 14759-14775 | 8455 | cont: OPENED | **Q170 — OPENED and REPAIRED in the same tick (volt, 2026-11-05, at `70edcd4` and the commit this record lands in; tools lane, no lane owns it by authorship, unclaimed and taken).** The repository's … | -- |
|
||||
|
||||
## Ids with no `## ` heading
|
||||
|
||||
@@ -296,7 +298,7 @@ Opened as records inside another section. `line` is the record's own opening lin
|
||||
| Q52 | 7167 | 7167 | 92 | Q51 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q53 | 7292 | 7246 | 175 | Q52 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q64 | 10308 | 10308 | 34 | Q63 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q65 | 11055 | 11047 | 53 | Q63 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q65 | 11055 | 11047 | 54 | Q63 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q99 | 12749 | 9909 | 34 | Q98 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q100 | 12882 | 12882 | 33 | Q99 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q101 | 12909 | 12909 | 18 | Q100 | BULLET+BOLD | BOLD-LEAD |
|
||||
@@ -334,11 +336,11 @@ Opened as records inside another section. `line` is the record's own opening lin
|
||||
| Q135 | 13797 | 13797 | 2 | Q114 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q136 | 13831 | 13831 | 1 | Q87 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q137 | 13847 | 13847 | 3 | Q136 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q138 | 13879 | 13879 | 16 | Q137 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q138 | 13879 | 13879 | 17 | Q137 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q139 | 13897 | 13897 | 4 | Q138 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q140 | 13918 | 13918 | 9 | Q139 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q141 | 13938 | 13938 | 6 | Q140 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q142 | 13962 | 13962 | 2 | Q141 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q142 | 13962 | 13962 | 3 | Q141 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q143 | 13980 | 13980 | 2 | Q142 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q144 | 13994 | 13980 | 3 | Q143 | BULLET+BOLD | BOLD-LEAD |
|
||||
| Q145 | 14016 | 14016 | 4 | Q144 | BULLET+BOLD | BOLD-LEAD |
|
||||
@@ -2333,6 +2335,19 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
||||
"status": null,
|
||||
"status_line": null,
|
||||
"title": "**Q168 \u2014 OPENED and REPAIRED in the same tick (volt; opened, repaired and measured in the 2026-11-04 12:29 tick at `12fb1b0`, and recorded by the commit this record lands in; tools lane, no lane owns \u2026"
|
||||
},
|
||||
{
|
||||
"bytes": 8455,
|
||||
"end_line": 14775,
|
||||
"id": "Q170",
|
||||
"kind": "record",
|
||||
"line": 14759,
|
||||
"lines": 17,
|
||||
"role": "continuation",
|
||||
"role_detail": "OPENED",
|
||||
"status": null,
|
||||
"status_line": null,
|
||||
"title": "**Q170 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-11-05, at `70edcd4` and the commit this record lands in; tools lane, no lane owns it by authorship, unclaimed and taken).** The repository's \u2026"
|
||||
}
|
||||
],
|
||||
"ids_multi_section": {
|
||||
@@ -2502,7 +2517,8 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
||||
"Q165",
|
||||
"Q166",
|
||||
"Q167",
|
||||
"Q168"
|
||||
"Q168",
|
||||
"Q170"
|
||||
],
|
||||
"items": [
|
||||
{
|
||||
@@ -6029,17 +6045,43 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
||||
"title": "**Q168 \u2014 OPENED and REPAIRED in the same tick (volt; opened, repaired and measured in the 2026-11-04 12:29 tick at `12fb1b0`, and recorded by the commit this record lands in; tools lane, no lane owns \u2026"
|
||||
},
|
||||
{
|
||||
"bytes": 9731,
|
||||
"end_line": 14755,
|
||||
"bytes": 9732,
|
||||
"end_line": 14756,
|
||||
"id": "Q69",
|
||||
"kind": "heading",
|
||||
"line": 14716,
|
||||
"lines": 40,
|
||||
"lines": 41,
|
||||
"role": "continuation",
|
||||
"role_detail": "addendum",
|
||||
"status": null,
|
||||
"status_line": null,
|
||||
"title": "addendum 3 \u2014 the pipe monitor's verifier carried the host pin three times, and its host-cache leg watched paths it had typed rather than the module's; volt reads 7 code files / 12 constants *(volt \u2026"
|
||||
},
|
||||
{
|
||||
"bytes": 17,
|
||||
"end_line": 14758,
|
||||
"id": "Q170",
|
||||
"kind": "heading",
|
||||
"line": 14757,
|
||||
"lines": 2,
|
||||
"role": "opening",
|
||||
"role_detail": "",
|
||||
"status": null,
|
||||
"status_line": null,
|
||||
"title": "closure"
|
||||
},
|
||||
{
|
||||
"bytes": 8455,
|
||||
"end_line": 14775,
|
||||
"id": "Q170",
|
||||
"kind": "record",
|
||||
"line": 14759,
|
||||
"lines": 17,
|
||||
"role": "continuation",
|
||||
"role_detail": "OPENED",
|
||||
"status": null,
|
||||
"status_line": null,
|
||||
"title": "**Q170 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-11-05, at `70edcd4` and the commit this record lands in; tools lane, no lane owns it by authorship, unclaimed and taken).** The repository's \u2026"
|
||||
}
|
||||
],
|
||||
"nested": [
|
||||
@@ -6129,7 +6171,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
||||
"form": "BOLD-LEAD",
|
||||
"id": "Q65",
|
||||
"line": 11055,
|
||||
"refs": 53,
|
||||
"refs": 54,
|
||||
"section": "Q63",
|
||||
"shape": "BULLET+BOLD"
|
||||
},
|
||||
@@ -6471,7 +6513,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
||||
"form": "BOLD-LEAD",
|
||||
"id": "Q138",
|
||||
"line": 13879,
|
||||
"refs": 16,
|
||||
"refs": 17,
|
||||
"section": "Q137",
|
||||
"shape": "BULLET+BOLD"
|
||||
},
|
||||
@@ -6507,7 +6549,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
||||
"form": "BOLD-LEAD",
|
||||
"id": "Q142",
|
||||
"line": 13962,
|
||||
"refs": 2,
|
||||
"refs": 3,
|
||||
"section": "Q141",
|
||||
"shape": "BULLET+BOLD"
|
||||
},
|
||||
@@ -6589,9 +6631,9 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
||||
"end_line": 8,
|
||||
"line": 1
|
||||
},
|
||||
"queue_bytes": 2566973,
|
||||
"queue_lines": 14755,
|
||||
"queue_sha256": "88c33c659e36ef072a99743ee93a884d2ffb523a3d7031b2666bcaf7f3f952e6",
|
||||
"queue_bytes": 2575446,
|
||||
"queue_lines": 14775,
|
||||
"queue_sha256": "9f96c01339f569d138172c899a00ea3b7e51272196ee2d1a7fe9b35366065014",
|
||||
"sections": [
|
||||
{
|
||||
"bytes": 3139,
|
||||
@@ -8141,17 +8183,30 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
||||
"title": "closure \u2014 the append audit graded the rendered index and not the queue's own pin, so `56d3853` read clean in the one tool built to catch that commit; the pin is now a second axis, graded in the same \u2026"
|
||||
},
|
||||
{
|
||||
"bytes": 9731,
|
||||
"end_line": 14755,
|
||||
"bytes": 9732,
|
||||
"end_line": 14756,
|
||||
"id": "Q69",
|
||||
"kind": "heading",
|
||||
"line": 14716,
|
||||
"lines": 40,
|
||||
"lines": 41,
|
||||
"role": "continuation",
|
||||
"role_detail": "addendum",
|
||||
"status": null,
|
||||
"status_line": null,
|
||||
"title": "addendum 3 \u2014 the pipe monitor's verifier carried the host pin three times, and its host-cache leg watched paths it had typed rather than the module's; volt reads 7 code files / 12 constants *(volt \u2026"
|
||||
},
|
||||
{
|
||||
"bytes": 17,
|
||||
"end_line": 14758,
|
||||
"id": "Q170",
|
||||
"kind": "heading",
|
||||
"line": 14757,
|
||||
"lines": 2,
|
||||
"role": "opening",
|
||||
"role_detail": "",
|
||||
"status": null,
|
||||
"status_line": null,
|
||||
"title": "closure"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+74
-7
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""verify.py -- the HOME the index gate did not have: one command, twelve legs,
|
||||
"""verify.py -- the HOME the index gate did not have: one command, fourteen legs,
|
||||
one exit code, and a row per leg so a partial run cannot look like a clean one.
|
||||
|
||||
WHY THIS EXISTS
|
||||
@@ -188,7 +188,7 @@ TOOLS ARE RESOLVED FROM --repo, NOT FROM THIS FILE'S DIRECTORY
|
||||
The path must still be the ROOT: a subdirectory is rc 2 naming the root,
|
||||
because widening `--repo` silently would grade a tree nobody named.
|
||||
|
||||
`--selftest` fixture corpus, 61 cells
|
||||
`--selftest` fixture corpus, 65 cells
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -239,6 +239,16 @@ LEGS = (
|
||||
"what": "every committed test file binds the module it imports to the "
|
||||
"repo, not to a host path (static sys.path scan plus "
|
||||
"--import-probe)"},
|
||||
{"key": "compile-sweep", "tool": "q169_compile_sweep.py", "slow": False,
|
||||
"path": os.path.join("wizard-work", "volt", "q169_compile_sweep.py"),
|
||||
"what": "every tracked *.py PARSES -- ast.parse over the committed "
|
||||
"population, which no other leg reads (import-resolve grades 194 "
|
||||
"test files of 1400)"},
|
||||
{"key": "import-order", "tool": "q170_import_order_check.py", "slow": False,
|
||||
"path": os.path.join("wizard-work", "volt", "q170_import_order_check.py"),
|
||||
"what": "every module-level import is BOUND before the name it binds is "
|
||||
"EXECUTED -- the axis beside the parse sweep: a file that parses "
|
||||
"and dies with NameError on the first run (Q170)"},
|
||||
{"key": "closed-artifacts", "tool": "closed_artifact_guard.py", "slow": False,
|
||||
"path": os.path.join("wizard-work", "spark", "closed_artifact_guard.py"),
|
||||
"what": "every repair a closed item landed is still the bytes that landed "
|
||||
@@ -469,6 +479,24 @@ def leg_argv(key, repo, queue, index, checkin_day=None):
|
||||
os.path.join(repo, "wizard-work", "spark",
|
||||
"closed_artifact_guard.py"),
|
||||
"--root", repo]
|
||||
if key == "compile-sweep":
|
||||
# WORKTREE, not HEAD: the file this tick is about to commit is the one
|
||||
# that must parse, and a leg that read HEAD would grade the last commit
|
||||
# while the unparseable edit sat beside it -- the blind spot this leg
|
||||
# was opened for (`verify_dir_analyzer.py`, 2026-10-31: py_compile rc 1
|
||||
# in the worktree, gate rc 0).
|
||||
return [sys.executable,
|
||||
os.path.join(repo, "wizard-work", "volt",
|
||||
"q169_compile_sweep.py"),
|
||||
"--repo", repo, "--rev", "WORKTREE"]
|
||||
if key == "import-order":
|
||||
# WORKTREE for the same reason: the misplaced import is written by the
|
||||
# tick that moves a path, and a leg reading HEAD would grade the commit
|
||||
# before the edit (Q170: three files parsed clean over a NameError).
|
||||
return [sys.executable,
|
||||
os.path.join(repo, "wizard-work", "volt",
|
||||
"q170_import_order_check.py"),
|
||||
"--repo", repo, "--rev", "WORKTREE", "--quiet"]
|
||||
if key == "open-set":
|
||||
# --repo, so the queue graded is the one in the tree under test and not
|
||||
# the copy beside this tool; --check reads and compares, nothing more.
|
||||
@@ -741,6 +769,10 @@ GREEN_ALL = {"checkin_check.py": 0, "path_claim_check.py": 0,
|
||||
"tool_map.py": 0,
|
||||
"selftest_sweep.py": 0, "write_probe.py": 0,
|
||||
"import_resolve_check.py": 0,
|
||||
os.path.join("wizard-work", "volt",
|
||||
"q169_compile_sweep.py"): 0,
|
||||
os.path.join("wizard-work", "volt",
|
||||
"q170_import_order_check.py"): 0,
|
||||
os.path.join("wizard-work", "spark",
|
||||
"closed_artifact_guard.py"): 0,
|
||||
os.path.join("wizard-work", "volt", "q137-open-set",
|
||||
@@ -761,11 +793,12 @@ def selftest():
|
||||
failures.append("%s\n got: %r\n want: %r" % (label, got, want))
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="verify-selftest-") as td:
|
||||
# 1. twelve green stubs -> rc 0
|
||||
# 1. fourteen green stubs -> rc 0
|
||||
repo = _fixture(os.path.join(td, "c1"), GREEN_ALL)
|
||||
rc, out = _verify("--repo", repo)
|
||||
check("1 all twelve legs green -> rc 0", rc, 0)
|
||||
check("1b summary counts the twelve legs", "verify: 12 legs, 12 pass, 0 fault, "
|
||||
check("1 all fourteen legs green -> rc 0", rc, 0)
|
||||
check("1b summary counts the fourteen legs",
|
||||
"verify: 14 legs, 14 pass, 0 fault, "
|
||||
"0 ungradable, 0 skipped" in out, True)
|
||||
# 1c. the drift leg is graded on the same terms as any other: a map that
|
||||
# does not reproduce is a FAULT, and its DRIFT line reaches the row.
|
||||
@@ -778,6 +811,38 @@ def selftest():
|
||||
check("1d the DRIFT line is carried into the row",
|
||||
"tools-map" in out and "DRIFT at line 35" in out
|
||||
and "fault: tools-map" in out, True)
|
||||
# 1e. the compile leg is wired to the population no other leg parses: a
|
||||
# tree with one unparseable file reds the gate and names the file.
|
||||
repo = _fixture(os.path.join(td, "c1e"),
|
||||
dict(GREEN_ALL, **{os.path.join(
|
||||
"wizard-work", "volt", "q169_compile_sweep.py"): 1}),
|
||||
outs={os.path.join(
|
||||
"wizard-work", "volt", "q169_compile_sweep.py"):
|
||||
"FAULT wizard-work/beam/x.py:11 IndentationError: "
|
||||
"unexpected indent\n"
|
||||
"compile_sweep: 1400 tracked .py at the worktree, "
|
||||
"1 fault(s)\n"})
|
||||
rc, out = _verify("--repo", repo)
|
||||
check("1e a compile fault -> rc 1", rc, 1)
|
||||
check("1f the fault names its file", "fault: compile-sweep" in out
|
||||
and "beam/x.py:11" in out, True)
|
||||
# 1g. the import-order leg is the execution axis: a file that PARSES and
|
||||
# reads a name before its import reds the gate and names the call site.
|
||||
repo = _fixture(os.path.join(td, "c1g"),
|
||||
dict(GREEN_ALL, **{os.path.join(
|
||||
"wizard-work", "volt",
|
||||
"q170_import_order_check.py"): 1}),
|
||||
outs={os.path.join(
|
||||
"wizard-work", "volt",
|
||||
"q170_import_order_check.py"):
|
||||
"FAULT wizard-work/beam/y.py:45 Path read by "
|
||||
"main() at line 45, imported at line 46\n"
|
||||
"import_order: 1400 tracked .py at the worktree, "
|
||||
"1 fault(s)\n"})
|
||||
rc, out = _verify("--repo", repo)
|
||||
check("1g an import-order fault -> rc 1", rc, 1)
|
||||
check("1h the row names the leg and the call site",
|
||||
"fault: import-order" in out and "beam/y.py:45" in out, True)
|
||||
|
||||
# 2. a stale index is a FAULT, and its own line reaches the row
|
||||
repo = _fixture(os.path.join(td, "c2"),
|
||||
@@ -834,7 +899,9 @@ def selftest():
|
||||
["checkin", "path-claims", "reply-headings", "index-current",
|
||||
"append-audit", "doc-commands", "tools-map", "selftest-sweep",
|
||||
"write-axis",
|
||||
"import-resolve", "closed-artifacts", "open-set"])
|
||||
"import-resolve", "compile-sweep", "import-order",
|
||||
"closed-artifacts",
|
||||
"open-set"])
|
||||
check("7b --json carries the same rc the process returned",
|
||||
obj.get("rc"), rc)
|
||||
|
||||
@@ -878,7 +945,7 @@ def selftest():
|
||||
check("12 skipped leg is its own count in --json",
|
||||
obj.get("counts", {}).get("SKIP"), 1)
|
||||
check("12b and the skipped leg is not counted as a pass",
|
||||
obj.get("counts", {}).get(PASS), 11)
|
||||
obj.get("counts", {}).get(PASS), 13)
|
||||
|
||||
# 13. the detail line must not contradict the verdict: a row graded
|
||||
# FAULT beside "0 fatal" is the SILENT failure this gate exists to catch.
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
"""Verification test for wordcount.py - must match wc output exactly."""
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main():
|
||||
# Create test file with known content
|
||||
test_content = "Hello world\nThis is a test file\nWith three lines\n"
|
||||
test_file = "/root/troupe-standup/test_verification.txt"
|
||||
test_file = Path(__file__).resolve().parents[2] / 'test_verification.txt'
|
||||
with open(test_file, 'w') as f:
|
||||
f.write(test_content)
|
||||
|
||||
@@ -18,6 +19,7 @@ def main():
|
||||
# Get wordcount.py output
|
||||
pyc_result = subprocess.run(
|
||||
['python3', '/root/.hermes/wizard-outputs/spark/wordcount.py', test_file],
|
||||
# subject: /root/.hermes/wizard-outputs/spark/wordcount.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
capture_output=True, text=True
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
# Q63 (2026-10-24): no committed gate invokes this file -- `git grep -F -n verify_append_only_audit_dry_refactor.py HEAD
|
||||
# -- '*.py' '*.sh' CHECK` matches nothing outside this file -- so it is a one-off
|
||||
# artifact, run by hand against its subject, not a standing check.
|
||||
@@ -63,7 +64,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = "tools/append_only_audit.py"
|
||||
VERIFIER_NAME = "verify_append_only_audit_dry_refactor.py"
|
||||
VERIFIER_PATH = "wizard-work/forge/" + VERIFIER_NAME
|
||||
|
||||
@@ -28,6 +28,7 @@ emit order is unchanged. Five verdicts, all must be green.
|
||||
|
||||
Exit code is the verdict: 0 = all green, 1 = any mismatch.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import io
|
||||
@@ -38,7 +39,7 @@ import sys
|
||||
import tempfile
|
||||
from collections import Counter
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
DCC = os.path.join(REPO, "tools", "append_only_owners.py")
|
||||
SAME, OTHER, FIRST = "SAME_PARTY", "OTHER_PARTY", "FIRST_CHANGE_ON_PATH"
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ Equivalence methodology (post-commit: HEAD = refactored, git HEAD~? ... see note
|
||||
|
||||
Exit 0 iff all checks pass, else 1.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -28,7 +29,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = "tools/census_row_delta.py"
|
||||
VERIFIER_NAME = "verify_census_row_delta_exit_contract.py"
|
||||
VERIFIER_PATH = "wizard-work/forge/" + VERIFIER_NAME
|
||||
|
||||
@@ -69,13 +69,14 @@ NOTE ON PROVENANCE
|
||||
committed original since the refactor has not been committed). That pin is
|
||||
robust to any number of commits landing before the run.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = "tools/check_lint.py"
|
||||
SUMMARY = "selftest: 37 cells, 37 passed, 0 failed"
|
||||
TMPDIR = os.environ.get("TMPDIR", tempfile.gettempdir())
|
||||
|
||||
@@ -25,6 +25,7 @@ row, matching `c in r["class"`] over a list. Four checks; exit 0 = all green.
|
||||
of the old census `sum(1 for r in rows ...)` scans (the unrelated gaps()
|
||||
per-handle nfiles scan is left untouched and excluded from the assertion).
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -36,7 +37,7 @@ import tempfile
|
||||
from collections import Counter
|
||||
from datetime import date
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
DCC = os.path.join(REPO, "tools", "checkin_check.py")
|
||||
FAULTS = ("BAD_NAME", "UNKNOWN_HANDLE", "BAD_DATE", "FUTURE", "EMPTY")
|
||||
|
||||
|
||||
@@ -8,13 +8,15 @@ Compares the ORIGINAL (pre-refactor) verdict logic against the REFACTORED versio
|
||||
4. The AMBIGUOUS+bound divergence (which the old selftest had but scan() didn't)
|
||||
is now FIXED: refactored uses one logic, not two
|
||||
"""
|
||||
from pathlib import Path
|
||||
import importlib.util
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
ORIG = "/root/.hermes/wizard-outputs/forge/citation_hash_class.orig.py"
|
||||
# subject: /root/.hermes/wizard-outputs/forge/citation_hash_class.orig.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
|
||||
|
||||
def load_module(name, path):
|
||||
|
||||
@@ -47,6 +47,7 @@ LEGS
|
||||
|
||||
Exit 0 iff all checks pass, else 1.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -54,7 +55,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = "tools/citation_hash_class.py"
|
||||
PORTABLE_EXPR = "os.path.dirname(os.path.dirname(os.path.abspath(__file__)))"
|
||||
HARD_CODED = "default='/root/troupe-standup'"
|
||||
|
||||
@@ -20,13 +20,14 @@ end-to-end and comparing:
|
||||
|
||||
Exit 0 iff all checks pass, else 1.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = "tools/citation_resolve_check.py"
|
||||
ORIG_SPEC = f"HEAD~1:{TOOL}"
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ Three verdicts, all must be green:
|
||||
|
||||
Exit code is the verdict: 0 = all green, 1 = any mismatch.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import os
|
||||
@@ -54,7 +55,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = os.path.join(REPO, "tools", "doc_census_check.py")
|
||||
NAME = "doc_census_check.py"
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ Three verdicts, all must be green:
|
||||
|
||||
Exit code is the verdict: 0 = all green, 1 = any mismatch.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import io
|
||||
@@ -41,7 +42,7 @@ import sys
|
||||
import tempfile
|
||||
from collections import Counter
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
DCC = f"{REPO}/tools/doc_command_check.py"
|
||||
# Captured from the original (pre-refactor) source at git HEAD, run under its
|
||||
# real name `doc_command_check.py`:
|
||||
|
||||
@@ -81,7 +81,7 @@ SEED_FILENAME = "seed-2026-09-26.json"
|
||||
# The original probe's literal -- used only to assert the ORIGINAL still carries
|
||||
# the host-absolute smell (clone-independent: the original's literal is
|
||||
# host-absolute by design).
|
||||
HOST_SEED_LITERAL = "/root/troupe-standup/inbox/seed-2026-09-26.json"
|
||||
HOST_SEED_LITERAL = Path(__file__).resolve().parents[2] / 'inbox' / 'seed-2026-09-26.json'
|
||||
# The seed resolved from THIS file's location (two dirs up = repo root), not a
|
||||
# host-absolute literal, so a clone grades its own inbox. On this host the
|
||||
# resolution is identical to the old literal -> check 2 stays byte-identical.
|
||||
|
||||
@@ -55,13 +55,14 @@ NOTE ON PROVENANCE
|
||||
since the refactor has not been committed). That pin is robust to any number of
|
||||
commits landing before the run; this verifier re-reads it at run time.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = "tools/gate_blame.py"
|
||||
SUMMARY = "selftest: 21 cells, 21 passed, 0 failed"
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
@@ -52,6 +52,7 @@ REFACTORED marker: `return Counter(row["verdict"] for row in rows)
|
||||
|
||||
Exit 0 iff all checks pass, else 1.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -59,7 +60,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = "tools/landing_check.py"
|
||||
SMILE_ORIGINAL = 'counts.get(row["verdict"], 0) + 1' # the dict.get loop, original-only
|
||||
REFACTORED_MARKER = 'return Counter(row["verdict"] for row in rows)' # refactored-only
|
||||
|
||||
@@ -12,6 +12,7 @@ from PIL import Image, ImageChops
|
||||
|
||||
SCRIPTS = [33, 34, 35, 36, 38, 39, 40]
|
||||
DIR = "/root/.hermes"
|
||||
# subject: /root/.hermes -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
|
||||
|
||||
def check(nn: int) -> bool:
|
||||
|
||||
@@ -70,6 +70,7 @@ WHAT THIS VERIFIER PROVES (measured, not claimed)
|
||||
Exit 0 iff every check holds, else 1.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import json
|
||||
import os
|
||||
@@ -85,7 +86,7 @@ import tempfile
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
REPO = os.path.dirname(os.path.dirname(os.path.dirname(_HERE)))
|
||||
if not os.path.isdir(os.path.join(REPO, "tools", "path_claim_check.py")):
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = "tools/path_claim_check.py"
|
||||
SELTEST_SUMMARY = "selftest: 31 cells, 31 passed, 0 failed"
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ tools/path_claim_check.py (forge lane: Refactor).
|
||||
|
||||
THE SMELL
|
||||
DEFAULT_REPO was a host-literal constant:
|
||||
DEFAULT_REPO = "/root/troupe-standup"
|
||||
DEFAULT_REPO = Path(__file__).resolve().parents[2]
|
||||
The tool's own USAGE documents that paths are "resolved against --repo,
|
||||
not against the caller's cwd" -- but the DEFAULT violated that contract. A
|
||||
bare invocation from any other checkout, clone, or host graded
|
||||
@@ -48,6 +48,7 @@ LEGS
|
||||
|
||||
Exit 0 iff all checks pass, else 1.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -55,7 +56,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = "tools/path_claim_check.py"
|
||||
PORTABLE_EXPR = "os.path.dirname(os.path.dirname(os.path.abspath(__file__)))"
|
||||
HARD_CODED = '"/root/troupe-standup"' # the host literal DEFAULT_REPO carried verbatim
|
||||
|
||||
@@ -39,6 +39,7 @@ import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
CREATE_ORIG = Path("/root/.hermes/wizard-outputs/forge/before/pdf_create.py")
|
||||
# subject: /root/.hermes/wizard-outputs/forge/before/pdf_create.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
CREATE_REF = Path("/root/.hermes/profiles/forge/skills/productivity/pdf/scripts/pdf_create.py")
|
||||
FORM_ORIG = Path("/root/.hermes/wizard-outputs/forge/before/pdf_make_form.py")
|
||||
FORM_REF = Path("/root/.hermes/profiles/forge/skills/productivity/pdf/scripts/pdf_make_form.py")
|
||||
|
||||
@@ -39,6 +39,7 @@ The verdict is proven on three legs:
|
||||
|
||||
Exit code is the verdict: 0 = all green, 1 = any mismatch.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -48,7 +49,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
DCC = os.path.join(REPO, "tools", "queue_append_audit.py")
|
||||
INDEX_DEFAULT = "docs/queue-index.md"
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
FORGE = "/root/.hermes/wizard-outputs/forge"
|
||||
# subject: /root/.hermes/wizard-outputs/forge -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
ORIGINAL = "/root/.hermes/ops-pulse-card-r41.py"
|
||||
REFACTORED = os.path.join(FORGE, "refactored_cards", "ops-pulse-card-r41_refactored.py")
|
||||
SHARED_OUT = "/root/.hermes/ops-pulse.png"
|
||||
|
||||
@@ -9,12 +9,13 @@ CHECK target. Exits 0 (correct) iff ALL of:
|
||||
Otherwise exits 1. No fallback arms: the script's own exit code IS the verdict.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import ast
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
PATH = "/root/troupe-standup/wizard-work/forge/chunker.py"
|
||||
PATH = Path(__file__).resolve().parents[2] / 'wizard-work' / 'forge' / 'chunker.py'
|
||||
TESTS = ["python", "-m", "pytest", "test_chunker_refactor.py", "-q"]
|
||||
|
||||
|
||||
@@ -75,7 +76,7 @@ def main() -> int:
|
||||
print("OK (b): `del overlap_tokens` present in reconstruct body")
|
||||
|
||||
# (c) behavior preserved: chunker test suite.
|
||||
proc = subprocess.run(TESTS, cwd="/root/troupe-standup/wizard-work/forge")
|
||||
proc = subprocess.run(TESTS, cwd=Path(__file__).resolve().parents[2] / 'wizard-work' / 'forge')
|
||||
if proc.returncode != 0:
|
||||
print(f"FAIL (c): chunker test suite exited {proc.returncode}")
|
||||
return 1
|
||||
|
||||
@@ -24,13 +24,14 @@ the SAME live repo and compares:
|
||||
|
||||
Exit 0 iff all pass, else 1. The command's own exit code is the verdict.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = "tools/reply_check.py"
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ module's docstring *names* the patterns it removes (e.g. ``any_overflow``,
|
||||
``CASES``), which would be false positives as residual smells.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import ast
|
||||
import contextlib
|
||||
@@ -30,7 +31,7 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO_ROOT = "/root/troupe-standup"
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
WORK_DIR = os.path.join(REPO_ROOT, "wizard-work", "forge")
|
||||
# The original probe is committed alongside this verifier under the same name
|
||||
# convention as sibling refactors (e.g. _smell_scan.py / _smell_scan_refactored.py).
|
||||
|
||||
@@ -12,13 +12,14 @@ Checks:
|
||||
Exit 0 = all pass; exit 1 = at least one failure.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import difflib
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO_ROOT = "/root/troupe-standup"
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
WORK_DIR = os.path.join(REPO_ROOT, "wizard-work", "forge")
|
||||
REFACTORED = "_test_roundtrip_refactored.py"
|
||||
ORIGINAL = "_test_roundtrip.py"
|
||||
|
||||
@@ -50,6 +50,7 @@ Four checks, all must be green:
|
||||
|
||||
Exit 0 if all pass, 1 if any fail.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import contextlib
|
||||
import hashlib
|
||||
import importlib.util
|
||||
@@ -60,9 +61,10 @@ import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TOOL = os.path.join(REPO, "tools", "selftest_sweep.py")
|
||||
BEFORE = "/root/.hermes/profiles/forge/cache/scratch/selftest_sweep_before.py"
|
||||
# subject: /root/.hermes/profiles/forge/cache/scratch/selftest_sweep_before.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
|
||||
GOOD = ('#!/usr/bin/env python3\n"""fake_good.py -- a fake.\n\nUSAGE:\n'
|
||||
' python3 fake_good.py --selftest # 7 cells\n"""\n'
|
||||
|
||||
@@ -26,11 +26,12 @@ This verify script checks:
|
||||
[4] --selftest passes 39 cells with 0 failed
|
||||
[5] a live sweep on tools/ reports 0 fatal verdicts
|
||||
"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
PATH = "tools/selftest_sweep.py"
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ they are unchanged. Four verdicts, all must be green:
|
||||
|
||||
Exit code is the verdict: 0 = all green, 1 = any mismatch.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -45,7 +46,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
DCC = os.path.join(REPO, "tools", "served_drift_check.py")
|
||||
|
||||
# Captured from the ORIGINAL (pre-refactor) source, i.e. `git show HEAD:tools/
|
||||
|
||||
@@ -19,6 +19,7 @@ Checks:
|
||||
non-monotonic estimator.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import ast
|
||||
import difflib
|
||||
@@ -29,7 +30,7 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO_ROOT = "/root/troupe-standup"
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
WORK_DIR = os.path.join(REPO_ROOT, "wizard-work", "forge")
|
||||
ORIGINAL = "_smell_check_monotonic.py"
|
||||
REFACTORED = "_smell_check_monotonic_refactored.py"
|
||||
|
||||
@@ -16,6 +16,7 @@ docstring *names* the patterns it removes, and flagging those self-describing
|
||||
mentions would be a false positive rather than a residual smell.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import ast
|
||||
import difflib
|
||||
@@ -23,7 +24,7 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
REPO_ROOT = "/root/troupe-standup"
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
WORK_DIR = os.path.join(REPO_ROOT, "wizard-work", "forge")
|
||||
ORIGINAL = "_smell_scan.py"
|
||||
REFACTORED = "_smell_scan_refactored.py"
|
||||
|
||||
@@ -22,6 +22,7 @@ Two verdicts are produced, both must be green:
|
||||
|
||||
Exit code is the verdict: 0 = both green, 1 = any mismatch.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -29,7 +30,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
TM = f"{REPO}/tools/tool_map.py"
|
||||
SWEET = f"{REPO}/tools/selftest_sweep.py"
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ probe itself (same JSON input, same output). Checks:
|
||||
identical to the old literal, so check 2 stays byte-identical.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import ast
|
||||
import contextlib
|
||||
@@ -74,7 +75,7 @@ SEED_FILENAME = "seed-2026-09-26.json"
|
||||
# The original probe's literal -- used only to assert the ORIGINAL still carries
|
||||
# the host-absolute smell (clone-independent, unlike comparing against a resolved
|
||||
# real-seed path).
|
||||
HOST_SEED_LITERAL = "/root/troupe-standup/inbox/seed-2026-09-26.json"
|
||||
HOST_SEED_LITERAL = Path(__file__).resolve().parents[2] / 'inbox' / 'seed-2026-09-26.json'
|
||||
# The seed is derived from THIS file's location (walked up to the repo root),
|
||||
# not a host-absolute literal, so a clone grades its own inbox (Q86). On this
|
||||
# host the resolution is identical to the old literal -> check 2 stays green.
|
||||
|
||||
@@ -60,6 +60,7 @@ Checks:
|
||||
identical to the old literal, so check 2 stays byte-identical.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import ast
|
||||
import contextlib
|
||||
@@ -88,7 +89,7 @@ SEED_FILENAME = "seed-2026-09-26.json"
|
||||
# The original probe's literal -- used only to assert the ORIGINAL still carries
|
||||
# the host-absolute smell (clone-independent, unlike comparing against a resolved
|
||||
# real-seed path).
|
||||
HOST_SEED_LITERAL = "/root/troupe-standup/inbox/seed-2026-09-26.json"
|
||||
HOST_SEED_LITERAL = Path(__file__).resolve().parents[2] / 'inbox' / 'seed-2026-09-26.json'
|
||||
# The seed is derived from THIS file's location (walked up to the repo root),
|
||||
# not a host-absolute literal, so a clone grades its own inbox (Q86). On this
|
||||
# host the resolution is identical to the old literal -> check 2 stays green.
|
||||
|
||||
@@ -58,6 +58,7 @@ Checks:
|
||||
identical to the old literal, so check 2 stays byte-identical.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import ast
|
||||
import contextlib
|
||||
@@ -86,7 +87,7 @@ SEED_FILENAME = "seed-2026-09-26.json"
|
||||
# The original probe's literal -- used only to assert the ORIGINAL still carries
|
||||
# the host-absolute smell (clone-independent, unlike comparing against a resolved
|
||||
# real-seed path).
|
||||
HOST_SEED_LITERAL = "/root/troupe-standup/inbox/seed-2026-09-26.json"
|
||||
HOST_SEED_LITERAL = Path(__file__).resolve().parents[2] / 'inbox' / 'seed-2026-09-26.json'
|
||||
# The seed is derived from THIS file's location (walked up to the repo root),
|
||||
# not a host-absolute literal, so a clone grades its own inbox (Q86). On this
|
||||
# host the resolution is identical to the old literal -> check 2 stays green.
|
||||
|
||||
@@ -42,6 +42,7 @@ LEGS
|
||||
on code lines (0 Counter usages); the refactored worktree carries ONE
|
||||
Counter usage and ZERO two-scan lines. Counted on code lines only.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
@@ -50,7 +51,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
DCC = os.path.join(REPO, "tools", "write_probe.py")
|
||||
REAL_REPO = os.path.join(REPO, ".")
|
||||
TWO_SCAN = 'len([r for r in rows if r.get("axis")' # the collapsed scan-shape
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"""Verify 2026-10-25-git-history.json against live git for date extremes."""
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
|
||||
@@ -35,4 +36,3 @@ def main():
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
from pathlib import Path
|
||||
@@ -8,6 +8,7 @@ blob hasher) and requires artifact_seal to agree with both.
|
||||
Exit 0 iff every cross-check agrees.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
@@ -19,7 +20,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
import artifact_seal as A # noqa: E402
|
||||
|
||||
REPO = "/root/troupe-standup"
|
||||
REPO = Path(__file__).resolve().parents[3]
|
||||
|
||||
fails = []
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ Exit code 0 iff every applicable file matched.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from pathlib import Path
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
@@ -29,7 +30,7 @@ def git(repo: str, *args: str) -> tuple[int, bytes]:
|
||||
|
||||
|
||||
def main() -> int:
|
||||
repo = sys.argv[1] if len(sys.argv) > 1 else "/root/troupe-standup"
|
||||
repo = sys.argv[1] if len(sys.argv) > 1 else Path(__file__).resolve().parents[3]
|
||||
rev = sys.argv[2] if len(sys.argv) > 2 else "HEAD~1"
|
||||
limit = int(sys.argv[3]) if len(sys.argv) > 3 else 15
|
||||
verbose = os.environ.get("VERBOSE") == "1"
|
||||
|
||||
@@ -7,11 +7,13 @@ Scans:
|
||||
|
||||
Exits 0 on clean pass; exits 1 on any failure.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import re, os, sys
|
||||
|
||||
REPO_ROOT = "/root/troupe-standup"
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
ROOTS = [
|
||||
"/root/.hermes/wizard-outputs/quill/",
|
||||
# subject: /root/.hermes/wizard-outputs/quill/ -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
"/root/troupe-standup/wizard-work/quill/",
|
||||
]
|
||||
STANDARD = "/root/.hermes/wizard-outputs/quill/placeholder-id-format-standard.md"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import pathlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
output_dir = Path("/root/troupe-standup/wizard-work/spark")
|
||||
output_dir = Path(__file__).resolve().parents[2] / 'wizard-work' / 'spark'
|
||||
results = {
|
||||
"timestamp": "2026-10-22T05:40:00Z",
|
||||
"tests": {},
|
||||
@@ -11,6 +12,7 @@ results = {
|
||||
# Test 1: Check file exists in wizard-outputs
|
||||
results["tests"]["validator_in_wizard_outputs"] = {
|
||||
"passed": Path("/root/.hermes/wizard-outputs/spark/json_validator.py").exists()
|
||||
# subject: /root/.hermes/wizard-outputs/spark/json_validator.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
}
|
||||
|
||||
# Test 2: Check validation_results.json exists in wizard-outputs
|
||||
@@ -43,4 +45,4 @@ with open(output_file, 'w') as f:
|
||||
json.dump(results, f, indent=2)
|
||||
|
||||
print(f"Validation results: {output_file}")
|
||||
print(f"All tests passed: {passed}")
|
||||
print(f"All tests passed: {passed}")
|
||||
@@ -9,6 +9,7 @@ def verify():
|
||||
"""Run number_analyzer.py and verify output is correct."""
|
||||
result = subprocess.run(
|
||||
["python3", "/root/.hermes/wizard-outputs/spark/number_analyzer.py"],
|
||||
# subject: /root/.hermes/wizard-outputs/spark/number_analyzer.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
@@ -6,9 +6,9 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
def run_check():
|
||||
validator = Path("/root/troupe-standup/wizard-work/spark/json_syntax_checker.py")
|
||||
valid_test = Path("/root/troupe-standup/wizard-work/spark/ts_valid_demo.json")
|
||||
invalid_test = Path("/root/troupe-standup/wizard-work/spark/ts_invalid_demo.json")
|
||||
validator = Path(__file__).resolve().parents[2] / 'wizard-work' / 'spark' / 'json_syntax_checker.py'
|
||||
valid_test = Path(__file__).resolve().parents[2] / 'wizard-work' / 'spark' / 'ts_valid_demo.json'
|
||||
invalid_test = Path(__file__).resolve().parents[2] / 'wizard-work' / 'spark' / 'ts_invalid_demo.json'
|
||||
|
||||
# Test 1: Valid JSON should return exit code 0
|
||||
result = subprocess.run(["python3", str(validator), str(valid_test)], capture_output=True)
|
||||
@@ -36,4 +36,5 @@ def run_check():
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = run_check()
|
||||
sys.exit(0 if success else 1)
|
||||
sys.exit(0 if success else 1)
|
||||
import pathlib
|
||||
@@ -3,6 +3,7 @@
|
||||
import json
|
||||
|
||||
with open('/root/.hermes/wizard-outputs/spark/skills_catalog.json') as f:
|
||||
# subject: /root/.hermes/wizard-outputs/spark/skills_catalog.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
data = json.load(f)
|
||||
|
||||
skills = [s for cat in data['categories'].values() for s in cat]
|
||||
|
||||
@@ -13,6 +13,7 @@ def _check(cond, msg):
|
||||
|
||||
# Verify CSV is valid
|
||||
with open('/root/.hermes/wizard-outputs/spark/output.csv', 'r') as f:
|
||||
# subject: /root/.hermes/wizard-outputs/spark/output.csv -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
reader = csv.DictReader(f)
|
||||
rows = list(reader)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import os
|
||||
|
||||
# Create output directory
|
||||
os.makedirs('/root/.hermes/wizard-outputs/spark', exist_ok=True)
|
||||
# subject: /root/.hermes/wizard-outputs/spark -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
|
||||
# Generate test data
|
||||
test_data = {
|
||||
|
||||
@@ -21,5 +21,6 @@ def verify_directory(path):
|
||||
|
||||
if __name__ == "__main__":
|
||||
target = sys.argv[1] if len(sys.argv) > 1 else "/root/.hermes/wizard-outputs/spark"
|
||||
# subject: /root/.hermes/wizard-outputs/spark -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
success = verify_directory(target)
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -3,6 +3,7 @@
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _check(cond, msg):
|
||||
@@ -13,7 +14,7 @@ def _check(cond, msg):
|
||||
|
||||
# Test JSON mode
|
||||
result = subprocess.run(['python3', 'dir_analyzer.py', '/etc', '--json'],
|
||||
capture_output=True, text=True, cwd='/root/troupe-standup/wizard-work/spark')
|
||||
capture_output=True, text=True, cwd=Path(__file__).resolve().parents[2] / 'wizard-work' / 'spark')
|
||||
data = json.loads(result.stdout)
|
||||
|
||||
_check('total_files' in data, "Missing total_files")
|
||||
@@ -25,7 +26,7 @@ print(f"JSON mode: PASS - {data['total_files']} files, {data['total_size_bytes']
|
||||
|
||||
# Test text mode
|
||||
result2 = subprocess.run(['python3', 'dir_analyzer.py', '/tmp'],
|
||||
capture_output=True, text=True, cwd='/root/troupe-standup/wizard-work/spark')
|
||||
capture_output=True, text=True, cwd=Path(__file__).resolve().parents[2] / 'wizard-work' / 'spark')
|
||||
_check('Total files:' in result2.stdout, "Should show file count")
|
||||
_check('Total size:' in result2.stdout, "Should show size")
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ results = {
|
||||
# Key directories to check
|
||||
dirs_to_check = [
|
||||
"/root/.hermes/profiles/spark",
|
||||
# subject: /root/.hermes/profiles/spark -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
"/root/.hermes/tools",
|
||||
"/root/.hermes/cache",
|
||||
"/root/.hermes/wizard-outputs/spark",
|
||||
|
||||
@@ -7,6 +7,7 @@ import json
|
||||
# Key paths to verify
|
||||
paths = [
|
||||
"/root/.hermes",
|
||||
# subject: /root/.hermes -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
"/root/.hermes/profiles/spark",
|
||||
"/root/.hermes/skills",
|
||||
"/root/.hermes/plugins",
|
||||
|
||||
@@ -20,6 +20,7 @@ def verify_snippet_environment():
|
||||
"python_ok": sys.version_info >= (3, 14),
|
||||
"cwd_is_root": os.getcwd() == "/root",
|
||||
"hermes_profiles": os.path.isdir("/root/.hermes/profiles/spark"),
|
||||
# subject: /root/.hermes/profiles/spark -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
"wizard_outputs": os.path.isdir("/root/.hermes/wizard-outputs/spark"),
|
||||
"troupe_standup": os.path.isdir("/root/troupe-standup"),
|
||||
"wizard_work_spark": os.path.isdir("/root/troupe-standup/wizard-work/spark"),
|
||||
|
||||
@@ -22,6 +22,7 @@ direct_sum = sum(fibs)
|
||||
|
||||
# Write verification results
|
||||
with open("/root/.hermes/wizard-outputs/spark/math_verification.txt", "w") as f:
|
||||
# subject: /root/.hermes/wizard-outputs/spark/math_verification.txt -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
f.write(f"Sequence length: {len(fibs)}\n")
|
||||
f.write(f"Sum (direct calculation): {direct_sum}\n")
|
||||
f.write(f"Sum (F(n+2)-1 formula): {calc_sum}\n")
|
||||
|
||||
@@ -5,6 +5,7 @@ import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
[sys.executable, '/root/.hermes/wizard-outputs/spark/fibonacci_verifier.py'],
|
||||
# subject: /root/.hermes/wizard-outputs/spark/fibonacci_verifier.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ def test_formatter():
|
||||
# Test 1: Valid JSON with key reordering
|
||||
result = subprocess.run(
|
||||
['python3', '/root/.hermes/wizard-outputs/spark/json_formatter.py'],
|
||||
# subject: /root/.hermes/wizard-outputs/spark/json_formatter.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
input='{"z": 1, "a": 2}',
|
||||
capture_output=True,
|
||||
text=True
|
||||
|
||||
@@ -21,6 +21,7 @@ def main():
|
||||
|
||||
# Check 2: Verify search_files works
|
||||
result = search_files(pattern="*.py", path="/root/.hermes", limit=3)
|
||||
# subject: /root/.hermes -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
if "matches" in result and len(result["matches"]) > 0:
|
||||
results["checks"].append({"name": "search_files", "status": "passed", "hits": len(result["matches"])})
|
||||
else:
|
||||
|
||||
@@ -50,6 +50,7 @@ def main():
|
||||
dirs = {
|
||||
'hermes_profile': os.path.isdir(os.path.expanduser('~/.hermes/profiles/spark/')),
|
||||
'wizard_outputs': os.path.isdir('/root/.hermes/wizard-outputs/spark/'),
|
||||
# subject: /root/.hermes/wizard-outputs/spark/ -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
'troupe_standup': os.path.isdir('/root/troupe-standup/'),
|
||||
}
|
||||
results['directories'] = dirs
|
||||
|
||||
@@ -15,6 +15,7 @@ def verify_hermes_env():
|
||||
|
||||
# Check core directories
|
||||
base = Path("/root/.hermes")
|
||||
# subject: /root/.hermes -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
profile_dir = base / "profiles" / "spark"
|
||||
wizard_outputs = base / "wizard-outputs" / "spark"
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ def verify_hermes_structure():
|
||||
"""Check that required hermes directories exist."""
|
||||
required_paths = [
|
||||
Path("/root/.hermes/wizard-outputs/spark"),
|
||||
# subject: /root/.hermes/wizard-outputs/spark -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
Path("/root/troupe-standup/wizard-work/spark"),
|
||||
Path("/root/.hermes/profiles/spark"),
|
||||
]
|
||||
|
||||
@@ -23,6 +23,7 @@ def verify_hermes_setup():
|
||||
|
||||
# Check 2: Hermes profile directory
|
||||
profile_dir = '/root/.hermes/profiles/spark'
|
||||
# subject: /root/.hermes/profiles/spark -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
exists = os.path.isdir(profile_dir)
|
||||
results['profile_directory_exists'] = exists
|
||||
if not exists:
|
||||
|
||||
@@ -9,6 +9,7 @@ import os
|
||||
|
||||
def verify_inventory():
|
||||
inventory_path = "/root/.hermes/wizard-outputs/spark/skills_inventory.json"
|
||||
# subject: /root/.hermes/wizard-outputs/spark/skills_inventory.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
with open(inventory_path, 'r') as f:
|
||||
inventory = json.load(f)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import json
|
||||
|
||||
with open('/root/.hermes/wizard-outputs/spark/skills_inventory.json') as f:
|
||||
# subject: /root/.hermes/wizard-outputs/spark/skills_inventory.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
data = json.load(f)
|
||||
|
||||
print('VALID JSON: True')
|
||||
|
||||
@@ -5,6 +5,7 @@ import sys
|
||||
|
||||
result = subprocess.run(
|
||||
['python3', '/root/.hermes/wizard-outputs/spark/line_counter_snippet.py', '/root/.hermes/profiles/spark/'],
|
||||
# subject: /root/.hermes/wizard-outputs/spark/line_counter_snippet.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
capture_output=True, text=True
|
||||
)
|
||||
output = result.stdout.strip()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
with open('/root/.hermes/wizard-outputs/spark/code_stats.py', 'rb') as f:
|
||||
# subject: /root/.hermes/wizard-outputs/spark/code_stats.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
content = f.read()
|
||||
print('File length:', len(content))
|
||||
print('Last byte:', content[-1], '(10 = newline, 32 = space)')
|
||||
|
||||
@@ -5,6 +5,7 @@ import sys
|
||||
|
||||
# Read the JSON report
|
||||
with open('/root/.hermes/wizard-outputs/spark/profile_loc_report.json', 'r') as f:
|
||||
# subject: /root/.hermes/wizard-outputs/spark/profile_loc_report.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
data = json.load(f)
|
||||
|
||||
# Check that the report has the expected structure
|
||||
|
||||
@@ -6,6 +6,7 @@ import subprocess
|
||||
|
||||
result = subprocess.run(
|
||||
['python3', '/root/.hermes/wizard-outputs/spark/sys_metrics.py'],
|
||||
# subject: /root/.hermes/wizard-outputs/spark/sys_metrics.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
@@ -29,6 +29,7 @@ def main():
|
||||
|
||||
# Check 2: Skills directory exists
|
||||
if os.path.isdir("/root/.hermes/skills"):
|
||||
# subject: /root/.hermes/skills -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
skill_count = len([f for f in os.listdir("/root/.hermes/skills") if f.endswith(".py") or os.path.isdir(os.path.join("/root/.hermes/skills", f))])
|
||||
results["checks"].append({"name": "skills_dir", "status": "passed", "item_count": skill_count})
|
||||
else:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
h = hashlib.sha256(open('/root/troupe-standup/wizard-work/spark/file_hash_validator.py', 'rb').read()).hexdigest()
|
||||
print(h)
|
||||
h = hashlib.sha256(open(Path(__file__).resolve().parents[2] / 'wizard-work' / 'spark' / 'file_hash_validator.py', 'rb').read()).hexdigest()
|
||||
print(h)
|
||||
@@ -20,6 +20,7 @@ def verify_environment():
|
||||
def main():
|
||||
results = verify_environment()
|
||||
output_path = "/root/.hermes/wizard-outputs/spark/verification_report.json"
|
||||
# subject: /root/.hermes/wizard-outputs/spark/verification_report.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(results, f, indent=2)
|
||||
|
||||
@@ -9,6 +9,7 @@ def _check(cond, msg):
|
||||
|
||||
|
||||
report = json.load(open('/root/.hermes/wizard-outputs/spark/validation_report.json'))
|
||||
# subject: /root/.hermes/wizard-outputs/spark/validation_report.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
_check(report['summary']['valid_count'] == 1, "Expected 1 valid file")
|
||||
_check(report['summary']['invalid_count'] == 1, "Expected 1 invalid file")
|
||||
_check(report['test_passed'] == True, "Test should be marked as passed")
|
||||
|
||||
@@ -9,6 +9,7 @@ def _check(cond, msg):
|
||||
|
||||
|
||||
with open("/root/.hermes/wizard-outputs/spark/scrape_result.json") as f:
|
||||
# subject: /root/.hermes/wizard-outputs/spark/scrape_result.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
data = json.load(f)
|
||||
_check(data["title"] == "Example Domain", f"wrong title: {data['title']}")
|
||||
_check(data["status"] == "success", f"wrong status: {data['status']}")
|
||||
|
||||
@@ -11,6 +11,7 @@ def _check(cond, msg):
|
||||
|
||||
|
||||
with open("/root/.hermes/wizard-outputs/spark/scratch_analysis.json") as f:
|
||||
# subject: /root/.hermes/wizard-outputs/spark/scratch_analysis.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
data = json.load(f)
|
||||
|
||||
# Verify required fields
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
from pathlib import Path
|
||||
|
||||
inv_path = Path('/root/.hermes/wizard-outputs/spark/skills_inventory.json')
|
||||
# subject: /root/.hermes/wizard-outputs/spark/skills_inventory.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
data = json.loads(inv_path.read_text())
|
||||
skills = data.get('skills', [])
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from pathlib import Path
|
||||
|
||||
# Read the inventory
|
||||
inv_path = Path('/root/.hermes/wizard-outputs/spark/skills_inventory.json')
|
||||
# subject: /root/.hermes/wizard-outputs/spark/skills_inventory.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
data = json.loads(inv_path.read_text())
|
||||
skills = data.get('skills', [])
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from pathlib import Path
|
||||
|
||||
# Read the inventory
|
||||
inv_path = Path('/root/.hermes/wizard-outputs/spark/skills_inventory.json')
|
||||
# subject: /root/.hermes/wizard-outputs/spark/skills_inventory.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
data = json.loads(inv_path.read_text())
|
||||
skills = data.get('skills', [])
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import json
|
||||
from pathlib import Path
|
||||
|
||||
inv_path = Path('/root/.hermes/wizard-outputs/spark/skills_inventory.json')
|
||||
# subject: /root/.hermes/wizard-outputs/spark/skills_inventory.json -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
data = json.loads(inv_path.read_text())
|
||||
skills = data.get('skills', [])
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from datetime import datetime, timezone
|
||||
|
||||
def verify_word_counter():
|
||||
"""Verify word_counter.py functions correctly."""
|
||||
sys.path.insert(0, '/root/troupe-standup/wizard-work/spark')
|
||||
sys.path.insert(0, Path(__file__).resolve().parents[2] / 'wizard-work' / 'spark')
|
||||
from word_counter import count_words
|
||||
|
||||
tests = [
|
||||
|
||||
@@ -36,6 +36,7 @@ def check_os():
|
||||
def check_model_config():
|
||||
"""Read model config from YAML file."""
|
||||
config_path = Path("/root/.hermes/profiles/spark/config.yaml")
|
||||
# subject: /root/.hermes/profiles/spark/config.yaml -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
if not config_path.exists():
|
||||
return None
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import sys
|
||||
# Run the script on itself
|
||||
proc = subprocess.run(
|
||||
['python3', '/root/.hermes/wizard-outputs/spark/text_stats.py',
|
||||
# subject: /root/.hermes/wizard-outputs/spark/text_stats.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
'/root/.hermes/wizard-outputs/spark/text_stats.py'],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import json
|
||||
|
||||
# Test 1: Valid JSON should exit 0
|
||||
result = subprocess.run(['python3', '/root/.hermes/wizard-outputs/spark/json_validator.py',
|
||||
# subject: /root/.hermes/wizard-outputs/spark/json_validator.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
'/root/.hermes/wizard-outputs/spark/test_valid.json'],
|
||||
capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
nums = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
|
||||
s = sum(nums)
|
||||
with open("/root/.hermes/wizard-outputs/spark/count.txt", "w") as f:
|
||||
# subject: /root/.hermes/wizard-outputs/spark/count.txt -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
f.write(f"sum={s}\ncount={len(nums)}\n")
|
||||
@@ -10,6 +10,7 @@ def verify_wordcount(filepath):
|
||||
# Run our script
|
||||
result = subprocess.run(
|
||||
["python3", "/root/.hermes/wizard-outputs/spark/wordcount.py", filepath],
|
||||
# subject: /root/.hermes/wizard-outputs/spark/wordcount.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
"""Verification test for wordcount.py - must match wc output exactly."""
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main():
|
||||
# Create test file with known content
|
||||
test_content = "Hello world\nThis is a test file\nWith three lines\n"
|
||||
test_file = "/root/troupe-standup/test_verification.txt"
|
||||
test_file = Path(__file__).resolve().parents[2] / 'test_verification.txt'
|
||||
with open(test_file, 'w') as f:
|
||||
f.write(test_content)
|
||||
|
||||
@@ -18,6 +19,7 @@ def main():
|
||||
# Get wordcount.py output
|
||||
pyc_result = subprocess.run(
|
||||
['python3', '/root/.hermes/wizard-outputs/spark/wordcount.py', test_file],
|
||||
# subject: /root/.hermes/wizard-outputs/spark/wordcount.py -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
capture_output=True, text=True
|
||||
)
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import subprocess
|
||||
import sys
|
||||
|
||||
DEFAULT_DB = "/root/.hermes/wizard-outputs/sphinx/archive-index-2026-10-23/archive.fts5.sqlite"
|
||||
# subject: /root/.hermes/wizard-outputs/sphinx/archive-index-2026-10-23/archive.fts5.sqlite -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
STAGE_DEFAULT = "/root/.hermes/profiles/sphinx/cache/scratch/ocr-stage"
|
||||
|
||||
TEXT_EXTS = {"md", "txt", "py", "json", "jsonl", "csv", "tsv", "html", "htm", "sh",
|
||||
|
||||
@@ -13,6 +13,7 @@ import sys
|
||||
import tempfile
|
||||
|
||||
OUT = "/root/.hermes/wizard-outputs/sphinx/archive-longctx-retrieval-2026-10-26"
|
||||
# subject: /root/.hermes/wizard-outputs/sphinx/archive-longctx-retrieval-2026-10-26 -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
INDEX = os.path.join(OUT, "index.json")
|
||||
MANIFEST = os.path.join(OUT, "MANIFEST.sha256")
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ LIST_BASE = "https://arxiv.org/list/{cat}/recent"
|
||||
UA = "sphinx-archive-verifier/1.0 (evidence-lane; contact: sphinx@agents.invalid)"
|
||||
|
||||
DIGEST = "/root/.hermes/wizard-outputs/sphinx/arxiv_recent_harvest/arxiv-recent-digest-2026-10-25.md"
|
||||
# subject: /root/.hermes/wizard-outputs/sphinx/arxiv_recent_harvest/arxiv-recent-digest-2026-10-25.md -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
CORPUS = "/root/.hermes/wizard-outputs/sphinx/arxiv_recent_harvest/merged_all.jsonl"
|
||||
OUTDIR = "/root/.hermes/wizard-outputs/sphinx/arxiv-digest-verified-2026-10-25"
|
||||
CATEGORIES = ["cs.CL", "cs.CV", "cs.LG"]
|
||||
|
||||
@@ -26,6 +26,7 @@ import urllib.request
|
||||
|
||||
UA = {"User-Agent": "sphinx-archive-verify/1.0 (mailto:sphinx@agents.invalid)"}
|
||||
D = sys.argv[1] if len(sys.argv) > 1 else "/root/.hermes/wizard-outputs/sphinx/mm-vla-dossier-2026-10-22"
|
||||
# subject: /root/.hermes/wizard-outputs/sphinx/mm-vla-dossier-2026-10-22 -- grades the live tree outside the repo by design (not a repo fixture)
|
||||
|
||||
fails = []
|
||||
notes = []
|
||||
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# CHECK-q169 -- the compile sweep still answers its question.
|
||||
#
|
||||
# Legs
|
||||
# selftest every fixture cell green (22 today), and the cell count pinned
|
||||
# worktree every tracked *.py on disk parses, rc 0
|
||||
# head every tracked *.py at HEAD parses, rc 0
|
||||
# historic 99eba1e -- the rev carrying a bash script under a .py name --
|
||||
# reads rc 1 with exactly one fault, and no file is called
|
||||
# UNREADABLE (the pre-repair batch framing reported seven)
|
||||
# wiring tools/verify.py names the leg and runs it at WORKTREE
|
||||
#
|
||||
# rc 0 every leg as expected | 1 a graded fault | 2 could not grade.
|
||||
# Run from anywhere; paths resolve from $0, the repo from this file's place.
|
||||
set -u
|
||||
|
||||
HERE="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
||||
REPO="$(cd "$HERE/../.." && pwd)"
|
||||
TOOL="$HERE/q169_compile_sweep.py"
|
||||
CELLS="22 cells, 22 passed, 0 failed"
|
||||
legs=0
|
||||
faults=0
|
||||
|
||||
ok() { legs=$((legs + 1)); printf 'ok %-9s %s\n' "$1" "$2"; }
|
||||
bad() { legs=$((legs + 1)); faults=$((faults + 1)); printf 'FAULT %-9s %s\n' "$1" "$2"; }
|
||||
skip(){ legs=$((legs + 1)); printf 'skip %-9s %s\n' "$1" "$2"; }
|
||||
|
||||
[ -f "$TOOL" ] || { echo "cannot grade -- no tool: $TOOL"; exit 2; }
|
||||
command -v python3 >/dev/null || { echo "cannot grade -- no python3"; exit 2; }
|
||||
|
||||
# selftest -- the fixture corpus, and the pin on its size
|
||||
out="$(python3 "$TOOL" --selftest 2>&1)"; rc=$?
|
||||
if [ $rc -eq 0 ] && printf '%s' "$out" | grep -qF "$CELLS"; then
|
||||
ok selftest "$CELLS, rc 0"
|
||||
else
|
||||
bad selftest "rc $rc, want 0 and '$CELLS'; last: $(printf '%s' "$out" | tail -n 1)"
|
||||
fi
|
||||
|
||||
# worktree -- the tree about to be committed
|
||||
out="$(python3 "$TOOL" --repo "$REPO" 2>&1)"; rc=$?
|
||||
line="$(printf '%s' "$out" | tail -n 1)"
|
||||
if [ $rc -eq 0 ] && printf '%s' "$line" | grep -q ', 0 fault(s)$'; then
|
||||
ok worktree "$line"
|
||||
else
|
||||
bad worktree "rc $rc: $line"
|
||||
fi
|
||||
|
||||
# head -- the commit
|
||||
out="$(python3 "$TOOL" --repo "$REPO" --rev HEAD 2>&1)"; rc=$?
|
||||
line="$(printf '%s' "$out" | tail -n 1)"
|
||||
if [ $rc -eq 0 ] && printf '%s' "$line" | grep -q ', 0 fault(s)$'; then
|
||||
ok head "$line"
|
||||
else
|
||||
bad head "rc $rc: $line"
|
||||
fi
|
||||
|
||||
# historic -- the red reading the batch framing used to bend
|
||||
if git -C "$REPO" cat-file -e '99eba1e^{commit}' 2>/dev/null; then
|
||||
out="$(python3 "$TOOL" --repo "$REPO" --rev 99eba1e 2>&1)"; rc=$?
|
||||
if [ $rc -eq 1 ] && printf '%s' "$out" | grep -q ', 1 fault(s)$' \
|
||||
&& ! printf '%s' "$out" | grep -q UNREADABLE; then
|
||||
ok historic "rc 1, 1 fault, no UNREADABLE: $(printf '%s' "$out" | head -n 1 | cut -c1-60)..."
|
||||
else
|
||||
bad historic "rc $rc, want 1 and exactly one fault, no UNREADABLE"
|
||||
fi
|
||||
else
|
||||
skip historic "99eba1e not in this clone (shallow?) -- not a pass, not a fault"
|
||||
fi
|
||||
|
||||
# wiring -- the gate names the leg and runs it at the worktree
|
||||
if grep -q '"compile-sweep"' "$REPO/tools/verify.py" \
|
||||
&& grep -q 'q169_compile_sweep.py' "$REPO/tools/verify.py" \
|
||||
&& grep -q '"--rev", "WORKTREE"' "$REPO/tools/verify.py"; then
|
||||
ok wiring "tools/verify.py: leg compile-sweep -> q169_compile_sweep.py at WORKTREE"
|
||||
else
|
||||
bad wiring "tools/verify.py no longer names the compile-sweep leg at WORKTREE"
|
||||
fi
|
||||
|
||||
echo "CHECK-q169: $legs leg(s), $faults fault(s)"
|
||||
[ $faults -eq 0 ] || exit 1
|
||||
exit 0
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
# CHECK-q170 -- the import-order guard still answers its question.
|
||||
#
|
||||
# Legs
|
||||
# selftest every fixture cell green (16 today), and the count pinned
|
||||
# worktree every tracked *.py on disk binds its imports first, rc 0
|
||||
# head the same reading at HEAD, rc 0
|
||||
# red a synthetic tree carrying the three shapes Q170 repaired
|
||||
# (module-level read, def called too early, both in one file)
|
||||
# reads rc 1 and names each one -- the green control is not the
|
||||
# only thing this guard can print
|
||||
# runs the three files the repair touched still execute: rc 0 each
|
||||
# wiring tools/verify.py names the leg and runs it at WORKTREE
|
||||
#
|
||||
# rc 0 every leg as expected | 1 a graded fault | 2 could not grade.
|
||||
# Run from anywhere; paths resolve from $0, the repo from this file's place.
|
||||
set -u
|
||||
|
||||
HERE="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
||||
REPO="$(cd "$HERE/../.." && pwd)"
|
||||
TOOL="$HERE/q170_import_order_check.py"
|
||||
CELLS="16 cells, 16 passed, 0 failed"
|
||||
legs=0
|
||||
faults=0
|
||||
|
||||
ok() { legs=$((legs + 1)); printf 'ok %-9s %s\n' "$1" "$2"; }
|
||||
bad() { legs=$((legs + 1)); faults=$((faults + 1)); printf 'FAULT %-9s %s\n' "$1" "$2"; }
|
||||
skip(){ legs=$((legs + 1)); printf 'skip %-9s %s\n' "$1" "$2"; }
|
||||
|
||||
[ -f "$TOOL" ] || { echo "cannot grade -- no tool: $TOOL"; exit 2; }
|
||||
command -v python3 >/dev/null || { echo "cannot grade -- no python3"; exit 2; }
|
||||
|
||||
# selftest -- the fixture corpus, and the pin on its size
|
||||
out="$(python3 "$TOOL" --selftest 2>&1)"; rc=$?
|
||||
if [ $rc -eq 0 ] && printf '%s' "$out" | grep -qF "$CELLS"; then
|
||||
ok selftest "$CELLS, rc 0"
|
||||
else
|
||||
bad selftest "rc $rc, want 0 and '$CELLS'; last: $(printf '%s' "$out" | tail -n 1)"
|
||||
fi
|
||||
|
||||
# worktree -- the tree about to be committed
|
||||
out="$(python3 "$TOOL" --repo "$REPO" 2>&1)"; rc=$?
|
||||
line="$(printf '%s' "$out" | tail -n 1)"
|
||||
if [ $rc -eq 0 ] && printf '%s' "$line" | grep -q ', 0 fault(s)$'; then
|
||||
ok worktree "$line"
|
||||
else
|
||||
bad worktree "rc $rc: $line"
|
||||
fi
|
||||
|
||||
# head -- the commit
|
||||
out="$(python3 "$TOOL" --repo "$REPO" --rev HEAD 2>&1)"; rc=$?
|
||||
line="$(printf '%s' "$out" | tail -n 1)"
|
||||
if [ $rc -eq 0 ] && printf '%s' "$line" | grep -q ', 0 fault(s)$'; then
|
||||
ok head "$line"
|
||||
else
|
||||
bad head "rc $rc: $line"
|
||||
fi
|
||||
|
||||
# red -- the guard can still print red, on the three shapes it was opened for
|
||||
red="$(mktemp -d "${TMPDIR:-/tmp}/q170-red.XXXXXX")"
|
||||
mkdir -p "$red/wizard-work/beam"
|
||||
printf 'import sys\n\n\ndef main():\n return Path(".")\n\n\nif __name__ == "__main__":\n sys.exit(main())\nfrom pathlib import Path\n' \
|
||||
> "$red/wizard-work/beam/early_call.py"
|
||||
printf 'REPO = Path(__file__).resolve()\nfrom pathlib import Path\n' \
|
||||
> "$red/wizard-work/beam/module_level.py"
|
||||
printf 'import os\nfrom pathlib import Path\n\nprint(Path(os.sep))\n' \
|
||||
> "$red/wizard-work/beam/clean.py"
|
||||
git -C "$red" init -q . 2>/dev/null
|
||||
git -C "$red" add -A 2>/dev/null
|
||||
out="$(python3 "$TOOL" --repo "$red" 2>&1)"; rc=$?
|
||||
n="$(printf '%s\n' "$out" | grep -c '^FAULT ')"
|
||||
if [ $rc -eq 1 ] && [ "$n" -ge 2 ] \
|
||||
&& printf '%s' "$out" | grep -q 'early_call.py' \
|
||||
&& printf '%s' "$out" | grep -q 'module_level.py' \
|
||||
&& ! printf '%s' "$out" | grep -q 'clean.py'; then
|
||||
ok red "rc 1, $n fault(s), both shapes named, the clean file silent"
|
||||
else
|
||||
bad red "rc $rc, want 1 with both shapes named and clean.py silent"
|
||||
fi
|
||||
rm -rf "$red"
|
||||
|
||||
# runs -- the property the guard exists for: these files EXECUTE
|
||||
runs_ok=1
|
||||
for f in wizard-work/beam/verify_wordcount_test.py \
|
||||
wizard-work/spark/verify_wordcount_test.py \
|
||||
wizard-work/ledger/verify_dates.py; do
|
||||
if ! python3 "$REPO/$f" >/dev/null 2>&1; then
|
||||
runs_ok=0
|
||||
bad runs "python3 $f did not exit 0"
|
||||
break
|
||||
fi
|
||||
done
|
||||
[ $runs_ok -eq 1 ] && ok runs "the three repaired files exit 0"
|
||||
|
||||
# wiring -- the gate names the leg and runs it at the worktree
|
||||
if grep -q '"import-order"' "$REPO/tools/verify.py" \
|
||||
&& grep -q 'q170_import_order_check.py' "$REPO/tools/verify.py" \
|
||||
&& grep -q '"--rev", "WORKTREE", "--quiet"' "$REPO/tools/verify.py"; then
|
||||
ok wiring "tools/verify.py: leg import-order -> q170 at WORKTREE"
|
||||
else
|
||||
bad wiring "tools/verify.py no longer names the import-order leg at WORKTREE"
|
||||
fi
|
||||
|
||||
echo "CHECK-q170: $legs leg(s), $faults fault(s)"
|
||||
[ $faults -eq 0 ] || exit 1
|
||||
exit 0
|
||||
@@ -654,8 +654,15 @@ PIN_OPENED = [51, 52, 53, 65, 100, 101, 102, 103, 104, 105, 106, 107, 109,
|
||||
# records 131, record_ids 97, ids 158, record_only 55, open 0, opened 26.
|
||||
# Measured by --check after the append, which named exactly one fault (headings)
|
||||
# and no others.
|
||||
PIN_COUNTS = {"headings": 120, "heading_ids": 103, "records": 131,
|
||||
"record_ids": 97, "ids": 158, "open": 0, "opened": 26,
|
||||
# Q170 (2026-11-05): one heading (## Q170 closure) and one record (**Q170 --
|
||||
# OPENED and REPAIRED ...) for a new id: headings 120 -> 121, heading_ids 103 ->
|
||||
# 104, records 131 -> 132, record_ids 97 -> 98, ids 158 -> 159; record_only
|
||||
# stays 55 and open / opened stay 0 / 26 (same-tick closure, no probe;
|
||||
# CHECK-q170 is its guard). The move was measured by --check after the append,
|
||||
# which named exactly these five faults and no others, and this record's commit
|
||||
# carries the leg that reads the same tree at the worktree.
|
||||
PIN_COUNTS = {"headings": 121, "heading_ids": 104, "records": 132,
|
||||
"record_ids": 98, "ids": 159, "open": 0, "opened": 26,
|
||||
"record_only": 55}
|
||||
|
||||
PIN_STATE = {
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
#!/usr/bin/env python3
|
||||
"""q169 compile sweep -- every tracked `.py` must PARSE, in the worktree and in
|
||||
the commit.
|
||||
|
||||
WHY THIS EXISTS
|
||||
`wizard-work/spark/verify_dir_analyzer.py` sat in the worktree with
|
||||
|
||||
def _check(cond, msg):
|
||||
\"\"\"A failure path `-O` cannot compile away ...\"\"\"
|
||||
from pathlib import Path
|
||||
if not cond:
|
||||
|
||||
-- a `from pathlib import Path` line inserted inside the function body by a
|
||||
mechanical path refactor, at column 0 between the docstring and the `if`.
|
||||
`python3 -m py_compile wizard-work/spark/verify_dir_analyzer.py` answered
|
||||
**rc 1**, `Sorry: IndentationError: unexpected indent (verify_dir_analyzer.py,
|
||||
line 11)`. In the same worktree `python3 tools/verify.py --fast` read **12
|
||||
legs, 11 pass, 0 fault, 1 skipped**, `gate: OK`, **rc 0**.
|
||||
|
||||
The two readings are both true and that is the gap. Every leg that parses
|
||||
Python parses a POPULATION, not the tree: `import_resolve_check.py` grades the
|
||||
committed test files (194 at the time), while `git ls-files '*.py'` held
|
||||
**1399**. A tracked file the gate never parses can be unimportable, and no leg
|
||||
says so -- the class this sweep names.
|
||||
|
||||
WHAT IT MEASURES -- one row per tracked `*.py`, at one revision
|
||||
WORKTREE (default) the files on disk, listed by `git ls-files` -- the tree
|
||||
about to be committed, which is what the tick writes.
|
||||
<rev> the commit: `git ls-tree -r` names the population and
|
||||
`git cat-file --batch` reads the bytes, so no export is
|
||||
materialised and the reading is of the commit itself.
|
||||
|
||||
A file is a FAULT when `ast.parse` cannot read it (SyntaxError, a NUL byte, a
|
||||
decode failure) or when a file git tracks at the rev is GONE from the
|
||||
worktree -- a file that is not there is not in the tree about to be committed.
|
||||
|
||||
EXIT CODES (the tools/ contract)
|
||||
0 every tracked .py parses
|
||||
1 at least one does not (or is missing from the worktree)
|
||||
2 could not grade: not a git repository, unknown rev, no tracked .py, git absent
|
||||
|
||||
Stdlib only; nothing here writes to the tree (`--json` writes the named file).
|
||||
"""
|
||||
import argparse
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
REV_WORKTREE = "WORKTREE"
|
||||
FAULT_LIMIT = 40
|
||||
|
||||
|
||||
def run_git(repo, *args, **kw):
|
||||
return subprocess.run(["git", "-C", repo] + list(args), capture_output=True,
|
||||
text=True, **kw)
|
||||
|
||||
|
||||
def resolve_repo(repo):
|
||||
"""(topdir, None) or (None, reason). A refusal names what it read."""
|
||||
if not os.path.isdir(repo):
|
||||
return None, "could not grade -- %s is not a directory" % repo
|
||||
try:
|
||||
r = run_git(repo, "rev-parse", "--show-toplevel")
|
||||
except OSError as exc:
|
||||
return None, "could not grade -- git is not runnable (%s)" % exc
|
||||
if r.returncode != 0:
|
||||
return None, ("could not grade -- %s is not a git repository (%s)"
|
||||
% (repo, (r.stderr or "").strip().splitlines()[-1:][0]
|
||||
if r.stderr.strip() else "no reason given"))
|
||||
return r.stdout.strip(), None
|
||||
|
||||
|
||||
def tracked(top, rev):
|
||||
"""(paths, None) at `rev`, or ([], reason). Sorted, NUL-split, no duplicates.
|
||||
|
||||
The pathspec is applied here, in Python, and not handed to `ls-tree`:
|
||||
`git ls-tree -r HEAD -- '*.py'` matches NOTHING (measured, 0 rows against a
|
||||
tree holding 1400), so a sweep built on it would report a clean tree by
|
||||
grading no files at all -- the exact shape of a false green this file names.
|
||||
|
||||
Read as bytes and decoded with `surrogateescape`, so a path that is not
|
||||
UTF-8 survives the round trip back to `read_rev`'s request bytes. They are
|
||||
sorted as `str`, which is only meaningful for the display order.
|
||||
"""
|
||||
if rev == REV_WORKTREE:
|
||||
r = subprocess.run(["git", "-C", top, "ls-files", "-z"], capture_output=True)
|
||||
else:
|
||||
r = subprocess.run(
|
||||
["git", "-C", top, "ls-tree", "-r", "-z", "--name-only", rev],
|
||||
capture_output=True)
|
||||
if r.returncode != 0:
|
||||
why = r.stderr.decode("utf-8", "replace").strip().splitlines()
|
||||
return [], ("could not grade -- revision %s: %s"
|
||||
% (rev, why[-1] if why else "git refused"))
|
||||
names = r.stdout.decode("utf-8", "surrogateescape").split("\0")
|
||||
return sorted({p for p in names if p.endswith(".py")}), None
|
||||
|
||||
|
||||
def read_worktree(top, rel):
|
||||
path = os.path.join(top, rel)
|
||||
if not os.path.isfile(path):
|
||||
return None, "MISSING from the worktree"
|
||||
try:
|
||||
with open(path, "rb") as fh:
|
||||
return fh.read(), None
|
||||
except OSError as exc:
|
||||
return None, "unreadable (%s)" % exc
|
||||
|
||||
|
||||
def read_rev(top, rev, rels):
|
||||
"""{rel: bytes} for every rel at `rev`, read in one `git cat-file --batch`.
|
||||
|
||||
Requests are NUL-delimited (`-z`), not newline-delimited. A tracked path may
|
||||
itself contain a newline -- this repository carried one at `99eba1e`,
|
||||
`wizard-work/beam/CHECK\\ncontent">\\n#!...`, 218 bytes -- and a
|
||||
newline-delimited request splits it into four extra requests, so every
|
||||
later response is mapped to the wrong path. Measured at `99eba1e` over the
|
||||
773 tracked `*.py`: the newline form recovered 766 and reported 7 real,
|
||||
parseable files as `UNREADABLE`; the `-z` form recovers 773, including the
|
||||
newline path byte-exact (185 B). A response shorter than its own header
|
||||
size means the framing drifted, so the blob is dropped rather than graded
|
||||
on truncated bytes -- a fact, not a silent pass.
|
||||
"""
|
||||
out = {}
|
||||
if not rels:
|
||||
return out
|
||||
req = b"".join(rev.encode("utf-8", "surrogateescape") + b":" +
|
||||
rel.encode("utf-8", "surrogateescape") + b"\0" for rel in rels)
|
||||
r = subprocess.run(["git", "-C", top, "cat-file", "--batch", "-z"],
|
||||
input=req, capture_output=True)
|
||||
if r.returncode != 0:
|
||||
return out
|
||||
buf, i = r.stdout, 0
|
||||
for rel in rels:
|
||||
nl = buf.find(b"\n", i)
|
||||
if nl < 0:
|
||||
break
|
||||
header = buf[i:nl].decode("utf-8", "replace")
|
||||
i = nl + 1
|
||||
parts = header.split()
|
||||
if len(parts) < 3 or parts[-1] == "missing":
|
||||
continue
|
||||
try:
|
||||
size = int(parts[2])
|
||||
except ValueError:
|
||||
continue
|
||||
blob = buf[i:i + size]
|
||||
i += size + 1
|
||||
if len(blob) == size:
|
||||
out[rel] = blob
|
||||
return out
|
||||
|
||||
|
||||
def grade_bytes(src, rel):
|
||||
"""(line, why) or None. `ast.parse` is the question: can Python read it."""
|
||||
import warnings
|
||||
try:
|
||||
with warnings.catch_warnings():
|
||||
# An invalid escape sequence is a SyntaxWarning, not a parse
|
||||
# failure: the file still compiles, and this sweep's question is
|
||||
# "can Python read it", not "is it lint-clean".
|
||||
warnings.simplefilter("ignore", SyntaxWarning)
|
||||
ast.parse(src, filename=rel)
|
||||
return None
|
||||
except SyntaxError as exc:
|
||||
return (exc.lineno or 0, "%s: %s" % (type(exc).__name__, exc.msg))
|
||||
except (ValueError, UnicodeDecodeError) as exc:
|
||||
return (0, "%s: %s" % (type(exc).__name__, exc))
|
||||
|
||||
|
||||
def sweep(top, rev):
|
||||
"""(files, faults, reason). faults: [{"path","line","why"}]."""
|
||||
rels, why = tracked(top, rev)
|
||||
if why:
|
||||
return 0, [], why
|
||||
if not rels:
|
||||
return 0, [], "could not grade -- no tracked *.py at %s" % (
|
||||
"the worktree" if rev == REV_WORKTREE else rev)
|
||||
faults = []
|
||||
if rev == REV_WORKTREE:
|
||||
for rel in rels:
|
||||
src, err = read_worktree(top, rel)
|
||||
if err:
|
||||
faults.append({"path": rel, "line": 0, "why": err})
|
||||
continue
|
||||
bad = grade_bytes(src, rel)
|
||||
if bad:
|
||||
faults.append({"path": rel, "line": bad[0], "why": bad[1]})
|
||||
else:
|
||||
blobs = read_rev(top, rev, rels)
|
||||
for rel in rels:
|
||||
if rel not in blobs:
|
||||
faults.append({"path": rel, "line": 0,
|
||||
"why": "UNREADABLE at %s" % rev})
|
||||
continue
|
||||
bad = grade_bytes(blobs[rel], rel)
|
||||
if bad:
|
||||
faults.append({"path": rel, "line": bad[0], "why": bad[1]})
|
||||
return len(rels), faults, None
|
||||
|
||||
|
||||
def shown(path):
|
||||
"""A path as ONE printable line: newlines and undecodable bytes escaped.
|
||||
|
||||
A fault line is read by a human and by `grep`; the tracked path at
|
||||
`99eba1e` contains four newlines, so printing it raw turned one fault into
|
||||
eight lines of bash source in the middle of the report.
|
||||
"""
|
||||
return (path.encode("utf-8", "backslashreplace")
|
||||
.decode("utf-8", "replace").replace("\n", "\\n"))
|
||||
|
||||
|
||||
def report(rev, files, faults, quiet):
|
||||
where = "the worktree" if rev == REV_WORKTREE else rev
|
||||
if not quiet:
|
||||
for f in faults[:FAULT_LIMIT]:
|
||||
print("FAULT %s:%d %s" % (shown(f["path"]), f["line"], f["why"]))
|
||||
if len(faults) > FAULT_LIMIT:
|
||||
print(" ... %d more fault(s) not printed" % (len(faults) - FAULT_LIMIT))
|
||||
print("compile_sweep: %d tracked .py at %s, %d fault(s)"
|
||||
% (files, where, len(faults)))
|
||||
return 1 if faults else 0
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- selftest
|
||||
|
||||
def _git(repo, *args):
|
||||
return subprocess.run(["git", "-C", repo] + list(args), capture_output=True,
|
||||
text=True)
|
||||
|
||||
|
||||
def _write(path, text):
|
||||
with open(path, "w", encoding="utf-8") as fh:
|
||||
fh.write(text)
|
||||
|
||||
|
||||
def _commit(repo, msg="fixture"):
|
||||
_git(repo, "add", "-A")
|
||||
_git(repo, "-c", "user.name=selftest", "-c", "user.email=selftest@invalid",
|
||||
"-c", "commit.gpgsign=false", "commit", "-q", "-m", msg)
|
||||
|
||||
|
||||
GOOD = "def f():\n return 1\n"
|
||||
BROKEN = ("def _check(cond, msg):\n"
|
||||
" \"\"\"A failure path.\"\"\"\n"
|
||||
"from pathlib import Path\n"
|
||||
" if not cond:\n"
|
||||
" raise RuntimeError(msg)\n")
|
||||
|
||||
|
||||
def selftest():
|
||||
cells = passed = 0
|
||||
failures = []
|
||||
|
||||
def check(name, got, want):
|
||||
nonlocal cells, passed
|
||||
cells += 1
|
||||
if got == want:
|
||||
passed += 1
|
||||
else:
|
||||
failures.append("%s: got %r want %r" % (name, got, want))
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="q169-selftest-") as td:
|
||||
def repo(name, files, do_commit=True):
|
||||
p = os.path.join(td, name)
|
||||
os.makedirs(p)
|
||||
for rel, text in files.items():
|
||||
full = os.path.join(p, rel)
|
||||
os.makedirs(os.path.dirname(full) or p, exist_ok=True)
|
||||
_write(full, text)
|
||||
_git(p, "init", "-q", ".")
|
||||
if do_commit:
|
||||
_commit(p)
|
||||
return p
|
||||
|
||||
# 1 -- a clean tree is clean
|
||||
a = repo("clean", {"a.py": GOOD, "pkg/b.py": GOOD})
|
||||
files, faults, why = sweep(a, REV_WORKTREE)
|
||||
check("1 files counted", files, 2)
|
||||
check("1 no fault", (faults, why), ([], None))
|
||||
|
||||
# 2 -- one unparseable file names itself and its line
|
||||
b = repo("broken", {"a.py": GOOD, "bad.py": BROKEN})
|
||||
files, faults, why = sweep(b, REV_WORKTREE)
|
||||
check("2 one fault", len(faults), 1)
|
||||
check("2 names path", faults[0]["path"], "bad.py")
|
||||
check("2 names a line", faults[0]["line"] > 0, True)
|
||||
|
||||
# 3 -- the rev is the axis: HEAD clean, worktree broken
|
||||
c = repo("revaxis", {"a.py": GOOD})
|
||||
_write(os.path.join(c, "a.py"), BROKEN)
|
||||
_, wf, _ = sweep(c, REV_WORKTREE)
|
||||
_, hf, _ = sweep(c, "HEAD")
|
||||
check("3 worktree faults", len(wf), 1)
|
||||
check("3 HEAD clean", (len(hf), hf), (0, []))
|
||||
|
||||
# 4 -- and the other way: the break is committed, the worktree fixed
|
||||
d = repo("revaxis2", {"a.py": BROKEN})
|
||||
_write(os.path.join(d, "a.py"), GOOD)
|
||||
_, wf2, _ = sweep(d, REV_WORKTREE)
|
||||
_, hf2, _ = sweep(d, "HEAD")
|
||||
check("4 worktree clean", len(wf2), 0)
|
||||
check("4 HEAD faults", [f["path"] for f in hf2], ["a.py"])
|
||||
|
||||
# 5 -- a tracked file deleted from the worktree is a fault, not a skip
|
||||
e = repo("gone", {"a.py": GOOD, "b.py": GOOD})
|
||||
os.unlink(os.path.join(e, "b.py"))
|
||||
_, gf, _ = sweep(e, REV_WORKTREE)
|
||||
check("5 missing is a fault", [(f["path"], f["why"][:7]) for f in gf],
|
||||
[("b.py", "MISSING")])
|
||||
|
||||
# 6 -- a directory that is not a repository is refused, not graded
|
||||
plain = os.path.join(td, "plain")
|
||||
os.makedirs(plain)
|
||||
top, why6 = resolve_repo(plain)
|
||||
check("6 refused", (top, "not a git repository" in (why6 or "")),
|
||||
(None, True))
|
||||
|
||||
# 7 -- a repository with no python is a refusal too (rc 2)
|
||||
g = repo("nopy", {"README.md": "hi\n"})
|
||||
files7, _, why7 = sweep(g, REV_WORKTREE)
|
||||
check("7 no .py refused", (files7, "no tracked *.py" in (why7 or "")),
|
||||
(0, True))
|
||||
|
||||
# 8 -- an unknown rev is a refusal, never a clean zero
|
||||
_, _, why8 = sweep(a, "no-such-rev")
|
||||
check("8 bad rev refused", "revision no-such-rev" in (why8 or ""), True)
|
||||
|
||||
# 9 -- --json carries the fault and the rc
|
||||
jpath = os.path.join(td, "rows.json")
|
||||
rc9 = main(["--repo", b, "--json", jpath, "--quiet"])
|
||||
with open(jpath) as fh:
|
||||
doc = json.load(fh)
|
||||
check("9 json rc", (rc9, doc["rc"]), (1, 1))
|
||||
check("9 json fault", [f["path"] for f in doc["faults"]], ["bad.py"])
|
||||
|
||||
# 10 -- an unwritable --json target is rc 2, legible, no traceback
|
||||
rc10 = main(["--repo", a, "--quiet", "--json",
|
||||
os.path.join(td, "no-such-dir", "x.json")])
|
||||
check("10 unwritable json rc 2", rc10, 2)
|
||||
|
||||
# 11 -- the broken reading is rc 1 through main(), the clean one rc 0
|
||||
check("11 main rc", (main(["--repo", a, "--quiet"]),
|
||||
main(["--repo", b, "--quiet"])), (0, 1))
|
||||
|
||||
# 12 -- a tracked path containing a NEWLINE is read at the rev, and the
|
||||
# file sorted after it is still read: the batch framing must be NUL.
|
||||
# The pre-repair reader (newline-delimited requests) split this path
|
||||
# into extra requests and lost `z.py`, which is what this cell kills.
|
||||
h = repo("newlinepath", {"a.py": GOOD})
|
||||
_write(os.path.join(h, "0weird\nname.py"), GOOD)
|
||||
_write(os.path.join(h, "z.py"), GOOD)
|
||||
_commit(h, "newline path")
|
||||
files12, faults12, why12 = sweep(h, "HEAD")
|
||||
check("12 every file read", (files12, why12), (3, None))
|
||||
check("12 no fault", [(f["path"], f["why"]) for f in faults12], [])
|
||||
check("12 newline path recovered",
|
||||
[p for p in read_rev(h, "HEAD", tracked(h, "HEAD")[0])
|
||||
if "\n" in p], ["0weird\nname.py"])
|
||||
|
||||
# 13 -- and a fault on such a path prints as ONE line
|
||||
i = repo("newlinefault", {"0weird\nname.py": BROKEN, "z.py": GOOD})
|
||||
files13, faults13, _ = sweep(i, "HEAD")
|
||||
check("13 fault found", (files13, [f["path"] for f in faults13]),
|
||||
(2, ["0weird\nname.py"]))
|
||||
check("13 one printable line", shown(faults13[0]["path"]),
|
||||
"0weird\\nname.py")
|
||||
|
||||
print("selftest: %d cells, %d passed, %d failed"
|
||||
% (cells, passed, len(failures)))
|
||||
for f in failures:
|
||||
print("FAIL %s" % f)
|
||||
return 0 if passed == cells else 1
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------- main
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(
|
||||
description="every tracked *.py must parse, at the worktree or a rev")
|
||||
ap.add_argument("--repo", default=".",
|
||||
help="repository to grade (default .)")
|
||||
ap.add_argument("--rev", default=REV_WORKTREE,
|
||||
help="WORKTREE (default) or a git revision")
|
||||
ap.add_argument("--json", metavar="PATH", help="write the rows here")
|
||||
ap.add_argument("--quiet", action="store_true", help="summary line only")
|
||||
ap.add_argument("--selftest", action="store_true", help="run the fixtures")
|
||||
a = ap.parse_args(argv)
|
||||
if a.selftest:
|
||||
return selftest()
|
||||
|
||||
top, why = resolve_repo(a.repo)
|
||||
if why:
|
||||
print(why, file=sys.stderr)
|
||||
return 2
|
||||
files, faults, why = sweep(top, a.rev)
|
||||
if why:
|
||||
print(why, file=sys.stderr)
|
||||
return 2
|
||||
rc = report(a.rev, files, faults, a.quiet)
|
||||
if a.json:
|
||||
try:
|
||||
with open(a.json, "w") as fh:
|
||||
json.dump({"repo": top, "rev": a.rev, "files": files,
|
||||
"faults": faults, "rc": rc}, fh, indent=1)
|
||||
except OSError as exc:
|
||||
print("cannot write --json %s: %s" % (a.json, exc), file=sys.stderr)
|
||||
return 2
|
||||
return rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,355 @@
|
||||
#!/usr/bin/env python3
|
||||
"""q170 import-order check -- a module-level import must be BOUND before the name
|
||||
it binds is EXECUTED.
|
||||
|
||||
WHY THIS EXISTS
|
||||
The 2026-11-05 host-path refactor moved 85 verifier files off
|
||||
`/root/troupe-standup` onto `Path(__file__).resolve().parents[2]` and, by hand,
|
||||
inserted `from pathlib import Path` into each of them. In three files the line
|
||||
landed where a text replacement could reach and not where Python reads first:
|
||||
|
||||
wizard-work/beam/verify_wordcount_test.py:46 import, after main() runs
|
||||
wizard-work/spark/verify_wordcount_test.py:46 import, after main() runs
|
||||
wizard-work/ledger/verify_dates.py:38 import, after REPO = Path(...)
|
||||
|
||||
Both shapes fail at RUN time with `NameError: name 'Path' is not defined`, and
|
||||
both PARSE cleanly: `ast.parse` has no opinion about order, so the compile leg
|
||||
(q169) read the tree green, `tools/verify.py --fast` read `gate: OK`, and the
|
||||
three files were dead the whole time. Measured before the repair:
|
||||
|
||||
python3 wizard-work/beam/verify_wordcount_test.py
|
||||
NameError: name 'Path' is not defined (line 10, inside main)
|
||||
|
||||
A file that parses and cannot run is the class this check names. The gate had
|
||||
no leg with an opinion on execution order; the compile sweep made the parse
|
||||
axis visible and this file is the axis beside it.
|
||||
|
||||
WHAT IT MEASURES -- three sound rules, no heuristics about style
|
||||
A. MODULE-LEVEL USE. A top-level statement that EXECUTES (not a `def`, a
|
||||
`class`, or the module docstring) reads the name before the import's line.
|
||||
`REPO = Path(__file__)...` above `from pathlib import Path` is this rule.
|
||||
B. A FUNCTION CALLED TOO EARLY. A `def` that references the name, defined
|
||||
above the import, and CALLED by a top-level statement above the import.
|
||||
`sys.exit(main())` above the import is this rule -- the shape that parses
|
||||
clean and dies on the first run.
|
||||
C. A `def` whose decorator, default, or annotation reads the name (those are
|
||||
evaluated where the `def` sits, not when it is called).
|
||||
|
||||
Deliberately NOT graded, because they are legitimate and a checker that fired
|
||||
on them would be noise: a name used only inside a `def` that is called after
|
||||
the import (rule B's counter-cell); `sys.path.insert(...)` above an import
|
||||
(this repository does it on purpose -- the name is not read before it is
|
||||
bound); imports inside a function body; `from __future__` and star imports.
|
||||
|
||||
POPULATION AND REVISION AXIS
|
||||
Identical to the compile leg by construction: `tracked`, `read_worktree`,
|
||||
`read_rev` and `resolve_repo` are IMPORTED from `q169_compile_sweep` rather
|
||||
than copied, so the two legs cannot drift on which files they call the tree
|
||||
(Q142 closed exactly that: a duplicate scanner with a different rule list
|
||||
called an independent verifier).
|
||||
|
||||
EXIT CODES (the tools/ contract)
|
||||
0 no fault | 1 at least one fault | 2 could not grade
|
||||
|
||||
Stdlib only; nothing here writes to the tree (`--json` writes the named file).
|
||||
"""
|
||||
import argparse
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from q169_compile_sweep import (REV_WORKTREE, read_rev, read_worktree, # noqa: E402
|
||||
resolve_repo, shown, tracked)
|
||||
|
||||
FAULT_LIMIT = 40
|
||||
SCOPE = "module-level imports and the top-level statements that execute"
|
||||
|
||||
|
||||
def _imports(tree):
|
||||
"""{name: (import_lineno, import_end_lineno)} for module-level imports.
|
||||
|
||||
Walks the module body but never into a `def`/`class`/`lambda` body: an
|
||||
import inside a function binds nothing at module scope, so it cannot be
|
||||
the import an earlier top-level statement was counting on.
|
||||
"""
|
||||
found = {}
|
||||
stack = list(tree.body)
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef,
|
||||
ast.Lambda)):
|
||||
continue
|
||||
if isinstance(node, (ast.Import, ast.ImportFrom)):
|
||||
mod = getattr(node, "module", None) or ""
|
||||
for alias in node.names:
|
||||
if alias.name == "*":
|
||||
continue
|
||||
name = alias.asname or alias.name.split(".")[0]
|
||||
# EARLIEST binding, not the first one visited: the walk is a
|
||||
# stack, so visit order is not document order, and a name
|
||||
# imported twice (pathlib at line 5 and again at line 12) was
|
||||
# reported against the LATER line -- a fault for a file that
|
||||
# binds the name before it reads it.
|
||||
cand = (node.lineno, node.end_lineno or node.lineno, mod)
|
||||
if name not in found or cand[0] < found[name][0]:
|
||||
found[name] = cand
|
||||
continue
|
||||
stack.extend(ast.iter_child_nodes(node))
|
||||
return found
|
||||
|
||||
|
||||
def _executed_reads(tree):
|
||||
"""{name: first_lineno} for names READ by a top-level statement that runs.
|
||||
|
||||
"Runs" means: not inside a `def`/`class`/`lambda` body. A `def`'s own
|
||||
decorators, defaults and annotations DO run where the `def` sits, so those
|
||||
are collected separately by `_definition_time_reads`.
|
||||
"""
|
||||
first = {}
|
||||
|
||||
def note(name, lineno):
|
||||
if lineno and lineno < first.get(name, 10 ** 9):
|
||||
first[name] = lineno
|
||||
|
||||
def walk(node):
|
||||
for child in ast.iter_child_nodes(node):
|
||||
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef,
|
||||
ast.ClassDef)):
|
||||
# decorators, defaults and annotations execute here; the body
|
||||
# does not. `walk_definition_time` collects the former.
|
||||
walk_definition_time(child)
|
||||
continue
|
||||
if isinstance(child, ast.Lambda):
|
||||
continue
|
||||
if isinstance(child, ast.Name) and isinstance(child.ctx, ast.Load):
|
||||
note(child.id, child.lineno)
|
||||
walk(child)
|
||||
|
||||
def walk_definition_time(fn):
|
||||
exprs = list(getattr(fn, "decorator_list", []))
|
||||
args = getattr(fn, "args", None)
|
||||
if args is not None:
|
||||
exprs.extend(a for a in args.defaults if a is not None)
|
||||
exprs.extend(a for a in args.kw_defaults if a is not None)
|
||||
for a in exprs:
|
||||
for sub in ast.walk(a):
|
||||
if isinstance(sub, ast.Name) and isinstance(sub.ctx, ast.Load):
|
||||
note(sub.id, sub.lineno)
|
||||
|
||||
for stmt in tree.body:
|
||||
if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
||||
walk_definition_time(stmt)
|
||||
continue
|
||||
if isinstance(stmt, (ast.Import, ast.ImportFrom)):
|
||||
continue
|
||||
if isinstance(stmt, ast.Name) and isinstance(stmt.ctx, ast.Load):
|
||||
note(stmt.id, stmt.lineno)
|
||||
walk(stmt)
|
||||
return first
|
||||
|
||||
|
||||
def _fn_reads(fn):
|
||||
"""{name: first_lineno} for names read anywhere inside a def/class body."""
|
||||
first = {}
|
||||
for sub in ast.walk(fn):
|
||||
if isinstance(sub, ast.Name) and isinstance(sub.ctx, ast.Load):
|
||||
if sub.lineno < first.get(sub.id, 10 ** 9):
|
||||
first[sub.id] = sub.lineno
|
||||
return first
|
||||
|
||||
|
||||
def order_faults(src, rel):
|
||||
"""[(line, why)] -- rule A, B and C, sorted by line."""
|
||||
import warnings
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", SyntaxWarning)
|
||||
tree = ast.parse(src, filename=rel)
|
||||
imports = _imports(tree)
|
||||
if not imports:
|
||||
return []
|
||||
executed = _executed_reads(tree)
|
||||
faults = []
|
||||
seen = set()
|
||||
|
||||
def add(line, why):
|
||||
if (line, why) not in seen:
|
||||
seen.add((line, why))
|
||||
faults.append((line, why))
|
||||
|
||||
# A -- read at module level before the import line
|
||||
for name, (start, _end, _mod) in imports.items():
|
||||
line = executed.get(name)
|
||||
if line is not None and line < start:
|
||||
add(line, "%s read here, imported at line %d" % (name, start))
|
||||
|
||||
# B / C -- a def above the import that reads the name
|
||||
for stmt in tree.body:
|
||||
if not isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef,
|
||||
ast.ClassDef)):
|
||||
continue
|
||||
body_reads = _fn_reads(stmt)
|
||||
for name, (start, _end, _mod) in imports.items():
|
||||
if stmt.lineno >= start or name not in body_reads:
|
||||
continue
|
||||
called = executed.get(stmt.name)
|
||||
if called is not None and called < start:
|
||||
add(called, "%s read by %s() at line %d, imported at line %d"
|
||||
% (name, stmt.name, called, start))
|
||||
return sorted(faults)
|
||||
|
||||
|
||||
def sweep(top, rev):
|
||||
"""(files, faults, reason). faults: [{"path","line","why"}]."""
|
||||
rels, why = tracked(top, rev)
|
||||
if why:
|
||||
return 0, [], why
|
||||
if not rels:
|
||||
return 0, [], "could not grade -- no tracked *.py at %s" % (
|
||||
"the worktree" if rev == REV_WORKTREE else rev)
|
||||
faults = []
|
||||
if rev == REV_WORKTREE:
|
||||
for rel in rels:
|
||||
src, err = read_worktree(top, rel)
|
||||
if err:
|
||||
continue # q169 owns "the file is gone"; not this question
|
||||
bad = order_faults(src, rel)
|
||||
for line, why in bad:
|
||||
faults.append({"path": rel, "line": line, "why": why})
|
||||
else:
|
||||
blobs = read_rev(top, rev, rels)
|
||||
for rel in rels:
|
||||
src = blobs.get(rel)
|
||||
if src is None:
|
||||
continue
|
||||
for line, why in order_faults(src, rel):
|
||||
faults.append({"path": rel, "line": line, "why": why})
|
||||
return len(rels), faults, None
|
||||
|
||||
|
||||
def report(rev, files, faults, quiet):
|
||||
where = "the worktree" if rev == REV_WORKTREE else rev
|
||||
if not quiet:
|
||||
for f in faults[:FAULT_LIMIT]:
|
||||
print("FAULT %s:%d %s" % (shown(f["path"]), f["line"], f["why"]))
|
||||
if len(faults) > FAULT_LIMIT:
|
||||
print(" ... %d more fault(s) not printed" % (len(faults) - FAULT_LIMIT))
|
||||
print("import_order: %d tracked .py at %s, %d fault(s)"
|
||||
% (files, where, len(faults)))
|
||||
return 1 if faults else 0
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- selftest
|
||||
|
||||
CLEAN = "import os\nfrom pathlib import Path\n\n\ndef f():\n return Path(os.sep)\n"
|
||||
SIMPLE_BAD = "REPO = Path(__file__).resolve()\nfrom pathlib import Path\n"
|
||||
EARLY_CALL = ("import sys\n\n\ndef main():\n return Path('.')\n\n\n"
|
||||
"if __name__ == '__main__':\n sys.exit(main())\n"
|
||||
"from pathlib import Path\n")
|
||||
LATE_CALL = ("import sys\nfrom pathlib import Path\n\n\ndef main():\n"
|
||||
" return Path('.')\n\n\nif __name__ == '__main__':\n"
|
||||
" sys.exit(main())\n")
|
||||
SYSPATH = ("import sys\nsys.path.insert(0, '/somewhere')\nimport helpers\n"
|
||||
"print(helpers.x)\n")
|
||||
STAR = "from os.path import *\nprint(join('a', 'b'))\n"
|
||||
DECORATED = ("import functools\n\n\n@functools.wraps(Path)\ndef f():\n pass\n"
|
||||
"from pathlib import Path\n")
|
||||
NESTED = ("def f():\n from pathlib import Path\n return Path('.')\n")
|
||||
|
||||
|
||||
def selftest():
|
||||
cells = passed = 0
|
||||
failures = []
|
||||
|
||||
def check(name, got, want):
|
||||
nonlocal cells, passed
|
||||
cells += 1
|
||||
if got == want:
|
||||
passed += 1
|
||||
else:
|
||||
failures.append("%s: got %r want %r" % (name, got, want))
|
||||
|
||||
def names(src):
|
||||
return [why.split()[0] for _line, why in order_faults(src, "x.py")]
|
||||
|
||||
check("1 clean file has no fault", order_faults(CLEAN, "x.py"), [])
|
||||
check("2 module-level read before import", names(SIMPLE_BAD), ["Path"])
|
||||
check("2b the line is the read, not the import",
|
||||
[ln for ln, _ in order_faults(SIMPLE_BAD, "x.py")], [1])
|
||||
check("2c the import line is named in the why",
|
||||
"imported at line 2" in order_faults(SIMPLE_BAD, "x.py")[0][1], True)
|
||||
check("3 def called before the import", names(EARLY_CALL), ["Path"])
|
||||
check("3b the call site is the line",
|
||||
[ln for ln, _ in order_faults(EARLY_CALL, "x.py")], [9])
|
||||
check("4 def called after the import is silent",
|
||||
order_faults(LATE_CALL, "x.py"), [])
|
||||
check("5 sys.path.insert above an import is silent",
|
||||
order_faults(SYSPATH, "x.py"), [])
|
||||
check("6 star import is not a binding", order_faults(STAR, "x.py"), [])
|
||||
check("7 a decorator reading the name is a fault",
|
||||
names(DECORATED), ["Path"])
|
||||
check("8 an import inside a function binds nothing at module scope",
|
||||
order_faults(NESTED, "x.py"), [])
|
||||
check("9 a file with no imports has no fault",
|
||||
order_faults("x = 1\nprint(x)\n", "x.py"), [])
|
||||
check("10 one fault per name, not one per read",
|
||||
len(order_faults("print(os)\nprint(os)\nimport os\n", "x.py")), 1)
|
||||
check("11 report() is rc 1 on a fault and rc 0 clean",
|
||||
(report("WORKTREE", 2, [{"path": "x.py", "line": 1, "why": "y"}], True),
|
||||
report("WORKTREE", 2, [], True)), (1, 0))
|
||||
|
||||
with __import__("tempfile").TemporaryDirectory(prefix="q170-selftest-") as td:
|
||||
plain = os.path.join(td, "plain")
|
||||
os.makedirs(plain)
|
||||
top, why = resolve_repo(plain)
|
||||
check("12 non-repository refused",
|
||||
(top, "not a git repository" in (why or "")), (None, True))
|
||||
rc = main(["--repo", td, "--quiet"])
|
||||
check("13 a tree with no .py is rc 2", rc, 2)
|
||||
|
||||
print("selftest: %d cells, %d passed, %d failed"
|
||||
% (cells, passed, len(failures)))
|
||||
for f in failures:
|
||||
print("FAIL %s" % f)
|
||||
return 0 if passed == cells else 1
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------- main
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser(
|
||||
description="every module-level import is bound before the name it "
|
||||
"binds is executed")
|
||||
ap.add_argument("--repo", default=".", help="repository to grade (default .)")
|
||||
ap.add_argument("--rev", default=REV_WORKTREE,
|
||||
help="WORKTREE (default) or a git revision")
|
||||
ap.add_argument("--json", metavar="PATH", help="write the rows here")
|
||||
ap.add_argument("--quiet", action="store_true", help="summary line only")
|
||||
ap.add_argument("--selftest", action="store_true", help="run the fixtures")
|
||||
a = ap.parse_args(argv)
|
||||
if a.selftest:
|
||||
return selftest()
|
||||
|
||||
top, why = resolve_repo(a.repo)
|
||||
if why:
|
||||
print(why, file=sys.stderr)
|
||||
return 2
|
||||
files, faults, why = sweep(top, a.rev)
|
||||
if why:
|
||||
print(why, file=sys.stderr)
|
||||
return 2
|
||||
rc = report(a.rev, files, faults, a.quiet)
|
||||
if a.json:
|
||||
try:
|
||||
with open(a.json, "w") as fh:
|
||||
json.dump({"repo": top, "rev": a.rev, "files": files,
|
||||
"faults": faults, "rc": rc}, fh, indent=1)
|
||||
except OSError as exc:
|
||||
print("cannot write --json %s: %s" % (a.json, exc), file=sys.stderr)
|
||||
return 2
|
||||
return rc
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user