gennady 0.8.4-next.8 → 0.8.4-next.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/ai/directives/sdd/audit.directive.xml +14 -1
  2. package/ai/directives/sdd/phase-execution-protocol.xml +3 -2
  3. package/ai/directives/testing/node-test.xml +4 -0
  4. package/ai/directives/testing/vitest-rules.xml +4 -0
  5. package/ai/skills/sdd-execute/SKILL.md +3 -2
  6. package/ai/skills/sdd-execute/scripts/check.sh +9 -6
  7. package/ai/skills/sdd-execute/scripts/verify.sh +14 -5
  8. package/dist/ai/directives/sdd/audit.directive.xml +14 -1
  9. package/dist/ai/directives/sdd/phase-execution-protocol.xml +3 -2
  10. package/dist/ai/directives/testing/node-test.xml +4 -0
  11. package/dist/ai/directives/testing/vitest-rules.xml +4 -0
  12. package/dist/ai/skills/sdd-execute/SKILL.md +3 -2
  13. package/dist/ai/skills/sdd-execute/scripts/check.sh +9 -6
  14. package/dist/ai/skills/sdd-execute/scripts/verify.sh +14 -5
  15. package/dist/chunks/{classify-mr-stage.logic-C9nlmo7G.js → classify-mr-stage.logic-Dnd980CN.js} +2 -2
  16. package/dist/chunks/{help-B02LDQzW.js → help-B_ASr0VC.js} +2 -2
  17. package/dist/chunks/{help-CjeiaUQ_.js → help-CS6ySEQr.js} +1 -1
  18. package/dist/chunks/{index-CrpSPpC-.js → index-B0QEFQp0.js} +2 -2
  19. package/dist/chunks/{index-Y-juM436.js → index-BBXM3lsU.js} +2 -2
  20. package/dist/chunks/{index-BMWZFp4U.js → index-BDLu3R0J.js} +2 -2
  21. package/dist/chunks/{index-BGgXGXg-.js → index-BEFk5tEp.js} +1 -1
  22. package/dist/chunks/{index-C-y9uwFd.js → index-BFpKNrY5.js} +1 -1
  23. package/dist/chunks/{index-9Bkdk7RL.js → index-BHK0Porr.js} +2 -2
  24. package/dist/chunks/{index-Bd51QuVH.js → index-BI_YHCD-.js} +2 -2
  25. package/dist/chunks/{index-BK1NwQr1.js → index-BSJjwB2R.js} +2 -2
  26. package/dist/chunks/{index-C_Cz5D_b.js → index-Bd-zIMib.js} +2 -2
  27. package/dist/chunks/index-BgYuwxiz.js +189 -0
  28. package/dist/chunks/{index-DW3WSoOG.js → index-Bk1YqNdY.js} +2 -2
  29. package/dist/chunks/{index-DGDuSviU.js → index-BlMEzvPi.js} +2 -2
  30. package/dist/chunks/{index-BJ1a8lhw.js → index-BvtD7rdl.js} +1 -1
  31. package/dist/chunks/{index-BEggEGo7.js → index-ByMppTkp.js} +2 -2
  32. package/dist/chunks/{index-BA76NeUs.js → index-C5Y2OgqW.js} +2 -2
  33. package/dist/chunks/{index-C4EN2eIa.js → index-C5YNRfwf.js} +2 -2
  34. package/dist/chunks/{index-CkQ3o27J.js → index-C8-ASGs3.js} +2 -2
  35. package/dist/chunks/{index-BUe8xaDt.js → index-CCqb-2J2.js} +3 -3
  36. package/dist/chunks/{index-BMLtkwzj.js → index-CJOe_V3T.js} +2 -2
  37. package/dist/chunks/{index-CJvv33n7.js → index-COSkBKSt.js} +2 -2
  38. package/dist/chunks/{index-e9JD7pCh.js → index-CUAkHNZj.js} +1 -1
  39. package/dist/chunks/{index-Ddot2Nhe.js → index-Ck6FvMDh.js} +2 -2
  40. package/dist/chunks/{index-BDl2IAXu.js → index-CyRUoAkC.js} +3 -3
  41. package/dist/chunks/{index-ChNXCDzn.js → index-D0SqcZV8.js} +2 -2
  42. package/dist/chunks/{index-l9U1wyLq.js → index-D1Vjjx-G.js} +1 -1
  43. package/dist/chunks/{index-DpAdFAL8.js → index-DBkie7c-.js} +2 -2
  44. package/dist/chunks/{index-CXRsLQR8.js → index-DTRnPURh.js} +2 -2
  45. package/dist/chunks/{index-RxmZo2qu.js → index-DVe5_tHO.js} +2 -2
  46. package/dist/chunks/{index-CvJ5NhsB.js → index-DhmJocBf.js} +1 -1
  47. package/dist/chunks/{index-BLfQaUv5.js → index-NUbc9nsY.js} +2 -2
  48. package/dist/chunks/{index-DPdRu-zq.js → index-QBAP-DXO.js} +2 -2
  49. package/dist/chunks/{index-DJ2acfLK.js → index-dyIXTgzD.js} +2 -2
  50. package/dist/chunks/{index-CgZ9VMDN.js → index-mXhoqAHl.js} +2 -2
  51. package/dist/chunks/{run-review-command.logic-C08f3D9C.js → run-review-command.logic-C7b7XU2K.js} +2 -2
  52. package/dist/chunks/{services-q8rLnybu.js → services-BD0-3T2K.js} +144 -126
  53. package/dist/chunks/{shared-DbIfVgIg.js → shared-CKWIgYym.js} +1 -1
  54. package/dist/cli/cmd/lint/index.d.ts +1 -1
  55. package/dist/gennady.js +36 -36
  56. package/dist/index.js +3 -3
  57. package/dist/services/stack/gate-runner.d.ts +4 -1
  58. package/dist/services/stack/tree-guard.d.ts +15 -1
  59. package/package.json +1 -1
  60. package/dist/chunks/index-BNVroM3w.js +0 -188
@@ -84,6 +84,19 @@
84
84
  | `INFO` | observation without mandatory action (often `INSIGHT_BACKFLOW`) |
85
85
 
86
86
  Severity does not depend on agent's mood. Closed-world drift = `BLOCKER` by default; coding-rules violation = `MAJOR`; stylistic deviation = `MINOR`.
87
+
88
+ **Overall status is computed from severities, never judged.** First matching row wins; print which row matched:
89
+
90
+ | Condition | Status |
91
+ |---|---|
92
+ | any open `BLOCKER` | `FAIL` |
93
+ | any open `MAJOR` without operator acknowledgement | `FAIL` |
94
+ | every open `BLOCKER`/`MAJOR` acknowledged + Decision Log entry | `PASS_WITH_ACKNOWLEDGED_RISKS` |
95
+ | only `MINOR` / `INFO`, or nothing | `PASS` |
96
+
97
+ `RULE_FILE_INCOMPLETE` and every other project-scope finding enters this table capped at `MINOR` (per `AX_FINDING_ROUTING`): shared infrastructure no task owns cannot decide that task's verdict. The `sev` field still reports the finding's own severity — the cap applies to the computation, not to the record.
98
+
99
+ This matters because the status is consumed mechanically: the orchestrator sets `[x] DONE` on PASS, runs a fix round on the first FAIL, and blocks the task on the second. An unspecified verdict here makes those transitions arbitrary.
87
100
  </Axiom>
88
101
 
89
102
  <Axiom id="AX_FINDINGS_AS_PROPOSALS">
@@ -421,7 +434,7 @@
421
434
  <Action>
422
435
  1. TRACE_HEADER (STACK `ROOT__SUMMARY`).
423
436
  2. Compile all findings: counts by severity + ordered findings table + proposed actions per `AX_FINDING_ROUTING`.
424
- 3. Determine overall status: PASS / PASS_WITH_ACKNOWLEDGED_RISKS / FAIL.
437
+ 3. Compute overall status from the table in `AX_SEVERITY_TAGGING` and print the row that matched. Project-scope findings enter capped at `MINOR`.
425
438
  4. **Map findings to phases (`phases_to_fix`).** For each finding whose `route` is `code-fix` or `ticket-reopen`: read its `Location.path`; find the phase block in ticket section 3 Phases whose `Target Files:` contains that path. Collect the set of phase IDs. Empty set → orchestrator will be told «no phases to fix» (e.g., findings only on specs or trackers). Used by `sdd-execute` orchestrator for selective phase re-run.
426
439
  5. Emit ephemeral summary per `AUDIT_SESSION_SUMMARY_FORMAT` in chat — include `phases_to_fix=[P<N>, …]` in the header line.
427
440
  6. If overall status is FAIL OR any finding routes to ticket reopen (per `AX_FINDING_ROUTING`) → append an `## Audit Rounds` entry to the ticket per `TICKET_AUDIT_ROUND_FORMAT` (`AX_REOPEN_ROUNDS_IN_TICKET`). PASS audits without reopen do NOT write to the ticket.
@@ -75,11 +75,12 @@
75
75
 
76
76
  <Axiom id="AX_PERMITTED_BASH_COMMANDS">
77
77
  Phase agent may ONLY run these bash commands:
78
- - **Must run:** `<sdd-path> verify <target-files>` — MANDATORY. Auto-discovers and runs typecheck, gennady DBC lint, linter, tests, and format check for the project. Runs before §5 commands. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Failing gate → fix and re-run before EMIT_HANDOFF. ONE `ver` line per invocation (see `STEP_5_VERIFY`).
78
+ - **Must run:** `<sdd-path> verify --wip <target-files>` — MANDATORY. `--wip` is not optional: verify refuses a dirty tree, and yours always is — you just edited your Target Files, and every git command is forbidden to you, so you cannot commit or stash to satisfy it. Auto-discovers and runs typecheck, gennady DBC lint, linter, tests, and format check for the project. Runs before §5 commands. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Failing gate → fix and re-run before EMIT_HANDOFF. ONE `ver` line per invocation (see `STEP_5_VERIFY`).
79
79
  - **Must run:** verification commands from ticket §5 that match this phase's Rules (per `AX_VERIFICATION_BEFORE_HANDOFF`).
80
80
  - **May run:** `ls <dir>` for targeted recon (NOT `-la` or recursive); `tsc --noEmit` after code changes; `node --test <specific test file>` for test-kind phases; `date -u +%Y-%m-%dT%H:%M:%SZ` for timestamps.
81
81
  - **May run:** `<sdd-path> extract <file> <NAME>` — extract anchored section from ticket/spec.
82
82
  - **May run:** `<sdd-path> lint <file>...` — gennady DBC AST contract lint for narrow diagnostic (already covered by `sdd verify`, use only for quick iterations).
83
+ - **May run:** `<sdd-path> verify --wip --json <target-files>` — same gates, machine-readable. `results[]` names every gate with its command and status, passing ones included; use it when you need to state which gates ran rather than only that they all passed.
83
84
 
84
85
  **Forbidden:** `git` ANY subcommand (status, branch, log, diff, add, commit); `npm run lint` alone (overkill — use `sdd verify` or `tsc --noEmit` for narrower checks); `npm run format`; `prettier` directly; any recursive or project-wide file scan; any command that reads unrelated files for «context».
85
86
 
@@ -282,7 +283,7 @@
282
283
  <Step id="STEP_5_VERIFY">
283
284
  <Goal>Run MANDATORY sdd verify on target files, then ticket §5 commands. Log only final results.</Goal>
284
285
  <Action>
285
- 1. **MANDATORY — sdd verify gate:** Run `<sdd-path> verify <target-files>`. This auto-discovers and executes typecheck, gennady DBC lint, linter, tests, and format check from package.json scripts. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Any failure is yours to fix unless `AX_PHASE_SCOPE_LOCK` puts the file in another phase's hands — «pre-existing» and «not my file» are not available for the rest. Fix, re-run the full `sdd verify`, and do not proceed to EMIT_HANDOFF while a gate you own is red.
286
+ 1. **MANDATORY — sdd verify gate:** Run `<sdd-path> verify --wip <target-files>`. This auto-discovers and executes typecheck, gennady DBC lint, linter, tests, and format check from package.json scripts. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Any failure is yours to fix unless `AX_PHASE_SCOPE_LOCK` puts the file in another phase's hands — «pre-existing» and «not my file» are not available for the rest. Fix, re-run the full `sdd verify`, and do not proceed to EMIT_HANDOFF while a gate you own is red.
286
287
 
287
288
  Log ONE `ver` line for this invocation, quoting the tool's own verdict — `sdd verify` names only the gates that FAILED, so a per-gate «pass» line reports a result it never printed. That is `fabricated-verification`, same BLOCKER class as logging a §5 command you did not run.
288
289
  2. Then run ticket §5 commands per existing logic below.
@@ -5,6 +5,10 @@
5
5
  Read `testing/common.xml` first. Everything about contract boundary, case flow, phase anchors, unified context, factory-over-hooks, BDD mapping, snapshot operator-confirm, file budget — lives there.
6
6
  </Mission>
7
7
 
8
+ <DependsOn>
9
+ - ai/directives/testing/common.xml
10
+ </DependsOn>
11
+
8
12
  <InheritedBaseline>
9
13
  All axioms in [`testing/common`](./common.xml) `Belief_State` apply here UNCHANGED — reader-agent treats them as binding under this directive. `BDD_Mapping_Hint`, `Definitions`, `Workflow_Outline` likewise inherited. This file only adds node:test-specific overrides and patterns.
10
14
  </InheritedBaseline>
@@ -5,6 +5,10 @@
5
5
  Read `testing/common.xml` first. Everything about contract boundary, case flow, phase anchors, unified context, factory-over-hooks, BDD mapping, snapshot operator-confirm, file budget — lives there.
6
6
  </Mission>
7
7
 
8
+ <DependsOn>
9
+ - ai/directives/testing/common.xml
10
+ </DependsOn>
11
+
8
12
  <InheritedBaseline>
9
13
  All axioms in [`testing/common`](./common.xml) `Belief_State` apply here UNCHANGED — reader-agent treats them as binding under this directive. `BDD_Mapping_Hint`, `Definitions`, `Workflow_Outline` likewise inherited. This file only adds Vitest-specific overrides and patterns. Mechanical assertion-API substitutions when the inherited text mentions node:test assertions: `assert.throws()` → `expect(fn).toThrow(...)`; `assert.rejects()` → `await expect(promise).rejects.toThrow(...)`; `assert.match()` → `expect(str).toMatch(regex)`.
10
14
  </InheritedBaseline>
@@ -78,8 +78,9 @@ Pause path (distinguish from failure — skill is awaiting operator, not broken)
78
78
  SDD tooling available at: ~/Developer/gennady/ai/skills/sdd-execute/scripts/sdd
79
79
  (run "~/Developer/gennady/ai/skills/sdd-execute/scripts/sdd help" for surface; use these
80
80
  for any extraction/lint/verify operations the directive references).
81
- MANDATORY before EMIT_HANDOFF: sdd verify <target-files> — auto-discovers and runs
82
- typecheck, gennady lint, linter, tests, and format check for the project.
81
+ MANDATORY before EMIT_HANDOFF: sdd verify --wip <target-files> — auto-discovers and runs
82
+ typecheck, gennady lint, linter, tests, and format check for the project. --wip is
83
+ required: verify refuses a dirty tree, and a phase agent's tree always is.
83
84
 
84
85
  Follow the directive. This ONE phase only. Do not invoke audit.
85
86
  ```
@@ -323,19 +323,19 @@ while IFS= read -r f; do
323
323
  for (i in r) retired[r[i]] = 1
324
324
  # Blocker lifecycle markers are their own shape, not action tokens.
325
325
  valid["🛑"] = 1; valid["✅"] = 1
326
- round = "-"; inlog = 0; inclose = 0; closelines = 0; closebad = 0
326
+ round = "-"; inlog = 0; inclose = 0; closelines = 0; closebad = 0; roundwork = 0
327
327
  }
328
328
  /^## 7\. Execution Log/ { inlog = 1; next }
329
329
  /^## [0-9]+\./ { if (inlog) inlog = 0 }
330
330
  inlog == 0 { next }
331
331
  /^### Round / {
332
- if (inclose && closelines != 1 && closebad == 0)
332
+ if (inclose && closelines != 1 && closebad == 0 && roundwork == 1)
333
333
  printf "%s\t%s\t%d\tbad-round-close\t-\texpected exactly one DONE line, found %d\n", ticket, round, closeline, closelines
334
- round = $3; inclose = 0; closelines = 0; closebad = 0; next
334
+ round = $3; inclose = 0; closelines = 0; closebad = 0; roundwork = 0; next
335
335
  }
336
336
  /^#### Round close/ { inclose = 1; closelines = 0; closebad = 0; closeline = NR; next }
337
337
  /^#### / {
338
- if (inclose && closelines != 1 && closebad == 0)
338
+ if (inclose && closelines != 1 && closebad == 0 && roundwork == 1)
339
339
  printf "%s\t%s\t%d\tbad-round-close\t-\texpected exactly one DONE line, found %d\n", ticket, round, closeline, closelines
340
340
  inclose = 0; closebad = 0; next
341
341
  }
@@ -345,6 +345,7 @@ while IFS= read -r f; do
345
345
  split(rest, w, " "); tok = w[1]
346
346
  sub(/:$/, "", tok) # a trailing colon is cosmetic, not a different token
347
347
  if (inclose) { if (tok == "DONE") closelines++ }
348
+ else if ($0 ~ /^- \[x\]/) roundwork = 1
348
349
  if (tok in valid) next
349
350
  # Every real token is lowercase except BLOCKED / DONE. A capitalised first word is a
350
351
  # sentence from the pre-consolidation prose plan ("Implementation file:", "Tracker
@@ -367,13 +368,15 @@ while IFS= read -r f; do
367
368
  if ($0 ~ /\[x\]/ && $0 ~ /DONE/) {
368
369
  printf "%s\t%s\t%d\tround-close-no-timestamp\tDONE\tclosed, but the `<ts>` is missing\n", ticket, round, NR
369
370
  closelines++
370
- } else {
371
+ } else if (roundwork == 1) {
372
+ # Only a round that actually ran can be "not closed". An untouched round is the
373
+ # pre-filled skeleton of a [ ] TODO ticket, which is its normal state.
371
374
  printf "%s\t%s\t%d\tunclosed-round\t-\tRound close carries no ticked DONE: %s\n", ticket, round, NR, substr($0, 1, 40)
372
375
  closebad = 1
373
376
  }
374
377
  }
375
378
  END {
376
- if (inclose && closelines != 1 && closebad == 0)
379
+ if (inclose && closelines != 1 && closebad == 0 && roundwork == 1)
377
380
  printf "%s\t%s\t%d\tbad-round-close\t-\texpected exactly one DONE line, found %d\n", ticket, round, closeline, closelines
378
381
  }
379
382
  ' "$f" >> "$LOG_TMP"
@@ -12,7 +12,9 @@
12
12
  # as a fallback for environments where gennady itself is not runnable.
13
13
  #
14
14
  # Usage:
15
- # verify.sh <file1> [<file2> ...]
15
+ # verify.sh [--wip] [--json] [--only=...] <file1> [<file2> ...]
16
+ # Flags pass through to `gennady verify`. Phase agents need --wip: verify refuses a dirty
17
+ # tree, and a phase agent has uncommitted work by definition.
16
18
  #
17
19
  # Exit codes:
18
20
  # 0 — all gates PASS
@@ -38,11 +40,18 @@ EOF
38
40
  exit 4
39
41
  fi
40
42
 
41
- for file in "$@"; do
42
- if [[ ! -f "$file" ]]; then
43
- echo "[$PROG] FILE_NOT_FOUND: $file"
43
+ # Flags are forwarded to `gennady verify` untouched; only positional targets are checked here.
44
+ # `--wip` is the one a phase agent always needs: it has uncommitted work by definition.
45
+ FILE_ARGS=()
46
+ for arg in "$@"; do
47
+ case "$arg" in
48
+ -*) continue ;;
49
+ esac
50
+ if [[ ! -f "$arg" ]]; then
51
+ echo "[$PROG] FILE_NOT_FOUND: $arg"
44
52
  exit 4
45
53
  fi
54
+ FILE_ARGS+=("$arg")
46
55
  done
47
56
 
48
57
  # -----------------------------------------------------------
@@ -145,7 +154,7 @@ fi
145
154
  total=0
146
155
  passed=0
147
156
  failures=""
148
- FILES=("$@") # save original file args for file-specific commands
157
+ FILES=("${FILE_ARGS[@]}") # positional targets only; flags never reach a file-specific command
149
158
 
150
159
  run_cmd() {
151
160
  local label="$1"
@@ -84,6 +84,19 @@
84
84
  | `INFO` | observation without mandatory action (often `INSIGHT_BACKFLOW`) |
85
85
 
86
86
  Severity does not depend on agent's mood. Closed-world drift = `BLOCKER` by default; coding-rules violation = `MAJOR`; stylistic deviation = `MINOR`.
87
+
88
+ **Overall status is computed from severities, never judged.** First matching row wins; print which row matched:
89
+
90
+ | Condition | Status |
91
+ |---|---|
92
+ | any open `BLOCKER` | `FAIL` |
93
+ | any open `MAJOR` without operator acknowledgement | `FAIL` |
94
+ | every open `BLOCKER`/`MAJOR` acknowledged + Decision Log entry | `PASS_WITH_ACKNOWLEDGED_RISKS` |
95
+ | only `MINOR` / `INFO`, or nothing | `PASS` |
96
+
97
+ `RULE_FILE_INCOMPLETE` and every other project-scope finding enters this table capped at `MINOR` (per `AX_FINDING_ROUTING`): shared infrastructure no task owns cannot decide that task's verdict. The `sev` field still reports the finding's own severity — the cap applies to the computation, not to the record.
98
+
99
+ This matters because the status is consumed mechanically: the orchestrator sets `[x] DONE` on PASS, runs a fix round on the first FAIL, and blocks the task on the second. An unspecified verdict here makes those transitions arbitrary.
87
100
  </Axiom>
88
101
 
89
102
  <Axiom id="AX_FINDINGS_AS_PROPOSALS">
@@ -421,7 +434,7 @@
421
434
  <Action>
422
435
  1. TRACE_HEADER (STACK `ROOT__SUMMARY`).
423
436
  2. Compile all findings: counts by severity + ordered findings table + proposed actions per `AX_FINDING_ROUTING`.
424
- 3. Determine overall status: PASS / PASS_WITH_ACKNOWLEDGED_RISKS / FAIL.
437
+ 3. Compute overall status from the table in `AX_SEVERITY_TAGGING` and print the row that matched. Project-scope findings enter capped at `MINOR`.
425
438
  4. **Map findings to phases (`phases_to_fix`).** For each finding whose `route` is `code-fix` or `ticket-reopen`: read its `Location.path`; find the phase block in ticket section 3 Phases whose `Target Files:` contains that path. Collect the set of phase IDs. Empty set → orchestrator will be told «no phases to fix» (e.g., findings only on specs or trackers). Used by `sdd-execute` orchestrator for selective phase re-run.
426
439
  5. Emit ephemeral summary per `AUDIT_SESSION_SUMMARY_FORMAT` in chat — include `phases_to_fix=[P<N>, …]` in the header line.
427
440
  6. If overall status is FAIL OR any finding routes to ticket reopen (per `AX_FINDING_ROUTING`) → append an `## Audit Rounds` entry to the ticket per `TICKET_AUDIT_ROUND_FORMAT` (`AX_REOPEN_ROUNDS_IN_TICKET`). PASS audits without reopen do NOT write to the ticket.
@@ -75,11 +75,12 @@
75
75
 
76
76
  <Axiom id="AX_PERMITTED_BASH_COMMANDS">
77
77
  Phase agent may ONLY run these bash commands:
78
- - **Must run:** `<sdd-path> verify <target-files>` — MANDATORY. Auto-discovers and runs typecheck, gennady DBC lint, linter, tests, and format check for the project. Runs before §5 commands. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Failing gate → fix and re-run before EMIT_HANDOFF. ONE `ver` line per invocation (see `STEP_5_VERIFY`).
78
+ - **Must run:** `<sdd-path> verify --wip <target-files>` — MANDATORY. `--wip` is not optional: verify refuses a dirty tree, and yours always is — you just edited your Target Files, and every git command is forbidden to you, so you cannot commit or stash to satisfy it. Auto-discovers and runs typecheck, gennady DBC lint, linter, tests, and format check for the project. Runs before §5 commands. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Failing gate → fix and re-run before EMIT_HANDOFF. ONE `ver` line per invocation (see `STEP_5_VERIFY`).
79
79
  - **Must run:** verification commands from ticket §5 that match this phase's Rules (per `AX_VERIFICATION_BEFORE_HANDOFF`).
80
80
  - **May run:** `ls <dir>` for targeted recon (NOT `-la` or recursive); `tsc --noEmit` after code changes; `node --test <specific test file>` for test-kind phases; `date -u +%Y-%m-%dT%H:%M:%SZ` for timestamps.
81
81
  - **May run:** `<sdd-path> extract <file> <NAME>` — extract anchored section from ticket/spec.
82
82
  - **May run:** `<sdd-path> lint <file>...` — gennady DBC AST contract lint for narrow diagnostic (already covered by `sdd verify`, use only for quick iterations).
83
+ - **May run:** `<sdd-path> verify --wip --json <target-files>` — same gates, machine-readable. `results[]` names every gate with its command and status, passing ones included; use it when you need to state which gates ran rather than only that they all passed.
83
84
 
84
85
  **Forbidden:** `git` ANY subcommand (status, branch, log, diff, add, commit); `npm run lint` alone (overkill — use `sdd verify` or `tsc --noEmit` for narrower checks); `npm run format`; `prettier` directly; any recursive or project-wide file scan; any command that reads unrelated files for «context».
85
86
 
@@ -282,7 +283,7 @@
282
283
  <Step id="STEP_5_VERIFY">
283
284
  <Goal>Run MANDATORY sdd verify on target files, then ticket §5 commands. Log only final results.</Goal>
284
285
  <Action>
285
- 1. **MANDATORY — sdd verify gate:** Run `<sdd-path> verify <target-files>`. This auto-discovers and executes typecheck, gennady DBC lint, linter, tests, and format check from package.json scripts. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Any failure is yours to fix unless `AX_PHASE_SCOPE_LOCK` puts the file in another phase's hands — «pre-existing» and «not my file» are not available for the rest. Fix, re-run the full `sdd verify`, and do not proceed to EMIT_HANDOFF while a gate you own is red.
286
+ 1. **MANDATORY — sdd verify gate:** Run `<sdd-path> verify --wip <target-files>`. This auto-discovers and executes typecheck, gennady DBC lint, linter, tests, and format check from package.json scripts. **RUN-ALL**: every gate executes regardless of previous failures; failures accumulate. **SUPPRESS-ON-SUCCESS**: passing gates produce zero output; only failed gates dump their command, exit code, and captured output. On all-pass: single summary line. Any failure is yours to fix unless `AX_PHASE_SCOPE_LOCK` puts the file in another phase's hands — «pre-existing» and «not my file» are not available for the rest. Fix, re-run the full `sdd verify`, and do not proceed to EMIT_HANDOFF while a gate you own is red.
286
287
 
287
288
  Log ONE `ver` line for this invocation, quoting the tool's own verdict — `sdd verify` names only the gates that FAILED, so a per-gate «pass» line reports a result it never printed. That is `fabricated-verification`, same BLOCKER class as logging a §5 command you did not run.
288
289
  2. Then run ticket §5 commands per existing logic below.
@@ -5,6 +5,10 @@
5
5
  Read `testing/common.xml` first. Everything about contract boundary, case flow, phase anchors, unified context, factory-over-hooks, BDD mapping, snapshot operator-confirm, file budget — lives there.
6
6
  </Mission>
7
7
 
8
+ <DependsOn>
9
+ - ai/directives/testing/common.xml
10
+ </DependsOn>
11
+
8
12
  <InheritedBaseline>
9
13
  All axioms in [`testing/common`](./common.xml) `Belief_State` apply here UNCHANGED — reader-agent treats them as binding under this directive. `BDD_Mapping_Hint`, `Definitions`, `Workflow_Outline` likewise inherited. This file only adds node:test-specific overrides and patterns.
10
14
  </InheritedBaseline>
@@ -5,6 +5,10 @@
5
5
  Read `testing/common.xml` first. Everything about contract boundary, case flow, phase anchors, unified context, factory-over-hooks, BDD mapping, snapshot operator-confirm, file budget — lives there.
6
6
  </Mission>
7
7
 
8
+ <DependsOn>
9
+ - ai/directives/testing/common.xml
10
+ </DependsOn>
11
+
8
12
  <InheritedBaseline>
9
13
  All axioms in [`testing/common`](./common.xml) `Belief_State` apply here UNCHANGED — reader-agent treats them as binding under this directive. `BDD_Mapping_Hint`, `Definitions`, `Workflow_Outline` likewise inherited. This file only adds Vitest-specific overrides and patterns. Mechanical assertion-API substitutions when the inherited text mentions node:test assertions: `assert.throws()` → `expect(fn).toThrow(...)`; `assert.rejects()` → `await expect(promise).rejects.toThrow(...)`; `assert.match()` → `expect(str).toMatch(regex)`.
10
14
  </InheritedBaseline>
@@ -78,8 +78,9 @@ Pause path (distinguish from failure — skill is awaiting operator, not broken)
78
78
  SDD tooling available at: ~/Developer/gennady/ai/skills/sdd-execute/scripts/sdd
79
79
  (run "~/Developer/gennady/ai/skills/sdd-execute/scripts/sdd help" for surface; use these
80
80
  for any extraction/lint/verify operations the directive references).
81
- MANDATORY before EMIT_HANDOFF: sdd verify <target-files> — auto-discovers and runs
82
- typecheck, gennady lint, linter, tests, and format check for the project.
81
+ MANDATORY before EMIT_HANDOFF: sdd verify --wip <target-files> — auto-discovers and runs
82
+ typecheck, gennady lint, linter, tests, and format check for the project. --wip is
83
+ required: verify refuses a dirty tree, and a phase agent's tree always is.
83
84
 
84
85
  Follow the directive. This ONE phase only. Do not invoke audit.
85
86
  ```
@@ -323,19 +323,19 @@ while IFS= read -r f; do
323
323
  for (i in r) retired[r[i]] = 1
324
324
  # Blocker lifecycle markers are their own shape, not action tokens.
325
325
  valid["🛑"] = 1; valid["✅"] = 1
326
- round = "-"; inlog = 0; inclose = 0; closelines = 0; closebad = 0
326
+ round = "-"; inlog = 0; inclose = 0; closelines = 0; closebad = 0; roundwork = 0
327
327
  }
328
328
  /^## 7\. Execution Log/ { inlog = 1; next }
329
329
  /^## [0-9]+\./ { if (inlog) inlog = 0 }
330
330
  inlog == 0 { next }
331
331
  /^### Round / {
332
- if (inclose && closelines != 1 && closebad == 0)
332
+ if (inclose && closelines != 1 && closebad == 0 && roundwork == 1)
333
333
  printf "%s\t%s\t%d\tbad-round-close\t-\texpected exactly one DONE line, found %d\n", ticket, round, closeline, closelines
334
- round = $3; inclose = 0; closelines = 0; closebad = 0; next
334
+ round = $3; inclose = 0; closelines = 0; closebad = 0; roundwork = 0; next
335
335
  }
336
336
  /^#### Round close/ { inclose = 1; closelines = 0; closebad = 0; closeline = NR; next }
337
337
  /^#### / {
338
- if (inclose && closelines != 1 && closebad == 0)
338
+ if (inclose && closelines != 1 && closebad == 0 && roundwork == 1)
339
339
  printf "%s\t%s\t%d\tbad-round-close\t-\texpected exactly one DONE line, found %d\n", ticket, round, closeline, closelines
340
340
  inclose = 0; closebad = 0; next
341
341
  }
@@ -345,6 +345,7 @@ while IFS= read -r f; do
345
345
  split(rest, w, " "); tok = w[1]
346
346
  sub(/:$/, "", tok) # a trailing colon is cosmetic, not a different token
347
347
  if (inclose) { if (tok == "DONE") closelines++ }
348
+ else if ($0 ~ /^- \[x\]/) roundwork = 1
348
349
  if (tok in valid) next
349
350
  # Every real token is lowercase except BLOCKED / DONE. A capitalised first word is a
350
351
  # sentence from the pre-consolidation prose plan ("Implementation file:", "Tracker
@@ -367,13 +368,15 @@ while IFS= read -r f; do
367
368
  if ($0 ~ /\[x\]/ && $0 ~ /DONE/) {
368
369
  printf "%s\t%s\t%d\tround-close-no-timestamp\tDONE\tclosed, but the `<ts>` is missing\n", ticket, round, NR
369
370
  closelines++
370
- } else {
371
+ } else if (roundwork == 1) {
372
+ # Only a round that actually ran can be "not closed". An untouched round is the
373
+ # pre-filled skeleton of a [ ] TODO ticket, which is its normal state.
371
374
  printf "%s\t%s\t%d\tunclosed-round\t-\tRound close carries no ticked DONE: %s\n", ticket, round, NR, substr($0, 1, 40)
372
375
  closebad = 1
373
376
  }
374
377
  }
375
378
  END {
376
- if (inclose && closelines != 1 && closebad == 0)
379
+ if (inclose && closelines != 1 && closebad == 0 && roundwork == 1)
377
380
  printf "%s\t%s\t%d\tbad-round-close\t-\texpected exactly one DONE line, found %d\n", ticket, round, closeline, closelines
378
381
  }
379
382
  ' "$f" >> "$LOG_TMP"
@@ -12,7 +12,9 @@
12
12
  # as a fallback for environments where gennady itself is not runnable.
13
13
  #
14
14
  # Usage:
15
- # verify.sh <file1> [<file2> ...]
15
+ # verify.sh [--wip] [--json] [--only=...] <file1> [<file2> ...]
16
+ # Flags pass through to `gennady verify`. Phase agents need --wip: verify refuses a dirty
17
+ # tree, and a phase agent has uncommitted work by definition.
16
18
  #
17
19
  # Exit codes:
18
20
  # 0 — all gates PASS
@@ -38,11 +40,18 @@ EOF
38
40
  exit 4
39
41
  fi
40
42
 
41
- for file in "$@"; do
42
- if [[ ! -f "$file" ]]; then
43
- echo "[$PROG] FILE_NOT_FOUND: $file"
43
+ # Flags are forwarded to `gennady verify` untouched; only positional targets are checked here.
44
+ # `--wip` is the one a phase agent always needs: it has uncommitted work by definition.
45
+ FILE_ARGS=()
46
+ for arg in "$@"; do
47
+ case "$arg" in
48
+ -*) continue ;;
49
+ esac
50
+ if [[ ! -f "$arg" ]]; then
51
+ echo "[$PROG] FILE_NOT_FOUND: $arg"
44
52
  exit 4
45
53
  fi
54
+ FILE_ARGS+=("$arg")
46
55
  done
47
56
 
48
57
  # -----------------------------------------------------------
@@ -145,7 +154,7 @@ fi
145
154
  total=0
146
155
  passed=0
147
156
  failures=""
148
- FILES=("$@") # save original file args for file-specific commands
157
+ FILES=("${FILE_ARGS[@]}") # positional targets only; flags never reach a file-specific command
149
158
 
150
159
  run_cmd() {
151
160
  local label="$1"
@@ -1,5 +1,5 @@
1
- import { a as c } from "./shared-DbIfVgIg.js";
2
- import { V as h } from "./services-q8rLnybu.js";
1
+ import { a as c } from "./shared-CKWIgYym.js";
2
+ import { V as h } from "./services-BD0-3T2K.js";
3
3
  function f(s) {
4
4
  const e = s?.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
5
5
  if (e) return e;
@@ -1,4 +1,4 @@
1
- import { B as n } from "./services-q8rLnybu.js";
1
+ import { B as n } from "./services-BD0-3T2K.js";
2
2
  function t() {
3
3
  console.info("gennady verify — stack-agnostic verification gates (one command for every stack)"), console.info(""), console.info("Usage:"), console.info(" npx gennady verify [path...] [options]"), console.info(""), console.info("Stacks (auto-detected by root marker file; all detected stacks run together):");
4
4
  for (const o of n)
@@ -7,7 +7,7 @@ function t() {
7
7
  " --changed Changed vs origin/HEAD, falling back to main|master (default)"
8
8
  ), console.info(""), console.info("Options:"), console.info(
9
9
  " --plan, --dry-run Print detection, diagnostics, config provenance and the plan; run nothing"
10
- ), console.info(" --json Machine-readable detection + plan + results"), console.info(" --full-output Do not truncate gate output in --json results"), console.info(
10
+ ), console.info(" --json Machine-readable detection + plan + results"), console.info(" --wip Verify uncommitted work: no clean-tree precondition, no"), console.info(" drift detection, never resets. Waits for a held lock."), console.info(" --full-output Do not truncate gate output in --json results"), console.info(
11
11
  " --only=<a,b> Run only these gates: `stack:gate` or bare `gate` (all stacks)"
12
12
  ), console.info(" --skip=<a,b> Drop these gates from the plan (same notation)"), console.info(" --stack=<id> One-shot stack.use (anystack | golang | node)"), console.info(" --root=<path> Repository root (default: cwd)"), console.info(" --help, -h Show this help"), console.info(""), console.info("Timeouts: every gate runs under a per-gate timeout — a plugin default, 10m for"), console.info(" a config gate that omits one, or the `timeout` you set. No global timeout — the"), console.info(" run upper bound is the sum of the plan; timeouts are shown in --plan."), console.info(""), console.info("Repo config — gennady.yaml (committable) deep-merged with personal .gennadyrc"), console.info('(repo, then $HOME); per-key winner shown in --plan. Section "stack":'), console.info(" stack:"), console.info(" use: [golang]"), console.info(" golang:"), console.info(" skipGates: [lint]"), console.info(" overrideGates:"), console.info(" test: { argv: [make, test], timeout: 15m }"), console.info(' build: { env: { GOPROXY: "https://goproxy.example.com/" } }'), console.info(" extraGates:"), console.info(" - { id: tidy-drift, argv: [go, mod, tidy, -diff], timeout: 5m }"), console.info(" Invalid config (unknown key, wrong type, bad duration) stops verify: exit 4."), console.info(""), console.info("Contract:"), console.info(" RUN-ALL every gate runs; failures accumulate in one report"), console.info(" SUPPRESS-ON-SUCCESS passing gates print nothing"), console.info(
13
13
  " gates never mutate gofmt -l / prettier --check; mutating ops belong to `gennady fix`"
@@ -1,4 +1,4 @@
1
- import { s as o } from "./shared-DbIfVgIg.js";
1
+ import { s as o } from "./shared-CKWIgYym.js";
2
2
  function n() {
3
3
  console.info(o.bold("gennady inbox-context — атомарный сбор контекста MR")), console.info(""), console.info(" gennady inbox-context --ref <group/project!iid> [флаги]"), console.info(""), console.info(o.bold("Флаги:")), console.info(" --ref <ref> MR ref (обязателен)"), console.info(" --vcs-host=<host> GitLab host (автодетект из origin)"), console.info(" --skip-worktree Без клона — changeset и worktree пусты"), console.info(" --skip-threads Без обсуждений — threads/stage пусты"), console.info(" --repos-base=<dir> База поиска клонов (default ~/Developer)"), console.info(" --state-dir=<dir> Директория состояния (default ~/.gennady)"), console.info(" --json Вывод в JSON (по умолчанию)"), console.info(""), console.info(o.bold("Вывод (JSON):")), console.info(" ref, title, webUrl — идентификация MR"), console.info(" worktree — path, base, diffRefs, repoLayout"), console.info(" changeset — files [{path, status, plus, minus}], totals, byCategory"), console.info(" stage — review_needed | reply_needed | awaiting_reply | idle"), console.info(" openQuestions, lastAuthor — из обсуждений"), console.info(" threads — { all, drafts } — полные треды и черновики"), console.info(" package — role, author, reviewers, description, approvedBy");
4
4
  }
@@ -1,5 +1,5 @@
1
- import { p as $, V as w, r as y } from "./shared-DbIfVgIg.js";
2
- import { l as n, a as f, V as h } from "./services-q8rLnybu.js";
1
+ import { p as $, V as w, r as y } from "./shared-CKWIgYym.js";
2
+ import { l as n, a as f, V as h } from "./services-BD0-3T2K.js";
3
3
  function b() {
4
4
  return {
5
5
  resolveVcsContext: y,
@@ -1,5 +1,5 @@
1
- import { s as i } from "./shared-DbIfVgIg.js";
2
- import { b as E, f as B, c as L, l as C, a as _ } from "./classify-mr-stage.logic-C9nlmo7G.js";
1
+ import { s as i } from "./shared-CKWIgYym.js";
2
+ import { b as E, f as B, c as L, l as C, a as _ } from "./classify-mr-stage.logic-Dnd980CN.js";
3
3
  import { existsSync as U, rmSync as k, readFileSync as F, mkdirSync as J, writeFileSync as Q, renameSync as V } from "node:fs";
4
4
  import { dirname as z } from "node:path";
5
5
  import { r as G, f as D, o as T, d as K, w as Y } from "./worktree-ops.logic-BmJ0w0WS.js";
@@ -1,6 +1,6 @@
1
- import { p as D, r as N, s as o } from "./shared-DbIfVgIg.js";
1
+ import { p as D, r as N, s as o } from "./shared-CKWIgYym.js";
2
2
  import R from "node:fs";
3
- import { a as j, V as A } from "./services-q8rLnybu.js";
3
+ import { a as j, V as A } from "./services-BD0-3T2K.js";
4
4
  function _(s, r) {
5
5
  const t = r?.above ?? 0, y = r?.below ?? 0;
6
6
  return ["```suggestion:-", t, "+", y, `
@@ -1,5 +1,5 @@
1
1
  import { parseArgs as c } from "node:util";
2
- import { l as d, r as l, A as p } from "./services-q8rLnybu.js";
2
+ import { l as d, r as l, A as p } from "./services-BD0-3T2K.js";
3
3
  function f(i) {
4
4
  const { positionals: e, values: n } = c({
5
5
  args: i.slice(3),
@@ -1,4 +1,4 @@
1
- import { p as e, r } from "./run-review-command.logic-C08f3D9C.js";
1
+ import { p as e, r } from "./run-review-command.logic-C7b7XU2K.js";
2
2
  const s = e(process.argv), o = await r({
3
3
  mode: "verify",
4
4
  args: s
@@ -7,8 +7,8 @@ import { promisify as h } from "node:util";
7
7
  import z from "node:os";
8
8
  import g from "node:fs";
9
9
  import { Buffer as X } from "node:buffer";
10
- import { p as G } from "./shared-DbIfVgIg.js";
11
- import { v as K } from "./services-q8rLnybu.js";
10
+ import { p as G } from "./shared-CKWIgYym.js";
11
+ import { s as K } from "./services-BD0-3T2K.js";
12
12
  let F;
13
13
  function q() {
14
14
  try {
@@ -1,5 +1,5 @@
1
- import { p as y, f as j, V as x, r as C } from "./shared-DbIfVgIg.js";
2
- import { l as i, a as $, V as w } from "./services-q8rLnybu.js";
1
+ import { p as y, f as j, V as x, r as C } from "./shared-CKWIgYym.js";
2
+ import { l as i, a as $, V as w } from "./services-BD0-3T2K.js";
3
3
  function M() {
4
4
  return {
5
5
  resolveVcsContext: C,
@@ -1,5 +1,5 @@
1
- import { p as N, V as A, r as C } from "./shared-DbIfVgIg.js";
2
- import { l as M, a as q, V as L } from "./services-q8rLnybu.js";
1
+ import { p as N, V as A, r as C } from "./shared-CKWIgYym.js";
2
+ import { l as M, a as q, V as L } from "./services-BD0-3T2K.js";
3
3
  function U() {
4
4
  return {
5
5
  resolveVcsContext: C,
@@ -1,5 +1,5 @@
1
- import { p as R, V as _, r as C } from "./shared-DbIfVgIg.js";
2
- import { l as S, a as D, V as U } from "./services-q8rLnybu.js";
1
+ import { p as R, V as _, r as C } from "./shared-CKWIgYym.js";
2
+ import { l as S, a as D, V as U } from "./services-BD0-3T2K.js";
3
3
  function q() {
4
4
  return {
5
5
  resolveVcsContext: C,