mandrel 2.36.0 → 2.38.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 +16 -16
- package/.agents/agents/plan-critic.md +1 -1
- package/.agents/docs/SDLC.md +40 -40
- package/.agents/docs/configuration.md +41 -16
- package/.agents/docs/execution-reference.md +3 -3
- package/.agents/docs/quality-gates.md +2 -2
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +5 -5
- package/.agents/rules/changelog-style.md +3 -3
- package/.agents/rules/ci-remediation.md +1 -1
- package/.agents/rules/git-conventions-reference.md +4 -4
- package/.agents/rules/testing-standards.md +56 -0
- package/.agents/schemas/agentrc.schema.json +9 -9
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +1 -1
- package/.agents/scripts/apply-quality-bootstrap.js +1 -1
- package/.agents/scripts/audit-to-stories.js +2 -2
- package/.agents/scripts/boot-sweep.js +7 -1
- package/.agents/scripts/check-context-budget.js +195 -103
- package/.agents/scripts/check-cyclomatic.js +112 -42
- package/.agents/scripts/check-doc-links.js +32 -4
- package/.agents/scripts/check-generated-validator.js +202 -0
- package/.agents/scripts/check-lifecycle-lint.js +2 -68
- package/.agents/scripts/check-schema-references.js +2 -28
- package/.agents/scripts/check-test-temp-hygiene.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +1 -1
- package/.agents/scripts/deliver-light.js +10 -10
- package/.agents/scripts/deliver-recover.js +1 -1
- package/.agents/scripts/drain-pending-cleanup.js +19 -2
- package/.agents/scripts/evidence-gate.js +1 -1
- package/.agents/scripts/generate-workflows-doc.js +1 -1
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +5 -5
- package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +1 -1
- package/.agents/scripts/lib/baselines/components.js +32 -2
- package/.agents/scripts/lib/baselines/env-overrides.js +1 -1
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-budget.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
- package/.agents/scripts/lib/bdd-step-index.js +1 -1
- package/.agents/scripts/lib/bootstrap/branch-protection.js +2 -2
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/bootstrap/install-ledger.js +1 -1
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +1 -1
- package/.agents/scripts/lib/bootstrap/prompt.js +1 -1
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +2 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +1 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +1 -1
- package/.agents/scripts/lib/cli-args.js +1 -3
- package/.agents/scripts/lib/close-validation/gates.js +1 -1
- package/.agents/scripts/lib/close-validation/process.js +1 -1
- package/.agents/scripts/lib/command-header.js +1 -1
- package/.agents/scripts/lib/config/delivery-routing.js +1 -1
- package/.agents/scripts/lib/config/explain.js +1 -1
- package/.agents/scripts/lib/config/runners.js +2 -2
- package/.agents/scripts/lib/config/runtime.js +1 -1
- package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +3 -3
- package/.agents/scripts/lib/config-settings-schema-quality.js +6 -6
- package/.agents/scripts/lib/config-settings-schema.js +49 -9
- package/.agents/scripts/lib/coverage-baseline.js +2 -2
- package/.agents/scripts/lib/cpu-pool.js +90 -10
- package/.agents/scripts/lib/crap-utils.js +6 -2
- package/.agents/scripts/lib/cyclomatic-ceiling.js +28 -6
- package/.agents/scripts/lib/cyclomatic-scope.js +144 -0
- package/.agents/scripts/lib/dependency-version.js +86 -0
- package/.agents/scripts/lib/duplicate-search.js +2 -2
- package/.agents/scripts/lib/errors/index.js +1 -1
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +2 -2
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +5 -5
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +2 -2
- package/.agents/scripts/lib/findings/classify-finding.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +12 -12
- package/.agents/scripts/lib/findings/route-finding.js +2 -2
- package/.agents/scripts/lib/generated/agentrc-validator.js +16 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/install-cmd-parser.js +1 -1
- package/.agents/scripts/lib/knip-entry-sync.js +1 -57
- package/.agents/scripts/lib/label-constants.js +2 -2
- package/.agents/scripts/lib/label-taxonomy.js +1 -1
- package/.agents/scripts/lib/maintainability-utils.js +108 -10
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/onboard/init-tail.js +4 -4
- package/.agents/scripts/lib/onboard/scaffold-docs.js +2 -2
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
- package/.agents/scripts/lib/orchestration/code-review.js +4 -4
- package/.agents/scripts/lib/orchestration/complexity-gate.js +5 -5
- package/.agents/scripts/lib/orchestration/deliver-recover.js +3 -3
- package/.agents/scripts/lib/orchestration/docs-digest.js +3 -3
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +2 -2
- package/.agents/scripts/lib/orchestration/light-backstop.js +1 -1
- package/.agents/scripts/lib/orchestration/light-escalation.js +6 -6
- package/.agents/scripts/lib/orchestration/light-suitability.js +19 -19
- package/.agents/scripts/lib/orchestration/plan-context.js +4 -4
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-metrics.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +3 -3
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +6 -6
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +86 -25
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +3 -3
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +6 -6
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +7 -7
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -1
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +3 -3
- package/.agents/scripts/lib/orchestration/pr-watch.js +1 -30
- package/.agents/scripts/lib/orchestration/reassert-status-column.js +2 -2
- package/.agents/scripts/lib/orchestration/remote-verifier.js +2 -2
- package/.agents/scripts/lib/orchestration/resolve-stories.js +4 -4
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +3 -3
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/native.js +9 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +3 -3
- package/.agents/scripts/lib/orchestration/run-epilogue.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +5 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +52 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +3 -3
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +2 -5
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +14 -12
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +34 -9
- package/.agents/scripts/lib/orchestration/ticket-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +6 -4
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
- package/.agents/scripts/lib/preflight-runner.js +3 -3
- package/.agents/scripts/lib/qa/qa-session.js +1 -1
- package/.agents/scripts/lib/runtime-deps/manifest.js +1 -7
- package/.agents/scripts/lib/runtime-deps/scan-imports.js +3 -58
- package/.agents/scripts/lib/signals/schema.js +1 -1
- package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +284 -25
- package/.agents/scripts/lib/skills/walk-skill-files.js +1 -1
- package/.agents/scripts/lib/source-text/strip-js-comments.js +115 -0
- package/.agents/scripts/lib/story-adjacency.js +2 -2
- package/.agents/scripts/lib/templates/decomposer-prompts.js +1 -1
- package/.agents/scripts/lib/test-isolate/runner.js +3 -3
- package/.agents/scripts/lib/test-runner-contract.js +14 -6
- package/.agents/scripts/lib/test-tiers.js +135 -18
- package/.agents/scripts/lib/ticket-body-sections.js +1 -1
- package/.agents/scripts/lib/transpile.js +16 -3
- package/.agents/scripts/lib/util/concurrent-map.js +1 -1
- package/.agents/scripts/lib/util/parse-id-list.js +1 -1
- package/.agents/scripts/lib/wave-runner/live-probe.js +4 -4
- package/.agents/scripts/lib/wave-runner/ready-set.js +1 -1
- package/.agents/scripts/lib/workflow-closure.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +170 -32
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +2 -5
- package/.agents/scripts/lint-issue-body.js +3 -3
- package/.agents/scripts/plan-context.js +4 -4
- package/.agents/scripts/plan-critics.js +2 -2
- package/.agents/scripts/plan-persist.js +2 -2
- package/.agents/scripts/plan-run-epilogue.js +2 -2
- package/.agents/scripts/pr-watch-with-update.js +305 -137
- package/.agents/scripts/providers/github/board-add.js +1 -1
- package/.agents/scripts/providers/github/errors.js +1 -1
- package/.agents/scripts/providers/github/tickets.js +114 -21
- package/.agents/scripts/quality-preview.js +162 -70
- package/.agents/scripts/resolve-stories.js +3 -3
- package/.agents/scripts/resync-status-column.js +1 -1
- package/.agents/scripts/run-lint.js +1 -1
- package/.agents/scripts/run-test-profile.js +8 -5
- package/.agents/scripts/run-tests.js +79 -14
- package/.agents/scripts/single-story-close.js +2 -4
- package/.agents/scripts/single-story-init.js +10 -6
- package/.agents/scripts/stories-wave-tick.js +8 -8
- package/.agents/skills/core/idea-refinement/SKILL.md +5 -5
- package/.agents/skills/core/scope-triage/SKILL.md +3 -3
- package/.agents/skills/skills.index.json +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -1
- package/.agents/workflows/audit-data-model.md +1 -1
- package/.agents/workflows/audit-documentation.md +2 -2
- package/.agents/workflows/audit-to-stories.md +11 -11
- package/.agents/workflows/git-deliver.md +4 -4
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/audit-lens-core.md +1 -1
- package/.agents/workflows/helpers/code-review.md +4 -4
- package/.agents/workflows/helpers/deliver-light.md +26 -26
- package/.agents/workflows/helpers/deliver-reference.md +15 -7
- package/.agents/workflows/helpers/deliver-story-reference.md +9 -4
- package/.agents/workflows/helpers/deliver-story.md +6 -7
- package/.agents/workflows/helpers/diagnose.md +2 -2
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/helpers/plan-reference.md +9 -9
- package/.agents/workflows/helpers/qa-core.md +8 -8
- package/.agents/workflows/helpers/worktree-lifecycle.md +6 -6
- package/.agents/workflows/{deliver.md → mandrel-deliver.md} +15 -19
- package/.agents/workflows/{plan.md → mandrel-plan.md} +9 -9
- package/.agents/workflows/memory-consolidate.md +4 -4
- package/.agents/workflows/prototype.md +3 -3
- package/.agents/workflows/qa-assist.md +12 -12
- package/.agents/workflows/qa-explore.md +6 -6
- package/.agents/workflows/qa-run.md +1 -1
- package/README.md +7 -7
- package/docs/CHANGELOG.md +43 -0
- package/lib/cli/doctor.js +1 -1
- package/lib/cli/init.js +1 -1
- package/lib/cli/migrate.js +100 -94
- package/lib/migrations/helpers/retire-agentrc-key.js +200 -0
- package/lib/migrations/index.js +32 -33
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +21 -80
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +15 -87
- package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +13 -71
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +1 -1
- package/lib/migrations/steps/2.20.0-retire-codebase-snapshot.js +13 -101
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +13 -100
- package/package.json +7 -3
- package/.agents/scripts/lib/audit-suite/frontmatter-lint.js +0 -32
- package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +0 -37
- package/.agents/scripts/lib/cli/parse-numeric.js +0 -60
- package/.agents/scripts/lib/close-validation/telemetry.js +0 -79
- package/.agents/scripts/lib/orchestration/label-transitions.js +0 -44
- package/.agents/scripts/lib/orchestration/parked-follow-ons.js +0 -147
- package/.agents/scripts/lib/orchestration/phase-runner.js +0 -88
- package/.agents/scripts/lib/orchestration/recut.js +0 -56
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
resolveCyclomaticPolicy,
|
|
36
36
|
scanCyclomatic,
|
|
37
37
|
} from './lib/cyclomatic-ceiling.js';
|
|
38
|
+
import { resolveScanScope } from './lib/cyclomatic-scope.js';
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Parse `--baseline <path>`, `--json`, and `--update`.
|
|
@@ -82,6 +83,86 @@ function loadCyclomaticBaseline(baselinePath) {
|
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
/**
|
|
87
|
+
* `--update`: rewrite the baseline from the current (whole-tree) scan. The
|
|
88
|
+
* sanctioned motion after a deliberate refactor lands, and the only way the
|
|
89
|
+
* recorded breach count is allowed to rise.
|
|
90
|
+
*
|
|
91
|
+
* @param {{ scan: object, ceiling: number, baselinePath: string, writeFileImpl: Function, stdout: { write: (s: string) => void } }} args
|
|
92
|
+
* @returns {number} Always 0 — writing a baseline cannot fail the ratchet.
|
|
93
|
+
*/
|
|
94
|
+
function writeUpdatedBaseline({
|
|
95
|
+
scan,
|
|
96
|
+
ceiling,
|
|
97
|
+
baselinePath,
|
|
98
|
+
writeFileImpl,
|
|
99
|
+
stdout,
|
|
100
|
+
}) {
|
|
101
|
+
const envelope = buildCyclomaticEnvelope({ rows: scan.rows, ceiling });
|
|
102
|
+
writeFileImpl(baselinePath, `${JSON.stringify(envelope, null, 2)}\n`);
|
|
103
|
+
stdout.write(
|
|
104
|
+
`[cyclomatic] wrote ${scan.rows.length} breach row(s) at ceiling c=${ceiling} to ${baselinePath}\n`,
|
|
105
|
+
);
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* `--json`: the machine-readable comparison envelope.
|
|
111
|
+
*
|
|
112
|
+
* @param {{ policy: object, baseline: object|null, baselineRows: Array<object>, baselinePath: string, scan: object, diff: object, exitCode: number }} args
|
|
113
|
+
* @returns {string}
|
|
114
|
+
*/
|
|
115
|
+
function renderJsonReport({
|
|
116
|
+
policy,
|
|
117
|
+
baseline,
|
|
118
|
+
baselineRows,
|
|
119
|
+
baselinePath,
|
|
120
|
+
scan,
|
|
121
|
+
diff,
|
|
122
|
+
exitCode,
|
|
123
|
+
}) {
|
|
124
|
+
return `${JSON.stringify(
|
|
125
|
+
{
|
|
126
|
+
kind: 'cyclomatic-report',
|
|
127
|
+
ceiling: policy.mustFix,
|
|
128
|
+
flag: policy.flag,
|
|
129
|
+
baselinePath,
|
|
130
|
+
baselineCeiling: baseline?.ceiling ?? null,
|
|
131
|
+
scannedFiles: scan.scannedFiles,
|
|
132
|
+
parseErrors: scan.parseErrors,
|
|
133
|
+
baselineRows,
|
|
134
|
+
currentRows: scan.rows,
|
|
135
|
+
...diff,
|
|
136
|
+
exitCode,
|
|
137
|
+
},
|
|
138
|
+
null,
|
|
139
|
+
2,
|
|
140
|
+
)}\n`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Announce a baseline the comparison cannot fully trust: absent, or recorded
|
|
145
|
+
* at a ceiling the config no longer uses. Both are warnings rather than
|
|
146
|
+
* failures — the diff still runs, and staying silent is what would let an
|
|
147
|
+
* operator read a meaningless verdict as a clean one.
|
|
148
|
+
*
|
|
149
|
+
* @param {{ baseline: object|null, mustFix: number, baselinePath: string, stderr: { write: (s: string) => void } }} args
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
function warnAboutBaseline({ baseline, mustFix, baselinePath, stderr }) {
|
|
153
|
+
if (!baseline) {
|
|
154
|
+
stderr.write(
|
|
155
|
+
`[cyclomatic] ⚠ baseline not found at ${baselinePath} — treating as empty\n`,
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (typeof baseline.ceiling === 'number' && baseline.ceiling !== mustFix) {
|
|
160
|
+
stderr.write(
|
|
161
|
+
`[cyclomatic] ⚠ baseline was recorded at ceiling c=${baseline.ceiling} but the configured cyclomaticMustFix is c=${mustFix} — re-run with --update\n`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
85
166
|
/**
|
|
86
167
|
* Top-level CLI entry. Exported so tests can drive the whole pipeline through
|
|
87
168
|
* the injected seams below without spawning a process.
|
|
@@ -109,75 +190,64 @@ export async function runCli({
|
|
|
109
190
|
writeFileImpl = (p, data) => fs.writeFileSync(p, data),
|
|
110
191
|
} = {}) {
|
|
111
192
|
const { baselinePath, json, update } = parseArgv(argv);
|
|
112
|
-
const
|
|
193
|
+
const config = resolveConfigImpl({ cwd });
|
|
194
|
+
const quality = getQuality(config);
|
|
113
195
|
const policy = resolveCyclomaticPolicy(quality);
|
|
114
196
|
const resolvedBaselinePath = path.resolve(
|
|
115
197
|
cwd,
|
|
116
198
|
baselinePath ?? DEFAULT_CYCLOMATIC_BASELINE,
|
|
117
199
|
);
|
|
118
200
|
|
|
201
|
+
// Read the baseline before scanning: its rows are half the diff scope
|
|
202
|
+
// (Story #5109). `--update` rewrites the baseline from the whole tree, and
|
|
203
|
+
// `BASELINE_SCOPE=full` is the operator's explicit "re-derive everything",
|
|
204
|
+
// so both opt out of scoping entirely.
|
|
205
|
+
const baseline = loadBaselineImpl(resolvedBaselinePath);
|
|
206
|
+
const baselineRows = Array.isArray(baseline?.rows) ? baseline.rows : [];
|
|
207
|
+
const scopeFiles = resolveScanScope({ cwd, config, update, baselineRows });
|
|
208
|
+
|
|
119
209
|
const scan = scanImpl({
|
|
120
210
|
targetDirs: policy.targetDirs,
|
|
121
211
|
ignoreGlobs: policy.ignoreGlobs,
|
|
122
212
|
ceiling: policy.mustFix,
|
|
123
213
|
cwd,
|
|
214
|
+
scopeFiles,
|
|
124
215
|
});
|
|
125
216
|
|
|
126
217
|
if (update) {
|
|
127
|
-
|
|
128
|
-
|
|
218
|
+
return writeUpdatedBaseline({
|
|
219
|
+
scan,
|
|
129
220
|
ceiling: policy.mustFix,
|
|
221
|
+
baselinePath: resolvedBaselinePath,
|
|
222
|
+
writeFileImpl,
|
|
223
|
+
stdout,
|
|
130
224
|
});
|
|
131
|
-
writeFileImpl(
|
|
132
|
-
resolvedBaselinePath,
|
|
133
|
-
`${JSON.stringify(envelope, null, 2)}\n`,
|
|
134
|
-
);
|
|
135
|
-
stdout.write(
|
|
136
|
-
`[cyclomatic] wrote ${scan.rows.length} breach row(s) at ceiling c=${policy.mustFix} to ${resolvedBaselinePath}\n`,
|
|
137
|
-
);
|
|
138
|
-
return 0;
|
|
139
225
|
}
|
|
140
226
|
|
|
141
|
-
const baseline = loadBaselineImpl(resolvedBaselinePath);
|
|
142
|
-
const baselineRows = Array.isArray(baseline?.rows) ? baseline.rows : [];
|
|
143
227
|
const diff = diffCyclomaticRows(baselineRows, scan.rows);
|
|
144
228
|
const exitCode = diff.added.length + diff.worsened.length > 0 ? 1 : 0;
|
|
145
229
|
|
|
146
230
|
if (json) {
|
|
147
231
|
stdout.write(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
baselineRows,
|
|
158
|
-
currentRows: scan.rows,
|
|
159
|
-
...diff,
|
|
160
|
-
exitCode,
|
|
161
|
-
},
|
|
162
|
-
null,
|
|
163
|
-
2,
|
|
164
|
-
)}\n`,
|
|
232
|
+
renderJsonReport({
|
|
233
|
+
policy,
|
|
234
|
+
baseline,
|
|
235
|
+
baselineRows,
|
|
236
|
+
baselinePath: resolvedBaselinePath,
|
|
237
|
+
scan,
|
|
238
|
+
diff,
|
|
239
|
+
exitCode,
|
|
240
|
+
}),
|
|
165
241
|
);
|
|
166
242
|
return exitCode;
|
|
167
243
|
}
|
|
168
244
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
baseline.ceiling !== policy.mustFix
|
|
176
|
-
) {
|
|
177
|
-
stderr.write(
|
|
178
|
-
`[cyclomatic] ⚠ baseline was recorded at ceiling c=${baseline.ceiling} but the configured cyclomaticMustFix is c=${policy.mustFix} — re-run with --update\n`,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
245
|
+
warnAboutBaseline({
|
|
246
|
+
baseline,
|
|
247
|
+
mustFix: policy.mustFix,
|
|
248
|
+
baselinePath: resolvedBaselinePath,
|
|
249
|
+
stderr,
|
|
250
|
+
});
|
|
181
251
|
stdout.write('\n--- cyclomatic preview ---\n');
|
|
182
252
|
stdout.write(`${renderCyclomaticDiff(diff, policy.mustFix)}\n`);
|
|
183
253
|
return exitCode;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
//
|
|
20
20
|
// 3. No active doc mentions any retired slash command. The retired-command
|
|
21
21
|
// blocklist is seeded with `agents-bootstrap-github`,
|
|
22
|
-
// `single-story-plan` (renamed to `/plan`), and `mandrel`
|
|
22
|
+
// `single-story-plan` (renamed to `/mandrel-plan`), and `mandrel`
|
|
23
23
|
// (retired in favor of the generated `.agents/docs/workflows.md`
|
|
24
24
|
// catalog) and takes precedence over the workflow-resolution check —
|
|
25
25
|
// a retired token is always a non-zero exit even if a stale workflow
|
|
@@ -61,6 +61,18 @@ export const RETIRED_COMMANDS = new Set([
|
|
|
61
61
|
'git-merge-pr',
|
|
62
62
|
]);
|
|
63
63
|
|
|
64
|
+
// Command spellings a *historical* record may still name, keyed by the file
|
|
65
|
+
// allowed to name them. A decisions log that cannot quote the name a command
|
|
66
|
+
// had is a broken record: an ADR describes the world as it was, so freezing
|
|
67
|
+
// its prose is the point. Scoped per-file and per-token rather than
|
|
68
|
+
// allowlisted globally — everywhere else these are simply wrong, and a typo
|
|
69
|
+
// like `/mandrl-plan` must still fail here. Story #5126 renamed `/plan` →
|
|
70
|
+
// `/mandrel-plan` and `/deliver` → `/mandrel-deliver`; the ADRs that decided
|
|
71
|
+
// the Story-only model still name them as they were called then.
|
|
72
|
+
export const SUPERSEDED_COMMAND_SPELLINGS = new Map([
|
|
73
|
+
['docs/decisions.md', new Set(['plan', 'deliver'])],
|
|
74
|
+
]);
|
|
75
|
+
|
|
64
76
|
// Tokens that look like `/foo` in prose but are not slash commands. Tokens
|
|
65
77
|
// are stored WITHOUT the leading slash. The list focuses on common URL path
|
|
66
78
|
// fragments and on-disk path roots that appear in tutorial prose. The
|
|
@@ -379,6 +391,20 @@ export function extractSlashTokens(masked) {
|
|
|
379
391
|
|
|
380
392
|
// --- Per-file check --------------------------------------------------------
|
|
381
393
|
|
|
394
|
+
/**
|
|
395
|
+
* The slash tokens `relFile` may name without resolving to a workflow: the
|
|
396
|
+
* global allowlist, plus any superseded spelling this particular file is
|
|
397
|
+
* allowed to quote.
|
|
398
|
+
*
|
|
399
|
+
* @param {string} relFile Repo-relative path of the file being checked.
|
|
400
|
+
* @returns {Set<string>} Tokens to skip, stored without the leading slash.
|
|
401
|
+
*/
|
|
402
|
+
function slashAllowlistFor(relFile) {
|
|
403
|
+
const superseded = SUPERSEDED_COMMAND_SPELLINGS.get(relFile);
|
|
404
|
+
if (!superseded) return SLASH_ALLOWLIST;
|
|
405
|
+
return new Set([...SLASH_ALLOWLIST, ...superseded]);
|
|
406
|
+
}
|
|
407
|
+
|
|
382
408
|
export function checkFile(absPath, repoRoot) {
|
|
383
409
|
const violations = [];
|
|
384
410
|
const source = fs.readFileSync(absPath, 'utf8');
|
|
@@ -454,14 +480,16 @@ export function checkFile(absPath, repoRoot) {
|
|
|
454
480
|
}
|
|
455
481
|
}
|
|
456
482
|
|
|
457
|
-
// 3. Slash-command resolution. Skip retired hits (already reported)
|
|
458
|
-
// allowlisted tokens
|
|
483
|
+
// 3. Slash-command resolution. Skip retired hits (already reported),
|
|
484
|
+
// allowlisted tokens, and a superseded spelling in the one file allowed
|
|
485
|
+
// to quote it (SUPERSEDED_COMMAND_SPELLINGS). A command is valid if it resolves to a top-level
|
|
459
486
|
// workflow file OR to a helpers/ module (helpers are not projected into
|
|
460
487
|
// the `.claude/commands/` tree but are still legitimate named workflows
|
|
461
488
|
// that parent workflows invoke by prose reference).
|
|
489
|
+
const slashAllowlist = slashAllowlistFor(relFile);
|
|
462
490
|
for (const { token, line } of slashTokens) {
|
|
463
491
|
if (RETIRED_COMMANDS.has(token)) continue;
|
|
464
|
-
if (
|
|
492
|
+
if (slashAllowlist.has(token)) continue;
|
|
465
493
|
// Namespaced loop commands (`/loops:<name>`, Story #4289) resolve to a
|
|
466
494
|
// loop unit under `.agents/workflows/loops/<name>.md`. Split on the `:`
|
|
467
495
|
// and resolve the namespaced path rather than a flat `loops:<name>.md`.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI: keep the committed `.agentrc` validator in step with its schema
|
|
4
|
+
* (Story #5109).
|
|
5
|
+
*
|
|
6
|
+
* `getAgentrcValidator()` used to compile `AGENTRC_SCHEMA` with AJV in every
|
|
7
|
+
* process that touched configuration — 36 of the 79 entry scripts, at ~35 ms
|
|
8
|
+
* each — to produce a function that is a pure derivative of a literal already
|
|
9
|
+
* committed to the repository. The compile is now done **once, here**, and its
|
|
10
|
+
* output is committed as `lib/generated/agentrc-validator.js`; the runtime
|
|
11
|
+
* imports that module and only falls back to a live compile behind
|
|
12
|
+
* `MANDREL_AGENTRC_VALIDATOR=dynamic`.
|
|
13
|
+
*
|
|
14
|
+
* A generated artifact that nothing re-derives is a lie waiting to happen, so
|
|
15
|
+
* this script joins the standalone-ratchet family — `check-arch-cycles.js`,
|
|
16
|
+
* `check-cyclomatic.js`, `check-dead-exports.js` — and follows their contract:
|
|
17
|
+
*
|
|
18
|
+
* - `--check` regenerates in memory and compares against the committed
|
|
19
|
+
* file. Exit 0 when they agree, exit 1 (naming the fix) when they do not.
|
|
20
|
+
* `npm run lint` runs this, so a schema edit that skips regeneration is
|
|
21
|
+
* refused before it can ship a validator that accepts the wrong config.
|
|
22
|
+
* - No flag rewrites the file. That is the sanctioned motion after a
|
|
23
|
+
* deliberate schema change.
|
|
24
|
+
*
|
|
25
|
+
* The comparison is over the exact bytes, which is what makes the gate
|
|
26
|
+
* meaningful: AJV's standalone emit is deterministic for a given schema and
|
|
27
|
+
* AJV version, so a diff means either the schema moved or the compiler did,
|
|
28
|
+
* and both demand a fresh artifact.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import fs from 'node:fs';
|
|
32
|
+
import { createRequire } from 'node:module';
|
|
33
|
+
import path from 'node:path';
|
|
34
|
+
import process from 'node:process';
|
|
35
|
+
import { runAsCli } from './lib/cli-utils.js';
|
|
36
|
+
import { AGENTRC_SCHEMA } from './lib/config-settings-schema.js';
|
|
37
|
+
|
|
38
|
+
const require = createRequire(import.meta.url);
|
|
39
|
+
|
|
40
|
+
/** Repo-relative home of the committed artifact. */
|
|
41
|
+
export const GENERATED_VALIDATOR_PATH =
|
|
42
|
+
'.agents/scripts/lib/generated/agentrc-validator.js';
|
|
43
|
+
|
|
44
|
+
/** The command that rewrites it — quoted in every failure message. */
|
|
45
|
+
const REGEN_COMMAND = 'npm run validator:gen';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* AJV options for the generated validator.
|
|
49
|
+
*
|
|
50
|
+
* `allErrors: true` MUST match the runtime fallback in
|
|
51
|
+
* `config-settings-schema.js#getAgentrcValidator` — it is the difference
|
|
52
|
+
* between reporting one error and reporting all of them, and every caller
|
|
53
|
+
* that renders `validate.errors` would change behaviour if the two diverged.
|
|
54
|
+
* `code.source` is what makes the instance emittable; `code.esm` selects the
|
|
55
|
+
* ES-module emit. Neither touches validation semantics.
|
|
56
|
+
*/
|
|
57
|
+
const AJV_OPTIONS = Object.freeze({
|
|
58
|
+
allErrors: true,
|
|
59
|
+
code: { source: true, esm: true },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Banner prepended to the emitted module.
|
|
64
|
+
*
|
|
65
|
+
* The `createRequire` shim is load-bearing: AJV's `esm` emit still reaches for
|
|
66
|
+
* its shared runtime helpers (`ajv/dist/runtime/*`) with `require(...)`, which
|
|
67
|
+
* is not defined inside an ES module. Handing the generated code a real
|
|
68
|
+
* `require` is the smallest fix that keeps the emit verbatim — rewriting those
|
|
69
|
+
* calls into imports would mean re-deriving AJV's own codegen by hand, and
|
|
70
|
+
* would silently rot the next time it changes shape.
|
|
71
|
+
*
|
|
72
|
+
* The coverage pragma matches `config-settings-schema.js`: generated code has
|
|
73
|
+
* no branches anyone authored, so a coverage figure over it measures nothing.
|
|
74
|
+
*/
|
|
75
|
+
const BANNER = `/* node:coverage ignore file -- generated artifact; see check-generated-validator.js */
|
|
76
|
+
/**
|
|
77
|
+
* GENERATED FILE — DO NOT EDIT.
|
|
78
|
+
*
|
|
79
|
+
* Precompiled AJV validator for \`AGENTRC_SCHEMA\`
|
|
80
|
+
* (.agents/scripts/lib/config-settings-schema.js), emitted by
|
|
81
|
+
* .agents/scripts/check-generated-validator.js.
|
|
82
|
+
*
|
|
83
|
+
* Regenerate with \`${REGEN_COMMAND}\`; \`npm run lint\` fails when this file
|
|
84
|
+
* and the schema disagree.
|
|
85
|
+
*/
|
|
86
|
+
import { createRequire } from 'node:module';
|
|
87
|
+
|
|
88
|
+
const require = createRequire(import.meta.url);
|
|
89
|
+
`;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Emit the standalone validator source for `AGENTRC_SCHEMA`.
|
|
93
|
+
*
|
|
94
|
+
* Exported so the parity test can assert the committed artifact and a fresh
|
|
95
|
+
* emit agree without shelling out to this CLI.
|
|
96
|
+
*
|
|
97
|
+
* @returns {string} Complete module source, newline-terminated.
|
|
98
|
+
*/
|
|
99
|
+
export function generateValidatorSource() {
|
|
100
|
+
const ajvModule = require('ajv');
|
|
101
|
+
const Ajv = ajvModule.default ?? ajvModule;
|
|
102
|
+
const standaloneModule = require('ajv/dist/standalone');
|
|
103
|
+
const standaloneCode = standaloneModule.default ?? standaloneModule;
|
|
104
|
+
const ajv = new Ajv(AJV_OPTIONS);
|
|
105
|
+
const validate = ajv.compile(AGENTRC_SCHEMA);
|
|
106
|
+
const body = standaloneCode(ajv, validate);
|
|
107
|
+
return `${BANNER}\n${body.trim()}\n`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Read the committed artifact, or `null` when it is absent.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} absPath
|
|
114
|
+
* @returns {string | null}
|
|
115
|
+
*/
|
|
116
|
+
function readCommitted(absPath) {
|
|
117
|
+
try {
|
|
118
|
+
return fs.readFileSync(absPath, 'utf-8');
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Top-level CLI entry. Exported so tests can drive both modes through the
|
|
126
|
+
* injected seams rather than by spawning a process.
|
|
127
|
+
*
|
|
128
|
+
* @param {{
|
|
129
|
+
* argv?: string[],
|
|
130
|
+
* cwd?: string,
|
|
131
|
+
* stdout?: { write: (s: string) => void },
|
|
132
|
+
* stderr?: { write: (s: string) => void },
|
|
133
|
+
* generateImpl?: () => string,
|
|
134
|
+
* writeFileImpl?: (p: string, data: string) => void,
|
|
135
|
+
* }} [opts]
|
|
136
|
+
* @returns {number} 0 = in step (or written); 1 = stale artifact.
|
|
137
|
+
*/
|
|
138
|
+
export function runCli({
|
|
139
|
+
argv = process.argv.slice(2),
|
|
140
|
+
cwd = process.cwd(),
|
|
141
|
+
stdout = process.stdout,
|
|
142
|
+
stderr = process.stderr,
|
|
143
|
+
generateImpl = generateValidatorSource,
|
|
144
|
+
writeFileImpl = (p, data) => fs.writeFileSync(p, data),
|
|
145
|
+
} = {}) {
|
|
146
|
+
const checkOnly = argv.includes('--check');
|
|
147
|
+
const absPath = path.resolve(cwd, GENERATED_VALIDATOR_PATH);
|
|
148
|
+
const expected = generateImpl();
|
|
149
|
+
|
|
150
|
+
if (!checkOnly) {
|
|
151
|
+
fs.mkdirSync(path.dirname(absPath), { recursive: true });
|
|
152
|
+
writeFileImpl(absPath, expected);
|
|
153
|
+
stdout.write(
|
|
154
|
+
`[generated-validator] wrote ${expected.length} bytes to ${GENERATED_VALIDATOR_PATH}\n`,
|
|
155
|
+
);
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const actual = readCommitted(absPath);
|
|
160
|
+
if (actual === null) {
|
|
161
|
+
stderr.write(
|
|
162
|
+
`[generated-validator] ❌ ${GENERATED_VALIDATOR_PATH} is missing — run \`${REGEN_COMMAND}\`\n`,
|
|
163
|
+
);
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
if (actual !== expected) {
|
|
167
|
+
stderr.write(
|
|
168
|
+
`[generated-validator] ❌ ${GENERATED_VALIDATOR_PATH} is stale against AGENTRC_SCHEMA ` +
|
|
169
|
+
`(committed ${actual.length} bytes, regenerated ${expected.length}) — run \`${REGEN_COMMAND}\`\n`,
|
|
170
|
+
);
|
|
171
|
+
return 1;
|
|
172
|
+
}
|
|
173
|
+
stdout.write(
|
|
174
|
+
`[generated-validator] ✅ ${GENERATED_VALIDATOR_PATH} is in step with AGENTRC_SCHEMA\n`,
|
|
175
|
+
);
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function main() {
|
|
180
|
+
return runCli();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
runAsCli(import.meta.url, main, {
|
|
184
|
+
source: 'generated-validator',
|
|
185
|
+
propagateExitCode: true,
|
|
186
|
+
errorPrefix: '[generated-validator] ❌ Fatal error',
|
|
187
|
+
usage: {
|
|
188
|
+
invocation: 'node .agents/scripts/check-generated-validator.js [--check]',
|
|
189
|
+
summary:
|
|
190
|
+
'Regenerate the committed AJV validator for `AGENTRC_SCHEMA`, or (with --check) fail when the committed artifact is stale against the schema.',
|
|
191
|
+
flags: [
|
|
192
|
+
[
|
|
193
|
+
'--check',
|
|
194
|
+
'Compare only: exit 1 when the committed artifact differs from a fresh emit. Writes nothing.',
|
|
195
|
+
],
|
|
196
|
+
],
|
|
197
|
+
notes: [
|
|
198
|
+
'Exit codes:\n 0 artifact written, or already in step\n 1 committed artifact is missing or stale',
|
|
199
|
+
`Regenerate with \`${REGEN_COMMAND}\`. The runtime falls back to a live AJV compile when MANDREL_AGENTRC_VALIDATOR=dynamic.`,
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
});
|
|
@@ -78,6 +78,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
78
78
|
|
|
79
79
|
import { runAsCli } from './lib/cli-utils.js';
|
|
80
80
|
import { walkFilesByExtension } from './lib/fs-walk.js';
|
|
81
|
+
import { stripJsComments } from './lib/source-text/strip-js-comments.js';
|
|
81
82
|
|
|
82
83
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
83
84
|
const REPO_ROOT = path.resolve(__dirname, '..', '..');
|
|
@@ -162,73 +163,6 @@ export function findPromiseAllViolations(
|
|
|
162
163
|
return violations;
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
/**
|
|
166
|
-
* Strip block (`/* … */`) and line (`// …`) comments from a source
|
|
167
|
-
* string. Pure — exported for tests so the comment-stripping contract
|
|
168
|
-
* is explicit. Defends against the corner case where the literal
|
|
169
|
-
* `'gh pr merge'` appears INSIDE a justification comment at the
|
|
170
|
-
* deletion site (Story #2253 deliberately leaves a prose explanation
|
|
171
|
-
* referencing the removed CLI call).
|
|
172
|
-
*
|
|
173
|
-
* The implementation is a tiny state machine rather than a regex so it
|
|
174
|
-
* correctly handles the (legal) case of a string literal containing
|
|
175
|
-
* `//` or `/*` characters.
|
|
176
|
-
*
|
|
177
|
-
* @param {string} src
|
|
178
|
-
* @returns {string} source with comments replaced by spaces (line
|
|
179
|
-
* numbers preserved so violations report the original line).
|
|
180
|
-
*/
|
|
181
|
-
export function stripComments(src) {
|
|
182
|
-
let out = '';
|
|
183
|
-
let i = 0;
|
|
184
|
-
const n = src.length;
|
|
185
|
-
while (i < n) {
|
|
186
|
-
const ch = src[i];
|
|
187
|
-
const nx = src[i + 1];
|
|
188
|
-
// line comment
|
|
189
|
-
if (ch === '/' && nx === '/') {
|
|
190
|
-
while (i < n && src[i] !== '\n') {
|
|
191
|
-
i += 1;
|
|
192
|
-
}
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
// block comment
|
|
196
|
-
if (ch === '/' && nx === '*') {
|
|
197
|
-
i += 2;
|
|
198
|
-
while (i < n && !(src[i] === '*' && src[i + 1] === '/')) {
|
|
199
|
-
// preserve newlines so line numbers in violation reports stay
|
|
200
|
-
// aligned with the original file.
|
|
201
|
-
if (src[i] === '\n') out += '\n';
|
|
202
|
-
i += 1;
|
|
203
|
-
}
|
|
204
|
-
i += 2; // skip closing */
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
// string literal — copy through unchanged (we WANT to keep these
|
|
208
|
-
// so Rule 3 can flag them).
|
|
209
|
-
if (ch === '"' || ch === "'" || ch === '`') {
|
|
210
|
-
const quote = ch;
|
|
211
|
-
out += ch;
|
|
212
|
-
i += 1;
|
|
213
|
-
while (i < n) {
|
|
214
|
-
const c = src[i];
|
|
215
|
-
out += c;
|
|
216
|
-
if (c === '\\' && i + 1 < n) {
|
|
217
|
-
out += src[i + 1];
|
|
218
|
-
i += 2;
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
i += 1;
|
|
222
|
-
if (c === quote) break;
|
|
223
|
-
}
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
out += ch;
|
|
227
|
-
i += 1;
|
|
228
|
-
}
|
|
229
|
-
return out;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
166
|
/**
|
|
233
167
|
* Rule 3 enforcement (Story #2253 / Task #2255, Epic #2172 review
|
|
234
168
|
* High-1). Returns an array of `{ file, line, hint }` violations for
|
|
@@ -263,7 +197,7 @@ export function findMergeLockoutViolations(
|
|
|
263
197
|
|
|
264
198
|
const raw = read(file, 'utf8');
|
|
265
199
|
const rawLines = raw.split('\n');
|
|
266
|
-
const stripped =
|
|
200
|
+
const stripped = stripJsComments(raw);
|
|
267
201
|
const lines = stripped.split('\n');
|
|
268
202
|
for (let i = 0; i < lines.length; i += 1) {
|
|
269
203
|
const line = lines[i];
|
|
@@ -88,6 +88,7 @@ import fs from 'node:fs';
|
|
|
88
88
|
import path from 'node:path';
|
|
89
89
|
import process from 'node:process';
|
|
90
90
|
import { runAsCli } from './lib/cli-utils.js';
|
|
91
|
+
import { stripJsComments } from './lib/source-text/strip-js-comments.js';
|
|
91
92
|
|
|
92
93
|
/** Schema root, relative to the repository root. */
|
|
93
94
|
const SCHEMA_ROOT = path.join('.agents', 'schemas');
|
|
@@ -114,33 +115,6 @@ const EXEMPTION_KEY = 'x-mandrel-uncompiled';
|
|
|
114
115
|
*/
|
|
115
116
|
const COMPUTED_PATH_RE = /\$\{[^}]*\}\.schema\.json/;
|
|
116
117
|
|
|
117
|
-
/** Block comments, including the JSDoc prose every module here opens with. */
|
|
118
|
-
const BLOCK_COMMENT_RE = /\/\*[\s\S]*?\*\//g;
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Line comments. Anchored to start-of-line or whitespace so a `//` inside a
|
|
122
|
-
* URL (`http://…`) is left alone — truncating that line could delete a real
|
|
123
|
-
* reference sitting after it.
|
|
124
|
-
*/
|
|
125
|
-
const LINE_COMMENT_RE = /(^|\s)\/\/[^\n]*/g;
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Strip comments from a source before matching.
|
|
129
|
-
*
|
|
130
|
-
* Prose is not a compile. This module's own header names
|
|
131
|
-
* `friction-event.schema.json` a dozen times while compiling nothing, and
|
|
132
|
-
* every schema-loading module documents the path it loads directly above the
|
|
133
|
-
* line that loads it — matching raw text would let a gate certify itself and
|
|
134
|
-
* would count a docblock as enforcement, which is precisely the confusion
|
|
135
|
-
* Story #4938 exists to end.
|
|
136
|
-
*
|
|
137
|
-
* @param {string} text raw source
|
|
138
|
-
* @returns {string} source with comment bodies removed
|
|
139
|
-
*/
|
|
140
|
-
function stripComments(text) {
|
|
141
|
-
return text.replace(BLOCK_COMMENT_RE, ' ').replace(LINE_COMMENT_RE, '$1');
|
|
142
|
-
}
|
|
143
|
-
|
|
144
118
|
/**
|
|
145
119
|
* Parse argv. Exported so a unit test can pin the parser without spawning.
|
|
146
120
|
*
|
|
@@ -194,7 +168,7 @@ function walk(dir, extensions, acc = []) {
|
|
|
194
168
|
function readSources(root) {
|
|
195
169
|
return walk(root, JS_EXTENSIONS).map((file) => ({
|
|
196
170
|
rel: path.relative(root, file),
|
|
197
|
-
text:
|
|
171
|
+
text: stripJsComments(fs.readFileSync(file, 'utf8')),
|
|
198
172
|
}));
|
|
199
173
|
}
|
|
200
174
|
|
|
@@ -422,7 +422,7 @@ export function findReservedIdStreamFiles(tempDir) {
|
|
|
422
422
|
* the **repository-root** temp tree (Story #4892).
|
|
423
423
|
*
|
|
424
424
|
* Resolution matters more than it looks: every writer anchors a relative
|
|
425
|
-
* `tempRoot` to the *main checkout* (so a Story worktree and its `/deliver`
|
|
425
|
+
* `tempRoot` to the *main checkout* (so a Story worktree and its `/mandrel-deliver`
|
|
426
426
|
* host converge on one ledger), so a guard that scanned `cwd` would scan an
|
|
427
427
|
* empty worktree tree and pass vacuously on the very tree it is meant to
|
|
428
428
|
* protect. `resolveRoot` is the injection seam for tests.
|
|
@@ -136,7 +136,7 @@ export function toParagraphs(lines) {
|
|
|
136
136
|
* heading (inclusive) — i.e. the section is *about* that script, so the
|
|
137
137
|
* table is restating its surface.
|
|
138
138
|
*
|
|
139
|
-
* A slash command's own argument table (`/plan`, `/deliver`) has no script
|
|
139
|
+
* A slash command's own argument table (`/mandrel-plan`, `/mandrel-deliver`) has no script
|
|
140
140
|
* behind it — nothing owns those flags but the workflow prose itself — so
|
|
141
141
|
* condition 2 leaves it alone by design.
|
|
142
142
|
*
|