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,103 +0,0 @@
1
- /**
2
- * GitHub Provider — PullRequestGateway.
3
- *
4
- * Owns `createPullRequest`. Uses `gh pr create` for the create call and
5
- * follows up with `gh pr view` to harvest the canonical `{number, url, id}`
6
- * envelope (the create stdout is just the html_url string).
7
- *
8
- * Extracted from `../github.js` in Story #2462 / Task #2479. Public
9
- * surface on `GitHubProvider` is unchanged — `createPullRequest`
10
- * delegates here.
11
- *
12
- * @see Story #2462 — Split GitHubProvider god class into seven composed gateways.
13
- */
14
-
15
- import { Logger } from '../../lib/Logger.js';
16
- import { assertStoryPrBaseAllowed } from '../../lib/orchestration/pr-base-guard.js';
17
-
18
- export class PullRequestGateway {
19
- /**
20
- * @param {{
21
- * gh: object,
22
- * hooks?: {
23
- * getTicket?: (id: number) => Promise<object>,
24
- * addItemToProject?: (nodeId: string) => Promise<unknown>,
25
- * getProjectNumber?: () => number|null,
26
- * },
27
- * }} deps
28
- */
29
- constructor({ gh, hooks = {} } = {}) {
30
- this._gh = gh;
31
- this._hooks = hooks;
32
- }
33
-
34
- /**
35
- * Open a Pull Request linking `ticketId` to `branchName` against
36
- * `baseBranch`. Uses `gh pr create` for the create call and follows up
37
- * with `gh pr view` to harvest the JSON envelope (`{number, url, id}`).
38
- *
39
- * Returns `{ number, url, htmlUrl, nodeId }`.
40
- */
41
- /* node:coverage ignore next */
42
- async createPullRequest(branchName, ticketId, baseBranch = 'main') {
43
- const getTicket = this._hooks.getTicket;
44
- if (typeof getTicket !== 'function') {
45
- throw new Error(
46
- '[PullRequestGateway] createPullRequest requires a getTicket hook',
47
- );
48
- }
49
- const ticket = await getTicket(ticketId);
50
-
51
- // Story #2960 — refuse `--base main` (or any non-Epic branch) when
52
- // the ticket body declares an `Epic: #N` parent. Stand-alone Stories
53
- // pass through untouched.
54
- assertStoryPrBaseAllowed({
55
- storyId: ticketId,
56
- storyBody: ticket?.body,
57
- baseBranch,
58
- });
59
-
60
- const createResult = await this._gh.pr.create([
61
- '--title',
62
- ticket.title,
63
- '--body',
64
- `Closes #${ticketId}`,
65
- '--base',
66
- baseBranch,
67
- '--head',
68
- branchName,
69
- ]);
70
- const htmlUrl = (createResult?.stdout ?? '').trim();
71
-
72
- // `gh pr view <url> --json number,url,id` returns the canonical
73
- // numeric id, api url, and node id we need for the {number, url,
74
- // nodeId} envelope and for the Project V2 link below.
75
- const viewResult = await this._gh.pr.view(htmlUrl, ['number', 'url', 'id']);
76
- const view = JSON.parse(viewResult?.stdout ?? '{}');
77
-
78
- try {
79
- const projectNumber =
80
- typeof this._hooks.getProjectNumber === 'function'
81
- ? this._hooks.getProjectNumber()
82
- : null;
83
- if (
84
- projectNumber &&
85
- view.id &&
86
- typeof this._hooks.addItemToProject === 'function'
87
- ) {
88
- await this._hooks.addItemToProject(view.id);
89
- }
90
- } catch (err) {
91
- Logger.warn(
92
- `[GitHubProvider] Failed to add PR #${view.number} to project: ${err.message}`,
93
- );
94
- }
95
-
96
- return {
97
- number: view.number,
98
- url: view.url,
99
- htmlUrl,
100
- nodeId: view.id,
101
- };
102
- }
103
- }
@@ -1,309 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * signals-view.js — `/signals` viewer CLI (Epic #1181 / Story #1440 /
5
- * Task #1463).
6
- *
7
- * Reads signals via `lib/signals/read`, materialises a span-tree via
8
- * `lib/signals/buildSpanTree`, and prints a readable run → Story →
9
- * events tree to stdout. The output is **plain text via
10
- * `process.stdout.write`** — no Ink, no blessed, no terminal-control
11
- * escape sequences — so it works on Windows + bash hosts (see
12
- * `parallel-tooling.md`). The Task ticket's "console.log only" rule
13
- * specified the absence of TUI libraries; we route through
14
- * `process.stdout.write` to comply with the framework-wide
15
- * `tests/enforcement/no-console.test.js` allowlist (machine-parsable
16
- * stdout uses `process.stdout.write`, not the console).
17
- *
18
- * Usage:
19
- * node .agents/scripts/signals-view.js <run-id> [--story <id>]
20
- *
21
- * Args:
22
- * <run-id> Positive integer run ID. Required.
23
- * --story <id> Optional positive integer Story ID. When set,
24
- * narrows the printed tree to a single Story
25
- * subtree.
26
- *
27
- * Exit codes:
28
- * 0 — happy path, OR missing signals file (friendly message printed,
29
- * no stack trace).
30
- * 1 — bad arguments (non-integer run, missing positional, etc.).
31
- *
32
- * Tempfile contract:
33
- * The viewer resolves the on-disk signals path via the configured
34
- * `project.paths.tempRoot`. The `phase_timings_uses_project_root`
35
- * memory captures the failure mode this guards against — earlier
36
- * post-merge work leaked to the real repo root regardless of test
37
- * sandbox `tempRoot`. The `--temp-root` flag is the test hook;
38
- * production callers leave it unset and pick up the resolved config.
39
- *
40
- * @module signals-view
41
- */
42
-
43
- import { parseStandardCliArgs } from './lib/cli/standard-args.js';
44
- import { runAsCli } from './lib/cli-utils.js';
45
- import * as signals from './lib/signals/index.js';
46
-
47
- /**
48
- * Single sink for every line the viewer emits. Centralised so the
49
- * enforcement test (`tests/enforcement/no-console.test.js`) sees one
50
- * audit point and the unit tests patch one seam, not eleven.
51
- *
52
- * @param {string} line
53
- * @returns {void}
54
- */
55
- function println(line) {
56
- process.stdout.write(`${line}\n`);
57
- }
58
-
59
- const USAGE =
60
- 'Usage: node .agents/scripts/signals-view.js <run-id> [--story <id>] [--temp-root <path>]';
61
-
62
- /**
63
- * Parse a token as a strict positive integer (no leading +, no float, no
64
- * trailing junk). Returns `null` on any non-conforming input.
65
- *
66
- * @param {unknown} tok
67
- * @returns {number|null}
68
- */
69
- function parseStrictPositiveInt(tok) {
70
- if (typeof tok !== 'string') return null;
71
- const n = Number.parseInt(tok, 10);
72
- if (!Number.isInteger(n) || n <= 0) return null;
73
- if (String(n) !== tok.trim()) return null;
74
- return n;
75
- }
76
-
77
- function err(error) {
78
- return { ok: false, error };
79
- }
80
-
81
- const HELP_FLAGS = new Set(['--help', '-h']);
82
-
83
- /**
84
- * Parse argv slice (the array passed to `main` excludes node + script).
85
- * Returns a `{ ok, ... }` envelope rather than throwing so `main` can
86
- * print a friendly message and exit 1 without a stack trace.
87
- *
88
- * Delegates the flag walking to `parseStandardCliArgs` and post-validates
89
- * the signals-view-specific invariants (single positional run-id, strictly
90
- * positive integers for both ids).
91
- *
92
- * @param {string[]} argv
93
- * @returns {{ ok: true, epic: number, story: number | null, tempRoot: string | null } | { ok: false, error: string }}
94
- */
95
- export function parseArgs(argv) {
96
- if (!Array.isArray(argv) || argv.length === 0) {
97
- return err(`missing <run-id>. ${USAGE}`);
98
- }
99
- if (argv.some((t) => HELP_FLAGS.has(t))) return err(USAGE);
100
-
101
- let parsed;
102
- try {
103
- parsed = parseStandardCliArgs({
104
- argv,
105
- extras: {
106
- 'temp-root': { type: 'string', alias: 'tempRoot' },
107
- },
108
- });
109
- } catch (e) {
110
- if (e && e.code === 'UNKNOWN_FLAG') {
111
- return err(`unexpected token "--${e.flag}". ${USAGE}`);
112
- }
113
- return err(`${e.message}. ${USAGE}`);
114
- }
115
-
116
- const { values, positionals } = parsed;
117
-
118
- // Exactly one positional, which must be a strictly positive integer
119
- // (no leading `+`, no float, no trailing junk).
120
- if (positionals.length === 0) {
121
- return err(`missing <run-id>. ${USAGE}`);
122
- }
123
- if (positionals.length > 1) {
124
- return err(`unexpected token ${JSON.stringify(positionals[1])}. ${USAGE}`);
125
- }
126
- const epic = parseStrictPositiveInt(positionals[0]);
127
- if (epic == null) {
128
- return err(
129
- `<run-id> must be a positive integer; got ${JSON.stringify(positionals[0])}. ${USAGE}`,
130
- );
131
- }
132
-
133
- // `--story` is the standard ticket flag; the shared parser already ran
134
- // `parseTicketId` on it, but we need to reject "supplied-but-invalid"
135
- // (token present, value null) — `parseStandardCliArgs` silently coerces
136
- // an unparseable value to null, which would otherwise look the same as
137
- // the "absent" case.
138
- const storyTokenIdx = argv.indexOf('--story');
139
- if (storyTokenIdx !== -1 && values.storyId == null) {
140
- const rawNext = argv[storyTokenIdx + 1];
141
- const target = rawNext === undefined ? 'a value' : JSON.stringify(rawNext);
142
- return err(`--story expects a positive integer; got ${target}. ${USAGE}`);
143
- }
144
-
145
- return {
146
- ok: true,
147
- epic,
148
- story: values.storyId,
149
- tempRoot: values.tempRoot,
150
- };
151
- }
152
-
153
- function formatDuration(ms) {
154
- if (ms == null) return '(no end)';
155
- if (!Number.isFinite(ms)) return '(no end)';
156
- if (ms < 1000) return `${ms}ms`;
157
- if (ms < 60_000) return `${(ms / 1000).toFixed(2)}s`;
158
- const mins = Math.floor(ms / 60_000);
159
- const secs = ((ms % 60_000) / 1000).toFixed(1);
160
- return `${mins}m${secs}s`;
161
- }
162
-
163
- function describeEvent(evt) {
164
- const ts = evt?.ts ?? '(no ts)';
165
- const kind = evt?.kind ?? '(no kind)';
166
- const phase = evt?.phase ? ` phase=${evt.phase}` : '';
167
- const category = evt?.category ? ` category=${evt.category}` : '';
168
- return `[${ts}] ${kind}${phase}${category}`;
169
- }
170
-
171
- /**
172
- * Render the span-tree to stdout. Pure formatter — every output line
173
- * goes through the local `println` helper (which delegates to
174
- * `process.stdout.write`). Logger is intentionally not used: the
175
- * viewer's contract is "dumb terminal compatible, parseable output".
176
- *
177
- * @param {{ run: number | null, stories: Array<object> }} tree
178
- * @param {{ storyFilter?: number | null }} [opts]
179
- * @returns {void}
180
- */
181
- export function renderTree(tree, opts = {}) {
182
- const filter = opts.storyFilter ?? null;
183
- println(`Run #${tree.run ?? '?'}`);
184
- const stories =
185
- filter == null ? tree.stories : tree.stories.filter((s) => s.id === filter);
186
-
187
- if (stories.length === 0) {
188
- println(' (no story spans)');
189
- return;
190
- }
191
-
192
- for (const story of stories) {
193
- const label = story.id == null ? '(no story id)' : `#${story.id}`;
194
- println(
195
- ` Story ${label} ${formatDuration(story.durationMs)} ` +
196
- `[${story.startedAt ?? '?'} → ${story.endedAt ?? '?'}]`,
197
- );
198
- if (story.events.length > 0) {
199
- println(
200
- ` (${story.events.length} story-level event${story.events.length === 1 ? '' : 's'})`,
201
- );
202
- for (const evt of story.events) {
203
- println(` ${describeEvent(evt)}`);
204
- }
205
- }
206
- }
207
- }
208
-
209
- function buildConfig(tempRoot) {
210
- if (tempRoot == null) return undefined;
211
- return { project: { paths: { tempRoot } } };
212
- }
213
-
214
- /**
215
- * CLI entry point. Returns the process exit code rather than calling
216
- * `process.exit` directly so the unit tests can drive it as a pure
217
- * function.
218
- *
219
- * @param {string[]} argv — argv slice (no node, no script)
220
- * @param {{ read?: Function, buildSpanTree?: Function }} [deps] — test seam
221
- * @returns {Promise<number>} exit code
222
- */
223
- export async function main(argv, deps = {}) {
224
- const parsed = parseArgs(argv);
225
- if (!parsed.ok) {
226
- println(parsed.error);
227
- return 1;
228
- }
229
- const { epic: runId, story, tempRoot } = parsed;
230
- const read = deps.read ?? signals.read;
231
- const buildSpanTree = deps.buildSpanTree ?? signals.buildSpanTree;
232
- const config = buildConfig(tempRoot);
233
-
234
- // The reader resolves the run directory through temp-paths
235
- // (`temp/run-<id>/`) keyed by the `run` id.
236
- const iter = read(
237
- story != null ? { run: runId, story, config } : { run: runId, config },
238
- );
239
-
240
- // Eagerly collect to detect the missing-file case before we start
241
- // printing — when the iterator yields nothing we want a friendly
242
- // message, not an empty tree.
243
- let tree;
244
- try {
245
- tree = await buildSpanTree(iter);
246
- } catch (err) {
247
- println(
248
- `signals: failed to read signals for run #${runId}: ${
249
- err instanceof Error ? err.message : String(err)
250
- }`,
251
- );
252
- return 1;
253
- }
254
-
255
- if (tree.stories.length === 0) {
256
- const scope = story != null ? ` (Story #${story})` : '';
257
- println(`No signals found for run #${runId}${scope}.`);
258
- return 0;
259
- }
260
-
261
- // The reader fans out across every Story file under the Epic when
262
- // `story` is omitted. If the requested Story filter doesn't match any
263
- // observed Story id, treat that as the missing-file case too.
264
- if (story != null && !tree.stories.some((s) => s.id === story)) {
265
- println(`No signals found for run #${runId} (Story #${story}).`);
266
- return 0;
267
- }
268
-
269
- // Pin the run id on the tree to the requested one — `buildSpanTree`
270
- // stores it in the `run` field from the first observed event, which is
271
- // normally the same, but if every event lacks that field the tree's
272
- // `run` would be `null` while we still know what was requested.
273
- if (tree.run == null) tree = { ...tree, run: runId };
274
-
275
- renderTree(tree, { storyFilter: story });
276
- return 0;
277
- }
278
-
279
- // Direct-CLI guard: when this module is executed (not imported by a
280
- // test), drive `main(process.argv.slice(2))` through the framework's
281
- // `runAsCli` helper. The helper enforces the canonical main-guard
282
- // shape (the enforcement test in `tests/enforcement/cli-wrapper.test.js`
283
- // fails any top-level script that bypasses it) and centralises the
284
- // fatal-error path.
285
- runAsCli(
286
- import.meta.url,
287
- async () => {
288
- const code = await main(process.argv.slice(2));
289
- if (code !== 0) process.exit(code);
290
- },
291
- {
292
- source: 'signals-view',
293
- usage: {
294
- invocation:
295
- 'node .agents/scripts/signals-view.js <run-id> [--story <id>] [--temp-root <path>]',
296
- summary:
297
- 'Render a run’s signal ledger as a tree, optionally narrowed to one Story.',
298
- flags: [
299
- ['<run-id>', 'Run id whose signals are read (required positional).'],
300
- ['--story <id>', 'Show only signals emitted for this Story.'],
301
- ['--temp-root <path>', 'Temp root holding the signal ledgers.'],
302
- ],
303
- },
304
- onError(err) {
305
- println(`signals-view: unexpected error: ${err?.message ?? err}`);
306
- process.exit(1);
307
- },
308
- },
309
- );