mandrel 2.36.0 → 2.38.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 (225) hide show
  1. package/.agents/README.md +16 -16
  2. package/.agents/agents/plan-critic.md +1 -1
  3. package/.agents/docs/SDLC.md +40 -40
  4. package/.agents/docs/configuration.md +41 -16
  5. package/.agents/docs/execution-reference.md +3 -3
  6. package/.agents/docs/quality-gates.md +2 -2
  7. package/.agents/docs/workflows.md +6 -6
  8. package/.agents/instructions.md +5 -5
  9. package/.agents/rules/changelog-style.md +3 -3
  10. package/.agents/rules/ci-remediation.md +1 -1
  11. package/.agents/rules/git-conventions-reference.md +4 -4
  12. package/.agents/rules/testing-standards.md +56 -0
  13. package/.agents/schemas/agentrc.schema.json +9 -9
  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/boot-sweep.js +7 -1
  20. package/.agents/scripts/check-context-budget.js +195 -103
  21. package/.agents/scripts/check-cyclomatic.js +112 -42
  22. package/.agents/scripts/check-doc-links.js +32 -4
  23. package/.agents/scripts/check-generated-validator.js +202 -0
  24. package/.agents/scripts/check-lifecycle-lint.js +2 -68
  25. package/.agents/scripts/check-schema-references.js +2 -28
  26. package/.agents/scripts/check-test-temp-hygiene.js +1 -1
  27. package/.agents/scripts/check-workflow-cli-lint.js +1 -1
  28. package/.agents/scripts/deliver-light.js +10 -10
  29. package/.agents/scripts/deliver-recover.js +1 -1
  30. package/.agents/scripts/drain-pending-cleanup.js +19 -2
  31. package/.agents/scripts/evidence-gate.js +1 -1
  32. package/.agents/scripts/generate-workflows-doc.js +1 -1
  33. package/.agents/scripts/lib/audit-suite/selector.js +1 -1
  34. package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +1 -1
  35. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +1 -1
  36. package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +5 -5
  37. package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +1 -1
  38. package/.agents/scripts/lib/baselines/components.js +32 -2
  39. package/.agents/scripts/lib/baselines/env-overrides.js +1 -1
  40. package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
  41. package/.agents/scripts/lib/bdd-scenario-budget.js +1 -1
  42. package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
  43. package/.agents/scripts/lib/bdd-step-index.js +1 -1
  44. package/.agents/scripts/lib/bootstrap/branch-protection.js +2 -2
  45. package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
  46. package/.agents/scripts/lib/bootstrap/install-ledger.js +1 -1
  47. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +1 -1
  48. package/.agents/scripts/lib/bootstrap/prompt.js +1 -1
  49. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +2 -2
  50. package/.agents/scripts/lib/checks/core-bare-clean.js +1 -1
  51. package/.agents/scripts/lib/checks/loop-health.js +1 -1
  52. package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +1 -1
  53. package/.agents/scripts/lib/cli-args.js +1 -3
  54. package/.agents/scripts/lib/close-validation/gates.js +1 -1
  55. package/.agents/scripts/lib/close-validation/process.js +1 -1
  56. package/.agents/scripts/lib/command-header.js +1 -1
  57. package/.agents/scripts/lib/config/delivery-routing.js +1 -1
  58. package/.agents/scripts/lib/config/explain.js +1 -1
  59. package/.agents/scripts/lib/config/runners.js +2 -2
  60. package/.agents/scripts/lib/config/runtime.js +1 -1
  61. package/.agents/scripts/lib/config/sync-agentrc.js +1 -1
  62. package/.agents/scripts/lib/config/temp-paths.js +2 -2
  63. package/.agents/scripts/lib/config-settings-schema-delivery.js +3 -3
  64. package/.agents/scripts/lib/config-settings-schema-quality.js +6 -6
  65. package/.agents/scripts/lib/config-settings-schema.js +49 -9
  66. package/.agents/scripts/lib/coverage-baseline.js +2 -2
  67. package/.agents/scripts/lib/cpu-pool.js +90 -10
  68. package/.agents/scripts/lib/crap-utils.js +6 -2
  69. package/.agents/scripts/lib/cyclomatic-ceiling.js +28 -6
  70. package/.agents/scripts/lib/cyclomatic-scope.js +144 -0
  71. package/.agents/scripts/lib/dependency-version.js +86 -0
  72. package/.agents/scripts/lib/duplicate-search.js +2 -2
  73. package/.agents/scripts/lib/errors/index.js +1 -1
  74. package/.agents/scripts/lib/feedback-loop/graduator-core.js +2 -2
  75. package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +5 -5
  76. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +2 -2
  77. package/.agents/scripts/lib/findings/classify-finding.js +1 -1
  78. package/.agents/scripts/lib/findings/promote-finding.js +12 -12
  79. package/.agents/scripts/lib/findings/route-finding.js +2 -2
  80. package/.agents/scripts/lib/generated/agentrc-validator.js +16 -0
  81. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  82. package/.agents/scripts/lib/install-cmd-parser.js +1 -1
  83. package/.agents/scripts/lib/knip-entry-sync.js +1 -57
  84. package/.agents/scripts/lib/label-constants.js +2 -2
  85. package/.agents/scripts/lib/label-taxonomy.js +1 -1
  86. package/.agents/scripts/lib/maintainability-utils.js +108 -10
  87. package/.agents/scripts/lib/observability/source-classifier.js +1 -0
  88. package/.agents/scripts/lib/onboard/init-tail.js +4 -4
  89. package/.agents/scripts/lib/onboard/scaffold-docs.js +2 -2
  90. package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +1 -1
  91. package/.agents/scripts/lib/orchestration/code-review.js +4 -4
  92. package/.agents/scripts/lib/orchestration/complexity-gate.js +5 -5
  93. package/.agents/scripts/lib/orchestration/deliver-recover.js +3 -3
  94. package/.agents/scripts/lib/orchestration/docs-digest.js +3 -3
  95. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +2 -2
  96. package/.agents/scripts/lib/orchestration/light-backstop.js +1 -1
  97. package/.agents/scripts/lib/orchestration/light-escalation.js +6 -6
  98. package/.agents/scripts/lib/orchestration/light-suitability.js +19 -19
  99. package/.agents/scripts/lib/orchestration/plan-context.js +4 -4
  100. package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +2 -2
  101. package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +2 -2
  102. package/.agents/scripts/lib/orchestration/plan-metrics.js +1 -1
  103. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +1 -1
  104. package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +3 -3
  105. package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +6 -6
  106. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +86 -25
  107. package/.agents/scripts/lib/orchestration/plan-persist/summary.js +3 -3
  108. package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +6 -6
  109. package/.agents/scripts/lib/orchestration/planning/authoring-context.js +7 -7
  110. package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -1
  111. package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +3 -3
  112. package/.agents/scripts/lib/orchestration/pr-watch.js +1 -30
  113. package/.agents/scripts/lib/orchestration/reassert-status-column.js +2 -2
  114. package/.agents/scripts/lib/orchestration/remote-verifier.js +2 -2
  115. package/.agents/scripts/lib/orchestration/resolve-stories.js +4 -4
  116. package/.agents/scripts/lib/orchestration/review-providers/codex.js +3 -3
  117. package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +1 -1
  118. package/.agents/scripts/lib/orchestration/review-providers/native.js +9 -2
  119. package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +2 -2
  120. package/.agents/scripts/lib/orchestration/review-providers/security-review.js +3 -3
  121. package/.agents/scripts/lib/orchestration/run-epilogue.js +2 -2
  122. package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +2 -2
  123. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +1 -1
  124. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +5 -1
  125. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +52 -1
  126. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +1 -1
  127. package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +3 -3
  128. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -2
  129. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
  130. package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +2 -5
  131. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +14 -12
  132. package/.agents/scripts/lib/orchestration/story-follow-ups.js +1 -1
  133. package/.agents/scripts/lib/orchestration/ticket-lease.js +34 -9
  134. package/.agents/scripts/lib/orchestration/ticket-validator.js +1 -1
  135. package/.agents/scripts/lib/orchestration/ticketing/reads.js +6 -4
  136. package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
  137. package/.agents/scripts/lib/preflight-runner.js +3 -3
  138. package/.agents/scripts/lib/qa/qa-session.js +1 -1
  139. package/.agents/scripts/lib/runtime-deps/manifest.js +1 -7
  140. package/.agents/scripts/lib/runtime-deps/scan-imports.js +3 -58
  141. package/.agents/scripts/lib/signals/schema.js +1 -1
  142. package/.agents/scripts/lib/single-story-sweep/sweep-lock.js +284 -25
  143. package/.agents/scripts/lib/skills/walk-skill-files.js +1 -1
  144. package/.agents/scripts/lib/source-text/strip-js-comments.js +115 -0
  145. package/.agents/scripts/lib/story-adjacency.js +2 -2
  146. package/.agents/scripts/lib/templates/decomposer-prompts.js +1 -1
  147. package/.agents/scripts/lib/test-isolate/runner.js +3 -3
  148. package/.agents/scripts/lib/test-runner-contract.js +14 -6
  149. package/.agents/scripts/lib/test-tiers.js +135 -18
  150. package/.agents/scripts/lib/ticket-body-sections.js +1 -1
  151. package/.agents/scripts/lib/transpile.js +16 -3
  152. package/.agents/scripts/lib/util/concurrent-map.js +1 -1
  153. package/.agents/scripts/lib/util/parse-id-list.js +1 -1
  154. package/.agents/scripts/lib/wave-runner/live-probe.js +4 -4
  155. package/.agents/scripts/lib/wave-runner/ready-set.js +1 -1
  156. package/.agents/scripts/lib/workflow-closure.js +1 -1
  157. package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +170 -32
  158. package/.agents/scripts/lib/worktree/node-modules-strategy.js +2 -5
  159. package/.agents/scripts/lint-issue-body.js +3 -3
  160. package/.agents/scripts/plan-context.js +4 -4
  161. package/.agents/scripts/plan-critics.js +2 -2
  162. package/.agents/scripts/plan-persist.js +2 -2
  163. package/.agents/scripts/plan-run-epilogue.js +2 -2
  164. package/.agents/scripts/pr-watch-with-update.js +305 -137
  165. package/.agents/scripts/providers/github/board-add.js +1 -1
  166. package/.agents/scripts/providers/github/errors.js +1 -1
  167. package/.agents/scripts/providers/github/tickets.js +114 -21
  168. package/.agents/scripts/quality-preview.js +162 -70
  169. package/.agents/scripts/resolve-stories.js +3 -3
  170. package/.agents/scripts/resync-status-column.js +1 -1
  171. package/.agents/scripts/run-lint.js +1 -1
  172. package/.agents/scripts/run-test-profile.js +8 -5
  173. package/.agents/scripts/run-tests.js +79 -14
  174. package/.agents/scripts/single-story-close.js +2 -4
  175. package/.agents/scripts/single-story-init.js +10 -6
  176. package/.agents/scripts/stories-wave-tick.js +8 -8
  177. package/.agents/skills/core/idea-refinement/SKILL.md +5 -5
  178. package/.agents/skills/core/scope-triage/SKILL.md +3 -3
  179. package/.agents/skills/skills.index.json +1 -1
  180. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -1
  181. package/.agents/workflows/audit-data-model.md +1 -1
  182. package/.agents/workflows/audit-documentation.md +2 -2
  183. package/.agents/workflows/audit-to-stories.md +11 -11
  184. package/.agents/workflows/git-deliver.md +4 -4
  185. package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
  186. package/.agents/workflows/helpers/audit-lens-core.md +1 -1
  187. package/.agents/workflows/helpers/code-review.md +4 -4
  188. package/.agents/workflows/helpers/deliver-light.md +26 -26
  189. package/.agents/workflows/helpers/deliver-reference.md +15 -7
  190. package/.agents/workflows/helpers/deliver-story-reference.md +9 -4
  191. package/.agents/workflows/helpers/deliver-story.md +6 -7
  192. package/.agents/workflows/helpers/diagnose.md +2 -2
  193. package/.agents/workflows/helpers/parallel-tooling.md +1 -1
  194. package/.agents/workflows/helpers/plan-reference.md +9 -9
  195. package/.agents/workflows/helpers/qa-core.md +8 -8
  196. package/.agents/workflows/helpers/worktree-lifecycle.md +6 -6
  197. package/.agents/workflows/{deliver.md → mandrel-deliver.md} +15 -19
  198. package/.agents/workflows/{plan.md → mandrel-plan.md} +9 -9
  199. package/.agents/workflows/memory-consolidate.md +4 -4
  200. package/.agents/workflows/prototype.md +3 -3
  201. package/.agents/workflows/qa-assist.md +12 -12
  202. package/.agents/workflows/qa-explore.md +6 -6
  203. package/.agents/workflows/qa-run.md +1 -1
  204. package/README.md +7 -7
  205. package/docs/CHANGELOG.md +43 -0
  206. package/lib/cli/doctor.js +1 -1
  207. package/lib/cli/init.js +1 -1
  208. package/lib/cli/migrate.js +100 -94
  209. package/lib/migrations/helpers/retire-agentrc-key.js +200 -0
  210. package/lib/migrations/index.js +32 -33
  211. package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +21 -80
  212. package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +15 -87
  213. package/lib/migrations/steps/2.11.0-retire-max-seed-words.js +13 -71
  214. package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +1 -1
  215. package/lib/migrations/steps/2.20.0-retire-codebase-snapshot.js +13 -101
  216. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +13 -100
  217. package/package.json +7 -3
  218. package/.agents/scripts/lib/audit-suite/frontmatter-lint.js +0 -32
  219. package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +0 -37
  220. package/.agents/scripts/lib/cli/parse-numeric.js +0 -60
  221. package/.agents/scripts/lib/close-validation/telemetry.js +0 -79
  222. package/.agents/scripts/lib/orchestration/label-transitions.js +0 -44
  223. package/.agents/scripts/lib/orchestration/parked-follow-ons.js +0 -147
  224. package/.agents/scripts/lib/orchestration/phase-runner.js +0 -88
  225. package/.agents/scripts/lib/orchestration/recut.js +0 -56
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mandrel",
3
- "version": "2.36.0",
3
+ "version": "2.38.0",
4
4
  "description": "Claude Code-first opinionated workflow framework: instructions, skills, rules, and SDLC workflows that govern AI coding assistants.",
5
5
  "files": [
6
6
  ".agents/",
@@ -19,7 +19,9 @@
19
19
  "test": "node .agents/scripts/run-tests.js",
20
20
  "test:quick": "node .agents/scripts/run-tests.js --tier quick",
21
21
  "test:integration": "node .agents/scripts/run-tests.js --tier integration",
22
+ "test:e2e": "node .agents/scripts/run-tests.js --tier e2e",
22
23
  "test:profile": "node .agents/scripts/run-test-profile.js",
24
+ "test:census": "SPAWN_CENSUS_OUT=\"${SPAWN_CENSUS_OUT:-temp/census.json}\" NODE_OPTIONS=\"--require $PWD/tests/fixtures/spawn-census.cjs\" node .agents/scripts/run-tests.js",
23
25
  "test:isolate": "node .agents/scripts/test-isolate.js",
24
26
  "verify": "node .agents/scripts/run-verify.js",
25
27
  "typecheck": "node -e \"process.exit(0)\"",
@@ -31,7 +33,7 @@
31
33
  "baselines:scope": "node .agents/scripts/check-baseline-scope.js",
32
34
  "baselines:prune": "node .agents/scripts/prune-baseline-orphans.js",
33
35
  "lint:md": "markdownlint-cli2 \".agents/**/*.md\" \"*.md\" \"!node_modules/**\" \"!.worktrees/**\"",
34
- "lint": "node .agents/scripts/run-lint.js && npm run docs:check",
36
+ "lint": "node .agents/scripts/run-lint.js && node .agents/scripts/check-generated-validator.js --check && npm run docs:check",
35
37
  "docs:gen": "node .agents/scripts/generate-config-docs.js && node .agents/scripts/generate-workflows-doc.js && node .agents/scripts/generate-lens-checklists.js",
36
38
  "docs:check": "node .agents/scripts/generate-config-docs.js --check && node .agents/scripts/generate-workflows-doc.js --check && node .agents/scripts/generate-lens-checklists.js --check && node .agents/scripts/check-doc-links.js && npm run skills:check",
37
39
  "skills:index": "node .agents/scripts/generate-skills-index.js",
@@ -47,6 +49,8 @@
47
49
  "cyclomatic:update": "node .agents/scripts/check-cyclomatic.js --update",
48
50
  "dead-exports:update": "node .agents/scripts/update-dead-exports-baseline.js && node .agents/scripts/update-dead-exports-baseline.js --production",
49
51
  "check:knip-entries": "node .agents/scripts/check-knip-entries.js",
52
+ "validator:gen": "node .agents/scripts/check-generated-validator.js",
53
+ "validator:check": "node .agents/scripts/check-generated-validator.js --check",
50
54
  "check:schema-refs": "node .agents/scripts/check-schema-references.js",
51
55
  "check:workflow-citations": "node .agents/scripts/check-workflow-citations.js",
52
56
  "crap:check": "node .agents/scripts/check-baselines.js --gate crap",
@@ -101,7 +105,7 @@
101
105
  "markdownlint-cli2": "^0.22.1",
102
106
  "memfs": "^4.57.7",
103
107
  "node-pty": "^1.0.0",
104
- "typescript": ">=5.0.0"
108
+ "typescript": "^6.0.3"
105
109
  },
106
110
  "dependencies": {
107
111
  "ajv": "^8.20.0",
@@ -1,32 +0,0 @@
1
- /**
2
- * lib/audit-suite/frontmatter-lint.js — workflow frontmatter linter.
3
- *
4
- * Lives next to `frontmatter.js` but stays in its own module so adding
5
- * lints doesn't drag the summary helper's maintainability score down.
6
- * Pure: no IO, no provider calls, safe to unit-test in isolation.
7
- *
8
- * Story #1324, Epic #1185 — Dispatch performance pass.
9
- *
10
- * Story #2824, Epic #2815 — Model-hint frontmatter was removed from
11
- * every workflow and from the validator's field list. The function is
12
- * retained as a no-op safety net so callers (and any future frontmatter
13
- * lints) keep a stable entry point.
14
- */
15
-
16
- import { extractFrontmatter } from './frontmatter.js';
17
-
18
- /**
19
- * Pure: lint a frontmatter map (or raw workflow content). Currently a
20
- * no-op — no frontmatter fields are validated after the model-hint
21
- * removal. The signature is preserved so future field-level lints can
22
- * slot in without churning callers.
23
- *
24
- * @param {string | Record<string, string>} input
25
- * @returns {{ ok: boolean, errors: Array<{ field: string, value: string, message: string }> }}
26
- */
27
- export function validateFrontmatter(input) {
28
- // Extract so callers still pay the same "is this parseable?" cost as
29
- // before; we just have no field-level rules to enforce right now.
30
- if (typeof input === 'string') extractFrontmatter(input);
31
- return { ok: true, errors: [] };
32
- }
@@ -1,37 +0,0 @@
1
- import path from 'node:path';
2
- import {
3
- write as writeBaselineEnvelope,
4
- writeFile as writeBaselineFile,
5
- } from './writer.js';
6
-
7
- /**
8
- * Saves a new maintainability baseline to disk at `baselinePath`.
9
- *
10
- * Accepts the legacy flat `{ path: mi }` shape for backwards compatibility
11
- * with existing callers (`regenerateMainFromTree`, refresh helpers). The
12
- * map is transformed into the canonical envelope shape (`$schema`,
13
- * `kernelVersion`, `generatedAt`, `rollup`, `rows`) via the shared
14
- * `lib/baselines/writer.js` pipeline before being persisted, so every
15
- * write produces a file that round-trips through `lib/baselines/reader.js`
16
- * without schema errors.
17
- *
18
- * @param {Record<string, number>} baseline path→MI flat map.
19
- * @param {string} baselinePath Required — caller supplies via getBaselines().
20
- */
21
- export function saveBaseline(baseline, baselinePath) {
22
- if (typeof baselinePath !== 'string' || baselinePath.length === 0) {
23
- throw new TypeError(
24
- 'maintainability-utils.saveBaseline: baselinePath is required.',
25
- );
26
- }
27
- const abs = path.isAbsolute(baselinePath)
28
- ? baselinePath
29
- : path.resolve(process.cwd(), baselinePath);
30
-
31
- const rows = Object.entries(baseline ?? {}).map(([p, mi]) => ({
32
- path: p,
33
- mi,
34
- }));
35
- const envelope = writeBaselineEnvelope({ kind: 'maintainability', rows });
36
- writeBaselineFile(abs, envelope);
37
- }
@@ -1,60 +0,0 @@
1
- /**
2
- * parse-numeric.js — shared CLI integer-flag validators (Story #2993).
3
- *
4
- * Extracted from `lifecycle-emit-story-dispatch.js` so future scripts that
5
- * need a strict positive-integer or non-negative-integer flag value share
6
- * a single validator instead of hand-rolling another `Number.parseInt`
7
- * dance. Both helpers throw with a clear "required" / "must be …" message
8
- * shape so a `runAsCli` boundary surfaces a clean exit-1.
9
- *
10
- * Contract:
11
- * - Empty / `null` / `undefined` raw values throw "is required".
12
- * - Non-integer or out-of-range values throw "must be a … integer".
13
- * - A leading `#` on the raw value is stripped (matches the ticket-ID
14
- * ergonomic on the rest of the framework).
15
- *
16
- * @module lib/cli/parse-numeric
17
- */
18
-
19
- /**
20
- * Parse a flag value as a strict positive integer (≥ 1). Leading `#` is
21
- * stripped to match the `parseTicketId` ergonomic.
22
- *
23
- * @param {string|number|null|undefined} raw
24
- * @param {string} flag Flag label used in the thrown error message.
25
- * @param {string} [tool='cli'] Optional tool prefix for the error message.
26
- * @returns {number}
27
- */
28
- export function parseRequiredPositiveInt(raw, flag, tool = 'cli') {
29
- if (raw === undefined || raw === null || raw === '') {
30
- throw new Error(`${tool}: ${flag} is required`);
31
- }
32
- const n = Number.parseInt(String(raw).replace(/^#/, ''), 10);
33
- if (!Number.isInteger(n) || n < 1) {
34
- throw new Error(`${tool}: ${flag} must be a positive integer (got ${raw})`);
35
- }
36
- return n;
37
- }
38
-
39
- /**
40
- * Parse a flag value as a strict non-negative integer (≥ 0). Leading `#`
41
- * is NOT stripped — non-negative use cases (wave index, attempt offset)
42
- * do not carry the ticket-ID convention.
43
- *
44
- * @param {string|number|null|undefined} raw
45
- * @param {string} flag
46
- * @param {string} [tool='cli']
47
- * @returns {number}
48
- */
49
- export function parseRequiredNonNegativeInt(raw, flag, tool = 'cli') {
50
- if (raw === undefined || raw === null || raw === '') {
51
- throw new Error(`${tool}: ${flag} is required`);
52
- }
53
- const n = Number.parseInt(String(raw), 10);
54
- if (!Number.isInteger(n) || n < 0) {
55
- throw new Error(
56
- `${tool}: ${flag} must be a non-negative integer (got ${raw})`,
57
- );
58
- }
59
- return n;
60
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * close-validation/telemetry.js — gh-spawn telemetry emitter.
3
- */
4
-
5
- import { writeFile as defaultWriteFile } from 'node:fs/promises';
6
- import path from 'node:path';
7
- import { storyTempDir } from '../config/temp-paths.js';
8
- import { getSpawnCount as defaultGetSpawnCount } from '../gh-exec.js';
9
-
10
- /**
11
- * Throw-away ghSpawnCount emitter (Story #1795 / Epic #1788).
12
- *
13
- * Writes the current `gh-exec` spawn counter to
14
- * `temp/run-<id>/stories/story-<sid>/gh-spawn-count.json`.
15
- *
16
- * Story #4545 — its reader is gone: `analyze-execution.js` consumed this file
17
- * to emit a `ghSpawnCount` field on the `story-perf-summary` payload, and both
18
- * that CLI and that payload were deleted with the execution-analysis surface.
19
- * The writer itself already had no production caller before that (the
20
- * `runPostMergeClose` orchestrator named below went in the v2.0.0 cutover), so
21
- * this module is production-dead and kept alive only by its own test — the
22
- * test-importer blind spot the dead-exports ratchet cannot see. It is left in
23
- * place rather than deleted because reviving spawn telemetry against the live
24
- * close path is a decision, not a sweep.
25
- *
26
- * @param {object} opts
27
- * @param {number|string} opts.epicId
28
- * @param {number|string} opts.storyId
29
- * @param {object} [opts.config] - Resolved config bag so `tempRoot`
30
- * resolution honours the consumer's configured path.
31
- * @param {() => number} [opts.getSpawnCountFn=defaultGetSpawnCount] - Test seam.
32
- * @param {typeof defaultWriteFile} [opts.writeFileFn=defaultWriteFile] - Test seam.
33
- * @param {{ warn?: (s: string) => void }} [opts.logger] - Best-effort
34
- * failure-path logger; never throws.
35
- * @returns {Promise<{ status: 'ok'|'failed', path?: string, ghSpawnCount?: number, reason?: string }>}
36
- */
37
- export async function emitGhSpawnCount({
38
- epicId,
39
- storyId,
40
- config,
41
- getSpawnCountFn = defaultGetSpawnCount,
42
- writeFileFn = defaultWriteFile,
43
- logger,
44
- } = {}) {
45
- const eid = Number(epicId);
46
- const sid = Number(storyId);
47
- if (!Number.isInteger(eid) || eid < 1 || !Number.isInteger(sid) || sid < 1) {
48
- return { status: 'failed', reason: 'invalid-ids' };
49
- }
50
- let ghSpawnCount;
51
- try {
52
- ghSpawnCount = getSpawnCountFn();
53
- } catch (err) {
54
- logger?.warn?.(
55
- `[close-validation] gh-spawn-count read failed: ${err?.message ?? err}`,
56
- );
57
- return { status: 'failed', reason: 'counter-read-failed' };
58
- }
59
- const targetPath = path.join(
60
- storyTempDir(eid, sid, config),
61
- 'gh-spawn-count.json',
62
- );
63
- const payload = {
64
- kind: 'gh-spawn-count',
65
- epicId: eid,
66
- storyId: sid,
67
- ghSpawnCount,
68
- capturedAt: new Date().toISOString(),
69
- };
70
- try {
71
- await writeFileFn(targetPath, JSON.stringify(payload, null, 2));
72
- return { status: 'ok', path: targetPath, ghSpawnCount };
73
- } catch (err) {
74
- logger?.warn?.(
75
- `[close-validation] gh-spawn-count emit failed: ${err?.message ?? err}`,
76
- );
77
- return { status: 'failed', reason: 'write-failed' };
78
- }
79
- }
@@ -1,44 +0,0 @@
1
- /**
2
- * label-transitions.js — readability wrappers over `transitionTicketState`.
3
- *
4
- * These are deliberately thin: each helper names the *target* state and
5
- * forwards to the underlying SDK call. They exist so that init / close /
6
- * deliver-tail call sites read as prose ("toExecuting(provider, taskId)")
7
- * instead of forcing readers to parse the `STATE_LABELS.X` constant at
8
- * every call site.
9
- *
10
- * Not an abstraction — the underlying `transitionTicketState` remains the
11
- * authoritative single-ticket transition path. Opts are forwarded verbatim.
12
- */
13
-
14
- import { STATE_LABELS, transitionTicketState } from './ticketing.js';
15
-
16
- /** Transition a ticket to `agent::executing`. */
17
- export function toExecuting(provider, ticketId, opts) {
18
- return transitionTicketState(
19
- provider,
20
- ticketId,
21
- STATE_LABELS.EXECUTING,
22
- opts,
23
- );
24
- }
25
-
26
- /**
27
- * Transition an array of tickets to `agent::done`, in order. Each call
28
- * triggers its own cascade (via `transitionTicketState`). Failures for
29
- * individual tickets propagate — the loop aborts on the first error, so
30
- * callers that need per-ticket tolerance must catch around each id
31
- * themselves.
32
- *
33
- * @param {object} provider
34
- * @param {number[]} ticketIds
35
- * @param {object} [opts]
36
- */
37
- export async function toDone(provider, ticketIds, opts) {
38
- if (!Array.isArray(ticketIds)) {
39
- throw new TypeError('toDone: ticketIds must be an array');
40
- }
41
- for (const id of ticketIds) {
42
- await transitionTicketState(provider, id, STATE_LABELS.DONE, opts);
43
- }
44
- }
@@ -1,147 +0,0 @@
1
- /**
2
- * Classify Stories that exist under an Epic but are absent from the frozen
3
- * dispatch manifest. Two cases:
4
- *
5
- * - "recut" — the Story carries a `<!-- recut-of: #N -->` marker whose
6
- * parent ID matches a manifest entry. These are attributable
7
- * to a manifest Story and must be satisfied alongside it.
8
- *
9
- * - "parked" — the Story is genuinely outside the manifest (carved off
10
- * mid-sprint, no recut lineage). The operator should explicitly
11
- * adopt it into the current Epic or defer it. Surfaced as a
12
- * structured comment so `/deliver` has a single checkpoint.
13
- *
14
- * Both categories are informational at the wave-completeness gate — they do
15
- * not fail closure by themselves. The gate continues to enforce that every
16
- * manifest Story is closed; recuts and parked follow-ons are additional
17
- * transparency.
18
- */
19
-
20
- import { parseRecutMarker } from './recut.js';
21
-
22
- /**
23
- * Partition Stories under an Epic into manifest, recut, and parked buckets.
24
- *
25
- * @param {number[]} manifestStoryIds IDs present in the dispatch manifest.
26
- * @param {Array<{ id: number, title?: string, body?: string, state?: string, labels?: string[] }>} storiesUnderEpic
27
- * All `type::story` tickets under the Epic.
28
- * @returns {{
29
- * manifest: Array<object>,
30
- * recuts: Array<{ storyId: number, parentId: number, title: string, state: string }>,
31
- * parked: Array<{ storyId: number, title: string, state: string }>,
32
- * }}
33
- */
34
- export function classifyStoriesAgainstManifest(
35
- manifestStoryIds,
36
- storiesUnderEpic,
37
- ) {
38
- const manifestSet = new Set(manifestStoryIds.map(Number));
39
- const manifest = [];
40
- const recuts = [];
41
- const parked = [];
42
-
43
- for (const story of storiesUnderEpic) {
44
- if (manifestSet.has(story.id)) {
45
- manifest.push(story);
46
- continue;
47
- }
48
- const marker = parseRecutMarker(story.body);
49
- if (marker && manifestSet.has(marker.parentStoryId)) {
50
- recuts.push({
51
- storyId: story.id,
52
- parentId: marker.parentStoryId,
53
- title: story.title ?? '',
54
- state: story.state ?? 'open',
55
- });
56
- } else {
57
- parked.push({
58
- storyId: story.id,
59
- title: story.title ?? '',
60
- state: story.state ?? 'open',
61
- });
62
- }
63
- }
64
-
65
- return { manifest, recuts, parked };
66
- }
67
-
68
- /**
69
- * Render the structured `parked-follow-ons` comment body for an Epic.
70
- * Idempotent: the same input produces identical output, so repeated upserts
71
- * don't churn comment revisions.
72
- *
73
- * @param {number} epicId
74
- * @param {ReturnType<typeof classifyStoriesAgainstManifest>} classification
75
- * @returns {string}
76
- */
77
- export function renderParkedFollowOnsComment(epicId, classification) {
78
- const { recuts, parked } = classification;
79
-
80
- const lines = [
81
- `## 🪝 Parked Follow-Ons & Recuts — Epic #${epicId}`,
82
- '',
83
- 'Stories created under this Epic that are **not** in the frozen dispatch',
84
- 'manifest. Surfaced here so `/deliver` can gate on them at the',
85
- 'completeness check.',
86
- '',
87
- `- **Recuts** (attributable to a manifest Story): ${recuts.length}`,
88
- `- **Parked follow-ons** (no manifest lineage): ${parked.length}`,
89
- '',
90
- ];
91
-
92
- if (recuts.length > 0) {
93
- lines.push('### Recuts');
94
- lines.push('');
95
- lines.push('| Story | Recut-of | State | Title |');
96
- lines.push('| :--- | :--- | :--- | :--- |');
97
- for (const r of recuts) {
98
- lines.push(
99
- `| #${r.storyId} | #${r.parentId} | ${r.state} | ${r.title} |`,
100
- );
101
- }
102
- lines.push('');
103
- }
104
-
105
- if (parked.length > 0) {
106
- lines.push('### Parked Follow-Ons');
107
- lines.push('');
108
- lines.push('| Story | State | Title |');
109
- lines.push('| :--- | :--- | :--- |');
110
- for (const p of parked) {
111
- lines.push(`| #${p.storyId} | ${p.state} | ${p.title} |`);
112
- }
113
- lines.push('');
114
- lines.push(
115
- '> **Action required**: adopt each Story into the current Epic (by',
116
- '> re-running the dispatcher so the manifest is refreshed), or explicitly',
117
- '> defer by closing the Story with `state_reason=not_planned`.',
118
- );
119
- lines.push('');
120
- }
121
-
122
- if (recuts.length === 0 && parked.length === 0) {
123
- lines.push(
124
- '✅ No out-of-manifest Stories detected — every Story under this Epic is in the dispatch manifest.',
125
- );
126
- lines.push('');
127
- }
128
-
129
- lines.push('```json');
130
- lines.push(
131
- JSON.stringify(
132
- {
133
- recuts: recuts.map((r) => ({
134
- storyId: r.storyId,
135
- parentId: r.parentId,
136
- state: r.state,
137
- })),
138
- parked: parked.map((p) => ({ storyId: p.storyId, state: p.state })),
139
- },
140
- null,
141
- 2,
142
- ),
143
- );
144
- lines.push('```');
145
-
146
- return lines.join('\n');
147
- }
@@ -1,88 +0,0 @@
1
- /**
2
- * phase-runner.js — Lightweight wrappers for "named phase" error handling.
3
- *
4
- * Many orchestration entry points (`single-story-init.js`,
5
- * `single-story-close.js`, and other phase modules) repeat the same
6
- * `try { ... } catch (err) {
7
- * logger.error('[phase=foo] ' + err.message); ... }` block. These two
8
- * helpers consolidate that pattern.
9
- *
10
- * - `runPhase(name, fn, opts)`: Run `fn()`. On error, log with a
11
- * `[phase=<name>]` prefix. If `fatal: true`, rethrow; otherwise
12
- * return `opts.fallback` (default `undefined`) so the caller can
13
- * continue.
14
- *
15
- * - `runSafely(fn, opts)`: Run `fn()` and swallow any error after
16
- * logging it. Convenience for unnamed best-effort cleanup steps.
17
- *
18
- * Both helpers handle sync and async `fn`s — the return value is always
19
- * a Promise that resolves to the function's value (or `fallback`).
20
- */
21
-
22
- import { NOOP_LOGGER } from '../Logger.js';
23
-
24
- /**
25
- * @typedef {object} PhaseLogger
26
- * @property {(msg: string) => void} [error]
27
- * @property {(msg: string) => void} [warn]
28
- */
29
-
30
- function pickLogger(logger) {
31
- if (!logger) return NOOP_LOGGER;
32
- return {
33
- error:
34
- typeof logger.error === 'function'
35
- ? logger.error.bind(logger)
36
- : NOOP_LOGGER.error,
37
- warn:
38
- typeof logger.warn === 'function'
39
- ? logger.warn.bind(logger)
40
- : NOOP_LOGGER.warn,
41
- };
42
- }
43
-
44
- /**
45
- * Run `fn` under a named phase. On error:
46
- * - logs `[phase=<name>] <message>` via `logger.error`
47
- * - if `fatal` is true, rethrows the original error
48
- * - otherwise returns `fallback`
49
- *
50
- * @template T
51
- * @param {string} name - Phase name (used in the log prefix).
52
- * @param {() => (T | Promise<T>)} fn
53
- * @param {{ logger?: PhaseLogger, fallback?: T, fatal?: boolean }} [opts]
54
- * @returns {Promise<T | undefined>}
55
- */
56
- export async function runPhase(name, fn, opts = {}) {
57
- const logger = pickLogger(opts.logger);
58
- const fatal = opts.fatal === true;
59
- try {
60
- return await fn();
61
- } catch (err) {
62
- const message = err instanceof Error ? err.message : String(err);
63
- logger.error(`[phase=${name}] ${message}`);
64
- if (fatal) throw err;
65
- return opts.fallback;
66
- }
67
- }
68
-
69
- /**
70
- * Run `fn` and swallow errors after logging. Equivalent to
71
- * `runPhase('safe', fn, { logger, fatal: false })` but without requiring
72
- * the caller to invent a phase name.
73
- *
74
- * @template T
75
- * @param {() => (T | Promise<T>)} fn
76
- * @param {{ logger?: PhaseLogger }} [opts]
77
- * @returns {Promise<T | undefined>}
78
- */
79
- export async function runSafely(fn, opts = {}) {
80
- const logger = pickLogger(opts.logger);
81
- try {
82
- return await fn();
83
- } catch (err) {
84
- const message = err instanceof Error ? err.message : String(err);
85
- logger.warn(`[phase=safe] ${message}`);
86
- return undefined;
87
- }
88
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * Recut markers track when a Story is carved off mid-sprint from another
3
- * Story that is already in the dispatch manifest. The marker is an HTML
4
- * comment embedded in the new Story's body:
5
- *
6
- * <!-- recut-of: #641 -->
7
- *
8
- * Downstream consumers (the wave-completeness gate, the sprint retro)
9
- * read the marker to attribute the new Story back to its ancestor so
10
- * counts line up with the frozen manifest.
11
- */
12
-
13
- const MARKER_RE = /<!--\s*recut-of:\s*#?(\d+)\s*-->/i;
14
-
15
- /**
16
- * Parse a recut marker out of a Story body.
17
- *
18
- * @param {string | null | undefined} body
19
- * @returns {{ parentStoryId: number, raw: string } | null}
20
- */
21
- export function parseRecutMarker(body) {
22
- if (typeof body !== 'string') return null;
23
- const match = body.match(MARKER_RE);
24
- if (!match) return null;
25
- return { parentStoryId: Number.parseInt(match[1], 10), raw: match[0] };
26
- }
27
-
28
- /**
29
- * Format a recut marker for a given parent Story ID.
30
- *
31
- * @param {number} parentStoryId
32
- * @returns {string}
33
- */
34
- export function formatRecutMarker(parentStoryId) {
35
- return `<!-- recut-of: #${parentStoryId} -->`;
36
- }
37
-
38
- /**
39
- * Return a body with the recut marker present. Replaces any existing
40
- * marker so the parent reference is always up to date; appends a fresh
41
- * marker when none is present.
42
- *
43
- * @param {string | null | undefined} body
44
- * @param {number} parentStoryId
45
- * @returns {string}
46
- */
47
- export function injectRecutMarker(body, parentStoryId) {
48
- const marker = formatRecutMarker(parentStoryId);
49
- const existing = parseRecutMarker(body);
50
- if (existing) {
51
- if (existing.parentStoryId === parentStoryId) return body ?? '';
52
- return (body ?? '').replace(MARKER_RE, marker);
53
- }
54
- const trimmed = (body ?? '').replace(/\s+$/, '');
55
- return trimmed.length > 0 ? `${trimmed}\n\n${marker}\n` : `${marker}\n`;
56
- }