mandrel 1.93.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +59 -73
- package/.agents/agents/acceptance-critic.md +129 -0
- package/.agents/agents/story-worker.md +161 -0
- package/.agents/docs/SDLC.md +489 -1285
- package/.agents/docs/agentrc-reference.json +177 -67
- package/.agents/docs/configuration.md +108 -136
- package/.agents/docs/execution-reference.md +44 -22
- package/.agents/docs/quality-gates.md +13 -19
- package/.agents/docs/workflows.md +3 -3
- package/.agents/instructions.md +107 -108
- package/.agents/rules/ci-remediation.md +8 -12
- package/.agents/rules/git-conventions-reference.md +224 -0
- package/.agents/rules/git-conventions.md +42 -223
- package/.agents/rules/security-baseline.md +5 -0
- package/.agents/rules/testing-standards.md +106 -13
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +71 -201
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
- package/.agents/schemas/risk-verdict.schema.json +0 -13
- package/.agents/scripts/acceptance-eval.js +62 -18
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/analyze-execution.js +1 -1
- package/.agents/scripts/audit-to-stories.js +7 -7
- package/.agents/scripts/boot-sweep.js +1 -1
- package/.agents/scripts/check-context-budget.js +62 -5
- package/.agents/scripts/check-lifecycle-lint.js +6 -9
- package/.agents/scripts/check-prepush-recovery.js +1 -1
- package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
- package/.agents/scripts/diagnose-friction.js +0 -6
- package/.agents/scripts/lib/Logger.js +6 -10
- package/.agents/scripts/lib/audit-suite/runner.js +2 -2
- package/.agents/scripts/lib/audit-suite/selector.js +5 -5
- package/.agents/scripts/lib/audit-to-stories/{seed-epic-from-findings.js → seed-from-findings.js} +9 -9
- package/.agents/scripts/lib/baselines/kernel.js +206 -18
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -4
- package/.agents/scripts/lib/baselines/reader.js +1 -6
- package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +56 -18
- package/.agents/scripts/lib/checks/core-bare-clean.js +2 -2
- package/.agents/scripts/lib/checks/index.js +2 -1
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +23 -21
- package/.agents/scripts/lib/cli/standard-args.js +13 -22
- package/.agents/scripts/lib/cli-args.js +16 -7
- package/.agents/scripts/lib/close-validation/gates.js +160 -22
- package/.agents/scripts/lib/config/acceptance-eval.js +52 -5
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +103 -0
- package/.agents/scripts/lib/config/explain.js +57 -36
- package/.agents/scripts/lib/config/limits.js +17 -58
- package/.agents/scripts/lib/config/paths.js +0 -2
- package/.agents/scripts/lib/config/quality.js +1 -1
- package/.agents/scripts/lib/config/runners.js +17 -50
- package/.agents/scripts/lib/config/temp-paths.js +19 -14
- package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
- package/.agents/scripts/lib/config-resolver.js +3 -8
- package/.agents/scripts/lib/config-settings-schema-delivery.js +46 -136
- package/.agents/scripts/lib/config-settings-schema-quality.js +17 -14
- package/.agents/scripts/lib/config-settings-schema.js +52 -38
- package/.agents/scripts/lib/dependency-parser.js +3 -2
- package/.agents/scripts/lib/doc-tiers.js +39 -4
- package/.agents/scripts/lib/duplicate-search.js +211 -41
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +5 -5
- package/.agents/scripts/lib/framework-version.js +2 -3
- package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
- package/.agents/scripts/lib/git-branch-lifecycle.js +17 -22
- package/.agents/scripts/lib/git-utils.js +32 -6
- package/.agents/scripts/lib/github/framework-repo.js +6 -0
- package/.agents/scripts/lib/label-constants.js +10 -23
- package/.agents/scripts/lib/label-taxonomy.js +9 -43
- package/.agents/scripts/lib/observability/active-story-env.js +112 -3
- package/.agents/scripts/lib/observability/hook-heartbeat.js +187 -0
- package/.agents/scripts/lib/observability/source-classifier.js +3 -3
- package/.agents/scripts/lib/observability/tool-trace-hook.js +15 -4
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +111 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +32 -4
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +128 -0
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +273 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +204 -0
- package/.agents/scripts/lib/orchestration/code-review.js +20 -268
- package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +1 -1
- package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
- package/.agents/scripts/lib/orchestration/docs-digest.js +8 -8
- package/.agents/scripts/lib/orchestration/file-assumptions.js +7 -13
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +8 -8
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +2 -2
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +17 -43
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
- package/.agents/scripts/lib/orchestration/merge-block-class.js +1 -1
- package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +248 -266
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +61 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +97 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +223 -854
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +361 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +35 -108
- package/.agents/scripts/lib/orchestration/plan-reachability.js +9 -14
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +1 -1
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/authoring-context.js +14 -14
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/risk-verdict.js +3 -4
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +2 -2
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +8 -20
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +3 -2
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
- package/.agents/scripts/lib/orchestration/preflight-cache.js +5 -5
- package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +155 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +8 -8
- package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
- package/.agents/scripts/lib/orchestration/review-depth.js +26 -12
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
- package/.agents/scripts/lib/orchestration/run-epilogue.js +426 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +1 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +95 -41
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +16 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +40 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +11 -3
- package/.agents/scripts/lib/orchestration/spec-freshness.js +14 -205
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +4 -5
- package/.agents/scripts/lib/orchestration/spec-spill.js +60 -0
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +188 -0
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +49 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -12
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +237 -0
- package/.agents/scripts/lib/orchestration/story-init-remote.js +47 -0
- package/.agents/scripts/lib/orchestration/story-plan-state.js +48 -0
- package/.agents/scripts/lib/orchestration/{epic-runner → story-progress}/story-run-progress-writer.js +3 -3
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -18
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +11 -61
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +189 -373
- package/.agents/scripts/lib/orchestration/ticket-validator.js +3 -8
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +0 -25
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +29 -26
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -5
- package/.agents/scripts/lib/planning-corpus.js +16 -11
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/provider-factory.js +1 -1
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +36 -0
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
- package/.agents/scripts/lib/story-adjacency.js +11 -14
- package/.agents/scripts/lib/story-body/story-body.js +124 -70
- package/.agents/scripts/lib/story-plan.js +2 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +46 -45
- package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
- package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +26 -26
- package/.agents/scripts/lib/validation-evidence.js +1 -1
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -6
- package/.agents/scripts/lib/workspace-provisioner.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +5 -7
- package/.agents/scripts/lint-issue-body.js +71 -40
- package/.agents/scripts/mandrel-update-preflight.js +1 -1
- package/.agents/scripts/notify.js +4 -3
- package/.agents/scripts/plan-context.js +64 -74
- package/.agents/scripts/plan-persist.js +121 -280
- package/.agents/scripts/plan-run-epilogue.js +97 -0
- package/.agents/scripts/post-structured-comment.js +38 -0
- package/.agents/scripts/providers/github/issues.js +17 -33
- package/.agents/scripts/providers/github/mappers.js +0 -12
- package/.agents/scripts/providers/github/tickets.js +2 -5
- package/.agents/scripts/resolve-plan-run.js +117 -0
- package/.agents/scripts/signals-view.js +24 -19
- package/.agents/scripts/single-story-close.js +11 -14
- package/.agents/scripts/single-story-confirm-merge.js +39 -23
- package/.agents/scripts/single-story-init.js +29 -20
- package/.agents/scripts/stories-wave-tick.js +6 -6
- package/.agents/scripts/story-plan.js +26 -47
- package/.agents/scripts/sync-claude-agents.js +165 -0
- package/.agents/scripts/update-ticket-state.js +37 -15
- package/.agents/skills/core/analyze-execution/SKILL.md +21 -18
- package/.agents/skills/core/api-and-interface-design/SKILL.md +5 -3
- package/.agents/skills/core/code-review-and-quality/SKILL.md +63 -7
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +1 -1
- package/.agents/skills/core/gates-and-baselines/SKILL.md +149 -0
- package/.agents/skills/core/idea-refinement/SKILL.md +8 -14
- package/.agents/skills/core/qa-coverage-mapping/SKILL.md +7 -7
- package/.agents/skills/core/scope-triage/SKILL.md +28 -172
- package/.agents/skills/skills.index.json +8 -418
- package/.agents/starter-agentrc.json +0 -5
- package/.agents/templates/agent-protocol.md +9 -10
- package/.agents/workflows/audit-architecture.md +3 -3
- package/.agents/workflows/audit-clean-code.md +3 -3
- package/.agents/workflows/audit-dependencies.md +3 -3
- package/.agents/workflows/audit-devops.md +3 -3
- package/.agents/workflows/audit-documentation.md +5 -5
- package/.agents/workflows/audit-lighthouse.md +3 -3
- package/.agents/workflows/audit-navigability.md +3 -2
- package/.agents/workflows/audit-performance.md +3 -3
- package/.agents/workflows/audit-privacy.md +3 -3
- package/.agents/workflows/audit-quality.md +3 -3
- package/.agents/workflows/audit-security.md +3 -3
- package/.agents/workflows/audit-seo.md +3 -3
- package/.agents/workflows/audit-sre.md +3 -3
- package/.agents/workflows/audit-to-stories.md +20 -20
- package/.agents/workflows/audit-ux-ui.md +3 -3
- package/.agents/workflows/deliver.md +122 -131
- package/.agents/workflows/git-cleanup.md +3 -4
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +52 -40
- package/.agents/workflows/helpers/code-review.md +70 -193
- package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +12 -14
- package/.agents/workflows/helpers/{single-story-deliver.md → deliver-story.md} +113 -139
- package/.agents/workflows/helpers/diagnose.md +10 -10
- package/.agents/workflows/helpers/mandrel-sync-config.md +1 -1
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/helpers/signals.md +16 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +48 -64
- package/.agents/workflows/mandrel-update.md +3 -2
- package/.agents/workflows/plan.md +112 -145
- package/.agents/workflows/qa-assist.md +24 -30
- package/.agents/workflows/qa-explore.md +29 -38
- package/.agents/workflows/qa-run.md +2 -2
- package/README.md +9 -8
- package/docs/CHANGELOG.md +46 -0
- package/lib/cli/registry.js +95 -0
- package/lib/migrations/index.js +6 -5
- package/package.json +5 -3
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer-mobile.md +0 -120
- package/.agents/personas/engineer-web.md +0 -111
- package/.agents/personas/engineer.md +0 -119
- package/.agents/personas/product.md +0 -94
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/refactorer.md +0 -113
- package/.agents/personas/security-engineer.md +0 -112
- package/.agents/personas/sre.md +0 -86
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/personas/ux-designer.md +0 -95
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- package/.agents/scripts/dispatcher.js +0 -295
- package/.agents/scripts/epic-audit-prepare.js +0 -497
- package/.agents/scripts/epic-audit-recheck.js +0 -274
- package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
- package/.agents/scripts/epic-deliver-preflight.js +0 -462
- package/.agents/scripts/epic-deliver-prepare.js +0 -590
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-decompose.js +0 -54
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-plan-spec.js +0 -64
- package/.agents/scripts/epic-reconcile.js +0 -625
- package/.agents/scripts/lib/baseline-snapshot.js +0 -979
- package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
- package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
- package/.agents/scripts/lib/config/lifecycle.js +0 -40
- package/.agents/scripts/lib/config/preflight.js +0 -58
- package/.agents/scripts/lib/config/retro.js +0 -77
- package/.agents/scripts/lib/epic-merge-lock.js +0 -322
- package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
- package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -660
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -388
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +0 -276
- package/.agents/scripts/lib/orchestration/epic-runner/wave-scheduler.js +0 -66
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +0 -789
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +0 -676
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-discriminator.js +0 -389
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-format.js +0 -230
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +0 -361
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
- package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -384
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -278
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +0 -174
- package/.agents/scripts/lib/orchestration/manifest-builder.js +0 -222
- package/.agents/scripts/lib/orchestration/plan-persist/amend.js +0 -359
- package/.agents/scripts/lib/orchestration/plan-persist/delivery-mode.js +0 -127
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +0 -152
- package/.agents/scripts/lib/orchestration/retro/phases/checks.js +0 -94
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +0 -571
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +0 -450
- package/.agents/scripts/lib/orchestration/retro/phases/post-and-mirror.js +0 -191
- package/.agents/scripts/lib/orchestration/retro-heuristics.js +0 -57
- package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
- package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +0 -109
- package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
- package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
- package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
- package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
- package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
- package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
- package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
- package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -301
- package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
- package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
- package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
- package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
- package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
- package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
- package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
- package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
- package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
- package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
- package/.agents/scripts/lib/push-epic-retry.js +0 -209
- package/.agents/scripts/lib/spec/index.js +0 -36
- package/.agents/scripts/lib/spec/loader.js +0 -425
- package/.agents/scripts/lib/spec/state.js +0 -208
- package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
- package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
- package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
- package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
- package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
- package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
- package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
- package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
- package/.agents/scripts/lib/wave-runner/tick.js +0 -754
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
- package/.agents/scripts/lifecycle-emit.js +0 -510
- package/.agents/scripts/plan-critics.js +0 -199
- package/.agents/scripts/retro-run.js +0 -218
- package/.agents/scripts/standalone-feedback-rollup.js +0 -188
- package/.agents/scripts/story-close.js +0 -294
- package/.agents/scripts/story-init.js +0 -599
- package/.agents/scripts/story-phase.js +0 -369
- package/.agents/scripts/wave-tick.js +0 -335
- package/.agents/skills/core/baseline-refresh/SKILL.md +0 -181
- package/.agents/skills/core/ci-cd-and-automation/SKILL.md +0 -274
- package/.agents/skills/core/ci-cd-and-automation/examples.md +0 -211
- package/.agents/skills/core/code-simplification/SKILL.md +0 -389
- package/.agents/skills/core/context-engineering/SKILL.md +0 -309
- package/.agents/skills/core/context-engineering/examples.md +0 -58
- package/.agents/skills/core/deprecation-and-migration/SKILL.md +0 -250
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
- package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
- package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -413
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/skills/core/frontend-ui-engineering/SKILL.md +0 -357
- package/.agents/skills/core/hydrate-context/SKILL.md +0 -123
- package/.agents/skills/core/idea-refinement/examples.md +0 -437
- package/.agents/skills/core/idea-refinement/frameworks.md +0 -135
- package/.agents/skills/core/incremental-implementation/SKILL.md +0 -271
- package/.agents/skills/core/introducing-a-baseline-gate/SKILL.md +0 -213
- package/.agents/skills/core/knowledge-transfer/SKILL.md +0 -180
- package/.agents/skills/core/mutation-survivor-remediation/SKILL.md +0 -117
- package/.agents/skills/core/performance-optimization/SKILL.md +0 -314
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +0 -277
- package/.agents/skills/core/property-based-testing/SKILL.md +0 -148
- package/.agents/skills/core/refactoring-discipline/SKILL.md +0 -111
- package/.agents/skills/core/shipping-and-launch/SKILL.md +0 -328
- package/.agents/skills/core/spec-driven-development/SKILL.md +0 -252
- package/.agents/skills/core/test-driven-development/SKILL.md +0 -475
- package/.agents/skills/core/using-agent-skills/SKILL.md +0 -232
- package/.agents/skills/stack/architecture/monorepo-path-strategist/SKILL.md +0 -31
- package/.agents/skills/stack/architecture/structured-output-zod/SKILL.md +0 -51
- package/.agents/skills/stack/architecture/subagent-orchestration/SKILL.md +0 -76
- package/.agents/skills/stack/backend/cloudflare-hono-architect/SKILL.md +0 -31
- package/.agents/skills/stack/backend/cloudflare-hono-architect/examples/route-template.ts +0 -33
- package/.agents/skills/stack/backend/cloudflare-queue-manager/SKILL.md +0 -31
- package/.agents/skills/stack/backend/cloudflare-workers/SKILL.md +0 -51
- package/.agents/skills/stack/backend/highlevel-crm/SKILL.md +0 -54
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/SKILL.md +0 -29
- package/.agents/skills/stack/backend/sqlite-drizzle-expert/examples/schema-template.ts +0 -30
- package/.agents/skills/stack/backend/stripe-integration/SKILL.md +0 -57
- package/.agents/skills/stack/backend/stripe-integration/scripts/listen-stripe.sh +0 -9
- package/.agents/skills/stack/backend/turso-sqlite/SKILL.md +0 -48
- package/.agents/skills/stack/frontend/astro/SKILL.md +0 -62
- package/.agents/skills/stack/frontend/astro-react-island-strategist/SKILL.md +0 -30
- package/.agents/skills/stack/frontend/expo-react-native-developer/SKILL.md +0 -29
- package/.agents/skills/stack/frontend/google-analytics-v4/SKILL.md +0 -50
- package/.agents/skills/stack/frontend/tailwind-v4/SKILL.md +0 -58
- package/.agents/skills/stack/frontend/ui-accessibility-engineer/SKILL.md +0 -34
- package/.agents/skills/stack/qa/audit-accessibility/SKILL.md +0 -51
- package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +0 -199
- package/.agents/skills/stack/security/backend-security-patterns/SKILL.md +0 -68
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -534
- package/.agents/workflows/helpers/deliver-epic.md +0 -955
- package/.agents/workflows/helpers/deliver-stories.md +0 -440
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -427
- package/.agents/workflows/helpers/epic-testing.md +0 -125
- package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
- package/.agents/workflows/helpers/plan-epic.md +0 -351
- package/.agents/workflows/helpers/plan-story.md +0 -251
- package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
- /package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +0 -0
|
@@ -4,59 +4,30 @@
|
|
|
4
4
|
*
|
|
5
5
|
* All label names are sourced from `label-constants.js` so renames only need
|
|
6
6
|
* to happen in one place. Colors come from `LABEL_COLORS` in the same module.
|
|
7
|
+
*
|
|
8
|
+
* v2 deleted the behavioral persona concept (`.agents/personas/` +
|
|
9
|
+
* `persona::*` labels). Role framing for spawns lives in `.agents/agents/`
|
|
10
|
+
* via `delivery.routing.roleScopedAgents`; QA auth identities live in
|
|
11
|
+
* `qa.personas` — neither is a GitHub label axis.
|
|
7
12
|
*/
|
|
8
13
|
|
|
9
|
-
import fs from 'node:fs';
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
import { fileURLToPath } from 'node:url';
|
|
12
14
|
import {
|
|
13
15
|
ACCEPTANCE_LABELS,
|
|
14
16
|
AGENT_LABELS,
|
|
15
17
|
DELIVERY_LABELS,
|
|
16
18
|
LABEL_COLORS,
|
|
17
|
-
PERSONA_LABEL_PREFIX,
|
|
18
19
|
PLANNING_LABELS,
|
|
19
20
|
STATUS_LABELS,
|
|
20
21
|
TYPE_LABELS,
|
|
21
22
|
} from './label-constants.js';
|
|
22
23
|
|
|
23
|
-
const PERSONAS_DIR = path.resolve(
|
|
24
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
25
|
-
'..',
|
|
26
|
-
'..',
|
|
27
|
-
'personas',
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Discover persona labels from `.agents/personas/*.md`. The filename
|
|
32
|
-
* (without extension) is the label suffix — this is the same value the
|
|
33
|
-
* context hydrator uses to resolve `persona::<name>` to its markdown file.
|
|
34
|
-
*/
|
|
35
|
-
function buildPersonaLabels() {
|
|
36
|
-
return fs
|
|
37
|
-
.readdirSync(PERSONAS_DIR)
|
|
38
|
-
.filter((f) => f.endsWith('.md'))
|
|
39
|
-
.map((f) => f.slice(0, -3))
|
|
40
|
-
.sort()
|
|
41
|
-
.map((name) => ({
|
|
42
|
-
name: `${PERSONA_LABEL_PREFIX}${name}`,
|
|
43
|
-
color: LABEL_COLORS.PERSONA,
|
|
44
|
-
description: `${name} persona`,
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
24
|
/** @type {Array<{ name: string, color: string, description: string }>} */
|
|
49
25
|
export const LABEL_TAXONOMY = [
|
|
50
26
|
// Type
|
|
51
|
-
{
|
|
52
|
-
name: TYPE_LABELS.EPIC,
|
|
53
|
-
color: LABEL_COLORS.TYPE,
|
|
54
|
-
description: 'Epic-level work item',
|
|
55
|
-
},
|
|
56
27
|
{
|
|
57
28
|
name: TYPE_LABELS.STORY,
|
|
58
29
|
color: LABEL_COLORS.TYPE,
|
|
59
|
-
description: '
|
|
30
|
+
description: 'Story work item',
|
|
60
31
|
},
|
|
61
32
|
|
|
62
33
|
// Agent State
|
|
@@ -95,9 +66,6 @@ export const LABEL_TAXONOMY = [
|
|
|
95
66
|
description: 'Blocked by a dependency',
|
|
96
67
|
},
|
|
97
68
|
|
|
98
|
-
// Persona — dynamically derived from .agents/personas/*.md
|
|
99
|
-
...buildPersonaLabels(),
|
|
100
|
-
|
|
101
69
|
// Acceptance axis — explicit opt-out signal for Epics that
|
|
102
70
|
// intentionally have no acceptance-table coverage (waives the Epic
|
|
103
71
|
// body's ## Acceptance Table section — Story #4324).
|
|
@@ -108,15 +76,13 @@ export const LABEL_TAXONOMY = [
|
|
|
108
76
|
},
|
|
109
77
|
|
|
110
78
|
// Planning axis — operator-applied waivers for the planning → delivery
|
|
111
|
-
// handoff gates.
|
|
112
|
-
//
|
|
113
|
-
// (`runPlanHealthcheck`) returned `ok: false` for a reason the
|
|
114
|
-
// operator has triaged and accepted.
|
|
79
|
+
// handoff gates. `healthcheck-waived` remains for tickets that still
|
|
80
|
+
// carry the historical label; the healthcheck CLI was retired.
|
|
115
81
|
{
|
|
116
82
|
name: PLANNING_LABELS.HEALTHCHECK_WAIVED,
|
|
117
83
|
color: LABEL_COLORS.PLANNING,
|
|
118
84
|
description:
|
|
119
|
-
'
|
|
85
|
+
'Historical operator override for the retired post-plan healthcheck',
|
|
120
86
|
},
|
|
121
87
|
|
|
122
88
|
// Delivery routing — applied by `plan-persist.js` when the risk verdict
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* This module is the single writer/clearer of those vars:
|
|
8
8
|
*
|
|
9
9
|
* - `setActiveStoryEnv({ epicId, storyId, workCwd })` is called from
|
|
10
|
-
* `story-init.js` after the worktree is materialised. It sets the
|
|
10
|
+
* `single-story-init.js` after the worktree is materialised. It sets the
|
|
11
11
|
* vars on the current `process.env` (so any child commands the
|
|
12
12
|
* orchestrator spawns inherit them) and exports them to a sibling
|
|
13
13
|
* `.env.local` inside the worktree. The harness re-spawns the
|
|
@@ -37,9 +37,21 @@ const ENV_LOCAL_BASENAME = '.env.local';
|
|
|
37
37
|
/**
|
|
38
38
|
* Names of the env vars we own. Keeping the list central makes the
|
|
39
39
|
* round-trip (set on init, clear on close) trivially auditable —
|
|
40
|
-
* grep `CC_EPIC_ID` / `CC_STORY_ID` to find every read site.
|
|
40
|
+
* grep `CC_EPIC_ID` / `CC_STORY_ID` / `CC_SLICE_ID` to find every read site.
|
|
41
|
+
*
|
|
42
|
+
* Epic #4476 (M5) added `CC_SLICE_ID` (the single-delivery analogue of
|
|
43
|
+
* `CC_STORY_ID` — set on the ONE long guarded session so the PostToolUse
|
|
44
|
+
* hook can emit `slice.heartbeat` off the token stream) and `CC_OPERATOR`
|
|
45
|
+
* (the resolved lease-owner handle, stamped onto hook-emitted heartbeats so
|
|
46
|
+
* `latestHeartbeatForOwner` keeps resolving a live claim). The clear path
|
|
47
|
+
* wipes all four so no stale context leaks past close.
|
|
41
48
|
*/
|
|
42
|
-
export const ACTIVE_STORY_ENV_KEYS = [
|
|
49
|
+
export const ACTIVE_STORY_ENV_KEYS = [
|
|
50
|
+
'CC_EPIC_ID',
|
|
51
|
+
'CC_STORY_ID',
|
|
52
|
+
'CC_SLICE_ID',
|
|
53
|
+
'CC_OPERATOR',
|
|
54
|
+
];
|
|
43
55
|
|
|
44
56
|
/**
|
|
45
57
|
* Render the `.env.local` body. One `KEY=value` line per var, LF
|
|
@@ -180,3 +192,100 @@ export function clearActiveStoryEnv({
|
|
|
180
192
|
}
|
|
181
193
|
return { envCleared: true, fileRemoved, filePath };
|
|
182
194
|
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Render the single-delivery `.env.local` body. The single-delivery executor
|
|
198
|
+
* (`deliver-epic-single.md`) walks the Delivery Slicing table inside ONE long
|
|
199
|
+
* session; there is no Story fan-out and thus no `CC_STORY_ID`. Instead the
|
|
200
|
+
* "current slice" is exported as `CC_SLICE_ID` so the PostToolUse hook emits
|
|
201
|
+
* `slice.heartbeat` — the watchdog's forward-progress signal for the single
|
|
202
|
+
* session — as a free byproduct of tool activity.
|
|
203
|
+
*
|
|
204
|
+
* `CC_STORY_ID` MUST be absent (its presence would make the hook emit
|
|
205
|
+
* `story.heartbeat` and the trace path key off a non-existent Story). The
|
|
206
|
+
* optional `operator` handle is emitted as `CC_OPERATOR` only when supplied.
|
|
207
|
+
*
|
|
208
|
+
* Exported for testing.
|
|
209
|
+
*
|
|
210
|
+
* @param {{ epicId: number, sliceId: string, operator?: string }} input
|
|
211
|
+
* @returns {string}
|
|
212
|
+
*/
|
|
213
|
+
export function renderActiveSliceEnvFile({ epicId, sliceId, operator }) {
|
|
214
|
+
const lines = [
|
|
215
|
+
'# Auto-managed by .agents/scripts/lib/observability/active-story-env.js',
|
|
216
|
+
'# Re-generated on every slice-start; deleted on epic-close.',
|
|
217
|
+
`CC_EPIC_ID=${epicId}`,
|
|
218
|
+
`CC_SLICE_ID=${sliceId}`,
|
|
219
|
+
];
|
|
220
|
+
if (typeof operator === 'string' && operator.length > 0) {
|
|
221
|
+
lines.push(`CC_OPERATOR=${operator}`);
|
|
222
|
+
}
|
|
223
|
+
lines.push('');
|
|
224
|
+
return lines.join('\n');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Set `CC_EPIC_ID` / `CC_SLICE_ID` (+ optional `CC_OPERATOR`) on the current
|
|
229
|
+
* process and, when `workCwd` is provided, export them to
|
|
230
|
+
* `<workCwd>/.env.local` for the harness to reload on the next tool call. The
|
|
231
|
+
* single-delivery analogue of {@link setActiveStoryEnv}; called at each
|
|
232
|
+
* `slice.start` boundary so the hook's throttled `slice.heartbeat` is keyed to
|
|
233
|
+
* the slice currently being implemented.
|
|
234
|
+
*
|
|
235
|
+
* `CC_STORY_ID` is explicitly removed so a prior Story context (if any ever
|
|
236
|
+
* leaked in) cannot make the hook emit the wrong heartbeat shape.
|
|
237
|
+
*
|
|
238
|
+
* @param {{ epicId: number, sliceId: string, operator?: string|null,
|
|
239
|
+
* workCwd?: string, env?: NodeJS.ProcessEnv, fs?: typeof nodeFs,
|
|
240
|
+
* logger?: { warn?: (m: string) => void } }} args
|
|
241
|
+
* @returns {{ envSet: boolean, fileWritten: boolean, filePath: string|null }}
|
|
242
|
+
*/
|
|
243
|
+
export function setActiveSliceEnv({
|
|
244
|
+
epicId,
|
|
245
|
+
sliceId,
|
|
246
|
+
operator,
|
|
247
|
+
workCwd,
|
|
248
|
+
env = process.env,
|
|
249
|
+
fs = nodeFs,
|
|
250
|
+
logger,
|
|
251
|
+
} = {}) {
|
|
252
|
+
if (!Number.isInteger(epicId) || epicId <= 0) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
`[active-story-env] epicId must be a positive integer; got ${epicId}`,
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
if (typeof sliceId !== 'string' || sliceId.length === 0) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`[active-story-env] sliceId must be a non-empty string; got ${sliceId}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
const normOperator =
|
|
263
|
+
typeof operator === 'string' && operator.length > 0 ? operator : undefined;
|
|
264
|
+
|
|
265
|
+
if ('CC_STORY_ID' in env) delete env.CC_STORY_ID;
|
|
266
|
+
env.CC_EPIC_ID = String(epicId);
|
|
267
|
+
env.CC_SLICE_ID = sliceId;
|
|
268
|
+
if (normOperator) env.CC_OPERATOR = normOperator;
|
|
269
|
+
else if ('CC_OPERATOR' in env) delete env.CC_OPERATOR;
|
|
270
|
+
|
|
271
|
+
let fileWritten = false;
|
|
272
|
+
let filePath = null;
|
|
273
|
+
if (typeof workCwd === 'string' && workCwd.length > 0) {
|
|
274
|
+
filePath = nodePath.join(workCwd, ENV_LOCAL_BASENAME);
|
|
275
|
+
try {
|
|
276
|
+
fs.writeFileSync(
|
|
277
|
+
filePath,
|
|
278
|
+
renderActiveSliceEnvFile({ epicId, sliceId, operator: normOperator }),
|
|
279
|
+
{ encoding: 'utf8' },
|
|
280
|
+
);
|
|
281
|
+
fileWritten = true;
|
|
282
|
+
} catch (err) {
|
|
283
|
+
logger?.warn?.(
|
|
284
|
+
`[active-story-env] Failed to write ${filePath}: ${
|
|
285
|
+
err instanceof Error ? err.message : String(err)
|
|
286
|
+
}`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return { envSet: true, fileWritten, filePath };
|
|
291
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hook-heartbeat.js — Epic #4476 (M5): heartbeats OFF the token stream.
|
|
3
|
+
*
|
|
4
|
+
* Before M5, the `story.heartbeat` / `slice.heartbeat` forward-progress
|
|
5
|
+
* signal that idle-watchdog consumers read from the lifecycle ledger was an
|
|
6
|
+
* **LLM obligation**: the delivery workflow instructed the agent to run
|
|
7
|
+
* `story-phase.js` / `slice-phase.js --event heartbeat` at least once per
|
|
8
|
+
* meaningful step, and every such call was a full-priced LLM turn re-reading
|
|
9
|
+
* ~100k of cached context just to append one NDJSON line.
|
|
10
|
+
*
|
|
11
|
+
* This module makes that liveness signal a **free byproduct of the agent
|
|
12
|
+
* doing any work**. It is invoked from the existing PostToolUse trace hook
|
|
13
|
+
* (`tool-trace-hook.js`), so every tool call the agent makes refreshes the
|
|
14
|
+
* heartbeat — no dedicated bookkeeping turn required. The record it appends is
|
|
15
|
+
* byte-for-byte the same `story.heartbeat` / `slice.heartbeat` shape the
|
|
16
|
+
* watchdog already consumes (emitted through the same
|
|
17
|
+
* `emit-story-heartbeat.js` / `emit-slice-lifecycle.js` emitters), so the
|
|
18
|
+
* watchdog needs no change: the signal is preserved, only its emission
|
|
19
|
+
* mechanism moves off the token stream.
|
|
20
|
+
*
|
|
21
|
+
* ## Robustness contract (mirrors `tool-trace-hook.js`)
|
|
22
|
+
* - **No-op outside an active Story with an Epic-scoped ledger.** When a
|
|
23
|
+
* valid `{ CC_EPIC_ID, CC_STORY_ID }` pair is not present in the
|
|
24
|
+
* environment (v2 standalone Stories omit `CC_EPIC_ID`), `emitHeartbeatFromHook`
|
|
25
|
+
* returns without touching the filesystem.
|
|
26
|
+
* - **Best-effort.** Every failure is swallowed. A heartbeat is
|
|
27
|
+
* observability, not state; a hook must never block tool execution.
|
|
28
|
+
* - **Throttled across processes.** Command hooks are spawned as fresh
|
|
29
|
+
* `node` processes per tool call, so in-process state cannot throttle. The
|
|
30
|
+
* throttle is anchored to the **mtime of a sidecar marker file** under the
|
|
31
|
+
* Epic temp dir: at most one heartbeat per `HEARTBEAT_MIN_INTERVAL_MS`
|
|
32
|
+
* lands, regardless of how many tool calls fire in that window. This keeps
|
|
33
|
+
* the ledger from ballooning while still refreshing liveness faster than
|
|
34
|
+
* the watchdog's threshold.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { statSync, utimesSync, writeFileSync } from 'node:fs';
|
|
38
|
+
import path from 'node:path';
|
|
39
|
+
|
|
40
|
+
import { epicTempDir } from '../config/temp-paths.js';
|
|
41
|
+
import { emitStoryHeartbeat } from '../orchestration/lifecycle/emit-story-heartbeat.js';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Default minimum wall-clock gap between two hook-emitted heartbeats for the
|
|
45
|
+
* same target. Comfortably below the watchdog's default 30-minute staleness
|
|
46
|
+
* threshold, so an actively-working agent never trips a false stall, while
|
|
47
|
+
* still bounding ledger growth to ~1 line/minute of activity.
|
|
48
|
+
*/
|
|
49
|
+
const HEARTBEAT_MIN_INTERVAL_MS = 60_000;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the heartbeat target from the active-Story / active-slice env vars.
|
|
53
|
+
*
|
|
54
|
+
* A `story.heartbeat` requires a parent `epicId` (its schema pins
|
|
55
|
+
* `epicId >= 1`), so a **standalone** Story (`CC_STORY_ID` set, `CC_EPIC_ID`
|
|
56
|
+
* absent) yields `null` — there is no Epic-scoped ledger to write to and the
|
|
57
|
+
* standalone path is not watched by the Epic idle watchdog. The trace hook
|
|
58
|
+
* still records that context's traces; only the heartbeat is skipped.
|
|
59
|
+
*
|
|
60
|
+
* Inert `slice.*` lifecycle (and `CC_SLICE_ID` heartbeats) were removed in
|
|
61
|
+
* the v2 ceremony lock-in — `## Slicing` is prose-only.
|
|
62
|
+
*
|
|
63
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
64
|
+
* @returns {{ kind: 'story', epicId: number, storyId: number, operator?: string }
|
|
65
|
+
* | null}
|
|
66
|
+
*/
|
|
67
|
+
export function resolveHeartbeatTarget(env = process.env) {
|
|
68
|
+
const epicRaw = env.CC_EPIC_ID;
|
|
69
|
+
const storyRaw = env.CC_STORY_ID;
|
|
70
|
+
const operator =
|
|
71
|
+
typeof env.CC_OPERATOR === 'string' && env.CC_OPERATOR.length > 0
|
|
72
|
+
? env.CC_OPERATOR
|
|
73
|
+
: undefined;
|
|
74
|
+
|
|
75
|
+
const epicId = epicRaw ? Number.parseInt(epicRaw, 10) : Number.NaN;
|
|
76
|
+
const epicOk = Number.isInteger(epicId) && epicId > 0;
|
|
77
|
+
|
|
78
|
+
if (storyRaw) {
|
|
79
|
+
const storyId = Number.parseInt(storyRaw, 10);
|
|
80
|
+
if (!Number.isInteger(storyId) || storyId <= 0) return null;
|
|
81
|
+
// story.heartbeat has no meaning without a parent Epic ledger.
|
|
82
|
+
if (!epicOk) return null;
|
|
83
|
+
return {
|
|
84
|
+
kind: 'story',
|
|
85
|
+
epicId,
|
|
86
|
+
storyId,
|
|
87
|
+
...(operator ? { operator } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Stable, filesystem-safe marker basename for a target. The marker's mtime is
|
|
96
|
+
* the throttle anchor; its contents are irrelevant.
|
|
97
|
+
*
|
|
98
|
+
* @param {{ kind: string, storyId?: number, sliceId?: string }} target
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
export function heartbeatMarkerName(target) {
|
|
102
|
+
return `.heartbeat-story-${target.storyId}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Throttle predicate. Returns `true` when no marker exists yet, or the marker
|
|
107
|
+
* is older than `intervalMs`. Any stat error (missing file / unreadable) is
|
|
108
|
+
* treated as "emit" — the safe direction for a liveness signal.
|
|
109
|
+
*
|
|
110
|
+
* @param {{ markerPath: string, now: Date, intervalMs: number, statFn?: typeof statSync }} args
|
|
111
|
+
* @returns {boolean}
|
|
112
|
+
*/
|
|
113
|
+
export function shouldEmitHeartbeat({
|
|
114
|
+
markerPath,
|
|
115
|
+
now,
|
|
116
|
+
intervalMs,
|
|
117
|
+
statFn = statSync,
|
|
118
|
+
}) {
|
|
119
|
+
try {
|
|
120
|
+
const st = statFn(markerPath);
|
|
121
|
+
return now.getTime() - st.mtimeMs >= intervalMs;
|
|
122
|
+
} catch {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Update (or create) the throttle marker so its mtime is `now`. Best-effort:
|
|
129
|
+
* a failure here at worst lets the next tool call emit a second heartbeat.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} markerPath
|
|
132
|
+
* @param {Date} now
|
|
133
|
+
*/
|
|
134
|
+
function touchMarker(markerPath, now) {
|
|
135
|
+
try {
|
|
136
|
+
writeFileSync(markerPath, '', { flag: 'a' });
|
|
137
|
+
utimesSync(markerPath, now, now);
|
|
138
|
+
} catch {
|
|
139
|
+
// swallow — throttle degrades to "emit again next call", never fatal.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Emit one throttled heartbeat for the active target, if any. The whole body
|
|
145
|
+
* is best-effort: any failure returns a `{ emitted: false }` envelope rather
|
|
146
|
+
* than throwing, so the calling PostToolUse hook never blocks the tool.
|
|
147
|
+
*
|
|
148
|
+
* @param {object} [opts]
|
|
149
|
+
* @param {NodeJS.ProcessEnv} [opts.env] Defaults to `process.env`.
|
|
150
|
+
* @param {Date} [opts.now] Injected clock (tests).
|
|
151
|
+
* @param {number} [opts.intervalMs] Throttle window.
|
|
152
|
+
* @param {object} [opts.config] Resolved config (tempRoot).
|
|
153
|
+
* @returns {{ emitted: boolean, reason?: string, kind?: string, ledgerPath?: string }}
|
|
154
|
+
*/
|
|
155
|
+
export function emitHeartbeatFromHook({
|
|
156
|
+
env = process.env,
|
|
157
|
+
now = new Date(),
|
|
158
|
+
intervalMs = HEARTBEAT_MIN_INTERVAL_MS,
|
|
159
|
+
config,
|
|
160
|
+
} = {}) {
|
|
161
|
+
try {
|
|
162
|
+
const target = resolveHeartbeatTarget(env);
|
|
163
|
+
if (!target) return { emitted: false, reason: 'no-target' };
|
|
164
|
+
|
|
165
|
+
const markerPath = path.join(
|
|
166
|
+
epicTempDir(target.epicId, config),
|
|
167
|
+
heartbeatMarkerName(target),
|
|
168
|
+
);
|
|
169
|
+
if (!shouldEmitHeartbeat({ markerPath, now, intervalMs })) {
|
|
170
|
+
return { emitted: false, reason: 'throttled', kind: target.kind };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const timestamp = now.toISOString();
|
|
174
|
+
const res = emitStoryHeartbeat({
|
|
175
|
+
epicId: target.epicId,
|
|
176
|
+
storyId: target.storyId,
|
|
177
|
+
phase: 'implementing',
|
|
178
|
+
timestamp,
|
|
179
|
+
...(target.operator ? { operator: target.operator } : {}),
|
|
180
|
+
config: config ?? undefined,
|
|
181
|
+
});
|
|
182
|
+
touchMarker(markerPath, now);
|
|
183
|
+
return { emitted: true, kind: target.kind, ledgerPath: res?.ledgerPath };
|
|
184
|
+
} catch {
|
|
185
|
+
return { emitted: false, reason: 'error' };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -57,7 +57,7 @@ function toScanString(value) {
|
|
|
57
57
|
/**
|
|
58
58
|
* Return true when `haystack` contains any framework prefix as a
|
|
59
59
|
* substring. We use `includes` (not `startsWith`) because a command line
|
|
60
|
-
* like `node .agents/scripts/story-init.js` carries the prefix in the
|
|
60
|
+
* like `node .agents/scripts/single-story-init.js` carries the prefix in the
|
|
61
61
|
* middle, and a failing-path like `repo/.agents/foo.js` may carry an
|
|
62
62
|
* absolute prefix.
|
|
63
63
|
*
|
|
@@ -83,13 +83,13 @@ function containsFrameworkPrefix(haystack) {
|
|
|
83
83
|
* Framework-wins: if either input matches a framework prefix, the result
|
|
84
84
|
* is `"framework"` even when the other input looks consumer-shaped. This
|
|
85
85
|
* matters for mixed cases like a consumer test invoking
|
|
86
|
-
* `node .agents/scripts/story-init.js` — that's framework friction even
|
|
86
|
+
* `node .agents/scripts/single-story-init.js` — that's framework friction even
|
|
87
87
|
* though the failing test path lives under the consumer.
|
|
88
88
|
*
|
|
89
89
|
* @param {unknown} failingPath The path of the file or directory the
|
|
90
90
|
* signal blames (e.g. `"tests/foo.test.js"`).
|
|
91
91
|
* @param {unknown} command The command line the signal blames
|
|
92
|
-
* (e.g. `"node .agents/scripts/story-init.js"`).
|
|
92
|
+
* (e.g. `"node .agents/scripts/single-story-init.js"`).
|
|
93
93
|
* @returns {"framework"|"consumer"}
|
|
94
94
|
*/
|
|
95
95
|
export function classifyPathSource(failingPath, command) {
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
|
|
49
49
|
import { createHash } from 'node:crypto';
|
|
50
50
|
|
|
51
|
+
import { emitHeartbeatFromHook } from './hook-heartbeat.js';
|
|
51
52
|
import { appendTrace } from './signals-writer.js';
|
|
52
53
|
|
|
53
54
|
/**
|
|
@@ -381,15 +382,25 @@ export async function handlePost(event, active) {
|
|
|
381
382
|
*/
|
|
382
383
|
export async function main(event) {
|
|
383
384
|
try {
|
|
384
|
-
const active = resolveActiveStory();
|
|
385
|
-
if (!active) return; // No active Story => zero filesystem calls.
|
|
386
385
|
if (!event || typeof event !== 'object') return;
|
|
386
|
+
const active = resolveActiveStory();
|
|
387
387
|
|
|
388
388
|
const phase = event.hook_event_name;
|
|
389
389
|
if (phase === 'PreToolUse') {
|
|
390
|
-
|
|
390
|
+
// Pre-pairing only matters for the trace-line duration, which only
|
|
391
|
+
// the Story-scoped trace path records; slice-only context (single
|
|
392
|
+
// delivery) needs no Pre.
|
|
393
|
+
if (active) handlePre(event);
|
|
391
394
|
} else if (phase === 'PostToolUse') {
|
|
392
|
-
await handlePost(event, active);
|
|
395
|
+
if (active) await handlePost(event, active);
|
|
396
|
+
// Heartbeat OFF the token stream (Epic #4476 M5). A throttled
|
|
397
|
+
// story.heartbeat / slice.heartbeat, keyed off the same active-Story /
|
|
398
|
+
// active-slice env vars, so the §2e Idle Watchdog's forward-progress
|
|
399
|
+
// signal is a free byproduct of ANY tool call — no dedicated
|
|
400
|
+
// bookkeeping LLM turn. Best-effort and self-guarded; resolves its own
|
|
401
|
+
// target (fires for slice context even when `active` is null because
|
|
402
|
+
// there is no CC_STORY_ID under single delivery).
|
|
403
|
+
emitHeartbeatFromHook();
|
|
393
404
|
}
|
|
394
405
|
// Any other phase is silently ignored — the hook is registered for
|
|
395
406
|
// Pre/Post only; receiving anything else is a configuration error
|
|
@@ -37,9 +37,7 @@ import { STUB_MARKER, scaffoldDocs } from './scaffold-docs.js';
|
|
|
37
37
|
*/
|
|
38
38
|
export const PLAN_HANDOFF_TEXT =
|
|
39
39
|
'\n✅ Mandrel is ready. Start your first project:\n\n' +
|
|
40
|
-
' /plan --
|
|
41
|
-
'Or, if you already have a `type::epic` Issue open:\n\n' +
|
|
42
|
-
' /plan <epicId>\n';
|
|
40
|
+
' /plan --seed "<one-line description of what you want to build>"\n';
|
|
43
41
|
|
|
44
42
|
// ---------------------------------------------------------------------------
|
|
45
43
|
// Internal helpers
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* acceptance-clusters.js — Epic #4475 (M4-B), design §S2a.
|
|
3
|
+
*
|
|
4
|
+
* The **acceptance-dilution guard** for single delivery. In the fan-out
|
|
5
|
+
* shape, each Story ran its own fresh-context acceptance self-eval critic, so
|
|
6
|
+
* a 14-AC Epic decomposed into 4 Stories got ~4 independent critic passes for
|
|
7
|
+
* free. Single delivery collapses the whole Epic into ONE guarded session — if
|
|
8
|
+
* we ran a single critic over all 14 ACs it would degrade to two redraft
|
|
9
|
+
* rounds scoring everything at once (the exact "acceptance dilution" risk the
|
|
10
|
+
* adopted design calls out as blocking).
|
|
11
|
+
*
|
|
12
|
+
* This module restores the distributed coverage: the Epic's
|
|
13
|
+
* `## Acceptance Table` AC ids are split into clusters of at most
|
|
14
|
+
* `clusterCeiling` (config `delivery.acceptanceEval.clusterCeiling`, default 4,
|
|
15
|
+
* hard-clamped to `[1, 8]`), and the executor spawns ONE maker-blind
|
|
16
|
+
* fresh-context `Agent` critic per cluster. The cluster count is therefore
|
|
17
|
+
* `ceil(totalACs / clusterCeiling)` — a deterministic fan-out width the
|
|
18
|
+
* `deliver-epic-single.md` executor and its isolation test both key off.
|
|
19
|
+
*
|
|
20
|
+
* Pure and total — inputs in, clusters out. No I/O, no throws on malformed
|
|
21
|
+
* input (a non-array / empty AC set yields zero clusters — the executor then
|
|
22
|
+
* has nothing to critique, which the front gate already forecloses by refusing
|
|
23
|
+
* `acceptance::n-a`).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { getAcceptanceEval } from '../config/acceptance-eval.js';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @typedef {object} AcceptanceCluster
|
|
30
|
+
* @property {number} clusterIndex Zero-based position in the fan-out.
|
|
31
|
+
* @property {string} clusterId Stable id (`ac-cluster-<n>`, 1-based).
|
|
32
|
+
* @property {string[]} acIds The AC ids this critic scores (≤ ceiling).
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Split an ordered list of AC ids into fresh-context critic clusters of at
|
|
37
|
+
* most `ceiling` ids each. The count is exactly `ceil(acIds.length / ceiling)`
|
|
38
|
+
* — the load-bearing fan-out-width invariant the isolation test pins.
|
|
39
|
+
*
|
|
40
|
+
* The input order is preserved (the executor orders ACs by their associated
|
|
41
|
+
* Delivery-Slicing slice before calling this, so same-slice ACs land adjacent
|
|
42
|
+
* and tend to share a cluster), but ordering is a nicety — the count guarantee
|
|
43
|
+
* is what forecloses dilution.
|
|
44
|
+
*
|
|
45
|
+
* @param {string[]} acIds Ordered AC ids (e.g. `['AC-1', 'AC-2', …]`).
|
|
46
|
+
* @param {number} [ceiling=4] Max ACs per cluster. A non-positive / non-
|
|
47
|
+
* integer ceiling degrades to 1 (one AC per
|
|
48
|
+
* cluster — the maximally-distributed, never-
|
|
49
|
+
* collapsed fan-out).
|
|
50
|
+
* @returns {AcceptanceCluster[]} `ceil(n / ceiling)` clusters; `[]` when there
|
|
51
|
+
* are no AC ids.
|
|
52
|
+
*/
|
|
53
|
+
export function clusterAcceptanceCriteria(acIds, ceiling = 4) {
|
|
54
|
+
const ids = Array.isArray(acIds)
|
|
55
|
+
? acIds.filter((id) => typeof id === 'string' && id.length > 0)
|
|
56
|
+
: [];
|
|
57
|
+
if (ids.length === 0) return [];
|
|
58
|
+
|
|
59
|
+
const size =
|
|
60
|
+
typeof ceiling === 'number' && Number.isInteger(ceiling) && ceiling >= 1
|
|
61
|
+
? ceiling
|
|
62
|
+
: 1;
|
|
63
|
+
|
|
64
|
+
const clusters = [];
|
|
65
|
+
for (let start = 0; start < ids.length; start += size) {
|
|
66
|
+
const clusterIndex = clusters.length;
|
|
67
|
+
clusters.push({
|
|
68
|
+
clusterIndex,
|
|
69
|
+
clusterId: `ac-cluster-${clusterIndex + 1}`,
|
|
70
|
+
acIds: ids.slice(start, start + size),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return clusters;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The expected fan-out width for `total` ACs at `ceiling` — the number of
|
|
78
|
+
* independent maker-blind critic passes the single-delivery executor spawns.
|
|
79
|
+
* Exported so the executor (and its isolation test) can assert the number of
|
|
80
|
+
* `Agent` critic spawns equals this without re-deriving the ceil math.
|
|
81
|
+
*
|
|
82
|
+
* @param {number} total Total AC count.
|
|
83
|
+
* @param {number} ceiling Max ACs per cluster (≥ 1; degrades to 1 otherwise).
|
|
84
|
+
* @returns {number} `ceil(total / ceiling)`; `0` when `total <= 0`.
|
|
85
|
+
*/
|
|
86
|
+
export function expectedClusterCount(total, ceiling) {
|
|
87
|
+
const n = Number.isInteger(total) && total > 0 ? total : 0;
|
|
88
|
+
if (n === 0) return 0;
|
|
89
|
+
const size =
|
|
90
|
+
typeof ceiling === 'number' && Number.isInteger(ceiling) && ceiling >= 1
|
|
91
|
+
? ceiling
|
|
92
|
+
: 1;
|
|
93
|
+
return Math.ceil(n / size);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Convenience wrapper: resolve the effective `clusterCeiling` from config
|
|
98
|
+
* (applying the framework default + the undisableable `[1, 8]` clamp) and
|
|
99
|
+
* cluster `acIds` with it. The single home the executor's `slice-phase`
|
|
100
|
+
* substrate and any future CLI use so the clamp is applied once.
|
|
101
|
+
*
|
|
102
|
+
* @param {string[]} acIds
|
|
103
|
+
* @param {object | null | undefined} config Resolved `.agentrc.json`.
|
|
104
|
+
* @returns {{ clusters: AcceptanceCluster[], clusterCeiling: number, totalAcs: number }}
|
|
105
|
+
*/
|
|
106
|
+
export function clusterAcceptanceForConfig(acIds, config) {
|
|
107
|
+
const { clusterCeiling } = getAcceptanceEval(config);
|
|
108
|
+
const clusters = clusterAcceptanceCriteria(acIds, clusterCeiling);
|
|
109
|
+
const ids = Array.isArray(acIds) ? acIds.filter(Boolean) : [];
|
|
110
|
+
return { clusters, clusterCeiling, totalAcs: ids.length };
|
|
111
|
+
}
|
|
@@ -41,7 +41,10 @@
|
|
|
41
41
|
|
|
42
42
|
import { readFileSync } from 'node:fs';
|
|
43
43
|
|
|
44
|
-
import { signalsFile } from '../config/temp-paths.js';
|
|
44
|
+
import { epicArtifactPath, signalsFile } from '../config/temp-paths.js';
|
|
45
|
+
|
|
46
|
+
/** Epic-level signals stream basename (mirrors signals-writer). */
|
|
47
|
+
const EPIC_SIGNALS_BASENAME = 'signals.ndjson';
|
|
45
48
|
|
|
46
49
|
/**
|
|
47
50
|
* Verdicts that clear a criterion. Anything else (`partial`, `unmet`, or
|
|
@@ -168,11 +171,18 @@ export function buildAcceptanceEvalSignal({
|
|
|
168
171
|
epicId,
|
|
169
172
|
outcome,
|
|
170
173
|
phase = 'implement',
|
|
174
|
+
clusterId = null,
|
|
171
175
|
}) {
|
|
172
176
|
return {
|
|
173
177
|
kind: 'acceptance-eval',
|
|
174
178
|
epicId: epicId ?? null,
|
|
175
|
-
storyId,
|
|
179
|
+
storyId: storyId ?? null,
|
|
180
|
+
// Epic #4475 (M4-B): single-delivery acceptance critics score an AC
|
|
181
|
+
// *cluster*, not a Story. `clusterId` scopes the per-cluster round count
|
|
182
|
+
// on the epic-level signals stream; omitted (null) for the per-Story path.
|
|
183
|
+
...(typeof clusterId === 'string' && clusterId.length > 0
|
|
184
|
+
? { clusterId }
|
|
185
|
+
: {}),
|
|
176
186
|
phase,
|
|
177
187
|
emitter: { tool: 'acceptance-eval.js' },
|
|
178
188
|
details: {
|
|
@@ -216,13 +226,27 @@ export function buildAcceptanceEvalSignal({
|
|
|
216
226
|
export function deriveAcceptanceEvalRound({
|
|
217
227
|
epicId,
|
|
218
228
|
storyId,
|
|
229
|
+
clusterId = null,
|
|
219
230
|
config,
|
|
220
231
|
readFile = (p) => readFileSync(p, 'utf8'),
|
|
221
232
|
signalsPathResolver = signalsFile,
|
|
233
|
+
epicSignalsPathResolver = (eid, cfg) =>
|
|
234
|
+
epicArtifactPath(eid, EPIC_SIGNALS_BASENAME, cfg),
|
|
222
235
|
}) {
|
|
236
|
+
// Epic #4475 (M4-B): single-delivery critics score AC clusters, not
|
|
237
|
+
// Stories. When `clusterId` is supplied the round is counted per cluster
|
|
238
|
+
// off the epic-level signals stream; otherwise the per-Story path
|
|
239
|
+
// (unchanged) counts by `storyId` off the Story's stream.
|
|
240
|
+
const clusterMode =
|
|
241
|
+
typeof clusterId === 'string' &&
|
|
242
|
+
clusterId.length > 0 &&
|
|
243
|
+
Number.isInteger(epicId);
|
|
244
|
+
|
|
223
245
|
let text;
|
|
224
246
|
try {
|
|
225
|
-
text =
|
|
247
|
+
text = clusterMode
|
|
248
|
+
? readFile(epicSignalsPathResolver(epicId, config))
|
|
249
|
+
: readFile(signalsPathResolver(epicId ?? null, storyId, config));
|
|
226
250
|
} catch (_err) {
|
|
227
251
|
// No ledger yet → no prior rounds.
|
|
228
252
|
return 1;
|
|
@@ -240,7 +264,11 @@ export function deriveAcceptanceEvalRound({
|
|
|
240
264
|
}
|
|
241
265
|
if (!record || typeof record !== 'object') continue;
|
|
242
266
|
if (record.kind !== 'acceptance-eval') continue;
|
|
243
|
-
if (
|
|
267
|
+
if (clusterMode) {
|
|
268
|
+
if (record.clusterId !== clusterId) continue;
|
|
269
|
+
} else if (record.storyId !== storyId) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
244
272
|
priorRounds += 1;
|
|
245
273
|
}
|
|
246
274
|
return priorRounds + 1;
|