mandrel 2.36.0 → 2.37.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/docs/configuration.md +25 -0
- package/.agents/rules/testing-standards.md +56 -0
- 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-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/drain-pending-cleanup.js +18 -1
- package/.agents/scripts/lib/baselines/components.js +32 -2
- package/.agents/scripts/lib/baselines/env-overrides.js +1 -1
- package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
- package/.agents/scripts/lib/bootstrap/install-ledger.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/loop-health.js +1 -1
- package/.agents/scripts/lib/cli-args.js +0 -2
- package/.agents/scripts/lib/close-validation/gates.js +1 -1
- package/.agents/scripts/lib/close-validation/process.js +1 -1
- package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
- package/.agents/scripts/lib/config-settings-schema.js +46 -6
- 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 +1 -1
- 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 +1 -1
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/generated/agentrc-validator.js +16 -0
- package/.agents/scripts/lib/install-cmd-parser.js +1 -1
- package/.agents/scripts/lib/knip-entry-sync.js +1 -57
- package/.agents/scripts/lib/maintainability-utils.js +108 -10
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +70 -9
- 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/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/single-story-close/phases/options.js +52 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +2 -5
- 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/ticketing/reads.js +3 -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/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/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/transpile.js +16 -3
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +169 -31
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +2 -5
- package/.agents/scripts/pr-watch-with-update.js +305 -137
- package/.agents/scripts/providers/github/tickets.js +109 -16
- package/.agents/scripts/quality-preview.js +162 -70
- package/.agents/scripts/run-test-profile.js +8 -5
- package/.agents/scripts/run-tests.js +79 -14
- package/.agents/scripts/single-story-close.js +0 -2
- package/.agents/scripts/single-story-init.js +5 -1
- package/docs/CHANGELOG.md +32 -0
- 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.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
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
// lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js
|
|
2
2
|
/**
|
|
3
|
-
* Story #
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* than bounding anything; that CLI is deleted in the same Story. Setting the
|
|
10
|
-
* knob to any value only ever changed a number printed in a report.
|
|
11
|
-
*
|
|
12
|
-
* `delivery.deliverRunner` declares `additionalProperties: false`, so a
|
|
13
|
-
* consumer whose config still carries the key hits a hard AJV validation
|
|
14
|
-
* failure on upgrade — every script that resolves config, not just the one
|
|
15
|
-
* that read it. This step strips it before that check runs.
|
|
3
|
+
* Story #4531 — `delivery.deliverRunner.verifyConcurrencyCap` was retired
|
|
4
|
+
* when verification stopped being separately capped, but it stayed
|
|
5
|
+
* schema-validated in consumers' configs under a block carrying
|
|
6
|
+
* `additionalProperties: false`, so a config still setting it fails AJV
|
|
7
|
+
* validation on upgrade rather than warning. This step strips it before
|
|
8
|
+
* that check runs.
|
|
16
9
|
*
|
|
17
10
|
* Unlike the 2.1.0 mi-drop step, this one also sweeps `.agentrc.local.json`:
|
|
18
11
|
* the resolver deep-merges the local override over `.agentrc.json` and
|
|
@@ -20,82 +13,17 @@
|
|
|
20
13
|
* same way a key in the committed file does.
|
|
21
14
|
*/
|
|
22
15
|
|
|
23
|
-
import
|
|
24
|
-
import path from 'node:path';
|
|
25
|
-
|
|
26
|
-
const AGENTRC_FILENAMES = ['.agentrc.json', '.agentrc.local.json'];
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* @param {unknown} ctx
|
|
30
|
-
* @param {string} filename
|
|
31
|
-
* @returns {string}
|
|
32
|
-
*/
|
|
33
|
-
function resolveAgentrcPath(ctx, filename) {
|
|
34
|
-
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
35
|
-
return path.join(projectRoot, filename);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @param {unknown} ctx
|
|
40
|
-
* @param {string} filename
|
|
41
|
-
* @param {typeof nodeFs} fsImpl
|
|
42
|
-
* @returns {object | null}
|
|
43
|
-
*/
|
|
44
|
-
function readAgentrcConfig(ctx, filename, fsImpl) {
|
|
45
|
-
try {
|
|
46
|
-
const raw = fsImpl.readFileSync(resolveAgentrcPath(ctx, filename), 'utf8');
|
|
47
|
-
return JSON.parse(raw);
|
|
48
|
-
} catch {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
16
|
+
import { createRetireAgentrcKeyStep } from '../helpers/retire-agentrc-key.js';
|
|
52
17
|
|
|
53
|
-
|
|
54
|
-
* @param {object | null} config
|
|
55
|
-
* @returns {boolean}
|
|
56
|
-
*/
|
|
57
|
-
function hasRetiredKey(config) {
|
|
58
|
-
const deliverRunner = config?.delivery?.deliverRunner;
|
|
59
|
-
return (
|
|
60
|
-
Boolean(deliverRunner) &&
|
|
61
|
-
Object.hasOwn(deliverRunner, 'verifyConcurrencyCap')
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export const retireVerifyConcurrencyCap = {
|
|
18
|
+
export const retireVerifyConcurrencyCap = createRetireAgentrcKeyStep({
|
|
66
19
|
version: '2.1.0',
|
|
67
20
|
description:
|
|
68
21
|
'strip retired delivery.deliverRunner.verifyConcurrencyCap from ' +
|
|
69
22
|
'.agentrc.json and .agentrc.local.json',
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
hasRetiredKey(readAgentrcConfig(ctx, filename, fsImpl)),
|
|
78
|
-
);
|
|
79
|
-
},
|
|
80
|
-
/**
|
|
81
|
-
* @param {{ projectRoot?: string, fs?: typeof nodeFs }} [ctx]
|
|
82
|
-
* @returns {void}
|
|
83
|
-
*/
|
|
84
|
-
apply(ctx) {
|
|
85
|
-
const fsImpl = ctx?.fs ?? nodeFs;
|
|
86
|
-
for (const filename of AGENTRC_FILENAMES) {
|
|
87
|
-
const config = readAgentrcConfig(ctx, filename, fsImpl);
|
|
88
|
-
if (!hasRetiredKey(config)) continue;
|
|
89
|
-
|
|
90
|
-
delete config.delivery.deliverRunner.verifyConcurrencyCap;
|
|
91
|
-
if (Object.keys(config.delivery.deliverRunner).length === 0) {
|
|
92
|
-
delete config.delivery.deliverRunner;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
fsImpl.writeFileSync(
|
|
96
|
-
resolveAgentrcPath(ctx, filename),
|
|
97
|
-
`${JSON.stringify(config, null, 2)}\n`,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
};
|
|
23
|
+
keys: [
|
|
24
|
+
{
|
|
25
|
+
path: ['delivery', 'deliverRunner', 'verifyConcurrencyCap'],
|
|
26
|
+
pruneDepth: 1,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
});
|
|
@@ -12,81 +12,23 @@
|
|
|
12
12
|
* `maxSeedWords` hits a hard validation failure on upgrade, not a
|
|
13
13
|
* warning. This step strips the key before that check runs — the same
|
|
14
14
|
* contract-cutover pattern as `2.1.0-retire-mi-drop-knobs.js`.
|
|
15
|
+
*
|
|
16
|
+
* `planning` is an optional block, so an emptied `complexityGate` and an
|
|
17
|
+
* emptied `planning` are both pruned rather than left as orphan objects.
|
|
15
18
|
*/
|
|
16
19
|
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {unknown} ctx
|
|
24
|
-
* @returns {string}
|
|
25
|
-
*/
|
|
26
|
-
function resolveAgentrcPath(ctx) {
|
|
27
|
-
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
28
|
-
return path.join(projectRoot, AGENTRC_FILENAME);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @param {unknown} ctx
|
|
33
|
-
* @param {typeof nodeFs} fsImpl
|
|
34
|
-
* @returns {object | null}
|
|
35
|
-
*/
|
|
36
|
-
function readAgentrcConfig(ctx, fsImpl) {
|
|
37
|
-
try {
|
|
38
|
-
const raw = fsImpl.readFileSync(resolveAgentrcPath(ctx), 'utf8');
|
|
39
|
-
return JSON.parse(raw);
|
|
40
|
-
} catch {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* @param {object | null} config
|
|
47
|
-
* @returns {boolean}
|
|
48
|
-
*/
|
|
49
|
-
function hasRetiredKey(config) {
|
|
50
|
-
const gate = config?.planning?.complexityGate;
|
|
51
|
-
return Boolean(gate) && Object.hasOwn(gate, 'maxSeedWords');
|
|
52
|
-
}
|
|
20
|
+
import {
|
|
21
|
+
AGENTRC_BASE_FILENAME,
|
|
22
|
+
createRetireAgentrcKeyStep,
|
|
23
|
+
} from '../helpers/retire-agentrc-key.js';
|
|
53
24
|
|
|
54
|
-
export const retireMaxSeedWords = {
|
|
25
|
+
export const retireMaxSeedWords = createRetireAgentrcKeyStep({
|
|
55
26
|
version: '2.11.0',
|
|
56
27
|
description:
|
|
57
28
|
'strip retired planning.complexityGate.maxSeedWords from .agentrc.json ' +
|
|
58
29
|
'(complexity routes on Story shape, never seed word count — Story #4722)',
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const fsImpl = ctx?.fs ?? nodeFs;
|
|
65
|
-
return hasRetiredKey(readAgentrcConfig(ctx, fsImpl));
|
|
66
|
-
},
|
|
67
|
-
/**
|
|
68
|
-
* @param {{ projectRoot?: string, fs?: typeof nodeFs }} [ctx]
|
|
69
|
-
* @returns {void}
|
|
70
|
-
*/
|
|
71
|
-
apply(ctx) {
|
|
72
|
-
const fsImpl = ctx?.fs ?? nodeFs;
|
|
73
|
-
const config = readAgentrcConfig(ctx, fsImpl);
|
|
74
|
-
if (!config) return;
|
|
75
|
-
|
|
76
|
-
const gate = config.planning?.complexityGate;
|
|
77
|
-
if (gate && Object.hasOwn(gate, 'maxSeedWords')) {
|
|
78
|
-
delete gate.maxSeedWords;
|
|
79
|
-
if (Object.keys(gate).length === 0) {
|
|
80
|
-
delete config.planning.complexityGate;
|
|
81
|
-
if (Object.keys(config.planning).length === 0) {
|
|
82
|
-
delete config.planning;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
fsImpl.writeFileSync(
|
|
88
|
-
resolveAgentrcPath(ctx),
|
|
89
|
-
`${JSON.stringify(config, null, 2)}\n`,
|
|
90
|
-
);
|
|
91
|
-
},
|
|
92
|
-
};
|
|
30
|
+
filenames: [AGENTRC_BASE_FILENAME],
|
|
31
|
+
keys: [
|
|
32
|
+
{ path: ['planning', 'complexityGate', 'maxSeedWords'], pruneDepth: 2 },
|
|
33
|
+
],
|
|
34
|
+
});
|
|
@@ -1,113 +1,25 @@
|
|
|
1
1
|
// lib/migrations/steps/2.20.0-retire-codebase-snapshot.js
|
|
2
2
|
/**
|
|
3
3
|
* Story #4811 — strip the retired `planning.codebaseSnapshot` block from a
|
|
4
|
-
* consumer's `.agentrc.json`
|
|
5
|
-
*
|
|
6
|
-
* #4811 hard-cutover-removed the `/plan` codebase snapshot: the pre-computed
|
|
7
|
-
* structural view grounded nothing it promised (its default include globs
|
|
8
|
-
* missed the standard monorepo layout, and its knobs only re-filtered the same
|
|
9
|
-
* matched set), and spec authoring is grounded instead by the author's own
|
|
10
|
-
* targeted repo retrieval plus the Phase 8 `validateStoryFileAssumptions`
|
|
11
|
-
* gate. The whole block was dropped from the runtime AJV schema and the
|
|
12
|
-
* published mirror. `planning` carries `additionalProperties: false`, so a
|
|
13
|
-
* consumer whose config still sets `codebaseSnapshot` hits a hard validation
|
|
14
|
-
* failure on upgrade, not a warning. This step strips the key before that
|
|
15
|
-
* check runs — the same contract-cutover pattern as
|
|
16
|
-
* `2.11.0-retire-max-seed-words.js`, widened to the local override file
|
|
17
|
-
* because a snapshot narrowed for one checkout is exactly the kind of knob an
|
|
4
|
+
* consumer's `.agentrc.json` and from the `.agentrc.local.json` overlay an
|
|
18
5
|
* operator pins in `.agentrc.local.json`.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import nodeFs from 'node:fs';
|
|
22
|
-
import path from 'node:path';
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Both config files the resolver deep-merges. A key surviving in either one
|
|
26
|
-
* fails validation, so both are stripped.
|
|
27
|
-
*/
|
|
28
|
-
const AGENTRC_FILENAMES = ['.agentrc.json', '.agentrc.local.json'];
|
|
29
|
-
|
|
30
|
-
const RETIRED_KEY = 'codebaseSnapshot';
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @param {unknown} ctx
|
|
34
|
-
* @param {string} filename
|
|
35
|
-
* @returns {string}
|
|
36
|
-
*/
|
|
37
|
-
function resolveAgentrcPath(ctx, filename) {
|
|
38
|
-
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
39
|
-
return path.join(projectRoot, filename);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @param {unknown} ctx
|
|
44
|
-
* @param {string} filename
|
|
45
|
-
* @param {typeof nodeFs} fsImpl
|
|
46
|
-
* @returns {object | null}
|
|
47
|
-
*/
|
|
48
|
-
function readAgentrcConfig(ctx, filename, fsImpl) {
|
|
49
|
-
try {
|
|
50
|
-
const raw = fsImpl.readFileSync(resolveAgentrcPath(ctx, filename), 'utf8');
|
|
51
|
-
return JSON.parse(raw);
|
|
52
|
-
} catch {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @param {object | null} config
|
|
59
|
-
* @returns {boolean}
|
|
60
|
-
*/
|
|
61
|
-
function hasRetiredKey(config) {
|
|
62
|
-
const planning = config?.planning;
|
|
63
|
-
return Boolean(planning) && Object.hasOwn(planning, RETIRED_KEY);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Strip the key and prune the containers it emptied, so a config that carried
|
|
68
|
-
* nothing but the snapshot does not keep an orphan `planning: {}`.
|
|
69
6
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
7
|
+
* Spec authoring is grounded by targeted retrieval plus the Phase 8
|
|
8
|
+
* file-assumption gate, so the snapshot block has no consumer; the block
|
|
9
|
+
* carries `additionalProperties: false`, so a config still setting it fails
|
|
10
|
+
* validation on upgrade rather than warning.
|
|
11
|
+
*
|
|
12
|
+
* `planning` is optional, so a config that carried nothing but the snapshot
|
|
13
|
+
* does not keep an orphan `planning: {}`.
|
|
72
14
|
*/
|
|
73
|
-
function stripRetiredKey(config) {
|
|
74
|
-
delete config.planning[RETIRED_KEY];
|
|
75
|
-
if (Object.keys(config.planning).length === 0) {
|
|
76
|
-
delete config.planning;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
15
|
|
|
80
|
-
|
|
16
|
+
import { createRetireAgentrcKeyStep } from '../helpers/retire-agentrc-key.js';
|
|
17
|
+
|
|
18
|
+
export const retireCodebaseSnapshot = createRetireAgentrcKeyStep({
|
|
81
19
|
version: '2.20.0',
|
|
82
20
|
description:
|
|
83
21
|
'strip retired planning.codebaseSnapshot from .agentrc.json / ' +
|
|
84
22
|
'.agentrc.local.json (spec authoring is grounded by targeted retrieval ' +
|
|
85
23
|
'plus the Phase 8 file-assumption gate — Story #4811)',
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
* @returns {boolean}
|
|
89
|
-
*/
|
|
90
|
-
detect(ctx) {
|
|
91
|
-
const fsImpl = ctx?.fs ?? nodeFs;
|
|
92
|
-
return AGENTRC_FILENAMES.some((filename) =>
|
|
93
|
-
hasRetiredKey(readAgentrcConfig(ctx, filename, fsImpl)),
|
|
94
|
-
);
|
|
95
|
-
},
|
|
96
|
-
/**
|
|
97
|
-
* @param {{ projectRoot?: string, fs?: typeof nodeFs }} [ctx]
|
|
98
|
-
* @returns {void}
|
|
99
|
-
*/
|
|
100
|
-
apply(ctx) {
|
|
101
|
-
const fsImpl = ctx?.fs ?? nodeFs;
|
|
102
|
-
for (const filename of AGENTRC_FILENAMES) {
|
|
103
|
-
const config = readAgentrcConfig(ctx, filename, fsImpl);
|
|
104
|
-
if (!hasRetiredKey(config)) continue;
|
|
105
|
-
|
|
106
|
-
stripRetiredKey(config);
|
|
107
|
-
fsImpl.writeFileSync(
|
|
108
|
-
resolveAgentrcPath(ctx, filename),
|
|
109
|
-
`${JSON.stringify(config, null, 2)}\n`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
};
|
|
24
|
+
keys: [{ path: ['planning', 'codebaseSnapshot'], pruneDepth: 1 }],
|
|
25
|
+
});
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
// lib/migrations/steps/2.32.0-retire-lint-baseline-command.js
|
|
2
2
|
/**
|
|
3
|
-
* Story #5004
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `lib/orchestration/lint-baseline-service.js`: the CLI spawned the configured
|
|
8
|
-
* command and parsed ESLint-shaped JSON, a shape this repo's own `npm run
|
|
9
|
-
* lint` (Biome + markdownlint fan-out) never produced, and nothing invoked the
|
|
10
|
-
* service. The key left the runtime AJV schema, the published mirror,
|
|
11
|
-
* `.agentrc.json` and `agentrc-reference.json` in the same change.
|
|
12
|
-
*
|
|
13
|
-
* `project.commands` carries `additionalProperties: false`, so a consumer
|
|
14
|
-
* whose config still sets `lintBaseline` hits a hard validation failure on
|
|
15
|
-
* upgrade, not a warning. This step strips the key before that check runs —
|
|
16
|
-
* the same contract-cutover pattern as `2.11.0-retire-max-seed-words.js`.
|
|
3
|
+
* Story #5004 — strip the retired `project.commands.lintBaseline` from a
|
|
4
|
+
* consumer's config. The framework-owned lint-baseline capture CLI is gone,
|
|
5
|
+
* and the `commands` block carries `additionalProperties: false`, so a
|
|
6
|
+
* config still naming the command fails validation on upgrade.
|
|
17
7
|
*
|
|
18
8
|
* It sweeps **both** config surfaces. `config-resolver.js` deep-merges
|
|
19
9
|
* `.agentrc.local.json` over `.agentrc.json` and validates the result, so a
|
|
@@ -27,6 +17,11 @@
|
|
|
27
17
|
* `baselines/lint.json`; only the framework-owned capture shell is gone, so
|
|
28
18
|
* nothing here touches the baseline file or the gate config.
|
|
29
19
|
*
|
|
20
|
+
* An emptied `commands` block is deliberately left in place (`pruneDepth: 0`):
|
|
21
|
+
* unlike `planning.complexityGate`, `project` is a required block and an empty
|
|
22
|
+
* `commands` object is valid against the schema, so pruning it would be a
|
|
23
|
+
* cosmetic edit to a config the consumer owns.
|
|
24
|
+
*
|
|
30
25
|
* This step is also the carrier for the release note #5004 never emitted: its
|
|
31
26
|
* commit ships the `BREAKING CHANGE:` footer that the squash subject on `main`
|
|
32
27
|
* (`227e1af4`) lacks, and which release-please therefore could not surface.
|
|
@@ -34,94 +29,12 @@
|
|
|
34
29
|
* fix that stops the next one being lost.
|
|
35
30
|
*/
|
|
36
31
|
|
|
37
|
-
import
|
|
38
|
-
import path from 'node:path';
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Both config surfaces the resolver reads. `.agentrc.local.json` is deep-merged
|
|
42
|
-
* over the base *before* the AJV gate runs
|
|
43
|
-
* (`.agents/scripts/lib/config-resolver.js`), so a `lintBaseline` surviving in
|
|
44
|
-
* the overlay fails validation exactly as one in the base would — and a step
|
|
45
|
-
* that swept only the base would leave that consumer hard-broken with no
|
|
46
|
-
* self-service remedy, since re-running `mandrel update` would keep reporting
|
|
47
|
-
* clean. The overlay is operator-owned and gitignored, which is why it is easy
|
|
48
|
-
* to forget and why the sweep has to name it explicitly.
|
|
49
|
-
*/
|
|
50
|
-
const AGENTRC_FILENAMES = Object.freeze([
|
|
51
|
-
'.agentrc.json',
|
|
52
|
-
'.agentrc.local.json',
|
|
53
|
-
]);
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param {unknown} ctx
|
|
57
|
-
* @param {string} filename
|
|
58
|
-
* @returns {string}
|
|
59
|
-
*/
|
|
60
|
-
function resolveAgentrcPath(ctx, filename) {
|
|
61
|
-
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
62
|
-
return path.join(projectRoot, filename);
|
|
63
|
-
}
|
|
32
|
+
import { createRetireAgentrcKeyStep } from '../helpers/retire-agentrc-key.js';
|
|
64
33
|
|
|
65
|
-
|
|
66
|
-
* @param {unknown} ctx
|
|
67
|
-
* @param {typeof nodeFs} fsImpl
|
|
68
|
-
* @param {string} filename
|
|
69
|
-
* @returns {object | null}
|
|
70
|
-
*/
|
|
71
|
-
function readAgentrcConfig(ctx, fsImpl, filename) {
|
|
72
|
-
try {
|
|
73
|
-
const raw = fsImpl.readFileSync(resolveAgentrcPath(ctx, filename), 'utf8');
|
|
74
|
-
return JSON.parse(raw);
|
|
75
|
-
} catch {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* @param {object | null} config
|
|
82
|
-
* @returns {boolean}
|
|
83
|
-
*/
|
|
84
|
-
function hasRetiredKey(config) {
|
|
85
|
-
const commands = config?.project?.commands;
|
|
86
|
-
return Boolean(commands) && Object.hasOwn(commands, 'lintBaseline');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export const retireLintBaselineCommand = {
|
|
34
|
+
export const retireLintBaselineCommand = createRetireAgentrcKeyStep({
|
|
90
35
|
version: '2.32.0',
|
|
91
36
|
description:
|
|
92
37
|
'strip retired project.commands.lintBaseline from .agentrc.json ' +
|
|
93
38
|
'(the framework lint-baseline capture CLI is gone — Story #5004)',
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
* @returns {boolean}
|
|
97
|
-
*/
|
|
98
|
-
detect(ctx) {
|
|
99
|
-
const fsImpl = ctx?.fs ?? nodeFs;
|
|
100
|
-
return AGENTRC_FILENAMES.some((filename) =>
|
|
101
|
-
hasRetiredKey(readAgentrcConfig(ctx, fsImpl, filename)),
|
|
102
|
-
);
|
|
103
|
-
},
|
|
104
|
-
/**
|
|
105
|
-
* @param {{ projectRoot?: string, fs?: typeof nodeFs }} [ctx]
|
|
106
|
-
* @returns {void}
|
|
107
|
-
*/
|
|
108
|
-
apply(ctx) {
|
|
109
|
-
const fsImpl = ctx?.fs ?? nodeFs;
|
|
110
|
-
for (const filename of AGENTRC_FILENAMES) {
|
|
111
|
-
const config = readAgentrcConfig(ctx, fsImpl, filename);
|
|
112
|
-
// An absent overlay is the common case, not an error.
|
|
113
|
-
if (!config || !hasRetiredKey(config)) continue;
|
|
114
|
-
|
|
115
|
-
delete config.project.commands.lintBaseline;
|
|
116
|
-
// An emptied `commands` block is left in place: unlike
|
|
117
|
-
// `planning.complexityGate`, `project` is a required block and an empty
|
|
118
|
-
// `commands` object is valid against the schema, so pruning it would be
|
|
119
|
-
// a cosmetic edit to a config the consumer owns.
|
|
120
|
-
|
|
121
|
-
fsImpl.writeFileSync(
|
|
122
|
-
resolveAgentrcPath(ctx, filename),
|
|
123
|
-
`${JSON.stringify(config, null, 2)}\n`,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
};
|
|
39
|
+
keys: [{ path: ['project', 'commands', 'lintBaseline'], pruneDepth: 0 }],
|
|
40
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mandrel",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.37.0",
|
|
4
4
|
"description": "Claude Code-first opinionated workflow framework: instructions, skills, rules, and SDLC workflows that govern AI coding assistants.",
|
|
5
5
|
"files": [
|
|
6
6
|
".agents/",
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"test": "node .agents/scripts/run-tests.js",
|
|
20
20
|
"test:quick": "node .agents/scripts/run-tests.js --tier quick",
|
|
21
21
|
"test:integration": "node .agents/scripts/run-tests.js --tier integration",
|
|
22
|
+
"test:e2e": "node .agents/scripts/run-tests.js --tier e2e",
|
|
22
23
|
"test:profile": "node .agents/scripts/run-test-profile.js",
|
|
24
|
+
"test:census": "SPAWN_CENSUS_OUT=\"${SPAWN_CENSUS_OUT:-temp/census.json}\" NODE_OPTIONS=\"--require $PWD/tests/fixtures/spawn-census.cjs\" node .agents/scripts/run-tests.js",
|
|
23
25
|
"test:isolate": "node .agents/scripts/test-isolate.js",
|
|
24
26
|
"verify": "node .agents/scripts/run-verify.js",
|
|
25
27
|
"typecheck": "node -e \"process.exit(0)\"",
|
|
@@ -31,7 +33,7 @@
|
|
|
31
33
|
"baselines:scope": "node .agents/scripts/check-baseline-scope.js",
|
|
32
34
|
"baselines:prune": "node .agents/scripts/prune-baseline-orphans.js",
|
|
33
35
|
"lint:md": "markdownlint-cli2 \".agents/**/*.md\" \"*.md\" \"!node_modules/**\" \"!.worktrees/**\"",
|
|
34
|
-
"lint": "node .agents/scripts/run-lint.js && npm run docs:check",
|
|
36
|
+
"lint": "node .agents/scripts/run-lint.js && node .agents/scripts/check-generated-validator.js --check && npm run docs:check",
|
|
35
37
|
"docs:gen": "node .agents/scripts/generate-config-docs.js && node .agents/scripts/generate-workflows-doc.js && node .agents/scripts/generate-lens-checklists.js",
|
|
36
38
|
"docs:check": "node .agents/scripts/generate-config-docs.js --check && node .agents/scripts/generate-workflows-doc.js --check && node .agents/scripts/generate-lens-checklists.js --check && node .agents/scripts/check-doc-links.js && npm run skills:check",
|
|
37
39
|
"skills:index": "node .agents/scripts/generate-skills-index.js",
|
|
@@ -47,6 +49,8 @@
|
|
|
47
49
|
"cyclomatic:update": "node .agents/scripts/check-cyclomatic.js --update",
|
|
48
50
|
"dead-exports:update": "node .agents/scripts/update-dead-exports-baseline.js && node .agents/scripts/update-dead-exports-baseline.js --production",
|
|
49
51
|
"check:knip-entries": "node .agents/scripts/check-knip-entries.js",
|
|
52
|
+
"validator:gen": "node .agents/scripts/check-generated-validator.js",
|
|
53
|
+
"validator:check": "node .agents/scripts/check-generated-validator.js --check",
|
|
50
54
|
"check:schema-refs": "node .agents/scripts/check-schema-references.js",
|
|
51
55
|
"check:workflow-citations": "node .agents/scripts/check-workflow-citations.js",
|
|
52
56
|
"crap:check": "node .agents/scripts/check-baselines.js --gate crap",
|
|
@@ -101,7 +105,7 @@
|
|
|
101
105
|
"markdownlint-cli2": "^0.22.1",
|
|
102
106
|
"memfs": "^4.57.7",
|
|
103
107
|
"node-pty": "^1.0.0",
|
|
104
|
-
"typescript": "
|
|
108
|
+
"typescript": "^6.0.3"
|
|
105
109
|
},
|
|
106
110
|
"dependencies": {
|
|
107
111
|
"ajv": "^8.20.0",
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/audit-suite/frontmatter-lint.js — workflow frontmatter linter.
|
|
3
|
-
*
|
|
4
|
-
* Lives next to `frontmatter.js` but stays in its own module so adding
|
|
5
|
-
* lints doesn't drag the summary helper's maintainability score down.
|
|
6
|
-
* Pure: no IO, no provider calls, safe to unit-test in isolation.
|
|
7
|
-
*
|
|
8
|
-
* Story #1324, Epic #1185 — Dispatch performance pass.
|
|
9
|
-
*
|
|
10
|
-
* Story #2824, Epic #2815 — Model-hint frontmatter was removed from
|
|
11
|
-
* every workflow and from the validator's field list. The function is
|
|
12
|
-
* retained as a no-op safety net so callers (and any future frontmatter
|
|
13
|
-
* lints) keep a stable entry point.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { extractFrontmatter } from './frontmatter.js';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Pure: lint a frontmatter map (or raw workflow content). Currently a
|
|
20
|
-
* no-op — no frontmatter fields are validated after the model-hint
|
|
21
|
-
* removal. The signature is preserved so future field-level lints can
|
|
22
|
-
* slot in without churning callers.
|
|
23
|
-
*
|
|
24
|
-
* @param {string | Record<string, string>} input
|
|
25
|
-
* @returns {{ ok: boolean, errors: Array<{ field: string, value: string, message: string }> }}
|
|
26
|
-
*/
|
|
27
|
-
export function validateFrontmatter(input) {
|
|
28
|
-
// Extract so callers still pay the same "is this parseable?" cost as
|
|
29
|
-
// before; we just have no field-level rules to enforce right now.
|
|
30
|
-
if (typeof input === 'string') extractFrontmatter(input);
|
|
31
|
-
return { ok: true, errors: [] };
|
|
32
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import {
|
|
3
|
-
write as writeBaselineEnvelope,
|
|
4
|
-
writeFile as writeBaselineFile,
|
|
5
|
-
} from './writer.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Saves a new maintainability baseline to disk at `baselinePath`.
|
|
9
|
-
*
|
|
10
|
-
* Accepts the legacy flat `{ path: mi }` shape for backwards compatibility
|
|
11
|
-
* with existing callers (`regenerateMainFromTree`, refresh helpers). The
|
|
12
|
-
* map is transformed into the canonical envelope shape (`$schema`,
|
|
13
|
-
* `kernelVersion`, `generatedAt`, `rollup`, `rows`) via the shared
|
|
14
|
-
* `lib/baselines/writer.js` pipeline before being persisted, so every
|
|
15
|
-
* write produces a file that round-trips through `lib/baselines/reader.js`
|
|
16
|
-
* without schema errors.
|
|
17
|
-
*
|
|
18
|
-
* @param {Record<string, number>} baseline path→MI flat map.
|
|
19
|
-
* @param {string} baselinePath Required — caller supplies via getBaselines().
|
|
20
|
-
*/
|
|
21
|
-
export function saveBaseline(baseline, baselinePath) {
|
|
22
|
-
if (typeof baselinePath !== 'string' || baselinePath.length === 0) {
|
|
23
|
-
throw new TypeError(
|
|
24
|
-
'maintainability-utils.saveBaseline: baselinePath is required.',
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
const abs = path.isAbsolute(baselinePath)
|
|
28
|
-
? baselinePath
|
|
29
|
-
: path.resolve(process.cwd(), baselinePath);
|
|
30
|
-
|
|
31
|
-
const rows = Object.entries(baseline ?? {}).map(([p, mi]) => ({
|
|
32
|
-
path: p,
|
|
33
|
-
mi,
|
|
34
|
-
}));
|
|
35
|
-
const envelope = writeBaselineEnvelope({ kind: 'maintainability', rows });
|
|
36
|
-
writeBaselineFile(abs, envelope);
|
|
37
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* parse-numeric.js — shared CLI integer-flag validators (Story #2993).
|
|
3
|
-
*
|
|
4
|
-
* Extracted from `lifecycle-emit-story-dispatch.js` so future scripts that
|
|
5
|
-
* need a strict positive-integer or non-negative-integer flag value share
|
|
6
|
-
* a single validator instead of hand-rolling another `Number.parseInt`
|
|
7
|
-
* dance. Both helpers throw with a clear "required" / "must be …" message
|
|
8
|
-
* shape so a `runAsCli` boundary surfaces a clean exit-1.
|
|
9
|
-
*
|
|
10
|
-
* Contract:
|
|
11
|
-
* - Empty / `null` / `undefined` raw values throw "is required".
|
|
12
|
-
* - Non-integer or out-of-range values throw "must be a … integer".
|
|
13
|
-
* - A leading `#` on the raw value is stripped (matches the ticket-ID
|
|
14
|
-
* ergonomic on the rest of the framework).
|
|
15
|
-
*
|
|
16
|
-
* @module lib/cli/parse-numeric
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Parse a flag value as a strict positive integer (≥ 1). Leading `#` is
|
|
21
|
-
* stripped to match the `parseTicketId` ergonomic.
|
|
22
|
-
*
|
|
23
|
-
* @param {string|number|null|undefined} raw
|
|
24
|
-
* @param {string} flag Flag label used in the thrown error message.
|
|
25
|
-
* @param {string} [tool='cli'] Optional tool prefix for the error message.
|
|
26
|
-
* @returns {number}
|
|
27
|
-
*/
|
|
28
|
-
export function parseRequiredPositiveInt(raw, flag, tool = 'cli') {
|
|
29
|
-
if (raw === undefined || raw === null || raw === '') {
|
|
30
|
-
throw new Error(`${tool}: ${flag} is required`);
|
|
31
|
-
}
|
|
32
|
-
const n = Number.parseInt(String(raw).replace(/^#/, ''), 10);
|
|
33
|
-
if (!Number.isInteger(n) || n < 1) {
|
|
34
|
-
throw new Error(`${tool}: ${flag} must be a positive integer (got ${raw})`);
|
|
35
|
-
}
|
|
36
|
-
return n;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Parse a flag value as a strict non-negative integer (≥ 0). Leading `#`
|
|
41
|
-
* is NOT stripped — non-negative use cases (wave index, attempt offset)
|
|
42
|
-
* do not carry the ticket-ID convention.
|
|
43
|
-
*
|
|
44
|
-
* @param {string|number|null|undefined} raw
|
|
45
|
-
* @param {string} flag
|
|
46
|
-
* @param {string} [tool='cli']
|
|
47
|
-
* @returns {number}
|
|
48
|
-
*/
|
|
49
|
-
export function parseRequiredNonNegativeInt(raw, flag, tool = 'cli') {
|
|
50
|
-
if (raw === undefined || raw === null || raw === '') {
|
|
51
|
-
throw new Error(`${tool}: ${flag} is required`);
|
|
52
|
-
}
|
|
53
|
-
const n = Number.parseInt(String(raw), 10);
|
|
54
|
-
if (!Number.isInteger(n) || n < 0) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
`${tool}: ${flag} must be a non-negative integer (got ${raw})`,
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
return n;
|
|
60
|
-
}
|