mandrel 2.4.0 → 2.6.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 (70) hide show
  1. package/.agents/audit-checklists/accessibility.md +29 -0
  2. package/.agents/audit-checklists/architecture.md +4 -5
  3. package/.agents/audit-checklists/clean-code.md +10 -0
  4. package/.agents/audit-checklists/data-model.md +22 -0
  5. package/.agents/audit-checklists/dependencies.md +11 -2
  6. package/.agents/audit-checklists/devops.md +4 -0
  7. package/.agents/audit-checklists/navigability.md +3 -0
  8. package/.agents/audit-checklists/performance.md +8 -11
  9. package/.agents/audit-checklists/privacy.md +3 -4
  10. package/.agents/audit-checklists/quality.md +2 -0
  11. package/.agents/audit-checklists/security.md +4 -5
  12. package/.agents/audit-checklists/seo.md +7 -1
  13. package/.agents/audit-checklists/sre.md +14 -12
  14. package/.agents/audit-checklists/ux-ui.md +4 -0
  15. package/.agents/docs/configuration.md +3 -0
  16. package/.agents/docs/workflows.md +4 -3
  17. package/.agents/schemas/agentrc.schema.json +17 -0
  18. package/.agents/schemas/audit-rules.json +134 -19
  19. package/.agents/schemas/audit-rules.schema.json +6 -2
  20. package/.agents/scripts/audit-labels-bootstrap.js +4 -4
  21. package/.agents/scripts/audit-to-stories.js +244 -19
  22. package/.agents/scripts/lib/audit-suite/checklist-threading.js +26 -3
  23. package/.agents/scripts/lib/audit-suite/dispatch-checklist.js +132 -0
  24. package/.agents/scripts/lib/audit-suite/index.js +1 -0
  25. package/.agents/scripts/lib/audit-suite/selector.js +290 -14
  26. package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +2 -1
  27. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +5 -1
  28. package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +23 -3
  29. package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +38 -0
  30. package/.agents/scripts/lib/audit-to-stories/ledger.js +256 -0
  31. package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +41 -7
  32. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +20 -2
  33. package/.agents/scripts/lib/command-header.js +1 -1
  34. package/.agents/scripts/lib/config-settings-schema-delivery.js +21 -0
  35. package/.agents/scripts/lib/dynamic-workflow/performance-report-contract.js +5 -3
  36. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +56 -0
  37. package/.agents/scripts/lib/findings/route-finding.js +108 -10
  38. package/.agents/scripts/lib/observability/runtime-friction.js +137 -6
  39. package/.agents/scripts/lib/orchestration/retro-proposals.js +0 -0
  40. package/.agents/scripts/lib/orchestration/run-epilogue.js +5 -19
  41. package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +11 -0
  42. package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +81 -1
  43. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +1 -0
  44. package/.agents/scripts/lib/orchestration/story-follow-ups.js +53 -13
  45. package/.agents/scripts/nav-registry-diff.js +449 -0
  46. package/.agents/workflows/audit-accessibility.md +243 -0
  47. package/.agents/workflows/audit-architecture.md +89 -71
  48. package/.agents/workflows/audit-clean-code.md +87 -53
  49. package/.agents/workflows/audit-data-model.md +198 -0
  50. package/.agents/workflows/audit-dependencies.md +143 -28
  51. package/.agents/workflows/audit-devops.md +109 -18
  52. package/.agents/workflows/audit-documentation.md +25 -53
  53. package/.agents/workflows/audit-navigability.md +78 -22
  54. package/.agents/workflows/audit-performance.md +207 -103
  55. package/.agents/workflows/audit-privacy.md +51 -13
  56. package/.agents/workflows/audit-quality.md +71 -61
  57. package/.agents/workflows/audit-security.md +94 -71
  58. package/.agents/workflows/audit-seo.md +80 -25
  59. package/.agents/workflows/audit-sre.md +99 -66
  60. package/.agents/workflows/audit-to-stories.md +44 -5
  61. package/.agents/workflows/audit-ux-ui.md +71 -17
  62. package/.agents/workflows/helpers/audit-dual-path.md +59 -0
  63. package/.agents/workflows/helpers/audit-self-check.md +70 -0
  64. package/.agents/workflows/helpers/audit-severity-scale.md +19 -0
  65. package/.agents/workflows/helpers/deliver-story.md +25 -0
  66. package/docs/CHANGELOG.md +23 -0
  67. package/package.json +1 -1
  68. package/.agents/audit-checklists/lighthouse.md +0 -15
  69. package/.agents/schemas/audit-results.schema.json +0 -69
  70. package/.agents/workflows/audit-lighthouse.md +0 -269
@@ -35,53 +35,158 @@ before this section existed.
35
35
  proceed with the full codebase-wide scan defined in the remaining
36
36
  steps.
37
37
 
38
- ## Step 1: Inventory & Stale Check
39
-
40
- > Apply [`helpers/parallel-tooling.md`](helpers/parallel-tooling.md) when batching the scan below — independent reads belong in one turn, long shells run via `run_in_background` + `Monitor`.
41
-
42
- 1. Run `npm outdated` (or equivalent for the package manager) to see which
43
- packages are behind.
44
- 2. Identify "stale" dependencies (packages with no updates for >1 year).
45
- 3. Check for "bloat" large dependencies that could be replaced by smaller
46
- alternatives or native code.
47
-
48
- ## Step 2: Vulnerability Scan
49
-
50
- 1. Run `npm audit` to find security vulnerabilities.
51
- 2. Cross-reference critical dependencies with known CVE databases if necessary.
52
- 3. Highlight any peer dependency conflicts that might arise from upgrades.
53
-
54
- ## Step 3: Output Requirements
38
+ ## Step 1: Inventory, Staleness & Unused Detection
39
+
40
+ > Apply [`helpers/parallel-tooling.md`](helpers/parallel-tooling.md) when batching the scans below — independent reads belong in one turn, long shells run via `run_in_background` + `Monitor`.
41
+
42
+ Run each probe as a concrete, machine-readable command so the Health Summary
43
+ counts are **exact** rather than eyeballed:
44
+
45
+ 1. **Outdated inventory.** Run `npm outdated --json` (or the package
46
+ manager's equivalent). Every key is a behind package with its `current`,
47
+ `wanted`, and `latest` fields; the count of keys is the exact **Outdated
48
+ Packages** figure never hand-count from prose.
49
+ 2. **Unused dependencies.** Run `npx knip --production` to find declared
50
+ dependencies with no import reachable from a production entry point.
51
+ ⚠️ **`knip --production` silent-no-op gotcha:** knip's `--production`
52
+ mode analyses nothing **unless the project's `entry` patterns carry a
53
+ `!` suffix** — without the bang-suffixed production entries it reports
54
+ `{"issues":[]}` and looks green while scanning zero files. Confirm the
55
+ consumer's knip config uses `!`-suffixed entries before trusting a clean
56
+ result; when it does not, fall back to `npx depcheck --json` and record
57
+ the config gap itself as a finding. Report each genuinely-unused
58
+ dependency as a `Removal` finding.
59
+ 3. **Staleness.** For each critical or outdated dependency, probe its last
60
+ publish with `npm view <pkg> time.modified` and flag any package with no
61
+ release in over a year as **stale** (unmaintained-supply-chain risk),
62
+ independent of whether a newer version exists.
63
+ 4. **Node-engine drift.** Compare the Node version declared across every
64
+ source of truth and flag any mismatch between them:
65
+ - `package.json` `engines.node`,
66
+ - `.nvmrc`,
67
+ - the CI matrix `node-version` entries under `.github/workflows/**`,
68
+ - the locally observed `node --version`.
69
+ A drift between any two (e.g. `.nvmrc` pinning `20` while the CI matrix
70
+ still tests `18`) is a finding: the floor the code is actually tested
71
+ against has diverged from the floor it advertises.
72
+
73
+ ## Step 2: Reachability-triaged Vulnerability Scan
74
+
75
+ A vulnerability in a build-only devDependency is not the same risk as one that
76
+ ships to production. Triage every advisory by **production reachability**
77
+ before grading it — this mirrors the security baseline's "reachable in
78
+ production code" standard.
79
+
80
+ 1. **Two-pass audit diff.** Run `npm audit --json` (the full tree) **and**
81
+ `npm audit --json --omit=dev` (production-reachable only). An advisory
82
+ present in the full run but absent from the `--omit=dev` run is
83
+ **dev-only**; one present in both is **production-reachable**.
84
+ 2. **Severity rubric.** Grade each advisory on the shared
85
+ [`Critical | High | Medium | Low` scale](helpers/audit-severity-scale.md)
86
+ as a function of the advisory's own CVSS band, its reachability
87
+ (production-reachable escalates; dev-only caps at Medium), and its
88
+ dependency position (a direct dependency whose version you control is more
89
+ actionable than a deep transitive one).
90
+ 3. **Report shape — no flooding.** Emit **one finding per
91
+ production-reachable Critical or High advisory** — these are the ones that
92
+ gate a release. Collapse **all dev-only advisories into a single aggregate
93
+ finding** ("N dev-only advisories, no production reachability") rather than
94
+ one block per advisory, so dev-only noise never drowns the production
95
+ signal.
96
+
97
+ ## Step 3: Supply-chain scoped mode (lockfile-delta)
98
+
99
+ When the `## Scope` block above resolved to a change-set file list **and that
100
+ list contains a lockfile** (`package-lock.json`, `pnpm-lock.yaml`, or
101
+ `yarn.lock`), run this lens as a **supply-chain delta pass** instead of a
102
+ whole-manifest re-scan. The close-time question is not "what is stale across
103
+ the whole repo" — it is "what just entered the dependency tree, and is it
104
+ safe". Diff the lockfile against the base branch and analyse only the delta:
105
+
106
+ 1. **Enumerate the delta.** Run `git diff <base>...HEAD -- <lockfile>` and
107
+ list every **added** package and every **version-bumped** package the
108
+ change introduces. These are the only packages in scope for this pass.
109
+ 2. **Provenance.** Run `npm audit signatures` to verify the registry
110
+ signatures / provenance attestations of the installed tree, and flag any
111
+ added package that fails signature verification.
112
+ 3. **New install scripts.** Flag any added or bumped package that declares a
113
+ `preinstall`, `install`, or `postinstall` lifecycle script — arbitrary
114
+ code that runs at `npm install` time is the classic supply-chain execution
115
+ vector and warrants an explicit eyeball.
116
+ 4. **Typosquat near-misses.** Compare each **added** package name against the
117
+ existing dependency set and well-known package names for a near-miss
118
+ (single-character edits, dropped scopes, hyphen/underscore swaps) that
119
+ suggests a typosquat, and flag it.
120
+
121
+ ## Step 4: Output Requirements
55
122
 
56
123
  Generate and save a highly structured Markdown audit report to
57
124
  `{{auditOutputDir}}/audit-dependencies-results.md`, using the exact template
58
125
  below.
59
126
 
127
+ > Grade every finding's severity on the shared
128
+ > [`Critical | High | Medium | Low` scale](helpers/audit-severity-scale.md).
129
+ >
130
+ > **Version-free titles (mandatory).** A finding title MUST NOT embed a
131
+ > concrete version number — write ``### `package.json` — lodash unused``, not
132
+ > ``… — lodash@4.17.20 unused``. Periodic re-runs of this lens re-detect the
133
+ > same issue at a drifted version; a version-free title keeps the finding's
134
+ > fingerprint stable so `audit-to-stories` dedupes it against the existing
135
+ > Story instead of filing a fresh duplicate on every bump.
136
+
60
137
  ```markdown
61
138
  # Dependency Audit Report
62
139
 
63
140
  ## Health Summary
64
141
 
65
- - **Outdated Packages:** [Count]
66
- - **Vulnerabilities:** [Critical: #, High: #, Mod: #]
142
+ - **Outdated Packages:** [exact count from `npm outdated --json`]
143
+ - **Unused Dependencies:** [exact count from `npx knip --production` / `depcheck`]
144
+ - **Vulnerabilities:** [Critical: #, High: #, Mod: #] (production-reachable / dev-only split)
145
+ - **Node-engine drift:** [None | describe the mismatch across engines / .nvmrc / CI matrix]
67
146
 
68
147
  ## Detailed Findings
69
148
 
70
- [For every security fix or major update identified, use the following strict
71
- structure:]
149
+ [For every production-reachable Critical/High advisory, unused dependency, or
150
+ Node-engine drift, use the strict structure below. Lead each title with the
151
+ manifest the dependency lives in, and keep the title version-free:]
72
152
 
73
- ### [Package Name Update]
153
+ ### `path/to/package.json` — [Package name — issue, no version]
74
154
 
75
- - **Dimension:** [Security Fix | Major Upgrade | Removal]
76
- - **Impact:** [High | Medium | Low]
77
- - **Current State:** [Current vs Target version and reason for update]
78
- - **Recommendation & Rationale:** [How to perform the update and potential
79
- breaking changes to watch for]
155
+ - **Dimension:** [Security Fix | Removal | Engine Drift | Major Upgrade | Supply-chain]
156
+ - **Impact:** [Critical | High | Medium | Low]
157
+ - **Location:** `path/to/package.json:line`
158
+ - **Current State:** [Current vs target, the reachability verdict (production-reachable | dev-only), and the reason for the change]
159
+ - **Recommendation & Rationale:** [How to remediate and the breaking changes to watch for]
160
+ - **Acceptance signal:** [the command or observable that proves this finding is remediated — e.g. `npm audit --omit=dev` reporting zero for this advisory, or `npx knip --production` no longer listing the package]
80
161
  - **Agent Prompt:**
81
- `[A copy-pasteable, highly specific prompt to execute this update independently (e.g., npm install package@version)]`
162
+ `[A copy-pasteable, highly specific prompt to execute this update independently (e.g., npm install package@latest)]`
163
+
164
+ ### Dev-only advisories (aggregate)
165
+
166
+ - **Dimension:** Security Fix
167
+ - **Impact:** [Low | Medium]
168
+ - **Location:** `package-lock.json`
169
+ - **Current State:** [N dev-only advisories with no production reachability; not release-gating]
170
+ - **Recommendation & Rationale:** [Batch-remediate on the next dependency-maintenance pass — do not block the release on these]
171
+ - **Acceptance signal:** `npm audit --json` dev-only advisory count returns to zero.
172
+
173
+ ## Upgrade Batches
174
+
175
+ Group the safe upgrade path into batches so a maintainer can act on them as
176
+ discrete units. Each batch carries its own acceptance signal:
177
+
178
+ - **Batch: patch + minor bumps** — every non-breaking `npm outdated` entry
179
+ whose `wanted` satisfies the declared range, grouped into ONE batch.
180
+ - **Acceptance signal:** `npm outdated` reports no remaining patch/minor
181
+ drift and the test suite passes after the bump.
182
+ - **Batch: `<package>` major upgrade** — one batch **per** major bump (each
183
+ crosses a breaking boundary and lands independently).
184
+ - **Acceptance signal:** `<package>` at the new major with its migration
185
+ notes applied and the test suite green.
82
186
 
83
187
  ## Recommended Removals/Replacements
84
188
 
189
+ - Remove `[unused-package]` — no production import per `npx knip --production`.
85
190
  - Replace `[heavy-library]` with `[light-library]` or native `[browser-api]`.
86
191
  ```
87
192
 
@@ -89,3 +194,13 @@ structure:]
89
194
 
90
195
  This is a **read-only** evaluation. Do not run `npm install` or `npm update`
91
196
  unless explicitly requested by the user after reviewing this report.
197
+
198
+ ## Self-cross-check (mandatory — filter false positives before you finalize)
199
+
200
+ Before you write the report artifact from the previous step, run the shared
201
+ adversarial self-cross-check over your Detailed Findings — see
202
+ [`helpers/audit-self-check.md`](helpers/audit-self-check.md). It defines the
203
+ per-finding evidence bar, the exclusion list, and the final re-open-and-drop
204
+ pass whose `kept <k> / dropped <d>` counts you record in the Executive
205
+ Summary, so the sequential single-pass path filters unverified findings just as
206
+ the orchestrated path's adversarial reviewer does.
@@ -36,43 +36,121 @@ before this section existed.
36
36
  proceed with the full codebase-wide scan defined in the remaining
37
37
  steps.
38
38
 
39
- ## Step 1: Context Gathering (Read-Only Scan)
39
+ ## Step 1: Detection Battery (Read-Only, Tool-First)
40
40
 
41
41
  > Apply [`helpers/parallel-tooling.md`](helpers/parallel-tooling.md) when batching the scan below — independent reads belong in one turn, long shells run via `run_in_background` + `Monitor`.
42
42
 
43
- Before generating the report, silently scan the workspace for relevant
44
- configuration files. Pay special attention to:
43
+ Do **not** audit CI/CD from memory. Run the deterministic battery below first
44
+ and let its output ground every finding. Each tool is **presence-gated**: when
45
+ the binary is absent, record the gap as a Low-severity `Standardization`
46
+ finding (recommend adopting the scanner) and continue — a missing scanner
47
+ degrades the audit gracefully, it never aborts it.
45
48
 
46
- - CI/CD pipelines (e.g., `.github/workflows/`, `.gitlab-ci.yml`,
47
- `azure-pipelines.yml`).
48
- - Dependency manifests and script definitions (e.g., `package.json`,
49
- `pnpm-workspace.yaml`).
50
- - Linting, formatting, and static analysis configs (e.g., `.eslintrc*`,
51
- `.prettierrc*`, `biome.json`, `tsconfig.json`).
52
- - Git hooks and commit standards (e.g., `.husky/`, `commitlint.config.js`).
49
+ 1. **Workflow static analysis (`actionlint`).** When `.github/workflows/`
50
+ contains any `*.yml` / `*.yaml`, run:
51
+
52
+ ```bash
53
+ command -v actionlint >/dev/null 2>&1 && actionlint -color=never || \
54
+ echo "actionlint: not installed — recommend adding it (Standardization gap)"
55
+ ```
56
+
57
+ Every diagnostic `actionlint` emits is a finding (shell-quoting bugs,
58
+ invalid `runs-on`, undefined `needs`, mis-scoped `${{ }}` expressions).
59
+
60
+ 2. **Workflow security posture (`zizmor`).** Over the same
61
+ `.github/workflows/` set, run:
62
+
63
+ ```bash
64
+ command -v zizmor >/dev/null 2>&1 && zizmor --no-progress .github/workflows/ || \
65
+ echo "zizmor: not installed — recommend adding it (Security & Compliance gap)"
66
+ ```
67
+
68
+ Treat each `zizmor` finding (unpinned action refs, `pull_request_target`
69
+ misuse, over-broad `GITHUB_TOKEN` permissions, template-injection sinks) as
70
+ a Security & Compliance finding at the severity `zizmor` assigns.
71
+
72
+ 3. **Container linting (`hadolint`), presence-gated on Dockerfiles.** Only when
73
+ the change set (or repo) contains a `Dockerfile*`:
74
+
75
+ ```bash
76
+ command -v hadolint >/dev/null 2>&1 && hadolint <Dockerfile paths> || \
77
+ echo "hadolint: not installed — recommend adding it (Security & Compliance gap)"
78
+ ```
79
+
80
+ 4. **Pipeline reliability history (`gh run list`).** Cite real durations and
81
+ failure rates rather than guessing which steps are slow or flaky:
82
+
83
+ ```bash
84
+ gh run list --limit 50 --json conclusion,durationMs,workflowName,createdAt 2>/dev/null || \
85
+ echo "gh run history unavailable — Performance/Reliability findings degrade to config-only reasoning"
86
+ ```
87
+
88
+ Compute the failure rate (`failure` + `cancelled` / total) and the p50/p95
89
+ duration per workflow; a workflow whose recent failure rate is non-trivial
90
+ or whose p95 duration is an outlier is a Reliability or Performance finding
91
+ with the observed number cited in **Current State**.
92
+
93
+ Then read the surfaces the battery flags plus the standing config set:
94
+ CI/CD pipelines (`.github/workflows/`, `.gitlab-ci.yml`, `azure-pipelines.yml`),
95
+ dependency/script manifests (`package.json`, `pnpm-workspace.yaml`), lint/format
96
+ configs (`.eslintrc*`, `.prettierrc*`, `biome.json`, `tsconfig.json`), and git
97
+ hooks / commit standards (`.husky/`, `commitlint.config.js`).
53
98
 
54
99
  ## Step 2: Analysis Dimensions
55
100
 
56
- Evaluate the gathered context against the following dimensions:
101
+ Evaluate the battery output and gathered context against the following
102
+ dimensions. The three **presence-gated sub-steps** (Dockerfile, IaC, Release
103
+ pipeline) run only when their surface is present in the change set or repo —
104
+ when absent, state "not present in scope" and skip.
57
105
 
58
106
  1. **Redundancy & Duplication:** Overlapping tools or conflicting rules (e.g.,
59
107
  Prettier vs. ESLint formatting, duplicated scripts in `package.json` and CI).
60
108
  2. **Performance Gaps:** Bottlenecks in CI/CD, slow caching strategies, or
61
- unoptimized hooks (e.g., missing `lint-staged`).
109
+ unoptimized hooks (e.g., missing `lint-staged`) — cite the `gh run list`
110
+ durations from Step 1.
62
111
  3. **Security & Compliance:** Missing secret scanning, loose permissions (e.g.,
63
112
  `GITHUB_TOKEN` scopes), outdated or vulnerable dependency resolution
64
- strategies.
113
+ strategies — grounded in the `zizmor` output.
65
114
  4. **Standardization & Modernization:** Opportunities to consolidate tooling
66
115
  (e.g., migrating to unified tools like Biome) or extract inline
67
- configurations into dedicated dotfiles.
116
+ configurations into dedicated dotfiles; include any absent-scanner gaps
117
+ surfaced in Step 1.
68
118
  5. **Reliability & Resilience:** Fragile pipeline steps, missing error handling,
69
- silent failures, or lack of retries for network-dependent tasks.
119
+ silent failures, or lack of retries for network-dependent tasks — cite the
120
+ `gh run list` failure rates from Step 1.
121
+
122
+ ### Sub-step A — Dockerfile hardening (gated: `Dockerfile*` present)
123
+
124
+ Audit each Dockerfile for the standard hardening set: a pinned, digest-or-tag
125
+ base image (never `:latest`), a non-root `USER`, multi-stage builds that keep
126
+ build tooling out of the runtime image, no secrets baked into layers
127
+ (`ARG`/`ENV` for credentials), a `HEALTHCHECK`, and `.dockerignore` coverage.
128
+ Ground every finding in the `hadolint` output from Step 1.
129
+
130
+ ### Sub-step B — Infrastructure-as-Code (gated: `*.tf` / `infra/**` / k8s manifests present)
131
+
132
+ Audit IaC for hardcoded secrets and account IDs, over-permissive IAM / security
133
+ groups (`0.0.0.0/0` ingress, wildcard actions), unpinned module/provider
134
+ versions, missing remote state locking, and resources provisioned without
135
+ encryption-at-rest. Recommend `tflint` / `checkov` / `tfsec` where the scanner
136
+ is absent.
137
+
138
+ ### Sub-step C — Release & deployment pipeline (gated: release/deploy workflow present)
139
+
140
+ Audit the release path for an unpinned or mutable deployment action, missing
141
+ environment protection rules / required reviewers on the production
142
+ environment, absent rollback or canary strategy, and publish steps that run
143
+ without provenance / SLSA attestation. Cite the `gh run list` history for the
144
+ release workflow's reliability.
70
145
 
71
146
  ## Step 3: Output Requirements
72
147
 
73
148
  Generate and save a highly structured Markdown audit report to
74
149
  `{{auditOutputDir}}/audit-devops-results.md`, using the exact template below.
75
150
 
151
+ > Grade every finding's severity on the shared
152
+ > [`Critical | High | Medium | Low` scale](helpers/audit-severity-scale.md).
153
+
76
154
  ```markdown
77
155
  # DevOps Infrastructure Audit Report
78
156
 
@@ -83,15 +161,18 @@ highlight the most critical overarching themes from the findings.]
83
161
 
84
162
  ## Detailed Findings
85
163
 
86
- [For every gap identified, use the following strict structure:]
164
+ [For every gap identified, use the following strict structure. Lead each title
165
+ with the primary file the finding lives in:]
87
166
 
88
- ### [Short Title of the Issue]
167
+ ### `path/to/primary-file.ext` — [Short title of the issue]
89
168
 
90
169
  - **Dimension:** [e.g., Security & Compliance]
91
- - **Impact:** [High | Medium | Low]
170
+ - **Impact:** [Critical | High | Medium | Low]
171
+ - **Location:** `path/to/primary-file.ext:line`
92
172
  - **Current State:** [What is currently configured in the codebase]
93
173
  - **Recommendation & Rationale:** [The specific fix and why it improves the
94
174
  system]
175
+ - **Acceptance signal:** [the command or observable that proves this finding is remediated — e.g. a green CI run, a hardened config lint, or a re-run of this lens]
95
176
  - **Agent Prompt:**
96
177
  `[A copy-pasteable, highly specific prompt to execute this fix independently]`
97
178
 
@@ -108,3 +189,13 @@ Phase 3: Modernization / Tech Debt.]
108
189
 
109
190
  Do NOT execute any code modifications, edit files, create branches, or install
110
191
  packages. This is strictly a read-only analysis. Output the report and stop.
192
+
193
+ ## Self-cross-check (mandatory — filter false positives before you finalize)
194
+
195
+ Before you write the report artifact from the previous step, run the shared
196
+ adversarial self-cross-check over your Detailed Findings — see
197
+ [`helpers/audit-self-check.md`](helpers/audit-self-check.md). It defines the
198
+ per-finding evidence bar, the exclusion list, and the final re-open-and-drop
199
+ pass whose `kept <k> / dropped <d>` counts you record in the Executive
200
+ Summary, so the sequential single-pass path filters unverified findings just as
201
+ the orchestrated path's adversarial reviewer does.
@@ -70,56 +70,12 @@ before this section existed.
70
70
 
71
71
  ## Execution strategy (dual-path)
72
72
 
73
- This lens runs along one of two execution paths. Both emit the **identical**
74
- report contract (Step 3); downstream consumers (`audit-to-stories`) are agnostic to which path produced it.
75
-
76
- - **Orchestrated (dynamic-workflow) path.** When Claude Code's
77
- [dynamic workflows](https://code.claude.com/docs/en/workflows) are
78
- available, the saved project workflow
79
- `.claude/workflows/audit-documentation.workflow.js` fans the semantic
80
- dimensions below out as parallel read-only subagents — each agent walks
81
- the full target set per doc for its dimension — runs an **adversarial
82
- verify** stage (an independent agent re-checks every stale-claim finding
83
- against the current code and drops claims it cannot reproduce — doc
84
- staleness is notoriously false-positive-prone), then synthesises the
85
- Step 3 report. The orchestrator derives its per-dimension prompts from
86
- *this* markdown at run time — the lens stays the single source of truth;
87
- the script does not fork a second copy of the spec. Step 1's
88
- deterministic checkers still run in the calling session (the analysis
89
- subagents are read-only and cannot execute them); their results are
90
- passed to the workflow as the `deterministicFindings` input and folded
91
- into the synthesis.
92
- - **Sequential (single-pass) path.** When dynamic workflows are unavailable,
93
- follow Steps 1–3 below turn-by-turn exactly as before. This is the default
94
- fallback and changes nothing about the existing behaviour.
95
-
96
- **Strategy selection** is computed by
97
- [`lib/dynamic-workflow/capability.js`](../scripts/lib/dynamic-workflow/capability.js)
98
- (`selectAuditStrategy`). The orchestrated path is chosen only when the runtime
99
- is Claude Code, `disableWorkflows` is not set (settings.json **or**
100
- `CLAUDE_CODE_DISABLE_WORKFLOWS`), and the Claude Code version meets the
101
- research-preview floor (`>= 2.1.154`). Any other runtime, a disabled setting,
102
- or an older version degrades gracefully to the sequential path.
103
-
104
- > **Capability degradation, not a contract shim.** This dual path is **not**
105
- > covered by the No-Shim / hard-cutover rule in
106
- > [`git-conventions.md`](../rules/git-conventions.md). That rule forbids
107
- > running two shapes of the *same contract* side by side. Here there is **one**
108
- > report contract; only the *execution strategy* is selected from a runtime
109
- > capability — the same pattern the protocol already endorses for live-docs
110
- > fallback in [`instructions.md` §1.C/§1.D](../instructions.md). The full
111
- > capability-degradation rationale lives in the
112
- > [`capability.js`](../scripts/lib/dynamic-workflow/capability.js) module
113
- > docstring; the orchestrated-run evidence and per-lens cost/precision gate
114
- > verdicts live in [`docs/roadmap.md`](../../docs/roadmap.md) (Part 3 —
115
- > Dynamic-Workflow Orchestration).
116
-
117
- **Forcing a path (for testing).** Set `MANDREL_AUDIT_STRATEGY=sequential` to
118
- verify the fallback path with the feature notionally disabled, or
119
- `MANDREL_AUDIT_STRATEGY=orchestrated` to pin the dynamic path. To exercise the
120
- real disable signals instead, set `CLAUDE_CODE_DISABLE_WORKFLOWS=1` (env) or
121
- `disableWorkflows: true` in `.claude/settings.json` and re-run the lens — both
122
- degrade to the sequential path.
73
+ This lens runs along one of two execution paths (orchestrated dynamic-workflow
74
+ or sequential single-pass). Both emit the **identical** Step 3 report contract;
75
+ downstream consumers (`audit-to-stories`) are agnostic to which path produced
76
+ it. See [`helpers/audit-dual-path.md`](helpers/audit-dual-path.md) for strategy
77
+ selection, the forcing flags, and the read-only guarantee — read `audit-<lens>`
78
+ there as this lens's name.
123
79
 
124
80
  ## Step 1: Deterministic Signal First
125
81
 
@@ -245,6 +201,9 @@ Generate and save a highly structured Markdown audit report to
245
201
  `{{auditOutputDir}}/audit-documentation-results.md`, using the exact
246
202
  template below.
247
203
 
204
+ > Grade every finding's severity on the shared
205
+ > [`Critical | High | Medium | Low` scale](helpers/audit-severity-scale.md).
206
+
248
207
  ```markdown
249
208
  # Documentation Audit Report
250
209
 
@@ -261,16 +220,19 @@ match the code), the deterministic-gate verdicts, and primary drift themes.]
261
220
 
262
221
  ## Detailed Findings
263
222
 
264
- [For every gap identified, use the following strict structure:]
223
+ [For every gap identified, use the following strict structure. Lead each title
224
+ with the primary doc the finding lives in:]
265
225
 
266
- ### [Short Title of the Issue]
226
+ ### `path/to/primary-doc.md` — [Short title of the issue]
267
227
 
268
228
  - **Category:** [Broken Instruction | Stale Description | Missing Coverage | Generator Drift | Link Integrity | History Bloat | Contradiction | Authority Drift]
269
- - **Impact:** [High | Medium | Low] — for a Context Economy finding, this is the base severity **after** any read-tier escalation (Step 2.5); state the doc's tier in Current State.
229
+ - **Impact:** [Critical | High | Medium | Low] — for a Context Economy finding, this is the base severity **after** any read-tier escalation (Step 2.5); state the doc's tier in Current State.
230
+ - **Location:** `path/to/primary-doc.md:line`
270
231
  - **Current State:** [The doc, the exact claim, and what the code actually
271
232
  does — cite file paths and lines on both sides]
272
233
  - **Recommendation & Rationale:** [The specific doc edit (or generator
273
234
  rerun) and why it restores accuracy]
235
+ - **Acceptance signal:** [the command or observable that proves this finding is remediated — e.g. `npm run docs:check` passing, the corrected claim now matching the code, or a re-run of this lens]
274
236
  - **Agent Prompt:**
275
237
  `[A copy-pasteable, highly specific prompt to execute this doc fix independently]`
276
238
  ```
@@ -301,3 +263,13 @@ This workflow is **read-only** with respect to the repository: run the
301
263
  deterministic checkers in `--check` mode only, and do not edit any
302
264
  documentation or code. The single write is the report artifact. Provide the
303
265
  analysis and remediation prompts; do not apply changes.
266
+
267
+ ## Self-cross-check (mandatory — filter false positives before you finalize)
268
+
269
+ Before you write the report artifact from the previous step, run the shared
270
+ adversarial self-cross-check over your Detailed Findings — see
271
+ [`helpers/audit-self-check.md`](helpers/audit-self-check.md). It defines the
272
+ per-finding evidence bar, the exclusion list, and the final re-open-and-drop
273
+ pass whose `kept <k> / dropped <d>` counts you record in the Executive
274
+ Summary, so the sequential single-pass path filters unverified findings just as
275
+ the orchestrated path's adversarial reviewer does.
@@ -69,17 +69,19 @@ exemption never lets a foreign Epic's change set leak into a scoped lens.
69
69
 
70
70
  Read the consumer's navigability config (resolved from `.agentrc.json`):
71
71
 
72
- - `delivery.quality.navigability.routeGlobs` — globs identifying the
73
- route-adding files / route tree (e.g. `pages/**`, `app/**/route.ts`). Drives
74
- both the route-tree enumeration here and the plan-persist draft
75
- reachability gate
76
- ([`plan-reachability.js`](../scripts/lib/orchestration/plan-reachability.js)).
77
- - `delivery.quality.navigability.navRegistry` path(s) to the consumer's
78
- nav-registry SSOT this lens reads.
79
-
80
- If **neither** `routeGlobs` nor `navRegistry` is present, emit a one-line
81
- "navigability not configured skipped" note and exit without findings. Do
82
- **not** invent a route tree or guess a nav registry.
72
+ - `planning.navigation.routeGlobs` — globs identifying the route-adding files /
73
+ route tree (e.g. `pages/**`, `app/**/route.ts`). This is the same key the
74
+ plan-persist draft reachability gate
75
+ ([`plan-reachability.js`](../scripts/lib/orchestration/plan-reachability.js))
76
+ reads via `resolveNavConfig`, so the lens and the plan gate enumerate the
77
+ route tree from one SSOT.
78
+ - `planning.navigation.navRegistry` path(s) to the consumer's nav-registry
79
+ SSOT this lens reads.
80
+
81
+ If **neither** `routeGlobs` nor `navRegistry` is present under
82
+ `planning.navigation`, emit a one-line "navigability not configured skipped"
83
+ note and exit without findings. Do **not** invent a route tree or guess a nav
84
+ registry.
83
85
 
84
86
  ## Step 1: Enumerate the route tree
85
87
 
@@ -93,20 +95,61 @@ only** — never the full route body or any persona PII.
93
95
  Read every nav door from the `navRegistry` SSOT. Record each door's target
94
96
  path and the persona shell it renders in.
95
97
 
96
- ## Step 3: Cross-check (the two invariants)
98
+ ## Step 3: Run the deterministic cross-check
97
99
 
98
- 1. **Every route has a persona nav door.** For each enumerated route, assert at
99
- least one nav-registry entry surfaces it for an entitled persona. A route
100
- with no door for any of its personas is an **orphaned route**.
101
- 2. **No nav href is dead.** For each nav door, assert its target resolves to a
102
- real route in the route tree. A door whose target is absent is a **dead nav
103
- href**.
100
+ The two invariants are a **set-difference over two identifier lists**, not a
101
+ judgement call so run them mechanically rather than eyeballing the two files.
102
+ Serialize the enumerated route tree (Step 1) and nav registry (Step 2) to two
103
+ JSON files and run the shipped diff tool:
104
+
105
+ ```bash
106
+ node .agents/scripts/nav-registry-diff.js \
107
+ --routes <routes.json> --nav <nav-registry.json> [--refs <inbound-refs.json>] --json
108
+ ```
109
+
110
+ It prints, deterministically, the two invariants:
111
+
112
+ 1. **Every route has a persona nav door.** A route no door surfaces for an
113
+ entitled persona is an **orphaned route** (`orphanedRoutes`).
114
+ 2. **No nav href is dead.** A door whose target resolves to no route is a
115
+ **dead nav href** (`deadHrefs`).
116
+
117
+ The tool also returns `exemptRoutes` — routes it verified are _not_ genuine
118
+ orphans (see Step 3a). **Triage the tool's output**: promote each
119
+ `orphanedRoutes` / `deadHrefs` entry to a Detailed Finding, and do not report
120
+ anything the tool placed in `exemptRoutes`.
121
+
122
+ ## Step 3a: Orphan-verification exemption taxonomy
123
+
124
+ A naive route-minus-nav set-difference over-reports. Before an unsurfaced route
125
+ is reported as orphaned, it must survive this exemption taxonomy (the diff tool
126
+ applies it, and you MUST apply the same reasoning to anything you assess by
127
+ hand):
128
+
129
+ - **Dynamic-segment children of a surfaced parent** — a detail route such as
130
+ `/users/:id` (or `/blog/[slug]`) is reached _through_ its surfaced parent
131
+ list, so it is exempt when its parent path has a nav door. It is **not** exempt
132
+ when the parent itself is unsurfaced.
133
+ - **System routes** — `/login`, `/logout`, `/register`, `/auth/callback`,
134
+ `/404`, `/401`, `/403`, `/500`, `/unauthorized`, `/forbidden`, and similar are
135
+ reachable by construction (auth walls, error boundaries), never through a
136
+ persona nav door.
137
+ - **Inbound in-app references** — a route linked from within the app (a
138
+ `<Link to="…">`, a programmatic `router.push`, an in-content anchor) is
139
+ reachable even without a top-level nav door. Grep the source for an inbound
140
+ reference before reporting the route as orphaned; feed the referenced paths to
141
+ the tool via `--refs`.
142
+
143
+ Only a route that clears **all** of these is a genuine orphan worth a finding.
104
144
 
105
145
  ## Step 4: Output Requirements
106
146
 
107
147
  Generate and save a structured Markdown audit report to
108
148
  `{{auditOutputDir}}/audit-navigability-results.md`, using the template below.
109
149
 
150
+ > Grade every finding's severity on the shared
151
+ > [`Critical | High | Medium | Low` scale](helpers/audit-severity-scale.md).
152
+
110
153
  ```markdown
111
154
  # Navigability Audit report
112
155
 
@@ -116,19 +159,22 @@ Generate and save a structured Markdown audit report to
116
159
 
117
160
  ## Detailed Findings
118
161
 
119
- [For every orphaned route or dead nav href, use the following strict
120
- structure:]
162
+ [For every orphaned route or dead nav href, use the following strict structure.
163
+ Lead each title with the primary file (route module or nav registry) the
164
+ finding lives in:]
121
165
 
122
- ### [Short Title of the Issue]
166
+ ### `path/to/nav-registry-or-route.ext` — [Short title of the issue]
123
167
 
124
168
  - **Dimension:** [Orphaned Route | Dead Nav Href]
125
- - **Impact:** [High | Medium | Low]
169
+ - **Impact:** [Critical | High | Medium | Low]
170
+ - **Location:** `path/to/nav-registry-or-route.ext:line`
126
171
  - **Route / Door:** [the route path or nav-door identifier — identifier only]
127
172
  - **Persona(s):** [the persona(s) affected]
128
173
  - **Current State:** [why the route is unreachable or the href is dead]
129
174
  - **Recommendation & Rationale:** [the nav-registry change that restores
130
175
  reachability — add a door for the orphaned route, or fix/remove the dead
131
176
  href]
177
+ - **Acceptance signal:** [the command or observable that proves this finding is remediated — e.g. a re-run of this lens reporting the route reachable]
132
178
  - **Agent Prompt:**
133
179
  `[A copy-pasteable, specific prompt to execute the nav-registry fix.]`
134
180
  ```
@@ -138,3 +184,13 @@ structure:]
138
184
  This is a **read-only** audit. Provide the critique and the nav-registry fixes,
139
185
  but do not modify the route tree or the nav registry. Log route and door
140
186
  identifiers only — never full route bodies, source contents, or persona data.
187
+
188
+ ## Self-cross-check (mandatory — filter false positives before you finalize)
189
+
190
+ Before you write the report artifact from the previous step, run the shared
191
+ adversarial self-cross-check over your Detailed Findings — see
192
+ [`helpers/audit-self-check.md`](helpers/audit-self-check.md). It defines the
193
+ per-finding evidence bar, the exclusion list, and the final re-open-and-drop
194
+ pass whose `kept <k> / dropped <d>` counts you record in the Executive
195
+ Summary, so the sequential single-pass path filters unverified findings just as
196
+ the orchestrated path's adversarial reviewer does.