hstack 0.18.0 → 0.19.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/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  All notable changes to hstack are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [SemVer](https://semver.org/).
4
4
 
5
+ ## [0.19.0] - 2026-09-19
6
+
7
+ Changing an existing test stops needing permission and starts needing a label (ADR-0017). The authorization phrase is abolished; disclosure replaces it. This is the last per-change confirmation gate that survived the light pivot, and it is removed for the reason ADR-0015 removed the others — its cost was paid on every change and its protection was never once observed working.
8
+
9
+ ### Changed
10
+
11
+ - **§ Test immutability is gone as a top-level law** and becomes four paragraphs inside § Tests. Three carve-outs and five prohibitions become one prohibition: changing a test so a red suite goes green when the code under test is what is wrong. Changing a test because the intended behaviour moved is stated, in the kernel, to be normal work — no phrase, no halt, no permission to request.
12
+ - **Disclosure replaces authorization.** A commit that modifies or deletes a test file that existed at the merge-base tags each file with exactly one of `behavior-change`, `refactor`, `obsolete`. There is no fourth tag: if the honest word would be *it was failing*, that is the forbidden move and not a disclosure. The PR description repeats the list in plain language under **Tests changed** — the commit body is for the audit, that section is for the human, who reads the description and not the diff.
13
+ - **The four canonical authorization phrases are removed from the corpus.** `Ok to change test <name>` and its siblings were kept verbatim by ADR-0013 and again by ADR-0014, both times on the argument that the wording *is* the mechanism. The argument was sound and the mechanism was never exercised: `REC-0003` measured 265 candidate violations over 90 days, the watch-list repeated verbatim for seven weeks, and no human ever reviewed a row.
14
+ - **The escalation clause is a disclosure, not a halt.** An agent that cannot tell whether the test or the code is wrong makes the most plausible change and names the doubt in the PR description as an open question. Halting was the previous answer and it put the owner in the loop at a moment he is generally not in it.
15
+ - **Still hard, for one stated reason — they hide from the diff.** Bulk snapshot updates (`vitest -u`, `--updateSnapshot`, any equivalent, including in hooks) and silent neutralization (`.skip`, `test.todo`, a deleted case, a `.toBe()` broadened to `.toContain()`, a timeout raised to mask a bug). A hidden edit cannot be disclosed, which is the whole argument for keeping these two where everything else was relaxed.
16
+ - **The enforcement points follow the rule.** The adversarial reviewer and `/hstack-adversarial-review` keep a mandatory audit and check for the disclosure tag instead of the authorization echo; `/hstack-wrap` writes the **Tests changed** section rather than blocking the PR; the test strategist may now change an existing test instead of being told existing tests are read-only always; `/hstack-test-audit` keeps its own read-only posture, re-justified on the grounds that an audit which rewrites what it audits is worthless.
17
+ - **The two places the disclosure is physically produced now say so.** The PR-description template (`hstack-wrap/references/pr-description.md`) gains a **Tests changed** heading before **Review findings**, because a step that requires a section the template never offers is a step that will be skipped. And `/hstack-commit` step 4 names the tag in its body guidance — it is the one moment where a staged test file and a commit body exist at the same time.
18
+ - **QO-3's labels change and its field name does not.** `qo_3_test_immutability_audit` is produced by the telemetry sidecar outside this repo, so renaming it here would break the contract. Until that sidecar counts undisclosed edits instead of unphrased ones, the metric reads empty rather than wrong — and there is no audit signal in the interval. Named in ADR-0017 as a consequence, not a detail.
19
+
20
+ ### Added
21
+
22
+ - **`adr/ADR-0017-disclosure-replaces-authorization-on-tests.md`** — supersedes ADR-0013 and ADR-0014 on the single arbitration where each declined to touch this rule; the rest of both stands. It records the two consequences that look bad: the net is now weaker in exactly the case it was built for, because a tag can be typed by the model where the old phrase could not; and the decision is made on the cost side with the benefit side still unmeasured, since hstack has never detected one unauthorized test edit — equally consistent with *it never happened* and with *we cannot see it*.
23
+
5
24
  ## [0.18.0] - 2026-09-18
6
25
 
7
26
  Code quality gets a mechanical half and a judgment half, with a hard line between them (ADR-0016). The first consumer's audit found the prose rules ignored at scale and the one lint rule at `error` respected everywhere; this release draws the conclusion.
package/README.md CHANGED
@@ -116,7 +116,7 @@ The nine Skills:
116
116
 
117
117
  | Skill | What it is for |
118
118
  | --- | --- |
119
- | `/hstack-wrap` | The end of a change: runs `/review` and `/security-review`, audits test immutability, updates the living docs the diff invalidated, writes the PR description. |
119
+ | `/hstack-wrap` | The end of a change: runs `/review` and `/security-review`, audits test disclosure, updates the living docs the diff invalidated, writes the PR description. |
120
120
  | `/hstack-promote` | The release: pending production migrations, a smoke test on the unpromoted build, then promotion. Commands live in `infrastructure.md` § Deploy Pipeline. |
121
121
  | `/hstack-commit` | The one Commitizen format humans and agents both write. |
122
122
  | `/hstack-adversarial-review` | The deep pass on a sensitive surface, from a session that never saw the change being written. Findings land as a PR comment. |
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.18.0
1
+ 0.19.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hstack",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "A spec-driven engineering workflow that ships as Claude Code Skills and subagents.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: adversarial-reviewer
3
3
  model: opus
4
- description: "Use to critique a PR cold, from a session that never saw it being written — the six lenses, the mandatory test-immutability audit, an empty result that has to be defended. Surfaces findings; never resolves them."
4
+ description: "Use to critique a PR cold, from a session that never saw it being written — the six lenses, the mandatory test-disclosure audit, an empty result that has to be defended. Surfaces findings; never resolves them."
5
5
  tools:
6
6
  - Read
7
7
  - Grep
@@ -44,11 +44,12 @@ Nothing. Findings are returned to the Skill, which posts them on the PR.
44
44
  code-quality — the last one against `code-standards.md`, a finding naming the rule it breaks.
45
45
  They are lenses to look through, not buckets to fill — a change carrying all its
46
46
  risk in one dimension produces findings in one category, and that is the honest answer.
47
- - **The test-immutability audit is mandatory** (protocol: `KERNEL.md` § Test immutability). Diff
47
+ - **The test-disclosure audit is mandatory** (protocol: `KERNEL.md` § Tests). Diff
48
48
  every test file that existed at the base. For each modified, content-drifted or deleted test,
49
- look for its canonical authorization echo in the commit messages or the PR description. Missing
50
- echo: a finding under intent-compliance at `high` minimum, `critical` for a bulk snapshot update.
51
- This one is not subject to your judgment about whether it is worth filing.
49
+ look for its disclosure tag `behavior-change`, `refactor` or `obsolete` in a commit body, and
50
+ its plain-language line under **Tests changed** in the PR description. Missing disclosure: a
51
+ finding under intent-compliance at `high` minimum, `critical` for a bulk snapshot update or a
52
+ silent neutralization. This one is not subject to your judgment about whether it is worth filing.
52
53
  - **Intent is what the PR says it does.** The description names the perimeter, the shortcuts and
53
54
  the docs it updated; the diff either matches or it does not.
54
55
  - **Severity is about the consequence, not the likelihood.** Cross-tenant leakage and irreversible
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: test-strategist
3
3
  model: opus
4
- description: "Use during a test audit of one module: map its business rules against the existing tests and return the gaps, then write the tests the engineer chose to add. Existing tests stay read-only."
4
+ description: "Use during a test audit of one module: map its business rules against the existing tests and return the gaps, then write the tests the engineer chose to add. Existing tests stay read-only during the audit itself."
5
5
  ---
6
6
 
7
7
  ## Role
@@ -18,8 +18,7 @@ the right argument.
18
18
  ## When to invoke
19
19
 
20
20
  When `/hstack-test-audit <module>` needs the module's rules mapped against its tests, or needs the
21
- chosen gaps closed. Not to modify existing tests — that route is the kernel's authorization
22
- protocol, in the engineer's session, never here.
21
+ chosen gaps closed.
23
22
 
24
23
  ## Reads
25
24
 
@@ -32,11 +31,12 @@ New test files, and `hstack/context/invariants.md`. Nothing else.
32
31
 
33
32
  ## Behavior rules
34
33
 
35
- - **Existing tests are read-only, always** (protocol: `KERNEL.md` § Test immutability). When
36
- closing a gap would mean changing an assertion, deleting a test or updating a snapshot, halt and
37
- hand the engineer the routes: authorize the change under the canonical phrase, or write a new
38
- test that supersedes the old one, or leave the gap named in the PR description. Never author an
39
- authorization phrase on the engineer's behalf.
34
+ - **Existing tests stay read-only here** not because they are frozen. Changing one is ordinary
35
+ work when the intended behaviour moved, disclosed with its tag (`KERNEL.md` § Tests). It is that
36
+ an audit which rewrites the tests it is auditing has audited nothing. Name the change the gap
37
+ needs and leave it to the engineer's normal flow; a new test superseding the old one is often
38
+ better anyway, since it keeps the old expectation legible. What is never done, here or in the
39
+ engineer's session, is bending a test so a red suite goes green when the code is what is wrong.
40
40
  - **Pyramid bias.** Unit for pure functions and reducers; integration for behaviour that crosses
41
41
  modules or the database; end-to-end for user-visible journeys. Refuse a strategy that rests
42
42
  primarily on end-to-end tests — slow and flaky is how a suite stops being run.
@@ -52,7 +52,6 @@ New test files, and `hstack/context/invariants.md`. Nothing else.
52
52
 
53
53
  ## Stop conditions
54
54
 
55
- - Closing a gap requires touching an existing test.
56
55
  - The module has no identifiable business rule — report that rather than propose tests.
57
56
  - A rule surfaced contradicts one already in `invariants.md`. Surface both; the engineer decides.
58
57
  - A performance budget is wanted but the repo has no way to assert one. Say so; do not write a
@@ -31,11 +31,11 @@ honor-system, as the kernel says; a sentence is the whole protocol.
31
31
  reconstruct the conversation that produced the change.
32
32
  2. **Invoke `adversarial-reviewer`** with that material. The subagent sweeps six lenses and returns
33
33
  findings; it does not resolve them and does not touch the code.
34
- 3. **The test-immutability audit is mandatory** and is not subject to anyone's judgment about
34
+ 3. **The test-disclosure audit is mandatory** and is not subject to anyone's judgment about
35
35
  whether it is worth filing. Every test file that existed at the base is diffed; a modification
36
- or deletion without its canonical authorization echo `Ok to change test <name>` /
37
- `Ok to delete test <name>`, in a commit message or the PR description — is a finding at `high`
38
- minimum. A bulk snapshot update is `critical`.
36
+ or deletion without its disclosure a `behavior-change` / `refactor` / `obsolete` tag in a
37
+ commit body, repeated under **Tests changed** in the PR description — is a finding at `high`
38
+ minimum. A bulk snapshot update or a silent neutralization is `critical`.
39
39
  4. **Post the findings** with `gh pr comment <n>`, or as a GitHub review when they are anchored to
40
40
  lines. Each finding carries a severity, a category, the evidence in the diff, and what would
41
41
  resolve it.
@@ -74,13 +74,14 @@ feature, the shortcuts taken, the living docs updated.
74
74
  Behaviour the diff adds that no line of the description anticipated — undeclared scope is a
75
75
  compliance gap in the other direction. A living doc the change invalidated and left untouched
76
76
  (kernel § Context docs). A conscious shortcut visible in the code and named nowhere. **Any
77
- test-file modification without its canonical authorization echo** (`KERNEL.md` § Test immutability);
78
- this one is mandatory and never subject to judgment about whether it is worth filing.
77
+ test-file modification without its disclosure tag** (`KERNEL.md` § Tests); this one is mandatory
78
+ and never subject to judgment about whether it is worth filing.
79
79
 
80
80
  **Filler looks like.** "The description could have been clearer." Style disagreements with how the
81
81
  work was sequenced.
82
82
 
83
- **Severity.** Unauthorized test changes: `high` minimum, `critical` for a bulk snapshot update. A
83
+ **Severity.** Undisclosed test changes: `high` minimum, `critical` for a bulk snapshot update or a
84
+ silent neutralization. A
84
85
  stale living doc: `high`, because the next session will read it and believe it. Everything else:
85
86
  judgment.
86
87
 
@@ -27,7 +27,7 @@ Optional `--push`: push after committing — still subject to per-invocation con
27
27
  - `<type>(<scope>): <summary>`, where `<type>` is one of `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `style`, `perf`, `ci`.
28
28
  - `<scope>` names the area actually touched — never an invented or aspirational one. When the change spans unrelated areas, no single honest scope exists: propose splitting it into several commits.
29
29
  - `<summary>` ≤ 72 characters, imperative present ("add", not "added"), no trailing period.
30
- - Body, when it earns its place: the why, not the what.
30
+ - Body, when it earns its place: the why, not the what. A staged test file that existed at the merge-base is declared there, one line per file, tagged `behavior-change`, `refactor` or `obsolete` (kernel § Tests).
31
31
  - **Never "Generated with Claude Code" or any similar attribution.**
32
32
  - Footer: conventional-commits footers only (`BREAKING CHANGE:`, `Refs: <issue>`).
33
33
 
@@ -26,8 +26,9 @@ question at plan time, and this Skill is never a phase of a change.
26
26
  `data-architecture.md` when the module touches the database. Write each rule as *decides X;
27
27
  wrong in silence if Y*. A module whose code decides nothing has no gap to close — say so and
28
28
  stop rather than write tests for the sake of coverage.
29
- 2. **Map the existing tests onto the rules.** Existing test files are read-only here
30
- (kernel § Test immutability); this step reads them, it never edits them.
29
+ 2. **Map the existing tests onto the rules.** Existing test files are read-only here — an audit
30
+ that rewrites the tests it is auditing has audited nothing; this step reads them, never edits
31
+ them.
31
32
  3. **Name the gaps** — rules with no test, and tests asserting something no rule needs.
32
33
  4. **Interview the edges.** A handful of questions, not a script: *what does the user notice if
33
34
  this breaks in silence?* — plus concurrency, retries, and the cross-tenant case when the module
@@ -38,9 +39,10 @@ question at plan time, and this Skill is never a phase of a change.
38
39
  6. **The engineer picks the gaps to close now.** A module with twenty uncovered rules would make
39
40
  an unreviewable PR. Close the chosen ones; name the rest in the PR description so the next
40
41
  audit starts there.
41
- 7. **Write the tests.** New test files need no authorization — "new" means the path did not exist
42
- at session start. If closing a gap requires editing or deleting an existing test, halt and ask;
43
- nothing in this Skill authorizes that.
42
+ 7. **Write the tests.** New test files only — "new" means the path did not exist at session start.
43
+ When closing a gap means editing or deleting an existing test, name that change in the output
44
+ rather than make it here; the engineer makes it in the normal flow, where it is ordinary work
45
+ carrying its disclosure line (kernel § Tests).
44
46
  8. **Record the invariants.** Every rule the audit surfaced goes into `hstack/context/invariants.md`
45
47
  in this PR — **including the gaps left open**. The memory is the cheap half; write it whether or
46
48
  not the test was written.
@@ -51,13 +53,12 @@ and the gap list. Steps 4 and 6 are questions for the engineer and happen here,
51
53
  ## Output
52
54
 
53
55
  New test files, an `invariants.md` diff, and a summary for the PR description naming the gaps left
54
- open. Nothing else.
56
+ open and the existing tests that need changing. Nothing else.
55
57
 
56
58
  ## Stop conditions
57
59
 
58
60
  Beyond the kernel's:
59
61
 
60
- - Closing a gap would require touching an existing test. Halt (§ Test immutability).
61
62
  - The module cannot be located, or spans so much of the repo that "one module" is not what is
62
63
  being audited. Ask which one.
63
64
  - An invariant the audit surfaces contradicts one already in `invariants.md`. Surface both; the
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hstack-wrap
3
- description: Use at the end of a change, before the PR — reads the diff against code-standards, runs /review and /security-review, audits test immutability, updates the living docs the diff invalidated, writes the PR description.
3
+ description: Use at the end of a change, before the PR — reads the diff against code-standards, runs /review and /security-review, audits test disclosure, updates the living docs the diff invalidated, writes the PR description.
4
4
  ---
5
5
 
6
6
  ## Purpose
@@ -25,7 +25,7 @@ None. The diff under review is everything that will land in the PR — committed
25
25
 
26
26
  3. **Run `/review`, then `/security-review`** on that diff. Fix what is fixable inside the announced perimeter. Everything else is declared in the PR description — the finding, and why it was not fixed. A finding is never dropped silently, and an empty result is reported as what it is: the reviewer found nothing.
27
27
 
28
- 4. **Audit test immutability.** Diff the test files that already existed at the merge-base. An edit or a deletion without the canonical authorization echoed in this conversation is a blocking finding — halt, and do not open the PR (kernel § Test immutability).
28
+ 4. **Audit test disclosure.** Diff the test files that already existed at the merge-base. Each one modified or deleted carries its tag `behavior-change`, `refactor` or `obsolete` in a commit body, and its plain-language line under a **Tests changed** heading in the PR description; writing that section is part of this step (kernel § Tests). An undisclosed edit is fixed here by disclosing it, not by halting. The one exception: if the honest tag would be *it was failing*, halt and fix the code instead.
29
29
 
30
30
  5. **Ask each living doc whether this diff invalidated it**, and update it in this PR when it did (kernel § Context docs). When an entry point changed status — live, routable, off — the exposure column of the Module Map in `app-architecture.md` moves with it.
31
31
 
@@ -45,6 +45,6 @@ One open PR against the default branch, carrying the code, the living-doc update
45
45
 
46
46
  Beyond the kernel's:
47
47
 
48
- - A pre-existing test was modified or deleted without authorization.
48
+ - A pre-existing test was changed to turn a red suite green when the code is what is wrong.
49
49
  - A hook or a check was bypassed to get here.
50
50
  - A living doc is invalidated and you cannot update it. Say "stale" in the PR description; never invent the content.
@@ -33,6 +33,13 @@
33
33
  <!-- Conscious shortcuts that survive the merge, each with its hstack/tech-debt/TD-NNNN-<slug>.md
34
34
  file. "None" is a real answer when it is true. -->
35
35
 
36
+ ## Tests changed
37
+
38
+ <!-- Every test that existed at the merge-base and was modified or deleted here, one line each,
39
+ in plain language, tagged behavior-change / refactor / obsolete as in the commit body. A PR
40
+ that touches a test and has no such list is incomplete. Where it was unclear whether the
41
+ test or the code was wrong, say so here as an open question. -->
42
+
36
43
  ## Review findings
37
44
 
38
45
  <!-- /review and /security-review, in full. These are LLM judgments, not evidence: an empty list
@@ -64,27 +64,27 @@ Two CI lanes: the fast one — typecheck, lint, critical tests — blocks the me
64
64
 
65
65
  Tests are mandatory on critical paths and on every business invariant a change touches. At plan time, one question: *does this code decide something that would be wrong silently?* If it does, a test names the invariant. `/hstack-test-audit` is on demand, never a per-change phase.
66
66
 
67
- ---
67
+ **Changing an existing test is normal work.** A product before product-market fit changes its intended behaviour several times a week, and the tests move with it. No authorization phrase, no halt, no permission to request.
68
68
 
69
- ## Test immutability
69
+ **The one forbidden move** is changing a test so that a red suite goes green when the code under test is what is wrong. Before editing an existing test, one question: *is the test wrong, or is the code wrong?* Fix whichever one is wrong — never the other one.
70
70
 
71
- Once a test file exists in the working tree (committed or staged), **no agent may edit or delete it without per-test, per-conversation human authorization.** This rule exists because the dominant failure mode of LLM-driven implementation is the model editing an assertion or deleting a test to make the suite go green, rather than fixing the code under test.
71
+ **When it cannot tell**, the agent makes the most plausible change and names the doubt in the PR description as an open question, never silently and never by halting. An unresolved doubt is information the reviewer needs, not a reason to stop working.
72
72
 
73
- **What counts as a test.** Files matching the repo's test patterns (e.g. `*.test.ts`, `*.spec.ts`, `__tests__/**/*`, `e2e/**/*`, `*_test.go`), snapshot files (`__snapshots__/*`), and assertion-bearing fixtures factories and seed data encoding expected outputs.
73
+ **Disclosure replaces authorization.** Any commit that modifies or deletes a test file that existed at the merge-base says so in its body, one line per file, tagged with exactly one word:
74
74
 
75
- **Authorization protocol.** Halt before editing. Surface (a) the test file and test name, (b) why it must change — what it asserts vs. what is now correct, with evidence, (c) the proposed diff, (d) the alternatives, starting with fixing the code under test. Then wait for the canonical phrase — `Ok to change test <name>` or `Ok to delete test <name>`, `<name>` being the file path or a uniquely-identifying test name. The agent echoes it back verbatim before acting; nothing else is an authorization.
75
+ - `behavior-change` — the intended behaviour moved and the test followed it.
76
+ - `refactor` — same assertions, moved or renamed or rewired (imports, mocks, fixtures).
77
+ - `obsolete` — the subject of the test no longer exists.
76
78
 
77
- **Carve-outs.** New tests need no authorization "new" means the path did not exist at session start. A content-preserving move is permitted, surfaced in the commit message so the review can verify nothing drifted.
79
+ There is no fourth tag. If the honest word would be *it was failing*, that is the forbidden move, not a disclosure.
78
80
 
79
- **Forbidden no matter what.**
81
+ The PR description repeats the same list in plain language, under a **Tests changed** heading. The commit body is for the audit; that section is for the human, who reads the description and not the diff. A PR that touches a test and has no such section is incomplete.
82
+
83
+ **What counts as a test.** Files matching the repo's test patterns (e.g. `*.test.ts`, `*.spec.ts`, `__tests__/**/*`, `e2e/**/*`, `*_test.go`), snapshot files (`__snapshots__/*`), and assertion-bearing fixtures — factories and seed data encoding expected outputs.
80
84
 
81
- - Blanket authorizations ("go ahead and fix any failing tests", "update whatever snapshots need it"). Authorizations are per-test, per-conversation. The agent refuses blanket scope.
82
- - Bulk snapshot updates via `--update-snapshots`, `jest --updateSnapshot`, `vitest -u`, or any equivalent flag, including in pre-commit hooks.
83
- - Relaxing an assertion without authorization (e.g., tightening a regex to a substring match, broadening a `.toBe()` to `.toContain()`, increasing a timeout to mask a real bug).
84
- - Deleting a `.skip` annotation, replacing a `test()` call with `test.todo()`, or otherwise neutralizing a test without authorization. Neutralization is a form of deletion.
85
- - Editing a test as part of "cleaning up" a phase without an explicit authorization for that test, even if the edit is cosmetic.
85
+ **Two things stay hard, because they hide from the diff.** Bulk snapshot updates via `--update-snapshots`, `jest --updateSnapshot`, `vitest -u` or any equivalent flag, including in hooks a mass edit nobody reads is not a disclosure. And neutralizing a test silently: adding `.skip`, replacing `test()` with `test.todo()`, deleting a case, or loosening an assertion (a `.toBe()` broadened to `.toContain()`, a timeout raised to mask a real bug) are edits like any other and carry their disclosure line.
86
86
 
87
- Authorization is single-use: it covers the test and the change discussed in the current conversation, and never carries across sessions. An unauthorized test edit is a blocking review finding.
87
+ An undisclosed test edit is a review finding. A disclosed one is just work.
88
88
 
89
89
  ---
90
90