mandrel 2.24.0 → 2.26.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/.agents/agents/acceptance-critic.md +19 -21
- package/.agents/agents/auditor.md +16 -19
- package/.agents/agents/story-worker.md +41 -54
- package/.agents/audit-checklists/architecture.md +5 -1
- package/.agents/audit-checklists/baselines.md +21 -0
- package/.agents/audit-checklists/quality.md +3 -0
- package/.agents/docs/agentrc-reference.json +2 -1
- package/.agents/docs/configuration.md +2 -1
- package/.agents/docs/quality-gates.md +80 -18
- package/.agents/docs/workflows.md +4 -2
- package/.agents/instructions.md +1 -1
- package/.agents/rules/ci-remediation.md +68 -3
- package/.agents/schemas/agentrc.schema.json +6 -1
- package/.agents/schemas/audit-rules.json +15 -0
- package/.agents/schemas/baselines/audit-baselines-envelope.schema.json +242 -0
- package/.agents/schemas/baselines/baseline-envelope.schema.json +8 -0
- package/.agents/schemas/baselines/crap.schema.json +13 -1
- package/.agents/schemas/crap-report.schema.json +37 -0
- package/.agents/schemas/model-attribution.schema.json +4 -0
- package/.agents/scripts/acceptance-eval.js +124 -15
- package/.agents/scripts/audit-baselines.js +136 -0
- package/.agents/scripts/audit-labels-bootstrap.js +16 -95
- package/.agents/scripts/audit-to-stories.js +33 -3
- package/.agents/scripts/check-arch-cycles.js +12 -93
- package/.agents/scripts/check-baseline-drift.js +16 -3
- package/.agents/scripts/check-baselines.js +19 -3
- package/.agents/scripts/check-context-budget.js +95 -14
- package/.agents/scripts/check-cyclomatic.js +214 -0
- package/.agents/scripts/check-schema-references.js +392 -0
- package/.agents/scripts/check-test-temp-hygiene.js +121 -1
- package/.agents/scripts/check-workflow-timeouts.js +291 -0
- package/.agents/scripts/cleanup-repo-test-temp.js +7 -5
- package/.agents/scripts/deliver-light.js +3 -4
- package/.agents/scripts/deliver-recover.js +13 -0
- package/.agents/scripts/diagnose-friction.js +85 -19
- package/.agents/scripts/lib/audit-baselines/engine.js +177 -0
- package/.agents/scripts/lib/audit-baselines/gate-surface.js +63 -0
- package/.agents/scripts/lib/audit-baselines/headroom.js +72 -0
- package/.agents/scripts/lib/audit-baselines/hotspots.js +69 -0
- package/.agents/scripts/lib/audit-baselines/kinds.js +313 -0
- package/.agents/scripts/lib/audit-baselines/outliers.js +100 -0
- package/.agents/scripts/lib/audit-baselines/read.js +87 -0
- package/.agents/scripts/lib/audit-baselines/staleness.js +123 -0
- package/.agents/scripts/lib/audit-baselines/surface-entry.js +106 -0
- package/.agents/scripts/lib/audit-baselines/trend.js +125 -0
- package/.agents/scripts/lib/audit-baselines/weights.js +193 -0
- package/.agents/scripts/lib/audit-suite/index.js +0 -5
- package/.agents/scripts/lib/audit-suite/selector.js +9 -62
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +177 -0
- package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +1 -0
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +35 -3
- package/.agents/scripts/lib/audit-to-stories/group-findings.js +20 -11
- package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +23 -9
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +9 -2
- package/.agents/scripts/lib/baseline-loader.js +0 -0
- package/.agents/scripts/lib/baseline-schema-registry.js +13 -1
- package/.agents/scripts/lib/baselines/diff-scope-cli.js +22 -160
- package/.agents/scripts/lib/baselines/duplication-scanner.js +27 -0
- package/.agents/scripts/lib/baselines/git-base.js +0 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +499 -57
- package/.agents/scripts/lib/baselines/preview-gates.js +58 -15
- package/.agents/scripts/lib/baselines/reader.js +10 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +69 -11
- package/.agents/scripts/lib/baselines/scope.js +39 -90
- package/.agents/scripts/lib/baselines/writer.js +16 -7
- package/.agents/scripts/lib/changed-files.js +8 -1
- package/.agents/scripts/lib/cli-args.js +115 -1
- package/.agents/scripts/lib/close-validation/runner.js +70 -25
- package/.agents/scripts/lib/config-settings-schema-delivery.js +5 -0
- package/.agents/scripts/lib/crap-engine.js +124 -27
- package/.agents/scripts/lib/crap-method-identity.js +153 -0
- package/.agents/scripts/lib/crap-utils.js +86 -13
- package/.agents/scripts/lib/cyclomatic-ceiling.js +265 -0
- package/.agents/scripts/lib/env-loader.js +46 -16
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -2
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +0 -2
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +0 -2
- package/.agents/scripts/lib/findings/route-finding.js +123 -17
- package/.agents/scripts/lib/findings/severity.js +80 -2
- package/.agents/scripts/lib/git/cached-fetch.js +0 -0
- package/.agents/scripts/lib/git-utils.js +136 -80
- package/.agents/scripts/lib/import-graph.js +156 -0
- package/.agents/scripts/lib/label-constants.js +17 -0
- package/.agents/scripts/lib/observability/runtime-friction.js +17 -2
- package/.agents/scripts/lib/observability/source-classifier.js +175 -2
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +110 -17
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +17 -12
- package/.agents/scripts/lib/orchestration/check-baselines/phases/compare.js +36 -6
- package/.agents/scripts/lib/orchestration/check-baselines/phases/evaluate.js +5 -0
- package/.agents/scripts/lib/orchestration/check-baselines/phases/floors.js +12 -1
- package/.agents/scripts/lib/orchestration/check-baselines/phases/report.js +8 -1
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +548 -0
- package/.agents/scripts/lib/orchestration/deliver-recover.js +2 -2
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/phase-drivers.js +10 -5
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/render.js +39 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +71 -20
- package/.agents/scripts/lib/orchestration/light-backstop.js +20 -3
- package/.agents/scripts/lib/orchestration/light-escalation.js +82 -3
- package/.agents/scripts/lib/orchestration/light-suitability.js +103 -5
- package/.agents/scripts/lib/orchestration/plan-context.js +119 -66
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +31 -5
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +216 -109
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +61 -13
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +79 -22
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +51 -20
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +70 -74
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +231 -0
- package/.agents/scripts/lib/orchestration/resolve-stories.js +18 -17
- package/.agents/scripts/lib/orchestration/retro-proposals.js +0 -0
- package/.agents/scripts/lib/orchestration/run-epilogue.js +12 -0
- package/.agents/scripts/lib/orchestration/run-scoped-config.js +276 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +41 -10
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +125 -37
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +6 -6
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +42 -38
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +129 -21
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +6 -1
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +267 -125
- package/.agents/scripts/lib/orchestration/spec-budget.js +16 -5
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +182 -95
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +22 -0
- package/.agents/scripts/lib/orchestration/ticket-validator.js +5 -11
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +4 -4
- package/.agents/scripts/lib/reserved-test-ids.js +77 -0
- package/.agents/scripts/lib/story-adjacency.js +3 -3
- package/.agents/scripts/lib/story-plan.js +137 -42
- package/.agents/scripts/lib/templates/decomposer-prompts.js +4 -4
- package/.agents/scripts/lib/test-runner-contract.js +134 -0
- package/.agents/scripts/lib/test-tiers.js +11 -2
- package/.agents/scripts/lib/ticket-body-sections.js +1 -1
- package/.agents/scripts/lib/util/concurrent-map.js +17 -0
- package/.agents/scripts/lib/util/parse-id-list.js +103 -0
- package/.agents/scripts/lib/util/poll-loop.js +18 -2
- package/.agents/scripts/lib/wave-runner/live-probe.js +29 -13
- package/.agents/scripts/lib/wave-runner/ready-set.js +254 -43
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +10 -13
- package/.agents/scripts/lib/workers/crap-worker.js +8 -13
- package/.agents/scripts/lib/workers/maintainability-report-worker.js +4 -10
- package/.agents/scripts/lib/workers/maintainability-worker.js +4 -10
- package/.agents/scripts/lib/workers/serve-worker-messages.js +35 -0
- package/.agents/scripts/lib/worktree/git-hooks.js +206 -0
- package/.agents/scripts/lib/worktree/lifecycle/creation.js +6 -0
- package/.agents/scripts/lib/worktree-manager.js +14 -0
- package/.agents/scripts/plan-run-epilogue.js +17 -5
- package/.agents/scripts/pr-watch-with-update.js +568 -213
- package/.agents/scripts/providers/github/tickets.js +33 -10
- package/.agents/scripts/provision-git-hooks.js +85 -0
- package/.agents/scripts/quality-preview.js +137 -28
- package/.agents/scripts/resolve-stories.js +4 -1
- package/.agents/scripts/run-coverage.js +86 -35
- package/.agents/scripts/run-lint.js +20 -0
- package/.agents/scripts/run-tests.js +36 -36
- package/.agents/scripts/single-story-close.js +34 -2
- package/.agents/scripts/single-story-confirm-merge.js +22 -6
- package/.agents/scripts/single-story-init.js +7 -0
- package/.agents/scripts/stories-wave-tick.js +308 -47
- package/.agents/scripts/story-plan.js +65 -9
- package/.agents/scripts/update-coverage-baseline.js +34 -4
- package/.agents/scripts/update-crap-baseline.js +42 -4
- package/.agents/scripts/update-duplication-baseline.js +209 -83
- package/.agents/scripts/update-maintainability-baseline.js +31 -4
- package/.agents/scripts/validate-docs-freshness.js +1 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +4 -1
- package/.agents/skills/core/gates-and-baselines/SKILL.md +17 -11
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/templates/single-story-body.md +16 -8
- package/.agents/workflows/audit-architecture.md +69 -13
- package/.agents/workflows/audit-baselines.md +289 -0
- package/.agents/workflows/audit-navigability.md +5 -4
- package/.agents/workflows/audit-quality.md +26 -0
- package/.agents/workflows/audit-to-stories.md +30 -4
- package/.agents/workflows/deliver.md +92 -97
- package/.agents/workflows/helpers/acceptance-self-eval.md +47 -10
- package/.agents/workflows/helpers/audit-lens-core.md +25 -4
- package/.agents/workflows/helpers/code-quality-guardrails.md +9 -2
- package/.agents/workflows/helpers/deliver-digest.md +41 -21
- package/.agents/workflows/helpers/deliver-reference.md +95 -4
- package/.agents/workflows/helpers/deliver-story-reference.md +174 -52
- package/.agents/workflows/helpers/deliver-story.md +104 -119
- package/.agents/workflows/helpers/plan-reference.md +49 -5
- package/.agents/workflows/memory-consolidate.md +116 -0
- package/.agents/workflows/plan.md +62 -75
- package/README.md +13 -6
- package/docs/CHANGELOG.md +93 -0
- package/lib/cli/update.js +14 -11
- package/lib/cli/version-check.js +9 -1
- package/package.json +9 -4
- package/.agents/schemas/friction-event.schema.json +0 -56
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +0 -707
|
@@ -18,7 +18,7 @@ allowed_tools:
|
|
|
18
18
|
- **No gate may be skipped.** Failing lint means fix lint, not disable the rule; a failing test means fix the code, not `.skip` or delete the test. Gates are ordered shift-left so cheap checks fail first, and CI failure output is fed back verbatim with the directive to reproduce and fix locally before re-pushing.
|
|
19
19
|
- **Introducing a gate that asserts on pre-existing state** (doc-drift, lint-vocabulary, dependency-cycle, missing-coverage) MUST land green at merge: either advisory-first (report-only until the backlog is burned down) or with the populated baseline committed in the same change that turns the gate on. Never wire a gate into `requiredChecks` that lands red on latent findings nobody authored.
|
|
20
20
|
- **Refresh a baseline only when the change is deliberate** — a rename/move, an operator-approved complexity bump, a signed-off perf delta, an intentional API-surface change. Never refresh to paper over an unintentional regression; fix the regression instead.
|
|
21
|
-
-
|
|
21
|
+
- **Pick the refresh that matches why you are refreshing** — the two are different scopes, and the wrong one silently no-ops. `npm run <kind>:update` is **diff-scoped**: it re-scores only files changed in `origin/main..HEAD` and preserves every other row verbatim. That is what you want after changing code. `npm run <kind>:reanchor` is the same script with `--full-scope`: it re-scores every file in every target dir. That is what you want when the baseline is **stale** — a scorer-version bump, a coverage-shape change, accumulated drift — because a diff-scoped run would leave the stale rows exactly as they were and the staleness finding would re-fire. Kinds: `coverage`, `crap`, `maintainability`, `duplication`; dead-exports and lighthouse have no npm script — regenerate the rows and edit `baselines/dead-exports*.json` / `baselines/lighthouse.json` directly. Run either on the **Story branch**, not on `main`.
|
|
22
22
|
- Verify the refresh diff is scoped to the relevant `baselines/<kind>.json` (plus cosmetic `package-lock.json` churn only). If unrelated files appear, STOP — the refresh is contaminated. Stage baseline files **explicitly** (`git add baselines/<kind>.json`); never `git add -A` in a refresh commit.
|
|
23
23
|
- Commit-subject contract: a **Conventional-Commits** subject `chore(baselines): refresh <kind> snapshot for <reason>` — never an ad-hoc leading token like `baseline-refresh:` (commitlint and the planner validator reject it). The body is **mandatory** and non-empty: what changed, why the new floor is correct, and the Story that triggered it.
|
|
24
24
|
- Add the machine-readable trailer `baseline-refresh: true` (git-trailer `Key: value` style) and `Story: #<storyId>` to the body whenever observability classification matters. Never pass `--no-verify`; the `commit-msg` hook (commitlint) MUST run and pass.
|
|
@@ -95,18 +95,24 @@ own as the parseable marker for any future reader.)
|
|
|
95
95
|
|
|
96
96
|
### Procedure
|
|
97
97
|
|
|
98
|
-
| Kind |
|
|
99
|
-
| --------------- |
|
|
100
|
-
| CRAP | `npm run crap:update`
|
|
101
|
-
| Maintainability | `npm run maintainability:update` |
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
98
|
+
| Kind | After a code change (diff-scoped) | Stale baseline (full re-anchor) |
|
|
99
|
+
| --------------- | ---------------------------------- | ------------------------------------ |
|
|
100
|
+
| CRAP | `npm run crap:update` | `npm run crap:reanchor` |
|
|
101
|
+
| Maintainability | `npm run maintainability:update` | `npm run maintainability:reanchor` |
|
|
102
|
+
| Coverage | `npm run coverage:update` | `npm run coverage:reanchor` |
|
|
103
|
+
| Duplication | `npm run duplication:update` | `npm run duplication:reanchor` |
|
|
104
|
+
| Dead-exports | edit `baselines/dead-exports.json` / `baselines/dead-exports-production.json` (rows are `(file, symbol)`; `check-dead-exports.js --json` prints the current rows) | same — no scope flag applies |
|
|
105
|
+
| Lighthouse | edit `baselines/lighthouse.json` | same — no scope flag applies |
|
|
106
|
+
|
|
107
|
+
1. **Run the matching command for your reason** — left column after a code
|
|
108
|
+
change, right column for a stale baseline — on the Story branch (HEAD must
|
|
109
|
+
already be the Story branch, not `main`).
|
|
107
110
|
2. **Verify the diff is scoped** to the relevant `baselines/<kind>.json` (and
|
|
108
111
|
possibly cosmetic `package-lock.json` churn). Unrelated files → STOP, the
|
|
109
|
-
refresh is contaminated.
|
|
112
|
+
refresh is contaminated. Judge contamination by **which files** changed, not
|
|
113
|
+
how many rows moved inside the baseline: a `:reanchor` legitimately rewrites
|
|
114
|
+
most of its own file, so say in the commit body that the breadth is a
|
|
115
|
+
re-anchor and not a mass regression.
|
|
110
116
|
3. **Author the commit.** Stage the baseline file(s) explicitly, then commit with
|
|
111
117
|
the Conventional-Commits subject + body trailer above:
|
|
112
118
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-
|
|
2
|
+
"generatedAt": "2026-08-02T11:35:53.522Z",
|
|
3
3
|
"generator": "generate-skills-index.js@1",
|
|
4
4
|
"skills": [
|
|
5
5
|
{
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"category": "core",
|
|
49
49
|
"path": ".agents/skills/core/diagnose-friction/SKILL.md",
|
|
50
50
|
"description": "Wrap a shell command with diagnostic capture. On failure, print static suggestions and append a structured `friction` record to the per-Story signals.ndjson stream. Use whenever a script in the orchestration loop invokes a tool whose failure shape we want the analyzer to attribute.",
|
|
51
|
-
"policyCapsuleBullets":
|
|
51
|
+
"policyCapsuleBullets": 9,
|
|
52
52
|
"allowedTools": ["Bash", "Read"],
|
|
53
53
|
"vendor": null
|
|
54
54
|
},
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
# {{title}}
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Goal
|
|
4
4
|
|
|
5
|
-
{{
|
|
5
|
+
{{goal}}
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Spec
|
|
8
8
|
|
|
9
|
-
{{
|
|
9
|
+
{{spec}}
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Changes
|
|
12
12
|
|
|
13
|
-
{{
|
|
13
|
+
{{changes}}
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Non-Goals
|
|
16
16
|
|
|
17
|
-
{{
|
|
17
|
+
{{nonGoals}}
|
|
18
|
+
|
|
19
|
+
<!--
|
|
20
|
+
`acceptance[]` and `verify[]` are the ticket's top-level machine contract.
|
|
21
|
+
Author each list ONCE, at the top level (`--acceptance` / `--verify`); do
|
|
22
|
+
NOT mirror them into this body — persist synthesizes the `## Acceptance`
|
|
23
|
+
and `## Verify` sections from those arrays so the issue still reads as a
|
|
24
|
+
complete executable document.
|
|
25
|
+
-->
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Audit architectural boundaries, module coupling, and
|
|
2
|
+
description: Audit architectural boundaries, module coupling, layering violations, and shipped-but-uncalled seams; emit a structured findings report keyed to the canonical severity scale.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Architecture & Clean Code Audit
|
|
@@ -57,9 +57,14 @@ the failure mode this lens exists to prevent.
|
|
|
57
57
|
node .agents/scripts/check-dead-exports.js
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
Each unreferenced export is a grounded candidate. **Cede
|
|
61
|
-
audit-clean-code's Dead Code dimension rather
|
|
62
|
-
the deferral in Step 2)
|
|
60
|
+
Each unreferenced export is a grounded candidate. **Cede the
|
|
61
|
+
merely-unreferenced ones** to audit-clean-code's Dead Code dimension rather
|
|
62
|
+
than re-deriving them here (see the deferral in Step 2); keep the ones that
|
|
63
|
+
are dead *wiring* — a seam a delivery shipped that no live production path
|
|
64
|
+
reaches — for this lens's Shipped-But-Never-Wired dimension. Note that this
|
|
65
|
+
checker only sees unreferenced symbols, so it cannot surface the worst shape
|
|
66
|
+
(a writer with no reader, both fully referenced); that one you must trace by
|
|
67
|
+
hand. When the shipped checker is unavailable, fall back
|
|
63
68
|
to `npx knip --production` — and heed the `!`-suffix entry-pattern caveat
|
|
64
69
|
that [`audit-clean-code`](audit-clean-code.md) documents, since
|
|
65
70
|
`knip --production` is a silent no-op without it.
|
|
@@ -116,21 +121,35 @@ legitimately have no layered architecture to guard.
|
|
|
116
121
|
|
|
117
122
|
## Step 2: Analysis Dimensions
|
|
118
123
|
|
|
119
|
-
For every finding you surface, grade **Impact** on
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
For every finding you surface, grade **Impact** on the **canonical severity
|
|
125
|
+
scale** — `Critical | High | Medium | Low | Info`, defined in the core's
|
|
126
|
+
[Severity scale](helpers/audit-lens-core.md#severity-scale) and owned by
|
|
127
|
+
`lib/findings/severity.js`. This lens relabels the axis `Impact` (it reflects how
|
|
128
|
+
much correctness, maintainability, or testability the gap erodes), but the
|
|
129
|
+
*levels* are the shared five and nothing else: a narrower or invented vocabulary
|
|
130
|
+
resolves to no severity and the finding is dropped from every severity-filtered
|
|
131
|
+
run. In particular an architectural defect that is actively data-losing or
|
|
132
|
+
release-blocking grades **Critical**, and a grounded observation that asks for no
|
|
133
|
+
scheduled work grades **Info** — neither collapses into `High` or `Low`.
|
|
134
|
+
|
|
135
|
+
Impact is independent of the **Category** effort axis — a Quick Win can still be
|
|
136
|
+
High Impact, and a Structural Change can be Medium. As a loose default, Quick
|
|
137
|
+
Wins typically land High (cheap to fix, real payoff) and Structural Changes
|
|
138
|
+
Medium/High, but grade Impact on the risk itself rather than deriving it
|
|
139
|
+
mechanically from Category.
|
|
126
140
|
|
|
127
141
|
> **Boundary with `audit-clean-code`.** The clean-code-overlapping smells
|
|
128
142
|
> (over-engineering & abstractions, cognitive load & nesting, dead code &
|
|
129
143
|
> redundancy, naming & self-documentation, coupling & cohesion) are owned by
|
|
130
144
|
> [`audit-clean-code`](audit-clean-code.md); the Step 0 dead-export candidates
|
|
131
145
|
> flow into its Dead Code dimension. Do **not** duplicate them here. This lens
|
|
132
|
-
> keeps only the
|
|
133
|
-
> testable-surface boundary
|
|
146
|
+
> keeps only the three structural dimensions no other lens owns — the
|
|
147
|
+
> testable-surface boundary, the automated-guardrail maturity, and
|
|
148
|
+
> shipped-but-never-wired seams. The split with clean-code's Dead Code dimension
|
|
149
|
+
> is by *question asked*: clean-code asks whether a symbol is referenced at all,
|
|
150
|
+
> this lens asks whether a **live production path** reaches it. An unreferenced
|
|
151
|
+
> helper is clean-code's; a fully-referenced writer whose reader was never built
|
|
152
|
+
> is this lens's.
|
|
134
153
|
|
|
135
154
|
Evaluate the gathered context against the following architecture dimensions:
|
|
136
155
|
|
|
@@ -208,6 +227,43 @@ Evaluate the gathered context against the following architecture dimensions:
|
|
|
208
227
|
full maturity assessment runs only in codebase-wide mode (when
|
|
209
228
|
`{{changedFiles}}` renders as the literal substitution token).
|
|
210
229
|
|
|
230
|
+
3. **Shipped-But-Never-Wired Seams (mandatory).** Report code this repository
|
|
231
|
+
ships that **no live production caller ever reaches**. This is the failure
|
|
232
|
+
mode the per-Story suite structurally cannot catch: every piece passes its own
|
|
233
|
+
unit tests, the diff looks complete, and the assembled path is dead — so it is
|
|
234
|
+
found only after delivery, if at all. A green suite is not evidence of a live
|
|
235
|
+
path; only a caller is.
|
|
236
|
+
|
|
237
|
+
Walk the seam **from the consumer backwards**, not from the producer forwards.
|
|
238
|
+
For each candidate, name the production entry point you traced to — or state
|
|
239
|
+
that you could not reach one, which is the finding. Cover at least:
|
|
240
|
+
|
|
241
|
+
- **A produced-but-never-consumed artifact.** Something the code computes,
|
|
242
|
+
stamps, writes, or returns that nothing downstream ever reads: an envelope
|
|
243
|
+
field no consumer parses, a file or log written and never opened, a
|
|
244
|
+
provenance marker stamped by the writer with no reader. Both halves must
|
|
245
|
+
exist for the feature to work, and shipping only the writer looks exactly
|
|
246
|
+
like shipping the feature.
|
|
247
|
+
- **An optional field nothing populates.** The mirror image: a parameter,
|
|
248
|
+
config key, or schema property a consumer reads and branches on that no
|
|
249
|
+
caller ever sets. The branch is unreachable, so the behaviour it guards has
|
|
250
|
+
never once run, and the default silently *is* the behaviour.
|
|
251
|
+
- **An exported seam with no in-tree caller** that the core's exclusion list
|
|
252
|
+
does not bless — not a test seam, not a CLI/`exports` entry point, not
|
|
253
|
+
dynamically reached. Cite the exclusion and drop it when it is one of those;
|
|
254
|
+
the Step 0 dead-export reading is the grounding instrument here, and the
|
|
255
|
+
candidates it surfaces that are genuinely dead **internal** wiring belong in
|
|
256
|
+
this dimension rather than ceded to clean-code's Dead Code dimension.
|
|
257
|
+
|
|
258
|
+
Grade Impact by what the dead wiring was supposed to do: a dead *guard, gate,
|
|
259
|
+
or enforcement path* is **High** or **Critical** — the protection it was
|
|
260
|
+
shipped to provide has never been in force, and the gate reads green because
|
|
261
|
+
it never runs. Dead reporting or convenience wiring is **Medium**. Each
|
|
262
|
+
finding's **Acceptance signal** must be the observable that proves the path is
|
|
263
|
+
live: a test that fails when the caller is removed, or a trace from the
|
|
264
|
+
production entry point to the seam. "Added a unit test for the seam" is not
|
|
265
|
+
that signal — the seam already had one.
|
|
266
|
+
|
|
211
267
|
### Maturity Rubric
|
|
212
268
|
|
|
213
269
|
Use these definitions to classify the project's `Architecture Guardrail
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Audit the committed baseline surface — dead instruments, stale baselines, cross-gate hotspot clusters, trend drift, and floor-tightening headroom — and emit findings whose remediation burns the measured debt down and tightens the ratchet behind it.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Baseline & Ratchet Audit
|
|
6
|
+
|
|
7
|
+
You are a Principal Engineer and Quality-Systems Owner auditing this
|
|
8
|
+
repository's **committed baseline surface**: the ratchet artifacts under
|
|
9
|
+
`baselines/` and the gate floors under `delivery.quality.gates` in
|
|
10
|
+
`.agentrc.json`. Those instruments only prevent **regression** — nothing owns
|
|
11
|
+
the loop that burns the measured debt **down** and tightens the floors behind
|
|
12
|
+
it, so a repo can hold a floor it cleared years ago and never notice. This lens
|
|
13
|
+
is that loop's read-only entry point. The shared lens machinery — read-only
|
|
14
|
+
constraint, scope interpretation, report envelope + finding-block skeleton,
|
|
15
|
+
severity scale, self-cross-check, and execution strategy — lives in
|
|
16
|
+
[`helpers/audit-lens-core.md`](helpers/audit-lens-core.md). Write the report to
|
|
17
|
+
`{{auditOutputDir}}/audit-baselines-results.md`. Dimension values:
|
|
18
|
+
`Dead Instrument | Staleness | Hotspot Cluster | Trend Drift | Tightening Headroom`.
|
|
19
|
+
|
|
20
|
+
> **Value-free titles (mandatory).** A finding title MUST NOT embed a measured
|
|
21
|
+
> number — write ``### `baselines/crap.json` — crap floor holds unused slack``,
|
|
22
|
+
> not ``… — crap floor 13 vs measured 8``. Every re-run re-measures, so a title
|
|
23
|
+
> carrying the reading changes each pass, its fingerprint changes with it, and
|
|
24
|
+
> `/audit-to-stories` files a duplicate instead of deduping against the open
|
|
25
|
+
> Story. Numbers belong in Current State, never in the title.
|
|
26
|
+
|
|
27
|
+
## Scope
|
|
28
|
+
|
|
29
|
+
Interpret this lens's change-set fence per the core's Scope interpretation:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
{{changedFiles}}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
When the fence resolves to a file list, keep only Hotspot Cluster findings
|
|
36
|
+
whose cluster key is in that list. The other four dimensions are properties of
|
|
37
|
+
the instrument set as a whole rather than of any changed file, so report them
|
|
38
|
+
only in codebase-wide mode.
|
|
39
|
+
|
|
40
|
+
## Constraint (lens-specific carve-out)
|
|
41
|
+
|
|
42
|
+
This lens **refines** the core's read-only constraint; it never relaxes it.
|
|
43
|
+
|
|
44
|
+
- The only command it runs is the read-only engine in Step 0. It never runs a
|
|
45
|
+
test, coverage, mutation, duplication, or lint suite.
|
|
46
|
+
- It never writes under `baselines/` and never edits `.agentrc.json`. It never
|
|
47
|
+
invokes an `update-*-baseline` script. **Regeneration is a finding, never an
|
|
48
|
+
in-run step** — the remediation Story owns every write to the surface.
|
|
49
|
+
- Reading committed baseline rows is explicitly permitted and required: citing
|
|
50
|
+
an already-computed metric is analysis, not measurement.
|
|
51
|
+
|
|
52
|
+
## Execution strategy
|
|
53
|
+
|
|
54
|
+
Run this lens as a single `subagent_type: auditor` dispatch returning the report
|
|
55
|
+
path + Executive Summary; sequential inline execution is the fallback (see the
|
|
56
|
+
core's Execution strategy).
|
|
57
|
+
|
|
58
|
+
## Step 0: Run the engine (mandatory — measure before you judge)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
node .agents/scripts/audit-baselines.js --out temp/audit-baselines/envelope.json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Optional flags: `--cwd` (repository root), `--top-n` (outlier rows per gate,
|
|
65
|
+
default 20), `--hotspot-limit` (clusters emitted, default 50), `--trend-depth`
|
|
66
|
+
(baseline commits sampled per kind, default 5). Exit 0 means evidence was
|
|
67
|
+
assembled **including every degraded input**; exit 1 means the envelope could
|
|
68
|
+
not be built or written — report that and stop, because no evidence base
|
|
69
|
+
exists to author against.
|
|
70
|
+
|
|
71
|
+
The envelope is validated against
|
|
72
|
+
`.agents/schemas/baselines/audit-baselines-envelope.schema.json` and is this
|
|
73
|
+
lens's sole evidence base. Cite its fields by name:
|
|
74
|
+
|
|
75
|
+
| Section | Fields you cite |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| root | `generatedAt`, `cwd`, `topN`, `configError`, `degradations` |
|
|
78
|
+
| `gateSurface[]` | `kind`, `surface`, `baselinePath`, `configured`, `baselineExists`, `stub`, `rowCount`, `measured` (`unit` plus `value`), `generatedAt`, `staleDays`, `staleCommits`, `surfaceStale`, `deadIgnoreGlobs`, `parseError` |
|
|
79
|
+
| `hotspots[]` | `path`, `gates` (each `kind`, `metric`, `value`, `rowCount`, `severityWeight`), `gateKinds`, `gateCount`, `severityWeight`, `churnWeight`, `centralityWeight`, `frictionWeight`, `rank` |
|
|
80
|
+
| `trend[]` | `kind`, `baselinePath`, `sampleCount`, `from`, `to` (each a `sha` plus `committedAt`), `deltas` |
|
|
81
|
+
| `headroom[]` | `kind`, `axis`, `floor`, `measured`, `direction`, `headroom` |
|
|
82
|
+
|
|
83
|
+
`surface` is `gate` (a closed `delivery.quality.gates` kind) or `ratchet` (an
|
|
84
|
+
out-of-band baseline the CI baselines job owns). `direction` is `gte` or `lte`.
|
|
85
|
+
`rowCount` counts rows **after** per-file aggregation; `measured` is the
|
|
86
|
+
quantity the instrument reports, in its own unit. Cite `measured` when the two
|
|
87
|
+
disagree — 589 dead-export symbols sit in 187 files.
|
|
88
|
+
|
|
89
|
+
Two envelope-level reads come **before** any finding:
|
|
90
|
+
|
|
91
|
+
- **`configError` non-null.** Floors, target directories, and ignore globs
|
|
92
|
+
were unavailable and the engine fell back to default baseline paths. Every
|
|
93
|
+
Tightening Headroom finding would be unfounded this run: file the config
|
|
94
|
+
failure itself as one `Dead Instrument` finding and skip that dimension.
|
|
95
|
+
- **`degradations`.** Each of `gitHistory`, `importGraph`, `frictionLedger`
|
|
96
|
+
reading `true` collapsed its rank multiplier to exactly 1.0, so the hotspot
|
|
97
|
+
ordering is weaker evidence. Name the degraded inputs in the Executive
|
|
98
|
+
Summary; never present a degraded rank as a churn-informed one.
|
|
99
|
+
|
|
100
|
+
## Step 1: Evaluation Dimensions
|
|
101
|
+
|
|
102
|
+
1. **Dead Instruments.** An instrument that cannot fail is worse than none: it
|
|
103
|
+
reads green forever and the surface it names looks governed. Four shapes,
|
|
104
|
+
read straight off `gateSurface[]`:
|
|
105
|
+
- `stub` is `true` — zero rows **and** an all-zero rollup, so the gate
|
|
106
|
+
passes vacuously. The engine requires both halves, so a ratchet with
|
|
107
|
+
genuinely nothing to report is never mistaken for a dead one.
|
|
108
|
+
- `configured` is `false` on a `gate` row — a baseline is committed but no
|
|
109
|
+
`delivery.quality.gates` block enforces it, so nothing reads it.
|
|
110
|
+
- `baselineExists` is `false`, or `parseError` is non-null — the instrument
|
|
111
|
+
cannot be read at all.
|
|
112
|
+
- `deadIgnoreGlobs` is non-empty — a configured ignore pattern matches zero
|
|
113
|
+
files. It protects nothing today and silently exempts the next file that
|
|
114
|
+
happens to match it.
|
|
115
|
+
|
|
116
|
+
Grade a stub or unenforced gate **Medium**, a `parseError` on an enforced
|
|
117
|
+
gate **High** (delivery reads that file every run), a dead glob **Low**.
|
|
118
|
+
|
|
119
|
+
2. **Staleness.** Two clocks. `staleDays` is whole days since the baseline's
|
|
120
|
+
own `generatedAt`; `staleCommits` is commits touching the measured surface
|
|
121
|
+
since the baseline was last committed, with `surfaceStale` its boolean. A
|
|
122
|
+
`null` on either is never a fabricated zero — the stamp is unreadable, git
|
|
123
|
+
cannot answer, or the rows are not file paths — and is itself the finding.
|
|
124
|
+
**`surfaceStale` with `staleDays: 0` is still stale:** refreshed recently in
|
|
125
|
+
wall time, already behind the surface it scores. Grade an enforced gate
|
|
126
|
+
stale beyond roughly a month or `surfaceStale` **Medium**, a `null` stamp
|
|
127
|
+
**Medium**, an unenforced kind **Low** or **Info**.
|
|
128
|
+
|
|
129
|
+
**Regeneration is the remediation, never an in-run step.** The Agent Prompt
|
|
130
|
+
names the matching script and its one-shot acknowledgment:
|
|
131
|
+
|
|
132
|
+
| Kind | Regeneration script | Acknowledgment |
|
|
133
|
+
| --- | --- | --- |
|
|
134
|
+
| `coverage` | `npm run coverage:reanchor` | `COVERAGE_REFRESH=1` |
|
|
135
|
+
| `crap` | `npm run crap:reanchor` | `CRAP_REFRESH=1` |
|
|
136
|
+
| `duplication` | `npm run duplication:reanchor` | `DUPLICATION_REFRESH=1` |
|
|
137
|
+
| `maintainability` | `npm run maintainability:reanchor` | `MAINTAINABILITY_REFRESH=1` |
|
|
138
|
+
|
|
139
|
+
**Prescribe the `:reanchor` script, never the bare `:update` one.** Every
|
|
140
|
+
updater defaults to a **diff-scoped** refresh — only files changed in
|
|
141
|
+
`origin/main..HEAD` are re-scored, and everything else is preserved
|
|
142
|
+
verbatim. That is the right default for "I changed code, re-score what I
|
|
143
|
+
touched", and it is exactly wrong here: a baseline is stale because the
|
|
144
|
+
*world* moved (a scorer bump, a coverage-shape change, months of unrelated
|
|
145
|
+
drift), so a diff-scoped run leaves almost every stale row untouched and
|
|
146
|
+
the staleness finding re-fires on the next sweep. `:reanchor` is the same
|
|
147
|
+
script with `--full-scope`, which re-scores every file in every target
|
|
148
|
+
dir. Confirm the flag on any kind you are unsure of with that script's
|
|
149
|
+
`--help`.
|
|
150
|
+
|
|
151
|
+
Expect a re-anchor to touch far more rows than a code change would — that
|
|
152
|
+
breadth is the point, but say so in the finding so a reviewer can tell a
|
|
153
|
+
re-anchor from a mass regression.
|
|
154
|
+
|
|
155
|
+
The acknowledgment is the kind upper-snaked. It demotes that kind's
|
|
156
|
+
head-vs-base regressions to unchanged **for one run only** — floors stay
|
|
157
|
+
enforced, so a genuine breach is still caught. The durable equivalent is a
|
|
158
|
+
commit in the compared range whose subject carries the gate's `refreshTag`
|
|
159
|
+
(default `baseline-refresh:`) **and** whose diff touches that kind's
|
|
160
|
+
baseline file. Confirm both against
|
|
161
|
+
`node .agents/scripts/check-baselines.js --help` before writing the prompt,
|
|
162
|
+
and never invent an acknowledgment for a kind that ships no regeneration
|
|
163
|
+
script — there, the remediation is to add one, not to hand-edit rows.
|
|
164
|
+
|
|
165
|
+
3. **Hotspot Clusters.** `hotspots[]` is already the cross-gate join, one entry
|
|
166
|
+
per cluster key, ranked highest first. **Emit one finding per cluster —
|
|
167
|
+
never one per metric row.** A file that is a CRAP outlier and a
|
|
168
|
+
maintainability outlier is one debt item with two symptoms; splitting it
|
|
169
|
+
files two Stories that fight over the same refactor. The cluster key is a
|
|
170
|
+
repository file path for every kind except `lighthouse` (a route) and
|
|
171
|
+
`bundle-size` (a bundle name) — say which it is when it is not a file.
|
|
172
|
+
|
|
173
|
+
Quote `rank` with the four factors behind it — `severityWeight`,
|
|
174
|
+
`churnWeight`, `centralityWeight`, `frictionWeight` — and the per-gate rows
|
|
175
|
+
under `gates`. Grade by breadth first: three or more entries in `gateKinds`
|
|
176
|
+
is **High**, two is **Medium**, one is **Low** unless its `severityWeight`
|
|
177
|
+
alone is extreme.
|
|
178
|
+
|
|
179
|
+
4. **Trend Drift.** `trend[]` carries newest-versus-previous rollup deltas per
|
|
180
|
+
kind, bracketed by the commits in `from` and `to`. A delta moving **away**
|
|
181
|
+
from the floor is the finding; one moving toward it is headroom the next
|
|
182
|
+
dimension owns. Read the axis's `direction` in `headroom[]` before assigning
|
|
183
|
+
a sign — lower is not universally better. An entry needs `sampleCount` of at
|
|
184
|
+
least 2 to mean anything, and an empty `trend[]` means no readable history:
|
|
185
|
+
record that as **Info** rather than inferring a flat trend from silence.
|
|
186
|
+
Each `deltas` key **names its unit** — `symbols`, `bytes`, `filesTracked` —
|
|
187
|
+
so quote the axis with the number, never a bare delta.
|
|
188
|
+
|
|
189
|
+
5. **Tightening Headroom.** `headroom[]` is what this lens exists for. Positive
|
|
190
|
+
headroom is slack the floor could be tightened into; negative headroom means
|
|
191
|
+
the floor is already breached — grade that **High** and route it as a
|
|
192
|
+
regression, not an opportunity. File a tightening finding only when the
|
|
193
|
+
slack is **durable**: the same kind's trend is flat or improving. A one-run
|
|
194
|
+
dip tightened into a floor turns the next honest change red for no defect.
|
|
195
|
+
Grade durable multi-point slack **Medium**, marginal slack **Low**.
|
|
196
|
+
|
|
197
|
+
## Step 2: Hotspot budget and the dropped log
|
|
198
|
+
|
|
199
|
+
Cap the Detailed Findings at the **top 8 hotspot clusters by `rank`**. The
|
|
200
|
+
engine emits up to `--hotspot-limit` clusters, and the point of the lens is a
|
|
201
|
+
ranked actionable batch, not an exhaustive dump nobody schedules.
|
|
202
|
+
|
|
203
|
+
A silent truncation reads as full coverage, so the report MUST carry a
|
|
204
|
+
**Dropped Hotspots** section naming every cluster the cap excluded with its
|
|
205
|
+
cluster key, `rank`, and `gateKinds`. Write `_None dropped._` when the cap did
|
|
206
|
+
not bite; the section's absence is itself a defect. This budget log is separate
|
|
207
|
+
from — and additional to — the core's self-cross-check `kept / dropped` line,
|
|
208
|
+
which counts evidence-bar drops rather than budget drops. State the cap in the
|
|
209
|
+
Executive Summary and change it only on an explicit operator instruction.
|
|
210
|
+
|
|
211
|
+
## Step 3: The floor-tightening contract (mandatory)
|
|
212
|
+
|
|
213
|
+
A remediation Story that only burns debt down leaves the floor where it was,
|
|
214
|
+
and the reclaimed slack is silently re-spent by the next change — the loop
|
|
215
|
+
runs and the ratchet never moves. So **every Hotspot Cluster and Tightening
|
|
216
|
+
Headroom finding's Agent Prompt MUST** end the remediation with the ratchet
|
|
217
|
+
tightened and gate-enforced:
|
|
218
|
+
|
|
219
|
+
1. Lower the floor under `delivery.quality.gates` in `.agentrc.json` to the
|
|
220
|
+
newly measured level, **or** delete the burnt-down rows from that kind's
|
|
221
|
+
file under `baselines/`.
|
|
222
|
+
2. Carry `node .agents/scripts/check-baselines.js --gate <kind>` in the
|
|
223
|
+
remediation Story's `verify[]`, so the tightened floor is enforced by the
|
|
224
|
+
gate that already exists at that Story's delivery time rather than by prose
|
|
225
|
+
nobody runs.
|
|
226
|
+
|
|
227
|
+
Use these two Agent Prompt templates verbatim, substituting the envelope's own
|
|
228
|
+
values for the angle-bracketed slots:
|
|
229
|
+
|
|
230
|
+
- **Hotspot Cluster template:**
|
|
231
|
+
`Burn down the measured debt in <hotspots.path>, an outlier across <gateKinds>. Refactor and add tests until its rows leave that kind's file under baselines/, then regenerate that baseline with the matching update-*-baseline script in a commit whose subject carries the baseline-refresh: tag. Finish by TIGHTENING the ratchet in the same Story — lower the kind's floor under delivery.quality.gates in .agentrc.json to the new measured level, or delete the burnt-down rows — and carry node .agents/scripts/check-baselines.js --gate <kind> in this Story's verify[] so the tightened floor is enforced at delivery.`
|
|
232
|
+
- **Tightening Headroom template:**
|
|
233
|
+
`The <kind> gate's <axis> floor sits at <floor> while the measured rollup is <measured> (headroom <headroom>, direction <direction>), and that kind's trend is flat or improving. Tighten it: set that axis under delivery.quality.gates in .agentrc.json to the measured level so no slack remains for the next change to re-spend, and carry node .agents/scripts/check-baselines.js --gate <kind> in this Story's verify[] so the new floor is enforced. Regenerate no baseline in this Story — the floor edit is the whole change.`
|
|
234
|
+
|
|
235
|
+
Staleness, Dead Instrument, and Trend Drift findings do **not** carry the
|
|
236
|
+
tightening clause: there is no measured slack to claim until the instrument is
|
|
237
|
+
alive and current again.
|
|
238
|
+
|
|
239
|
+
## Step 4: Cadence (host-owned — documented, never scheduled)
|
|
240
|
+
|
|
241
|
+
This lens ships **no scheduler**, and building one is out of scope; cadence
|
|
242
|
+
belongs to the host that invokes it. Document the intent and let the operator
|
|
243
|
+
or the host's own timer drive it: **monthly** for a codebase-wide pass (long
|
|
244
|
+
enough for `trend[]` to hold signal, short enough to catch a stale instrument
|
|
245
|
+
before a release leans on it); **after a large refactor lands**, when headroom
|
|
246
|
+
appears and is most likely to be silently re-spent; and **before any floor is
|
|
247
|
+
raised**, so the raise is argued against measured headroom rather than
|
|
248
|
+
convenience. Nothing here self-triggers.
|
|
249
|
+
|
|
250
|
+
## Step 5: Hand off to `/audit-to-stories`
|
|
251
|
+
|
|
252
|
+
The report is the deliverable. Hand it to the converter, which parses the
|
|
253
|
+
shared finding skeleton, fingerprints each finding for dedupe, and groups the
|
|
254
|
+
batch:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
node .agents/scripts/audit-to-stories.js --scan --glob temp/audits/audit-baselines-results.md --out temp/audits/audit-to-stories-plan.json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Report the plan path and the group count; the converter owns everything
|
|
261
|
+
downstream, including whether a finding becomes a Story at all.
|
|
262
|
+
|
|
263
|
+
## Report additions
|
|
264
|
+
|
|
265
|
+
Beyond the shared skeleton (Executive Summary + Detailed Findings from the
|
|
266
|
+
core), this report carries its own title, a Gate Surface Health table, a
|
|
267
|
+
Tightening Ledger, and the Dropped Hotspots budget log:
|
|
268
|
+
|
|
269
|
+
```markdown
|
|
270
|
+
# Baseline & Ratchet Audit Report
|
|
271
|
+
|
|
272
|
+
## Gate Surface Health
|
|
273
|
+
|
|
274
|
+
| Kind | Surface | Configured | Rows | Measured | Stale (days) | Stale (commits) | Verdict |
|
|
275
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
276
|
+
| [kind] | [gate / ratchet] | [yes / no] | [rowCount] | [measured.value measured.unit] | [staleDays or `null`] | [staleCommits or `null`] | [Live / Stub / Unenforced / Unreadable] |
|
|
277
|
+
|
|
278
|
+
## Tightening Ledger
|
|
279
|
+
|
|
280
|
+
| Kind | Axis | Floor | Measured | Headroom | Trend | Proposed floor |
|
|
281
|
+
| --- | --- | --- | --- | --- | --- | --- |
|
|
282
|
+
| [kind] | [axis] | [floor] | [measured] | [headroom] | [improving / flat / worsening] | [value] |
|
|
283
|
+
|
|
284
|
+
## Dropped Hotspots
|
|
285
|
+
|
|
286
|
+
| Cluster key | Rank | Gates |
|
|
287
|
+
| --- | --- | --- |
|
|
288
|
+
| [key] | [rank] | [gateKinds] |
|
|
289
|
+
```
|
|
@@ -42,10 +42,11 @@ touched only one route file. Reachability is a global property: adding one
|
|
|
42
42
|
route can orphan it, but removing or renaming a route elsewhere can also break
|
|
43
43
|
a nav href that the change set never touched.
|
|
44
44
|
|
|
45
|
-
Because of this, the navigability lens
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
Because of this, the navigability lens declares `"scope": "global"` in
|
|
46
|
+
[`audit-rules.json`](../schemas/audit-rules.json) — the single source of truth
|
|
47
|
+
`resolveLensTier` in
|
|
48
|
+
[`lib/audit-suite/selector.js`](../scripts/lib/audit-suite/selector.js) reads —
|
|
49
|
+
and is **exempt from the cross-epic-leak guard** that narrows every other lens's
|
|
49
50
|
evidence to the change set's `changedFiles`. The exemption is scoped to this
|
|
50
51
|
lens only — the guard is **not** weakened for any other lens, and it never lets
|
|
51
52
|
a foreign change set leak into a scoped lens.
|
|
@@ -107,6 +107,32 @@ Evaluate the gathered context against the following test quality dimensions:
|
|
|
107
107
|
criteria to ensure they have corresponding and complete test coverage.
|
|
108
108
|
Verify that the implementation found in the codebase correctly matches the
|
|
109
109
|
architectural requirements and highlight any inconsistencies or gaps.
|
|
110
|
+
7. **Unwired Seams — Coverage Without a Caller (mandatory).** Report code the
|
|
111
|
+
suite **covers** but no live production path **calls**. This is the blind spot
|
|
112
|
+
this dimension exists for, and it is a property of the suite, not of the code:
|
|
113
|
+
a seam with its own passing unit test reports as covered, contributes to the
|
|
114
|
+
coverage number, and is never exercised in assembly — so the suite reads green
|
|
115
|
+
over wiring that has never run once. High coverage is therefore not evidence
|
|
116
|
+
of a live path; it is what conceals a dead one. Cover at least:
|
|
117
|
+
|
|
118
|
+
- **A produced-but-never-consumed artifact** — a field, file, or marker the
|
|
119
|
+
code writes that no test and no consumer ever reads back. A writer test that
|
|
120
|
+
asserts on the writer's own output is not a reader.
|
|
121
|
+
`assert(written === expected)` proves the write, never the round-trip.
|
|
122
|
+
- **An optional field nothing populates** — a parameter or config key a
|
|
123
|
+
consumer branches on that only *tests* ever set. The suite covers both
|
|
124
|
+
branches; production has only ever taken the default.
|
|
125
|
+
|
|
126
|
+
For each, name the missing test rather than the missing caller: the gap is
|
|
127
|
+
that **no test would fail if the wiring were deleted**. That is the assertion
|
|
128
|
+
to recommend — an integration-tier test that drives the real production entry
|
|
129
|
+
point and fails when the seam is unwired. Grade a dead guard/gate **High** or
|
|
130
|
+
**Critical** (the enforcement has never been in force and the bar reads green
|
|
131
|
+
because it never runs), other dead wiring **Medium**. The core's exclusion
|
|
132
|
+
list still applies: a sanctioned test seam or a declared entry point is not a
|
|
133
|
+
finding here. Route the *architectural* framing of the same defect to
|
|
134
|
+
[`audit-architecture`](audit-architecture.md)'s Shipped-But-Never-Wired
|
|
135
|
+
dimension; this lens owns the **missing-test** framing.
|
|
110
136
|
|
|
111
137
|
## Constraint (lens-specific carve-out)
|
|
112
138
|
|
|
@@ -143,9 +143,24 @@ Chain into the existing planning entrypoint:
|
|
|
143
143
|
|
|
144
144
|
(`/plan --seed "$(cat <path>)"` also works for small seeds). `/plan`
|
|
145
145
|
then runs its author → persist path, as documented in its workflow.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
|
|
147
|
+
**Dedup provenance is carried mechanically — do not hand-copy it.** The seed's
|
|
148
|
+
MVP Scope bullets carry each group's `audit-fingerprints` and
|
|
149
|
+
`audit-semantic-keys` footers as HTML comments (invisible in the rendered
|
|
150
|
+
one-pager). `plan-persist` harvests them out of the seed on the
|
|
151
|
+
`plan-context.json` envelope and appends them to **every** Story body it
|
|
152
|
+
persists, via `carryProvenanceFooters`
|
|
153
|
+
([`lib/findings/route-finding.js`](../scripts/lib/findings/route-finding.js)).
|
|
154
|
+
The carry is additive, union-preserving and idempotent, so a resumed persist
|
|
155
|
+
cannot stack footers and a hand-authored fingerprint is never dropped.
|
|
156
|
+
|
|
157
|
+
This is deliberately not an authoring step. It used to be: the footers reached
|
|
158
|
+
the seed and stopped there, leaving the authoring agent to notice HTML comments
|
|
159
|
+
in a one-pager and copy them forward — a remembered step, which is to say no
|
|
160
|
+
step at all. Stories filed on the recommended path were therefore invisible to
|
|
161
|
+
the next sweep's Phase 6 dedup, which re-filed work it had already planned. If
|
|
162
|
+
you find yourself copying a footer by hand, the carry is broken — fix it there
|
|
163
|
+
rather than papering over it in the body.
|
|
149
164
|
|
|
150
165
|
## Phase 5b — Standalone-Stories path
|
|
151
166
|
|
|
@@ -261,7 +276,18 @@ When the single-plan path ran, link the Story (or plan-run) the chained
|
|
|
261
276
|
dedup/route implementation, shared with `qa-explore`.
|
|
262
277
|
- **Always** stamp the fingerprint footer (via the helper's
|
|
263
278
|
`fingerprintFooter`) in the body of every created Story. Without it,
|
|
264
|
-
the next run cannot dedupe.
|
|
279
|
+
the next run cannot dedupe. On the Single-plan path this is mechanical
|
|
280
|
+
(`carryProvenanceFooters`, Phase 5a) — never an authoring step.
|
|
281
|
+
- **Never** re-mint a finding fingerprint while normalising a finding.
|
|
282
|
+
`severity` and `labels` are identity fields folded into the sha, so
|
|
283
|
+
normalising either without holding the hash stable silently breaks
|
|
284
|
+
dedup for every finding already filed. The projection that keeps the
|
|
285
|
+
fingerprint invariant under normalisation is
|
|
286
|
+
`severity.js#fingerprintSeverity`; the contract is pinned by
|
|
287
|
+
`tests/lib/findings/route-finding.contract.test.js`.
|
|
288
|
+
- **Always** grade findings on the canonical five-level scale
|
|
289
|
+
(`lib/findings/severity.js`). A level outside it parses as no severity
|
|
290
|
+
and the finding is dropped by every severity-filtered run.
|
|
265
291
|
- **Always** present the Phase 2, 3, and 4 HITL gates. Do not bypass —
|
|
266
292
|
even when "obvious" — because the severity / grouping / mode picks
|
|
267
293
|
are operator decisions that the workflow's UX contract relies on.
|