mandrel 2.67.0 → 2.69.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/agents/story-worker.md +15 -11
- package/.agents/docs/agentrc-reference.json +3 -1
- package/.agents/docs/configuration.md +36 -1
- package/.agents/schemas/agentrc.schema.json +14 -1
- package/.agents/schemas/story-deliver-terminal.schema.json +23 -1
- package/.agents/schemas/validation-evidence.schema.json +3 -1
- package/.agents/scripts/coverage-capture.js +65 -9
- package/.agents/scripts/evidence-gate.js +106 -8
- package/.agents/scripts/lib/baselines/coverage-refresh-scope.js +60 -0
- package/.agents/scripts/lib/baselines/crap-updater-cli.js +101 -4
- package/.agents/scripts/lib/baselines/refresh-service.js +1 -1
- package/.agents/scripts/lib/baselines/seat-missing.js +228 -0
- package/.agents/scripts/lib/child-exec.js +39 -1
- package/.agents/scripts/lib/close-validation/gates.js +59 -19
- package/.agents/scripts/lib/close-validation/process.js +23 -24
- package/.agents/scripts/lib/close-validation/runner.js +71 -40
- package/.agents/scripts/lib/config/gates/coverage.schema.js +21 -0
- package/.agents/scripts/lib/config/quality.js +7 -1
- package/.agents/scripts/lib/config/temp-paths.js +15 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +1 -1
- package/.agents/scripts/lib/coverage-baseline.js +78 -5
- package/.agents/scripts/lib/coverage-capture-affected.js +345 -0
- package/.agents/scripts/lib/coverage-capture-delta.js +180 -0
- package/.agents/scripts/lib/coverage-capture-fullscope.js +53 -32
- package/.agents/scripts/lib/coverage-capture-incremental.js +49 -26
- package/.agents/scripts/lib/coverage-capture-usage.js +1 -1
- package/.agents/scripts/lib/coverage-capture.js +121 -81
- package/.agents/scripts/lib/full-suite-lock.js +49 -46
- package/.agents/scripts/lib/full-suite-queue.js +83 -8
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/orchestration/code-review.js +15 -3
- package/.agents/scripts/lib/orchestration/merge-poll.js +5 -0
- package/.agents/scripts/lib/orchestration/review-deposit.js +219 -0
- package/.agents/scripts/lib/orchestration/review-providers/code-review.js +11 -7
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +29 -10
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +124 -73
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +38 -20
- package/.agents/scripts/lib/orchestration/single-story-close/phases/lock-wait-pending.js +8 -2
- package/.agents/scripts/lib/orchestration/single-story-close/review-overlap.js +161 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +47 -7
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +8 -16
- package/.agents/scripts/lib/process-group.js +1 -1
- package/.agents/scripts/lib/supervised-suite.js +247 -0
- package/.agents/scripts/lib/wave-runner/cross-run-overlap.js +120 -0
- package/.agents/scripts/lib/wave-runner/live-probe.js +5 -1
- package/.agents/scripts/quality-preview.js +112 -14
- package/.agents/scripts/stories-wave-tick.js +47 -0
- package/.agents/scripts/story-review-compute.js +207 -0
- package/.agents/scripts/update-coverage-baseline.js +15 -10
- package/.agents/scripts/update-crap-baseline.js +12 -2
- package/.agents/scripts/update-maintainability-baseline.js +12 -2
- package/.agents/workflows/audit-security.md +0 -1
- package/.agents/workflows/helpers/code-review.md +7 -5
- package/.agents/workflows/helpers/deliver-digest.md +39 -36
- package/.agents/workflows/helpers/deliver-reference.md +115 -5
- package/.agents/workflows/helpers/deliver-story.md +2 -1
- package/docs/CHANGELOG.md +33 -0
- package/lib/cli/registry.js +125 -18
- package/lib/migrations/steps/strip-removed-agentrc-keys.js +0 -5
- package/package.json +1 -1
package/docs/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,39 @@ All notable changes to this project will be documented in this file.
|
|
|
15
15
|
-->
|
|
16
16
|
<!-- markdownlint-disable-file MD004 MD012 MD037 -->
|
|
17
17
|
|
|
18
|
+
## [2.69.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.68.0...mandrel-v2.69.0) (2026-09-27)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
* **audit:** ship /audit-security as a slash command (refs [#5498](https://github.com/dsj1984/mandrel/issues/5498)) ([#5499](https://github.com/dsj1984/mandrel/issues/5499)) ([7df7649](https://github.com/dsj1984/mandrel/commit/7df76496cfc35f636d117961bb7042b5aa662ec6))
|
|
24
|
+
|
|
25
|
+
## [2.68.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.67.0...mandrel-v2.68.0) (2026-09-26)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
* baselines: add an insert-only `--seat-missing` refresh and have the story-worker seat rows for methods its diff adds before push ([#5486](https://github.com/dsj1984/mandrel/issues/5486)) ([#5492](https://github.com/dsj1984/mandrel/issues/5492)) ([325d7bb](https://github.com/dsj1984/mandrel/commit/325d7bbbc0bb08bb6fe3f05287d959f2b42d4da2))
|
|
31
|
+
* close-validation: fail cheap gates before coverage capture and report lock expiry / timeout honestly ([#5471](https://github.com/dsj1984/mandrel/issues/5471)) ([#5474](https://github.com/dsj1984/mandrel/issues/5474)) ([ece115d](https://github.com/dsj1984/mandrel/commit/ece115d7961566c21de45f79f44b3957389e7623))
|
|
32
|
+
* coverage-capture: after a base-sync, re-measure only main's delta instead of re-running the whole suite ([#5487](https://github.com/dsj1984/mandrel/issues/5487)) ([#5496](https://github.com/dsj1984/mandrel/issues/5496)) ([03694a4](https://github.com/dsj1984/mandrel/commit/03694a4bfa970ba122c1f508a7ed4b1271ccb222))
|
|
33
|
+
* coverage-capture: opt-in `affected` scope that runs a consumer-owned scoped coverage script ([#5472](https://github.com/dsj1984/mandrel/issues/5472)) ([#5483](https://github.com/dsj1984/mandrel/issues/5483)) ([50bf942](https://github.com/dsj1984/mandrel/commit/50bf9424d38e776270cfaf19935dd265bb950625))
|
|
34
|
+
* coverage-capture: spend the suite timeout on tests, queue on the full-suite lock, and make the cap configurable ([#5485](https://github.com/dsj1984/mandrel/issues/5485)) ([#5493](https://github.com/dsj1984/mandrel/issues/5493)) ([6a5bc69](https://github.com/dsj1984/mandrel/commit/6a5bc6929138e70ca66a9aea0e4354dcf5319ed7))
|
|
35
|
+
* deliver: compute the Story-scope review at worker push time, keyed on the diff digest, so close posts a held result and a CRITICAL is fixed before hand-off ([#5480](https://github.com/dsj1984/mandrel/issues/5480)) ([#5490](https://github.com/dsj1984/mandrel/issues/5490)) ([c43b6bf](https://github.com/dsj1984/mandrel/commit/c43b6bf3beca4aeb0dc6350f3fe1f5c9cdcda250))
|
|
36
|
+
* deliver: make the worker's one credited run the coverage capture close will otherwise re-pay, and let doctor name the credit gaps ([#5477](https://github.com/dsj1984/mandrel/issues/5477)) ([#5482](https://github.com/dsj1984/mandrel/issues/5482)) ([67e8d06](https://github.com/dsj1984/mandrel/commit/67e8d0687a6254f8ff8ab9957867c69588642aad))
|
|
37
|
+
* deliver: warn in the beat envelope when a Story's footprint overlaps another session's in-flight Story ([#5488](https://github.com/dsj1984/mandrel/issues/5488)) ([#5491](https://github.com/dsj1984/mandrel/issues/5491)) ([be68220](https://github.com/dsj1984/mandrel/commit/be682205dc5959e12819bd2f2463c352449acd09))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
* close: in async merge-watch mode return `pending` after one probe instead of burning the 60s window ([#5479](https://github.com/dsj1984/mandrel/issues/5479)) ([#5484](https://github.com/dsj1984/mandrel/issues/5484)) ([440b8bf](https://github.com/dsj1984/mandrel/commit/440b8bf70ed53d973c635662a12791a1d9f51953))
|
|
43
|
+
* **close:** bound the full-suite lock wait by the suite's kill bound (refs [#5478](https://github.com/dsj1984/mandrel/issues/5478)) ([#5481](https://github.com/dsj1984/mandrel/issues/5481)) ([c7b7719](https://github.com/dsj1984/mandrel/commit/c7b7719bf50f7bc557c1979505ebab6a6851345b))
|
|
44
|
+
* coverage-capture: a red capture must never leave an artifact a later pr… ([#5494](https://github.com/dsj1984/mandrel/issues/5494)) ([#5495](https://github.com/dsj1984/mandrel/issues/5495)) ([fa72738](https://github.com/dsj1984/mandrel/commit/fa72738d486626e892b5fd89e0350f745d925cfc))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Performance
|
|
48
|
+
|
|
49
|
+
* close: run the Story-scope code review concurrently with the validation gates ([#5473](https://github.com/dsj1984/mandrel/issues/5473)) ([#5476](https://github.com/dsj1984/mandrel/issues/5476)) ([e098205](https://github.com/dsj1984/mandrel/commit/e09820592d70ba600df288c4e4b5ae1abd5b9aa1))
|
|
50
|
+
|
|
18
51
|
## [2.67.0](https://github.com/dsj1984/mandrel/compare/mandrel-v2.66.0...mandrel-v2.67.0) (2026-09-26)
|
|
19
52
|
|
|
20
53
|
|
package/lib/cli/registry.js
CHANGED
|
@@ -864,59 +864,166 @@ const MANDREL_TEST_RUNNER = 'run-tests.js';
|
|
|
864
864
|
const TEST_CREDIT_DEPOSIT_COMMAND =
|
|
865
865
|
'node .agents/scripts/evidence-gate.js --standalone --scope-id <storyId> --gate test --worktree <workCwd> -- npm test';
|
|
866
866
|
|
|
867
|
+
/** The capture close's `coverage-capture` gate finds fresh, per digest § 5. */
|
|
868
|
+
const COVERAGE_CAPTURE_COMMAND =
|
|
869
|
+
'node .agents/scripts/coverage-capture.js --cwd <workCwd>';
|
|
870
|
+
|
|
867
871
|
const TEST_CREDIT_REMEDY = `run the suite through the depositor instead of bare \`npm test\`: ${TEST_CREDIT_DEPOSIT_COMMAND}`;
|
|
868
872
|
|
|
873
|
+
const PRE_PUSH_REMEDY =
|
|
874
|
+
'wrap each hook gate in `node .agents/scripts/evidence-gate.js --standalone --scope-id <storyId> --gate <typecheck|lint> -- <command>` so the push deposits the credit close reads';
|
|
875
|
+
|
|
876
|
+
/** A hook line that runs a typecheck or lint gate. */
|
|
877
|
+
const HOOK_GATE_RE = /\b(typecheck|tsc|lint|eslint|biome)\b/;
|
|
878
|
+
|
|
869
879
|
/**
|
|
870
880
|
* Read from `package.json`, not `project.commands.test`: the close gate's argv
|
|
871
881
|
* is the literal `npm test`.
|
|
872
882
|
*
|
|
873
883
|
* @param {string} projectRoot
|
|
874
884
|
* @param {typeof fs.readFileSync} readFileImpl
|
|
875
|
-
* @returns {string
|
|
885
|
+
* @returns {Record<string, string>} The `scripts` map, empty when unreadable.
|
|
876
886
|
*/
|
|
877
|
-
function
|
|
887
|
+
function readProjectScripts(projectRoot, readFileImpl) {
|
|
878
888
|
try {
|
|
879
889
|
const raw = readFileImpl(path.join(projectRoot, 'package.json'), 'utf8');
|
|
880
|
-
const
|
|
881
|
-
return typeof
|
|
882
|
-
? script.trim()
|
|
883
|
-
: null;
|
|
890
|
+
const scripts = JSON.parse(raw)?.scripts;
|
|
891
|
+
return scripts && typeof scripts === 'object' ? scripts : {};
|
|
884
892
|
} catch {
|
|
885
|
-
return
|
|
893
|
+
return {};
|
|
886
894
|
}
|
|
887
895
|
}
|
|
888
896
|
|
|
889
897
|
/**
|
|
890
|
-
*
|
|
891
|
-
*
|
|
892
|
-
*
|
|
893
|
-
|
|
898
|
+
* @param {Record<string, string>} scripts
|
|
899
|
+
* @param {string} name
|
|
900
|
+
* @returns {string|null} The trimmed script, or null when there is none.
|
|
901
|
+
*/
|
|
902
|
+
function scriptNamed(scripts, name) {
|
|
903
|
+
const script = scripts[name];
|
|
904
|
+
return typeof script === 'string' && script.trim().length > 0
|
|
905
|
+
? script.trim()
|
|
906
|
+
: null;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* `delivery.quality.gates.crap.enabled` from `.agentrc.json` overlaid by
|
|
911
|
+
* `.agentrc.local.json`, defaulting on — the predicate close registers
|
|
912
|
+
* `coverage-capture` on. Read without `resolveConfig()` (built-ins only).
|
|
894
913
|
*
|
|
895
|
-
* @param {
|
|
896
|
-
* @
|
|
914
|
+
* @param {string} projectRoot
|
|
915
|
+
* @param {typeof fs.readFileSync} readFileImpl
|
|
916
|
+
* @returns {boolean}
|
|
897
917
|
*/
|
|
898
|
-
function
|
|
899
|
-
|
|
918
|
+
function isCrapEnabled(projectRoot, readFileImpl) {
|
|
919
|
+
let enabled = true;
|
|
920
|
+
for (const file of ['.agentrc.json', '.agentrc.local.json']) {
|
|
921
|
+
const value = readJsonSafe(path.join(projectRoot, file), {
|
|
922
|
+
readFileSync: readFileImpl,
|
|
923
|
+
})?.delivery?.quality?.gates?.crap?.enabled;
|
|
924
|
+
if (typeof value === 'boolean') enabled = value;
|
|
925
|
+
}
|
|
926
|
+
return enabled;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Which depositor digest § 5 selects, stated with its exact command.
|
|
931
|
+
*
|
|
932
|
+
* @param {{ scripts: Record<string, string>, crapEnabled: boolean }} probe
|
|
933
|
+
* @returns {{ detail: string, remedy?: string }}
|
|
934
|
+
*/
|
|
935
|
+
function describeCreditedRun({ scripts, crapEnabled }) {
|
|
936
|
+
if (crapEnabled && scriptNamed(scripts, 'test:coverage') !== null) {
|
|
937
|
+
return {
|
|
938
|
+
detail: `the CRAP gate is enabled and \`test:coverage\` exists, so close registers \`coverage-capture\` and the credited run is the coverage capture: \`${COVERAGE_CAPTURE_COMMAND}\` — its fresh stamp lets close's capture skip the suite`,
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
const script = scriptNamed(scripts, 'test');
|
|
900
942
|
if (script === null) {
|
|
901
943
|
return {
|
|
902
|
-
ok: true,
|
|
903
944
|
detail: `no \`test\` script in package.json — close still spawns \`npm test\`, so ${TEST_CREDIT_REMEDY}`,
|
|
904
945
|
remedy: TEST_CREDIT_REMEDY,
|
|
905
946
|
};
|
|
906
947
|
}
|
|
907
948
|
if (script.includes(MANDREL_TEST_RUNNER)) {
|
|
908
949
|
return {
|
|
909
|
-
ok: true,
|
|
910
950
|
detail: `\`npm test\` → \`${script}\` reaches mandrel's runner — a green full run on a story branch deposits the close \`test\` credit itself`,
|
|
911
951
|
};
|
|
912
952
|
}
|
|
913
953
|
return {
|
|
914
|
-
ok: true,
|
|
915
954
|
detail: `\`npm test\` → \`${script}\` is this project's own runner and never reaches \`${MANDREL_TEST_RUNNER}\`, so it deposits no close \`test\` credit and prints nothing — ${TEST_CREDIT_REMEDY}`,
|
|
916
955
|
remedy: TEST_CREDIT_REMEDY,
|
|
917
956
|
};
|
|
918
957
|
}
|
|
919
958
|
|
|
959
|
+
/**
|
|
960
|
+
* Does the pre-push hook deposit gate evidence? Read, never executed.
|
|
961
|
+
*
|
|
962
|
+
* @param {string} projectRoot
|
|
963
|
+
* @param {typeof fs.readFileSync} readFileImpl
|
|
964
|
+
* @returns {{ detail: string, remedy?: string }}
|
|
965
|
+
*/
|
|
966
|
+
function describePrePushHook(projectRoot, readFileImpl) {
|
|
967
|
+
let hook = null;
|
|
968
|
+
let hookPath = null;
|
|
969
|
+
for (const rel of [
|
|
970
|
+
path.join('.husky', 'pre-push'),
|
|
971
|
+
path.join('.git', 'hooks', 'pre-push'),
|
|
972
|
+
]) {
|
|
973
|
+
try {
|
|
974
|
+
hook = readFileImpl(path.join(projectRoot, rel), 'utf8');
|
|
975
|
+
hookPath = rel.split(path.sep).join('/');
|
|
976
|
+
break;
|
|
977
|
+
} catch {
|
|
978
|
+
// Absent here — try the next hooks location.
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (hook === null) {
|
|
982
|
+
return { detail: 'no pre-push hook found, so nothing runs a gate there' };
|
|
983
|
+
}
|
|
984
|
+
if (hook.includes('evidence-gate.js')) {
|
|
985
|
+
return {
|
|
986
|
+
detail: `\`${hookPath}\` routes its gates through \`evidence-gate.js\`, so the push deposits evidence close can credit`,
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
if (HOOK_GATE_RE.test(hook)) {
|
|
990
|
+
return {
|
|
991
|
+
detail: `\`${hookPath}\` runs typecheck or lint without \`evidence-gate.js\`, so it deposits nothing and close re-runs them — ${PRE_PUSH_REMEDY}`,
|
|
992
|
+
remedy: PRE_PUSH_REMEDY,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
return {
|
|
996
|
+
detail: `\`${hookPath}\` runs no typecheck or lint gate, so close has nothing of it to re-run`,
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Which credited run does digest § 5 select for this project, and does its
|
|
1002
|
+
* pre-push hook deposit the gate evidence close reads? Always `ok: true` —
|
|
1003
|
+
* every shape is supported, some just cost close a re-run — so any remedy
|
|
1004
|
+
* rides a passing verdict and is repeated in `detail`. Detected from files
|
|
1005
|
+
* only; nothing is executed.
|
|
1006
|
+
*
|
|
1007
|
+
* @param {{ projectRoot?: string, readFile?: typeof fs.readFileSync }} [opts]
|
|
1008
|
+
* @returns {{ ok: boolean, detail: string, remedy?: string }}
|
|
1009
|
+
*/
|
|
1010
|
+
function runTestCreditPath({ projectRoot, readFile = fs.readFileSync } = {}) {
|
|
1011
|
+
const root = projectRoot ?? process.cwd();
|
|
1012
|
+
const parts = [
|
|
1013
|
+
describeCreditedRun({
|
|
1014
|
+
scripts: readProjectScripts(root, readFile),
|
|
1015
|
+
crapEnabled: isCrapEnabled(root, readFile),
|
|
1016
|
+
}),
|
|
1017
|
+
describePrePushHook(root, readFile),
|
|
1018
|
+
];
|
|
1019
|
+
const remedies = parts.map((p) => p.remedy).filter(Boolean);
|
|
1020
|
+
return {
|
|
1021
|
+
ok: true,
|
|
1022
|
+
detail: parts.map((p) => p.detail).join('; '),
|
|
1023
|
+
...(remedies.length > 0 ? { remedy: remedies.join('; ') } : {}),
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
|
|
920
1027
|
/** `advisory: true` marks a check whose `run()` never fails. */
|
|
921
1028
|
export const registry = [
|
|
922
1029
|
{
|
|
@@ -88,11 +88,6 @@ const REMOVED_AGENTRC_KEYS = Object.freeze([
|
|
|
88
88
|
'BASELINE_REFRESH_TAG',
|
|
89
89
|
'baseline-refresh:',
|
|
90
90
|
),
|
|
91
|
-
folded(
|
|
92
|
-
'delivery.quality.gates.coverage.timeoutMs',
|
|
93
|
-
'COVERAGE_GATE_DEFAULTS.timeoutMs',
|
|
94
|
-
600000,
|
|
95
|
-
),
|
|
96
91
|
folded(
|
|
97
92
|
'delivery.quality.formatAutofix.timeoutMs',
|
|
98
93
|
'FORMAT_AUTOFIX_TIMEOUT_MS',
|
package/package.json
CHANGED