mandrel 1.58.0 → 1.60.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 +100 -98
- package/.agents/docs/SDLC.md +140 -141
- package/.agents/docs/configuration.md +16 -16
- package/.agents/docs/workflows.md +7 -8
- package/.agents/instructions.md +12 -11
- package/.agents/personas/architect.md +1 -1
- package/.agents/personas/product.md +1 -1
- package/.agents/personas/project-manager.md +14 -14
- package/.agents/personas/technical-writer.md +1 -1
- package/.agents/rules/changelog-style.md +5 -5
- package/.agents/rules/git-conventions.md +3 -3
- package/.agents/schemas/agentrc.schema.json +3 -3
- package/.agents/schemas/audit-rules.json +20 -0
- package/.agents/schemas/dispatch-manifest.json +4 -4
- package/.agents/schemas/epic-spec.schema.json +15 -45
- package/.agents/schemas/lifecycle/README.md +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/README.md +1 -1
- package/.agents/scripts/acceptance-eval.js +21 -4
- package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
- package/.agents/scripts/analyze-execution.js +2 -2
- package/.agents/scripts/assert-branch.js +1 -3
- package/.agents/scripts/audit-to-stories.js +1 -1
- package/.agents/scripts/bootstrap.js +1 -1
- package/.agents/scripts/check-arch-cycles.js +360 -0
- package/.agents/scripts/check-doc-links.js +2 -3
- package/.agents/scripts/coverage-capture.js +24 -3
- package/.agents/scripts/diagnose-friction.js +1 -1
- package/.agents/scripts/dispatcher.js +2 -2
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/epic-audit-prepare.js +3 -3
- package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
- package/.agents/scripts/epic-deliver-preflight.js +11 -9
- package/.agents/scripts/epic-deliver-prepare.js +13 -5
- package/.agents/scripts/epic-execute-record-wave.js +5 -5
- package/.agents/scripts/epic-plan-healthcheck.js +6 -10
- package/.agents/scripts/epic-plan-spec-validate.js +1 -1
- package/.agents/scripts/epic-reconcile.js +11 -29
- package/.agents/scripts/evidence-gate.js +2 -2
- package/.agents/scripts/generate-workflows-doc.js +1 -1
- package/.agents/scripts/git-rebase-and-resolve.js +1 -1
- package/.agents/scripts/hierarchy-gate.js +40 -24
- package/.agents/scripts/lib/ITicketingProvider.js +1 -1
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
- package/.agents/scripts/lib/baseline-snapshot.js +7 -7
- package/.agents/scripts/lib/baselines/kinds/coverage.js +33 -149
- package/.agents/scripts/lib/baselines/kinds/duplication.js +27 -116
- package/.agents/scripts/lib/baselines/kinds/kind-factory.js +192 -0
- package/.agents/scripts/lib/baselines/kinds/lighthouse.js +34 -133
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +31 -124
- package/.agents/scripts/lib/baselines/kinds/mutation.js +25 -111
- package/.agents/scripts/lib/baselines/maintainability-baseline-io.js +59 -0
- package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +37 -0
- package/.agents/scripts/lib/baselines/writer.js +1 -1
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
- package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/close-validation/commands.js +188 -0
- package/.agents/scripts/lib/close-validation/gates.js +235 -0
- package/.agents/scripts/lib/close-validation/process.js +101 -0
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +1 -1
- package/.agents/scripts/lib/close-validation/runner.js +325 -0
- package/.agents/scripts/lib/close-validation/telemetry.js +70 -0
- package/.agents/scripts/lib/codebase-snapshot.js +1 -1
- package/.agents/scripts/lib/config/explain.js +1 -1
- package/.agents/scripts/lib/config/quality.js +6 -6
- package/.agents/scripts/lib/config/runners.js +2 -2
- package/.agents/scripts/lib/config/runtime.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +2 -2
- package/.agents/scripts/lib/config-resolver.js +2 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
- package/.agents/scripts/lib/config-settings-schema.js +3 -3
- package/.agents/scripts/lib/coverage-capture.js +147 -4
- package/.agents/scripts/lib/cpu-pool.js +14 -0
- package/.agents/scripts/lib/crap-utils.js +6 -11
- package/.agents/scripts/lib/duplicate-search.js +1 -1
- package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
- package/.agents/scripts/lib/dynamic-workflow/documentation-report-contract.js +87 -0
- package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
- package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
- package/.agents/scripts/lib/findings/classify-finding.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +10 -10
- package/.agents/scripts/lib/git-utils.js +24 -22
- package/.agents/scripts/lib/label-constants.js +3 -4
- package/.agents/scripts/lib/label-taxonomy.js +3 -8
- package/.agents/scripts/lib/maintainability-engine.js +1 -1
- package/.agents/scripts/lib/maintainability-utils.js +4 -187
- package/.agents/scripts/lib/observability/perf-report-readers.js +32 -23
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +81 -7
- package/.agents/scripts/lib/orchestration/code-review.js +95 -82
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
- package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
- package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +14 -37
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +22 -22
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +2 -2
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +206 -58
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +27 -3
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +28 -8
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +13 -41
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +2 -3
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -8
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +103 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +22 -64
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +38 -76
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +10 -10
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +8 -20
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
- package/.agents/scripts/lib/orchestration/file-assumptions.js +6 -5
- package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +144 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +3 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +8 -8
- package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
- package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +3 -3
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +7 -7
- package/.agents/scripts/lib/orchestration/preflight-cache.js +36 -13
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
- package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
- package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +5 -60
- package/.agents/scripts/lib/orchestration/review-providers/native.js +7 -6
- package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +105 -0
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +7 -59
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +2 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -4
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +32 -35
- package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +1 -2
- package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +451 -503
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +8 -2
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +47 -2
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +2 -2
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +358 -54
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +3 -2
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +32 -5
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +5 -18
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +3 -3
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +33 -16
- package/.agents/scripts/lib/orchestration/story-reachability.js +47 -0
- package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
- package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +4 -35
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
- package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +44 -73
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +16 -7
- package/.agents/scripts/lib/orchestration/ticketing/state.js +53 -439
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +471 -0
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +3 -3
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +2 -8
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
- package/.agents/scripts/lib/preflight-runner.js +1 -1
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
- package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
- package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
- package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
- package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
- package/.agents/scripts/lib/project-root.js +17 -0
- package/.agents/scripts/lib/signals/schema.js +1 -1
- package/.agents/scripts/lib/spec/index.js +1 -1
- package/.agents/scripts/lib/spec/loader.js +2 -2
- package/.agents/scripts/lib/spec/state.js +7 -16
- package/.agents/scripts/lib/story-adjacency.js +76 -0
- package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
- package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
- package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
- package/.agents/scripts/lib/story-lifecycle.js +9 -9
- package/.agents/scripts/lib/story-plan.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
- package/.agents/scripts/lib/transpile.js +93 -0
- package/.agents/scripts/lib/wave-runner/tick.js +4 -153
- package/.agents/scripts/lib/workers/crap-worker.js +1 -1
- package/.agents/scripts/lib/workers/maintainability-report-worker.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/creation.js +20 -2
- package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +90 -0
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +26 -8
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +74 -0
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
- package/.agents/scripts/lifecycle-emit.js +1 -1
- package/.agents/scripts/providers/github/board-add.js +1 -1
- package/.agents/scripts/providers/github/errors.js +1 -1
- package/.agents/scripts/providers/github/mappers.js +2 -2
- package/.agents/scripts/providers/github/tickets.js +114 -10
- package/.agents/scripts/resync-status-column.js +1 -1
- package/.agents/scripts/retro-run.js +2 -2
- package/.agents/scripts/run-lint.js +10 -1
- package/.agents/scripts/run-tests.js +24 -4
- package/.agents/scripts/single-story-init.js +1 -1
- package/.agents/scripts/stories-wave-tick.js +13 -10
- package/.agents/scripts/story-close.js +1 -1
- package/.agents/scripts/story-init.js +162 -26
- package/.agents/scripts/story-phase.js +5 -5
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/sync-branch-from-base.js +2 -2
- package/.agents/scripts/validate-docs-freshness.js +1 -1
- package/.agents/scripts/wave-tick.js +1 -1
- package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
- package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
- package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
- package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
- package/.agents/skills/core/scope-triage/SKILL.md +9 -10
- package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
- package/.agents/skills/skills.index.json +7 -7
- package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +1 -1
- package/.agents/templates/agent-protocol.md +2 -2
- package/.agents/workflows/agents-update.md +2 -2
- package/.agents/workflows/audit-architecture.md +2 -2
- package/.agents/workflows/audit-clean-code.md +2 -2
- package/.agents/workflows/audit-dependencies.md +1 -1
- package/.agents/workflows/audit-devops.md +1 -1
- package/.agents/workflows/audit-documentation.md +226 -0
- package/.agents/workflows/audit-lighthouse.md +1 -1
- package/.agents/workflows/audit-performance.md +2 -2
- package/.agents/workflows/audit-privacy.md +1 -1
- package/.agents/workflows/audit-quality.md +2 -2
- package/.agents/workflows/audit-security.md +2 -2
- package/.agents/workflows/audit-seo.md +1 -1
- package/.agents/workflows/audit-sre.md +1 -1
- package/.agents/workflows/audit-to-stories.md +10 -10
- package/.agents/workflows/audit-ux-ui.md +1 -1
- package/.agents/workflows/deliver.md +85 -0
- package/.agents/workflows/explain.md +3 -3
- package/.agents/workflows/git-merge-pr.md +1 -1
- package/.agents/workflows/git-pr-all.md +13 -10
- package/.agents/workflows/git-push.md +6 -3
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
- package/.agents/workflows/helpers/code-review.md +5 -5
- package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +59 -66
- package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
- package/.agents/workflows/helpers/diagnose.md +1 -1
- package/.agents/workflows/helpers/epic-audit.md +6 -6
- package/.agents/workflows/helpers/epic-deliver-story.md +28 -39
- package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
- package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
- package/.agents/workflows/helpers/epic-testing.md +3 -3
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
- package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
- package/.agents/workflows/helpers/signals.md +1 -1
- package/.agents/workflows/helpers/single-story-deliver.md +12 -11
- package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
- package/.agents/workflows/onboard.md +21 -20
- package/.agents/workflows/plan.md +89 -0
- package/.agents/workflows/qa-explore.md +1 -1
- package/.agents/workflows/qa-run-harness.md +1 -1
- package/README.md +17 -20
- package/docs/CHANGELOG.md +1149 -0
- package/lib/cli/__tests__/update-changelog-surface.test.js +357 -0
- package/lib/cli/__tests__/update-reexec.test.js +513 -0
- package/lib/cli/init.js +338 -0
- package/lib/cli/update.js +413 -52
- package/package.json +3 -1
- package/.agents/scripts/lib/auto-refresh-baselines.js +0 -308
- package/.agents/scripts/lib/close-validation.js +0 -897
- package/.agents/scripts/lib/orchestration/cascade-grouping.js +0 -275
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter.js +0 -69
- package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
- package/.agents/scripts/lib/orchestration/story-close/format-autofix-scoped.js +0 -221
- package/.agents/scripts/lib/orchestration/story-close/format-autofix-shared.js +0 -123
- package/.agents/scripts/lib/task-utils.js +0 -26
- package/.agents/scripts/story-deliver-prepare.js +0 -267
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* removed.
|
|
13
13
|
*
|
|
14
14
|
* The renderer survives because its `{ body, payload }` output still feeds
|
|
15
|
-
* two non-comment contracts: the `renderedBody` markdown the `/
|
|
16
|
-
* and `single-story-deliver` CLIs (`story-phase.js`, `story-
|
|
15
|
+
* two non-comment contracts: the `renderedBody` markdown the `/deliver`
|
|
16
|
+
* and `single-story-deliver` CLIs (`story-phase.js`, the inline `story-init.js` prepare step)
|
|
17
17
|
* relay to chat so the operator sees the phase table inline, and the snapshot
|
|
18
18
|
* payload returned in those CLIs' JSON envelopes. `upsertStoryRunProgress`
|
|
19
19
|
* therefore renders-only: it computes the body/payload and (optionally) mirrors
|
|
@@ -71,10 +71,10 @@ const PHASE_EMOJI = {
|
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* Canonical
|
|
74
|
+
* Canonical 2-tier Story-phase order. The Story-phase snapshot replaces the
|
|
75
75
|
* 4-tier per-Task list when the Story carries inline acceptance (no child
|
|
76
76
|
* Tasks). Each entry tracks `status` + `startedAt` / `endedAt` so the parent
|
|
77
|
-
* `/
|
|
77
|
+
* `/deliver` aggregator can render a coarse progress bar without
|
|
78
78
|
* walking Task tickets.
|
|
79
79
|
*/
|
|
80
80
|
export const STORY_PHASE_ORDER = ['init', 'implement', 'validate', 'close'];
|
|
@@ -89,8 +89,8 @@ const STORY_PHASE_STATUS_EMOJI = {
|
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
91
|
* Build the canonical default `phases[]` array for a freshly-initialized
|
|
92
|
-
*
|
|
93
|
-
* Exported so call sites (story-
|
|
92
|
+
* 2-tier Story snapshot. All entries are `pending`; timestamps are null.
|
|
93
|
+
* Exported so call sites (the story-init prepare step, story-phase) and
|
|
94
94
|
* tests can build the same shape without re-implementing it.
|
|
95
95
|
*
|
|
96
96
|
* @returns {Array<{ name: string, status: 'pending', startedAt: null, endedAt: null }>}
|
|
@@ -175,7 +175,7 @@ function normalizeTask(task) {
|
|
|
175
175
|
* Exported so tests can pin the rendered shape without going through the
|
|
176
176
|
* upsert path.
|
|
177
177
|
*
|
|
178
|
-
* Two shapes are supported, selected by whether `input.phases` (
|
|
178
|
+
* Two shapes are supported, selected by whether `input.phases` (2-tier
|
|
179
179
|
* Story-phase snapshot) or `input.tasks` (legacy 4-tier per-Task list) is
|
|
180
180
|
* provided. Callers MUST pass exactly one of the two — passing both is
|
|
181
181
|
* rejected as a contract violation so a mistake at the call site fails
|
|
@@ -216,7 +216,7 @@ export function renderStoryRunProgressBody(input) {
|
|
|
216
216
|
const hasTasks = Array.isArray(input.tasks);
|
|
217
217
|
if (hasPhases && hasTasks) {
|
|
218
218
|
throw new TypeError(
|
|
219
|
-
'renderStoryRunProgressBody: pass either `phases` (
|
|
219
|
+
'renderStoryRunProgressBody: pass either `phases` (2-tier) or `tasks` ' +
|
|
220
220
|
'(4-tier), not both — the snapshot shape is mutually exclusive.',
|
|
221
221
|
);
|
|
222
222
|
}
|
|
@@ -296,7 +296,7 @@ function renderTasksBody({
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
/**
|
|
299
|
-
* Render the
|
|
299
|
+
* Render the 2-tier Story-phase body. Pure helper for
|
|
300
300
|
* `renderStoryRunProgressBody`. Emits a `phases[]` payload whose entries
|
|
301
301
|
* carry `{ name, status, startedAt, endedAt }` for init/implement/validate/close.
|
|
302
302
|
*/
|
|
@@ -351,7 +351,7 @@ function renderPhasesBody({ storyId, branch, phase, phases: raw, updatedAt }) {
|
|
|
351
351
|
* renders only and, when `notify` is supplied, mirrors a low-severity webhook
|
|
352
352
|
* event for operators who wire one up.
|
|
353
353
|
*
|
|
354
|
-
* Two shapes are supported, selected by whether `args.phases` (
|
|
354
|
+
* Two shapes are supported, selected by whether `args.phases` (2-tier
|
|
355
355
|
* Story-phase snapshot) or `args.tasks` (legacy 4-tier per-Task list) is
|
|
356
356
|
* provided. The webhook mirror's `done/total` count is computed from whichever
|
|
357
357
|
* shape is active.
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* sub-agent-return.js — parse and reconcile per-Story sub-agent return text.
|
|
3
3
|
*
|
|
4
|
-
* `/
|
|
4
|
+
* `/deliver` Step 2 dispatches one `Agent` tool call per Story per
|
|
5
5
|
* wave. Each sub-agent owes its parent the JSON return contract documented
|
|
6
|
-
* in `.agents/workflows/epic
|
|
6
|
+
* in `.agents/workflows/helpers/deliver-epic.md`:
|
|
7
7
|
*
|
|
8
8
|
* {
|
|
9
9
|
* "storyId": <number>,
|
|
10
10
|
* "status": "done" | "blocked" | "failed",
|
|
11
11
|
* "phase": "init|implementing|closing|blocked|done",
|
|
12
|
-
* "tasksDone": <number>,
|
|
13
|
-
* "tasksTotal": <number>,
|
|
14
12
|
* "branchDeleted": <boolean>,
|
|
15
13
|
* "blockerCommentId": <string|null>,
|
|
16
14
|
* "detail": <string|undefined>,
|
|
@@ -29,7 +27,7 @@
|
|
|
29
27
|
* directly to GitHub-state reconciliation plus a friction record — the
|
|
30
28
|
* stronger backstop that already caught everything the heuristics caught.
|
|
31
29
|
*
|
|
32
|
-
* This module provides the two helpers `/
|
|
30
|
+
* This module provides the two helpers `/deliver`'s wave dispatcher
|
|
33
31
|
* now uses:
|
|
34
32
|
*
|
|
35
33
|
* - `parseStoryAgentReturn(raw)` — accept an already-parsed object or a
|
|
@@ -119,8 +117,6 @@ function validateStoryReturnShape(obj) {
|
|
|
119
117
|
}
|
|
120
118
|
const value = { storyId, status };
|
|
121
119
|
if (typeof obj.phase === 'string') value.phase = obj.phase;
|
|
122
|
-
if (Number.isInteger(obj.tasksDone)) value.tasksDone = obj.tasksDone;
|
|
123
|
-
if (Number.isInteger(obj.tasksTotal)) value.tasksTotal = obj.tasksTotal;
|
|
124
120
|
if (typeof obj.branchDeleted === 'boolean') {
|
|
125
121
|
value.branchDeleted = obj.branchDeleted;
|
|
126
122
|
}
|
|
@@ -143,8 +139,8 @@ function quote(text) {
|
|
|
143
139
|
* Story ticket's live state when the sub-agent return cannot be trusted.
|
|
144
140
|
*
|
|
145
141
|
* The result is always conservative — `status: 'failed'` unless the live
|
|
146
|
-
* ticket carries `agent::done` (or `state: 'closed'`). The phase
|
|
147
|
-
*
|
|
142
|
+
* ticket carries `agent::done` (or `state: 'closed'`). The phase is
|
|
143
|
+
* best-effort, sourced from the Story's `story-run-progress` comment;
|
|
148
144
|
* absence of the comment is non-fatal.
|
|
149
145
|
*
|
|
150
146
|
* Story #3907 — a Story carrying `agent::blocked` reconciles to
|
|
@@ -163,8 +159,6 @@ function quote(text) {
|
|
|
163
159
|
* storyId: number,
|
|
164
160
|
* status: 'done' | 'blocked' | 'failed',
|
|
165
161
|
* phase?: string,
|
|
166
|
-
* tasksDone?: number,
|
|
167
|
-
* tasksTotal?: number,
|
|
168
162
|
* blockerCommentId?: string,
|
|
169
163
|
* reconciledFromGitHub: true,
|
|
170
164
|
* reconcileError?: string,
|
|
@@ -223,8 +217,8 @@ export async function reconcileStoryFromGitHub({ provider, storyId } = {}) {
|
|
|
223
217
|
}
|
|
224
218
|
}
|
|
225
219
|
|
|
226
|
-
// Cross-look the story-run-progress comment for phase
|
|
227
|
-
//
|
|
220
|
+
// Cross-look the story-run-progress comment for the phase when present.
|
|
221
|
+
// Story #3909 retired the per-Story story-run-progress *comment*
|
|
228
222
|
// (the redundant mid-flight surface), so this read now usually finds nothing
|
|
229
223
|
// and the reconciled row degrades to label-only — which is fine: the labels
|
|
230
224
|
// are the authoritative state. Failure here is non-fatal.
|
|
@@ -237,12 +231,6 @@ export async function reconcileStoryFromGitHub({ provider, storyId } = {}) {
|
|
|
237
231
|
const payload = comment ? parseFencedJsonComment(comment) : null;
|
|
238
232
|
if (payload && typeof payload === 'object') {
|
|
239
233
|
if (typeof payload.phase === 'string') out.phase = payload.phase;
|
|
240
|
-
if (Array.isArray(payload.tasks)) {
|
|
241
|
-
out.tasksTotal = payload.tasks.length;
|
|
242
|
-
out.tasksDone = payload.tasks.filter(
|
|
243
|
-
(t) => t && t.state === 'done',
|
|
244
|
-
).length;
|
|
245
|
-
}
|
|
246
234
|
}
|
|
247
235
|
} catch (err) {
|
|
248
236
|
out.reconcileError = err?.message ?? String(err);
|
|
@@ -269,7 +257,7 @@ export function renderMalformedReturnsFriction({ epicId, wave, failures }) {
|
|
|
269
257
|
'',
|
|
270
258
|
`**Reason:** \`malformed-subagent-return\``,
|
|
271
259
|
'',
|
|
272
|
-
`${failures.length} sub-agent return(s) did not match the /
|
|
260
|
+
`${failures.length} sub-agent return(s) did not match the /deliver return contract.`,
|
|
273
261
|
'Each Story below was reconciled from GitHub (labels + `story-run-progress`)',
|
|
274
262
|
'and its wave-row downgraded to `failed` unless the live ticket already carried',
|
|
275
263
|
'`agent::done`.',
|
|
@@ -730,21 +730,13 @@ function collectStructuralEdges(spec) {
|
|
|
730
730
|
const out = {};
|
|
731
731
|
if (!spec || typeof spec !== 'object') return out;
|
|
732
732
|
const epicSlug = 'epic';
|
|
733
|
-
for (const
|
|
734
|
-
if (!
|
|
735
|
-
out[
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
parentSlug: feature.slug,
|
|
741
|
-
dependsOn: [...(story.dependsOn ?? [])].sort(),
|
|
742
|
-
};
|
|
743
|
-
for (const task of story.tasks ?? []) {
|
|
744
|
-
if (!task?.slug) continue;
|
|
745
|
-
out[task.slug] = { entity: 'task', parentSlug: story.slug };
|
|
746
|
-
}
|
|
747
|
-
}
|
|
733
|
+
for (const story of spec.stories ?? []) {
|
|
734
|
+
if (!story?.slug) continue;
|
|
735
|
+
out[story.slug] = {
|
|
736
|
+
entity: 'story',
|
|
737
|
+
parentSlug: epicSlug,
|
|
738
|
+
dependsOn: [...(story.dependsOn ?? [])].sort(),
|
|
739
|
+
};
|
|
748
740
|
}
|
|
749
741
|
return out;
|
|
750
742
|
}
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
*
|
|
19
19
|
* @typedef {object} SpecInput
|
|
20
20
|
* The parsed YAML returned by `lib/spec/loader.js#loadSpec`. Shape is
|
|
21
|
-
* `{ epic: {...},
|
|
21
|
+
* `{ epic: {...}, stories: [...], gates?: {...} }`
|
|
22
22
|
* per `.agents/schemas/epic-spec.schema.json`.
|
|
23
23
|
*
|
|
24
24
|
* @typedef {object} StateMappingEntry
|
|
25
25
|
* @property {number} issueNumber GH issue number this slug maps to.
|
|
26
|
-
* @property {string} entity 'epic'|'
|
|
26
|
+
* @property {string} entity 'epic'|'story'.
|
|
27
27
|
* @property {string} [contentHash] Content hash captured at last
|
|
28
28
|
* reconcile; absence forces an
|
|
29
29
|
* update when ghState carries
|
|
@@ -115,7 +115,7 @@ function labelsEqual(a, b) {
|
|
|
115
115
|
* a naive replace-style label diff would propose removing operator-
|
|
116
116
|
* managed metadata that lives in these namespaces.
|
|
117
117
|
*
|
|
118
|
-
* Why: Story #2056 / Epic #1994 — `/
|
|
118
|
+
* Why: Story #2056 / Epic #1994 — `/plan` was silently stripping
|
|
119
119
|
* `type::epic` and `risk::*` from the parent Epic on every decompose,
|
|
120
120
|
* which then broke `dispatcher.js` (`type "unknown"`). Defence-in-depth
|
|
121
121
|
* lives here in the diff engine: even if a future spec author drops
|
|
@@ -133,7 +133,7 @@ const PROTECTED_EPIC_LABEL_NAMESPACES = Object.freeze([
|
|
|
133
133
|
'risk::',
|
|
134
134
|
// Story #3050 — `acceptance::*` is set by Phase 7 spec-persist when
|
|
135
135
|
// `planningRisk.acceptanceDisposition='not-applicable'` (or another
|
|
136
|
-
// disposition) and gates downstream `/
|
|
136
|
+
// disposition) and gates downstream `/deliver` start/finalize
|
|
137
137
|
// behavior. Before this namespace was protected, Phase 8 decompose
|
|
138
138
|
// diffed the Epic's labels against a spec entry that doesn't carry
|
|
139
139
|
// `acceptance::*`, silently emitting an Update that stripped the
|
|
@@ -190,7 +190,7 @@ function normaliseBody(value) {
|
|
|
190
190
|
* duplicate footer blocks and trailing `blocked by` lines are removed in
|
|
191
191
|
* a single pass. Kept local to this module so the diff engine does not
|
|
192
192
|
* depend on the Task-body renderer (Story #3185 / Epic #3163: the renderer
|
|
193
|
-
* is going away with the
|
|
193
|
+
* is going away with the 2-tier producer cutover).
|
|
194
194
|
*/
|
|
195
195
|
const ORCHESTRATOR_FOOTER_RE = /\n?---[ \t]*\r?\n+parent:\s*#\d+[\s\S]*$/;
|
|
196
196
|
|
|
@@ -247,9 +247,8 @@ function renderFooter({ parentId, epicId, dependencies = [] }) {
|
|
|
247
247
|
* `parent: #N` / `Epic: #M` / `blocked by #X` and breaking the cascade.
|
|
248
248
|
*
|
|
249
249
|
* Story #3185 — the footer compose/strip logic is inlined here rather
|
|
250
|
-
* than reused from the legacy Task-body renderer module.
|
|
251
|
-
*
|
|
252
|
-
* removed, so the diff engine carries its own footer shape. The shape
|
|
250
|
+
* than reused from the legacy Task-body renderer module. That renderer
|
|
251
|
+
* was removed, so the diff engine carries its own footer shape. The shape
|
|
253
252
|
* is byte-identical to the legacy renderer's `parent: #<n>` /
|
|
254
253
|
* `Epic: #<m>` / `blocked by #<x>` output so cascade-readers continue
|
|
255
254
|
* to parse it unchanged.
|
|
@@ -326,7 +325,7 @@ function fieldChanges(specEntity, obs, mapping, ctx = {}) {
|
|
|
326
325
|
// feature/story/task body fields): "When omitted, the GH issue body
|
|
327
326
|
// is left untouched". Pre-Story-#2283 the engine treated `undefined`
|
|
328
327
|
// as `""`, which emitted a destructive `body: <existing> → ""` Update
|
|
329
|
-
// on every `/
|
|
328
|
+
// on every `/plan` Phase 8 because the decomposer's renderer
|
|
330
329
|
// projects the Epic spec entry from `{ id, title }` only. Skip the
|
|
331
330
|
// body diff entirely when the spec did not carry a body string. An
|
|
332
331
|
// explicit `body: ""` in the spec still produces a clear-op when the
|
|
@@ -413,9 +412,8 @@ function sortBySlug(ops) {
|
|
|
413
412
|
|
|
414
413
|
/**
|
|
415
414
|
* Walk the spec and yield one structural-entity record per visited
|
|
416
|
-
* node. The
|
|
417
|
-
*
|
|
418
|
-
* 4 so an explicit work-queue would just obscure the shape.
|
|
415
|
+
* node. The spec is 2-tier (epic → stories), so the walk is a single
|
|
416
|
+
* flat pass over `spec.stories` after the Epic record.
|
|
419
417
|
*
|
|
420
418
|
* @param {SpecInput} spec
|
|
421
419
|
* @returns {Array<{
|
|
@@ -446,37 +444,35 @@ function flattenSpec(spec) {
|
|
|
446
444
|
}
|
|
447
445
|
|
|
448
446
|
const epicAnchor = spec.epic ? epicSlug(spec.epic) : null;
|
|
449
|
-
|
|
447
|
+
// Duplicate-slug guard. The schema cannot express slug uniqueness across
|
|
448
|
+
// a hand-edited spec, and two same-slug Creates would orphan one GH
|
|
449
|
+
// issue (the second create overwrites the first's mapping entry).
|
|
450
|
+
// flattenSpec is the chokepoint both the loader path and the diff path
|
|
451
|
+
// flow through, so the check lives here.
|
|
452
|
+
const seenSlugs = new Set();
|
|
453
|
+
const duplicateSlugs = new Set();
|
|
454
|
+
for (const story of spec.stories ?? []) {
|
|
455
|
+
if (seenSlugs.has(story.slug)) duplicateSlugs.add(story.slug);
|
|
456
|
+
seenSlugs.add(story.slug);
|
|
457
|
+
}
|
|
458
|
+
if (duplicateSlugs.size > 0) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
`spec contains duplicate story slug(s): ${[...duplicateSlugs].join(', ')}. ` +
|
|
461
|
+
`Each story slug must be unique — rename the duplicated entries in ` +
|
|
462
|
+
`the spec and re-run.`,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
for (const story of spec.stories ?? []) {
|
|
450
466
|
out.push({
|
|
451
|
-
slug:
|
|
452
|
-
entity: ENTITY_KINDS.
|
|
453
|
-
title: String(
|
|
454
|
-
body:
|
|
455
|
-
labels:
|
|
467
|
+
slug: story.slug,
|
|
468
|
+
entity: ENTITY_KINDS.STORY,
|
|
469
|
+
title: String(story.title ?? ''),
|
|
470
|
+
body: story.body,
|
|
471
|
+
labels: story.labels,
|
|
472
|
+
wave: story.wave,
|
|
456
473
|
parentSlug: epicAnchor,
|
|
474
|
+
dependsOn: story.dependsOn ?? [],
|
|
457
475
|
});
|
|
458
|
-
for (const story of feature.stories ?? []) {
|
|
459
|
-
out.push({
|
|
460
|
-
slug: story.slug,
|
|
461
|
-
entity: ENTITY_KINDS.STORY,
|
|
462
|
-
title: String(story.title ?? ''),
|
|
463
|
-
body: story.body,
|
|
464
|
-
labels: story.labels,
|
|
465
|
-
wave: story.wave,
|
|
466
|
-
parentSlug: feature.slug,
|
|
467
|
-
dependsOn: story.dependsOn ?? [],
|
|
468
|
-
});
|
|
469
|
-
for (const task of story.tasks ?? []) {
|
|
470
|
-
out.push({
|
|
471
|
-
slug: task.slug,
|
|
472
|
-
entity: ENTITY_KINDS.TASK,
|
|
473
|
-
title: String(task.title ?? ''),
|
|
474
|
-
body: task.body,
|
|
475
|
-
labels: task.labels,
|
|
476
|
-
parentSlug: story.slug,
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
476
|
}
|
|
481
477
|
return out;
|
|
482
478
|
}
|
|
@@ -509,6 +505,40 @@ function dependsOnEqual(a, b) {
|
|
|
509
505
|
return labelsEqual(a, b);
|
|
510
506
|
}
|
|
511
507
|
|
|
508
|
+
/**
|
|
509
|
+
* Entity kinds that only exist in pre-v4 (3-tier) state files. The v4
|
|
510
|
+
* hard cutover (v1.60.0) removed the Feature/Task tiers; encountering one
|
|
511
|
+
* of these in `state.mapping` means the state file predates the cutover
|
|
512
|
+
* and must be migrated by the operator — there is deliberately no legacy
|
|
513
|
+
* close-op support (hard-cutover policy, git-conventions.md).
|
|
514
|
+
*/
|
|
515
|
+
const LEGACY_ENTITY_KINDS = Object.freeze(new Set(['feature', 'task']));
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Throw a loud, actionable error when the state mapping carries legacy
|
|
519
|
+
* Feature/Task entries. Raised early in `diff()` so the operator sees a
|
|
520
|
+
* migration instruction instead of `unknown entity kind: feature` from
|
|
521
|
+
* deep inside `closeOp`.
|
|
522
|
+
*
|
|
523
|
+
* @param {Record<string, StateMappingEntry>} mapping
|
|
524
|
+
*/
|
|
525
|
+
function assertNoLegacyEntities(mapping) {
|
|
526
|
+
const legacy = Object.entries(mapping).filter(([, entry]) =>
|
|
527
|
+
LEGACY_ENTITY_KINDS.has(entry?.entity),
|
|
528
|
+
);
|
|
529
|
+
if (legacy.length === 0) return;
|
|
530
|
+
const summary = legacy
|
|
531
|
+
.map(([slug, entry]) => `${slug} (#${entry.issueNumber}, ${entry.entity})`)
|
|
532
|
+
.join(', ');
|
|
533
|
+
throw new Error(
|
|
534
|
+
`diff: the epic state file is pre-v4 — it carries legacy Feature/Task ` +
|
|
535
|
+
`mapping entries: ${summary}. The 2-tier reconciler cannot process ` +
|
|
536
|
+
`these. Close the legacy Feature issues manually, then delete (or ` +
|
|
537
|
+
`reseed) the .agents/epics/<epicId>.state.json file per the v1.60.0 ` +
|
|
538
|
+
`migration notes, and re-run.`,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
512
542
|
/**
|
|
513
543
|
* Diff `(spec, state, ghState)` into a `Plan`. See the module header for
|
|
514
544
|
* the full contract.
|
|
@@ -523,6 +553,7 @@ export function diff({ spec, state, ghState } = {}) {
|
|
|
523
553
|
throw new TypeError('diff: state argument is required');
|
|
524
554
|
}
|
|
525
555
|
const mapping = state.mapping ?? {};
|
|
556
|
+
assertNoLegacyEntities(mapping);
|
|
526
557
|
const seenSpecSlugs = new Set();
|
|
527
558
|
|
|
528
559
|
for (const entity of flattenSpec(spec)) {
|
|
@@ -595,7 +626,7 @@ export function diff({ spec, state, ghState } = {}) {
|
|
|
595
626
|
plan.closes.push(
|
|
596
627
|
closeOp({
|
|
597
628
|
slug,
|
|
598
|
-
entity: mapped.entity ?? ENTITY_KINDS.
|
|
629
|
+
entity: mapped.entity ?? ENTITY_KINDS.STORY,
|
|
599
630
|
issueNumber: mapped.issueNumber,
|
|
600
631
|
title: mapped.title,
|
|
601
632
|
}),
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* apply pipeline. Arrays are always present (never undefined); an empty
|
|
45
45
|
* plan has all four arrays at length 0.
|
|
46
46
|
*
|
|
47
|
-
* @typedef {'epic'|'
|
|
47
|
+
* @typedef {'epic'|'story'} EntityKind
|
|
48
48
|
* The structural entity kind. Matches schema $defs — agent-execution
|
|
49
49
|
* labels (agent::*) are owned by the wave-runner and never appear in
|
|
50
50
|
* the structural surface.
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
* @typedef {object} RelinkOp
|
|
88
88
|
* @property {'relink'} kind
|
|
89
89
|
* @property {string} slug
|
|
90
|
-
* @property {EntityKind} entity 'story' (dependsOn
|
|
90
|
+
* @property {EntityKind} entity 'story' (dependsOn / parent).
|
|
91
91
|
* @property {number} issueNumber
|
|
92
92
|
* @property {{ before: string|null, after: string|null }} [parent]
|
|
93
93
|
* Parent slug change. `null` on either side means "no parent" (epic
|
|
@@ -117,9 +117,7 @@ export const OP_KINDS = Object.freeze({
|
|
|
117
117
|
/** Entity-kind discriminator values, matching the schema $defs. */
|
|
118
118
|
export const ENTITY_KINDS = Object.freeze({
|
|
119
119
|
EPIC: 'epic',
|
|
120
|
-
FEATURE: 'feature',
|
|
121
120
|
STORY: 'story',
|
|
122
|
-
TASK: 'task',
|
|
123
121
|
});
|
|
124
122
|
|
|
125
123
|
const VALID_OP_KINDS = new Set(Object.values(OP_KINDS));
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* are batched per-Story and surfaced through the same error envelope the
|
|
9
9
|
* decompose loop already uses.
|
|
10
10
|
*
|
|
11
|
-
* Under the
|
|
11
|
+
* Under the 2-tier hierarchy (Epic → Story; Epic #3078 / #3238)
|
|
12
12
|
* the Story is the implementation unit — there is no `type::task` ticket
|
|
13
13
|
* layer — so the gate scans `type === 'story'` tickets and reads the
|
|
14
14
|
* `{ path, assumption }` entries inlined on each Story body.
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
* therefore validates each Story against the **simulated post-predecessor
|
|
26
26
|
* tree** — base-branch existence overlaid with the create/delete delta of
|
|
27
27
|
* the Story's transitive `depends_on` predecessors (the same reachability
|
|
28
|
-
* walk the conflict gate uses, imported from
|
|
29
|
-
* rather than re-derived). Two extra
|
|
28
|
+
* walk the conflict gate uses, imported from the shared
|
|
29
|
+
* `story-reachability.js` leaf rather than re-derived). Two extra
|
|
30
|
+
* wave-aware rules layer on top of the
|
|
30
31
|
* base-branch rules:
|
|
31
32
|
* - `creates` + path created by a **predecessor** → mismatch
|
|
32
33
|
* (`expected: 'refactors-existing'`) telling the planner to declare
|
|
@@ -48,8 +49,8 @@
|
|
|
48
49
|
import { gitSpawn } from '../git-utils.js';
|
|
49
50
|
import { parse as parseStoryBody } from '../story-body/story-body.js';
|
|
50
51
|
import { FILE_ASSUMPTION_VALUES } from './file-assumption-enum.js';
|
|
52
|
+
import { computeStoryReachability } from './story-reachability.js';
|
|
51
53
|
import { isObjectPathEntry } from './task-body-validator.js';
|
|
52
|
-
import { computeStoryReachability } from './ticket-validator-conflicts.js';
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* Default git probe — returns `true` when `path` exists at
|
|
@@ -254,7 +255,7 @@ function predecessorMutator(index, path, predecessors) {
|
|
|
254
255
|
* mismatches: object[] // structured payload for downstream tooling
|
|
255
256
|
* }
|
|
256
257
|
*
|
|
257
|
-
* Under the
|
|
258
|
+
* Under the 2-tier hierarchy the Story is the implementation unit, so the
|
|
258
259
|
* gate scans `type === 'story'` tickets and reads the inline
|
|
259
260
|
* `{ path, assumption }` entries on each Story body.
|
|
260
261
|
*
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* planning context tickets (PRD / Tech Spec / Acceptance Spec) linked
|
|
5
5
|
* from the Epic body's `## Planning Artifacts` section.
|
|
6
6
|
*
|
|
7
|
-
* Extracted from `/
|
|
7
|
+
* Extracted from `/deliver` Phase 7.1 prose (the `gh issue close`
|
|
8
8
|
* sequence) so the lifecycle Finalizer listener has a single async
|
|
9
9
|
* helper to call. Reuses `parseLinkedIssues` so the three planning ids
|
|
10
10
|
* are read from the same canonical body shape that `epic-plan` writes
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* request, or locates the existing one when finalize is replaying after
|
|
5
5
|
* a crash between `gh pr create` and the `pr.created` emit.
|
|
6
6
|
*
|
|
7
|
-
* Extracted from `/
|
|
7
|
+
* Extracted from `/deliver` Phase 7.1 prose so the lifecycle
|
|
8
8
|
* Finalizer listener has a single async helper that returns the
|
|
9
9
|
* `{ prNumber, url, created }` envelope it needs to compose
|
|
10
10
|
* downstream emits.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* the same head branch: the second call short-circuits at the probe and
|
|
27
27
|
* does not attempt to create a duplicate PR. This is the AC-10
|
|
28
28
|
* idempotency contract the Finalizer relies on for cross-process
|
|
29
|
-
* re-runs of `/
|
|
29
|
+
* re-runs of `/deliver`.
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { spawnSync } from 'node:child_process';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `[skip ci]` markers (and the four variant spellings GitHub honours)
|
|
5
5
|
* from a text payload.
|
|
6
6
|
*
|
|
7
|
-
* Story #3165: `/
|
|
7
|
+
* Story #3165: `/deliver` Phase 7 opens a PR squashed into `main`
|
|
8
8
|
* via `gh pr merge --squash`. Story commits legitimately carry
|
|
9
9
|
* `[skip ci]` markers under `delivery.ci.skipForStoryPushes: true` to
|
|
10
10
|
* keep CI from firing on the wave's intermediate pushes. When GitHub
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lease-guard-shared.js — Story #3992: single-source the lease-acquisition
|
|
3
|
+
* kernel shared by the three per-surface lease guards.
|
|
4
|
+
*
|
|
5
|
+
* `epic-deliver-lease-guard.js`, `epic-plan-lease-guard.js`, and
|
|
6
|
+
* `single-story-lease-guard.js` historically each carried their own copy of
|
|
7
|
+
* the operator-handle resolution and the fail-closed acquire wrapper around
|
|
8
|
+
* `ticket-lease.acquireLease` (anchor `heartbeatAt` to `now` so a foreign
|
|
9
|
+
* assignee always reads as a live claim, then throw an operator-facing
|
|
10
|
+
* refusal naming the current owner unless `--steal`). The three copies had
|
|
11
|
+
* already diverged — different `resolveOperator` signatures and different
|
|
12
|
+
* missing-handle behaviour (`null` vs `throw`) — and were synchronised only
|
|
13
|
+
* by docstring promise ("This mirrors the sibling lease guards…").
|
|
14
|
+
*
|
|
15
|
+
* This module is the single home for the kernel, modelled on the
|
|
16
|
+
* shared plumbing inside `story-close/format-autofix.js` (Story #3332,
|
|
17
|
+
* consolidated by Story #4017). The
|
|
18
|
+
* per-surface guards now differ only in injected **policy**:
|
|
19
|
+
*
|
|
20
|
+
* - **Operator candidates** — each surface supplies its own ordered
|
|
21
|
+
* candidate list (e.g. `--as` flag → `github.operatorHandle` →
|
|
22
|
+
* `git user.email` for `/deliver`; bare `operatorHandle` for the
|
|
23
|
+
* plan/standalone paths).
|
|
24
|
+
* - **Missing-handle behaviour** — `'null'` (return null; the caller fails
|
|
25
|
+
* closed at acquire time) vs `'throw'` (refuse immediately with surface
|
|
26
|
+
* wording). The divergence between the plan path (`null`) and the
|
|
27
|
+
* standalone path (`throw`) is intentional: the plan path also calls
|
|
28
|
+
* `resolveOperator` on its best-effort release leg, where a missing
|
|
29
|
+
* handle must degrade to a `no-operator` no-op rather than throw.
|
|
30
|
+
* - **Refusal wording** — each surface renders its own operator-facing
|
|
31
|
+
* message via `renderRefusal(result, ticketId)`.
|
|
32
|
+
* - **Liveness anchoring** — the plan/standalone paths have no heartbeat
|
|
33
|
+
* ledger, so they anchor `heartbeatAt` to `now` (fail-closed: every
|
|
34
|
+
* foreign claim reads live). `/deliver` threads a real
|
|
35
|
+
* `heartbeatAt` through from the lifecycle ledger, so it opts out of
|
|
36
|
+
* anchoring.
|
|
37
|
+
*
|
|
38
|
+
* The unclaimed / already-held / foreign-claim decision table itself (and
|
|
39
|
+
* the steal transfer) lives in `ticket-lease.acquireLease`; this kernel owns
|
|
40
|
+
* the fail-closed parameterisation and the refuse-by-throw boundary that the
|
|
41
|
+
* three guards previously each re-implemented.
|
|
42
|
+
*
|
|
43
|
+
* Per `.agents/rules/orchestration-error-handling.md`, failures surface via
|
|
44
|
+
* `throw new Error(...)`, never `Logger.fatal`.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
import { acquireLease, normalizeOperatorHandle } from './ticket-lease.js';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the operator handle from an ordered candidate list, applying the
|
|
51
|
+
* surface's missing-handle policy.
|
|
52
|
+
*
|
|
53
|
+
* Each candidate is passed through the shared `normalizeOperatorHandle` so a
|
|
54
|
+
* leading `@` is stripped (the assignees API expects bare logins, not
|
|
55
|
+
* `@`-prefixed mentions) and the shipped `@[USERNAME]` placeholder maps to
|
|
56
|
+
* `null` — otherwise the assignee PATCH is rejected (HTTP 422) and the
|
|
57
|
+
* self-held-claim comparison (`owner === operator`) never matches. The first
|
|
58
|
+
* candidate that normalises to a non-null handle wins.
|
|
59
|
+
*
|
|
60
|
+
* @param {object} opts
|
|
61
|
+
* @param {Array<string|null|undefined>} opts.candidates Ordered raw handles.
|
|
62
|
+
* @param {'null'|'throw'} [opts.missingHandleBehavior='null'] What to do
|
|
63
|
+
* when no candidate resolves: return `null`, or throw with the surface's
|
|
64
|
+
* configured wording.
|
|
65
|
+
* @param {string} [opts.missingHandleMessage] Error message used when
|
|
66
|
+
* `missingHandleBehavior` is `'throw'`.
|
|
67
|
+
* @returns {string|null} Bare operator handle, or `null` (policy `'null'`).
|
|
68
|
+
* @throws {Error} When no candidate resolves and the policy is `'throw'`.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveOperatorFromCandidates({
|
|
71
|
+
candidates,
|
|
72
|
+
missingHandleBehavior = 'null',
|
|
73
|
+
missingHandleMessage,
|
|
74
|
+
} = {}) {
|
|
75
|
+
for (const raw of candidates ?? []) {
|
|
76
|
+
const normalized = normalizeOperatorHandle(raw);
|
|
77
|
+
if (normalized !== null) return normalized;
|
|
78
|
+
}
|
|
79
|
+
if (missingHandleBehavior === 'throw') {
|
|
80
|
+
throw new Error(missingHandleMessage);
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Acquire a ticket lease, failing closed by throwing the surface's refusal
|
|
87
|
+
* message when the claim is refused (live foreign owner, no `steal`).
|
|
88
|
+
*
|
|
89
|
+
* When `anchorHeartbeatToNow` is set (the plan/standalone paths, which have
|
|
90
|
+
* no heartbeat ledger), `heartbeatAt` and `now` are both anchored to the
|
|
91
|
+
* same resolved clock value so `isClaimLive` returns true for ANY foreign
|
|
92
|
+
* owner — `acquireLease` then refuses a foreign assignee unless `steal` is
|
|
93
|
+
* set, while unclaimed and self-held tickets still proceed without a write.
|
|
94
|
+
* When unset (`/deliver`), the caller-supplied `heartbeatAt` / `now`
|
|
95
|
+
* pass through untouched.
|
|
96
|
+
*
|
|
97
|
+
* @param {object} opts
|
|
98
|
+
* @param {object} opts.provider Ticketing provider.
|
|
99
|
+
* @param {number} opts.ticketId Ticket to claim.
|
|
100
|
+
* @param {string} opts.operator Resolved operator handle.
|
|
101
|
+
* @param {number|null} [opts.heartbeatAt=null] Current owner's last
|
|
102
|
+
* heartbeat (epoch ms). Ignored when `anchorHeartbeatToNow` is set.
|
|
103
|
+
* @param {boolean} [opts.steal=false] Forcibly transfer a foreign claim.
|
|
104
|
+
* @param {object} [opts.config] Resolved config (TTL default).
|
|
105
|
+
* @param {number} [opts.now] Injectable clock (epoch ms; tests).
|
|
106
|
+
* @param {boolean} [opts.anchorHeartbeatToNow=false] Fail-closed liveness
|
|
107
|
+
* anchoring for surfaces with no heartbeat source.
|
|
108
|
+
* @param {(result: object, ticketId: number) => string} opts.renderRefusal
|
|
109
|
+
* Renders the operator-facing refusal message for a refused claim.
|
|
110
|
+
* @returns {Promise<{ acquired: boolean, owner: string, previousOwner: string|null, reason: string }>}
|
|
111
|
+
* @throws {Error} When the claim is refused (`result.acquired === false`).
|
|
112
|
+
*/
|
|
113
|
+
export async function acquireLeaseFailClosed({
|
|
114
|
+
provider,
|
|
115
|
+
ticketId,
|
|
116
|
+
operator,
|
|
117
|
+
heartbeatAt = null,
|
|
118
|
+
steal = false,
|
|
119
|
+
config,
|
|
120
|
+
now,
|
|
121
|
+
anchorHeartbeatToNow = false,
|
|
122
|
+
renderRefusal,
|
|
123
|
+
}) {
|
|
124
|
+
let resolvedHeartbeatAt = heartbeatAt;
|
|
125
|
+
let resolvedNow = now;
|
|
126
|
+
if (anchorHeartbeatToNow) {
|
|
127
|
+
resolvedNow =
|
|
128
|
+
typeof now === 'number' && Number.isFinite(now) ? now : Date.now();
|
|
129
|
+
resolvedHeartbeatAt = resolvedNow;
|
|
130
|
+
}
|
|
131
|
+
const result = await acquireLease({
|
|
132
|
+
provider,
|
|
133
|
+
ticketId,
|
|
134
|
+
operator,
|
|
135
|
+
heartbeatAt: resolvedHeartbeatAt,
|
|
136
|
+
steal,
|
|
137
|
+
config,
|
|
138
|
+
now: resolvedNow,
|
|
139
|
+
});
|
|
140
|
+
if (!result.acquired) {
|
|
141
|
+
throw new Error(renderRefusal(result, ticketId));
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* `lib/orchestration/wave-session.js`, which nothing imports outside
|
|
13
13
|
* tests — so every dispatched Story stayed "in-flight" forever and the
|
|
14
14
|
* idle watchdog flagged completed Stories as stalled. The host-LLM driven
|
|
15
|
-
* `/
|
|
15
|
+
* `/deliver` path closes a wave through `epic-execute-record-wave.js`,
|
|
16
16
|
* so that CLI is the correct place to emit the matching dispatch-end per
|
|
17
17
|
* recorded Story.
|
|
18
18
|
*
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
*
|
|
24
24
|
* The schema declares `additionalProperties: false`, so this emitter's
|
|
25
25
|
* signature is deliberately narrow: only the schema-allowed fields are
|
|
26
|
-
* accepted.
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* accepted. The earlier per-child Task id and progress counters
|
|
27
|
+
* were dropped under Epic #3078's
|
|
28
|
+
* 2-tier hard cutover — they would fail strict validation and have no
|
|
29
29
|
* meaning now that the Story is the leaf execution unit with no child
|
|
30
30
|
* tickets. The optional `operator` field (Story #3480) records the handle
|
|
31
31
|
* holding the assignee-as-lease claim; it is included only when supplied so
|