mandrel 2.0.0 → 2.2.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 -28
- package/.agents/agents/acceptance-critic.md +20 -9
- package/.agents/agents/story-worker.md +45 -48
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +60 -46
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +33 -57
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +17 -19
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +10 -0
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
- package/.agents/schemas/agentrc.schema.json +35 -46
- 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/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 +24 -68
- package/.agents/scripts/agents-bootstrap-github.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 +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- 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/audit-suite/selector.js +323 -23
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -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 +4 -1
- package/.agents/scripts/lib/checks/index.js +1 -1
- 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 +3 -3
- 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-args.js +23 -2
- package/.agents/scripts/lib/close-validation/gates.js +13 -13
- 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/acceptance-eval.js +2 -2
- package/.agents/scripts/lib/config/delivery-routing.js +7 -6
- package/.agents/scripts/lib/config/explain.js +10 -16
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +15 -25
- package/.agents/scripts/lib/config/quality.js +11 -14
- package/.agents/scripts/lib/config/runners.js +8 -21
- package/.agents/scripts/lib/config/temp-paths.js +18 -56
- package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
- package/.agents/scripts/lib/config-settings-schema.js +48 -22
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/duplicate-search.js +38 -7
- 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 -189
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
- package/.agents/scripts/lib/git-utils.js +0 -14
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +0 -15
- package/.agents/scripts/lib/label-taxonomy.js +1 -12
- package/.agents/scripts/lib/observability/active-story-env.js +42 -163
- 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 +2 -2
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- 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 +43 -45
- package/.agents/scripts/lib/orchestration/change-set.js +103 -0
- package/.agents/scripts/lib/orchestration/code-review.js +70 -191
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
- 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/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 +9 -11
- 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/listeners/README.md +7 -3
- 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/merge-block-class.js +76 -20
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
- package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
- 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/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 +374 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
- 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 +4 -13
- 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 +72 -30
- 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 +12 -8
- 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 +264 -43
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
- package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
- package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
- package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
- package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
- 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 -286
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- 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/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +8 -7
- package/.agents/scripts/lib/story-body/story-body.js +81 -13
- package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/ticket-body-sections.js +0 -14
- package/.agents/scripts/lib/validation-evidence.js +31 -59
- package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/plan-context.js +38 -7
- package/.agents/scripts/plan-critics.js +203 -0
- package/.agents/scripts/plan-persist.js +145 -35
- package/.agents/scripts/plan-run-epilogue.js +83 -38
- 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 +1 -11
- package/.agents/scripts/providers/github/mappers.js +5 -0
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -153
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/quality-preview.js +13 -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 +9 -10
- package/.agents/scripts/single-story-close.js +173 -18
- package/.agents/scripts/single-story-confirm-merge.js +288 -15
- package/.agents/scripts/single-story-init.js +6 -10
- package/.agents/scripts/stories-wave-tick.js +380 -53
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/update-ticket-state.js +8 -50
- 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 +3 -3
- package/.agents/skills/core/scope-triage/SKILL.md +3 -0
- 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 +2 -12
- 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/workflows/audit-architecture.md +3 -4
- package/.agents/workflows/audit-clean-code.md +4 -4
- package/.agents/workflows/audit-documentation.md +4 -5
- package/.agents/workflows/audit-lighthouse.md +8 -0
- package/.agents/workflows/audit-navigability.md +10 -0
- package/.agents/workflows/audit-performance.md +2 -3
- package/.agents/workflows/audit-quality.md +8 -9
- package/.agents/workflows/audit-security.md +1 -2
- package/.agents/workflows/audit-seo.md +10 -0
- package/.agents/workflows/audit-ux-ui.md +7 -0
- package/.agents/workflows/deliver.md +133 -45
- package/.agents/workflows/git-cleanup.md +2 -2
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
- package/.agents/workflows/helpers/code-review.md +14 -12
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +209 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +239 -19
- package/.agents/workflows/qa-assist.md +6 -6
- package/.agents/workflows/qa-explore.md +3 -3
- package/.agents/workflows/qa-run.md +1 -5
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +62 -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 +17 -9
- 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/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
- package/package.json +2 -2
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- 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/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -53
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- 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 -187
- 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/audit-lens-routing.js +0 -128
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- 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/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
- 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 -21
- 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 -397
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
- package/.agents/scripts/resolve-plan-run.js +0 -117
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* merge-block-class.js — Story #4426 (Epic #4425, slice 1: foundation).
|
|
4
4
|
*
|
|
5
|
-
* Shared block-class classifier
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* inventing its own ad hoc diagnosis.
|
|
5
|
+
* Shared block-class classifier for the `single-story-close` must-land
|
|
6
|
+
* terminal step and `deliver-recover`, so a headless delivery run that
|
|
7
|
+
* finishes its work without a confirmed merge is attributable to exactly one
|
|
8
|
+
* class from ONE decision logic, instead of each caller inventing its own ad
|
|
9
|
+
* hoc diagnosis. (It was written to serve an epic-path terminal too; the v2
|
|
10
|
+
* cutover left `single-story-close` as the only delivery path.)
|
|
12
11
|
*
|
|
13
12
|
* Block classes (Epic #4425 Goal; `predicate-refused` added by #4472):
|
|
14
13
|
* - `checks-pending-timeout` The watch/poll budget was
|
|
@@ -16,6 +15,24 @@
|
|
|
16
15
|
* were still pending/running — not
|
|
17
16
|
* a hard block, the run simply ran
|
|
18
17
|
* out of time.
|
|
18
|
+
* - `checks-failed` A required check went RED — red
|
|
19
|
+
* checks that branch protection does
|
|
20
|
+
* not require are NOT this class
|
|
21
|
+
* (see `failingChecksBlockMerge`). A
|
|
22
|
+
* definitive terminal the merge wait
|
|
23
|
+
* must fail fast on (Story #4543):
|
|
24
|
+
* before this class the in-close poll
|
|
25
|
+
* read only `state`/`mergedAt`, so a
|
|
26
|
+
* check that failed at minute one
|
|
27
|
+
* burned the entire budget and then
|
|
28
|
+
* classified as
|
|
29
|
+
* `branch-protection-human-required`
|
|
30
|
+
* (the exhaustion probe sees
|
|
31
|
+
* `mergeStateStatus: BLOCKED` with
|
|
32
|
+
* checks settled) — sending the
|
|
33
|
+
* operator to diagnose branch
|
|
34
|
+
* protection instead of the red check
|
|
35
|
+
* that is actually in their way.
|
|
19
36
|
* - `branch-protection-human-required` GitHub reports the PR needs a
|
|
20
37
|
* human action: a required review
|
|
21
38
|
* that hasn't been granted, or a
|
|
@@ -47,20 +64,27 @@
|
|
|
47
64
|
* this class makes it attributable.
|
|
48
65
|
*
|
|
49
66
|
* Pure function, no I/O: callers pass in the already-observed
|
|
50
|
-
* arm-result / PR-probe / budget signals (from
|
|
51
|
-
*
|
|
67
|
+
* arm-result / PR-probe / budget signals (from the close path's merge wait,
|
|
68
|
+
* a raw `gh pr view` read, or the standalone
|
|
52
69
|
* `single-story-confirm-merge.js` poll) and get back a
|
|
53
70
|
* `{ blockClass, reason }` verdict ready to hand to `emitMergeUnlanded`
|
|
54
71
|
* (`emit-merge-unlanded.js`).
|
|
55
72
|
*/
|
|
56
73
|
|
|
74
|
+
import { failingChecksBlockMerge } from './merge-poll.js';
|
|
75
|
+
|
|
57
76
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
77
|
+
* Every class `classifyMergeBlock` can return. Order is the evaluation
|
|
78
|
+
* priority documented on `classifyMergeBlock` below, NOT an arbitrary
|
|
79
|
+
* listing — earlier entries are checked first when a real input happens to
|
|
80
|
+
* satisfy more than one heuristic.
|
|
81
|
+
*
|
|
82
|
+
* Started as the four classes named in the Epic #4425 Goal; Story #4543
|
|
83
|
+
* added `checks-failed` so a red required check is attributable as itself
|
|
84
|
+
* rather than being absorbed by the timeout or branch-protection verdicts.
|
|
62
85
|
*/
|
|
63
86
|
export const BLOCK_CLASSES = Object.freeze([
|
|
87
|
+
'checks-failed',
|
|
64
88
|
'checks-pending-timeout',
|
|
65
89
|
'branch-protection-human-required',
|
|
66
90
|
'arm-failure',
|
|
@@ -70,10 +94,12 @@ export const BLOCK_CLASSES = Object.freeze([
|
|
|
70
94
|
/**
|
|
71
95
|
* The full set of block-class values a `merge.unlanded` record may carry.
|
|
72
96
|
* This is the classifier's four outputs PLUS `predicate-refused` (#4472),
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
97
|
+
* emitted DIRECTLY for a headless refusal that never reached the
|
|
98
|
+
* poll-exhaustion classifier — so it is a valid attribution value even though
|
|
99
|
+
* `classifyMergeBlock` never produces it. (The Epic-era listeners that used to
|
|
100
|
+
* emit it, AutomergePredicate and AutomergeArmer, are gone; the value stays
|
|
101
|
+
* because archived `merge.unlanded` records carry it and the schema enum
|
|
102
|
+
* must keep validating them.) `isValidBlockClass` (and the `merge.unlanded` schema enum)
|
|
77
103
|
* validate against this broader set; the classifier's own reachability
|
|
78
104
|
* invariant stays scoped to `BLOCK_CLASSES`.
|
|
79
105
|
*/
|
|
@@ -121,6 +147,12 @@ function describeApiRaceFallback(prProbe, budget) {
|
|
|
121
147
|
if (prProbe?.error) {
|
|
122
148
|
return `PR probe error: ${prProbe.error}`;
|
|
123
149
|
}
|
|
150
|
+
// Red checks that do not gate the merge (step 1b declined them). Name the
|
|
151
|
+
// situation precisely: the operator must NOT be sent to fix the red check,
|
|
152
|
+
// because auto-merge was free to land this PR and did not.
|
|
153
|
+
if (prProbe?.checksStatus === 'failure') {
|
|
154
|
+
return `PR did not land although its failing checks are not required (mergeStateStatus=${prProbe?.mergeStateStatus ?? 'n/a'}); the red checks are not the block — check that auto-merge is still armed`;
|
|
155
|
+
}
|
|
124
156
|
if (budget && budget.exhausted === true) {
|
|
125
157
|
return `watch budget exhausted with an unrecognised checks status (${prProbe?.checksStatus ?? 'unknown'})`;
|
|
126
158
|
}
|
|
@@ -137,6 +169,12 @@ function describeApiRaceFallback(prProbe, budget) {
|
|
|
137
169
|
* rejection surfaced AT arm time still routes to
|
|
138
170
|
* `branch-protection-human-required` rather than the generic
|
|
139
171
|
* `arm-failure`.
|
|
172
|
+
* 1b. A red required check — `checks-failed` (Story #4543). Evaluated
|
|
173
|
+
* before every budget and probe signal because it is *definitive*:
|
|
174
|
+
* no amount of remaining budget turns a failed check green, and on a
|
|
175
|
+
* protected branch it also presents as `mergeStateStatus: 'BLOCKED'`,
|
|
176
|
+
* so leaving it to step 3 would attribute the operator's red test run
|
|
177
|
+
* to branch protection.
|
|
140
178
|
* 2. Budget exhaustion while checks were still in flight —
|
|
141
179
|
* `checks-pending-timeout`. Evaluated BEFORE the human-required
|
|
142
180
|
* probe signals because on a protected branch GitHub reports
|
|
@@ -164,9 +202,10 @@ function describeApiRaceFallback(prProbe, budget) {
|
|
|
164
202
|
* (`REVIEW_REQUIRED`, `APPROVED`, …).
|
|
165
203
|
* @param {string} [input.prProbe.mergeStateStatus] GitHub merge-state
|
|
166
204
|
* status (`BLOCKED`, `BEHIND`, `CLEAN`, …).
|
|
167
|
-
* @param {string} [input.prProbe.checksStatus] Aggregate
|
|
168
|
-
*
|
|
169
|
-
* `still-running` | `failure` | `unknown`)
|
|
205
|
+
* @param {string} [input.prProbe.checksStatus] Aggregate status across ALL
|
|
206
|
+
* checks observed on the last probe (`success` | `pending` |
|
|
207
|
+
* `still-running` | `failure` | `unknown`) — required-ness is decided by
|
|
208
|
+
* `mergeStateStatus`, not by this field.
|
|
170
209
|
* @param {string} [input.prProbe.error] Set when the probe call itself
|
|
171
210
|
* errored (network / API failure reading the PR).
|
|
172
211
|
* @param {object} [input.budget] Poll-budget accounting.
|
|
@@ -205,6 +244,23 @@ export function classifyMergeBlock(input) {
|
|
|
205
244
|
const checksPendingEvidence =
|
|
206
245
|
checksStatus === 'pending' || checksStatus === 'still-running';
|
|
207
246
|
|
|
247
|
+
// 1b. A required check is RED. Definitive — no remaining budget makes a
|
|
248
|
+
// failed check pass — so this precedes both the budget branch and the
|
|
249
|
+
// BLOCKED-merge-state heuristic, which would otherwise attribute the red
|
|
250
|
+
// check to branch protection on any protected base.
|
|
251
|
+
//
|
|
252
|
+
// Gated on `failingChecksBlockMerge` rather than the raw rollup status:
|
|
253
|
+
// `checksStatus: 'failure'` covers optional checks too, and naming an
|
|
254
|
+
// optional red check as THE block sends the operator to fix a check that
|
|
255
|
+
// was never gating the merge. A red-but-not-gating PR that still failed to
|
|
256
|
+
// land falls through to the fallback, whose reason says exactly that.
|
|
257
|
+
if (failingChecksBlockMerge(prProbe)) {
|
|
258
|
+
return {
|
|
259
|
+
blockClass: 'checks-failed',
|
|
260
|
+
reason: `a required check failed (mergeStateStatus=${prProbe?.mergeStateStatus ?? 'n/a'})`,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
208
264
|
// 2. Budget exhausted while checks were still in flight. Ordered
|
|
209
265
|
// before the human-required probe signals: `mergeStateStatus:
|
|
210
266
|
// 'BLOCKED'` is the steady state on a protected branch while required
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* merge-poll.js — merge-wait constants and check-rollup derivation owned by
|
|
3
|
+
* the close path.
|
|
4
|
+
*
|
|
5
|
+
* Story #4545 — these three symbols used to live in the Epic-era
|
|
6
|
+
* `lifecycle/listeners/merge-watcher.js`. That listener class had no
|
|
7
|
+
* production caller after the v2.0.0 Story-only cutover, but it was not
|
|
8
|
+
* importer-less: the live close path (`single-story-close/phases/confirm-merge.js`)
|
|
9
|
+
* and `deliver-recover.js` both reached into it for the poll defaults and
|
|
10
|
+
* `deriveChecksStatus`. Relocating them here lets the listener go without
|
|
11
|
+
* leaving the close path importing a lifecycle module it does not otherwise
|
|
12
|
+
* participate in.
|
|
13
|
+
*
|
|
14
|
+
* Sits beside `merge-block-class.js`, its sole consumer pairing:
|
|
15
|
+
* `deriveChecksStatus` produces the `prProbe.checksStatus` value that
|
|
16
|
+
* `classifyMergeBlock` reads.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Default poll interval and cumulative budget for the merge wait. The schema
|
|
21
|
+
* in `.agents/schemas/agentrc.schema.json` exposes these as
|
|
22
|
+
* `delivery.mergeWatch.intervalSeconds` (default 30) and
|
|
23
|
+
* `delivery.mergeWatch.maxBudgetSeconds` (default 3600). Hard-coding the same
|
|
24
|
+
* numbers here keeps the close path self-contained when no config is wired in
|
|
25
|
+
* (e.g. unit tests).
|
|
26
|
+
*/
|
|
27
|
+
export const DEFAULT_INTERVAL_SECONDS = 30;
|
|
28
|
+
export const DEFAULT_MAX_BUDGET_SECONDS = 3600;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Pure: derive an aggregate `checksStatus` (`success` | `still-running` |
|
|
32
|
+
* `failure` | `unknown`) from a `statusCheckRollup` array (`gh pr view --json
|
|
33
|
+
* statusCheckRollup` shape: `{ status, conclusion }` per check). Mirrors the
|
|
34
|
+
* values `classifyMergeBlock` expects on `prProbe.checksStatus`.
|
|
35
|
+
*
|
|
36
|
+
* **Scope: EVERY check reported on the PR, required or not.** The rollup
|
|
37
|
+
* carries no required-vs-optional discriminator (`gh`'s projection has no
|
|
38
|
+
* `isRequired` field), so `failure` here means "something on this PR is red",
|
|
39
|
+
* NOT "the merge is blocked". Use {@link failingChecksBlockMerge} before
|
|
40
|
+
* treating a `failure` as terminal.
|
|
41
|
+
*/
|
|
42
|
+
export function deriveChecksStatus(statusCheckRollup) {
|
|
43
|
+
if (!Array.isArray(statusCheckRollup) || statusCheckRollup.length === 0) {
|
|
44
|
+
return 'unknown';
|
|
45
|
+
}
|
|
46
|
+
let anyPending = false;
|
|
47
|
+
for (const check of statusCheckRollup) {
|
|
48
|
+
const conclusion = String(check?.conclusion ?? '').toUpperCase();
|
|
49
|
+
const status = String(check?.status ?? '').toUpperCase();
|
|
50
|
+
if (['FAILURE', 'CANCELLED', 'TIMED_OUT', 'ERROR'].includes(conclusion)) {
|
|
51
|
+
return 'failure';
|
|
52
|
+
}
|
|
53
|
+
if (status !== 'COMPLETED') {
|
|
54
|
+
anyPending = true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return anyPending ? 'still-running' : 'success';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The one `mergeStateStatus` value that means GitHub itself is gating the
|
|
62
|
+
* merge. See {@link failingChecksBlockMerge}.
|
|
63
|
+
*/
|
|
64
|
+
const MERGE_GATED_STATE = 'BLOCKED';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Pure: does the PR's RED check status actually gate the merge?
|
|
68
|
+
*
|
|
69
|
+
* `deriveChecksStatus` aggregates the whole rollup, so it reports `failure`
|
|
70
|
+
* for a red check of any kind. Branch protection — and therefore GitHub
|
|
71
|
+
* native auto-merge — gates only on REQUIRED checks. A red optional check
|
|
72
|
+
* (an advisory bot, or a `CANCELLED` superseded workflow run, which the
|
|
73
|
+
* rollup derivation counts as a failure) says nothing about whether the PR
|
|
74
|
+
* will land: auto-merge lands it regardless. Treating that as terminal is
|
|
75
|
+
* what stranded a Story `agent::blocked` on a PR that merged anyway.
|
|
76
|
+
*
|
|
77
|
+
* `mergeStateStatus` is GitHub's own verdict, computed against the live
|
|
78
|
+
* branch-protection rules, so it supplies the required-vs-optional
|
|
79
|
+
* discrimination the rollup lacks:
|
|
80
|
+
*
|
|
81
|
+
* - `BLOCKED` — merging is gated. With red checks observed, the red
|
|
82
|
+
* required check is the gate.
|
|
83
|
+
* - `UNSTABLE` — "mergeable with non-passing commit status": the red
|
|
84
|
+
* checks are NOT required. Auto-merge will land it.
|
|
85
|
+
* - `CLEAN` / `BEHIND` / `UNKNOWN` / absent — not evidence that the red
|
|
86
|
+
* check gates the merge.
|
|
87
|
+
*
|
|
88
|
+
* Deliberately conservative: only `BLOCKED` returns `true`. A transient
|
|
89
|
+
* `UNKNOWN` (GitHub has not finished computing the merge state) or a token
|
|
90
|
+
* that cannot see the field degrades to "keep waiting" — the caller's poll
|
|
91
|
+
* budget still bounds the wait and the budget-exhausted classification still
|
|
92
|
+
* fires. The asymmetry is intentional: failing to fail fast costs poll time,
|
|
93
|
+
* whereas failing fast wrongly costs a merged-but-`agent::blocked` strand
|
|
94
|
+
* that only an operator can unpick.
|
|
95
|
+
*
|
|
96
|
+
* @param {{ checksStatus?: string, mergeStateStatus?: string }} [prProbe]
|
|
97
|
+
* @returns {boolean}
|
|
98
|
+
*/
|
|
99
|
+
export function failingChecksBlockMerge(prProbe) {
|
|
100
|
+
if (prProbe?.checksStatus !== 'failure') return false;
|
|
101
|
+
return (
|
|
102
|
+
String(prProbe?.mergeStateStatus ?? '').toUpperCase() === MERGE_GATED_STATE
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -35,19 +35,82 @@ import { buildDecomposerSystemPrompt } from './planning/decomposer-context.js';
|
|
|
35
35
|
const SOURCE_TICKET_FETCH_CONCURRENCY = 4;
|
|
36
36
|
/**
|
|
37
37
|
* Envelope byte ceiling (regression guard for the design's named PR2 risk:
|
|
38
|
-
* two envelopes → one bigger one).
|
|
39
|
-
* the `applyBudget
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
38
|
+
* two envelopes → one bigger one). This is the **only** live bound on
|
|
39
|
+
* envelope size: Story #4541 removed the `applyBudget` pass from
|
|
40
|
+
* `buildAuthoringContext`, because both builders below discard that budgeted
|
|
41
|
+
* body and ship the raw seed on `seed.content` instead — the budget bounded
|
|
42
|
+
* a field that never left the function.
|
|
43
|
+
*
|
|
44
|
+
* The envelope's bounded parts are: the tier-capped codebase snapshot
|
|
45
|
+
* (~35 KB skinny on this repo), the three rendered system prompts (~15 KB),
|
|
46
|
+
* and the digest-first `docsContext` (outline-only, or inline digest in
|
|
47
|
+
* one-pager/seed mode). The seed itself is operator-supplied and carried
|
|
48
|
+
* verbatim. Measured folded envelopes on this repo land at ~42 KB; 256 KB
|
|
49
|
+
* (~64K tokens at the ≈4-chars/token estimate) gives >2× headroom over a
|
|
50
|
+
* worst-case seed + medium-tier snapshot while staying an order of magnitude
|
|
51
|
+
* under the session budget. The test suite asserts serialized envelopes stay
|
|
52
|
+
* under this value — raise it only with a measured justification.
|
|
48
53
|
*/
|
|
49
54
|
export const PLAN_CONTEXT_ENVELOPE_BYTE_CEILING = 256_000;
|
|
50
55
|
|
|
56
|
+
/** Fields named in the over-ceiling error, to point at what to trim. */
|
|
57
|
+
const OVERSIZE_REPORT_FIELDS = 3;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Fail closed when an assembled envelope exceeds
|
|
61
|
+
* {@link PLAN_CONTEXT_ENVELOPE_BYTE_CEILING}.
|
|
62
|
+
*
|
|
63
|
+
* Until now the ceiling was enforced *only* by a test assertion over this
|
|
64
|
+
* repo's own fixtures, which bounds nothing at runtime: the value it actually
|
|
65
|
+
* has to hold for is a consumer's seed or `--tickets` source bodies, and no
|
|
66
|
+
* test sees those. That left the documented planner-context cap resting
|
|
67
|
+
* entirely on `planning.context.maxBytes` — which resolved but was wired to
|
|
68
|
+
* nothing (its `applyBudget` pass lost its last caller in the v2 cutover), so
|
|
69
|
+
* in practice no bound existed at all on the path that needed one. That key
|
|
70
|
+
* and its budget module were removed outright in Story #4541; this ceiling is
|
|
71
|
+
* the replacement.
|
|
72
|
+
*
|
|
73
|
+
* Failing closed is the right direction here and matches how an over-budget
|
|
74
|
+
* `## Spec` is handled (`spec-spill.js`): an envelope this size does not
|
|
75
|
+
* degrade the planner gracefully, it silently produces garbage Stories from a
|
|
76
|
+
* truncated-by-the-host context. Better to refuse and say what to trim. The
|
|
77
|
+
* bound is deliberately a fixed framework constant rather than an operator
|
|
78
|
+
* knob — a cap the operator can raise past what the model can read is a cap
|
|
79
|
+
* that fails silently again.
|
|
80
|
+
*
|
|
81
|
+
* Deliberately **not** exported: its only external caller would be a test, and
|
|
82
|
+
* a test-only export is a production-dead one. It is reachable end to end
|
|
83
|
+
* through {@link buildPlanContext}, which is where the behaviour matters.
|
|
84
|
+
*
|
|
85
|
+
* @param {object} envelope
|
|
86
|
+
* @param {{ ceiling?: number }} [opts]
|
|
87
|
+
* @returns {object} `envelope`, unchanged, when it fits.
|
|
88
|
+
*/
|
|
89
|
+
function assertPlanContextWithinCeiling(envelope, opts = {}) {
|
|
90
|
+
const ceiling = opts.ceiling ?? PLAN_CONTEXT_ENVELOPE_BYTE_CEILING;
|
|
91
|
+
const bytes = Buffer.byteLength(JSON.stringify(envelope) ?? '', 'utf-8');
|
|
92
|
+
if (bytes <= ceiling) return envelope;
|
|
93
|
+
|
|
94
|
+
const largest = Object.entries(envelope)
|
|
95
|
+
.map(([field, value]) => [
|
|
96
|
+
field,
|
|
97
|
+
Buffer.byteLength(JSON.stringify(value) ?? '', 'utf-8'),
|
|
98
|
+
])
|
|
99
|
+
.sort((a, b) => b[1] - a[1])
|
|
100
|
+
.slice(0, OVERSIZE_REPORT_FIELDS)
|
|
101
|
+
.map(([field, size]) => `${field} (${Math.round(size / 1024)} KB)`)
|
|
102
|
+
.join(', ');
|
|
103
|
+
|
|
104
|
+
throw new Error(
|
|
105
|
+
`[plan-context] the assembled "${envelope?.mode}" envelope is ` +
|
|
106
|
+
`${Math.round(bytes / 1024)} KB, over the ` +
|
|
107
|
+
`${Math.round(ceiling / 1024)} KB planner-context ceiling. Largest ` +
|
|
108
|
+
`fields: ${largest}. Trim the seed, plan fewer --tickets source issues ` +
|
|
109
|
+
'in one run, or narrow `planning.codebaseSnapshot`. Raising the ceiling ' +
|
|
110
|
+
'needs a measured justification — see PLAN_CONTEXT_ENVELOPE_BYTE_CEILING.',
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
51
114
|
/**
|
|
52
115
|
* Compact, machine-readable descriptor of the `tickets.json` array the
|
|
53
116
|
* authoring pass writes and `validateAndNormalizeTickets` gates at persist
|
|
@@ -61,10 +124,13 @@ export const TICKET_SCHEMA_DESCRIPTOR = Object.freeze({
|
|
|
61
124
|
slug: 'string — ^[a-z0-9][a-z0-9-]*$ (hyphen-case, unique per decompose)',
|
|
62
125
|
type: "string — literal 'story' (2-tier hierarchy: Epic → Story only)",
|
|
63
126
|
title: 'string — short descriptive title',
|
|
64
|
-
body: 'string — serialized Story-body markdown (never a JSON object)',
|
|
65
|
-
acceptance:
|
|
66
|
-
|
|
67
|
-
|
|
127
|
+
body: 'string — serialized Story-body markdown (never a JSON object); omit the ## Acceptance / ## Verify sections, persist syncs them in',
|
|
128
|
+
acceptance:
|
|
129
|
+
'string[] — top-level testable criteria; the machine contract, authored here and not in the body',
|
|
130
|
+
verify:
|
|
131
|
+
'string[] — top-level exact commands/test paths with (<tier>); the machine contract, authored here and not in the body',
|
|
132
|
+
labels:
|
|
133
|
+
"string[]? — extra labels to apply; 'type::story' is applied automatically. agent::*, type::*, and persona::* are rejected (runtime-owned or retired axes)",
|
|
68
134
|
depends_on: 'string[]? — sibling Story slugs that block execution',
|
|
69
135
|
}),
|
|
70
136
|
validatedBy:
|
|
@@ -295,17 +361,12 @@ export function buildDeliveryShapeSignal({ body } = {}) {
|
|
|
295
361
|
* authoritative from day one); the decompose prompt reuses the existing
|
|
296
362
|
* Story #4162 carrier including the risk-heuristics suffix.
|
|
297
363
|
*
|
|
298
|
-
* @param {{ heuristics?: string[], maxTickets?: number
|
|
364
|
+
* @param {{ heuristics?: string[], maxTickets?: number }} args
|
|
299
365
|
* @returns {{ spec: string, acceptance: string, decompose: string }}
|
|
300
366
|
*/
|
|
301
|
-
export function buildSystemPrompts({
|
|
302
|
-
heuristics = [],
|
|
303
|
-
maxTickets,
|
|
304
|
-
epicId = null,
|
|
305
|
-
} = {}) {
|
|
367
|
+
export function buildSystemPrompts({ heuristics = [], maxTickets } = {}) {
|
|
306
368
|
const decompose = buildDecomposerSystemPrompt(heuristics, {
|
|
307
369
|
maxTickets,
|
|
308
|
-
epicId,
|
|
309
370
|
});
|
|
310
371
|
return {
|
|
311
372
|
spec: renderTechSpecSystemPrompt(),
|
|
@@ -373,7 +434,6 @@ async function buildSeedFileModeEnvelope({
|
|
|
373
434
|
provider,
|
|
374
435
|
config,
|
|
375
436
|
settings,
|
|
376
|
-
fullContext,
|
|
377
437
|
cwd,
|
|
378
438
|
modeLabel = 'seed-file',
|
|
379
439
|
}) {
|
|
@@ -401,7 +461,6 @@ async function buildSeedFileModeEnvelope({
|
|
|
401
461
|
{ ...settings, docsContextFiles: [] },
|
|
402
462
|
{
|
|
403
463
|
epic: { id: 0, title: seedFilePath ?? 'seed', body: content },
|
|
404
|
-
fullContext,
|
|
405
464
|
github: config.github ?? null,
|
|
406
465
|
cwd,
|
|
407
466
|
},
|
|
@@ -436,7 +495,6 @@ async function buildSeedFileModeEnvelope({
|
|
|
436
495
|
systemPrompts: buildSystemPrompts({
|
|
437
496
|
heuristics,
|
|
438
497
|
maxTickets: limits.maxTickets,
|
|
439
|
-
epicId: null,
|
|
440
498
|
}),
|
|
441
499
|
planState: null,
|
|
442
500
|
// N=1 default: author one Story; skip Epic-scale decompose ceremony.
|
|
@@ -454,7 +512,6 @@ async function buildSeedModeEnvelope({
|
|
|
454
512
|
provider,
|
|
455
513
|
config,
|
|
456
514
|
settings,
|
|
457
|
-
fullContext,
|
|
458
515
|
cwd,
|
|
459
516
|
}) {
|
|
460
517
|
if (typeof seedText !== 'string' || seedText.trim().length === 0) {
|
|
@@ -468,7 +525,6 @@ async function buildSeedModeEnvelope({
|
|
|
468
525
|
provider,
|
|
469
526
|
config,
|
|
470
527
|
settings,
|
|
471
|
-
fullContext,
|
|
472
528
|
cwd,
|
|
473
529
|
modeLabel: 'seed',
|
|
474
530
|
});
|
|
@@ -528,7 +584,6 @@ async function buildTicketsModeEnvelope({
|
|
|
528
584
|
provider,
|
|
529
585
|
config,
|
|
530
586
|
settings,
|
|
531
|
-
fullContext,
|
|
532
587
|
cwd,
|
|
533
588
|
}) {
|
|
534
589
|
if (!Array.isArray(ticketIds) || ticketIds.length === 0) {
|
|
@@ -558,7 +613,6 @@ async function buildTicketsModeEnvelope({
|
|
|
558
613
|
title: sourceTickets[0]?.title ?? 'tickets',
|
|
559
614
|
body: seed,
|
|
560
615
|
},
|
|
561
|
-
fullContext,
|
|
562
616
|
github: config.github ?? null,
|
|
563
617
|
cwd,
|
|
564
618
|
},
|
|
@@ -594,7 +648,6 @@ async function buildTicketsModeEnvelope({
|
|
|
594
648
|
systemPrompts: buildSystemPrompts({
|
|
595
649
|
heuristics,
|
|
596
650
|
maxTickets: limits.maxTickets,
|
|
597
|
-
epicId: null,
|
|
598
651
|
}),
|
|
599
652
|
planState: null,
|
|
600
653
|
planProfile:
|
|
@@ -610,6 +663,10 @@ async function buildTicketsModeEnvelope({
|
|
|
610
663
|
/**
|
|
611
664
|
* Build the single planner-context envelope.
|
|
612
665
|
*
|
|
666
|
+
* Every mode returns through here, which makes this the one place the
|
|
667
|
+
* envelope's total size is decided — and therefore the only honest place to
|
|
668
|
+
* bound it (see {@link assertPlanContextWithinCeiling}).
|
|
669
|
+
*
|
|
613
670
|
* @param {{
|
|
614
671
|
* mode: 'seed-file'|'seed'|'tickets',
|
|
615
672
|
* seedFilePath?: string,
|
|
@@ -632,7 +689,36 @@ export async function buildPlanContext({
|
|
|
632
689
|
provider,
|
|
633
690
|
config = {},
|
|
634
691
|
settings = {},
|
|
635
|
-
|
|
692
|
+
cwd,
|
|
693
|
+
}) {
|
|
694
|
+
return assertPlanContextWithinCeiling(
|
|
695
|
+
await buildPlanContextEnvelope({
|
|
696
|
+
mode,
|
|
697
|
+
seedFilePath,
|
|
698
|
+
seedFileContent,
|
|
699
|
+
seedText,
|
|
700
|
+
ticketIds,
|
|
701
|
+
provider,
|
|
702
|
+
config,
|
|
703
|
+
settings,
|
|
704
|
+
cwd,
|
|
705
|
+
}),
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Mode dispatch for {@link buildPlanContext}. Split out so the ceiling check
|
|
711
|
+
* wraps every mode exactly once.
|
|
712
|
+
*/
|
|
713
|
+
async function buildPlanContextEnvelope({
|
|
714
|
+
mode,
|
|
715
|
+
seedFilePath,
|
|
716
|
+
seedFileContent,
|
|
717
|
+
seedText,
|
|
718
|
+
ticketIds,
|
|
719
|
+
provider,
|
|
720
|
+
config,
|
|
721
|
+
settings,
|
|
636
722
|
cwd,
|
|
637
723
|
}) {
|
|
638
724
|
if (mode === 'seed-file') {
|
|
@@ -647,7 +733,6 @@ export async function buildPlanContext({
|
|
|
647
733
|
provider,
|
|
648
734
|
config,
|
|
649
735
|
settings,
|
|
650
|
-
fullContext,
|
|
651
736
|
cwd,
|
|
652
737
|
modeLabel: 'seed-file',
|
|
653
738
|
});
|
|
@@ -658,7 +743,6 @@ export async function buildPlanContext({
|
|
|
658
743
|
provider,
|
|
659
744
|
config,
|
|
660
745
|
settings,
|
|
661
|
-
fullContext,
|
|
662
746
|
cwd,
|
|
663
747
|
});
|
|
664
748
|
}
|
|
@@ -668,7 +752,6 @@ export async function buildPlanContext({
|
|
|
668
752
|
provider,
|
|
669
753
|
config,
|
|
670
754
|
settings,
|
|
671
|
-
fullContext,
|
|
672
755
|
cwd,
|
|
673
756
|
});
|
|
674
757
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* plan-critic-conditions.js —
|
|
2
|
+
* plan-critic-conditions.js — size/heuristic-conditional dispatch decisions for
|
|
3
3
|
* the /plan author-step critics (Epic #4474 PR6, design §4).
|
|
4
4
|
*
|
|
5
|
-
* The collapsed plan flow keeps the consolidation
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
5
|
+
* The collapsed plan flow keeps the consolidation and pre-mortem critics as
|
|
6
|
+
* fresh-context sub-agent dispatches, but makes each dispatch
|
|
7
|
+
* **conditional** instead of unconditional — the dominant plan cost is
|
|
8
|
+
* turns × standing context, and an unconditional critic pays a full
|
|
9
|
+
* sub-agent spawn even when it provably has nothing to find. This module
|
|
10
|
+
* computes those decisions deterministically so the workflow never judges
|
|
11
|
+
* its own dispatch conditions:
|
|
12
12
|
*
|
|
13
|
-
* - **Consolidation
|
|
13
|
+
* - **Consolidation**: dispatch only when the existing
|
|
14
14
|
* `evaluateConsolidationPrecondition` gate says `dispatch: true` AND
|
|
15
15
|
* (the draft has more than `CONSOLIDATION_STORY_THRESHOLD` stories OR
|
|
16
16
|
* the precondition confirmed a divergence from the Delivery Slicing
|
|
@@ -18,27 +18,29 @@
|
|
|
18
18
|
* small draft is NOT a confirmed divergence — it skips, because a
|
|
19
19
|
* ≤-threshold draft is small enough for gate #2's single-view review
|
|
20
20
|
* to catch a distorted shape without a dedicated sub-agent.
|
|
21
|
-
* - **Pre-mortem
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* - **Pre-mortem**: dispatch when the ticket count is at least half
|
|
22
|
+
* of `maxTickets`, OR any configured `planning.riskHeuristics` phrase
|
|
23
|
+
* matches the plan text (case-insensitive substring). Story #4542 removed
|
|
24
|
+
* its third condition — the authored risk verdict's overall level — along
|
|
25
|
+
* with the verdict itself; both surviving conditions read the plan's own
|
|
26
|
+
* observable text and shape rather than a self-assessment.
|
|
25
27
|
*
|
|
26
28
|
* Under-firing risk (design PR6 note): the persist validators are
|
|
27
29
|
* unchanged hard gates and G2's cohort re-measures plan quality; every
|
|
28
30
|
* skip decision this module produces is logged to the plan-metrics ledger
|
|
29
|
-
* (`appendCriticSkip`) by the
|
|
31
|
+
* (`appendCriticSkip`) by the caller so under-firing is auditable.
|
|
30
32
|
*
|
|
31
|
-
* Pure, synchronous, no I/O
|
|
32
|
-
* `plan-
|
|
33
|
-
*
|
|
33
|
+
* Pure, synchronous, no I/O. The single caller is `plan-critics-evaluate.js`,
|
|
34
|
+
* driven by the `plan-critics.js` CLI that `/plan` runs between Author and
|
|
35
|
+
* Persist (Story #4592); the CLI owns reading the authored artifacts and the
|
|
36
|
+
* resolved config.
|
|
34
37
|
*/
|
|
35
38
|
|
|
36
39
|
import { evaluateConsolidationPrecondition } from './consolidation-precondition.js';
|
|
37
|
-
import { deriveRiskEnvelope } from './planning-risk.js';
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* Draft-story count above which the consolidation critic fires even
|
|
41
|
-
* without a confirmed slicing divergence (
|
|
43
|
+
* without a confirmed slicing divergence (#4474 PR6: "> 5 stories").
|
|
42
44
|
*/
|
|
43
45
|
export const CONSOLIDATION_STORY_THRESHOLD = 5;
|
|
44
46
|
|
|
@@ -52,7 +54,7 @@ export const CONSOLIDATION_STORY_THRESHOLD = 5;
|
|
|
52
54
|
*/
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
|
-
* Decide the
|
|
57
|
+
* Decide the consolidation dispatch: precondition AND size/divergence.
|
|
56
58
|
*
|
|
57
59
|
* @param {object} input
|
|
58
60
|
* @param {object[]} input.draftStories - The draft `tickets.json` array
|
|
@@ -105,14 +107,10 @@ export function evaluateConsolidationDispatch({ draftStories, specText }) {
|
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
/**
|
|
108
|
-
* Decide the
|
|
109
|
-
*
|
|
110
|
+
* Decide the pre-mortem dispatch: size ≥ ½ budget, or a risk-heuristic
|
|
111
|
+
* phrase match.
|
|
110
112
|
*
|
|
111
113
|
* @param {object} input
|
|
112
|
-
* @param {import('./planning-risk.js').RiskVerdict} input.riskVerdict -
|
|
113
|
-
* The authored `risk-verdict.json` payload; the overall level is derived
|
|
114
|
-
* deterministically from its axes (`deriveRiskEnvelope`), never trusted
|
|
115
|
-
* as a free-standing field.
|
|
116
114
|
* @param {number} input.ticketCount - Draft ticket count (0 in the
|
|
117
115
|
* single-delivery shape — no tickets exist).
|
|
118
116
|
* @param {number} input.maxTickets - The reviewability budget
|
|
@@ -120,11 +118,10 @@ export function evaluateConsolidationDispatch({ draftStories, specText }) {
|
|
|
120
118
|
* @param {string[]} [input.riskHeuristics] - `planning.riskHeuristics`
|
|
121
119
|
* phrases from the resolved config.
|
|
122
120
|
* @param {string} [input.planText] - Concatenated plan text the heuristics
|
|
123
|
-
* match against (tech spec + serialized tickets
|
|
121
|
+
* match against (tech spec + serialized tickets).
|
|
124
122
|
* @returns {CriticDispatchDecision}
|
|
125
123
|
*/
|
|
126
124
|
export function evaluatePremortemDispatch({
|
|
127
|
-
riskVerdict,
|
|
128
125
|
ticketCount,
|
|
129
126
|
maxTickets,
|
|
130
127
|
riskHeuristics = [],
|
|
@@ -137,13 +134,6 @@ export function evaluatePremortemDispatch({
|
|
|
137
134
|
}
|
|
138
135
|
const reasons = [];
|
|
139
136
|
|
|
140
|
-
const { overallLevel } = deriveRiskEnvelope(riskVerdict);
|
|
141
|
-
if (overallLevel === 'high') {
|
|
142
|
-
reasons.push(
|
|
143
|
-
'Risk verdict overall level is high — predicted-rework findings are worth a fresh-context pass.',
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
137
|
const count = Number.isInteger(ticketCount) ? ticketCount : 0;
|
|
148
138
|
if (count * 2 >= maxTickets) {
|
|
149
139
|
reasons.push(
|
|
@@ -172,7 +162,7 @@ export function evaluatePremortemDispatch({
|
|
|
172
162
|
critic: 'pre-mortem',
|
|
173
163
|
dispatch: false,
|
|
174
164
|
reasons: [
|
|
175
|
-
`
|
|
165
|
+
`Ticket count ${count} is under half the budget (maxTickets ${maxTickets}) and no planning.riskHeuristics phrase matches the plan text.`,
|
|
176
166
|
],
|
|
177
167
|
};
|
|
178
168
|
}
|