mandrel 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +59 -28
- package/.agents/agents/acceptance-critic.md +9 -7
- package/.agents/agents/story-worker.md +41 -46
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +51 -44
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +32 -56
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +14 -16
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/agentrc.schema.json +34 -45
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +22 -66
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- package/.agents/scripts/lib/audit-suite/selector.js +323 -23
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
- package/.agents/scripts/lib/checks/index.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli-args.js +23 -2
- package/.agents/scripts/lib/close-validation/gates.js +13 -13
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/delivery-routing.js +7 -6
- package/.agents/scripts/lib/config/explain.js +10 -16
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +15 -25
- package/.agents/scripts/lib/config/quality.js +11 -14
- package/.agents/scripts/lib/config/runners.js +8 -21
- package/.agents/scripts/lib/config/temp-paths.js +18 -56
- package/.agents/scripts/lib/config-settings-schema-delivery.js +31 -13
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
- package/.agents/scripts/lib/config-settings-schema.js +48 -22
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/duplicate-search.js +38 -7
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -189
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
- package/.agents/scripts/lib/git-utils.js +0 -14
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +0 -15
- package/.agents/scripts/lib/label-taxonomy.js +1 -12
- package/.agents/scripts/lib/observability/active-story-env.js +42 -163
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +2 -2
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
- package/.agents/scripts/lib/orchestration/code-review.js +58 -168
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +114 -24
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +11 -22
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +3 -7
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -75
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
- package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +3 -12
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +15 -32
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
- package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
- package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
- package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
- package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -286
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +8 -7
- package/.agents/scripts/lib/story-body/story-body.js +6 -5
- package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -3
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/ticket-body-sections.js +0 -14
- package/.agents/scripts/lib/validation-evidence.js +31 -59
- package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/plan-context.js +38 -6
- package/.agents/scripts/plan-persist.js +145 -35
- package/.agents/scripts/plan-run-epilogue.js +83 -38
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +1 -11
- package/.agents/scripts/providers/github/mappers.js +5 -0
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -153
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +9 -10
- package/.agents/scripts/single-story-close.js +173 -18
- package/.agents/scripts/single-story-confirm-merge.js +288 -15
- package/.agents/scripts/single-story-init.js +6 -10
- package/.agents/scripts/stories-wave-tick.js +79 -4
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/update-ticket-state.js +8 -50
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
- package/.agents/skills/core/scope-triage/SKILL.md +3 -0
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/workflows/audit-architecture.md +3 -4
- package/.agents/workflows/audit-clean-code.md +4 -4
- package/.agents/workflows/audit-documentation.md +4 -5
- package/.agents/workflows/audit-lighthouse.md +8 -0
- package/.agents/workflows/audit-navigability.md +10 -0
- package/.agents/workflows/audit-performance.md +2 -3
- package/.agents/workflows/audit-quality.md +8 -9
- package/.agents/workflows/audit-security.md +1 -2
- package/.agents/workflows/audit-seo.md +10 -0
- package/.agents/workflows/audit-ux-ui.md +7 -0
- package/.agents/workflows/deliver.md +98 -45
- package/.agents/workflows/git-cleanup.md +2 -2
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +21 -13
- package/.agents/workflows/helpers/code-quality-guardrails.md +7 -7
- package/.agents/workflows/helpers/code-review.md +12 -10
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +193 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +184 -19
- package/.agents/workflows/qa-assist.md +6 -6
- package/.agents/workflows/qa-explore.md +3 -3
- package/.agents/workflows/qa-run.md +1 -5
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +40 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +12 -9
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/package.json +1 -1
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -53
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
- package/.agents/scripts/resolve-plan-run.js +0 -117
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tech Spec #819 §"Evidence record (Story 7)". Each successful gate run
|
|
5
5
|
* writes a record keyed by `{ gateName, commitSha, commandConfigHash }` to
|
|
6
|
-
* a
|
|
6
|
+
* a storyId-anchored path under the resolved `tempRoot`:
|
|
7
7
|
*
|
|
8
|
-
* -
|
|
9
|
-
* `<tempRoot>/epic-<epicId>/validation-evidence.json`
|
|
10
|
-
* - Story-scoped (scopeId === storyId):
|
|
11
|
-
* `<tempRoot>/epic-<epicId>/story-<storyId>/validation-evidence.json`
|
|
8
|
+
* `<tempRoot>/standalone/stories/story-<storyId>/validation-evidence.json`
|
|
12
9
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* The path is gitignored via `temp/`. v2.0.0 removed the Epic tier along
|
|
11
|
+
* with the Epic-keyed `<tempRoot>/epic-<epicId>/…` keyspace.
|
|
15
12
|
*
|
|
16
13
|
* A subsequent caller can `shouldSkip(...)` to learn whether the same gate
|
|
17
14
|
* has already passed against the current HEAD with an identical
|
|
@@ -34,7 +31,7 @@ import path from 'node:path';
|
|
|
34
31
|
import { fileURLToPath } from 'node:url';
|
|
35
32
|
import Ajv from 'ajv/dist/2020.js';
|
|
36
33
|
import addFormats from 'ajv-formats';
|
|
37
|
-
import {
|
|
34
|
+
import { storyTempDir } from './config/temp-paths.js';
|
|
38
35
|
|
|
39
36
|
export const SCHEMA_VERSION = 1;
|
|
40
37
|
const DEFAULT_TEMP_DIR = 'temp';
|
|
@@ -97,37 +94,28 @@ function requirePositiveInt(value, label) {
|
|
|
97
94
|
|
|
98
95
|
/**
|
|
99
96
|
* Compute the absolute path of the evidence file for `scopeId` under the
|
|
100
|
-
*
|
|
97
|
+
* storyId-anchored temp tree.
|
|
101
98
|
*
|
|
102
|
-
*
|
|
103
|
-
* - `scopeId === epicId` → `<tempRoot>/epic-<epicId>/validation-evidence.json`
|
|
104
|
-
* - `scopeId !== epicId` → treated as a Story id → `<tempRoot>/epic-<epicId>/story-<scopeId>/validation-evidence.json`
|
|
105
|
-
*
|
|
106
|
-
* **Standalone keyspace (Story #4250).** When `opts.standalone === true`,
|
|
99
|
+
* **Standalone keyspace (Story #4250).** `opts.standalone` must be `true`:
|
|
107
100
|
* the Story has no parent Epic, so the evidence file is anchored on the
|
|
108
101
|
* Story id alone at
|
|
109
102
|
* `<tempRoot>/standalone/stories/story-<scopeId>/validation-evidence.json`
|
|
110
|
-
* (the `storyTempDir(null, sid)` layout from Story #2874).
|
|
111
|
-
* `epicId` is ignored — callers MUST NOT feed a `0`/`null` epicId into the
|
|
112
|
-
* Epic-keyed branch (the historical bug this keyspace replaces). Outside
|
|
113
|
-
* standalone mode `epicId` remains required.
|
|
103
|
+
* (the `storyTempDir(null, sid)` layout from Story #2874).
|
|
114
104
|
*
|
|
115
|
-
* The legacy flat `temp/validation-evidence-<scopeId>.json` layout
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* override the default `'temp'`.
|
|
105
|
+
* The legacy flat `temp/validation-evidence-<scopeId>.json` layout and the
|
|
106
|
+
* Epic-keyed `<tempRoot>/epic-<epicId>/…` layout are both gone. The
|
|
107
|
+
* synthetic config bag passed to `storyTempDir` keeps the resolver from
|
|
108
|
+
* doing a disk-bound `.agentrc.json` lookup; bare callers can pass
|
|
109
|
+
* `tempDir` via `opts` to override the default `'temp'`.
|
|
121
110
|
*
|
|
122
111
|
* @param {number|string} scopeId
|
|
123
|
-
* @param {{ cwd?: string, tempDir?: string,
|
|
112
|
+
* @param {{ cwd?: string, tempDir?: string, standalone?: boolean }} opts
|
|
124
113
|
* @returns {string}
|
|
125
114
|
*/
|
|
126
115
|
export function evidencePath(scopeId, opts = {}) {
|
|
127
|
-
|
|
128
|
-
if (!standalone && opts.epicId == null) {
|
|
116
|
+
if (opts.standalone !== true) {
|
|
129
117
|
throw new Error(
|
|
130
|
-
'[validation-evidence] evidencePath requires opts.
|
|
118
|
+
'[validation-evidence] evidencePath requires opts.standalone (the storyId-anchored keyspace is the only keyspace).',
|
|
131
119
|
);
|
|
132
120
|
}
|
|
133
121
|
const { cwd, tempDir } = resolveOpts(opts);
|
|
@@ -143,18 +131,9 @@ export function evidencePath(scopeId, opts = {}) {
|
|
|
143
131
|
? tempDir
|
|
144
132
|
: path.join(cwd, tempDir);
|
|
145
133
|
const configBag = { project: { paths: { tempRoot: absTempRoot } } };
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// standalone-story sentinel `storyTempDir` accepts (Story #2874).
|
|
150
|
-
dir = storyTempDir(null, scope, configBag);
|
|
151
|
-
} else {
|
|
152
|
-
const epicId = requirePositiveInt(opts.epicId, 'epicId');
|
|
153
|
-
dir =
|
|
154
|
-
scope === epicId
|
|
155
|
-
? epicTempDir(epicId, configBag)
|
|
156
|
-
: storyTempDir(epicId, scope, configBag);
|
|
157
|
-
}
|
|
134
|
+
// Story #4250 — storyId-anchored standalone keyspace. `null` is the
|
|
135
|
+
// standalone-story sentinel `storyTempDir` accepts (Story #2874).
|
|
136
|
+
const dir = storyTempDir(null, scope, configBag);
|
|
158
137
|
return path.join(dir, EVIDENCE_FILENAME);
|
|
159
138
|
}
|
|
160
139
|
|
|
@@ -189,19 +168,17 @@ function emptyDoc(scopeId) {
|
|
|
189
168
|
* cross-scopeId cases — callers don't have to branch on those failure
|
|
190
169
|
* modes; they manifest as `shouldSkip()` returning `skip: false`.
|
|
191
170
|
*
|
|
192
|
-
* `opts.
|
|
193
|
-
*
|
|
194
|
-
* storyId-anchored standalone keyspace.
|
|
171
|
+
* `opts.standalone` is required (Story #4250) — it routes to the
|
|
172
|
+
* storyId-anchored keyspace.
|
|
195
173
|
*
|
|
196
174
|
* @param {number|string} scopeId
|
|
197
|
-
* @param {{ cwd?: string, tempDir?: string,
|
|
175
|
+
* @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object }} opts
|
|
198
176
|
* @returns {{ storyId: number, schemaVersion: number, records: object[] }}
|
|
199
177
|
*/
|
|
200
178
|
export function loadEvidence(scopeId, opts = {}) {
|
|
201
179
|
const resolved = resolveOpts(opts);
|
|
202
180
|
const file = evidencePath(scopeId, {
|
|
203
181
|
...resolved,
|
|
204
|
-
epicId: opts.epicId,
|
|
205
182
|
standalone: opts.standalone,
|
|
206
183
|
});
|
|
207
184
|
if (!resolved.fs.existsSync(file)) return emptyDoc(scopeId);
|
|
@@ -223,9 +200,8 @@ export function loadEvidence(scopeId, opts = {}) {
|
|
|
223
200
|
* Validates the resulting document against the schema before writing — a
|
|
224
201
|
* malformed write throws so the bug surfaces immediately.
|
|
225
202
|
*
|
|
226
|
-
* `opts.
|
|
227
|
-
*
|
|
228
|
-
* storyId-anchored standalone keyspace.
|
|
203
|
+
* `opts.standalone` is required (Story #4250) — it routes to the
|
|
204
|
+
* storyId-anchored keyspace.
|
|
229
205
|
*
|
|
230
206
|
* @param {{
|
|
231
207
|
* storyId: number|string,
|
|
@@ -235,7 +211,7 @@ export function loadEvidence(scopeId, opts = {}) {
|
|
|
235
211
|
* exitCode?: number,
|
|
236
212
|
* durationMs?: number|null,
|
|
237
213
|
* }} input
|
|
238
|
-
* @param {{ cwd?: string, tempDir?: string,
|
|
214
|
+
* @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object, now?: Function }} opts
|
|
239
215
|
* @returns {object} The persisted record.
|
|
240
216
|
*/
|
|
241
217
|
export function recordPass(
|
|
@@ -258,7 +234,6 @@ export function recordPass(
|
|
|
258
234
|
const resolved = resolveOpts(opts);
|
|
259
235
|
const evidenceOpts = {
|
|
260
236
|
...resolved,
|
|
261
|
-
epicId: opts.epicId,
|
|
262
237
|
standalone: opts.standalone,
|
|
263
238
|
};
|
|
264
239
|
const doc = loadEvidence(storyId, evidenceOpts);
|
|
@@ -297,13 +272,12 @@ export function recordPass(
|
|
|
297
272
|
* with a machine-readable `reason` so callers can log why the skip didn't
|
|
298
273
|
* fire.
|
|
299
274
|
*
|
|
300
|
-
* `opts.
|
|
301
|
-
*
|
|
302
|
-
* storyId-anchored standalone keyspace. `opts` is forwarded verbatim to
|
|
275
|
+
* `opts.standalone` is required (Story #4250) — it routes to the
|
|
276
|
+
* storyId-anchored keyspace. `opts` is forwarded verbatim to
|
|
303
277
|
* `loadEvidence`, so `standalone` flows through unchanged.
|
|
304
278
|
*
|
|
305
279
|
* @param {{ storyId: number|string, gateName: string, currentSha: string, configHash: string }} input
|
|
306
|
-
* @param {{ cwd?: string, tempDir?: string,
|
|
280
|
+
* @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object }} opts
|
|
307
281
|
* @returns {{ skip: boolean, reason: string, record?: object }}
|
|
308
282
|
*/
|
|
309
283
|
export function shouldSkip(
|
|
@@ -340,19 +314,17 @@ export function shouldSkip(
|
|
|
340
314
|
* start of each Story so a re-run always starts clean. Idempotent —
|
|
341
315
|
* absent file is not an error.
|
|
342
316
|
*
|
|
343
|
-
* `opts.
|
|
344
|
-
*
|
|
345
|
-
* storyId-anchored standalone keyspace.
|
|
317
|
+
* `opts.standalone` is required (Story #4250) — it routes to the
|
|
318
|
+
* storyId-anchored keyspace.
|
|
346
319
|
*
|
|
347
320
|
* @param {number|string} scopeId
|
|
348
|
-
* @param {{ cwd?: string, tempDir?: string,
|
|
321
|
+
* @param {{ cwd?: string, tempDir?: string, standalone?: boolean, fs?: object }} opts
|
|
349
322
|
* @returns {{ cleared: boolean, path: string }}
|
|
350
323
|
*/
|
|
351
324
|
export function forceClear(scopeId, opts = {}) {
|
|
352
325
|
const resolved = resolveOpts(opts);
|
|
353
326
|
const file = evidencePath(scopeId, {
|
|
354
327
|
...resolved,
|
|
355
|
-
epicId: opts.epicId,
|
|
356
328
|
standalone: opts.standalone,
|
|
357
329
|
});
|
|
358
330
|
if (!resolved.fs.existsSync(file)) return { cleared: false, path: file };
|
|
@@ -156,15 +156,37 @@ export function storyFootprint(story) {
|
|
|
156
156
|
return out;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Does a declared path contain a glob metacharacter? Mirrors the detection
|
|
161
|
+
* in `story-body.js#extractChangePaths`, whose `isGlob` flag documents an
|
|
162
|
+
* "unknown-width footprint" policy that was never implemented downstream.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} path
|
|
165
|
+
* @returns {boolean}
|
|
166
|
+
*/
|
|
167
|
+
function isGlobPath(path) {
|
|
168
|
+
return path.includes('*') || path.includes('?') || path.includes('{');
|
|
169
|
+
}
|
|
170
|
+
|
|
159
171
|
/**
|
|
160
172
|
* File-overlap co-dispatch guard. Returns `true` when two Stories' declared
|
|
161
|
-
* file footprints intersect
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
173
|
+
* file footprints intersect — meaning they would race the same file if
|
|
174
|
+
* dispatched onto parallel `story-<id>` branches in the same beat. Two
|
|
175
|
+
* Stories that overlap MUST NOT both appear in one dispatch set; one is
|
|
176
|
+
* withheld until the other clears.
|
|
165
177
|
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
178
|
+
* Two deliberate asymmetries:
|
|
179
|
+
*
|
|
180
|
+
* - **An empty footprint means "no known overlap"** → `false`. A Story that
|
|
181
|
+
* declares no files is never withheld. This is permissive by necessity:
|
|
182
|
+
* an undeclared footprint carries no information, and withholding on
|
|
183
|
+
* absence would serialize every run.
|
|
184
|
+
* - **A glob footprint overlaps EVERYTHING** → `true` (Story #4539/#4540).
|
|
185
|
+
* Comparison is exact-string, so a Story declaring
|
|
186
|
+
* `.agents/scripts/lib/**` would not match another declaring
|
|
187
|
+
* `.agents/scripts/lib/story-adjacency.js` — the guard would silently
|
|
188
|
+
* pass two Stories that genuinely race. Unknown width is not the same as
|
|
189
|
+
* no width: fail safe by serializing.
|
|
168
190
|
*
|
|
169
191
|
* @param {StoryRecord} a
|
|
170
192
|
* @param {StoryRecord} b
|
|
@@ -176,8 +198,12 @@ export function storiesOverlap(a, b) {
|
|
|
176
198
|
const fb = storyFootprint(b);
|
|
177
199
|
if (fb.size === 0) return false;
|
|
178
200
|
for (const path of fa) {
|
|
201
|
+
if (isGlobPath(path)) return true;
|
|
179
202
|
if (fb.has(path)) return true;
|
|
180
203
|
}
|
|
204
|
+
for (const path of fb) {
|
|
205
|
+
if (isGlobPath(path)) return true;
|
|
206
|
+
}
|
|
181
207
|
return false;
|
|
182
208
|
}
|
|
183
209
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* on a Windows lock-class failure, the entry is appended to
|
|
8
8
|
* `.worktrees/.pending-cleanup.json`. The plan-time `worktree-sweep.js`
|
|
9
9
|
* reader (shipped in Epic #349) picks up the manifest on the next
|
|
10
|
-
* `/
|
|
10
|
+
* `/plan` run and retries removal
|
|
11
11
|
* (`git worktree remove` then `fs.rm`) — by then the live file handles from Node / AV / the Windows
|
|
12
12
|
* Search indexer are almost always gone. If `MAX_SWEEP_ATTEMPTS` elapses
|
|
13
13
|
* without clearing, an `OPERATOR ACTION REQUIRED: persistent-lock` line
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import fs from 'node:fs';
|
|
17
17
|
import { rm as fsPromisesRm } from 'node:fs/promises';
|
|
18
|
-
import { isInsideWorktree, samePath
|
|
18
|
+
import { isInsideWorktree, samePath } from '../inspector.js';
|
|
19
19
|
import { sleepSync } from '../node-modules-strategy.js';
|
|
20
20
|
import { checkMergeReachability } from './merge-reachability.js';
|
|
21
21
|
import { recordPendingCleanup } from './pending-cleanup.js';
|
|
@@ -72,21 +72,58 @@ export async function isSafeToRemove(ctx, wtPath, opts = {}) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
|
-
* Returns true iff `branch` is already
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
75
|
+
* Returns true iff `branch`'s work is demonstrably already integrated into
|
|
76
|
+
* `baseRef`. Used only to license discarding a **dirty** tree, so it must
|
|
77
|
+
* err toward `false`.
|
|
78
|
+
*
|
|
79
|
+
* Two-phase:
|
|
80
|
+
* 1. `merge-base --is-ancestor` — cheap SHA reachability; true for
|
|
81
|
+
* fast-forward and merge-commit integration.
|
|
82
|
+
* 2. `git cherry <baseRef> <branch>` — compares **patch-ids** rather than
|
|
83
|
+
* SHAs, marking a commit `-` when an equivalent change already exists
|
|
84
|
+
* upstream. All-`-` (or empty) means every commit on the branch is
|
|
85
|
+
* present in the base under some SHA.
|
|
86
|
+
*
|
|
87
|
+
* **Known limit — this is not a general squash detector.** A squash collapses
|
|
88
|
+
* N commits into ONE new commit whose patch equals their *combined* diff, so
|
|
89
|
+
* no individual original commit has an upstream patch-id equivalent and
|
|
90
|
+
* `git cherry` marks them all `+`. Phase 2 therefore only recognises a
|
|
91
|
+
* squash-landed branch when the branch had a **single** commit (verified
|
|
92
|
+
* empirically, Story #4539). A multi-commit squash still reads as unmerged
|
|
93
|
+
* and the dirty tree is refused — the safe direction, and the same answer
|
|
94
|
+
* the previous ancestor-only check gave. Detecting the general case needs
|
|
95
|
+
* the PR's merged state, which this module has no client for.
|
|
96
|
+
*
|
|
97
|
+
* A missing ref or a git failure yields false, so callers default to the
|
|
98
|
+
* safe, non-forcing behavior. Module-private: `ensureSafeOrForceDiscard` is
|
|
99
|
+
* its only caller, and the symbol it replaced was exported with no consumer
|
|
100
|
+
* and then baselined as a dead export — repeating that would just hide a
|
|
101
|
+
* new corpse.
|
|
102
|
+
*
|
|
103
|
+
* @param {object} ctx
|
|
104
|
+
* @param {string} branch
|
|
105
|
+
* @param {string} baseRef
|
|
106
|
+
* @returns {boolean}
|
|
79
107
|
*/
|
|
80
|
-
|
|
81
|
-
if (!branch || !
|
|
82
|
-
const
|
|
108
|
+
function isBranchMergedIntoBase(ctx, branch, baseRef) {
|
|
109
|
+
if (!branch || !baseRef) return false;
|
|
110
|
+
const ancestor = ctx.git.gitSpawn(
|
|
83
111
|
ctx.repoRoot,
|
|
84
112
|
'merge-base',
|
|
85
113
|
'--is-ancestor',
|
|
86
114
|
branch,
|
|
87
|
-
|
|
115
|
+
baseRef,
|
|
88
116
|
);
|
|
89
|
-
|
|
117
|
+
if (ancestor.status === 0) return true;
|
|
118
|
+
|
|
119
|
+
const cherry = ctx.git.gitSpawn(ctx.repoRoot, 'cherry', baseRef, branch);
|
|
120
|
+
if (cherry.status !== 0) return false;
|
|
121
|
+
const lines = (cherry.stdout ?? '')
|
|
122
|
+
.split('\n')
|
|
123
|
+
.map((l) => l.trim())
|
|
124
|
+
.filter(Boolean);
|
|
125
|
+
// Every line starting with '+' is a commit with no upstream equivalent.
|
|
126
|
+
return lines.every((line) => line.startsWith('-'));
|
|
90
127
|
}
|
|
91
128
|
|
|
92
129
|
/**
|
|
@@ -470,28 +507,40 @@ function checkReapPreconditions(ctx, _storyId, opts, wtPath) {
|
|
|
470
507
|
ok: false,
|
|
471
508
|
result: { removed: false, reason: 'not-a-worktree', path: wtPath },
|
|
472
509
|
};
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
510
|
+
// Story #4539 removed an `epic-branch-required` gate here: a
|
|
511
|
+
// `story-<id>` worktree used to be unreapable unless the caller supplied
|
|
512
|
+
// an Epic integration branch. v2 has no Epic branch, and the only v2
|
|
513
|
+
// caller (the close path) never passed one — so EVERY close silently
|
|
514
|
+
// failed to reap while reporting success, and cleanup fell to the next
|
|
515
|
+
// boot-sweep.
|
|
516
|
+
//
|
|
517
|
+
// Nothing is lost by dropping it. The close path reaps AFTER pushing
|
|
518
|
+
// `story-<id>` to origin and opening the PR, so the work is durable
|
|
519
|
+
// off-machine; and the real safety net is unchanged — `isSafeToRemove`
|
|
520
|
+
// still refuses a dirty tree (`uncommitted-changes`), which is what
|
|
521
|
+
// actually protects unsaved work.
|
|
479
522
|
return { ok: true };
|
|
480
523
|
}
|
|
481
524
|
|
|
482
525
|
async function ensureSafeOrForceDiscard(ctx, storyId, wtPath, opts) {
|
|
526
|
+
const baseRef = opts.baseRef ?? opts.epicBranch ?? null;
|
|
483
527
|
const safety = await isSafeToRemove(ctx, wtPath, {
|
|
484
|
-
epicBranch:
|
|
528
|
+
epicBranch: baseRef,
|
|
485
529
|
});
|
|
486
530
|
if (safety.safe) return { ok: true, discardedPaths: null };
|
|
487
531
|
|
|
488
532
|
const discardAfterMerge = opts.discardAfterMerge !== false;
|
|
489
533
|
const branchName = `story-${validateStoryId(storyId)}`;
|
|
534
|
+
// Discarding a dirty tree is only permissible when the branch's work is
|
|
535
|
+
// demonstrably already integrated. See `isBranchMergedIntoBase` for what
|
|
536
|
+
// that can and cannot prove — notably a multi-commit squash reads as
|
|
537
|
+
// unmerged, so the discard is refused and the tree survives. Refusing is
|
|
538
|
+
// the safe direction: the cost is a stale worktree, not lost work.
|
|
490
539
|
const canForceReap =
|
|
491
540
|
discardAfterMerge &&
|
|
492
541
|
safety.reason === 'uncommitted-changes' &&
|
|
493
|
-
|
|
494
|
-
|
|
542
|
+
baseRef &&
|
|
543
|
+
isBranchMergedIntoBase(ctx, branchName, baseRef);
|
|
495
544
|
if (!canForceReap) {
|
|
496
545
|
ctx.logger.warn(
|
|
497
546
|
`reap-skipped storyId=${storyId} reason=${safety.reason} path=${wtPath}`,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - `creation.js` — `ensure`
|
|
8
8
|
* - `registry-sync.js` — `pathFor`, `list`, `prune`, `getWorktreeList`,
|
|
9
9
|
* `invalidateWorktreeCache`, `findByPath`
|
|
10
|
-
* - `reap.js` — `isSafeToRemove`,
|
|
10
|
+
* - `reap.js` — `isSafeToRemove`,
|
|
11
11
|
* `removeWorktreeWithRecovery`, `reap`
|
|
12
12
|
* - `gc.js` — `gc`
|
|
13
13
|
* - `drift-detection.js` — `sweepStaleLocks`
|
|
@@ -26,7 +26,6 @@ export { sweepStaleLocks } from './lifecycle/drift-detection.js';
|
|
|
26
26
|
export { gc } from './lifecycle/gc.js';
|
|
27
27
|
export {
|
|
28
28
|
isSafeToRemove,
|
|
29
|
-
isStoryAlreadyMergedIntoEpic,
|
|
30
29
|
reap,
|
|
31
30
|
removeWorktreeWithRecovery,
|
|
32
31
|
} from './lifecycle/reap.js';
|
|
@@ -18,8 +18,14 @@
|
|
|
18
18
|
* Stories. Envelope carries `sourceTickets[]`.
|
|
19
19
|
*
|
|
20
20
|
* Flags:
|
|
21
|
+
* --out <path> Also write the envelope to <path> (parent dirs created).
|
|
22
|
+
* `/plan` points this at `<plan-dir>/plan-context.json`,
|
|
23
|
+
* which is where `plan-persist.js` auto-discovers the
|
|
24
|
+
* `--tickets` source ids from (Story #4554). Without a
|
|
25
|
+
* captured envelope persist cannot know a `--tickets` run
|
|
26
|
+
* happened, and superseding degrades to the
|
|
27
|
+
* `--source-tickets` flag.
|
|
21
28
|
* --pretty Pretty-print the JSON envelope.
|
|
22
|
-
* --full-context Bypass the planning-context budget (unbounded body).
|
|
23
29
|
*
|
|
24
30
|
* stdout is reserved for the JSON envelope (Story #2278 discipline):
|
|
25
31
|
* `routeAllOutputToStderr()` runs before any pipeline code so a captured
|
|
@@ -34,13 +40,15 @@
|
|
|
34
40
|
// first import so the check runs before any third-party-importing sibling
|
|
35
41
|
// module is evaluated (Story #3432).
|
|
36
42
|
import './lib/runtime-deps/ensure-installed.js';
|
|
43
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
44
|
+
import path from 'node:path';
|
|
37
45
|
import { parseArgs } from 'node:util';
|
|
38
46
|
import { runAsCli } from './lib/cli-utils.js';
|
|
39
47
|
import {
|
|
40
48
|
resolveConfig,
|
|
41
49
|
validateOrchestrationConfig,
|
|
42
50
|
} from './lib/config-resolver.js';
|
|
43
|
-
import { routeAllOutputToStderr } from './lib/Logger.js';
|
|
51
|
+
import { Logger, routeAllOutputToStderr } from './lib/Logger.js';
|
|
44
52
|
import { buildPlanContext } from './lib/orchestration/plan-context.js';
|
|
45
53
|
import { recordPlanInvocation } from './lib/orchestration/plan-metrics.js';
|
|
46
54
|
import { createProvider } from './lib/provider-factory.js';
|
|
@@ -84,8 +92,8 @@ export async function emitPlanContext({
|
|
|
84
92
|
provider,
|
|
85
93
|
config,
|
|
86
94
|
settings,
|
|
87
|
-
fullContext = false,
|
|
88
95
|
pretty = false,
|
|
96
|
+
outPath = null,
|
|
89
97
|
cwd,
|
|
90
98
|
stdout = process.stdout,
|
|
91
99
|
}) {
|
|
@@ -98,24 +106,48 @@ export async function emitPlanContext({
|
|
|
98
106
|
provider,
|
|
99
107
|
config,
|
|
100
108
|
settings,
|
|
101
|
-
fullContext,
|
|
102
109
|
cwd,
|
|
103
110
|
});
|
|
104
111
|
const json = pretty
|
|
105
112
|
? JSON.stringify(envelope, null, 2)
|
|
106
113
|
: JSON.stringify(envelope);
|
|
107
114
|
stdout.write(`${json}\n`);
|
|
115
|
+
if (outPath) await writeEnvelopeFile(outPath, json);
|
|
108
116
|
return envelope;
|
|
109
117
|
}
|
|
110
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Persist the envelope to `--out` so `plan-persist.js` can derive the
|
|
121
|
+
* `--tickets` source ids from it without an operator re-typing them.
|
|
122
|
+
*
|
|
123
|
+
* Writing is part of emitting, not a best-effort extra: a failed write means
|
|
124
|
+
* persist will silently see no source tickets, so it throws rather than
|
|
125
|
+
* warning past the problem.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} outPath
|
|
128
|
+
* @param {string} json
|
|
129
|
+
*/
|
|
130
|
+
async function writeEnvelopeFile(outPath, json) {
|
|
131
|
+
const resolved = path.resolve(outPath);
|
|
132
|
+
try {
|
|
133
|
+
await mkdir(path.dirname(resolved), { recursive: true });
|
|
134
|
+
await writeFile(resolved, `${json}\n`, 'utf8');
|
|
135
|
+
} catch (err) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`[plan-context] cannot write envelope to ${resolved}: ${err.message}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
Logger.info(`[plan-context] wrote envelope to ${resolved}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
111
143
|
async function main() {
|
|
112
144
|
const { values } = parseArgs({
|
|
113
145
|
options: {
|
|
114
146
|
seed: { type: 'string' },
|
|
115
147
|
'seed-file': { type: 'string' },
|
|
116
148
|
tickets: { type: 'string' },
|
|
149
|
+
out: { type: 'string' },
|
|
117
150
|
pretty: { type: 'boolean', default: false },
|
|
118
|
-
'full-context': { type: 'boolean', default: false },
|
|
119
151
|
},
|
|
120
152
|
strict: true,
|
|
121
153
|
});
|
|
@@ -185,8 +217,8 @@ async function main() {
|
|
|
185
217
|
provider,
|
|
186
218
|
config,
|
|
187
219
|
settings,
|
|
188
|
-
fullContext: values['full-context'],
|
|
189
220
|
pretty: values.pretty,
|
|
221
|
+
outPath: values.out || null,
|
|
190
222
|
}),
|
|
191
223
|
);
|
|
192
224
|
}
|