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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* phases/review-core.js — the shared Story-scope review spine.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `phases/code-review.js` (Story #4603). `runStoryReviewCore` is
|
|
5
|
+
* the one implementation both close paths call `runCodeReview` through — the
|
|
6
|
+
* epic-attached phase (`code-review.js#runStoryCodeReview`) and the standalone
|
|
7
|
+
* v2 path (`single-story-close/phases/code-review.js#runStoryScopeReview`) —
|
|
8
|
+
* so it belongs to neither and lives here rather than inside one path's phase
|
|
9
|
+
* file (Story #3653 established the shared-spine contract).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { gitSpawn } from '../../../git-utils.js';
|
|
13
|
+
import { computeChangeSet } from '../../change-set.js';
|
|
14
|
+
import { runCodeReview } from '../../code-review.js';
|
|
15
|
+
import { runLocalLensReview } from './local-lens-review.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Invoke `runCodeReviewFn` with the canonical Story-scope envelope and return
|
|
19
|
+
* the raw result.
|
|
20
|
+
*
|
|
21
|
+
* The caller is responsible for error handling and result interpretation —
|
|
22
|
+
* this function propagates throws rather than swallowing them, because the
|
|
23
|
+
* two callers have different advisory postures:
|
|
24
|
+
*
|
|
25
|
+
* - Epic-attached close: swallows throws (non-blocking advisory, same as
|
|
26
|
+
* `refresh.js`).
|
|
27
|
+
* - Standalone close: propagates throws (a review failure stops the close).
|
|
28
|
+
*
|
|
29
|
+
* Review depth is not passed in: `runCodeReview` derives it from the changed
|
|
30
|
+
* files — their sensitive-path intersection plus their count (Story #4542, which
|
|
31
|
+
* retired the planner-authored risk envelope this spine used to forward). Depth
|
|
32
|
+
* remains an **input-only** signal: it tells the provider how thorough to be and
|
|
33
|
+
* never alters the review's output envelope or the posted structured-comment
|
|
34
|
+
* body.
|
|
35
|
+
*
|
|
36
|
+
* Story #4593 — this spine is the **single injection point** for the change set.
|
|
37
|
+
* It enumerates `baseRef...headRef` exactly once via {@link computeChangeSet}
|
|
38
|
+
* and threads the resulting list into both the local-lens pass and
|
|
39
|
+
* `runCodeReview`, which otherwise each enumerated the diff for themselves. Both
|
|
40
|
+
* consumers ultimately route through `deriveChangeLevel`, so feeding them one
|
|
41
|
+
* list is what makes the lens roster and the review depth provably agree about
|
|
42
|
+
* what changed — even when a commit lands between the two calls.
|
|
43
|
+
*
|
|
44
|
+
* Story #4603 — the invariant now holds on the failure path too: an unenumerable
|
|
45
|
+
* diff injects an explicit `null`, and both consumers distinguish that from
|
|
46
|
+
* `undefined` ("nobody enumerated") rather than re-spawning git.
|
|
47
|
+
*
|
|
48
|
+
* @param {{
|
|
49
|
+
* storyId: number|string,
|
|
50
|
+
* baseRef: string,
|
|
51
|
+
* headRef: string,
|
|
52
|
+
* commentTargetId?: number|null,
|
|
53
|
+
* provider: object,
|
|
54
|
+
* progress: (tag: string, msg: string) => void,
|
|
55
|
+
* progressTag?: string,
|
|
56
|
+
* gitSpawnFn?: import('../../change-set.js').GitSpawnFn,
|
|
57
|
+
* computeChangeSetFn?: typeof computeChangeSet,
|
|
58
|
+
* runCodeReviewFn?: typeof runCodeReview,
|
|
59
|
+
* runLocalLensReviewFn?: typeof runLocalLensReview,
|
|
60
|
+
* }} args
|
|
61
|
+
* @returns {Promise<object>} Raw result envelope from `runCodeReview`, augmented
|
|
62
|
+
* with a `localLensReview` field carrying the Story-scope local-lens pass
|
|
63
|
+
* outcome (Epic #4405, Story #4409) and the `changeSet` this run computed
|
|
64
|
+
* (Story #4593).
|
|
65
|
+
*/
|
|
66
|
+
export async function runStoryReviewCore({
|
|
67
|
+
storyId,
|
|
68
|
+
baseRef,
|
|
69
|
+
headRef,
|
|
70
|
+
commentTargetId = null,
|
|
71
|
+
provider,
|
|
72
|
+
progress,
|
|
73
|
+
progressTag = 'CODE-REVIEW',
|
|
74
|
+
gitSpawnFn = gitSpawn,
|
|
75
|
+
computeChangeSetFn = computeChangeSet,
|
|
76
|
+
runCodeReviewFn = runCodeReview,
|
|
77
|
+
runLocalLensReviewFn = runLocalLensReview,
|
|
78
|
+
}) {
|
|
79
|
+
const storyIdNum = Number(storyId);
|
|
80
|
+
|
|
81
|
+
// The one enumeration per close run. Every consumer below is injected from
|
|
82
|
+
// this list; none of them re-derives the diff. `files` is `null` when the
|
|
83
|
+
// diff is unenumerable — an explicit "already tried" signal both consumers
|
|
84
|
+
// honour without retrying (Story #4603).
|
|
85
|
+
const changeSet = computeChangeSetFn({ baseRef, headRef, gitSpawnFn });
|
|
86
|
+
|
|
87
|
+
const opts = {
|
|
88
|
+
scope: 'story',
|
|
89
|
+
ticketId: storyIdNum,
|
|
90
|
+
baseRef,
|
|
91
|
+
headRef,
|
|
92
|
+
provider,
|
|
93
|
+
changedFiles: changeSet.files,
|
|
94
|
+
gitSpawnFn,
|
|
95
|
+
logger: {
|
|
96
|
+
info: (m) => progress(progressTag, m),
|
|
97
|
+
warn: (m) => progress(progressTag, `⚠️ ${m}`),
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
if (commentTargetId != null) {
|
|
101
|
+
opts.commentTargetId = commentTargetId;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Shift-left local-lens pass (Epic #4405). Runs matched local lenses at
|
|
105
|
+
// `light` depth against the actual Story diff, inside this close-subprocess
|
|
106
|
+
// spine so the maker never grades its own work. Advisory — it never blocks
|
|
107
|
+
// the close and its outcome rides on the returned envelope for downstream
|
|
108
|
+
// consumers.
|
|
109
|
+
const localLensReview = await runLocalLensReviewFn({
|
|
110
|
+
baseRef,
|
|
111
|
+
headRef,
|
|
112
|
+
changedFiles: changeSet.files,
|
|
113
|
+
progress,
|
|
114
|
+
progressTag,
|
|
115
|
+
gitSpawnFn,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result = await runCodeReviewFn(opts);
|
|
119
|
+
return { ...result, localLensReview, changeSet };
|
|
120
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* story-deliver-terminal.js — the one terminal envelope every Story
|
|
3
|
+
* close-and-land invocation emits, and the shared next-command vocabulary
|
|
4
|
+
* (Story #4543).
|
|
5
|
+
*
|
|
6
|
+
* Before this module the delivery tail had **two** divergent return
|
|
7
|
+
* contracts, both prose and neither validated:
|
|
8
|
+
* `.agents/workflows/helpers/deliver-story.md` defined one shape and
|
|
9
|
+
* `.agents/agents/story-worker.md` a different one, so a caller could not
|
|
10
|
+
* distinguish a landed Story from a parked one without re-probing GitHub.
|
|
11
|
+
* `story-deliver-terminal.schema.json` is now the SSOT both docs reference
|
|
12
|
+
* rather than restate, and this module is its only writer.
|
|
13
|
+
*
|
|
14
|
+
* Two exports carry the contract:
|
|
15
|
+
*
|
|
16
|
+
* - {@link buildTerminalEnvelope} assembles and **validates** the envelope.
|
|
17
|
+
* It throws on a schema violation rather than emitting a malformed
|
|
18
|
+
* terminal: a silently-wrong terminal is exactly the failure this Story
|
|
19
|
+
* exists to eliminate, so failing loudly at the writer is the point.
|
|
20
|
+
* - {@link NEXT_COMMANDS} is the next-command vocabulary shared with
|
|
21
|
+
* `deliver-recover.js`, so a `pending` envelope and a recovery probe
|
|
22
|
+
* name the same command for the same state instead of inventing two
|
|
23
|
+
* dialects for one condition.
|
|
24
|
+
*
|
|
25
|
+
* `TERMINAL_EXIT_CODES` maps status → process exit code. `pending` gets its
|
|
26
|
+
* **own** code (3) precisely so a caller can tell "slow CI, resume me" from
|
|
27
|
+
* "hard block, come look" without parsing stdout — the distinction the
|
|
28
|
+
* pre-#4543 pipeline collapsed by treating budget exhaustion as a block.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { readFileSync } from 'node:fs';
|
|
32
|
+
import path from 'node:path';
|
|
33
|
+
import { fileURLToPath } from 'node:url';
|
|
34
|
+
|
|
35
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
36
|
+
import addFormats from 'ajv-formats';
|
|
37
|
+
|
|
38
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const SCHEMA_PATH = path.resolve(
|
|
40
|
+
__dirname,
|
|
41
|
+
'..',
|
|
42
|
+
'..',
|
|
43
|
+
'..',
|
|
44
|
+
'schemas',
|
|
45
|
+
'story-deliver-terminal.schema.json',
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export const TERMINAL_ENVELOPE_KIND = 'story-deliver-terminal';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* status → process exit code.
|
|
52
|
+
*
|
|
53
|
+
* `pending` is **3**, not 0 and not 1. Zero would tell a headless caller the
|
|
54
|
+
* Story landed when it did not; one would conflate a resumable slow-CI wait
|
|
55
|
+
* with a hard block and send the operator to diagnose branch protection that
|
|
56
|
+
* is working fine. A distinct code is what lets `/deliver` resume without
|
|
57
|
+
* classifying.
|
|
58
|
+
*/
|
|
59
|
+
export const TERMINAL_EXIT_CODES = Object.freeze({
|
|
60
|
+
landed: 0,
|
|
61
|
+
pending: 3,
|
|
62
|
+
blocked: 1,
|
|
63
|
+
failed: 1,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export const TERMINAL_STATUSES = Object.freeze([
|
|
67
|
+
'landed',
|
|
68
|
+
'pending',
|
|
69
|
+
'blocked',
|
|
70
|
+
'failed',
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The shared next-command vocabulary. Every producer of a "what now?"
|
|
75
|
+
* answer — the `pending` terminal envelope and `deliver-recover.js` —
|
|
76
|
+
* builds its command from here, so the two surfaces never drift into
|
|
77
|
+
* naming different commands for the same observed state.
|
|
78
|
+
*/
|
|
79
|
+
export const NEXT_COMMANDS = Object.freeze({
|
|
80
|
+
/**
|
|
81
|
+
* Resume a bounded merge wait that expired with the PR still in flight.
|
|
82
|
+
*
|
|
83
|
+
* `--wait` is load-bearing: without it the confirm CLI probes once and
|
|
84
|
+
* answers `pending` again, so the cumulative-budget give-up never fires and
|
|
85
|
+
* a wedged PR is never escalated to anyone.
|
|
86
|
+
*/
|
|
87
|
+
resumeLand: (storyId) =>
|
|
88
|
+
`node .agents/scripts/single-story-confirm-merge.js --story ${storyId} --wait`,
|
|
89
|
+
/** Confirm a merged-but-mislabelled Story (the idempotent flip + tail). */
|
|
90
|
+
confirmMerge: (storyId) =>
|
|
91
|
+
`node .agents/scripts/single-story-confirm-merge.js --story ${storyId}`,
|
|
92
|
+
/** Enter the red-CI fix loop against the failing PR. */
|
|
93
|
+
watchCi: (storyId, prNumber) =>
|
|
94
|
+
`node .agents/scripts/pr-watch-with-update.js --pr ${prNumber} --story ${storyId}`,
|
|
95
|
+
/** Re-run close for a Story whose PR was never opened. */
|
|
96
|
+
close: (storyId) =>
|
|
97
|
+
`node .agents/scripts/single-story-close.js --story ${storyId}`,
|
|
98
|
+
/** Resume implementation in the Story worktree. */
|
|
99
|
+
implement: (storyId) =>
|
|
100
|
+
`node .agents/scripts/single-story-init.js --story ${storyId}`,
|
|
101
|
+
/** Re-assert a drifted Projects v2 Status column. */
|
|
102
|
+
resync: (storyId) =>
|
|
103
|
+
`node .agents/scripts/resync-status-column.js --story ${storyId}`,
|
|
104
|
+
/** Probe a stranded Story and print its single next command. */
|
|
105
|
+
recover: (storyId) =>
|
|
106
|
+
`node .agents/scripts/deliver-recover.js --story ${storyId}`,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/** @type {Function|null} */
|
|
110
|
+
let _validator = null;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Compile (once) and return the terminal-envelope validator.
|
|
114
|
+
*
|
|
115
|
+
* @returns {Function}
|
|
116
|
+
*/
|
|
117
|
+
function getValidator() {
|
|
118
|
+
if (_validator) return _validator;
|
|
119
|
+
const schema = JSON.parse(readFileSync(SCHEMA_PATH, 'utf8'));
|
|
120
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
121
|
+
addFormats(ajv);
|
|
122
|
+
_validator = ajv.compile(schema);
|
|
123
|
+
return _validator;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Validate a candidate envelope against the shipped schema.
|
|
128
|
+
*
|
|
129
|
+
* @param {object} envelope
|
|
130
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
131
|
+
*/
|
|
132
|
+
export function validateTerminalEnvelope(envelope) {
|
|
133
|
+
const validate = getValidator();
|
|
134
|
+
const valid = validate(envelope);
|
|
135
|
+
if (valid) return { valid: true, errors: [] };
|
|
136
|
+
const errors = (validate.errors ?? []).map(
|
|
137
|
+
(e) => `${e.instancePath || '/'} ${e.message}`,
|
|
138
|
+
);
|
|
139
|
+
return { valid: false, errors };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Drop `undefined`-valued keys so the schema's `additionalProperties: false`
|
|
144
|
+
* and its nullable unions both stay satisfiable from one optional-argument
|
|
145
|
+
* builder signature.
|
|
146
|
+
*
|
|
147
|
+
* @param {object} obj
|
|
148
|
+
* @returns {object}
|
|
149
|
+
*/
|
|
150
|
+
function compact(obj) {
|
|
151
|
+
const out = {};
|
|
152
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
153
|
+
if (v !== undefined) out[k] = v;
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Assemble the terminal envelope and validate it before returning.
|
|
160
|
+
*
|
|
161
|
+
* Throws a `TypeError` naming the schema violations when the assembled
|
|
162
|
+
* object does not validate. That is deliberate: the whole point of the
|
|
163
|
+
* envelope is that a caller can trust its status without re-probing
|
|
164
|
+
* GitHub, so emitting an unvalidated one would reintroduce the ambiguity
|
|
165
|
+
* this replaces.
|
|
166
|
+
*
|
|
167
|
+
* @param {object} args
|
|
168
|
+
* @param {number} args.storyId
|
|
169
|
+
* @param {'landed'|'pending'|'blocked'|'failed'} args.status
|
|
170
|
+
* @param {string} args.phase
|
|
171
|
+
* @param {string} [args.storyBranch]
|
|
172
|
+
* @param {string} [args.baseBranch]
|
|
173
|
+
* @param {object|null} [args.pr]
|
|
174
|
+
* @param {object} [args.gates]
|
|
175
|
+
* @param {object|null} [args.tail]
|
|
176
|
+
* @param {object|null} [args.blocked]
|
|
177
|
+
* @param {object|null} [args.failure]
|
|
178
|
+
* @param {string|null} [args.nextCommand]
|
|
179
|
+
* @param {number} args.elapsedSeconds
|
|
180
|
+
* @param {object|null} [args.waitBudget]
|
|
181
|
+
* @param {string} [args.timestamp]
|
|
182
|
+
* @returns {object} The validated envelope.
|
|
183
|
+
*/
|
|
184
|
+
export function buildTerminalEnvelope({
|
|
185
|
+
storyId,
|
|
186
|
+
status,
|
|
187
|
+
phase,
|
|
188
|
+
storyBranch,
|
|
189
|
+
baseBranch,
|
|
190
|
+
pr,
|
|
191
|
+
gates,
|
|
192
|
+
tail,
|
|
193
|
+
blocked,
|
|
194
|
+
failure,
|
|
195
|
+
nextCommand,
|
|
196
|
+
elapsedSeconds = 0,
|
|
197
|
+
waitBudget,
|
|
198
|
+
timestamp = new Date().toISOString(),
|
|
199
|
+
}) {
|
|
200
|
+
const envelope = compact({
|
|
201
|
+
kind: TERMINAL_ENVELOPE_KIND,
|
|
202
|
+
storyId: Number(storyId),
|
|
203
|
+
status,
|
|
204
|
+
phase,
|
|
205
|
+
storyBranch: storyBranch ?? null,
|
|
206
|
+
baseBranch: baseBranch ?? null,
|
|
207
|
+
pr: pr ?? null,
|
|
208
|
+
gates,
|
|
209
|
+
tail: tail ?? null,
|
|
210
|
+
blocked: blocked ?? null,
|
|
211
|
+
failure: failure ?? null,
|
|
212
|
+
nextCommand: nextCommand ?? null,
|
|
213
|
+
elapsedSeconds: Math.max(0, Number(elapsedSeconds) || 0),
|
|
214
|
+
waitBudget: waitBudget ?? null,
|
|
215
|
+
timestamp,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const { valid, errors } = validateTerminalEnvelope(envelope);
|
|
219
|
+
if (!valid) {
|
|
220
|
+
throw new TypeError(
|
|
221
|
+
`buildTerminalEnvelope: assembled envelope violates story-deliver-terminal.schema.json:\n` +
|
|
222
|
+
errors.map((e) => ` - ${e}`).join('\n'),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return envelope;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Resolve the process exit code for a terminal envelope.
|
|
230
|
+
*
|
|
231
|
+
* @param {object} envelope
|
|
232
|
+
* @returns {number}
|
|
233
|
+
*/
|
|
234
|
+
export function exitCodeForTerminal(envelope) {
|
|
235
|
+
return TERMINAL_EXIT_CODES[envelope?.status] ?? 1;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** The markers a caller scans stdout for to recover the envelope. */
|
|
239
|
+
export const TERMINAL_BEGIN_MARKER = '--- STORY DELIVER TERMINAL ---';
|
|
240
|
+
export const TERMINAL_END_MARKER = '--- END TERMINAL ---';
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Write a terminal envelope to stdout, between its markers.
|
|
244
|
+
*
|
|
245
|
+
* **Deliberately not `Logger.info`.** The envelope is this CLI's
|
|
246
|
+
* machine-readable contract — every invocation emits exactly ONE, and a
|
|
247
|
+
* headless caller parses it out of stdout to decide what happened.
|
|
248
|
+
* `Logger.info` is level-gated, so under the documented
|
|
249
|
+
* `AGENT_LOG_LEVEL=silent` (§ 1.H) the envelope silently vanished and the
|
|
250
|
+
* caller got a bare exit code: precisely the "no envelope at all" outcome
|
|
251
|
+
* Story #4543 exists to remove. A contract payload must not be suppressible
|
|
252
|
+
* by a verbosity knob.
|
|
253
|
+
*
|
|
254
|
+
* Single home for the marker format so the four emit sites (the runner's
|
|
255
|
+
* terminal, the close CLI's failed-terminal catch, and both confirm-CLI
|
|
256
|
+
* paths) cannot drift apart.
|
|
257
|
+
*
|
|
258
|
+
* @param {object} envelope
|
|
259
|
+
* @param {{ write?: (s: string) => void }} [opts] `write` is a test seam.
|
|
260
|
+
* @returns {void}
|
|
261
|
+
*/
|
|
262
|
+
export function emitTerminalEnvelope(
|
|
263
|
+
envelope,
|
|
264
|
+
{ write = (s) => process.stdout.write(s) } = {},
|
|
265
|
+
) {
|
|
266
|
+
write(
|
|
267
|
+
`\n${TERMINAL_BEGIN_MARKER}\n${JSON.stringify(envelope, null, 2)}\n${TERMINAL_END_MARKER}\n`,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Map a `runConfirmMergePhase` outcome onto the schema-validated terminal
|
|
273
|
+
* envelope (Story #4543). One writer, one shape — the two prose contracts
|
|
274
|
+
* this replaces disagreed with each other precisely because each surface
|
|
275
|
+
* assembled its own.
|
|
276
|
+
*
|
|
277
|
+
* @returns {object} A validated `story-deliver-terminal` envelope.
|
|
278
|
+
*/
|
|
279
|
+
export function terminalFromWaitOutcome({
|
|
280
|
+
waitOutcome,
|
|
281
|
+
storyId,
|
|
282
|
+
storyBranch,
|
|
283
|
+
baseBranch,
|
|
284
|
+
prNumber,
|
|
285
|
+
prUrl,
|
|
286
|
+
autoMergeEnabled,
|
|
287
|
+
gates,
|
|
288
|
+
elapsedSeconds,
|
|
289
|
+
}) {
|
|
290
|
+
const prBase = {
|
|
291
|
+
number: prNumber,
|
|
292
|
+
url: prUrl ?? null,
|
|
293
|
+
autoMergeEnabled: Boolean(autoMergeEnabled),
|
|
294
|
+
};
|
|
295
|
+
const common = {
|
|
296
|
+
storyId,
|
|
297
|
+
storyBranch,
|
|
298
|
+
baseBranch,
|
|
299
|
+
gates,
|
|
300
|
+
elapsedSeconds,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
if (waitOutcome.terminal === 'landed') {
|
|
304
|
+
return buildTerminalEnvelope({
|
|
305
|
+
...common,
|
|
306
|
+
status: 'landed',
|
|
307
|
+
phase: 'post-land',
|
|
308
|
+
pr: {
|
|
309
|
+
...prBase,
|
|
310
|
+
state: 'MERGED',
|
|
311
|
+
// The observed rollup, not an assumed 'success' — a merge can land by
|
|
312
|
+
// admin override or with non-required checks red.
|
|
313
|
+
checksStatus: waitOutcome.prProbe?.checksStatus ?? null,
|
|
314
|
+
},
|
|
315
|
+
tail: waitOutcome.tail,
|
|
316
|
+
nextCommand: null,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (waitOutcome.terminal === 'pending') {
|
|
321
|
+
return buildTerminalEnvelope({
|
|
322
|
+
...common,
|
|
323
|
+
status: 'pending',
|
|
324
|
+
phase: 'confirm-merge',
|
|
325
|
+
pr: {
|
|
326
|
+
...prBase,
|
|
327
|
+
state: waitOutcome.prProbe?.state ?? 'OPEN',
|
|
328
|
+
checksStatus: waitOutcome.prProbe?.checksStatus ?? null,
|
|
329
|
+
},
|
|
330
|
+
waitBudget: waitOutcome.waitBudget,
|
|
331
|
+
nextCommand: NEXT_COMMANDS.resumeLand(storyId),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// blocked — the classifier already named the class and the friction
|
|
336
|
+
// comment already carries the class-specific remediation, so the next
|
|
337
|
+
// command mirrors it rather than inventing a second opinion.
|
|
338
|
+
const nextCommand =
|
|
339
|
+
waitOutcome.blockClass === 'checks-failed'
|
|
340
|
+
? NEXT_COMMANDS.watchCi(storyId, prNumber)
|
|
341
|
+
: waitOutcome.blockClass === 'merged-flip-failed'
|
|
342
|
+
? NEXT_COMMANDS.confirmMerge(storyId)
|
|
343
|
+
: NEXT_COMMANDS.recover(storyId);
|
|
344
|
+
return buildTerminalEnvelope({
|
|
345
|
+
...common,
|
|
346
|
+
status: 'blocked',
|
|
347
|
+
phase: 'confirm-merge',
|
|
348
|
+
pr: {
|
|
349
|
+
...prBase,
|
|
350
|
+
state: waitOutcome.prProbe?.state ?? null,
|
|
351
|
+
checksStatus: waitOutcome.prProbe?.checksStatus ?? null,
|
|
352
|
+
},
|
|
353
|
+
blocked: {
|
|
354
|
+
blockClass: waitOutcome.blockClass,
|
|
355
|
+
reason: waitOutcome.reason,
|
|
356
|
+
frictionCommentId: waitOutcome.frictionCommentId ?? null,
|
|
357
|
+
},
|
|
358
|
+
nextCommand,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
@@ -72,15 +72,65 @@ export async function gatherStoryFrictionSignals(storyId, config) {
|
|
|
72
72
|
return signals;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Render the empty-roll-up line.
|
|
77
|
+
*
|
|
78
|
+
* Story #4578 — an empty roll-up over a multi-Story run must NOT read as
|
|
79
|
+
* success. The pre-#4578 text ("No friction signals — nothing to follow up")
|
|
80
|
+
* was *truthful* about the stream and *false* about the run: a 7-Story
|
|
81
|
+
* delivery containing a mid-run git outage, a parked worker, and a
|
|
82
|
+
* four-round acceptance critic rendered byte-identically to a genuinely
|
|
83
|
+
* clean run. An operator cannot tell "nothing went wrong" from "the
|
|
84
|
+
* telemetry never fired", and the second is likeliest exactly when the run
|
|
85
|
+
* went worst.
|
|
86
|
+
*
|
|
87
|
+
* So the line is a function of `storyCount`:
|
|
88
|
+
* - `storyCount <= 1` → the honest, quiet reading is retained. A single
|
|
89
|
+
* Story that emitted nothing plausibly *was* clean, and crying wolf on
|
|
90
|
+
* every clean Story is how a warning channel gets tuned out.
|
|
91
|
+
* - `storyCount > 1` → zero signals across N Stories is a **claim**, and
|
|
92
|
+
* the surrounding text says so and names the two readings, rather than
|
|
93
|
+
* asserting the flattering one.
|
|
94
|
+
*
|
|
95
|
+
* This mirrors the sibling precedent in `run-epilogue.js`'s
|
|
96
|
+
* `renderDiffLines`, which refuses to let an unresolvable base diff render
|
|
97
|
+
* as "0 changed files".
|
|
98
|
+
*
|
|
99
|
+
* @param {number} storyCount
|
|
100
|
+
* @returns {string[]}
|
|
101
|
+
*/
|
|
102
|
+
function renderEmptyRollupLines(storyCount) {
|
|
103
|
+
if (storyCount <= 1) {
|
|
104
|
+
return ['_No friction signals — nothing to follow up._'];
|
|
105
|
+
}
|
|
106
|
+
return [
|
|
107
|
+
`> ⚠️ **0 friction signals across ${storyCount} Stories — this is a claim, not a clean bill of health.**`,
|
|
108
|
+
'> Either the run was genuinely friction-free, or telemetry never fired.',
|
|
109
|
+
'> An empty stream is indistinguishable from a clean run, and it is least',
|
|
110
|
+
'> likely to fill exactly when a run is going badly and the agent is busy.',
|
|
111
|
+
'> The runtime emits friction from its own observables (`agent::blocked`',
|
|
112
|
+
'> transitions, failed closes, exhausted merge waits) — so zero here also',
|
|
113
|
+
'> means none of those fired. If the run had friction you can name, that',
|
|
114
|
+
'> gap is itself the follow-up worth filing.',
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
|
|
75
118
|
/**
|
|
76
119
|
* @param {{
|
|
77
120
|
* storyId: number,
|
|
78
121
|
* proposals: object,
|
|
79
122
|
* graduated: object,
|
|
80
|
-
*
|
|
123
|
+
* storyCount?: number,
|
|
124
|
+
* }} args - `storyCount` (default 1) is how many Stories the roll-up spans;
|
|
125
|
+
* it decides whether an empty result reads as quiet or as a flagged claim.
|
|
81
126
|
* @returns {string}
|
|
82
127
|
*/
|
|
83
|
-
export function buildFollowUpsCommentBody({
|
|
128
|
+
export function buildFollowUpsCommentBody({
|
|
129
|
+
storyId,
|
|
130
|
+
proposals,
|
|
131
|
+
graduated,
|
|
132
|
+
storyCount = 1,
|
|
133
|
+
}) {
|
|
84
134
|
const filed = Array.isArray(graduated?.filed) ? graduated.filed : [];
|
|
85
135
|
const framework = proposals?.framework ?? [];
|
|
86
136
|
const consumer = proposals?.consumer ?? [];
|
|
@@ -124,7 +174,7 @@ export function buildFollowUpsCommentBody({ storyId, proposals, graduated }) {
|
|
|
124
174
|
consumer.length === 0 &&
|
|
125
175
|
discarded.length === 0
|
|
126
176
|
) {
|
|
127
|
-
lines.push(
|
|
177
|
+
lines.push(...renderEmptyRollupLines(storyCount));
|
|
128
178
|
lines.push('');
|
|
129
179
|
}
|
|
130
180
|
lines.push('```json');
|
|
@@ -132,6 +182,7 @@ export function buildFollowUpsCommentBody({ storyId, proposals, graduated }) {
|
|
|
132
182
|
JSON.stringify(
|
|
133
183
|
{
|
|
134
184
|
storyId,
|
|
185
|
+
storyCount,
|
|
135
186
|
framework: framework.map((i) => i.category),
|
|
136
187
|
consumer: consumer.map((i) => i.category),
|
|
137
188
|
discarded: discarded.map((i) => i.category),
|
|
@@ -139,6 +190,15 @@ export function buildFollowUpsCommentBody({ storyId, proposals, graduated }) {
|
|
|
139
190
|
category: i.category,
|
|
140
191
|
url: i.url ?? null,
|
|
141
192
|
})),
|
|
193
|
+
// Story #4578 — an empty roll-up over N>1 Stories is a claim worth
|
|
194
|
+
// flagging, not a success. Machine-readable twin of the warning
|
|
195
|
+
// prose so a caller need not regex the body.
|
|
196
|
+
emptyRollupSuspect:
|
|
197
|
+
storyCount > 1 &&
|
|
198
|
+
filed.length === 0 &&
|
|
199
|
+
framework.length === 0 &&
|
|
200
|
+
consumer.length === 0 &&
|
|
201
|
+
discarded.length === 0,
|
|
142
202
|
},
|
|
143
203
|
null,
|
|
144
204
|
2,
|
|
@@ -149,8 +209,18 @@ export function buildFollowUpsCommentBody({ storyId, proposals, graduated }) {
|
|
|
149
209
|
}
|
|
150
210
|
|
|
151
211
|
/**
|
|
152
|
-
* Capture and persist Story follow-ups. Never throws —
|
|
153
|
-
* because follow-up filing flaked.
|
|
212
|
+
* Capture and persist Story follow-ups. Never throws — the land must not
|
|
213
|
+
* fail because follow-up filing flaked.
|
|
214
|
+
*
|
|
215
|
+
* Story #4543 retired the `captureFollowUpsAfterConfirm` action-gate wrapper
|
|
216
|
+
* (and its `withConfirmFollowUps` sibling) that used to front this function.
|
|
217
|
+
* Re-deriving "did the merge land?" from a confirmation envelope's `action`
|
|
218
|
+
* field was the coupling that made close-and-land — the DEFAULT path — skip
|
|
219
|
+
* capture entirely: the gate only opened on the standalone CLI's `done`, and
|
|
220
|
+
* a belated manual confirm could not backfill because the Story was already
|
|
221
|
+
* `agent::done` (confirm returns `noop`, the gate never opens). The shared
|
|
222
|
+
* land tail (`single-story-close/phases/post-land.js`) now calls this
|
|
223
|
+
* directly, after the merge is already confirmed.
|
|
154
224
|
*
|
|
155
225
|
* @param {object} args
|
|
156
226
|
* @param {number} args.storyId
|
|
@@ -160,15 +230,6 @@ export function buildFollowUpsCommentBody({ storyId, proposals, graduated }) {
|
|
|
160
230
|
* @param {(tag: string, msg: string) => void} [args.progress]
|
|
161
231
|
* @returns {Promise<object>}
|
|
162
232
|
*/
|
|
163
|
-
/**
|
|
164
|
-
* Capture follow-ups only when merge confirm landed (`action === 'done'`).
|
|
165
|
-
* One-liner seam for the confirm-merge CLI.
|
|
166
|
-
*/
|
|
167
|
-
export async function captureFollowUpsAfterConfirm(confirmation, ctx) {
|
|
168
|
-
if (confirmation?.action !== 'done') return null;
|
|
169
|
-
return captureStoryFollowUps(ctx);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
233
|
export async function captureStoryFollowUps({
|
|
173
234
|
storyId,
|
|
174
235
|
provider,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Logger } from '../Logger.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
STATE_LABELS,
|
|
4
|
+
transitionTicketState,
|
|
5
|
+
upsertStructuredComment,
|
|
6
|
+
} from './ticketing.js';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Fail closed when the repository remote cannot be verified. The Story is
|
|
@@ -30,13 +33,14 @@ export async function handleRemoteVerificationFailure({
|
|
|
30
33
|
`[single-story-init] failed to post remote-verification friction: ${err?.message ?? err}`,
|
|
31
34
|
);
|
|
32
35
|
}
|
|
36
|
+
// Story #4539 — the canonical mutator. This is the path where the
|
|
37
|
+
// skipped Projects v2 column sync (Story #2548) visibly drifts: the
|
|
38
|
+
// Story is still agent::ready (To Do) when the remote probe fails, so
|
|
39
|
+
// a direct label write leaves the board reading To Do for a blocked
|
|
40
|
+
// Story. single-story-init.js's own comment explains exactly why this
|
|
41
|
+
// must not bypass the mutator.
|
|
33
42
|
try {
|
|
34
|
-
await provider
|
|
35
|
-
labels: {
|
|
36
|
-
add: [AGENT_LABELS.BLOCKED],
|
|
37
|
-
remove: [AGENT_LABELS.READY, AGENT_LABELS.EXECUTING],
|
|
38
|
-
},
|
|
39
|
-
});
|
|
43
|
+
await transitionTicketState(provider, storyId, STATE_LABELS.BLOCKED, {});
|
|
40
44
|
} catch (err) {
|
|
41
45
|
Logger.warn(
|
|
42
46
|
`[single-story-init] failed to block Story after remote verification: ${err?.message ?? err}`,
|