mandrel 2.30.0 → 2.32.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 (253) hide show
  1. package/.agents/README.md +13 -17
  2. package/.agents/agents/acceptance-critic.md +1 -2
  3. package/.agents/docs/SDLC.md +4 -4
  4. package/.agents/docs/agentrc-reference.json +61 -57
  5. package/.agents/docs/configuration.md +274 -227
  6. package/.agents/docs/execution-reference.md +13 -14
  7. package/.agents/docs/quality-gates.md +195 -23
  8. package/.agents/instructions.md +2 -5
  9. package/.agents/rules/ci-remediation.md +44 -1
  10. package/.agents/rules/git-conventions-reference.md +27 -27
  11. package/.agents/rules/git-conventions.md +4 -2
  12. package/.agents/rules/known-tooling-behavior.md +66 -30
  13. package/.agents/rules/testing-standards.md +35 -71
  14. package/.agents/runtime-deps.json +0 -1
  15. package/.agents/schemas/agentrc.schema.json +1939 -1400
  16. package/.agents/schemas/lifecycle/README.md +21 -14
  17. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  18. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  19. package/.agents/scripts/README.md +7 -29
  20. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  21. package/.agents/scripts/bootstrap.js +28 -26
  22. package/.agents/scripts/check-baseline-drift.js +73 -13
  23. package/.agents/scripts/check-baseline-scope.js +362 -0
  24. package/.agents/scripts/check-dead-exports.js +9 -1
  25. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  26. package/.agents/scripts/check-knip-entries.js +136 -0
  27. package/.agents/scripts/check-lifecycle-lint.js +36 -112
  28. package/.agents/scripts/check-schema-references.js +1 -1
  29. package/.agents/scripts/diagnose-friction.js +7 -4
  30. package/.agents/scripts/generate-config-docs.js +263 -171
  31. package/.agents/scripts/install-matrix-assert.js +0 -1
  32. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  33. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  34. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  35. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  36. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  37. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
  38. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  39. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  40. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  41. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  42. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  43. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  44. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  45. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  46. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  47. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  48. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  49. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  50. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  51. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  52. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  53. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  54. package/.agents/scripts/lib/child-exec.js +193 -0
  55. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  56. package/.agents/scripts/lib/cli-args.js +64 -0
  57. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  58. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  59. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  60. package/.agents/scripts/lib/config/commands.js +3 -5
  61. package/.agents/scripts/lib/config/explain.js +5 -7
  62. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  63. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  64. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  65. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  66. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  67. package/.agents/scripts/lib/config/gates/index.js +5 -2
  68. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  69. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  70. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  71. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  72. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  73. package/.agents/scripts/lib/config/github.js +0 -1
  74. package/.agents/scripts/lib/config/limits.js +3 -48
  75. package/.agents/scripts/lib/config/qa.js +105 -0
  76. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  77. package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
  78. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  79. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  80. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  81. package/.agents/scripts/lib/crap-utils.js +84 -520
  82. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  83. package/.agents/scripts/lib/degraded-mode.js +2 -2
  84. package/.agents/scripts/lib/doc-tiers.js +3 -3
  85. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  86. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  87. package/.agents/scripts/lib/fs-walk.js +52 -0
  88. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  89. package/.agents/scripts/lib/git-utils.js +16 -36
  90. package/.agents/scripts/lib/knip-entry-sync.js +469 -0
  91. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  92. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  93. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  94. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  95. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  96. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  97. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  98. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  99. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  100. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  101. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  102. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  103. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  104. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  105. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  106. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  107. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
  108. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
  109. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  110. package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
  111. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  112. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  113. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  118. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  119. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  120. package/.agents/scripts/lib/orchestration/single-story-close/phases/push.js +17 -2
  121. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  122. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  123. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  124. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +65 -25
  125. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  126. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  127. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  128. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  129. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  130. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  131. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  132. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  133. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  134. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  135. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  136. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  137. package/.agents/scripts/lib/signals/index.js +8 -6
  138. package/.agents/scripts/lib/signals/schema.js +20 -25
  139. package/.agents/scripts/lib/signals/write.js +8 -8
  140. package/.agents/scripts/lib/story-body/story-body.js +12 -59
  141. package/.agents/scripts/lib/temp-retention.js +1 -1
  142. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  143. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  144. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  145. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  146. package/.agents/scripts/lib/worktree-manager.js +2 -3
  147. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  148. package/.agents/scripts/pr-watch-with-update.js +7 -5
  149. package/.agents/scripts/providers/github/cache.js +2 -2
  150. package/.agents/scripts/providers/github/comments.js +6 -28
  151. package/.agents/scripts/providers/github/compose.js +0 -15
  152. package/.agents/scripts/providers/github/errors.js +10 -27
  153. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  154. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  155. package/.agents/scripts/providers/github.js +4 -7
  156. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  157. package/.agents/scripts/resolve-stories.js +0 -2
  158. package/.agents/scripts/run-lint.js +61 -61
  159. package/.agents/scripts/run-test-profile.js +6 -6
  160. package/.agents/scripts/run-verify.js +48 -30
  161. package/.agents/scripts/single-story-close.js +20 -0
  162. package/.agents/scripts/single-story-init.js +12 -35
  163. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  164. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  165. package/.agents/skills/skills.index.json +2 -12
  166. package/.agents/skills/stack/qa/playwright/SKILL.md +48 -0
  167. package/.agents/workflows/audit-documentation.md +5 -6
  168. package/.agents/workflows/audit-to-stories.md +2 -2
  169. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  170. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  171. package/.agents/workflows/helpers/code-review.md +3 -8
  172. package/.agents/workflows/helpers/deliver-reference.md +2 -1
  173. package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
  174. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  175. package/.agents/workflows/mandrel-update.md +10 -10
  176. package/.agents/workflows/qa-assist.md +15 -20
  177. package/.agents/workflows/qa-explore.md +9 -8
  178. package/README.md +1 -1
  179. package/docs/CHANGELOG.md +49 -0
  180. package/lib/migrations/index.js +2 -0
  181. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  182. package/package.json +12 -3
  183. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  184. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  185. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  186. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  187. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  188. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  189. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  190. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  191. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  192. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  193. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  194. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  195. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  196. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  197. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  198. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  199. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  200. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  201. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  202. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  203. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  204. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  205. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  206. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  207. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  208. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  209. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  210. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  211. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  212. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  213. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  214. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  215. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  216. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  217. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  218. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  219. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  220. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  221. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  222. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  223. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  224. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  225. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  226. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  227. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  228. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  229. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  230. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  231. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  232. package/.agents/scripts/lib/planning-corpus.js +0 -37
  233. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  234. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  235. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  236. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  237. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  238. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  239. package/.agents/scripts/lib/signals/read.js +0 -268
  240. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  241. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  242. package/.agents/scripts/lib/story-plan.js +0 -379
  243. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  244. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  245. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  246. package/.agents/scripts/lint-baseline.js +0 -507
  247. package/.agents/scripts/providers/github/prs.js +0 -103
  248. package/.agents/scripts/signals-view.js +0 -309
  249. package/.agents/scripts/story-plan.js +0 -370
  250. package/.agents/scripts/sync-branch-from-base.js +0 -149
  251. package/.agents/scripts/validate-docs-freshness.js +0 -314
  252. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  253. package/.agents/workflows/helpers/signals.md +0 -112
@@ -1,370 +0,0 @@
1
- #!/usr/bin/env node
2
- /* node:coverage ignore file */
3
-
4
- /**
5
- * story-plan.js — QA helper for promote round-trips and `--emit-context`
6
- * envelope emission.
7
- *
8
- * Canonical operator planning is `plan-context.js` + `plan-persist.js` (see
9
- * `.agents/workflows/plan.md`). Use this CLI only for QA promote
10
- * round-trips (`--body` / `--dry-run`) or test harnesses that need the
11
- * standalone envelope shape.
12
- *
13
- * Operator planning:
14
- * node .agents/scripts/plan-context.js --seed "…" | --seed-file <path> | --tickets <ids>
15
- * node .agents/scripts/plan-persist.js --stories …
16
- */
17
-
18
- import { readFile } from 'node:fs/promises';
19
- import path from 'node:path';
20
- import { parseArgs } from 'node:util';
21
- import { runAsCli } from './lib/cli-utils.js';
22
- import { PROJECT_ROOT, resolveConfig } from './lib/config-resolver.js';
23
- import { exec as ghExec } from './lib/gh-exec.js';
24
- import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
25
- import { TYPE_LABELS } from './lib/label-constants.js';
26
- import { recordPlanInvocation } from './lib/orchestration/plan-metrics.js';
27
- import { buildCorpusContext } from './lib/planning-corpus.js';
28
- import { createProvider } from './lib/provider-factory.js';
29
- import {
30
- buildContextEnvelope,
31
- loadBodyTemplate,
32
- rankDuplicateCandidates,
33
- readTechStackSummary,
34
- shouldRefine,
35
- synthesizeContractSections,
36
- validateStoryBody,
37
- } from './lib/story-plan.js';
38
-
39
- const HELP = `\
40
- Usage:
41
- story-plan.js --emit-context (--seed "<seed>" | --seed-file <file>) \\
42
- [--refine | --no-refine] [--pretty]
43
-
44
- story-plan.js --body <file> [--acceptance <file>] [--verify <file>] \\
45
- [--dry-run]
46
-
47
- story-plan.js --help
48
-
49
- Modes:
50
- --emit-context Build the host-LLM authoring envelope and print it as
51
- JSON on stdout. Use this first; the host LLM authors a
52
- draft body using the envelope and the body template.
53
- --body <file> Persist a pre-authored body. Validates shape (## Goal
54
- and ## Changes present, no Epic: ref, an acceptance +
55
- verify contract) and calls \`gh issue create\` with
56
- type::story.
57
- --acceptance <file>
58
- JSON string[] of the Story's top-level acceptance
59
- criteria. Authored once here — NOT mirrored into the
60
- body by hand; the \`## Acceptance\` section is
61
- synthesized from it, matching the story-author prompt.
62
- --verify <file> JSON string[] of the Story's top-level verify entries,
63
- each ending in a (unit|contract|e2e|validate) tier.
64
- Synthesized into \`## Verify\` the same way.
65
- --dry-run With --body: print the body and the gh argv that would
66
- be invoked, then exit 0. No GitHub mutations.
67
-
68
- Options:
69
- --refine Force the idea-refinement hint on regardless of seed
70
- length. Default heuristic: refine when seed < 200
71
- chars.
72
- --no-refine Force the idea-refinement hint off.
73
- --pretty Pretty-print the JSON envelope.
74
- `;
75
-
76
- /**
77
- * Resolve the seed string from --seed or --seed-file. One of the two
78
- * must be present in --emit-context mode.
79
- */
80
- async function resolveSeed({ seed, seedFile }) {
81
- if (seed && seedFile) {
82
- throw new Error('Pass either --seed or --seed-file, not both.');
83
- }
84
- if (seed) return seed;
85
- if (seedFile) return (await readFile(seedFile, 'utf8')).trim();
86
- throw new Error(
87
- '--emit-context requires --seed "<seed>" or --seed-file <file>.',
88
- );
89
- }
90
-
91
- /**
92
- * Fetch open Stories via the ticketing provider. The github provider
93
- * exposes `listIssuesByLabel({ state, labels })`; other providers may
94
- * not, so the call is guarded.
95
- */
96
- async function fetchOpenStories(provider) {
97
- if (typeof provider.listIssuesByLabel !== 'function') return [];
98
- const issues = await provider.listIssuesByLabel({
99
- state: 'open',
100
- labels: TYPE_LABELS.STORY,
101
- });
102
- return issues.map((issue) => ({
103
- id: issue.number ?? issue.id,
104
- title: issue.title ?? '',
105
- body: issue.body ?? '',
106
- url: issue.html_url ?? issue.url ?? null,
107
- }));
108
- }
109
-
110
- /**
111
- * Render the draft body argv for `gh issue create`. Returns the argv
112
- * array; the caller decides whether to execute it (persist) or print
113
- * it (--dry-run).
114
- */
115
- function renderGhArgv({ title, bodyPath, labels, body = null }) {
116
- // `--body-file` streams the authored file untouched; an inline `--body` is
117
- // used only when persist synthesized sections into it (Story #4874), so the
118
- // file on disk is no longer what gets created.
119
- const source =
120
- typeof body === 'string' ? ['--body', body] : ['--body-file', bodyPath];
121
- const argv = ['issue', 'create', '--title', title, ...source];
122
- for (const label of labels) {
123
- argv.push('--label', label);
124
- }
125
- return argv;
126
- }
127
-
128
- /**
129
- * Extract the H1 title from a body. Falls back to a sensible default
130
- * when the body lacks one (host LLM should always emit `# <title>`).
131
- */
132
- export function extractTitle(body) {
133
- const m = body.match(/^#\s+(.+?)\s*$/m);
134
- return m ? m[1].trim() : 'Untitled standalone Story';
135
- }
136
-
137
- async function runEmitContext({
138
- values,
139
- provider,
140
- projectRoot,
141
- config,
142
- // Injectable stdout port so unit tests can capture the emitted envelope
143
- // without stubbing the process-global stream (mirrors the `runPersist`
144
- // pattern above — raw stdout writes corrupt the `node --test` runner's
145
- // structured report stream).
146
- write = (s) => process.stdout.write(s),
147
- }) {
148
- const seed = await resolveSeed({
149
- seed: values.seed,
150
- seedFile: values['seed-file'],
151
- });
152
- const override = values.refine ? 'on' : values['no-refine'] ? 'off' : null;
153
- const refine = shouldRefine({ seed, override });
154
-
155
- // Corpus lookup uses the raw (un-defaulted) docsContextFiles list, same
156
- // as the `/deliver` per-Epic digest builder: `config.project` fills in
157
- // the framework's default four-file set even when the operator
158
- // configured nothing, so a null-vs-configured distinction requires
159
- // reading `config.raw` directly.
160
- const docsContextFiles = config?.raw?.project?.docsContextFiles ?? [];
161
- // Resolve docsRoot against PROJECT_ROOT (not process.cwd()) so the
162
- // corpus digest reads the project's actual docs directory regardless
163
- // of the directory this CLI happens to be invoked from — matching the
164
- // sibling resolution pattern in
165
- // planning/authoring-context.js.
166
- const docsRoot = path.resolve(
167
- PROJECT_ROOT,
168
- config?.project?.paths?.docsRoot ?? 'docs',
169
- );
170
-
171
- const [bodyTemplate, openStories, techStack, corpusContext] =
172
- await Promise.all([
173
- loadBodyTemplate(projectRoot),
174
- fetchOpenStories(provider),
175
- readTechStackSummary(projectRoot),
176
- buildCorpusContext({ docsContextFiles, docsRoot }),
177
- ]);
178
-
179
- const duplicateCandidates = rankDuplicateCandidates({
180
- seed,
181
- openStories,
182
- });
183
-
184
- const envelope = buildContextEnvelope({
185
- seed,
186
- refine,
187
- bodyTemplate,
188
- duplicateCandidates,
189
- techStack,
190
- corpusContext,
191
- });
192
-
193
- const json = values.pretty
194
- ? JSON.stringify(envelope, null, 2)
195
- : JSON.stringify(envelope);
196
- write(`${json}\n`);
197
- }
198
-
199
- /**
200
- * Read an optional top-level contract array (`--acceptance` / `--verify`)
201
- * from a JSON file. Absent flag → an empty list, so the flags stay optional
202
- * for a body that already carries its own sections.
203
- *
204
- * @param {string|undefined} filePath
205
- * @param {'acceptance'|'verify'} label
206
- * @returns {Promise<string[]>}
207
- */
208
- async function readContractList(filePath, label) {
209
- if (!filePath) return [];
210
- let parsed;
211
- try {
212
- parsed = JSON.parse(await readFile(filePath, 'utf8'));
213
- } catch (err) {
214
- throw new Error(
215
- `Cannot read --${label} file "${filePath}": ${err.message}`,
216
- );
217
- }
218
- if (!Array.isArray(parsed) || parsed.some((v) => typeof v !== 'string')) {
219
- throw new Error(`--${label} file "${filePath}" must be a JSON string[].`);
220
- }
221
- return parsed;
222
- }
223
-
224
- async function runPersist({
225
- values,
226
- provider,
227
- dryRun,
228
- // Injectable stdout port so unit tests can capture/silence the summary
229
- // JSON without stubbing the process-global stream (raw stdout writes
230
- // corrupt the `node --test` runner's structured report stream).
231
- write = (s) => process.stdout.write(s),
232
- }) {
233
- const bodyPath = values.body;
234
- if (!bodyPath) {
235
- throw new Error('--body <file> is required in persist mode.');
236
- }
237
- const authored = await readFile(bodyPath, 'utf8');
238
- const contract = {
239
- acceptance: await readContractList(values.acceptance, 'acceptance'),
240
- verify: await readContractList(values.verify, 'verify'),
241
- };
242
- const validation = validateStoryBody(authored, contract);
243
- if (!validation.ok) {
244
- throw new Error(
245
- `Drafted body failed validation:\n - ${validation.errors.join('\n - ')}`,
246
- );
247
- }
248
-
249
- // Story #4874: the author writes acceptance[] / verify[] once, at the
250
- // ticket's top level, and persist renders their sections — never the
251
- // author by hand.
252
- const body = synthesizeContractSections(authored, contract);
253
- const title = extractTitle(body);
254
- const labels = [TYPE_LABELS.STORY];
255
- const argv = renderGhArgv({
256
- title,
257
- bodyPath,
258
- labels,
259
- body: body === authored ? null : body,
260
- });
261
-
262
- if (dryRun) {
263
- Logger.info('--- DRY RUN ---');
264
- Logger.info(`Title: ${title}`);
265
- Logger.info(`Labels: ${labels.join(', ')}`);
266
- Logger.info(`gh argv: gh ${argv.join(' ')}`);
267
- Logger.info('--- BODY ---');
268
- Logger.info(body);
269
- write(
270
- `${JSON.stringify({ dryRun: true, title, labels, argv }, null, 2)}\n`,
271
- );
272
- return;
273
- }
274
-
275
- // Persist via the provider when available so I/O stays inside the
276
- // injected ticketing surface. The GitHub provider's `createIssue`
277
- // also adds the new Story to the configured Projects V2 board via
278
- // the shared `addIssueToBoard` helper (Story #3822) — idempotent,
279
- // non-fatal, no-op when no project number is configured — so board
280
- // membership never depends on GitHub's "Auto-add to project"
281
- // built-in workflow. Fall back to `gh issue create` only when the
282
- // provider doesn't expose a createIssue analogue.
283
- let issueNumber;
284
- if (typeof provider.createIssue === 'function') {
285
- const created = await provider.createIssue({ title, body, labels });
286
- issueNumber = created.number ?? created.id;
287
- } else {
288
- // gh-exec returns { stdout, stderr, code }; parse the issue number
289
- // from the URL.
290
- const { stdout } = await ghExec({ args: argv });
291
- const m = stdout.match(/\/issues\/(\d+)/);
292
- if (!m) {
293
- throw new Error(
294
- `gh issue create did not return an issue URL; stdout was: ${stdout}`,
295
- );
296
- }
297
- issueNumber = Number(m[1]);
298
- }
299
-
300
- write(`${JSON.stringify({ issueNumber, title, labels }, null, 2)}\n`);
301
- Logger.info(`Next: /deliver ${issueNumber}`);
302
- }
303
-
304
- /* node:coverage ignore next */
305
- async function main() {
306
- const { values } = parseArgs({
307
- options: {
308
- 'emit-context': { type: 'boolean', default: false },
309
- seed: { type: 'string' },
310
- 'seed-file': { type: 'string' },
311
- body: { type: 'string' },
312
- acceptance: { type: 'string' },
313
- verify: { type: 'string' },
314
- refine: { type: 'boolean', default: false },
315
- 'no-refine': { type: 'boolean', default: false },
316
- pretty: { type: 'boolean', default: false },
317
- 'dry-run': { type: 'boolean', default: false },
318
- help: { type: 'boolean', default: false },
319
- },
320
- });
321
-
322
- if (values.help) {
323
- process.stdout.write(HELP);
324
- return;
325
- }
326
- if (values.refine && values['no-refine']) {
327
- throw new Error('Pass either --refine or --no-refine, not both.');
328
- }
329
-
330
- const config = resolveConfig();
331
- const provider = createProvider(config);
332
- const projectRoot = PROJECT_ROOT;
333
-
334
- if (values['emit-context']) {
335
- // Reserve stdout for the JSON envelope so a captured file is
336
- // unconditionally parseable by `JSON.parse`. Mirrors the contract
337
- // `plan-context.js` enforces for its emit mode.
338
- routeAllOutputToStderr();
339
- // Plan-metrics ledger (#4474 PR1): standalone plans have no Epic, so
340
- // `epicId: null` routes the stamp to the standalone stream
341
- // (`temp/standalone/plan-metrics.json`) — same pattern as friction.
342
- return recordPlanInvocation(
343
- { cli: 'story-plan', mode: 'emit-context', epicId: null, config },
344
- () => runEmitContext({ values, provider, projectRoot, config }),
345
- );
346
- }
347
-
348
- // Plan-metrics ledger (#4474 PR1): stamp entry/exit + mode.
349
- return recordPlanInvocation(
350
- { cli: 'story-plan', mode: 'persist', epicId: null, config },
351
- () =>
352
- runPersist({
353
- values,
354
- provider,
355
- dryRun: values['dry-run'],
356
- }),
357
- );
358
- }
359
-
360
- runAsCli(import.meta.url, main, { source: 'story-plan' });
361
-
362
- // Test surface — exported so unit tests can drive the helpers
363
- // without importing the CLI side.
364
- export {
365
- fetchOpenStories,
366
- renderGhArgv,
367
- resolveSeed,
368
- runEmitContext,
369
- runPersist,
370
- };
@@ -1,149 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * sync-branch-from-base.js — operator-facing CLI wrapper around
5
- * `lib/git/sync-from-base.js` (Story #2580).
6
- *
7
- * Use this from workflow markdown when the operator needs to sync a
8
- * working branch with `origin/<baseBranch>` before opening a PR — for
9
- * example as a pre-Phase-6 step in `/deliver` so the Epic→main PR
10
- * opens with the latest `main` commits already integrated.
11
- *
12
- * For `/single-story-deliver`, the sync runs in-process inside
13
- * `single-story-close.js`; this CLI is the operator surface for
14
- * workflows that don't have a built-in close pipeline.
15
- *
16
- * Usage:
17
- * node .agents/scripts/sync-branch-from-base.js \
18
- * --branch <branchName> --base <baseBranch> [--cwd <path>]
19
- *
20
- * Exit codes:
21
- * 0 — synced (`fast-forward`, `merge-commit`, or `noop-already-current`).
22
- * 1 — fetch/merge failed or conflict; error message on stderr names the
23
- * outcome kind and (for conflicts) the conflicting file list.
24
- *
25
- * The script does NOT post structured comments or flip ticket labels —
26
- * it is a pure git operation. Recovery surfaces are the caller's
27
- * responsibility (the workflow doc tells the operator what to do when
28
- * this exits non-zero).
29
- */
30
-
31
- import path from 'node:path';
32
- import { parseArgs } from 'node:util';
33
- import { runAsCli } from './lib/cli-utils.js';
34
- import { resolveConfig } from './lib/config-resolver.js';
35
- import { syncBranchFromBase } from './lib/git/sync-from-base.js';
36
- import { gitSpawn, gitSync } from './lib/git-utils.js';
37
- import { Logger } from './lib/Logger.js';
38
- import { emitTerseResult } from './lib/observability/terse-result.js';
39
- import { PROJECT_ROOT } from './lib/project-root.js';
40
-
41
- const progress = Logger.createProgress('sync-branch-from-base', {
42
- stderr: true,
43
- });
44
-
45
- /**
46
- * Core runner. Exported for tests so the CLI surface can be exercised
47
- * without spawning a subprocess.
48
- *
49
- * @param {object} [opts]
50
- * @param {string} [opts.branch] Branch name expected to be checked out.
51
- * @param {string} [opts.base] Base branch name on origin (e.g. `main`).
52
- * @param {string} [opts.cwd] Worktree or repo root. Defaults to PROJECT_ROOT.
53
- * @param {typeof syncBranchFromBase} [opts.injectedSync] Test-only seam.
54
- * @param {(cwd: string, ...args: string[]) => string} [opts.injectedGitSync]
55
- * Test-only seam for the branch-name guard.
56
- */
57
- export async function runSyncBranchFromBase(opts = {}) {
58
- const parsed = opts.branch ? opts : parseArgv(process.argv.slice(2));
59
- const branch = opts.branch ?? parsed.branch;
60
- const base = opts.base ?? parsed.base;
61
- if (!branch || !base) {
62
- throw new Error(
63
- 'Usage: node sync-branch-from-base.js --branch <branchName> --base <baseBranch> [--cwd <path>]',
64
- );
65
- }
66
- const cwd = path.resolve(opts.cwd ?? parsed.cwd ?? PROJECT_ROOT);
67
-
68
- // Guard: refuse to sync if the active branch doesn't match the
69
- // operator's intent. A mismatched checkout would merge `origin/<base>`
70
- // into the wrong branch — silently — and only surface as a confusing
71
- // diff later. Skipped when injectedGitSync is set so tests can run
72
- // without a real git repo.
73
- const gitSyncFn = opts.injectedGitSync ?? gitSync;
74
- let activeBranch = null;
75
- try {
76
- activeBranch = gitSyncFn(cwd, 'rev-parse', '--abbrev-ref', 'HEAD');
77
- } catch (err) {
78
- throw new Error(
79
- `[sync-branch-from-base] Could not resolve active branch in ${cwd}: ${err?.message ?? err}`,
80
- );
81
- }
82
- if (activeBranch !== branch) {
83
- throw new Error(
84
- `[sync-branch-from-base] Active branch is "${activeBranch}" but --branch is "${branch}". ` +
85
- `Check out ${branch} first, or pass --cwd to the correct worktree.`,
86
- );
87
- }
88
-
89
- progress('INIT', `Syncing ${branch} from origin/${base} in ${cwd}...`);
90
- const syncFn = opts.injectedSync ?? syncBranchFromBase;
91
- const result = await syncFn({
92
- cwd,
93
- baseBranch: base,
94
- log: (tag, msg) => progress(tag, msg),
95
- gitSpawn,
96
- });
97
-
98
- // Story #4685 — full detail to a temp log; emit a single summary line.
99
- // Story #4794 — resolve the config so the log honours `project.paths.tempRoot`
100
- // instead of the hardcoded `<cwd>/temp` this used to join. `resolveConfig`
101
- // degrades to the framework defaults when no `.agentrc.json` is present, so
102
- // a zero-config invocation needs no guard here.
103
- emitTerseResult({
104
- label: 'SYNC RESULT',
105
- result,
106
- scope: branch,
107
- config: resolveConfig({ cwd }),
108
- summary: { branch, base, synced: result.synced, kind: result.kind },
109
- });
110
-
111
- if (!result.synced) {
112
- const detail =
113
- result.kind === 'conflict'
114
- ? `: ${result.conflictFiles?.join(', ') ?? '(no file list)'}`
115
- : result.stderr
116
- ? `: ${result.stderr.slice(0, 200)}`
117
- : '';
118
- throw new Error(
119
- `[sync-branch-from-base] sync failed (${result.kind})${detail}`,
120
- );
121
- }
122
- progress('DONE', `✅ Synced ${branch} from origin/${base} (${result.kind}).`);
123
- return { success: true, result };
124
- }
125
-
126
- /**
127
- * Tiny argv parser scoped to this CLI's three flags. Exported for
128
- * tests.
129
- */
130
- export function parseArgv(argv) {
131
- const { values } = parseArgs({
132
- args: argv,
133
- options: {
134
- branch: { type: 'string' },
135
- base: { type: 'string' },
136
- cwd: { type: 'string' },
137
- },
138
- strict: false,
139
- });
140
- return {
141
- branch: values.branch ?? null,
142
- base: values.base ?? null,
143
- cwd: values.cwd ?? null,
144
- };
145
- }
146
-
147
- runAsCli(import.meta.url, runSyncBranchFromBase, {
148
- source: 'sync-branch-from-base',
149
- });