mandrel 2.3.0 → 2.5.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/audit-checklists/accessibility.md +29 -0
- package/.agents/audit-checklists/architecture.md +4 -5
- package/.agents/audit-checklists/clean-code.md +10 -0
- package/.agents/audit-checklists/data-model.md +22 -0
- package/.agents/audit-checklists/dependencies.md +11 -2
- package/.agents/audit-checklists/devops.md +4 -0
- package/.agents/audit-checklists/navigability.md +3 -0
- package/.agents/audit-checklists/performance.md +8 -11
- package/.agents/audit-checklists/privacy.md +3 -4
- package/.agents/audit-checklists/quality.md +2 -0
- package/.agents/audit-checklists/security.md +4 -5
- package/.agents/audit-checklists/seo.md +7 -1
- package/.agents/audit-checklists/sre.md +14 -12
- package/.agents/audit-checklists/ux-ui.md +4 -0
- package/.agents/docs/configuration.md +3 -0
- package/.agents/docs/workflows.md +4 -3
- package/.agents/schemas/agentrc.schema.json +17 -0
- package/.agents/schemas/audit-rules.json +134 -19
- package/.agents/schemas/audit-rules.schema.json +6 -2
- package/.agents/scripts/audit-labels-bootstrap.js +4 -4
- package/.agents/scripts/audit-to-stories.js +244 -19
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +26 -3
- package/.agents/scripts/lib/audit-suite/dispatch-checklist.js +132 -0
- package/.agents/scripts/lib/audit-suite/index.js +1 -0
- package/.agents/scripts/lib/audit-suite/selector.js +290 -14
- package/.agents/scripts/lib/audit-to-stories/audit-lenses.js +2 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +5 -1
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +23 -3
- package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +38 -0
- package/.agents/scripts/lib/audit-to-stories/ledger.js +256 -0
- package/.agents/scripts/lib/audit-to-stories/parse-audit-md.js +41 -7
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +20 -2
- package/.agents/scripts/lib/command-header.js +1 -1
- package/.agents/scripts/lib/config-settings-schema-delivery.js +21 -0
- package/.agents/scripts/lib/dynamic-workflow/performance-report-contract.js +5 -3
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +56 -0
- package/.agents/scripts/lib/findings/route-finding.js +108 -10
- package/.agents/scripts/lib/observability/runtime-friction.js +62 -0
- package/.agents/scripts/lib/orchestration/resolve-stories.js +9 -1
- package/.agents/scripts/lib/orchestration/retro-proposals.js +49 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +73 -15
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +81 -1
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +1 -0
- package/.agents/scripts/lib/orchestration/ticket-lease.js +78 -10
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +68 -16
- package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +73 -0
- package/.agents/scripts/lib/wave-runner/live-probe.js +93 -12
- package/.agents/scripts/nav-registry-diff.js +449 -0
- package/.agents/scripts/single-story-init.js +189 -51
- package/.agents/scripts/stories-wave-tick.js +39 -2
- package/.agents/workflows/audit-accessibility.md +243 -0
- package/.agents/workflows/audit-architecture.md +89 -71
- package/.agents/workflows/audit-clean-code.md +87 -53
- package/.agents/workflows/audit-data-model.md +198 -0
- package/.agents/workflows/audit-dependencies.md +143 -28
- package/.agents/workflows/audit-devops.md +109 -18
- package/.agents/workflows/audit-documentation.md +25 -53
- package/.agents/workflows/audit-navigability.md +78 -22
- package/.agents/workflows/audit-performance.md +207 -103
- package/.agents/workflows/audit-privacy.md +51 -13
- package/.agents/workflows/audit-quality.md +71 -61
- package/.agents/workflows/audit-security.md +94 -71
- package/.agents/workflows/audit-seo.md +80 -25
- package/.agents/workflows/audit-sre.md +99 -66
- package/.agents/workflows/audit-to-stories.md +44 -5
- package/.agents/workflows/audit-ux-ui.md +71 -17
- package/.agents/workflows/deliver.md +32 -8
- package/.agents/workflows/helpers/audit-dual-path.md +59 -0
- package/.agents/workflows/helpers/audit-self-check.md +70 -0
- package/.agents/workflows/helpers/audit-severity-scale.md +19 -0
- package/.agents/workflows/helpers/deliver-story.md +25 -0
- package/docs/CHANGELOG.md +29 -0
- package/package.json +1 -1
- package/.agents/audit-checklists/lighthouse.md +0 -15
- package/.agents/schemas/audit-results.schema.json +0 -69
- 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 &
|
|
39
|
-
|
|
40
|
-
> Apply [`helpers/parallel-tooling.md`](helpers/parallel-tooling.md) when batching the
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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:** [
|
|
66
|
-
- **
|
|
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
|
|
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
|
|
153
|
+
### `path/to/package.json` — [Package name — issue, no version]
|
|
74
154
|
|
|
75
|
-
- **Dimension:** [Security Fix | Major Upgrade |
|
|
76
|
-
- **Impact:** [High | Medium | Low]
|
|
77
|
-
- **
|
|
78
|
-
- **
|
|
79
|
-
|
|
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@
|
|
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:
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
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
|
|
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
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
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
|
-
- `
|
|
73
|
-
route
|
|
74
|
-
|
|
75
|
-
reachability
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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:
|
|
98
|
+
## Step 3: Run the deterministic cross-check
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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
|
|
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.
|