mandrel 2.31.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 (250) 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/git-conventions-reference.md +27 -27
  10. package/.agents/rules/git-conventions.md +4 -2
  11. package/.agents/rules/known-tooling-behavior.md +66 -30
  12. package/.agents/rules/testing-standards.md +35 -71
  13. package/.agents/runtime-deps.json +0 -1
  14. package/.agents/schemas/agentrc.schema.json +1939 -1400
  15. package/.agents/schemas/lifecycle/README.md +21 -14
  16. package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
  17. package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
  18. package/.agents/scripts/README.md +7 -29
  19. package/.agents/scripts/apply-quality-bootstrap.js +27 -34
  20. package/.agents/scripts/bootstrap.js +28 -26
  21. package/.agents/scripts/check-baseline-drift.js +73 -13
  22. package/.agents/scripts/check-baseline-scope.js +362 -0
  23. package/.agents/scripts/check-dead-exports.js +9 -1
  24. package/.agents/scripts/check-gherkin-corpus.js +508 -0
  25. package/.agents/scripts/check-knip-entries.js +136 -0
  26. package/.agents/scripts/check-lifecycle-lint.js +36 -112
  27. package/.agents/scripts/check-schema-references.js +1 -1
  28. package/.agents/scripts/diagnose-friction.js +7 -4
  29. package/.agents/scripts/generate-config-docs.js +263 -171
  30. package/.agents/scripts/install-matrix-assert.js +0 -1
  31. package/.agents/scripts/lib/ITicketingProvider.js +0 -58
  32. package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
  33. package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
  34. package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
  35. package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
  36. package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
  37. package/.agents/scripts/lib/baselines/envelope.js +41 -60
  38. package/.agents/scripts/lib/baselines/git-base.js +30 -37
  39. package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
  40. package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
  41. package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
  42. package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
  43. package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
  44. package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
  45. package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
  46. package/.agents/scripts/lib/bdd-step-index.js +326 -0
  47. package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
  48. package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
  49. package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
  50. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
  51. package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
  52. package/.agents/scripts/lib/checks/loop-health.js +9 -37
  53. package/.agents/scripts/lib/child-exec.js +193 -0
  54. package/.agents/scripts/lib/cli/standard-args.js +1 -1
  55. package/.agents/scripts/lib/cli-args.js +64 -0
  56. package/.agents/scripts/lib/close-validation/gates.js +2 -2
  57. package/.agents/scripts/lib/close-validation/runner.js +3 -3
  58. package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
  59. package/.agents/scripts/lib/config/commands.js +3 -5
  60. package/.agents/scripts/lib/config/explain.js +5 -7
  61. package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
  62. package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
  63. package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
  64. package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
  65. package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
  66. package/.agents/scripts/lib/config/gates/index.js +5 -2
  67. package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
  68. package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
  69. package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
  70. package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
  71. package/.agents/scripts/lib/config/gates/shared.js +58 -9
  72. package/.agents/scripts/lib/config/github.js +0 -1
  73. package/.agents/scripts/lib/config/limits.js +3 -48
  74. package/.agents/scripts/lib/config/qa.js +105 -0
  75. package/.agents/scripts/lib/config/temp-paths.js +6 -5
  76. package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
  77. package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
  78. package/.agents/scripts/lib/config-settings-schema.js +386 -39
  79. package/.agents/scripts/lib/crap-baseline-join.js +126 -9
  80. package/.agents/scripts/lib/crap-utils.js +84 -520
  81. package/.agents/scripts/lib/dead-exports-knip.js +79 -10
  82. package/.agents/scripts/lib/degraded-mode.js +2 -2
  83. package/.agents/scripts/lib/doc-tiers.js +3 -3
  84. package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
  85. package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
  86. package/.agents/scripts/lib/fs-walk.js +52 -0
  87. package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
  88. package/.agents/scripts/lib/git-utils.js +16 -36
  89. package/.agents/scripts/lib/knip-entry-sync.js +469 -0
  90. package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
  91. package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
  92. package/.agents/scripts/lib/observability/signal-validator.js +5 -85
  93. package/.agents/scripts/lib/observability/signals-writer.js +19 -62
  94. package/.agents/scripts/lib/observability/source-classifier.js +5 -7
  95. package/.agents/scripts/lib/observability/terse-result.js +3 -3
  96. package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
  97. package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
  98. package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
  99. package/.agents/scripts/lib/orchestration/code-review.js +2 -2
  100. package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
  101. package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
  102. package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
  103. package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
  104. package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
  105. package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
  106. package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
  107. package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
  108. package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
  109. package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
  110. package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
  111. package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
  112. package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
  113. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
  114. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
  115. package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
  116. package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
  117. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
  118. package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
  119. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
  120. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
  121. package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
  122. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
  123. package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
  124. package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
  125. package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
  126. package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
  127. package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
  128. package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
  129. package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
  130. package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
  131. package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
  132. package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
  133. package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
  134. package/.agents/scripts/lib/signals/detectors/common.js +1 -1
  135. package/.agents/scripts/lib/signals/index.js +8 -6
  136. package/.agents/scripts/lib/signals/schema.js +20 -25
  137. package/.agents/scripts/lib/signals/write.js +8 -8
  138. package/.agents/scripts/lib/story-body/story-body.js +12 -59
  139. package/.agents/scripts/lib/temp-retention.js +1 -1
  140. package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
  141. package/.agents/scripts/lib/ticket-body-sections.js +4 -5
  142. package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
  143. package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
  144. package/.agents/scripts/lib/worktree-manager.js +2 -3
  145. package/.agents/scripts/lint-label-vocabulary.js +2 -24
  146. package/.agents/scripts/pr-watch-with-update.js +7 -5
  147. package/.agents/scripts/providers/github/cache.js +2 -2
  148. package/.agents/scripts/providers/github/comments.js +6 -28
  149. package/.agents/scripts/providers/github/compose.js +0 -15
  150. package/.agents/scripts/providers/github/errors.js +10 -27
  151. package/.agents/scripts/providers/github/request-helpers.js +1 -2
  152. package/.agents/scripts/providers/github/sub-issues.js +10 -218
  153. package/.agents/scripts/providers/github.js +4 -7
  154. package/.agents/scripts/prune-baseline-orphans.js +181 -0
  155. package/.agents/scripts/resolve-stories.js +0 -2
  156. package/.agents/scripts/run-lint.js +61 -61
  157. package/.agents/scripts/run-test-profile.js +6 -6
  158. package/.agents/scripts/run-verify.js +48 -30
  159. package/.agents/scripts/single-story-close.js +20 -0
  160. package/.agents/scripts/single-story-init.js +12 -35
  161. package/.agents/scripts/update-dead-exports-baseline.js +321 -0
  162. package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
  163. package/.agents/skills/skills.index.json +1 -11
  164. package/.agents/workflows/audit-documentation.md +5 -6
  165. package/.agents/workflows/audit-to-stories.md +2 -2
  166. package/.agents/workflows/helpers/audit-lens-core.md +11 -12
  167. package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
  168. package/.agents/workflows/helpers/code-review.md +3 -8
  169. package/.agents/workflows/helpers/deliver-reference.md +2 -1
  170. package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
  171. package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
  172. package/.agents/workflows/mandrel-update.md +10 -10
  173. package/.agents/workflows/qa-assist.md +15 -20
  174. package/.agents/workflows/qa-explore.md +9 -8
  175. package/README.md +1 -1
  176. package/docs/CHANGELOG.md +42 -0
  177. package/lib/migrations/index.js +2 -0
  178. package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
  179. package/package.json +12 -3
  180. package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
  181. package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
  182. package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
  183. package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
  184. package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
  185. package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
  186. package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
  187. package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
  188. package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
  189. package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
  190. package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
  191. package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
  192. package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
  193. package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
  194. package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
  195. package/.agents/scripts/check-gherkin-placeholders.js +0 -663
  196. package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
  197. package/.agents/scripts/lib/audit-suite/cli.js +0 -64
  198. package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
  199. package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
  200. package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
  201. package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
  202. package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
  203. package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
  204. package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
  205. package/.agents/scripts/lib/crap-baseline-index.js +0 -46
  206. package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
  207. package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
  208. package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
  209. package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
  210. package/.agents/scripts/lib/mutation/config-detector.js +0 -119
  211. package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
  212. package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
  213. package/.agents/scripts/lib/observability/active-story-env.js +0 -170
  214. package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
  215. package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
  216. package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
  217. package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
  218. package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
  219. package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
  220. package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
  221. package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
  222. package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
  223. package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
  224. package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
  225. package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
  226. package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
  227. package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
  228. package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
  229. package/.agents/scripts/lib/planning-corpus.js +0 -37
  230. package/.agents/scripts/lib/qa/coverage-report.js +0 -181
  231. package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
  232. package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
  233. package/.agents/scripts/lib/signals/detectors/index.js +0 -14
  234. package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
  235. package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
  236. package/.agents/scripts/lib/signals/read.js +0 -268
  237. package/.agents/scripts/lib/signals/span-tree.js +0 -291
  238. package/.agents/scripts/lib/story-lifecycle.js +0 -194
  239. package/.agents/scripts/lib/story-plan.js +0 -379
  240. package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
  241. package/.agents/scripts/lib/util/phase-timer.js +0 -163
  242. package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
  243. package/.agents/scripts/lint-baseline.js +0 -507
  244. package/.agents/scripts/providers/github/prs.js +0 -103
  245. package/.agents/scripts/signals-view.js +0 -309
  246. package/.agents/scripts/story-plan.js +0 -370
  247. package/.agents/scripts/sync-branch-from-base.js +0 -149
  248. package/.agents/scripts/validate-docs-freshness.js +0 -314
  249. package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
  250. 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
- );