mandrel 2.65.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.
Files changed (82) hide show
  1. package/.agents/agents/acceptance-critic.md +7 -7
  2. package/.agents/agents/auditor.md +17 -18
  3. package/.agents/agents/plan-critic.md +5 -5
  4. package/.agents/agents/story-worker.md +5 -5
  5. package/.agents/docs/agentrc-reference.json +2 -1
  6. package/.agents/docs/configuration.md +2 -1
  7. package/.agents/docs/execution-reference.md +27 -5
  8. package/.agents/docs/workflows.md +4 -2
  9. package/.agents/instructions.md +12 -13
  10. package/.agents/rules/ci-remediation.md +3 -3
  11. package/.agents/rules/gherkin-standards.md +3 -2
  12. package/.agents/rules/git-conventions-reference.md +17 -8
  13. package/.agents/rules/git-conventions.md +10 -8
  14. package/.agents/rules/testing-standards.md +8 -7
  15. package/.agents/runtime-deps.json +1 -1
  16. package/.agents/schemas/agentrc.schema.json +6 -1
  17. package/.agents/scripts/boot-sweep.js +97 -9
  18. package/.agents/scripts/bootstrap.js +94 -89
  19. package/.agents/scripts/{git-cleanup.js → clean-git.js} +2 -2
  20. package/.agents/scripts/clean-temp.js +54 -0
  21. package/.agents/scripts/clean-worktrees.js +593 -0
  22. package/.agents/scripts/drain-pending-cleanup.js +5 -4
  23. package/.agents/scripts/lib/baselines/duplication-scanner.js +17 -7
  24. package/.agents/scripts/lib/bootstrap/project-bootstrap.js +78 -78
  25. package/.agents/scripts/lib/clean-temp.js +440 -0
  26. package/.agents/scripts/lib/cli/standard-args.js +60 -76
  27. package/.agents/scripts/lib/cli-args.js +26 -0
  28. package/.agents/scripts/lib/config/gates/shared.js +3 -3
  29. package/.agents/scripts/lib/config-settings-schema-delivery.js +11 -2
  30. package/.agents/scripts/lib/feedback-loop/graduate-steps.js +205 -0
  31. package/.agents/scripts/lib/feedback-loop/graduator-core.js +47 -782
  32. package/.agents/scripts/lib/feedback-loop/graduator-gh.js +449 -0
  33. package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
  34. package/.agents/scripts/lib/observability/close-telemetry.js +330 -0
  35. package/.agents/scripts/lib/observability/runtime-friction.js +2 -0
  36. package/.agents/scripts/lib/observability/signal-validator.js +17 -5
  37. package/.agents/scripts/lib/observability/source-classifier.js +3 -1
  38. package/.agents/scripts/lib/orchestration/code-review.js +22 -0
  39. package/.agents/scripts/lib/orchestration/git-cleanup/phases/cli.js +1 -1
  40. package/.agents/scripts/lib/orchestration/plan-metrics.js +76 -63
  41. package/.agents/scripts/lib/orchestration/plan-runner/worktree-sweep.js +149 -97
  42. package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +23 -0
  43. package/.agents/scripts/lib/orchestration/run-epilogue.js +6 -0
  44. package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +2 -0
  45. package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +349 -263
  46. package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +21 -7
  47. package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +4 -0
  48. package/.agents/scripts/lib/orchestration/single-story-close/runner.js +327 -314
  49. package/.agents/scripts/lib/orchestration/ticket-validator.js +19 -36
  50. package/.agents/scripts/lib/signals/detectors/common.js +63 -51
  51. package/.agents/scripts/lib/single-story-sweep.js +2 -2
  52. package/.agents/scripts/lib/temp-removal.js +110 -0
  53. package/.agents/scripts/lib/temp-retention.js +122 -73
  54. package/.agents/scripts/lib/transpile.js +28 -3
  55. package/.agents/scripts/lib/worktree/canonical-path.js +34 -0
  56. package/.agents/scripts/lib/worktree/lifecycle/reap.js +15 -4
  57. package/.agents/scripts/single-story-close.js +10 -2
  58. package/.agents/scripts/single-story-confirm-merge.js +267 -238
  59. package/.agents/scripts/single-story-init.js +120 -17
  60. package/.agents/skills/core/idea-refinement/SKILL.md +6 -6
  61. package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -2
  62. package/.agents/workflows/audit-architecture.md +5 -4
  63. package/.agents/workflows/audit-documentation.md +5 -5
  64. package/.agents/workflows/audit-performance.md +10 -10
  65. package/.agents/workflows/{git-cleanup.md → clean-git.md} +10 -10
  66. package/.agents/workflows/clean-temp.md +67 -0
  67. package/.agents/workflows/clean-worktrees.md +63 -0
  68. package/.agents/workflows/git-deliver.md +1 -1
  69. package/.agents/workflows/helpers/acceptance-self-eval.md +11 -10
  70. package/.agents/workflows/helpers/audit-lens-core.md +30 -57
  71. package/.agents/workflows/helpers/deliver-digest.md +2 -2
  72. package/.agents/workflows/helpers/deliver-reference.md +3 -1
  73. package/.agents/workflows/helpers/deliver-story-reference.md +2 -2
  74. package/.agents/workflows/helpers/deliver-story.md +6 -1
  75. package/.agents/workflows/helpers/parallel-tooling.md +16 -18
  76. package/.agents/workflows/mandrel-deliver.md +1 -1
  77. package/.agents/workflows/mandrel-plan.md +6 -5
  78. package/docs/CHANGELOG.md +39 -0
  79. package/lib/cli/guarded-sync.js +87 -0
  80. package/lib/cli/sync-agents.js +9 -92
  81. package/lib/cli/sync-commands.js +9 -101
  82. package/package.json +2 -2
@@ -229,10 +229,60 @@ export function decideStoryBranchSeed({ localHas, remoteHas }) {
229
229
  }
230
230
 
231
231
  /**
232
- * Reap merged `story-*` branches (excluding the current one). Never blocks
233
- * init. Protected candidates (unpushed work, dirty worktree, open Story) are
234
- * skipped; the lockfile is shared with `boot-sweep.js` via
235
- * `resolveSweepLockPath` so concurrent reaps cannot race.
232
+ * Remove closed-Story `.worktrees/story-<id>` trees through the boot sweep's
233
+ * own seam (`runWorktreeSweep`: same lock, same invariants). The Story being
234
+ * initialized is always kept, whatever its ticket state. Never throws: a
235
+ * failure lands in the returned outcome, which rides the init envelope.
236
+ *
237
+ * @returns {Promise<object>} `{ ok, reaped, skipped, reason?, error? }`.
238
+ */
239
+ export async function reapClosedStoryWorktrees({
240
+ cwd,
241
+ storyBranch,
242
+ provider,
243
+ lockPath,
244
+ lockTimeoutMs,
245
+ worktreeSweepFn,
246
+ acquireLockFn,
247
+ }) {
248
+ const logger = {
249
+ info: (m) => progress('CLEANUP', m),
250
+ warn: (m) => progress('CLEANUP', `⚠️ ${m}`),
251
+ };
252
+ try {
253
+ const { runWorktreeSweep } = await import('./boot-sweep.js');
254
+ const outcome = await runWorktreeSweep({
255
+ root: cwd,
256
+ provider,
257
+ lockPath,
258
+ lockTimeoutMs,
259
+ ...(worktreeSweepFn ? { sweepFn: worktreeSweepFn } : {}),
260
+ ...(acquireLockFn ? { acquireLockFn } : {}),
261
+ logger,
262
+ logTag: '[worktree-sweep]',
263
+ keepPaths: [path.join(cwd, '.worktrees', storyBranch)],
264
+ });
265
+ if (outcome.reaped?.length > 0) {
266
+ progress(
267
+ 'CLEANUP',
268
+ `🧹 removed ${outcome.reaped.length} closed-Story worktree(s).`,
269
+ );
270
+ }
271
+ return outcome;
272
+ } catch (err) {
273
+ const msg = err?.message ?? String(err);
274
+ logger.warn(`worktree sweep threw (init continues): ${msg}`);
275
+ return { ok: false, error: msg, reaped: [], skipped: [] };
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Reap merged `story-*` branches (excluding the current one), then the
281
+ * closed-Story worktrees. Never blocks init. Protected candidates (unpushed
282
+ * work, dirty worktree, open Story) are skipped; the lockfile is shared with
283
+ * `boot-sweep.js` via `resolveSweepLockPath` so concurrent reaps cannot race.
284
+ *
285
+ * @returns {Promise<{ worktreeSweep: object }>}
236
286
  */
237
287
  export async function reapMergedStoryBranches({
238
288
  cwd,
@@ -241,6 +291,8 @@ export async function reapMergedStoryBranches({
241
291
  config,
242
292
  provider,
243
293
  injectedSweep,
294
+ worktreeSweepFn,
295
+ acquireLockFn,
244
296
  }) {
245
297
  const sweepFn =
246
298
  injectedSweep ??
@@ -248,7 +300,37 @@ export async function reapMergedStoryBranches({
248
300
  const tempRoot = config?.project?.paths?.tempRoot ?? 'temp';
249
301
  const lockPath = resolveSweepLockPath({ cwd, tempRoot });
250
302
  const lockTimeoutMs =
251
- config.delivery?.worktreeIsolation?.sweepLockMs ?? 60_000;
303
+ config?.delivery?.worktreeIsolation?.sweepLockMs ?? 60_000;
304
+ await reapMergedBranches({
305
+ cwd,
306
+ baseBranch,
307
+ storyBranch,
308
+ provider,
309
+ sweepFn,
310
+ lockPath,
311
+ lockTimeoutMs,
312
+ });
313
+ const worktreeSweep = await reapClosedStoryWorktrees({
314
+ cwd,
315
+ storyBranch,
316
+ provider,
317
+ lockPath,
318
+ lockTimeoutMs,
319
+ worktreeSweepFn,
320
+ acquireLockFn,
321
+ });
322
+ return { worktreeSweep };
323
+ }
324
+
325
+ async function reapMergedBranches({
326
+ cwd,
327
+ baseBranch,
328
+ storyBranch,
329
+ provider,
330
+ sweepFn,
331
+ lockPath,
332
+ lockTimeoutMs,
333
+ }) {
252
334
  try {
253
335
  const sweep = await sweepFn({
254
336
  cwd,
@@ -300,9 +382,12 @@ export async function reapMergedStoryBranches({
300
382
  * @param {object} opts.config
301
383
  * @param {object} opts.provider
302
384
  * @param {Function|undefined} opts.injectedSweep
385
+ * @param {Function} [opts.worktreeSweepFn] Test override for the
386
+ * closed-Story worktree sweep.
303
387
  * @param {Function} opts.progress
304
388
  * @param {import('./lib/git/cached-fetch.js').FetchCache} [opts.fetchCache]
305
389
  * Test override; production shares the module singleton.
390
+ * @returns {Promise<{ worktreeSweep: object }>}
306
391
  */
307
392
  export async function materializeBaseBranch({
308
393
  cwd,
@@ -311,6 +396,7 @@ export async function materializeBaseBranch({
311
396
  config,
312
397
  provider,
313
398
  injectedSweep,
399
+ worktreeSweepFn,
314
400
  progress,
315
401
  fetchCache,
316
402
  }) {
@@ -326,13 +412,14 @@ export async function materializeBaseBranch({
326
412
  );
327
413
  }
328
414
 
329
- await reapMergedStoryBranches({
415
+ const { worktreeSweep } = await reapMergedStoryBranches({
330
416
  cwd,
331
417
  baseBranch,
332
418
  storyBranch,
333
419
  config,
334
420
  provider,
335
421
  injectedSweep,
422
+ worktreeSweepFn,
336
423
  });
337
424
 
338
425
  if (!branchExistsLocally(baseBranch, cwd)) {
@@ -342,10 +429,19 @@ export async function materializeBaseBranch({
342
429
  `Failed to fetch base branch ${baseBranch}: ${r.stderr || '(no stderr)'}`,
343
430
  );
344
431
  }
345
- return;
432
+ return { worktreeSweep };
346
433
  }
347
434
 
348
- // `git fetch` leaves local base at the old tip until fast-forwarded.
435
+ fastForwardBase({ cwd, baseBranch, progress });
436
+ return { worktreeSweep };
437
+ }
438
+
439
+ /**
440
+ * `git fetch` leaves local base at the old tip until fast-forwarded.
441
+ *
442
+ * @param {{ cwd: string, baseBranch: string, progress: Function }} opts
443
+ */
444
+ function fastForwardBase({ cwd, baseBranch, progress }) {
349
445
  const ffPlan = planFastForward({ cwd, baseBranch });
350
446
  const ff = executeFastForward({
351
447
  cwd,
@@ -461,6 +557,7 @@ export async function runSingleStoryInit({
461
557
  injectedProvider,
462
558
  injectedConfig,
463
559
  injectedSweep,
560
+ injectedWorktreeSweep,
464
561
  injectedAcquireLease,
465
562
  steal = false,
466
563
  injectedVerifyRemote,
@@ -533,6 +630,7 @@ export async function runSingleStoryInit({
533
630
  let workCwd = cwd;
534
631
  let worktreeCreated = false;
535
632
  let installStatus = { status: 'skipped', reason: 'dry-run' };
633
+ let worktreeSweep = null;
536
634
 
537
635
  if (!dryRun) {
538
636
  const acquire = injectedAcquireLease ?? acquireStoryLease;
@@ -563,15 +661,17 @@ export async function runSingleStoryInit({
563
661
  await rollUpContainerEpic(provider, storyId, config);
564
662
 
565
663
  try {
566
- await injectedMaterialize({
567
- cwd,
568
- baseBranch,
569
- storyBranch,
570
- config,
571
- provider,
572
- injectedSweep,
573
- progress,
574
- });
664
+ ({ worktreeSweep } =
665
+ (await injectedMaterialize({
666
+ cwd,
667
+ baseBranch,
668
+ storyBranch,
669
+ config,
670
+ provider,
671
+ injectedSweep,
672
+ worktreeSweepFn: injectedWorktreeSweep,
673
+ progress,
674
+ })) ?? {});
575
675
  injectedSeedBranch({ cwd, storyBranch, baseBranch, progress });
576
676
  ({ workCwd, worktreeCreated, installStatus } =
577
677
  await injectedProvisionWorktree({
@@ -610,6 +710,9 @@ export async function runSingleStoryInit({
610
710
  installStatus,
611
711
  dependenciesInstalled,
612
712
  installFailed: installStatus.status === 'failed',
713
+ // Closed-Story worktree sweep outcome; a failure degrades here, never
714
+ // into an init failure. `null` under --dry-run.
715
+ worktreeSweep: worktreeSweep ?? null,
613
716
  dryRun,
614
717
  remoteVerified: remote.remoteVerified,
615
718
  remoteProbe: { remoteUrl: remote.remoteUrl, detail: remote.detail },
@@ -22,12 +22,12 @@ description:
22
22
 
23
23
  ## Activation
24
24
 
25
- Called from [`/mandrel-plan`](../../../workflows/mandrel-plan.md) during ideation when the
26
- operator supplies `--seed "<text>"` (or runs ideation with no seed and the host
27
- collects one interactively). The skill sharpens freeform intent into the
28
- canonical planning sections that `/mandrel-plan` then folds into a Story. There is no
29
- separate Epic Clarity Gate path in v2 — N=1 Story authoring with a folded
30
- `## Spec` is the lean default.
25
+ Invoked directly by the operator (`idea-refine` / `ideate`), ahead of
26
+ planning — it is not a step of
27
+ [`/mandrel-plan`](../../../workflows/mandrel-plan.md), whose Gate #1 stops only
28
+ for a HITL unknown or a duplicate. The skill sharpens freeform intent into the
29
+ canonical planning sections, so a saved one-pager feeds
30
+ `/mandrel-plan <path>` (seed-file mode) directly.
31
31
 
32
32
  ## Detailed Instructions
33
33
 
@@ -109,8 +109,7 @@ not a soft preference.
109
109
  convenience to work around. Confirm authenticated state with a
110
110
  `take_snapshot` before driving.
111
111
  - **No headless fallback.** The chrome-devtools MCP surface is a host-provided
112
- runtime dependency. If it is unavailable, degrade with a clear error and stop
113
- — never fall back to the retired headless BDD runner.
112
+ runtime dependency. If it is unavailable, report a clear error and stop.
114
113
 
115
114
  ## 4. Mode — Known-Scenario Sweep (`/qa-run`)
116
115
 
@@ -23,10 +23,11 @@ Per the core's Scope interpretation:
23
23
 
24
24
  ## Execution strategy
25
25
 
26
- This is a **heavyweight lens**: dispatch it as a single `subagent_type: auditor`
27
- call, or fan its dimensions out per-dimension across parallel `auditor`
28
- subagents (parallel-tooling Rule 3) and merge under the self-cross-check.
29
- Sequential inline execution is the fallback (see the core's Execution strategy).
26
+ Dispatch this lens as one `subagent_type: auditor` call. Fan its dimensions
27
+ out across parallel `auditor` subagents (parallel-tooling Rule 3), merging
28
+ under the self-cross-check, only when the operator explicitly asks for
29
+ per-dimension fan-out. Sequential inline execution is the fallback (see the
30
+ core's Execution strategy).
30
31
 
31
32
  ## Step 0: Tool-first detection (mandatory — run before any LLM dimension)
32
33
 
@@ -62,11 +62,11 @@ with the config-driven target set above.
62
62
 
63
63
  ## Execution strategy
64
64
 
65
- This is a **heavyweight lens**: dispatch it as a single `subagent_type: auditor`
66
- call, or fan its per-doc / per-dimension verification out across parallel
67
- `auditor` subagents (parallel-tooling Rule 3) and merge under the
68
- self-cross-check. Sequential inline execution is the fallback (see the core's
69
- Execution strategy).
65
+ Dispatch this lens as one `subagent_type: auditor` call. Fan its per-doc /
66
+ per-dimension verification out across parallel `auditor` subagents
67
+ (parallel-tooling Rule 3), merging under the self-cross-check, only when the
68
+ operator explicitly asks for per-dimension fan-out. Sequential inline
69
+ execution is the fallback (see the core's Execution strategy).
70
70
 
71
71
  ## Step 1: Deterministic Signal First
72
72
 
@@ -27,18 +27,18 @@ Per the core's Scope interpretation:
27
27
 
28
28
  ## Execution strategy
29
29
 
30
- This is a **heavyweight lens**: dispatch it as a single `subagent_type: auditor`
31
- call, or fan its resource dimensions out per-dimension across parallel `auditor`
32
- subagents (parallel-tooling Rule 3) and merge under the self-cross-check.
33
- Sequential inline execution is the fallback (see the core's Execution strategy).
30
+ Dispatch this lens as one `subagent_type: auditor` call. Fan its resource
31
+ dimensions out across parallel `auditor` subagents (parallel-tooling Rule 3),
32
+ merging under the self-cross-check, only when the operator explicitly asks for
33
+ per-dimension fan-out. Sequential inline execution is the fallback (see the
34
+ core's Execution strategy).
34
35
 
35
36
  > **Measurement is non-mutating, not forbidden.** This lens is read-only with
36
- > respect to source, but it MUST be allowed to *run* measurements. The
37
- > orchestrated path grants its measurement agents a `Bash` tool restricted to a
38
- > **non-mutating command allowlist** (profilers, timers, bundle-stat and
39
- > file-size probes — never a command that writes source, installs, or mutates
40
- > git/labels). See the allowlist in the harness-generated
41
- > `.claude/workflows/audit-performance.workflow.js`.
37
+ > respect to source, but the auditor MUST be allowed to *run* measurements. It
38
+ > runs only **non-mutating** commands — profilers, timers, bundle-stat and
39
+ > file-size probes — and never a command that writes source, installs
40
+ > packages, or mutates git state or labels. The one write is the report
41
+ > artifact.
42
42
 
43
43
  ## Step 0: Measure before you judge (mandatory)
44
44
 
@@ -5,9 +5,9 @@ description: >-
5
5
  `git stash` entries — each step gated by operator confirmation.
6
6
  ---
7
7
 
8
- # /git-cleanup [--fast-forward-main] [--prune-remotes] [--branches] [--stashes] [--execute] [--remote] [--yes] [--include-content-merged] [--drop-stashes <ref>] [--exclude <pattern>] [--json]
8
+ # /clean-git [--fast-forward-main] [--prune-remotes] [--branches] [--stashes] [--execute] [--remote] [--yes] [--include-content-merged] [--drop-stashes <ref>] [--exclude <pattern>] [--json]
9
9
 
10
- `/git-cleanup` folds the four cleanup steps operators routinely run by hand
10
+ `/clean-git` folds the four cleanup steps operators routinely run by hand
11
11
  after a busy session into a single pipeline with per-step confirmation. It is a
12
12
  **recovery tool**, not a routine chore: the delivering flows already reap their
13
13
  own merged refs and fast-forward the base branch (see
@@ -22,13 +22,13 @@ Reach for it when the automated hygiene left an unusual state behind.
22
22
  > skill citation.
23
23
 
24
24
  The enumeration + reap logic lives in
25
- [`git-cleanup.js`](../scripts/git-cleanup.js) — it computes the candidate list,
25
+ [`clean-git.js`](../scripts/clean-git.js) — it computes the candidate list,
26
26
  the skip taxonomy, the detection signals, and the JSON envelope, and prints them
27
27
  itself. Without `--execute` the script is a **dry-run preview**; nothing is
28
28
  mutated. When no phase flag is passed, **all four phases run** sequentially; a
29
29
  phase flag narrows the run. A failure in one phase does not short-circuit the
30
30
  others — each runs and reports independently. The script documents its own
31
- flags: `node .agents/scripts/git-cleanup.js --help`.
31
+ flags: `node .agents/scripts/clean-git.js --help`.
32
32
 
33
33
  ## Phases
34
34
 
@@ -65,24 +65,24 @@ merged-PR branch in scope unless `--exclude`d.
65
65
 
66
66
  ```bash
67
67
  # Preview all four phases (no mutation).
68
- node .agents/scripts/git-cleanup.js
68
+ node .agents/scripts/clean-git.js
69
69
 
70
70
  # Run everything non-interactively, including origin refs. Branches detected
71
71
  # only by content-equivalence keep their origin ref — see the note below.
72
- node .agents/scripts/git-cleanup.js --execute --remote --yes
72
+ node .agents/scripts/clean-git.js --execute --remote --yes
73
73
 
74
74
  # Same, but also delete the origin refs of content-merged branches. Nobody is
75
75
  # watching, so opting in is the whole confirmation this delete ever gets.
76
- node .agents/scripts/git-cleanup.js --execute --remote --yes \
76
+ node .agents/scripts/clean-git.js --execute --remote --yes \
77
77
  --include-content-merged
78
78
 
79
79
  # Only fast-forward main.
80
- node .agents/scripts/git-cleanup.js --fast-forward-main --execute
80
+ node .agents/scripts/clean-git.js --fast-forward-main --execute
81
81
 
82
82
  # Only sweep merged branches + their origin refs.
83
- node .agents/scripts/git-cleanup.js --branches --execute --remote
83
+ node .agents/scripts/clean-git.js --branches --execute --remote
84
84
 
85
85
  # Drop specific stashes under --yes.
86
- node .agents/scripts/git-cleanup.js --stashes --execute --yes \
86
+ node .agents/scripts/clean-git.js --stashes --execute --yes \
87
87
  --drop-stashes 'stash@{0}' --drop-stashes 'stash@{2}'
88
88
  ```
@@ -0,0 +1,67 @@
1
+ ---
2
+ description: >-
3
+ Clear the temp-tree backlog the land-time purge cannot attribute: sort every
4
+ top-level entry under the project's tempRoot into framework, closed-issue,
5
+ aged and kept buckets, preview by default, and delete only confirmed buckets.
6
+ ---
7
+
8
+ # /clean-temp [--execute] [--yes] [--json]
9
+
10
+ The land-time and boot-time purges only reap what they can attribute: the
11
+ framework's own temp layouts and `temp/scratch/`. Everything else an agent
12
+ dropped at the temp root is reported and left alone, so a busy consumer's temp
13
+ tree grows without bound. `/clean-temp` is the operator's catch-up for that
14
+ backlog. It classifies and deletes through the same temp-retention engine the
15
+ purges use — there is no second walker.
16
+
17
+ The script documents its own flags:
18
+ `node .agents/scripts/clean-temp.js --help`. Without `--execute` it is a
19
+ **dry-run preview**; nothing is deleted.
20
+
21
+ ## Buckets
22
+
23
+ Every top-level entry under tempRoot lands in exactly one:
24
+
25
+ | Bucket | What it holds | Unattended (`--yes`) |
26
+ | --- | --- | --- |
27
+ | **framework** | A framework layout holding artifacts the auto-purge would take — Story-keyed on a closed Story, or past `staleDays`. | Deleted |
28
+ | **closed-issue** | An unrecognized entry whose basename names exactly one issue id, and that issue reads `closed`. | Deleted |
29
+ | **aged** | An unrecognized entry naming no id, or several, older than `staleDays`. | **Never** — an age heuristic has no attribution, so it needs a human |
30
+ | **kept** | Everything else, with the reason: issue open, issue read failed, too recent, reserved, or nothing spent. | Kept |
31
+
32
+ An id is a standalone run of up to seven digits in the basename. A basename
33
+ naming two ids is never attributed to either — it is treated as id-less.
34
+
35
+ ## Constraint
36
+
37
+ > [!WARNING] `--execute` deletes files. Interactive runs confirm each bucket;
38
+ > `--yes` deletes the framework and closed-issue buckets only.
39
+
40
+ - Reads fail safe: an open issue, or one whose read fails, keeps its entry.
41
+ - `qa/`, `cache/`, `*.lock` and `signals.ndjson` at any depth are never
42
+ deleted.
43
+ - Project-scoped: the script exits 1 without deleting anything when the
44
+ resolved tempRoot is not inside the project root it was invoked from. It
45
+ never touches `$TMPDIR` or a sibling checkout.
46
+
47
+ ## Steps
48
+
49
+ 1. Preview and read the table (bucket, entry, size, age, reason):
50
+
51
+ ```bash
52
+ node .agents/scripts/clean-temp.js
53
+ ```
54
+
55
+ 2. Delete, confirming each bucket:
56
+
57
+ ```bash
58
+ node .agents/scripts/clean-temp.js --execute
59
+ ```
60
+
61
+ Unattended, `--execute --yes` deletes the framework and closed-issue buckets
62
+ and reports the aged bucket as left for a human. `--json` emits the envelope
63
+ (per-bucket totals and `bytesReclaimed`) instead of the table.
64
+
65
+ Going forward, put ad-hoc scratch under `temp/scratch/story-<id>/` (or
66
+ `temp/scratch/` with no Story): a Story's landing reaps its scratch directory,
67
+ and the boot sweep age-floors the rest.
@@ -0,0 +1,63 @@
1
+ ---
2
+ description: >-
3
+ Reclaim disk from dead worktrees: list every worktree of this project as a
4
+ removal candidate (closed Story, merged branch, orphaned directory, detached
5
+ HEAD) or as kept with a reason, then remove candidates only on `--execute`.
6
+ ---
7
+
8
+ # /clean-worktrees [--execute] [--yes] [--json]
9
+
10
+ Every Story worktree carries its own `node_modules`, so a worktree left behind
11
+ costs gigabytes. Workflow boot already removes `.worktrees/story-<id>` trees
12
+ whose Story is closed or `agent::done` (the boot sweep in
13
+ [`boot-sweep.js`](../scripts/boot-sweep.js)); `/clean-worktrees` is the
14
+ **recovery tool** for everything that sweep does not own — the backlog, trees
15
+ on non-Story branches, directories git no longer registers, and detached
16
+ trees such as the Claude Code app's `.claude/worktrees/*`.
17
+
18
+ The enumeration, classification and removal live in
19
+ [`clean-worktrees.js`](../scripts/clean-worktrees.js), which documents its own
20
+ flags: `node .agents/scripts/clean-worktrees.js --help`.
21
+
22
+ ## Steps
23
+
24
+ 1. **Preview.** Run `node .agents/scripts/clean-worktrees.js` (dry-run, the
25
+ default). It prints one row per worktree — class, path, size, branch/HEAD,
26
+ action — and removes nothing. Show the operator the table.
27
+ 2. **Confirm.** Ask the operator which candidates to remove. Do not proceed
28
+ on your own judgment.
29
+ 3. **Remove.** Re-run with `--execute`. In a terminal it asks per entry;
30
+ `--execute --yes` removes every non-`detached` candidate without asking.
31
+ `--json` emits the envelope, including `bytesReclaimed`.
32
+
33
+ ## Classes
34
+
35
+ | Class | Candidate when |
36
+ | --- | --- |
37
+ | `closed-story` | `.worktrees/story-<id>` on `story-<id>` whose Story is closed or `agent::done`. |
38
+ | `merged-branch` | Any other branch whose PR is MERGED and whose HEAD is the merged head. |
39
+ | `orphan-dir` | A directory under `.worktrees/` that `git worktree list` does not register. |
40
+ | `detached` | A registered worktree with a detached HEAD. |
41
+
42
+ Everything else is **kept** with its reason: the main checkout, an open Story,
43
+ an unmerged branch, a dirty tree, unpushed commits, a tree in use.
44
+
45
+ ## Constraint
46
+
47
+ > [!WARNING] `--execute` deletes worktree directories. Without it the script
48
+ > only previews.
49
+
50
+ - **Project-scoped.** Only worktrees inside the invoking checkout's project
51
+ root are candidates; one registered elsewhere is reported, never removed.
52
+ - **Unique work survives.** A dirty tree, or a HEAD no remote-tracking ref
53
+ contains, is never removed.
54
+ - **Live trees survive.** The tree this process runs from is never removed;
55
+ on macOS and Linux a tree any live process uses is refused outright.
56
+ - **`detached` needs a person.** A detached tree carries no Story label and a
57
+ live session may own it (a blanket prune once destroyed a live delivery's
58
+ worktree), so it is removed only on a per-entry interactive yes — never
59
+ under `--yes`.
60
+ - **Removal goes through the worktree removal seam** (Windows lock retry and
61
+ the pending-cleanup hand-off), never raw deletion.
62
+ - **Branches are untouched.** Deleting local or remote branches is
63
+ [`/clean-git`](clean-git.md)'s job.
@@ -77,7 +77,7 @@ node .agents/scripts/boot-sweep.js \
77
77
  --current "$(git rev-parse --abbrev-ref HEAD)"
78
78
  ```
79
79
 
80
- The safe subset of the `/git-cleanup` phases: fast-forwards the base branch,
80
+ The safe subset of the `/clean-git` phases: fast-forwards the base branch,
81
81
  prunes stale remote-tracking refs, and reaps merged branches. It never
82
82
  touches the stash, never reaps a candidate with unpushed work / dirty
83
83
  worktree / open parent ticket, and always exits `0` — a failed sweep is
@@ -32,8 +32,8 @@ per-criterion, mid-delivery, and evaluates the actual work product.
32
32
  authors the Story's verdict, and it covers **every** `acceptance[]` item in
33
33
  one file. Which pass is named by the ceremony decision
34
34
  (`verdictOwner: 'fresh-critic' | 'inline-self-eval'` from
35
- `resolveCeremonyForRisk`), and since Story #5343 that follows the
36
- **ceremony profile alone**:
35
+ `resolveCeremonyForRisk`), which follows the **ceremony profile
36
+ alone**:
37
37
 
38
38
  > ```bash
39
39
  > node <main-repo>/.agents/scripts/ceremony-derive.js --story <storyId> --cwd <workCwd>
@@ -43,9 +43,8 @@ per-criterion, mid-delivery, and evaluates the actual work product.
43
43
  > classes **for review depth**, and resolves the owner (`mode`, `reason`,
44
44
  > `verdictOwner`): **`minimal` / `standard` → `inline`** (the default — you
45
45
  > author the verdict yourself), **`strict` → `fresh`** (dispatch the
46
- > maker-blind critic). The derived level no longer routes this decision;
47
- > it escalates `review-depth.js` instead, which still resolves `deep` for
48
- > any sensitive path.
46
+ > maker-blind critic). The derived level feeds `review-depth.js`, not
47
+ > this decision; review depth resolves `deep` for any sensitive path.
49
48
 
50
49
  **Never run both**, and never run a preliminary self-assessment before
51
50
  dispatching a fresh critic — the redundant pre-pass buys no measurable
@@ -72,9 +71,10 @@ per-criterion, mid-delivery, and evaluates the actual work product.
72
71
  > system prompt, no entry-doc @-closure) carrying the maker-blind
73
72
  > invariant and the verdict schema standalone. With the kill-switch off
74
73
  > (`roleScopedAgents: false`), fall back to
75
- > `subagent_type: general-purpose`. This loop already runs inside a Story
76
- > delivery sub-agent, so the critic sits at nesting depth 2 — supported by
77
- > any harness that carries `Agent` into sub-agents (Claude Code ≥ 2.1.202).
74
+ > `subagent_type: general-purpose`. Under sub-agent dispatch this loop
75
+ > runs inside a `story-worker`, so the critic sits at nesting depth 2
76
+ > (depth 1 inline) — supported by any harness that carries `Agent` into
77
+ > sub-agents (Claude Code ≥ 2.1.202).
78
78
 
79
79
  Whichever pass owns it, the verdict:
80
80
  + Inspects the **change set it was handed** — the one `files` list above —
@@ -95,7 +95,7 @@ per-criterion, mid-delivery, and evaluates the actual work product.
95
95
  without being respawned; a stale or absent stamp reports `spawn: true` and
96
96
  the command runs for real. The credited run itself is stated once, in
97
97
  [`deliver-digest.md`](deliver-digest.md) § 5.
98
- + Emits **one** verdict file under `temp/` conforming to
98
+ + Emits **one** verdict file under `temp/scratch/story-<id>/` conforming to
99
99
  [`acceptance-eval-verdict.schema.json`](../../schemas/acceptance-eval-verdict.schema.json):
100
100
  one `{ index, criterion, verdict: met|partial|unmet, evidence,
101
101
  verifyEvidence[] }` record per `acceptance[]` item, in acceptance-array
@@ -137,4 +137,5 @@ per-criterion, mid-delivery, and evaluates the actual work product.
137
137
  (transition to `agent::blocked`) and post a `friction` comment naming the
138
138
  unmet criteria and their evidence. Never silently proceed to close.
139
139
 
140
- Write the verdict under `temp/` only — it is a scratch artifact.
140
+ Write the verdict under `temp/scratch/story-<id>/` only — a scratch artifact
141
+ the Story's landing reaps.