mandrel 1.94.0 → 2.1.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 +116 -99
- package/.agents/agents/acceptance-critic.md +9 -7
- package/.agents/agents/story-worker.md +45 -51
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +498 -1287
- package/.agents/docs/agentrc-reference.json +185 -80
- package/.agents/docs/configuration.md +132 -190
- package/.agents/docs/execution-reference.md +51 -25
- package/.agents/docs/quality-gates.md +25 -33
- package/.agents/docs/workflows.md +8 -8
- package/.agents/instructions.md +113 -139
- package/.agents/rules/ci-remediation.md +11 -15
- package/.agents/rules/git-conventions-reference.md +48 -58
- package/.agents/rules/git-conventions.md +16 -22
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +83 -254
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/retro.end.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +22 -66
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/audit-to-stories.js +7 -7
- package/.agents/scripts/boot-sweep.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +7 -10
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/cleanup-repo-test-temp.js +6 -1
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- 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 +328 -28
- 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 -15
- package/.agents/scripts/lib/baselines/reader.js +1 -6
- package/.agents/scripts/lib/bdd-runner-detect.js +5 -9
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +32 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +6 -3
- package/.agents/scripts/lib/checks/index.js +3 -2
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +26 -24
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli/standard-args.js +13 -22
- package/.agents/scripts/lib/cli-args.js +39 -9
- package/.agents/scripts/lib/close-validation/gates.js +15 -15
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/ci.js +6 -31
- package/.agents/scripts/lib/config/delivery-routing.js +52 -35
- package/.agents/scripts/lib/config/explain.js +61 -48
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +29 -80
- package/.agents/scripts/lib/config/paths.js +0 -2
- package/.agents/scripts/lib/config/quality.js +12 -15
- package/.agents/scripts/lib/config/runners.js +20 -66
- package/.agents/scripts/lib/config/temp-paths.js +30 -63
- package/.agents/scripts/lib/config/worktree-isolation.js +0 -5
- package/.agents/scripts/lib/config-resolver.js +2 -7
- package/.agents/scripts/lib/config-settings-schema-delivery.js +55 -161
- package/.agents/scripts/lib/config-settings-schema-quality.js +17 -16
- package/.agents/scripts/lib/config-settings-schema.js +100 -60
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/dependency-parser.js +3 -2
- package/.agents/scripts/lib/doc-tiers.js +2 -2
- package/.agents/scripts/lib/duplicate-search.js +242 -41
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -190
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-cleanup.js +1 -10
- package/.agents/scripts/lib/git-branch-lifecycle.js +17 -180
- package/.agents/scripts/lib/git-utils.js +32 -20
- package/.agents/scripts/lib/github/framework-repo.js +6 -0
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +10 -38
- package/.agents/scripts/lib/label-taxonomy.js +10 -55
- package/.agents/scripts/lib/observability/active-story-env.js +44 -165
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +5 -5
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/onboard/init-tail.js +1 -3
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +105 -44
- package/.agents/scripts/lib/orchestration/code-review.js +78 -436
- package/.agents/scripts/lib/orchestration/column-sync.js +1 -1
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +4 -4
- package/.agents/scripts/lib/orchestration/context-envelope.js +2 -5
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- 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/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +17 -19
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +6 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +21 -43
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +4 -4
- package/.agents/scripts/lib/orchestration/merge-block-class.js +77 -21
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/phase-runner.js +3 -2
- package/.agents/scripts/lib/orchestration/plan-context.js +354 -282
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +4 -8
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-navigation.js +92 -0
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +71 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +245 -0
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +432 -858
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +778 -0
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +47 -115
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- 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 +52 -51
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +27 -0
- package/.agents/scripts/lib/orchestration/{epic-plan-spec/phases → planning}/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +18 -28
- package/.agents/scripts/lib/orchestration/remote-verifier.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/resolves-token.js +1 -1
- package/.agents/scripts/lib/orchestration/retro-proposals.js +140 -79
- package/.agents/scripts/lib/orchestration/review-depth.js +126 -47
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +2 -2
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +21 -56
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +784 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +25 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +86 -41
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +77 -32
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +44 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +275 -46
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- 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/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +28 -42
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +298 -0
- package/.agents/scripts/lib/orchestration/story-init-remote.js +51 -0
- package/.agents/scripts/lib/orchestration/story-plan-state.js +33 -0
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +1 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +60 -25
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +130 -75
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +192 -377
- package/.agents/scripts/lib/orchestration/ticket-validator.js +123 -25
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -72
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +45 -55
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +66 -6
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -281
- package/.agents/scripts/lib/preflight-runner.js +4 -4
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -5
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/single-story-sweep/protection-ctx.js +1 -1
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +17 -19
- package/.agents/scripts/lib/story-body/story-body.js +130 -75
- package/.agents/scripts/lib/story-plan.js +2 -4
- package/.agents/scripts/lib/templates/decomposer-prompts.js +51 -46
- package/.agents/scripts/lib/templates/spec-author-prompts.js +47 -45
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/{epic-body-sections.js → ticket-body-sections.js} +25 -39
- package/.agents/scripts/lib/validation-evidence.js +32 -60
- package/.agents/scripts/lib/wave-runner/ready-set.js +38 -12
- package/.agents/scripts/lib/workspace-provisioner.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +72 -25
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- 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 +102 -80
- package/.agents/scripts/plan-persist.js +230 -279
- package/.agents/scripts/plan-run-epilogue.js +142 -0
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +13 -39
- package/.agents/scripts/providers/github/mappers.js +5 -12
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -156
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +25 -21
- package/.agents/scripts/single-story-close.js +178 -26
- package/.agents/scripts/single-story-confirm-merge.js +313 -24
- package/.agents/scripts/single-story-init.js +35 -30
- package/.agents/scripts/stories-wave-tick.js +85 -10
- package/.agents/scripts/story-plan.js +28 -49
- package/.agents/scripts/update-ticket-state.js +14 -65
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +9 -9
- package/.agents/skills/core/scope-triage/SKILL.md +31 -172
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +3 -53
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/starter-agentrc.json +0 -5
- package/.agents/templates/agent-protocol.md +9 -10
- package/.agents/workflows/audit-architecture.md +6 -7
- package/.agents/workflows/audit-clean-code.md +7 -7
- package/.agents/workflows/audit-dependencies.md +3 -3
- package/.agents/workflows/audit-devops.md +3 -3
- package/.agents/workflows/audit-documentation.md +9 -10
- package/.agents/workflows/audit-lighthouse.md +11 -3
- package/.agents/workflows/audit-navigability.md +13 -2
- package/.agents/workflows/audit-performance.md +5 -6
- package/.agents/workflows/audit-privacy.md +3 -3
- package/.agents/workflows/audit-quality.md +11 -12
- package/.agents/workflows/audit-security.md +4 -5
- package/.agents/workflows/audit-seo.md +13 -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 +10 -3
- package/.agents/workflows/deliver.md +177 -176
- package/.agents/workflows/git-cleanup.md +5 -6
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +35 -40
- package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
- package/.agents/workflows/helpers/code-review.md +75 -196
- package/.agents/workflows/helpers/{single-story-deliver-reference.md → deliver-story-reference.md} +83 -44
- package/.agents/workflows/helpers/deliver-story.md +606 -0
- package/.agents/workflows/helpers/diagnose.md +10 -10
- package/.agents/workflows/helpers/parallel-tooling.md +3 -3
- package/.agents/workflows/helpers/signals.md +16 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +66 -86
- package/.agents/workflows/mandrel-update.md +2 -1
- package/.agents/workflows/plan.md +277 -145
- package/.agents/workflows/qa-assist.md +27 -33
- package/.agents/workflows/qa-explore.md +29 -38
- package/.agents/workflows/qa-run.md +2 -6
- package/README.md +9 -8
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +70 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +12 -8
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/package.json +2 -2
- package/.agents/agents/retro.md +0 -42
- package/.agents/personas/architect.md +0 -113
- package/.agents/personas/devops-engineer.md +0 -38
- package/.agents/personas/engineer.md +0 -33
- package/.agents/personas/project-manager.md +0 -114
- package/.agents/personas/qa-engineer.md +0 -95
- package/.agents/personas/security-engineer.md +0 -111
- package/.agents/personas/technical-writer.md +0 -101
- package/.agents/schemas/dispatch-manifest.json +0 -232
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- package/.agents/schemas/epic-spec.schema.json +0 -153
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/slice.end.schema.json +0 -21
- package/.agents/schemas/lifecycle/slice.heartbeat.schema.json +0 -20
- package/.agents/schemas/lifecycle/slice.start.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -66
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/acceptance-spec-reconciler.js +0 -642
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/bookkeeping-reconcile.js +0 -117
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- package/.agents/scripts/dispatcher.js +0 -295
- package/.agents/scripts/epic-audit-prepare.js +0 -497
- package/.agents/scripts/epic-audit-recheck.js +0 -274
- package/.agents/scripts/epic-deliver-note-intervention.js +0 -192
- package/.agents/scripts/epic-deliver-preflight.js +0 -462
- package/.agents/scripts/epic-deliver-prepare.js +0 -852
- package/.agents/scripts/epic-execute-record-wave.js +0 -449
- package/.agents/scripts/epic-plan-clarity.js +0 -211
- package/.agents/scripts/epic-plan-healthcheck.js +0 -581
- package/.agents/scripts/epic-reconcile.js +0 -625
- package/.agents/scripts/lib/baseline-snapshot.js +0 -979
- package/.agents/scripts/lib/checks/epic-merge-lock-stale.js +0 -54
- package/.agents/scripts/lib/checks/stale-origin-epic.js +0 -49
- package/.agents/scripts/lib/config/lifecycle.js +0 -40
- package/.agents/scripts/lib/config/preflight.js +0 -58
- package/.agents/scripts/lib/config/retro.js +0 -77
- package/.agents/scripts/lib/epic-merge-lock.js +0 -322
- package/.agents/scripts/lib/epic-plan-clarity.js +0 -181
- package/.agents/scripts/lib/epic-plan-ideation.js +0 -261
- package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -219
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -270
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +0 -539
- package/.agents/scripts/lib/orchestration/deliver-route.js +0 -173
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +0 -134
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +0 -183
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +0 -801
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +0 -310
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/context.js +0 -163
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +0 -140
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +0 -64
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +0 -72
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist-helpers.js +0 -156
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/persist.js +0 -345
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +0 -41
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/reconcile-spawn.js +0 -86
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +0 -391
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +0 -94
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +0 -236
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/risk-verdict.js +0 -105
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +0 -307
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +0 -621
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +0 -186
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +0 -50
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +0 -129
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +0 -103
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +0 -267
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +0 -210
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +0 -238
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/_bullet-format.js +0 -32
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +0 -203
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +0 -227
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +0 -117
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/stalled-worktree.js +0 -37
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +0 -127
- package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +0 -400
- 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/error-journal.js +0 -139
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +0 -306
- package/.agents/scripts/lib/orchestration/finalize/post-handoff-comment.js +0 -489
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +0 -88
- package/.agents/scripts/lib/orchestration/lifecycle/emit-slice-lifecycle.js +0 -270
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +0 -147
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/acceptance-reconciler.js +0 -465
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +0 -501
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +0 -984
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +0 -264
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/checkpoint-pointer-writer.js +0 -284
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/cleaner.js +0 -355
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +0 -673
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +0 -378
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/intervention-recorder.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/label-transitioner.js +0 -144
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
- 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/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -33
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -396
- package/.agents/scripts/lib/orchestration/post-merge-pipeline.js +0 -205
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- 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-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/retro-runner.js +0 -197
- package/.agents/scripts/lib/orchestration/spec-renderer.js +0 -447
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +0 -747
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/gate-failure.js +0 -211
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +0 -158
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +0 -446
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +0 -297
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/scope-discovery.js +0 -48
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution-wiring.js +0 -67
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution.js +0 -161
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +0 -117
- package/.agents/scripts/lib/orchestration/story-close/cd-out-guard.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/cleanup-reconciler.js +0 -147
- package/.agents/scripts/lib/orchestration/story-close/close-inputs.js +0 -142
- package/.agents/scripts/lib/orchestration/story-close/comment-bodies.js +0 -62
- package/.agents/scripts/lib/orchestration/story-close/merge-runner.js +0 -658
- package/.agents/scripts/lib/orchestration/story-close/merge-subject.js +0 -198
- package/.agents/scripts/lib/orchestration/story-close/phases/branch-restore.js +0 -105
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +0 -222
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +0 -292
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +0 -270
- package/.agents/scripts/lib/orchestration/story-close/phases/preflight.js +0 -110
- package/.agents/scripts/lib/orchestration/story-close/phases/refresh.js +0 -86
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked-emitter.js +0 -112
- package/.agents/scripts/lib/orchestration/story-close/phases/timeout-blocked.js +0 -157
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +0 -421
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +0 -302
- package/.agents/scripts/lib/orchestration/story-close/shared-checkout-guard.js +0 -163
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +0 -690
- package/.agents/scripts/lib/orchestration/wave-marker.js +0 -28
- package/.agents/scripts/lib/orchestration/wave-record-io.js +0 -218
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +0 -145
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +0 -212
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +0 -111
- package/.agents/scripts/lib/presentation/manifest-builder.js +0 -239
- package/.agents/scripts/lib/presentation/manifest-formatter.js +0 -242
- package/.agents/scripts/lib/presentation/manifest-helpers.js +0 -213
- package/.agents/scripts/lib/presentation/manifest-persistence.js +0 -261
- package/.agents/scripts/lib/presentation/manifest-procedures.js +0 -55
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +0 -306
- package/.agents/scripts/lib/presentation/manifest-renderer.js +0 -188
- package/.agents/scripts/lib/presentation/manifest-story-views.js +0 -110
- package/.agents/scripts/lib/push-epic-retry.js +0 -209
- package/.agents/scripts/lib/spec/index.js +0 -36
- package/.agents/scripts/lib/spec/loader.js +0 -425
- package/.agents/scripts/lib/spec/state.js +0 -208
- package/.agents/scripts/lib/story-init/blocker-validator.js +0 -68
- package/.agents/scripts/lib/story-init/branch-initializer.js +0 -408
- package/.agents/scripts/lib/story-init/context-resolver.js +0 -92
- package/.agents/scripts/lib/story-init/donor-precheck.js +0 -207
- package/.agents/scripts/lib/story-init/state-transitioner.js +0 -80
- package/.agents/scripts/lib/story-init/task-graph-builder.js +0 -124
- package/.agents/scripts/lib/story-init/transition-summary.js +0 -34
- package/.agents/scripts/lib/test-reserved-epic-temp-ids.js +0 -35
- package/.agents/scripts/lib/wave-runner/tick.js +0 -754
- package/.agents/scripts/lib/wave-runner/wave-runner-error.js +0 -20
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +0 -194
- package/.agents/scripts/lifecycle-emit.js +0 -510
- package/.agents/scripts/retro-run.js +0 -218
- package/.agents/scripts/slice-phase.js +0 -361
- package/.agents/scripts/standalone-feedback-rollup.js +0 -188
- package/.agents/scripts/story-close.js +0 -294
- package/.agents/scripts/story-init.js +0 -599
- package/.agents/scripts/story-phase.js +0 -369
- package/.agents/scripts/wave-tick.js +0 -464
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -98
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +0 -172
- package/.agents/skills/core/epic-plan-consolidate/examples.md +0 -51
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +0 -441
- package/.agents/skills/core/epic-plan-decompose-author/examples.md +0 -47
- package/.agents/skills/core/epic-plan-premortem/SKILL.md +0 -146
- package/.agents/skills/core/epic-plan-premortem/examples.md +0 -53
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +0 -383
- package/.agents/skills/core/epic-plan-spec-author/examples.md +0 -91
- package/.agents/workflows/helpers/deliver-epic-reference.md +0 -547
- package/.agents/workflows/helpers/deliver-epic-single.md +0 -331
- package/.agents/workflows/helpers/deliver-epic.md +0 -998
- package/.agents/workflows/helpers/deliver-stories.md +0 -450
- package/.agents/workflows/helpers/epic-audit.md +0 -189
- package/.agents/workflows/helpers/epic-deliver-story.md +0 -436
- package/.agents/workflows/helpers/epic-testing.md +0 -125
- package/.agents/workflows/helpers/plan-epic-reference.md +0 -160
- package/.agents/workflows/helpers/plan-epic.md +0 -353
- package/.agents/workflows/helpers/plan-story.md +0 -251
- package/.agents/workflows/helpers/scope-triage-gate.md +0 -108
- package/.agents/workflows/helpers/single-story-deliver.md +0 -557
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* story-follow-ups.js — capture actionable follow-ups from a landed Story.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the unwired Epic retro as the default closeout for v2: after a
|
|
5
|
+
* Story merges, read its standalone `signals.ndjson` friction stream, compose
|
|
6
|
+
* routed proposals, auto-file follow-up issues (when enabled), and upsert a
|
|
7
|
+
* structured `follow-ups` comment on the Story.
|
|
8
|
+
*
|
|
9
|
+
* @module lib/orchestration/story-follow-ups
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { graduateRetroProposals } from '../feedback-loop/retro-proposals-graduator.js';
|
|
13
|
+
import { DEFAULT_FRAMEWORK_REPO } from '../github/framework-repo.js';
|
|
14
|
+
import { Logger } from '../Logger.js';
|
|
15
|
+
import { forEachLine } from '../observability/signals-writer.js';
|
|
16
|
+
import { composeRoutedProposals } from './retro-proposals.js';
|
|
17
|
+
import { upsertStructuredComment } from './ticketing.js';
|
|
18
|
+
|
|
19
|
+
export const FOLLOW_UPS_COMMENT_TYPE = 'follow-ups';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {object} [config]
|
|
23
|
+
* @returns {{ frameworkRepo: string, consumerRepo: string, currentRepo: { owner: string, repo: string } }}
|
|
24
|
+
*/
|
|
25
|
+
export function resolveFollowUpRepos(config) {
|
|
26
|
+
const owner =
|
|
27
|
+
typeof config?.github?.owner === 'string' ? config.github.owner.trim() : '';
|
|
28
|
+
const repo =
|
|
29
|
+
typeof config?.github?.repo === 'string' ? config.github.repo.trim() : '';
|
|
30
|
+
const consumerRepo =
|
|
31
|
+
owner && repo ? `${owner}/${repo}` : DEFAULT_FRAMEWORK_REPO;
|
|
32
|
+
const frameworkRepo =
|
|
33
|
+
typeof config?.github?.frameworkRepo === 'string' &&
|
|
34
|
+
config.github.frameworkRepo.trim()
|
|
35
|
+
? config.github.frameworkRepo.trim()
|
|
36
|
+
: DEFAULT_FRAMEWORK_REPO;
|
|
37
|
+
const [cOwner, cRepo] = consumerRepo.split('/');
|
|
38
|
+
return {
|
|
39
|
+
frameworkRepo,
|
|
40
|
+
consumerRepo,
|
|
41
|
+
currentRepo: {
|
|
42
|
+
owner: cOwner || 'unknown',
|
|
43
|
+
repo: cRepo || 'unknown',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {number} storyId
|
|
50
|
+
* @param {object} [config]
|
|
51
|
+
* @returns {Promise<Array<{ category: string, source: 'framework'|'consumer' }>>}
|
|
52
|
+
*/
|
|
53
|
+
export async function gatherStoryFrictionSignals(storyId, config) {
|
|
54
|
+
const signals = [];
|
|
55
|
+
await forEachLine(
|
|
56
|
+
null,
|
|
57
|
+
storyId,
|
|
58
|
+
(parsed) => {
|
|
59
|
+
if (!parsed || typeof parsed !== 'object') return;
|
|
60
|
+
const kind = parsed.kind;
|
|
61
|
+
if (kind !== 'friction' && kind !== undefined) {
|
|
62
|
+
// Prefer friction records; also accept category-bearing rows.
|
|
63
|
+
}
|
|
64
|
+
const category =
|
|
65
|
+
typeof parsed.category === 'string' ? parsed.category.trim() : '';
|
|
66
|
+
if (!category) return;
|
|
67
|
+
const source = parsed.source === 'framework' ? 'framework' : 'consumer';
|
|
68
|
+
signals.push({ category, source });
|
|
69
|
+
},
|
|
70
|
+
config,
|
|
71
|
+
);
|
|
72
|
+
return signals;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Render the empty-roll-up line.
|
|
77
|
+
*
|
|
78
|
+
* Story #4578 — an empty roll-up over a multi-Story run must NOT read as
|
|
79
|
+
* success. The pre-#4578 text ("No friction signals — nothing to follow up")
|
|
80
|
+
* was *truthful* about the stream and *false* about the run: a 7-Story
|
|
81
|
+
* delivery containing a mid-run git outage, a parked worker, and a
|
|
82
|
+
* four-round acceptance critic rendered byte-identically to a genuinely
|
|
83
|
+
* clean run. An operator cannot tell "nothing went wrong" from "the
|
|
84
|
+
* telemetry never fired", and the second is likeliest exactly when the run
|
|
85
|
+
* went worst.
|
|
86
|
+
*
|
|
87
|
+
* So the line is a function of `storyCount`:
|
|
88
|
+
* - `storyCount <= 1` → the honest, quiet reading is retained. A single
|
|
89
|
+
* Story that emitted nothing plausibly *was* clean, and crying wolf on
|
|
90
|
+
* every clean Story is how a warning channel gets tuned out.
|
|
91
|
+
* - `storyCount > 1` → zero signals across N Stories is a **claim**, and
|
|
92
|
+
* the surrounding text says so and names the two readings, rather than
|
|
93
|
+
* asserting the flattering one.
|
|
94
|
+
*
|
|
95
|
+
* This mirrors the sibling precedent in `run-epilogue.js`'s
|
|
96
|
+
* `renderDiffLines`, which refuses to let an unresolvable base diff render
|
|
97
|
+
* as "0 changed files".
|
|
98
|
+
*
|
|
99
|
+
* @param {number} storyCount
|
|
100
|
+
* @returns {string[]}
|
|
101
|
+
*/
|
|
102
|
+
function renderEmptyRollupLines(storyCount) {
|
|
103
|
+
if (storyCount <= 1) {
|
|
104
|
+
return ['_No friction signals — nothing to follow up._'];
|
|
105
|
+
}
|
|
106
|
+
return [
|
|
107
|
+
`> ⚠️ **0 friction signals across ${storyCount} Stories — this is a claim, not a clean bill of health.**`,
|
|
108
|
+
'> Either the run was genuinely friction-free, or telemetry never fired.',
|
|
109
|
+
'> An empty stream is indistinguishable from a clean run, and it is least',
|
|
110
|
+
'> likely to fill exactly when a run is going badly and the agent is busy.',
|
|
111
|
+
'> The runtime emits friction from its own observables (`agent::blocked`',
|
|
112
|
+
'> transitions, failed closes, exhausted merge waits) — so zero here also',
|
|
113
|
+
'> means none of those fired. If the run had friction you can name, that',
|
|
114
|
+
'> gap is itself the follow-up worth filing.',
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {{
|
|
120
|
+
* storyId: number,
|
|
121
|
+
* proposals: object,
|
|
122
|
+
* graduated: object,
|
|
123
|
+
* storyCount?: number,
|
|
124
|
+
* }} args - `storyCount` (default 1) is how many Stories the roll-up spans;
|
|
125
|
+
* it decides whether an empty result reads as quiet or as a flagged claim.
|
|
126
|
+
* @returns {string}
|
|
127
|
+
*/
|
|
128
|
+
export function buildFollowUpsCommentBody({
|
|
129
|
+
storyId,
|
|
130
|
+
proposals,
|
|
131
|
+
graduated,
|
|
132
|
+
storyCount = 1,
|
|
133
|
+
}) {
|
|
134
|
+
const filed = Array.isArray(graduated?.filed) ? graduated.filed : [];
|
|
135
|
+
const framework = proposals?.framework ?? [];
|
|
136
|
+
const consumer = proposals?.consumer ?? [];
|
|
137
|
+
const discarded = proposals?.discarded ?? [];
|
|
138
|
+
const lines = [
|
|
139
|
+
'### follow-ups',
|
|
140
|
+
'',
|
|
141
|
+
`Actionable follow-ups captured from Story #${storyId} after merge.`,
|
|
142
|
+
'',
|
|
143
|
+
];
|
|
144
|
+
if (filed.length > 0) {
|
|
145
|
+
lines.push('**Filed**');
|
|
146
|
+
for (const item of filed) {
|
|
147
|
+
lines.push(
|
|
148
|
+
`- ${item.source}: ${item.title}${item.url ? ` — ${item.url}` : ''}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
lines.push('');
|
|
152
|
+
}
|
|
153
|
+
if (framework.length + consumer.length > 0 && filed.length === 0) {
|
|
154
|
+
lines.push('**Actionable (not auto-filed)**');
|
|
155
|
+
for (const item of [...framework, ...consumer]) {
|
|
156
|
+
lines.push(`- ${item.source}: ${item.title}`);
|
|
157
|
+
lines.push('');
|
|
158
|
+
lines.push('```bash');
|
|
159
|
+
lines.push(item.command);
|
|
160
|
+
lines.push('```');
|
|
161
|
+
}
|
|
162
|
+
lines.push('');
|
|
163
|
+
}
|
|
164
|
+
if (discarded.length > 0) {
|
|
165
|
+
lines.push('**Single-occurrence (not filed)**');
|
|
166
|
+
for (const item of discarded) {
|
|
167
|
+
lines.push(`- ${item.source}: \`${item.category}\` ×${item.occurrences}`);
|
|
168
|
+
}
|
|
169
|
+
lines.push('');
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
filed.length === 0 &&
|
|
173
|
+
framework.length === 0 &&
|
|
174
|
+
consumer.length === 0 &&
|
|
175
|
+
discarded.length === 0
|
|
176
|
+
) {
|
|
177
|
+
lines.push(...renderEmptyRollupLines(storyCount));
|
|
178
|
+
lines.push('');
|
|
179
|
+
}
|
|
180
|
+
lines.push('```json');
|
|
181
|
+
lines.push(
|
|
182
|
+
JSON.stringify(
|
|
183
|
+
{
|
|
184
|
+
storyId,
|
|
185
|
+
storyCount,
|
|
186
|
+
framework: framework.map((i) => i.category),
|
|
187
|
+
consumer: consumer.map((i) => i.category),
|
|
188
|
+
discarded: discarded.map((i) => i.category),
|
|
189
|
+
filed: filed.map((i) => ({
|
|
190
|
+
category: i.category,
|
|
191
|
+
url: i.url ?? null,
|
|
192
|
+
})),
|
|
193
|
+
// Story #4578 — an empty roll-up over N>1 Stories is a claim worth
|
|
194
|
+
// flagging, not a success. Machine-readable twin of the warning
|
|
195
|
+
// prose so a caller need not regex the body.
|
|
196
|
+
emptyRollupSuspect:
|
|
197
|
+
storyCount > 1 &&
|
|
198
|
+
filed.length === 0 &&
|
|
199
|
+
framework.length === 0 &&
|
|
200
|
+
consumer.length === 0 &&
|
|
201
|
+
discarded.length === 0,
|
|
202
|
+
},
|
|
203
|
+
null,
|
|
204
|
+
2,
|
|
205
|
+
),
|
|
206
|
+
);
|
|
207
|
+
lines.push('```');
|
|
208
|
+
return lines.join('\n');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Capture and persist Story follow-ups. Never throws — the land must not
|
|
213
|
+
* fail because follow-up filing flaked.
|
|
214
|
+
*
|
|
215
|
+
* Story #4543 retired the `captureFollowUpsAfterConfirm` action-gate wrapper
|
|
216
|
+
* (and its `withConfirmFollowUps` sibling) that used to front this function.
|
|
217
|
+
* Re-deriving "did the merge land?" from a confirmation envelope's `action`
|
|
218
|
+
* field was the coupling that made close-and-land — the DEFAULT path — skip
|
|
219
|
+
* capture entirely: the gate only opened on the standalone CLI's `done`, and
|
|
220
|
+
* a belated manual confirm could not backfill because the Story was already
|
|
221
|
+
* `agent::done` (confirm returns `noop`, the gate never opens). The shared
|
|
222
|
+
* land tail (`single-story-close/phases/post-land.js`) now calls this
|
|
223
|
+
* directly, after the merge is already confirmed.
|
|
224
|
+
*
|
|
225
|
+
* @param {object} args
|
|
226
|
+
* @param {number} args.storyId
|
|
227
|
+
* @param {object} args.provider
|
|
228
|
+
* @param {object} [args.config]
|
|
229
|
+
* @param {string} [args.cwd]
|
|
230
|
+
* @param {(tag: string, msg: string) => void} [args.progress]
|
|
231
|
+
* @returns {Promise<object>}
|
|
232
|
+
*/
|
|
233
|
+
export async function captureStoryFollowUps({
|
|
234
|
+
storyId,
|
|
235
|
+
provider,
|
|
236
|
+
config,
|
|
237
|
+
cwd,
|
|
238
|
+
progress,
|
|
239
|
+
}) {
|
|
240
|
+
const sid = Number(storyId);
|
|
241
|
+
if (!Number.isInteger(sid) || sid <= 0) {
|
|
242
|
+
return { ok: false, reason: 'invalid-story-id' };
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const signals = await gatherStoryFrictionSignals(sid, config);
|
|
246
|
+
const repos = resolveFollowUpRepos(config);
|
|
247
|
+
const proposals = composeRoutedProposals({
|
|
248
|
+
anchorId: sid,
|
|
249
|
+
anchorKind: 'story',
|
|
250
|
+
frameworkRepo: repos.frameworkRepo,
|
|
251
|
+
consumerRepo: repos.consumerRepo,
|
|
252
|
+
signals,
|
|
253
|
+
unresolvedBlockedEvents: [],
|
|
254
|
+
});
|
|
255
|
+
const graduated = await graduateRetroProposals({
|
|
256
|
+
epicId: sid,
|
|
257
|
+
provider,
|
|
258
|
+
config,
|
|
259
|
+
currentRepo: repos.currentRepo,
|
|
260
|
+
frameworkRepo: (() => {
|
|
261
|
+
const [owner, repo] = repos.frameworkRepo.split('/');
|
|
262
|
+
return { owner, repo };
|
|
263
|
+
})(),
|
|
264
|
+
routedProposals: proposals,
|
|
265
|
+
cwd,
|
|
266
|
+
});
|
|
267
|
+
const body = buildFollowUpsCommentBody({
|
|
268
|
+
storyId: sid,
|
|
269
|
+
proposals,
|
|
270
|
+
graduated,
|
|
271
|
+
});
|
|
272
|
+
await upsertStructuredComment(provider, sid, FOLLOW_UPS_COMMENT_TYPE, body);
|
|
273
|
+
progress?.(
|
|
274
|
+
'FOLLOW-UPS',
|
|
275
|
+
`Captured follow-ups for Story #${sid} (filed=${graduated.filed?.length ?? 0}).`,
|
|
276
|
+
);
|
|
277
|
+
return {
|
|
278
|
+
ok: true,
|
|
279
|
+
storyId: sid,
|
|
280
|
+
proposals,
|
|
281
|
+
graduated,
|
|
282
|
+
signalCount: signals.length,
|
|
283
|
+
};
|
|
284
|
+
} catch (err) {
|
|
285
|
+
Logger.warn(
|
|
286
|
+
`[story-follow-ups] capture failed for #${sid}: ${err?.message ?? err}`,
|
|
287
|
+
);
|
|
288
|
+
progress?.(
|
|
289
|
+
'FOLLOW-UPS',
|
|
290
|
+
`⚠️ Follow-up capture failed (close continues): ${err?.message ?? err}`,
|
|
291
|
+
);
|
|
292
|
+
return {
|
|
293
|
+
ok: false,
|
|
294
|
+
reason: 'capture-failed',
|
|
295
|
+
error: String(err?.message ?? err),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Logger } from '../Logger.js';
|
|
2
|
+
import {
|
|
3
|
+
STATE_LABELS,
|
|
4
|
+
transitionTicketState,
|
|
5
|
+
upsertStructuredComment,
|
|
6
|
+
} from './ticketing.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fail closed when the repository remote cannot be verified. The Story is
|
|
10
|
+
* blocked before lease/branch/worktree mutation so a host that misses the
|
|
11
|
+
* result envelope cannot strand an executing Story.
|
|
12
|
+
*/
|
|
13
|
+
export async function handleRemoteVerificationFailure({
|
|
14
|
+
provider,
|
|
15
|
+
storyId,
|
|
16
|
+
remote,
|
|
17
|
+
dryRun = false,
|
|
18
|
+
}) {
|
|
19
|
+
if (remote?.remoteVerified) return;
|
|
20
|
+
const message =
|
|
21
|
+
`[single-story-init] remote verification failed for Story #${storyId}: ` +
|
|
22
|
+
`${remote?.detail ?? 'origin is unavailable'}`;
|
|
23
|
+
if (!dryRun) {
|
|
24
|
+
try {
|
|
25
|
+
await upsertStructuredComment(
|
|
26
|
+
provider,
|
|
27
|
+
storyId,
|
|
28
|
+
'friction',
|
|
29
|
+
`### Remote verification blocked delivery\n\n${message}`,
|
|
30
|
+
);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
Logger.warn(
|
|
33
|
+
`[single-story-init] failed to post remote-verification friction: ${err?.message ?? err}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
// Story #4539 — the canonical mutator. This is the path where the
|
|
37
|
+
// skipped Projects v2 column sync (Story #2548) visibly drifts: the
|
|
38
|
+
// Story is still agent::ready (To Do) when the remote probe fails, so
|
|
39
|
+
// a direct label write leaves the board reading To Do for a blocked
|
|
40
|
+
// Story. single-story-init.js's own comment explains exactly why this
|
|
41
|
+
// must not bypass the mutator.
|
|
42
|
+
try {
|
|
43
|
+
await transitionTicketState(provider, storyId, STATE_LABELS.BLOCKED, {});
|
|
44
|
+
} catch (err) {
|
|
45
|
+
Logger.warn(
|
|
46
|
+
`[single-story-init] failed to block Story after remote verification: ${err?.message ?? err}`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw new Error(message);
|
|
51
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* story-plan-state.js — read the v2 Story planning checkpoint.
|
|
3
|
+
*
|
|
4
|
+
* `plan-persist.js` upserts one `story-plan-state` structured comment per
|
|
5
|
+
* created Story carrying the persist receipt (when the plan completed, how many
|
|
6
|
+
* Stories it created, and their ids). Story #4542 removed the risk fields it
|
|
7
|
+
* used to carry: the planner-authored verdict, the envelope derived from it,
|
|
8
|
+
* and the review routing computed from that envelope. Nothing read any of them
|
|
9
|
+
* back — review depth is now derived from the diff at close time
|
|
10
|
+
* (`review-depth.js#deriveChangeLevel`), so no checkpoint read sits on the
|
|
11
|
+
* delivery path at all.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { parseFencedJsonComment } from './structured-comment-parser.js';
|
|
15
|
+
import { findStructuredComment } from './ticketing.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Read the v2 Story planning checkpoint. Missing/malformed comments degrade
|
|
19
|
+
* to null — an unplanned Story simply has no persist receipt.
|
|
20
|
+
*/
|
|
21
|
+
export async function readStoryPlanState({
|
|
22
|
+
provider,
|
|
23
|
+
storyId,
|
|
24
|
+
findCommentFn = findStructuredComment,
|
|
25
|
+
}) {
|
|
26
|
+
const comment = await findCommentFn(
|
|
27
|
+
provider,
|
|
28
|
+
Number(storyId),
|
|
29
|
+
'story-plan-state',
|
|
30
|
+
);
|
|
31
|
+
const state = parseFencedJsonComment(comment);
|
|
32
|
+
return state && typeof state === 'object' ? state : null;
|
|
33
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* structured-comment-parser.js — shared parser for fenced-JSON structured
|
|
3
3
|
* comments.
|
|
4
4
|
*
|
|
5
|
-
* Several callers (epic-runner Checkpointer,
|
|
5
|
+
* Several callers (the deleted epic-runner Checkpointer/ProgressReporter,
|
|
6
6
|
* `parseStoryRunProgressComment` / `parsePhaseTimingsComment`, wave-gate's
|
|
7
7
|
* local `extractJsonBlock`) need to extract the `{...}` payload from the
|
|
8
8
|
* fenced ```json``` block of a structured comment body. They had each open-
|
|
@@ -24,16 +24,23 @@
|
|
|
24
24
|
* name at least one path-shaped token so vague verbs ("clean up",
|
|
25
25
|
* "refactor") can't slip through.
|
|
26
26
|
*
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* `acceptance` / `verify` are the **top-level machine contract** (Story
|
|
28
|
+
* #4541). The decomposer prompt tells authors to write those lists once at
|
|
29
|
+
* the ticket's top level and omit the matching body sections; persist syncs
|
|
30
|
+
* them into the body at assemble time. Validation runs *before* that sync,
|
|
31
|
+
* so this validator resolves each contract field from the parsed body and
|
|
32
|
+
* falls back to the ticket's top-level array when the body section is
|
|
33
|
+
* absent. Without that fallback the validator rejected the very shape its
|
|
34
|
+
* own prompt prescribes.
|
|
35
|
+
*
|
|
36
|
+
* `body.changes` items must be object-form `{ path: string, assumption: enum }`
|
|
37
|
+
* entries (Story #2636 shape). Plain string bullets are rejected at parse
|
|
38
|
+
* time and by this validator.
|
|
30
39
|
*
|
|
31
40
|
* Object-form items must declare an `assumption` ∈ `creates |
|
|
32
41
|
* refactors-existing | exists | deletes`. The optional `body.references`
|
|
33
42
|
* array uses the same object shape and is the home for paths the Story
|
|
34
43
|
* reads but does not modify (test fixtures, sibling modules, etc.).
|
|
35
|
-
* String-form `changes` items remain legal so legacy plans keep parsing,
|
|
36
|
-
* but they emit a deprecation signal via `validateStoryFileAssumptions`.
|
|
37
44
|
*
|
|
38
45
|
* `body.verify` entries must either name a testing tier in parentheses
|
|
39
46
|
* drawn from `VERIFY_TIER_VALUES` (e.g. `npm run test (unit)`) or be the
|
|
@@ -53,8 +60,8 @@ import { FILE_ASSUMPTION_VALUES } from './file-assumption-enum.js';
|
|
|
53
60
|
|
|
54
61
|
/**
|
|
55
62
|
* Canonical testing-tier labels that a `verify[]` entry must name (in
|
|
56
|
-
* parentheses) to pass plan-time validation. Mirrors the
|
|
57
|
-
*
|
|
63
|
+
* parentheses) to pass plan-time validation. Mirrors the verify-rules contract
|
|
64
|
+
* in `.agents/scripts/lib/templates/decomposer-prompts.js`.
|
|
58
65
|
*
|
|
59
66
|
* Entries that do not end with `(<tier>)` and are not `manual:<reason>` are
|
|
60
67
|
* rejected by `collectVerifyErrors`.
|
|
@@ -166,6 +173,39 @@ function resolveStructuredBody(ticket) {
|
|
|
166
173
|
}
|
|
167
174
|
}
|
|
168
175
|
|
|
176
|
+
/**
|
|
177
|
+
* The two contract fields that live at the ticket's top level and are
|
|
178
|
+
* synced into the body by `plan-persist` at assemble time.
|
|
179
|
+
*/
|
|
180
|
+
const CONTRACT_FIELDS = Object.freeze(['acceptance', 'verify']);
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Resolve the body's contract fields against the ticket's top-level arrays
|
|
184
|
+
* (Story #4541). The decomposer prompt prescribes authoring `acceptance[]`
|
|
185
|
+
* / `verify[]` **once** at top level and omitting the matching body
|
|
186
|
+
* sections; `assemblePlanStories#syncContractFieldFromTopLevel` performs
|
|
187
|
+
* the sync, but it runs *after* validation. So an absent body section is
|
|
188
|
+
* not a violation when the ticket carries the list at top level — it is the
|
|
189
|
+
* preferred shape. A body section that is present and disagrees with the
|
|
190
|
+
* top level is left alone here: the sync itself fails closed on that
|
|
191
|
+
* mismatch, and duplicating the check would report it twice.
|
|
192
|
+
*
|
|
193
|
+
* @param {object} ticket
|
|
194
|
+
* @param {object} bodyObject Parsed / structured body.
|
|
195
|
+
* @returns {object} A copy of `bodyObject` with the contract fields resolved.
|
|
196
|
+
*/
|
|
197
|
+
function resolveContractFieldsFromTopLevel(ticket, bodyObject) {
|
|
198
|
+
const resolved = { ...bodyObject };
|
|
199
|
+
for (const field of CONTRACT_FIELDS) {
|
|
200
|
+
const bodyValue = Array.isArray(resolved[field]) ? resolved[field] : [];
|
|
201
|
+
if (bodyValue.length > 0) continue;
|
|
202
|
+
const topLevel = Array.isArray(ticket?.[field]) ? ticket[field] : [];
|
|
203
|
+
if (topLevel.length === 0) continue;
|
|
204
|
+
resolved[field] = topLevel.map(String);
|
|
205
|
+
}
|
|
206
|
+
return resolved;
|
|
207
|
+
}
|
|
208
|
+
|
|
169
209
|
/**
|
|
170
210
|
* Validate one Story body and return every violation it exhibits. Empty
|
|
171
211
|
* array means clean. Splits the per-ticket cascade out of
|
|
@@ -184,13 +224,14 @@ function resolveStructuredBody(ticket) {
|
|
|
184
224
|
*/
|
|
185
225
|
export function validateTaskBodyShape(ticket) {
|
|
186
226
|
const prefix = `Story "${ticket.title}" (${ticket.slug})`;
|
|
187
|
-
const { body, error } = resolveStructuredBody(ticket);
|
|
227
|
+
const { body: parsed, error } = resolveStructuredBody(ticket);
|
|
188
228
|
if (error !== null) {
|
|
189
229
|
return [error];
|
|
190
230
|
}
|
|
191
|
-
if (
|
|
192
|
-
return [`${prefix}: body must be an object, got ${typeof
|
|
231
|
+
if (parsed === null || typeof parsed !== 'object') {
|
|
232
|
+
return [`${prefix}: body must be an object, got ${typeof parsed}.`];
|
|
193
233
|
}
|
|
234
|
+
const body = resolveContractFieldsFromTopLevel(ticket, parsed);
|
|
194
235
|
const errors = [];
|
|
195
236
|
if (typeof body.goal !== 'string' || body.goal.trim() === '') {
|
|
196
237
|
errors.push(`${prefix}: body.goal must be a non-empty string.`);
|
|
@@ -248,25 +289,17 @@ function collectChangesErrors(prefix, rawChanges) {
|
|
|
248
289
|
return [`${prefix}: body.changes must list at least one bullet.`];
|
|
249
290
|
}
|
|
250
291
|
const errors = [];
|
|
251
|
-
|
|
252
|
-
// string OR an object-form entry that passed the assumption schema.
|
|
253
|
-
const namesPath = (c) => {
|
|
254
|
-
if (typeof c === 'string') return bulletNamesPath(c);
|
|
255
|
-
return isObjectPathEntry(c);
|
|
256
|
-
};
|
|
292
|
+
const namesPath = (c) => isObjectPathEntry(c);
|
|
257
293
|
if (changes.every((c) => !namesPath(c))) {
|
|
258
294
|
errors.push(
|
|
259
|
-
`${prefix}: body.changes
|
|
295
|
+
`${prefix}: body.changes must declare at least one { path, assumption } object entry.`,
|
|
260
296
|
);
|
|
261
297
|
}
|
|
262
298
|
for (const entry of changes) {
|
|
263
299
|
if (typeof entry === 'string') {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
`${prefix}: body.changes bullet uses vague verb "${verb}" without a named target: "${entry}".`,
|
|
268
|
-
);
|
|
269
|
-
}
|
|
300
|
+
errors.push(
|
|
301
|
+
`${prefix}: body.changes entry must be a { path, assumption } object; plain string bullets are no longer accepted: "${entry}".`,
|
|
302
|
+
);
|
|
270
303
|
continue;
|
|
271
304
|
}
|
|
272
305
|
if (isMalformedObjectPathEntry(entry)) {
|
|
@@ -310,7 +343,9 @@ function collectReferencesErrors(prefix, rawReferences) {
|
|
|
310
343
|
function collectAcceptanceErrors(prefix, rawAcceptance) {
|
|
311
344
|
const acceptance = Array.isArray(rawAcceptance) ? rawAcceptance : [];
|
|
312
345
|
if (acceptance.length === 0) {
|
|
313
|
-
return [
|
|
346
|
+
return [
|
|
347
|
+
`${prefix}: acceptance must list at least one criterion — author it at the ticket's top level (preferred) or in the body's ## Acceptance section.`,
|
|
348
|
+
];
|
|
314
349
|
}
|
|
315
350
|
return [];
|
|
316
351
|
}
|
|
@@ -333,7 +368,7 @@ function collectVerifyErrors(prefix, rawVerify) {
|
|
|
333
368
|
const verify = Array.isArray(rawVerify) ? rawVerify : [];
|
|
334
369
|
if (verify.length === 0) {
|
|
335
370
|
return [
|
|
336
|
-
`${prefix}:
|
|
371
|
+
`${prefix}: verify must list at least one entry — author it at the ticket's top level (preferred) or in the body's ## Verify section. Use "manual:<reason>" only when truly unverifiable in isolation.`,
|
|
337
372
|
];
|
|
338
373
|
}
|
|
339
374
|
const errors = [];
|
|
@@ -5,9 +5,19 @@
|
|
|
5
5
|
* an exclusive, time-bounded claim on a ticket so two concurrent runs do not
|
|
6
6
|
* both drive the same Story. Rather than invent a new state column, the lease
|
|
7
7
|
* rides the ticket's existing **assignees** surface: the single assignee *is*
|
|
8
|
-
* the lease owner. Liveness is decided by the owner's
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* the lease owner. Liveness is decided by the owner's last-heartbeat epoch-ms
|
|
9
|
+
* compared against a configured TTL.
|
|
10
|
+
*
|
|
11
|
+
* **There is no live heartbeat source.** The `story.heartbeat` emitter this
|
|
12
|
+
* module was designed against was structurally inert (it demanded an
|
|
13
|
+
* `epicId >= 1` that v2, which has no Epics, never supplies) and has been
|
|
14
|
+
* deleted. Every caller now reaches `acquireLease` through
|
|
15
|
+
* `lease-guard-shared.acquireLeaseFailClosed` with `anchorHeartbeatToNow`,
|
|
16
|
+
* which pins `heartbeatAt` to `now`: the lease **fails closed**, so any
|
|
17
|
+
* foreign claim reads as live and refuses. A stranded claim is cleared with
|
|
18
|
+
* `--steal`, never by TTL expiry. The TTL and the stale-reclaim branch below
|
|
19
|
+
* are therefore reachable only via an explicit caller-supplied `heartbeatAt`
|
|
20
|
+
* — the seam is kept, the automatic expiry is not real.
|
|
11
21
|
*
|
|
12
22
|
* The three exported operations are deliberately thin and provider-agnostic:
|
|
13
23
|
*
|
|
@@ -31,19 +41,16 @@
|
|
|
31
41
|
* Liveness seam: callers supply the owner's last-heartbeat epoch-ms via the
|
|
32
42
|
* `heartbeatAt` option (a number, or `null`/`undefined` when no heartbeat has
|
|
33
43
|
* ever been recorded for the current owner). Threading the timestamp in keeps
|
|
34
|
-
* this module pure and trivially unit-testable — it does not read
|
|
35
|
-
*
|
|
36
|
-
*
|
|
44
|
+
* this module pure and trivially unit-testable — it does not read any ledger
|
|
45
|
+
* itself. A claim with no heartbeat is treated as stale (reclaimable) by
|
|
46
|
+
* `isClaimLive`; note the live guards never take that branch, per the
|
|
47
|
+
* fail-closed anchoring described above.
|
|
37
48
|
*
|
|
38
49
|
* `now` is injectable (epoch ms) for deterministic tests; it defaults to
|
|
39
50
|
* `Date.now()`.
|
|
40
51
|
*/
|
|
41
52
|
|
|
42
|
-
import { readFileSync } from 'node:fs';
|
|
43
|
-
|
|
44
53
|
import { resolveLeaseTtlMs } from '../config/limits.js';
|
|
45
|
-
import { epicLedgerPath } from '../config/temp-paths.js';
|
|
46
|
-
import { parseLedger } from './lifecycle/trace-logger.js';
|
|
47
54
|
|
|
48
55
|
/**
|
|
49
56
|
* The shipped, non-personal operator-identity placeholder (and its bare,
|
|
@@ -87,76 +94,22 @@ export function normalizeOperatorHandle(raw) {
|
|
|
87
94
|
return trimmed;
|
|
88
95
|
}
|
|
89
96
|
|
|
90
|
-
/**
|
|
91
|
-
* Read the most-recent `story.heartbeat` epoch-ms recorded for a given lease
|
|
92
|
-
* owner from an Epic lifecycle ledger. Returns `null` when the ledger is
|
|
93
|
-
* absent, unreadable, or carries no heartbeat for that owner — which the lease
|
|
94
|
-
* primitive treats as a stale (reclaimable) claim.
|
|
95
|
-
*
|
|
96
|
-
* The ledger is NDJSON; each `story.heartbeat` record carries
|
|
97
|
-
* `payload.operator` (Story #3480) and `payload.timestamp` (ISO-8601). A
|
|
98
|
-
* malformed ledger downgrades to `null` rather than throwing so a corrupt
|
|
99
|
-
* observability artifact never wedges the lease preflight.
|
|
100
|
-
*
|
|
101
|
-
* This is the shared liveness source the lease guards thread into
|
|
102
|
-
* `acquireLease` via `heartbeatAt`; `/plan` and `/deliver` both
|
|
103
|
-
* reuse it so a live foreign claim actually refuses.
|
|
104
|
-
*
|
|
105
|
-
* @param {object} args
|
|
106
|
-
* @param {number} args.epicId
|
|
107
|
-
* @param {string} args.owner Lease owner whose heartbeat to find.
|
|
108
|
-
* @param {object} [args.config] Resolved config (for ledger path).
|
|
109
|
-
* @param {string} [args.ledgerPath] Explicit path override (tests).
|
|
110
|
-
* @param {(eid: number, config?: object) => string} [args.ledgerPathResolver]
|
|
111
|
-
* Injectable resolver (tests). Defaults to `epicLedgerPath`.
|
|
112
|
-
* @param {(p: string) => string} [args.readFile] Injectable reader (tests).
|
|
113
|
-
* @returns {number|null}
|
|
114
|
-
*/
|
|
115
|
-
export function latestHeartbeatForOwner({
|
|
116
|
-
epicId,
|
|
117
|
-
owner,
|
|
118
|
-
config,
|
|
119
|
-
ledgerPath,
|
|
120
|
-
ledgerPathResolver = epicLedgerPath,
|
|
121
|
-
readFile = (p) => readFileSync(p, 'utf8'),
|
|
122
|
-
}) {
|
|
123
|
-
if (typeof owner !== 'string' || owner.length === 0) return null;
|
|
124
|
-
const resolvedPath = ledgerPath ?? ledgerPathResolver(epicId, config);
|
|
125
|
-
|
|
126
|
-
let text;
|
|
127
|
-
try {
|
|
128
|
-
text = readFile(resolvedPath);
|
|
129
|
-
} catch (_err) {
|
|
130
|
-
// No ledger yet (fresh Epic) → no heartbeat → reclaimable.
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let records;
|
|
135
|
-
try {
|
|
136
|
-
records = parseLedger(text);
|
|
137
|
-
} catch (_err) {
|
|
138
|
-
// Corrupt ledger is an observability problem, not a coordination blocker.
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let latest = null;
|
|
143
|
-
for (const record of records) {
|
|
144
|
-
const payload = record?.payload;
|
|
145
|
-
if (!payload || payload.event !== 'story.heartbeat') continue;
|
|
146
|
-
if (payload.operator !== owner) continue;
|
|
147
|
-
const ts = Date.parse(payload.timestamp ?? '');
|
|
148
|
-
if (!Number.isFinite(ts)) continue;
|
|
149
|
-
if (latest === null || ts > latest) latest = ts;
|
|
150
|
-
}
|
|
151
|
-
return latest;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
97
|
/**
|
|
155
98
|
* Decide whether a foreign claim is still "live" given the owner's last
|
|
156
99
|
* heartbeat and the configured TTL. A claim is live when a heartbeat exists
|
|
157
100
|
* and is no older than `ttlMs`. A missing heartbeat (`null`/`undefined`) or a
|
|
158
101
|
* heartbeat older than the TTL is stale and therefore reclaimable.
|
|
159
102
|
*
|
|
103
|
+
* The `heartbeatAt` seam is retained, but there is no longer any in-repo
|
|
104
|
+
* heartbeat *source*: the `story.heartbeat` emitter was structurally inert
|
|
105
|
+
* (it required an `epicId >= 1` that v2 never sets) and was deleted along
|
|
106
|
+
* with the ledger reader that scanned for it. Every live caller reaches this
|
|
107
|
+
* through `lease-guard-shared.acquireLeaseFailClosed` with
|
|
108
|
+
* `anchorHeartbeatToNow`, which pins `heartbeatAt` to `now` so ANY foreign
|
|
109
|
+
* claim reads live and the guard fails closed — a stranded claim is cleared
|
|
110
|
+
* with `--steal`, not by TTL expiry. The parameter stays because that
|
|
111
|
+
* anchoring is expressed through it.
|
|
112
|
+
*
|
|
160
113
|
* @param {object} args
|
|
161
114
|
* @param {number|null|undefined} args.heartbeatAt Owner's last heartbeat (epoch ms).
|
|
162
115
|
* @param {number} args.ttlMs Lease TTL in milliseconds.
|