hatch3r 2.1.1 → 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.
- package/README.md +3 -3
- package/dist/cli/index.js +77 -19
- package/dist/content/agents/hatch3r-fixer.md +20 -7
- package/dist/content/agents/hatch3r-handoff-loader.md +7 -2
- package/dist/content/agents/hatch3r-handoff-preparer.md +2 -1
- package/dist/content/agents/hatch3r-implementer.md +28 -2
- package/dist/content/agents/hatch3r-maintainability.md +2 -0
- package/dist/content/agents/hatch3r-researcher.md +1 -1
- package/dist/content/agents/hatch3r-reviewer.md +20 -12
- package/dist/content/agents/hatch3r-ui.md +1 -1
- package/dist/content/agents/hatch3r-ux.md +1 -1
- package/dist/content/agents/shared/cq-specialist-roster.md +2 -2
- package/dist/content/agents/shared/quality-charter.md +3 -2
- package/dist/content/agents/shared/quality-specialist-frame.md +1 -1
- package/dist/content/agents/shared/user-question-protocol.md +1 -0
- package/dist/content/checks/code-quality.md +11 -0
- package/dist/content/checks/security.md +5 -0
- package/dist/content/checks/testing.md +2 -0
- package/dist/content/commands/board/pickup-delegation-multi.md +18 -8
- package/dist/content/commands/board/pickup-delegation.md +5 -4
- package/dist/content/commands/hatch3r-api-spec.md +1 -1
- package/dist/content/commands/hatch3r-benchmark.md +1 -1
- package/dist/content/commands/hatch3r-board-fill.md +2 -2
- package/dist/content/commands/hatch3r-board-pickup.md +4 -3
- package/dist/content/commands/hatch3r-bug-pipeline.md +1 -1
- package/dist/content/commands/hatch3r-bug-plan.md +1 -1
- package/dist/content/commands/hatch3r-codebase-map.md +1 -1
- package/dist/content/commands/hatch3r-create.md +1 -1
- package/dist/content/commands/hatch3r-debug.md +1 -1
- package/dist/content/commands/hatch3r-design-system-create.md +254 -0
- package/dist/content/commands/hatch3r-feature-plan.md +1 -1
- package/dist/content/commands/hatch3r-handoff.md +1 -1
- package/dist/content/commands/hatch3r-healthcheck.md +1 -1
- package/dist/content/commands/hatch3r-migration-plan.md +1 -1
- package/dist/content/commands/hatch3r-onboard.md +1 -1
- package/dist/content/commands/hatch3r-pr-resolve.md +69 -29
- package/dist/content/commands/hatch3r-project-spec.md +1 -1
- package/dist/content/commands/hatch3r-quick-change.md +1 -1
- package/dist/content/commands/hatch3r-refactor-plan.md +1 -1
- package/dist/content/commands/hatch3r-release.md +1 -1
- package/dist/content/commands/hatch3r-revision.md +7 -7
- package/dist/content/commands/hatch3r-roadmap.md +1 -1
- package/dist/content/commands/hatch3r-security-audit.md +1 -1
- package/dist/content/commands/hatch3r-test-plan.md +1 -1
- package/dist/content/commands/hatch3r-workflow.md +12 -10
- package/dist/content/commands/revision/revision-quality.md +6 -5
- package/dist/content/rules/hatch3r-agent-orchestration-detail.md +2 -2
- package/dist/content/rules/hatch3r-agent-orchestration-detail.mdc +2 -2
- package/dist/content/rules/hatch3r-agent-orchestration.md +14 -14
- package/dist/content/rules/hatch3r-agent-orchestration.mdc +14 -14
- package/dist/content/rules/hatch3r-anti-duplication.md +10 -1
- package/dist/content/rules/hatch3r-anti-duplication.mdc +10 -1
- package/dist/content/rules/hatch3r-clarification-default.md +6 -5
- package/dist/content/rules/hatch3r-clarification-default.mdc +6 -5
- package/dist/content/rules/hatch3r-code-standards.md +1 -1
- package/dist/content/rules/hatch3r-code-standards.mdc +1 -1
- package/dist/content/rules/hatch3r-contract-census.md +160 -0
- package/dist/content/rules/hatch3r-contract-census.mdc +160 -0
- package/dist/content/rules/hatch3r-deep-context.md +1 -0
- package/dist/content/rules/hatch3r-deep-context.mdc +1 -0
- package/dist/content/rules/hatch3r-dynamic-stack-verification.md +114 -0
- package/dist/content/rules/hatch3r-dynamic-stack-verification.mdc +109 -0
- package/dist/content/rules/hatch3r-enhancability.md +1 -1
- package/dist/content/rules/hatch3r-enhancability.mdc +1 -1
- package/dist/content/rules/hatch3r-findings-ledger.md +129 -0
- package/dist/content/rules/hatch3r-findings-ledger.mdc +129 -0
- package/dist/content/rules/hatch3r-i18n.md +4 -0
- package/dist/content/rules/hatch3r-i18n.mdc +4 -0
- package/dist/content/rules/hatch3r-iteration-summary.md +2 -0
- package/dist/content/rules/hatch3r-iteration-summary.mdc +2 -0
- package/dist/content/rules/hatch3r-migrations.md +1 -1
- package/dist/content/rules/hatch3r-migrations.mdc +1 -1
- package/dist/content/rules/hatch3r-security-patterns.md +4 -0
- package/dist/content/rules/hatch3r-security-patterns.mdc +4 -0
- package/dist/content/rules/hatch3r-testing.md +14 -0
- package/dist/content/rules/hatch3r-testing.mdc +14 -0
- package/dist/content/skills/hatch3r-design-system-detect/SKILL.md +2 -0
- package/dist/content/skills/hatch3r-handoff-prepare/SKILL.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: hatch3r-contract-census
|
|
3
|
+
type: rule
|
|
4
|
+
description: Shared-contract discipline for brownfield changes — contract taxonomy, repo-wide consumer census as lane-exit gate, seam-owner protocol for parallel lanes, façade contract-hold on field drop/rename, value-drift census for shared constants.
|
|
5
|
+
tags: [implementation, review, orchestration, ctx:brownfield-only]
|
|
6
|
+
precedence: high
|
|
7
|
+
alwaysApply: true
|
|
8
|
+
---
|
|
9
|
+
# hatch3r Contract Census
|
|
10
|
+
|
|
11
|
+
**Pillars:** P2 (Scientific & Practical Quality), P8 (Clarification & Fan-out Discipline), CQ8 (Maintainability Quality)
|
|
12
|
+
|
|
13
|
+
## Binding Condition
|
|
14
|
+
|
|
15
|
+
This rule binds any diff that mutates a contract in the Shared-Contract Taxonomy below, in a repo where that contract has existing consumers. A change touching no taxonomy class exits with `Consumer census: N/A (no shared-contract change)`. A greenfield-new contract with zero consumers exits trivially — the census grep returns nothing and reports `clean`.
|
|
16
|
+
|
|
17
|
+
The failure class this rule closes: parallel file-disjoint lanes silently breaking a shared runtime contract — a renamed persisted collection, a changed store symbol, a dropped wire field — that no quality gate and, in a no-static-types stack, no compiler checks. File disjointness is not contract disjointness: two lanes whose diffs share zero files still collide when both touch the same contract.
|
|
18
|
+
|
|
19
|
+
## Shared-Contract Taxonomy
|
|
20
|
+
|
|
21
|
+
Seven contract classes. Each line states why file-disjointness does not protect it.
|
|
22
|
+
|
|
23
|
+
- **(a) Exported symbol** — function, class, or store: its name, signature, or subpath. Importers live in other files by definition. (researcher: `api_signature` for signature changes, `public_interface` for name/visibility/subpath changes)
|
|
24
|
+
- **(b) Persisted collection/table/field name** — string-typed: writer and reader each spell the string independently; no compiler links them. (researcher: `data_migration`)
|
|
25
|
+
- **(c) Client↔server wire field** — a JSON key crossing the network: serializer and deserializer sit in different trees, often different languages. (researcher: `type_shape`)
|
|
26
|
+
- **(d) Event name + payload schema** — emitter and subscribers are decoupled by design; the bus hides the seam. (researcher: `event_schema`)
|
|
27
|
+
- **(e) Shared constant** — rate, default, threshold, or enum value: copies compile independently, and a stale copy computes wrong values with no error. (researcher: `type_shape` for enum members, `cli_contract` for surfaced defaults; value-only drift sits below the researcher's radar → §Value-Drift Census)
|
|
28
|
+
- **(f) CLI flag / config / env key** — the producer is code; the consumers are shell scripts, CI YAML, and dotfiles no type-checker loads. (researcher: `cli_contract`)
|
|
29
|
+
- **(g) Type or schema shape where no checker is authoritative** — plain JS, dynamic access, JSON blobs: shape agreement exists only by convention, so classes (a)-(f) all degrade to grep.
|
|
30
|
+
|
|
31
|
+
The parenthesized categories map classes (a)-(f) onto the researcher's breaking-change vocabulary — `api_signature / type_shape / event_schema / public_interface / data_migration / cli_contract` (`agents/hatch3r-researcher.md` → Full-Mode Breaking-Change Detection) — so a researcher Breaking Change Candidates row converts directly into a taxonomy class and back.
|
|
32
|
+
|
|
33
|
+
## Consumer Census
|
|
34
|
+
|
|
35
|
+
**Trigger:** any taxonomy mutation — rename, removal, re-signature, revalue, key add/drop.
|
|
36
|
+
|
|
37
|
+
**Procedure:**
|
|
38
|
+
|
|
39
|
+
1. Repo-wide grep of the OLD identifier AND the NEW identifier. Never limit the grep to the lane's `affectedFiles` — consumers live outside the lane's file set by definition.
|
|
40
|
+
2. Per-class patterns:
|
|
41
|
+
- **String contracts** (classes b, d, f) — grep the quoted forms: `'name'` and `"name"` (plus template-literal and heredoc forms where the language has them).
|
|
42
|
+
- **Wire fields** (class c) — grep BOTH the client and server trees, plus fixtures, mocks, and recorded payloads: a test fixture still asserting the old key is an unreconciled consumer.
|
|
43
|
+
- **Constants** (class e) — grep the NAME and the literal VALUE; an independent copy defines the value without the name.
|
|
44
|
+
- **Infra/config** (class f) — include non-code files: CI YAML, Dockerfiles, `.env*`, deploy manifests.
|
|
45
|
+
3. Narrowing tactics for short identifiers that over-match: qualified member access (`\.field\b`), collection-prefixed forms (`users.field`), and quoted-literal-only matches.
|
|
46
|
+
|
|
47
|
+
**Reconciled** — a consumer counts as reconciled when it is updated to the new shape in this diff, OR reads through a guard added in this diff (façade-hold null-guard), OR is justified by name. Exactly three justifications are valid:
|
|
48
|
+
|
|
49
|
+
1. Consumer owned by another lane's seam — name the lane/issue.
|
|
50
|
+
2. Dead code with a linked deletion.
|
|
51
|
+
3. Dynamic/reflective access grep cannot resolve — name the mechanism, add a runtime guard.
|
|
52
|
+
|
|
53
|
+
**Output grammar** — the census field emits exactly one of:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Consumer census: clean | reconciled(N) | N unreconciled — justification | N/A (no shared-contract change)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- `clean` — the grep found zero consumers of the old shape outside this diff.
|
|
60
|
+
- `reconciled(N)` — N consumers found; every one updated, guarded, or justified by name.
|
|
61
|
+
- `N unreconciled — justification` — N consumers not yet updated or guarded; each carries one of the three justifications above. `unreconciled` WITHOUT a named justification caps the producing agent's Status at PARTIAL — done is declared from grep evidence, not assumed (the large-scale-change discipline of *Software Engineering at Google* ch. 22).
|
|
62
|
+
- `N/A (no shared-contract change)` — the diff touches no taxonomy class.
|
|
63
|
+
|
|
64
|
+
**Spec-time seed:** when a brownfield spec exists, its Integration-Surface consumer inventory (`agents/hatch3r-brownfield-spec.md` → Integration-Surface Analysis) seeds the grep list. The lane still re-runs the grep at lane exit: parallel lanes may have added consumers since spec time.
|
|
65
|
+
|
|
66
|
+
**Where it runs:** `agents/hatch3r-implementer.md` Step 5d and `agents/hatch3r-fixer.md` Step 5b carry this census as a lane-exit gate — `Status: SUCCESS` requires census ∈ {`clean`, `reconciled(N)`, `N/A`}.
|
|
67
|
+
|
|
68
|
+
## Consumer-Scoped Review
|
|
69
|
+
|
|
70
|
+
The reviewer re-derives the census itself — a self-run grep of every changed contract's OLD and NEW identifier. The implementer's census field and the Phase-1 blast radius, when present, seed the list; neither substitutes for the reviewer's own grep.
|
|
71
|
+
|
|
72
|
+
The reviewer then OPENS and READS each consumer at its use site, both sides of every seam:
|
|
73
|
+
|
|
74
|
+
- serializer AND deserializer for a wire field;
|
|
75
|
+
- exporter AND importers for a store symbol;
|
|
76
|
+
- writer AND readers for a persisted name.
|
|
77
|
+
|
|
78
|
+
The verdict cites the captured grep output per the reviewer's Grounding rule. Severity: a consumer left reading the old shape is **Critical**; an implementer census of `unreconciled` without a named justification is **Critical**; a taxonomy diff with no census field at all is a **Warning** (protocol violation). Mirrors `agents/hatch3r-reviewer.md` item 11 (contract preservation, consumer-scoped, two-lens).
|
|
79
|
+
|
|
80
|
+
## Seam-Owner Protocol
|
|
81
|
+
|
|
82
|
+
A contract touched by ≥2 parallel lanes in the same batch gets exactly ONE owning lane: the lane whose acceptance criteria REQUIRE the mutation. The owner lands the emitter change AND all consumer reconciliation in one diff. Every peer lane's dispatch prompt gains the line:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Seam constraint: contract <X> is owned by issue #<N> this batch — consume the current shape; do not mutate it
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Two lanes that BOTH require mutating the same contract are re-leveled sequential — the later lane consumes the owner's landed shape.
|
|
89
|
+
|
|
90
|
+
**Detection — two passes:**
|
|
91
|
+
|
|
92
|
+
1. First-pass textual scan of issue bodies and linked specs at batch triage: endpoint paths, collection/table names, event names, exported symbols, shared constants named in ≥2 issues (`commands/hatch3r-board-pickup.md` Step 3 item 5).
|
|
93
|
+
2. Authoritative cross-check before dispatch: union the per-issue researcher Breaking Change Candidates tables; any contract appearing in ≥2 issues of the same level is a collision (`commands/board/pickup-delegation-multi.md` Step 6c.2 item 4).
|
|
94
|
+
|
|
95
|
+
**Violation surface:** a post-batch semantic conflict on an owned contract names the violating peer lane. Reconciliation routes to the seam owner — one diff owns the contract; the conflict is never patched in the merge.
|
|
96
|
+
|
|
97
|
+
**Normative amendment to parallel safety:** the parallel-safety condition "disjoint writes" holds at file AND contract granularity (`rules/hatch3r-agent-orchestration.md` → parallel-safety conditions point here). Two lanes are NOT disjoint when their file-disjoint diffs mutate the same shared contract.
|
|
98
|
+
|
|
99
|
+
## Façade Contract-Hold
|
|
100
|
+
|
|
101
|
+
Never delete a field out from under consumers. On drop or rename of a shared output field:
|
|
102
|
+
|
|
103
|
+
1. **Hold the key-set** — the façade keeps emitting every key it emitted before, through the compatibility window.
|
|
104
|
+
2. **Hard-null the dropped field** — key present, value `null`. Absence is a silent shape change a consumer misreads without error; an explicit null fails loudly at the value, at the consumer, on the first read.
|
|
105
|
+
3. **Reconcile every consumer to guarded reads** — null-tolerant access, census-tracked: each guarded read counts as reconciled.
|
|
106
|
+
4. **Delete the key only at the contract phase** — when the census shows zero unguarded readers. This is Fowler's ParallelChange termination criterion: contract begins only "once all usages have been migrated"; skipping contract leaves you worse than you started (martinfowler.com/bliki/ParallelChange.html, accessed 2026-07-08).
|
|
107
|
+
|
|
108
|
+
**Renames** are a drop plus an add: the new key is added, the old key is held nulled or aliased through the window, and consumers migrate key-by-key under the census.
|
|
109
|
+
|
|
110
|
+
**Lane-exit question, answered verbatim before returning:**
|
|
111
|
+
|
|
112
|
+
**"Did you delete the field, or null it behind the façade?"**
|
|
113
|
+
|
|
114
|
+
"Deleted" during the compatibility window is a gate failure — revert to the hold pattern before returning.
|
|
115
|
+
|
|
116
|
+
**Precedence carve-out:** a held nulled field and its guarded reads are NOT dead code until the contract phase. The `rules/hatch3r-code-standards.md` dead-code reference sweep runs at the contract phase, when the held field is deleted — that rule carries the matching exception.
|
|
117
|
+
|
|
118
|
+
**Boundary:** this section is the in-code analog of expand-contract for the database (`rules/hatch3r-migrations.md`) and of the API field-removal lifecycle for public endpoints (`rules/hatch3r-api-versioning.md`). Those rules own their boundaries; this section owns every contract they do not load for — internal wire fields, store symbols, event payloads inside the app. Through the window the system builds and runs at all times, the BranchByAbstraction invariant.
|
|
119
|
+
|
|
120
|
+
## Value-Drift Census
|
|
121
|
+
|
|
122
|
+
A shared constant — rate, default, threshold, enum value — consumed by ≥2 features has exactly one owning module; every reader imports it. On touching such a constant:
|
|
123
|
+
|
|
124
|
+
1. Grep the constant NAME and the literal VALUE repo-wide.
|
|
125
|
+
2. Each independently-defined copy is a drift candidate.
|
|
126
|
+
3. Remedy per copy: import from the owner, or an inline ADR comment documenting intentional divergence.
|
|
127
|
+
|
|
128
|
+
**Damage ranking — silent-wrong beats loud-broken.** A deleted import fails at build; a stale copy computes wrong values in production with no signal.
|
|
129
|
+
|
|
130
|
+
One-line literals sit below clone-scan thresholds (≥30-line block matching) — this census is name+value grep, not block matching. `rules/hatch3r-anti-duplication.md` → Value-Drift Census carries the anti-duplication-side enforcement hook.
|
|
131
|
+
|
|
132
|
+
## Ownership Boundaries
|
|
133
|
+
|
|
134
|
+
| Surface | Owning artifact |
|
|
135
|
+
|---------|-----------------|
|
|
136
|
+
| Database schema (expand/migrate/contract, online DDL, backfill) | `rules/hatch3r-migrations.md` |
|
|
137
|
+
| Public API lifecycle (versioning, deprecation, `Sunset`) | `rules/hatch3r-api-versioning.md` |
|
|
138
|
+
| Event registry compatibility (BACKWARD/FORWARD/FULL) | `rules/hatch3r-event-schema-evolution.md` |
|
|
139
|
+
| Spec-time consumer inventory | `agents/hatch3r-brownfield-spec.md` → Integration-Surface Analysis |
|
|
140
|
+
| Code clones / block duplication | `rules/hatch3r-anti-duplication.md` |
|
|
141
|
+
|
|
142
|
+
This rule owns: internal cross-lane seams, the census output grammar, seam-owner assignment, the façade contract-hold, and value drift on shared constants.
|
|
143
|
+
|
|
144
|
+
## Enforcement Wiring
|
|
145
|
+
|
|
146
|
+
- `agents/hatch3r-implementer.md` Step 5d — consumer census as lane-exit gate.
|
|
147
|
+
- `agents/hatch3r-fixer.md` Step 5b — consumer census on shared-contract fixes.
|
|
148
|
+
- `agents/hatch3r-reviewer.md` item 11 — consumer-scoped review with self-run grep.
|
|
149
|
+
- `commands/hatch3r-board-pickup.md` Step 3 item 5 — first-pass contract-overlap scan at batch triage.
|
|
150
|
+
- `commands/board/pickup-delegation-multi.md` Step 6c.2 item 4 — seam-owner assignment; Step 6c.4 — per-lane census verification + cross-lane old-identifier grep.
|
|
151
|
+
- `agents/shared/quality-charter.md` → Data integrity quality — shared-contract census row + verification gate.
|
|
152
|
+
- `checks/code-quality.md` → Shared Contracts and Constants.
|
|
153
|
+
|
|
154
|
+
## References
|
|
155
|
+
|
|
156
|
+
- Fowler, M. "ParallelChange." martinfowler.com/bliki/ParallelChange.html (accessed 2026-07-08, T3 canonical, 2014). Expand/migrate/contract for any interface change; contract begins only "once all usages have been migrated" — skipping contract leaves you worse than you started.
|
|
157
|
+
- Fowler, M. "BranchByAbstraction." martinfowler.com/bliki/BranchByAbstraction.html (accessed 2026-07-08, T3 canonical, 2014). The system builds and runs at all times during the hold; the abstraction layer outlives the old supplier.
|
|
158
|
+
- Winters, T., Manshreck, T., Wright, H. *Software Engineering at Google*, ch. 22 "Large-Scale Changes." abseil.io/resources/swe-book/html/ch22.html (accessed 2026-07-08, T2, 2020). Tool-driven consumer discovery sharded by ownership; reintroduction guards; done is declared from evidence, not assumed.
|
|
159
|
+
- Sourcegraph. "Batch Changes." sourcegraph.com/docs/batch-changes (accessed 2026-07-08, T1, current). Text-search census as the consumer-discovery primitive; burndown-to-zero completion tracking.
|
|
160
|
+
- Pact. "can-i-deploy." docs.pact.io/pact_broker/can_i_deploy (accessed 2026-07-08, T1, current). Verification is recorded evidence per consumer; all known consumers gate the deploy.
|
|
@@ -28,6 +28,7 @@ Score every task against these signals before implementation. Each signal adds w
|
|
|
28
28
|
| Estimated file count > 5 | +2 | Infer from task scope and codebase exploration |
|
|
29
29
|
| Security-sensitive area (auth, payments, data access, secrets) | +2 | Match task against security-sensitive directories or keywords |
|
|
30
30
|
| Behavioral contract change (API signature, event schema, type interface) | +2 | Task implies changes to shared interfaces or public APIs |
|
|
31
|
+
| Module-boundary or client↔server contract change in files with no static type coverage | +2 | Changed exports / HTTP shapes / event names / store keys where no compiler validates consumers |
|
|
31
32
|
|
|
32
33
|
### Tier Assignment
|
|
33
34
|
|
|
@@ -23,6 +23,7 @@ Score every task against these signals before implementation. Each signal adds w
|
|
|
23
23
|
| Estimated file count > 5 | +2 | Infer from task scope and codebase exploration |
|
|
24
24
|
| Security-sensitive area (auth, payments, data access, secrets) | +2 | Match task against security-sensitive directories or keywords |
|
|
25
25
|
| Behavioral contract change (API signature, event schema, type interface) | +2 | Task implies changes to shared interfaces or public APIs |
|
|
26
|
+
| Module-boundary or client↔server contract change in files with no static type coverage | +2 | Changed exports / HTTP shapes / event names / store keys where no compiler validates consumers |
|
|
26
27
|
|
|
27
28
|
### Tier Assignment
|
|
28
29
|
|
|
@@ -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)
|