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.
@@ -1 +1 @@
1
- {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dorfl",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/wighawag/dorfl.git",
package/src/cli.ts CHANGED
@@ -108,7 +108,12 @@ import {harnessTaskReviewGate} from './tasker-review-loop.js';
108
108
  import {runVerify} from './verify.js';
109
109
  import {renderPrompt} from './prompt.js';
110
110
  import {resolvePromptGuidance} from './config.js';
111
- import {gc, RETAIN_REASON_TEXT} from './gc.js';
111
+ import {
112
+ gc,
113
+ resolveArbiterKeyFromCwd,
114
+ enumerateWorkspaceArbiters,
115
+ RETAIN_REASON_TEXT,
116
+ } from './gc.js';
112
117
  import {
113
118
  runPrdToSpec,
114
119
  type PrdToSpecResult,
@@ -822,6 +827,7 @@ interface GcFlags {
822
827
  ledger?: string;
823
828
  remoteBranches?: boolean;
824
829
  arbiter?: string;
830
+ allArbiters?: boolean;
825
831
  cwd?: string;
826
832
  dryRun?: boolean;
827
833
  reapStaleLocks?: boolean;
@@ -3391,7 +3397,7 @@ export function buildProgram(): Command {
3391
3397
  .command('gc')
3392
3398
  .helpGroup(ADVANCED_GROUP)
3393
3399
  .description(
3394
- 'Re-apply the provably-safe deletion predicate (ADR \u00a74) across every job worktree 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). --force overrides the predicate (discards un-saved work) \u2014 loud, never default.',
3400
+ '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.',
3395
3401
  )
3396
3402
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
3397
3403
  .option(
@@ -3416,8 +3422,12 @@ export function buildProgram(): Command {
3416
3422
  '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`.',
3417
3423
  )
3418
3424
  .option(
3419
- '--arbiter <remote>',
3420
- '(with --remote-branches) the arbiter git remote whose work/* branches to sweep (default: origin); resolved from --cwd',
3425
+ '--arbiter <remote-or-url>',
3426
+ '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.)',
3427
+ )
3428
+ .option(
3429
+ '--all-arbiters',
3430
+ '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.',
3421
3431
  )
3422
3432
  .option(
3423
3433
  '--cwd <dir>',
@@ -3614,6 +3624,77 @@ export function buildProgram(): Command {
3614
3624
  return;
3615
3625
  }
3616
3626
 
3627
+ // SCOPE RESOLUTION (this task): the worktree reaper is ARBITER-SCOPED by
3628
+ // default. `--all-arbiters` restores the GLOBAL (cross-arbiter) sweep behind
3629
+ // a LOUD banner; `--arbiter <remote-or-url>` targets a specific arbiter; the
3630
+ // bare default resolves the arbiter from the cwd (the SAME resolution
3631
+ // `do --isolated` / the mirror uses). With NO resolvable cwd arbiter and
3632
+ // neither flag, we REFUSE (never silently fall back to global) — gc used to
3633
+ // default to global, and a global `gc --force --yes` from repo A could
3634
+ // irreversibly discard repo B's un-pushed work.
3635
+ let arbiterKey: string | undefined;
3636
+ if (flags.allArbiters !== true) {
3637
+ if (flags.arbiter !== undefined && flags.arbiter.trim() !== '') {
3638
+ // An explicit arbiter: a direct URL (contains `://`, a scp-like `:`,
3639
+ // or an absolute path) is keyed directly; otherwise it is a remote NAME
3640
+ // resolved from the cwd (default `origin`).
3641
+ const spec = flags.arbiter.trim();
3642
+ const looksLikeUrl =
3643
+ spec.includes('://') ||
3644
+ spec.startsWith('/') ||
3645
+ /^[^/]+@/.test(spec);
3646
+ // A remote NAME resolves from --cwd if given (consistent with the
3647
+ // other gc sub-modes), else the process cwd.
3648
+ const resolveFrom = flags.cwd ?? process.cwd();
3649
+ arbiterKey = looksLikeUrl
3650
+ ? encodeRepoKey(spec)
3651
+ : resolveArbiterKeyFromCwd(resolveFrom, spec, process.env);
3652
+ if (arbiterKey === undefined) {
3653
+ console.error(
3654
+ `refusing: could not resolve arbiter '${spec}' \u2014 not a URL and ` +
3655
+ 'no git remote with that name in the cwd. Pass a remote name that ' +
3656
+ 'exists here, an arbiter URL, or --all-arbiters.',
3657
+ );
3658
+ process.exit(1);
3659
+ }
3660
+ } else {
3661
+ arbiterKey = resolveArbiterKeyFromCwd(
3662
+ flags.cwd ?? process.cwd(),
3663
+ config.defaultArbiter,
3664
+ process.env,
3665
+ );
3666
+ if (arbiterKey === undefined) {
3667
+ console.error(
3668
+ 'refusing: `gc` is now ARBITER-SCOPED and could not resolve an ' +
3669
+ `arbiter from the current directory (no '${config.defaultArbiter}' ` +
3670
+ 'git remote, or not inside a repo). It will NOT fall back to a ' +
3671
+ 'global cross-arbiter sweep. Run it from a repo with an arbiter ' +
3672
+ 'remote, pass --arbiter <remote-or-url>, or --all-arbiters to sweep ' +
3673
+ 'every arbiter.',
3674
+ );
3675
+ process.exit(1);
3676
+ }
3677
+ }
3678
+ } else {
3679
+ // GLOBAL sweep: LOUD banner naming EVERY arbiter it will act on before
3680
+ // doing anything (this task's "loud" requirement). Global + --force must be
3681
+ // opted into explicitly (the --yes gate below still applies), never reached
3682
+ // by default.
3683
+ const arbiters = enumerateWorkspaceArbiters(workspacesDir, process.env);
3684
+ console.error(
3685
+ '>> --all-arbiters: operating GLOBALLY across ALL arbiters under ' +
3686
+ `${workspacesDir}/work/* (NOT scoped to the current repo).`,
3687
+ );
3688
+ if (arbiters.length > 0) {
3689
+ console.error('>> arbiters in scope:');
3690
+ for (const a of arbiters) {
3691
+ console.error(`>> - ${a.url ?? a.key ?? '(malformed mirror)'}`);
3692
+ }
3693
+ } else {
3694
+ console.error('>> (no worktrees found under any arbiter)');
3695
+ }
3696
+ }
3697
+
3617
3698
  // `--force` discards un-saved work, so it is gated behind an explicit
3618
3699
  // confirmation (`--yes`) — loud + intentional, NEVER the default (ADR §4).
3619
3700
  if (flags.force && !flags.yes) {
@@ -3633,6 +3714,7 @@ export function buildProgram(): Command {
3633
3714
 
3634
3715
  const result = gc({
3635
3716
  workspacesDir,
3717
+ arbiterKey,
3636
3718
  force: flags.force === true,
3637
3719
  note: (message) => console.error(`>> ${message}`),
3638
3720
  });
@@ -3652,8 +3734,15 @@ export function buildProgram(): Command {
3652
3734
  ` [retained] ${retained.slug} \u2014 ${RETAIN_REASON_TEXT[retained.reason]}`,
3653
3735
  );
3654
3736
  }
3737
+ for (const orphan of result.sweptOrphans) {
3738
+ console.log(` [swept] ${orphan.dir} \u2014 ${orphan.kind}`);
3739
+ }
3740
+ const orphanNote =
3741
+ result.sweptOrphans.length > 0
3742
+ ? `, ${result.sweptOrphans.length} orphan(s) swept`
3743
+ : '';
3655
3744
  console.log(
3656
- `Summary: ${result.reaped.length} reaped, ${result.retained.length} retained.`,
3745
+ `Summary: ${result.reaped.length} reaped, ${result.retained.length} retained${orphanNote}.`,
3657
3746
  );
3658
3747
  });
3659
3748
 
package/src/gc.ts CHANGED
@@ -1,6 +1,7 @@
1
- import {existsSync, readdirSync, statSync} from 'node:fs';
1
+ import {existsSync, lstatSync, readdirSync, rmSync, statSync} from 'node:fs';
2
2
  import {join} from 'node:path';
3
3
  import {git, run} from './git.js';
4
+ import {encodeRepoKey, readOriginUrl} from './repo-mirror.js';
4
5
  import {
5
6
  JOB_RECORD_FILENAME,
6
7
  jobRecordPath,
@@ -251,6 +252,17 @@ export interface RetainedJob extends GcJob {
251
252
  export interface GcOptions {
252
253
  /** The execution working area (config `workspacesDir`, default `~/.dorfl`). */
253
254
  workspacesDir: string;
255
+ /**
256
+ * SCOPE the sweep to a SINGLE arbiter: only worktrees whose hub mirror's
257
+ * `origin` encodes to this hub key ({@link encodeRepoKey}) are considered.
258
+ * This is the DEFAULT operating mode of the `gc` CLI (the arbiter resolved
259
+ * from the cwd) — a `gc` run from repo A must NEVER touch repo B's worktrees.
260
+ *
261
+ * When UNSET the sweep is GLOBAL (every arbiter under `workspacesDir/work/*`).
262
+ * Global is the destructive, cross-repo mode: the CLI only reaches it behind
263
+ * the explicit + loud `--all-arbiters` flag, NEVER by defaulting here.
264
+ */
265
+ arbiterKey?: string;
254
266
  /**
255
267
  * Override the predicate for EVERY job: discard un-saved work. Loud +
256
268
  * explicit (the CLI guards it behind a confirmation) — NEVER the default.
@@ -261,11 +273,29 @@ export interface GcOptions {
261
273
  env?: NodeJS.ProcessEnv;
262
274
  }
263
275
 
276
+ /**
277
+ * An ORPHAN `<workspacesDir>/work/*` entry `gc` swept: a path git never
278
+ * registered as a worktree and that carries NO job record — a dangling symlink,
279
+ * or a bare directory left by a run that crashed BEFORE `git worktree add`
280
+ * registered it (an early `spawn git ENOENT`). It holds no durable work, so it is
281
+ * always safe to remove; sweeping it self-heals a half-set-up claim instead of
282
+ * leaving it to wedge the next `worktree add` ("already exists") until a human
283
+ * `rm`s it. Reported SEPARATELY from reaped jobs (it was never a real job).
284
+ */
285
+ export interface SweptOrphan {
286
+ /** Absolute path to the orphan entry that was removed. */
287
+ dir: string;
288
+ /** Whether the orphan was a dangling symlink or an un-registered directory. */
289
+ kind: 'dangling-symlink' | 'orphan-dir';
290
+ }
291
+
264
292
  export interface GcResult {
265
293
  /** The worktrees reaped this sweep (provably safe, or forced). */
266
294
  reaped: ReapedJob[];
267
295
  /** The worktrees retained, each with a clear reason. */
268
296
  retained: RetainedJob[];
297
+ /** Record-less orphan `work/*` entries swept (dangling symlinks / orphan dirs). */
298
+ sweptOrphans: SweptOrphan[];
269
299
  }
270
300
 
271
301
  /**
@@ -277,15 +307,42 @@ export interface GcResult {
277
307
  * `force: true` overrides the predicate for every job (discard un-saved work) —
278
308
  * loud + explicit (the CLI confirms first), NEVER the default. A job whose work
279
309
  * is NOT on the arbiter is never auto-removed without `force`.
310
+ *
311
+ * SCOPE: when `arbiterKey` is set the sweep considers ONLY worktrees belonging
312
+ * to that arbiter (the CLI's default, resolved from the cwd) — so a `gc` in repo
313
+ * A cannot reap repo B's worktrees. An UNSET `arbiterKey` sweeps GLOBALLY across
314
+ * every arbiter (the CLI reaches that only behind the loud `--all-arbiters`).
280
315
  */
281
316
  export function gc(options: GcOptions): GcResult {
282
317
  const note = options.note ?? (() => {});
283
318
  const env = options.env;
319
+ const arbiterKey = options.arbiterKey;
284
320
  const reaped: ReapedJob[] = [];
285
321
  const retained: RetainedJob[] = [];
286
322
 
323
+ // First, self-heal any record-less ORPHAN `work/*` entry (a dangling symlink
324
+ // or a dir git never registered) so a half-set-up claim does not linger unseen
325
+ // by the job loop below and wedge the next `worktree add`. Orphans carry NO
326
+ // durable work and are NOT any repo's real worktree (no record ⇒ no mirror to
327
+ // attribute to an arbiter), so the arbiter scope does not gate them — they are
328
+ // bounded, worthless residue either way.
329
+ const sweptOrphans = sweepOrphans(options.workspacesDir, note);
330
+
287
331
  for (const job of discoverJobs(options.workspacesDir)) {
288
332
  const mirrorPath = resolveMirrorPath(options.workspacesDir, job);
333
+
334
+ // ARBITER SCOPE (the default `gc` mode): when `arbiterKey` is set, consider
335
+ // ONLY worktrees whose mirror `origin` encodes to that hub key — a `gc` run
336
+ // from repo A must never reap repo B's worktrees. A job whose mirror origin
337
+ // is unreadable (a malformed mirror) is SKIPPED under scope (the safe
338
+ // direction: we cannot prove it belongs to this arbiter, so leave it).
339
+ if (
340
+ arbiterKey !== undefined &&
341
+ jobArbiterKey(mirrorPath, env) !== arbiterKey
342
+ ) {
343
+ continue;
344
+ }
345
+
289
346
  const result = reapJob({
290
347
  dir: job.dir,
291
348
  branch: job.branch,
@@ -310,7 +367,77 @@ export function gc(options: GcOptions): GcResult {
310
367
  note(`Retained ${job.slug}: ${reasonText}.`);
311
368
  }
312
369
 
313
- return {reaped, retained};
370
+ return {reaped, retained, sweptOrphans};
371
+ }
372
+
373
+ /**
374
+ * Sweep record-less ORPHAN entries under `<workspacesDir>/work/*`: a DANGLING
375
+ * SYMLINK (its target gone) or a directory with NO job record at either the
376
+ * sibling or legacy in-tree location. These are the residue of a run that
377
+ * crashed BETWEEN creating the `work/<id>` path and registering it as a git
378
+ * worktree (or writing its record) — e.g. an early `spawn git ENOENT`. They hold
379
+ * no durable work, are invisible to {@link discoverJobs} (which requires a
380
+ * record), and block the next same-id `worktree add`. Removing them is a bounded
381
+ * `rmSync` of ONE path each (never a registered worktree — those carry a record
382
+ * and go through the reap predicate). Best-effort per entry.
383
+ */
384
+ function sweepOrphans(
385
+ workspacesDir: string,
386
+ note: (message: string) => void,
387
+ ): SweptOrphan[] {
388
+ const workDir = join(workspacesDir, 'work');
389
+ if (!existsSync(workDir)) {
390
+ return [];
391
+ }
392
+ const swept: SweptOrphan[] = [];
393
+ for (const entry of readdirSync(workDir)) {
394
+ if (entry.endsWith('.json')) {
395
+ continue; // a sibling record file, not a work-id entry
396
+ }
397
+ const dir = join(workDir, entry);
398
+ let link;
399
+ try {
400
+ link = lstatSync(dir);
401
+ } catch {
402
+ continue; // vanished under us
403
+ }
404
+ const isSymlink = link.isSymbolicLink();
405
+ const targetExists = existsSync(dir); // follows the link; false ⇒ dangling
406
+ const hasRecord =
407
+ existsSync(jobRecordPath(dir)) ||
408
+ (targetExists && existsSync(join(dir, JOB_RECORD_FILENAME)));
409
+ // Orphan iff: a dangling symlink (target gone), OR a record-less entry that
410
+ // is not a live directory git could own (a stray symlink-to-elsewhere, or a
411
+ // dir with no record). A record-bearing entry is a real job → leave it to the
412
+ // reap predicate above.
413
+ const danglingSymlink = isSymlink && !targetExists;
414
+ if (hasRecord) {
415
+ continue;
416
+ }
417
+ if (!danglingSymlink) {
418
+ // A record-less real directory: only sweep it if git does not track it as a
419
+ // worktree here. `discoverJobs` already skips it (no record), and a
420
+ // registered worktree always has our record, so a record-less dir is an
421
+ // orphan. But be conservative: skip a NON-symlink dir that is not empty of
422
+ // a `.git` pointer only when it looks like a crashed pre-register dir.
423
+ if (isSymlink && targetExists) {
424
+ // symlink to a live path but no record → still an orphan link
425
+ } else if (!link.isDirectory()) {
426
+ continue; // not a dir, not a dangling link — leave alone
427
+ } else if (existsSync(join(dir, '.git'))) {
428
+ continue; // has a git pointer but no record: leave for a human (rare)
429
+ }
430
+ }
431
+ try {
432
+ rmSync(dir, {recursive: true, force: true});
433
+ const kind = danglingSymlink ? 'dangling-symlink' : 'orphan-dir';
434
+ swept.push({dir, kind});
435
+ note(`Swept orphan work entry ${entry} (${kind}).`);
436
+ } catch {
437
+ // best-effort: a permission error surfaces on the next add attempt
438
+ }
439
+ }
440
+ return swept;
314
441
  }
315
442
 
316
443
  /**
@@ -386,6 +513,79 @@ function resolveMirrorPath(workspacesDir: string, job: GcJob): string {
386
513
  return join(workspacesDir, 'repos');
387
514
  }
388
515
 
516
+ /**
517
+ * The hub KEY ({@link encodeRepoKey}) of the arbiter a job worktree belongs to,
518
+ * read from its hub mirror's `origin` URL (the arbiter URL the mirror was cloned
519
+ * from). This is the SAME identity `do --isolated` / `ensureMirror` key a repo's
520
+ * arbiter under (`mirrorPath` = `<workspacesDir>/repos/<key>.git`), so a `gc`
521
+ * scoped to the cwd's arbiter key matches exactly the worktrees cut for that
522
+ * arbiter. Returns `undefined` when the mirror `origin` cannot be read (a
523
+ * malformed mirror) — the caller then treats the job as NOT belonging to the
524
+ * scoped arbiter (the safe direction under a scoped sweep).
525
+ */
526
+ function jobArbiterKey(
527
+ mirrorPath: string,
528
+ env: NodeJS.ProcessEnv | undefined,
529
+ ): string | undefined {
530
+ const originUrl = readOriginUrl(mirrorPath, env);
531
+ return originUrl === undefined ? undefined : encodeRepoKey(originUrl);
532
+ }
533
+
534
+ /** One distinct arbiter with worktrees under the work area (for the loud
535
+ * `--all-arbiters` banner). */
536
+ export interface WorkspaceArbiter {
537
+ /** The hub key ({@link encodeRepoKey}) — or `undefined` for a malformed mirror. */
538
+ key?: string;
539
+ /** The arbiter `origin` URL of the hub mirror, when readable. */
540
+ url?: string;
541
+ }
542
+
543
+ /**
544
+ * Enumerate the DISTINCT arbiters that currently own a worktree under
545
+ * `<workspacesDir>/work/*` — i.e. the set a GLOBAL (`--all-arbiters`) sweep would
546
+ * touch. Reads each discovered job's hub mirror `origin` URL and de-duplicates
547
+ * by hub key. Used ONLY to print the loud banner before a cross-arbiter sweep;
548
+ * the sweep itself re-derives per-job scope in {@link gc}.
549
+ */
550
+ export function enumerateWorkspaceArbiters(
551
+ workspacesDir: string,
552
+ env: NodeJS.ProcessEnv | undefined,
553
+ ): WorkspaceArbiter[] {
554
+ const seen = new Map<string, WorkspaceArbiter>();
555
+ for (const job of discoverJobs(workspacesDir)) {
556
+ const mirrorPath = resolveMirrorPath(workspacesDir, job);
557
+ const url = readOriginUrl(mirrorPath, env);
558
+ const key = url === undefined ? undefined : encodeRepoKey(url);
559
+ const dedupe = key ?? `path:${mirrorPath}`;
560
+ if (!seen.has(dedupe)) {
561
+ seen.set(dedupe, {key, url});
562
+ }
563
+ }
564
+ return [...seen.values()];
565
+ }
566
+
567
+ /**
568
+ * The hub KEY ({@link encodeRepoKey}) of the arbiter reachable from a working
569
+ * repo `cwd` via its `<arbiter>` remote (default `origin`), i.e. `encodeRepoKey`
570
+ * of `git -C <cwd> remote get-url <arbiter>`. This is how the `gc` CLI resolves
571
+ * the DEFAULT scope from the current directory — the SAME arbiter-resolution the
572
+ * mirror / `do --isolated` path uses (`ensureMirror`'s `fromRepo` + `arbiter`).
573
+ * Returns `undefined` when `cwd` is not a git repo or has no such remote (the
574
+ * CLI then errors rather than silently going global).
575
+ */
576
+ export function resolveArbiterKeyFromCwd(
577
+ cwd: string,
578
+ arbiter: string,
579
+ env: NodeJS.ProcessEnv | undefined,
580
+ ): string | undefined {
581
+ const res = run('git', ['remote', 'get-url', arbiter], cwd, {env});
582
+ if (res.status !== 0) {
583
+ return undefined;
584
+ }
585
+ const url = res.stdout.trim();
586
+ return url === '' ? undefined : encodeRepoKey(url);
587
+ }
588
+
389
589
  /**
390
590
  * True iff the working tree has NO uncommitted WORK (tracked, staged, or
391
591
  * untracked). The agent's edits and any untracked artefacts count: untracked
package/src/git.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import {spawnSync, spawn} from 'node:child_process';
2
- import {mkdirSync} from 'node:fs';
3
- import {dirname} from 'node:path';
2
+ import {existsSync, mkdirSync, statSync} from 'node:fs';
3
+ import {delimiter, dirname, isAbsolute, join} from 'node:path';
4
4
 
5
5
  /** Result of running a git (or any) command. */
6
6
  export interface RunResult {
@@ -9,6 +9,176 @@ export interface RunResult {
9
9
  stderr: string;
10
10
  }
11
11
 
12
+ /**
13
+ * The standard system dirs core tools (`git`, `ssh`, `sh`) live in. Some callers
14
+ * launch `dorfl` with a CURATED `PATH` (a version-manager / MCP-agent env that
15
+ * lists only `~/.volta/bin`, `~/.cargo/bin`, `~/.local/bin`, …) that OMITS these.
16
+ * `dorfl` spawns bare `git` (and git in turn may shell out to `ssh`/`sh` for
17
+ * hooks and remote transport), so a `PATH` missing `/usr/bin` produces an opaque
18
+ * mid-run `spawn git ENOENT`. We UNION these onto whatever `PATH` we are given so
19
+ * core tools resolve even under a curated caller `PATH`, without discarding the
20
+ * caller's own entries (a project-pinned `git` earlier on `PATH` still wins).
21
+ */
22
+ const SYSTEM_PATH_DIRS = [
23
+ '/usr/local/bin',
24
+ '/usr/bin',
25
+ '/bin',
26
+ '/usr/sbin',
27
+ '/sbin',
28
+ ];
29
+
30
+ /**
31
+ * `PATH` with {@link SYSTEM_PATH_DIRS} APPENDED (caller entries kept FIRST, so a
32
+ * pinned tool earlier on `PATH` still wins; missing system dirs are added, not
33
+ * substituted). Deduplicated, preserving first-seen order.
34
+ */
35
+ function pathWithSystemDirs(path: string | undefined): string {
36
+ const seen = new Set<string>();
37
+ const out: string[] = [];
38
+ for (const dir of [
39
+ ...(path ? path.split(delimiter) : []),
40
+ ...SYSTEM_PATH_DIRS,
41
+ ]) {
42
+ if (dir !== '' && !seen.has(dir)) {
43
+ seen.add(dir);
44
+ out.push(dir);
45
+ }
46
+ }
47
+ return out.join(delimiter);
48
+ }
49
+
50
+ /**
51
+ * Return `env` with `PATH` hardened via {@link pathWithSystemDirs}, so the
52
+ * spawned tool (and any tool IT shells out to) can find core system binaries
53
+ * even when the caller's `PATH` omitted `/usr/bin`. Returns a COPY — never
54
+ * mutates the caller's env object. `PATH` is looked up case-insensitively so a
55
+ * Windows `Path`/`PATH` split is not silently missed.
56
+ */
57
+ function envWithSystemPath(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
58
+ const key =
59
+ Object.keys(env).find((k) => k.toUpperCase() === 'PATH') ?? 'PATH';
60
+ return {...env, [key]: pathWithSystemDirs(env[key])};
61
+ }
62
+
63
+ /**
64
+ * Per-effective-PATH cache of the resolved absolute `git` path. Keyed by the
65
+ * effective PATH (+ any `DORFL_GIT`/`GIT` override) so a DIFFERENT env — e.g. a
66
+ * test's PATH-prepended `git` shim, or a project that pins its own git — is
67
+ * resolved AGAINST ITS OWN PATH, never masked by a stale global memo. Bounded in
68
+ * practice (the process uses a handful of distinct envs) and reset in tests.
69
+ */
70
+ const gitBinaryCache = new Map<string, string>();
71
+
72
+ /** Clear the {@link resolveGitBinary} cache (tests only). */
73
+ export function resetResolvedGitBinaryForTest(): void {
74
+ gitBinaryCache.clear();
75
+ }
76
+
77
+ /**
78
+ * Resolve the `git` executable to an ABSOLUTE path for a given `env`, robustly —
79
+ * so a caller `PATH` that omits `/usr/bin` cannot produce a mid-run `spawn git
80
+ * ENOENT`, WITHOUT masking a git the caller deliberately put earlier on `PATH`
81
+ * (a project pin, or a test shim). Order:
82
+ *
83
+ * 1. an explicit `DORFL_GIT` / `GIT` env override (an absolute path to a git
84
+ * binary), for full operator control;
85
+ * 2. a probe of the env's OWN `PATH` FIRST (so a shim / pinned git earlier on
86
+ * `PATH` wins), then the standard system dirs APPENDED — so `/usr/bin/git`
87
+ * is still found when the caller dropped `/usr/bin`, but never AHEAD of the
88
+ * caller's own entries.
89
+ *
90
+ * Falls back to the bare name `'git'` when nothing resolves (git genuinely
91
+ * absent) so the spawn still runs and produces the diagnostic path. Cached PER
92
+ * effective PATH so distinct envs resolve independently.
93
+ */
94
+ export function resolveGitBinary(env: NodeJS.ProcessEnv = process.env): string {
95
+ const pathValue = pathWithSystemDirs(env.PATH);
96
+ const cacheKey = `${env.DORFL_GIT ?? ''}\u0000${env.GIT ?? ''}\u0000${pathValue}`;
97
+ const cached = gitBinaryCache.get(cacheKey);
98
+ if (cached !== undefined) {
99
+ return cached;
100
+ }
101
+ const resolved = resolveGitBinaryUncached(env, pathValue);
102
+ gitBinaryCache.set(cacheKey, resolved);
103
+ return resolved;
104
+ }
105
+
106
+ function resolveGitBinaryUncached(
107
+ env: NodeJS.ProcessEnv,
108
+ pathValue: string,
109
+ ): string {
110
+ for (const override of [env.DORFL_GIT, env.GIT]) {
111
+ if (override && isAbsolute(override) && isExecutableFile(override)) {
112
+ return override;
113
+ }
114
+ }
115
+ const exe = process.platform === 'win32' ? 'git.exe' : 'git';
116
+ for (const dir of pathValue.split(delimiter)) {
117
+ const candidate = join(dir, exe);
118
+ if (isExecutableFile(candidate)) {
119
+ return candidate;
120
+ }
121
+ }
122
+ return 'git'; // unresolved — let the spawn surface the ENOENT diagnostic
123
+ }
124
+
125
+ /** True iff `path` is a regular file (a symlink target is followed by statSync). */
126
+ function isExecutableFile(path: string): boolean {
127
+ try {
128
+ return existsSync(path) && statSync(path).isFile();
129
+ } catch {
130
+ return false;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Resolve a command + harden its spawn env: bare `'git'` is replaced by the
136
+ * absolute {@link resolveGitBinary} path, and the spawn env's `PATH` is unioned
137
+ * with the standard system dirs so git's own child processes (hooks, `ssh`) also
138
+ * resolve. Any OTHER command is passed through unchanged (only its `PATH` is
139
+ * hardened). This is the single choke-point both {@link run} and
140
+ * {@link runAsync} funnel through, so every git spawn in the codebase inherits
141
+ * the robust resolution for free.
142
+ */
143
+ function resolveSpawn(
144
+ command: string,
145
+ env: NodeJS.ProcessEnv,
146
+ ): {command: string; env: NodeJS.ProcessEnv} {
147
+ const hardenedEnv = envWithSystemPath(env);
148
+ const resolved = command === 'git' ? resolveGitBinary(hardenedEnv) : command;
149
+ return {command: resolved, env: hardenedEnv};
150
+ }
151
+
152
+ /**
153
+ * Build the spawn-failure message. A plain `spawn git ENOENT` is OPAQUE (it does
154
+ * not say WHY git was not found); for the `ENOENT` case we surface the effective
155
+ * `PATH` and point at `DORFL_GIT`, so a curated caller `PATH` that omits the
156
+ * system dirs is diagnosable at a glance instead of mid-run. Other spawn errors
157
+ * pass through with the original message.
158
+ */
159
+ function spawnErrorMessage(
160
+ command: string,
161
+ resolved: string,
162
+ env: NodeJS.ProcessEnv,
163
+ err: Error & {code?: string},
164
+ ): string {
165
+ if (err.code === 'ENOENT') {
166
+ const path =
167
+ env[Object.keys(env).find((k) => k.toUpperCase() === 'PATH') ?? 'PATH'] ??
168
+ '';
169
+ const hint =
170
+ command === 'git'
171
+ ? `Is git installed and on PATH? Set DORFL_GIT to an absolute git path, or add its dir to PATH. `
172
+ : `Is it installed and on PATH? `;
173
+ return (
174
+ `failed to spawn '${command}': not found (tried '${resolved}'). ` +
175
+ hint +
176
+ `Effective PATH=${path}`
177
+ );
178
+ }
179
+ return `failed to spawn '${command}': ${err.message}`;
180
+ }
181
+
12
182
  /**
13
183
  * Run a command synchronously in `cwd`, capturing output. A non-zero exit is NOT
14
184
  * thrown here — callers inspect `status` (claim.sh's exit codes are meaningful).
@@ -19,15 +189,16 @@ export function run(
19
189
  cwd: string,
20
190
  options: {input?: string; env?: NodeJS.ProcessEnv} = {},
21
191
  ): RunResult {
22
- const result = spawnSync(command, args, {
192
+ const {command: exe, env} = resolveSpawn(command, options.env ?? process.env);
193
+ const result = spawnSync(exe, args, {
23
194
  cwd,
24
195
  encoding: 'utf8',
25
196
  input: options.input,
26
- env: options.env ?? process.env,
197
+ env,
27
198
  maxBuffer: 64 * 1024 * 1024,
28
199
  });
29
200
  if (result.error) {
30
- throw new Error(`failed to spawn '${command}': ${result.error.message}`);
201
+ throw new Error(spawnErrorMessage(command, exe, env, result.error));
31
202
  }
32
203
  return {
33
204
  status: result.status ?? -1,
@@ -47,10 +218,11 @@ export function runAsync(
47
218
  cwd: string,
48
219
  options: {input?: string; env?: NodeJS.ProcessEnv} = {},
49
220
  ): Promise<RunResult> {
221
+ const {command: exe, env} = resolveSpawn(command, options.env ?? process.env);
50
222
  return new Promise((resolvePromise, reject) => {
51
- const child = spawn(command, args, {
223
+ const child = spawn(exe, args, {
52
224
  cwd,
53
- env: options.env ?? process.env,
225
+ env,
54
226
  });
55
227
  let stdout = '';
56
228
  let stderr = '';
@@ -61,7 +233,7 @@ export function runAsync(
61
233
  stderr += chunk.toString('utf8');
62
234
  });
63
235
  child.on('error', (err) =>
64
- reject(new Error(`failed to spawn '${command}': ${err.message}`)),
236
+ reject(new Error(spawnErrorMessage(command, exe, env, err))),
65
237
  );
66
238
  child.on('close', (code) => {
67
239
  resolvePromise({status: code ?? -1, stdout, stderr});