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
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/orchestration/retro-runner.js — In-process Retro module (sequencer).
|
|
3
|
-
*
|
|
4
|
-
* Story #1155 (Epic #1142, 5.40.0) — extracts the helper-driven
|
|
5
|
-
* `epic-retro` invocation into a callable module so the
|
|
6
|
-
* `/deliver` runner can fire Phase E without a separate LLM
|
|
7
|
-
* helper turn. (Story #2259, Epic #2172: the legacy deliver-runner
|
|
8
|
-
* CLI was retired once delivery moved entirely into the slash
|
|
9
|
-
* command.) The retro fires before `/deliver`'s finalize step
|
|
10
|
-
* opens the PR — the operator's PR-merge is the final human gate, not
|
|
11
|
-
* the retro itself.
|
|
12
|
-
*
|
|
13
|
-
* Story #3001 — the runner body was decomposed into `./retro/phases/`
|
|
14
|
-
* (one file per functional unit) following the established pattern at
|
|
15
|
-
* `./git-cleanup/phases/`, `./story-close/phases/`, and
|
|
16
|
-
* `./post-merge/phases/`. This file is now a thin sequencer that owns
|
|
17
|
-
* the `runRetro` entry point and re-exports the phase-level helpers so
|
|
18
|
-
* existing import sites stay unchanged.
|
|
19
|
-
*
|
|
20
|
-
* Public API:
|
|
21
|
-
* - `runRetro({ epicId, provider, logger })` → `{ posted, compact, scorecard, body, defectClasses }`.
|
|
22
|
-
* - `composeRetroBody(input)` (pure, exported for tests).
|
|
23
|
-
* - `gatherRetroSignals({ epicId, provider })` (exported for tests).
|
|
24
|
-
* - `appendChecksSection(body, findings)` (pure, exported for tests).
|
|
25
|
-
* - `deriveDefectClasses(routedProposals)` (pure, exported for tests).
|
|
26
|
-
*
|
|
27
|
-
* Story #4135 (Epic #4131, F11) — the runner now derives a
|
|
28
|
-
* **recurring-defect-class signal** from the routed-proposal actionable
|
|
29
|
-
* items (categories that recurred ≥2 times across review/deliver-caught
|
|
30
|
-
* friction, or a force-flagged `agent::blocked` category). The derived
|
|
31
|
-
* classes ride on the `runRetro` envelope as `defectClasses[]` and are
|
|
32
|
-
* stamped onto the proposed `gh issue create` commands as `friction::<class>`
|
|
33
|
-
* labels (the routed-proposals composer already emits that label), which is
|
|
34
|
-
* the durable substrate the `/plan` Phase 0 prior-feedback fetcher reads
|
|
35
|
-
* back to surface recurring classes to the planner. The derivation is
|
|
36
|
-
* **no-op-safe**: absent or empty routed proposals yield an empty array and
|
|
37
|
-
* no behavioural change to the existing retro/post path.
|
|
38
|
-
*
|
|
39
|
-
* Behaviour:
|
|
40
|
-
* - Reads child Stories' `story-perf-summary` comments to aggregate
|
|
41
|
-
* `frictionByCategory` totals (Story #1046 unified-summary path).
|
|
42
|
-
* - Reads child Stories' label sets to count HITL pause events
|
|
43
|
-
* (`agent::blocked`).
|
|
44
|
-
* - Reads the Epic's `parked-follow-ons` structured comment for the
|
|
45
|
-
* parked + recut counts (with a fallback to per-Story body grep on the
|
|
46
|
-
* `<!-- recut-of: #N -->` marker).
|
|
47
|
-
* - Selects the compact (clean-manifest) or full retro shape via
|
|
48
|
-
* `isCleanManifest`.
|
|
49
|
-
* - Posts the composed markdown as a `retro` structured comment on the
|
|
50
|
-
* Epic, terminated with the `retro-complete: <ISO>` HTML marker.
|
|
51
|
-
* - **Never** routes through `notify.js` — GitHub is the sole retro
|
|
52
|
-
* archive; the webhook must not see the retro body.
|
|
53
|
-
*/
|
|
54
|
-
|
|
55
|
-
import nodeFs from 'node:fs';
|
|
56
|
-
|
|
57
|
-
import { runChecks } from '../checks/index.js';
|
|
58
|
-
import { assembleState } from '../checks/state.js';
|
|
59
|
-
import { composeRetroBody } from './retro/phases/compose-body.js';
|
|
60
|
-
import { gatherRetroSignals } from './retro/phases/gather-signals.js';
|
|
61
|
-
import { composeAndPostRetro } from './retro/phases/post-and-mirror.js';
|
|
62
|
-
import { upsertStructuredComment } from './ticketing.js';
|
|
63
|
-
|
|
64
|
-
// Re-export phase-level helpers so existing import sites stay unchanged.
|
|
65
|
-
export { appendChecksSection } from './retro/phases/checks.js';
|
|
66
|
-
export {
|
|
67
|
-
composeRetroBody,
|
|
68
|
-
deriveDefectClasses,
|
|
69
|
-
} from './retro/phases/compose-body.js';
|
|
70
|
-
export { gatherRetroSignals } from './retro/phases/gather-signals.js';
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Public: compose and post the retro structured comment on the Epic.
|
|
74
|
-
*
|
|
75
|
-
* Story #1290 (Epic #1143) — at /deliver Phase 5, the runner invokes
|
|
76
|
-
* the self-healing checks registry with `scope: 'retro'` and
|
|
77
|
-
* `autoFix: false`. The retro is **read-only by construction**: the
|
|
78
|
-
* registry runner enforces the invariant by throwing if any caller flips
|
|
79
|
-
* `autoFix: true` under `scope: 'retro'`. Findings are appended to the
|
|
80
|
-
* retro body via `appendChecksSection`, which suppresses the section when
|
|
81
|
-
* findings are empty so the compact "🟢 Clean sprint" shape is preserved.
|
|
82
|
-
*
|
|
83
|
-
* Story #2252 — when `opts.bus` is supplied the runner emits
|
|
84
|
-
* `retro.start` immediately on entry and `retro.end` immediately before
|
|
85
|
-
* returning the envelope. On throw the helper emits `retro.end` with
|
|
86
|
-
* `posted: false` before re-throwing so the ledger always carries the
|
|
87
|
-
* closing boundary.
|
|
88
|
-
*
|
|
89
|
-
* @param {{
|
|
90
|
-
* epicId: number,
|
|
91
|
-
* provider: object,
|
|
92
|
-
* logger?: { info?: Function, warn?: Function },
|
|
93
|
-
* forceFull?: boolean,
|
|
94
|
-
* timestamp?: string,
|
|
95
|
-
* bus?: object|null,
|
|
96
|
-
* now?: () => number,
|
|
97
|
-
* manualInterventions?: number,
|
|
98
|
-
* frameworkRepo?: string,
|
|
99
|
-
* consumerRepo?: string,
|
|
100
|
-
* config?: object|null,
|
|
101
|
-
* gatherFn?: typeof gatherRetroSignals,
|
|
102
|
-
* composeFn?: typeof composeRetroBody,
|
|
103
|
-
* upsertFn?: typeof upsertStructuredComment,
|
|
104
|
-
* runChecksFn?: typeof runChecks,
|
|
105
|
-
* assembleStateFn?: typeof assembleState,
|
|
106
|
-
* cwd?: string,
|
|
107
|
-
* }} opts
|
|
108
|
-
* @returns {Promise<{
|
|
109
|
-
* posted: boolean,
|
|
110
|
-
* compact: boolean,
|
|
111
|
-
* scorecard: object,
|
|
112
|
-
* body: string,
|
|
113
|
-
* findings: object[],
|
|
114
|
-
* defectClasses: Array<{ category: string, occurrences: number, source: 'framework'|'consumer', label: string }>,
|
|
115
|
-
* commentId?: number,
|
|
116
|
-
* }>}
|
|
117
|
-
*/
|
|
118
|
-
export async function runRetro(opts = {}) {
|
|
119
|
-
const {
|
|
120
|
-
epicId,
|
|
121
|
-
provider,
|
|
122
|
-
logger,
|
|
123
|
-
forceFull = false,
|
|
124
|
-
timestamp,
|
|
125
|
-
bus,
|
|
126
|
-
now = Date.now,
|
|
127
|
-
manualInterventions = 0,
|
|
128
|
-
frameworkRepo,
|
|
129
|
-
consumerRepo,
|
|
130
|
-
config = null,
|
|
131
|
-
gatherFn = gatherRetroSignals,
|
|
132
|
-
composeFn = composeRetroBody,
|
|
133
|
-
upsertFn = upsertStructuredComment,
|
|
134
|
-
runChecksFn = runChecks,
|
|
135
|
-
assembleStateFn = assembleState,
|
|
136
|
-
cwd,
|
|
137
|
-
fsImpl = nodeFs,
|
|
138
|
-
perfThresholds = null,
|
|
139
|
-
} = opts;
|
|
140
|
-
|
|
141
|
-
if (!Number.isInteger(epicId) || epicId <= 0) {
|
|
142
|
-
throw new TypeError('runRetro: epicId is required (positive integer).');
|
|
143
|
-
}
|
|
144
|
-
if (!provider) {
|
|
145
|
-
throw new TypeError('runRetro: provider is required.');
|
|
146
|
-
}
|
|
147
|
-
// Epic #2646 Story C (Task #2700) — `bus` is now a hard input. The
|
|
148
|
-
// previous guarded `emitLifecycleSafe` helper that tolerated a null
|
|
149
|
-
// bus is gone.
|
|
150
|
-
if (!bus || typeof bus.emit !== 'function') {
|
|
151
|
-
throw new TypeError('runRetro: bus is required (object with emit()).');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
logger?.info?.(`[retro-runner] Composing retro for Epic #${epicId}...`);
|
|
155
|
-
const startedAt = typeof now === 'function' ? now() : Date.now();
|
|
156
|
-
await bus.emit('retro.start', { epicId });
|
|
157
|
-
let retroPathWritten = null;
|
|
158
|
-
try {
|
|
159
|
-
return await composeAndPostRetro({
|
|
160
|
-
epicId,
|
|
161
|
-
provider,
|
|
162
|
-
logger,
|
|
163
|
-
forceFull,
|
|
164
|
-
timestamp,
|
|
165
|
-
bus,
|
|
166
|
-
now,
|
|
167
|
-
manualInterventions,
|
|
168
|
-
frameworkRepo,
|
|
169
|
-
consumerRepo,
|
|
170
|
-
config,
|
|
171
|
-
gatherFn,
|
|
172
|
-
composeFn,
|
|
173
|
-
upsertFn,
|
|
174
|
-
runChecksFn,
|
|
175
|
-
assembleStateFn,
|
|
176
|
-
cwd,
|
|
177
|
-
fsImpl,
|
|
178
|
-
startedAt,
|
|
179
|
-
perfThresholds,
|
|
180
|
-
onMirrorWritten: (p) => {
|
|
181
|
-
retroPathWritten = p;
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
} catch (err) {
|
|
185
|
-
// Surface the closing boundary even on throw — the ledger must
|
|
186
|
-
// always show a matched start/end pair.
|
|
187
|
-
const endedAt = typeof now === 'function' ? now() : Date.now();
|
|
188
|
-
const payload = {
|
|
189
|
-
epicId,
|
|
190
|
-
posted: false,
|
|
191
|
-
durationMs: Math.max(0, Math.floor(endedAt - startedAt)),
|
|
192
|
-
};
|
|
193
|
-
if (retroPathWritten) payload.retroPath = retroPathWritten;
|
|
194
|
-
await bus.emit('retro.end', payload);
|
|
195
|
-
throw err;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
@@ -1,447 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/orchestration/spec-renderer.js — tickets.json → epic-spec.yaml
|
|
3
|
-
* projection (2-tier shape, Story #4041).
|
|
4
|
-
*
|
|
5
|
-
* The decomposer (`epic-plan-decompose-author`) produces a flat array
|
|
6
|
-
* of Story ticket objects keyed by stable `slug`s and linked by
|
|
7
|
-
* `depends_on`. The spec reconciler consumes the declarative
|
|
8
|
-
* structural representation defined by
|
|
9
|
-
* `.agents/schemas/epic-spec.schema.json` — a flat
|
|
10
|
-
* `{ epic, stories: [...] }` shape with Story-level `wave`,
|
|
11
|
-
* `dependsOn`, and inline `acceptance[]` / `verify[]` arrays
|
|
12
|
-
* projected from the decomposer's edges.
|
|
13
|
-
*
|
|
14
|
-
* Under the 2-tier hierarchy (Story #4041), Stories have no Feature
|
|
15
|
-
* parent and no Task children — acceptance and verify steps live
|
|
16
|
-
* inline on the Story, and Stories attach directly to the Epic. Any
|
|
17
|
-
* `feature` or `task` ticket in the decomposer input is rejected at
|
|
18
|
-
* index time (hard cutover; see `.agents/rules/git-conventions.md` §
|
|
19
|
-
* "Contract Cutovers — No Shim Layer").
|
|
20
|
-
*
|
|
21
|
-
* `renderSpec(tickets, opts)` is the pure projection between those
|
|
22
|
-
* two shapes. It:
|
|
23
|
-
*
|
|
24
|
-
* 1. Indexes the flat array by slug. Any unrecognised type
|
|
25
|
-
* (including the historical `feature` / `task`) raises
|
|
26
|
-
* immediately.
|
|
27
|
-
* 2. Filters Story `depends_on` edges down to inter-Story
|
|
28
|
-
* references in the same Epic.
|
|
29
|
-
* 3. Layers Stories into waves via `Graph.assignLayers` (depth in
|
|
30
|
-
* the story-only DAG = wave index). Stories with no inbound
|
|
31
|
-
* edges sit at `wave: 0`, matching the wave-runner's runtime
|
|
32
|
-
* convention (`build-wave-dag.js` produces the same layering at
|
|
33
|
-
* dispatch time from the live GH state, so the spec's waves are
|
|
34
|
-
* observationally identical to what dispatch will compute).
|
|
35
|
-
* 4. Walks the Stories in decomposer-declared order, preserving
|
|
36
|
-
* the order the LLM emitted so the reconciler's diff stays
|
|
37
|
-
* human-readable.
|
|
38
|
-
* 5. Strips `agent::*` labels from every entity. The decomposer
|
|
39
|
-
* doesn't normally write them, but they can leak via reverse-
|
|
40
|
-
* bootstrap from live GH state — and the schema forbids them
|
|
41
|
-
* (the reconciler explicitly enforces the structural/agent
|
|
42
|
-
* label split).
|
|
43
|
-
* 6. Projects each Story's inline `acceptance[]` / `verify[]`
|
|
44
|
-
* arrays onto the rendered Story. The decomposer may emit
|
|
45
|
-
* these either at the top level of the ticket (preferred) or
|
|
46
|
-
* nested under a structured `body` object — the renderer reads
|
|
47
|
-
* both and prefers the top-level fields when present.
|
|
48
|
-
* 7. Validates the produced object against the spec schema before
|
|
49
|
-
* returning. A renderer bug that emits a malformed spec is
|
|
50
|
-
* caught synchronously rather than failing later in `loadSpec`.
|
|
51
|
-
*
|
|
52
|
-
* Pure — no I/O. The validator is compiled once per process and
|
|
53
|
-
* cached by absolute schema path (same cache the loader uses
|
|
54
|
-
* internally; this module re-derives the path from its own location
|
|
55
|
-
* so the renderer imposes no new resolution surface).
|
|
56
|
-
*
|
|
57
|
-
* Round-trip: parse a tickets fixture → render → write to YAML →
|
|
58
|
-
* reload via `loadSpec` → the reloaded shape is structurally
|
|
59
|
-
* identical to the renderer output (modulo YAML's omission of
|
|
60
|
-
* `undefined` keys). Verified by `tests/scripts/spec-renderer.test.js`.
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
64
|
-
import path from 'node:path';
|
|
65
|
-
import { fileURLToPath } from 'node:url';
|
|
66
|
-
import Ajv2020 from 'ajv/dist/2020.js';
|
|
67
|
-
import addFormats from 'ajv-formats';
|
|
68
|
-
import { assignLayers } from '../Graph.js';
|
|
69
|
-
|
|
70
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
71
|
-
|
|
72
|
-
// scripts/lib/orchestration/ → scripts/lib/ → scripts/ → .agents/
|
|
73
|
-
const DEFAULT_SCHEMA_PATH = path.resolve(
|
|
74
|
-
__dirname,
|
|
75
|
-
'..',
|
|
76
|
-
'..',
|
|
77
|
-
'..',
|
|
78
|
-
'schemas',
|
|
79
|
-
'epic-spec.schema.json',
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const AGENT_LABEL_PREFIX = 'agent::';
|
|
83
|
-
|
|
84
|
-
let cachedValidator = null;
|
|
85
|
-
let cachedValidatorKey = null;
|
|
86
|
-
|
|
87
|
-
function getValidator(schemaPath) {
|
|
88
|
-
if (cachedValidator && cachedValidatorKey === schemaPath) {
|
|
89
|
-
return cachedValidator;
|
|
90
|
-
}
|
|
91
|
-
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
92
|
-
addFormats(ajv);
|
|
93
|
-
const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
|
|
94
|
-
cachedValidator = ajv.compile(schema);
|
|
95
|
-
cachedValidatorKey = schemaPath;
|
|
96
|
-
return cachedValidator;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Test-only hook: drop the cached validator so a subsequent call
|
|
101
|
-
* recompiles. The renderer reuses one Ajv instance per process for
|
|
102
|
-
* cost reasons; tests that swap to a sandbox schema must reset it.
|
|
103
|
-
*/
|
|
104
|
-
export function _resetRendererValidatorCacheForTests() {
|
|
105
|
-
cachedValidator = null;
|
|
106
|
-
cachedValidatorKey = null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Raised when the rendered spec object fails schema validation. The
|
|
111
|
-
* Ajv errors are normalised to `{ path, message }` so callers can
|
|
112
|
-
* report the offending JSON Pointer without unwrapping Ajv's envelope.
|
|
113
|
-
*/
|
|
114
|
-
export class SpecRenderValidationError extends Error {
|
|
115
|
-
/**
|
|
116
|
-
* @param {Array<{path: string, message: string, params?: object}>} issues
|
|
117
|
-
*/
|
|
118
|
-
constructor(issues) {
|
|
119
|
-
const head = issues[0] ?? { path: '/', message: 'unknown' };
|
|
120
|
-
super(
|
|
121
|
-
`Rendered spec failed schema validation at ${head.path}: ${head.message}`,
|
|
122
|
-
);
|
|
123
|
-
this.name = 'SpecRenderValidationError';
|
|
124
|
-
this.issues = issues;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function normaliseAjvErrors(ajvErrors) {
|
|
129
|
-
return ajvErrors.map((err) => {
|
|
130
|
-
let p = err.instancePath || '/';
|
|
131
|
-
if (
|
|
132
|
-
err.keyword === 'required' &&
|
|
133
|
-
typeof err.params?.missingProperty === 'string'
|
|
134
|
-
) {
|
|
135
|
-
const sep = p === '/' ? '' : '/';
|
|
136
|
-
p = `${p}${sep}${err.params.missingProperty}`;
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
path: p,
|
|
140
|
-
message: err.message ?? 'validation failed',
|
|
141
|
-
params: err.params,
|
|
142
|
-
};
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function sanitizeLabels(labels) {
|
|
147
|
-
if (!Array.isArray(labels)) return undefined;
|
|
148
|
-
const out = [];
|
|
149
|
-
const seen = new Set();
|
|
150
|
-
for (const raw of labels) {
|
|
151
|
-
if (typeof raw !== 'string' || raw.length === 0) continue;
|
|
152
|
-
if (raw.startsWith(AGENT_LABEL_PREFIX)) continue;
|
|
153
|
-
if (seen.has(raw)) continue;
|
|
154
|
-
seen.add(raw);
|
|
155
|
-
out.push(raw);
|
|
156
|
-
}
|
|
157
|
-
return out.length > 0 ? out : undefined;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Descriptor table for the structured-body → markdown projection, in
|
|
162
|
-
* canonical emit order (`## Goal`, `## Changes`, `## Acceptance`,
|
|
163
|
-
* `## Verify`). Each descriptor reads one body field and returns the
|
|
164
|
-
* section's markdown block when the field is present and non-empty, or `null`
|
|
165
|
-
* to omit it. Adding a section is a one-line data edit rather than a new
|
|
166
|
-
* branch in {@link renderBody}.
|
|
167
|
-
*
|
|
168
|
-
* @type {Array<{ field: string, render: (value: unknown) => string | null }>}
|
|
169
|
-
*/
|
|
170
|
-
const SPEC_BODY_SECTIONS = [
|
|
171
|
-
{
|
|
172
|
-
field: 'goal',
|
|
173
|
-
render: (goal) =>
|
|
174
|
-
typeof goal === 'string' && goal.length > 0 ? `## Goal\n${goal}` : null,
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
field: 'changes',
|
|
178
|
-
render: (changes) =>
|
|
179
|
-
Array.isArray(changes) && changes.length > 0
|
|
180
|
-
? `## Changes\n${changes.map((c) => `- ${String(c)}`).join('\n')}`
|
|
181
|
-
: null,
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
field: 'acceptance',
|
|
185
|
-
render: (acceptance) =>
|
|
186
|
-
Array.isArray(acceptance) && acceptance.length > 0
|
|
187
|
-
? `## Acceptance\n${acceptance.map((a) => `- [ ] ${String(a)}`).join('\n')}`
|
|
188
|
-
: null,
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
field: 'verify',
|
|
192
|
-
render: (verify) =>
|
|
193
|
-
Array.isArray(verify) && verify.length > 0
|
|
194
|
-
? `## Verify\n${verify.map((v) => `- ${String(v)}`).join('\n')}`
|
|
195
|
-
: null,
|
|
196
|
-
},
|
|
197
|
-
];
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Convert a decomposer body value into a spec `body` string. The
|
|
201
|
-
* decomposer schema admits two shapes for a Story body:
|
|
202
|
-
*
|
|
203
|
-
* - A short string (preferred under the 2-tier hierarchy).
|
|
204
|
-
* - A structured object (`{ goal, changes[], acceptance[],
|
|
205
|
-
* verify[] }`) carried over from the legacy 4-tier Task body
|
|
206
|
-
* shape, when the decomposer chooses to inline the Story's
|
|
207
|
-
* Goal/Changes alongside its acceptance/verify arrays.
|
|
208
|
-
*
|
|
209
|
-
* The spec schema only models `body` as a string. For structured
|
|
210
|
-
* bodies, render a compact markdown projection that preserves the
|
|
211
|
-
* original sections so the reconciler's downstream issue-body apply
|
|
212
|
-
* produces the same body the executing agent reads. For string
|
|
213
|
-
* bodies, pass through unchanged. `undefined` / empty values drop
|
|
214
|
-
* the field (the schema allows omission).
|
|
215
|
-
*
|
|
216
|
-
* The renderer does NOT round-trip structured bodies — by design,
|
|
217
|
-
* the spec is the canonical surface once it's authored, and
|
|
218
|
-
* structured bodies collapse into the markdown form on first
|
|
219
|
-
* projection.
|
|
220
|
-
*
|
|
221
|
-
* @param {unknown} body
|
|
222
|
-
* @returns {string | undefined}
|
|
223
|
-
*/
|
|
224
|
-
function renderBody(body) {
|
|
225
|
-
if (body == null) return undefined;
|
|
226
|
-
if (typeof body === 'string') {
|
|
227
|
-
return body.length > 0 ? body : undefined;
|
|
228
|
-
}
|
|
229
|
-
if (typeof body !== 'object') return undefined;
|
|
230
|
-
|
|
231
|
-
const sections = [];
|
|
232
|
-
for (const descriptor of SPEC_BODY_SECTIONS) {
|
|
233
|
-
const block = descriptor.render(body[descriptor.field]);
|
|
234
|
-
if (block !== null) sections.push(block);
|
|
235
|
-
}
|
|
236
|
-
return sections.length > 0 ? sections.join('\n\n') : undefined;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function assignNonEmpty(target, key, value) {
|
|
240
|
-
if (value === undefined) return;
|
|
241
|
-
target[key] = value;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Index the flat ticket array by slug. Returns the ordered Story slug
|
|
246
|
-
* list alongside the lookup map so the renderer can walk Stories in
|
|
247
|
-
* the decomposer's emit order.
|
|
248
|
-
*
|
|
249
|
-
* Under the 2-tier hierarchy (Story #4041), only the `story` type is
|
|
250
|
-
* recognised. Any other type — including the historical `feature` /
|
|
251
|
-
* `task` — falls through to the unknown-type guard and raises
|
|
252
|
-
* immediately; there is no silent drop.
|
|
253
|
-
*
|
|
254
|
-
* @param {Array<object>} tickets
|
|
255
|
-
*/
|
|
256
|
-
function indexTickets(tickets) {
|
|
257
|
-
const bySlug = new Map();
|
|
258
|
-
const storySlugs = [];
|
|
259
|
-
|
|
260
|
-
for (const t of tickets) {
|
|
261
|
-
if (!t || typeof t !== 'object') continue;
|
|
262
|
-
const slug = t.slug;
|
|
263
|
-
if (typeof slug !== 'string' || slug.length === 0) {
|
|
264
|
-
throw new Error(
|
|
265
|
-
`[spec-renderer] ticket missing slug: ${JSON.stringify(t).slice(0, 120)}`,
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
if (bySlug.has(slug)) {
|
|
269
|
-
throw new Error(`[spec-renderer] duplicate slug "${slug}"`);
|
|
270
|
-
}
|
|
271
|
-
bySlug.set(slug, t);
|
|
272
|
-
if (t.type === 'story') storySlugs.push(slug);
|
|
273
|
-
else {
|
|
274
|
-
throw new Error(
|
|
275
|
-
`[spec-renderer] ticket "${slug}" has unknown type "${t.type}"`,
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return { bySlug, storySlugs };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Build the slug-keyed story dependency graph used for wave layering.
|
|
284
|
-
* Drops every edge that does not reference another story slug in the
|
|
285
|
-
* same Epic (defensive — Story slugs that were typo'd or self-edges
|
|
286
|
-
* collapse here rather than polluting the wave count).
|
|
287
|
-
*
|
|
288
|
-
* @returns {{adjacency: Map<string, string[]>, layers: Map<string, number>}}
|
|
289
|
-
*/
|
|
290
|
-
function layerStories(storySlugs, bySlug) {
|
|
291
|
-
const storySet = new Set(storySlugs);
|
|
292
|
-
const adjacency = new Map();
|
|
293
|
-
for (const slug of storySlugs) {
|
|
294
|
-
const story = bySlug.get(slug);
|
|
295
|
-
const deps = Array.isArray(story.depends_on) ? story.depends_on : [];
|
|
296
|
-
const filtered = deps.filter((d) => storySet.has(d) && d !== slug);
|
|
297
|
-
adjacency.set(slug, filtered);
|
|
298
|
-
}
|
|
299
|
-
const layers = assignLayers(adjacency);
|
|
300
|
-
return { adjacency, layers };
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Project the decomposer ticket array into the structural spec object.
|
|
305
|
-
*
|
|
306
|
-
* @param {Array<object>} tickets — flat ticket array as emitted by the
|
|
307
|
-
* decomposer Skill (`type` = story, `slug`, `depends_on`, `title`,
|
|
308
|
-
* `body`, `labels`, `acceptance`, `verify`).
|
|
309
|
-
* @param {object} opts
|
|
310
|
-
* @param {{id: number, title: string, body?: string, labels?: string[]}} opts.epic
|
|
311
|
-
* — Epic descriptor (the decomposer doesn't emit the Epic row; it's
|
|
312
|
-
* supplied by the caller, which has the live Epic ticket in hand).
|
|
313
|
-
* @param {{baseline?: string, config?: string}} [opts.gates]
|
|
314
|
-
* — Optional gates section, passed through verbatim into the spec.
|
|
315
|
-
* @param {string} [opts.schemaPath] — override for the schema path
|
|
316
|
-
* (tests).
|
|
317
|
-
* @param {boolean} [opts.validate=true] — when `false`, skip final
|
|
318
|
-
* schema validation (used by tests that intentionally craft invalid
|
|
319
|
-
* inputs).
|
|
320
|
-
* @returns {object} spec — `{ epic, stories, gates? }` matching
|
|
321
|
-
* `.agents/schemas/epic-spec.schema.json`.
|
|
322
|
-
*/
|
|
323
|
-
function validateRenderSpecInputs(tickets, opts) {
|
|
324
|
-
if (!Array.isArray(tickets)) {
|
|
325
|
-
throw new TypeError('[spec-renderer] tickets must be an array');
|
|
326
|
-
}
|
|
327
|
-
if (!opts || typeof opts !== 'object' || !opts.epic) {
|
|
328
|
-
throw new TypeError('[spec-renderer] opts.epic is required');
|
|
329
|
-
}
|
|
330
|
-
const epic = opts.epic;
|
|
331
|
-
if (!Number.isInteger(epic.id) || epic.id < 1) {
|
|
332
|
-
throw new TypeError(
|
|
333
|
-
'[spec-renderer] opts.epic.id must be a positive integer',
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
if (typeof epic.title !== 'string' || epic.title.length === 0) {
|
|
337
|
-
throw new TypeError('[spec-renderer] opts.epic.title must be a string');
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
function pickStringArray(primary, fallback) {
|
|
342
|
-
const src = Array.isArray(primary)
|
|
343
|
-
? primary
|
|
344
|
-
: Array.isArray(fallback)
|
|
345
|
-
? fallback
|
|
346
|
-
: null;
|
|
347
|
-
if (!src) return null;
|
|
348
|
-
const filtered = src.filter((s) => typeof s === 'string' && s.length > 0);
|
|
349
|
-
return filtered.length > 0 ? filtered : null;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function extractStoryAcceptanceVerify(story) {
|
|
353
|
-
// Stories carry inline
|
|
354
|
-
// acceptance[] / verify[] arrays directly on the ticket. The
|
|
355
|
-
// decomposer may emit these either at the top level of the ticket
|
|
356
|
-
// (preferred) or nested under a structured `body` object (legacy
|
|
357
|
-
// shape shared with the historical Task body). Read from both and
|
|
358
|
-
// prefer the top-level fields when present.
|
|
359
|
-
const fromBody =
|
|
360
|
-
story.body && typeof story.body === 'object' && !Array.isArray(story.body)
|
|
361
|
-
? story.body
|
|
362
|
-
: null;
|
|
363
|
-
const out = {};
|
|
364
|
-
const acceptance = pickStringArray(story.acceptance, fromBody?.acceptance);
|
|
365
|
-
if (acceptance) out.acceptance = acceptance;
|
|
366
|
-
const verify = pickStringArray(story.verify, fromBody?.verify);
|
|
367
|
-
if (verify) out.verify = verify;
|
|
368
|
-
return out;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function buildStoryOut({ story, layers, storySet }) {
|
|
372
|
-
const deps = Array.isArray(story.depends_on) ? story.depends_on : [];
|
|
373
|
-
const dependsOn = [
|
|
374
|
-
...new Set(deps.filter((d) => storySet.has(d) && d !== story.slug)),
|
|
375
|
-
];
|
|
376
|
-
|
|
377
|
-
const out = {
|
|
378
|
-
slug: story.slug,
|
|
379
|
-
title: story.title,
|
|
380
|
-
wave: layers.get(story.slug) ?? 0,
|
|
381
|
-
};
|
|
382
|
-
assignNonEmpty(out, 'body', renderBody(story.body));
|
|
383
|
-
if (dependsOn.length > 0) out.dependsOn = dependsOn;
|
|
384
|
-
assignNonEmpty(out, 'labels', sanitizeLabels(story.labels));
|
|
385
|
-
const av = extractStoryAcceptanceVerify(story);
|
|
386
|
-
if (av.acceptance) out.acceptance = av.acceptance;
|
|
387
|
-
if (av.verify) out.verify = av.verify;
|
|
388
|
-
return out;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function buildEpicOut(epic) {
|
|
392
|
-
const out = { id: epic.id, title: epic.title };
|
|
393
|
-
assignNonEmpty(out, 'body', renderBody(epic.body));
|
|
394
|
-
assignNonEmpty(out, 'labels', sanitizeLabels(epic.labels));
|
|
395
|
-
return out;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function buildGatesOut(gates) {
|
|
399
|
-
if (!gates || typeof gates !== 'object') return null;
|
|
400
|
-
const out = {};
|
|
401
|
-
if (typeof gates.baseline === 'string' && gates.baseline.length > 0) {
|
|
402
|
-
out.baseline = gates.baseline;
|
|
403
|
-
}
|
|
404
|
-
if (typeof gates.config === 'string' && gates.config.length > 0) {
|
|
405
|
-
out.config = gates.config;
|
|
406
|
-
}
|
|
407
|
-
return Object.keys(out).length > 0 ? out : null;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function validateSpec(spec, schemaPath) {
|
|
411
|
-
const effectiveSchemaPath = schemaPath ?? DEFAULT_SCHEMA_PATH;
|
|
412
|
-
if (!existsSync(effectiveSchemaPath)) {
|
|
413
|
-
throw new Error(
|
|
414
|
-
`[spec-renderer] schema not found at ${effectiveSchemaPath}`,
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
const validator = getValidator(effectiveSchemaPath);
|
|
418
|
-
if (!validator(spec)) {
|
|
419
|
-
throw new SpecRenderValidationError(
|
|
420
|
-
normaliseAjvErrors(validator.errors ?? []),
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
export function renderSpec(tickets, opts = {}) {
|
|
426
|
-
validateRenderSpecInputs(tickets, opts);
|
|
427
|
-
const { epic, gates, schemaPath, validate = true } = opts;
|
|
428
|
-
|
|
429
|
-
const { bySlug, storySlugs } = indexTickets(tickets);
|
|
430
|
-
const { layers } = layerStories(storySlugs, bySlug);
|
|
431
|
-
const storySet = new Set(storySlugs);
|
|
432
|
-
|
|
433
|
-
const stories = storySlugs.map((storySlug) =>
|
|
434
|
-
buildStoryOut({
|
|
435
|
-
story: bySlug.get(storySlug),
|
|
436
|
-
layers,
|
|
437
|
-
storySet,
|
|
438
|
-
}),
|
|
439
|
-
);
|
|
440
|
-
|
|
441
|
-
const spec = { epic: buildEpicOut(epic), stories };
|
|
442
|
-
const gatesOut = buildGatesOut(gates);
|
|
443
|
-
if (gatesOut) spec.gates = gatesOut;
|
|
444
|
-
|
|
445
|
-
if (validate) validateSpec(spec, schemaPath);
|
|
446
|
-
return spec;
|
|
447
|
-
}
|