mandrel 2.0.0 → 2.2.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 +59 -28
- package/.agents/agents/acceptance-critic.md +20 -9
- package/.agents/agents/story-worker.md +45 -48
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +60 -46
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +33 -57
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +17 -19
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +10 -0
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
- package/.agents/schemas/agentrc.schema.json +35 -46
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +24 -68
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- package/.agents/scripts/lib/audit-suite/selector.js +323 -23
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
- package/.agents/scripts/lib/checks/index.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli-args.js +23 -2
- package/.agents/scripts/lib/close-validation/gates.js +13 -13
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
- package/.agents/scripts/lib/config/delivery-routing.js +7 -6
- package/.agents/scripts/lib/config/explain.js +10 -16
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +15 -25
- package/.agents/scripts/lib/config/quality.js +11 -14
- package/.agents/scripts/lib/config/runners.js +8 -21
- package/.agents/scripts/lib/config/temp-paths.js +18 -56
- package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
- package/.agents/scripts/lib/config-settings-schema.js +48 -22
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/duplicate-search.js +38 -7
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -189
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
- package/.agents/scripts/lib/git-utils.js +0 -14
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +0 -15
- package/.agents/scripts/lib/label-taxonomy.js +1 -12
- package/.agents/scripts/lib/observability/active-story-env.js +42 -163
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +2 -2
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
- package/.agents/scripts/lib/orchestration/change-set.js +103 -0
- package/.agents/scripts/lib/orchestration/code-review.js +70 -191
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
- package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
- package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
- package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
- package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
- package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -286
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +8 -7
- package/.agents/scripts/lib/story-body/story-body.js +81 -13
- package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/ticket-body-sections.js +0 -14
- package/.agents/scripts/lib/validation-evidence.js +31 -59
- package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/plan-context.js +38 -7
- package/.agents/scripts/plan-critics.js +203 -0
- package/.agents/scripts/plan-persist.js +145 -35
- package/.agents/scripts/plan-run-epilogue.js +83 -38
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +1 -11
- package/.agents/scripts/providers/github/mappers.js +5 -0
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -153
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/quality-preview.js +13 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +9 -10
- package/.agents/scripts/single-story-close.js +173 -18
- package/.agents/scripts/single-story-confirm-merge.js +288 -15
- package/.agents/scripts/single-story-init.js +6 -10
- package/.agents/scripts/stories-wave-tick.js +380 -53
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/update-ticket-state.js +8 -50
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
- package/.agents/skills/core/scope-triage/SKILL.md +3 -0
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/workflows/audit-architecture.md +3 -4
- package/.agents/workflows/audit-clean-code.md +4 -4
- package/.agents/workflows/audit-documentation.md +4 -5
- package/.agents/workflows/audit-lighthouse.md +8 -0
- package/.agents/workflows/audit-navigability.md +10 -0
- package/.agents/workflows/audit-performance.md +2 -3
- package/.agents/workflows/audit-quality.md +8 -9
- package/.agents/workflows/audit-security.md +1 -2
- package/.agents/workflows/audit-seo.md +10 -0
- package/.agents/workflows/audit-ux-ui.md +7 -0
- package/.agents/workflows/deliver.md +133 -45
- package/.agents/workflows/git-cleanup.md +2 -2
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
- package/.agents/workflows/helpers/code-review.md +14 -12
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +209 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +239 -19
- package/.agents/workflows/qa-assist.md +6 -6
- package/.agents/workflows/qa-explore.md +3 -3
- package/.agents/workflows/qa-run.md +1 -5
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +62 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +17 -9
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
- package/package.json +2 -2
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -53
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
- package/.agents/scripts/resolve-plan-run.js +0 -117
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
|
@@ -2,310 +2,36 @@
|
|
|
2
2
|
* planning-corpus.js — corpus-aware context for the standalone-Story
|
|
3
3
|
* planning path (Story #4432).
|
|
4
4
|
*
|
|
5
|
-
* `/plan --seed`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* project already has — the docs digest and the relevant Tech Spec
|
|
10
|
-
* sections of existing Epics that cover the touched area.
|
|
11
|
-
*
|
|
12
|
-
* This module assembles that inherited context (`corpusContext`) for
|
|
13
|
-
* `story-plan.js`'s `--emit-context` envelope:
|
|
5
|
+
* `/plan --seed` drafts a standalone Story from the seed, the body
|
|
6
|
+
* template, and a title-only duplicate scan. This module assembles the
|
|
7
|
+
* inherited context (`corpusContext`) for `story-plan.js`'s
|
|
8
|
+
* `--emit-context` envelope:
|
|
14
9
|
*
|
|
15
10
|
* 1. `docsDigest` — the same per-project docs digest
|
|
16
11
|
* `orchestration/docs-digest.js` builds for `/deliver` Story
|
|
17
12
|
* children, reused here so the standalone path gets the same
|
|
18
13
|
* compact outline instead of re-reading the whole docs set.
|
|
19
14
|
* `null` when `project.docsContextFiles` is not configured.
|
|
20
|
-
* 2. `relevantSections` —
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* list-scale payload with no `body`. Body content therefore requires an
|
|
27
|
-
* **explicit**, bounded per-candidate fetch via `provider.getEpic(id)` —
|
|
28
|
-
* never a silent assumption that the list response carries prose to score
|
|
29
|
-
* against.
|
|
30
|
-
*
|
|
31
|
-
* Relevance scoring reuses the same `tokenize` / `overlapScore` Jaccard
|
|
32
|
-
* primitives `duplicate-search.js` exports for Epic-dedupe and
|
|
33
|
-
* `story-plan.js` reuses for Story-dedupe — one matcher, three
|
|
34
|
-
* consumers, no forked scoring logic.
|
|
15
|
+
* 2. `relevantSections` — always `[]`. This field previously carried
|
|
16
|
+
* ranked Tech Spec excerpts mined from open Epics. v2.0.0 removed
|
|
17
|
+
* the Epic tier, and the provider's Epic-list surface was reduced
|
|
18
|
+
* to a `return []` stub, which made the entire ranking pipeline a
|
|
19
|
+
* permanent no-op. The pipeline has been removed; the field is
|
|
20
|
+
* retained so the envelope shape stays stable for its consumers.
|
|
35
21
|
*/
|
|
36
22
|
|
|
37
|
-
import { overlapScore, tokenize } from './duplicate-search.js';
|
|
38
|
-
import { Logger } from './Logger.js';
|
|
39
23
|
import { buildDocsDigest } from './orchestration/docs-digest.js';
|
|
40
|
-
import {
|
|
41
|
-
extractTicketSection,
|
|
42
|
-
hasTicketSection,
|
|
43
|
-
} from './ticket-body-sections.js';
|
|
44
|
-
|
|
45
|
-
/** Top-K Epics kept after the cheap title-only ranking pass. */
|
|
46
|
-
const DEFAULT_CORPUS_MAX_CANDIDATES = 5;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Bound on the explicit per-candidate body fetch. Keeps corpus-context
|
|
50
|
-
* assembly at a fixed, small number of GitHub reads regardless of how
|
|
51
|
-
* many open Epics the repo carries.
|
|
52
|
-
*/
|
|
53
|
-
export const DEFAULT_CORPUS_BODY_FETCH_TOP_K = 3;
|
|
54
|
-
|
|
55
|
-
/** Minimum Jaccard overlap for a section excerpt to be worth surfacing. */
|
|
56
|
-
const DEFAULT_CORPUS_MIN_SCORE = 0.1;
|
|
57
|
-
|
|
58
|
-
/** Max relevant-section excerpts returned in the envelope. */
|
|
59
|
-
const DEFAULT_CORPUS_MAX_SECTIONS = 5;
|
|
60
|
-
|
|
61
|
-
/** Excerpt length cap (chars) so one oversized Tech Spec doesn't blow the envelope. */
|
|
62
|
-
const EXCERPT_MAX_CHARS = 600;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Page-scan cap passed to `provider.getEpics({ state: 'open', pageCap })`.
|
|
66
|
-
* The corpus lookup only ever ranks the list down to a top-5 shortlist
|
|
67
|
-
* (`DEFAULT_CORPUS_MAX_CANDIDATES`), so there is no need to inherit
|
|
68
|
-
* `paginateRest`'s full default ceiling (50 pages / 5000 items) to build
|
|
69
|
-
* it — a bounded scan keeps this call a fixed, small number of GitHub
|
|
70
|
-
* reads regardless of how many open Epics the repo carries.
|
|
71
|
-
*/
|
|
72
|
-
const CORPUS_EPICS_PAGE_CAP = 5;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Rank open Epics by title-overlap with the seed. This is the cheap
|
|
76
|
-
* first pass over the list surface (title only — no `body`), used solely
|
|
77
|
-
* to pick the bounded top-K candidates
|
|
78
|
-
* worth an explicit body fetch. It is not the final relevance signal;
|
|
79
|
-
* `extractRelevantSections` re-scores against actual section content.
|
|
80
|
-
*
|
|
81
|
-
* @param {{ seed: string, epics: Array<{ id:number, title:string }>, maxResults?: number }} opts
|
|
82
|
-
* @returns {Array<{ id:number, title:string, score:number }>}
|
|
83
|
-
*/
|
|
84
|
-
export function rankCandidateEpics({
|
|
85
|
-
seed,
|
|
86
|
-
epics,
|
|
87
|
-
maxResults = DEFAULT_CORPUS_MAX_CANDIDATES,
|
|
88
|
-
}) {
|
|
89
|
-
if (!seed || typeof seed !== 'string') {
|
|
90
|
-
throw new Error('rankCandidateEpics: seed must be a non-empty string');
|
|
91
|
-
}
|
|
92
|
-
if (!Array.isArray(epics)) {
|
|
93
|
-
throw new Error('rankCandidateEpics: epics must be an array');
|
|
94
|
-
}
|
|
95
|
-
const seedTokens = tokenize(seed);
|
|
96
|
-
if (seedTokens.size === 0) return [];
|
|
97
|
-
|
|
98
|
-
const ranked = [];
|
|
99
|
-
for (const epic of epics) {
|
|
100
|
-
if (!epic || typeof epic.title !== 'string') continue;
|
|
101
|
-
const score = overlapScore(seedTokens, tokenize(epic.title));
|
|
102
|
-
ranked.push({
|
|
103
|
-
id: epic.id,
|
|
104
|
-
title: epic.title,
|
|
105
|
-
score: Number(score.toFixed(4)),
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
ranked.sort((a, b) => b.score - a.score);
|
|
109
|
-
return ranked.slice(0, maxResults);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Fetch full bodies for the top-K ranked candidates via the single-issue
|
|
114
|
-
* read (`provider.getEpic`), which — unlike the `getEpics` list mapper —
|
|
115
|
-
* does carry `body`. This is the explicit, bounded fetch the corpus
|
|
116
|
-
* lookup performs instead of assuming the list surface already has
|
|
117
|
-
* prose to score: a candidate never contributes a relevant section
|
|
118
|
-
* without this round-trip resolving its body.
|
|
119
|
-
*
|
|
120
|
-
* A single candidate's fetch failing (deleted issue, transient error) is
|
|
121
|
-
* non-fatal — it is dropped from the result rather than aborting corpus
|
|
122
|
-
* assembly for every other candidate. Failures are logged via
|
|
123
|
-
* `Logger.debug` (stderr) so they are visible under
|
|
124
|
-
* `AGENT_LOG_LEVEL=verbose` triage without violating the friction-
|
|
125
|
-
* telemetry posture in `.agents/instructions.md` §1.H of never silently
|
|
126
|
-
* swallowing an error.
|
|
127
|
-
*
|
|
128
|
-
* The bounded candidate slice is fetched concurrently
|
|
129
|
-
* (`Promise.allSettled`) rather than sequentially — `topK` is a fixed
|
|
130
|
-
* small ceiling (default 3), so this is a bounded fan-out, not an
|
|
131
|
-
* unbounded one, and it removes the serial network-latency stacking a
|
|
132
|
-
* plain `for`-await loop would otherwise incur.
|
|
133
|
-
*
|
|
134
|
-
* @param {{ provider: object, candidates: Array<{ id:number, title:string }>, topK?: number }} opts
|
|
135
|
-
* @returns {Promise<Array<{ id:number, title:string, body:string }>>}
|
|
136
|
-
*/
|
|
137
|
-
export async function fetchCandidateBodies({
|
|
138
|
-
provider,
|
|
139
|
-
candidates,
|
|
140
|
-
topK = DEFAULT_CORPUS_BODY_FETCH_TOP_K,
|
|
141
|
-
}) {
|
|
142
|
-
if (!provider || typeof provider.getEpic !== 'function') return [];
|
|
143
|
-
if (!Array.isArray(candidates) || candidates.length === 0) return [];
|
|
144
|
-
|
|
145
|
-
const bounded = candidates.slice(0, topK);
|
|
146
|
-
const settled = await Promise.allSettled(
|
|
147
|
-
bounded.map(async (candidate) => {
|
|
148
|
-
const epic = await provider.getEpic(candidate.id);
|
|
149
|
-
return {
|
|
150
|
-
id: candidate.id,
|
|
151
|
-
title: candidate.title ?? epic?.title ?? '',
|
|
152
|
-
body: epic?.body ?? '',
|
|
153
|
-
};
|
|
154
|
-
}),
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
const results = [];
|
|
158
|
-
for (let i = 0; i < settled.length; i += 1) {
|
|
159
|
-
const outcome = settled[i];
|
|
160
|
-
if (outcome.status === 'fulfilled') {
|
|
161
|
-
results.push(outcome.value);
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
// Best-effort: one candidate failing to resolve must not abort
|
|
165
|
-
// corpus-context assembly for the rest — but the failure is still
|
|
166
|
-
// surfaced for triage rather than silently swallowed.
|
|
167
|
-
Logger.debug(
|
|
168
|
-
`[planning-corpus] fetchCandidateBodies: candidate #${bounded[i].id} failed to resolve: ${outcome.reason?.message ?? outcome.reason}`,
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
return results;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Extract a scoreable excerpt from an Epic body: the managed Tech Spec
|
|
176
|
-
* region when present (the folded `## Delivery Slicing` section, #4324),
|
|
177
|
-
* otherwise the ideation lede — the prose before the first `##` heading
|
|
178
|
-
* — so a plain-body Epic still contributes something to score.
|
|
179
|
-
*
|
|
180
|
-
* @param {string} body
|
|
181
|
-
* @returns {{ kind:'techSpec'|'lede', content:string }}
|
|
182
|
-
*/
|
|
183
|
-
function extractScoreableExcerpt(body) {
|
|
184
|
-
if (hasTicketSection(body, 'techSpec')) {
|
|
185
|
-
return {
|
|
186
|
-
kind: 'techSpec',
|
|
187
|
-
content: extractTicketSection(body, 'techSpec'),
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
const lede = (body ?? '').split(/^##\s+/m)[0].trim();
|
|
191
|
-
return { kind: 'lede', content: lede };
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Rank existing-Epic body excerpts (Tech Spec section, or lede) by
|
|
196
|
-
* overlap with the seed. Reuses the same `tokenize` / `overlapScore`
|
|
197
|
-
* primitives as the title-ranking pass above and as
|
|
198
|
-
* `duplicate-search.js` — one matcher shared across every corpus /
|
|
199
|
-
* dedupe surface.
|
|
200
|
-
*
|
|
201
|
-
* @param {{ seed:string, epicBodies: Array<{ id:number, title:string, body:string }>, maxResults?: number, minScore?: number }} opts
|
|
202
|
-
* @returns {Array<{ epicId:number, epicTitle:string, section:'techSpec'|'lede', score:number, excerpt:string }>}
|
|
203
|
-
*/
|
|
204
|
-
export function extractRelevantSections({
|
|
205
|
-
seed,
|
|
206
|
-
epicBodies,
|
|
207
|
-
maxResults = DEFAULT_CORPUS_MAX_SECTIONS,
|
|
208
|
-
minScore = DEFAULT_CORPUS_MIN_SCORE,
|
|
209
|
-
}) {
|
|
210
|
-
if (!seed || typeof seed !== 'string') {
|
|
211
|
-
throw new Error('extractRelevantSections: seed must be a non-empty string');
|
|
212
|
-
}
|
|
213
|
-
if (!Array.isArray(epicBodies)) {
|
|
214
|
-
throw new Error('extractRelevantSections: epicBodies must be an array');
|
|
215
|
-
}
|
|
216
|
-
const seedTokens = tokenize(seed);
|
|
217
|
-
if (seedTokens.size === 0) return [];
|
|
218
|
-
|
|
219
|
-
const ranked = [];
|
|
220
|
-
for (const epic of epicBodies) {
|
|
221
|
-
if (!epic) continue;
|
|
222
|
-
const { kind, content } = extractScoreableExcerpt(epic.body ?? '');
|
|
223
|
-
if (!content) continue;
|
|
224
|
-
const score = overlapScore(seedTokens, tokenize(content));
|
|
225
|
-
if (score < minScore) continue;
|
|
226
|
-
ranked.push({
|
|
227
|
-
epicId: epic.id,
|
|
228
|
-
epicTitle: epic.title ?? null,
|
|
229
|
-
section: kind,
|
|
230
|
-
score: Number(score.toFixed(4)),
|
|
231
|
-
excerpt: content.slice(0, EXCERPT_MAX_CHARS),
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
ranked.sort((a, b) => b.score - a.score);
|
|
235
|
-
return ranked.slice(0, maxResults);
|
|
236
|
-
}
|
|
237
24
|
|
|
238
25
|
/**
|
|
239
26
|
* Assemble the `corpusContext` field of the story-plan context envelope.
|
|
240
|
-
* Pure orchestration over the three helpers above plus `buildDocsDigest`
|
|
241
|
-
* — no I/O beyond what `provider` and the docs-digest reader perform.
|
|
242
|
-
*
|
|
243
|
-
* `relevantSections` is `[]` (not an error) when the provider has no
|
|
244
|
-
* `getEpics` surface, the seed tokenizes to nothing, or no candidate
|
|
245
|
-
* clears `minScore` — the standalone-Story draft path degrades to
|
|
246
|
-
* exactly today's blank-slate behavior in that case.
|
|
247
27
|
*
|
|
248
28
|
* @param {{
|
|
249
|
-
* seed: string,
|
|
250
|
-
* provider?: { getEpics?: Function, getEpic?: Function },
|
|
251
29
|
* docsContextFiles?: string[],
|
|
252
30
|
* docsRoot?: string,
|
|
253
|
-
* maxCandidates?: number,
|
|
254
|
-
* bodyFetchTopK?: number,
|
|
255
|
-
* maxSections?: number,
|
|
256
|
-
* minScore?: number,
|
|
257
31
|
* }} opts
|
|
258
32
|
* @returns {Promise<{ docsDigest: string|null, relevantSections: Array<object> }>}
|
|
259
33
|
*/
|
|
260
|
-
export async function buildCorpusContext({
|
|
261
|
-
seed,
|
|
262
|
-
provider,
|
|
263
|
-
docsContextFiles,
|
|
264
|
-
docsRoot,
|
|
265
|
-
maxCandidates = DEFAULT_CORPUS_MAX_CANDIDATES,
|
|
266
|
-
bodyFetchTopK = DEFAULT_CORPUS_BODY_FETCH_TOP_K,
|
|
267
|
-
maxSections = DEFAULT_CORPUS_MAX_SECTIONS,
|
|
268
|
-
minScore = DEFAULT_CORPUS_MIN_SCORE,
|
|
269
|
-
}) {
|
|
34
|
+
export async function buildCorpusContext({ docsContextFiles, docsRoot }) {
|
|
270
35
|
const docsDigest = await buildDocsDigest({ docsContextFiles, docsRoot });
|
|
271
|
-
|
|
272
|
-
let relevantSections = [];
|
|
273
|
-
if (provider && typeof provider.getEpics === 'function') {
|
|
274
|
-
// A single candidate-listing call failing (rate limit, transient
|
|
275
|
-
// network error, provider outage) must not abort the whole
|
|
276
|
-
// `--emit-context` envelope build — degrade to an empty candidate
|
|
277
|
-
// list instead of letting the rejection propagate out of the
|
|
278
|
-
// caller's `Promise.all` and take down the rest of the envelope
|
|
279
|
-
// (body template, tech-stack summary, docs digest) with it.
|
|
280
|
-
let epics = [];
|
|
281
|
-
try {
|
|
282
|
-
epics = await provider.getEpics({
|
|
283
|
-
state: 'open',
|
|
284
|
-
pageCap: CORPUS_EPICS_PAGE_CAP,
|
|
285
|
-
});
|
|
286
|
-
} catch (err) {
|
|
287
|
-
Logger.debug(
|
|
288
|
-
`[planning-corpus] buildCorpusContext: provider.getEpics failed, degrading to an empty candidate list: ${err?.message ?? err}`,
|
|
289
|
-
);
|
|
290
|
-
epics = [];
|
|
291
|
-
}
|
|
292
|
-
const ranked = rankCandidateEpics({
|
|
293
|
-
seed,
|
|
294
|
-
epics: Array.isArray(epics) ? epics : [],
|
|
295
|
-
maxResults: maxCandidates,
|
|
296
|
-
});
|
|
297
|
-
const bodies = await fetchCandidateBodies({
|
|
298
|
-
provider,
|
|
299
|
-
candidates: ranked,
|
|
300
|
-
topK: bodyFetchTopK,
|
|
301
|
-
});
|
|
302
|
-
relevantSections = extractRelevantSections({
|
|
303
|
-
seed,
|
|
304
|
-
epicBodies: bodies,
|
|
305
|
-
maxResults: maxSections,
|
|
306
|
-
minScore,
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return { docsDigest, relevantSections };
|
|
36
|
+
return { docsDigest, relevantSections: [] };
|
|
311
37
|
}
|
|
@@ -52,8 +52,8 @@ const DEFAULT_LOGGER = {
|
|
|
52
52
|
* flag, finding/fix routing, and pretty-print of the blocker table.
|
|
53
53
|
*
|
|
54
54
|
* @param {object} opts
|
|
55
|
-
* @param {string} opts.scope Consumer surface — `'
|
|
56
|
-
* `'
|
|
55
|
+
* @param {string} opts.scope Consumer surface — `'story-close'`,
|
|
56
|
+
* `'npm-test'`, `'diagnose'`, etc.
|
|
57
57
|
* @param {boolean} [opts.autoFix=true] Forwarded to `runChecks`. Defaults
|
|
58
58
|
* to `true` because every wiring call site at this Story's level wants
|
|
59
59
|
* auto-correction (the `retro` consumer that needs `autoFix:false` does
|
|
@@ -142,7 +142,7 @@ export async function verifySurfaceMap(surfaceMap, gitPort, baseRef) {
|
|
|
142
142
|
* with no network when the ports are fakes.
|
|
143
143
|
*
|
|
144
144
|
* @param {{
|
|
145
|
-
*
|
|
145
|
+
* ticketNumber: number,
|
|
146
146
|
* githubPort: GithubPort,
|
|
147
147
|
* gitPort: GitPort,
|
|
148
148
|
* surfaceMap?: SurfaceMapEntry[],
|
|
@@ -162,7 +162,7 @@ export async function verifySurfaceMap(surfaceMap, gitPort, baseRef) {
|
|
|
162
162
|
*/
|
|
163
163
|
export async function hydrateQaContext(opts) {
|
|
164
164
|
const {
|
|
165
|
-
|
|
165
|
+
ticketNumber,
|
|
166
166
|
githubPort,
|
|
167
167
|
gitPort,
|
|
168
168
|
surfaceMap = [],
|
|
@@ -172,9 +172,9 @@ export async function hydrateQaContext(opts) {
|
|
|
172
172
|
fsImpl,
|
|
173
173
|
} = opts ?? {};
|
|
174
174
|
|
|
175
|
-
if (!Number.isInteger(
|
|
175
|
+
if (!Number.isInteger(ticketNumber)) {
|
|
176
176
|
throw new Error(
|
|
177
|
-
'hydrateQaContext: `
|
|
177
|
+
'hydrateQaContext: `ticketNumber` is required and must be an integer',
|
|
178
178
|
);
|
|
179
179
|
}
|
|
180
180
|
if (!githubPort || typeof githubPort.fetchIssue !== 'function') {
|
|
@@ -192,7 +192,7 @@ export async function hydrateQaContext(opts) {
|
|
|
192
192
|
);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
const epicIssue = await githubPort.fetchIssue(
|
|
195
|
+
const epicIssue = await githubPort.fetchIssue(ticketNumber);
|
|
196
196
|
const epic = {
|
|
197
197
|
number: epicIssue.number,
|
|
198
198
|
body: epicIssue.body ?? '',
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* + `schema`; `buildSpanTree` was a throwing placeholder.
|
|
11
11
|
* - Epic #1181 / Story #1440 / Task #1461 — placeholder replaced by
|
|
12
12
|
* the real export from `./span-tree.js`.
|
|
13
|
-
* - Story #1476 — `appendSignal` / `appendTrace`
|
|
14
|
-
*
|
|
13
|
+
* - Story #1476 — `appendSignal` / `appendTrace` re-exported here
|
|
14
|
+
* via `./write.js` so the gate scripts (and any
|
|
15
15
|
* new code) converge on `lib/signals/` instead of importing the
|
|
16
16
|
* writer directly from `lib/observability/`.
|
|
17
17
|
*
|
|
@@ -21,19 +21,6 @@
|
|
|
21
21
|
import { read } from './read.js';
|
|
22
22
|
import * as schema from './schema.js';
|
|
23
23
|
import { buildSpanTree } from './span-tree.js';
|
|
24
|
-
import {
|
|
25
|
-
appendEpicSignal,
|
|
26
|
-
appendSignal,
|
|
27
|
-
appendTrace,
|
|
28
|
-
forEachLine,
|
|
29
|
-
} from './write.js';
|
|
24
|
+
import { appendSignal, appendTrace, forEachLine } from './write.js';
|
|
30
25
|
|
|
31
|
-
export {
|
|
32
|
-
appendEpicSignal,
|
|
33
|
-
appendSignal,
|
|
34
|
-
appendTrace,
|
|
35
|
-
buildSpanTree,
|
|
36
|
-
forEachLine,
|
|
37
|
-
read,
|
|
38
|
-
schema,
|
|
39
|
-
};
|
|
26
|
+
export { appendSignal, appendTrace, buildSpanTree, forEachLine, read, schema };
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Streaming signals reader (Epic #1181 / Story #1438 / Task #1459).
|
|
3
3
|
*
|
|
4
|
-
* Provides one async-iterator entry point — `read({
|
|
4
|
+
* Provides one async-iterator entry point — `read({ run, story?, kind?,
|
|
5
5
|
* config? })` — that consumers use instead of opening the NDJSON file
|
|
6
6
|
* themselves. The reader streams line-by-line so callers never load the
|
|
7
7
|
* full file into memory (50MB+ traces stay under file size in RSS).
|
|
8
8
|
*
|
|
9
9
|
* On-disk layout (resolved via `lib/config/temp-paths.js`):
|
|
10
10
|
*
|
|
11
|
-
* <tempRoot>/
|
|
11
|
+
* <tempRoot>/run-<run>/story-<story>/signals.ndjson
|
|
12
12
|
*
|
|
13
13
|
* When `story` is omitted, the reader fans out across every
|
|
14
|
-
* `story-<id>/signals.ndjson` under `<tempRoot>/
|
|
15
|
-
*
|
|
14
|
+
* `story-<id>/signals.ndjson` under `<tempRoot>/run-<run>/`. When the
|
|
15
|
+
* run directory is missing, the iterator yields nothing (consumers
|
|
16
16
|
* treat absence as "no signals yet").
|
|
17
17
|
*
|
|
18
18
|
* ## Filter semantics
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* argument are yielded. Filtering happens after the per-line JSON
|
|
22
22
|
* parse + envelope guard (see `lib/signals/schema.js`).
|
|
23
23
|
* - `story` — narrows to a single Story's stream; otherwise we walk
|
|
24
|
-
* every Story directory under the
|
|
24
|
+
* every Story directory under the run.
|
|
25
25
|
*
|
|
26
26
|
* ## Warn-once policy (AC #3)
|
|
27
27
|
*
|
|
@@ -46,8 +46,8 @@ import fs from 'node:fs/promises';
|
|
|
46
46
|
import path from 'node:path';
|
|
47
47
|
import { createInterface } from 'node:readline';
|
|
48
48
|
import {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
runArtifactPath,
|
|
50
|
+
runTempDir,
|
|
51
51
|
signalsFile,
|
|
52
52
|
storyTempDir,
|
|
53
53
|
} from '../config/temp-paths.js';
|
|
@@ -145,43 +145,43 @@ async function* streamFile(target, kindFilter) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
/**
|
|
148
|
-
* List every `stories/story-<id>/signals.ndjson` path under the
|
|
149
|
-
* `<tempRoot>/
|
|
150
|
-
*
|
|
148
|
+
* List every `stories/story-<id>/signals.ndjson` path under the run's
|
|
149
|
+
* `<tempRoot>/run-<run>/` directory. Returns an empty array when the
|
|
150
|
+
* run directory is missing.
|
|
151
151
|
*
|
|
152
152
|
* The returned paths are sorted by Story ID ascending so the iterator's
|
|
153
153
|
* output is stable across runs.
|
|
154
154
|
*
|
|
155
155
|
* Story #2940 nested per-Story directories under a `stories/` segment.
|
|
156
|
-
*
|
|
156
|
+
* Run-level signals continue to live at the run root.
|
|
157
157
|
*
|
|
158
|
-
* @param {number}
|
|
158
|
+
* @param {number} run
|
|
159
159
|
* @param {object | undefined} config
|
|
160
160
|
* @returns {Promise<string[]>}
|
|
161
161
|
*/
|
|
162
|
-
async function
|
|
163
|
-
const
|
|
164
|
-
let
|
|
162
|
+
async function listRunStorySignalsFiles(run, config) {
|
|
163
|
+
const runDir = runTempDir(run, config);
|
|
164
|
+
let runEntries;
|
|
165
165
|
try {
|
|
166
|
-
|
|
166
|
+
runEntries = await fs.readdir(runDir, { withFileTypes: true });
|
|
167
167
|
} catch {
|
|
168
168
|
return [];
|
|
169
169
|
}
|
|
170
|
-
let
|
|
170
|
+
let hasRunLevelSignals = false;
|
|
171
171
|
let hasStoriesDir = false;
|
|
172
|
-
for (const ent of
|
|
172
|
+
for (const ent of runEntries) {
|
|
173
173
|
if (ent.isDirectory() && ent.name === 'stories') {
|
|
174
174
|
hasStoriesDir = true;
|
|
175
175
|
} else if (ent.isFile() && ent.name === 'signals.ndjson') {
|
|
176
176
|
// Story #1430 — wave-runner lifecycle signals land here.
|
|
177
|
-
|
|
177
|
+
hasRunLevelSignals = true;
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
const storyIds = [];
|
|
181
181
|
if (hasStoriesDir) {
|
|
182
182
|
let storyEntries;
|
|
183
183
|
try {
|
|
184
|
-
storyEntries = await fs.readdir(path.join(
|
|
184
|
+
storyEntries = await fs.readdir(path.join(runDir, 'stories'), {
|
|
185
185
|
withFileTypes: true,
|
|
186
186
|
});
|
|
187
187
|
} catch {
|
|
@@ -195,43 +195,43 @@ async function listEpicStorySignalsFiles(epic, config) {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
storyIds.sort((a, b) => a - b);
|
|
198
|
-
// Yield
|
|
198
|
+
// Yield run-level signals first (wave-start precedes per-Story friction),
|
|
199
199
|
// then walk the per-Story streams in ascending ID order.
|
|
200
|
-
const targets =
|
|
201
|
-
? [
|
|
200
|
+
const targets = hasRunLevelSignals
|
|
201
|
+
? [runArtifactPath(run, 'signals.ndjson', config)]
|
|
202
202
|
: [];
|
|
203
203
|
for (const sid of storyIds) {
|
|
204
|
-
targets.push(path.join(storyTempDir(
|
|
204
|
+
targets.push(path.join(storyTempDir(run, sid, config), 'signals.ndjson'));
|
|
205
205
|
}
|
|
206
206
|
return targets;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
/**
|
|
210
|
-
* Stream every event matching `{
|
|
211
|
-
* configured `tempRoot`'s `
|
|
210
|
+
* Stream every event matching `{ run, story?, kind? }` from the
|
|
211
|
+
* configured `tempRoot`'s `run-<run>/[story-<story>/]signals.ndjson`
|
|
212
212
|
* file(s).
|
|
213
213
|
*
|
|
214
214
|
* Returns an async iterable so callers can `for await` over it without
|
|
215
215
|
* buffering. The reader is **streaming** — peak memory stays below the
|
|
216
216
|
* file size for any input.
|
|
217
217
|
*
|
|
218
|
-
* @param {{
|
|
218
|
+
* @param {{ run: number, story?: number, kind?: string, config?: object }} args
|
|
219
219
|
* @returns {AsyncGenerator<object>}
|
|
220
220
|
*
|
|
221
221
|
* @example
|
|
222
|
-
* for await (const evt of read({
|
|
223
|
-
* for await (const evt of read({
|
|
222
|
+
* for await (const evt of read({ run: 1181 })) { ... }
|
|
223
|
+
* for await (const evt of read({ run: 1181, story: 1438, kind: 'friction' })) { ... }
|
|
224
224
|
*/
|
|
225
225
|
export async function* read(args) {
|
|
226
226
|
if (args == null || typeof args !== 'object') {
|
|
227
227
|
throw new TypeError(
|
|
228
|
-
`signals/read: args must be an object with at minimum {
|
|
228
|
+
`signals/read: args must be an object with at minimum { run }; got ${args}`,
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
|
-
const {
|
|
232
|
-
if (!isPositiveInt(
|
|
231
|
+
const { run, story, kind, config } = args;
|
|
232
|
+
if (!isPositiveInt(run)) {
|
|
233
233
|
throw new RangeError(
|
|
234
|
-
`signals/read:
|
|
234
|
+
`signals/read: run must be a positive integer (got ${run})`,
|
|
235
235
|
);
|
|
236
236
|
}
|
|
237
237
|
if (story !== undefined && !isPositiveInt(story)) {
|
|
@@ -259,8 +259,8 @@ export async function* read(args) {
|
|
|
259
259
|
const kindFilter = kind ?? null;
|
|
260
260
|
const targets =
|
|
261
261
|
story !== undefined
|
|
262
|
-
? [signalsFile(
|
|
263
|
-
: await
|
|
262
|
+
? [signalsFile(run, story, config)]
|
|
263
|
+
: await listRunStorySignalsFiles(run, config);
|
|
264
264
|
|
|
265
265
|
for (const target of targets) {
|
|
266
266
|
yield* streamFile(target, kindFilter);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* shape guards (Epic #1181 / Story #1438 / Task #1458).
|
|
4
4
|
*
|
|
5
5
|
* Both writers (`lib/observability/signals-writer.js` callers) and
|
|
6
|
-
* readers (`lib/signals/read.js`,
|
|
6
|
+
* readers (`lib/signals/read.js`, the retro's gather-signals phase)
|
|
7
7
|
* import their field names from this module so the on-disk NDJSON shape
|
|
8
8
|
* stays under one schema.
|
|
9
9
|
*
|
|
@@ -19,15 +19,12 @@
|
|
|
19
19
|
*
|
|
20
20
|
* Signals-writer `appendSignal` call sites:
|
|
21
21
|
* - `friction` — quality-gate / runtime friction (diagnose-friction.js,
|
|
22
|
-
* lib/gates/friction.js
|
|
23
|
-
* worktree-reap.js, lifecycle-emit.js)
|
|
22
|
+
* lib/gates/friction.js)
|
|
24
23
|
* - `acceptance-eval` — acceptance-eval.js per-criterion terminus signal.
|
|
25
|
-
* - `wave-start` / `wave-complete` — `
|
|
26
|
-
*
|
|
27
|
-
* `wave-start` also anchors span-tree Story spans)
|
|
24
|
+
* - `wave-start` / `wave-complete` — wave-window anchors; `wave-start` also
|
|
25
|
+
* anchors span-tree Story spans
|
|
28
26
|
* - `wave-end` — span-tree pairing anchor (retained for span-tree)
|
|
29
|
-
* - `state-transition` — notification-derived window anchor
|
|
30
|
-
* waveParallelism bucketer.
|
|
27
|
+
* - `state-transition` — notification-derived window anchor.
|
|
31
28
|
*
|
|
32
29
|
* The `dispatched` kind was deleted in the Epic #4406 signal-contract
|
|
33
30
|
* cutover — it had no live emitter and no consumer.
|
|
@@ -80,9 +77,9 @@ export const EVENT_KINDS = Object.freeze({
|
|
|
80
77
|
FRICTION: 'friction',
|
|
81
78
|
TRACE: 'trace',
|
|
82
79
|
// Wave-window forensics signals: `wave-start` / `wave-end` anchor the
|
|
83
|
-
// span-tree's Story spans
|
|
84
|
-
//
|
|
85
|
-
//
|
|
80
|
+
// span-tree's Story spans. Story #4545 deleted the `waveParallelism`
|
|
81
|
+
// consumer (perf-aggregator) with the execution-analysis surface; these
|
|
82
|
+
// kinds stay as the span-tree's anchors.
|
|
86
83
|
WAVE_START: 'wave-start',
|
|
87
84
|
WAVE_END: 'wave-end',
|
|
88
85
|
WAVE_COMPLETE: 'wave-complete',
|
|
@@ -192,15 +192,15 @@ function bootstrapStoryWindow(storyNode, ts) {
|
|
|
192
192
|
* from `buildSpanTree` so the outer function's CRAP score stays under
|
|
193
193
|
* the new-method ceiling.
|
|
194
194
|
*
|
|
195
|
-
* @param {object} state — mutable accumulator (stories, tasksByStory,
|
|
195
|
+
* @param {object} state — mutable accumulator (stories, tasksByStory, run)
|
|
196
196
|
* @param {unknown} evt
|
|
197
197
|
* @returns {void}
|
|
198
198
|
*/
|
|
199
199
|
function ingestEvent(state, evt) {
|
|
200
200
|
if (evt == null || typeof evt !== 'object') return;
|
|
201
201
|
|
|
202
|
-
const
|
|
203
|
-
if (state.
|
|
202
|
+
const eRun = epicOf(evt);
|
|
203
|
+
if (state.run == null && eRun != null) state.run = eRun;
|
|
204
204
|
|
|
205
205
|
const ts = tsOf(evt);
|
|
206
206
|
const sid = storyOf(evt);
|
|
@@ -258,11 +258,11 @@ function finalizeStory(state, story) {
|
|
|
258
258
|
* Build the span tree from an async iterable of signal events.
|
|
259
259
|
*
|
|
260
260
|
* @param {AsyncIterable<object> | Iterable<object>} iter
|
|
261
|
-
* @returns {Promise<{
|
|
261
|
+
* @returns {Promise<{ run: number | null, stories: Array<object> }>}
|
|
262
262
|
*
|
|
263
263
|
* @example
|
|
264
264
|
* import { read, buildSpanTree } from './lib/signals/index.js';
|
|
265
|
-
* const tree = await buildSpanTree(read({
|
|
265
|
+
* const tree = await buildSpanTree(read({ run: 1181 }));
|
|
266
266
|
*/
|
|
267
267
|
export async function buildSpanTree(iter) {
|
|
268
268
|
if (iter == null || typeof iter !== 'object') {
|
|
@@ -274,7 +274,7 @@ export async function buildSpanTree(iter) {
|
|
|
274
274
|
// Accumulator state. `stories` keys are `storyKeyOf(sid)` strings;
|
|
275
275
|
// `tasksByStory` mirrors that and holds per-task sub-maps.
|
|
276
276
|
const state = {
|
|
277
|
-
|
|
277
|
+
run: null,
|
|
278
278
|
stories: new Map(),
|
|
279
279
|
tasksByStory: new Map(),
|
|
280
280
|
};
|
|
@@ -287,5 +287,5 @@ export async function buildSpanTree(iter) {
|
|
|
287
287
|
storyEntries.sort(compareNodeIds);
|
|
288
288
|
for (const story of storyEntries) finalizeStory(state, story);
|
|
289
289
|
|
|
290
|
-
return {
|
|
290
|
+
return { run: state.run, stories: storyEntries };
|
|
291
291
|
}
|