mandrel 2.37.0 → 2.39.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 (162) hide show
  1. package/.agents/README.md +61 -24
  2. package/.agents/agents/plan-critic.md +1 -1
  3. package/.agents/docs/SDLC.md +40 -40
  4. package/.agents/docs/agentrc-reference.json +1 -4
  5. package/.agents/docs/configuration.md +18 -18
  6. package/.agents/docs/execution-reference.md +3 -3
  7. package/.agents/docs/quality-gates.md +2 -2
  8. package/.agents/docs/workflows.md +6 -6
  9. package/.agents/instructions.md +5 -5
  10. package/.agents/rules/changelog-style.md +3 -3
  11. package/.agents/rules/ci-remediation.md +1 -1
  12. package/.agents/rules/git-conventions-reference.md +4 -4
  13. package/.agents/schemas/agentrc.schema.json +15 -16
  14. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  15. package/.agents/schemas/validation-evidence.schema.json +1 -1
  16. package/.agents/scripts/acceptance-eval.js +1 -1
  17. package/.agents/scripts/apply-quality-bootstrap.js +1 -1
  18. package/.agents/scripts/audit-to-stories.js +2 -2
  19. package/.agents/scripts/check-doc-links.js +32 -4
  20. package/.agents/scripts/check-test-temp-hygiene.js +1 -1
  21. package/.agents/scripts/check-workflow-cli-lint.js +1 -1
  22. package/.agents/scripts/deliver-light.js +10 -10
  23. package/.agents/scripts/deliver-recover.js +1 -1
  24. package/.agents/scripts/drain-pending-cleanup.js +1 -1
  25. package/.agents/scripts/evidence-gate.js +1 -1
  26. package/.agents/scripts/generate-skills-index.js +158 -75
  27. package/.agents/scripts/generate-workflows-doc.js +1 -1
  28. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  29. package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +1 -1
  30. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +1 -1
  31. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +5 -5
  32. package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +1 -1
  33. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  34. package/.agents/scripts/lib/bdd-scenario-budget.js +1 -1
  35. package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
  36. package/.agents/scripts/lib/bdd-step-index.js +1 -1
  37. package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
  38. package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
  39. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +1 -1
  40. package/.agents/scripts/lib/changed-files.js +100 -9
  41. package/.agents/scripts/lib/checks/core-bare-clean.js +1 -1
  42. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +1 -1
  43. package/.agents/scripts/lib/cli-args.js +1 -1
  44. package/.agents/scripts/lib/command-header.js +1 -1
  45. package/.agents/scripts/lib/config/delivery-routing.js +1 -1
  46. package/.agents/scripts/lib/config/explain.js +1 -1
  47. package/.agents/scripts/lib/config/runners.js +2 -2
  48. package/.agents/scripts/lib/config/runtime.js +1 -1
  49. package/.agents/scripts/lib/config/temp-paths.js +2 -2
  50. package/.agents/scripts/lib/config-settings-schema-delivery.js +3 -3
  51. package/.agents/scripts/lib/config-settings-schema-quality.js +6 -6
  52. package/.agents/scripts/lib/config-settings-schema.js +28 -10
  53. package/.agents/scripts/lib/duplicate-search.js +1 -1
  54. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +4 -4
  55. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
  56. package/.agents/scripts/lib/findings/classify-finding.js +1 -1
  57. package/.agents/scripts/lib/findings/promote-finding.js +12 -12
  58. package/.agents/scripts/lib/findings/route-finding.js +2 -2
  59. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  60. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  61. package/.agents/scripts/lib/label-constants.js +2 -2
  62. package/.agents/scripts/lib/label-taxonomy.js +1 -1
  63. package/.agents/scripts/lib/onboard/init-tail.js +4 -4
  64. package/.agents/scripts/lib/onboard/scaffold-docs.js +2 -2
  65. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
  66. package/.agents/scripts/lib/orchestration/code-review.js +4 -4
  67. package/.agents/scripts/lib/orchestration/complexity-gate.js +5 -5
  68. package/.agents/scripts/lib/orchestration/deliver-recover.js +3 -3
  69. package/.agents/scripts/lib/orchestration/docs-digest.js +3 -3
  70. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +2 -2
  71. package/.agents/scripts/lib/orchestration/light-backstop.js +1 -1
  72. package/.agents/scripts/lib/orchestration/light-escalation.js +6 -6
  73. package/.agents/scripts/lib/orchestration/light-suitability.js +19 -19
  74. package/.agents/scripts/lib/orchestration/plan-context.js +4 -4
  75. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +2 -2
  76. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +2 -2
  77. package/.agents/scripts/lib/orchestration/plan-metrics.js +1 -1
  78. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +1 -1
  79. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +3 -3
  80. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +6 -6
  81. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -16
  82. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +3 -3
  83. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +6 -6
  84. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +7 -7
  85. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -1
  86. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +3 -3
  87. package/.agents/scripts/lib/orchestration/remote-verifier.js +2 -2
  88. package/.agents/scripts/lib/orchestration/resolve-stories.js +4 -4
  89. package/.agents/scripts/lib/orchestration/run-epilogue.js +2 -2
  90. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +2 -2
  91. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +1 -1
  92. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +5 -1
  93. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +1 -1
  94. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +3 -3
  95. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -2
  96. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  97. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +14 -12
  98. package/.agents/scripts/lib/orchestration/ticket-validator.js +1 -1
  99. package/.agents/scripts/lib/orchestration/ticketing/reads.js +3 -3
  100. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
  101. package/.agents/scripts/lib/qa/resolve-qa-contract.js +58 -6
  102. package/.agents/scripts/lib/signals/schema.js +1 -1
  103. package/.agents/scripts/lib/skills/skills-index.js +168 -0
  104. package/.agents/scripts/lib/skills/walk-skill-files.js +133 -9
  105. package/.agents/scripts/lib/story-adjacency.js +2 -2
  106. package/.agents/scripts/lib/templates/decomposer-prompts.js +1 -1
  107. package/.agents/scripts/lib/ticket-body-sections.js +1 -1
  108. package/.agents/scripts/lib/util/concurrent-map.js +1 -1
  109. package/.agents/scripts/lib/util/parse-id-list.js +1 -1
  110. package/.agents/scripts/lib/wave-runner/live-probe.js +4 -4
  111. package/.agents/scripts/lib/wave-runner/ready-set.js +1 -1
  112. package/.agents/scripts/lib/workflow-closure.js +1 -1
  113. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +2 -2
  114. package/.agents/scripts/lint-issue-body.js +3 -3
  115. package/.agents/scripts/plan-context.js +4 -4
  116. package/.agents/scripts/plan-critics.js +2 -2
  117. package/.agents/scripts/plan-persist.js +2 -2
  118. package/.agents/scripts/plan-run-epilogue.js +2 -2
  119. package/.agents/scripts/providers/github/board-add.js +1 -1
  120. package/.agents/scripts/providers/github/errors.js +1 -1
  121. package/.agents/scripts/providers/github/tickets.js +6 -6
  122. package/.agents/scripts/quality-preview.js +50 -9
  123. package/.agents/scripts/resolve-stories.js +3 -3
  124. package/.agents/scripts/resync-status-column.js +1 -1
  125. package/.agents/scripts/run-lint.js +1 -1
  126. package/.agents/scripts/single-story-close.js +2 -2
  127. package/.agents/scripts/single-story-init.js +5 -5
  128. package/.agents/scripts/stories-wave-tick.js +8 -8
  129. package/.agents/scripts/validate-skills.js +53 -66
  130. package/.agents/skills/core/idea-refinement/SKILL.md +5 -5
  131. package/.agents/skills/core/scope-triage/SKILL.md +3 -3
  132. package/.agents/skills/skills.index.json +1 -1
  133. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -1
  134. package/.agents/workflows/audit-data-model.md +1 -1
  135. package/.agents/workflows/audit-documentation.md +2 -2
  136. package/.agents/workflows/audit-to-stories.md +11 -11
  137. package/.agents/workflows/git-deliver.md +4 -4
  138. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  139. package/.agents/workflows/helpers/audit-lens-core.md +1 -1
  140. package/.agents/workflows/helpers/code-review.md +4 -4
  141. package/.agents/workflows/helpers/deliver-light.md +26 -26
  142. package/.agents/workflows/helpers/deliver-reference.md +15 -7
  143. package/.agents/workflows/helpers/deliver-story-reference.md +9 -4
  144. package/.agents/workflows/helpers/deliver-story.md +6 -7
  145. package/.agents/workflows/helpers/diagnose.md +2 -2
  146. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  147. package/.agents/workflows/helpers/plan-reference.md +9 -9
  148. package/.agents/workflows/helpers/qa-core.md +8 -8
  149. package/.agents/workflows/helpers/worktree-lifecycle.md +6 -6
  150. package/.agents/workflows/{deliver.md → mandrel-deliver.md} +15 -19
  151. package/.agents/workflows/{plan.md → mandrel-plan.md} +9 -9
  152. package/.agents/workflows/memory-consolidate.md +4 -4
  153. package/.agents/workflows/prototype.md +3 -3
  154. package/.agents/workflows/qa-assist.md +12 -12
  155. package/.agents/workflows/qa-explore.md +6 -6
  156. package/.agents/workflows/qa-run.md +14 -6
  157. package/README.md +7 -7
  158. package/docs/CHANGELOG.md +23 -0
  159. package/lib/cli/doctor.js +1 -1
  160. package/lib/cli/init.js +1 -1
  161. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +1 -1
  162. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * plan-run-epilogue.js — execute the real per-run closeout for a
4
- * multi-Story `/deliver`.
4
+ * multi-Story `/mandrel-deliver`.
5
5
  *
6
6
  * Usage:
7
7
  * node .agents/scripts/plan-run-epilogue.js --stories 1,2,3
@@ -67,7 +67,7 @@ export async function main(argv = process.argv.slice(2), deps = {}) {
67
67
  // along with the label itself. The epilogue is keyed on the delivered id
68
68
  // set, and the synthesized `adhoc-<ids>` id it already used for positional
69
69
  // runs is now the only id it needs.
70
- // Range tokens expand here too (`--stories 101-104`): /deliver blesses the
70
+ // Range tokens expand here too (`--stories 101-104`): /mandrel-deliver blesses the
71
71
  // dash range at the operator surface, so the id set the epilogue is keyed on
72
72
  // must read the same shape. Rejecting a bad token is deliberate — the old
73
73
  // silent filter turned a typo into an empty, wrongly-keyed rollup.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Story #3822 — single source of truth for the post-create board-add
5
5
  * step. Issues created through any create path (`createTicket`,
6
- * `createIssue` — which backs `/plan` persist and the `/plan`
6
+ * `createIssue` — which backs `/mandrel-plan` persist and the `/mandrel-plan`
7
7
  * Phase 4 Epic open) must land on the configured Projects V2 board
8
8
  * without relying on GitHub's "Auto-add to project" built-in workflow,
9
9
  * which is off by default on fresh boards and cannot be enabled via API.
@@ -156,7 +156,7 @@ export function classifyGithubError(err) {
156
156
  // Read-path callers (paginateRest, getTicket, getNativeSubIssues, …) all
157
157
  // absorb the same jittered exponential backoff on transient GitHub errors
158
158
  // through this one primitive, instead of bubbling a one-shot
159
- // 502/429/ECONNRESET that kills a longer pipeline (e.g. the /deliver
159
+ // 502/429/ECONNRESET that kills a longer pipeline (e.g. the /mandrel-deliver
160
160
  // Phase E retro). The network consumers repointed here
161
161
  // (branch-protection, labels, projects-v2-graphql) call with no opts, so
162
162
  // they adopt these defaults; their retry *classes* (the network blips) are
@@ -15,7 +15,7 @@
15
15
  * Epic-hierarchy write surface. It composed the exact `Epic: #N` footer that
16
16
  * `pr-base-guard.js` hard-refuses at delivery, so the framework retained the
17
17
  * ability to generate work it would then reject; it had no production caller
18
- * (`/plan` persists through the bare `createIssue` below).
18
+ * (`/mandrel-plan` persists through the bare `createIssue` below).
19
19
  *
20
20
  * Public surface: `GitHubProvider.createIssue / getTicket /
21
21
  * getTickets / updateTicket / getTicketDependencies / primeTicketCache /
@@ -251,8 +251,8 @@ export class TicketGateway {
251
251
 
252
252
  /**
253
253
  * Create a **bare** issue — no footer composition and no sub-issue link.
254
- * Since Story #4545 this is the *only* create path: `/plan` persist and the
255
- * `/plan` Phase 4 Epic open (`openEpicFromOnePager`'s `createIssue` port)
254
+ * Since Story #4545 this is the *only* create path: `/mandrel-plan` persist and the
255
+ * `/mandrel-plan` Phase 4 Epic open (`openEpicFromOnePager`'s `createIssue` port)
256
256
  * both route through it.
257
257
  *
258
258
  * After the POST, the new issue is added to the configured Project V2
@@ -264,13 +264,13 @@ export class TicketGateway {
264
264
  * The POST is wrapped in `withTransientRetry` (Story #4541) so it absorbs
265
265
  * the same 502/429/ECONNRESET blips the read surfaces already do. It used
266
266
  * to post bare, which made a single transient failure at story *k* of *N*
267
- * abort `/plan` persist with `1..k-1` already live on the tracker.
267
+ * abort `/mandrel-plan` persist with `1..k-1` already live on the tracker.
268
268
  *
269
269
  * Retry alone is not sufficient for that failure mode — a POST whose
270
270
  * response is lost would double-create on the retry. Story #5112 closed
271
271
  * that: `findExisting` is consulted **before every retry POST**, and a hit
272
272
  * is adopted instead of re-created, so a response-lost first attempt files
273
- * exactly one issue. `/plan` persist supplies the plan-fingerprint lookup
273
+ * exactly one issue. `/mandrel-plan` persist supplies the plan-fingerprint lookup
274
274
  * its resume path already uses (`plan-persist`'s `createStoryIssues`);
275
275
  * callers with no content identity omit it and keep the pre-#5112
276
276
  * retry-only behaviour.
@@ -405,7 +405,7 @@ export class TicketGateway {
405
405
  *
406
406
  * The additive POST goes through `withTransientRetry` (Story #4961) on the
407
407
  * same policy as every other call in this file. Story #4952 raised the
408
- * `/plan` write loops that reach this endpoint — the `agent::ready` flips
408
+ * `/mandrel-plan` write loops that reach this endpoint — the `agent::ready` flips
409
409
  * and the checkpoint fan-out — off serial, which is precisely what makes
410
410
  * GitHub's secondary rate limit likelier; `gh-exec` already classifies that
411
411
  * as transient, so the only thing missing was a backoff behind it.
@@ -12,9 +12,18 @@
12
12
  * 2. `npm run quality:watch` — chokidar wrapper re-emits on save.
13
13
  * 3. `.husky/pre-commit` — block the commit on threshold violations.
14
14
  *
15
- * Story #1394 (Epic #1386) flipped the default scope of both gates to
16
- * diff-against-`main`, so passing `--changed-since HEAD` here mirrors what the
17
- * pre-commit hook actually wants: the delta the operator is about to commit.
15
+ * The pre-commit hook passes `--staged` and nothing else the index is
16
+ * already the exact delta the operator is about to commit, and
17
+ * `tests/pre-commit-hook.test.js` pins that `--changed-since` stays off it.
18
+ * (This docblock previously claimed the hook passed `--changed-since HEAD`,
19
+ * describing a wiring the hook has not used for some time; the stale prose
20
+ * sent at least one bug report at the wrong flag — Story #5131.)
21
+ *
22
+ * `--staged` is merge-aware: while a merge is in progress the index is read
23
+ * against `MERGE_HEAD` rather than `HEAD`, so a base-sync merge commit is
24
+ * scored for the merging branch's own work and its conflict resolutions, not
25
+ * for everything the base branch landed. See `resolveMergeHead` in
26
+ * `lib/changed-files.js`.
18
27
  *
19
28
  * The CLI exits 0 when both envelopes report zero violations and the script
20
29
  * could not surface a regression. Any violation in either envelope, or any
@@ -29,6 +38,7 @@ import {
29
38
  runCrapPreview,
30
39
  runMaintainabilityPreview,
31
40
  } from './lib/baselines/preview-gates.js';
41
+ import { resolveMergeHead } from './lib/changed-files.js';
32
42
  import { respondToHelp } from './lib/cli-usage.js';
33
43
  import { getQuality, resolveConfig } from './lib/config-resolver.js';
34
44
  import { resolveCyclomaticPolicy } from './lib/cyclomatic-ceiling.js';
@@ -39,7 +49,10 @@ const USAGE = {
39
49
  summary:
40
50
  'Preview the per-file maintainability and CRAP deltas for the change set, and exit non-zero on any threshold violation.',
41
51
  flags: [
42
- ['--staged', 'Score the git index only (the pre-commit-hook scope).'],
52
+ [
53
+ '--staged',
54
+ 'Score the git index only (the pre-commit-hook scope). During a merge the index is read against MERGE_HEAD.',
55
+ ],
43
56
  [
44
57
  '--changed-since <ref>',
45
58
  'Score the diff against <ref> (default: HEAD). Last occurrence wins.',
@@ -388,6 +401,35 @@ function runGateSafely(runner, args, label, stderr) {
388
401
  });
389
402
  }
390
403
 
404
+ /**
405
+ * Render the scope header line.
406
+ *
407
+ * Story #5131 — when `--staged` runs during a merge the scope is re-based to
408
+ * `MERGE_HEAD`, and the header says so. Without that line the operator sees a
409
+ * table whose row count does not match `git diff --cached` with no way to tell
410
+ * the narrowing was deliberate.
411
+ *
412
+ * The merge state is resolved here rather than read back off a gate envelope's
413
+ * `summary.diffRef`: that field means "the ref this scope was resolved
414
+ * against" for every scope kind, so anything that populates it — a future
415
+ * scope mode, a test stub — would render a merge banner over a repo that is
416
+ * not merging. Resolving it after the `!staged` early return also keeps the
417
+ * probe off the `--changed-since` path, which has no use for it.
418
+ *
419
+ * @param {{ staged: boolean, ref: string|null, cwd: string }} args
420
+ * @returns {string}
421
+ */
422
+ function stagedScopeLine({ staged, ref, cwd }) {
423
+ if (!staged) return `scope=diff ref=${ref}\n\n`;
424
+ const mergeHead = resolveMergeHead({ cwd });
425
+ if (!mergeHead) return 'scope=staged (git diff --cached)\n\n';
426
+ return (
427
+ `scope=staged (git diff --cached ${mergeHead.slice(0, 12)}) — merge in ` +
428
+ "progress: scored against MERGE_HEAD, not HEAD, so the base branch's " +
429
+ 'incoming files are excluded\n\n'
430
+ );
431
+ }
432
+
391
433
  /**
392
434
  * Write the run's report — the `--json` envelope, or the human-readable
393
435
  * table plus any gate diagnostics and the non-zero-exit summary.
@@ -401,6 +443,7 @@ function runGateSafely(runner, args, label, stderr) {
401
443
  * json: boolean,
402
444
  * staged: boolean,
403
445
  * ref: string|null,
446
+ * cwd: string,
404
447
  * miResult: {exitCode: number, envelope: object|null},
405
448
  * crapResult: {exitCode: number, envelope: object|null},
406
449
  * merged: ReturnType<typeof mergeEnvelopes>,
@@ -413,6 +456,7 @@ function emitReport({
413
456
  json,
414
457
  staged,
415
458
  ref,
459
+ cwd,
416
460
  miResult,
417
461
  crapResult,
418
462
  merged,
@@ -438,11 +482,7 @@ function emitReport({
438
482
  return;
439
483
  }
440
484
  stdout.write('\n--- quality:preview ---\n');
441
- stdout.write(
442
- staged
443
- ? 'scope=staged (git diff --cached)\n\n'
444
- : `scope=diff ref=${ref}\n\n`,
445
- );
485
+ stdout.write(stagedScopeLine({ staged, ref, cwd }));
446
486
  stdout.write(`${renderTable(merged)}\n`);
447
487
  const diagnostics = renderDiagnostics([miResult, crapResult]);
448
488
  if (diagnostics) stdout.write(`\n${diagnostics}\n`);
@@ -518,6 +558,7 @@ export async function runCli({
518
558
  json,
519
559
  staged,
520
560
  ref,
561
+ cwd,
521
562
  miResult,
522
563
  crapResult,
523
564
  merged,
@@ -2,9 +2,9 @@
2
2
 
3
3
  /**
4
4
  * resolve-stories.js — resolve a list of Story ids into the
5
- * `{ stories, dag, done }` envelope `/deliver` sequences from.
5
+ * `{ stories, dag, done }` envelope `/mandrel-deliver` sequences from.
6
6
  *
7
- * This is the ONE resolution step for multi-Story delivery. `/deliver` takes
7
+ * This is the ONE resolution step for multi-Story delivery. `/mandrel-deliver` takes
8
8
  * only Story ids; the graph is discovered here, from live state, rather than
9
9
  * hand-transcribed by the host or implied by a batch label.
10
10
  *
@@ -63,7 +63,7 @@ const HELP = `\
63
63
  Usage:
64
64
  resolve-stories.js --ids <n,n,...> [--pretty] [--no-native]
65
65
 
66
- Resolve Story ids into the { stories, dag, done } envelope /deliver sequences
66
+ Resolve Story ids into the { stories, dag, done } envelope /mandrel-deliver sequences
67
67
  from. Dependencies are discovered from live state: body edges union native
68
68
  blocked_by edges, with every blocker (in-set or foreign) resolved against its
69
69
  real issue state.
@@ -5,7 +5,7 @@
5
5
  * resync-status-column.js — re-assert the GitHub Projects v2 Status
6
6
  * column for a ticket after auto-merge has fired (Story #2845).
7
7
  *
8
- * The `/single-story-deliver` and `/deliver` workflow docs call
8
+ * The `/single-story-deliver` and `/mandrel-deliver` workflow docs call
9
9
  * this CLI after Step 5 confirms `state: "MERGED"` so the orchestrator
10
10
  * wins the race against the GitHub built-in `Pull request merged`
11
11
  * workflow, which would otherwise overwrite Status to whatever value
@@ -84,7 +84,7 @@ const tasks = [
84
84
  nodeGate('lifecycle-lint', '.agents/scripts/check-lifecycle-lint.js'),
85
85
  // Workflow prose surface (Epic #4474 PR5). No workflow may instruct calling
86
86
  // an exported library function that has no CLI entrypoint — the measured
87
- // shim-writing failure mode the /plan collapse killed. See
87
+ // shim-writing failure mode the /mandrel-plan collapse killed. See
88
88
  // check-workflow-cli-lint.js for the paragraph-level heuristic.
89
89
  nodeGate('workflow-cli-lint', '.agents/scripts/check-workflow-cli-lint.js'),
90
90
  // Label-vocabulary citations in `.agents/docs/SDLC.md` and
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * single-story-close.js — Close a Story against `main` (v2 `/deliver` path).
4
+ * single-story-close.js — Close a Story against `main` (v2 `/mandrel-deliver` path).
5
5
  *
6
- * Thin CLI entry for `/deliver` / `helpers/deliver-story`. Opens a PR from
6
+ * Thin CLI entry for `/mandrel-deliver` / `helpers/deliver-story`. Opens a PR from
7
7
  * `story-<id>` to `project.baseBranch`, runs Story-scope review, and arms
8
8
  * auto-merge. There is no Epic parent, epic-merge-lock, or wave merge.
9
9
  *
@@ -2,7 +2,7 @@
2
2
  /* node:coverage ignore file */
3
3
 
4
4
  /**
5
- * single-story-init.js — Initialize a Story for v2 `/deliver`.
5
+ * single-story-init.js — Initialize a Story for v2 `/mandrel-deliver`.
6
6
  *
7
7
  * Seeds `story-<id>` from `project.baseBranch` (default `main`), materialises
8
8
  * the per-Story worktree when isolation is enabled, upserts a `story-init`
@@ -122,7 +122,7 @@ export function assertDeliverableStory(story, storyId) {
122
122
  if (!story.labels.includes(TYPE_LABELS.STORY)) {
123
123
  throw new Error(
124
124
  `Issue #${storyId} is not a Story (labels: ${story.labels.join(', ')}). ` +
125
- 'v2 /deliver accepts type::story tickets only.',
125
+ 'v2 /mandrel-deliver accepts type::story tickets only.',
126
126
  );
127
127
  }
128
128
  if (story.state === 'closed') {
@@ -132,7 +132,7 @@ export function assertDeliverableStory(story, storyId) {
132
132
  if (/\b(?:Epic|Parent):\s*#\d+/i.test(body)) {
133
133
  throw new Error(
134
134
  `Story #${storyId} still declares an Epic/Parent footer. ` +
135
- 'v2 delivery is Story-only — re-plan as a standalone Story before /deliver.',
135
+ 'v2 delivery is Story-only — re-plan as a standalone Story before /mandrel-deliver.',
136
136
  );
137
137
  }
138
138
  }
@@ -189,7 +189,7 @@ export async function assertNotForeignExecuting({
189
189
  (lease.previousOwner
190
190
  ? ` (assignee @${lease.previousOwner})`
191
191
  : ' with no assignee') +
192
- '. Another /deliver run may already own it. Confirm that run is dead, ' +
192
+ '. Another /mandrel-deliver run may already own it. Confirm that run is dead, ' +
193
193
  'then re-run with --steal to take it.',
194
194
  );
195
195
  }
@@ -607,7 +607,7 @@ export async function runSingleStoryInit({
607
607
  );
608
608
  progress('INIT', `Initializing standalone Story #${storyId}...`);
609
609
 
610
- // Issue #4483 — deterministic remote evidence at the v2 `/deliver` entry
610
+ // Issue #4483 — deterministic remote evidence at the v2 `/mandrel-deliver` entry
611
611
  // seam (`single-story-init.js`). The
612
612
  // probe is read-only, so it runs under --dry-run too. The CLI records
613
613
  // the fact; the workflow owns the `agent::blocked` transition on
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * stories-wave-tick.js — continuous ready-set planner for the standalone
5
- * `/deliver` story-list path.
5
+ * `/mandrel-deliver` story-list path.
6
6
  *
7
7
  * Thin **adapter** over the path-agnostic ready-set scheduling core
8
8
  * (`lib/wave-runner/ready-set.js#planReadySet`). It emits the set of
@@ -21,7 +21,7 @@
21
21
  * **Two modes, one kernel.**
22
22
  *
23
23
  * - **Probe mode** (`--stories <csv> --probe-live [--dispatched <csv>]`) is
24
- * the canonical `/deliver` beat: the graph, the done set, and the in-flight
24
+ * the canonical `/mandrel-deliver` beat: the graph, the done set, and the in-flight
25
25
  * count are resolved from **live state** via `lib/wave-runner/live-probe.js`.
26
26
  * The caller supplies ids, so there is no accounting to hand-maintain
27
27
  * across beats — the seed-the-first-beat's-`--done` footgun the workflow
@@ -97,14 +97,14 @@
97
97
  * `--done` and `ready` is empty.
98
98
  *
99
99
  * The per-beat concurrency cap is resolved from the same config seam
100
- * `/deliver` uses — `resolveConfig` + `getRunners` reading
100
+ * `/mandrel-deliver` uses — `resolveConfig` + `getRunners` reading
101
101
  * `delivery.deliverRunner.concurrencyCap` (default 3) — so a
102
102
  * `.agentrc.local.json` override is honored. A `--concurrency <n>` CLI flag
103
103
  * overrides the config-resolved value for that run only, and the envelope's
104
104
  * `capPrecedence` names which source won so the override is never silent
105
105
  * (Story #4875). This shares one
106
106
  * deterministic config source (`delivery.deliverRunner.concurrencyCap`) and
107
- * one scheduling kernel with every `/deliver` multi-Story invocation.
107
+ * one scheduling kernel with every `/mandrel-deliver` multi-Story invocation.
108
108
  *
109
109
  * Exit codes: 0 ok · 1 input error · 2 dependency cycle (`cycleError`) ·
110
110
  * 3 wedged (`wedged`) — ready is empty, nothing is in flight, and undone
@@ -170,7 +170,7 @@ and the same file-overlap guard as planReadySet.
170
170
 
171
171
  Two modes:
172
172
  --probe-live Resolve the graph and derive done / in-flight from LIVE state
173
- (the canonical /deliver beat). Nothing is hand-maintained
173
+ (the canonical /mandrel-deliver beat). Nothing is hand-maintained
174
174
  across beats. Mutually exclusive with --dag/--dag-file/--done/
175
175
  --in-flight. Adds "done" and "epilogueDue" to the envelope.
176
176
  --dag Legacy flag mode: the caller supplies the graph and the run
@@ -662,7 +662,7 @@ export function parseConcurrencyOverride(raw) {
662
662
  /**
663
663
  * Resolve the per-beat concurrency cap.
664
664
  *
665
- * Mirrors the `/deliver` multi-Story seam (`helpers/deliver-story.md`): resolve the
665
+ * Mirrors the `/mandrel-deliver` multi-Story seam (`helpers/deliver-story.md`): resolve the
666
666
  * project config (which deep-merges `.agentrc.local.json` over `.agentrc.json`)
667
667
  * then read `delivery.deliverRunner.concurrencyCap` via `getRunners` (default
668
668
  * 3). An explicit `override` (the `--concurrency <n>` CLI flag) wins over
@@ -855,7 +855,7 @@ export function buildReadySetEnvelope(
855
855
  return {
856
856
  envelope: {
857
857
  ...base,
858
- cycleError: `Dependency cycle detected: ${cycle.join(' → ')}. Fix the depends_on declarations before running /deliver.`,
858
+ cycleError: `Dependency cycle detected: ${cycle.join(' → ')}. Fix the depends_on declarations before running /mandrel-deliver.`,
859
859
  },
860
860
  exitCode: 2,
861
861
  };
@@ -1087,7 +1087,7 @@ export function runStoriesWaveTick({
1087
1087
  *
1088
1088
  * This is the flag-free beat. The caller supplies only the Story ids it was
1089
1089
  * asked to deliver; `done` and `inFlight` are probed rather than transcribed,
1090
- * which is what makes the `/deliver` loop's old seed-the-first-beat footgun
1090
+ * which is what makes the `/mandrel-deliver` loop's old seed-the-first-beat footgun
1091
1091
  * structurally impossible instead of merely documented.
1092
1092
  *
1093
1093
  * The envelope is the flag mode's, plus three probe-only fields the caller can
@@ -1,11 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  // .agents/scripts/validate-skills.js
3
3
  //
4
- // Walk `.agents/skills/{core,stack}/**/SKILL.md` via the shared parser
4
+ // Walk `SKILL.md` under both skills roots — the package payload
5
+ // (`.agents/skills/{core,stack}/`) and the consumer-writable local zone
6
+ // (`.agents/local/skills/{core,stack}/`, Story #5135) — via the shared parser
5
7
  // helper, validate each frontmatter block against
6
8
  // `.agents/schemas/skill.schema.json`, enforce Policy Capsule presence
7
- // (5–12 bullets), and verify membership in `.agents/skills/skills.index.json`
8
- // when that manifest exists. All findings are batched into a single
9
+ // (5–12 bullets), and verify membership in each root's own manifest when it
10
+ // exists. A consumer-authored skill is held to exactly the same bar as a
11
+ // shipped one; the roots are validated separately because each carries its
12
+ // own index (the shipped manifest is a payload file and must stay
13
+ // payload-only — see generate-skills-index.js). All findings are batched into a single
9
14
  // human-readable report; the process exits non-zero when any finding is
10
15
  // surfaced.
11
16
  //
@@ -30,7 +35,17 @@ import { parseStandardCliArgs } from './lib/cli/standard-args.js';
30
35
  import { runAsCli } from './lib/cli-utils.js';
31
36
  import { Logger } from './lib/Logger.js';
32
37
  import { parseSkill } from './lib/skills/parse-skill.js';
33
- import { collectSkillFiles } from './lib/skills/walk-skill-files.js';
38
+ import {
39
+ auditIndex,
40
+ indexPathFor,
41
+ readIndexPaths,
42
+ } from './lib/skills/skills-index.js';
43
+ import {
44
+ collectLocalSkillFiles,
45
+ collectSkillFiles,
46
+ LOCAL_SKILLS_SEGMENTS,
47
+ PAYLOAD_SKILLS_SEGMENTS,
48
+ } from './lib/skills/walk-skill-files.js';
34
49
 
35
50
  const MIN_CAPSULE_BULLETS = 5;
36
51
  const MAX_CAPSULE_BULLETS = 12;
@@ -118,56 +133,11 @@ function buildManifestValidator(repoRoot) {
118
133
  }
119
134
 
120
135
  /**
121
- * Read the on-disk skills.index.json manifest, returning `{ exists, paths,
122
- * manifest }` where `paths` is the Set of `entry.path` values when the file
123
- * is present and parseable, or null otherwise.
136
+ * Read one root's manifest into the `{ exists, paths, manifest, indexPath }`
137
+ * shape the findings pass consumes.
124
138
  */
125
- function readIndex(repoRoot) {
126
- const indexPath = path.join(
127
- repoRoot,
128
- '.agents',
129
- 'skills',
130
- 'skills.index.json',
131
- );
132
- if (!fs.existsSync(indexPath)) {
133
- return { exists: false, paths: null, manifest: null, indexPath };
134
- }
135
- try {
136
- const manifest = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
137
- const paths = new Set(
138
- Array.isArray(manifest.skills)
139
- ? manifest.skills
140
- .map((s) => s.path)
141
- .filter((p) => typeof p === 'string')
142
- : [],
143
- );
144
- return { exists: true, paths, manifest, indexPath };
145
- } catch (err) {
146
- return {
147
- exists: true,
148
- paths: null,
149
- manifest: null,
150
- indexPath,
151
- parseError: err.message,
152
- };
153
- }
154
- }
155
-
156
- /**
157
- * Validate a parsed manifest against skills-index.schema.json. Returns
158
- * finding strings tagged with the `manifest-schema` pillar.
159
- */
160
- function validateManifestSchema(manifest, indexRelPath, validateManifest) {
161
- const findings = [];
162
- if (!validateManifest(manifest)) {
163
- for (const err of validateManifest.errors ?? []) {
164
- const where = err.instancePath || '(root)';
165
- findings.push(
166
- `${indexRelPath}: manifest-schema: schema violation at ${where}: ${err.message}`,
167
- );
168
- }
169
- }
170
- return findings;
139
+ function readIndex(repoRoot, rootSegments = PAYLOAD_SKILLS_SEGMENTS) {
140
+ return readIndexPaths(indexPathFor(repoRoot, rootSegments));
171
141
  }
172
142
 
173
143
  /**
@@ -216,6 +186,7 @@ function rel(absPath, repoRoot) {
216
186
  /**
217
187
  * Pure entry point used by tests. Returns `{ status, output, findings }`.
218
188
  */
189
+
219
190
  export function run({ argv = [], repoRoot } = {}) {
220
191
  const parsed = parseArgs(argv);
221
192
  if (parsed.help) {
@@ -234,25 +205,41 @@ export function run({ argv = [], repoRoot } = {}) {
234
205
  const indexRel = rel(indexInfo.indexPath, root);
235
206
 
236
207
  const findings = [];
237
- if (!indexInfo.exists) {
238
- findings.push(
239
- `index missing: ${indexRel} not found — run 'node .agents/scripts/generate-skills-index.js'`,
240
- );
241
- } else if (indexInfo.paths === null) {
242
- findings.push(`index unparseable: ${indexRel} — ${indexInfo.parseError}`);
243
- } else if (indexInfo.manifest !== null) {
244
- findings.push(
245
- ...validateManifestSchema(indexInfo.manifest, indexRel, validateManifest),
246
- );
247
- }
208
+ findings.push(
209
+ ...auditIndex(indexInfo, indexRel, validateManifest, { required: true }),
210
+ );
248
211
 
249
- const skillFiles = collectSkillFiles(root);
212
+ const payloadFiles = collectSkillFiles(root);
250
213
  const indexPaths =
251
214
  indexInfo.exists && indexInfo.paths !== null ? indexInfo.paths : null;
252
- for (const file of skillFiles) {
215
+ for (const file of payloadFiles) {
253
216
  findings.push(...validateOne(file, root, validateFrontmatter, indexPaths));
254
217
  }
255
218
 
219
+ // The local zone is optional: a repo with no consumer-authored skills has
220
+ // no local root and no local index, and that is a clean run, not a finding.
221
+ const localFiles = collectLocalSkillFiles(root);
222
+ if (localFiles.length > 0) {
223
+ const localIndexInfo = readIndex(root, LOCAL_SKILLS_SEGMENTS);
224
+ const localIndexRel = rel(localIndexInfo.indexPath, root);
225
+ findings.push(
226
+ ...auditIndex(localIndexInfo, localIndexRel, validateManifest, {
227
+ required: true,
228
+ }),
229
+ );
230
+ const localIndexPaths =
231
+ localIndexInfo.exists && localIndexInfo.paths !== null
232
+ ? localIndexInfo.paths
233
+ : null;
234
+ for (const file of localFiles) {
235
+ findings.push(
236
+ ...validateOne(file, root, validateFrontmatter, localIndexPaths),
237
+ );
238
+ }
239
+ }
240
+
241
+ const skillFiles = [...payloadFiles, ...localFiles];
242
+
256
243
  if (findings.length === 0) {
257
244
  Logger.info(`validate-skills: ${skillFiles.length} skill(s) passed`);
258
245
  return { status: 0, output: '', findings };
@@ -13,7 +13,7 @@ description:
13
13
  - Phase 1 MUST restate the idea as a "How Might We" statement, ask 3–5 sharpening questions via `AskUserQuestion`, and generate 5–8 variations (never 20+ shallow ones) — each carrying a reason it exists, told as a short story, not a bare bullet. Do not proceed until target user and success criteria are explicit.
14
14
  - Phase 2 grill loop poses **one** question at a time, each with a recommended answer + one-line rationale grounded in user input / codebase / first principles; never batch questions and never omit the recommendation.
15
15
  - Re-enumerate open branches after every grill answer; stop only when no unresolved decisions remain. Take the off-ramp directly to Phase 3 when the idea is already crisply scoped. A branch consciously deferred rather than resolved records its deferral reason.
16
- - Phase 3 emits a markdown one-pager with the canonical five planning headings exactly: `## Context`, `## Goal`, `## Non-Goals`, `## Scope`, `## Acceptance Criteria` (plus optional `## Open Questions`). No alternate heading text — the `/plan` clarity gate depends on this verbatim.
16
+ - Phase 3 emits a markdown one-pager with the canonical five planning headings exactly: `## Context`, `## Goal`, `## Non-Goals`, `## Scope`, `## Acceptance Criteria` (plus optional `## Open Questions`). No alternate heading text — the `/mandrel-plan` clarity gate depends on this verbatim.
17
17
  - Surface every key assumption inside `## Context` (or `## Scope`); assumptions do not get their own heading. Unresolved decisions MUST NOT carry into the one-pager.
18
18
  - The `## Non-Goals` list is mandatory and each entry includes a reason — focus is created by explicit exclusion.
19
19
  - Be honest, not supportive: push back on weak ideas with kindness; never function as a yes-machine.
@@ -22,10 +22,10 @@ description:
22
22
 
23
23
  ## Activation
24
24
 
25
- Called from [`/plan`](../../../workflows/plan.md) during ideation when the
25
+ Called from [`/mandrel-plan`](../../../workflows/mandrel-plan.md) during ideation when the
26
26
  operator supplies `--seed "<text>"` (or runs ideation with no seed and the host
27
27
  collects one interactively). The skill sharpens freeform intent into the
28
- canonical planning sections that `/plan` then folds into a Story. There is no
28
+ canonical planning sections that `/mandrel-plan` then folds into a Story. There is no
29
29
  separate Epic Clarity Gate path in v2 — N=1 Story authoring with a folded
30
30
  `## Spec` is the lean default.
31
31
 
@@ -134,9 +134,9 @@ clothes — inside the grill loop, not after the one-pager is written.
134
134
  #### Phase 3: Sharpen & Ship
135
135
 
136
136
  Produce a markdown one-pager that moves work forward. The five canonical
137
- headings below match `.agents/templates/epic-from-idea.md` and the `/plan`
137
+ headings below match `.agents/templates/epic-from-idea.md` and the `/mandrel-plan`
138
138
  clarity gate; emit them verbatim so the renderer can substitute the body into a
139
- `/plan` Story seed without translation.
139
+ `/mandrel-plan` Story seed without translation.
140
140
 
141
141
  ```markdown
142
142
  # [Idea Name]
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: scope-triage
3
3
  description:
4
- Optional split-advisory for `/plan`. Under v2 there is no epic|story routing
5
- verdict — `/plan` always authors Stories. Use this skill only when judging
4
+ Optional split-advisory for `/mandrel-plan`. Under v2 there is no epic|story routing
5
+ verdict — `/mandrel-plan` always authors Stories. Use this skill only when judging
6
6
  whether a draft should stay one Story or legitimately split (near-zero
7
7
  overlap or an architectural seam).
8
8
  ---
@@ -11,7 +11,7 @@ description:
11
11
 
12
12
  ## Policy Capsule
13
13
 
14
- - There is **no** `epic | story | borderline` routing verdict in v2. `/plan`
14
+ - There is **no** `epic | story | borderline` routing verdict in v2. `/mandrel-plan`
15
15
  is a single path that emits **one Story by default**.
16
16
  - This skill is an optional **split advisory**: should the author keep one
17
17
  Story, or does the seed clear the default-single split policy?
@@ -97,7 +97,7 @@
97
97
  "tier": "core",
98
98
  "category": "core",
99
99
  "path": ".agents/skills/core/scope-triage/SKILL.md",
100
- "description": "Optional split-advisory for `/plan`. Under v2 there is no epic|story routing verdict — `/plan` always authors Stories. Use this skill only when judging whether a draft should stay one Story or legitimately split (near-zero overlap or an architectural seam).",
100
+ "description": "Optional split-advisory for `/mandrel-plan`. Under v2 there is no epic|story routing verdict — `/mandrel-plan` always authors Stories. Use this skill only when judging whether a draft should stay one Story or legitimately split (near-zero overlap or an architectural seam).",
101
101
  "policyCapsuleBullets": 5,
102
102
  "allowedTools": null,
103
103
  "vendor": null
@@ -170,7 +170,7 @@ Record each `F#` finding as a `QaLedgerItem` on the shared session ledger under
170
170
  `temp/qa/`, then route the ledger through the shared classify → route →
171
171
  disposition → promote core — both stated once in
172
172
  [`qa-core.md`](../../../../workflows/helpers/qa-core.md). The harness **MUST
173
- NOT** create tickets autonomously: findings are promoted through `/plan` only
173
+ NOT** create tickets autonomously: findings are promoted through `/mandrel-plan` only
174
174
  after the operator confirms each disposition at the HITL write gate. That gate
175
175
  is the safety boundary against spurious filing.
176
176
 
@@ -27,7 +27,7 @@ than empty findings. The applicability probe (`hasPersistenceLayer` in
27
27
  [`lib/audit-suite/selector.js`](../scripts/lib/audit-suite/selector.js), gated
28
28
  by `target: "data-model"` in
29
29
  [`schemas/audit-rules.json`](../schemas/audit-rules.json)) makes this decision
30
- automatically in `/deliver` and plan-run modes; in a manual invocation you MUST
30
+ automatically in `/mandrel-deliver` and plan-run modes; in a manual invocation you MUST
31
31
  make the same determination yourself before reading anything else.
32
32
 
33
33
  ## Scope
@@ -188,7 +188,7 @@ auditable.
188
188
  ## Periodic full-scope sweep
189
189
 
190
190
  Context Economy findings accrete slowly — a doc that is lean today grows a
191
- bloated tail over many deliveries, and no single change-set-scoped `/deliver`
191
+ bloated tail over many deliveries, and no single change-set-scoped `/mandrel-deliver`
192
192
  run sees the whole picture. Run this lens **full-scope** on a
193
193
  recurring cadence so the drift is caught before it compounds:
194
194
 
@@ -202,7 +202,7 @@ recurring cadence so the drift is caught before it compounds:
202
202
  Route the resulting `audit-documentation-results.md` through
203
203
  [`/audit-to-stories`](audit-to-stories.md), which groups the findings,
204
204
  deduplicates them against existing Issues by fingerprint, and opens
205
- remediation Stories (or chains into `/plan --seed`) so the Context-Economy
205
+ remediation Stories (or chains into `/mandrel-plan --seed`) so the Context-Economy
206
206
  findings land as actionable, tracked work rather than a report nobody reads.
207
207
 
208
208
  ## Constraint (lens-specific carve-out)