mandrel 2.66.0 → 2.68.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/agents/acceptance-critic.md +2 -2
- package/.agents/agents/story-worker.md +15 -11
- package/.agents/docs/agentrc-reference.json +5 -2
- package/.agents/docs/configuration.md +38 -2
- package/.agents/docs/workflows.md +4 -2
- package/.agents/instructions.md +2 -1
- package/.agents/rules/git-conventions-reference.md +5 -5
- package/.agents/rules/git-conventions.md +1 -1
- package/.agents/schemas/agentrc.schema.json +20 -2
- package/.agents/schemas/story-deliver-terminal.schema.json +23 -1
- package/.agents/schemas/validation-evidence.schema.json +3 -1
- package/.agents/scripts/boot-sweep.js +97 -9
- package/.agents/scripts/{git-cleanup.js → clean-git.js} +2 -2
- package/.agents/scripts/clean-temp.js +54 -0
- package/.agents/scripts/clean-worktrees.js +593 -0
- package/.agents/scripts/coverage-capture.js +65 -9
- package/.agents/scripts/drain-pending-cleanup.js +5 -4
- package/.agents/scripts/evidence-gate.js +106 -8
- package/.agents/scripts/lib/baselines/coverage-refresh-scope.js +60 -0
- package/.agents/scripts/lib/baselines/crap-updater-cli.js +101 -4
- package/.agents/scripts/lib/baselines/refresh-service.js +1 -1
- package/.agents/scripts/lib/baselines/seat-missing.js +228 -0
- package/.agents/scripts/lib/child-exec.js +39 -1
- package/.agents/scripts/lib/clean-temp.js +440 -0
- package/.agents/scripts/lib/close-validation/gates.js +59 -19
- package/.agents/scripts/lib/close-validation/process.js +23 -24
- package/.agents/scripts/lib/close-validation/runner.js +71 -40
- package/.agents/scripts/lib/config/gates/coverage.schema.js +21 -0
- package/.agents/scripts/lib/config/quality.js +7 -1
- package/.agents/scripts/lib/config/temp-paths.js +15 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +12 -3
- package/.agents/scripts/lib/coverage-baseline.js +78 -5
- package/.agents/scripts/lib/coverage-capture-affected.js +345 -0
- package/.agents/scripts/lib/coverage-capture-delta.js +180 -0
- package/.agents/scripts/lib/coverage-capture-fullscope.js +53 -32
- package/.agents/scripts/lib/coverage-capture-incremental.js +49 -26
- package/.agents/scripts/lib/coverage-capture-usage.js +1 -1
- package/.agents/scripts/lib/coverage-capture.js +121 -81
- package/.agents/scripts/lib/full-suite-lock.js +49 -46
- package/.agents/scripts/lib/full-suite-queue.js +83 -8
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +4 -1
- package/.agents/scripts/lib/orchestration/code-review.js +15 -3
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/merge-poll.js +5 -0
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +149 -97
- package/.agents/scripts/lib/orchestration/review-deposit.js +219 -0
- package/.agents/scripts/lib/orchestration/review-providers/code-review.js +11 -7
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +29 -10
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +124 -73
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +38 -20
- package/.agents/scripts/lib/orchestration/single-story-close/phases/lock-wait-pending.js +8 -2
- package/.agents/scripts/lib/orchestration/single-story-close/review-overlap.js +161 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +47 -7
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +8 -16
- package/.agents/scripts/lib/process-group.js +1 -1
- package/.agents/scripts/lib/single-story-sweep.js +2 -2
- package/.agents/scripts/lib/supervised-suite.js +247 -0
- package/.agents/scripts/lib/temp-removal.js +110 -0
- package/.agents/scripts/lib/temp-retention.js +122 -73
- package/.agents/scripts/lib/wave-runner/cross-run-overlap.js +120 -0
- package/.agents/scripts/lib/wave-runner/live-probe.js +5 -1
- package/.agents/scripts/lib/worktree/canonical-path.js +34 -0
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +15 -4
- package/.agents/scripts/quality-preview.js +112 -14
- package/.agents/scripts/single-story-init.js +120 -17
- package/.agents/scripts/stories-wave-tick.js +47 -0
- package/.agents/scripts/story-review-compute.js +207 -0
- package/.agents/scripts/update-coverage-baseline.js +15 -10
- package/.agents/scripts/update-crap-baseline.js +12 -2
- package/.agents/scripts/update-maintainability-baseline.js +12 -2
- package/.agents/workflows/{git-cleanup.md → clean-git.md} +10 -10
- package/.agents/workflows/clean-temp.md +67 -0
- package/.agents/workflows/clean-worktrees.md +63 -0
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +3 -2
- package/.agents/workflows/helpers/code-review.md +7 -5
- package/.agents/workflows/helpers/deliver-digest.md +39 -36
- package/.agents/workflows/helpers/deliver-reference.md +115 -5
- package/.agents/workflows/helpers/deliver-story-reference.md +2 -2
- package/.agents/workflows/helpers/deliver-story.md +2 -1
- package/docs/CHANGELOG.md +39 -0
- package/lib/cli/registry.js +125 -18
- package/lib/migrations/steps/strip-removed-agentrc-keys.js +0 -5
- package/package.json +1 -1
|
@@ -98,8 +98,8 @@ For each acceptance item:
|
|
|
98
98
|
|
|
99
99
|
## Verdict schema (MUST)
|
|
100
100
|
|
|
101
|
-
Write **one** verdict file under `temp
|
|
102
|
-
`temp/acceptance-verdict
|
|
101
|
+
Write **one** verdict file under `temp/scratch/story-<storyId>/` (e.g.
|
|
102
|
+
`temp/scratch/story-<storyId>/acceptance-verdict-r<round>.json`) conforming to
|
|
103
103
|
[`acceptance-eval-verdict.schema.json`](../schemas/acceptance-eval-verdict.schema.json):
|
|
104
104
|
one `criteria[]` record per acceptance item, in acceptance-array order, with
|
|
105
105
|
`index` being the criterion's position in that array.
|
|
@@ -87,14 +87,16 @@ names. A null digest path means no docs mandate.
|
|
|
87
87
|
|
|
88
88
|
`single-story-close.js` runs the canonical close-validation chain
|
|
89
89
|
(**typecheck, lint, test, format, maintainability, coverage, crap**) and is
|
|
90
|
-
the authoritative gate — do not pre-run it.
|
|
91
|
-
full suite: after the self-eval loop's last fix commit, run it once in
|
|
90
|
+
the authoritative gate — do not pre-run it. Two exceptions, both run in
|
|
92
91
|
`<workCwd>` exactly as
|
|
93
|
-
[`deliver-digest.md`](../workflows/helpers/deliver-digest.md) § 5 states
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
[`deliver-digest.md`](../workflows/helpers/deliver-digest.md) § 5 states them:
|
|
93
|
+
the **blocking** lint + quality-preview preflight, then the one credited run
|
|
94
|
+
(the coverage capture or the test depositor, as § 5 picks), once after the
|
|
95
|
+
self-eval loop's last fix commit, then the `--seat-missing` baseline seat
|
|
96
|
+
before push. That section is their only home, so read the invocations there
|
|
97
|
+
rather than from a copy here.
|
|
98
|
+
|
|
99
|
+
If the run outruns the host's sync Bash ceiling, dispatch it in the
|
|
98
100
|
**background**: its completion re-invokes you. Never spawn a task to poll or
|
|
99
101
|
`sleep`-loop against it; a waiter with a wrong condition outlives the agent.
|
|
100
102
|
An exit code is never evidence a gate did work — its **output** is. Redraft
|
|
@@ -142,11 +144,13 @@ the remote ref moved — then return: a turn that ends unpushed reads as
|
|
|
142
144
|
unfinished work. The orchestrator runs
|
|
143
145
|
`single-story-close.js` in its own session, serialized against your
|
|
144
146
|
siblings. Do not open the PR, flip `agent::done`, or spawn a child to
|
|
145
|
-
close for you. If the push fails, take the blocked path above.
|
|
147
|
+
close for you. If the push fails, take the blocked path above. After
|
|
148
|
+
the push, compute the held review and fix a CRITICAL before returning
|
|
149
|
+
([`deliver-reference.md`](../workflows/helpers/deliver-reference.md) § Held review).
|
|
146
150
|
|
|
147
151
|
## Return contract — the hand-off report
|
|
148
152
|
|
|
149
153
|
A short, literal hand-off your caller can act on: Story id, `workCwd`,
|
|
150
|
-
branch, pushed head SHA, self-eval verdict, `verify[]` evidence
|
|
151
|
-
branch is pushed and unclosed. Never hand-compose a
|
|
152
|
-
inventing one makes an unlanded Story look landed.
|
|
154
|
+
branch, pushed head SHA, self-eval verdict, `verify[]` evidence, review
|
|
155
|
+
tally. Say the branch is pushed and unclosed. Never hand-compose a
|
|
156
|
+
terminal envelope — inventing one makes an unlanded Story look landed.
|
|
@@ -87,7 +87,8 @@
|
|
|
87
87
|
"orchestrationLogs": true,
|
|
88
88
|
"validationEvidence": true,
|
|
89
89
|
"auditResults": true,
|
|
90
|
-
"planDirs": true
|
|
90
|
+
"planDirs": true,
|
|
91
|
+
"scratch": true
|
|
91
92
|
}
|
|
92
93
|
},
|
|
93
94
|
"deliverRunner": {
|
|
@@ -124,7 +125,9 @@
|
|
|
124
125
|
"functions": 90
|
|
125
126
|
}
|
|
126
127
|
},
|
|
127
|
-
"coveragePath": "coverage/coverage-final.json"
|
|
128
|
+
"coveragePath": "coverage/coverage-final.json",
|
|
129
|
+
"captureScope": "full",
|
|
130
|
+
"timeoutMs": 600000
|
|
128
131
|
},
|
|
129
132
|
"crap": {
|
|
130
133
|
"enabled": true,
|
|
@@ -136,16 +136,17 @@ Everything `/mandrel-deliver` and `single-story-close` consume: worktree isolati
|
|
|
136
136
|
| Key | Required | Type | Default | Description |
|
|
137
137
|
| --- | --- | --- | --- | --- |
|
|
138
138
|
| `execution` | No | `object` | — | Serialization of the full-suite spawns delivery drives. |
|
|
139
|
-
| `execution.fullSuiteLock` | No | `boolean` | `true` | Serialize full-suite spawns (`npm test` / `npm run test:coverage`) behind a host-level advisory lock, so two concurrent deliveries on one checkout do not run two suites against the same cores.
|
|
139
|
+
| `execution.fullSuiteLock` | No | `boolean` | `true` | Serialize full-suite spawns (`npm test` / `npm run test:coverage`) behind a host-level advisory lock, so two concurrent deliveries on one checkout do not run two suites against the same cores. The lock queues, it never overlaps: a wait that expires with a live holder spawns nothing and exits 75 (resumable), naming the holder. A dead or non-heartbeating holder is taken over, and a broken lockfile proceeds unserialized, so the lock can never fail a delivery. Set false — or export `MANDREL_FULL_SUITE_LOCK=0` for one invocation — to disable. |
|
|
140
140
|
| `docsFreshness` | No | `object` | — | Documentation-freshness scope: the files a change of consequence is expected to touch. Read by the audit-documentation lens to seed its target set; no delivery gate enforces it. |
|
|
141
141
|
| `docsFreshness.paths` | No | `array<string>` | `["README.md"]` | Repo-relative documentation paths the audit-documentation lens adds to its target set. |
|
|
142
|
-
| `tempRetention` | No | `object` | — | Story #4794. Auto-purge of spent temp artifacts once their Story lands. Classification is an allowlist: only the declared classes below are ever deleted, so
|
|
142
|
+
| `tempRetention` | No | `object` | — | Story #4794. Auto-purge of spent temp artifacts once their Story lands. Classification is an allowlist: only the declared classes below are ever deleted, so unrecognized files under tempRoot are reported with their size and left alone (`/clean-temp` is the operator path for them). signals.ndjson is never purged by any path. |
|
|
143
143
|
| `tempRetention.enabled` | No | `boolean` | `true` | Master switch. Default true — reclaiming a landed Story's gate transcripts and validation evidence is the behaviour, and this knob turns it off. When false every purge path is a reported no-op. |
|
|
144
144
|
| `tempRetention.classes` | No | `object` | — | Per-class opt-out. Each defaults to true; set one false to keep that family while the rest are purged. |
|
|
145
145
|
| `tempRetention.classes.orchestrationLogs` | No | `boolean` | `true` | <tempRoot>/orchestration/*.log — close gate transcripts and terse-result detail dumps. |
|
|
146
146
|
| `tempRetention.classes.validationEvidence` | No | `boolean` | `true` | Per-Story validation-evidence.json, lifecycle.ndjson, and manifest.md under the standalone and per-run story trees. |
|
|
147
147
|
| `tempRetention.classes.auditResults` | No | `boolean` | `true` | <tempRoot>/audits/ — audit lens reports. |
|
|
148
148
|
| `tempRetention.classes.planDirs` | No | `boolean` | `true` | <tempRoot>/plan-<slug>/ — abandoned plan authoring dirs. Age-floored only; the current run is always excluded. |
|
|
149
|
+
| `tempRetention.classes.scratch` | No | `boolean` | `true` | <tempRoot>/scratch/ — agent-authored scratch. `scratch/story-<id>/` is purged when that Story lands; any other `scratch/` entry is age-floored. |
|
|
149
150
|
| `deliverRunner` | No | `object` | — | Bounded-concurrency knob for the /mandrel-deliver fan-out. |
|
|
150
151
|
| `deliverRunner.concurrencyCap` | No | `integer` | `3` | Maximum ready Stories dispatched by /mandrel-deliver at once. Default 3. Moderate by design — keeps host-quota consumption predictable while allowing a small ready-set fan-out. Set 1 for strictly sequential delivery; raise further on hosts with adequate parallel-agent quota. See deliver.md for the sequencing model and throughput tradeoff. |
|
|
151
152
|
| `deliverRunner.footprintGuard` | No | `"enforce"` \| `"advisory"` | `"enforce"` | How a file-footprint collision affects dispatch. 'enforce' (default, and the behaviour to keep unless you have a reason) withholds a Story whose footprint races a peer admitted this beat or one still in flight — the guard encodes delivery-time-only knowledge (open implementation windows, foreign leases, ground that moved since planning) that no depends_on edge can carry. 'advisory' still DETECTS every collision and reports each would-be withhold in the tick envelope, but lets dispatch follow the declared depends_on edges alone — a deliberate throughput trade for a run whose ordering is fully declared. See stories-wave-tick.js and helpers/deliver-reference.md. |
|
|
@@ -168,6 +169,8 @@ Everything `/mandrel-deliver` and `single-story-close` consume: worktree isolati
|
|
|
168
169
|
| `quality.gates.coverage.floors` | No | `object<map>` | `{"*":{"lines":90,"branches":85,"functions":90}}` | Workspace-keyed absolute floors: `{ "<workspace>": { "<metric>": number } }`. `"*"` is the project-wide catch-all; the metric keyset is open so per-rollup keys flow through without each gate enumerating them. Floors are absolute — unlike `tolerance`, they are enforced regardless of the baseline. |
|
|
169
170
|
| `quality.gates.coverage.components` | No | `object<map>` | — | Per-gate component map — component name to the glob list whose files roll up under it. Defaults to `{ "*": ["**"] }` at the resolver layer. |
|
|
170
171
|
| `quality.gates.coverage.coveragePath` | No | `string` | `"coverage/coverage-final.json"` | Repo-relative path to the Istanbul `coverage-final.json` the capture step writes and the gate reads. |
|
|
172
|
+
| `quality.gates.coverage.captureScope` | No | `"full"` \| `"affected"` | `"full"` | What coverage-capture runs. `full` (default) runs `npm run test:coverage`. `affected` runs the consumer-owned `npm run test:coverage:affected` with the base ref in `MANDREL_COVERAGE_BASE_REF`, merges its rows over the prior artifact and stamps it `affected`; baseline rows the scoped run did not measure are treated as unmeasured, never removed. Falls back to `full` with a warning when the script is absent. Meant for consumers whose CI already enforces coverage on the full suite. |
|
|
173
|
+
| `quality.gates.coverage.timeoutMs` | No | `integer` | `600000` | Kill bound (ms) for one full-suite run — the coverage capture, the close-validation full-suite gate and the full-suite lock-wait budget all read it. The clock starts at spawn, never while queued on the host lock; a suite that signals `MANDREL_SUITE_READY_FILE` gets a fresh bound for its test phase, so worst-case wall is lock wait + 2 × timeoutMs. On expiry the run exits 124 so callers can tell a hang from a failure. |
|
|
171
174
|
| `quality.gates.crap` | No | `object` | — | CRAP (Change Risk Anti-Pattern) ratchet — per-method cyclomatic complexity joined against per-method coverage. |
|
|
172
175
|
| `quality.gates.crap.enabled` | No | `boolean` | `true` | When false, the checker exits 0 with a skip line and the gate is reported as `skipped`, never omitted. |
|
|
173
176
|
| `quality.gates.crap.baselinePath` | No | `string` | `"baselines/crap.json"` | Repo-root-relative path to the gate's committed baseline artifact. |
|
|
@@ -364,6 +367,39 @@ A config still carrying the retired key is a hard validation failure; the
|
|
|
364
367
|
Extend the list-valued gate keys with the deep-merge extender form (see
|
|
365
368
|
[How to extend](#how-to-extend)).
|
|
366
369
|
|
|
370
|
+
#### `delivery.quality.gates.coverage.captureScope` — delta refresh
|
|
371
|
+
|
|
372
|
+
Under `captureScope: "affected"`, a capture stamp records the commit it
|
|
373
|
+
measured. When close's base-sync merges `main` into the Story branch, the
|
|
374
|
+
next capture re-measures only the merged commits' tests instead of the
|
|
375
|
+
Story's whole affected scope: it runs `npm run test:coverage:affected` with
|
|
376
|
+
`MANDREL_COVERAGE_BASE_REF` set to the **stamped commit**, drops the prior
|
|
377
|
+
rows of the delta's files, merges the new rows over the prior artifact and
|
|
378
|
+
stamps the merged tree fresh. It holds the full-suite lock like any capture,
|
|
379
|
+
and a red delta refresh fails the capture — it never falls back to a wider
|
|
380
|
+
run.
|
|
381
|
+
|
|
382
|
+
A delta refresh runs only when **every** condition holds; otherwise the
|
|
383
|
+
capture behaves exactly as before (the Story's affected scope, or the full
|
|
384
|
+
suite when the script is absent):
|
|
385
|
+
|
|
386
|
+
- the stamp is stale by content digest, and records a `commit` (a stamp
|
|
387
|
+
written before this field existed never qualifies) that is an ancestor of
|
|
388
|
+
`HEAD`;
|
|
389
|
+
- `captureScope` is `affected` and the `test:coverage:affected` script
|
|
390
|
+
exists;
|
|
391
|
+
- the worktree is clean, and the delta (`git diff --name-only <commit> HEAD`)
|
|
392
|
+
is non-empty and shares no path with the Story's change set;
|
|
393
|
+
- the delta touches no coverage-determining config: `package.json`, a
|
|
394
|
+
lockfile, `tsconfig*.json`, or a vitest / jest / c8 / nyc config;
|
|
395
|
+
- a prior artifact exists to merge over.
|
|
396
|
+
|
|
397
|
+
Any git error or unresolvable input fails closed to the ordinary capture.
|
|
398
|
+
Every freshness log line names the stamp's scope, the required scope and the
|
|
399
|
+
verdict (`fresh`, `stale`, `scope-mismatch`, `missing`, or `delta-refresh`
|
|
400
|
+
with the delta's file count), so an unexpected full re-run is explainable
|
|
401
|
+
from the log alone. `captureScope: "full"` is unaffected.
|
|
402
|
+
|
|
367
403
|
#### `delivery.worktreeIsolation` — node_modules strategies
|
|
368
404
|
|
|
369
405
|
When `enabled: true`, each Story runs in its own worktree under
|
|
@@ -32,7 +32,7 @@ by `node .agents/scripts/generate-workflows-doc.js`; `npm run docs:check`
|
|
|
32
32
|
fails when it drifts from the on-disk workflow set. To change a command’s
|
|
33
33
|
description, edit the workflow file’s front-matter and regenerate.
|
|
34
34
|
|
|
35
|
-
## Commands (
|
|
35
|
+
## Commands (31)
|
|
36
36
|
|
|
37
37
|
| Command | Description |
|
|
38
38
|
| --- | --- |
|
|
@@ -55,7 +55,9 @@ description, edit the workflow file’s front-matter and regenerate.
|
|
|
55
55
|
| `/audit-sre` | "Audit production-readiness for a release candidate: SLOs, observability, runbooks, error budgets, and rollback paths." |
|
|
56
56
|
| `/audit-to-stories` | Convert findings produced by the audit-\* workflows into actionable GitHub Stories. Reads temp/audits/audit-\*-results.md, groups findings cross-audit, deduplicates against existing Issues by fingerprint, and either chains into /mandrel-plan --seed-file or opens standalone Stories. |
|
|
57
57
|
| `/audit-ux-ui` | Audit UX/UI consistency and design system adherence |
|
|
58
|
-
| `/git
|
|
58
|
+
| `/clean-git` | Tidy the local checkout in four phases: fast-forward `main`, prune stale remote-tracking refs, sweep merged branches (squash-aware), and triage `git stash` entries — each step gated by operator confirmation. |
|
|
59
|
+
| `/clean-temp` | Clear the temp-tree backlog the land-time purge cannot attribute: sort every top-level entry under the project's tempRoot into framework, closed-issue, aged and kept buckets, preview by default, and delete only confirmed buckets. |
|
|
60
|
+
| `/clean-worktrees` | Reclaim disk from dead worktrees: list every worktree of this project as a removal candidate (closed Story, merged branch, orphaned directory, detached HEAD) or as kept with a reason, then remove candidates only on `--execute`. |
|
|
59
61
|
| `/git-deliver` | Single ad-hoc delivery command for working-tree changes. Detects the git setup and escalates to the right terminal step — commit only, commit + push, or commit + push + open a PR with native auto-merge — picking the default from observable state and letting flags pin any level explicitly. Replaces the retired git-commit-all, git-push, and git-pr-all trio. |
|
|
60
62
|
| `/mandrel-deliver` | Unified delivery entry point. Takes Story ids or a plain-language prompt, derives which path the work belongs on, and lands it via the single deliver-story engine — story-<id> → PR → main. |
|
|
61
63
|
| `/mandrel-plan` | Unified planning entry point. Interrogate → author → persist. Emits one Story by default; splits into N>1 only under the default-single split policy. |
|
package/.agents/instructions.md
CHANGED
|
@@ -181,7 +181,8 @@ never delivered): `/mandrel-plan` offers one above 2 Stories and
|
|
|
181
181
|
|
|
182
182
|
All temporary files, scratch scripts, and intermediate outputs MUST
|
|
183
183
|
live in the gitignored workspace-root `/temp/` directory — do NOT commit
|
|
184
|
-
anything under it.
|
|
184
|
+
anything under it. Put ad-hoc scratch in `temp/scratch/story-<id>/` (or
|
|
185
|
+
`temp/scratch/` with no Story), the layout the temp purge reaps.
|
|
185
186
|
|
|
186
187
|
---
|
|
187
188
|
|
|
@@ -96,13 +96,13 @@ signature is worth naming:
|
|
|
96
96
|
|
|
97
97
|
**Invariant (stated in the core): the delivering flow owns tidying the local
|
|
98
98
|
checkout — reaping its own merged refs and fast-forwarding the base branch.
|
|
99
|
-
`/git
|
|
99
|
+
`/clean-git` is a recovery tool, not a routine chore.** The outcome every
|
|
100
100
|
delivering flow (`/mandrel-deliver`, `/git-deliver`) guarantees, with the mechanics
|
|
101
|
-
owned by `boot-sweep.js` / `git
|
|
101
|
+
owned by `boot-sweep.js` / `clean-git.js`:
|
|
102
102
|
|
|
103
103
|
- **`main` is fast-forwarded** by the flow itself in its cleanup phase, so the
|
|
104
104
|
next init seeds from a current base. No workflow ends by telling the operator
|
|
105
|
-
to run `/git
|
|
105
|
+
to run `/clean-git` to catch up.
|
|
106
106
|
- **Merged local refs are reaped** at the next workflow boot's protected sweep
|
|
107
107
|
(`boot-sweep.js`) — every local branch whose PR is already merged, skipping
|
|
108
108
|
any candidate with unpushed work, a dirty worktree, or a still-open parent
|
|
@@ -112,9 +112,9 @@ owned by `boot-sweep.js` / `git-cleanup.js`:
|
|
|
112
112
|
weaker content-equivalence signal (`detectedBy: 'content-merged'` — content
|
|
113
113
|
already landed in the base by another route, with no merged PR or git
|
|
114
114
|
ancestry of its own) is **never** reaped by the boot sweep; it is surfaced
|
|
115
|
-
under `contentMerged` for the operator to send to `/git
|
|
115
|
+
under `contentMerged` for the operator to send to `/clean-git` for a
|
|
116
116
|
confirmed, eyeballed reap.
|
|
117
|
-
- **`/git
|
|
117
|
+
- **`/clean-git` is recovery, not routine.** Run it by hand only for a state
|
|
118
118
|
the automated hygiene does not cover — triaging stashes, reaping across
|
|
119
119
|
non-standard namespaces, or `--remote` pruning after a force-push. Reaching
|
|
120
120
|
for it after every routine delivery signals the owning flow's hygiene step
|
|
@@ -56,7 +56,7 @@ subject referencing the Story via `(refs #<storyId>)` — see
|
|
|
56
56
|
**The delivering flow owns tidying the local checkout** — it
|
|
57
57
|
fast-forwards the base branch itself and reaps its own merged refs on
|
|
58
58
|
the next workflow boot (the `boot-sweep.js` protected sweep).
|
|
59
|
-
`/git
|
|
59
|
+
`/clean-git` is a recovery tool, not a routine chore — never end a
|
|
60
60
|
workflow by telling the operator to run it. Scope rules and the
|
|
61
61
|
shared-checkout contention guard:
|
|
62
62
|
[`git-conventions-reference.md` § Local checkout hygiene](git-conventions-reference.md).
|
|
@@ -359,7 +359,7 @@
|
|
|
359
359
|
"properties": {
|
|
360
360
|
"fullSuiteLock": {
|
|
361
361
|
"type": "boolean",
|
|
362
|
-
"description": "Serialize full-suite spawns (`npm test` / `npm run test:coverage`) behind a host-level advisory lock, so two concurrent deliveries on one checkout do not run two suites against the same cores.
|
|
362
|
+
"description": "Serialize full-suite spawns (`npm test` / `npm run test:coverage`) behind a host-level advisory lock, so two concurrent deliveries on one checkout do not run two suites against the same cores. The lock queues, it never overlaps: a wait that expires with a live holder spawns nothing and exits 75 (resumable), naming the holder. A dead or non-heartbeating holder is taken over, and a broken lockfile proceeds unserialized, so the lock can never fail a delivery. Set false — or export `MANDREL_FULL_SUITE_LOCK=0` for one invocation — to disable.",
|
|
363
363
|
"default": true
|
|
364
364
|
}
|
|
365
365
|
},
|
|
@@ -386,7 +386,7 @@
|
|
|
386
386
|
},
|
|
387
387
|
"tempRetention": {
|
|
388
388
|
"type": "object",
|
|
389
|
-
"description": "Story #4794. Auto-purge of spent temp artifacts once their Story lands. Classification is an allowlist: only the declared classes below are ever deleted, so
|
|
389
|
+
"description": "Story #4794. Auto-purge of spent temp artifacts once their Story lands. Classification is an allowlist: only the declared classes below are ever deleted, so unrecognized files under tempRoot are reported with their size and left alone (`/clean-temp` is the operator path for them). signals.ndjson is never purged by any path.",
|
|
390
390
|
"properties": {
|
|
391
391
|
"enabled": {
|
|
392
392
|
"type": "boolean",
|
|
@@ -416,6 +416,11 @@
|
|
|
416
416
|
"type": "boolean",
|
|
417
417
|
"description": "<tempRoot>/plan-<slug>/ — abandoned plan authoring dirs. Age-floored only; the current run is always excluded.",
|
|
418
418
|
"default": true
|
|
419
|
+
},
|
|
420
|
+
"scratch": {
|
|
421
|
+
"type": "boolean",
|
|
422
|
+
"description": "<tempRoot>/scratch/ — agent-authored scratch. `scratch/story-<id>/` is purged when that Story lands; any other `scratch/` entry is age-floored.",
|
|
423
|
+
"default": true
|
|
419
424
|
}
|
|
420
425
|
},
|
|
421
426
|
"additionalProperties": false
|
|
@@ -595,6 +600,19 @@
|
|
|
595
600
|
"minLength": 1,
|
|
596
601
|
"description": "Repo-relative path to the Istanbul `coverage-final.json` the capture step writes and the gate reads.",
|
|
597
602
|
"default": "coverage/coverage-final.json"
|
|
603
|
+
},
|
|
604
|
+
"captureScope": {
|
|
605
|
+
"type": "string",
|
|
606
|
+
"enum": ["full", "affected"],
|
|
607
|
+
"description": "What coverage-capture runs. `full` (default) runs `npm run test:coverage`. `affected` runs the consumer-owned `npm run test:coverage:affected` with the base ref in `MANDREL_COVERAGE_BASE_REF`, merges its rows over the prior artifact and stamps it `affected`; baseline rows the scoped run did not measure are treated as unmeasured, never removed. Falls back to `full` with a warning when the script is absent. Meant for consumers whose CI already enforces coverage on the full suite.",
|
|
608
|
+
"default": "full"
|
|
609
|
+
},
|
|
610
|
+
"timeoutMs": {
|
|
611
|
+
"type": "integer",
|
|
612
|
+
"minimum": 60000,
|
|
613
|
+
"maximum": 7200000,
|
|
614
|
+
"description": "Kill bound (ms) for one full-suite run — the coverage capture, the close-validation full-suite gate and the full-suite lock-wait budget all read it. The clock starts at spawn, never while queued on the host lock; a suite that signals `MANDREL_SUITE_READY_FILE` gets a fresh bound for its test phase, so worst-case wall is lock wait + 2 × timeoutMs. On expiry the run exits 124 so callers can tell a hang from a failure.",
|
|
615
|
+
"default": 600000
|
|
598
616
|
}
|
|
599
617
|
},
|
|
600
618
|
"additionalProperties": false
|
|
@@ -200,7 +200,29 @@
|
|
|
200
200
|
"required": ["waitedSeconds", "expired"],
|
|
201
201
|
"properties": {
|
|
202
202
|
"waitedSeconds": { "type": "number", "minimum": 0 },
|
|
203
|
-
"expired": { "type": "boolean" }
|
|
203
|
+
"expired": { "type": "boolean" },
|
|
204
|
+
"holder": {
|
|
205
|
+
"type": "object",
|
|
206
|
+
"description": "Story #5485 — the live holder an expired wait gave up on, read from the lockfile: its owner id, pid and lock age. Each field is null when the lockfile did not say.",
|
|
207
|
+
"required": ["ownerId", "pid", "ageSeconds"],
|
|
208
|
+
"properties": {
|
|
209
|
+
"ownerId": { "type": ["string", "null"] },
|
|
210
|
+
"pid": { "type": ["integer", "null"], "minimum": 1 },
|
|
211
|
+
"ageSeconds": { "type": ["number", "null"], "minimum": 0 }
|
|
212
|
+
},
|
|
213
|
+
"additionalProperties": false
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"additionalProperties": false
|
|
217
|
+
},
|
|
218
|
+
"suiteTimings": {
|
|
219
|
+
"type": ["object", "null"],
|
|
220
|
+
"description": "Story #5485 — the close's one full-suite run split into three separate figures: lockWaitMs (queued on the host full-suite lock), hostWaitMs (spawn until the suite signalled MANDREL_SUITE_READY_FILE; null when it did not use the handshake) and testRunMs (the test phase the coverage.timeoutMs kill bound applies to). null when no full suite ran in this close.",
|
|
221
|
+
"required": ["lockWaitMs", "hostWaitMs", "testRunMs"],
|
|
222
|
+
"properties": {
|
|
223
|
+
"lockWaitMs": { "type": "number", "minimum": 0 },
|
|
224
|
+
"hostWaitMs": { "type": ["number", "null"], "minimum": 0 },
|
|
225
|
+
"testRunMs": { "type": "number", "minimum": 0 }
|
|
204
226
|
},
|
|
205
227
|
"additionalProperties": false
|
|
206
228
|
},
|
|
@@ -41,10 +41,12 @@
|
|
|
41
41
|
"check-baselines-independent",
|
|
42
42
|
"check-baselines-coverage",
|
|
43
43
|
"quality-preview",
|
|
44
|
+
"quality-preview-mi",
|
|
45
|
+
"quality-preview-crap",
|
|
44
46
|
"check-maintainability",
|
|
45
47
|
"check-crap"
|
|
46
48
|
],
|
|
47
|
-
"description": "Stable gate identifier. Closed enum — additions require a schema bump. Must be a superset of every gate name buildDefaultGates() can emit (lib/close-validation/gates.js); tests/close-validation-gates-enum.test.js pins that gate-list ⊆ enum invariant. `coverage-capture` and `check-baselines` are the real close-validation gates (Story #4697); `check-baselines-independent` / `check-baselines-coverage` are the split pair the gate registers as when its enabled-kind set resolves (Story #5172), with `check-baselines` kept as both the unsplit fail-closed fallback and the historical name; `quality-preview`
|
|
49
|
+
"description": "Stable gate identifier. Closed enum — additions require a schema bump. Must be a superset of every gate name buildDefaultGates() can emit (lib/close-validation/gates.js); tests/close-validation-gates-enum.test.js pins that gate-list ⊆ enum invariant. `coverage-capture` and `check-baselines` are the real close-validation gates (Story #4697); `check-baselines-independent` / `check-baselines-coverage` are the split pair the gate registers as when its enabled-kind set resolves (Story #5172), with `check-baselines` kept as both the unsplit fail-closed fallback and the historical name; `quality-preview-mi` / `quality-preview-crap` replay the pre-push quality preview at close as its two halves — the MI half in the parallel partition, the CRAP half serial behind `coverage-capture` (Story #5471) — with `quality-preview` kept as the historical name of the unsplit gate (Story #5378); `check-maintainability` / `check-crap` are the retired per-kind gates (Story #2210) kept so historical evidence records still validate."
|
|
48
50
|
},
|
|
49
51
|
"commitSha": {
|
|
50
52
|
"type": "string",
|
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* boot-sweep.js — non-interactive *protected* merged-branch sweep over
|
|
6
|
-
* `sweepMergedBranches` (flags: see HELP). Unlike `git
|
|
6
|
+
* `sweepMergedBranches` (flags: see HELP). Unlike `clean-git --branches` it
|
|
7
7
|
* always skips a branch with unpushed work, a dirty worktree or an open
|
|
8
8
|
* parent Story. Best-effort: failures land in the envelope, exit is always 0.
|
|
9
9
|
*
|
|
10
|
+
* After the branch sweep it runs the closed-Story worktree sweep
|
|
11
|
+
* (`sweepStaleStoryWorktrees`) under the same lock: `.worktrees/story-<id>`
|
|
12
|
+
* trees whose Story is closed or `agent::done` are removed; an open Story's
|
|
13
|
+
* tree and the tree this process runs from are never touched.
|
|
14
|
+
*
|
|
10
15
|
* `content-merged` branches (merge-tree equivalence — no merge check ever
|
|
11
16
|
* validated their exact diff) are never reaped here, only reported for
|
|
12
|
-
* `/git
|
|
17
|
+
* `/clean-git`.
|
|
13
18
|
*/
|
|
14
19
|
|
|
15
20
|
import path from 'node:path';
|
|
@@ -18,9 +23,13 @@ import { parseArgs } from 'node:util';
|
|
|
18
23
|
import { runAsCli } from './lib/cli-utils.js';
|
|
19
24
|
import { PROJECT_ROOT, resolveConfig } from './lib/config-resolver.js';
|
|
20
25
|
import { Logger } from './lib/Logger.js';
|
|
26
|
+
import { sweepStaleStoryWorktrees } from './lib/orchestration/plan-runner/worktree-sweep.js';
|
|
21
27
|
import { createProvider } from './lib/provider-factory.js';
|
|
22
28
|
import { buildProtectionCtx } from './lib/single-story-sweep/protection-ctx.js';
|
|
23
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
acquireSweepLock,
|
|
31
|
+
resolveSweepLockPath,
|
|
32
|
+
} from './lib/single-story-sweep/sweep-lock.js';
|
|
24
33
|
import { sweepMergedBranches } from './lib/single-story-sweep.js';
|
|
25
34
|
import { sweepTempRetention } from './lib/temp-retention.js';
|
|
26
35
|
|
|
@@ -46,10 +55,13 @@ Runs the protected merged-branch boot sweep non-interactively: reaps every
|
|
|
46
55
|
local branch whose PR is MERGED and whose HEAD matches the merged headRefOid,
|
|
47
56
|
skipping any candidate the protection partition flags (unpushed work, dirty
|
|
48
57
|
worktree, still-open parent Story), then fast-forwards the base branch.
|
|
58
|
+
Then removes every .worktrees/story-<id> tree whose Story is closed or
|
|
59
|
+
agent::done (never an open Story's, never the tree this process runs from);
|
|
60
|
+
the outcome lands under "worktreeSweep".
|
|
49
61
|
Branches detected only via the weaker content-equivalence signal
|
|
50
62
|
(detectedBy: 'content-merged') are never reaped here — they are reported
|
|
51
63
|
under "contentMerged" (and a routing hint in the summary line) for the
|
|
52
|
-
operator to send to /git
|
|
64
|
+
operator to send to /clean-git.
|
|
53
65
|
|
|
54
66
|
Options:
|
|
55
67
|
--include <glob> Branch glob to sweep (repeatable). Default: story-*
|
|
@@ -60,6 +72,63 @@ Options:
|
|
|
60
72
|
--json Emit the result envelope as JSON.
|
|
61
73
|
`;
|
|
62
74
|
|
|
75
|
+
/**
|
|
76
|
+
* The closed-Story worktree sweep under the shared sweep lock; never throws.
|
|
77
|
+
* A contended lock skips it — the holder's next boot picks the trees up.
|
|
78
|
+
* Shared with `single-story-init.js`, whose boot never reaches
|
|
79
|
+
* {@link runBootSweep}: one sweep, one lock, two callers. `keepPaths` joins
|
|
80
|
+
* the sweep's running-tree guard — init passes the tree it is about to
|
|
81
|
+
* work in, so the Story being initialized is never removed.
|
|
82
|
+
*
|
|
83
|
+
* @param {{
|
|
84
|
+
* root: string,
|
|
85
|
+
* provider: object,
|
|
86
|
+
* lockPath: string,
|
|
87
|
+
* lockTimeoutMs: number,
|
|
88
|
+
* sweepFn?: Function,
|
|
89
|
+
* acquireLockFn?: Function,
|
|
90
|
+
* logger: object,
|
|
91
|
+
* logTag?: string,
|
|
92
|
+
* keepPaths?: string[],
|
|
93
|
+
* }} args
|
|
94
|
+
* @returns {Promise<object>} `{ ok, reaped, skipped, reason?, error? }`.
|
|
95
|
+
*/
|
|
96
|
+
export async function runWorktreeSweep({
|
|
97
|
+
root,
|
|
98
|
+
provider,
|
|
99
|
+
lockPath,
|
|
100
|
+
lockTimeoutMs,
|
|
101
|
+
sweepFn = sweepStaleStoryWorktrees,
|
|
102
|
+
acquireLockFn = acquireSweepLock,
|
|
103
|
+
logger,
|
|
104
|
+
logTag = '[boot-sweep]',
|
|
105
|
+
keepPaths = [],
|
|
106
|
+
}) {
|
|
107
|
+
const lock = acquireLockFn({ lockPath, timeoutMs: lockTimeoutMs });
|
|
108
|
+
if (!lock.acquired) {
|
|
109
|
+
return { ok: true, reason: `lock-${lock.reason}`, reaped: [], skipped: [] };
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const result = await sweepFn({
|
|
113
|
+
provider,
|
|
114
|
+
repoRoot: root,
|
|
115
|
+
runningPaths: keepPaths,
|
|
116
|
+
logger: {
|
|
117
|
+
info: (m) => logger.info?.(`${logTag} ${m}`),
|
|
118
|
+
warn: (m) => logger.warn?.(`${logTag} ${m}`),
|
|
119
|
+
error: (m) => logger.warn?.(`${logTag} ${m}`),
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
return { ok: true, ...result };
|
|
123
|
+
} catch (err) {
|
|
124
|
+
const msg = err?.message ?? String(err);
|
|
125
|
+
logger.warn?.(`${logTag} worktree sweep threw (host continues): ${msg}`);
|
|
126
|
+
return { ok: false, error: msg, reaped: [], skipped: [] };
|
|
127
|
+
} finally {
|
|
128
|
+
lock.release();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
63
132
|
/**
|
|
64
133
|
* Run the protected boot sweep; never throws.
|
|
65
134
|
*
|
|
@@ -73,11 +142,13 @@ Options:
|
|
|
73
142
|
* injectedConfig?: object,
|
|
74
143
|
* injectedProvider?: object,
|
|
75
144
|
* injectedSweep?: Function,
|
|
145
|
+
* worktreeSweepFn?: Function,
|
|
146
|
+
* acquireLockFn?: Function,
|
|
76
147
|
* purgeFn?: Function,
|
|
77
148
|
* logger?: { info?: Function, warn?: Function },
|
|
78
149
|
* }} [args]
|
|
79
150
|
* @returns {Promise<object>} the {@link sweepMergedBranches} envelope plus
|
|
80
|
-
* `tempPurge`.
|
|
151
|
+
* `worktreeSweep` and `tempPurge`.
|
|
81
152
|
*/
|
|
82
153
|
export async function runBootSweep({
|
|
83
154
|
cwd,
|
|
@@ -89,6 +160,8 @@ export async function runBootSweep({
|
|
|
89
160
|
injectedConfig,
|
|
90
161
|
injectedProvider,
|
|
91
162
|
injectedSweep,
|
|
163
|
+
worktreeSweepFn = sweepStaleStoryWorktrees,
|
|
164
|
+
acquireLockFn = acquireSweepLock,
|
|
92
165
|
purgeFn = sweepTempRetention,
|
|
93
166
|
logger = Logger,
|
|
94
167
|
} = {}) {
|
|
@@ -130,6 +203,16 @@ export async function runBootSweep({
|
|
|
130
203
|
lockTimeoutMs,
|
|
131
204
|
});
|
|
132
205
|
|
|
206
|
+
const worktreeSweep = await runWorktreeSweep({
|
|
207
|
+
root,
|
|
208
|
+
provider,
|
|
209
|
+
lockPath,
|
|
210
|
+
lockTimeoutMs,
|
|
211
|
+
sweepFn: worktreeSweepFn,
|
|
212
|
+
acquireLockFn,
|
|
213
|
+
logger,
|
|
214
|
+
});
|
|
215
|
+
|
|
133
216
|
// Temp-retention catch-up: reaped branches are confirmed merges, so their
|
|
134
217
|
// artifacts are spent; the age floor collects the rest.
|
|
135
218
|
const purge = await purgeFn({
|
|
@@ -138,7 +221,7 @@ export async function runBootSweep({
|
|
|
138
221
|
label: 'boot-sweep',
|
|
139
222
|
logger,
|
|
140
223
|
});
|
|
141
|
-
return { ...result, tempPurge: purge };
|
|
224
|
+
return { ...result, worktreeSweep, tempPurge: purge };
|
|
142
225
|
} catch (err) {
|
|
143
226
|
const msg = err?.message ?? String(err);
|
|
144
227
|
logger.warn?.(`[boot-sweep] sweep threw (host continues): ${msg}`);
|
|
@@ -157,7 +240,7 @@ export async function runBootSweep({
|
|
|
157
240
|
}
|
|
158
241
|
|
|
159
242
|
/**
|
|
160
|
-
* One-line summary; a nonzero `contentMerged` count adds a `/git
|
|
243
|
+
* One-line summary; a nonzero `contentMerged` count adds a `/clean-git` hint.
|
|
161
244
|
*
|
|
162
245
|
* @param {{ localDeleted: number, remoteDeleted: number, protected?: Array, contentMerged?: Array }} result
|
|
163
246
|
* @returns {string}
|
|
@@ -165,11 +248,16 @@ export async function runBootSweep({
|
|
|
165
248
|
export function buildSummaryLine(result) {
|
|
166
249
|
const protectedCount = result.protected?.length ?? 0;
|
|
167
250
|
const contentMergedCount = result.contentMerged?.length ?? 0;
|
|
251
|
+
const worktreesReaped = result.worktreeSweep?.reaped?.length ?? 0;
|
|
252
|
+
const worktreeSuffix =
|
|
253
|
+
worktreesReaped > 0
|
|
254
|
+
? `; removed ${worktreesReaped} closed-Story worktree(s)`
|
|
255
|
+
: '';
|
|
168
256
|
const contentMergedSuffix =
|
|
169
257
|
contentMergedCount > 0
|
|
170
|
-
? `; ${contentMergedCount} content-merged branch(es) left for /git
|
|
258
|
+
? `; ${contentMergedCount} content-merged branch(es) left for /clean-git`
|
|
171
259
|
: '';
|
|
172
|
-
return `[boot-sweep] reaped ${result.localDeleted} local + ${result.remoteDeleted} remote; protected ${protectedCount}${contentMergedSuffix}.`;
|
|
260
|
+
return `[boot-sweep] reaped ${result.localDeleted} local + ${result.remoteDeleted} remote; protected ${protectedCount}${worktreeSuffix}${contentMergedSuffix}.`;
|
|
173
261
|
}
|
|
174
262
|
|
|
175
263
|
/**
|
|
@@ -100,10 +100,10 @@ async function main() {
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
runAsCli(import.meta.url, main, {
|
|
103
|
-
source: 'git
|
|
103
|
+
source: 'clean-git',
|
|
104
104
|
usage: {
|
|
105
105
|
invocation:
|
|
106
|
-
'node .agents/scripts/git
|
|
106
|
+
'node .agents/scripts/clean-git.js [--execute] [--yes] [--json] [phase flags] [filters]',
|
|
107
107
|
summary:
|
|
108
108
|
'Tidy the local checkout in four phases — fast-forward the base branch, prune stale remote refs, reap merged branches, triage stashes. Dry-run unless --execute.',
|
|
109
109
|
flags: [
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* node:coverage ignore file -- thin CLI shell; the engine is lib/clean-temp.js */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `/clean-temp` — preview (default) or delete spent entries under the
|
|
6
|
+
* project's tempRoot. Thin shell over `lib/clean-temp.js`.
|
|
7
|
+
*
|
|
8
|
+
* Exit codes: 0 clean or dry-run, 1 refused (tempRoot outside the project
|
|
9
|
+
* root) or a deletion failed.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { runCleanTemp } from './lib/clean-temp.js';
|
|
13
|
+
import { runAsCli } from './lib/cli-utils.js';
|
|
14
|
+
import { resolveConfig } from './lib/config-resolver.js';
|
|
15
|
+
import { promptYesNo } from './lib/orchestration/git-cleanup/phases/prompts.js';
|
|
16
|
+
import { createProvider } from './lib/provider-factory.js';
|
|
17
|
+
|
|
18
|
+
/* node:coverage ignore next */
|
|
19
|
+
async function main() {
|
|
20
|
+
const { exitCode } = await runCleanTemp({
|
|
21
|
+
argv: process.argv.slice(2),
|
|
22
|
+
cwd: process.cwd(),
|
|
23
|
+
loadConfig: (projectRoot) => resolveConfig({ cwd: projectRoot }),
|
|
24
|
+
getProvider: createProvider,
|
|
25
|
+
confirm: promptYesNo,
|
|
26
|
+
write: (text) => process.stdout.write(text),
|
|
27
|
+
writeErr: (text) => process.stderr.write(text),
|
|
28
|
+
});
|
|
29
|
+
return exitCode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
runAsCli(import.meta.url, main, {
|
|
33
|
+
source: 'clean-temp',
|
|
34
|
+
propagateExitCode: true,
|
|
35
|
+
usage: {
|
|
36
|
+
invocation:
|
|
37
|
+
'node .agents/scripts/clean-temp.js [--execute] [--yes] [--json] [--cwd <path>]',
|
|
38
|
+
summary:
|
|
39
|
+
"Sort every top-level entry under the project's tempRoot into framework / closed-issue / aged / kept buckets. Dry-run unless --execute.",
|
|
40
|
+
flags: [
|
|
41
|
+
['--execute', 'Delete the confirmed buckets (default is a dry run).'],
|
|
42
|
+
[
|
|
43
|
+
'--yes',
|
|
44
|
+
'Skip the per-bucket prompts; deletes framework and closed-issue only — aged is never deleted unattended.',
|
|
45
|
+
],
|
|
46
|
+
['--json', 'Emit the result envelope as JSON.'],
|
|
47
|
+
['--cwd <path>', 'Project directory (default: process cwd).'],
|
|
48
|
+
],
|
|
49
|
+
notes: [
|
|
50
|
+
'Refuses (exit 1) when the resolved tempRoot is not inside the project root.',
|
|
51
|
+
'qa/, cache/, *.lock and signals.ndjson (at any depth) are never deleted.',
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
});
|