mandrel 2.30.0 → 2.32.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/README.md +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +4 -4
- package/.agents/docs/agentrc-reference.json +61 -57
- package/.agents/docs/configuration.md +274 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/ci-remediation.md +44 -1
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +0 -1
- package/.agents/schemas/agentrc.schema.json +1939 -1400
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +7 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +136 -0
- package/.agents/scripts/check-lifecycle-lint.js +36 -112
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-entry-sync.js +469 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +17 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +65 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/story-body.js +12 -59
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +0 -2
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +2 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +2 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +49 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +12 -3
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- package/.agents/workflows/helpers/signals.md +0 -112
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* update-dead-exports-baseline.js — the producer behind the dead-export
|
|
5
|
+
* ratchet (Story #5011).
|
|
6
|
+
*
|
|
7
|
+
* `check-dead-exports.js` has shipped since Story #1852 with no way to write
|
|
8
|
+
* the baseline it ratchets against. Every seed and every refresh was therefore
|
|
9
|
+
* a hand-edit: run the checker with `--json`, copy `currentRows` out of the
|
|
10
|
+
* report, re-sort them, and preserve `$schema` / `kernelVersion` / `mode` by
|
|
11
|
+
* hand. That is the gap this CLI closes — dead-exports now sits beside its four
|
|
12
|
+
* `update-*-baseline.js` siblings instead of being the one baseline an operator
|
|
13
|
+
* had to author in a text editor.
|
|
14
|
+
*
|
|
15
|
+
* **Fail closed, unlike the checker.** `check-dead-exports.js` treats a knip
|
|
16
|
+
* spawn or parse failure as advisory (exit 0 + a stderr warning) because it
|
|
17
|
+
* still holds a committed snapshot to compare against — a broken knip install
|
|
18
|
+
* must not redden CI on its own. The producer has no such fallback: the file it
|
|
19
|
+
* is about to write *is* the snapshot. An empty row set persisted from a failed
|
|
20
|
+
* run would silently grandfather every dead export in the repository and blind
|
|
21
|
+
* the ratchet permanently. So this CLI exits non-zero and writes **nothing**
|
|
22
|
+
* whenever knip cannot run, its report cannot be parsed, or the knip version
|
|
23
|
+
* cannot be resolved. That asymmetry with the checker is deliberate.
|
|
24
|
+
*
|
|
25
|
+
* **Envelope, not the shared writer.** Dead-exports does not route through
|
|
26
|
+
* `lib/baselines/writer.js`: that writer admits only the kinds registered in
|
|
27
|
+
* `lib/baselines/envelope.js` and requires a `*` rollup row, while dead-exports
|
|
28
|
+
* is an out-of-band ratchet kind (`lib/audit-baselines/kinds.js`) that carries
|
|
29
|
+
* no rollup. This CLI writes exactly the shape the checker already reads and
|
|
30
|
+
* both committed baselines already carry:
|
|
31
|
+
*
|
|
32
|
+
* { $schema, kernelVersion, generatedAt, [mode], rows: [{ file, symbol }] }
|
|
33
|
+
*
|
|
34
|
+
* `kernelVersion` is knip's own installed version — knip is the scorer, so a
|
|
35
|
+
* knip upgrade is what invalidates the rows. `mode` is stamped on the
|
|
36
|
+
* `--production` pass only, matching the committed production baseline.
|
|
37
|
+
*
|
|
38
|
+
* Rows are de-duplicated and sorted by `(file, symbol)` with the same
|
|
39
|
+
* comparator `check-dead-exports.js` uses for its diff output, so a re-run
|
|
40
|
+
* against an unchanged tree differs only in `generatedAt` and review sees real
|
|
41
|
+
* movement rather than reordering noise.
|
|
42
|
+
*
|
|
43
|
+
* The pass is selected with `--production`, and the baseline/label/mode triple
|
|
44
|
+
* comes from `lib/dead-exports-mode.js` — the same resolver the checker uses.
|
|
45
|
+
* Resolving that pairing independently here is precisely how a producer would
|
|
46
|
+
* end up writing production rows over the default baseline.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
import fs from 'node:fs';
|
|
50
|
+
import path from 'node:path';
|
|
51
|
+
import process from 'node:process';
|
|
52
|
+
import { runAsCli } from './lib/cli-utils.js';
|
|
53
|
+
import {
|
|
54
|
+
extractRowsFromKnip,
|
|
55
|
+
readKnipOutput,
|
|
56
|
+
runKnip,
|
|
57
|
+
} from './lib/dead-exports-knip.js';
|
|
58
|
+
import { resolveDeadExportsMode } from './lib/dead-exports-mode.js';
|
|
59
|
+
|
|
60
|
+
/** `$schema` ref stamped into every dead-export baseline envelope. */
|
|
61
|
+
export const DEAD_EXPORTS_SCHEMA_REF =
|
|
62
|
+
'https://mandrel.dev/baselines/dead-exports.schema.json';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Parse argv for `--production`, `--baseline <path>` and `--knip-output
|
|
66
|
+
* <path>`. `--knip-output` is the test seam: it feeds a pre-captured knip JSON
|
|
67
|
+
* report instead of spawning knip, and mirrors the checker's flag of the same
|
|
68
|
+
* name so a captured report drives both sides of the ratchet.
|
|
69
|
+
*
|
|
70
|
+
* @param {string[]} argv
|
|
71
|
+
* @returns {{ baselinePath: string | null, knipOutputPath: string | null, production: boolean }}
|
|
72
|
+
*/
|
|
73
|
+
export function parseArgv(argv = []) {
|
|
74
|
+
const out = { baselinePath: null, knipOutputPath: null, production: false };
|
|
75
|
+
const valueFlags = {
|
|
76
|
+
'--baseline': 'baselinePath',
|
|
77
|
+
'--knip-output': 'knipOutputPath',
|
|
78
|
+
};
|
|
79
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
80
|
+
const arg = argv[i];
|
|
81
|
+
if (arg === '--production') {
|
|
82
|
+
out.production = true;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const field = valueFlags[arg];
|
|
86
|
+
const next = argv[i + 1];
|
|
87
|
+
if (field && next && !next.startsWith('--')) {
|
|
88
|
+
out[field] = next;
|
|
89
|
+
i += 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Resolve the knip version to stamp as `kernelVersion`, read from the
|
|
97
|
+
* installed package rather than recalled. Returns `null` when knip is not
|
|
98
|
+
* installed or its manifest is unreadable — the caller treats that as a
|
|
99
|
+
* fail-closed condition, because an unstamped baseline cannot signal to a
|
|
100
|
+
* later run that the scorer moved underneath it.
|
|
101
|
+
*
|
|
102
|
+
* @param {{ cwd?: string, readFileImpl?: typeof fs.readFileSync }} [opts]
|
|
103
|
+
* @returns {string | null}
|
|
104
|
+
*/
|
|
105
|
+
export function resolveKnipKernelVersion({
|
|
106
|
+
cwd = process.cwd(),
|
|
107
|
+
readFileImpl = fs.readFileSync,
|
|
108
|
+
} = {}) {
|
|
109
|
+
try {
|
|
110
|
+
const manifest = path.resolve(cwd, 'node_modules', 'knip', 'package.json');
|
|
111
|
+
const version = JSON.parse(readFileImpl(manifest, 'utf-8'))?.version;
|
|
112
|
+
return typeof version === 'string' && version.length > 0 ? version : null;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* De-duplicate and sort `{ file, symbol }` rows into the committed order.
|
|
120
|
+
*
|
|
121
|
+
* Identity is `(file, symbol)` — the same identity `diffRows` in
|
|
122
|
+
* `check-dead-exports.js` uses — so two knip issue records naming the same
|
|
123
|
+
* dead file collapse to one row. The comparator is that checker's comparator,
|
|
124
|
+
* which is why re-sorting an already-committed baseline is a no-op.
|
|
125
|
+
*
|
|
126
|
+
* @param {Array<{ file?: unknown, symbol?: unknown }>} rows
|
|
127
|
+
* @returns {Array<{ file: string, symbol: string }>}
|
|
128
|
+
*/
|
|
129
|
+
export function normalizeRows(rows) {
|
|
130
|
+
const seen = new Set();
|
|
131
|
+
const out = [];
|
|
132
|
+
for (const row of rows ?? []) {
|
|
133
|
+
if (typeof row?.file !== 'string' || typeof row?.symbol !== 'string')
|
|
134
|
+
continue;
|
|
135
|
+
const key = `${row.file}\0${row.symbol}`;
|
|
136
|
+
if (seen.has(key)) continue;
|
|
137
|
+
seen.add(key);
|
|
138
|
+
out.push({ file: row.file, symbol: row.symbol });
|
|
139
|
+
}
|
|
140
|
+
return out.sort(
|
|
141
|
+
(a, b) => a.file.localeCompare(b.file) || a.symbol.localeCompare(b.symbol),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Assemble the baseline envelope. `mode` is stamped on the production pass
|
|
147
|
+
* only: the default baseline has never carried the key, and adding it would
|
|
148
|
+
* churn the committed file for no signal.
|
|
149
|
+
*
|
|
150
|
+
* @param {{ kernelVersion: string, mode: string, rows: Array<object>, generatedAt: string }} args
|
|
151
|
+
* @returns {object}
|
|
152
|
+
*/
|
|
153
|
+
export function buildEnvelope({ kernelVersion, mode, rows, generatedAt }) {
|
|
154
|
+
const envelope = {
|
|
155
|
+
$schema: DEAD_EXPORTS_SCHEMA_REF,
|
|
156
|
+
kernelVersion,
|
|
157
|
+
generatedAt,
|
|
158
|
+
};
|
|
159
|
+
if (mode === 'production') envelope.mode = mode;
|
|
160
|
+
envelope.rows = rows;
|
|
161
|
+
return envelope;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Obtain a knip report, either from the `--knip-output` seam or by spawning
|
|
166
|
+
* knip for the requested pass. Never throws; a failure is reported as
|
|
167
|
+
* `{ ok: false, error }` so the caller can fail closed without a try/catch.
|
|
168
|
+
*
|
|
169
|
+
* @param {{
|
|
170
|
+
* cwd: string,
|
|
171
|
+
* production: boolean,
|
|
172
|
+
* knipOutputPath: string | null,
|
|
173
|
+
* runKnipImpl: typeof runKnip,
|
|
174
|
+
* readKnipOutputImpl: typeof readKnipOutput,
|
|
175
|
+
* }} args
|
|
176
|
+
* @returns {{ ok: true, envelope: unknown } | { ok: false, error: string }}
|
|
177
|
+
*/
|
|
178
|
+
export function collectKnipReport({
|
|
179
|
+
cwd,
|
|
180
|
+
production,
|
|
181
|
+
knipOutputPath,
|
|
182
|
+
runKnipImpl,
|
|
183
|
+
readKnipOutputImpl,
|
|
184
|
+
}) {
|
|
185
|
+
if (knipOutputPath) {
|
|
186
|
+
const envelope = readKnipOutputImpl(path.resolve(cwd, knipOutputPath));
|
|
187
|
+
return envelope == null
|
|
188
|
+
? { ok: false, error: `could not read knip report at ${knipOutputPath}` }
|
|
189
|
+
: { ok: true, envelope };
|
|
190
|
+
}
|
|
191
|
+
return runKnipImpl({ cwd, production });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Reject a report the extractor cannot read. `extractRowsFromKnip` is total —
|
|
196
|
+
* it answers `[]` for any shape it does not recognise — which is the right
|
|
197
|
+
* posture for an advisory checker and the wrong one for a producer: an
|
|
198
|
+
* unrecognised report and a genuinely clean repository would persist the same
|
|
199
|
+
* empty row set. Returns an error string, or `null` when the report is usable.
|
|
200
|
+
*
|
|
201
|
+
* @param {unknown} envelope
|
|
202
|
+
* @returns {string | null}
|
|
203
|
+
*/
|
|
204
|
+
export function describeUnusableReport(envelope) {
|
|
205
|
+
if (!envelope || typeof envelope !== 'object')
|
|
206
|
+
return 'knip report is not a JSON object';
|
|
207
|
+
if (!Array.isArray(envelope.issues))
|
|
208
|
+
return 'knip report carries no `issues` array';
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Top-level CLI entry, exported so tests drive the whole pipeline through
|
|
214
|
+
* injected hooks without spawning knip or touching a committed baseline.
|
|
215
|
+
*
|
|
216
|
+
* @param {{
|
|
217
|
+
* argv?: string[],
|
|
218
|
+
* cwd?: string,
|
|
219
|
+
* stdout?: { write: (s: string) => void },
|
|
220
|
+
* stderr?: { write: (s: string) => void },
|
|
221
|
+
* runKnipImpl?: typeof runKnip,
|
|
222
|
+
* readKnipOutputImpl?: typeof readKnipOutput,
|
|
223
|
+
* readFileImpl?: typeof fs.readFileSync,
|
|
224
|
+
* writeFileImpl?: typeof fs.writeFileSync,
|
|
225
|
+
* renameImpl?: typeof fs.renameSync,
|
|
226
|
+
* now?: () => string,
|
|
227
|
+
* }} [opts]
|
|
228
|
+
* @returns {Promise<number>} 0 on a written baseline; 1 on any fail-closed path.
|
|
229
|
+
*/
|
|
230
|
+
export async function runCli({
|
|
231
|
+
argv = process.argv.slice(2),
|
|
232
|
+
cwd = process.cwd(),
|
|
233
|
+
stdout = process.stdout,
|
|
234
|
+
stderr = process.stderr,
|
|
235
|
+
runKnipImpl = runKnip,
|
|
236
|
+
readKnipOutputImpl = readKnipOutput,
|
|
237
|
+
readFileImpl = fs.readFileSync,
|
|
238
|
+
writeFileImpl = fs.writeFileSync,
|
|
239
|
+
renameImpl = fs.renameSync,
|
|
240
|
+
now = () => new Date().toISOString(),
|
|
241
|
+
} = {}) {
|
|
242
|
+
const { baselinePath, knipOutputPath, production } = parseArgv(argv);
|
|
243
|
+
const { mode, label, baseline } = resolveDeadExportsMode(production);
|
|
244
|
+
const target = path.resolve(cwd, baselinePath ?? baseline);
|
|
245
|
+
|
|
246
|
+
const kernelVersion = resolveKnipKernelVersion({ cwd, readFileImpl });
|
|
247
|
+
if (!kernelVersion) {
|
|
248
|
+
stderr.write(
|
|
249
|
+
`[${label}] ❌ cannot resolve knip's version from node_modules/knip/package.json — refusing to write ${target}\n`,
|
|
250
|
+
);
|
|
251
|
+
return 1;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const report = collectKnipReport({
|
|
255
|
+
cwd,
|
|
256
|
+
production,
|
|
257
|
+
knipOutputPath,
|
|
258
|
+
runKnipImpl,
|
|
259
|
+
readKnipOutputImpl,
|
|
260
|
+
});
|
|
261
|
+
const failure = report.ok
|
|
262
|
+
? describeUnusableReport(report.envelope)
|
|
263
|
+
: report.error;
|
|
264
|
+
if (failure) {
|
|
265
|
+
stderr.write(
|
|
266
|
+
`[${label}] ❌ ${failure} — refusing to write ${target} (an empty baseline would grandfather every dead export)\n`,
|
|
267
|
+
);
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const rows = normalizeRows(extractRowsFromKnip(report.envelope));
|
|
272
|
+
const envelope = buildEnvelope({
|
|
273
|
+
kernelVersion,
|
|
274
|
+
mode,
|
|
275
|
+
rows,
|
|
276
|
+
generatedAt: now(),
|
|
277
|
+
});
|
|
278
|
+
// Write-then-rename, matching `lib/baselines/writer.js`: a crash or a full
|
|
279
|
+
// disk mid-write must not leave a truncated envelope behind. An unparseable
|
|
280
|
+
// baseline reads as empty to `check-dead-exports.js`, which would report
|
|
281
|
+
// every pre-existing row as newly added.
|
|
282
|
+
const tmpTarget = `${target}.tmp`;
|
|
283
|
+
writeFileImpl(tmpTarget, `${JSON.stringify(envelope, null, 2)}\n`, 'utf-8');
|
|
284
|
+
renameImpl(tmpTarget, target);
|
|
285
|
+
stdout.write(
|
|
286
|
+
`[${label}] ✅ wrote ${rows.length} row(s) to ${target} (kernelVersion=${kernelVersion}).\n`,
|
|
287
|
+
);
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function main() {
|
|
292
|
+
return runCli();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
runAsCli(import.meta.url, main, {
|
|
296
|
+
source: 'dead-exports-baseline',
|
|
297
|
+
propagateExitCode: true,
|
|
298
|
+
errorPrefix: '[dead-exports-baseline] ❌ Fatal error',
|
|
299
|
+
usage: {
|
|
300
|
+
invocation:
|
|
301
|
+
'node .agents/scripts/update-dead-exports-baseline.js [--production] [--baseline <path>] [--knip-output <path>]',
|
|
302
|
+
summary:
|
|
303
|
+
'Scan → write one dead-export baseline. Regenerates the rows `check-dead-exports.js` ratchets against, so the snapshot is produced rather than hand-edited.',
|
|
304
|
+
flags: [
|
|
305
|
+
[
|
|
306
|
+
'--production',
|
|
307
|
+
'Write the production-pass baseline (baselines/dead-exports-production.json) instead of the default one.',
|
|
308
|
+
],
|
|
309
|
+
['--baseline <path>', 'Write to this path instead of the mode default.'],
|
|
310
|
+
[
|
|
311
|
+
'--knip-output <path>',
|
|
312
|
+
'Read a saved knip JSON report instead of running knip.',
|
|
313
|
+
],
|
|
314
|
+
],
|
|
315
|
+
notes: [
|
|
316
|
+
'Fails closed: when knip cannot run or its report cannot be parsed, the CLI exits 1 and writes nothing — the checker is advisory on that failure, the producer must not be.',
|
|
317
|
+
'There is no scope flag: knip scores the whole graph, so a diff-scoped refresh is not expressible. Run `npm run dead-exports:update` to refresh both passes.',
|
|
318
|
+
'Exit codes:\n 0 baseline written\n 1 knip unavailable, unparseable, or version unresolvable',
|
|
319
|
+
],
|
|
320
|
+
},
|
|
321
|
+
});
|
|
@@ -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
|
-
- **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
|
|
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 with both scopes: `coverage`, `crap`, `maintainability`, `duplication`. Dead-exports has a producer but no scope axis — knip scores the whole graph, so `npm run dead-exports:update` (both passes, via `.agents/scripts/update-dead-exports-baseline.js`) is the only sanctioned way to write `baselines/dead-exports.json` / `baselines/dead-exports-production.json`; never hand-edit those files. Lighthouse still has no producer — edit `baselines/lighthouse.json` directly. Run any of them 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.
|
|
@@ -101,7 +101,7 @@ own as the parseable marker for any future reader.)
|
|
|
101
101
|
| Maintainability | `npm run maintainability:update` | `npm run maintainability:reanchor` |
|
|
102
102
|
| Coverage | `npm run coverage:update` | `npm run coverage:reanchor` |
|
|
103
103
|
| Duplication | `npm run duplication:update` | `npm run duplication:reanchor` |
|
|
104
|
-
| Dead-exports |
|
|
104
|
+
| Dead-exports | `npm run dead-exports:update` (regenerates both passes; never hand-edit the JSON) | same — no scope flag applies |
|
|
105
105
|
| Lighthouse | edit `baselines/lighthouse.json` | same — no scope flag applies |
|
|
106
106
|
|
|
107
107
|
1. **Run the matching command for your reason** — left column after a code
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-08-
|
|
2
|
+
"generatedAt": "2026-08-06T10:26:18.702Z",
|
|
3
3
|
"generator": "generate-skills-index.js@1",
|
|
4
4
|
"skills": [
|
|
5
5
|
{
|
|
@@ -42,16 +42,6 @@
|
|
|
42
42
|
"allowedTools": null,
|
|
43
43
|
"vendor": null
|
|
44
44
|
},
|
|
45
|
-
{
|
|
46
|
-
"name": "diagnose-friction",
|
|
47
|
-
"tier": "core",
|
|
48
|
-
"category": "core",
|
|
49
|
-
"path": ".agents/skills/core/diagnose-friction/SKILL.md",
|
|
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": 9,
|
|
52
|
-
"allowedTools": ["Bash", "Read"],
|
|
53
|
-
"vendor": null
|
|
54
|
-
},
|
|
55
45
|
{
|
|
56
46
|
"name": "documentation-and-adrs",
|
|
57
47
|
"tier": "core",
|
|
@@ -148,7 +138,7 @@
|
|
|
148
138
|
"category": "qa",
|
|
149
139
|
"path": ".agents/skills/stack/qa/playwright/SKILL.md",
|
|
150
140
|
"description": "Robust E2E browser testing with Playwright. Use when writing browser-driven tests — leverage auto-waiting (no `waitForTimeout`), prefer user-visible locators (`getByRole`, `getByText`, `getByLabel`) over CSS/XPath, reuse `storageState` for auth, and enable trace-on-first-retry for CI debugging.",
|
|
151
|
-
"policyCapsuleBullets":
|
|
141
|
+
"policyCapsuleBullets": 8,
|
|
152
142
|
"allowedTools": null,
|
|
153
143
|
"vendor": "playwright"
|
|
154
144
|
},
|
|
@@ -19,3 +19,51 @@ vendor: playwright
|
|
|
19
19
|
- Write tests independent of one another so they run in parallel; clean up shared state in fixtures, not afterwards.
|
|
20
20
|
- Enable `trace: 'on-first-retry'` (or `'retain-on-failure'`) so CI failures are debuggable in the Trace Viewer.
|
|
21
21
|
- Use a unique data set per test run, or tear down state explicitly, to prevent cross-test contamination.
|
|
22
|
+
- Never let Playwright own the lifetime of a dev server it did not start: boot the server out-of-band, point the suite at the running origin, and set `reuseExistingServer` so `webServer` only probes readiness.
|
|
23
|
+
|
|
24
|
+
## Running a `webServer`-backed suite outside CI
|
|
25
|
+
|
|
26
|
+
Playwright's `webServer` block **watches the process it spawned**. That
|
|
27
|
+
assumption holds for a dev server that stays in the foreground, and breaks for
|
|
28
|
+
any manager that daemonizes one — the foreground process exits `0` while the
|
|
29
|
+
server keeps serving, Playwright reads the exit as a crash, and the run aborts
|
|
30
|
+
before a single test executes:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
Process from config.webServer exited early
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Read that line as a **lifetime-ownership mismatch, not a flake**. It reproduces
|
|
37
|
+
on every invocation, clean tree or not, and no amount of retrying, tree-cleaning
|
|
38
|
+
or timeout-raising changes it. Agent sandboxes and IDE harnesses commonly manage
|
|
39
|
+
dev servers this way (`Dev server already running at … (pid N)`), so an agent
|
|
40
|
+
meets this far more often than a developer does.
|
|
41
|
+
|
|
42
|
+
### Attach, don't boot
|
|
43
|
+
|
|
44
|
+
Invert the ownership instead of fighting it — the manager owns the process,
|
|
45
|
+
Playwright owns only the probe:
|
|
46
|
+
|
|
47
|
+
1. **Boot the server out-of-band** through whatever manages it, and confirm it
|
|
48
|
+
is serving. Its lifetime is now the manager's concern, not the runner's.
|
|
49
|
+
2. **Point the suite at the already-running origin** — set the config's
|
|
50
|
+
`baseURL` (or the `webServer.url` the block probes) to that origin, via the
|
|
51
|
+
project's own environment seam rather than an edit to committed config.
|
|
52
|
+
3. **Set `reuseExistingServer: true`** so Playwright probes the URL, finds it
|
|
53
|
+
live, and never spawns or supervises a process of its own.
|
|
54
|
+
|
|
55
|
+
This is the same convention the QA harness already encodes as
|
|
56
|
+
`qa.environments[].baseUrl`: attach to a running origin, never boot one. A suite
|
|
57
|
+
run this way exercises identical browser behavior — only the process supervision
|
|
58
|
+
differs.
|
|
59
|
+
|
|
60
|
+
### When no attachable origin exists
|
|
61
|
+
|
|
62
|
+
Some apps genuinely cannot be reached this way — the server is unreachable from
|
|
63
|
+
the sandbox, or the suite depends on a build step the sandbox cannot run. Do
|
|
64
|
+
**not** burn a timebox rediscovering that. Record the observed signature, state
|
|
65
|
+
which of the three steps above failed, and escalate on the first encounter:
|
|
66
|
+
that evidence is exactly what the `unreproducible-tier` verdict in
|
|
67
|
+
[`ci-remediation.md`](../../../../rules/ci-remediation.md) requires, and it is
|
|
68
|
+
the only verdict that lets an unrunnable tier route somewhere other than a dead
|
|
69
|
+
end.
|
|
@@ -7,10 +7,10 @@ description: Audit the repository's main documentation for staleness, semantic d
|
|
|
7
7
|
You are a Staff Engineer & Documentation Steward verifying the repository's prose
|
|
8
8
|
documentation is **up to date and complete**. Prose rots silently: commands get
|
|
9
9
|
renamed, scripts move, workflows change shape, version/topology claims go stale.
|
|
10
|
-
The deterministic gates (`check-doc-links.js
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
The deterministic gates (`check-doc-links.js` and the generators' `--check`
|
|
11
|
+
mode) catch broken links and generator drift — they cannot tell whether the
|
|
12
|
+
prose still describes how the code actually behaves. That semantic
|
|
13
|
+
verification is this lens's job. The
|
|
14
14
|
shared lens machinery — read-only constraint, scope interpretation, report
|
|
15
15
|
envelope + finding-block skeleton, severity scale, self-cross-check, and
|
|
16
16
|
execution strategy — lives in
|
|
@@ -78,7 +78,6 @@ are cheap, exact, and de-duplicate the easy findings:
|
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
80
|
node .agents/scripts/check-doc-links.js
|
|
81
|
-
node .agents/scripts/check-lifecycle-doc-drift.js
|
|
82
81
|
node .agents/scripts/generate-config-docs.js --check
|
|
83
82
|
node .agents/scripts/generate-lifecycle-docs.js --check
|
|
84
83
|
node .agents/scripts/generate-workflows-doc.js --check
|
|
@@ -87,7 +86,7 @@ node .agents/scripts/resolve-doc-tiers.js --json
|
|
|
87
86
|
|
|
88
87
|
Fold the results in as findings:
|
|
89
88
|
|
|
90
|
-
- **Checker failures** (broken links,
|
|
89
|
+
- **Checker failures** (broken links, generator drift) become individual
|
|
91
90
|
findings with `Category: Link Integrity` (or `Generator Drift` for the
|
|
92
91
|
lifecycle gate), citing the checker output verbatim.
|
|
93
92
|
- **Generator dirtiness** (any `--check` reporting stale output, including
|
|
@@ -227,8 +227,8 @@ runs FIRST and widens the net across open + closed issues; the exact
|
|
|
227
227
|
was reworded but whose *location* is unchanged still confirms against the Issue
|
|
228
228
|
that already tracks that location, because the audit filers stamp a
|
|
229
229
|
location-based `audit-semantic-keys` footer alongside the `audit-fingerprints`
|
|
230
|
-
footer.
|
|
231
|
-
[`
|
|
230
|
+
footer. Filings from the
|
|
231
|
+
[`retro-proposals-graduator`](../scripts/lib/feedback-loop/retro-proposals-graduator.js)
|
|
232
232
|
carry the same canonical `audit-fingerprints` footer, so a sweep recognizes a
|
|
233
233
|
graduator-filed issue and never re-files it.
|
|
234
234
|
|
|
@@ -229,18 +229,17 @@ which path produced it.
|
|
|
229
229
|
> stage before synthesising the report. It derives its per-dimension prompts
|
|
230
230
|
> from the *lens* markdown at run time — the lens stays the single source of
|
|
231
231
|
> truth. This is a performance optimization over path 1, **not** a separate
|
|
232
|
-
> contract
|
|
233
|
-
> [`../../
|
|
234
|
-
>
|
|
235
|
-
>
|
|
236
|
-
>
|
|
237
|
-
>
|
|
238
|
-
>
|
|
239
|
-
>
|
|
240
|
-
>
|
|
241
|
-
>
|
|
242
|
-
>
|
|
243
|
-
> the final report artifact.
|
|
232
|
+
> contract, and it is not covered by the No-Shim / hard-cutover rule in
|
|
233
|
+
> [`../../rules/git-conventions.md`](../../rules/git-conventions.md) because
|
|
234
|
+
> there is one report contract and only the execution strategy varies — the
|
|
235
|
+
> same capability-degradation pattern the protocol endorses for live-docs
|
|
236
|
+
> fallback. **The host owns the choice.** Mandrel ships no in-repo strategy
|
|
237
|
+
> selector and no force-override env var: Claude Code launches the saved
|
|
238
|
+
> workflow when it can, and you get path 1 or 2 above when it cannot.
|
|
239
|
+
> Suppress the orchestrated path with `CLAUDE_CODE_DISABLE_WORKFLOWS=1`
|
|
240
|
+
> or `disableWorkflows: true` in `.claude/settings.json`. On the orchestrated
|
|
241
|
+
> path the analysis subagents are granted only read/search tools (`Read`,
|
|
242
|
+
> `Grep`, `Glob`) — the single write is the final report artifact.
|
|
244
243
|
|
|
245
244
|
## Parallel tooling {#parallel-tooling}
|
|
246
245
|
|
|
@@ -95,17 +95,18 @@ suppress the noise.
|
|
|
95
95
|
|
|
96
96
|
## When a number changes
|
|
97
97
|
|
|
98
|
-
Update
|
|
99
|
-
|
|
100
|
-
1. `
|
|
101
|
-
[`
|
|
102
|
-
|
|
103
|
-
[`
|
|
104
|
-
|
|
105
|
-
[`
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
98
|
+
Update two places **in the same commit**:
|
|
99
|
+
|
|
100
|
+
1. `CODING_GUARDRAILS_DEFAULTS` in
|
|
101
|
+
[`scripts/lib/config/quality.js`](../../scripts/lib/config/quality.js).
|
|
102
|
+
The schema literal in
|
|
103
|
+
[`config-settings-schema-quality.js`](../../scripts/lib/config-settings-schema-quality.js)
|
|
104
|
+
imports it, and `npm run docs:gen` propagates the number into
|
|
105
|
+
[`agentrc-reference.json`](../../docs/agentrc-reference.json), the shipped
|
|
106
|
+
JSON-Schema mirror, and the `configuration.md` key table.
|
|
107
|
+
2. The threshold cell or sentence in this helper.
|
|
108
|
+
|
|
109
|
+
The generator-fidelity test (`tests/config-schema-mirror-drift.test.js`)
|
|
110
|
+
catches a stale generated artifact; the helper-prose drift is caught by
|
|
111
|
+
`audit-clean-code` and `agent-protocol` linking back here rather than
|
|
112
|
+
restating the numbers.
|
|
@@ -293,14 +293,9 @@ For every finding, provide:
|
|
|
293
293
|
|
|
294
294
|
Findings that Step 4.5 remediated on `[HEAD_REF]` MUST be rendered under a
|
|
295
295
|
dedicated **`## Fixed on-branch`** heading, **not** in the severity groups
|
|
296
|
-
above. This
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
(the sole canonical reader of the unified comment)
|
|
300
|
-
skips every entry inside this section (both because a fixed entry is
|
|
301
|
-
rendered with a **✅ prefix** — so it carries no leading severity emoji the
|
|
302
|
-
parser would match — and because the parser has an explicit
|
|
303
|
-
Fixed-on-branch section guard).
|
|
296
|
+
above. This keeps a remediated finding legible as remediated to every reader
|
|
297
|
+
of the unified comment — human or downstream — rather than reading as an
|
|
298
|
+
outstanding severity-grouped finding.
|
|
304
299
|
|
|
305
300
|
Render each fixed finding as a `✅`-prefixed line naming its original
|
|
306
301
|
severity, the file path in backticks, and the remediating commit SHA, e.g.:
|
|
@@ -118,7 +118,8 @@ engine runs, never what runs — gates, PR, and terminal envelope are identical.
|
|
|
118
118
|
**Read the mode; never infer it from shape.** Before spawning
|
|
119
119
|
anything, read the Story's `dispatchMode` from the resolver envelope
|
|
120
120
|
(`stories[].dispatchMode`, produced by `resolveStoryDispatchMode` in
|
|
121
|
-
`lib/orchestration/complexity-gate.js
|
|
121
|
+
`lib/orchestration/complexity-gate.js`, which decides on the resolved set size
|
|
122
|
+
alone — it does not read the Story body). A Story with `dispatchMode: "inline"`
|
|
122
123
|
executes [`deliver-story.md`](deliver-story.md) **inline in this session** — no
|
|
123
124
|
`story-worker` sub-agent boot and no fresh acceptance-critic sub-agents
|
|
124
125
|
(sub-agent boots are the dominant deliver-phase token cost at trivial scope) —
|
|
@@ -139,19 +139,18 @@ and the `rules/security-baseline.md` MUSTs all run exactly as for a
|
|
|
139
139
|
full-ceremony Story. The lite route's `preserves` field is the machine-readable
|
|
140
140
|
record of those non-negotiables; there is no lite-specific gate bypass.
|
|
141
141
|
|
|
142
|
-
**
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
That derived route sets ceremony. It does **not** set the dispatch mode,
|
|
142
|
+
**Ceremony comes from the landed diff; the dispatch mode comes from the
|
|
143
|
+
run.** Persist stamps a lite cohort's Stories with the `route::lite` label as a
|
|
144
|
+
_human-visible hint only_ (and ledgers the authored verdict — recorded reason
|
|
145
|
+
plus per-Story shape evidence — on the `story-plan-state` checkpoint); the
|
|
146
|
+
label is never the control signal. Ceremony is resolved from the **derived
|
|
147
|
+
change level** (`deriveChangeLevel` over the computed change set — digest § 3),
|
|
148
|
+
not from a body-shape read: a footprint intersecting a sensitive-path class
|
|
149
|
+
derives `high`, so the Story keeps its fresh acceptance critic. The light path
|
|
150
|
+
is the one caller that reads the authored body's shape, through
|
|
151
|
+
`deriveStoryShape` (`lib/orchestration/complexity-gate.js`).
|
|
152
|
+
|
|
153
|
+
That derived level sets ceremony. It does **not** set the dispatch mode,
|
|
155
154
|
because `inline` names one indivisible resource — the router's own session —
|
|
156
155
|
and only run topology can say whether it is free: a **single-Story run**
|
|
157
156
|
executes inline, and every Story of a multi-Story run dispatches as a
|
|
@@ -200,8 +199,8 @@ critic (the redundant pre-pass buys no measurable quality and roughly
|
|
|
200
199
|
triples the acceptance-block cost). `acceptance-eval.js` is the
|
|
201
200
|
deterministic **scorer** of that one authored verdict — schema validation,
|
|
202
201
|
round cap, proceed / redraft / block — not an independent additional pass
|
|
203
|
-
over the criteria. The M4-B floor holds: one verdict per cluster, the
|
|
204
|
-
cluster count owned by
|
|
202
|
+
over the criteria. The M4-B floor holds: one verdict per cluster, with the
|
|
203
|
+
cluster count owned by the dispatching caller and never by routing.
|
|
205
204
|
|
|
206
205
|
**One round = N cluster critics → ONE merged verdict → ONE gate call.** The
|
|
207
206
|
clusters are how a round is _authored_; they are not how it is _scored_.
|
|
@@ -279,7 +278,9 @@ floor forces `fresh`). Review depth reads the same derived level via
|
|
|
279
278
|
`review-depth.js` inside close, so the two decisions cannot disagree.
|
|
280
279
|
|
|
281
280
|
**Inline-dispatch override.** When the Story dispatches
|
|
282
|
-
`inline` (`resolveStoryDispatchMode` → `inline`,
|
|
281
|
+
`inline` (`resolveStoryDispatchMode` → `inline`, which is exactly a
|
|
282
|
+
single-Story run — the function reads the resolved set size and nothing
|
|
283
|
+
else), run
|
|
283
284
|
every acceptance critic **inline** — do not spawn fresh-context critic
|
|
284
285
|
sub-agents regardless of what the profile would otherwise resolve. The self-eval rigor
|
|
285
286
|
(scoring each `acceptance[]` item against the one computed change set, with
|
|
@@ -393,6 +394,16 @@ judgment that help text cannot carry.
|
|
|
393
394
|
wants the PR left at `agent::closing` for a human land (or a wrapper that
|
|
394
395
|
will invoke `single-story-confirm-merge.js` itself). Reports `pending` —
|
|
395
396
|
the work is not done, nothing is broken, and one named command finishes it.
|
|
397
|
+
- `--override-review-block "<reason>"` — when the Story-scope review's
|
|
398
|
+
**critical** blocker is one you have read and judged wrong (a false positive,
|
|
399
|
+
or a finding the ratchet correctly exempts). It is the only sanctioned way
|
|
400
|
+
past that halt: reach for it instead of merging the PR by hand, because a
|
|
401
|
+
hand-merge bypasses the gate and records nothing. The reason is mandatory and
|
|
402
|
+
is written to three places (Story comment, PR comment, a
|
|
403
|
+
`review-block-overridden` friction signal), and the terminal envelope reports
|
|
404
|
+
`gates.codeReview: "overridden"` rather than `"passed"`. If you find yourself
|
|
405
|
+
reaching for it twice for the same shape of finding, the gate is
|
|
406
|
+
miscalibrated — fix the gate, not the run.
|
|
396
407
|
- `--max-wait-seconds <n>` — from a headless caller with no host
|
|
397
408
|
tool-invocation ceiling, to keep single-block semantics
|
|
398
409
|
without editing the consumer's config.
|