mandrel 2.0.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 +59 -28
- package/.agents/agents/acceptance-critic.md +9 -7
- package/.agents/agents/story-worker.md +41 -46
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +51 -44
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +32 -56
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +14 -16
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/agentrc.schema.json +34 -45
- 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 +22 -66
- 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/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 +31 -13
- 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/code-review.js +58 -168
- 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 +114 -24
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +3 -7
- 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 -75
- 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/planning/authoring-context.js +41 -40
- 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 +3 -12
- 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 +15 -32
- 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 +6 -5
- package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -3
- 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/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 -6
- 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/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 +79 -4
- 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 +98 -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 +21 -13
- package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
- package/.agents/workflows/helpers/code-review.md +12 -10
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +193 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +184 -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 +40 -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 -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/package.json +1 -1
- 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:
|
|
@@ -373,7 +439,6 @@ async function buildSeedFileModeEnvelope({
|
|
|
373
439
|
provider,
|
|
374
440
|
config,
|
|
375
441
|
settings,
|
|
376
|
-
fullContext,
|
|
377
442
|
cwd,
|
|
378
443
|
modeLabel = 'seed-file',
|
|
379
444
|
}) {
|
|
@@ -401,7 +466,6 @@ async function buildSeedFileModeEnvelope({
|
|
|
401
466
|
{ ...settings, docsContextFiles: [] },
|
|
402
467
|
{
|
|
403
468
|
epic: { id: 0, title: seedFilePath ?? 'seed', body: content },
|
|
404
|
-
fullContext,
|
|
405
469
|
github: config.github ?? null,
|
|
406
470
|
cwd,
|
|
407
471
|
},
|
|
@@ -454,7 +518,6 @@ async function buildSeedModeEnvelope({
|
|
|
454
518
|
provider,
|
|
455
519
|
config,
|
|
456
520
|
settings,
|
|
457
|
-
fullContext,
|
|
458
521
|
cwd,
|
|
459
522
|
}) {
|
|
460
523
|
if (typeof seedText !== 'string' || seedText.trim().length === 0) {
|
|
@@ -468,7 +531,6 @@ async function buildSeedModeEnvelope({
|
|
|
468
531
|
provider,
|
|
469
532
|
config,
|
|
470
533
|
settings,
|
|
471
|
-
fullContext,
|
|
472
534
|
cwd,
|
|
473
535
|
modeLabel: 'seed',
|
|
474
536
|
});
|
|
@@ -528,7 +590,6 @@ async function buildTicketsModeEnvelope({
|
|
|
528
590
|
provider,
|
|
529
591
|
config,
|
|
530
592
|
settings,
|
|
531
|
-
fullContext,
|
|
532
593
|
cwd,
|
|
533
594
|
}) {
|
|
534
595
|
if (!Array.isArray(ticketIds) || ticketIds.length === 0) {
|
|
@@ -558,7 +619,6 @@ async function buildTicketsModeEnvelope({
|
|
|
558
619
|
title: sourceTickets[0]?.title ?? 'tickets',
|
|
559
620
|
body: seed,
|
|
560
621
|
},
|
|
561
|
-
fullContext,
|
|
562
622
|
github: config.github ?? null,
|
|
563
623
|
cwd,
|
|
564
624
|
},
|
|
@@ -610,6 +670,10 @@ async function buildTicketsModeEnvelope({
|
|
|
610
670
|
/**
|
|
611
671
|
* Build the single planner-context envelope.
|
|
612
672
|
*
|
|
673
|
+
* Every mode returns through here, which makes this the one place the
|
|
674
|
+
* envelope's total size is decided — and therefore the only honest place to
|
|
675
|
+
* bound it (see {@link assertPlanContextWithinCeiling}).
|
|
676
|
+
*
|
|
613
677
|
* @param {{
|
|
614
678
|
* mode: 'seed-file'|'seed'|'tickets',
|
|
615
679
|
* seedFilePath?: string,
|
|
@@ -632,7 +696,36 @@ export async function buildPlanContext({
|
|
|
632
696
|
provider,
|
|
633
697
|
config = {},
|
|
634
698
|
settings = {},
|
|
635
|
-
|
|
699
|
+
cwd,
|
|
700
|
+
}) {
|
|
701
|
+
return assertPlanContextWithinCeiling(
|
|
702
|
+
await buildPlanContextEnvelope({
|
|
703
|
+
mode,
|
|
704
|
+
seedFilePath,
|
|
705
|
+
seedFileContent,
|
|
706
|
+
seedText,
|
|
707
|
+
ticketIds,
|
|
708
|
+
provider,
|
|
709
|
+
config,
|
|
710
|
+
settings,
|
|
711
|
+
cwd,
|
|
712
|
+
}),
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Mode dispatch for {@link buildPlanContext}. Split out so the ceiling check
|
|
718
|
+
* wraps every mode exactly once.
|
|
719
|
+
*/
|
|
720
|
+
async function buildPlanContextEnvelope({
|
|
721
|
+
mode,
|
|
722
|
+
seedFilePath,
|
|
723
|
+
seedFileContent,
|
|
724
|
+
seedText,
|
|
725
|
+
ticketIds,
|
|
726
|
+
provider,
|
|
727
|
+
config,
|
|
728
|
+
settings,
|
|
636
729
|
cwd,
|
|
637
730
|
}) {
|
|
638
731
|
if (mode === 'seed-file') {
|
|
@@ -647,7 +740,6 @@ export async function buildPlanContext({
|
|
|
647
740
|
provider,
|
|
648
741
|
config,
|
|
649
742
|
settings,
|
|
650
|
-
fullContext,
|
|
651
743
|
cwd,
|
|
652
744
|
modeLabel: 'seed-file',
|
|
653
745
|
});
|
|
@@ -658,7 +750,6 @@ export async function buildPlanContext({
|
|
|
658
750
|
provider,
|
|
659
751
|
config,
|
|
660
752
|
settings,
|
|
661
|
-
fullContext,
|
|
662
753
|
cwd,
|
|
663
754
|
});
|
|
664
755
|
}
|
|
@@ -668,7 +759,6 @@ export async function buildPlanContext({
|
|
|
668
759
|
provider,
|
|
669
760
|
config,
|
|
670
761
|
settings,
|
|
671
|
-
fullContext,
|
|
672
762
|
cwd,
|
|
673
763
|
});
|
|
674
764
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
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
5
|
* The collapsed plan flow keeps the consolidation (8.3) and pre-mortem
|
|
@@ -18,10 +18,12 @@
|
|
|
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 (8.5)**: dispatch when the
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* - **Pre-mortem (8.5)**: 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
|
|
@@ -34,7 +36,6 @@
|
|
|
34
36
|
*/
|
|
35
37
|
|
|
36
38
|
import { evaluateConsolidationPrecondition } from './consolidation-precondition.js';
|
|
37
|
-
import { deriveRiskEnvelope } from './planning-risk.js';
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Draft-story count above which the consolidation critic fires even
|
|
@@ -105,14 +106,10 @@ export function evaluateConsolidationDispatch({ draftStories, specText }) {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
/**
|
|
108
|
-
* Decide the 8.5 pre-mortem dispatch:
|
|
109
|
-
*
|
|
109
|
+
* Decide the 8.5 pre-mortem dispatch: size ≥ ½ budget, or a risk-heuristic
|
|
110
|
+
* phrase match.
|
|
110
111
|
*
|
|
111
112
|
* @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
113
|
* @param {number} input.ticketCount - Draft ticket count (0 in the
|
|
117
114
|
* single-delivery shape — no tickets exist).
|
|
118
115
|
* @param {number} input.maxTickets - The reviewability budget
|
|
@@ -120,11 +117,10 @@ export function evaluateConsolidationDispatch({ draftStories, specText }) {
|
|
|
120
117
|
* @param {string[]} [input.riskHeuristics] - `planning.riskHeuristics`
|
|
121
118
|
* phrases from the resolved config.
|
|
122
119
|
* @param {string} [input.planText] - Concatenated plan text the heuristics
|
|
123
|
-
* match against (tech spec + serialized tickets
|
|
120
|
+
* match against (tech spec + serialized tickets).
|
|
124
121
|
* @returns {CriticDispatchDecision}
|
|
125
122
|
*/
|
|
126
123
|
export function evaluatePremortemDispatch({
|
|
127
|
-
riskVerdict,
|
|
128
124
|
ticketCount,
|
|
129
125
|
maxTickets,
|
|
130
126
|
riskHeuristics = [],
|
|
@@ -137,13 +133,6 @@ export function evaluatePremortemDispatch({
|
|
|
137
133
|
}
|
|
138
134
|
const reasons = [];
|
|
139
135
|
|
|
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
136
|
const count = Number.isInteger(ticketCount) ? ticketCount : 0;
|
|
148
137
|
if (count * 2 >= maxTickets) {
|
|
149
138
|
reasons.push(
|
|
@@ -172,7 +161,7 @@ export function evaluatePremortemDispatch({
|
|
|
172
161
|
critic: 'pre-mortem',
|
|
173
162
|
dispatch: false,
|
|
174
163
|
reasons: [
|
|
175
|
-
`
|
|
164
|
+
`Ticket count ${count} is under half the budget (maxTickets ${maxTickets}) and no planning.riskHeuristics phrase matches the plan text.`,
|
|
176
165
|
],
|
|
177
166
|
};
|
|
178
167
|
}
|
|
@@ -47,13 +47,12 @@ function resolveRiskHeuristics(config = {}) {
|
|
|
47
47
|
* - Consolidation: skipped outright when `tickets` is null/absent (the
|
|
48
48
|
* single-delivery shape authors no draft tickets); otherwise the
|
|
49
49
|
* deterministic precondition + size/divergence conditions.
|
|
50
|
-
* - Pre-mortem:
|
|
51
|
-
*
|
|
52
|
-
* the
|
|
50
|
+
* - Pre-mortem: ticket count at least half `maxTickets`, OR any
|
|
51
|
+
* `planning.riskHeuristics` phrase matching the plan text. Story #4542
|
|
52
|
+
* retired its authored-risk-level condition with the verdict itself.
|
|
53
53
|
*
|
|
54
54
|
* @param {{
|
|
55
55
|
* techSpecContent: string,
|
|
56
|
-
* riskVerdict: { summary?: string },
|
|
57
56
|
* tickets?: Array<object>|null,
|
|
58
57
|
* config?: object,
|
|
59
58
|
* }} args
|
|
@@ -64,7 +63,6 @@ function resolveRiskHeuristics(config = {}) {
|
|
|
64
63
|
*/
|
|
65
64
|
export function evaluatePlanCritics({
|
|
66
65
|
techSpecContent,
|
|
67
|
-
riskVerdict,
|
|
68
66
|
tickets = null,
|
|
69
67
|
config = {},
|
|
70
68
|
}) {
|
|
@@ -84,14 +82,12 @@ export function evaluatePlanCritics({
|
|
|
84
82
|
});
|
|
85
83
|
|
|
86
84
|
const premortem = evaluatePremortemDispatch({
|
|
87
|
-
riskVerdict,
|
|
88
85
|
ticketCount: ticketList?.length ?? 0,
|
|
89
86
|
maxTickets: getLimits(config).maxTickets,
|
|
90
87
|
riskHeuristics: resolveRiskHeuristics(config),
|
|
91
88
|
planText: [
|
|
92
89
|
techSpecContent ?? '',
|
|
93
90
|
ticketList ? JSON.stringify(ticketList) : '',
|
|
94
|
-
riskVerdict?.summary ?? '',
|
|
95
91
|
].join('\n'),
|
|
96
92
|
});
|
|
97
93
|
|