mandrel 2.67.0 → 2.68.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.
Files changed (60) hide show
  1. package/.agents/agents/story-worker.md +15 -11
  2. package/.agents/docs/agentrc-reference.json +3 -1
  3. package/.agents/docs/configuration.md +36 -1
  4. package/.agents/schemas/agentrc.schema.json +14 -1
  5. package/.agents/schemas/story-deliver-terminal.schema.json +23 -1
  6. package/.agents/schemas/validation-evidence.schema.json +3 -1
  7. package/.agents/scripts/coverage-capture.js +65 -9
  8. package/.agents/scripts/evidence-gate.js +106 -8
  9. package/.agents/scripts/lib/baselines/coverage-refresh-scope.js +60 -0
  10. package/.agents/scripts/lib/baselines/crap-updater-cli.js +101 -4
  11. package/.agents/scripts/lib/baselines/refresh-service.js +1 -1
  12. package/.agents/scripts/lib/baselines/seat-missing.js +228 -0
  13. package/.agents/scripts/lib/child-exec.js +39 -1
  14. package/.agents/scripts/lib/close-validation/gates.js +59 -19
  15. package/.agents/scripts/lib/close-validation/process.js +23 -24
  16. package/.agents/scripts/lib/close-validation/runner.js +71 -40
  17. package/.agents/scripts/lib/config/gates/coverage.schema.js +21 -0
  18. package/.agents/scripts/lib/config/quality.js +7 -1
  19. package/.agents/scripts/lib/config/temp-paths.js +15 -0
  20. package/.agents/scripts/lib/config-settings-schema-delivery.js +1 -1
  21. package/.agents/scripts/lib/coverage-baseline.js +78 -5
  22. package/.agents/scripts/lib/coverage-capture-affected.js +345 -0
  23. package/.agents/scripts/lib/coverage-capture-delta.js +180 -0
  24. package/.agents/scripts/lib/coverage-capture-fullscope.js +53 -32
  25. package/.agents/scripts/lib/coverage-capture-incremental.js +49 -26
  26. package/.agents/scripts/lib/coverage-capture-usage.js +1 -1
  27. package/.agents/scripts/lib/coverage-capture.js +121 -81
  28. package/.agents/scripts/lib/full-suite-lock.js +49 -46
  29. package/.agents/scripts/lib/full-suite-queue.js +83 -8
  30. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  31. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  32. package/.agents/scripts/lib/orchestration/code-review.js +15 -3
  33. package/.agents/scripts/lib/orchestration/merge-poll.js +5 -0
  34. package/.agents/scripts/lib/orchestration/review-deposit.js +219 -0
  35. package/.agents/scripts/lib/orchestration/review-providers/code-review.js +11 -7
  36. package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +29 -10
  37. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +124 -73
  38. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +38 -20
  39. package/.agents/scripts/lib/orchestration/single-story-close/phases/lock-wait-pending.js +8 -2
  40. package/.agents/scripts/lib/orchestration/single-story-close/review-overlap.js +161 -0
  41. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +47 -7
  42. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +8 -16
  43. package/.agents/scripts/lib/process-group.js +1 -1
  44. package/.agents/scripts/lib/supervised-suite.js +247 -0
  45. package/.agents/scripts/lib/wave-runner/cross-run-overlap.js +120 -0
  46. package/.agents/scripts/lib/wave-runner/live-probe.js +5 -1
  47. package/.agents/scripts/quality-preview.js +112 -14
  48. package/.agents/scripts/stories-wave-tick.js +47 -0
  49. package/.agents/scripts/story-review-compute.js +207 -0
  50. package/.agents/scripts/update-coverage-baseline.js +15 -10
  51. package/.agents/scripts/update-crap-baseline.js +12 -2
  52. package/.agents/scripts/update-maintainability-baseline.js +12 -2
  53. package/.agents/workflows/helpers/code-review.md +7 -5
  54. package/.agents/workflows/helpers/deliver-digest.md +39 -36
  55. package/.agents/workflows/helpers/deliver-reference.md +115 -5
  56. package/.agents/workflows/helpers/deliver-story.md +2 -1
  57. package/docs/CHANGELOG.md +26 -0
  58. package/lib/cli/registry.js +125 -18
  59. package/lib/migrations/steps/strip-removed-agentrc-keys.js +0 -5
  60. package/package.json +1 -1
@@ -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|null} The trimmed script, or null when there is none.
885
+ * @returns {Record<string, string>} The `scripts` map, empty when unreadable.
876
886
  */
877
- function readProjectTestScript(projectRoot, readFileImpl) {
887
+ function readProjectScripts(projectRoot, readFileImpl) {
878
888
  try {
879
889
  const raw = readFileImpl(path.join(projectRoot, 'package.json'), 'utf8');
880
- const script = JSON.parse(raw)?.scripts?.test;
881
- return typeof script === 'string' && script.trim().length > 0
882
- ? script.trim()
883
- : null;
890
+ const scripts = JSON.parse(raw)?.scripts;
891
+ return scripts && typeof scripts === 'object' ? scripts : {};
884
892
  } catch {
885
- return null;
893
+ return {};
886
894
  }
887
895
  }
888
896
 
889
897
  /**
890
- * Does a bare `npm test` reach mandrel's runner and so deposit the close
891
- * `test` credit? Always `ok: true` — another runner is supported, it just
892
- * needs the explicit depositor — so the remedy rides a passing verdict and is
893
- * repeated in `detail`. Detected by name only, never executed.
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 {{ projectRoot?: string, readFile?: typeof fs.readFileSync }} [opts]
896
- * @returns {{ ok: boolean, detail: string, remedy?: string }}
914
+ * @param {string} projectRoot
915
+ * @param {typeof fs.readFileSync} readFileImpl
916
+ * @returns {boolean}
897
917
  */
898
- function runTestCreditPath({ projectRoot, readFile = fs.readFileSync } = {}) {
899
- const script = readProjectTestScript(projectRoot ?? process.cwd(), readFile);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "2.67.0",
3
+ "version": "2.68.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/",