hatch3r 2.1.0 → 2.2.0

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 (103) hide show
  1. package/README.md +3 -3
  2. package/dist/cli/index.js +77 -19
  3. package/dist/content/agents/hatch3r-brownfield-spec.md +1 -1
  4. package/dist/content/agents/hatch3r-fixer.md +21 -8
  5. package/dist/content/agents/hatch3r-handoff-loader.md +7 -2
  6. package/dist/content/agents/hatch3r-handoff-preparer.md +5 -16
  7. package/dist/content/agents/hatch3r-implementer.md +29 -3
  8. package/dist/content/agents/hatch3r-maintainability.md +2 -0
  9. package/dist/content/agents/hatch3r-researcher.md +1 -1
  10. package/dist/content/agents/hatch3r-reviewer.md +21 -13
  11. package/dist/content/agents/hatch3r-ui.md +1 -1
  12. package/dist/content/agents/hatch3r-ux.md +1 -1
  13. package/dist/content/agents/shared/cq-specialist-roster.md +2 -2
  14. package/dist/content/agents/shared/efficiency-patterns.md +1 -1
  15. package/dist/content/agents/shared/quality-charter.md +6 -5
  16. package/dist/content/agents/shared/quality-specialist-frame.md +1 -1
  17. package/dist/content/agents/shared/severity-mapping.md +1 -1
  18. package/dist/content/agents/shared/triage-vocabulary.md +1 -1
  19. package/dist/content/agents/shared/user-content-templates.md +1 -1
  20. package/dist/content/agents/shared/user-question-protocol.md +4 -3
  21. package/dist/content/checks/code-quality.md +11 -0
  22. package/dist/content/checks/security.md +5 -0
  23. package/dist/content/checks/testing.md +2 -0
  24. package/dist/content/commands/board/pickup-delegation-multi.md +18 -8
  25. package/dist/content/commands/board/pickup-delegation.md +5 -4
  26. package/dist/content/commands/hatch3r-api-spec.md +5 -17
  27. package/dist/content/commands/hatch3r-auth-scaffold.md +7 -20
  28. package/dist/content/commands/hatch3r-benchmark.md +5 -17
  29. package/dist/content/commands/hatch3r-board-fill.md +6 -18
  30. package/dist/content/commands/hatch3r-board-pickup.md +8 -19
  31. package/dist/content/commands/hatch3r-bug-pipeline.md +4 -16
  32. package/dist/content/commands/hatch3r-bug-plan.md +5 -17
  33. package/dist/content/commands/hatch3r-codebase-map.md +5 -17
  34. package/dist/content/commands/hatch3r-create.md +5 -17
  35. package/dist/content/commands/hatch3r-debug.md +5 -17
  36. package/dist/content/commands/hatch3r-design-system-create.md +254 -0
  37. package/dist/content/commands/hatch3r-diagnose.md +10 -18
  38. package/dist/content/commands/hatch3r-feature-plan.md +5 -17
  39. package/dist/content/commands/hatch3r-handoff.md +5 -17
  40. package/dist/content/commands/hatch3r-healthcheck.md +5 -17
  41. package/dist/content/commands/hatch3r-incident-response.md +3 -15
  42. package/dist/content/commands/hatch3r-migration-plan.md +5 -17
  43. package/dist/content/commands/hatch3r-onboard.md +6 -18
  44. package/dist/content/commands/hatch3r-pack-install.md +6 -29
  45. package/dist/content/commands/hatch3r-pr-resolve.md +77 -79
  46. package/dist/content/commands/hatch3r-project-spec.md +5 -17
  47. package/dist/content/commands/hatch3r-quick-change.md +5 -17
  48. package/dist/content/commands/hatch3r-refactor-plan.md +5 -17
  49. package/dist/content/commands/hatch3r-release.md +6 -16
  50. package/dist/content/commands/hatch3r-revision.md +11 -23
  51. package/dist/content/commands/hatch3r-roadmap.md +5 -17
  52. package/dist/content/commands/hatch3r-security-audit.md +5 -17
  53. package/dist/content/commands/hatch3r-slo-scaffold.md +8 -20
  54. package/dist/content/commands/hatch3r-spec.md +11 -16
  55. package/dist/content/commands/hatch3r-test-plan.md +5 -17
  56. package/dist/content/commands/hatch3r-workflow.md +16 -26
  57. package/dist/content/commands/revision/revision-quality.md +6 -5
  58. package/dist/content/commands/shared/orchestration-frame.md +2 -2
  59. package/dist/content/rules/hatch3r-agent-orchestration-detail.md +2 -2
  60. package/dist/content/rules/hatch3r-agent-orchestration-detail.mdc +2 -2
  61. package/dist/content/rules/hatch3r-agent-orchestration.md +15 -15
  62. package/dist/content/rules/hatch3r-agent-orchestration.mdc +15 -15
  63. package/dist/content/rules/hatch3r-anti-duplication.md +15 -4
  64. package/dist/content/rules/hatch3r-anti-duplication.mdc +15 -4
  65. package/dist/content/rules/hatch3r-clarification-default.md +8 -7
  66. package/dist/content/rules/hatch3r-clarification-default.mdc +8 -7
  67. package/dist/content/rules/hatch3r-code-standards.md +1 -1
  68. package/dist/content/rules/hatch3r-code-standards.mdc +1 -1
  69. package/dist/content/rules/hatch3r-contract-census.md +160 -0
  70. package/dist/content/rules/hatch3r-contract-census.mdc +160 -0
  71. package/dist/content/rules/hatch3r-cost-visibility.md +11 -4
  72. package/dist/content/rules/hatch3r-cost-visibility.mdc +11 -4
  73. package/dist/content/rules/hatch3r-deep-context.md +1 -0
  74. package/dist/content/rules/hatch3r-deep-context.mdc +1 -0
  75. package/dist/content/rules/hatch3r-dynamic-stack-verification.md +114 -0
  76. package/dist/content/rules/hatch3r-dynamic-stack-verification.mdc +109 -0
  77. package/dist/content/rules/hatch3r-enhancability.md +1 -1
  78. package/dist/content/rules/hatch3r-enhancability.mdc +1 -1
  79. package/dist/content/rules/hatch3r-findings-ledger.md +129 -0
  80. package/dist/content/rules/hatch3r-findings-ledger.mdc +129 -0
  81. package/dist/content/rules/hatch3r-handoff-readiness.md +1 -1
  82. package/dist/content/rules/hatch3r-handoff-readiness.mdc +1 -1
  83. package/dist/content/rules/hatch3r-i18n.md +4 -0
  84. package/dist/content/rules/hatch3r-i18n.mdc +4 -0
  85. package/dist/content/rules/hatch3r-iteration-summary.md +47 -49
  86. package/dist/content/rules/hatch3r-iteration-summary.mdc +47 -49
  87. package/dist/content/rules/hatch3r-learning-system.md +6 -6
  88. package/dist/content/rules/hatch3r-learning-system.mdc +6 -6
  89. package/dist/content/rules/hatch3r-migrations.md +1 -1
  90. package/dist/content/rules/hatch3r-migrations.mdc +1 -1
  91. package/dist/content/rules/hatch3r-reviewer-calibration.md +2 -2
  92. package/dist/content/rules/hatch3r-reviewer-calibration.mdc +2 -2
  93. package/dist/content/rules/hatch3r-security-patterns.md +4 -0
  94. package/dist/content/rules/hatch3r-security-patterns.mdc +4 -0
  95. package/dist/content/rules/hatch3r-testing.md +14 -0
  96. package/dist/content/rules/hatch3r-testing.mdc +14 -0
  97. package/dist/content/rules/hatch3r-tool-currency.md +1 -1
  98. package/dist/content/rules/hatch3r-tool-currency.mdc +1 -1
  99. package/dist/content/skills/hatch3r-adhoc-orchestrate/SKILL.md +1 -1
  100. package/dist/content/skills/hatch3r-design-system-detect/SKILL.md +2 -0
  101. package/dist/content/skills/hatch3r-handoff-prepare/SKILL.md +4 -4
  102. package/dist/content/skills/hatch3r-report/SKILL.md +1 -1
  103. package/package.json +2 -2
@@ -0,0 +1,114 @@
1
+ ---
2
+ id: hatch3r-dynamic-stack-verification
3
+ type: rule
4
+ description: Runtime-evidence gates for stacks with no static type layer (plain-JS Vue, dynamic persistence) — smoke-mount changed components, dormant-collection-read ratchet, runtime smoke evidence on module-boundary changes; a green lint+build+mock-suite proves a layer, not the system.
5
+ scope: conditional
6
+ globs: "**/*.vue,**/*.js,**/*.mjs,**/*.cjs,**/*.jsx,**/composables/**,**/stores/**"
7
+ tags: [review, implementation, testing]
8
+ precedence: high
9
+ quality_charter: agents/shared/quality-charter.md
10
+ cache_friendly: true
11
+ ---
12
+ # Dynamic Stack Verification
13
+
14
+ **Pillars:** P2 (Scientific & Practical Quality), CQ5 (Testability)
15
+
16
+ ## Applicability Gate
17
+
18
+ The globs above over-attach by construction: editor rule-scoping is glob-only, while "no static type layer" is a project property no glob can express. Self-resolve on activation — exit early when a compiler already validates the consumer side of the changed file:
19
+
20
+ 1. **Checked JS:** the changed `.js`/`.mjs`/`.cjs`/`.jsx` file is reached by a `tsconfig.json` or `jsconfig.json` (`include`/`files`) that enables `checkJs` → skip.
21
+ 2. **Typed SFC:** the changed Vue SFC declares `<script lang="ts">` or `<script setup lang="ts">` → skip.
22
+ 3. **Pragma coverage:** a `// @ts-check` or `/* @flow */` pragma covers the changed file → skip.
23
+
24
+ When a checker is authoritative, defer to the compiler gates (`tsc --noEmit`, `vue-tsc`, `flow check`) and the rules that govern typed code. Record the skip in one review-note line — `dynamic-stack-verification: skipped (vue-tsc covers file)` — so "gate passed" and "gate not applicable" stay distinguishable. This rule exists for the residue: files where no compiler validates cross-module contracts, so contract drift reaches runtime unflagged.
25
+
26
+ ## Layer-vs-System
27
+
28
+ Lint, build, and a mocked test suite each prove one layer; none proves the system. Drift classes that pass all three in a typeless stack and fail only at runtime:
29
+
30
+ - **Client↔server field-name drift** — the server renames `userId` → `user_id`; the client bundle still builds.
31
+ - **Event-name drift** — the emitter renames `order:placed` → `order.placed`; every listener still lints green.
32
+ - **Store-key drift** — a state key is renamed in the store; consumers read `undefined` without a single thrown error.
33
+ - **i18n-key drift** — an interpolated key no longer resolves; the UI renders the raw key string.
34
+ - **Persisted collection-name drift** — writes target `orders`, reads target `order`; both queries execute without error against empty results.
35
+
36
+ Empirical anchor: a static type layer flags ~15% of shipped JavaScript bugs (Gao/Bird/Barr, ICSE 2017 — References). A stack that forgoes that layer substitutes runtime evidence for it — not nothing. Report a green lint+build+mock-suite as a layer-level result, never as system verification.
37
+
38
+ ## Smoke-Mount Gate
39
+
40
+ Every changed component mounts in the test runner — Vue Test Utils `mount()` or Testing Library `render` — with minimal REAL plumbing, not a fully stubbed shell:
41
+
42
+ - **Store:** the real store plugin via `global.plugins`, or `provide` keyed by the REAL injection key the component injects — a stub keyed to a guessed shape defeats the gate.
43
+ - **i18n:** a real i18n instance with the default locale loaded.
44
+ - **Props:** the component's required props at realistic values.
45
+
46
+ Minimal assertion — both clauses required:
47
+
48
+ 1. the component constructs without throwing, and
49
+ 2. one expected node is present (`wrapper.find('[data-test="..."]').exists()` / `screen.getByRole(...)`).
50
+
51
+ ```js
52
+ const wrapper = mount(OrderSummary, {
53
+ props: { orderId: 'ord_123' },
54
+ global: { plugins: [store, i18n] }, // real store, real injection keys
55
+ })
56
+ expect(wrapper.find('[data-test="order-total"]').exists()).toBe(true)
57
+ ```
58
+
59
+ Why mount, not import: `mount()` executes `setup()`, computed store access, and injection resolution immediately, so store-shape mismatches, missing injection keys, and prop-contract breaks surface at mount time — before any interaction is scripted.
60
+
61
+ **Severity:** missing smoke-mount on a changed component = Warning; on a route-level component (a router target) = Critical.
62
+
63
+ This gate is the floor BELOW the visual/behavioral mandates in `rules/hatch3r-testability.md` and `rules/hatch3r-ux-states-and-flows.md` — both stay authoritative for their depth (test-class mandates, state-flow and visual coverage); this gate adds only the construct-against-real-contracts floor beneath them.
64
+
65
+ ## Dynamic i18n Keys
66
+
67
+ Static extraction cannot see interpolated keys (`t('status.' + value)`, template-literal keys). The verification contract is owned by `rules/hatch3r-i18n.md` → Dynamic Keys: a test iterating the full value set and asserting each resolved key exists in the default locale, or a co-located comment enumerating the concrete keys.
68
+
69
+ Typeless-stack addendum: with no enum type constraining the interpolated value, the full-value-set key-existence test is the only available proof — treat every dynamic i18n call site in the diff as unverified until it carries one.
70
+
71
+ ## Dormant-Collection-Read Ratchet
72
+
73
+ No compiler links a string-named write site to its read site. Per changed module:
74
+
75
+ 1. **Inventory (grep-derived):** collect persistence identifiers — collection/table/model names — at call sites in the changed module: `collection('x')`, `.from('x')`, `db.x`, `model('X')` (e.g. `grep -nE "collection\(|\.from\(|db\.\w|model\(" <changed-module>`).
76
+ 2. **Classify each identifier's edge:**
77
+ - **written-and-read** — both sides exist in the codebase; the pairing is the proof.
78
+ - **written-never-read** — data persisted that no code consumes.
79
+ - **read-never-written** — a consumer waiting on data no code produces.
80
+ 3. **Resolve every one-sided (dormant) edge** with exactly one of:
81
+ - **runtime proof** — a smoke test executing that read/write path against a real or emulated store, or
82
+ - **a co-located annotation** — `// dormant-by-design: <reason>` (external writer, scheduled job, analytics consumer, migration in flight).
83
+
84
+ An identifier resolved from a variable (`db[collectionVar]`, `collection(name)`) cannot be inventoried statically — list the call site itself as a dormant edge until a runtime proof covers it.
85
+
86
+ **Ratchet mechanics (Betterer-style — References):**
87
+
88
+ - The count of unproven dormant edges per module is a committed baseline.
89
+ - A PR MUST NOT increase the count — fail the check on new violations.
90
+ - An improvement shrinks the baseline in the same PR.
91
+ - A deliberate increase requires an explicit baseline-update step with a written rationale in the PR description.
92
+ - Report before/after counts in the review verdict.
93
+
94
+ ## Boundary-Change Runtime Evidence
95
+
96
+ A client↔server or module-boundary change — changed exports, HTTP request/response shapes, event names, store contracts — requires ONE piece of captured runtime evidence crossing the changed boundary, cited in the review verdict:
97
+
98
+ - an executed request/response transcript (curl output, recorded fetch, HAR excerpt),
99
+ - a mounted-flow console log or screenshot exercising the changed contract, or
100
+ - a seeded local run whose output shows the boundary round-trip.
101
+
102
+ Evidence MUST postdate the diff — a transcript captured before the change proves the old contract, not the new one. A green build is not that evidence: each side passing against its own mock proves agreement with the mock, not agreement between the sides (contract-testing rationale — Pact, References). Deep-context scoring already weights this change class +2 (`rules/hatch3r-deep-context.md`, complexity signal "Module-boundary or client↔server contract change in files with no static type coverage").
103
+
104
+ **Severity:** boundary change with zero runtime evidence = Warning; on payment, auth, or data-mutation paths = Critical.
105
+
106
+ ## References
107
+
108
+ - Vue Test Utils — crash course + Testing Vuex guide: https://test-utils.vuejs.org/guide/essentials/a-crash-course.html and https://test-utils.vuejs.org/guide/advanced/vuex.html (accessed 2026-07-08, T1, current) — 3-line mount smoke; real store via `global.plugins`; mount executes store access.
109
+ - Testing Library — Vue Testing Library intro: https://testing-library.com/docs/vue-testing-library/intro/ (accessed 2026-07-08, T1, current) — tests resemble the way the software is used.
110
+ - Gao, Bird, Barr — "To Type or Not to Type: Quantifying Detectable Bugs in JavaScript" (ICSE 2017): https://earlbarr.com/publications/typestudy.pdf (accessed 2026-07-08, T1 peer-reviewed, canonical) — a static type layer detects ~15% of shipped JS bugs (CI 11.5–18.5%); 2022 follow-up as currency pointer: https://arxiv.org/abs/2203.11115.
111
+ - Pact — "How Pact works": https://docs.pact.io/getting_started/how_pact_works (accessed 2026-07-08, T1, page updated 2024-12-23) — mocked suites pass while the real provider differs.
112
+ - i18next-parser — README: https://github.com/i18next/i18next-parser (accessed 2026-07-08, T2, repository archived 2026-02-22) — documents the dynamic-key blind spot in static extraction.
113
+ - knip: https://knip.dev (accessed 2026-07-08, T2, current) — dynamic references as the static-reachability failure class.
114
+ - Betterer: https://phenomnomnominal.github.io/betterer (accessed 2026-07-08, T2, current) — baseline snapshot, fail-on-worse, shrink-on-better.
@@ -0,0 +1,109 @@
1
+ ---
2
+ description: Runtime-evidence gates for stacks with no static type layer (plain-JS Vue, dynamic persistence) — smoke-mount changed components, dormant-collection-read ratchet, runtime smoke evidence on module-boundary changes; a green lint+build+mock-suite proves a layer, not the system.
3
+ globs: ["**/*.vue", "**/*.js", "**/*.mjs", "**/*.cjs", "**/*.jsx", "**/composables/**", "**/stores/**"]
4
+ alwaysApply: false
5
+ precedence: high
6
+ ---
7
+ # Dynamic Stack Verification
8
+
9
+ **Pillars:** P2 (Scientific & Practical Quality), CQ5 (Testability)
10
+
11
+ ## Applicability Gate
12
+
13
+ The globs above over-attach by construction: editor rule-scoping is glob-only, while "no static type layer" is a project property no glob can express. Self-resolve on activation — exit early when a compiler already validates the consumer side of the changed file:
14
+
15
+ 1. **Checked JS:** the changed `.js`/`.mjs`/`.cjs`/`.jsx` file is reached by a `tsconfig.json` or `jsconfig.json` (`include`/`files`) that enables `checkJs` → skip.
16
+ 2. **Typed SFC:** the changed Vue SFC declares `<script lang="ts">` or `<script setup lang="ts">` → skip.
17
+ 3. **Pragma coverage:** a `// @ts-check` or `/* @flow */` pragma covers the changed file → skip.
18
+
19
+ When a checker is authoritative, defer to the compiler gates (`tsc --noEmit`, `vue-tsc`, `flow check`) and the rules that govern typed code. Record the skip in one review-note line — `dynamic-stack-verification: skipped (vue-tsc covers file)` — so "gate passed" and "gate not applicable" stay distinguishable. This rule exists for the residue: files where no compiler validates cross-module contracts, so contract drift reaches runtime unflagged.
20
+
21
+ ## Layer-vs-System
22
+
23
+ Lint, build, and a mocked test suite each prove one layer; none proves the system. Drift classes that pass all three in a typeless stack and fail only at runtime:
24
+
25
+ - **Client↔server field-name drift** — the server renames `userId` → `user_id`; the client bundle still builds.
26
+ - **Event-name drift** — the emitter renames `order:placed` → `order.placed`; every listener still lints green.
27
+ - **Store-key drift** — a state key is renamed in the store; consumers read `undefined` without a single thrown error.
28
+ - **i18n-key drift** — an interpolated key no longer resolves; the UI renders the raw key string.
29
+ - **Persisted collection-name drift** — writes target `orders`, reads target `order`; both queries execute without error against empty results.
30
+
31
+ Empirical anchor: a static type layer flags ~15% of shipped JavaScript bugs (Gao/Bird/Barr, ICSE 2017 — References). A stack that forgoes that layer substitutes runtime evidence for it — not nothing. Report a green lint+build+mock-suite as a layer-level result, never as system verification.
32
+
33
+ ## Smoke-Mount Gate
34
+
35
+ Every changed component mounts in the test runner — Vue Test Utils `mount()` or Testing Library `render` — with minimal REAL plumbing, not a fully stubbed shell:
36
+
37
+ - **Store:** the real store plugin via `global.plugins`, or `provide` keyed by the REAL injection key the component injects — a stub keyed to a guessed shape defeats the gate.
38
+ - **i18n:** a real i18n instance with the default locale loaded.
39
+ - **Props:** the component's required props at realistic values.
40
+
41
+ Minimal assertion — both clauses required:
42
+
43
+ 1. the component constructs without throwing, and
44
+ 2. one expected node is present (`wrapper.find('[data-test="..."]').exists()` / `screen.getByRole(...)`).
45
+
46
+ ```js
47
+ const wrapper = mount(OrderSummary, {
48
+ props: { orderId: 'ord_123' },
49
+ global: { plugins: [store, i18n] }, // real store, real injection keys
50
+ })
51
+ expect(wrapper.find('[data-test="order-total"]').exists()).toBe(true)
52
+ ```
53
+
54
+ Why mount, not import: `mount()` executes `setup()`, computed store access, and injection resolution immediately, so store-shape mismatches, missing injection keys, and prop-contract breaks surface at mount time — before any interaction is scripted.
55
+
56
+ **Severity:** missing smoke-mount on a changed component = Warning; on a route-level component (a router target) = Critical.
57
+
58
+ This gate is the floor BELOW the visual/behavioral mandates in `rules/hatch3r-testability.md` and `rules/hatch3r-ux-states-and-flows.md` — both stay authoritative for their depth (test-class mandates, state-flow and visual coverage); this gate adds only the construct-against-real-contracts floor beneath them.
59
+
60
+ ## Dynamic i18n Keys
61
+
62
+ Static extraction cannot see interpolated keys (`t('status.' + value)`, template-literal keys). The verification contract is owned by `rules/hatch3r-i18n.md` → Dynamic Keys: a test iterating the full value set and asserting each resolved key exists in the default locale, or a co-located comment enumerating the concrete keys.
63
+
64
+ Typeless-stack addendum: with no enum type constraining the interpolated value, the full-value-set key-existence test is the only available proof — treat every dynamic i18n call site in the diff as unverified until it carries one.
65
+
66
+ ## Dormant-Collection-Read Ratchet
67
+
68
+ No compiler links a string-named write site to its read site. Per changed module:
69
+
70
+ 1. **Inventory (grep-derived):** collect persistence identifiers — collection/table/model names — at call sites in the changed module: `collection('x')`, `.from('x')`, `db.x`, `model('X')` (e.g. `grep -nE "collection\(|\.from\(|db\.\w|model\(" <changed-module>`).
71
+ 2. **Classify each identifier's edge:**
72
+ - **written-and-read** — both sides exist in the codebase; the pairing is the proof.
73
+ - **written-never-read** — data persisted that no code consumes.
74
+ - **read-never-written** — a consumer waiting on data no code produces.
75
+ 3. **Resolve every one-sided (dormant) edge** with exactly one of:
76
+ - **runtime proof** — a smoke test executing that read/write path against a real or emulated store, or
77
+ - **a co-located annotation** — `// dormant-by-design: <reason>` (external writer, scheduled job, analytics consumer, migration in flight).
78
+
79
+ An identifier resolved from a variable (`db[collectionVar]`, `collection(name)`) cannot be inventoried statically — list the call site itself as a dormant edge until a runtime proof covers it.
80
+
81
+ **Ratchet mechanics (Betterer-style — References):**
82
+
83
+ - The count of unproven dormant edges per module is a committed baseline.
84
+ - A PR MUST NOT increase the count — fail the check on new violations.
85
+ - An improvement shrinks the baseline in the same PR.
86
+ - A deliberate increase requires an explicit baseline-update step with a written rationale in the PR description.
87
+ - Report before/after counts in the review verdict.
88
+
89
+ ## Boundary-Change Runtime Evidence
90
+
91
+ A client↔server or module-boundary change — changed exports, HTTP request/response shapes, event names, store contracts — requires ONE piece of captured runtime evidence crossing the changed boundary, cited in the review verdict:
92
+
93
+ - an executed request/response transcript (curl output, recorded fetch, HAR excerpt),
94
+ - a mounted-flow console log or screenshot exercising the changed contract, or
95
+ - a seeded local run whose output shows the boundary round-trip.
96
+
97
+ Evidence MUST postdate the diff — a transcript captured before the change proves the old contract, not the new one. A green build is not that evidence: each side passing against its own mock proves agreement with the mock, not agreement between the sides (contract-testing rationale — Pact, References). Deep-context scoring already weights this change class +2 (`rules/hatch3r-deep-context.md`, complexity signal "Module-boundary or client↔server contract change in files with no static type coverage").
98
+
99
+ **Severity:** boundary change with zero runtime evidence = Warning; on payment, auth, or data-mutation paths = Critical.
100
+
101
+ ## References
102
+
103
+ - Vue Test Utils — crash course + Testing Vuex guide: https://test-utils.vuejs.org/guide/essentials/a-crash-course.html and https://test-utils.vuejs.org/guide/advanced/vuex.html (accessed 2026-07-08, T1, current) — 3-line mount smoke; real store via `global.plugins`; mount executes store access.
104
+ - Testing Library — Vue Testing Library intro: https://testing-library.com/docs/vue-testing-library/intro/ (accessed 2026-07-08, T1, current) — tests resemble the way the software is used.
105
+ - Gao, Bird, Barr — "To Type or Not to Type: Quantifying Detectable Bugs in JavaScript" (ICSE 2017): https://earlbarr.com/publications/typestudy.pdf (accessed 2026-07-08, T1 peer-reviewed, canonical) — a static type layer detects ~15% of shipped JS bugs (CI 11.5–18.5%); 2022 follow-up as currency pointer: https://arxiv.org/abs/2203.11115.
106
+ - Pact — "How Pact works": https://docs.pact.io/getting_started/how_pact_works (accessed 2026-07-08, T1, page updated 2024-12-23) — mocked suites pass while the real provider differs.
107
+ - i18next-parser — README: https://github.com/i18next/i18next-parser (accessed 2026-07-08, T2, repository archived 2026-02-22) — documents the dynamic-key blind spot in static extraction.
108
+ - knip: https://knip.dev (accessed 2026-07-08, T2, current) — dynamic references as the static-reachability failure class.
109
+ - Betterer: https://phenomnomnominal.github.io/betterer (accessed 2026-07-08, T2, current) — baseline snapshot, fail-on-worse, shrink-on-better.
@@ -83,7 +83,7 @@ The OpenAPI / AsyncAPI / GraphQL SDL `info.version` MUST be aligned to the relea
83
83
 
84
84
  On stable endpoints:
85
85
 
86
- - **Additive schema** — new fields are optional; default values are documented; consumers ignore unknown fields. Removing a field is breaking; renaming is breaking.
86
+ - **Additive schema** — new fields are optional; default values are documented; consumers ignore unknown fields. Removing a field is breaking; renaming is breaking. Remedy: façade contract-hold (`rules/hatch3r-contract-census.md`) — keep the key emitted, hard-null it behind the façade, delete only at the contract phase.
87
87
  - **Deprecation header (RFC 9745)** — emit `Deprecation: @1735689600` (Unix-time) or `Deprecation: Tue, 20 May 2025 00:00:00 GMT` (IMF-fixdate) on retiring endpoints.
88
88
  - **Sunset header (RFC 8594)** — emit `Sunset: Tue, 31 Dec 2025 23:59:59 GMT` (IMF-fixdate only) on retiring endpoints. Sunset date MUST be later than Deprecation date.
89
89
  - **Migration link** — emit `Link: <https://api.example.com/docs/migration>; rel="deprecation"` + `Link: <…>; rel="sunset"`.
@@ -78,7 +78,7 @@ The OpenAPI / AsyncAPI / GraphQL SDL `info.version` MUST be aligned to the relea
78
78
 
79
79
  On stable endpoints:
80
80
 
81
- - **Additive schema** — new fields are optional; default values are documented; consumers ignore unknown fields. Removing a field is breaking; renaming is breaking.
81
+ - **Additive schema** — new fields are optional; default values are documented; consumers ignore unknown fields. Removing a field is breaking; renaming is breaking. Remedy: façade contract-hold (`rules/hatch3r-contract-census.md`) — keep the key emitted, hard-null it behind the façade, delete only at the contract phase.
82
82
  - **Deprecation header (RFC 9745)** — emit `Deprecation: @1735689600` (Unix-time) or `Deprecation: Tue, 20 May 2025 00:00:00 GMT` (IMF-fixdate) on retiring endpoints.
83
83
  - **Sunset header (RFC 8594)** — emit `Sunset: Tue, 31 Dec 2025 23:59:59 GMT` (IMF-fixdate only) on retiring endpoints. Sunset date MUST be later than Deprecation date.
84
84
  - **Migration link** — emit `Link: <https://api.example.com/docs/migration>; rel="deprecation"` + `Link: <…>; rel="sunset"`.
@@ -0,0 +1,129 @@
1
+ ---
2
+ id: hatch3r-findings-ledger
3
+ type: rule
4
+ description: Write-ahead findings ledger — every review-loop finding is registered on disk before fix dispatch, folds to a terminal disposition at run exit, and survives interruption; no finding ends a run pending.
5
+ tags: [orchestration, review, floor:protocol]
6
+ precedence: high
7
+ scope: always
8
+ ---
9
+ # hatch3r Findings Ledger
10
+
11
+ **Pillars:** P2 (Scientific & Practical Quality), P8 (Clarification & Fan-out Discipline)
12
+
13
+ This rule closes one failure class: review-loop findings that are registered in chat but never implemented — Suggestions dropped on clean exits, findings lost at loop exhaustion, sub-agent death, or context compaction, and cross-iteration finding identity reduced to a bare unresolved-count. The fix is a write-ahead ledger: every finding is persisted to disk BEFORE the fixer that would address it is dispatched — the ordering Temporal applies to workflow commands (persisted before execution; `docs.temporal.io/encyclopedia/event-history`, accessed 2026-07-08) — and disk state, not chat context, is the recovery source after interruption (durable file/JSON state over context, per Anthropic's long-running-agent harness guidance; `anthropic.com/engineering/effective-harnesses-for-long-running-agents`, accessed 2026-07-08). At run exit every row folds to exactly one terminal disposition; no finding ends a run pending.
14
+
15
+ ## Store & Format
16
+
17
+ One JSONL file per run: `.hatch3r/findings/<YYYY-MM-DD>-<command>-<run8>.jsonl`, where `<run8>` is the first 8 hex chars of the run's `correlation_id` (`rules/hatch3r-agent-orchestration.md` → Correlation ID) and `<command>` is the invoking command's short name — the `hatch3r-` prefix dropped (e.g. `workflow`, `pr-resolve`). Append-only; atomic append via the `src/merge/safeWrite.ts` pattern (temp+rename then concat) — the same convention as `.hatch3r/bypass-log.jsonl`.
18
+
19
+ - **Full-row snapshots.** Every append is a complete row, never a partial patch. **Fold rule: last line per `finding_id` wins.** Any reader reconstructs current state by folding the file top to bottom.
20
+ - **Closure marker.** A final marker row with `"finding_id":"run-exit"` closes a file. Closed = last line is `run-exit` AND the fold is all-terminal. The marker row carries `ts`, `run_id`, `command`, `iteration`, and `finding_id` only.
21
+ - **Committed, not gitignored.** `.hatch3r/findings/` gets no gitignore entry — matching the `.hatch3r/review-findings/` + `todo.md` team-memory precedent — so open findings are visible in the PR diff. Degradation: in a repo with a blanket `.hatch3r/` ignore, interruption-proofing stays (same machine) but team visibility is lost.
22
+
23
+ Row schema (JSON keys):
24
+
25
+ | Key | Value |
26
+ |---|---|
27
+ | `ts` | ISO-8601 UTC append time |
28
+ | `run_id` | `<run8>` |
29
+ | `command` | the filename's `<command>` token |
30
+ | `iteration` | review-loop iteration number at append time |
31
+ | `finding_id` | `<run8>-F<seq>` — see Finding IDs |
32
+ | `severity` | `Critical \| Warning \| Suggestion` |
33
+ | `source` | `reviewer \| specialist \| edge-case-ledger \| pr-comment \| spec-review` |
34
+ | `file` | `path:line`, or `issue#N` for board-scoped findings |
35
+ | `summary` | ≤200 chars |
36
+ | `disposition` | `registered` → `targeted \| deferred \| declined \| accepted-risk \| escalated \| already-resolved \| surfaced` |
37
+ | `status` | `pending` → `in-fix` → `done \| failed \| never-attempted` |
38
+ | `closure_ref` | commit SHA, fixer `Findings addressed` quote, todo.md anchor, quoted user reply ≤100 chars, or `review-findings/<id>` |
39
+
40
+ `status: done` with `closure_ref: null` is ILLEGAL — closure always names its evidence.
41
+
42
+ ## Finding IDs
43
+
44
+ `finding_id` = `<run8>-F<seq>`; the orchestrator assigns `<seq>` in registration order (F1, F2, …). The reviewer is stateless, so ID continuity is the orchestrator's job: it passes the prior iteration's findings table (`finding_id`, file, summary, status) in every re-review prompt. The reviewer's severity tables carry an `ID` column — reuse the supplied ID for a persisting finding; write `new` for a first appearance (the orchestrator assigns the next `F<seq>`) — plus a `Resolved since last iteration: <ids | none>` line below the tables. Identity heuristic when uncertain: same file + same defect class = same ID. This contract is mirrored in `agents/hatch3r-reviewer.md` → Finding IDs; keep the two aligned when either changes.
45
+
46
+ ## Write Points
47
+
48
+ | Point | Trigger | Appends |
49
+ |---|---|---|
50
+ | **W1 write-ahead** | reviewer verdict parsed, BEFORE any fixer dispatch — every iteration | new Critical/Warning rows as `targeted`/`pending`; new Suggestions as `registered`/`pending`; one `registered`/`pending` Warning row per reviewer `deferred:` partial-review remainder (summary = the unreviewed item), so unreviewed surfaces survive the session |
51
+ | **W2 per-iteration reconciliation** | after fixer return AND re-review | fixed-and-confirmed rows flip to `done` + `closure_ref`; attempted-but-still-failing rows to `failed`; rows the re-review reports resolved without this run's fix to `already-resolved` |
52
+ | **W3 loop-exit reconciliation** | every exit path: clean, max-iterations, DESIGN_OBJECTION, user abort | resolve every non-terminal row to a legal terminal (Run-Exit Invariant), then append the `run-exit` marker row |
53
+ | **W4 sub-agent-death flush** | the retry/BLOCKED_OTHER path of sub-agent-failure handling (`rules/hatch3r-agent-orchestration.md`) | flush in-flight rows — `in-fix` → `failed`, reason noted in `closure_ref` — before the retry and again before the ASK; a retry that lands re-flips the row at W2 (last line wins) |
54
+ | **W5 Suggestion terminalization** | at W3 | every Suggestion row goes terminal — grammar in Suggestion Handling |
55
+
56
+ At fixer dispatch the orchestrator appends each targeted row's `pending` → `in-fix` snapshot — the in-flight state W4 flushes. Run-start side task, with W1's first append: create `.hatch3r/findings/` when absent, then run the Hygiene prune.
57
+
58
+ ## Run-Exit Invariant
59
+
60
+ After W3, zero rows fold to `status: pending` or `in-fix`, and zero rows fold to `disposition: registered`. Every folded row lands on one of the legal terminals:
61
+
62
+ - (a) `targeted` + `done` + `closure_ref`
63
+ - (b) `already-resolved`
64
+ - (c) `deferred` — with a todo.md anchor in `closure_ref`
65
+ - (d) `declined` or `accepted-risk` — user-attested only: each requires a quoted user reply in `closure_ref`; an autonomous run cannot produce them (`agents/shared/user-question-protocol.md` → Unattested product decision)
66
+ - (e) `escalated` — with an open ASK recorded; legal only when the run's own status is not SUCCESS (unattended runs take this path and exit PARTIAL)
67
+ - (f) `surfaced` — Suggestion severity only; the ID appears on the recap's `Open findings:` line
68
+
69
+ A Critical/Warning finding that cannot reach (a)–(d) forces an ASK. Terminal `status` pairs mechanically with the outcome: `done` = this run's fix landed (`closure_ref` required); `failed` = attempted and not landed (never a resting terminal alone — the disposition must still reach (c)–(e)); `never-attempted` = closed without a fix attempt this run (`deferred`, `declined`, `accepted-risk`, `escalated`, `surfaced`, `already-resolved`).
70
+
71
+ ## Suggestion Handling
72
+
73
+ Suggestions register once, at first appearance (W1, `registered`/`pending`); a reviewer repeating one in a later iteration causes no re-append — unless its severity changed, which appends a new full-row snapshot (same `finding_id`, new severity). At W3 every Suggestion row goes terminal (W5) via exactly one of:
74
+
75
+ - `surfaced` — the ID is printed on the Iteration Summary's `Open findings:` line (`rules/hatch3r-iteration-summary.md` → Exception Lines). This is the never-silently-dropped guarantee: a clean exit still names every unaddressed Suggestion. Unattended default.
76
+ - `deferred` — the user chose deferral; `closure_ref` = the todo.md anchor.
77
+ - `declined` — the user declined; `closure_ref` = the quoted user reply (≤100 chars).
78
+
79
+ ## Session-Start Surfacing
80
+
81
+ `agents/hatch3r-handoff-loader.md` owns this step. At session start the loader scans `.hatch3r/findings/*.jsonl` (skips silently when the directory is absent), skips closed files, folds each remaining file, and lists open rows — non-terminal rows plus rows terminal as `escalated`/`surfaced` — as `id · severity · file · summary · disposition`. A ledger file older than 14 days that still folds open rows is flagged stale, with three closure options: close as declined with reason "stale", re-defer to todo.md, or resume the work. This scan is the self-healing detector for runs that died before W3: whatever a crash left open re-surfaces next session instead of staying lost.
82
+
83
+ ## Handoff Integration
84
+
85
+ No handoff-schema change. `agents/hatch3r-handoff-preparer.md` folds the active run's ledger in its collect-state step, and that fold is authoritative for the handoff's Work Remaining `Open findings` bullet at composition time — the handoff inherits open findings from the same fold every other consumer reads. The recap's `Open findings:` exception line (`rules/hatch3r-iteration-summary.md` → Exception Lines) is cross-check provenance, not the source: when it agrees with the fold the preparer copies it verbatim; when it is absent or disagrees (stale recap, mid-session interrupt) the fold wins and the bullet notes `(fold-derived; last recap stale or absent)`; zero open rows ⇒ no bullet. Composition procedure lives in `agents/hatch3r-handoff-preparer.md`, mirrored in `skills/hatch3r-handoff-prepare/SKILL.md` — keep the three aligned when any changes.
86
+
87
+ ## Store Boundaries
88
+
89
+ Three stores, three jobs. Writes flow ledger-outward — the ledger is written first; the other stores derive from it, never the reverse.
90
+
91
+ | Store | Role |
92
+ |---|---|
93
+ | `.hatch3r/findings/` (this ledger) | Execution-lifecycle write-ahead log. Run-scoped; carries ALL severities; the source of truth for "is anything open?" |
94
+ | `.hatch3r/review-findings/` | Cross-PR reviewer memory of RESOLVED Critical/Warning findings. Its post-loop append DERIVES from the ledger fold — each entry cites its `finding_id` — never the reverse |
95
+ | `todo.md` | Human follow-up backlog. A `deferred` row's `closure_ref` is the todo.md anchor; the todo bullet carries `[ledger: <finding_id>]`; after deferral the board owns the item (the ledger row is terminal) |
96
+
97
+ ## Hygiene
98
+
99
+ - The owning orchestrator closes its own file at W3. A dead run's file is never closed by another run — the loader surfaces it (Session-Start Surfacing) for a user decision.
100
+ - Prune at any run's W1: when more than 20 closed files exist, or any closed file is older than 30 days, delete the oldest closed files down to those bounds. Git history preserves pruned files; open files are never pruned.
101
+
102
+ ## Worked Example
103
+
104
+ A 2-iteration `hatch3r-workflow` run (`run8` = `a3f81c2e`, file `.hatch3r/findings/2026-07-08-workflow-a3f81c2e.jsonl`): iteration 1 registers one Critical, one Warning, one Suggestion; the fixer takes the first two; iteration 2's re-review confirms both resolved; the Suggestion terminalizes as `surfaced`, so the recap prints `Open findings: a3f81c2e-F3 Suggestion — surfaced`.
105
+
106
+ ```jsonl
107
+ {"ts":"2026-07-08T14:02:11Z","run_id":"a3f81c2e","command":"workflow","iteration":1,"finding_id":"a3f81c2e-F1","severity":"Critical","source":"reviewer","file":"src/auth/session.ts:88","summary":"Session token compared with non-constant-time equality","disposition":"targeted","status":"pending","closure_ref":null}
108
+ {"ts":"2026-07-08T14:02:11Z","run_id":"a3f81c2e","command":"workflow","iteration":1,"finding_id":"a3f81c2e-F2","severity":"Warning","source":"reviewer","file":"src/auth/session.ts:97","summary":"New expiry branch has no test","disposition":"targeted","status":"pending","closure_ref":null}
109
+ {"ts":"2026-07-08T14:02:11Z","run_id":"a3f81c2e","command":"workflow","iteration":1,"finding_id":"a3f81c2e-F3","severity":"Suggestion","source":"reviewer","file":"src/auth/session.ts:41","summary":"Extract shared cookie-parse helper","disposition":"registered","status":"pending","closure_ref":null}
110
+ {"ts":"2026-07-08T14:02:38Z","run_id":"a3f81c2e","command":"workflow","iteration":1,"finding_id":"a3f81c2e-F1","severity":"Critical","source":"reviewer","file":"src/auth/session.ts:88","summary":"Session token compared with non-constant-time equality","disposition":"targeted","status":"in-fix","closure_ref":null}
111
+ {"ts":"2026-07-08T14:02:38Z","run_id":"a3f81c2e","command":"workflow","iteration":1,"finding_id":"a3f81c2e-F2","severity":"Warning","source":"reviewer","file":"src/auth/session.ts:97","summary":"New expiry branch has no test","disposition":"targeted","status":"in-fix","closure_ref":null}
112
+ {"ts":"2026-07-08T14:11:02Z","run_id":"a3f81c2e","command":"workflow","iteration":2,"finding_id":"a3f81c2e-F1","severity":"Critical","source":"reviewer","file":"src/auth/session.ts:88","summary":"Session token compared with non-constant-time equality","disposition":"targeted","status":"done","closure_ref":"fixer: Findings addressed — a3f81c2e-F1 timingSafeEqual"}
113
+ {"ts":"2026-07-08T14:11:02Z","run_id":"a3f81c2e","command":"workflow","iteration":2,"finding_id":"a3f81c2e-F2","severity":"Warning","source":"reviewer","file":"src/auth/session.ts:97","summary":"New expiry branch has no test","disposition":"targeted","status":"done","closure_ref":"fixer: Findings addressed — a3f81c2e-F2 expiry-branch test added"}
114
+ {"ts":"2026-07-08T14:11:30Z","run_id":"a3f81c2e","command":"workflow","iteration":2,"finding_id":"a3f81c2e-F3","severity":"Suggestion","source":"reviewer","file":"src/auth/session.ts:41","summary":"Extract shared cookie-parse helper","disposition":"surfaced","status":"never-attempted","closure_ref":null}
115
+ {"ts":"2026-07-08T14:11:31Z","run_id":"a3f81c2e","command":"workflow","iteration":2,"finding_id":"run-exit"}
116
+ ```
117
+
118
+ ## Pillar Service
119
+
120
+ - P2 — findings become falsifiable disk records with evidence-bearing closure (`closure_ref` names a commit, quote, or anchor), and the fold answers "is anything open?" from disk rather than from memory.
121
+ - P8 — `declined`/`accepted-risk` require quoted user attestation, and an open Critical/Warning forces an ASK — autonomy never absorbs a product decision silently.
122
+
123
+ ## References
124
+
125
+ - Anthropic — Effective harnesses for long-running agents: durable file/JSON state over context; session-start re-read; session-end clean-state sweep. `anthropic.com/engineering/effective-harnesses-for-long-running-agents` (accessed 2026-07-08; T1, published 2025-11-26)
126
+ - Anthropic — Harness design for long-running application development: success criteria written before implementation; files as the inter-agent channel. `anthropic.com/engineering/harness-design-long-running-apps` (accessed 2026-07-08; T1, published 2026-03-24)
127
+ - OASIS — SARIF 2.1.0 Errata 01: `result.kind` open-by-default; `suppression.status: underReview` is never a final state. `docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html` (accessed 2026-07-08; T1, canonical spec, 2023-08-28)
128
+ - DefectDojo — Finding status definitions: risk-accepted carries a revisit pointer; dispositions carry cross-round semantics. `docs.defectdojo.com/triage_findings/findings_workflows/finding_status_definitions/` (accessed 2026-07-08; T2, current)
129
+ - Temporal — Event history / workflow execution: commands persisted before execution; a closed execution has exactly one terminal status. `docs.temporal.io/encyclopedia/event-history` (accessed 2026-07-08; T1, current)
@@ -0,0 +1,129 @@
1
+ ---
2
+ id: hatch3r-findings-ledger
3
+ type: rule
4
+ description: Write-ahead findings ledger — every review-loop finding is registered on disk before fix dispatch, folds to a terminal disposition at run exit, and survives interruption; no finding ends a run pending.
5
+ tags: [orchestration, review, floor:protocol]
6
+ precedence: high
7
+ alwaysApply: true
8
+ ---
9
+ # hatch3r Findings Ledger
10
+
11
+ **Pillars:** P2 (Scientific & Practical Quality), P8 (Clarification & Fan-out Discipline)
12
+
13
+ This rule closes one failure class: review-loop findings that are registered in chat but never implemented — Suggestions dropped on clean exits, findings lost at loop exhaustion, sub-agent death, or context compaction, and cross-iteration finding identity reduced to a bare unresolved-count. The fix is a write-ahead ledger: every finding is persisted to disk BEFORE the fixer that would address it is dispatched — the ordering Temporal applies to workflow commands (persisted before execution; `docs.temporal.io/encyclopedia/event-history`, accessed 2026-07-08) — and disk state, not chat context, is the recovery source after interruption (durable file/JSON state over context, per Anthropic's long-running-agent harness guidance; `anthropic.com/engineering/effective-harnesses-for-long-running-agents`, accessed 2026-07-08). At run exit every row folds to exactly one terminal disposition; no finding ends a run pending.
14
+
15
+ ## Store & Format
16
+
17
+ One JSONL file per run: `.hatch3r/findings/<YYYY-MM-DD>-<command>-<run8>.jsonl`, where `<run8>` is the first 8 hex chars of the run's `correlation_id` (`rules/hatch3r-agent-orchestration.md` → Correlation ID) and `<command>` is the invoking command's short name — the `hatch3r-` prefix dropped (e.g. `workflow`, `pr-resolve`). Append-only; atomic append via the `src/merge/safeWrite.ts` pattern (temp+rename then concat) — the same convention as `.hatch3r/bypass-log.jsonl`.
18
+
19
+ - **Full-row snapshots.** Every append is a complete row, never a partial patch. **Fold rule: last line per `finding_id` wins.** Any reader reconstructs current state by folding the file top to bottom.
20
+ - **Closure marker.** A final marker row with `"finding_id":"run-exit"` closes a file. Closed = last line is `run-exit` AND the fold is all-terminal. The marker row carries `ts`, `run_id`, `command`, `iteration`, and `finding_id` only.
21
+ - **Committed, not gitignored.** `.hatch3r/findings/` gets no gitignore entry — matching the `.hatch3r/review-findings/` + `todo.md` team-memory precedent — so open findings are visible in the PR diff. Degradation: in a repo with a blanket `.hatch3r/` ignore, interruption-proofing stays (same machine) but team visibility is lost.
22
+
23
+ Row schema (JSON keys):
24
+
25
+ | Key | Value |
26
+ |---|---|
27
+ | `ts` | ISO-8601 UTC append time |
28
+ | `run_id` | `<run8>` |
29
+ | `command` | the filename's `<command>` token |
30
+ | `iteration` | review-loop iteration number at append time |
31
+ | `finding_id` | `<run8>-F<seq>` — see Finding IDs |
32
+ | `severity` | `Critical \| Warning \| Suggestion` |
33
+ | `source` | `reviewer \| specialist \| edge-case-ledger \| pr-comment \| spec-review` |
34
+ | `file` | `path:line`, or `issue#N` for board-scoped findings |
35
+ | `summary` | ≤200 chars |
36
+ | `disposition` | `registered` → `targeted \| deferred \| declined \| accepted-risk \| escalated \| already-resolved \| surfaced` |
37
+ | `status` | `pending` → `in-fix` → `done \| failed \| never-attempted` |
38
+ | `closure_ref` | commit SHA, fixer `Findings addressed` quote, todo.md anchor, quoted user reply ≤100 chars, or `review-findings/<id>` |
39
+
40
+ `status: done` with `closure_ref: null` is ILLEGAL — closure always names its evidence.
41
+
42
+ ## Finding IDs
43
+
44
+ `finding_id` = `<run8>-F<seq>`; the orchestrator assigns `<seq>` in registration order (F1, F2, …). The reviewer is stateless, so ID continuity is the orchestrator's job: it passes the prior iteration's findings table (`finding_id`, file, summary, status) in every re-review prompt. The reviewer's severity tables carry an `ID` column — reuse the supplied ID for a persisting finding; write `new` for a first appearance (the orchestrator assigns the next `F<seq>`) — plus a `Resolved since last iteration: <ids | none>` line below the tables. Identity heuristic when uncertain: same file + same defect class = same ID. This contract is mirrored in `agents/hatch3r-reviewer.md` → Finding IDs; keep the two aligned when either changes.
45
+
46
+ ## Write Points
47
+
48
+ | Point | Trigger | Appends |
49
+ |---|---|---|
50
+ | **W1 write-ahead** | reviewer verdict parsed, BEFORE any fixer dispatch — every iteration | new Critical/Warning rows as `targeted`/`pending`; new Suggestions as `registered`/`pending`; one `registered`/`pending` Warning row per reviewer `deferred:` partial-review remainder (summary = the unreviewed item), so unreviewed surfaces survive the session |
51
+ | **W2 per-iteration reconciliation** | after fixer return AND re-review | fixed-and-confirmed rows flip to `done` + `closure_ref`; attempted-but-still-failing rows to `failed`; rows the re-review reports resolved without this run's fix to `already-resolved` |
52
+ | **W3 loop-exit reconciliation** | every exit path: clean, max-iterations, DESIGN_OBJECTION, user abort | resolve every non-terminal row to a legal terminal (Run-Exit Invariant), then append the `run-exit` marker row |
53
+ | **W4 sub-agent-death flush** | the retry/BLOCKED_OTHER path of sub-agent-failure handling (`rules/hatch3r-agent-orchestration.md`) | flush in-flight rows — `in-fix` → `failed`, reason noted in `closure_ref` — before the retry and again before the ASK; a retry that lands re-flips the row at W2 (last line wins) |
54
+ | **W5 Suggestion terminalization** | at W3 | every Suggestion row goes terminal — grammar in Suggestion Handling |
55
+
56
+ At fixer dispatch the orchestrator appends each targeted row's `pending` → `in-fix` snapshot — the in-flight state W4 flushes. Run-start side task, with W1's first append: create `.hatch3r/findings/` when absent, then run the Hygiene prune.
57
+
58
+ ## Run-Exit Invariant
59
+
60
+ After W3, zero rows fold to `status: pending` or `in-fix`, and zero rows fold to `disposition: registered`. Every folded row lands on one of the legal terminals:
61
+
62
+ - (a) `targeted` + `done` + `closure_ref`
63
+ - (b) `already-resolved`
64
+ - (c) `deferred` — with a todo.md anchor in `closure_ref`
65
+ - (d) `declined` or `accepted-risk` — user-attested only: each requires a quoted user reply in `closure_ref`; an autonomous run cannot produce them (`agents/shared/user-question-protocol.md` → Unattested product decision)
66
+ - (e) `escalated` — with an open ASK recorded; legal only when the run's own status is not SUCCESS (unattended runs take this path and exit PARTIAL)
67
+ - (f) `surfaced` — Suggestion severity only; the ID appears on the recap's `Open findings:` line
68
+
69
+ A Critical/Warning finding that cannot reach (a)–(d) forces an ASK. Terminal `status` pairs mechanically with the outcome: `done` = this run's fix landed (`closure_ref` required); `failed` = attempted and not landed (never a resting terminal alone — the disposition must still reach (c)–(e)); `never-attempted` = closed without a fix attempt this run (`deferred`, `declined`, `accepted-risk`, `escalated`, `surfaced`, `already-resolved`).
70
+
71
+ ## Suggestion Handling
72
+
73
+ Suggestions register once, at first appearance (W1, `registered`/`pending`); a reviewer repeating one in a later iteration causes no re-append — unless its severity changed, which appends a new full-row snapshot (same `finding_id`, new severity). At W3 every Suggestion row goes terminal (W5) via exactly one of:
74
+
75
+ - `surfaced` — the ID is printed on the Iteration Summary's `Open findings:` line (`rules/hatch3r-iteration-summary.md` → Exception Lines). This is the never-silently-dropped guarantee: a clean exit still names every unaddressed Suggestion. Unattended default.
76
+ - `deferred` — the user chose deferral; `closure_ref` = the todo.md anchor.
77
+ - `declined` — the user declined; `closure_ref` = the quoted user reply (≤100 chars).
78
+
79
+ ## Session-Start Surfacing
80
+
81
+ `agents/hatch3r-handoff-loader.md` owns this step. At session start the loader scans `.hatch3r/findings/*.jsonl` (skips silently when the directory is absent), skips closed files, folds each remaining file, and lists open rows — non-terminal rows plus rows terminal as `escalated`/`surfaced` — as `id · severity · file · summary · disposition`. A ledger file older than 14 days that still folds open rows is flagged stale, with three closure options: close as declined with reason "stale", re-defer to todo.md, or resume the work. This scan is the self-healing detector for runs that died before W3: whatever a crash left open re-surfaces next session instead of staying lost.
82
+
83
+ ## Handoff Integration
84
+
85
+ No handoff-schema change. `agents/hatch3r-handoff-preparer.md` folds the active run's ledger in its collect-state step, and that fold is authoritative for the handoff's Work Remaining `Open findings` bullet at composition time — the handoff inherits open findings from the same fold every other consumer reads. The recap's `Open findings:` exception line (`rules/hatch3r-iteration-summary.md` → Exception Lines) is cross-check provenance, not the source: when it agrees with the fold the preparer copies it verbatim; when it is absent or disagrees (stale recap, mid-session interrupt) the fold wins and the bullet notes `(fold-derived; last recap stale or absent)`; zero open rows ⇒ no bullet. Composition procedure lives in `agents/hatch3r-handoff-preparer.md`, mirrored in `skills/hatch3r-handoff-prepare/SKILL.md` — keep the three aligned when any changes.
86
+
87
+ ## Store Boundaries
88
+
89
+ Three stores, three jobs. Writes flow ledger-outward — the ledger is written first; the other stores derive from it, never the reverse.
90
+
91
+ | Store | Role |
92
+ |---|---|
93
+ | `.hatch3r/findings/` (this ledger) | Execution-lifecycle write-ahead log. Run-scoped; carries ALL severities; the source of truth for "is anything open?" |
94
+ | `.hatch3r/review-findings/` | Cross-PR reviewer memory of RESOLVED Critical/Warning findings. Its post-loop append DERIVES from the ledger fold — each entry cites its `finding_id` — never the reverse |
95
+ | `todo.md` | Human follow-up backlog. A `deferred` row's `closure_ref` is the todo.md anchor; the todo bullet carries `[ledger: <finding_id>]`; after deferral the board owns the item (the ledger row is terminal) |
96
+
97
+ ## Hygiene
98
+
99
+ - The owning orchestrator closes its own file at W3. A dead run's file is never closed by another run — the loader surfaces it (Session-Start Surfacing) for a user decision.
100
+ - Prune at any run's W1: when more than 20 closed files exist, or any closed file is older than 30 days, delete the oldest closed files down to those bounds. Git history preserves pruned files; open files are never pruned.
101
+
102
+ ## Worked Example
103
+
104
+ A 2-iteration `hatch3r-workflow` run (`run8` = `a3f81c2e`, file `.hatch3r/findings/2026-07-08-workflow-a3f81c2e.jsonl`): iteration 1 registers one Critical, one Warning, one Suggestion; the fixer takes the first two; iteration 2's re-review confirms both resolved; the Suggestion terminalizes as `surfaced`, so the recap prints `Open findings: a3f81c2e-F3 Suggestion — surfaced`.
105
+
106
+ ```jsonl
107
+ {"ts":"2026-07-08T14:02:11Z","run_id":"a3f81c2e","command":"workflow","iteration":1,"finding_id":"a3f81c2e-F1","severity":"Critical","source":"reviewer","file":"src/auth/session.ts:88","summary":"Session token compared with non-constant-time equality","disposition":"targeted","status":"pending","closure_ref":null}
108
+ {"ts":"2026-07-08T14:02:11Z","run_id":"a3f81c2e","command":"workflow","iteration":1,"finding_id":"a3f81c2e-F2","severity":"Warning","source":"reviewer","file":"src/auth/session.ts:97","summary":"New expiry branch has no test","disposition":"targeted","status":"pending","closure_ref":null}
109
+ {"ts":"2026-07-08T14:02:11Z","run_id":"a3f81c2e","command":"workflow","iteration":1,"finding_id":"a3f81c2e-F3","severity":"Suggestion","source":"reviewer","file":"src/auth/session.ts:41","summary":"Extract shared cookie-parse helper","disposition":"registered","status":"pending","closure_ref":null}
110
+ {"ts":"2026-07-08T14:02:38Z","run_id":"a3f81c2e","command":"workflow","iteration":1,"finding_id":"a3f81c2e-F1","severity":"Critical","source":"reviewer","file":"src/auth/session.ts:88","summary":"Session token compared with non-constant-time equality","disposition":"targeted","status":"in-fix","closure_ref":null}
111
+ {"ts":"2026-07-08T14:02:38Z","run_id":"a3f81c2e","command":"workflow","iteration":1,"finding_id":"a3f81c2e-F2","severity":"Warning","source":"reviewer","file":"src/auth/session.ts:97","summary":"New expiry branch has no test","disposition":"targeted","status":"in-fix","closure_ref":null}
112
+ {"ts":"2026-07-08T14:11:02Z","run_id":"a3f81c2e","command":"workflow","iteration":2,"finding_id":"a3f81c2e-F1","severity":"Critical","source":"reviewer","file":"src/auth/session.ts:88","summary":"Session token compared with non-constant-time equality","disposition":"targeted","status":"done","closure_ref":"fixer: Findings addressed — a3f81c2e-F1 timingSafeEqual"}
113
+ {"ts":"2026-07-08T14:11:02Z","run_id":"a3f81c2e","command":"workflow","iteration":2,"finding_id":"a3f81c2e-F2","severity":"Warning","source":"reviewer","file":"src/auth/session.ts:97","summary":"New expiry branch has no test","disposition":"targeted","status":"done","closure_ref":"fixer: Findings addressed — a3f81c2e-F2 expiry-branch test added"}
114
+ {"ts":"2026-07-08T14:11:30Z","run_id":"a3f81c2e","command":"workflow","iteration":2,"finding_id":"a3f81c2e-F3","severity":"Suggestion","source":"reviewer","file":"src/auth/session.ts:41","summary":"Extract shared cookie-parse helper","disposition":"surfaced","status":"never-attempted","closure_ref":null}
115
+ {"ts":"2026-07-08T14:11:31Z","run_id":"a3f81c2e","command":"workflow","iteration":2,"finding_id":"run-exit"}
116
+ ```
117
+
118
+ ## Pillar Service
119
+
120
+ - P2 — findings become falsifiable disk records with evidence-bearing closure (`closure_ref` names a commit, quote, or anchor), and the fold answers "is anything open?" from disk rather than from memory.
121
+ - P8 — `declined`/`accepted-risk` require quoted user attestation, and an open Critical/Warning forces an ASK — autonomy never absorbs a product decision silently.
122
+
123
+ ## References
124
+
125
+ - Anthropic — Effective harnesses for long-running agents: durable file/JSON state over context; session-start re-read; session-end clean-state sweep. `anthropic.com/engineering/effective-harnesses-for-long-running-agents` (accessed 2026-07-08; T1, published 2025-11-26)
126
+ - Anthropic — Harness design for long-running application development: success criteria written before implementation; files as the inter-agent channel. `anthropic.com/engineering/harness-design-long-running-apps` (accessed 2026-07-08; T1, published 2026-03-24)
127
+ - OASIS — SARIF 2.1.0 Errata 01: `result.kind` open-by-default; `suppression.status: underReview` is never a final state. `docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html` (accessed 2026-07-08; T1, canonical spec, 2023-08-28)
128
+ - DefectDojo — Finding status definitions: risk-accepted carries a revisit pointer; dispositions carry cross-round semantics. `docs.defectdojo.com/triage_findings/findings_workflows/finding_status_definitions/` (accessed 2026-07-08; T2, current)
129
+ - Temporal — Event history / workflow execution: commands persisted before execution; a closed execution has exactly one terminal status. `docs.temporal.io/encyclopedia/event-history` (accessed 2026-07-08; T1, current)
@@ -50,6 +50,6 @@ Effect: self-assessed confidence becomes a track-record signal rather than a sin
50
50
  ## Cross-references
51
51
 
52
52
  - Body sections schema: `.hatch3r/handoffs/README.md`
53
- - Iteration Summary contract (populates Work Done / Work Remaining / Blockers): `rules/hatch3r-iteration-summary.md`
53
+ - Iteration Summary recap contract (Work Done recap outcome + files facet; Work Remaining `Not done:` line, absent ⇒ `None — full scope completed`; Blockers ← `Blockers:` line, absent ⇒ `None`): `rules/hatch3r-iteration-summary.md`
54
54
  - Injection-pattern catalog: `agents/shared/injection-patterns.md` Section B
55
55
  - Quality charter (confidence levels): `agents/shared/quality-charter.md`
@@ -45,6 +45,6 @@ Effect: self-assessed confidence becomes a track-record signal rather than a sin
45
45
  ## Cross-references
46
46
 
47
47
  - Body sections schema: `.hatch3r/handoffs/README.md`
48
- - Iteration Summary contract (populates Work Done / Work Remaining / Blockers): `rules/hatch3r-iteration-summary.md`
48
+ - Iteration Summary recap contract (Work Done recap outcome + files facet; Work Remaining `Not done:` line, absent ⇒ `None — full scope completed`; Blockers ← `Blockers:` line, absent ⇒ `None`): `rules/hatch3r-iteration-summary.md`
49
49
  - Injection-pattern catalog: `agents/shared/injection-patterns.md` Section B
50
50
  - Quality charter (confidence levels): `agents/shared/quality-charter.md`
@@ -27,6 +27,10 @@ cache_friendly: true
27
27
  - Never concatenate translated fragments to build sentences — each complete sentence is a single translation key.
28
28
  - Maintain a string extraction workflow (e.g., `i18next-parser`, `vue-i18n-extract`) that runs in CI to flag unused and missing keys.
29
29
 
30
+ ### Dynamic Keys
31
+
32
+ String extraction cannot see interpolated keys (`t('status.' + value)`, template-literal keys). Every dynamic-key call site either (a) has a test iterating the full value set and asserting each resolved key exists in the default locale (`i18n.te(key)` or equivalent), or (b) carries a co-located comment enumerating the concrete keys for the parser. CI extraction reports count dynamic call sites as unverified, never as passing. Verification pattern owner: `rules/hatch3r-dynamic-stack-verification.md` → Dynamic i18n Keys.
33
+
30
34
  ## RTL Support
31
35
 
32
36
  - Use CSS logical properties exclusively: `margin-inline-start` (not `margin-left`), `padding-inline-end` (not `padding-right`), `inset-inline-start` (not `left`), `text-align: start` (not `text-align: left`).
@@ -22,6 +22,10 @@ alwaysApply: false
22
22
  - Never concatenate translated fragments to build sentences — each complete sentence is a single translation key.
23
23
  - Maintain a string extraction workflow (e.g., `i18next-parser`, `vue-i18n-extract`) that runs in CI to flag unused and missing keys.
24
24
 
25
+ ### Dynamic Keys
26
+
27
+ String extraction cannot see interpolated keys (`t('status.' + value)`, template-literal keys). Every dynamic-key call site either (a) has a test iterating the full value set and asserting each resolved key exists in the default locale (`i18n.te(key)` or equivalent), or (b) carries a co-located comment enumerating the concrete keys for the parser. CI extraction reports count dynamic call sites as unverified, never as passing. Verification pattern owner: `rules/hatch3r-dynamic-stack-verification.md` → Dynamic i18n Keys.
28
+
25
29
  ## RTL Support
26
30
 
27
31
  - Use CSS logical properties exclusively: `margin-inline-start` (not `margin-left`), `padding-inline-end` (not `padding-right`), `inset-inline-start` (not `left`), `text-align: start` (not `text-align: left`).