mandrel 1.93.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/acceptance-critic.md +129 -0
- package/.agents/agents/story-worker.md +161 -0
- package/.agents/docs/SDLC.md +489 -1285
- package/.agents/docs/agentrc-reference.json +177 -67
- package/.agents/docs/configuration.md +108 -136
- package/.agents/docs/execution-reference.md +44 -22
- package/.agents/docs/quality-gates.md +13 -19
- package/.agents/docs/workflows.md +3 -3
- package/.agents/instructions.md +107 -108
- package/.agents/rules/ci-remediation.md +8 -12
- package/.agents/rules/git-conventions-reference.md +224 -0
- package/.agents/rules/git-conventions.md +42 -223
- package/.agents/rules/security-baseline.md +5 -0
- package/.agents/rules/testing-standards.md +106 -13
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +71 -201
- 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/acceptance-eval.js +62 -18
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- 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-context-budget.js +62 -5
- 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/diagnose-friction.js +0 -6
- 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/bootstrap/project-bootstrap.js +56 -18
- 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 +160 -22
- package/.agents/scripts/lib/config/acceptance-eval.js +52 -5
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +103 -0
- package/.agents/scripts/lib/config/explain.js +57 -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 +3 -8
- package/.agents/scripts/lib/config-settings-schema-delivery.js +46 -136
- package/.agents/scripts/lib/config-settings-schema-quality.js +17 -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 +39 -4
- 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 +112 -3
- package/.agents/scripts/lib/observability/hook-heartbeat.js +187 -0
- package/.agents/scripts/lib/observability/source-classifier.js +3 -3
- package/.agents/scripts/lib/observability/tool-trace-hook.js +15 -4
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +111 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +32 -4
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +273 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +204 -0
- 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-critic-conditions.js +3 -2
- 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 +14 -14
- 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/close-validation.js +1 -0
- 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 +3 -8
- 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/provider-factory.js +1 -1
- 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/post-structured-comment.js +38 -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/sync-claude-agents.js +165 -0
- package/.agents/scripts/update-ticket-state.js +37 -15
- package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
- package/.agents/skills/core/api-and-interface-design/SKILL.md +5 -3
- package/.agents/skills/core/code-review-and-quality/SKILL.md +63 -7
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +1 -1
- package/.agents/skills/core/gates-and-baselines/SKILL.md +149 -0
- package/.agents/skills/core/idea-refinement/SKILL.md +8 -14
- package/.agents/skills/core/qa-coverage-mapping/SKILL.md +7 -7
- package/.agents/skills/core/scope-triage/SKILL.md +28 -172
- package/.agents/skills/skills.index.json +8 -418
- 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 -131
- 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 +52 -40
- 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/mandrel-sync-config.md +1 -1
- 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 +3 -2
- 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 +46 -0
- package/lib/cli/registry.js +95 -0
- package/lib/migrations/index.js +6 -5
- package/package.json +5 -3
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer-mobile.md +0 -120
- package/.agents/personas/engineer-web.md +0 -111
- package/.agents/personas/engineer.md +0 -119
- package/.agents/personas/product.md +0 -94
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/refactorer.md +0 -113
- package/.agents/personas/security-engineer.md +0 -112
- package/.agents/personas/sre.md +0 -86
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/personas/ux-designer.md +0 -95
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- 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 -590
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-decompose.js +0 -54
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-plan-spec.js +0 -64
- 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 -660
- 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 -388
- 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-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -384
- 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 -278
- 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/skill-capsule-loader.js +0 -109
- 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 -301
- 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/plan-critics.js +0 -199
- package/.agents/scripts/retro-run.js +0 -218
- 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 -335
- package/.agents/skills/core/baseline-refresh/SKILL.md +0 -181
- package/.agents/skills/core/ci-cd-and-automation/SKILL.md +0 -274
- package/.agents/skills/core/ci-cd-and-automation/examples.md +0 -211
- package/.agents/skills/core/code-simplification/SKILL.md +0 -389
- package/.agents/skills/core/context-engineering/SKILL.md +0 -309
- package/.agents/skills/core/context-engineering/examples.md +0 -58
- package/.agents/skills/core/deprecation-and-migration/SKILL.md +0 -250
- 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 -413
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/skills/core/frontend-ui-engineering/SKILL.md +0 -357
- package/.agents/skills/core/hydrate-context/SKILL.md +0 -123
- package/.agents/skills/core/idea-refinement/examples.md +0 -437
- package/.agents/skills/core/idea-refinement/frameworks.md +0 -135
- package/.agents/skills/core/incremental-implementation/SKILL.md +0 -271
- package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +0 -213
- package/.agents/skills/core/knowledge-transfer/SKILL.md +0 -180
- package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +0 -117
- package/.agents/skills/core/performance-optimization/SKILL.md +0 -314
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +0 -277
- package/.agents/skills/core/property-based-testing/SKILL.md +0 -148
- package/.agents/skills/core/refactoring-discipline/SKILL.md +0 -111
- package/.agents/skills/core/shipping-and-launch/SKILL.md +0 -328
- package/.agents/skills/core/spec-driven-development/SKILL.md +0 -252
- package/.agents/skills/core/test-driven-development/SKILL.md +0 -475
- package/.agents/skills/core/using-agent-skills/SKILL.md +0 -232
- package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +0 -31
- package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +0 -51
- package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +0 -76
- package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +0 -31
- package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +0 -33
- package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +0 -31
- package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +0 -51
- package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +0 -54
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +0 -29
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +0 -30
- package/.agents/skills/stack/backend/stripe-integration/SKILL.md +0 -57
- package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +0 -9
- package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +0 -48
- package/.agents/skills/stack/frontend/astro/SKILL.md +0 -62
- package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +0 -30
- package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +0 -29
- package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +0 -50
- package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +0 -58
- package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +0 -34
- package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +0 -51
- package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +0 -199
- package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +0 -68
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -534
- package/.agents/workflows/helpers/deliver-epic.md +0 -955
- package/.agents/workflows/helpers/deliver-stories.md +0 -440
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -427
- 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 -351
- 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,676 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/orchestration/epic-spec-reconciler-diff.js — pure-function diff
|
|
3
|
-
* engine for the epic-spec reconciler (Epic #1182 / Tech Spec #1483 /
|
|
4
|
-
* Story #1492).
|
|
5
|
-
*
|
|
6
|
-
* `diff({ spec, state, ghState })` walks the three inputs and emits a
|
|
7
|
-
* `Plan` (see `epic-spec-reconciler-ops.js`) carrying the structural
|
|
8
|
-
* mutations the apply engine needs to perform. The function is **pure**:
|
|
9
|
-
*
|
|
10
|
-
* • No file I/O, no GitHub provider calls, no clock, no env lookups.
|
|
11
|
-
* • Same inputs → byte-identical plan (operations sorted by slug; field
|
|
12
|
-
* keys + label arrays canonicalised).
|
|
13
|
-
* • Safe to call repeatedly. An empty diff is idempotent — re-running
|
|
14
|
-
* against the same `(spec, state, ghState)` triple yields the same
|
|
15
|
-
* empty plan.
|
|
16
|
-
*
|
|
17
|
-
* ## Inputs
|
|
18
|
-
*
|
|
19
|
-
* @typedef {object} SpecInput
|
|
20
|
-
* The parsed YAML returned by `lib/spec/loader.js#loadSpec`. Shape is
|
|
21
|
-
* `{ epic: {...}, stories: [...], gates?: {...} }`
|
|
22
|
-
* per `.agents/schemas/epic-spec.schema.json`.
|
|
23
|
-
*
|
|
24
|
-
* @typedef {object} StateMappingEntry
|
|
25
|
-
* @property {number} issueNumber GH issue number this slug maps to.
|
|
26
|
-
* @property {string} entity 'epic'|'story'.
|
|
27
|
-
* @property {string} [contentHash] Content hash captured at last
|
|
28
|
-
* reconcile; absence forces an
|
|
29
|
-
* update when ghState carries
|
|
30
|
-
* structural fields. Present from
|
|
31
|
-
* the writer in the apply phase.
|
|
32
|
-
* @property {string} [parentSlug] Parent slug at last reconcile.
|
|
33
|
-
* Used for relink detection.
|
|
34
|
-
* @property {string[]} [dependsOn] Sibling-story slugs at last
|
|
35
|
-
* reconcile (stories only).
|
|
36
|
-
*
|
|
37
|
-
* @typedef {object} StateInput
|
|
38
|
-
* @property {number} epicId
|
|
39
|
-
* @property {Record<string, StateMappingEntry>} mapping Slug → entry.
|
|
40
|
-
* @property {string} [lastReconciledAt]
|
|
41
|
-
*
|
|
42
|
-
* @typedef {object} GhIssueObservation
|
|
43
|
-
* @property {string} title
|
|
44
|
-
* @property {string} [body]
|
|
45
|
-
* @property {string[]} [labels]
|
|
46
|
-
* @property {'open'|'closed'} [state]
|
|
47
|
-
*
|
|
48
|
-
* @typedef {Record<string|number, GhIssueObservation>} GhStateInput
|
|
49
|
-
* Keyed by GH issue number. Stringified numeric keys are coerced so
|
|
50
|
-
* callers may pass either `{ 1234: {...} }` or `{ "1234": {...} }`.
|
|
51
|
-
*
|
|
52
|
-
* ## Algorithm
|
|
53
|
-
*
|
|
54
|
-
* 1. Walk the spec depth-first, emitting one logical entity per
|
|
55
|
-
* `(epic|feature|story|task)`. For each entity:
|
|
56
|
-
* - look up the slug in `state.mapping`.
|
|
57
|
-
* - if no mapping → Create.
|
|
58
|
-
* - if mapped → compare structural fields against ghState[
|
|
59
|
-
* mapping.issueNumber] → Update for any diff in title/body/
|
|
60
|
-
* labels/wave.
|
|
61
|
-
* - if mapping carries `parentSlug` or `dependsOn` and they
|
|
62
|
-
* differ from the spec → Relink.
|
|
63
|
-
* 2. Walk `state.mapping` for any slug that did NOT appear in the
|
|
64
|
-
* spec walk → Close.
|
|
65
|
-
* 3. Sort each bucket by slug for deterministic output, then return
|
|
66
|
-
* the plan.
|
|
67
|
-
*
|
|
68
|
-
* Edge cases the engine deliberately handles:
|
|
69
|
-
*
|
|
70
|
-
* • Epic-level entity has no parent → `parentSlug` is always `null`
|
|
71
|
-
* for the epic; relink never fires on the epic.
|
|
72
|
-
* • Tasks have no `wave` / `dependsOn` → those fields are skipped on
|
|
73
|
-
* tasks even when present in the input (defensive).
|
|
74
|
-
* • A slug in the mapping whose `ghState` is missing entirely still
|
|
75
|
-
* yields a Close (the mapping itself is the ground truth that the
|
|
76
|
-
* spec dropped this entity).
|
|
77
|
-
* • Label comparisons ignore order: arrays are sorted before compare.
|
|
78
|
-
* • `body` and other optional fields treat `undefined === ''` as
|
|
79
|
-
* equality so a spec that omits `body` does not flap against a GH
|
|
80
|
-
* issue with an empty body.
|
|
81
|
-
*/
|
|
82
|
-
|
|
83
|
-
import { composeStoryBody } from '../../providers/github/tickets.js';
|
|
84
|
-
import {
|
|
85
|
-
extractFrameworkStamp,
|
|
86
|
-
stampFrameworkVersion,
|
|
87
|
-
} from '../framework-version.js';
|
|
88
|
-
import { assertPlanLabelAllowList } from './epic-spec-reconciler-discriminator.js';
|
|
89
|
-
import {
|
|
90
|
-
closeOp,
|
|
91
|
-
createOp,
|
|
92
|
-
ENTITY_KINDS,
|
|
93
|
-
emptyPlan,
|
|
94
|
-
relinkOp,
|
|
95
|
-
updateOp,
|
|
96
|
-
} from './epic-spec-reconciler-ops.js';
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Compare two label arrays for equality, ignoring order. Returns true
|
|
100
|
-
* when both lists carry the same multiset of strings.
|
|
101
|
-
*
|
|
102
|
-
* @param {string[]|undefined} a
|
|
103
|
-
* @param {string[]|undefined} b
|
|
104
|
-
* @returns {boolean}
|
|
105
|
-
*/
|
|
106
|
-
function labelsEqual(a, b) {
|
|
107
|
-
const left = [...(a ?? [])].sort();
|
|
108
|
-
const right = [...(b ?? [])].sort();
|
|
109
|
-
if (left.length !== right.length) return false;
|
|
110
|
-
for (let i = 0; i < left.length; i += 1) {
|
|
111
|
-
if (left[i] !== right[i]) return false;
|
|
112
|
-
}
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Label-namespace prefixes that the reconciler must NOT strip from the
|
|
118
|
-
* Epic on persist. The decomposer renders the Epic spec entry from
|
|
119
|
-
* `{ id, title }` only — it does not carry `epic.labels` through — so
|
|
120
|
-
* a naive replace-style label diff would propose removing operator-
|
|
121
|
-
* managed metadata that lives in these namespaces.
|
|
122
|
-
*
|
|
123
|
-
* Why: Story #2056 / Epic #1994 — `/plan` was silently stripping
|
|
124
|
-
* `type::epic` and `risk::*` from the parent Epic on every decompose,
|
|
125
|
-
* which then broke `dispatcher.js` (`type "unknown"`). Defence-in-depth
|
|
126
|
-
* lives here in the diff engine: even if a future spec author drops
|
|
127
|
-
* these labels, the reconciler will not propose their removal.
|
|
128
|
-
*
|
|
129
|
-
* Symmetry with the `agent::*` allow-list (owned by the wave-runner,
|
|
130
|
-
* defended in `epic-spec-reconciler-discriminator.js`): the diff engine
|
|
131
|
-
* treats both namespaces as out-of-scope for structural reconciliation,
|
|
132
|
-
* but via different mechanisms — `agent::*` is rejected at construction
|
|
133
|
-
* time, while these structural namespaces are merged into the Epic's
|
|
134
|
-
* after-set so the comparison stays a no-op.
|
|
135
|
-
*/
|
|
136
|
-
const PROTECTED_EPIC_LABEL_NAMESPACES = Object.freeze([
|
|
137
|
-
'type::',
|
|
138
|
-
'risk::',
|
|
139
|
-
// Story #3050 — `acceptance::*` is set by Phase 7 spec-persist when
|
|
140
|
-
// `planningRisk.acceptanceDisposition='not-applicable'` (or another
|
|
141
|
-
// disposition) and gates downstream `/deliver` start/finalize
|
|
142
|
-
// behavior. Before this namespace was protected, Phase 8 decompose
|
|
143
|
-
// diffed the Epic's labels against a spec entry that doesn't carry
|
|
144
|
-
// `acceptance::*`, silently emitting an Update that stripped the
|
|
145
|
-
// waiver and broke the contract documented in `.agents/docs/SDLC.md`.
|
|
146
|
-
'acceptance::',
|
|
147
|
-
// `planning::*` carries operator-applied planning-gate waivers
|
|
148
|
-
// (e.g. `planning::healthcheck-waived`, see persist.js gate). Same
|
|
149
|
-
// failure mode as `acceptance::*`: applied between spec and
|
|
150
|
-
// decompose, stripped by the naive replace-style diff.
|
|
151
|
-
'planning::',
|
|
152
|
-
]);
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* @param {unknown} label
|
|
156
|
-
* @returns {boolean}
|
|
157
|
-
*/
|
|
158
|
-
function isProtectedEpicLabel(label) {
|
|
159
|
-
if (typeof label !== 'string') return false;
|
|
160
|
-
return PROTECTED_EPIC_LABEL_NAMESPACES.some((ns) => label.startsWith(ns));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Return the spec's label list for the Epic entity, augmented with any
|
|
165
|
-
* protected-namespace labels observed on the live GH issue. Stable
|
|
166
|
-
* across calls (uses a Set to deduplicate). When neither input carries
|
|
167
|
-
* anything to merge, the original `specLabels` reference is returned
|
|
168
|
-
* unchanged so callers that compare references stay correct.
|
|
169
|
-
*
|
|
170
|
-
* @param {string[]|undefined} specLabels
|
|
171
|
-
* @param {string[]|undefined} obsLabels
|
|
172
|
-
* @returns {string[]|undefined}
|
|
173
|
-
*/
|
|
174
|
-
function mergeProtectedEpicLabels(specLabels, obsLabels) {
|
|
175
|
-
if (!Array.isArray(obsLabels) || obsLabels.length === 0) return specLabels;
|
|
176
|
-
const preserved = obsLabels.filter(isProtectedEpicLabel);
|
|
177
|
-
if (preserved.length === 0) return specLabels;
|
|
178
|
-
const merged = new Set([...(specLabels ?? []), ...preserved]);
|
|
179
|
-
return [...merged];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Treat undefined/null body as the empty string for comparison.
|
|
184
|
-
*
|
|
185
|
-
* @param {string|undefined|null} value
|
|
186
|
-
* @returns {string}
|
|
187
|
-
*/
|
|
188
|
-
function normaliseBody(value) {
|
|
189
|
-
return typeof value === 'string' ? value : '';
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Match the canonical orchestrator footer anchored on a `---` separator
|
|
194
|
-
* line followed by `parent: #<digits>`. Greedy through end of string so
|
|
195
|
-
* duplicate footer blocks and trailing `blocked by` lines are removed in
|
|
196
|
-
* a single pass. Kept local to this module so the diff engine does not
|
|
197
|
-
* depend on the Task-body renderer (Story #3185 / Epic #3163: the renderer
|
|
198
|
-
* is going away with the 2-tier producer cutover).
|
|
199
|
-
*/
|
|
200
|
-
const ORCHESTRATOR_FOOTER_RE = /\n?---[ \t]*\r?\n+parent:\s*#\d+[\s\S]*$/;
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Strip the canonical orchestrator footer from a body and return just the
|
|
204
|
-
* description portion. Safe to call on bodies without a footer — returns
|
|
205
|
-
* the input unchanged modulo a trailing-whitespace trim. The trim keeps
|
|
206
|
-
* the result byte-stable when fed back through `composeBodyWithFooter`.
|
|
207
|
-
*
|
|
208
|
-
* @param {string|null|undefined} body
|
|
209
|
-
* @returns {string}
|
|
210
|
-
*/
|
|
211
|
-
function stripFooter(body) {
|
|
212
|
-
const value = typeof body === 'string' ? body : '';
|
|
213
|
-
return value.replace(ORCHESTRATOR_FOOTER_RE, '').replace(/\s+$/, '');
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Compose the canonical orchestrator footer onto a spec body for non-epic
|
|
218
|
-
* entities. Resolves `parentSlug`/`dependsOn` slugs against the running
|
|
219
|
-
* `state.mapping` so the rendered footer carries the live issue numbers.
|
|
220
|
-
* Pure: identical inputs produce a byte-identical body.
|
|
221
|
-
*
|
|
222
|
-
* Story #2982 — without this re-composition, a body Update sourced from
|
|
223
|
-
* the YAML spec writes just the description, silently stripping
|
|
224
|
-
* `parent: #N` / `Epic: #M` / `blocked by #X` and breaking the cascade.
|
|
225
|
-
*
|
|
226
|
-
* Story #4300 — the footer rendering is single-sourced from
|
|
227
|
-
* `composeStoryBody` (`providers/github/tickets.js`), the same helper the
|
|
228
|
-
* CREATE path (`epic-spec-reconciler-apply.js` → `provider.createTicket`)
|
|
229
|
-
* uses. Story #3185 previously inlined a parallel `renderFooter` here to
|
|
230
|
-
* avoid depending on the (now-removed) legacy Task-body renderer; that
|
|
231
|
-
* inlined copy silently diverged from `composeStoryBody` by gating the
|
|
232
|
-
* `Epic: #<id>` line on `epicId !== parentId` — a 3-tier-era condition
|
|
233
|
-
* that is always false under the 2-tier hierarchy (a Story's parent IS
|
|
234
|
-
* the Epic), so force re-decompose (`/plan --force`, which routes through
|
|
235
|
-
* this UPDATE path) silently dropped `Epic: #<id>` from every refreshed
|
|
236
|
-
* Story body and broke `story-init.js`'s hierarchy resolution. Importing
|
|
237
|
-
* `composeStoryBody` directly makes that divergence structurally
|
|
238
|
-
* impossible going forward.
|
|
239
|
-
*
|
|
240
|
-
* Story #4382 — the create-time authoring stamp (`mandrel_version` /
|
|
241
|
-
* `authored_at` meta field + visible marker) is provenance, not spec-derived
|
|
242
|
-
* content, so this UPDATE path never mints or bumps it (which would also break
|
|
243
|
-
* this function's purity — the stamp uses a clock). Instead the stamp already
|
|
244
|
-
* present on the **live** GH body (`obsBody`) is preserved verbatim, and a
|
|
245
|
-
* legacy stamp-less body is left stamp-less (no backfill — see the Story's
|
|
246
|
-
* Out-of-Scope). Footer recomposition therefore runs in `stamp: false` mode.
|
|
247
|
-
*
|
|
248
|
-
* @param {{entity: string, parentSlug?: string|null, dependsOn?: string[]}} specEntity
|
|
249
|
-
* @param {string} specBody
|
|
250
|
-
* @param {{state?: StateInput}} ctx
|
|
251
|
-
* @param {string} [obsBody] - The live GH body, source of the preserved stamp.
|
|
252
|
-
* @returns {string}
|
|
253
|
-
*/
|
|
254
|
-
function composeBodyWithFooter(specEntity, specBody, ctx, obsBody = '') {
|
|
255
|
-
const state = ctx?.state ?? {};
|
|
256
|
-
const mapping = state.mapping ?? {};
|
|
257
|
-
const parentSlug = specEntity.parentSlug ?? null;
|
|
258
|
-
const parentId =
|
|
259
|
-
parentSlug && mapping[parentSlug]
|
|
260
|
-
? mapping[parentSlug].issueNumber
|
|
261
|
-
: undefined;
|
|
262
|
-
// Without a resolved parent we cannot render a meaningful footer.
|
|
263
|
-
// Fall back to the raw spec body — apply still writes something the
|
|
264
|
-
// operator can inspect; the missing-parent case is rare (the only
|
|
265
|
-
// current trigger is a relink-in-flight where the new parent has not
|
|
266
|
-
// landed yet, which surfaces elsewhere via the relink op anyway).
|
|
267
|
-
if (typeof parentId !== 'number') return specBody;
|
|
268
|
-
const epicId = typeof state.epicId === 'number' ? state.epicId : undefined;
|
|
269
|
-
const dependencies = Array.isArray(specEntity.dependsOn)
|
|
270
|
-
? specEntity.dependsOn
|
|
271
|
-
.map((slug) => mapping[slug]?.issueNumber)
|
|
272
|
-
.filter((id) => typeof id === 'number')
|
|
273
|
-
: [];
|
|
274
|
-
// Strip any orchestrator footer the spec already carries before
|
|
275
|
-
// recomposing. Without the strip we double-wrap when the spec body
|
|
276
|
-
// came from a producer that stored the raw GH body verbatim (footer
|
|
277
|
-
// included) or emits a canonical-form body. With the strip, the
|
|
278
|
-
// function is idempotent against its own output.
|
|
279
|
-
const head = stripFooter(specBody);
|
|
280
|
-
// Preserve the live body's create-time authoring stamp (Story #4382). When
|
|
281
|
-
// the spec head already carries it, `stampFrameworkVersion` is a no-op
|
|
282
|
-
// (immutable); when it doesn't, the stamp is restored from the live body so
|
|
283
|
-
// the recomposed form matches GH and no spurious body Update fires. A
|
|
284
|
-
// legacy body with no stamp stays stamp-less (no backfill).
|
|
285
|
-
const priorStamp = extractFrameworkStamp(obsBody);
|
|
286
|
-
const stampedHead = priorStamp
|
|
287
|
-
? stampFrameworkVersion(head, {
|
|
288
|
-
version: priorStamp.version,
|
|
289
|
-
authoredAt: priorStamp.authoredAt,
|
|
290
|
-
})
|
|
291
|
-
: head;
|
|
292
|
-
return composeStoryBody({
|
|
293
|
-
body: stampedHead,
|
|
294
|
-
parentId,
|
|
295
|
-
epicId,
|
|
296
|
-
dependencies,
|
|
297
|
-
stamp: false,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Pick the ghState observation for an issue number, coercing the key
|
|
303
|
-
* type so numeric and string keys interop.
|
|
304
|
-
*
|
|
305
|
-
* @param {GhStateInput|undefined|null} ghState
|
|
306
|
-
* @param {number} issueNumber
|
|
307
|
-
* @returns {GhIssueObservation|undefined}
|
|
308
|
-
*/
|
|
309
|
-
function ghObservation(ghState, issueNumber) {
|
|
310
|
-
if (!ghState) return undefined;
|
|
311
|
-
return ghState[issueNumber] ?? ghState[String(issueNumber)] ?? undefined;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Compute the structural-field changes between a spec entity and the GH
|
|
316
|
-
* observation. Returns an empty object when nothing changed.
|
|
317
|
-
*
|
|
318
|
-
* @param {{title: string, body?: string, labels?: string[], wave?: number, entity: string}} specEntity
|
|
319
|
-
* @param {GhIssueObservation|undefined} obs
|
|
320
|
-
* @param {StateMappingEntry} mapping
|
|
321
|
-
* @returns {Record<string, {before: unknown, after: unknown}>}
|
|
322
|
-
*/
|
|
323
|
-
function fieldChanges(specEntity, obs, mapping, ctx = {}) {
|
|
324
|
-
const changes = {};
|
|
325
|
-
if (!obs) {
|
|
326
|
-
// Mapped but GH side missing → treat as full update (apply will
|
|
327
|
-
// recreate body/labels/title). Callers can choose to escalate via
|
|
328
|
-
// the close-discriminator.
|
|
329
|
-
return changes;
|
|
330
|
-
}
|
|
331
|
-
if (specEntity.title !== obs.title) {
|
|
332
|
-
changes.title = { before: obs.title, after: specEntity.title };
|
|
333
|
-
}
|
|
334
|
-
// Schema contract (epic-spec.schema.json §epic.body and the parallel
|
|
335
|
-
// feature/story/task body fields): "When omitted, the GH issue body
|
|
336
|
-
// is left untouched". Pre-Story-#2283 the engine treated `undefined`
|
|
337
|
-
// as `""`, which emitted a destructive `body: <existing> → ""` Update
|
|
338
|
-
// on every `/plan` Phase 8 because the decomposer's renderer
|
|
339
|
-
// projects the Epic spec entry from `{ id, title }` only. Skip the
|
|
340
|
-
// body diff entirely when the spec did not carry a body string. An
|
|
341
|
-
// explicit `body: ""` in the spec still produces a clear-op when the
|
|
342
|
-
// GH side is non-empty (operator-authored intent to blank the body).
|
|
343
|
-
if (typeof specEntity.body === 'string') {
|
|
344
|
-
const specBody = specEntity.body;
|
|
345
|
-
const obsBody = normaliseBody(obs.body);
|
|
346
|
-
const isEpic = specEntity.entity === ENTITY_KINDS.EPIC;
|
|
347
|
-
if (isEpic) {
|
|
348
|
-
if (specBody !== obsBody) {
|
|
349
|
-
changes.body = { before: obsBody, after: specBody };
|
|
350
|
-
}
|
|
351
|
-
} else {
|
|
352
|
-
// Story #2982 — for non-epic entities, compare the spec body
|
|
353
|
-
// (re-composed with the canonical orchestrator footer) against
|
|
354
|
-
// the raw GH body. Single comparison catches:
|
|
355
|
-
// • description-only changes,
|
|
356
|
-
// • parent/Epic id changes (footer differs),
|
|
357
|
-
// • dependsOn changes (`blocked by` block differs),
|
|
358
|
-
// • duplicated footer blocks (obs has more than one),
|
|
359
|
-
// • missing footer (obs has none).
|
|
360
|
-
// Emit a body change only when the canonical form differs from
|
|
361
|
-
// what is on GH today — and write the canonical form back, so the
|
|
362
|
-
// footer cascade-readers depend on stays intact across resumes.
|
|
363
|
-
const after = composeBodyWithFooter(specEntity, specBody, ctx, obsBody);
|
|
364
|
-
if (after !== obsBody) {
|
|
365
|
-
changes.body = { before: obsBody, after };
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
const effectiveAfterLabels =
|
|
370
|
-
specEntity.entity === ENTITY_KINDS.EPIC
|
|
371
|
-
? mergeProtectedEpicLabels(specEntity.labels, obs.labels)
|
|
372
|
-
: specEntity.labels;
|
|
373
|
-
if (!labelsEqual(effectiveAfterLabels, obs.labels)) {
|
|
374
|
-
changes.labels = {
|
|
375
|
-
before: [...(obs.labels ?? [])].sort(),
|
|
376
|
-
after: [...(effectiveAfterLabels ?? [])].sort(),
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
// wave is story-only; only fire when both sides carry an integer and
|
|
380
|
-
// they differ. Mapping carries the last-known wave under
|
|
381
|
-
// `mapping.wave` (apply-engine populates it); absent → skip.
|
|
382
|
-
if (specEntity.entity === ENTITY_KINDS.STORY) {
|
|
383
|
-
const beforeWave =
|
|
384
|
-
typeof mapping.wave === 'number' ? mapping.wave : undefined;
|
|
385
|
-
const afterWave =
|
|
386
|
-
typeof specEntity.wave === 'number' ? specEntity.wave : undefined;
|
|
387
|
-
if (
|
|
388
|
-
beforeWave !== undefined &&
|
|
389
|
-
afterWave !== undefined &&
|
|
390
|
-
beforeWave !== afterWave
|
|
391
|
-
) {
|
|
392
|
-
changes.wave = { before: beforeWave, after: afterWave };
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return changes;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Compare two parent-edge values. `null` represents "no parent" (the
|
|
400
|
-
* epic root). Strings compare by value.
|
|
401
|
-
*
|
|
402
|
-
* @param {string|null|undefined} a
|
|
403
|
-
* @param {string|null|undefined} b
|
|
404
|
-
* @returns {boolean}
|
|
405
|
-
*/
|
|
406
|
-
function parentEqual(a, b) {
|
|
407
|
-
const left = a == null ? null : a;
|
|
408
|
-
const right = b == null ? null : b;
|
|
409
|
-
return left === right;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Sort an array of operations by slug, returning a new array.
|
|
414
|
-
*
|
|
415
|
-
* @template {{slug: string}} T
|
|
416
|
-
* @param {T[]} ops
|
|
417
|
-
* @returns {T[]}
|
|
418
|
-
*/
|
|
419
|
-
function sortBySlug(ops) {
|
|
420
|
-
return [...ops].sort((a, b) => a.slug.localeCompare(b.slug));
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Walk the spec and yield one structural-entity record per visited
|
|
425
|
-
* node. The spec is 2-tier (epic → stories), so the walk is a single
|
|
426
|
-
* flat pass over `spec.stories` after the Epic record.
|
|
427
|
-
*
|
|
428
|
-
* @param {SpecInput} spec
|
|
429
|
-
* @returns {Array<{
|
|
430
|
-
* slug: string,
|
|
431
|
-
* entity: string,
|
|
432
|
-
* title: string,
|
|
433
|
-
* body?: string,
|
|
434
|
-
* labels?: string[],
|
|
435
|
-
* wave?: number,
|
|
436
|
-
* parentSlug: string|null,
|
|
437
|
-
* dependsOn?: string[],
|
|
438
|
-
* }>}
|
|
439
|
-
*/
|
|
440
|
-
function flattenSpec(spec) {
|
|
441
|
-
const out = [];
|
|
442
|
-
if (!spec || typeof spec !== 'object') return out;
|
|
443
|
-
|
|
444
|
-
// Epic — no parent.
|
|
445
|
-
if (spec.epic && typeof spec.epic === 'object') {
|
|
446
|
-
out.push({
|
|
447
|
-
slug: epicSlug(spec.epic),
|
|
448
|
-
entity: ENTITY_KINDS.EPIC,
|
|
449
|
-
title: String(spec.epic.title ?? ''),
|
|
450
|
-
body: spec.epic.body,
|
|
451
|
-
labels: spec.epic.labels,
|
|
452
|
-
parentSlug: null,
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
const epicAnchor = spec.epic ? epicSlug(spec.epic) : null;
|
|
457
|
-
// Duplicate-slug guard. The schema cannot express slug uniqueness across
|
|
458
|
-
// a hand-edited spec, and two same-slug Creates would orphan one GH
|
|
459
|
-
// issue (the second create overwrites the first's mapping entry).
|
|
460
|
-
// flattenSpec is the chokepoint both the loader path and the diff path
|
|
461
|
-
// flow through, so the check lives here.
|
|
462
|
-
const seenSlugs = new Set();
|
|
463
|
-
const duplicateSlugs = new Set();
|
|
464
|
-
for (const story of spec.stories ?? []) {
|
|
465
|
-
if (seenSlugs.has(story.slug)) duplicateSlugs.add(story.slug);
|
|
466
|
-
seenSlugs.add(story.slug);
|
|
467
|
-
}
|
|
468
|
-
if (duplicateSlugs.size > 0) {
|
|
469
|
-
throw new Error(
|
|
470
|
-
`spec contains duplicate story slug(s): ${[...duplicateSlugs].join(', ')}. ` +
|
|
471
|
-
`Each story slug must be unique — rename the duplicated entries in ` +
|
|
472
|
-
`the spec and re-run.`,
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
for (const story of spec.stories ?? []) {
|
|
476
|
-
out.push({
|
|
477
|
-
slug: story.slug,
|
|
478
|
-
entity: ENTITY_KINDS.STORY,
|
|
479
|
-
title: String(story.title ?? ''),
|
|
480
|
-
body: story.body,
|
|
481
|
-
labels: story.labels,
|
|
482
|
-
wave: story.wave,
|
|
483
|
-
parentSlug: epicAnchor,
|
|
484
|
-
dependsOn: story.dependsOn ?? [],
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
return out;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* The epic-level slug is synthetic — the spec keys the epic by GH issue
|
|
492
|
-
* number, not by a slug — but the reconciler needs a stable identifier
|
|
493
|
-
* to thread the epic entity through the operation surface (state
|
|
494
|
-
* mapping, plan formatter, etc). We use the canonical literal `epic`
|
|
495
|
-
* so the formatter can render it without special-casing.
|
|
496
|
-
*
|
|
497
|
-
* @param {{id: number}} epic
|
|
498
|
-
* @returns {string}
|
|
499
|
-
*/
|
|
500
|
-
function epicSlug(_epic) {
|
|
501
|
-
// Single epic per spec — schema requires it — so a constant slug is
|
|
502
|
-
// unambiguous and matches the way mapping is keyed in writeState
|
|
503
|
-
// (where the epic entry is stored under `epic`).
|
|
504
|
-
return `epic`;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Compute equality between two `dependsOn` lists, ignoring order.
|
|
509
|
-
*
|
|
510
|
-
* @param {string[]|undefined} a
|
|
511
|
-
* @param {string[]|undefined} b
|
|
512
|
-
* @returns {boolean}
|
|
513
|
-
*/
|
|
514
|
-
function dependsOnEqual(a, b) {
|
|
515
|
-
return labelsEqual(a, b);
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Entity kinds that only exist in pre-v4 (3-tier) state files. The v4
|
|
520
|
-
* hard cutover (v1.60.0) removed the Feature/Task tiers; encountering one
|
|
521
|
-
* of these in `state.mapping` means the state file predates the cutover
|
|
522
|
-
* and must be migrated by the operator — there is deliberately no legacy
|
|
523
|
-
* close-op support (hard-cutover policy, git-conventions.md).
|
|
524
|
-
*/
|
|
525
|
-
const LEGACY_ENTITY_KINDS = Object.freeze(new Set(['feature', 'task']));
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* Throw a loud, actionable error when the state mapping carries legacy
|
|
529
|
-
* Feature/Task entries. Raised early in `diff()` so the operator sees a
|
|
530
|
-
* migration instruction instead of `unknown entity kind: feature` from
|
|
531
|
-
* deep inside `closeOp`.
|
|
532
|
-
*
|
|
533
|
-
* @param {Record<string, StateMappingEntry>} mapping
|
|
534
|
-
*/
|
|
535
|
-
function assertNoLegacyEntities(mapping) {
|
|
536
|
-
const legacy = Object.entries(mapping).filter(([, entry]) =>
|
|
537
|
-
LEGACY_ENTITY_KINDS.has(entry?.entity),
|
|
538
|
-
);
|
|
539
|
-
if (legacy.length === 0) return;
|
|
540
|
-
const summary = legacy
|
|
541
|
-
.map(([slug, entry]) => `${slug} (#${entry.issueNumber}, ${entry.entity})`)
|
|
542
|
-
.join(', ');
|
|
543
|
-
throw new Error(
|
|
544
|
-
`diff: the epic state file is pre-v4 — it carries legacy Feature/Task ` +
|
|
545
|
-
`mapping entries: ${summary}. The 2-tier reconciler cannot process ` +
|
|
546
|
-
`these. Close the legacy Feature issues manually, then delete (or ` +
|
|
547
|
-
`reseed) the .agents/epics/<epicId>.state.json file per the v1.60.0 ` +
|
|
548
|
-
`migration notes, and re-run.`,
|
|
549
|
-
);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Diff `(spec, state, ghState)` into a `Plan`. See the module header for
|
|
554
|
-
* the full contract.
|
|
555
|
-
*
|
|
556
|
-
* @param {{spec: SpecInput, state: StateInput, ghState?: GhStateInput}} input
|
|
557
|
-
* @returns {import('./epic-spec-reconciler-ops.js').Plan}
|
|
558
|
-
*/
|
|
559
|
-
export function diff({ spec, state, ghState } = {}) {
|
|
560
|
-
const plan = emptyPlan();
|
|
561
|
-
if (!spec || typeof spec !== 'object') return plan;
|
|
562
|
-
if (!state || typeof state !== 'object') {
|
|
563
|
-
throw new TypeError('diff: state argument is required');
|
|
564
|
-
}
|
|
565
|
-
const mapping = state.mapping ?? {};
|
|
566
|
-
assertNoLegacyEntities(mapping);
|
|
567
|
-
const seenSpecSlugs = new Set();
|
|
568
|
-
|
|
569
|
-
for (const entity of flattenSpec(spec)) {
|
|
570
|
-
seenSpecSlugs.add(entity.slug);
|
|
571
|
-
// Epic #4474 (PR3) — a mapping entry without a numeric `issueNumber`
|
|
572
|
-
// is a partial-failure tombstone: the apply's state writer projects an
|
|
573
|
-
// entry per spec slug, but only completed creations carry a number
|
|
574
|
-
// (`buildState` seeds new slugs with `issueNumber: null`). Treating
|
|
575
|
-
// such an entry as "mapped" made a `--resume` after a rate-limit crash
|
|
576
|
-
// silently skip every failed creation (no Create emitted; the content
|
|
577
|
-
// hash matches, so no Update either — a lossy resume). A numberless
|
|
578
|
-
// entry is therefore unmapped here → Create. No duplicate risk: any
|
|
579
|
-
// live same-title issue was already reseeded onto the slug (with its
|
|
580
|
-
// number) by `reseedMappingFromGh` before the diff runs.
|
|
581
|
-
const mappedEntry = mapping[entity.slug];
|
|
582
|
-
const mapped = Number.isInteger(mappedEntry?.issueNumber)
|
|
583
|
-
? mappedEntry
|
|
584
|
-
: undefined;
|
|
585
|
-
|
|
586
|
-
if (!mapped) {
|
|
587
|
-
plan.creates.push(
|
|
588
|
-
createOp({
|
|
589
|
-
slug: entity.slug,
|
|
590
|
-
entity: entity.entity,
|
|
591
|
-
title: entity.title,
|
|
592
|
-
body: entity.body,
|
|
593
|
-
labels: entity.labels,
|
|
594
|
-
parentSlug:
|
|
595
|
-
entity.parentSlug === null ? undefined : entity.parentSlug,
|
|
596
|
-
dependsOn: entity.dependsOn,
|
|
597
|
-
wave: entity.wave,
|
|
598
|
-
}),
|
|
599
|
-
);
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// Mapped: check for content updates.
|
|
604
|
-
const obs = ghObservation(ghState, mapped.issueNumber);
|
|
605
|
-
const changes = fieldChanges(entity, obs, mapped, { state });
|
|
606
|
-
if (Object.keys(changes).length > 0) {
|
|
607
|
-
plan.updates.push(
|
|
608
|
-
updateOp({
|
|
609
|
-
slug: entity.slug,
|
|
610
|
-
entity: entity.entity,
|
|
611
|
-
issueNumber: mapped.issueNumber,
|
|
612
|
-
changes,
|
|
613
|
-
}),
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Mapped: check for relink (parent / dependsOn edge changes).
|
|
618
|
-
const relinkPayload = {};
|
|
619
|
-
const beforeParent = mapped.parentSlug ?? null;
|
|
620
|
-
const afterParent = entity.parentSlug ?? null;
|
|
621
|
-
if (
|
|
622
|
-
!parentEqual(beforeParent, afterParent) &&
|
|
623
|
-
entity.entity !== ENTITY_KINDS.EPIC
|
|
624
|
-
) {
|
|
625
|
-
relinkPayload.parent = { before: beforeParent, after: afterParent };
|
|
626
|
-
}
|
|
627
|
-
if (entity.entity === ENTITY_KINDS.STORY) {
|
|
628
|
-
const beforeDeps = mapped.dependsOn ?? [];
|
|
629
|
-
const afterDeps = entity.dependsOn ?? [];
|
|
630
|
-
if (!dependsOnEqual(beforeDeps, afterDeps)) {
|
|
631
|
-
relinkPayload.dependsOn = { before: beforeDeps, after: afterDeps };
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
if (Object.keys(relinkPayload).length > 0) {
|
|
635
|
-
plan.relinks.push(
|
|
636
|
-
relinkOp({
|
|
637
|
-
slug: entity.slug,
|
|
638
|
-
entity: entity.entity,
|
|
639
|
-
issueNumber: mapped.issueNumber,
|
|
640
|
-
...relinkPayload,
|
|
641
|
-
}),
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// Closes — anything in mapping not seen in spec. A numberless
|
|
647
|
-
// partial-failure tombstone (see above) has no live issue to close;
|
|
648
|
-
// dropping it from the spec simply drops the entry.
|
|
649
|
-
for (const [slug, mapped] of Object.entries(mapping)) {
|
|
650
|
-
if (seenSpecSlugs.has(slug)) continue;
|
|
651
|
-
if (!Number.isInteger(mapped?.issueNumber)) continue;
|
|
652
|
-
plan.closes.push(
|
|
653
|
-
closeOp({
|
|
654
|
-
slug,
|
|
655
|
-
entity: mapped.entity ?? ENTITY_KINDS.STORY,
|
|
656
|
-
issueNumber: mapped.issueNumber,
|
|
657
|
-
title: mapped.title,
|
|
658
|
-
}),
|
|
659
|
-
);
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
plan.creates = sortBySlug(plan.creates);
|
|
663
|
-
plan.updates = sortBySlug(plan.updates);
|
|
664
|
-
plan.closes = sortBySlug(plan.closes);
|
|
665
|
-
plan.relinks = sortBySlug(plan.relinks);
|
|
666
|
-
|
|
667
|
-
// Diff-time safety net (Story #1493 / Task #1515). The diff engine
|
|
668
|
-
// never *intends* to emit an agent::* payload, but defence-in-depth
|
|
669
|
-
// catches both a future spec-loader bug that would smuggle an agent::*
|
|
670
|
-
// through a structural field and an apply-pipeline regression that
|
|
671
|
-
// would otherwise silently corrupt wave-runner state. Throws
|
|
672
|
-
// `LabelAllowListViolation` synchronously.
|
|
673
|
-
assertPlanLabelAllowList(plan);
|
|
674
|
-
|
|
675
|
-
return plan;
|
|
676
|
-
}
|