mandrel 2.7.0 → 2.9.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 (95) hide show
  1. package/.agents/README.md +104 -330
  2. package/.agents/agents/auditor.md +135 -0
  3. package/.agents/agents/plan-critic.md +80 -0
  4. package/.agents/audit-checklists/dependencies.md +7 -0
  5. package/.agents/audit-checklists/documentation.md +1 -0
  6. package/.agents/docs/SDLC.md +69 -162
  7. package/.agents/docs/configuration.md +159 -499
  8. package/.agents/docs/quality-gates.md +59 -180
  9. package/.agents/instructions.md +170 -295
  10. package/.agents/rules/changelog-style.md +8 -66
  11. package/.agents/rules/ci-remediation.md +65 -124
  12. package/.agents/rules/gherkin-standards.md +10 -31
  13. package/.agents/rules/git-conventions-reference.md +28 -61
  14. package/.agents/rules/git-conventions.md +1 -1
  15. package/.agents/rules/orchestration-error-handling.md +5 -15
  16. package/.agents/rules/security-baseline.md +7 -13
  17. package/.agents/rules/shell-conventions.md +4 -13
  18. package/.agents/rules/test-seams.md +2 -2
  19. package/.agents/rules/testing-standards.md +7 -17
  20. package/.agents/schemas/agentrc.schema.json +21 -0
  21. package/.agents/scripts/audit-to-stories.js +51 -0
  22. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +120 -55
  23. package/.agents/scripts/lib/config-settings-schema.js +32 -0
  24. package/.agents/scripts/lib/findings/semantic-issue-search.js +43 -5
  25. package/.agents/scripts/lib/observability/terse-result.js +114 -0
  26. package/.agents/scripts/lib/orchestration/complexity-gate.js +207 -0
  27. package/.agents/scripts/lib/orchestration/plan-context.js +3 -0
  28. package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +221 -8
  29. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +55 -14
  30. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +9 -3
  31. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +4 -1
  32. package/.agents/scripts/lib/orchestration/task-body-validator.js +13 -40
  33. package/.agents/scripts/lib/story-body/body-format-lints.js +215 -0
  34. package/.agents/scripts/lib/story-body/story-body.js +18 -2
  35. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -0
  36. package/.agents/scripts/providers/github/issues.js +54 -7
  37. package/.agents/scripts/providers/github/search-budget.js +124 -0
  38. package/.agents/scripts/providers/github/search-query.js +71 -0
  39. package/.agents/scripts/single-story-confirm-merge.js +14 -5
  40. package/.agents/scripts/single-story-init.js +19 -3
  41. package/.agents/scripts/sync-branch-from-base.js +9 -3
  42. package/.agents/skills/core/api-and-interface-design/SKILL.md +23 -297
  43. package/.agents/skills/core/api-and-interface-design/reference.md +76 -0
  44. package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +20 -327
  45. package/.agents/skills/core/browser-testing-with-devtools/reference.md +74 -0
  46. package/.agents/skills/core/code-review-and-quality/reference.md +16 -398
  47. package/.agents/skills/core/debugging-and-error-recovery/reference.md +14 -281
  48. package/.agents/skills/core/documentation-and-adrs/reference.md +42 -338
  49. package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +28 -326
  50. package/.agents/skills/core/idea-refinement/SKILL.md +67 -193
  51. package/.agents/skills/core/security-and-hardening/SKILL.md +15 -31
  52. package/.agents/skills/core/security-and-hardening/reference.md +15 -273
  53. package/.agents/skills/skills.index.json +5 -5
  54. package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +16 -222
  55. package/.agents/skills/stack/qa/gherkin-authoring/reference.md +157 -0
  56. package/.agents/skills/stack/qa/playwright/SKILL.md +0 -29
  57. package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +19 -23
  58. package/.agents/skills/stack/qa/qa-harness/SKILL.md +35 -53
  59. package/.agents/skills/stack/qa/vitest/SKILL.md +0 -29
  60. package/.agents/workflows/audit-accessibility.md +34 -108
  61. package/.agents/workflows/audit-architecture.md +35 -123
  62. package/.agents/workflows/audit-clean-code.md +19 -78
  63. package/.agents/workflows/audit-data-model.md +32 -100
  64. package/.agents/workflows/audit-dependencies.md +47 -111
  65. package/.agents/workflows/audit-devops.md +16 -83
  66. package/.agents/workflows/audit-documentation.md +46 -93
  67. package/.agents/workflows/audit-navigability.md +26 -80
  68. package/.agents/workflows/audit-performance.md +40 -106
  69. package/.agents/workflows/audit-privacy.md +17 -80
  70. package/.agents/workflows/audit-quality.md +35 -99
  71. package/.agents/workflows/audit-security.md +20 -78
  72. package/.agents/workflows/audit-seo.md +20 -98
  73. package/.agents/workflows/audit-sre.md +20 -88
  74. package/.agents/workflows/audit-to-stories.md +1 -8
  75. package/.agents/workflows/audit-ux-ui.md +17 -80
  76. package/.agents/workflows/deliver.md +54 -9
  77. package/.agents/workflows/git-cleanup.md +50 -275
  78. package/.agents/workflows/helpers/audit-lens-core.md +230 -0
  79. package/.agents/workflows/helpers/code-review.md +11 -23
  80. package/.agents/workflows/helpers/deliver-story-reference.md +114 -17
  81. package/.agents/workflows/helpers/deliver-story.md +36 -186
  82. package/.agents/workflows/helpers/qa-core.md +174 -0
  83. package/.agents/workflows/helpers/qa-run-scenario-reference.md +35 -0
  84. package/.agents/workflows/helpers/qa-run-scenario.md +11 -25
  85. package/.agents/workflows/helpers/worktree-lifecycle.md +6 -67
  86. package/.agents/workflows/mandrel-update.md +7 -13
  87. package/.agents/workflows/plan.md +44 -17
  88. package/.agents/workflows/qa-assist.md +140 -269
  89. package/.agents/workflows/qa-explore.md +125 -316
  90. package/.agents/workflows/qa-run.md +180 -380
  91. package/docs/CHANGELOG.md +30 -0
  92. package/package.json +1 -1
  93. package/.agents/workflows/helpers/audit-dual-path.md +0 -59
  94. package/.agents/workflows/helpers/audit-self-check.md +0 -70
  95. package/.agents/workflows/helpers/audit-severity-scale.md +0 -19
@@ -5,299 +5,74 @@ description: >-
5
5
  `git stash` entries — each step gated by operator confirmation.
6
6
  ---
7
7
 
8
- # Git Cleanup Workflow
8
+ # /git-cleanup [--fast-forward-main] [--prune-remotes] [--branches] [--stashes] [--execute] [--remote] [--yes] [--drop-stashes <ref>] [--exclude <pattern>] [--json]
9
9
 
10
- `/git-cleanup` folds the four cleanup steps that operators routinely
11
- run by hand after a busy session into a single pipeline with per-step
12
- confirmation:
10
+ `/git-cleanup` folds the four cleanup steps operators routinely run by hand
11
+ after a busy session into a single pipeline with per-step confirmation. It is a
12
+ **recovery tool**, not a routine chore: the delivering flows already reap their
13
+ own merged refs and fast-forward the base branch (see
14
+ [`rules/git-conventions.md` § Local checkout hygiene](../rules/git-conventions.md)).
15
+ Reach for it when the automated hygiene left an unusual state behind.
13
16
 
14
- 1. **fast-forward `main`** `git fetch origin <base>` then
15
- `git merge --ff-only origin/<base>` on a clean working tree.
16
- 2. **prune stale remote-tracking refs** — `git fetch --prune origin`
17
- to drop `refs/remotes/origin/*` entries that GitHub already deleted.
18
- 3. **reap merged local branches** — the existing squash-aware
19
- `gh pr list --state merged` + `git branch --merged <base>` sweep,
20
- with attached worktrees removed first. Optionally also deletes the
21
- `origin/<branch>` ref when `--remote` is passed. Every default run
22
- also **enumerates** `refs/remotes/origin/*` and reports any
23
- **remote-only** merged branches — branches whose local ref is
24
- already gone (or never existed) but whose `origin/<branch>` still
25
- points at a merged PR — even without `--remote`; `--remote` is still
26
- required to *delete* them. A third branch, whose content already
27
- landed in `<base>` by another route (a squash-merged PR, a
28
- cherry-pick, a manual `merge --squash`), is caught by a
29
- **content-equivalence probe** (`git merge-tree --write-tree`,
30
- git ≥ 2.38) even when it has no merged PR of its own and is not a
31
- git ancestor of `<base>`.
32
- 4. **triage `git stash` entries** — list every stash and prompt for
33
- `drop / keep / quit` per entry (or pass `--drop-stashes <ref>` for
34
- non-interactive use).
35
-
36
- The enumeration + reap logic lives in
37
- [`git-cleanup.js`](../scripts/git-cleanup.js). When no phase flag is
38
- passed, **all four phases run** sequentially. Pass any of
39
- `--fast-forward-main`, `--prune-remotes`, `--branches`, `--stashes` to
40
- narrow the run.
41
-
42
- > **When to run**: After a session that landed several PRs, or before
43
- > starting a new Story, to put the local checkout into a known
44
- > tidy state.
17
+ > **When to run**: after a session that landed several PRs, or before starting a
18
+ > new Story, to put the local checkout into a known tidy state.
45
19
  >
46
20
  > **Persona**: `devops-engineer` · **Skills**:
47
21
  > `core/git-workflow-and-versioning`
48
22
 
49
- <!-- markdownlint-disable-next-line MD028 -->
23
+ The enumeration + reap logic lives in
24
+ [`git-cleanup.js`](../scripts/git-cleanup.js) — it computes the candidate list,
25
+ the skip taxonomy, the detection signals, and the JSON envelope (add `--json`),
26
+ and prints them itself. Without `--execute` the script is a **dry-run preview**; nothing is
27
+ mutated. When no phase flag is passed, **all four phases run** sequentially; pass
28
+ any of `--fast-forward-main`, `--prune-remotes`, `--branches`, `--stashes` to
29
+ narrow the run. A failure in one phase does not short-circuit the others — each
30
+ runs and reports independently.
31
+
32
+ ## Phases
33
+
34
+ | Phase | What it does | Safety contract |
35
+ | --- | --- | --- |
36
+ | **fast-forward-main** | `git fetch origin <base>` then `git merge --ff-only origin/<base>`. | Skipped silently on a dirty tree or a non-fast-forward; otherwise prompts `Fast-forward main by N commit(s)?`. Checks out `<base>` first when HEAD is elsewhere and does **not** restore the prior branch. |
37
+ | **prune-remotes** | `git fetch --prune origin` to drop `refs/remotes/origin/*` GitHub already deleted. | Prompts before pruning. Runs as its own phase regardless of `--remote`. |
38
+ | **branches** | Reaps merged local branches (squash-aware: merged-PR, git-ancestry, and content-equivalence signals), removing an attached worktree first. Also enumerates **remote-only** merged branches. | Prints the candidate list, then prompts `Reap N merged branch(es)?`. `--remote` is required **on top of** `--execute` to delete any `origin/<branch>`. `content-merged` candidates carry a weaker-signal warning. |
39
+ | **stashes** | Lists every stash and triages it. | Interactive: `drop / keep / quit` per entry (default `keep`). Under `--yes` / `--json`, drops require an explicit `--drop-stashes <ref>` allowlist (repeatable) — there is no "drop all". |
50
40
 
51
- > [!WARNING] The `--execute` flag mutates state: it can fast-forward
52
- > `main`, delete local branches, delete remote refs (with `--remote`),
53
- > and drop stashes. Without `--execute` the script is a dry-run preview.
41
+ ## Constraint
54
42
 
55
- ## Step 1 Dry-run preview
43
+ > [!WARNING] `--execute` mutates state: it can fast-forward `main`, delete local
44
+ > branches, delete remote refs (with `--remote`), and drop stashes. Without
45
+ > `--execute` the script only previews.
56
46
 
57
- ```powershell
58
- node .agents/scripts/git-cleanup.js
59
- ```
47
+ - **`--execute`** — the master gate. Omit it for a preview of all four phases.
48
+ - **`--remote`** — extends the branches phase to delete the matching
49
+ `origin/<branch>` ref (and to delete remote-only merged branches). Cannot be
50
+ undone without re-pushing.
51
+ - **`--yes`** — bypass every per-step prompt (CI / non-interactive). Under it,
52
+ stash drops still require `--drop-stashes <ref>`.
53
+ - **`--exclude '<pattern>'`** — carve a branch out of the reap. This is the only
54
+ way to protect an in-scope merged-PR branch you want to keep.
60
55
 
61
- Walks all four phases without mutating anything. The fast-forward phase
62
- reports whether the tree is clean and how far behind `origin/<base>` the
63
- local ref is. The branches phase prints the merged-branch candidate
64
- list. The stashes phase lists every stash with its created-at and
65
- message.
56
+ Do **not** run with `--execute` if there is unmerged work that needs saving. The
57
+ fast-forward phase skips on a dirty tree (safe), but the branches phase reaps any
58
+ merged-PR branch in scope unless `--exclude`d.
66
59
 
67
- Add `--json` for a structured `{ fastForward, prune, stashes,
68
- candidates, … }` envelope.
60
+ ## Examples
69
61
 
70
- ## Step 2 — Run a single phase
62
+ ```bash
63
+ # Preview all four phases (no mutation).
64
+ node .agents/scripts/git-cleanup.js
71
65
 
72
- Each phase is independently selectable. Useful when you want to scope
73
- the cleanup tightly:
66
+ # Run everything non-interactively, including origin refs.
67
+ node .agents/scripts/git-cleanup.js --execute --remote --yes
74
68
 
75
- ```powershell
76
- # Only fast-forward main (skip everything else)
69
+ # Only fast-forward main.
77
70
  node .agents/scripts/git-cleanup.js --fast-forward-main --execute
78
71
 
79
- # Only sweep merged branches + their origin refs
72
+ # Only sweep merged branches + their origin refs.
80
73
  node .agents/scripts/git-cleanup.js --branches --execute --remote
81
74
 
82
- # Only prune stale tracking refs
83
- node .agents/scripts/git-cleanup.js --prune-remotes --execute
84
- ```
85
-
86
- ## Step 3 — Run all four phases
87
-
88
- The default. Confirms each destructive step independently:
89
-
90
- ```powershell
91
- node .agents/scripts/git-cleanup.js --execute
92
- ```
93
-
94
- The script prompts before each mutation:
95
-
96
- - **fast-forward-main**: skipped silently when the tree is dirty or the
97
- FF would not be a fast-forward; otherwise prompts
98
- `Fast-forward main by N commit(s)? [y/N]`.
99
- - **prune-remotes**: prompts before running
100
- `git fetch --prune origin`.
101
- - **branches**: prints the dry-run candidate list, then prompts
102
- `Reap N merged branch(es)? [y/N]`.
103
- - **stashes**: prompts per-stash with `drop / keep / quit`. A `quit`
104
- reply stops the per-stash loop without dropping anything further.
105
-
106
- Add `--remote` to also reap `origin/<branch>` during the branches phase.
107
-
108
- ## Step 4 — Non-interactive / CI
109
-
110
- Pass `--yes` to bypass every per-step prompt:
111
-
112
- ```powershell
113
- node .agents/scripts/git-cleanup.js --execute --remote --yes
114
- ```
115
-
116
- For the stash phase under `--yes` or `--json`, drops require an explicit
117
- allowlist via `--drop-stashes <ref>` (repeatable). Without that flag,
118
- stashes are listed but never dropped:
119
-
120
- ```powershell
75
+ # Drop specific stashes under --yes.
121
76
  node .agents/scripts/git-cleanup.js --stashes --execute --yes \
122
77
  --drop-stashes 'stash@{0}' --drop-stashes 'stash@{2}'
123
78
  ```
124
-
125
- This keeps the JSON / CI invocation contract explicit — the operator
126
- names exactly which stashes to drop, by ref.
127
-
128
- ## Step 5 — JSON envelope
129
-
130
- `--json` emits a single line of structured output suitable for
131
- programmatic consumption:
132
-
133
- ```json
134
- {
135
- "dryRun": false,
136
- "baseBranch": "main",
137
- "fastForward": {
138
- "ok": true,
139
- "applied": true,
140
- "skipped": false,
141
- "behind": 2
142
- },
143
- "prune": {
144
- "ok": true,
145
- "attempted": true,
146
- "remote": "origin",
147
- "pruned": ["fix/old"]
148
- },
149
- "candidates": [
150
- {
151
- "branch": "fix/foo",
152
- "prNumber": 1471,
153
- "mergedAt": "2026-05-09T12:00:00Z",
154
- "hasWorktree": true,
155
- "worktreePath": "C:/repo/.worktrees/fix-foo",
156
- "detectedBy": "gh"
157
- }
158
- ],
159
- "skipped": [
160
- { "branch": "story-4200", "reason": "not-merged", "lastCommitAt": "2026-05-01T00:00:00Z" }
161
- ],
162
- "ghDegraded": false,
163
- "worktrees": [{ "path": "C:/repo/.worktrees/fix-foo", "ok": true, "dirty": false }],
164
- "local": [{ "branch": "fix/foo", "ok": true, "alreadyGone": false }],
165
- "remote": [{ "branch": "fix/foo", "ok": true, "alreadyGone": true }],
166
- "stashes": {
167
- "ok": true,
168
- "actions": [
169
- { "ref": "stash@{0}", "action": "drop", "dropped": true }
170
- ],
171
- "failures": []
172
- },
173
- "failures": [],
174
- "ok": true
175
- }
176
- ```
177
-
178
- ## Exit codes
179
-
180
- - `0` — clean: dry-run preview, or at least one phase produced work and
181
- no phase failed.
182
- - `1` — at least one phase reported a failure (fast-forward, prune,
183
- branch reap, or stash drop). A failure in one phase **does not**
184
- short-circuit later phases — each runs and reports independently.
185
- - `2` — every active phase produced nothing to do (informational; the
186
- repo is already tidy).
187
-
188
- ## Phase-specific behaviour
189
-
190
- ### fast-forward-main
191
-
192
- - Skip reasons surfaced in `fastForward.reason`:
193
- - `dirty-tree` — `git status --porcelain` returned non-empty.
194
- - `not-fast-forward` — local `<base>` has commits the remote does
195
- not (`git rev-list --left-right --count` shows local-ahead > 0).
196
- - `already-up-to-date` — local and remote `<base>` point at the same
197
- commit.
198
- - `fetch-failed` / `merge-failed` — surface git's stderr verbatim.
199
- - When the current HEAD is not `<base>`, the script runs
200
- `git checkout <base>` before merging. The original branch is **not**
201
- restored at the end — operators on a feature branch should run
202
- `git checkout -` after.
203
-
204
- ### prune-remotes
205
-
206
- Runs as its own phase regardless of whether `--remote` was passed
207
- during the branches phase. The branches phase still runs its own
208
- follow-up prune when `--remote` is set, so passing both is idempotent
209
- (the second prune just reports `pruned: []`).
210
-
211
- ### branches
212
-
213
- The merged-branch sweep recognizes three detection signals, in order:
214
-
215
- 1. **`detectedBy: 'gh'`** — the branch has a merged PR
216
- (`gh pr list --head <branch> --state all`, classified by the
217
- **latest** PR's state).
218
- 2. **`detectedBy: 'git-merged'`** — the branch is a git ancestor of
219
- `<base>` (`git branch --merged <base>`), or of `origin/<base>` when
220
- that remote-tracking ref exists (unioned so a stale local `<base>` —
221
- fast-forward phase skipped, or `--branches` run alone — no longer
222
- hides a branch already merged on the remote).
223
- 3. **`detectedBy: 'content-merged'`** (Story #4395) — the branch has no
224
- reapable PR verdict and is not an ancestor of `<base>` under either
225
- anchor, but simulating the merge via
226
- `git merge-tree --write-tree <base> <branch>` (git ≥ 2.38) produces a
227
- tree identical to `<base>`'s own tree — i.e. applying the branch's
228
- changes on top of `<base>` is a content no-op. This catches
229
- `story-<id>` branches whose PR **squash-merged** to `main` (the story
230
- commits are not ancestors of `main`), and any other branch whose content
231
- landed via a different route (a renamed
232
- head, a cherry-pick, a manual `merge --squash`). When git rejects
233
- `--write-tree` (git < 2.38) or the simulated merge conflicts, the
234
- probe is inconclusive and the branch keeps its existing `not-merged`
235
- skip — the signal never guesses. `content-merged` candidates render
236
- with a "weaker signal — verify before deleting" annotation in the
237
- dry-run list and are called out separately in the confirmation
238
- prompt, since — unlike a merged PR or git ancestry — no CI or GitHub
239
- merge check ever validated this branch's exact diff.
240
-
241
- Other candidate semantics:
242
-
243
- - A branch is a candidate iff it is not `<base>`, not the current HEAD,
244
- not in `git config branch.protectedBranches`, and matches one of the
245
- three signals above.
246
- - When a candidate has an attached worktree, the worktree is removed
247
- (force if dirty) **before** `git branch -D`, mirroring the pattern in
248
- [`worktree-lifecycle.md`](helpers/worktree-lifecycle.md).
249
- - `--remote` is required on top of `--execute` to touch `origin/`.
250
- - A throwing `gh` runner (auth failure, rate limit, missing binary) no
251
- longer aborts the run: the branches phase logs one warning and
252
- continues with the git-only signals (ancestry + content-equivalence).
253
- The JSON envelope's `ghDegraded: true` records that this happened for
254
- the run, and the dry-run text carries a matching warning line.
255
-
256
- The skip taxonomy:
257
-
258
- - `reason: 'protected'` — the base branch or a name in
259
- `git config branch.protectedBranches`. Not reapable; ignore.
260
- - `reason: 'current-head'` — the current branch. Reapable after
261
- `git checkout <base>`. The dry-run output surfaces a remediation
262
- hint so the operator sees the recovery path without having to look
263
- in the JSON envelope.
264
- - `reason: 'tip-diverged-from-merge'` — the latest PR merged, but the
265
- branch's tip has since moved past the merged commit (a post-merge
266
- force-push). The dry-run line names both SHAs and a remediation hint
267
- (delete manually via `git branch -D <branch>`, or push the follow-up
268
- commit).
269
- - `reason: 'not-merged'` — none of the three detection signals matched.
270
- Previously silent; the dry-run output now lists every surviving
271
- `not-merged` branch as a one-line-per-branch summary with its
272
- last-commit age, so the operator can see why a leftover branch isn't
273
- reaped instead of hunting for it by hand.
274
-
275
- Every default run also opts the planner into a **remote-only
276
- enumeration pass**: in addition to walking `refs/heads/*`, the planner
277
- also walks `refs/remotes/origin/*` and emits candidates for any branch
278
- that exists on `origin` with a merged PR but has no local ref. These
279
- candidates carry `detectedBy: 'remote-only'` and `localExists: false`
280
- and are always shown in the dry-run list; **deleting** them (via the
281
- `git push --delete origin/<branch>` path — no local `git branch -D` is
282
- attempted, since there is no local branch) still requires `--remote` on
283
- top of `--execute`, unchanged.
284
-
285
- ### stashes
286
-
287
- - Stashes are dropped high-index-first so the indices of remaining
288
- stashes stay stable across consecutive drops (git renumbers from the
289
- top of the stack).
290
- - In interactive mode (no `--yes`, no `--json`), each stash gets a
291
- prompt: `drop / keep / quit`. The default on bare ENTER is `keep`.
292
- - In `--yes` or `--json` mode, only refs explicitly named via
293
- `--drop-stashes <ref>` are dropped. Everything else is kept. There is
294
- intentionally no "drop all" shorthand.
295
-
296
- ## Constraint
297
-
298
- Do **not** run with `--execute` if there is unmerged work that needs
299
- saving. The fast-forward phase skips on a dirty tree (safe), but the
300
- branches phase will reap any merged-PR branch in scope — passing
301
- `--exclude '<pattern>'` is the only way to carve out exceptions. The
302
- remote reap (`--remote`) crosses `origin/` and cannot be undone without
303
- re-pushing.
@@ -0,0 +1,230 @@
1
+ # Audit lens core (shared contract)
2
+
3
+ > **Single source of truth for every audit lens's shared machinery
4
+ > (Story #4665).** Each `audit-<lens>.md` workflow references this file instead
5
+ > of re-stating the blocks below. A lens carries only its own frontmatter, a
6
+ > short preamble, its `{{changedFiles}}` fence, and its genuinely lens-specific
7
+ > dimensions / detection batteries / applicability gates / boundary
8
+ > demarcations. Everything a lens shares with every other lens — the read-only
9
+ > constraint, scope interpretation, the report envelope and finding-block
10
+ > skeleton, the severity scale, the self-cross-check, and the execution
11
+ > strategy — lives here.
12
+ >
13
+ > This file absorbs the three retired helpers `audit-severity-scale.md`,
14
+ > `audit-self-check.md`, and `audit-dual-path.md`.
15
+
16
+ ## Read-only constraint {#read-only}
17
+
18
+ Every audit lens is a **read-only** analysis. Do **not** modify application
19
+ code, styles, configuration, dependencies, branches, or labels. Surfacing the
20
+ findings is the deliverable; fixing them is a separate pass. The **only** write
21
+ a lens performs is its report artifact (plus, where a lens explicitly declares
22
+ it, a single measurement/baseline artifact named in that lens's own
23
+ Constraint). A lens that names a stricter or looser read-only carve-out in its
24
+ own body (e.g. performance's non-mutating measurements, quality's permitted
25
+ committed-baseline reads, data-model's no-database rule) refines this shared
26
+ constraint for that lens only.
27
+
28
+ ## Scope interpretation (Story / plan-run mode) {#scope-interpretation}
29
+
30
+ Each lens carries its own `{{changedFiles}}` fence — the substitution anchor
31
+ consumed by `.agents/scripts/lib/audit-suite/` — and reads it as follows:
32
+
33
+ - When this lens is invoked from `/deliver` close lenses (or a plan-run audit),
34
+ the fence is populated with the Story (or plan-run) change-set file list.
35
+ **Restrict analysis to those files** (and their direct dependencies when the
36
+ lens explicitly calls for cross-file reasoning).
37
+ - Otherwise — for any manual `/audit-<lens>` invocation — the fence renders the
38
+ literal string `{{changedFiles}}`. Treat that as **no scope filter — run the
39
+ lens codebase-wide** exactly as if the block were absent.
40
+
41
+ A handful of lenses deliberately deviate (documentation intersects the fence
42
+ with its config-driven target set; navigability always evaluates the whole
43
+ route tree regardless of the fence). Those lenses state their deviation in
44
+ their own Scope section; every other lens follows the rule above verbatim.
45
+
46
+ ## Report envelope & finding-block skeleton {#report-envelope}
47
+
48
+ Each lens writes exactly one structured Markdown report to
49
+ `{{auditOutputDir}}/audit-<lens>-results.md` (the lens preamble names its own
50
+ path). The report MUST include every section its lens template mandates — write
51
+ `_No findings._` rather than omitting a section — and always an
52
+ `## Executive Summary` and a `## Detailed Findings` section. The Executive
53
+ Summary carries the self-cross-check `kept <k> / dropped <d>` line
54
+ ([below](#self-cross-check)).
55
+
56
+ Every finding under `## Detailed Findings` uses the shared 7-field skeleton
57
+ below. A lens may **add** fields (e.g. a WCAG success criterion, a CWE ID, a
58
+ `Baseline MUST`, a `Route / Door` + `Persona(s)` pair, an `Evidence` tag) and
59
+ may **relabel** the two normalized axes — `Severity` ↔ `Impact`, `Dimension` ↔
60
+ `Category` ↔ `Type` — as its preamble declares; the parser
61
+ (`lib/audit-to-stories/parse-audit-md.js`) recognizes every variant. It never
62
+ drops or renames a shared field.
63
+
64
+ ```markdown
65
+ ## Executive Summary
66
+
67
+ [The lens's headline read plus the self-cross-check `kept <k> / dropped <d>`
68
+ line. A lens may mandate additional report sections between here and the
69
+ findings — its own body names them.]
70
+
71
+ ## Detailed Findings
72
+
73
+ [For every finding, use the following strict structure. Lead each title with
74
+ the primary file the finding lives in:]
75
+
76
+ ### `path/to/primary-file.ext` — [Short title of the issue]
77
+
78
+ - **Dimension:** [the lens-specific dimension — see the lens's own list]
79
+ - **Severity:** [Critical | High | Medium | Low]
80
+ - **Location:** `path/to/primary-file.ext:line`
81
+ - **Current State:** [the specific file/line/module and why it is problematic]
82
+ - **Recommendation & Rationale:** [how to remediate and why it matters]
83
+ - **Acceptance signal:** [the command or observable that proves this finding is
84
+ remediated — e.g. a grep that now returns empty, an added regression test, or
85
+ a re-run of this lens]
86
+ - **Agent Prompt:**
87
+ `[A copy-pasteable, highly specific prompt to execute this remediation independently]`
88
+ ```
89
+
90
+ ## Severity scale {#severity-scale}
91
+
92
+ Every finding grades its severity (labelled `Severity` or `Impact`) on this
93
+ ordered scale. `parse-audit-md.js` recognizes every level, and a surviving
94
+ **Critical** finding halts the delivery gate
95
+ (`lib/audit-suite/findings.js#hasSurvivingCritical`).
96
+
97
+ - **Critical** — an active, exploitable, or data-losing defect that must be
98
+ fixed before the change can ship (e.g. a leaked secret, an auth bypass, a
99
+ guaranteed production outage or data-loss path).
100
+ - **High** — a serious correctness, security, or maintainability risk that
101
+ should be fixed promptly, but does not by itself block the release.
102
+ - **Medium** — a real problem worth scheduling; contained blast radius, or a
103
+ reasonable workaround exists.
104
+ - **Low** — minor or cosmetic; fix opportunistically.
105
+
106
+ ## Self-cross-check (mandatory — filter false positives before you finalize) {#self-cross-check}
107
+
108
+ You are your own adversarial reviewer. After you have drafted the Detailed
109
+ Findings but **before** you write the report artifact, re-open every finding
110
+ and hold it to the bar below. This pass is **read-only** — it filters and
111
+ tightens the findings you already have; it never invents new ones. It gives the
112
+ sequential single-pass path the same false-positive filter the orchestrated
113
+ path's independent adversarial reviewer applies.
114
+
115
+ ### Per-finding evidence bar (keep or drop)
116
+
117
+ Keep a finding only when **all** of the following hold. Drop it otherwise.
118
+
119
+ - **Grounded location** — it names a concrete `path:line` (or a concrete
120
+ symbol / config key) that you have actually read, not a hypothetical or a
121
+ "somewhere in the codebase" claim.
122
+ - **Reproducible evidence** — the problem is backed by an observable: a tool
123
+ reading (a baseline row, a complexity/MI/duplication number, a failing
124
+ command), a quoted code snippet, or a specific standard it violates. A
125
+ finding whose entire basis is "this looks wrong" does not clear the bar.
126
+ - **In-scope** — when a change-set scope filter was supplied (the fence
127
+ resolved to a file list), the finding lives in that scope or a direct
128
+ dependency the lens explicitly reasons across. A finding outside the scope
129
+ filter is dropped, not reported.
130
+ - **Actionable** — the recommendation is specific enough to execute. Drop
131
+ vague exhortations ("improve error handling generally") that carry no
132
+ concrete change.
133
+
134
+ ### Exclusion list (never a finding)
135
+
136
+ Treat the following as **out of scope by construction** and drop any finding
137
+ that rests on one of them:
138
+
139
+ - **Sanctioned test seams** — exports consumed only by tests, and other
140
+ patterns the `test-seams` rule blesses. Never a production defect.
141
+ - **Entry points & public API surface** — CLI mains, `bin/` scripts, declared
142
+ `exports` / `bin` / `main`, and barrel contracts consumed out-of-tree. A
143
+ zero in-repo consumer count is not death.
144
+ - **Dynamic / framework reachability** — symbols reached via `import()`,
145
+ string-keyed dispatch, decorators, lifecycle listeners, or convention-loaded
146
+ plugin directories. Invisible to static analysis, not dead.
147
+ - **Intentional, documented deviations** — a pattern an in-tree comment, ADR,
148
+ or config explicitly sanctions. Cite it and drop the finding.
149
+ - **Style-only nits already enforced by a formatter/linter** — do not
150
+ re-litigate what the committed tooling already governs.
151
+
152
+ ### Final re-open-and-drop pass (mandatory)
153
+
154
+ 1. Walk your Detailed Findings once more, applying the bar and the exclusion
155
+ list above. Remove every finding that fails.
156
+ 2. Count what you kept (`k`) and what you dropped (`d`).
157
+ 3. Record the outcome in the report's **Executive Summary** as a single line:
158
+
159
+ ```text
160
+ Self-cross-check: kept <k> / dropped <d>.
161
+ ```
162
+
163
+ When `d > 0`, name the dropped findings (title + the bar/exclusion reason)
164
+ in one short list under that line, so the filtering is auditable and never
165
+ silent.
166
+
167
+ A lens that keeps every finding still records `dropped 0` — the line's absence
168
+ is itself a defect (it means the pass did not run).
169
+
170
+ ## Execution strategy {#execution-strategy}
171
+
172
+ A lens is a self-contained, read-only unit of work — exactly the shape a
173
+ role-scoped subagent is for. Run it along the first path below that is
174
+ available; every path emits the **identical** report contract (the finding-block
175
+ skeleton above), so downstream consumers (`audit-to-stories`) are agnostic to
176
+ which path produced it.
177
+
178
+ 1. **Subagent dispatch (first-class).** Dispatch the lens as a single
179
+ `subagent_type: auditor` call — the standalone boot context in
180
+ [`../../agents/auditor.md`](../../agents/auditor.md) carries the read-only
181
+ MUSTs, the finding-block skeleton, the severity scale, and the
182
+ self-cross-check bar, so the child needs only the lens's own dimensions to
183
+ run. The subagent returns the **report path plus the Executive Summary**
184
+ (including the self-cross-check line); the parent never needs the full
185
+ findings inline. This is the default: the auditor boots without the full
186
+ project closure, so the spawn is cheap relative to running the lens inline
187
+ in the parent's context.
188
+
189
+ - **Per-dimension fan-out (heavyweight lenses).** `audit-architecture`,
190
+ `audit-performance`, and `audit-documentation` carry enough independent
191
+ dimensions to be worth fanning out: dispatch one `subagent_type: auditor`
192
+ call **per dimension** in a single turn via
193
+ [`parallel-tooling.md`](parallel-tooling.md) Rule 3, then **merge** the
194
+ per-dimension findings under this file's self-cross-check (the merge is
195
+ where cross-dimension duplicates and false positives are dropped). Respect
196
+ the nesting-depth budget and the concurrency cap that Rule 3 documents.
197
+
198
+ 2. **Sequential inline execution (documented fallback).** When subagent
199
+ dispatch is unavailable, run the lens's steps turn-by-turn in the current
200
+ context exactly as written, ending with the self-cross-check. This changes
201
+ nothing about the report contract.
202
+
203
+ > **Orchestrated dynamic-workflow path (optimization note).** Six lenses ship a
204
+ > saved project workflow at `.claude/workflows/audit-<lens>.workflow.js` that,
205
+ > **when Claude Code dynamic workflows are available** (runtime is Claude Code,
206
+ > `disableWorkflows` unset, version `>= 2.1.154`), fans the dimensions out as
207
+ > parallel read-only subagents and runs an independent adversarial cross-check
208
+ > stage before synthesising the report. It derives its per-dimension prompts
209
+ > from the *lens* markdown at run time — the lens stays the single source of
210
+ > truth. This is a performance optimization over path 1, **not** a separate
211
+ > contract: strategy selection lives in
212
+ > [`../../scripts/lib/dynamic-workflow/capability.js`](../../scripts/lib/dynamic-workflow/capability.js)
213
+ > (`selectAuditStrategy`), and it is not covered by the No-Shim / hard-cutover
214
+ > rule in [`../../rules/git-conventions.md`](../../rules/git-conventions.md)
215
+ > because there is one report contract and only the execution strategy varies —
216
+ > the same capability-degradation pattern the protocol endorses for live-docs
217
+ > fallback. Force a path for testing with `MANDREL_AUDIT_STRATEGY=sequential`
218
+ > or `MANDREL_AUDIT_STRATEGY=orchestrated`; exercise the real disable signals
219
+ > with `CLAUDE_CODE_DISABLE_WORKFLOWS=1` or `disableWorkflows: true` in
220
+ > `.claude/settings.json`. On the orchestrated path the analysis subagents are
221
+ > granted only read/search tools (`Read`, `Grep`, `Glob`) — the single write is
222
+ > the final report artifact.
223
+
224
+ ## Parallel tooling {#parallel-tooling}
225
+
226
+ When a lens batches independent reads/greps, runs a long shell (a scanner, a
227
+ profiler, a suite time), or fans out per-dimension, apply
228
+ [`parallel-tooling.md`](parallel-tooling.md): batch independent reads in one
229
+ turn (Rule 1), run long shells via `run_in_background` + `Monitor` (Rule 2),
230
+ and dispatch N independent units as N `Agent` calls in one turn (Rule 3).
@@ -18,10 +18,6 @@ is merged to `main`. The live v2 path is **Story scope only**:
18
18
  after the PR opens and before auto-merge. Findings post to the PR;
19
19
  critical findings block close (`agent::blocked`).
20
20
 
21
- Legacy `scope: epic` / Epic-branch review procedure (including
22
- `epic-audit-prepare.js` / `epic-audit-recheck.js`) was removed with the
23
- v2 Story-only cutover.
24
-
25
21
  **Invariant — Story-scope review runs outside the maker's LLM context.**
26
22
  The Story-scope review executes inside the `single-story-close.js` close
27
23
  subprocess, **not** in the delivering child's (maker agent's) LLM context.
@@ -45,28 +41,21 @@ The caller passes the following arguments (`/deliver` passes
45
41
 
46
42
  | Argument | Type | Required | Meaning |
47
43
  | ----------- | ------------------------------------- | -------- | ------------------------------------------------------------------------------------------------ |
48
- | `scope` | `"story"` \| `"epic"` | yes | Live path is `"story"`. `"epic"` remains only for legacy cumulative adapters. |
49
- | `ticketId` | integer | yes | GitHub issue number of the Story (when `scope === 'story'`) or cumulative ticket (legacy `scope === 'epic'`). |
50
- | `baseRef` | string (git ref) | yes | The diff base. Story scope: `main` (or `project.baseBranch`). Legacy cumulative scope: caller-provided base. |
51
- | `headRef` | string (git ref) | yes | The branch tip under review. Story scope: `story-<storyId>`. Legacy cumulative scope: caller-provided head. |
44
+ | `scope` | `"story"` | yes | The live delivery path. |
45
+ | `ticketId` | integer | yes | GitHub issue number of the Story. |
46
+ | `baseRef` | string (git ref) | yes | The diff base `main` (or `project.baseBranch`). |
47
+ | `headRef` | string (git ref) | yes | The branch tip under review `story-<storyId>`. |
52
48
  | `depth` | `"light"` \| `"standard"` \| `"deep"` | no | Risk-derived review thoroughness lever. Absent → `standard`. See **Review depth** below. |
53
49
 
54
- All scope-dependent behavior in this helper branches off the first four
55
- arguments. Do not hard-code branch names or ticket types — read them from
56
- the argument envelope.
50
+ Do not hard-code branch names or ticket types read them from the argument
51
+ envelope.
57
52
 
58
53
  ### Review depth (`depth`)
59
54
 
60
- `depth` is the thoroughness lever introduced by Story #3876, made a live
61
- consumed signal end to end by Story #3937, and re-based on an observable signal
62
- by Story #4542. `runCodeReview` derives it from the diff it already enumerates,
63
- via [`review-depth.js`](../../scripts/lib/orchestration/review-depth.js): the
64
- changed files' intersection with the `sensitivePaths` classes registered in
65
- `audit-rules.json` gives the level, their count gives the width, and
66
- `resolveDepth` folds the two (a sensitive path OR a wide diff → `deep`; neither,
67
- on a small diff → `light`; an unenumerable diff → `standard`). It takes no
68
- planner-authored input and reads no checkpoint. `runCodeReview` forwards `depth`
69
- to every provider's `runReview` input.
55
+ `depth` is the thoroughness lever: `runCodeReview` derives it from the diff via
56
+ [`review-depth.js`](../../scripts/lib/orchestration/review-depth.js) (Story #4542
57
+ re-based it on that observable signal, so it takes no planner-authored input) and
58
+ forwards it to every provider's `runReview` input.
70
59
 
71
60
  It is an **input-only** signal: it changes *how thorough* the review is, never
72
61
  the findings envelope (`{ status, severity, posted, report, halted,
@@ -270,8 +259,7 @@ prior baseline before merging.
270
259
  Findings are **persisted as a `verification-results` structured comment on
271
260
  the `[TICKET_ID]` issue** by `runCodeReview` (the unified findings contract of
272
261
  Story #4411; this single comment carries the
273
- Epic-close lens findings). The target ticket is the Story when
274
- `scope === 'story'` and the Epic when `scope === 'epic'`. The comment
262
+ Story-scope lens findings). The target ticket is the Story. The comment
275
263
  is idempotent — re-runs replace the prior one — and its body includes
276
264
  severity-tier counts plus the full findings list so downstream workflows
277
265
  (notably the retro helper) can summarise blockers/high findings without