dorfl 0.10.0 → 0.11.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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +74 -4
- package/dist/cli.js.map +1 -1
- package/dist/gc.d.ts +59 -0
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +145 -2
- package/dist/gc.js.map +1 -1
- package/dist/git.d.ts +20 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +155 -8
- package/dist/git.js.map +1 -1
- package/dist/skills/drive-tasks/SKILL.md +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +64 -21
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +94 -5
- package/src/gc.ts +202 -2
- package/src/git.ts +180 -8
- package/src/workspace.ts +74 -20
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,OAAO,EAAS,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,OAAO,EAAS,MAAM,WAAW,CAAC;AAwH1C,OAAO,EAAiB,KAAK,YAAY,EAAC,MAAM,sBAAsB,CAAC;AAwCvE,OAAO,EAAgB,KAAK,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAgyB5E;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,OAAO,GACb,MAAM,CAoCR;AA6RD,wBAAgB,YAAY,IAAI,OAAO,CAwgHtC;AAaD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,OAAO,GACZ,MAAM,CAyBR;AA+FD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,MAAM,CAAC,IAAI,GAAE,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBzE"}
|
package/dist/cli.js
CHANGED
|
@@ -44,7 +44,7 @@ import { harnessTaskReviewGate } from './tasker-review-loop.js';
|
|
|
44
44
|
import { runVerify } from './verify.js';
|
|
45
45
|
import { renderPrompt } from './prompt.js';
|
|
46
46
|
import { resolvePromptGuidance } from './config.js';
|
|
47
|
-
import { gc, RETAIN_REASON_TEXT } from './gc.js';
|
|
47
|
+
import { gc, resolveArbiterKeyFromCwd, enumerateWorkspaceArbiters, RETAIN_REASON_TEXT, } from './gc.js';
|
|
48
48
|
import { runPrdToSpec, } from './prd-to-spec.js';
|
|
49
49
|
import { resyncProtocol } from './resync-protocol.js';
|
|
50
50
|
import { sweepRemoteMergedBranches } from './reap-branches.js';
|
|
@@ -2332,7 +2332,7 @@ export function buildProgram() {
|
|
|
2332
2332
|
program
|
|
2333
2333
|
.command('gc')
|
|
2334
2334
|
.helpGroup(ADVANCED_GROUP)
|
|
2335
|
-
.description('Re-apply the provably-safe deletion predicate (ADR \u00a74)
|
|
2335
|
+
.description('Re-apply the provably-safe deletion predicate (ADR \u00a74) to job worktrees under workspacesDir/work/*: reap the provably-safe ones (clean tree AND branch tip reachable on the arbiter \u2014 merged or pushed) via git worktree remove (+ prune, never rm -rf), and report each RETAINED one with a reason. The catch-up for when end-of-job auto-reap did not run (runner crash/kill). SCOPE: by DEFAULT this is ARBITER-SCOPED \u2014 it acts ONLY on worktrees belonging to the arbiter resolved from the cwd (the same arbiter do --isolated/the mirror uses), so a gc in repo A can never reap repo B\u2019s worktrees. Pass --all-arbiters to sweep across EVERY arbiter (loud + global), or --arbiter <remote-or-url> to target a specific one. --force overrides the predicate (discards un-saved work) and requires --yes \u2014 loud, never default; --all-arbiters --force must be opted into explicitly, never reached by default.')
|
|
2336
2336
|
.option('-c, --config <path>', 'config file path', defaultConfigPath())
|
|
2337
2337
|
.option('--workspace <dir>', 'execution working area to sweep (default: workspacesDir / ~/.dorfl)')
|
|
2338
2338
|
.option('--force', 'OVERRIDE the predicate: remove worktrees even with un-saved work (requires --yes; never the default)')
|
|
@@ -2340,7 +2340,8 @@ export function buildProgram() {
|
|
|
2340
2340
|
.option('--ledger [repoPath]', 'SWEEP the work/ lifecycle LEDGER instead of job worktrees: REPORT (never delete) every slug present in more than one work/ status folder (the one-slug-one-folder belt-and-suspenders), with its folders + candidate canonical folder, for a HUMAN to resolve. Defaults to the cwd repo.')
|
|
2341
2341
|
.option('--reap-stale-locks', '(with --ledger) OPT-IN: also CLEAR every ORPHAN lock the report finds via the SAME leased delete `release-lock` uses, so one command sweeps all orphaned locks instead of N hand-run release-locks. Reaps the `cleared-stale` class: a held `active` lock that is EITHER terminal on `<arbiter>/main` (stranded between the durable move and the release) OR non-terminal + SURFACED (`needsAnswers:true` + sidecar on `<arbiter>/main`) = the crash-window orphan the ordered bounce transition leaves when step 1 lands but step 2 never runs. A `kept-in-flight` (active + non-terminal + NOT surfaced — a healthy build) is NEVER reaped, even with this flag. A concurrent change to a lock ref makes its leased delete REJECT (reported), never --force. WITHOUT this flag `gc --ledger` stays report-only (fail-loud, deletes nothing).')
|
|
2342
2342
|
.option('--remote-branches', 'SWEEP the arbiter’s remote work/* BRANCHES instead of job worktrees: delete (via git push --delete, NEVER --force) exactly those PROVABLY MERGED into <arbiter>/main (git merge-base --is-ancestor, the SAME predicate the worktree reaper uses), and RETAIN the rest with a reason. An in-flight/un-merged branch (the recovery point) is NEVER touched. Provider-agnostic plain git — works on a --bare arbiter. The merged-only complement of `requeue --reset`.')
|
|
2343
|
-
.option('--arbiter <remote>', '(
|
|
2343
|
+
.option('--arbiter <remote-or-url>', 'SCOPE the worktree sweep to a SPECIFIC arbiter (a remote NAME resolved from --cwd, or a direct arbiter URL) instead of the cwd\u2019s. (With --remote-branches / --ledger it names the arbiter git remote whose work/* branches / locks to sweep; default there: origin.)')
|
|
2344
|
+
.option('--all-arbiters', 'GLOBAL, LOUD override: sweep worktrees across EVERY arbiter under workspacesDir/work/* (today\u2019s pre-scoping behaviour), not just the cwd\u2019s. Prints a banner naming every arbiter it will act on before doing anything; combining it with --force still requires --yes. This is the destructive cross-repo path \u2014 opt into it explicitly.')
|
|
2344
2345
|
.option('--cwd <dir>', '(with --remote-branches) the local repo/clone whose --arbiter remote points at the arbiter to sweep (default: cwd); only remote refs are read + deleted, never the working tree')
|
|
2345
2346
|
.option('--dry-run', '(with --remote-branches) REPORT which merged branches WOULD be reaped without deleting anything (a read-only preview)')
|
|
2346
2347
|
.option('--json', 'output the raw result as JSON')
|
|
@@ -2511,6 +2512,68 @@ export function buildProgram() {
|
|
|
2511
2512
|
console.log(`Summary: ${reapedCount} ${verb}, ${sweep.retained.length} retained.`);
|
|
2512
2513
|
return;
|
|
2513
2514
|
}
|
|
2515
|
+
// SCOPE RESOLUTION (this task): the worktree reaper is ARBITER-SCOPED by
|
|
2516
|
+
// default. `--all-arbiters` restores the GLOBAL (cross-arbiter) sweep behind
|
|
2517
|
+
// a LOUD banner; `--arbiter <remote-or-url>` targets a specific arbiter; the
|
|
2518
|
+
// bare default resolves the arbiter from the cwd (the SAME resolution
|
|
2519
|
+
// `do --isolated` / the mirror uses). With NO resolvable cwd arbiter and
|
|
2520
|
+
// neither flag, we REFUSE (never silently fall back to global) — gc used to
|
|
2521
|
+
// default to global, and a global `gc --force --yes` from repo A could
|
|
2522
|
+
// irreversibly discard repo B's un-pushed work.
|
|
2523
|
+
let arbiterKey;
|
|
2524
|
+
if (flags.allArbiters !== true) {
|
|
2525
|
+
if (flags.arbiter !== undefined && flags.arbiter.trim() !== '') {
|
|
2526
|
+
// An explicit arbiter: a direct URL (contains `://`, a scp-like `:`,
|
|
2527
|
+
// or an absolute path) is keyed directly; otherwise it is a remote NAME
|
|
2528
|
+
// resolved from the cwd (default `origin`).
|
|
2529
|
+
const spec = flags.arbiter.trim();
|
|
2530
|
+
const looksLikeUrl = spec.includes('://') ||
|
|
2531
|
+
spec.startsWith('/') ||
|
|
2532
|
+
/^[^/]+@/.test(spec);
|
|
2533
|
+
// A remote NAME resolves from --cwd if given (consistent with the
|
|
2534
|
+
// other gc sub-modes), else the process cwd.
|
|
2535
|
+
const resolveFrom = flags.cwd ?? process.cwd();
|
|
2536
|
+
arbiterKey = looksLikeUrl
|
|
2537
|
+
? encodeRepoKey(spec)
|
|
2538
|
+
: resolveArbiterKeyFromCwd(resolveFrom, spec, process.env);
|
|
2539
|
+
if (arbiterKey === undefined) {
|
|
2540
|
+
console.error(`refusing: could not resolve arbiter '${spec}' \u2014 not a URL and ` +
|
|
2541
|
+
'no git remote with that name in the cwd. Pass a remote name that ' +
|
|
2542
|
+
'exists here, an arbiter URL, or --all-arbiters.');
|
|
2543
|
+
process.exit(1);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
else {
|
|
2547
|
+
arbiterKey = resolveArbiterKeyFromCwd(flags.cwd ?? process.cwd(), config.defaultArbiter, process.env);
|
|
2548
|
+
if (arbiterKey === undefined) {
|
|
2549
|
+
console.error('refusing: `gc` is now ARBITER-SCOPED and could not resolve an ' +
|
|
2550
|
+
`arbiter from the current directory (no '${config.defaultArbiter}' ` +
|
|
2551
|
+
'git remote, or not inside a repo). It will NOT fall back to a ' +
|
|
2552
|
+
'global cross-arbiter sweep. Run it from a repo with an arbiter ' +
|
|
2553
|
+
'remote, pass --arbiter <remote-or-url>, or --all-arbiters to sweep ' +
|
|
2554
|
+
'every arbiter.');
|
|
2555
|
+
process.exit(1);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
else {
|
|
2560
|
+
// GLOBAL sweep: LOUD banner naming EVERY arbiter it will act on before
|
|
2561
|
+
// doing anything (this task's "loud" requirement). Global + --force must be
|
|
2562
|
+
// opted into explicitly (the --yes gate below still applies), never reached
|
|
2563
|
+
// by default.
|
|
2564
|
+
const arbiters = enumerateWorkspaceArbiters(workspacesDir, process.env);
|
|
2565
|
+
console.error('>> --all-arbiters: operating GLOBALLY across ALL arbiters under ' +
|
|
2566
|
+
`${workspacesDir}/work/* (NOT scoped to the current repo).`);
|
|
2567
|
+
if (arbiters.length > 0) {
|
|
2568
|
+
console.error('>> arbiters in scope:');
|
|
2569
|
+
for (const a of arbiters) {
|
|
2570
|
+
console.error(`>> - ${a.url ?? a.key ?? '(malformed mirror)'}`);
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
else {
|
|
2574
|
+
console.error('>> (no worktrees found under any arbiter)');
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2514
2577
|
// `--force` discards un-saved work, so it is gated behind an explicit
|
|
2515
2578
|
// confirmation (`--yes`) — loud + intentional, NEVER the default (ADR §4).
|
|
2516
2579
|
if (flags.force && !flags.yes) {
|
|
@@ -2525,6 +2588,7 @@ export function buildProgram() {
|
|
|
2525
2588
|
}
|
|
2526
2589
|
const result = gc({
|
|
2527
2590
|
workspacesDir,
|
|
2591
|
+
arbiterKey,
|
|
2528
2592
|
force: flags.force === true,
|
|
2529
2593
|
note: (message) => console.error(`>> ${message}`),
|
|
2530
2594
|
});
|
|
@@ -2541,7 +2605,13 @@ export function buildProgram() {
|
|
|
2541
2605
|
for (const retained of result.retained) {
|
|
2542
2606
|
console.log(` [retained] ${retained.slug} \u2014 ${RETAIN_REASON_TEXT[retained.reason]}`);
|
|
2543
2607
|
}
|
|
2544
|
-
|
|
2608
|
+
for (const orphan of result.sweptOrphans) {
|
|
2609
|
+
console.log(` [swept] ${orphan.dir} \u2014 ${orphan.kind}`);
|
|
2610
|
+
}
|
|
2611
|
+
const orphanNote = result.sweptOrphans.length > 0
|
|
2612
|
+
? `, ${result.sweptOrphans.length} orphan(s) swept`
|
|
2613
|
+
: '';
|
|
2614
|
+
console.log(`Summary: ${result.reaped.length} reaped, ${result.retained.length} retained${orphanNote}.`);
|
|
2545
2615
|
});
|
|
2546
2616
|
// `dorfl sync`: bring an already-onboarded repo up to the CURRENT protocol.
|
|
2547
2617
|
// Always re-syncs `work/protocol/*` + bumps VERSION (the deterministic slice of
|