mandrel 2.66.0 → 2.67.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/docs/agentrc-reference.json +2 -1
- package/.agents/docs/configuration.md +2 -1
- 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 +6 -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/drain-pending-cleanup.js +5 -4
- package/.agents/scripts/lib/clean-temp.js +440 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +11 -2
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +3 -1
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
- package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +149 -97
- package/.agents/scripts/lib/single-story-sweep.js +2 -2
- package/.agents/scripts/lib/temp-removal.js +110 -0
- package/.agents/scripts/lib/temp-retention.js +122 -73
- package/.agents/scripts/lib/worktree/canonical-path.js +34 -0
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +15 -4
- package/.agents/scripts/single-story-init.js +120 -17
- 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/deliver-story-reference.md +2 -2
- package/docs/CHANGELOG.md +13 -0
- 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.
|
|
@@ -139,13 +139,14 @@ Everything `/mandrel-deliver` and `single-story-close` consume: worktree isolati
|
|
|
139
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. Best-effort: a wait that expires spawns anyway, 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. |
|
|
@@ -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).
|
|
@@ -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
|
|
@@ -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
|
+
});
|