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
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* format-autofix-scoped.js — Story #2533: scoped biome-format auto-apply
|
|
3
|
-
* inside story-close's pre-merge gate chain.
|
|
4
|
-
*
|
|
5
|
-
* Background. The whole-tree `runFormatAutofix` (sibling module) heals
|
|
6
|
-
* drift across `.` before the gate chain runs. That step is intentionally
|
|
7
|
-
* broad because it covers files (JSON / YAML / config) that lint-staged
|
|
8
|
-
* does not glob. This module is the narrower companion: it scopes
|
|
9
|
-
* `biome format --write` to the **changed-file set** between the Epic
|
|
10
|
-
* branch and the Story branch and folds any auto-fixed paths into a
|
|
11
|
-
* dedicated commit on the Story branch *before* `biome ci` runs in the
|
|
12
|
-
* gate chain.
|
|
13
|
-
*
|
|
14
|
-
* Why scoped + warn-level. The Tech Spec (Epic #2527, Story 5) calls out
|
|
15
|
-
* that format diffs introduced by Story commits should never surface to
|
|
16
|
-
* Phase 3 close-validation. The whole-tree autofix already covers that,
|
|
17
|
-
* but emits `info` so operators routinely miss it. This module emits
|
|
18
|
-
* `Logger.warn` naming the auto-fixed files so the signal is visible in
|
|
19
|
-
* the close transcript and downstream ledger.
|
|
20
|
-
*
|
|
21
|
-
* Dependencies are injected so unit tests pin behaviour without spawning
|
|
22
|
-
* git or biome.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { execFileSync } from 'node:child_process';
|
|
26
|
-
|
|
27
|
-
import { diffNameOnly } from '../../changed-files.js';
|
|
28
|
-
import { Logger as DefaultLogger } from '../../Logger.js';
|
|
29
|
-
import {
|
|
30
|
-
commitDirtyPaths,
|
|
31
|
-
currentBranch,
|
|
32
|
-
listDirtyPaths,
|
|
33
|
-
resolveFormatterCmd,
|
|
34
|
-
} from './format-autofix-shared.js';
|
|
35
|
-
|
|
36
|
-
const TAG = '[format-autofix-scoped]';
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* List the files changed between `epicBranch` and `storyBranch` using the
|
|
40
|
-
* three-dot merge-base diff. Delegates parsing to `diffNameOnly` from
|
|
41
|
-
* `changed-files.js` so the stdout → path-list conversion lives in one place.
|
|
42
|
-
*
|
|
43
|
-
* The `git` parameter uses the caller's local interface:
|
|
44
|
-
* `(args: string[], opts: object) => string`. A bridge adapter wraps it into
|
|
45
|
-
* the `gitSpawn(cwd, ...args)` shape that `diffNameOnly` expects.
|
|
46
|
-
*
|
|
47
|
-
* @param {{ cwd: string, epicBranch: string, storyBranch: string, git: Function }} opts
|
|
48
|
-
* @returns {string[]}
|
|
49
|
-
*/
|
|
50
|
-
function listChangedFiles({ cwd, epicBranch, storyBranch, git }) {
|
|
51
|
-
// Bridge the (args, opts) → string interface into gitSpawn(cwd, ...args).
|
|
52
|
-
const gitSpawn = (_cwd, ...args) => {
|
|
53
|
-
try {
|
|
54
|
-
const stdout = git(args, {
|
|
55
|
-
cwd: _cwd,
|
|
56
|
-
encoding: 'utf8',
|
|
57
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
58
|
-
});
|
|
59
|
-
return { status: 0, stdout: stdout ?? '', stderr: '' };
|
|
60
|
-
} catch (err) {
|
|
61
|
-
return {
|
|
62
|
-
status: err.status ?? 1,
|
|
63
|
-
stdout: err.stdout ?? '',
|
|
64
|
-
stderr: err.stderr ?? err.message,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
return diffNameOnly({
|
|
69
|
-
range: `${epicBranch}...${storyBranch}`,
|
|
70
|
-
cwd,
|
|
71
|
-
gitSpawn,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Run `biome format --write <changedFiles>` on the Epic→Story diff. If
|
|
77
|
-
* any file is modified, stage and commit the changes on the Story branch
|
|
78
|
-
* with a conventional `fix(story-close):` subject and emit a
|
|
79
|
-
* `Logger.warn` naming the auto-fixed files. Returns a structured
|
|
80
|
-
* envelope so callers can log a single line.
|
|
81
|
-
*
|
|
82
|
-
* No-op envelopes:
|
|
83
|
-
* - `{ ran: false, reason: 'no-changed-files' }` — empty diff.
|
|
84
|
-
* - `{ ran: false, reason: 'dirty-tree' }` — refused to
|
|
85
|
-
* absorb pre-existing edits.
|
|
86
|
-
* - `{ ran: true, committed: false }` — formatter
|
|
87
|
-
* was clean.
|
|
88
|
-
*
|
|
89
|
-
* **Worktree scope (Story #3907).** All git + formatter operations run in
|
|
90
|
-
* `worktreePath` (the Story worktree where `story-<id>` is checked out), not
|
|
91
|
-
* `cwd` (the main checkout). The earlier implementation ran every step
|
|
92
|
-
* against `cwd`, so the `git add -u` + `git commit` could land an unreviewed
|
|
93
|
-
* `fix(story-close):` commit on whatever branch the main checkout happened to
|
|
94
|
-
* have out — including `main`. Before committing, the worktree's checked-out
|
|
95
|
-
* branch is asserted to equal `storyBranch`; a mismatch refuses to commit and
|
|
96
|
-
* returns `{ ran: true, committed: false, reason: 'wrong-branch' }` so a
|
|
97
|
-
* stale-state checkout can never absorb the autofix into the wrong history.
|
|
98
|
-
* `worktreePath` defaults to `cwd` for the resume/legacy callers that have no
|
|
99
|
-
* separate worktree.
|
|
100
|
-
*
|
|
101
|
-
* @param {{
|
|
102
|
-
* cwd: string,
|
|
103
|
-
* worktreePath?: string,
|
|
104
|
-
* storyId: number|string,
|
|
105
|
-
* epicBranch: string,
|
|
106
|
-
* storyBranch: string,
|
|
107
|
-
* config?: object,
|
|
108
|
-
* logger?: object,
|
|
109
|
-
* spawnSync?: typeof execFileSync,
|
|
110
|
-
* gitSync?: (args: string[], opts: object) => string,
|
|
111
|
-
* }} opts
|
|
112
|
-
* @returns {{
|
|
113
|
-
* ran: boolean,
|
|
114
|
-
* committed: boolean,
|
|
115
|
-
* sha?: string,
|
|
116
|
-
* modifiedPaths?: string[],
|
|
117
|
-
* reason?: string,
|
|
118
|
-
* }}
|
|
119
|
-
*/
|
|
120
|
-
export function runScopedFormatAutofix({
|
|
121
|
-
cwd,
|
|
122
|
-
worktreePath,
|
|
123
|
-
storyId,
|
|
124
|
-
epicBranch,
|
|
125
|
-
storyBranch,
|
|
126
|
-
config,
|
|
127
|
-
logger = DefaultLogger,
|
|
128
|
-
spawnSync = execFileSync,
|
|
129
|
-
gitSync,
|
|
130
|
-
} = {}) {
|
|
131
|
-
if (!cwd) throw new Error('runScopedFormatAutofix: cwd is required');
|
|
132
|
-
if (!epicBranch)
|
|
133
|
-
throw new Error('runScopedFormatAutofix: epicBranch is required');
|
|
134
|
-
if (!storyBranch)
|
|
135
|
-
throw new Error('runScopedFormatAutofix: storyBranch is required');
|
|
136
|
-
|
|
137
|
-
// Story #3907 — the formatter writes + the commit must land in the Story
|
|
138
|
-
// worktree, never the main checkout. Fall back to `cwd` only for callers
|
|
139
|
-
// that do not run under worktree isolation.
|
|
140
|
-
const workTree = worktreePath || cwd;
|
|
141
|
-
|
|
142
|
-
const git = gitSync ?? ((args, opts) => spawnSync('git', args, opts));
|
|
143
|
-
|
|
144
|
-
// Resolve the formatter base command (e.g. `npx biome format --write`).
|
|
145
|
-
// We drop a trailing `.` so we can append the changed-file set explicitly.
|
|
146
|
-
const { writeCmdString, writeCmd, writeArgs } = resolveFormatterCmd({
|
|
147
|
-
commands: config?.project?.commands,
|
|
148
|
-
dropTrailingDot: true,
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const changed = listChangedFiles({
|
|
152
|
-
cwd: workTree,
|
|
153
|
-
epicBranch,
|
|
154
|
-
storyBranch,
|
|
155
|
-
git,
|
|
156
|
-
});
|
|
157
|
-
if (changed.length === 0) {
|
|
158
|
-
logger.info?.(
|
|
159
|
-
`${TAG} skipped — no changed files between ${epicBranch} and ${storyBranch}.`,
|
|
160
|
-
);
|
|
161
|
-
return { ran: false, committed: false, reason: 'no-changed-files' };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const dirtyBefore = listDirtyPaths(workTree, git);
|
|
165
|
-
if (dirtyBefore.length) {
|
|
166
|
-
logger.info?.(
|
|
167
|
-
`${TAG} skipped — working tree dirty before scoped autofix (${dirtyBefore.length} paths).`,
|
|
168
|
-
);
|
|
169
|
-
return { ran: false, committed: false, reason: 'dirty-tree' };
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Run the formatter against the changed-file set. We tolerate non-zero
|
|
173
|
-
// exit because the downstream check gate is the source of truth for
|
|
174
|
-
// "did formatting succeed".
|
|
175
|
-
try {
|
|
176
|
-
spawnSync(writeCmd, [...writeArgs, ...changed], {
|
|
177
|
-
cwd: workTree,
|
|
178
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
179
|
-
encoding: 'utf8',
|
|
180
|
-
});
|
|
181
|
-
} catch (err) {
|
|
182
|
-
logger.warn?.(
|
|
183
|
-
`${TAG} \`${writeCmdString}\` on ${changed.length} changed file(s) exited non-zero (${err?.status ?? 'unknown'}); falling through to the format check gate.`,
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const dirtyAfter = listDirtyPaths(workTree, git);
|
|
188
|
-
if (!dirtyAfter.length) {
|
|
189
|
-
logger.info?.(
|
|
190
|
-
`${TAG} no format drift on ${changed.length} changed file(s).`,
|
|
191
|
-
);
|
|
192
|
-
return { ran: true, committed: false };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Story #3907 — assert the worktree is actually on `storyBranch` before we
|
|
196
|
-
// stage + commit. Without this guard a stale-state checkout (or a
|
|
197
|
-
// mis-wired `cwd`) could absorb the autofix onto the wrong branch (incl.
|
|
198
|
-
// `main`). A mismatch refuses to commit and leaves the format drift for the
|
|
199
|
-
// downstream check gate to surface.
|
|
200
|
-
const onBranch = currentBranch(workTree, git);
|
|
201
|
-
if (onBranch !== storyBranch) {
|
|
202
|
-
logger.warn?.(
|
|
203
|
-
`${TAG} refusing to commit — worktree ${workTree} is on "${onBranch ?? 'unknown'}", expected "${storyBranch}". ` +
|
|
204
|
-
`${dirtyAfter.length} format-drift path(s) left for the check gate.`,
|
|
205
|
-
);
|
|
206
|
-
return { ran: true, committed: false, reason: 'wrong-branch' };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Stage every modified path and commit. Hooks must run; do not pass
|
|
210
|
-
// --no-verify (project policy: never skip git hooks).
|
|
211
|
-
const subject = `fix(story-close): auto-apply biome format in scoped lint (story #${storyId})`;
|
|
212
|
-
const sha = commitDirtyPaths({ cwd: workTree, git, subject });
|
|
213
|
-
|
|
214
|
-
// The warn-level emission is the Tech Spec contract — operators read
|
|
215
|
-
// this line in the close transcript to know auto-fix landed in the
|
|
216
|
-
// close commit, and downstream ledger inspectors filter on it.
|
|
217
|
-
logger.warn?.(
|
|
218
|
-
`${TAG} auto-applied biome format to ${dirtyAfter.length} path(s) on story #${storyId}: ${dirtyAfter.join(', ')}; committed as ${sha}.`,
|
|
219
|
-
);
|
|
220
|
-
return { ran: true, committed: true, sha, modifiedPaths: dirtyAfter };
|
|
221
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* format-autofix-shared.js — Story #3332 (Epic #3316): single-source the
|
|
3
|
-
* git/formatter plumbing shared by the two format-autofix forks.
|
|
4
|
-
*
|
|
5
|
-
* `format-autofix.js` (whole-tree heal) and `format-autofix-scoped.js`
|
|
6
|
-
* (Epic→Story changed-file heal) historically each carried their own copy
|
|
7
|
-
* of the porcelain-status parse, the formatter-command resolution, and the
|
|
8
|
-
* stage→commit→rev-parse sequence. The three forked helpers are
|
|
9
|
-
* byte-for-byte equivalent apart from cosmetics, so a fix to (say) the
|
|
10
|
-
* porcelain-line slice had to land twice. This module is the single home
|
|
11
|
-
* for all three; the two forks now differ only in file-scope, commit
|
|
12
|
-
* subject, and log level.
|
|
13
|
-
*
|
|
14
|
-
* Every helper takes an injected `git` runner (`(args, opts) => string`) so
|
|
15
|
-
* unit tests pin behaviour without spawning git.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { resolveFormatWriteCommand } from '../../close-validation.js';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Run `git status --porcelain` and return the list of changed paths.
|
|
22
|
-
*
|
|
23
|
-
* Porcelain lines are `XY <path>` — exactly two status chars, one space,
|
|
24
|
-
* then the path. Leading whitespace inside the status pair is significant
|
|
25
|
-
* (e.g. ` M file` for unstaged-modified) so we slice a fixed 3 chars off
|
|
26
|
-
* the front rather than trimming.
|
|
27
|
-
*
|
|
28
|
-
* @param {string} cwd
|
|
29
|
-
* @param {(args: string[], opts: object) => string} git
|
|
30
|
-
* @returns {string[]}
|
|
31
|
-
*/
|
|
32
|
-
export function listDirtyPaths(cwd, git) {
|
|
33
|
-
const out = git(['status', '--porcelain'], {
|
|
34
|
-
cwd,
|
|
35
|
-
encoding: 'utf8',
|
|
36
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
37
|
-
});
|
|
38
|
-
return out
|
|
39
|
-
.split('\n')
|
|
40
|
-
.filter((line) => line.length >= 4)
|
|
41
|
-
.map((line) => line.slice(3));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Resolve the formatter write command from `project.commands.formatWrite`
|
|
46
|
-
* (falling back to the historical `npx biome format --write .`) and split
|
|
47
|
-
* it into an executable + argv pair ready for `execFileSync`.
|
|
48
|
-
*
|
|
49
|
-
* The whole-tree fork runs the command verbatim (keeping the trailing `.`
|
|
50
|
-
* so biome formats the entire tree). The scoped fork appends an explicit
|
|
51
|
-
* changed-file set, so it passes `dropTrailingDot: true` to strip the `.`
|
|
52
|
-
* before its file list.
|
|
53
|
-
*
|
|
54
|
-
* @param {{
|
|
55
|
-
* commands?: object,
|
|
56
|
-
* dropTrailingDot?: boolean,
|
|
57
|
-
* }} [opts]
|
|
58
|
-
* @returns {{ writeCmdString: string, writeCmd: string, writeArgs: string[] }}
|
|
59
|
-
*/
|
|
60
|
-
export function resolveFormatterCmd({
|
|
61
|
-
commands,
|
|
62
|
-
dropTrailingDot = false,
|
|
63
|
-
} = {}) {
|
|
64
|
-
// `resolveFormatWriteCommand` reads `config.project.commands`; wrap the
|
|
65
|
-
// caller-supplied `commands` map into that canonical shape.
|
|
66
|
-
const writeCmdString = resolveFormatWriteCommand({ project: { commands } });
|
|
67
|
-
const parts = writeCmdString.split(/\s+/).filter(Boolean);
|
|
68
|
-
if (dropTrailingDot && parts[parts.length - 1] === '.') parts.pop();
|
|
69
|
-
const [writeCmd, ...writeArgs] = parts;
|
|
70
|
-
return { writeCmdString, writeCmd, writeArgs };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Resolve the branch currently checked out at `cwd` via
|
|
75
|
-
* `git rev-parse --abbrev-ref HEAD`. Returns the trimmed branch name, or
|
|
76
|
-
* `null` when the call fails or the tree is in a detached-HEAD state
|
|
77
|
-
* (`HEAD`). Used as the commit-target guard before
|
|
78
|
-
* {@link commitDirtyPaths} writes a scoped-autofix commit, so the commit
|
|
79
|
-
* can never land on the wrong branch (e.g. the main checkout's `main`).
|
|
80
|
-
*
|
|
81
|
-
* @param {string} cwd
|
|
82
|
-
* @param {(args: string[], opts: object) => string} git
|
|
83
|
-
* @returns {string|null}
|
|
84
|
-
*/
|
|
85
|
-
export function currentBranch(cwd, git) {
|
|
86
|
-
try {
|
|
87
|
-
const out = git(['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
88
|
-
cwd,
|
|
89
|
-
encoding: 'utf8',
|
|
90
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
91
|
-
});
|
|
92
|
-
const branch = (out ?? '').toString().trim();
|
|
93
|
-
if (!branch || branch === 'HEAD') return null;
|
|
94
|
-
return branch;
|
|
95
|
-
} catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Stage every modified path (`git add -u`), commit with the caller-supplied
|
|
102
|
-
* `subject`, and return the short HEAD SHA. Hooks must run; we never pass
|
|
103
|
-
* `--no-verify` (project policy: never skip git hooks).
|
|
104
|
-
*
|
|
105
|
-
* @param {{
|
|
106
|
-
* cwd: string,
|
|
107
|
-
* git: (args: string[], opts: object) => string,
|
|
108
|
-
* subject: string,
|
|
109
|
-
* }} opts
|
|
110
|
-
* @returns {string} short HEAD SHA of the new commit
|
|
111
|
-
*/
|
|
112
|
-
export function commitDirtyPaths({ cwd, git, subject }) {
|
|
113
|
-
git(['add', '-u'], { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
114
|
-
git(['commit', '-m', subject], {
|
|
115
|
-
cwd,
|
|
116
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
117
|
-
});
|
|
118
|
-
return git(['rev-parse', '--short', 'HEAD'], {
|
|
119
|
-
cwd,
|
|
120
|
-
encoding: 'utf8',
|
|
121
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
122
|
-
}).trim();
|
|
123
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* task-utils.js
|
|
3
|
-
*
|
|
4
|
-
* Shared utilities for inspecting task objects throughout the pipeline.
|
|
5
|
-
* Centralises the bookend-detection predicate so that any future addition of a
|
|
6
|
-
* new bookend type only requires a change in one place.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Returns true when the given task is a bookend (lifecycle-management) task.
|
|
11
|
-
*
|
|
12
|
-
* Bookend tasks are distinguished from regular development tasks by one of the
|
|
13
|
-
* following flags being truthy:
|
|
14
|
-
* - isQA — automated testing phase
|
|
15
|
-
* - isCodeReview — architectural review phase
|
|
16
|
-
* - isRetro — retrospective phase
|
|
17
|
-
* - isCloseSprint — sprint close-out + tagging phase
|
|
18
|
-
*
|
|
19
|
-
* @param {object} task - A task object from the sprint manifest.
|
|
20
|
-
* @returns {boolean}
|
|
21
|
-
*/
|
|
22
|
-
export function isBookendTask(task) {
|
|
23
|
-
return Boolean(
|
|
24
|
-
task.isQA || task.isCodeReview || task.isRetro || task.isCloseSprint,
|
|
25
|
-
);
|
|
26
|
-
}
|
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/* node:coverage ignore file */
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* story-deliver-prepare.js — post-init / pre-implementation prep step.
|
|
6
|
-
*
|
|
7
|
-
* After `story-init.js` has prepared the worktree and the operator (or
|
|
8
|
-
* sub-agent) has `cd`'d into the workCwd, this CLI consolidates the three
|
|
9
|
-
* things `/story-deliver` Step 0.5/0.6 used to express in English prose:
|
|
10
|
-
*
|
|
11
|
-
* 1. Read the `story-init` structured comment off the Story ticket via
|
|
12
|
-
* `findStructuredComment`. The comment carries `workCwd`, the install
|
|
13
|
-
* tri-state, and the canonical task list — re-derived here so a
|
|
14
|
-
* resumed run doesn't have to retain `story-init` stdout.
|
|
15
|
-
*
|
|
16
|
-
* 2. Apply the `dependenciesInstalled` tri-state truth table:
|
|
17
|
-
* - `'true'` → install already succeeded; skip.
|
|
18
|
-
* - `'false'` → install was attempted and failed; run the install
|
|
19
|
-
* command (default `npm ci`; `project.commands`
|
|
20
|
-
* doesn't carry a dedicated `install` key today —
|
|
21
|
-
* the `commands.test` adjacency is the spec hook for
|
|
22
|
-
* a future override).
|
|
23
|
-
* - `'skipped'` → no per-worktree install was performed (single-tree
|
|
24
|
-
* or symlink/pnpm-store strategy); trust the strategy.
|
|
25
|
-
*
|
|
26
|
-
* 3. Render the initial Story-phase snapshot with every Story phase
|
|
27
|
-
* (init/implement/validate/close) pinned to `pending` and
|
|
28
|
-
* `phase: 'init'` via `upsertStoryRunProgress`. Story #3909 — this is
|
|
29
|
-
* render-only: no `story-run-progress` comment is posted (that redundant
|
|
30
|
-
* mid-flight surface was deleted).
|
|
31
|
-
*
|
|
32
|
-
* Stdout: a single JSON envelope `{ workCwd, dependenciesInstalled,
|
|
33
|
-
* installAction, snapshot, renderedBody }` so the caller can decide what to
|
|
34
|
-
* do next. `renderedBody` is the markdown body for the initial Story-phase
|
|
35
|
-
* table — `/story-deliver` relays it as a chat message at the start of each
|
|
36
|
-
* Story so operators see it before the first commit lands.
|
|
37
|
-
*/
|
|
38
|
-
|
|
39
|
-
import { parseArgs } from 'node:util';
|
|
40
|
-
|
|
41
|
-
import { runAsCli } from './lib/cli-utils.js';
|
|
42
|
-
import { resolveConfig } from './lib/config-resolver.js';
|
|
43
|
-
import { runInstallCommand } from './lib/install-cmd-parser.js';
|
|
44
|
-
import {
|
|
45
|
-
defaultStoryPhases,
|
|
46
|
-
STORY_RUN_PROGRESS_TYPE,
|
|
47
|
-
upsertStoryRunProgress,
|
|
48
|
-
} from './lib/orchestration/epic-runner/story-run-progress-writer.js';
|
|
49
|
-
import { parseFencedJsonComment } from './lib/orchestration/structured-comment-parser.js';
|
|
50
|
-
import { findStructuredComment } from './lib/orchestration/ticketing.js';
|
|
51
|
-
import { createProvider } from './lib/provider-factory.js';
|
|
52
|
-
import { notify } from './notify.js';
|
|
53
|
-
|
|
54
|
-
const HELP = `Usage: node .agents/scripts/story-deliver-prepare.js \\
|
|
55
|
-
--story <id> [--cwd <workCwd>] [--skip-install] [--install-cmd "<cmd>"]
|
|
56
|
-
|
|
57
|
-
Reads the story-init structured comment off Story #<id>, runs the install
|
|
58
|
-
command when dependenciesInstalled === 'false', then renders the initial
|
|
59
|
-
Story-phase snapshot (phase=init, every Story phase pending) for chat relay.
|
|
60
|
-
No story-run-progress comment is posted (Story #3909).
|
|
61
|
-
`;
|
|
62
|
-
|
|
63
|
-
const VALID_INSTALLED_STATES = new Set(['true', 'false', 'skipped']);
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Apply the dependenciesInstalled tri-state to derive the next install action.
|
|
67
|
-
* Pure helper — exposes the Step 0.5 truth table as data so tests can pin
|
|
68
|
-
* each branch without spinning up a child process.
|
|
69
|
-
*
|
|
70
|
-
* @param {'true' | 'false' | 'skipped'} dependenciesInstalled
|
|
71
|
-
* @param {{ skipInstall?: boolean }} [options]
|
|
72
|
-
* @returns {'skip' | 'install'}
|
|
73
|
-
*/
|
|
74
|
-
export function deriveInstallAction(dependenciesInstalled, options = {}) {
|
|
75
|
-
if (!VALID_INSTALLED_STATES.has(dependenciesInstalled)) {
|
|
76
|
-
throw new RangeError(
|
|
77
|
-
`deriveInstallAction: dependenciesInstalled "${dependenciesInstalled}" must be one of: ${[...VALID_INSTALLED_STATES].join(', ')}`,
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
if (options.skipInstall) return 'skip';
|
|
81
|
-
return dependenciesInstalled === 'false' ? 'install' : 'skip';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Resolve the install command to run when `dependenciesInstalled === 'false'`.
|
|
86
|
-
* `project.commands` does not currently carry a dedicated install key,
|
|
87
|
-
* so this defaults to `npm ci`. Operators can override per-invocation via
|
|
88
|
-
* `--install-cmd` (mirrors the spec note about a `commands.test`-adjacent
|
|
89
|
-
* future override).
|
|
90
|
-
*
|
|
91
|
-
* @param {{ override?: string }} [options]
|
|
92
|
-
* @returns {string}
|
|
93
|
-
*/
|
|
94
|
-
export function resolveInstallCommand(options = {}) {
|
|
95
|
-
const trimmed = options.override?.trim();
|
|
96
|
-
if (trimmed) {
|
|
97
|
-
return trimmed;
|
|
98
|
-
}
|
|
99
|
-
return 'npm ci';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Hydrate the `story-init` payload off the Story ticket. Returns `null` when
|
|
104
|
-
* the comment can't be located (the operator must run `story-init` first).
|
|
105
|
-
*
|
|
106
|
-
* @param {{ provider: object, storyId: number }} args
|
|
107
|
-
* @returns {Promise<object | null>}
|
|
108
|
-
*/
|
|
109
|
-
export async function readStoryInitComment({ provider, storyId }) {
|
|
110
|
-
const comment = await findStructuredComment(provider, storyId, 'story-init');
|
|
111
|
-
if (!comment) return null;
|
|
112
|
-
const payload = parseFencedJsonComment(comment);
|
|
113
|
-
if (!payload || typeof payload !== 'object') return null;
|
|
114
|
-
return payload;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* End-to-end prepare. DI-friendly: tests pass `provider`, `runner`, and
|
|
119
|
-
* skip the real network/child-process side effects.
|
|
120
|
-
*
|
|
121
|
-
* @param {{
|
|
122
|
-
* storyId: number,
|
|
123
|
-
* cwd?: string,
|
|
124
|
-
* skipInstall?: boolean,
|
|
125
|
-
* installCmd?: string,
|
|
126
|
-
* provider?: object,
|
|
127
|
-
* runInstall?: (cmd: string, cwd: string) => { status: number, stderr?: string },
|
|
128
|
-
* }} args
|
|
129
|
-
* @returns {Promise<{
|
|
130
|
-
* storyId: number,
|
|
131
|
-
* workCwd: string,
|
|
132
|
-
* dependenciesInstalled: string,
|
|
133
|
-
* installAction: 'skip' | 'install',
|
|
134
|
-
* installCmd: string | null,
|
|
135
|
-
* installResult: { status: number, stderr?: string } | null,
|
|
136
|
-
* snapshot: object,
|
|
137
|
-
* renderedBody: string,
|
|
138
|
-
* }>}
|
|
139
|
-
*/
|
|
140
|
-
export async function runStoryDeliverPrepare(args) {
|
|
141
|
-
const {
|
|
142
|
-
storyId,
|
|
143
|
-
cwd: cwdOverride,
|
|
144
|
-
skipInstall = false,
|
|
145
|
-
installCmd: installCmdOverride,
|
|
146
|
-
provider: providerOverride,
|
|
147
|
-
runInstall: runInstallOverride,
|
|
148
|
-
} = args ?? {};
|
|
149
|
-
|
|
150
|
-
if (!Number.isInteger(storyId) || storyId <= 0) {
|
|
151
|
-
throw new TypeError(
|
|
152
|
-
'runStoryDeliverPrepare: --story must be a positive integer',
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const config = providerOverride ? null : resolveConfig();
|
|
157
|
-
const provider = providerOverride ?? createProvider(config);
|
|
158
|
-
const notifyFn = providerOverride
|
|
159
|
-
? null
|
|
160
|
-
: (ticketId, payload, opts = {}) =>
|
|
161
|
-
notify(ticketId, payload, {
|
|
162
|
-
config,
|
|
163
|
-
provider,
|
|
164
|
-
...opts,
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// 1. Hydrate the story-init payload off the Story ticket.
|
|
168
|
-
const initPayload = await readStoryInitComment({ provider, storyId });
|
|
169
|
-
if (!initPayload) {
|
|
170
|
-
throw new Error(
|
|
171
|
-
`runStoryDeliverPrepare: no story-init comment found on #${storyId}; ` +
|
|
172
|
-
`run \`node .agents/scripts/story-init.js --story ${storyId}\` first.`,
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
const dependenciesInstalled = String(
|
|
176
|
-
initPayload.dependenciesInstalled ?? 'skipped',
|
|
177
|
-
);
|
|
178
|
-
const workCwd = String(initPayload.workCwd ?? cwdOverride ?? process.cwd());
|
|
179
|
-
|
|
180
|
-
// 2. Apply the install tri-state.
|
|
181
|
-
const installAction = deriveInstallAction(dependenciesInstalled, {
|
|
182
|
-
skipInstall,
|
|
183
|
-
});
|
|
184
|
-
let installCmd = null;
|
|
185
|
-
let installResult = null;
|
|
186
|
-
if (installAction === 'install') {
|
|
187
|
-
installCmd = resolveInstallCommand({ override: installCmdOverride });
|
|
188
|
-
installResult = (runInstallOverride ?? runInstallCommand)(
|
|
189
|
-
installCmd,
|
|
190
|
-
workCwd,
|
|
191
|
-
);
|
|
192
|
-
if (installResult.status !== 0) {
|
|
193
|
-
throw new Error(
|
|
194
|
-
`runStoryDeliverPrepare: install command \`${installCmd}\` failed with status ${installResult.status}: ${installResult.stderr ?? ''}`,
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// 3. Render the initial Story-phase snapshot (render-only; Story #3909
|
|
200
|
-
// deleted the redundant story-run-progress comment).
|
|
201
|
-
//
|
|
202
|
-
// Under the 3-tier hierarchy (Epic → Feature → Story) the
|
|
203
|
-
// inline-acceptance Story is the only ticket shape: Stories have no
|
|
204
|
-
// child Tasks, so the snapshot is always the Story-phase `phases[]`
|
|
205
|
-
// shape (init/implement/validate/close pinned to `pending`).
|
|
206
|
-
const hierarchy = String(initPayload.hierarchy ?? '3-tier');
|
|
207
|
-
const branch = String(initPayload.storyBranch ?? `story-${storyId}`);
|
|
208
|
-
|
|
209
|
-
const phases = defaultStoryPhases();
|
|
210
|
-
const { body: renderedBody, payload: snapshot } =
|
|
211
|
-
await upsertStoryRunProgress({
|
|
212
|
-
provider,
|
|
213
|
-
storyId,
|
|
214
|
-
branch,
|
|
215
|
-
phase: 'init',
|
|
216
|
-
phases,
|
|
217
|
-
notify: notifyFn,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
storyId,
|
|
222
|
-
workCwd,
|
|
223
|
-
dependenciesInstalled,
|
|
224
|
-
installAction,
|
|
225
|
-
installCmd,
|
|
226
|
-
installResult,
|
|
227
|
-
hierarchy,
|
|
228
|
-
snapshot,
|
|
229
|
-
renderedBody,
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
export function parseArgv(argv) {
|
|
234
|
-
const { values } = parseArgs({
|
|
235
|
-
args: argv,
|
|
236
|
-
options: {
|
|
237
|
-
story: { type: 'string' },
|
|
238
|
-
cwd: { type: 'string' },
|
|
239
|
-
'skip-install': { type: 'boolean' },
|
|
240
|
-
'install-cmd': { type: 'string' },
|
|
241
|
-
help: { type: 'boolean' },
|
|
242
|
-
},
|
|
243
|
-
strict: false,
|
|
244
|
-
});
|
|
245
|
-
return {
|
|
246
|
-
help: Boolean(values.help),
|
|
247
|
-
storyId: Number.parseInt(values.story ?? '', 10),
|
|
248
|
-
cwd: values.cwd,
|
|
249
|
-
skipInstall: Boolean(values['skip-install']),
|
|
250
|
-
installCmd: values['install-cmd'],
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
export async function main(argv = process.argv.slice(2)) {
|
|
255
|
-
const parsed = parseArgv(argv);
|
|
256
|
-
if (parsed.help) {
|
|
257
|
-
process.stdout.write(HELP);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const envelope = await runStoryDeliverPrepare(parsed);
|
|
261
|
-
process.stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Re-export for symmetry with the other prepare-suite CLIs.
|
|
265
|
-
export { STORY_RUN_PROGRESS_TYPE };
|
|
266
|
-
|
|
267
|
-
runAsCli(import.meta.url, main, { source: 'story-deliver-prepare' });
|