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
|
@@ -41,9 +41,34 @@ import { emitSpawnTimeoutBlockedResult } from './timeout-blocked-emitter.js';
|
|
|
41
41
|
* Run pre-merge gates and, on a clean outcome, the bounded baseline
|
|
42
42
|
* auto-refresh. Returns `{ blocked }` so the locked pipeline can
|
|
43
43
|
* short-circuit on a `blocked` / `blocked-timeout` gate outcome.
|
|
44
|
+
*
|
|
45
|
+
* `runPreMergeValidation` is an injectable seam (Story #3973): the
|
|
46
|
+
* forwarding of `config: ctx.config` into the gate builder is load-bearing
|
|
47
|
+
* — without it the typecheck gate ignores `project.commands.typecheck` and
|
|
48
|
+
* falls back to the hardcoded `npm run typecheck`, blocking every close for
|
|
49
|
+
* any consumer with a non-default typecheck command. The default is the
|
|
50
|
+
* module-level `runPreMergeValidation`; tests inject a spy to pin that
|
|
51
|
+
* `config` reaches the gate builder through this call site.
|
|
52
|
+
*
|
|
53
|
+
* @param {object} ctx - The locked-pipeline context bundle.
|
|
54
|
+
* @param {object} [deps] - Injectable collaborators.
|
|
55
|
+
* @param {typeof runPreMergeValidation} [deps.runPreMergeValidationFn]
|
|
56
|
+
* @param {typeof runAutoRefreshSafely} [deps.runAutoRefreshSafelyFn]
|
|
44
57
|
*/
|
|
45
|
-
async function runGatesAndRefresh(
|
|
46
|
-
|
|
58
|
+
export async function runGatesAndRefresh(
|
|
59
|
+
ctx,
|
|
60
|
+
{
|
|
61
|
+
runPreMergeValidationFn = runPreMergeValidation,
|
|
62
|
+
runAutoRefreshSafelyFn = runAutoRefreshSafely,
|
|
63
|
+
} = {},
|
|
64
|
+
) {
|
|
65
|
+
// Story #4017 — one shared cycle-state object per close cycle. The
|
|
66
|
+
// gate-failure attribution retry and the post-gates auto-refresh both
|
|
67
|
+
// funnel through `runRefreshCommit`, which keys its idempotency token
|
|
68
|
+
// off this object so each baseline kind is refreshed (scored +
|
|
69
|
+
// committed) at most once per close.
|
|
70
|
+
const cycleState = { refreshedKinds: new Set(), lastRefreshSha: null };
|
|
71
|
+
const gateOutcome = await runPreMergeValidationFn({
|
|
47
72
|
cwd: ctx.cwd,
|
|
48
73
|
worktreePath: ctx.worktreePath,
|
|
49
74
|
epicBranch: ctx.epicBranch,
|
|
@@ -55,6 +80,7 @@ async function runGatesAndRefresh(ctx) {
|
|
|
55
80
|
phaseTimer: ctx.phaseTimer,
|
|
56
81
|
provider: ctx.provider,
|
|
57
82
|
bus: ctx.bus,
|
|
83
|
+
cycleState,
|
|
58
84
|
});
|
|
59
85
|
if (gateOutcome?.status === 'blocked') {
|
|
60
86
|
return {
|
|
@@ -82,7 +108,7 @@ async function runGatesAndRefresh(ctx) {
|
|
|
82
108
|
}),
|
|
83
109
|
};
|
|
84
110
|
}
|
|
85
|
-
await
|
|
111
|
+
await runAutoRefreshSafelyFn(
|
|
86
112
|
{
|
|
87
113
|
storyId: ctx.storyId,
|
|
88
114
|
epicId: ctx.epicId,
|
|
@@ -90,6 +116,7 @@ async function runGatesAndRefresh(ctx) {
|
|
|
90
116
|
epicBranch: ctx.epicBranch,
|
|
91
117
|
storyBranch: ctx.storyBranch,
|
|
92
118
|
config: ctx.config,
|
|
119
|
+
cycleState,
|
|
93
120
|
},
|
|
94
121
|
{ progress: ctx.progress },
|
|
95
122
|
);
|
|
@@ -100,7 +127,7 @@ async function runGatesAndRefresh(ctx) {
|
|
|
100
127
|
* Read the parent Epic's judged `planningRisk` envelope off its
|
|
101
128
|
* `epic-plan-state` checkpoint so the Story-scope review can inherit the
|
|
102
129
|
* Epic's review depth (Story #3940). Best-effort and total — it reuses the
|
|
103
|
-
* shared `read` from `epic-plan-state-store.js` (the same reader `/
|
|
130
|
+
* shared `read` from `epic-plan-state-store.js` (the same reader `/plan
|
|
104
131
|
* --resume` and `epic-audit-prepare.js`'s `resolveRiskRoutedLenses` use, no
|
|
105
132
|
* third bespoke reader) and never fails the close:
|
|
106
133
|
*
|
|
@@ -110,7 +137,7 @@ async function runGatesAndRefresh(ctx) {
|
|
|
110
137
|
*
|
|
111
138
|
* A `null` result threads through `runStoryCodeReview` → `runCodeReview`
|
|
112
139
|
* unchanged, so depth resolves from diff width alone (`standard`), preserving
|
|
113
|
-
* today's behaviour for an Epic that skipped `/
|
|
140
|
+
* today's behaviour for an Epic that skipped `/plan`.
|
|
114
141
|
*
|
|
115
142
|
* @param {{ provider: object, epicId: number|null|undefined, readPlanStateFn?: typeof readPlanState }} args
|
|
116
143
|
* @returns {Promise<{ overallLevel?: string, axes?: Array<object> }|null>}
|
|
@@ -21,14 +21,8 @@
|
|
|
21
21
|
* read it; this helper writes the file and hands the path to the pipeline.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
// Legacy key on the post-merge-pipeline / cleanup-reconciler input bag.
|
|
25
|
-
// Built from substrings so the migrated-subsystem grep does not match it;
|
|
26
|
-
// the downstream helpers live outside the migrated subsystem and will
|
|
27
|
-
// rename their parameters when their own subsystems are swept.
|
|
28
|
-
const LEGACY_PIPELINE_CONFIG_KEY = `orches${'tration'}`;
|
|
29
|
-
|
|
30
24
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
31
|
-
import { emitGhSpawnCount as defaultEmitGhSpawnCount } from '../../close-validation.js';
|
|
25
|
+
import { emitGhSpawnCount as defaultEmitGhSpawnCount } from '../../close-validation/telemetry.js';
|
|
32
26
|
import { storyArtifactPath, storyTempDir } from '../../config/temp-paths.js';
|
|
33
27
|
import { gitSpawn as defaultGitSpawn } from '../../git-utils.js';
|
|
34
28
|
import { clearActiveStoryEnv as defaultClearActiveStoryEnv } from '../../observability/active-story-env.js';
|
|
@@ -340,18 +334,11 @@ export async function runPostMergeClose({
|
|
|
340
334
|
// in this order — see post-merge-pipeline.js. The `perf-summary` phase
|
|
341
335
|
// inside the pipeline shells out to analyze-execution.js, which is the
|
|
342
336
|
// single writer of the `<!-- structured:story-perf-summary -->` comment.
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
const legacyPipelineBlock = {
|
|
347
|
-
provider: 'github',
|
|
348
|
-
github: config?.github,
|
|
349
|
-
notifications: config?.github?.notifications,
|
|
350
|
-
worktreeIsolation: deliveryBlock?.worktreeIsolation,
|
|
351
|
-
runners: { deliverRunner: deliveryBlock?.deliverRunner ?? {} },
|
|
352
|
-
};
|
|
337
|
+
// The pipeline phases take the resolved `delivery` block directly, same
|
|
338
|
+
// as the cleanup-reconciler (Story #3986 — the legacy `orchestration`-keyed
|
|
339
|
+
// input bag is gone).
|
|
353
340
|
const pipelineState = await runPostMergePipeline({
|
|
354
|
-
|
|
341
|
+
delivery: deliveryBlock,
|
|
355
342
|
storyId,
|
|
356
343
|
epicId,
|
|
357
344
|
story,
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
// no-spawn spy proves the projection path never reaches a per-kind CLI
|
|
30
30
|
// subprocess.
|
|
31
31
|
import * as maintainabilityKind from '../../baselines/kinds/maintainability.js';
|
|
32
|
+
import { buildDefaultGates as defaultBuildDefaultGates } from '../../close-validation/gates.js';
|
|
32
33
|
import {
|
|
33
|
-
buildDefaultGates as defaultBuildDefaultGates,
|
|
34
34
|
formatMaintainabilityProjection as defaultFormatMaintainabilityProjection,
|
|
35
35
|
projectMaintainabilityRegressions as defaultProjectMaintainabilityRegressions,
|
|
36
|
-
|
|
37
|
-
} from '../../close-validation.js';
|
|
36
|
+
} from '../../close-validation/projections/maintainability.js';
|
|
37
|
+
import { runCloseValidation as defaultRunCloseValidation } from '../../close-validation/runner.js';
|
|
38
38
|
import { getBaselines as defaultGetBaselines } from '../../config-resolver.js';
|
|
39
39
|
import { Logger as DefaultLogger } from '../../Logger.js';
|
|
40
40
|
|
|
@@ -276,7 +276,7 @@ function detectAlreadyMerged({ cwd, storyId, epicId, lsrOut, detail, git }) {
|
|
|
276
276
|
// from the Epic history itself: locate the integration commit whose
|
|
277
277
|
// subject carries `(resolves #<id>)` / `(refs #<id>)`. Without this
|
|
278
278
|
// branch, detection falls to FRESH and the resumed close re-enters the
|
|
279
|
-
// pre-merge gate chain, which crashes in
|
|
279
|
+
// pre-merge gate chain, which crashes in the scoped format-autofix step on
|
|
280
280
|
// `git diff <epicBranch>...story-<id>` because the Story ref is gone.
|
|
281
281
|
if (!resolvedDetail) {
|
|
282
282
|
const mc = findMergeCommitForStory({ cwd, storyId, epicId, git });
|
|
@@ -431,24 +431,35 @@ export function computeRecoveryMode({ state, resume, restart } = {}) {
|
|
|
431
431
|
};
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
-
function dropWorktreeIfPresent({
|
|
434
|
+
function dropWorktreeIfPresent({
|
|
435
|
+
cwd,
|
|
436
|
+
wtPath,
|
|
437
|
+
progress,
|
|
438
|
+
logger,
|
|
439
|
+
gitSpawnFn = gitSpawn,
|
|
440
|
+
}) {
|
|
435
441
|
if (!fs.existsSync(wtPath)) return;
|
|
436
442
|
progress('RESTART', `Removing worktree ${wtPath}`);
|
|
437
|
-
const remove =
|
|
443
|
+
const remove = gitSpawnFn(cwd, 'worktree', 'remove', '--force', wtPath);
|
|
438
444
|
if (remove.status !== 0) {
|
|
439
445
|
logger.error(
|
|
440
446
|
`[story-close] Worktree remove failed: ${remove.stderr || 'unknown'}. ` +
|
|
441
447
|
'Attempting prune to clean stale registration.',
|
|
442
448
|
);
|
|
443
449
|
}
|
|
444
|
-
|
|
450
|
+
gitSpawnFn(cwd, 'worktree', 'prune');
|
|
445
451
|
}
|
|
446
452
|
|
|
447
|
-
function recreateStoryBranchRef({
|
|
448
|
-
|
|
449
|
-
|
|
453
|
+
function recreateStoryBranchRef({
|
|
454
|
+
cwd,
|
|
455
|
+
storyBranch,
|
|
456
|
+
epicBranch,
|
|
457
|
+
gitSpawnFn = gitSpawn,
|
|
458
|
+
}) {
|
|
459
|
+
gitSpawnFn(cwd, 'branch', '-D', storyBranch);
|
|
460
|
+
const create = gitSpawnFn(cwd, 'branch', storyBranch, epicBranch);
|
|
450
461
|
if (create.status !== 0) {
|
|
451
|
-
|
|
462
|
+
throw new Error(
|
|
452
463
|
`Failed to recreate ${storyBranch} from ${epicBranch}: ${create.stderr || 'unknown'}`,
|
|
453
464
|
);
|
|
454
465
|
}
|
|
@@ -460,13 +471,13 @@ function reseedWorktreeIfNeeded({
|
|
|
460
471
|
storyId,
|
|
461
472
|
storyBranch,
|
|
462
473
|
progress,
|
|
463
|
-
|
|
474
|
+
gitSpawnFn = gitSpawn,
|
|
464
475
|
}) {
|
|
465
476
|
if (!wtConfig?.enabled) return;
|
|
466
477
|
const wtPath = storyWorktreePath(cwd, storyId, wtConfig.root);
|
|
467
|
-
const add =
|
|
478
|
+
const add = gitSpawnFn(cwd, 'worktree', 'add', wtPath, storyBranch);
|
|
468
479
|
if (add.status !== 0) {
|
|
469
|
-
|
|
480
|
+
throw new Error(
|
|
470
481
|
`Failed to re-seed worktree at ${wtPath}: ${add.stderr || 'unknown'}`,
|
|
471
482
|
);
|
|
472
483
|
}
|
|
@@ -477,8 +488,12 @@ function reseedWorktreeIfNeeded({
|
|
|
477
488
|
* Restart path: abort any in-progress merge, drop the worktree, delete the
|
|
478
489
|
* story branch ref, and re-seed branch + worktree from the Epic branch. The
|
|
479
490
|
* caller then falls through to the normal fresh-close flow.
|
|
491
|
+
*
|
|
492
|
+
* Throws (never `logger.fatal`) on a failed branch recreate or worktree
|
|
493
|
+
* re-seed, per `rules/orchestration-error-handling.md` — a failed recreate
|
|
494
|
+
* MUST NOT fall through into the worktree re-seed.
|
|
480
495
|
*/
|
|
481
|
-
function restartStoryState({
|
|
496
|
+
export function restartStoryState({
|
|
482
497
|
cwd,
|
|
483
498
|
orchestration,
|
|
484
499
|
storyId,
|
|
@@ -486,9 +501,10 @@ function restartStoryState({
|
|
|
486
501
|
storyBranch,
|
|
487
502
|
progress = () => {},
|
|
488
503
|
logger = Logger,
|
|
504
|
+
gitSpawnFn = gitSpawn,
|
|
489
505
|
} = {}) {
|
|
490
506
|
progress('RESTART', `Resetting prior state for Story #${storyId}...`);
|
|
491
|
-
|
|
507
|
+
gitSpawnFn(cwd, 'merge', '--abort');
|
|
492
508
|
|
|
493
509
|
const wtConfig = orchestration?.worktreeIsolation;
|
|
494
510
|
if (wtConfig?.enabled) {
|
|
@@ -497,17 +513,18 @@ function restartStoryState({
|
|
|
497
513
|
wtPath: storyWorktreePath(cwd, storyId, wtConfig.root),
|
|
498
514
|
progress,
|
|
499
515
|
logger,
|
|
516
|
+
gitSpawnFn,
|
|
500
517
|
});
|
|
501
518
|
}
|
|
502
519
|
|
|
503
|
-
recreateStoryBranchRef({ cwd, storyBranch, epicBranch,
|
|
520
|
+
recreateStoryBranchRef({ cwd, storyBranch, epicBranch, gitSpawnFn });
|
|
504
521
|
reseedWorktreeIfNeeded({
|
|
505
522
|
cwd,
|
|
506
523
|
wtConfig,
|
|
507
524
|
storyId,
|
|
508
525
|
storyBranch,
|
|
509
526
|
progress,
|
|
510
|
-
|
|
527
|
+
gitSpawnFn,
|
|
511
528
|
});
|
|
512
529
|
}
|
|
513
530
|
|
|
@@ -563,7 +580,7 @@ export function dispatchRecovery({
|
|
|
563
580
|
restartFn = restartStoryState,
|
|
564
581
|
} = {}) {
|
|
565
582
|
if (resume && restart) {
|
|
566
|
-
|
|
583
|
+
throw new Error('--resume and --restart are mutually exclusive');
|
|
567
584
|
}
|
|
568
585
|
|
|
569
586
|
const priorPhase = detectFn({ cwd, storyId, epicId });
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/orchestration/story-reachability.js — Story `depends_on` graph walk.
|
|
3
|
+
*
|
|
4
|
+
* Houses the transitive-predecessor closure over the story-level
|
|
5
|
+
* `depends_on` graph. Extracted from `./ticket-validator-conflicts.js`
|
|
6
|
+
* under Story #3995 to break the `file-assumptions.js ↔
|
|
7
|
+
* ticket-validator-conflicts.js` import cycle: both the conflict gate and
|
|
8
|
+
* the wave-aware file-assumption gate need this graph traversal, so
|
|
9
|
+
* pulling it down into a dependency-free leaf lets both import it from
|
|
10
|
+
* below rather than from each other.
|
|
11
|
+
*
|
|
12
|
+
* This is a pure graph utility — no I/O, no policy. Its behaviour is
|
|
13
|
+
* unchanged from the original `computeStoryReachability`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compute transitive predecessor sets over the story-level `depends_on`
|
|
18
|
+
* graph. The returned map is `Map<storySlug, Set<storySlug>>`, where the
|
|
19
|
+
* set contains every story reachable by following `depends_on` edges from
|
|
20
|
+
* the key (i.e. every story the key transitively depends on).
|
|
21
|
+
*
|
|
22
|
+
* BFS, no cycles assumed — callers must run `assertAcyclic` first.
|
|
23
|
+
*
|
|
24
|
+
* Exported so both the conflict gate (`ticket-validator-conflicts.js`)
|
|
25
|
+
* and the wave-aware file-assumption gate (`file-assumptions.js`) can
|
|
26
|
+
* reuse the same transitive-predecessor walk rather than re-deriving the
|
|
27
|
+
* `depends_on` closure.
|
|
28
|
+
*/
|
|
29
|
+
export function computeStoryReachability(stories) {
|
|
30
|
+
const reach = new Map();
|
|
31
|
+
for (const story of stories) reach.set(story.slug, new Set());
|
|
32
|
+
for (const story of stories) {
|
|
33
|
+
const visited = reach.get(story.slug);
|
|
34
|
+
const stack = [...(story.depends_on ?? [])];
|
|
35
|
+
while (stack.length > 0) {
|
|
36
|
+
const next = stack.pop();
|
|
37
|
+
if (!reach.has(next)) continue;
|
|
38
|
+
if (visited.has(next)) continue;
|
|
39
|
+
visited.add(next);
|
|
40
|
+
const nextStory = stories.find((s) => s.slug === next);
|
|
41
|
+
if (nextStory && Array.isArray(nextStory.depends_on)) {
|
|
42
|
+
for (const dep of nextStory.depends_on) stack.push(dep);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return reach;
|
|
47
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Story body schema validator (v5.33+).
|
|
3
3
|
*
|
|
4
|
-
* Enforces the four-section structured body shape on
|
|
4
|
+
* Enforces the four-section structured body shape on 2-tier Stories emitted
|
|
5
5
|
* by the decomposer. The canonical decomposition serializes every Story
|
|
6
6
|
* `body` to a **markdown string** via `serialize()` from
|
|
7
7
|
* `lib/story-body/story-body.js` (the decompose-author skill mandates this,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Only `type: 'story'` tickets are validated; Feature/Epic tickets and
|
|
19
19
|
* null/empty bodies pass through. There is no `type::task` ticket layer in
|
|
20
|
-
* the
|
|
20
|
+
* the 2-tier hierarchy (Epic → Story).
|
|
21
21
|
*
|
|
22
22
|
* Required after parse/normalize: a non-empty `goal`, and non-empty
|
|
23
23
|
* `changes`, `acceptance`, and `verify` arrays — and `changes` items must
|
|
@@ -110,7 +110,7 @@ function vagueVerbWithoutTarget(bullet) {
|
|
|
110
110
|
* - it has no body (null / undefined / empty-or-whitespace string — there
|
|
111
111
|
* is nothing to inspect).
|
|
112
112
|
*
|
|
113
|
-
* Under the
|
|
113
|
+
* Under the 2-tier hierarchy (Epic → Story), Stories carry the
|
|
114
114
|
* implementation scope inline. A canonical decomposition serializes the
|
|
115
115
|
* Story body to a markdown string, so a *string* body is NOT skipped here
|
|
116
116
|
* (Story #3906) — `validateTaskBodyShape` parses it back into structured
|
|
@@ -127,7 +127,7 @@ function vagueVerbWithoutTarget(bullet) {
|
|
|
127
127
|
*/
|
|
128
128
|
function shouldSkipTicket(ticket) {
|
|
129
129
|
if (!ticket) return true;
|
|
130
|
-
// Only Stories carry an inline implementation contract in the
|
|
130
|
+
// Only Stories carry an inline implementation contract in the 2-tier
|
|
131
131
|
// world. Features (and everything else) use narrative bodies.
|
|
132
132
|
if (ticket.type !== 'story') return true;
|
|
133
133
|
const body = ticket.body;
|
|
@@ -197,7 +197,7 @@ export function validateTaskBodyShape(ticket) {
|
|
|
197
197
|
}
|
|
198
198
|
errors.push(...collectChangesErrors(prefix, body.changes));
|
|
199
199
|
errors.push(...collectAcceptanceErrors(prefix, body.acceptance));
|
|
200
|
-
// Tier-suffix validation is always enforced on Story bodies (
|
|
200
|
+
// Tier-suffix validation is always enforced on Story bodies (2-tier world).
|
|
201
201
|
errors.push(...collectVerifyErrors(prefix, body.verify));
|
|
202
202
|
errors.push(...collectReferencesErrors(prefix, body.references));
|
|
203
203
|
return errors;
|
|
@@ -359,7 +359,7 @@ function collectVerifyErrors(prefix, rawVerify) {
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
/**
|
|
362
|
-
* Validate every
|
|
362
|
+
* Validate every 2-tier Story in `tickets` whose `body` is a structured
|
|
363
363
|
* object. Returns an array of error strings (one per offending slug); empty
|
|
364
364
|
* array means clean.
|
|
365
365
|
*
|
|
@@ -96,7 +96,7 @@ export function normalizeOperatorHandle(raw) {
|
|
|
96
96
|
* observability artifact never wedges the lease preflight.
|
|
97
97
|
*
|
|
98
98
|
* This is the shared liveness source the lease guards thread into
|
|
99
|
-
* `acquireLease` via `heartbeatAt`; `/
|
|
99
|
+
* `acquireLease` via `heartbeatAt`; `/plan` and `/deliver` both
|
|
100
100
|
* reuse it so a live foreign claim actually refuses.
|
|
101
101
|
*
|
|
102
102
|
* @param {object} args
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { collectStoryAssumptionEntries } from './file-assumptions.js';
|
|
2
|
+
import { computeStoryReachability } from './story-reachability.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Cross-Story path-conflict & implicit-dependency findings.
|
|
@@ -66,7 +67,7 @@ const DEFAULT_POLICY = Object.freeze({
|
|
|
66
67
|
* - exact path — `lib/orchestration/lifecycle/listeners/index.js`
|
|
67
68
|
* - `**` suffix — `**\/listeners/index.js` (matches any depth)
|
|
68
69
|
*/
|
|
69
|
-
const DEFAULT_REGISTRY_PATTERNS = Object.freeze([
|
|
70
|
+
export const DEFAULT_REGISTRY_PATTERNS = Object.freeze([
|
|
70
71
|
'lib/orchestration/lifecycle/listeners/index.js',
|
|
71
72
|
'**/listeners/index.js',
|
|
72
73
|
'**/handlers/index.js',
|
|
@@ -169,7 +170,7 @@ function collectStoryProducerPaths(story) {
|
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
/**
|
|
172
|
-
* Resolve the Story-identifying slug for a
|
|
173
|
+
* Resolve the Story-identifying slug for a 2-tier Story. A Story is its
|
|
173
174
|
* own implementation unit (Epic #3238) — there is no parent Task — so the
|
|
174
175
|
* producer/consumer indices key on the Story's own `slug`.
|
|
175
176
|
*/
|
|
@@ -186,7 +187,7 @@ function storySlugOf(story) {
|
|
|
186
187
|
* producers.
|
|
187
188
|
*
|
|
188
189
|
* `taskSlug` is retained in the entry shape for finding/render
|
|
189
|
-
* compatibility; in the
|
|
190
|
+
* compatibility; in the 2-tier model it carries the Story's own slug since
|
|
190
191
|
* the Story is the implementation unit.
|
|
191
192
|
*/
|
|
192
193
|
function indexProducers(stories) {
|
|
@@ -242,38 +243,6 @@ function indexConsumers(stories, producers) {
|
|
|
242
243
|
return consumers;
|
|
243
244
|
}
|
|
244
245
|
|
|
245
|
-
/**
|
|
246
|
-
* Compute transitive predecessor sets over the story-level `depends_on`
|
|
247
|
-
* graph. The returned map is `Map<storySlug, Set<storySlug>>`, where the
|
|
248
|
-
* set contains every story reachable by following `depends_on` edges from
|
|
249
|
-
* the key (i.e. every story the key transitively depends on).
|
|
250
|
-
*
|
|
251
|
-
* BFS, no cycles assumed — callers must run `assertAcyclic` first.
|
|
252
|
-
*
|
|
253
|
-
* Exported so the wave-aware file-assumption gate
|
|
254
|
-
* (`file-assumptions.js`) can reuse the same transitive-predecessor walk
|
|
255
|
-
* rather than re-deriving the `depends_on` closure.
|
|
256
|
-
*/
|
|
257
|
-
export function computeStoryReachability(stories) {
|
|
258
|
-
const reach = new Map();
|
|
259
|
-
for (const story of stories) reach.set(story.slug, new Set());
|
|
260
|
-
for (const story of stories) {
|
|
261
|
-
const visited = reach.get(story.slug);
|
|
262
|
-
const stack = [...(story.depends_on ?? [])];
|
|
263
|
-
while (stack.length > 0) {
|
|
264
|
-
const next = stack.pop();
|
|
265
|
-
if (!reach.has(next)) continue;
|
|
266
|
-
if (visited.has(next)) continue;
|
|
267
|
-
visited.add(next);
|
|
268
|
-
const nextStory = stories.find((s) => s.slug === next);
|
|
269
|
-
if (nextStory && Array.isArray(nextStory.depends_on)) {
|
|
270
|
-
for (const dep of nextStory.depends_on) stack.push(dep);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return reach;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
246
|
function inSameWave(reach, slugA, slugB) {
|
|
278
247
|
if (slugA === slugB) return false;
|
|
279
248
|
const a = reach.get(slugA);
|
|
@@ -39,15 +39,6 @@ export const DEFAULT_TASK_SIZING = Object.freeze({
|
|
|
39
39
|
maxAcceptance: 14,
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* Soft prose guidance for how many Stories a Feature typically decomposes
|
|
44
|
-
* into before the Feature scope smells like two Features. Rendered into the
|
|
45
|
-
* decomposer prompt's Feature-count sentence from this single constant so
|
|
46
|
-
* the prompt prose cannot drift from the SSOT module (Story #3874). Advisory
|
|
47
|
-
* only — no validator finding keys off it.
|
|
48
|
-
*/
|
|
49
|
-
export const SOFT_STORIES_PER_FEATURE = 7;
|
|
50
|
-
|
|
51
42
|
/**
|
|
52
43
|
* `DELIVERABLE_GRANULARITY_GUIDANCE` is the **single source of truth** for the
|
|
53
44
|
* deliverable-granularity definition of a Story (Story #3777). It is stated
|
|
@@ -339,7 +330,7 @@ function computeStorySizingFindings(story, sizing) {
|
|
|
339
330
|
* `findings`; the AC-visible `errors[]` channel is the rendered
|
|
340
331
|
* subset where `severity === 'hard'`.
|
|
341
332
|
*
|
|
342
|
-
*
|
|
333
|
+
* 2-tier (Epic #3238): each Story is its own implementation unit and
|
|
343
334
|
* carries the `body` (acceptance / changes / wide) that the sizing layers
|
|
344
335
|
* score. There is no Task tier, so findings are computed directly over
|
|
345
336
|
* `stories`.
|
|
@@ -200,9 +200,8 @@ function makeMemoizedGitRunner(runner) {
|
|
|
200
200
|
* path was deleted between planning and decomposition) — refuse to decompose
|
|
201
201
|
* because the resulting Task would be unimplementable as written.
|
|
202
202
|
*
|
|
203
|
-
* Only
|
|
204
|
-
*
|
|
205
|
-
* the freshness regex would (correctly) ignore.
|
|
203
|
+
* Only Stories are scanned — they are the implementation unit; the Epic
|
|
204
|
+
* carries narrative copy, not implementation paths.
|
|
206
205
|
*
|
|
207
206
|
* @param {object} opts
|
|
208
207
|
* @param {object[]} opts.tickets - Validated ticket hierarchy.
|
|
@@ -416,7 +415,6 @@ function renderMissLine({ slug, path }) {
|
|
|
416
415
|
|
|
417
416
|
function indexTicketsBySlug(tickets) {
|
|
418
417
|
const ticketBySlug = new Map();
|
|
419
|
-
const features = [];
|
|
420
418
|
const stories = [];
|
|
421
419
|
const slugAdjacency = new Map();
|
|
422
420
|
for (const t of tickets) {
|
|
@@ -429,75 +427,37 @@ function indexTicketsBySlug(tickets) {
|
|
|
429
427
|
ticketBySlug.set(t.slug, t);
|
|
430
428
|
}
|
|
431
429
|
slugAdjacency.set(t.slug, t.depends_on ?? []);
|
|
432
|
-
if (t.type === '
|
|
433
|
-
else if (t.type === 'story') stories.push(t);
|
|
430
|
+
if (t.type === 'story') stories.push(t);
|
|
434
431
|
}
|
|
435
|
-
return { ticketBySlug,
|
|
432
|
+
return { ticketBySlug, stories, slugAdjacency };
|
|
436
433
|
}
|
|
437
434
|
|
|
438
|
-
|
|
439
|
-
|
|
435
|
+
/**
|
|
436
|
+
* 2-tier invariant (Story #4041): the decomposer emits Stories only — every
|
|
437
|
+
* ticket in the backlog must be `type: "story"` and at least one must be
|
|
438
|
+
* present. Any other type (the retired `feature`/`task` tiers, or planner
|
|
439
|
+
* hallucinations) HARD-rejects the decomposition.
|
|
440
|
+
*/
|
|
441
|
+
function assertAllTicketsAreStories({ tickets, stories }) {
|
|
442
|
+
const nonStories = (tickets ?? []).filter((t) => t.type !== 'story');
|
|
443
|
+
if (nonStories.length > 0) {
|
|
444
|
+
const list = nonStories
|
|
445
|
+
.map((t) => `"${t.title}" (${t.slug ?? '<no slug>'}, type: ${t.type})`)
|
|
446
|
+
.join(', ');
|
|
440
447
|
throw new Error(
|
|
441
|
-
|
|
448
|
+
`Cross-Validation Failed: ${nonStories.length} ticket(s) are not Stories: ${list}. ` +
|
|
449
|
+
'The 2-tier hierarchy (Epic → Story) admits type "story" only — there is no Feature or Task tier.',
|
|
442
450
|
);
|
|
451
|
+
}
|
|
443
452
|
if (stories.length === 0)
|
|
444
453
|
throw new Error(
|
|
445
454
|
'Cross-Validation Failed: Backlog must contain at least one Story.',
|
|
446
455
|
);
|
|
447
456
|
}
|
|
448
457
|
|
|
449
|
-
function assertHierarchy({ stories, ticketBySlug }) {
|
|
450
|
-
for (const story of stories) {
|
|
451
|
-
if (!story.parent_slug)
|
|
452
|
-
throw new Error(
|
|
453
|
-
`Cross-Validation Failed: Story "${story.title}" must have a parent_slug.`,
|
|
454
|
-
);
|
|
455
|
-
const parent = ticketBySlug.get(story.parent_slug);
|
|
456
|
-
if (!parent || parent.type !== 'feature')
|
|
457
|
-
throw new Error(
|
|
458
|
-
`Cross-Validation Failed: Story "${story.title}" parent must be a Feature.`,
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
/**
|
|
464
|
-
* Deterministic invariant (Story #3777): a Feature MUST decompose into at
|
|
465
|
-
* least two Stories. A single-Story Feature is the work of a Story, not a
|
|
466
|
-
* Feature — the Feature wrapper is dead weight and signals decomposition at
|
|
467
|
-
* module/task granularity rather than deliverable granularity. HARD-reject
|
|
468
|
-
* the decomposition, naming the offending Feature(s) and telling the planner
|
|
469
|
-
* to collapse them, in the same throw-on-violation style as the surrounding
|
|
470
|
-
* hierarchy invariants.
|
|
471
|
-
*/
|
|
472
|
-
function assertNoSingleStoryFeature({ features, stories }) {
|
|
473
|
-
const storyCountByParent = new Map();
|
|
474
|
-
for (const story of stories) {
|
|
475
|
-
if (!story.parent_slug) continue;
|
|
476
|
-
storyCountByParent.set(
|
|
477
|
-
story.parent_slug,
|
|
478
|
-
(storyCountByParent.get(story.parent_slug) ?? 0) + 1,
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
const undersized = features.filter(
|
|
482
|
-
(feature) => (storyCountByParent.get(feature.slug) ?? 0) < 2,
|
|
483
|
-
);
|
|
484
|
-
if (undersized.length === 0) return;
|
|
485
|
-
const list = undersized
|
|
486
|
-
.map((feature) => {
|
|
487
|
-
const count = storyCountByParent.get(feature.slug) ?? 0;
|
|
488
|
-
return `"${feature.title}" (${feature.slug}, ${count} ${count === 1 ? 'Story' : 'Stories'})`;
|
|
489
|
-
})
|
|
490
|
-
.join(', ');
|
|
491
|
-
throw new Error(
|
|
492
|
-
`Cross-Validation Failed: ${undersized.length} Feature(s) decompose into fewer than two Stories: ${list}. ` +
|
|
493
|
-
'Every Feature MUST contain at least two Stories — a single-Story Feature is the work of a Story, not a Feature. ' +
|
|
494
|
-
'Collapse each offending Feature: drop the Feature wrapper and attach its lone Story to a sibling Feature, or merge the Feature into another.',
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
458
|
/**
|
|
499
459
|
* Return true when a Story object carries inline acceptance + verify
|
|
500
|
-
* arrays — the
|
|
460
|
+
* arrays — the inline-contract shape (Epic #3078) where the Story is itself the
|
|
501
461
|
* implementation unit and acceptance / verify live on the Story body
|
|
502
462
|
* rather than in child Task tickets.
|
|
503
463
|
*
|
|
@@ -506,7 +466,7 @@ function assertNoSingleStoryFeature({ features, stories }) {
|
|
|
506
466
|
* `acceptance[]` (no `verify[]`) cannot be implemented without a
|
|
507
467
|
* verification handle, and a Story with only `verify[]` (no
|
|
508
468
|
* `acceptance[]`) carries no observable criterion. Requiring both is the
|
|
509
|
-
* inline-contract invariant every Story must satisfy
|
|
469
|
+
* inline-contract invariant every Story must satisfy.
|
|
510
470
|
*/
|
|
511
471
|
function hasInlineAcceptanceAndVerify(story) {
|
|
512
472
|
if (story === null || typeof story !== 'object') return false;
|
|
@@ -520,7 +480,7 @@ function hasInlineAcceptanceAndVerify(story) {
|
|
|
520
480
|
}
|
|
521
481
|
|
|
522
482
|
function assertEveryStoryHasInlineContract({ stories }) {
|
|
523
|
-
//
|
|
483
|
+
// Every Story is its own implementation
|
|
524
484
|
// unit and MUST carry a non-empty inline contract — top-level
|
|
525
485
|
// `acceptance[]` AND `verify[]`. A Story missing either is the legacy
|
|
526
486
|
// 4-tier shape that expected child Tasks; there is no Task tier any
|
|
@@ -577,12 +537,9 @@ function attachFindingsAndErrors(tickets, findings, errors) {
|
|
|
577
537
|
}
|
|
578
538
|
|
|
579
539
|
export function validateAndNormalizeTickets(tickets, opts = {}) {
|
|
580
|
-
const { ticketBySlug,
|
|
581
|
-
indexTicketsBySlug(tickets);
|
|
540
|
+
const { ticketBySlug, stories, slugAdjacency } = indexTicketsBySlug(tickets);
|
|
582
541
|
|
|
583
|
-
|
|
584
|
-
assertHierarchy({ stories, ticketBySlug });
|
|
585
|
-
assertNoSingleStoryFeature({ features, stories });
|
|
542
|
+
assertAllTicketsAreStories({ tickets, stories });
|
|
586
543
|
assertEveryStoryHasInlineContract({ stories });
|
|
587
544
|
assertNoUnknownDeps({ tickets, ticketBySlug });
|
|
588
545
|
|
|
@@ -680,9 +637,7 @@ export function validateAndNormalizeTickets(tickets, opts = {}) {
|
|
|
680
637
|
// Internal helpers exposed for unit tests; not part of the public surface.
|
|
681
638
|
export const _internal = {
|
|
682
639
|
indexTicketsBySlug,
|
|
683
|
-
|
|
684
|
-
assertHierarchy,
|
|
685
|
-
assertNoSingleStoryFeature,
|
|
640
|
+
assertAllTicketsAreStories,
|
|
686
641
|
assertEveryStoryHasInlineContract,
|
|
687
642
|
assertNoUnknownDeps,
|
|
688
643
|
assertAcyclic,
|