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
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* analyze-execution.js — single writer of the structured perf-summary
|
|
5
|
-
* comments (Epic #1030 / Story #1123).
|
|
6
|
-
*
|
|
7
|
-
* Reads NDJSON from `temp/epic-<eid>/stories/story-<sid>/signals.ndjson` and
|
|
8
|
-
* upserts one of two structured comments:
|
|
9
|
-
*
|
|
10
|
-
* - `--story <sid> --epic <eid>` (Story mode): posts
|
|
11
|
-
* `<!-- structured:story-perf-summary -->` on the Story ticket. The
|
|
12
|
-
* payload combines the Story's NDJSON signals with the timing
|
|
13
|
-
* summary written by `post-merge-close.js` to
|
|
14
|
-
* `temp/epic-<eid>/stories/story-<sid>/phase-timings.json` (path overridable
|
|
15
|
-
* via `--phase-timings <path>`).
|
|
16
|
-
*
|
|
17
|
-
* - `--epic <eid>` (Epic mode): rolls up every Story under the Epic by
|
|
18
|
-
* fetching each `story-perf-summary` structured comment from the
|
|
19
|
-
* ticketing provider. Posts the
|
|
20
|
-
* `<!-- structured:epic-perf-report -->` comment on the Epic ticket.
|
|
21
|
-
* Run from the retro composer / `/deliver` Phase 6.0.
|
|
22
|
-
*
|
|
23
|
-
* Both modes are idempotent: `upsertStructuredComment` deletes the prior
|
|
24
|
-
* marker before posting the new one.
|
|
25
|
-
*
|
|
26
|
-
* Exit code is `0` on success, `0` on tolerated soft failures (missing
|
|
27
|
-
* NDJSON for a Story, no children for an Epic) so the close pipelines
|
|
28
|
-
* never block on observability output. Hard failures (bad CLI args,
|
|
29
|
-
* provider error, schema-violating payload) exit non-zero — the call
|
|
30
|
-
* sites in `single-story-close.js` / `helpers/deliver-story.md` treat that
|
|
31
|
-
* as a non-fatal warning.
|
|
32
|
-
*
|
|
33
|
-
* This file is a thin CLI: it wires the I/O readers from
|
|
34
|
-
* `lib/observability/perf-report-readers.js` to the pure renderers in
|
|
35
|
-
* `lib/observability/perf-report-render.js` inside `runStoryMode` /
|
|
36
|
-
* `runEpicMode`, then parses argv and dispatches. The report-formatting
|
|
37
|
-
* and signal-reading logic live in those two modules so report formatting
|
|
38
|
-
* is unit-testable without fixtures (Story #3350).
|
|
39
|
-
*
|
|
40
|
-
* Usage:
|
|
41
|
-
* node .agents/scripts/analyze-execution.js --story <sid> --epic <eid> \
|
|
42
|
-
* [--phase-timings <path>]
|
|
43
|
-
* node .agents/scripts/analyze-execution.js --epic <eid>
|
|
44
|
-
* node .agents/scripts/analyze-execution.js --epic <eid> --plan-metrics-json
|
|
45
|
-
*
|
|
46
|
-
* `--plan-metrics-json` (Epic #4474 PR7 — the G2 extraction surface) is a
|
|
47
|
-
* **local read-only** mode: it reads the per-Epic plan-metrics ledger
|
|
48
|
-
* (`temp/epic-<eid>/plan-metrics.json`), rolls it up with
|
|
49
|
-
* `summarizePlanMetrics`, and prints one compact JSON envelope
|
|
50
|
-
* `{ epicId, planMetrics, summaryLine }` to stdout — no provider call, no
|
|
51
|
-
* comment write, all logs on stderr — so a bench cohort can extract
|
|
52
|
-
* turns-per-plan / per-mode invocation counts / critic skips without
|
|
53
|
-
* scraping GitHub or the human log.
|
|
54
|
-
*
|
|
55
|
-
* @see docs/data-dictionary.md §StoryPerfSummary, §EpicPerfReport
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
import fs from 'node:fs/promises';
|
|
59
|
-
import path from 'node:path';
|
|
60
|
-
import { parseArgs } from 'node:util';
|
|
61
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
62
|
-
import { signalsFile } from './lib/config/temp-paths.js';
|
|
63
|
-
import { PROJECT_ROOT, resolveConfig } from './lib/config-resolver.js';
|
|
64
|
-
import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
|
|
65
|
-
import { computeBaselineRefreshRate } from './lib/observability/baseline-refresh-rate.js';
|
|
66
|
-
import {
|
|
67
|
-
computeEpicPerfReport,
|
|
68
|
-
computeStoryPerfSummary,
|
|
69
|
-
} from './lib/observability/perf-aggregator.js';
|
|
70
|
-
import {
|
|
71
|
-
aggregateBaselineFrictionFromSignals,
|
|
72
|
-
collectStorySummaries,
|
|
73
|
-
gatherEpicCommitsFromGit,
|
|
74
|
-
readEpicLifecycleEvents,
|
|
75
|
-
readGhSpawnCount,
|
|
76
|
-
readPhaseTimings,
|
|
77
|
-
readStorySignals,
|
|
78
|
-
} from './lib/observability/perf-report-readers.js';
|
|
79
|
-
import {
|
|
80
|
-
EPIC_PERF_TYPE,
|
|
81
|
-
renderEpicBody,
|
|
82
|
-
renderStoryBody,
|
|
83
|
-
STORY_PERF_TYPE,
|
|
84
|
-
} from './lib/observability/perf-report-render.js';
|
|
85
|
-
import {
|
|
86
|
-
readPlanMetrics,
|
|
87
|
-
renderPlanMetricsSummaryLine,
|
|
88
|
-
summarizePlanMetrics,
|
|
89
|
-
} from './lib/orchestration/plan-metrics.js';
|
|
90
|
-
import { upsertStructuredComment } from './lib/orchestration/ticketing.js';
|
|
91
|
-
import { createProvider } from './lib/provider-factory.js';
|
|
92
|
-
|
|
93
|
-
// Re-exported so existing importers (tests, retro composer) keep a stable
|
|
94
|
-
// entry point after the read/render split (Story #3350).
|
|
95
|
-
export { extractStoryPerfSummaryFromComment } from './lib/observability/perf-report-render.js';
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Resolve the wave-execution concurrency cap from the resolved config
|
|
99
|
-
* (`delivery.deliverRunner.concurrencyCap`), falling back to the
|
|
100
|
-
* dispatcher's safe default of 2. Story #3025 / Task #3030.
|
|
101
|
-
*/
|
|
102
|
-
function resolveConcurrencyCap(config) {
|
|
103
|
-
const cap = config?.delivery?.deliverRunner?.concurrencyCap;
|
|
104
|
-
return Number.isInteger(cap) && cap >= 1 ? cap : 2;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Resolve the verify-results concurrency cap from the resolved config
|
|
109
|
-
* (`delivery.deliverRunner.verifyConcurrencyCap`), falling back to 4.
|
|
110
|
-
* Story #3025 / Task #3030.
|
|
111
|
-
*/
|
|
112
|
-
function resolveVerifyConcurrencyCap(config) {
|
|
113
|
-
const cap = config?.delivery?.deliverRunner?.verifyConcurrencyCap;
|
|
114
|
-
return Number.isInteger(cap) && cap >= 1 ? cap : 4;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Story mode: read NDJSON + phase-timings, build the payload, upsert.
|
|
119
|
-
*
|
|
120
|
-
* @param {{
|
|
121
|
-
* storyId: number,
|
|
122
|
-
* epicId: number,
|
|
123
|
-
* phaseTimingsPath?: string|null,
|
|
124
|
-
* provider: object,
|
|
125
|
-
* config: object,
|
|
126
|
-
* logger?: object,
|
|
127
|
-
* now?: () => Date,
|
|
128
|
-
* }} ctx
|
|
129
|
-
* @returns {Promise<{ commentId: number, payload: object }>}
|
|
130
|
-
*/
|
|
131
|
-
export async function runStoryMode(ctx) {
|
|
132
|
-
const { storyId, epicId, provider, config } = ctx;
|
|
133
|
-
const logger = ctx.logger ?? Logger;
|
|
134
|
-
const now = ctx.now ?? (() => new Date());
|
|
135
|
-
|
|
136
|
-
logger.info?.(
|
|
137
|
-
`[analyze-execution] story-mode story=#${storyId} epic=#${epicId}`,
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
const events = await readStorySignals(epicId, storyId, config);
|
|
141
|
-
const phaseTiming = await readPhaseTimings(
|
|
142
|
-
epicId,
|
|
143
|
-
storyId,
|
|
144
|
-
config,
|
|
145
|
-
ctx.phaseTimingsPath ?? null,
|
|
146
|
-
);
|
|
147
|
-
const ghSpawnCount = await readGhSpawnCount(epicId, storyId, config);
|
|
148
|
-
|
|
149
|
-
const payload = computeStoryPerfSummary(events, {
|
|
150
|
-
storyId,
|
|
151
|
-
epicId,
|
|
152
|
-
closedAt: now().toISOString(),
|
|
153
|
-
phaseTiming,
|
|
154
|
-
});
|
|
155
|
-
if (ghSpawnCount !== null) {
|
|
156
|
-
// Throw-away measurement field (Story #1795). Surfaces the in-process
|
|
157
|
-
// `gh-exec.getSpawnCount()` snapshot captured at the end of close
|
|
158
|
-
// validation so the Story-close structured comment can be diffed
|
|
159
|
-
// against the baseline recorded under `temp/epic-1788/` for the
|
|
160
|
-
// "≥100 fewer spawns" acceptance criterion.
|
|
161
|
-
payload.ghSpawnCount = ghSpawnCount;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const body = renderStoryBody(payload);
|
|
165
|
-
const result = await upsertStructuredComment(
|
|
166
|
-
provider,
|
|
167
|
-
storyId,
|
|
168
|
-
STORY_PERF_TYPE,
|
|
169
|
-
body,
|
|
170
|
-
);
|
|
171
|
-
logger.info?.(
|
|
172
|
-
`[analyze-execution] story-perf-summary upserted on Story #${storyId} (commentId=${result.commentId})`,
|
|
173
|
-
);
|
|
174
|
-
return { commentId: result.commentId, payload };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Epic mode: collect every Story's perf summary, roll them up, upsert
|
|
179
|
-
* the epic-perf-report comment.
|
|
180
|
-
*
|
|
181
|
-
* @param {{
|
|
182
|
-
* epicId: number,
|
|
183
|
-
* provider: object,
|
|
184
|
-
* config?: object,
|
|
185
|
-
* cwd?: string,
|
|
186
|
-
* windowDays?: number,
|
|
187
|
-
* logger?: object,
|
|
188
|
-
* now?: () => Date,
|
|
189
|
-
* collectSummariesFn?: typeof collectStorySummaries,
|
|
190
|
-
* gatherEpicCommitsFn?: typeof gatherEpicCommitsFromGit,
|
|
191
|
-
* aggregateFrictionFn?: typeof aggregateBaselineFrictionFromSignals,
|
|
192
|
-
* }} ctx
|
|
193
|
-
* @returns {Promise<{ commentId: number, payload: object, baselineRefreshRate: object | null, qualityGateFriction: object | null }>}
|
|
194
|
-
*/
|
|
195
|
-
export async function runEpicMode(ctx) {
|
|
196
|
-
const { epicId, provider } = ctx;
|
|
197
|
-
const logger = ctx.logger ?? Logger;
|
|
198
|
-
const now = ctx.now ?? (() => new Date());
|
|
199
|
-
const windowDays =
|
|
200
|
-
Number.isFinite(ctx.windowDays) && ctx.windowDays > 0
|
|
201
|
-
? Math.floor(ctx.windowDays)
|
|
202
|
-
: 28;
|
|
203
|
-
const collectFn = ctx.collectSummariesFn ?? collectStorySummaries;
|
|
204
|
-
const gatherCommitsFn = ctx.gatherEpicCommitsFn ?? gatherEpicCommitsFromGit;
|
|
205
|
-
const aggregateFrictionFn =
|
|
206
|
-
ctx.aggregateFrictionFn ?? aggregateBaselineFrictionFromSignals;
|
|
207
|
-
|
|
208
|
-
logger.info?.(`[analyze-execution] epic-mode epic=#${epicId}`);
|
|
209
|
-
|
|
210
|
-
const summaries = await collectFn(provider, epicId, logger);
|
|
211
|
-
|
|
212
|
-
// Story #3025 / Task #3030 — forward lifecycle events to the
|
|
213
|
-
// aggregator so the report's `waveParallelism` array is populated
|
|
214
|
-
// (the analyzer used to omit the field, leaving the report at `[]`).
|
|
215
|
-
const readLifecycleEventsFn =
|
|
216
|
-
ctx.readLifecycleEventsFn ?? readEpicLifecycleEvents;
|
|
217
|
-
const lifecycleEvents = ctx.config
|
|
218
|
-
? await readLifecycleEventsFn(epicId, ctx.config, logger)
|
|
219
|
-
: [];
|
|
220
|
-
const concurrencyCap = resolveConcurrencyCap(ctx.config);
|
|
221
|
-
const verifyConcurrencyCap = resolveVerifyConcurrencyCap(ctx.config);
|
|
222
|
-
|
|
223
|
-
// Only forward `events` when we actually have something — passing an
|
|
224
|
-
// empty array suppresses the friction-from-summaries fallback in
|
|
225
|
-
// `computeEpicPerfReport.buildSignalCounts`, which would zero the
|
|
226
|
-
// counts for callers that have summaries but no lifecycle stream
|
|
227
|
-
// (e.g. legacy/test paths without a `config`).
|
|
228
|
-
const reportOpts = {
|
|
229
|
-
epicId,
|
|
230
|
-
generatedAt: now().toISOString(),
|
|
231
|
-
concurrencyCap,
|
|
232
|
-
verifyConcurrencyCap,
|
|
233
|
-
};
|
|
234
|
-
if (lifecycleEvents.length > 0) {
|
|
235
|
-
reportOpts.events = lifecycleEvents;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const payload = computeEpicPerfReport(summaries, reportOpts);
|
|
239
|
-
|
|
240
|
-
// Story #1400 / Task #1427 — baseline-refresh-rate row. Pure reporter
|
|
241
|
-
// operates on a fixture of git-log records gathered by the injected
|
|
242
|
-
// `gatherEpicCommitsFn`. A spawn failure surfaces as `[]` and degrades
|
|
243
|
-
// to the "no Story merges in window" line in the rendered body.
|
|
244
|
-
let baselineRefreshRate = null;
|
|
245
|
-
try {
|
|
246
|
-
const commits = await gatherCommitsFn({
|
|
247
|
-
epicId,
|
|
248
|
-
windowDays,
|
|
249
|
-
cwd: ctx.cwd ?? process.cwd(),
|
|
250
|
-
logger,
|
|
251
|
-
});
|
|
252
|
-
baselineRefreshRate = computeBaselineRefreshRate(commits, {
|
|
253
|
-
windowDays,
|
|
254
|
-
now,
|
|
255
|
-
});
|
|
256
|
-
} catch (err) {
|
|
257
|
-
logger.warn?.(
|
|
258
|
-
`[analyze-execution] baseline-refresh-rate gather failed (non-fatal): ${
|
|
259
|
-
err instanceof Error ? err.message : String(err)
|
|
260
|
-
}`,
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Story #1400 / Task #1429 — Quality gate friction block. Aggregates
|
|
265
|
-
// `baseline-refresh-regression` records from the existing
|
|
266
|
-
// signals.ndjson stream (no new file format).
|
|
267
|
-
let qualityGateFriction = null;
|
|
268
|
-
if (ctx.config) {
|
|
269
|
-
try {
|
|
270
|
-
const storyIds = (summaries ?? [])
|
|
271
|
-
.map((s) => s?.storyId)
|
|
272
|
-
.filter((n) => Number.isInteger(n) && n > 0);
|
|
273
|
-
qualityGateFriction = await aggregateFrictionFn({
|
|
274
|
-
epicId,
|
|
275
|
-
storyIds,
|
|
276
|
-
config: ctx.config,
|
|
277
|
-
windowDays,
|
|
278
|
-
now,
|
|
279
|
-
});
|
|
280
|
-
} catch (err) {
|
|
281
|
-
logger.warn?.(
|
|
282
|
-
`[analyze-execution] quality-gate-friction aggregate failed (non-fatal): ${
|
|
283
|
-
err instanceof Error ? err.message : String(err)
|
|
284
|
-
}`,
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const body = renderEpicBody(payload, {
|
|
290
|
-
baselineRefreshRate,
|
|
291
|
-
qualityGateFriction,
|
|
292
|
-
});
|
|
293
|
-
const result = await upsertStructuredComment(
|
|
294
|
-
provider,
|
|
295
|
-
epicId,
|
|
296
|
-
EPIC_PERF_TYPE,
|
|
297
|
-
body,
|
|
298
|
-
);
|
|
299
|
-
logger.info?.(
|
|
300
|
-
`[analyze-execution] epic-perf-report upserted on Epic #${epicId} (commentId=${result.commentId}, stories=${summaries.length})`,
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
// Plan-metrics roll-up (#4474 PR1) — surface the plan-CLI invocation
|
|
304
|
-
// ledger (`temp/epic-<id>/plan-metrics.json`) alongside the perf report.
|
|
305
|
-
// Additive and non-fatal: a missing or unreadable ledger yields `null`.
|
|
306
|
-
let planMetrics = null;
|
|
307
|
-
try {
|
|
308
|
-
planMetrics = summarizePlanMetrics(
|
|
309
|
-
await readPlanMetrics(epicId, ctx.config),
|
|
310
|
-
);
|
|
311
|
-
if (planMetrics) {
|
|
312
|
-
logger.info?.(
|
|
313
|
-
`[analyze-execution] ${renderPlanMetricsSummaryLine(planMetrics)}`,
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
} catch (err) {
|
|
317
|
-
logger.warn?.(
|
|
318
|
-
`[analyze-execution] plan-metrics read failed (non-fatal): ${
|
|
319
|
-
err instanceof Error ? err.message : String(err)
|
|
320
|
-
}`,
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
commentId: result.commentId,
|
|
326
|
-
payload,
|
|
327
|
-
baselineRefreshRate,
|
|
328
|
-
qualityGateFriction,
|
|
329
|
-
planMetrics,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function parseCli(argv) {
|
|
334
|
-
const { values } = parseArgs({
|
|
335
|
-
args: argv,
|
|
336
|
-
options: {
|
|
337
|
-
story: { type: 'string' },
|
|
338
|
-
epic: { type: 'string' },
|
|
339
|
-
'phase-timings': { type: 'string' },
|
|
340
|
-
'window-days': { type: 'string' },
|
|
341
|
-
'plan-metrics-json': { type: 'boolean', default: false },
|
|
342
|
-
cwd: { type: 'string' },
|
|
343
|
-
},
|
|
344
|
-
strict: false,
|
|
345
|
-
});
|
|
346
|
-
const story = values.story != null ? Number.parseInt(values.story, 10) : null;
|
|
347
|
-
const epic = values.epic != null ? Number.parseInt(values.epic, 10) : null;
|
|
348
|
-
const windowDays =
|
|
349
|
-
values['window-days'] != null
|
|
350
|
-
? Number.parseInt(values['window-days'], 10)
|
|
351
|
-
: null;
|
|
352
|
-
return {
|
|
353
|
-
storyId: Number.isInteger(story) && story > 0 ? story : null,
|
|
354
|
-
epicId: Number.isInteger(epic) && epic > 0 ? epic : null,
|
|
355
|
-
phaseTimingsPath: values['phase-timings'] ?? null,
|
|
356
|
-
windowDays:
|
|
357
|
-
Number.isInteger(windowDays) && windowDays > 0 ? windowDays : null,
|
|
358
|
-
planMetricsJson: values['plan-metrics-json'] === true,
|
|
359
|
-
cwd: values.cwd ?? null,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* `--plan-metrics-json` — local, read-only G2 extraction surface (Epic
|
|
365
|
-
* #4474 PR7). Reads the per-Epic plan-metrics ledger and prints one
|
|
366
|
-
* compact JSON envelope to stdout. No provider, no comment write; stdout
|
|
367
|
-
* is reserved for the envelope (all logs on stderr). A missing ledger is
|
|
368
|
-
* not an error — it yields `planMetrics: null` so a cohort script can
|
|
369
|
-
* branch on the field instead of the exit code.
|
|
370
|
-
*
|
|
371
|
-
* @param {{ epicId: number, config: object }} input
|
|
372
|
-
* @returns {Promise<{ epicId: number, planMetrics: object|null, summaryLine: string }>}
|
|
373
|
-
*/
|
|
374
|
-
export async function runPlanMetricsJsonMode({ epicId, config }) {
|
|
375
|
-
const planMetrics = summarizePlanMetrics(
|
|
376
|
-
await readPlanMetrics(epicId, config),
|
|
377
|
-
);
|
|
378
|
-
return {
|
|
379
|
-
epicId,
|
|
380
|
-
planMetrics,
|
|
381
|
-
summaryLine: renderPlanMetricsSummaryLine(planMetrics),
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
async function main(argv = process.argv.slice(2)) {
|
|
386
|
-
const args = parseCli(argv);
|
|
387
|
-
|
|
388
|
-
if (!args.epicId) {
|
|
389
|
-
throw new Error(
|
|
390
|
-
'Usage: analyze-execution.js --epic <eid> [--story <sid>] [--phase-timings <path>]',
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const cwd = path.resolve(args.cwd ?? PROJECT_ROOT);
|
|
395
|
-
const config = resolveConfig({ cwd });
|
|
396
|
-
|
|
397
|
-
if (args.planMetricsJson) {
|
|
398
|
-
// Stdout is reserved for the compact envelope — flip every log sink to
|
|
399
|
-
// stderr before any pipeline code runs (Story #2278 discipline).
|
|
400
|
-
routeAllOutputToStderr();
|
|
401
|
-
const result = await runPlanMetricsJsonMode({
|
|
402
|
-
epicId: args.epicId,
|
|
403
|
-
config,
|
|
404
|
-
});
|
|
405
|
-
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const provider = createProvider(config);
|
|
410
|
-
|
|
411
|
-
if (args.storyId) {
|
|
412
|
-
// Existence guard: surface a clear log when the Story has no signals
|
|
413
|
-
// yet, but still post the comment with empty arrays so the marker
|
|
414
|
-
// exists for idempotence checks downstream.
|
|
415
|
-
const sigPath = signalsFile(args.epicId, args.storyId, config);
|
|
416
|
-
try {
|
|
417
|
-
await fs.access(sigPath);
|
|
418
|
-
} catch {
|
|
419
|
-
Logger.info?.(
|
|
420
|
-
`[analyze-execution] no signals.ndjson at ${sigPath} — posting empty story-perf-summary`,
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
const result = await runStoryMode({
|
|
424
|
-
storyId: args.storyId,
|
|
425
|
-
epicId: args.epicId,
|
|
426
|
-
phaseTimingsPath: args.phaseTimingsPath,
|
|
427
|
-
provider,
|
|
428
|
-
config,
|
|
429
|
-
});
|
|
430
|
-
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const result = await runEpicMode({
|
|
435
|
-
epicId: args.epicId,
|
|
436
|
-
provider,
|
|
437
|
-
config,
|
|
438
|
-
cwd,
|
|
439
|
-
windowDays: args.windowDays ?? undefined,
|
|
440
|
-
});
|
|
441
|
-
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
runAsCli(import.meta.url, main, { source: 'analyze-execution' });
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* check-prepush-recovery.js — gate decision for the pre-push coverage step.
|
|
4
|
-
*
|
|
5
|
-
* Story #3162. The pre-push hook normally runs `coverage-capture.js` +
|
|
6
|
-
* `crap:check` on every push. During a pre-v2 `story-close.js` recovery (operator
|
|
7
|
-
* pushing `epic/<id>` to unblock a run after a merge-orchestrator failure),
|
|
8
|
-
* a pre-existing coverage regression on `epic/<id>` — unrelated to the
|
|
9
|
-
* Story under recovery — hard-blocks the push and forces a `--no-verify`
|
|
10
|
-
* bypass, which violates the global "no hook bypass" rule.
|
|
11
|
-
*
|
|
12
|
-
* This script is the scoped escape hatch. It exits 0 ("skip coverage gate")
|
|
13
|
-
* only when BOTH conditions hold:
|
|
14
|
-
*
|
|
15
|
-
* 1. `STORY_CLOSE_RECOVERY=1` is set in the environment.
|
|
16
|
-
* 2. At least one ref being pushed has a local ref of the shape
|
|
17
|
-
* `refs/heads/epic/<id>` (parsed from the pre-push stdin protocol).
|
|
18
|
-
*
|
|
19
|
-
* Otherwise it exits 1 ("run coverage gate as today"). The narrow scope is
|
|
20
|
-
* deliberate: a generic env-var bypass would erode the gate; tying the
|
|
21
|
-
* skip to an epic-branch target keeps every other push honest.
|
|
22
|
-
*
|
|
23
|
-
* Stdin contract (git pre-push hook protocol): zero or more lines, each
|
|
24
|
-
* `<local_ref> <local_sha> <remote_ref> <remote_sha>`
|
|
25
|
-
* separated by whitespace. Empty stdin (no refs being pushed) yields exit 1.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
29
|
-
|
|
30
|
-
const EPIC_REF_PATTERN = /^refs\/heads\/epic\//;
|
|
31
|
-
const SKIP_LOG = '[pre-push] coverage gate skipped by STORY_CLOSE_RECOVERY';
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Parse pre-push stdin into the list of local refs being pushed. Lines with
|
|
35
|
-
* fewer than four whitespace-separated tokens are tolerated (git in practice
|
|
36
|
-
* always emits four, but defensive parsing keeps the helper robust against
|
|
37
|
-
* empty pushes and trailing whitespace).
|
|
38
|
-
*
|
|
39
|
-
* @param {string} stdin
|
|
40
|
-
* @returns {string[]} local ref names (first token of each non-empty line)
|
|
41
|
-
*/
|
|
42
|
-
export function parsePrePushLocalRefs(stdin) {
|
|
43
|
-
if (typeof stdin !== 'string' || stdin.length === 0) return [];
|
|
44
|
-
return stdin
|
|
45
|
-
.split(/\r?\n/)
|
|
46
|
-
.map((line) => line.trim())
|
|
47
|
-
.filter(Boolean)
|
|
48
|
-
.map((line) => line.split(/\s+/)[0])
|
|
49
|
-
.filter(Boolean);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Pure decision function. `true` means "skip the coverage gate".
|
|
54
|
-
*
|
|
55
|
-
* @param {object} input
|
|
56
|
-
* @param {string|undefined} input.env value of STORY_CLOSE_RECOVERY
|
|
57
|
-
* @param {string[]} input.localRefs parsed local refs
|
|
58
|
-
* @returns {boolean}
|
|
59
|
-
*/
|
|
60
|
-
export function shouldSkipCoverageGate({ env, localRefs }) {
|
|
61
|
-
if (env !== '1') return false;
|
|
62
|
-
if (!Array.isArray(localRefs) || localRefs.length === 0) return false;
|
|
63
|
-
return localRefs.some((ref) => EPIC_REF_PATTERN.test(ref));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function readStdin() {
|
|
67
|
-
if (process.stdin.isTTY) return '';
|
|
68
|
-
let data = '';
|
|
69
|
-
process.stdin.setEncoding('utf8');
|
|
70
|
-
for await (const chunk of process.stdin) data += chunk;
|
|
71
|
-
return data;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function main() {
|
|
75
|
-
const stdin = await readStdin();
|
|
76
|
-
const localRefs = parsePrePushLocalRefs(stdin);
|
|
77
|
-
const skip = shouldSkipCoverageGate({
|
|
78
|
-
env: process.env.STORY_CLOSE_RECOVERY,
|
|
79
|
-
localRefs,
|
|
80
|
-
});
|
|
81
|
-
if (skip) {
|
|
82
|
-
process.stdout.write(`${SKIP_LOG}\n`);
|
|
83
|
-
process.exit(0);
|
|
84
|
-
}
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
runAsCli(import.meta.url, main);
|
|
89
|
-
|
|
90
|
-
export const __testing = { EPIC_REF_PATTERN, SKIP_LOG };
|