mandrel 1.94.0 → 2.0.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 -73
- package/.agents/agents/story-worker.md +12 -13
- package/.agents/docs/SDLC.md +489 -1285
- package/.agents/docs/agentrc-reference.json +177 -67
- package/.agents/docs/configuration.md +104 -138
- package/.agents/docs/execution-reference.md +17 -20
- package/.agents/docs/quality-gates.md +13 -19
- package/.agents/docs/workflows.md +3 -3
- package/.agents/instructions.md +70 -81
- package/.agents/rules/ci-remediation.md +8 -12
- package/.agents/rules/git-conventions-reference.md +6 -7
- package/.agents/rules/git-conventions.md +16 -22
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +54 -214
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
- package/.agents/schemas/risk-verdict.schema.json +0 -13
- package/.agents/scripts/analyze-execution.js +1 -1
- package/.agents/scripts/audit-to-stories.js +7 -7
- package/.agents/scripts/boot-sweep.js +1 -1
- package/.agents/scripts/check-lifecycle-lint.js +6 -9
- package/.agents/scripts/check-prepush-recovery.js +1 -1
- package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
- package/.agents/scripts/lib/Logger.js +6 -10
- package/.agents/scripts/lib/audit-suite/runner.js +2 -2
- package/.agents/scripts/lib/audit-suite/selector.js +5 -5
- package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
- package/.agents/scripts/lib/baselines/kernel.js +206 -18
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
- package/.agents/scripts/lib/baselines/reader.js +1 -6
- package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
- package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
- package/.agents/scripts/lib/checks/index.js +2 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
- package/.agents/scripts/lib/cli/standard-args.js +13 -22
- package/.agents/scripts/lib/cli-args.js +16 -7
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +45 -29
- package/.agents/scripts/lib/config/explain.js +55 -36
- package/.agents/scripts/lib/config/limits.js +17 -58
- package/.agents/scripts/lib/config/paths.js +0 -2
- package/.agents/scripts/lib/config/quality.js +1 -1
- package/.agents/scripts/lib/config/runners.js +17 -50
- package/.agents/scripts/lib/config/temp-paths.js +19 -14
- package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
- package/.agents/scripts/lib/config-resolver.js +2 -7
- package/.agents/scripts/lib/config-settings-schema-delivery.js +24 -148
- package/.agents/scripts/lib/config-settings-schema-quality.js +8 -14
- package/.agents/scripts/lib/config-settings-schema.js +52 -38
- package/.agents/scripts/lib/dependency-parser.js +3 -2
- package/.agents/scripts/lib/doc-tiers.js +2 -2
- package/.agents/scripts/lib/duplicate-search.js +211 -41
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +5 -5
- package/.agents/scripts/lib/framework-version.js +2 -3
- package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
- package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
- package/.agents/scripts/lib/git-utils.js +32 -6
- package/.agents/scripts/lib/github/framework-repo.js +6 -0
- package/.agents/scripts/lib/label-constants.js +10 -23
- package/.agents/scripts/lib/label-taxonomy.js +9 -43
- package/.agents/scripts/lib/observability/active-story-env.js +1 -1
- package/.agents/scripts/lib/observability/hook-heartbeat.js +20 -52
- package/.agents/scripts/lib/observability/source-classifier.js +3 -3
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +8 -5
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +83 -20
- package/.agents/scripts/lib/orchestration/code-review.js +20 -268
- package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
- package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
- package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
- package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
- package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
- package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
- package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +13 -13
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
- package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
- package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
- package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
- package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
- package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
- package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
- package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
- package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
- package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
- package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
- package/.agents/scripts/lib/orchestration/ticket-validator.js +2 -7
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
- package/.agents/scripts/lib/planning-corpus.js +16 -11
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
- package/.agents/scripts/lib/story-adjacency.js +11 -14
- package/.agents/scripts/lib/story-body/story-body.js +124 -70
- package/.agents/scripts/lib/story-plan.js +2 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
- package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
- package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
- package/.agents/scripts/lib/validation-evidence.js +1 -1
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
- package/.agents/scripts/lib/workspace-provisioner.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
- package/.agents/scripts/lint-issue-body.js +71 -40
- package/.agents/scripts/mandrel-update-preflight.js +1 -1
- package/.agents/scripts/notify.js +4 -3
- package/.agents/scripts/plan-context.js +64 -74
- package/.agents/scripts/plan-persist.js +121 -280
- package/.agents/scripts/plan-run-epilogue.js +97 -0
- package/.agents/scripts/providers/github/issues.js +17 -33
- package/.agents/scripts/providers/github/mappers.js +0 -12
- package/.agents/scripts/providers/github/tickets.js +2 -5
- package/.agents/scripts/resolve-plan-run.js +117 -0
- package/.agents/scripts/signals-view.js +24 -19
- package/.agents/scripts/single-story-close.js +11 -14
- package/.agents/scripts/single-story-confirm-merge.js +39 -23
- package/.agents/scripts/single-story-init.js +29 -20
- package/.agents/scripts/stories-wave-tick.js +6 -6
- package/.agents/scripts/story-plan.js +26 -47
- package/.agents/scripts/update-ticket-state.js +6 -15
- package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
- package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
- package/.agents/skills/core/scope-triage/SKILL.md +28 -172
- package/.agents/skills/skills.index.json +3 -43
- package/.agents/starter-agentrc.json +0 -5
- package/.agents/templates/agent-protocol.md +9 -10
- package/.agents/workflows/audit-architecture.md +3 -3
- package/.agents/workflows/audit-clean-code.md +3 -3
- package/.agents/workflows/audit-dependencies.md +3 -3
- package/.agents/workflows/audit-devops.md +3 -3
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-lighthouse.md +3 -3
- package/.agents/workflows/audit-navigability.md +3 -2
- package/.agents/workflows/audit-performance.md +3 -3
- package/.agents/workflows/audit-privacy.md +3 -3
- package/.agents/workflows/audit-quality.md +3 -3
- package/.agents/workflows/audit-security.md +3 -3
- package/.agents/workflows/audit-seo.md +3 -3
- package/.agents/workflows/audit-sre.md +3 -3
- package/.agents/workflows/audit-to-stories.md +20 -20
- package/.agents/workflows/audit-ux-ui.md +3 -3
- package/.agents/workflows/deliver.md +122 -174
- package/.agents/workflows/git-cleanup.md +3 -4
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +16 -29
- package/.agents/workflows/helpers/code-review.md +70 -193
- package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
- package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
- package/.agents/workflows/helpers/diagnose.md +10 -10
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/helpers/signals.md +16 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
- package/.agents/workflows/mandrel-update.md +2 -1
- package/.agents/workflows/plan.md +112 -145
- package/.agents/workflows/qa-assist.md +24 -30
- package/.agents/workflows/qa-explore.md +29 -38
- package/.agents/workflows/qa-run.md +2 -2
- package/README.md +9 -8
- package/docs/CHANGELOG.md +30 -0
- package/lib/migrations/index.js +6 -5
- package/package.json +2 -2
- package/.agents/agents/retro.md +0 -42
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer.md +0 -33
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/security-engineer.md +0 -111
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
- package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
- package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- package/.agents/scripts/bookkeeping-reconcile.js +0 -117
- package/.agents/scripts/dispatcher.js +0 -295
- package/.agents/scripts/epic-audit-prepare.js +0 -497
- package/.agents/scripts/epic-audit-recheck.js +0 -274
- package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
- package/.agents/scripts/epic-deliver-preflight.js +0 -462
- package/.agents/scripts/epic-deliver-prepare.js +0 -852
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-reconcile.js +0 -625
- package/.agents/scripts/lib/baseline-snapshot.js +0 -979
- package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
- package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
- package/.agents/scripts/lib/config/lifecycle.js +0 -40
- package/.agents/scripts/lib/config/preflight.js +0 -58
- package/.agents/scripts/lib/config/retro.js +0 -77
- package/.agents/scripts/lib/epic-merge-lock.js +0 -322
- package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
- package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
- package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
- package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
- package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
- package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
- package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
- package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
- package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
- package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
- package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
- package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
- package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
- package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
- package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
- package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
- package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
- package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
- package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
- package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
- package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
- package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
- package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
- package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
- package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
- package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
- package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
- package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
- package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
- package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
- package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
- package/.agents/scripts/lib/push-epic-retry.js +0 -209
- package/.agents/scripts/lib/spec/index.js +0 -36
- package/.agents/scripts/lib/spec/loader.js +0 -425
- package/.agents/scripts/lib/spec/state.js +0 -208
- package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
- package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
- package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
- package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
- package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
- package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
- package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
- package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
- package/.agents/scripts/lib/wave-runner/tick.js +0 -754
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
- package/.agents/scripts/lifecycle-emit.js +0 -510
- package/.agents/scripts/retro-run.js +0 -218
- package/.agents/scripts/slice-phase.js +0 -361
- package/.agents/scripts/standalone-feedback-rollup.js +0 -188
- package/.agents/scripts/story-close.js +0 -294
- package/.agents/scripts/story-init.js +0 -599
- package/.agents/scripts/story-phase.js +0 -369
- package/.agents/scripts/wave-tick.js +0 -464
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
- package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
- package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
- package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
- package/.agents/workflows/helpers/deliver-epic.md +0 -998
- package/.agents/workflows/helpers/deliver-stories.md +0 -450
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
- package/.agents/workflows/helpers/epic-testing.md +0 -125
- package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
- package/.agents/workflows/helpers/plan-epic.md +0 -353
- package/.agents/workflows/helpers/plan-story.md +0 -251
- package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
- /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import nodeFs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { calculateForSource } from '../../../maintainability-engine.js';
|
|
5
|
-
import { formatNumber } from './_bullet-format.js';
|
|
6
|
-
import {
|
|
7
|
-
createDriftDetector,
|
|
8
|
-
walkComponentRegressions,
|
|
9
|
-
} from './component-drift.js';
|
|
10
|
-
|
|
11
|
-
const DEFAULT_THRESHOLD = 2.0;
|
|
12
|
-
// Distinct from the canonical ratchet baseline at `baselines/maintainability.json`
|
|
13
|
-
// (Epic #730 Story 5.5) — this file is a per-wave drift SNAPSHOT, not the
|
|
14
|
-
// committed score baseline. Filename intentionally differs so a repo-wide
|
|
15
|
-
// grep for the canonical baseline no longer hits the snapshot.
|
|
16
|
-
const BASELINE_FILENAME = 'wave-mi-snapshot.json';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Detect per-component maintainability regressions from a baseline rollup
|
|
20
|
-
* against the gate's configured floors. Pure — the caller loads the
|
|
21
|
-
* baseline via `lib/baselines/reader.js#load('maintainability')` and passes
|
|
22
|
-
* the resulting `{ rollup }` plus the gate config block.
|
|
23
|
-
*
|
|
24
|
-
* Bullet shape (Task #1919, Epic #1786):
|
|
25
|
-
*
|
|
26
|
-
* 📉 maintainability: <component> <axis> <value> < floor <floor>
|
|
27
|
-
*
|
|
28
|
-
* Maintainability is "higher is better" — every axis breach reports when
|
|
29
|
-
* `value < floor`. Component-scoped breaches do NOT trigger a `*` bullet
|
|
30
|
-
* unless `*` itself breaches. The walk itself is the shared
|
|
31
|
-
* `component-drift.js` helper (Story #3984).
|
|
32
|
-
*
|
|
33
|
-
* @param {{
|
|
34
|
-
* rollup?: Record<string, Record<string, number>>,
|
|
35
|
-
* gateConfig?: { floors?: Record<string, Record<string, number>> } & object,
|
|
36
|
-
* }} params
|
|
37
|
-
* @returns {string[]}
|
|
38
|
-
*/
|
|
39
|
-
export function detectComponentRegressions(params = {}) {
|
|
40
|
-
return walkComponentRegressions(params, {
|
|
41
|
-
isBreach: (value, target) => value < target,
|
|
42
|
-
formatBullet: (name, axis, value, target) =>
|
|
43
|
-
`📉 maintainability: ${name} ${axis} ${formatNumber(value)} < floor ${formatNumber(target)}`,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Detects per-file maintainability drop versus a wave-start baseline.
|
|
49
|
-
*
|
|
50
|
-
* Usage:
|
|
51
|
-
* const detector = createMaintainabilityDriftDetector({ cwd, files });
|
|
52
|
-
* detector.captureBaseline(); // call once at wave-start
|
|
53
|
-
* await detector.detect(rows); // call each progress fire
|
|
54
|
-
*
|
|
55
|
-
* A baseline is a map of file -> score, persisted to
|
|
56
|
-
* `<cwd>/<baselineDir>/wave-mi-snapshot.json` so that a resumed epic
|
|
57
|
-
* run can re-read the snapshot from the previous wave rather than lose the
|
|
58
|
-
* anchor. Any file whose score has dropped by >= `threshold` since baseline
|
|
59
|
-
* is surfaced as a bullet:
|
|
60
|
-
*
|
|
61
|
-
* 📉 Maintainability drift: <file> -<n.nn> vs wave-start baseline
|
|
62
|
-
*
|
|
63
|
-
* Errors while reading/scoring individual files are swallowed — a single
|
|
64
|
-
* bad file must not take the progress reporter down.
|
|
65
|
-
*
|
|
66
|
-
* @param {{
|
|
67
|
-
* cwd?: string,
|
|
68
|
-
* files?: string[], // files to watch; repo-relative paths
|
|
69
|
-
* fs?: { readFileSync: Function, writeFileSync?: Function, mkdirSync?: Function, existsSync?: Function },
|
|
70
|
-
* calculate?: (source: string) => number,
|
|
71
|
-
* threshold?: number, // drop (baseline - current) that trips a bullet
|
|
72
|
-
* baselineDir?: string, // directory (under cwd) to persist snapshot
|
|
73
|
-
* }} [opts]
|
|
74
|
-
*/
|
|
75
|
-
export function createMaintainabilityDriftDetector(opts = {}) {
|
|
76
|
-
const fs = opts.fs ?? nodeFs;
|
|
77
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
78
|
-
const calculate = opts.calculate ?? calculateForSource;
|
|
79
|
-
const threshold = Number.isFinite(opts.threshold)
|
|
80
|
-
? opts.threshold
|
|
81
|
-
: DEFAULT_THRESHOLD;
|
|
82
|
-
|
|
83
|
-
function scoreFile(relPath) {
|
|
84
|
-
try {
|
|
85
|
-
const abs = path.join(cwd, relPath);
|
|
86
|
-
const src = fs.readFileSync(abs, 'utf-8');
|
|
87
|
-
const score = calculate(src);
|
|
88
|
-
return Number.isFinite(score) ? score : null;
|
|
89
|
-
} catch {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return createDriftDetector({
|
|
95
|
-
fs,
|
|
96
|
-
cwd,
|
|
97
|
-
files: opts.files,
|
|
98
|
-
baselineDir: opts.baselineDir,
|
|
99
|
-
baselineFilename: BASELINE_FILENAME,
|
|
100
|
-
scoreFile,
|
|
101
|
-
captureScore: (score) => score,
|
|
102
|
-
async detect({ baseline, scoreFile: score }) {
|
|
103
|
-
const bullets = [];
|
|
104
|
-
for (const [relPath, baseScore] of Object.entries(baseline)) {
|
|
105
|
-
const current = score(relPath);
|
|
106
|
-
if (current == null) continue;
|
|
107
|
-
const drop = baseScore - current;
|
|
108
|
-
if (drop >= threshold) {
|
|
109
|
-
bullets.push(
|
|
110
|
-
`📉 Maintainability drift: ${relPath} -${drop.toFixed(2)} vs wave-start baseline`,
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return bullets;
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import nodeFs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Detects "worktree residue" — stories that the ticket graph reports as done,
|
|
6
|
-
* but whose `.worktrees/story-<id>/` directory is still on disk. This usually
|
|
7
|
-
* means story-close failed to reap the worktree (see the Windows
|
|
8
|
-
* partial-reap failure mode in friction memory), and a future run of the
|
|
9
|
-
* same story id would collide with the stale directory.
|
|
10
|
-
*
|
|
11
|
-
* The detector is pure (aside from fs reads) and accepts an fs adapter so the
|
|
12
|
-
* unit test can drive present/absent cases without touching disk.
|
|
13
|
-
*
|
|
14
|
-
* @param {{
|
|
15
|
-
* fs?: { existsSync: (p: string) => boolean },
|
|
16
|
-
* cwd?: string,
|
|
17
|
-
* }} [opts]
|
|
18
|
-
* @returns {(rows: Array<{id:number|string, state:string}>) => Promise<string[]>}
|
|
19
|
-
*/
|
|
20
|
-
export function createStalledWorktreeDetector(opts = {}) {
|
|
21
|
-
const fs = opts.fs ?? nodeFs;
|
|
22
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
23
|
-
return async function detect(rows = []) {
|
|
24
|
-
const bullets = [];
|
|
25
|
-
for (const row of rows) {
|
|
26
|
-
if (row?.state !== 'done') continue;
|
|
27
|
-
const relPath = `.worktrees/story-${row.id}`;
|
|
28
|
-
const absPath = path.join(cwd, relPath);
|
|
29
|
-
if (fs.existsSync(absPath)) {
|
|
30
|
-
bullets.push(
|
|
31
|
-
`⚠️ Worktree residue: #${row.id} marked done but .worktrees/story-${row.id}/ still present`,
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return bullets;
|
|
36
|
-
};
|
|
37
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StoryLauncher — produces the per-wave dispatch plan and (optionally)
|
|
3
|
-
* delegates execution to an injected `dispatch` adapter.
|
|
4
|
-
*
|
|
5
|
-
* After Story #908, in-session Agent-tool fan-out replaces the subprocess
|
|
6
|
-
* spawn pipeline. The launcher's primary responsibility is `planWave(stories)`:
|
|
7
|
-
* given a wave's Story tickets it returns a stable list of
|
|
8
|
-
* `{ storyId, worktree }` entries. The `/deliver` skill consumes that
|
|
9
|
-
* list (one wave at a time) to format one assistant turn containing N
|
|
10
|
-
* parallel `Agent` tool calls (subagent_type `general-purpose`), each of
|
|
11
|
-
* which drives `/deliver <storyId>` for one Story.
|
|
12
|
-
*
|
|
13
|
-
* `launchWave(stories)` is a convenience for callers that already hold a
|
|
14
|
-
* concrete dispatch adapter (tests, future programmatic harnesses). It calls
|
|
15
|
-
* `planWave` and forwards the plan to `dispatch({ plan, concurrencyCap, signal })`,
|
|
16
|
-
* returning the adapter's result list. The CLI does not provide a default
|
|
17
|
-
* dispatch adapter — invoking the engine without one is an explicit error.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
const DEFAULT_TIMEOUT_MS = 6 * 60 * 60 * 1000;
|
|
21
|
-
|
|
22
|
-
export class StoryLauncher {
|
|
23
|
-
/**
|
|
24
|
-
* @param {{
|
|
25
|
-
* ctx?: { dispatch?: Function, concurrencyCap?: number, worktreeResolver?: (storyId: number) => string, logger?: object },
|
|
26
|
-
* concurrencyCap?: number,
|
|
27
|
-
* dispatch?: (args: { plan: Array<{ storyId: number, worktree?: string }>, concurrencyCap: number, signal?: AbortSignal }) => Promise<Array<{ storyId: number, status: string, detail?: string }>>,
|
|
28
|
-
* worktreeResolver?: (storyId: number) => string,
|
|
29
|
-
* timeoutMs?: number,
|
|
30
|
-
* logger?: { info: Function, warn: Function, error: Function }
|
|
31
|
-
* }} opts
|
|
32
|
-
*/
|
|
33
|
-
constructor(opts = {}) {
|
|
34
|
-
const ctx = opts?.ctx;
|
|
35
|
-
const dispatch = opts?.dispatch ?? ctx?.dispatch ?? null;
|
|
36
|
-
const concurrencyCap = opts?.concurrencyCap ?? ctx?.concurrencyCap;
|
|
37
|
-
if (!Number.isInteger(concurrencyCap) || concurrencyCap < 1) {
|
|
38
|
-
throw new RangeError('concurrencyCap must be a positive integer');
|
|
39
|
-
}
|
|
40
|
-
this.concurrencyCap = concurrencyCap;
|
|
41
|
-
this.dispatch = dispatch;
|
|
42
|
-
this.worktreeResolver = opts?.worktreeResolver ?? ctx?.worktreeResolver;
|
|
43
|
-
this.timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
44
|
-
this.logger = opts?.logger ?? ctx?.logger ?? console;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Produce the dispatch plan for a wave. Pure: no side effects, no IO. The
|
|
49
|
-
* caller (the `/deliver` skill's wave loop, or `launchWave` below)
|
|
50
|
-
* decides what to do with the plan.
|
|
51
|
-
*
|
|
52
|
-
* @param {Array<number|{id?:number,storyId?:number,number?:number}>} stories
|
|
53
|
-
* @returns {Array<{ storyId: number, worktree?: string }>}
|
|
54
|
-
*/
|
|
55
|
-
planWave(stories) {
|
|
56
|
-
return (stories ?? []).map((s) => {
|
|
57
|
-
const storyId =
|
|
58
|
-
typeof s === 'object' && s !== null
|
|
59
|
-
? (s.id ?? s.storyId ?? s.number)
|
|
60
|
-
: s;
|
|
61
|
-
const id = Number(storyId);
|
|
62
|
-
return {
|
|
63
|
-
storyId: id,
|
|
64
|
-
worktree: this.worktreeResolver?.(id),
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Plan + dispatch the wave through the injected adapter. Returns one result
|
|
71
|
-
* entry per input Story preserving input order. Throws when no dispatch
|
|
72
|
-
* adapter was injected — the engine is no longer callable without one.
|
|
73
|
-
*
|
|
74
|
-
* @param {object[]} stories
|
|
75
|
-
* @param {AbortSignal} [signal]
|
|
76
|
-
* @returns {Promise<Array<{ storyId: number, status: string, detail?: string }>>}
|
|
77
|
-
*/
|
|
78
|
-
async launchWave(stories, signal) {
|
|
79
|
-
if (typeof this.dispatch !== 'function') {
|
|
80
|
-
throw new TypeError(
|
|
81
|
-
'StoryLauncher.launchWave requires a dispatch adapter (in-session Agent-tool fan-out is the responsibility of the /deliver skill).',
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
const plan = this.planWave(stories);
|
|
85
|
-
if (plan.length === 0) return [];
|
|
86
|
-
|
|
87
|
-
const controller = new AbortController();
|
|
88
|
-
const timer = setTimeout(
|
|
89
|
-
() =>
|
|
90
|
-
controller.abort(new Error(`wave timed out after ${this.timeoutMs}ms`)),
|
|
91
|
-
this.timeoutMs,
|
|
92
|
-
);
|
|
93
|
-
const onParentAbort = () => controller.abort(signal?.reason);
|
|
94
|
-
signal?.addEventListener?.('abort', onParentAbort, { once: true });
|
|
95
|
-
try {
|
|
96
|
-
const results = await this.dispatch({
|
|
97
|
-
plan,
|
|
98
|
-
concurrencyCap: this.concurrencyCap,
|
|
99
|
-
signal: controller.signal,
|
|
100
|
-
});
|
|
101
|
-
return this.#alignResults(plan, results);
|
|
102
|
-
} catch (err) {
|
|
103
|
-
return plan.map((p) => ({
|
|
104
|
-
storyId: p.storyId,
|
|
105
|
-
status: 'failed',
|
|
106
|
-
detail: err?.message ?? String(err),
|
|
107
|
-
}));
|
|
108
|
-
} finally {
|
|
109
|
-
clearTimeout(timer);
|
|
110
|
-
signal?.removeEventListener?.('abort', onParentAbort);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
#alignResults(plan, results) {
|
|
115
|
-
const list = Array.isArray(results) ? results : [];
|
|
116
|
-
const byId = new Map(list.map((r) => [Number(r.storyId), r]));
|
|
117
|
-
return plan.map((p) => {
|
|
118
|
-
const r = byId.get(p.storyId);
|
|
119
|
-
if (r) return { storyId: p.storyId, ...r };
|
|
120
|
-
return {
|
|
121
|
-
storyId: p.storyId,
|
|
122
|
-
status: 'failed',
|
|
123
|
-
detail: 'dispatch returned no result for this story',
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sub-agent-return.js — parse and reconcile per-Story sub-agent return text.
|
|
3
|
-
*
|
|
4
|
-
* `/deliver` Step 2 dispatches one `Agent` tool call per Story per
|
|
5
|
-
* wave. Each sub-agent owes its parent the JSON return contract documented
|
|
6
|
-
* in `.agents/workflows/helpers/deliver-epic.md`:
|
|
7
|
-
*
|
|
8
|
-
* {
|
|
9
|
-
* "storyId": <number>,
|
|
10
|
-
* "status": "done" | "blocked" | "failed",
|
|
11
|
-
* "phase": "init|implementing|closing|blocked|done",
|
|
12
|
-
* "branchDeleted": <boolean>,
|
|
13
|
-
* "blockerCommentId": <string|null>,
|
|
14
|
-
* "detail": <string|undefined>,
|
|
15
|
-
* "renderedBody": <string|undefined>
|
|
16
|
-
* }
|
|
17
|
-
*
|
|
18
|
-
* On the current model tier a sub-agent that does real work returns the
|
|
19
|
-
* envelope verbatim; a return that fails to parse is a rare protocol error,
|
|
20
|
-
* not a normal recovery path. Parsing is therefore strict — an already-parsed
|
|
21
|
-
* object or a return string that is pure JSON — with **no free-form
|
|
22
|
-
* extraction heuristics**: the prose-wrapped extraction candidates
|
|
23
|
-
* (fenced ```json``` blocks, balanced-`{...}`-substring scans, chat-prelude
|
|
24
|
-
* tolerance) were measured to never fire in practice (Story #3864:
|
|
25
|
-
* zero malformed terminal returns across the sampled delivered Epics) and
|
|
26
|
-
* were deleted in a hard cutover. A return that does not parse routes
|
|
27
|
-
* directly to GitHub-state reconciliation plus a friction record — the
|
|
28
|
-
* stronger backstop that already caught everything the heuristics caught.
|
|
29
|
-
*
|
|
30
|
-
* This module provides the two helpers `/deliver`'s wave dispatcher
|
|
31
|
-
* now uses:
|
|
32
|
-
*
|
|
33
|
-
* - `parseStoryAgentReturn(raw)` — accept an already-parsed object or a
|
|
34
|
-
* return string that is pure JSON, then schema-validate it. Returns
|
|
35
|
-
* `{ ok: true, value }` on success, `{ ok: false, error }` otherwise.
|
|
36
|
-
*
|
|
37
|
-
* - `reconcileStoryFromGitHub({ provider, storyId })` — reads the Story
|
|
38
|
-
* ticket's labels and `story-run-progress` structured comment to derive
|
|
39
|
-
* an authoritative result row. Used as a fallback whenever the
|
|
40
|
-
* sub-agent's return text fails to parse. Status is downgraded to
|
|
41
|
-
* `failed` unless the live ticket actually carries `agent::done`
|
|
42
|
-
* (in which case the operator may have manually closed the Story).
|
|
43
|
-
*/
|
|
44
|
-
|
|
45
|
-
import { parseFencedJsonComment } from '../structured-comment-parser.js';
|
|
46
|
-
import { findStructuredComment } from '../ticketing.js';
|
|
47
|
-
import { STORY_RUN_PROGRESS_TYPE } from './story-run-progress-writer.js';
|
|
48
|
-
|
|
49
|
-
const VALID_STATUS = new Set(['done', 'blocked', 'failed']);
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Strict parse of a per-Story sub-agent return. Accepts:
|
|
53
|
-
* 1. an object that already looks parsed, or
|
|
54
|
-
* 2. a return string that is pure JSON.
|
|
55
|
-
*
|
|
56
|
-
* There is no free-form extraction: a string that is not pure JSON (prose,
|
|
57
|
-
* a fenced block, a chat-prelude + envelope) yields `ok: false` and the
|
|
58
|
-
* caller MUST reconcile from GitHub. Parsing is strict on both sides —
|
|
59
|
-
* callers always get a fully-validated object or `ok: false`.
|
|
60
|
-
*
|
|
61
|
-
* @param {unknown} raw
|
|
62
|
-
* @returns {{ ok: true, value: object } | { ok: false, error: string }}
|
|
63
|
-
*/
|
|
64
|
-
export function parseStoryAgentReturn(raw) {
|
|
65
|
-
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
66
|
-
return validateStoryReturnShape(raw);
|
|
67
|
-
}
|
|
68
|
-
if (typeof raw !== 'string') {
|
|
69
|
-
return {
|
|
70
|
-
ok: false,
|
|
71
|
-
error: `expected string or object, got ${raw === null ? 'null' : typeof raw}`,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
const trimmed = raw.trim();
|
|
75
|
-
if (trimmed.length === 0) {
|
|
76
|
-
return { ok: false, error: 'empty return text' };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let parsed;
|
|
80
|
-
try {
|
|
81
|
-
parsed = JSON.parse(trimmed);
|
|
82
|
-
} catch {
|
|
83
|
-
return {
|
|
84
|
-
ok: false,
|
|
85
|
-
error: `return text is not a JSON envelope (${quote(trimmed)})`,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
89
|
-
return {
|
|
90
|
-
ok: false,
|
|
91
|
-
error: `return text parsed to a non-object (${quote(trimmed)})`,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
return validateStoryReturnShape(parsed);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Validate the per-Story return contract. Pure helper — no IO.
|
|
99
|
-
*
|
|
100
|
-
* @param {object} obj
|
|
101
|
-
* @returns {{ ok: true, value: object } | { ok: false, error: string }}
|
|
102
|
-
*/
|
|
103
|
-
function validateStoryReturnShape(obj) {
|
|
104
|
-
const storyId = Number(obj.storyId ?? obj.id);
|
|
105
|
-
if (!Number.isInteger(storyId) || storyId <= 0) {
|
|
106
|
-
return {
|
|
107
|
-
ok: false,
|
|
108
|
-
error: `storyId must be a positive integer; got ${JSON.stringify(obj.storyId ?? obj.id)}`,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
const status = String(obj.status ?? '');
|
|
112
|
-
if (!VALID_STATUS.has(status)) {
|
|
113
|
-
return {
|
|
114
|
-
ok: false,
|
|
115
|
-
error: `status "${status}" must be one of: ${[...VALID_STATUS].join(', ')}`,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
const value = { storyId, status };
|
|
119
|
-
if (typeof obj.phase === 'string') value.phase = obj.phase;
|
|
120
|
-
if (typeof obj.branchDeleted === 'boolean') {
|
|
121
|
-
value.branchDeleted = obj.branchDeleted;
|
|
122
|
-
}
|
|
123
|
-
if (obj.blockerCommentId != null) {
|
|
124
|
-
value.blockerCommentId = String(obj.blockerCommentId);
|
|
125
|
-
}
|
|
126
|
-
if (typeof obj.detail === 'string') value.detail = obj.detail;
|
|
127
|
-
if (typeof obj.renderedBody === 'string')
|
|
128
|
-
value.renderedBody = obj.renderedBody;
|
|
129
|
-
return { ok: true, value };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function quote(text) {
|
|
133
|
-
const oneline = text.replace(/\s+/g, ' ').trim();
|
|
134
|
-
return oneline.length > 120 ? `${oneline.slice(0, 117)}...` : oneline;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Authoritative reconciliation: rebuild a per-Story result row from the
|
|
139
|
-
* Story ticket's live state when the sub-agent return cannot be trusted.
|
|
140
|
-
*
|
|
141
|
-
* The result is always conservative — `status: 'failed'` unless the live
|
|
142
|
-
* ticket carries `agent::done` (or `state: 'closed'`). The phase is
|
|
143
|
-
* best-effort, sourced from the Story's `story-run-progress` comment;
|
|
144
|
-
* absence of the comment is non-fatal.
|
|
145
|
-
*
|
|
146
|
-
* Story #3907 — a Story carrying `agent::blocked` reconciles to
|
|
147
|
-
* `status: 'blocked'`, not `failed`. A garbled / unparseable return from a
|
|
148
|
-
* genuinely blocked child previously collapsed to `failed`, which erased the
|
|
149
|
-
* `blockerCommentId` and steered the operator toward the wrong remediation
|
|
150
|
-
* (re-run vs. resolve-the-blocker). The blocked arm recovers the blocker
|
|
151
|
-
* comment id from the Story's latest `friction` structured comment so the
|
|
152
|
-
* wave row carries it through to `epic-run-progress`.
|
|
153
|
-
*
|
|
154
|
-
* @param {{
|
|
155
|
-
* provider: { getTicket: Function, getTicketComments: Function },
|
|
156
|
-
* storyId: number,
|
|
157
|
-
* }} args
|
|
158
|
-
* @returns {Promise<{
|
|
159
|
-
* storyId: number,
|
|
160
|
-
* status: 'done' | 'blocked' | 'failed',
|
|
161
|
-
* phase?: string,
|
|
162
|
-
* blockerCommentId?: string,
|
|
163
|
-
* reconciledFromGitHub: true,
|
|
164
|
-
* reconcileError?: string,
|
|
165
|
-
* }>}
|
|
166
|
-
*/
|
|
167
|
-
export async function reconcileStoryFromGitHub({ provider, storyId } = {}) {
|
|
168
|
-
if (!Number.isInteger(storyId) || storyId <= 0) {
|
|
169
|
-
throw new TypeError(
|
|
170
|
-
'reconcileStoryFromGitHub: storyId must be a positive integer',
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
if (!provider || typeof provider.getTicket !== 'function') {
|
|
174
|
-
throw new TypeError(
|
|
175
|
-
'reconcileStoryFromGitHub: provider.getTicket is required',
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
let ticket;
|
|
180
|
-
try {
|
|
181
|
-
ticket = await provider.getTicket(storyId, { fresh: true });
|
|
182
|
-
} catch (err) {
|
|
183
|
-
return {
|
|
184
|
-
storyId,
|
|
185
|
-
status: 'failed',
|
|
186
|
-
reconciledFromGitHub: true,
|
|
187
|
-
reconcileError: err?.message ?? String(err),
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
const labels = Array.isArray(ticket?.labels) ? ticket.labels : [];
|
|
192
|
-
const isDone = labels.includes('agent::done') || ticket?.state === 'closed';
|
|
193
|
-
const isBlocked = !isDone && labels.includes('agent::blocked');
|
|
194
|
-
let status = 'failed';
|
|
195
|
-
if (isDone) status = 'done';
|
|
196
|
-
else if (isBlocked) status = 'blocked';
|
|
197
|
-
|
|
198
|
-
const out = { storyId, status, reconciledFromGitHub: true };
|
|
199
|
-
|
|
200
|
-
// Story #3907 — for a blocked Story, recover the blocker comment id from
|
|
201
|
-
// the latest `friction` structured comment so the operator is routed to the
|
|
202
|
-
// blocker (and its evidence), not a blind re-run. Best-effort: a missing
|
|
203
|
-
// friction comment leaves `status: 'blocked'` with no id, which is still
|
|
204
|
-
// strictly more accurate than the old `failed` downgrade.
|
|
205
|
-
if (isBlocked) {
|
|
206
|
-
try {
|
|
207
|
-
const friction = await findStructuredComment(
|
|
208
|
-
provider,
|
|
209
|
-
storyId,
|
|
210
|
-
'friction',
|
|
211
|
-
);
|
|
212
|
-
if (friction && friction.id != null) {
|
|
213
|
-
out.blockerCommentId = String(friction.id);
|
|
214
|
-
}
|
|
215
|
-
} catch (err) {
|
|
216
|
-
out.reconcileError = err?.message ?? String(err);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Cross-look the story-run-progress comment for the phase when present.
|
|
221
|
-
// Story #3909 retired the per-Story story-run-progress *comment*
|
|
222
|
-
// (the redundant mid-flight surface), so this read now usually finds nothing
|
|
223
|
-
// and the reconciled row degrades to label-only — which is fine: the labels
|
|
224
|
-
// are the authoritative state. Failure here is non-fatal.
|
|
225
|
-
try {
|
|
226
|
-
const comment = await findStructuredComment(
|
|
227
|
-
provider,
|
|
228
|
-
storyId,
|
|
229
|
-
STORY_RUN_PROGRESS_TYPE,
|
|
230
|
-
);
|
|
231
|
-
const payload = comment ? parseFencedJsonComment(comment) : null;
|
|
232
|
-
if (payload && typeof payload === 'object') {
|
|
233
|
-
if (typeof payload.phase === 'string') out.phase = payload.phase;
|
|
234
|
-
}
|
|
235
|
-
} catch (err) {
|
|
236
|
-
out.reconcileError = err?.message ?? String(err);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return out;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Render a single friction-comment body listing every malformed sub-agent
|
|
244
|
-
* return for a recorder beat. Pure helper — no provider call. Exposed so
|
|
245
|
-
* tests can pin the body shape.
|
|
246
|
-
*
|
|
247
|
-
* Story #4155 — under the ready-set runtime there is no wave index; the
|
|
248
|
-
* recorder records the Stories it was handed, so the body is keyed by Epic
|
|
249
|
-
* only.
|
|
250
|
-
*
|
|
251
|
-
* @param {{
|
|
252
|
-
* epicId: number,
|
|
253
|
-
* failures: Array<{ storyId: number, error: string, returnText: string }>,
|
|
254
|
-
* }} args
|
|
255
|
-
* @returns {string}
|
|
256
|
-
*/
|
|
257
|
-
export function renderMalformedReturnsFriction({ epicId, failures }) {
|
|
258
|
-
const lines = [
|
|
259
|
-
`### 🚧 epic-execute friction — Epic #${epicId}`,
|
|
260
|
-
'',
|
|
261
|
-
`**Reason:** \`malformed-subagent-return\``,
|
|
262
|
-
'',
|
|
263
|
-
`${failures.length} sub-agent return(s) did not match the /deliver return contract.`,
|
|
264
|
-
'Each Story below was reconciled from GitHub (labels + `story-run-progress`)',
|
|
265
|
-
'and its recorded status downgraded to `failed` unless the live ticket',
|
|
266
|
-
'already carried `agent::done`.',
|
|
267
|
-
'',
|
|
268
|
-
];
|
|
269
|
-
for (const f of failures) {
|
|
270
|
-
lines.push(`- **Story #${f.storyId}** — ${f.error}`);
|
|
271
|
-
if (f.returnText) {
|
|
272
|
-
lines.push(` Original return: \`${quote(f.returnText)}\``);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return lines.join('\n');
|
|
276
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WaveScheduler — iterates the wave DAG returned by `Graph.computeWaves()`.
|
|
3
|
-
*
|
|
4
|
-
* Tracks progress (current wave index, waves completed) but never spawns
|
|
5
|
-
* workers. The `story-launcher` consumes the stories it yields.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export class WaveScheduler {
|
|
9
|
-
/**
|
|
10
|
-
* @param {object[][]} waves - Array of waves, each a list of story objects.
|
|
11
|
-
*/
|
|
12
|
-
constructor(waves) {
|
|
13
|
-
if (!Array.isArray(waves)) {
|
|
14
|
-
throw new TypeError('WaveScheduler expects a waves array');
|
|
15
|
-
}
|
|
16
|
-
this.waves = waves;
|
|
17
|
-
this.currentIndex = 0;
|
|
18
|
-
this.completed = new Set();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
get totalWaves() {
|
|
22
|
-
return this.waves.length;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
get currentWave() {
|
|
26
|
-
return this.currentIndex;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
hasMoreWaves() {
|
|
30
|
-
return this.currentIndex < this.waves.length;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Returns the next wave to dispatch and advances the pointer. Returns null
|
|
35
|
-
* if no waves remain.
|
|
36
|
-
*
|
|
37
|
-
* @returns {{ index: number, stories: object[] } | null}
|
|
38
|
-
*/
|
|
39
|
-
nextWave() {
|
|
40
|
-
if (!this.hasMoreWaves()) return null;
|
|
41
|
-
const index = this.currentIndex;
|
|
42
|
-
const stories = this.waves[index];
|
|
43
|
-
this.currentIndex += 1;
|
|
44
|
-
return { index, stories };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Marks a previously-yielded wave as complete. Idempotent. Throws if the
|
|
49
|
-
* index is out of range or refers to a wave not yet yielded.
|
|
50
|
-
*
|
|
51
|
-
* @param {number} index
|
|
52
|
-
*/
|
|
53
|
-
markWaveComplete(index) {
|
|
54
|
-
if (!Number.isInteger(index) || index < 0 || index >= this.waves.length) {
|
|
55
|
-
throw new RangeError(`Wave index ${index} is out of range`);
|
|
56
|
-
}
|
|
57
|
-
if (index >= this.currentIndex) {
|
|
58
|
-
throw new Error(`Wave ${index} has not been yielded yet`);
|
|
59
|
-
}
|
|
60
|
-
this.completed.add(index);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
completedWaves() {
|
|
64
|
-
return [...this.completed].sort((a, b) => a - b);
|
|
65
|
-
}
|
|
66
|
-
}
|