mandrel 1.58.0 → 1.60.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 +100 -98
- package/.agents/docs/SDLC.md +140 -141
- package/.agents/docs/configuration.md +16 -16
- package/.agents/docs/workflows.md +7 -8
- package/.agents/instructions.md +12 -11
- package/.agents/personas/architect.md +1 -1
- package/.agents/personas/product.md +1 -1
- package/.agents/personas/project-manager.md +14 -14
- package/.agents/personas/technical-writer.md +1 -1
- package/.agents/rules/changelog-style.md +5 -5
- package/.agents/rules/git-conventions.md +3 -3
- package/.agents/schemas/agentrc.schema.json +3 -3
- package/.agents/schemas/audit-rules.json +20 -0
- package/.agents/schemas/dispatch-manifest.json +4 -4
- package/.agents/schemas/epic-spec.schema.json +15 -45
- package/.agents/schemas/lifecycle/README.md +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +1 -1
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +1 -1
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/README.md +1 -1
- package/.agents/scripts/acceptance-eval.js +21 -4
- package/.agents/scripts/acceptance-spec-reconciler.js +2 -2
- package/.agents/scripts/analyze-execution.js +2 -2
- package/.agents/scripts/assert-branch.js +1 -3
- package/.agents/scripts/audit-to-stories.js +1 -1
- package/.agents/scripts/bootstrap.js +1 -1
- package/.agents/scripts/check-arch-cycles.js +360 -0
- package/.agents/scripts/check-doc-links.js +2 -3
- package/.agents/scripts/coverage-capture.js +24 -3
- package/.agents/scripts/diagnose-friction.js +1 -1
- package/.agents/scripts/dispatcher.js +2 -2
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/epic-audit-prepare.js +3 -3
- package/.agents/scripts/epic-deliver-note-intervention.js +2 -2
- package/.agents/scripts/epic-deliver-preflight.js +11 -9
- package/.agents/scripts/epic-deliver-prepare.js +13 -5
- package/.agents/scripts/epic-execute-record-wave.js +5 -5
- package/.agents/scripts/epic-plan-healthcheck.js +6 -10
- package/.agents/scripts/epic-plan-spec-validate.js +1 -1
- package/.agents/scripts/epic-reconcile.js +11 -29
- package/.agents/scripts/evidence-gate.js +2 -2
- package/.agents/scripts/generate-workflows-doc.js +1 -1
- package/.agents/scripts/git-rebase-and-resolve.js +1 -1
- package/.agents/scripts/hierarchy-gate.js +40 -24
- package/.agents/scripts/lib/ITicketingProvider.js +1 -1
- package/.agents/scripts/lib/audit-suite/selector.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/seed-epic-from-findings.js +2 -2
- package/.agents/scripts/lib/baseline-snapshot.js +7 -7
- package/.agents/scripts/lib/baselines/kinds/coverage.js +33 -149
- package/.agents/scripts/lib/baselines/kinds/duplication.js +27 -116
- package/.agents/scripts/lib/baselines/kinds/kind-factory.js +192 -0
- package/.agents/scripts/lib/baselines/kinds/lighthouse.js +34 -133
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +31 -124
- package/.agents/scripts/lib/baselines/kinds/mutation.js +25 -111
- package/.agents/scripts/lib/baselines/maintainability-baseline-io.js +59 -0
- package/.agents/scripts/lib/baselines/maintainability-baseline-save.js +37 -0
- package/.agents/scripts/lib/baselines/writer.js +1 -1
- package/.agents/scripts/lib/bdd-runner-detect.js +1 -1
- package/.agents/scripts/lib/bdd-scenario-scanner.js +3 -3
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +1 -1
- package/.agents/scripts/lib/bootstrap/branch-protection.js +1 -1
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +1 -1
- package/.agents/scripts/lib/bootstrap/commit-push.js +2 -2
- package/.agents/scripts/lib/close-validation/commands.js +188 -0
- package/.agents/scripts/lib/close-validation/gates.js +235 -0
- package/.agents/scripts/lib/close-validation/process.js +101 -0
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +1 -1
- package/.agents/scripts/lib/close-validation/runner.js +325 -0
- package/.agents/scripts/lib/close-validation/telemetry.js +70 -0
- package/.agents/scripts/lib/codebase-snapshot.js +1 -1
- package/.agents/scripts/lib/config/explain.js +1 -1
- package/.agents/scripts/lib/config/quality.js +6 -6
- package/.agents/scripts/lib/config/runners.js +2 -2
- package/.agents/scripts/lib/config/runtime.js +1 -1
- package/.agents/scripts/lib/config/temp-paths.js +2 -2
- package/.agents/scripts/lib/config-resolver.js +2 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +2 -2
- package/.agents/scripts/lib/config-settings-schema-quality.js +1 -1
- package/.agents/scripts/lib/config-settings-schema.js +3 -3
- package/.agents/scripts/lib/coverage-capture.js +147 -4
- package/.agents/scripts/lib/cpu-pool.js +14 -0
- package/.agents/scripts/lib/crap-utils.js +6 -11
- package/.agents/scripts/lib/duplicate-search.js +1 -1
- package/.agents/scripts/lib/dynamic-workflow/capability.js +1 -1
- package/.agents/scripts/lib/dynamic-workflow/documentation-report-contract.js +87 -0
- package/.agents/scripts/lib/epic-plan-clarity.js +1 -1
- package/.agents/scripts/lib/epic-plan-ideation.js +1 -1
- package/.agents/scripts/lib/feedback-loop/memory-freshness.js +1 -1
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +1 -1
- package/.agents/scripts/lib/findings/classify-finding.js +1 -1
- package/.agents/scripts/lib/findings/promote-finding.js +10 -10
- package/.agents/scripts/lib/git-utils.js +24 -22
- package/.agents/scripts/lib/label-constants.js +3 -4
- package/.agents/scripts/lib/label-taxonomy.js +3 -8
- package/.agents/scripts/lib/maintainability-engine.js +1 -1
- package/.agents/scripts/lib/maintainability-utils.js +4 -187
- package/.agents/scripts/lib/observability/perf-report-readers.js +32 -23
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +81 -7
- package/.agents/scripts/lib/orchestration/code-review.js +95 -82
- package/.agents/scripts/lib/orchestration/context-hydration-engine.js +8 -9
- package/.agents/scripts/lib/orchestration/dependency-analyzer.js +3 -3
- package/.agents/scripts/lib/orchestration/detectors-phase.js +2 -2
- package/.agents/scripts/lib/orchestration/dispatch-engine.js +30 -38
- package/.agents/scripts/lib/orchestration/dispatch-pipeline.js +14 -37
- package/.agents/scripts/lib/orchestration/epic-cleanup.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-deliver-lease-guard.js +22 -22
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/creation.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/dag.js +7 -21
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/diagnostics.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-plan-decompose/phases/planning-artifacts.js +2 -2
- package/.agents/scripts/lib/orchestration/epic-plan-lease-guard.js +206 -58
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/drain.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/plan-epic.js +27 -3
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/prompts.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-plan-spec/phases/run-spec-phase.js +28 -8
- package/.agents/scripts/lib/orchestration/epic-plan-state-store.js +1 -1
- package/.agents/scripts/lib/orchestration/epic-run-state-store.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/concurrency-gate.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/deliver-phases.js +3 -3
- package/.agents/scripts/lib/orchestration/epic-runner/phases/build-wave-dag.js +13 -41
- package/.agents/scripts/lib/orchestration/epic-runner/phases/snapshot.js +7 -7
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/composition.js +2 -3
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/signals.js +2 -8
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter/transport.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/component-drift.js +103 -0
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/crap-drift.js +22 -64
- package/.agents/scripts/lib/orchestration/epic-runner/progress-signals/maintainability-drift.js +38 -76
- package/.agents/scripts/lib/orchestration/epic-runner/story-launcher.js +4 -4
- package/.agents/scripts/lib/orchestration/epic-runner/story-run-progress-writer.js +10 -10
- package/.agents/scripts/lib/orchestration/epic-runner/sub-agent-return.js +8 -20
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-apply.js +7 -15
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-diff.js +72 -41
- package/.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js +2 -4
- package/.agents/scripts/lib/orchestration/file-assumptions.js +6 -5
- package/.agents/scripts/lib/orchestration/finalize/close-planning-tickets.js +1 -1
- package/.agents/scripts/lib/orchestration/finalize/open-or-locate-pr.js +2 -2
- package/.agents/scripts/lib/orchestration/finalize/sanitize-skip-ci.js +1 -1
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +144 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-dispatch-end.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +3 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-armer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/automerge-predicate.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/branch-cleaner.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/finalizer.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/index.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/notify-dispatcher.js +1 -1
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +8 -8
- package/.agents/scripts/lib/orchestration/manifest-builder.js +5 -5
- package/.agents/scripts/lib/orchestration/parked-follow-ons.js +2 -2
- package/.agents/scripts/lib/orchestration/plan-runner/plan-router.js +5 -5
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +3 -3
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +3 -3
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +7 -7
- package/.agents/scripts/lib/orchestration/preflight-cache.js +36 -13
- package/.agents/scripts/lib/orchestration/recurring-failure-detector.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/compose-body.js +1 -1
- package/.agents/scripts/lib/orchestration/retro/phases/gather-signals.js +2 -2
- package/.agents/scripts/lib/orchestration/retro-runner.js +3 -3
- package/.agents/scripts/lib/orchestration/review-depth.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +5 -60
- package/.agents/scripts/lib/orchestration/review-providers/native.js +7 -6
- package/.agents/scripts/lib/orchestration/review-providers/parse-findings.js +105 -0
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +7 -59
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +2 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +2 -4
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +32 -35
- package/.agents/scripts/lib/orchestration/skill-capsule-loader.js +1 -2
- package/.agents/scripts/lib/orchestration/spec-freshness.js +1 -1
- package/.agents/scripts/lib/orchestration/spec-renderer.js +36 -73
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/auto-refresh-runner.js +451 -503
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/pre-merge-attribution.js +8 -2
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js +47 -2
- package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/regression-projection.js +2 -2
- package/.agents/scripts/lib/orchestration/story-close/baseline-friction-body.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +358 -54
- package/.agents/scripts/lib/orchestration/story-close/phases/close.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/phases/gates.js +3 -2
- package/.agents/scripts/lib/orchestration/story-close/phases/locked-pipeline.js +32 -5
- package/.agents/scripts/lib/orchestration/story-close/post-merge-close.js +5 -18
- package/.agents/scripts/lib/orchestration/story-close/pre-merge-validation.js +3 -3
- package/.agents/scripts/lib/orchestration/story-close-recovery.js +33 -16
- package/.agents/scripts/lib/orchestration/story-reachability.js +47 -0
- package/.agents/scripts/lib/orchestration/task-body-validator.js +6 -6
- package/.agents/scripts/lib/orchestration/ticket-lease.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +4 -35
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -10
- package/.agents/scripts/lib/orchestration/ticket-validator.js +25 -70
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +44 -73
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +16 -7
- package/.agents/scripts/lib/orchestration/ticketing/state.js +53 -439
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +471 -0
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/orchestration/wave-record-notifications.js +3 -3
- package/.agents/scripts/lib/orchestration/wave-record-projection.js +2 -8
- package/.agents/scripts/lib/plan-phase-cleanup.js +1 -1
- package/.agents/scripts/lib/preflight-runner.js +1 -1
- package/.agents/scripts/lib/presentation/dispatch-manifest-render.js +4 -5
- package/.agents/scripts/lib/presentation/manifest-builder.js +28 -34
- package/.agents/scripts/lib/presentation/manifest-formatter.js +3 -4
- package/.agents/scripts/lib/presentation/manifest-helpers.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-procedures.js +4 -4
- package/.agents/scripts/lib/presentation/manifest-render-waves.js +4 -23
- package/.agents/scripts/lib/presentation/manifest-renderer.js +1 -1
- package/.agents/scripts/lib/presentation/manifest-story-views.js +2 -11
- package/.agents/scripts/lib/project-root.js +17 -0
- package/.agents/scripts/lib/signals/schema.js +1 -1
- package/.agents/scripts/lib/spec/index.js +1 -1
- package/.agents/scripts/lib/spec/loader.js +2 -2
- package/.agents/scripts/lib/spec/state.js +7 -16
- package/.agents/scripts/lib/story-adjacency.js +76 -0
- package/.agents/scripts/lib/story-init/context-resolver.js +3 -3
- package/.agents/scripts/lib/story-init/state-transitioner.js +2 -2
- package/.agents/scripts/lib/story-init/task-graph-builder.js +7 -7
- package/.agents/scripts/lib/story-lifecycle.js +9 -9
- package/.agents/scripts/lib/story-plan.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +59 -52
- package/.agents/scripts/lib/transpile.js +93 -0
- package/.agents/scripts/lib/wave-runner/tick.js +4 -153
- package/.agents/scripts/lib/workers/crap-worker.js +1 -1
- package/.agents/scripts/lib/workers/maintainability-report-worker.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/creation.js +20 -2
- package/.agents/scripts/lib/worktree/lifecycle/force-drain.js +90 -0
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +26 -8
- package/.agents/scripts/lib/worktree/node-modules-strategy.js +74 -0
- package/.agents/scripts/lifecycle-emit-story-dispatch.js +1 -1
- package/.agents/scripts/lifecycle-emit.js +1 -1
- package/.agents/scripts/providers/github/board-add.js +1 -1
- package/.agents/scripts/providers/github/errors.js +1 -1
- package/.agents/scripts/providers/github/mappers.js +2 -2
- package/.agents/scripts/providers/github/tickets.js +114 -10
- package/.agents/scripts/resync-status-column.js +1 -1
- package/.agents/scripts/retro-run.js +2 -2
- package/.agents/scripts/run-lint.js +10 -1
- package/.agents/scripts/run-tests.js +24 -4
- package/.agents/scripts/single-story-init.js +1 -1
- package/.agents/scripts/stories-wave-tick.js +13 -10
- package/.agents/scripts/story-close.js +1 -1
- package/.agents/scripts/story-init.js +162 -26
- package/.agents/scripts/story-phase.js +5 -5
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/sync-branch-from-base.js +2 -2
- package/.agents/scripts/validate-docs-freshness.js +1 -1
- package/.agents/scripts/wave-tick.js +1 -1
- package/.agents/skills/core/analyze-execution/SKILL.md +2 -2
- package/.agents/skills/core/epic-plan-consolidate/SKILL.md +21 -26
- package/.agents/skills/core/epic-plan-decompose-author/SKILL.md +23 -56
- package/.agents/skills/core/epic-plan-spec-author/SKILL.md +4 -4
- package/.agents/skills/core/hydrate-context/SKILL.md +2 -2
- package/.agents/skills/core/idea-refinement/SKILL.md +4 -4
- package/.agents/skills/core/knowledge-transfer/SKILL.md +2 -2
- package/.agents/skills/core/planning-and-task-breakdown/SKILL.md +1 -1
- package/.agents/skills/core/scope-triage/SKILL.md +9 -10
- package/.agents/skills/core/using-agent-skills/SKILL.md +1 -1
- package/.agents/skills/skills.index.json +7 -7
- package/.agents/skills/stack/qa/lighthouse-baseline/SKILL.md +1 -1
- package/.agents/templates/agent-protocol.md +2 -2
- package/.agents/workflows/agents-update.md +2 -2
- package/.agents/workflows/audit-architecture.md +2 -2
- package/.agents/workflows/audit-clean-code.md +2 -2
- package/.agents/workflows/audit-dependencies.md +1 -1
- package/.agents/workflows/audit-devops.md +1 -1
- package/.agents/workflows/audit-documentation.md +226 -0
- package/.agents/workflows/audit-lighthouse.md +1 -1
- package/.agents/workflows/audit-performance.md +2 -2
- package/.agents/workflows/audit-privacy.md +1 -1
- package/.agents/workflows/audit-quality.md +2 -2
- package/.agents/workflows/audit-security.md +2 -2
- package/.agents/workflows/audit-seo.md +1 -1
- package/.agents/workflows/audit-sre.md +1 -1
- package/.agents/workflows/audit-to-stories.md +10 -10
- package/.agents/workflows/audit-ux-ui.md +1 -1
- package/.agents/workflows/deliver.md +85 -0
- package/.agents/workflows/explain.md +3 -3
- package/.agents/workflows/git-merge-pr.md +1 -1
- package/.agents/workflows/git-pr-all.md +13 -10
- package/.agents/workflows/git-push.md +6 -3
- package/.agents/workflows/helpers/_merge-conflict-template.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +1 -1
- package/.agents/workflows/helpers/code-review.md +5 -5
- package/.agents/workflows/{epic-deliver.md → helpers/deliver-epic.md} +59 -66
- package/.agents/workflows/{story-deliver.md → helpers/deliver-stories.md} +25 -25
- package/.agents/workflows/helpers/diagnose.md +1 -1
- package/.agents/workflows/helpers/epic-audit.md +6 -6
- package/.agents/workflows/helpers/epic-deliver-story.md +28 -39
- package/.agents/workflows/helpers/epic-plan-decompose.md +23 -23
- package/.agents/workflows/helpers/epic-plan-spec.md +6 -6
- package/.agents/workflows/helpers/epic-testing.md +3 -3
- package/.agents/workflows/helpers/parallel-tooling.md +1 -1
- package/.agents/workflows/{epic-plan.md → helpers/plan-epic.md} +84 -84
- package/.agents/workflows/{story-plan.md → helpers/plan-story.md} +43 -43
- package/.agents/workflows/helpers/signals.md +1 -1
- package/.agents/workflows/helpers/single-story-deliver.md +12 -11
- package/.agents/workflows/helpers/worktree-lifecycle.md +18 -18
- package/.agents/workflows/onboard.md +21 -20
- package/.agents/workflows/plan.md +89 -0
- package/.agents/workflows/qa-explore.md +1 -1
- package/.agents/workflows/qa-run-harness.md +1 -1
- package/README.md +17 -20
- package/docs/CHANGELOG.md +1149 -0
- package/lib/cli/__tests__/update-changelog-surface.test.js +357 -0
- package/lib/cli/__tests__/update-reexec.test.js +513 -0
- package/lib/cli/init.js +338 -0
- package/lib/cli/update.js +413 -52
- package/package.json +3 -1
- package/.agents/scripts/lib/auto-refresh-baselines.js +0 -308
- package/.agents/scripts/lib/close-validation.js +0 -897
- package/.agents/scripts/lib/orchestration/cascade-grouping.js +0 -275
- package/.agents/scripts/lib/orchestration/epic-runner/progress-reporter.js +0 -69
- package/.agents/scripts/lib/orchestration/reconciler.js +0 -137
- package/.agents/scripts/lib/orchestration/story-close/format-autofix-scoped.js +0 -221
- package/.agents/scripts/lib/orchestration/story-close/format-autofix-shared.js +0 -123
- package/.agents/scripts/lib/task-utils.js +0 -26
- package/.agents/scripts/story-deliver-prepare.js +0 -267
|
@@ -55,13 +55,19 @@ export async function runPreMergeGatesWithAttribution({
|
|
|
55
55
|
projectRegressionsFn = projectRegressionsForGate,
|
|
56
56
|
logger = DefaultLogger,
|
|
57
57
|
maxAttempts = 2,
|
|
58
|
+
cycleState: cycleStateParam = null,
|
|
58
59
|
} = {}) {
|
|
59
60
|
let attempt = 0;
|
|
60
61
|
const gateCwd = worktreePath || cwd;
|
|
61
62
|
// Story #2205: single mutable cycle state object — `refreshedKinds`
|
|
62
63
|
// gates the idempotency token enforcing AC-9 (one refresh commit per
|
|
63
|
-
// kind per close cycle).
|
|
64
|
-
|
|
64
|
+
// kind per close cycle). Story #4017: the caller may thread the close
|
|
65
|
+
// cycle's shared object so the post-gates auto-refresh sees the kinds
|
|
66
|
+
// already refreshed by this retry loop and never re-scores them.
|
|
67
|
+
const cycleState = cycleStateParam ?? {
|
|
68
|
+
refreshedKinds: new Set(),
|
|
69
|
+
lastRefreshSha: null,
|
|
70
|
+
};
|
|
65
71
|
while (attempt < maxAttempts) {
|
|
66
72
|
attempt += 1;
|
|
67
73
|
try {
|
package/.agents/scripts/lib/orchestration/story-close/baseline-attribution/phases/refresh-commit.js
CHANGED
|
@@ -261,8 +261,17 @@ export function stageAndCheckBaselineDrift({
|
|
|
261
261
|
* Story branch — this helper does NOT re-check the branch. story-close.js
|
|
262
262
|
* holds that invariant via `withEpicMergeLock`.
|
|
263
263
|
*
|
|
264
|
+
* Story #4017 — this helper is the **single refresh→stage→commit funnel**
|
|
265
|
+
* for story-close. Both call paths route through it: the gate-failure
|
|
266
|
+
* attribution retry (`gate-failure.js`, no `capCheck`) and the post-gates
|
|
267
|
+
* bounded auto-refresh (`auto-refresh-runner.js`, which injects a
|
|
268
|
+
* `capCheck` evaluating the refreshed envelope against the configured
|
|
269
|
+
* delta caps). When `capCheck` returns `{ canAutoRefresh: false }`, the
|
|
270
|
+
* staged refresh is rolled back to HEAD and the helper returns
|
|
271
|
+
* `{ ok: true, refused: true, verdict }` without committing.
|
|
272
|
+
*
|
|
264
273
|
* @returns {Promise<
|
|
265
|
-
* | { ok: true, sha: string, skipped?: boolean, reason?: string }
|
|
274
|
+
* | { ok: true, sha: string, skipped?: boolean, reason?: string, refused?: boolean, verdict?: object }
|
|
266
275
|
* | { ok: false, error: string }
|
|
267
276
|
* >}
|
|
268
277
|
*/
|
|
@@ -274,9 +283,11 @@ export async function runRefreshCommit({
|
|
|
274
283
|
storyBranch,
|
|
275
284
|
config,
|
|
276
285
|
cycleState = null,
|
|
286
|
+
capCheck = null,
|
|
277
287
|
refreshBaseline = defaultRefreshBaseline,
|
|
278
288
|
scorerBuilder = buildKindScorer,
|
|
279
289
|
getBaselines: getBaselinesImpl = defaultGetBaselines,
|
|
290
|
+
getQuality: getQualityImpl = defaultGetQuality,
|
|
280
291
|
fsImpl = fs,
|
|
281
292
|
gitRunner = { gitSpawn: defaultGitSpawn },
|
|
282
293
|
logger = DefaultLogger,
|
|
@@ -326,8 +337,9 @@ export async function runRefreshCommit({
|
|
|
326
337
|
};
|
|
327
338
|
}
|
|
328
339
|
|
|
340
|
+
let refreshResult;
|
|
329
341
|
try {
|
|
330
|
-
await refreshBaseline({
|
|
342
|
+
refreshResult = await refreshBaseline({
|
|
331
343
|
kind,
|
|
332
344
|
baseRef,
|
|
333
345
|
headRef,
|
|
@@ -341,6 +353,7 @@ export async function runRefreshCommit({
|
|
|
341
353
|
requiredScopeFilePredicate: buildRequiredScopeFilePredicate({
|
|
342
354
|
kind,
|
|
343
355
|
config,
|
|
356
|
+
getQuality: getQualityImpl,
|
|
344
357
|
}),
|
|
345
358
|
});
|
|
346
359
|
} catch (err) {
|
|
@@ -350,6 +363,18 @@ export async function runRefreshCommit({
|
|
|
350
363
|
};
|
|
351
364
|
}
|
|
352
365
|
|
|
366
|
+
// Story #4017 — when the service reports it persisted nothing, skip the
|
|
367
|
+
// stage/diff round-trip entirely: there is no drift to fold in.
|
|
368
|
+
if (refreshResult?.wrote === false) {
|
|
369
|
+
if (cycleState?.refreshedKinds instanceof Set) {
|
|
370
|
+
cycleState.refreshedKinds.add(kind);
|
|
371
|
+
}
|
|
372
|
+
logger?.info?.(
|
|
373
|
+
`[baseline-attribution-wiring] refresh wrote nothing for kind=${kind} (story-${storyId}); skipping.`,
|
|
374
|
+
);
|
|
375
|
+
return { ok: true, sha: '', skipped: true, reason: 'no-baseline-drift' };
|
|
376
|
+
}
|
|
377
|
+
|
|
353
378
|
const drift = stageAndCheckBaselineDrift({
|
|
354
379
|
cwd,
|
|
355
380
|
baselineFile: writePath,
|
|
@@ -366,6 +391,26 @@ export async function runRefreshCommit({
|
|
|
366
391
|
return { ok: true, sha: '', skipped: true, reason: 'no-baseline-drift' };
|
|
367
392
|
}
|
|
368
393
|
|
|
394
|
+
// Story #4017 — optional bounded-delta cap check (injected by the
|
|
395
|
+
// post-gates auto-refresh path). A refusal rolls the staged refresh back
|
|
396
|
+
// to HEAD so the merge consumes the pre-refresh baseline unchanged.
|
|
397
|
+
if (typeof capCheck === 'function') {
|
|
398
|
+
const verdict = await capCheck({ kind, writePath });
|
|
399
|
+
if (verdict && verdict.canAutoRefresh === false) {
|
|
400
|
+
const rel = path.isAbsolute(writePath)
|
|
401
|
+
? path.relative(cwd, writePath)
|
|
402
|
+
: writePath;
|
|
403
|
+
const posixRel = rel.split(path.sep).join('/');
|
|
404
|
+
const res = gitRunner.gitSpawn(cwd, 'checkout', 'HEAD', '--', posixRel);
|
|
405
|
+
if (res.status !== 0) {
|
|
406
|
+
logger?.warn?.(
|
|
407
|
+
`[baseline-attribution-wiring] failed to restore ${rel} after cap refusal: ${res.stderr || res.stdout}`,
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
return { ok: true, sha: '', refused: true, verdict };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
369
414
|
const subject = `chore(baselines): refresh ${kind} for story-${storyId}`;
|
|
370
415
|
const commitRes = gitRunner.gitSpawn(cwd, 'commit', '-m', subject);
|
|
371
416
|
if (commitRes.status !== 0) {
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
* Two projectors live here:
|
|
11
11
|
*
|
|
12
12
|
* - `projectMaintainabilityForGate` — reuses the MI projection from
|
|
13
|
-
* `close-validation.js` (Story #874).
|
|
13
|
+
* `close-validation/projections/maintainability.js` (Story #874).
|
|
14
14
|
* - `projectCrapForGate` / `projectCrapRegressions` — diff CRAP envelopes
|
|
15
15
|
* at `origin/<epicBranch>` vs `storyBranch`, optionally scoped to the
|
|
16
16
|
* Story's touched files (Story #1124).
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { readBaselineAtRef as defaultReadBaselineAtRef } from '../../../../baseline-loader.js';
|
|
20
|
-
import { projectMaintainabilityRegressions as defaultProjectMaintainabilityRegressions } from '../../../../close-validation.js';
|
|
20
|
+
import { projectMaintainabilityRegressions as defaultProjectMaintainabilityRegressions } from '../../../../close-validation/projections/maintainability.js';
|
|
21
21
|
import { getBaselines as defaultGetBaselines } from '../../../../config-resolver.js';
|
|
22
22
|
import {
|
|
23
23
|
computeStoryDiffPaths,
|
|
@@ -100,7 +100,7 @@ export function renderBaselineFrictionBody({ rows, epicId, storyId } = {}) {
|
|
|
100
100
|
const triage = [
|
|
101
101
|
'**Triage:**',
|
|
102
102
|
`1. Open each suspect Story above and run \`npm run maintainability:update\` (or \`npm run crap:update\`) on **its** branch, then commit with a \`baseline-refresh:\` subject and re-close it.`,
|
|
103
|
-
`2. Re-run \`/
|
|
103
|
+
`2. Re-run \`/deliver ${storyId}\` once the suspect Story's refresh has merged into \`epic/${epicId}\`.`,
|
|
104
104
|
`3. If the suspect column reads \`_unknown_\`, the path has no commit on \`epic/${epicId}\` — investigate the baseline file directly before refreshing.`,
|
|
105
105
|
];
|
|
106
106
|
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* format-autofix.js — self-healing biome-format step for story-close.
|
|
3
3
|
*
|
|
4
|
+
* Story #4017 collapsed the historical three-module split (a whole-tree
|
|
5
|
+
* fork, a scoped changed-file fork, and a shared plumbing module) into
|
|
6
|
+
* this single module. The two entry points differ only in file-scope,
|
|
7
|
+
* commit subject, and log level; the git/formatter plumbing is shared
|
|
8
|
+
* below.
|
|
9
|
+
*
|
|
4
10
|
* Background. The pre-merge `biome format` gate is check-only — it fails
|
|
5
11
|
* the close when the working tree has any format drift. In practice
|
|
6
12
|
* upstream waves frequently leave drift in files that lint-staged does
|
|
@@ -9,35 +15,31 @@
|
|
|
9
15
|
* `style:` commit before the close can resume. That manual loop is
|
|
10
16
|
* trivially automatable.
|
|
11
17
|
*
|
|
12
|
-
*
|
|
13
|
-
* gate chain. If it rewrites files, we stage and commit them on the
|
|
14
|
-
* Story branch with a `style:` subject so the merge into `epic/<id>`
|
|
15
|
-
* carries the fix forward atomically. The subsequent `biome format`
|
|
16
|
-
* check gate then passes deterministically.
|
|
18
|
+
* Entry points:
|
|
17
19
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* - {@link runFormatAutofix} — whole-tree heal (`biome format --write .`)
|
|
21
|
+
* before the pre-merge gate chain, for resume/legacy callers that have
|
|
22
|
+
* no Epic→Story diff anchor. Bounded by a wall-clock timeout
|
|
23
|
+
* (Story #2165).
|
|
24
|
+
* - {@link runScopedFormatAutofix} — Story #2533: scopes the formatter to
|
|
25
|
+
* the changed-file set between the Epic branch and the Story branch and
|
|
26
|
+
* folds auto-fixed paths into a dedicated `fix(story-close):` commit,
|
|
27
|
+
* emitting `Logger.warn` naming the files. Carries the worktree-cwd fix
|
|
28
|
+
* and branch assert from Story #3907.
|
|
25
29
|
*
|
|
26
|
-
* Dependencies are injected so unit tests pin behaviour without
|
|
27
|
-
*
|
|
30
|
+
* Dependencies are injected so unit tests pin behaviour without spawning
|
|
31
|
+
* git or biome.
|
|
28
32
|
*/
|
|
29
33
|
|
|
30
34
|
import { execFileSync } from 'node:child_process';
|
|
31
35
|
|
|
36
|
+
import { diffNameOnly } from '../../changed-files.js';
|
|
37
|
+
import { resolveFormatWriteCommand } from '../../close-validation/commands.js';
|
|
32
38
|
import { getQuality } from '../../config-resolver.js';
|
|
33
39
|
import { Logger as DefaultLogger } from '../../Logger.js';
|
|
34
|
-
import {
|
|
35
|
-
commitDirtyPaths,
|
|
36
|
-
listDirtyPaths,
|
|
37
|
-
resolveFormatterCmd,
|
|
38
|
-
} from './format-autofix-shared.js';
|
|
39
40
|
|
|
40
41
|
const TAG = '[format-autofix]';
|
|
42
|
+
const SCOPED_TAG = '[format-autofix-scoped]';
|
|
41
43
|
|
|
42
44
|
/**
|
|
43
45
|
* Story #2165 — exit code surfaced when the bounded `npx biome format
|
|
@@ -49,17 +51,161 @@ const TAG = '[format-autofix]';
|
|
|
49
51
|
*/
|
|
50
52
|
export const FORMAT_AUTOFIX_TIMEOUT_EXIT_CODE = 124;
|
|
51
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Run `git status --porcelain` and return the list of changed paths.
|
|
56
|
+
*
|
|
57
|
+
* Porcelain lines are `XY <path>` — exactly two status chars, one space,
|
|
58
|
+
* then the path. Leading whitespace inside the status pair is significant
|
|
59
|
+
* (e.g. ` M file` for unstaged-modified) so we slice a fixed 3 chars off
|
|
60
|
+
* the front rather than trimming.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} cwd
|
|
63
|
+
* @param {(args: string[], opts: object) => string} git
|
|
64
|
+
* @returns {string[]}
|
|
65
|
+
*/
|
|
66
|
+
export function listDirtyPaths(cwd, git) {
|
|
67
|
+
const out = git(['status', '--porcelain'], {
|
|
68
|
+
cwd,
|
|
69
|
+
encoding: 'utf8',
|
|
70
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
71
|
+
});
|
|
72
|
+
return out
|
|
73
|
+
.split('\n')
|
|
74
|
+
.filter((line) => line.length >= 4)
|
|
75
|
+
.map((line) => line.slice(3));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve the formatter write command from `project.commands.formatWrite`
|
|
80
|
+
* (falling back to the historical `npx biome format --write .`) and split
|
|
81
|
+
* it into an executable + argv pair ready for `execFileSync`.
|
|
82
|
+
*
|
|
83
|
+
* The whole-tree entry point runs the command verbatim (keeping the
|
|
84
|
+
* trailing `.` so biome formats the entire tree). The scoped entry point
|
|
85
|
+
* appends an explicit changed-file set, so it passes
|
|
86
|
+
* `dropTrailingDot: true` to strip the `.` before its file list.
|
|
87
|
+
*
|
|
88
|
+
* @param {{
|
|
89
|
+
* commands?: object,
|
|
90
|
+
* dropTrailingDot?: boolean,
|
|
91
|
+
* }} [opts]
|
|
92
|
+
* @returns {{ writeCmdString: string, writeCmd: string, writeArgs: string[] }}
|
|
93
|
+
*/
|
|
94
|
+
export function resolveFormatterCmd({
|
|
95
|
+
commands,
|
|
96
|
+
dropTrailingDot = false,
|
|
97
|
+
} = {}) {
|
|
98
|
+
// `resolveFormatWriteCommand` reads `config.project.commands`; wrap the
|
|
99
|
+
// caller-supplied `commands` map into that canonical shape.
|
|
100
|
+
const writeCmdString = resolveFormatWriteCommand({ project: { commands } });
|
|
101
|
+
const parts = writeCmdString.split(/\s+/).filter(Boolean);
|
|
102
|
+
if (dropTrailingDot && parts[parts.length - 1] === '.') parts.pop();
|
|
103
|
+
const [writeCmd, ...writeArgs] = parts;
|
|
104
|
+
return { writeCmdString, writeCmd, writeArgs };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolve the branch currently checked out at `cwd` via
|
|
109
|
+
* `git rev-parse --abbrev-ref HEAD`. Returns the trimmed branch name, or
|
|
110
|
+
* `null` when the call fails or the tree is in a detached-HEAD state
|
|
111
|
+
* (`HEAD`). Used as the commit-target guard before
|
|
112
|
+
* {@link commitDirtyPaths} writes a scoped-autofix commit, so the commit
|
|
113
|
+
* can never land on the wrong branch (e.g. the main checkout's `main`).
|
|
114
|
+
*
|
|
115
|
+
* @param {string} cwd
|
|
116
|
+
* @param {(args: string[], opts: object) => string} git
|
|
117
|
+
* @returns {string|null}
|
|
118
|
+
*/
|
|
119
|
+
export function currentBranch(cwd, git) {
|
|
120
|
+
try {
|
|
121
|
+
const out = git(['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
122
|
+
cwd,
|
|
123
|
+
encoding: 'utf8',
|
|
124
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
125
|
+
});
|
|
126
|
+
const branch = (out ?? '').toString().trim();
|
|
127
|
+
if (!branch || branch === 'HEAD') return null;
|
|
128
|
+
return branch;
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Stage every modified path (`git add -u`), commit with the caller-supplied
|
|
136
|
+
* `subject`, and return the short HEAD SHA. Hooks must run; we never pass
|
|
137
|
+
* `--no-verify` (project policy: never skip git hooks).
|
|
138
|
+
*
|
|
139
|
+
* @param {{
|
|
140
|
+
* cwd: string,
|
|
141
|
+
* git: (args: string[], opts: object) => string,
|
|
142
|
+
* subject: string,
|
|
143
|
+
* }} opts
|
|
144
|
+
* @returns {string} short HEAD SHA of the new commit
|
|
145
|
+
*/
|
|
146
|
+
export function commitDirtyPaths({ cwd, git, subject }) {
|
|
147
|
+
git(['add', '-u'], { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
148
|
+
git(['commit', '-m', subject], {
|
|
149
|
+
cwd,
|
|
150
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
151
|
+
});
|
|
152
|
+
return git(['rev-parse', '--short', 'HEAD'], {
|
|
153
|
+
cwd,
|
|
154
|
+
encoding: 'utf8',
|
|
155
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
156
|
+
}).trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Story #2165 — resolve the format-autofix spawn timeout. An explicit
|
|
161
|
+
* caller-supplied positive integer wins over both
|
|
162
|
+
* `delivery.quality.formatAutofix.timeoutMs` and the framework default
|
|
163
|
+
* (60 s). Any resolver failure surfaces as `null`; the caller treats that
|
|
164
|
+
* as "no timeout" and the spawn runs unbounded — same fail-open contract
|
|
165
|
+
* coverage-capture uses.
|
|
166
|
+
*/
|
|
167
|
+
function resolveFormatTimeoutMs({ timeoutMs, config }) {
|
|
168
|
+
if (
|
|
169
|
+
typeof timeoutMs === 'number' &&
|
|
170
|
+
Number.isInteger(timeoutMs) &&
|
|
171
|
+
timeoutMs > 0
|
|
172
|
+
) {
|
|
173
|
+
return timeoutMs;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const resolved = getQuality(config)?.formatAutofix?.timeoutMs;
|
|
177
|
+
if (
|
|
178
|
+
typeof resolved === 'number' &&
|
|
179
|
+
Number.isInteger(resolved) &&
|
|
180
|
+
resolved > 0
|
|
181
|
+
) {
|
|
182
|
+
return resolved;
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
// resolver failure → fall through to "no timeout"
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
52
190
|
/**
|
|
53
191
|
* Run `npx biome format --write .` then, if anything changed, commit
|
|
54
192
|
* the result on the Story branch with a `style:` subject. Returns a
|
|
55
193
|
* structured envelope so callers can log a single line.
|
|
56
194
|
*
|
|
57
|
-
* Story #2165: the formatter spawn is
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
195
|
+
* Story #2165: the formatter spawn is bounded by a wall-clock timeout
|
|
196
|
+
* (resolved from `delivery.quality.formatAutofix.timeoutMs`, default
|
|
197
|
+
* 60 s). A SIGKILL fired at the budget boundary is translated to the
|
|
198
|
+
* `timedOut: true` envelope below so the close orchestrator can flip the
|
|
199
|
+
* Story to `agent::blocked` with a friction comment naming the spawn,
|
|
200
|
+
* mirroring the coverage-capture pattern from Story #2142.
|
|
201
|
+
*
|
|
202
|
+
* The step is a no-op when:
|
|
203
|
+
* - biome rewrites nothing (clean tree),
|
|
204
|
+
* - the working tree is dirty for unrelated reasons (we refuse to
|
|
205
|
+
* opportunistically commit those — operator intent is unclear), or
|
|
206
|
+
* - `npx biome format --write` exits non-zero (we surface the error
|
|
207
|
+
* and let the existing format gate report it with the canonical
|
|
208
|
+
* hint).
|
|
63
209
|
*
|
|
64
210
|
* @param {{
|
|
65
211
|
* cwd: string,
|
|
@@ -96,7 +242,7 @@ export function runFormatAutofix({
|
|
|
96
242
|
// Resolve the formatter command from `project.commands.formatWrite` so
|
|
97
243
|
// Prettier / dprint repos use their own formatter. Falls back to the
|
|
98
244
|
// historical `npx biome format --write .` for repos that haven't opted in.
|
|
99
|
-
// The whole-tree
|
|
245
|
+
// The whole-tree entry point keeps the trailing `.` (formats the tree).
|
|
100
246
|
const { writeCmdString, writeCmd, writeArgs } = resolveFormatterCmd({
|
|
101
247
|
commands: config?.project?.commands,
|
|
102
248
|
});
|
|
@@ -112,10 +258,7 @@ export function runFormatAutofix({
|
|
|
112
258
|
return { ran: false, committed: false, dirtyPathsBefore: dirtyBefore };
|
|
113
259
|
}
|
|
114
260
|
|
|
115
|
-
// Story #2165 — bounded wall-clock for the formatter spawn.
|
|
116
|
-
// through `getQuality` so consumers can tune via
|
|
117
|
-
// `delivery.quality.formatAutofix.timeoutMs`; an explicit caller-supplied
|
|
118
|
-
// positive integer wins over both the config and the framework default.
|
|
261
|
+
// Story #2165 — bounded wall-clock for the formatter spawn.
|
|
119
262
|
// execFileSync's contract: on a SIGKILL trip the thrown error carries
|
|
120
263
|
// `err.signal === 'SIGKILL'` and `err.status === null`, so we branch on
|
|
121
264
|
// that to surface the 124 envelope below — same shape coverage-capture
|
|
@@ -185,32 +328,193 @@ export function runFormatAutofix({
|
|
|
185
328
|
}
|
|
186
329
|
|
|
187
330
|
/**
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* `
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
331
|
+
* List the files changed between `epicBranch` and `storyBranch` using the
|
|
332
|
+
* three-dot merge-base diff. Delegates parsing to `diffNameOnly` from
|
|
333
|
+
* `changed-files.js` so the stdout → path-list conversion lives in one place.
|
|
334
|
+
*
|
|
335
|
+
* The `git` parameter uses the caller's local interface:
|
|
336
|
+
* `(args: string[], opts: object) => string`. A bridge adapter wraps it into
|
|
337
|
+
* the `gitSpawn(cwd, ...args)` shape that `diffNameOnly` expects.
|
|
338
|
+
*
|
|
339
|
+
* @param {{ cwd: string, epicBranch: string, storyBranch: string, git: Function }} opts
|
|
340
|
+
* @returns {string[]}
|
|
194
341
|
*/
|
|
195
|
-
function
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
342
|
+
function listChangedFiles({ cwd, epicBranch, storyBranch, git }) {
|
|
343
|
+
// Bridge the (args, opts) → string interface into gitSpawn(cwd, ...args).
|
|
344
|
+
const gitSpawn = (_cwd, ...args) => {
|
|
345
|
+
try {
|
|
346
|
+
const stdout = git(args, {
|
|
347
|
+
cwd: _cwd,
|
|
348
|
+
encoding: 'utf8',
|
|
349
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
350
|
+
});
|
|
351
|
+
return { status: 0, stdout: stdout ?? '', stderr: '' };
|
|
352
|
+
} catch (err) {
|
|
353
|
+
return {
|
|
354
|
+
status: err.status ?? 1,
|
|
355
|
+
stdout: err.stdout ?? '',
|
|
356
|
+
stderr: err.stderr ?? err.message,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
return diffNameOnly({
|
|
361
|
+
range: `${epicBranch}...${storyBranch}`,
|
|
362
|
+
cwd,
|
|
363
|
+
gitSpawn,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Story #2533 — run `biome format --write <changedFiles>` on the Epic→Story
|
|
369
|
+
* diff. If any file is modified, stage and commit the changes on the Story
|
|
370
|
+
* branch with a conventional `fix(story-close):` subject and emit a
|
|
371
|
+
* `Logger.warn` naming the auto-fixed files. Returns a structured
|
|
372
|
+
* envelope so callers can log a single line.
|
|
373
|
+
*
|
|
374
|
+
* Why scoped + warn-level. The Tech Spec (Epic #2527, Story 5) calls out
|
|
375
|
+
* that format diffs introduced by Story commits should never surface to
|
|
376
|
+
* Phase 3 close-validation. The whole-tree autofix already covers that,
|
|
377
|
+
* but emits `info` so operators routinely miss it. This entry point emits
|
|
378
|
+
* `Logger.warn` naming the auto-fixed files so the signal is visible in
|
|
379
|
+
* the close transcript and downstream ledger.
|
|
380
|
+
*
|
|
381
|
+
* No-op envelopes:
|
|
382
|
+
* - `{ ran: false, reason: 'no-changed-files' }` — empty diff.
|
|
383
|
+
* - `{ ran: false, reason: 'dirty-tree' }` — refused to
|
|
384
|
+
* absorb pre-existing edits.
|
|
385
|
+
* - `{ ran: true, committed: false }` — formatter
|
|
386
|
+
* was clean.
|
|
387
|
+
*
|
|
388
|
+
* **Worktree scope (Story #3907).** All git + formatter operations run in
|
|
389
|
+
* `worktreePath` (the Story worktree where `story-<id>` is checked out), not
|
|
390
|
+
* `cwd` (the main checkout). The earlier implementation ran every step
|
|
391
|
+
* against `cwd`, so the `git add -u` + `git commit` could land an unreviewed
|
|
392
|
+
* `fix(story-close):` commit on whatever branch the main checkout happened to
|
|
393
|
+
* have out — including `main`. Before committing, the worktree's checked-out
|
|
394
|
+
* branch is asserted to equal `storyBranch`; a mismatch refuses to commit and
|
|
395
|
+
* returns `{ ran: true, committed: false, reason: 'wrong-branch' }` so a
|
|
396
|
+
* stale-state checkout can never absorb the autofix into the wrong history.
|
|
397
|
+
* `worktreePath` defaults to `cwd` for the resume/legacy callers that have no
|
|
398
|
+
* separate worktree.
|
|
399
|
+
*
|
|
400
|
+
* @param {{
|
|
401
|
+
* cwd: string,
|
|
402
|
+
* worktreePath?: string,
|
|
403
|
+
* storyId: number|string,
|
|
404
|
+
* epicBranch: string,
|
|
405
|
+
* storyBranch: string,
|
|
406
|
+
* config?: object,
|
|
407
|
+
* logger?: object,
|
|
408
|
+
* spawnSync?: typeof execFileSync,
|
|
409
|
+
* gitSync?: (args: string[], opts: object) => string,
|
|
410
|
+
* }} opts
|
|
411
|
+
* @returns {{
|
|
412
|
+
* ran: boolean,
|
|
413
|
+
* committed: boolean,
|
|
414
|
+
* sha?: string,
|
|
415
|
+
* modifiedPaths?: string[],
|
|
416
|
+
* reason?: string,
|
|
417
|
+
* }}
|
|
418
|
+
*/
|
|
419
|
+
export function runScopedFormatAutofix({
|
|
420
|
+
cwd,
|
|
421
|
+
worktreePath,
|
|
422
|
+
storyId,
|
|
423
|
+
epicBranch,
|
|
424
|
+
storyBranch,
|
|
425
|
+
config,
|
|
426
|
+
logger = DefaultLogger,
|
|
427
|
+
spawnSync = execFileSync,
|
|
428
|
+
gitSync,
|
|
429
|
+
} = {}) {
|
|
430
|
+
if (!cwd) throw new Error('runScopedFormatAutofix: cwd is required');
|
|
431
|
+
if (!epicBranch)
|
|
432
|
+
throw new Error('runScopedFormatAutofix: epicBranch is required');
|
|
433
|
+
if (!storyBranch)
|
|
434
|
+
throw new Error('runScopedFormatAutofix: storyBranch is required');
|
|
435
|
+
|
|
436
|
+
// Story #3907 — the formatter writes + the commit must land in the Story
|
|
437
|
+
// worktree, never the main checkout. Fall back to `cwd` only for callers
|
|
438
|
+
// that do not run under worktree isolation.
|
|
439
|
+
const workTree = worktreePath || cwd;
|
|
440
|
+
|
|
441
|
+
const git = gitSync ?? ((args, opts) => spawnSync('git', args, opts));
|
|
442
|
+
|
|
443
|
+
// Resolve the formatter base command (e.g. `npx biome format --write`).
|
|
444
|
+
// We drop a trailing `.` so we can append the changed-file set explicitly.
|
|
445
|
+
const { writeCmdString, writeCmd, writeArgs } = resolveFormatterCmd({
|
|
446
|
+
commands: config?.project?.commands,
|
|
447
|
+
dropTrailingDot: true,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
const changed = listChangedFiles({
|
|
451
|
+
cwd: workTree,
|
|
452
|
+
epicBranch,
|
|
453
|
+
storyBranch,
|
|
454
|
+
git,
|
|
455
|
+
});
|
|
456
|
+
if (changed.length === 0) {
|
|
457
|
+
logger.info?.(
|
|
458
|
+
`${SCOPED_TAG} skipped — no changed files between ${epicBranch} and ${storyBranch}.`,
|
|
459
|
+
);
|
|
460
|
+
return { ran: false, committed: false, reason: 'no-changed-files' };
|
|
202
461
|
}
|
|
462
|
+
|
|
463
|
+
const dirtyBefore = listDirtyPaths(workTree, git);
|
|
464
|
+
if (dirtyBefore.length) {
|
|
465
|
+
logger.info?.(
|
|
466
|
+
`${SCOPED_TAG} skipped — working tree dirty before scoped autofix (${dirtyBefore.length} paths).`,
|
|
467
|
+
);
|
|
468
|
+
return { ran: false, committed: false, reason: 'dirty-tree' };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Run the formatter against the changed-file set. We tolerate non-zero
|
|
472
|
+
// exit because the downstream check gate is the source of truth for
|
|
473
|
+
// "did formatting succeed".
|
|
203
474
|
try {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// resolver failure → fall through to "no timeout"
|
|
475
|
+
spawnSync(writeCmd, [...writeArgs, ...changed], {
|
|
476
|
+
cwd: workTree,
|
|
477
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
478
|
+
encoding: 'utf8',
|
|
479
|
+
});
|
|
480
|
+
} catch (err) {
|
|
481
|
+
logger.warn?.(
|
|
482
|
+
`${SCOPED_TAG} \`${writeCmdString}\` on ${changed.length} changed file(s) exited non-zero (${err?.status ?? 'unknown'}); falling through to the format check gate.`,
|
|
483
|
+
);
|
|
214
484
|
}
|
|
215
|
-
|
|
485
|
+
|
|
486
|
+
const dirtyAfter = listDirtyPaths(workTree, git);
|
|
487
|
+
if (!dirtyAfter.length) {
|
|
488
|
+
logger.info?.(
|
|
489
|
+
`${SCOPED_TAG} no format drift on ${changed.length} changed file(s).`,
|
|
490
|
+
);
|
|
491
|
+
return { ran: true, committed: false };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Story #3907 — assert the worktree is actually on `storyBranch` before we
|
|
495
|
+
// stage + commit. Without this guard a stale-state checkout (or a
|
|
496
|
+
// mis-wired `cwd`) could absorb the autofix onto the wrong branch (incl.
|
|
497
|
+
// `main`). A mismatch refuses to commit and leaves the format drift for the
|
|
498
|
+
// downstream check gate to surface.
|
|
499
|
+
const onBranch = currentBranch(workTree, git);
|
|
500
|
+
if (onBranch !== storyBranch) {
|
|
501
|
+
logger.warn?.(
|
|
502
|
+
`${SCOPED_TAG} refusing to commit — worktree ${workTree} is on "${onBranch ?? 'unknown'}", expected "${storyBranch}". ` +
|
|
503
|
+
`${dirtyAfter.length} format-drift path(s) left for the check gate.`,
|
|
504
|
+
);
|
|
505
|
+
return { ran: true, committed: false, reason: 'wrong-branch' };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Stage every modified path and commit. Hooks must run; do not pass
|
|
509
|
+
// --no-verify (project policy: never skip git hooks).
|
|
510
|
+
const subject = `fix(story-close): auto-apply biome format in scoped lint (story #${storyId})`;
|
|
511
|
+
const sha = commitDirtyPaths({ cwd: workTree, git, subject });
|
|
512
|
+
|
|
513
|
+
// The warn-level emission is the Tech Spec contract — operators read
|
|
514
|
+
// this line in the close transcript to know auto-fix landed in the
|
|
515
|
+
// close commit, and downstream ledger inspectors filter on it.
|
|
516
|
+
logger.warn?.(
|
|
517
|
+
`${SCOPED_TAG} auto-applied biome format to ${dirtyAfter.length} path(s) on story #${storyId}: ${dirtyAfter.join(', ')}; committed as ${sha}.`,
|
|
518
|
+
);
|
|
519
|
+
return { ran: true, committed: true, sha, modifiedPaths: dirtyAfter };
|
|
216
520
|
}
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* CC < 12 phase-file budget).
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import { PROJECT_ROOT } from '../../../config-resolver.js';
|
|
24
23
|
import { Logger } from '../../../Logger.js';
|
|
24
|
+
import { PROJECT_ROOT } from '../../../project-root.js';
|
|
25
25
|
import { STATE_LABELS } from '../../ticketing.js';
|
|
26
26
|
import { runFinalizeMerge, runResumeMerge } from '../merge-runner.js';
|
|
27
27
|
import { runPostMergeClose } from '../post-merge-close.js';
|
|
@@ -30,8 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
import { Logger } from '../../../Logger.js';
|
|
32
32
|
import { runPreMergeGatesWithAttribution } from '../baseline-attribution-wiring.js';
|
|
33
|
-
import { runFormatAutofix } from '../format-autofix.js';
|
|
34
|
-
import { runScopedFormatAutofix } from '../format-autofix-scoped.js';
|
|
33
|
+
import { runFormatAutofix, runScopedFormatAutofix } from '../format-autofix.js';
|
|
35
34
|
import { emitBlockedCloseResult } from '../merge-runner.js';
|
|
36
35
|
import { emitMaintainabilityProjection } from '../pre-merge-validation.js';
|
|
37
36
|
|
|
@@ -223,6 +222,7 @@ export async function runPreMergeValidation({
|
|
|
223
222
|
phaseTimer,
|
|
224
223
|
provider,
|
|
225
224
|
bus = null,
|
|
225
|
+
cycleState = null,
|
|
226
226
|
}) {
|
|
227
227
|
// Story #2533: scope-narrowed biome-format auto-apply on the Epic→Story
|
|
228
228
|
// diff. The scoped step folds format drift introduced by Story commits
|
|
@@ -273,6 +273,7 @@ export async function runPreMergeValidation({
|
|
|
273
273
|
config,
|
|
274
274
|
storyId,
|
|
275
275
|
epicId,
|
|
276
|
+
cycleState,
|
|
276
277
|
useEvidence: !noEvidenceFlag,
|
|
277
278
|
phaseTimer,
|
|
278
279
|
provider,
|