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.
- package/.agents/README.md +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +4 -4
- package/.agents/docs/agentrc-reference.json +61 -57
- package/.agents/docs/configuration.md +274 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +0 -1
- package/.agents/schemas/agentrc.schema.json +1939 -1400
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +7 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +136 -0
- package/.agents/scripts/check-lifecycle-lint.js +36 -112
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-entry-sync.js +469 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/story-body.js +12 -59
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +0 -2
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +1 -11
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +2 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +2 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +42 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +12 -3
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- 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
|
-
);
|