mandrel 2.0.0 → 2.1.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 +9 -7
- package/.agents/agents/story-worker.md +41 -46
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +51 -44
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +32 -56
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +14 -16
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/agentrc.schema.json +34 -45
- 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 +22 -66
- 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/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 +31 -13
- 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/code-review.js +58 -168
- 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 +114 -24
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +3 -7
- 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 -75
- 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/planning/authoring-context.js +41 -40
- 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 +3 -12
- 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 +15 -32
- 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 +6 -5
- package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -3
- 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/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 -6
- 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/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 +79 -4
- 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 +98 -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 +21 -13
- package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
- package/.agents/workflows/helpers/code-review.md +12 -10
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +193 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +184 -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 +40 -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 +12 -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/package.json +1 -1
- 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
|
@@ -82,27 +82,13 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
82
82
|
'epic-run-progress',
|
|
83
83
|
'epic-plan-state',
|
|
84
84
|
'parked-follow-ons',
|
|
85
|
-
// Story #566 — per-phase wall-clock summary posted by single-story-close.js
|
|
86
|
-
// and consumed by analyze-execution / perf tooling to surface median /
|
|
87
|
-
// p95 phase timings across completed stories.
|
|
85
|
+
// Story #566 — per-phase wall-clock summary posted by single-story-close.js.
|
|
88
86
|
'phase-timings',
|
|
89
87
|
// Story #831 — story-init upserts a `story-init` comment that
|
|
90
88
|
// surfaces `dependenciesInstalled` (and the underlying installStatus) so
|
|
91
89
|
// downstream workflow steps don't have to infer install state from
|
|
92
90
|
// node_modules presence.
|
|
93
91
|
'story-init',
|
|
94
|
-
// Story #908 — /deliver upserts a `story-run-progress` snapshot
|
|
95
|
-
// on each Story per Task transition. The /deliver aggregator and
|
|
96
|
-
// `analyze-execution.js` both read this comment to derive
|
|
97
|
-
// Story-level state without re-fetching ticket labels.
|
|
98
|
-
'story-run-progress',
|
|
99
|
-
// Story #1123 — analyze-execution.js upserts perf summaries at close
|
|
100
|
-
// time. Story-mode posts `story-perf-summary` on each Story; Epic-mode
|
|
101
|
-
// posts `epic-perf-report` on the Epic. Both replace the legacy
|
|
102
|
-
// per-Task `friction` fan-out and the standalone `phase-timings`
|
|
103
|
-
// surface (Epic #1030).
|
|
104
|
-
'story-perf-summary',
|
|
105
|
-
'epic-perf-report',
|
|
106
92
|
// Story #2128 — Phase 6 Epic Clarity Gate (CLI retired). Historical
|
|
107
93
|
// `clarity-gate-update` comments may still exist on older tickets.
|
|
108
94
|
'clarity-gate-update',
|
|
@@ -151,19 +137,13 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
151
137
|
// the documented remediation actually executable
|
|
152
138
|
// (assertValidStructuredCommentType would otherwise throw).
|
|
153
139
|
'wave-stall',
|
|
154
|
-
// Story #
|
|
155
|
-
// comment
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
//
|
|
161
|
-
// comment on the Epic at lease-acquire time, recording the claiming
|
|
162
|
-
// operator and the claim timestamp. `/plan` emits no
|
|
163
|
-
// `story.heartbeat`, so this claim-time is the liveness signal that
|
|
164
|
-
// makes the documented `--steal` contract decidable: a foreign claim
|
|
165
|
-
// older than the lease TTL is reclaimed automatically; a fresh one
|
|
166
|
-
// refuses with the claim age. One entry per Epic; re-acquires upsert
|
|
140
|
+
// Story #4019 — the plan-tier lease guard upserts a `plan-lease`
|
|
141
|
+
// comment at lease-acquire time, recording the claiming operator and the
|
|
142
|
+
// claim timestamp. Nothing emits a per-run liveness beat, so this
|
|
143
|
+
// claim-time is the only age signal a reader has when reasoning about the
|
|
144
|
+
// documented `--steal` contract. The guard itself fails closed regardless
|
|
145
|
+
// (it anchors liveness to `now`), so a stranded claim is cleared with
|
|
146
|
+
// `--steal`, not by TTL expiry. One entry per ticket; re-acquires upsert
|
|
167
147
|
// in place.
|
|
168
148
|
'plan-lease',
|
|
169
149
|
// Story #4415 (Epic #4406) — the feedback-loop graduators
|
|
@@ -183,6 +163,13 @@ export const STRUCTURED_COMMENT_TYPES = Object.freeze([
|
|
|
183
163
|
// v2 Stage 3 — flat Story persist checkpoint on every created Story
|
|
184
164
|
// (replaces epic-plan-state for new plans). plan-summary stays primary-only.
|
|
185
165
|
'story-plan-state',
|
|
166
|
+
// Story #4535 — `plan-persist.js` upserts a `superseded-by` comment on
|
|
167
|
+
// each `/plan --tickets` source issue at persist time, naming the single
|
|
168
|
+
// Story that claims it (plus any per-supersede note the plan authored),
|
|
169
|
+
// immediately before closing the issue as `not_planned`. Keying off this
|
|
170
|
+
// marker rather than a bare `postComment` is what makes a re-run
|
|
171
|
+
// non-double-commenting. One entry per source issue.
|
|
172
|
+
'superseded-by',
|
|
186
173
|
]);
|
|
187
174
|
|
|
188
175
|
export const WAVE_TYPE_PATTERN = WAVE_MARKER_RE;
|
|
@@ -268,8 +255,8 @@ export function structuredCommentMarker(type, attrs = null) {
|
|
|
268
255
|
* recent `findStructuredComment` / `upsertStructuredComment` call.
|
|
269
256
|
* Story #1795 — every Epic run owns its process for the duration of
|
|
270
257
|
* the run, so a single seed-then-reuse window saves one
|
|
271
|
-
* `getTicketComments` per repeat upsert on the hot path (
|
|
272
|
-
* `
|
|
258
|
+
* `getTicketComments` per repeat upsert on the hot path (`story-init`,
|
|
259
|
+
* `verification-results`, etc).
|
|
273
260
|
*
|
|
274
261
|
* Lifecycle:
|
|
275
262
|
* - First call to `findStructuredComment(provider, t, type, attrs)`
|
|
@@ -355,8 +342,8 @@ export function structuredCommentCacheKey(ticketId, type, attrs) {
|
|
|
355
342
|
* array returned by the most recent `provider.getTicketComments(ticketId)`
|
|
356
343
|
* call. Story #2465 — `findStructuredComment` is invoked back-to-back for
|
|
357
344
|
* different `type` discriminators against the same ticket (e.g.
|
|
358
|
-
* `story-
|
|
359
|
-
*
|
|
345
|
+
* `story-init` + `verification-results` + a `friction` probe during a
|
|
346
|
+
* single close). Without this cache each lookup pays a full
|
|
360
347
|
* pagination round-trip even when the prior call already fetched the
|
|
361
348
|
* same comments. The structured-comment-id cache short-circuits *repeat*
|
|
362
349
|
* lookups for the same `(type, attrs)` tuple but does not help across
|
|
@@ -37,6 +37,10 @@ import {
|
|
|
37
37
|
eventSeverity,
|
|
38
38
|
renderTransitionMessage,
|
|
39
39
|
} from '../../notifications/notifier.js';
|
|
40
|
+
import {
|
|
41
|
+
emitRuntimeFriction,
|
|
42
|
+
RUNTIME_FRICTION_CATEGORIES,
|
|
43
|
+
} from '../../observability/runtime-friction.js';
|
|
40
44
|
import { ColumnSync } from '../column-sync.js';
|
|
41
45
|
import {
|
|
42
46
|
ALL_STATES,
|
|
@@ -279,6 +283,46 @@ function dispatchTransitionNotification(args) {
|
|
|
279
283
|
});
|
|
280
284
|
}
|
|
281
285
|
|
|
286
|
+
/**
|
|
287
|
+
* Emit a runtime-derived `friction` signal when a ticket parks at
|
|
288
|
+
* `agent::blocked` (Story #4578). No-op for every other target state.
|
|
289
|
+
*
|
|
290
|
+
* Why here: `agent::blocked` is the single runtime HITL pause point
|
|
291
|
+
* (`.agents/instructions.md` § 1.J), and this is its canonical mutator — so
|
|
292
|
+
* one hook catches every block regardless of which path drove it (the
|
|
293
|
+
* `merge.unlanded` and `merge.flip-failed` paths in
|
|
294
|
+
* `single-story-close/phases/confirm-merge.js`, the review-block path, an
|
|
295
|
+
* operator's `update-ticket-state.js`, a Story worker giving up). Before
|
|
296
|
+
* this, a block was only ever a *label* — it left no trace in the friction
|
|
297
|
+
* stream the retro reads, so a run could park a worker and still produce a
|
|
298
|
+
* zero-signal roll-up.
|
|
299
|
+
*
|
|
300
|
+
* This is also why the terminal-envelope hook deliberately skips `blocked`
|
|
301
|
+
* (see `frictionForTerminal`): the two would otherwise count one incident
|
|
302
|
+
* twice.
|
|
303
|
+
*
|
|
304
|
+
* Best-effort and awaited: `emitRuntimeFriction` swallows its own failures
|
|
305
|
+
* and resolves `false`, so this can neither throw nor block the transition.
|
|
306
|
+
* It is awaited rather than fire-and-forget because CLI entry points exit
|
|
307
|
+
* via `process.exit` as soon as `main` resolves (`cli-utils.runAsCli` with
|
|
308
|
+
* `propagateExitCode`), which would discard a still-pending append.
|
|
309
|
+
*
|
|
310
|
+
* @param {number} ticketId
|
|
311
|
+
* @param {string} newState
|
|
312
|
+
* @param {{ config?: object }} opts
|
|
313
|
+
* @returns {Promise<void>}
|
|
314
|
+
*/
|
|
315
|
+
async function emitBlockedFriction(ticketId, newState, opts) {
|
|
316
|
+
if (newState !== STATE_LABELS.BLOCKED) return;
|
|
317
|
+
await emitRuntimeFriction({
|
|
318
|
+
storyId: ticketId,
|
|
319
|
+
category: RUNTIME_FRICTION_CATEGORIES.STORY_BLOCKED,
|
|
320
|
+
tool: 'transitionTicketState',
|
|
321
|
+
details: { toState: newState },
|
|
322
|
+
config: opts?.config,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
282
326
|
/**
|
|
283
327
|
* Transitions a ticket's label to the new state.
|
|
284
328
|
* Removes other agent:: state labels.
|
|
@@ -361,6 +405,10 @@ export async function transitionTicketState(
|
|
|
361
405
|
_ticketSnapshot: ticketSnapshot,
|
|
362
406
|
});
|
|
363
407
|
|
|
408
|
+
// Story #4578 — derive a friction signal from the block, at the point the
|
|
409
|
+
// runtime already knows. Best-effort; never blocks the transition.
|
|
410
|
+
await emitBlockedFriction(ticketId, newState, opts);
|
|
411
|
+
|
|
364
412
|
// Story #2548 — mirror the new state onto the Projects v2 Status
|
|
365
413
|
// column. Best-effort; never blocks the transition.
|
|
366
414
|
// Story #3645 — thread the DIP seam so callers can inject a stub.
|
|
@@ -460,14 +508,25 @@ export async function toggleTasklistCheckbox(
|
|
|
460
508
|
/**
|
|
461
509
|
* Post a structured comment to a ticket.
|
|
462
510
|
*
|
|
511
|
+
* Returns whatever the provider's `postComment` resolved to (Story #4543).
|
|
512
|
+
* Previously the result was swallowed, which made it impossible for a caller
|
|
513
|
+
* to reference the comment it had just written — the terminal envelope's
|
|
514
|
+
* `blocked.frictionCommentId` pointer needs exactly that, so an operator can
|
|
515
|
+
* be sent straight to the remediation instead of told to go find it. Callers
|
|
516
|
+
* that don't need the id simply ignore the return, so this is additive.
|
|
517
|
+
*
|
|
518
|
+
* The shape is provider-defined and may be `undefined` for providers that do
|
|
519
|
+
* not surface one; callers must treat the id as best-effort.
|
|
520
|
+
*
|
|
463
521
|
* @param {import('../../ITicketingProvider.js').ITicketingProvider} provider
|
|
464
522
|
* @param {number} ticketId
|
|
465
523
|
* @param {'progress'|'friction'|'notification'} type
|
|
466
524
|
* @param {string} payload
|
|
525
|
+
* @returns {Promise<unknown>} The provider's `postComment` result.
|
|
467
526
|
*/
|
|
468
527
|
export async function postStructuredComment(provider, ticketId, type, payload) {
|
|
469
528
|
assertValidStructuredCommentType(type);
|
|
470
|
-
await provider.postComment(ticketId, {
|
|
529
|
+
const posted = await provider.postComment(ticketId, {
|
|
471
530
|
type,
|
|
472
531
|
body: payload,
|
|
473
532
|
});
|
|
@@ -475,4 +534,5 @@ export async function postStructuredComment(provider, ticketId, type, payload) {
|
|
|
475
534
|
// `findStructuredComment` against this ticket re-fetches and sees the
|
|
476
535
|
// freshly-posted comment.
|
|
477
536
|
invalidateRawCommentsCache(provider, ticketId);
|
|
537
|
+
return posted;
|
|
478
538
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* plan-phase-cleanup.js — Post-phase temp-file cleanup for `/plan`.
|
|
3
3
|
*
|
|
4
|
-
* The spec and decompose phases write several
|
|
5
|
-
* the per-
|
|
6
|
-
* `temp/
|
|
4
|
+
* The spec and decompose phases write several run-scoped temp files under
|
|
5
|
+
* the per-run tree (`temp/run-<id>/planner-context.json`,
|
|
6
|
+
* `temp/run-<id>/techspec.md`, etc. — see `lib/config/temp-paths.js`). The
|
|
7
7
|
* workflow .md previously told the operator to `Remove-Item` those files by
|
|
8
8
|
* name at the end of each phase, which rots: adding a new temp file in the
|
|
9
9
|
* script required a synchronized markdown edit, and missed edits left
|
|
@@ -19,20 +19,20 @@
|
|
|
19
19
|
* never sinks a successful phase.
|
|
20
20
|
*
|
|
21
21
|
* Migration note (Epic #1030 Story #1040): the legacy flat layout
|
|
22
|
-
* (`temp/planner-context-
|
|
23
|
-
* resolver now delegates to `
|
|
24
|
-
* `lib/config/temp-paths.js`, which yields `temp/
|
|
22
|
+
* (`temp/planner-context-for-<id>.json` etc.) has been retired. The
|
|
23
|
+
* resolver now delegates to `runArtifactPath` from
|
|
24
|
+
* `lib/config/temp-paths.js`, which yields `temp/run-<id>/<basename>`
|
|
25
25
|
* under the configured `tempRoot`.
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import fs from 'node:fs/promises';
|
|
29
29
|
import path from 'node:path';
|
|
30
|
-
import {
|
|
30
|
+
import { runArtifactPath } from './config/temp-paths.js';
|
|
31
31
|
import { PROJECT_ROOT, resolveConfig } from './config-resolver.js';
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
* Map of phase → artifact basenames (no path components). The per-
|
|
35
|
-
* directory prefix is supplied by `
|
|
34
|
+
* Map of phase → artifact basenames (no path components). The per-run
|
|
35
|
+
* directory prefix is supplied by `runArtifactPath` at resolution time.
|
|
36
36
|
*/
|
|
37
37
|
export const PHASE_TEMP_BASENAMES = Object.freeze({
|
|
38
38
|
spec: Object.freeze([
|
|
@@ -45,15 +45,13 @@ export const PHASE_TEMP_BASENAMES = Object.freeze({
|
|
|
45
45
|
// plan-phase artifact and deletes them ONLY at terminal success (after
|
|
46
46
|
// the `agent::ready` flip), fixing the mid-pipeline deletion defect where
|
|
47
47
|
// per-phase cleanup removed artifacts a `--force`/`--resume` re-persist
|
|
48
|
-
// was still entitled to reuse.
|
|
49
|
-
// the split-phase cleanup never owned it, which orphaned it in temp/.
|
|
48
|
+
// was still entitled to reuse.
|
|
50
49
|
// `plan-metrics.json` stays deliberately excluded (PR1) — the ledger
|
|
51
50
|
// must survive cleanup so the whole plan run is visible in one stream.
|
|
52
51
|
persist: Object.freeze([
|
|
53
52
|
'planner-context.json',
|
|
54
53
|
'techspec.md',
|
|
55
54
|
'acceptance-spec.md',
|
|
56
|
-
'risk-verdict.json',
|
|
57
55
|
'decomposer-context.json',
|
|
58
56
|
'tickets.json',
|
|
59
57
|
]),
|
|
@@ -84,8 +82,8 @@ export function resolvePhaseTempPaths(phase, epicId, repoRoot = PROJECT_ROOT) {
|
|
|
84
82
|
config = undefined;
|
|
85
83
|
}
|
|
86
84
|
return basenames.map((basename) => {
|
|
87
|
-
const rel =
|
|
88
|
-
// `
|
|
85
|
+
const rel = runArtifactPath(epicId, basename, config);
|
|
86
|
+
// `runArtifactPath` yields a path under `tempRoot` (relative when
|
|
89
87
|
// tempRoot is itself relative — the framework default). Rebase
|
|
90
88
|
// relative paths against `repoRoot` so callers always receive an
|
|
91
89
|
// absolute path — matches the pre-migration contract that
|
|
@@ -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 ?? '',
|