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,464 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* node:coverage ignore file */
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* wave-tick.js — thin CLI shim around `lib/wave-runner/tick.js`.
|
|
6
|
-
*
|
|
7
|
-
* Reads the `epic-run-state` checkpoint plus fresh Story labels and
|
|
8
|
-
* prints one `WaveTickResult` envelope. The slash-command operator
|
|
9
|
-
* (`/deliver`) consumes the envelope to decide whether to dispatch
|
|
10
|
-
* the next wave, observe in-flight stories, or finalize the Epic.
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* node .agents/scripts/wave-tick.js --epic <epicId> [--check-idle <minutes>]
|
|
14
|
-
*
|
|
15
|
-
* The tick is stateless — "loop until terminal" is the caller's job
|
|
16
|
-
* (today: the markdown's wave loop).
|
|
17
|
-
*
|
|
18
|
-
* Output: one JSON object on stdout. Schema in `lib/wave-runner/tick.js`.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { execFileSync } from 'node:child_process';
|
|
22
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
23
|
-
import { parseArgs } from 'node:util';
|
|
24
|
-
|
|
25
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
26
|
-
import { epicLedgerPath } from './lib/config/temp-paths.js';
|
|
27
|
-
import { resolveConfig } from './lib/config-resolver.js';
|
|
28
|
-
import { Logger } from './lib/Logger.js';
|
|
29
|
-
import { createProvider } from './lib/provider-factory.js';
|
|
30
|
-
import { tick } from './lib/wave-runner/tick.js';
|
|
31
|
-
|
|
32
|
-
const HELP = `Usage: node .agents/scripts/wave-tick.js --epic <epicId> [--check-idle <minutes>]
|
|
33
|
-
|
|
34
|
-
Stateless wave-loop planner. Reads the epic-run-state checkpoint on
|
|
35
|
-
Epic #<id>, evaluates the live story-label state, and prints one
|
|
36
|
-
WaveTickResult envelope describing the next dispatchable action.
|
|
37
|
-
|
|
38
|
-
When --check-idle <minutes> is supplied, the planner skips the normal
|
|
39
|
-
WaveTickResult emit and instead scans the per-Epic lifecycle ledger
|
|
40
|
-
(temp/epic-<id>/lifecycle.ndjson) for in-flight Stories whose most
|
|
41
|
-
recent lifecycle event is older than the threshold. Emits a single
|
|
42
|
-
JSON envelope on stdout and exits non-zero when at least one stalled
|
|
43
|
-
Story is detected. The envelope shape mirrors the wave-stall
|
|
44
|
-
structured-comment payload so the slash-command host can post it
|
|
45
|
-
verbatim.
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
export async function runWaveTickCli({
|
|
49
|
-
epicId,
|
|
50
|
-
cwd,
|
|
51
|
-
injectedProvider,
|
|
52
|
-
injectedConfig,
|
|
53
|
-
} = {}) {
|
|
54
|
-
if (!Number.isInteger(epicId) || epicId <= 0) {
|
|
55
|
-
throw new TypeError('runWaveTickCli: --epic must be a positive integer');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const config = injectedConfig ?? resolveConfig({ cwd });
|
|
59
|
-
const provider = injectedProvider ?? createProvider(config);
|
|
60
|
-
|
|
61
|
-
const result = await tick({
|
|
62
|
-
epic: epicId,
|
|
63
|
-
collaborators: { provider },
|
|
64
|
-
ctx: { config },
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
return result;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Scan the per-Epic lifecycle ledger and compute, per in-flight Story,
|
|
72
|
-
* the timestamp of its most recent lifecycle event. A Story is
|
|
73
|
-
* "in-flight" when the ledger carries a `story.dispatch.start` record
|
|
74
|
-
* without a matching `story.dispatch.end`. Any other `story.*` event
|
|
75
|
-
* (notably `story.heartbeat` from Story #3057) refreshes the Story's
|
|
76
|
-
* last-event timestamp.
|
|
77
|
-
*
|
|
78
|
-
* Pure helper — exported for unit tests.
|
|
79
|
-
*
|
|
80
|
-
* @param {string} ledgerPath Absolute path to `lifecycle.ndjson`.
|
|
81
|
-
* @returns {Map<number, string>} `storyId → ISO-8601 timestamp`. Empty
|
|
82
|
-
* when the ledger is missing, unreadable, or carries no in-flight
|
|
83
|
-
* Stories.
|
|
84
|
-
*/
|
|
85
|
-
export function readLedgerLastEvents(ledgerPath) {
|
|
86
|
-
if (!ledgerPath || !existsSync(ledgerPath)) return new Map();
|
|
87
|
-
let raw;
|
|
88
|
-
try {
|
|
89
|
-
raw = readFileSync(ledgerPath, 'utf8');
|
|
90
|
-
} catch {
|
|
91
|
-
return new Map();
|
|
92
|
-
}
|
|
93
|
-
if (!raw) return new Map();
|
|
94
|
-
const started = new Set();
|
|
95
|
-
const ended = new Set();
|
|
96
|
-
const lastTs = new Map();
|
|
97
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
98
|
-
if (!line) continue;
|
|
99
|
-
let record;
|
|
100
|
-
try {
|
|
101
|
-
record = JSON.parse(line);
|
|
102
|
-
} catch {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (!record || record.kind !== 'emitted') continue;
|
|
106
|
-
const event = typeof record.event === 'string' ? record.event : '';
|
|
107
|
-
if (!event.startsWith('story.')) continue;
|
|
108
|
-
const storyId = record.payload?.storyId;
|
|
109
|
-
if (!Number.isInteger(storyId) || storyId <= 0) continue;
|
|
110
|
-
if (event === 'story.dispatch.start') started.add(storyId);
|
|
111
|
-
else if (event === 'story.dispatch.end') ended.add(storyId);
|
|
112
|
-
const ts = typeof record.ts === 'string' ? record.ts : null;
|
|
113
|
-
if (ts) {
|
|
114
|
-
const prior = lastTs.get(storyId);
|
|
115
|
-
if (!prior || prior < ts) lastTs.set(storyId, ts);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
const out = new Map();
|
|
119
|
-
for (const id of started) {
|
|
120
|
-
if (ended.has(id)) continue;
|
|
121
|
-
const ts = lastTs.get(id);
|
|
122
|
-
if (ts) out.set(id, ts);
|
|
123
|
-
}
|
|
124
|
-
return out;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Single-delivery analogue of {@link readLedgerLastEvents} (Epic #4476, M5).
|
|
129
|
-
* A single-delivery run has no Story fan-out; the whole Epic is one long
|
|
130
|
-
* guarded session walking the Delivery Slicing table, and its liveness signal
|
|
131
|
-
* is `slice.heartbeat` (emitted off the token stream by the PostToolUse hook).
|
|
132
|
-
* This scans the ledger for `slice.*` events, treats a slice carrying a
|
|
133
|
-
* `slice.start` without a matching `slice.end` as in-flight, and returns each
|
|
134
|
-
* in-flight slice's most recent event timestamp — the same
|
|
135
|
-
* started-without-ended shape the Story reader uses, keyed by the string
|
|
136
|
-
* `sliceId`. Any `slice.*` event (notably `slice.heartbeat`) refreshes the
|
|
137
|
-
* timestamp.
|
|
138
|
-
*
|
|
139
|
-
* Pure helper — exported for unit tests.
|
|
140
|
-
*
|
|
141
|
-
* @param {string} ledgerPath Absolute path to `lifecycle.ndjson`.
|
|
142
|
-
* @returns {Map<string, string>} `sliceId → ISO-8601 timestamp`. Empty when
|
|
143
|
-
* the ledger is missing, unreadable, or carries no in-flight slices.
|
|
144
|
-
*/
|
|
145
|
-
export function readLedgerSliceEvents(ledgerPath) {
|
|
146
|
-
if (!ledgerPath || !existsSync(ledgerPath)) return new Map();
|
|
147
|
-
let raw;
|
|
148
|
-
try {
|
|
149
|
-
raw = readFileSync(ledgerPath, 'utf8');
|
|
150
|
-
} catch {
|
|
151
|
-
return new Map();
|
|
152
|
-
}
|
|
153
|
-
if (!raw) return new Map();
|
|
154
|
-
const started = new Set();
|
|
155
|
-
const ended = new Set();
|
|
156
|
-
const lastTs = new Map();
|
|
157
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
158
|
-
if (!line) continue;
|
|
159
|
-
let record;
|
|
160
|
-
try {
|
|
161
|
-
record = JSON.parse(line);
|
|
162
|
-
} catch {
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (!record || record.kind !== 'emitted') continue;
|
|
166
|
-
const event = typeof record.event === 'string' ? record.event : '';
|
|
167
|
-
if (!event.startsWith('slice.')) continue;
|
|
168
|
-
const sliceId = record.payload?.sliceId;
|
|
169
|
-
if (typeof sliceId !== 'string' || sliceId.length === 0) continue;
|
|
170
|
-
if (event === 'slice.start') started.add(sliceId);
|
|
171
|
-
else if (event === 'slice.end') ended.add(sliceId);
|
|
172
|
-
const ts = typeof record.ts === 'string' ? record.ts : null;
|
|
173
|
-
if (ts) {
|
|
174
|
-
const prior = lastTs.get(sliceId);
|
|
175
|
-
if (!prior || prior < ts) lastTs.set(sliceId, ts);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
const out = new Map();
|
|
179
|
-
for (const id of started) {
|
|
180
|
-
if (ended.has(id)) continue;
|
|
181
|
-
const ts = lastTs.get(id);
|
|
182
|
-
if (ts) out.set(id, ts);
|
|
183
|
-
}
|
|
184
|
-
return out;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Deterministic branch-activity resolver (Story #3900). Returns the epoch-ms
|
|
189
|
-
* timestamp of the most recent commit on `story-<storyId>` via
|
|
190
|
-
* `git log -1 --format=%cI`, or `null` when the branch does not exist, has no
|
|
191
|
-
* commits, or git is unavailable.
|
|
192
|
-
*
|
|
193
|
-
* This is the secondary liveness signal the idle watchdog consults before
|
|
194
|
-
* flagging a Story as stalled: a healthy long-running Story routinely exceeds
|
|
195
|
-
* the heartbeat threshold during `implementing → closing` (heartbeats fire
|
|
196
|
-
* only at phase transitions), but its branch keeps gaining commits. A recent
|
|
197
|
-
* commit is hard, deterministic evidence of forward progress that does not
|
|
198
|
-
* depend on a heartbeat landing in the ledger. Pure-ish (shells out to git)
|
|
199
|
-
* and injectable so unit tests never spawn a subprocess.
|
|
200
|
-
*
|
|
201
|
-
* @param {number} storyId
|
|
202
|
-
* @param {{ cwd?: string, exec?: typeof execFileSync, branch?: string }} [deps]
|
|
203
|
-
* `branch` overrides the default `story-<storyId>` ref — the single-delivery
|
|
204
|
-
* watchdog passes `epic-<id>` (Epic #4476, M5) to consult the one
|
|
205
|
-
* integration branch instead of a per-Story branch.
|
|
206
|
-
* @returns {number|null} epoch-ms of the last commit, or null.
|
|
207
|
-
*/
|
|
208
|
-
export function branchLastCommitMs(storyId, deps = {}) {
|
|
209
|
-
const exec = deps.exec ?? execFileSync;
|
|
210
|
-
const cwd = deps.cwd ?? process.cwd();
|
|
211
|
-
const branch =
|
|
212
|
-
typeof deps.branch === 'string' && deps.branch
|
|
213
|
-
? deps.branch
|
|
214
|
-
: `story-${storyId}`;
|
|
215
|
-
try {
|
|
216
|
-
const out = exec('git', ['log', '-1', '--format=%cI', branch, '--'], {
|
|
217
|
-
cwd,
|
|
218
|
-
encoding: 'utf8',
|
|
219
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
220
|
-
}).trim();
|
|
221
|
-
if (!out) return null;
|
|
222
|
-
const ms = Date.parse(out);
|
|
223
|
-
return Number.isFinite(ms) ? ms : null;
|
|
224
|
-
} catch {
|
|
225
|
-
// Branch absent / git unavailable — no deterministic signal.
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Build the wave-stall envelope. Pure helper — exported for unit tests.
|
|
232
|
-
*
|
|
233
|
-
* A Story whose latest ledger event is older than the threshold is *not*
|
|
234
|
-
* flagged as stalled when its `story-<id>` branch carries a commit newer
|
|
235
|
-
* than the threshold (Story #3900). Branch commits are deterministic
|
|
236
|
-
* forward-progress evidence that survives the heartbeat-cadence gap during
|
|
237
|
-
* `implementing → closing`, so consulting them eliminates the false-positive
|
|
238
|
-
* re-dispatch hazard (two agents on one branch) the watchdog otherwise
|
|
239
|
-
* manufactures for every healthy long-running Story.
|
|
240
|
-
*
|
|
241
|
-
* @param {object} args
|
|
242
|
-
* @param {number} args.epicId
|
|
243
|
-
* @param {number} args.thresholdMinutes
|
|
244
|
-
* @param {Map<number, string>} args.lastEvents `storyId → ISO-8601 ts`
|
|
245
|
-
* from {@link readLedgerLastEvents}.
|
|
246
|
-
* @param {Date} args.now Reference clock for the staleness comparison.
|
|
247
|
-
* @param {(storyId: number) => (number|null)} [args.branchActivity]
|
|
248
|
-
* Resolver for the last commit epoch-ms on `story-<id>`. Defaults to
|
|
249
|
-
* {@link branchLastCommitMs}. Injected for tests.
|
|
250
|
-
* @param {Map<string, string>} [args.sliceEvents] `sliceId → ISO-8601 ts`
|
|
251
|
-
* from {@link readLedgerSliceEvents} (Epic #4476, M5). Single-delivery has
|
|
252
|
-
* no Story fan-out; its liveness signal is `slice.heartbeat`. When
|
|
253
|
-
* non-empty, in-flight slices are surfaced under the additive
|
|
254
|
-
* `inFlightSlices` / `stalledSlices` fields — the story fields are
|
|
255
|
-
* untouched, so a fan-out run's envelope is byte-for-byte unchanged.
|
|
256
|
-
* @param {(epicId: number) => (number|null)} [args.epicBranchActivity]
|
|
257
|
-
* Resolver for the last commit epoch-ms on the single-delivery integration
|
|
258
|
-
* branch `epic-<id>` — the deterministic forward-progress signal for the
|
|
259
|
-
* one long session, mirroring the per-Story branch check. Injected for
|
|
260
|
-
* tests.
|
|
261
|
-
* @returns {{
|
|
262
|
-
* kind: 'wave-stall',
|
|
263
|
-
* epicId: number,
|
|
264
|
-
* thresholdMinutes: number,
|
|
265
|
-
* checkedAt: string,
|
|
266
|
-
* stalled: Array<{storyId: number, lastEventAt: string, idleMinutes: number}>,
|
|
267
|
-
* inFlight: number[],
|
|
268
|
-
* stalledSlices: Array<{sliceId: string, lastEventAt: string, idleMinutes: number}>,
|
|
269
|
-
* inFlightSlices: string[]
|
|
270
|
-
* }}
|
|
271
|
-
*/
|
|
272
|
-
export function buildWaveStallEnvelope({
|
|
273
|
-
epicId,
|
|
274
|
-
thresholdMinutes,
|
|
275
|
-
lastEvents,
|
|
276
|
-
now,
|
|
277
|
-
branchActivity = branchLastCommitMs,
|
|
278
|
-
sliceEvents = new Map(),
|
|
279
|
-
epicBranchActivity = (eid) =>
|
|
280
|
-
branchLastCommitMs(0, { branch: `epic-${eid}` }),
|
|
281
|
-
}) {
|
|
282
|
-
const checkedAt = now.toISOString();
|
|
283
|
-
const thresholdMs = thresholdMinutes * 60 * 1000;
|
|
284
|
-
const stalled = [];
|
|
285
|
-
const inFlight = [];
|
|
286
|
-
for (const [storyId, ts] of lastEvents) {
|
|
287
|
-
inFlight.push(storyId);
|
|
288
|
-
const eventMs = Date.parse(ts);
|
|
289
|
-
if (!Number.isFinite(eventMs)) continue;
|
|
290
|
-
const idleMs = now.getTime() - eventMs;
|
|
291
|
-
if (idleMs < thresholdMs) continue;
|
|
292
|
-
|
|
293
|
-
// Ledger says idle — consult the branch as a deterministic liveness
|
|
294
|
-
// signal before declaring a stall. A recent commit means the Story is
|
|
295
|
-
// making progress despite the heartbeat-cadence gap.
|
|
296
|
-
const commitMs = branchActivity(storyId);
|
|
297
|
-
if (
|
|
298
|
-
typeof commitMs === 'number' &&
|
|
299
|
-
Number.isFinite(commitMs) &&
|
|
300
|
-
now.getTime() - commitMs < thresholdMs
|
|
301
|
-
) {
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
stalled.push({
|
|
306
|
-
storyId,
|
|
307
|
-
lastEventAt: ts,
|
|
308
|
-
idleMinutes: Math.floor(idleMs / 60000),
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
inFlight.sort((a, b) => a - b);
|
|
312
|
-
stalled.sort((a, b) => a.storyId - b.storyId);
|
|
313
|
-
|
|
314
|
-
// Single-delivery slice liveness (additive). A slice that has gone silent
|
|
315
|
-
// past the threshold is a stall UNLESS the `epic-<id>` branch carries a
|
|
316
|
-
// recent commit — the same forward-progress carve-out the Story path uses.
|
|
317
|
-
const stalledSlices = [];
|
|
318
|
-
const inFlightSlices = [];
|
|
319
|
-
for (const [sliceId, ts] of sliceEvents) {
|
|
320
|
-
inFlightSlices.push(sliceId);
|
|
321
|
-
const eventMs = Date.parse(ts);
|
|
322
|
-
if (!Number.isFinite(eventMs)) continue;
|
|
323
|
-
const idleMs = now.getTime() - eventMs;
|
|
324
|
-
if (idleMs < thresholdMs) continue;
|
|
325
|
-
|
|
326
|
-
const commitMs = epicBranchActivity(epicId);
|
|
327
|
-
if (
|
|
328
|
-
typeof commitMs === 'number' &&
|
|
329
|
-
Number.isFinite(commitMs) &&
|
|
330
|
-
now.getTime() - commitMs < thresholdMs
|
|
331
|
-
) {
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
stalledSlices.push({
|
|
336
|
-
sliceId,
|
|
337
|
-
lastEventAt: ts,
|
|
338
|
-
idleMinutes: Math.floor(idleMs / 60000),
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
inFlightSlices.sort();
|
|
342
|
-
stalledSlices.sort((a, b) => a.sliceId.localeCompare(b.sliceId));
|
|
343
|
-
|
|
344
|
-
return {
|
|
345
|
-
kind: 'wave-stall',
|
|
346
|
-
epicId,
|
|
347
|
-
thresholdMinutes,
|
|
348
|
-
checkedAt,
|
|
349
|
-
stalled,
|
|
350
|
-
inFlight,
|
|
351
|
-
stalledSlices,
|
|
352
|
-
inFlightSlices,
|
|
353
|
-
};
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Run the `--check-idle` path. Exported so unit tests can drive the
|
|
358
|
-
* branch without spawning the CLI subprocess.
|
|
359
|
-
*
|
|
360
|
-
* @param {object} args
|
|
361
|
-
* @param {number} args.epicId
|
|
362
|
-
* @param {number} args.thresholdMinutes
|
|
363
|
-
* @param {string} [args.ledgerPath] Override for tests; defaults to the
|
|
364
|
-
* canonical `temp/epic-<id>/lifecycle.ndjson`.
|
|
365
|
-
* @param {Date} [args.now] Override for tests; defaults to wall clock.
|
|
366
|
-
* @param {object} [args.config] Optional resolved config for tempRoot.
|
|
367
|
-
* @param {(storyId: number) => (number|null)} [args.branchActivity]
|
|
368
|
-
* Branch-liveness resolver; defaults to {@link branchLastCommitMs}.
|
|
369
|
-
* Injected for tests so the watchdog never spawns git.
|
|
370
|
-
* @param {(epicId: number) => (number|null)} [args.epicBranchActivity]
|
|
371
|
-
* Single-delivery integration-branch (`epic-<id>`) liveness resolver;
|
|
372
|
-
* injected for tests. Defaults to {@link branchLastCommitMs} with the
|
|
373
|
-
* `epic-<id>` ref.
|
|
374
|
-
* @returns {{envelope: object, stalledCount: number}} `stalledCount` counts
|
|
375
|
-
* both stalled Stories (fan-out) and stalled slices (single delivery), so a
|
|
376
|
-
* dead single-delivery session still exits the CLI non-zero.
|
|
377
|
-
*/
|
|
378
|
-
export function runCheckIdle({
|
|
379
|
-
epicId,
|
|
380
|
-
thresholdMinutes,
|
|
381
|
-
ledgerPath,
|
|
382
|
-
now = new Date(),
|
|
383
|
-
config,
|
|
384
|
-
branchActivity = branchLastCommitMs,
|
|
385
|
-
epicBranchActivity,
|
|
386
|
-
}) {
|
|
387
|
-
if (!Number.isInteger(epicId) || epicId <= 0) {
|
|
388
|
-
throw new TypeError('runCheckIdle: epicId must be a positive integer');
|
|
389
|
-
}
|
|
390
|
-
if (!Number.isInteger(thresholdMinutes) || thresholdMinutes <= 0) {
|
|
391
|
-
throw new TypeError(
|
|
392
|
-
'runCheckIdle: thresholdMinutes must be a positive integer',
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
const resolvedPath = ledgerPath ?? epicLedgerPath(epicId, config);
|
|
396
|
-
const lastEvents = readLedgerLastEvents(resolvedPath);
|
|
397
|
-
const sliceEvents = readLedgerSliceEvents(resolvedPath);
|
|
398
|
-
const envelope = buildWaveStallEnvelope({
|
|
399
|
-
epicId,
|
|
400
|
-
thresholdMinutes,
|
|
401
|
-
lastEvents,
|
|
402
|
-
now,
|
|
403
|
-
branchActivity,
|
|
404
|
-
sliceEvents,
|
|
405
|
-
...(epicBranchActivity ? { epicBranchActivity } : {}),
|
|
406
|
-
});
|
|
407
|
-
return {
|
|
408
|
-
envelope,
|
|
409
|
-
stalledCount: envelope.stalled.length + envelope.stalledSlices.length,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
async function main(argv) {
|
|
414
|
-
const { values } = parseArgs({
|
|
415
|
-
args: argv,
|
|
416
|
-
options: {
|
|
417
|
-
epic: { type: 'string' },
|
|
418
|
-
'check-idle': { type: 'string' },
|
|
419
|
-
help: { type: 'boolean', short: 'h' },
|
|
420
|
-
},
|
|
421
|
-
strict: false,
|
|
422
|
-
allowPositionals: true,
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
if (values.help) {
|
|
426
|
-
process.stdout.write(`${HELP}\n`);
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const epicId = Number.parseInt(
|
|
431
|
-
String(values.epic ?? '').replace(/^#/, ''),
|
|
432
|
-
10,
|
|
433
|
-
);
|
|
434
|
-
if (!Number.isInteger(epicId) || epicId <= 0) {
|
|
435
|
-
Logger.error('wave-tick: --epic <id> is required (positive integer)');
|
|
436
|
-
process.exitCode = 2;
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (values['check-idle'] !== undefined) {
|
|
441
|
-
const thresholdMinutes = Number.parseInt(String(values['check-idle']), 10);
|
|
442
|
-
if (!Number.isInteger(thresholdMinutes) || thresholdMinutes <= 0) {
|
|
443
|
-
Logger.error(
|
|
444
|
-
'wave-tick: --check-idle <minutes> must be a positive integer',
|
|
445
|
-
);
|
|
446
|
-
process.exitCode = 2;
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
const { envelope, stalledCount } = runCheckIdle({
|
|
450
|
-
epicId,
|
|
451
|
-
thresholdMinutes,
|
|
452
|
-
});
|
|
453
|
-
process.stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
454
|
-
process.exitCode = stalledCount > 0 ? 1 : 0;
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const result = await runWaveTickCli({ epicId });
|
|
459
|
-
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
runAsCli(import.meta.url, () => main(process.argv.slice(2)), {
|
|
463
|
-
source: 'wave-tick',
|
|
464
|
-
});
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: epic-plan-consolidate
|
|
3
|
-
description: >-
|
|
4
|
-
Run a holistic, pre-persist consolidation pass over the draft Story
|
|
5
|
-
ticket array an Epic's decompose phase produced. Use during Phase 8 of
|
|
6
|
-
`/plan`, after `epic-plan-decompose-author` writes
|
|
7
|
-
`temp/epic-<Epic_ID>/tickets.json` and before `plan-persist.js`
|
|
8
|
-
validates and persists it. Reconciles the draft against the Tech Spec
|
|
9
|
-
"Delivery Slicing" ceiling via scope-preserving operations only.
|
|
10
|
-
allowed_tools:
|
|
11
|
-
- Read
|
|
12
|
-
- Write
|
|
13
|
-
- Bash
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
# epic-plan-consolidate
|
|
17
|
-
|
|
18
|
-
## Policy Capsule
|
|
19
|
-
|
|
20
|
-
- Run only after `epic-plan-decompose-author` has written `temp/epic-<Epic_ID>/tickets.json`; fail loudly if the draft array is missing. Read the sectioned Epic body (which carries the folded Tech Spec sections) from `temp/epic-<Epic_ID>/decomposer-context.json` (the same envelope the author skill consumed) — never re-fetch from GitHub, and never call the GitHub API from this Skill.
|
|
21
|
-
- Emit exactly two artifacts inside `temp/epic-<Epic_ID>/`: the **consolidated** `tickets.json` (overwriting the draft array in place) and a human-readable `consolidation-report.md` (the rationale + before/after diff the operator reviews at the HITL gate). Both MUST exist before returning.
|
|
22
|
-
- **Scope conservation is the load-bearing invariant.** You are a *critic*, not a second author: you MUST NOT add scope, invent tickets, or drop acceptance criteria. Every acceptance item and every `verify` entry present in the draft MUST survive into the consolidated array (possibly re-homed onto a merged Story). **This is your contract, not a machine guarantee:** there is **no runtime acceptance-union diff** on your output. The only deterministic runtime backstop the validator applies after you run is the standard ticket-structure validation — it does not re-derive the pre-consolidation acceptance/verify union, so a critic that silently dropped an acceptance item would **not** be caught downstream. (The repo's unit test exercises a *pure model* of the merge over an over-fragmented fixture to document the intended invariant; it does not inspect this Skill's actual output.) Conserve scope yourself, deliberately, on every merge.
|
|
23
|
-
- Your operations are constrained to exactly two shapes: **(1) merge two or more Stories** into one (union their `changes`/`acceptance`/`verify`/`references`, keep one coherent `goal`); **(2) rewire `depends_on`** so the edges still reference surviving sibling-Story slugs. No other mutation is permitted.
|
|
24
|
-
- Consume the Tech Spec **"Delivery Slicing"** section as a **ceiling, not a target**, when one is present: the Architect's proposed N shippable Stories is an *upper bound*, not a floor. Cluster the draft's Stories toward that grouping, and go **below** N when slices form dependent single-consumer chains — a slice whose "Independent? No" row carries no justification (parallelism, risk isolation, or delivery-envelope pressure) folds into its consumer. You **never** split above N. When the section is **absent**, degrade gracefully — apply only the cohesion rules below and leave the rest of the draft shape intact.
|
|
25
|
-
- Apply the same cohesion heuristic the author skill leads with: **one Story = one coherent change with one reason to exist**, and the **single-consumer merge rule** (a Story whose only consumer is one sibling Story is merged into that sibling). Lead every merge decision with the change's reason, not its file count.
|
|
26
|
-
- **Missing reason-to-exist check (cohesion gate).** Every draft Story body MUST carry a non-empty `reason_to_exist` meta field (the parseable form of "one Story = one coherent change with one reason to exist", encoded in the `<!-- meta: {"reason_to_exist": "..."} -->` comment and surfaced as `body.reason_to_exist` by `lib/story-body/story-body.js`). Flag any Story whose body carries **no** non-empty reason to exist: a Story that cannot state its single reason in one sentence is a cohesion smell — it is probably two Stories, or two Stories that should be merged into one. Name each flagged Story in the consolidation report so the operator sees the cohesion gap at the HITL diff gate.
|
|
27
|
-
- After every merge, **rewire `depends_on`**: drop self-edges, collapse edges that now point at the absorbing Story onto itself, and re-point any edge that named a now-deleted slug at its surviving successor. Never leave a `depends_on` referencing a slug absent from the consolidated array — the validator HARD-rejects unknown deps.
|
|
28
|
-
- The consolidation report MUST name each operation applied (merged slugs → surviving slug, rewired edges) with a one-line reason, plus a before/after Story-count line, so the operator can approve or reject at the HITL diff gate before the persist call. When the consolidated count drops **below** the Delivery-Slicing count (a below-ceiling coarsening), the report MUST surface that fact explicitly — e.g. `Below ceiling: N (Delivery Slicing) → M (consolidated)` — and attach a one-line rationale to each below-ceiling merge (which dependent single-consumer slice folded into which consumer, and why it was unjustified), so the operator sees the coarsening at the Phase 8.3 advisory diff.
|
|
29
|
-
|
|
30
|
-
## Role
|
|
31
|
-
|
|
32
|
-
Senior Project Manager + Orchestrator, acting as a **holistic critic** with
|
|
33
|
-
fresh context — deliberately *separate* from `epic-plan-decompose-author` (the
|
|
34
|
-
generator) so the pass is a fresh-context review, not a same-pass self-critique.
|
|
35
|
-
The `/plan` workflow delivers that fresh context by **dispatching this skill
|
|
36
|
-
inside a genuine sub-agent** (`Agent` tool, `subagent_type: general-purpose`) at
|
|
37
|
-
Phase 8.3, rather than activating it inline in the authoring turn — the
|
|
38
|
-
sub-agent does not inherit the conversation that authored the draft, so the
|
|
39
|
-
critic cannot grade its own homework.
|
|
40
|
-
|
|
41
|
-
> **Read [`examples.md`](./examples.md) on demand** for the extended rationale:
|
|
42
|
-
> why this critic runs with fresh context, why scope conservation is your
|
|
43
|
-
> contract rather than a runtime guarantee, and how the below-ceiling
|
|
44
|
-
> coarsening case is reported.
|
|
45
|
-
|
|
46
|
-
## When to use
|
|
47
|
-
|
|
48
|
-
`/plan` Phase 8, as the **8.3 — Holistic Consolidation** sub-step:
|
|
49
|
-
immediately after `epic-plan-decompose-author` writes
|
|
50
|
-
`temp/epic-<Epic_ID>/tickets.json` and **before**
|
|
51
|
-
`plan-persist.js --tickets …` validates and persists. The pass operates
|
|
52
|
-
on the temp artifact so the holistic adjustment happens before the GitHub
|
|
53
|
-
write; the deterministic validator runs *after* it, so the critic can never
|
|
54
|
-
emit a plan the validator would reject.
|
|
55
|
-
|
|
56
|
-
## Inputs
|
|
57
|
-
|
|
58
|
-
The `/plan` workflow dispatches this skill inside a fresh-context sub-agent,
|
|
59
|
-
passing the Epic ID as the Skill argument. The Skill itself reads:
|
|
60
|
-
|
|
61
|
-
- `temp/epic-<Epic_ID>/tickets.json` — the **draft** Story array the
|
|
62
|
-
`epic-plan-decompose-author` Skill wrote. This is the consolidation input.
|
|
63
|
-
- `temp/epic-<Epic_ID>/decomposer-context.json` — the authoring envelope
|
|
64
|
-
emitted by `plan-context.js --epic <Epic_ID>`. Read `epicBody`
|
|
65
|
-
from it — the sectioned Epic body carrying the folded Tech Spec
|
|
66
|
-
sections (there is no separate `techSpec` key — Story #4324). The
|
|
67
|
-
**"Delivery Slicing"**
|
|
68
|
-
section (authored by `epic-plan-spec-author`) is the
|
|
69
|
-
grouping **ceiling** when present (you may merge below it, never split above
|
|
70
|
-
it); degrade gracefully when it is absent.
|
|
71
|
-
|
|
72
|
-
## Outputs
|
|
73
|
-
|
|
74
|
-
- `temp/epic-<Epic_ID>/tickets.json` — the **consolidated** array, overwriting
|
|
75
|
-
the draft. Same schema as the author skill emits (flat Story array; Stories
|
|
76
|
-
carry top-level `acceptance[]` / `verify[]`; `body` is a serialized string).
|
|
77
|
-
The downstream `plan-persist.js --tickets …` validator is the final
|
|
78
|
-
gate — author for its rules, not for "looks right."
|
|
79
|
-
- `temp/epic-<Epic_ID>/consolidation-report.md` — a human-readable
|
|
80
|
-
rationale + before/after diff. This is the artifact the workflow shows the
|
|
81
|
-
operator at the HITL diff gate before the persist call.
|
|
82
|
-
|
|
83
|
-
Both files MUST exist before the Skill returns.
|
|
84
|
-
|
|
85
|
-
## Procedure
|
|
86
|
-
|
|
87
|
-
### Step 1 — Load the draft and the ceiling
|
|
88
|
-
|
|
89
|
-
Read `temp/epic-<Epic_ID>/tickets.json` (the draft array) and
|
|
90
|
-
`temp/epic-<Epic_ID>/decomposer-context.json` (for the `epicBody`, which
|
|
91
|
-
carries the Tech Spec sections). Locate
|
|
92
|
-
the **"Delivery Slicing"** section. Pin two facts before mutating
|
|
93
|
-
anything:
|
|
94
|
-
|
|
95
|
-
1. The **grouping ceiling** — the N shippable Stories the Architect proposed in
|
|
96
|
-
Delivery Slicing, or `null` when the section is absent (graceful-degrade
|
|
97
|
-
mode: cohesion rules only). Treat N as an upper bound you may go below, not a
|
|
98
|
-
floor you must hit. Note which slices are marked "Independent? No" and
|
|
99
|
-
whether each carries a justification (parallelism, risk isolation, or
|
|
100
|
-
delivery-envelope pressure) — an unjustified dependent slice is a
|
|
101
|
-
fold-into-consumer candidate.
|
|
102
|
-
2. The **draft Story count** — so you can spot over-fragmented capability
|
|
103
|
-
clusters.
|
|
104
|
-
|
|
105
|
-
### Step 2 — Plan the consolidation
|
|
106
|
-
|
|
107
|
-
Across the draft Story array, decide which Stories merge:
|
|
108
|
-
|
|
109
|
-
- **Over-fragmented capability** → when several draft Stories map to one
|
|
110
|
-
Delivery-Slicing slice (or one coherent reason to exist), merge them into a
|
|
111
|
-
single Story: union their `changes` / `acceptance` / `verify` / `references`,
|
|
112
|
-
write one coherent `goal`, and keep the union of labels.
|
|
113
|
-
- **Single-consumer Story** → merge into the one sibling that consumes it. This
|
|
114
|
-
is the primary lever for going **below** the Delivery-Slicing ceiling: an
|
|
115
|
-
"Independent? No" slice whose only consumer is one sibling folds into that
|
|
116
|
-
sibling unless its row justifies staying separate (parallelism, risk
|
|
117
|
-
isolation, or delivery-envelope pressure). Coarsening below N here is
|
|
118
|
-
expected, not an over-reach — record it as a below-ceiling merge (see Step 4).
|
|
119
|
-
- **Missing reason to exist** → flag any Story whose body carries no non-empty
|
|
120
|
-
`reason_to_exist` meta field. A Story that cannot name its single coherent
|
|
121
|
-
reason is a cohesion smell; record it in the report so the operator can
|
|
122
|
-
re-scope it (merge, split, or have the author supply the reason) at the HITL
|
|
123
|
-
gate.
|
|
124
|
-
|
|
125
|
-
Never split a draft Story to *reach* N — N is a ceiling, and adding scope is
|
|
126
|
-
out of contract. Record each decision with its one-line reason for the report.
|
|
127
|
-
|
|
128
|
-
### Step 3 — Rewire dependencies and conserve scope
|
|
129
|
-
|
|
130
|
-
After applying the operations:
|
|
131
|
-
|
|
132
|
-
- **Rewire `depends_on`**: drop self-edges, re-point any edge that named a
|
|
133
|
-
deleted slug at its surviving successor, and dedupe. No `depends_on` may
|
|
134
|
-
reference a slug absent from the consolidated array.
|
|
135
|
-
- **Conserve scope**: assert the union of all `acceptance` items and all
|
|
136
|
-
`verify` entries across the consolidated array equals the union across the
|
|
137
|
-
draft. Nothing is dropped; nothing new is invented. If you cannot preserve an
|
|
138
|
-
item, you have over-reached — back the operation out.
|
|
139
|
-
|
|
140
|
-
### Step 4 — Write both artifacts
|
|
141
|
-
|
|
142
|
-
Write the consolidated array to `temp/epic-<Epic_ID>/tickets.json` (2-space
|
|
143
|
-
indent, machine-consumed) and the rationale + before/after diff to
|
|
144
|
-
`temp/epic-<Epic_ID>/consolidation-report.md`. When the consolidated count
|
|
145
|
-
landed **below** the Delivery-Slicing ceiling, the report MUST call that out
|
|
146
|
-
explicitly — a `Below ceiling: N (Delivery Slicing) → M (consolidated)` line
|
|
147
|
-
plus a one-line rationale per below-ceiling merge (which dependent
|
|
148
|
-
single-consumer slice folded into which consumer, and why it was unjustified) —
|
|
149
|
-
so the operator sees the coarsening at the Phase 8.3 advisory diff.
|
|
150
|
-
|
|
151
|
-
### Step 5 — Hand back to `/plan`
|
|
152
|
-
|
|
153
|
-
Return control. The workflow shows the operator the consolidation report at the
|
|
154
|
-
HITL diff gate; on approval it runs
|
|
155
|
-
`node .agents/scripts/plan-persist.js --epic <Epic_ID> --tickets
|
|
156
|
-
temp/epic-<Epic_ID>/tickets.json`, which validates the consolidated array,
|
|
157
|
-
persists the hierarchy, and flips the Epic to `agent::ready`.
|
|
158
|
-
|
|
159
|
-
## Constraints
|
|
160
|
-
|
|
161
|
-
- Do **not** call the GitHub API from this Skill. It reads two temp artifacts
|
|
162
|
-
and writes two temp artifacts; persistence belongs to the script.
|
|
163
|
-
- Do **not** write outside `temp/epic-<Epic_ID>/`.
|
|
164
|
-
- Do **not** add scope or invent tickets. The two permitted operations (merge
|
|
165
|
-
Stories, rewire `depends_on`) are exhaustive — anything else is out of
|
|
166
|
-
contract.
|
|
167
|
-
- If `temp/epic-<Epic_ID>/tickets.json` is missing, fail loudly and instruct
|
|
168
|
-
the caller to run the `epic-plan-decompose-author` Skill first.
|
|
169
|
-
- The validator
|
|
170
|
-
([`lib/orchestration/ticket-validator.js`](../../../scripts/lib/orchestration/ticket-validator.js))
|
|
171
|
-
is the authoritative post-consolidation gate. Re-consolidate when it
|
|
172
|
-
rejects rather than patching tickets by hand.
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# epic-plan-consolidate — examples & extended rationale
|
|
2
|
-
|
|
3
|
-
Read this file on demand. The SKILL.md carries the operating contract (Policy
|
|
4
|
-
Capsule, Inputs / Outputs, Procedure, Constraints); this file carries the
|
|
5
|
-
extended rationale behind the consolidation pass — why it is a separate,
|
|
6
|
-
fresh-context critic and how the scope-conservation invariant is enforced by
|
|
7
|
-
you rather than by the runtime.
|
|
8
|
-
|
|
9
|
-
## Why a separate fresh-context critic
|
|
10
|
-
|
|
11
|
-
This Skill acts as a **holistic critic** with fresh context (a Senior Project
|
|
12
|
-
Manager + Orchestrator lens). It is deliberately *separate* from
|
|
13
|
-
`epic-plan-decompose-author` (the generator): a same-pass self-critique is the
|
|
14
|
-
weak mode this is built to escape. The generator maps Epic capabilities to
|
|
15
|
-
Stories ~1:1; this critic steps back and looks at the *whole* decomposition
|
|
16
|
-
against the Tech Spec's intentional grouping before any GitHub write.
|
|
17
|
-
|
|
18
|
-
## Scope conservation is your contract, not a machine guarantee
|
|
19
|
-
|
|
20
|
-
Scope conservation is the load-bearing invariant: you MUST NOT add scope,
|
|
21
|
-
invent tickets, or drop acceptance criteria. Every acceptance item and every
|
|
22
|
-
`verify` entry present in the draft MUST survive into the consolidated array
|
|
23
|
-
(possibly re-homed onto a merged Story).
|
|
24
|
-
|
|
25
|
-
**This is your contract, not a machine guarantee.** There is **no runtime
|
|
26
|
-
acceptance-union diff** on your output. The only deterministic runtime backstop
|
|
27
|
-
the validator applies after you run is the standard ticket-structure
|
|
28
|
-
validation — it does not re-derive the pre-consolidation acceptance/verify
|
|
29
|
-
union, so a critic that silently dropped an acceptance item would **not** be
|
|
30
|
-
caught downstream. (The repo's unit test exercises a *pure model* of the merge
|
|
31
|
-
over an over-fragmented fixture to document the intended invariant; it does not
|
|
32
|
-
inspect this Skill's actual output.) Conserve scope yourself, deliberately, on
|
|
33
|
-
every merge — and if you cannot preserve an item, you have over-reached, so back
|
|
34
|
-
the operation out.
|
|
35
|
-
|
|
36
|
-
## Ceiling, not target — the below-ceiling coarsening case
|
|
37
|
-
|
|
38
|
-
Consume the Tech Spec **"Delivery Slicing"** section as a **ceiling, not a
|
|
39
|
-
target** when present: the Architect's proposed N shippable Stories is an
|
|
40
|
-
*upper bound*, not a floor. Cluster the draft's Stories toward that grouping,
|
|
41
|
-
and go **below** N when slices form dependent single-consumer chains — a slice
|
|
42
|
-
whose "Independent? No" row carries no justification (parallelism, risk
|
|
43
|
-
isolation, or delivery-envelope pressure) folds into its consumer. You **never**
|
|
44
|
-
split above N.
|
|
45
|
-
|
|
46
|
-
When the consolidated count drops **below** the Delivery-Slicing count, the
|
|
47
|
-
consolidation report MUST surface that fact explicitly — e.g.
|
|
48
|
-
`Below ceiling: N (Delivery Slicing) → M (consolidated)` — and attach a one-line
|
|
49
|
-
rationale to each below-ceiling merge (which dependent single-consumer slice
|
|
50
|
-
folded into which consumer, and why it was unjustified), so the operator sees
|
|
51
|
-
the coarsening at the Phase 8.3 advisory diff.
|