dorfl 0.8.1 → 0.10.1
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 +70 -26
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +82 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -2
- package/dist/config.js.map +1 -1
- package/dist/do.d.ts +16 -1
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +14 -9
- package/dist/do.js.map +1 -1
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +25 -3
- package/dist/env-config.js.map +1 -1
- package/dist/gc.d.ts +17 -0
- package/dist/gc.d.ts.map +1 -1
- package/dist/gc.js +75 -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/intake-trigger-template.d.ts +91 -53
- package/dist/intake-trigger-template.d.ts.map +1 -1
- package/dist/intake-trigger-template.js +213 -114
- package/dist/intake-trigger-template.js.map +1 -1
- package/dist/intake.d.ts +53 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +59 -15
- package/dist/intake.js.map +1 -1
- package/dist/placement.d.ts +42 -33
- package/dist/placement.d.ts.map +1 -1
- package/dist/placement.js +20 -17
- package/dist/placement.js.map +1 -1
- package/dist/repo-config.d.ts +1 -1
- package/dist/repo-config.d.ts.map +1 -1
- package/dist/repo-config.js +35 -0
- package/dist/repo-config.js.map +1 -1
- package/dist/tasking.d.ts +43 -0
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +39 -19
- package/dist/tasking.js.map +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 +83 -29
- package/src/config.ts +96 -9
- package/src/do.ts +30 -10
- package/src/env-config.ts +25 -3
- package/src/gc.ts +95 -2
- package/src/git.ts +180 -8
- package/src/intake-trigger-template.ts +263 -143
- package/src/intake.ts +136 -18
- package/src/placement.ts +42 -36
- package/src/repo-config.ts +35 -0
- package/src/tasking.ts +66 -41
- package/src/workspace.ts +74 -20
package/src/cli.ts
CHANGED
|
@@ -400,11 +400,13 @@ function buildRegistrySetAdvanceTick(options: {
|
|
|
400
400
|
// The per-TRANSITION TASKING override: the `do spec:` tasking path threads
|
|
401
401
|
// `taskingIntegration ?? integration`; the build path stays on `integration`.
|
|
402
402
|
taskingIntegration: config.taskingIntegration,
|
|
403
|
-
// The TASK-PLACEMENT configured
|
|
403
|
+
// The TASK-PLACEMENT configured defaults (`do spec:` tasking output:
|
|
404
404
|
// `backlog` staged vs `ready` pool). No operator flag on this
|
|
405
|
-
// registry-driven advance context, so only the configured default
|
|
406
|
-
// threaded (the
|
|
405
|
+
// registry-driven advance context, so only the configured default rungs
|
|
406
|
+
// are threaded (trusted + the untrusted twin the tasker selects from the
|
|
407
|
+
// spec's stamp; the resolver layers the built-in floor).
|
|
407
408
|
tasksLandIn: config.tasksLandIn,
|
|
409
|
+
untrustedTasksLandIn: config.untrustedTasksLandIn,
|
|
408
410
|
prepare: config.prepare,
|
|
409
411
|
verify: config.verify,
|
|
410
412
|
// Single-job build path: gate the REBASED tip (the default) unconditionally.
|
|
@@ -719,13 +721,13 @@ function explicitTasksLandInFromFlag(
|
|
|
719
721
|
function explicitSpecsLandInFromFlag(
|
|
720
722
|
raw: string | undefined,
|
|
721
723
|
flagName = '--specs-land-in',
|
|
722
|
-
): '
|
|
724
|
+
): 'proposed' | 'ready' | undefined {
|
|
723
725
|
if (raw === undefined) {
|
|
724
726
|
return undefined;
|
|
725
727
|
}
|
|
726
|
-
if (raw !== '
|
|
728
|
+
if (raw !== 'proposed' && raw !== 'ready') {
|
|
727
729
|
throw new Error(
|
|
728
|
-
`${flagName} must be '
|
|
730
|
+
`${flagName} must be 'proposed' or 'ready' (got '${raw}').`,
|
|
729
731
|
);
|
|
730
732
|
}
|
|
731
733
|
return raw;
|
|
@@ -800,8 +802,10 @@ interface IntakeFlags {
|
|
|
800
802
|
* flags. UNSET (a local intake) ⇒ emit unstamped ⇒ human/trusted.
|
|
801
803
|
*/
|
|
802
804
|
originTrust?: string;
|
|
803
|
-
/** `--specs-land-in <
|
|
805
|
+
/** `--specs-land-in <proposed|ready>`: the explicit operator spec-placement override (top of the precedence). Resolves into the `specsLandIn` config key. */
|
|
804
806
|
specsLandIn?: string;
|
|
807
|
+
/** `--tasks-land-in <backlog|ready>`: the explicit operator TASK-placement override for a DIRECT-from-issue task emit (top of the precedence). The task twin of `--specs-land-in`. Resolves into the `tasksLandIn` config key. */
|
|
808
|
+
tasksLandIn?: string;
|
|
805
809
|
agentCmd?: string;
|
|
806
810
|
model?: string;
|
|
807
811
|
harness?: string;
|
|
@@ -2592,10 +2596,12 @@ export function buildProgram(): Command {
|
|
|
2592
2596
|
explicitMerge: flagMode === 'merge',
|
|
2593
2597
|
// Per-TRANSITION TASKING override (the `do --remote spec:` tasking path).
|
|
2594
2598
|
taskingIntegration: remoteConfig.taskingIntegration,
|
|
2595
|
-
// TASK-PLACEMENT: the configured
|
|
2596
|
-
//
|
|
2597
|
-
//
|
|
2599
|
+
// TASK-PLACEMENT: the configured defaults (trusted + the untrusted twin
|
|
2600
|
+
// the tasker selects from the spec's stamp) + the EXPLICIT operator
|
|
2601
|
+
// override (`--tasks-land-in`), the top of the placement precedence —
|
|
2602
|
+
// mirrors `explicitMerge` (set only when the flag was typed).
|
|
2598
2603
|
tasksLandIn: remoteConfig.tasksLandIn,
|
|
2604
|
+
untrustedTasksLandIn: remoteConfig.untrustedTasksLandIn,
|
|
2599
2605
|
explicitTasksLandIn: explicitTasksLandInFromFlag(flags.tasksLandIn),
|
|
2600
2606
|
prepare: remoteConfig.prepare,
|
|
2601
2607
|
verify: remoteConfig.verify,
|
|
@@ -2766,12 +2772,13 @@ export function buildProgram(): Command {
|
|
|
2766
2772
|
// `taskingIntegration ?? integration`; the task-build path stays on
|
|
2767
2773
|
// `integration`. Unset ⇒ tasking falls back to `integration` (today's behaviour).
|
|
2768
2774
|
taskingIntegration: config.taskingIntegration,
|
|
2769
|
-
// TASK-PLACEMENT (`do spec:` tasking output): the configured default
|
|
2775
|
+
// TASK-PLACEMENT (`do spec:` tasking output): the configured default rungs
|
|
2776
|
+
// (trusted + the untrusted twin the tasker selects from the spec's stamp) +
|
|
2770
2777
|
// the EXPLICIT operator override `--tasks-land-in` (top of the precedence).
|
|
2771
2778
|
// `explicitTasksLandIn` is set ONLY when the flag was typed (mirrors
|
|
2772
|
-
// `explicitMerge`)
|
|
2773
|
-
// config default.
|
|
2779
|
+
// `explicitMerge`).
|
|
2774
2780
|
tasksLandIn: config.tasksLandIn,
|
|
2781
|
+
untrustedTasksLandIn: config.untrustedTasksLandIn,
|
|
2775
2782
|
explicitTasksLandIn: explicitTasksLandInFromFlag(flags.tasksLandIn),
|
|
2776
2783
|
// In-place divergence guard override (mirrors --ignore-not-ready).
|
|
2777
2784
|
ignoreDivergedMain: flags.ignoreDivergedMain === true,
|
|
@@ -3104,9 +3111,10 @@ export function buildProgram(): Command {
|
|
|
3104
3111
|
explicitMerge: flagMode === 'merge',
|
|
3105
3112
|
// Per-TRANSITION TASKING override (the isolated `do --remote spec:` path).
|
|
3106
3113
|
taskingIntegration: remoteConfig.taskingIntegration,
|
|
3107
|
-
// TASK-PLACEMENT: configured
|
|
3108
|
-
// (set only when typed, mirroring `explicitMerge`).
|
|
3114
|
+
// TASK-PLACEMENT: configured defaults (trusted + untrusted twin) + EXPLICIT
|
|
3115
|
+
// `--tasks-land-in` override (set only when typed, mirroring `explicitMerge`).
|
|
3109
3116
|
tasksLandIn: remoteConfig.tasksLandIn,
|
|
3117
|
+
untrustedTasksLandIn: remoteConfig.untrustedTasksLandIn,
|
|
3110
3118
|
explicitTasksLandIn: explicitTasksLandInFromFlag(flags.tasksLandIn),
|
|
3111
3119
|
prepare: remoteConfig.prepare,
|
|
3112
3120
|
verify: remoteConfig.verify,
|
|
@@ -3247,9 +3255,10 @@ export function buildProgram(): Command {
|
|
|
3247
3255
|
// Per-TRANSITION TASKING override (the `do spec:` tasking path threads
|
|
3248
3256
|
// `taskingIntegration ?? integration`; the build path stays on `integration`).
|
|
3249
3257
|
taskingIntegration: config.taskingIntegration,
|
|
3250
|
-
// TASK-PLACEMENT: configured
|
|
3251
|
-
// (set only when typed, mirroring `explicitMerge`).
|
|
3258
|
+
// TASK-PLACEMENT: configured defaults (trusted + untrusted twin) + EXPLICIT
|
|
3259
|
+
// `--tasks-land-in` override (set only when typed, mirroring `explicitMerge`).
|
|
3252
3260
|
tasksLandIn: config.tasksLandIn,
|
|
3261
|
+
untrustedTasksLandIn: config.untrustedTasksLandIn,
|
|
3253
3262
|
explicitTasksLandIn: explicitTasksLandInFromFlag(flags.tasksLandIn),
|
|
3254
3263
|
prepare: config.prepare,
|
|
3255
3264
|
verify: config.verify,
|
|
@@ -3643,8 +3652,15 @@ export function buildProgram(): Command {
|
|
|
3643
3652
|
` [retained] ${retained.slug} \u2014 ${RETAIN_REASON_TEXT[retained.reason]}`,
|
|
3644
3653
|
);
|
|
3645
3654
|
}
|
|
3655
|
+
for (const orphan of result.sweptOrphans) {
|
|
3656
|
+
console.log(` [swept] ${orphan.dir} \u2014 ${orphan.kind}`);
|
|
3657
|
+
}
|
|
3658
|
+
const orphanNote =
|
|
3659
|
+
result.sweptOrphans.length > 0
|
|
3660
|
+
? `, ${result.sweptOrphans.length} orphan(s) swept`
|
|
3661
|
+
: '';
|
|
3646
3662
|
console.log(
|
|
3647
|
-
`Summary: ${result.reaped.length} reaped, ${result.retained.length} retained.`,
|
|
3663
|
+
`Summary: ${result.reaped.length} reaped, ${result.retained.length} retained${orphanNote}.`,
|
|
3648
3664
|
);
|
|
3649
3665
|
});
|
|
3650
3666
|
|
|
@@ -4277,7 +4293,11 @@ export function buildProgram(): Command {
|
|
|
4277
4293
|
)
|
|
4278
4294
|
.option(
|
|
4279
4295
|
'--specs-land-in <where>',
|
|
4280
|
-
'where an intake-authored spec lands: `
|
|
4296
|
+
'where an intake-authored spec lands: `proposed` (staged, not auto-taskable) or `ready` (the auto-tasking pool). The EXPLICIT operator override at the top of the placement precedence (explicit flag > untrusted-origin forces staging > specsLandIn default > built-in). Resolved flag > env (DORFL_SPECS_LAND_IN) > per-repo > global > built-in.',
|
|
4297
|
+
)
|
|
4298
|
+
.option(
|
|
4299
|
+
'--tasks-land-in <where>',
|
|
4300
|
+
'where an intake-authored TASK (emitted DIRECTLY from an issue) lands: `backlog` (staged, not agent-eligible) or `ready` (the agent POOL). The task twin of --specs-land-in: the EXPLICIT operator override at the top of the placement precedence (explicit flag > configured default > built-in staging; the caller selects the trusted-vs-untrusted default from the origin-trust stamp). Resolved flag > env (DORFL_TASKS_LAND_IN) > per-repo > global > built-in.',
|
|
4281
4301
|
)
|
|
4282
4302
|
.option('--agent-cmd <cmd>', 'command to run the decision agent')
|
|
4283
4303
|
.option(
|
|
@@ -4327,14 +4347,21 @@ export function buildProgram(): Command {
|
|
|
4327
4347
|
// Resolve the PER-OUTCOME integration modes (spec US #9): `intake` decides
|
|
4328
4348
|
// the artifact TYPE at runtime, so a single --merge/--propose can't express
|
|
4329
4349
|
// a type-conditional policy. The granular flags override the aggregate; an
|
|
4330
|
-
// UNSET type falls back to the per-repo/global `
|
|
4331
|
-
// `
|
|
4332
|
-
//
|
|
4333
|
-
//
|
|
4334
|
-
//
|
|
4350
|
+
// UNSET type falls back to the per-repo/global `intakeIntegration ??
|
|
4351
|
+
// integration` (the INTAKE-DOCUMENT knob, twin of the tasking path's
|
|
4352
|
+
// `taskingIntegration ?? integration`; spec
|
|
4353
|
+
// `intake-integration-knob-and-specs-land-in-proposed-rename`). DECOUPLED
|
|
4354
|
+
// from the autonomy gates: `intake` is GATE-FREE, so autoTask/autoBuild are
|
|
4355
|
+
// NOT consulted for the document mode (the explicit invocation is its own
|
|
4356
|
+
// authorization; ADR untrusted-origin-carries-via-stamp-not-forced-staging).
|
|
4357
|
+
// `intake` owns only these KNOBS; WHICH knobs CI sets is CI's POLICY
|
|
4358
|
+
// (`runner-in-ci`), NOT here.
|
|
4335
4359
|
let modes;
|
|
4336
4360
|
try {
|
|
4337
|
-
modes = resolveIntakeIntegrationModes(
|
|
4361
|
+
modes = resolveIntakeIntegrationModes(
|
|
4362
|
+
flags,
|
|
4363
|
+
config.intakeIntegration ?? config.integration,
|
|
4364
|
+
);
|
|
4338
4365
|
} catch (err) {
|
|
4339
4366
|
console.error(
|
|
4340
4367
|
`error: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -4367,7 +4394,7 @@ export function buildProgram(): Command {
|
|
|
4367
4394
|
// The OPERATOR's EXPLICIT spec-placement override (`--specs-land-in`), the
|
|
4368
4395
|
// TOP of the placement precedence — mirrors `explicitTasksLandInFromFlag` on
|
|
4369
4396
|
// the `do spec:` path. Fails loudly on a bad value.
|
|
4370
|
-
let explicitSpecsLandIn: '
|
|
4397
|
+
let explicitSpecsLandIn: 'proposed' | 'ready' | undefined;
|
|
4371
4398
|
try {
|
|
4372
4399
|
explicitSpecsLandIn = explicitSpecsLandInFromFlag(flags.specsLandIn);
|
|
4373
4400
|
} catch (err) {
|
|
@@ -4376,6 +4403,19 @@ export function buildProgram(): Command {
|
|
|
4376
4403
|
);
|
|
4377
4404
|
process.exit(1);
|
|
4378
4405
|
}
|
|
4406
|
+
// The OPERATOR's EXPLICIT task-placement override (`--tasks-land-in`), the
|
|
4407
|
+
// TOP of the placement precedence for a DIRECT-from-issue task emit — the
|
|
4408
|
+
// task twin of `explicitSpecsLandIn` above (reuses the same `do`-path
|
|
4409
|
+
// helper). Fails loudly on a bad value.
|
|
4410
|
+
let explicitTasksLandIn: 'backlog' | 'ready' | undefined;
|
|
4411
|
+
try {
|
|
4412
|
+
explicitTasksLandIn = explicitTasksLandInFromFlag(flags.tasksLandIn);
|
|
4413
|
+
} catch (err) {
|
|
4414
|
+
console.error(
|
|
4415
|
+
`error: ${err instanceof Error ? err.message : String(err)}`,
|
|
4416
|
+
);
|
|
4417
|
+
process.exit(1);
|
|
4418
|
+
}
|
|
4379
4419
|
const result = await performIntake({
|
|
4380
4420
|
issueNumber,
|
|
4381
4421
|
cwd,
|
|
@@ -4391,11 +4431,25 @@ export function buildProgram(): Command {
|
|
|
4391
4431
|
// The origin-trust stamp the CI shell passes IN (unset ⇒ unstamped).
|
|
4392
4432
|
originTrust,
|
|
4393
4433
|
noPR: config.noPR,
|
|
4394
|
-
// SPEC-PLACEMENT: the configured
|
|
4395
|
-
//
|
|
4396
|
-
//
|
|
4434
|
+
// SPEC-PLACEMENT: the TRUSTED-side configured default (`specsLandIn`) +
|
|
4435
|
+
// the UNTRUSTED-side twin (`untrustedSpecsLandIn`, selected in
|
|
4436
|
+
// `dispatchSpec` when the stamp is untrusted) + the EXPLICIT
|
|
4437
|
+
// `--specs-land-in` override (top of the precedence). The placement
|
|
4438
|
+
// resolver no longer has an untrusted-forces-staging rung (ADR
|
|
4439
|
+
// `untrusted-origin-carries-via-stamp-not-forced-staging`); the caller
|
|
4440
|
+
// selects the trusted-vs-untrusted default from the stamp.
|
|
4397
4441
|
specsLandIn: config.specsLandIn,
|
|
4442
|
+
untrustedSpecsLandIn: config.untrustedSpecsLandIn,
|
|
4398
4443
|
explicitSpecsLandIn,
|
|
4444
|
+
// TASK-PLACEMENT (parity with the spec-placement block above; ADR
|
|
4445
|
+
// `untrusted-origin-carries-via-stamp-not-forced-staging`): the
|
|
4446
|
+
// TRUSTED-side default (`tasksLandIn`) + the UNTRUSTED-side twin
|
|
4447
|
+
// (`untrustedTasksLandIn`, selected in `dispatchTask` when the stamp is
|
|
4448
|
+
// untrusted) + the EXPLICIT `--tasks-land-in` override. Replaces the old
|
|
4449
|
+
// hardcoded `tasks-ready` path; the resolver picks backlog vs ready.
|
|
4450
|
+
tasksLandIn: config.tasksLandIn,
|
|
4451
|
+
untrustedTasksLandIn: config.untrustedTasksLandIn,
|
|
4452
|
+
explicitTasksLandIn,
|
|
4399
4453
|
harness,
|
|
4400
4454
|
agentCmd: config.agentCmd,
|
|
4401
4455
|
model: config.model,
|
package/src/config.ts
CHANGED
|
@@ -44,21 +44,23 @@ export type TasksLandIn = 'backlog' | 'ready';
|
|
|
44
44
|
* `pre-prd-staging-pool-split-and-untrusted-prd-placement`, governing ADR
|
|
45
45
|
* `placement-is-runner-deterministic-humanonly-is-agent-judgement`). Which
|
|
46
46
|
* folder the runner lands `intake`-authored spec files in BY DEFAULT —
|
|
47
|
-
* `'
|
|
47
|
+
* `'proposed'` (staging — durable + readable but NOT in the auto-tasking
|
|
48
48
|
* POOL; a runner/human promotion is needed to make the spec auto-taskable; the
|
|
49
49
|
* on-disk folder for this value is `work/specs/proposed/`) or `'ready'` (the
|
|
50
50
|
* auto-tasking POOL — the trusted fast-path landing, on-disk
|
|
51
51
|
* `work/specs/ready/`). The same runner-deterministic placement RESOLVER
|
|
52
52
|
* (`src/placement.ts`) layers on top:
|
|
53
|
-
* `explicit operator flag >
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* `
|
|
53
|
+
* `explicit operator flag > configured default > built-in (proposed)`.
|
|
54
|
+
* Author-trust is NO LONGER a rung in the resolver (ADR
|
|
55
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`): the CALLER reads the
|
|
56
|
+
* `originTrust:` stamp and selects the untrusted twin `untrustedSpecsLandIn`
|
|
57
|
+
* (default `'proposed'`; opt-in `'ready'`) as the configured default for an
|
|
58
|
+
* untrusted intake spec. The SPEC twin of
|
|
57
59
|
* {@link TasksLandIn}; the SAME shape, the SAME precedence chain. The value
|
|
58
60
|
* spellings mirror the live spec folders (`specs/proposed/` staging,
|
|
59
61
|
* `specs/ready/` pool), exactly as {@link TasksLandIn} mirrors the task folders.
|
|
60
62
|
*/
|
|
61
|
-
export type SpecsLandIn = '
|
|
63
|
+
export type SpecsLandIn = 'proposed' | 'ready';
|
|
62
64
|
|
|
63
65
|
/**
|
|
64
66
|
* The observation-triage gate (ADR `ci-config-policy-and-gate-family` §2): a
|
|
@@ -357,6 +359,37 @@ export interface Config {
|
|
|
357
359
|
* per-LIFECYCLE-TRANSITION knob, inside the trust boundary, operator/config-only.
|
|
358
360
|
*/
|
|
359
361
|
taskingIntegration?: IntegrationMode;
|
|
362
|
+
/**
|
|
363
|
+
* **Per-TRANSITION override for the INTAKE DOCUMENT emit only** — the twin of
|
|
364
|
+
* {@link taskingIntegration} for the intake front door. When set, an
|
|
365
|
+
* `intake`-emitted DOCUMENT (a task file `work/tasks/*` OR a spec file
|
|
366
|
+
* `work/specs/*`) integrates with THIS mode instead of the flat
|
|
367
|
+
* {@link integration}; the task-BUILD transition is unaffected (it always reads
|
|
368
|
+
* {@link integration}), and neither is the tasking transition (it reads
|
|
369
|
+
* {@link taskingIntegration}). UNSET (the default) ⇒ intake falls back to
|
|
370
|
+
* {@link integration} — byte-for-byte today's behaviour for any repo that does
|
|
371
|
+
* not set it, so a single `integration: 'merge'` merges documents across BOTH
|
|
372
|
+
* the tasking and intake transitions with no extra key.
|
|
373
|
+
*
|
|
374
|
+
* A SINGLE value applies to both the task AND the spec document (spec
|
|
375
|
+
* `intake-integration-knob-and-specs-land-in-proposed-rename` US #1 chose a
|
|
376
|
+
* single knob, NOT a per-type `{task, spec}` split). Decoupled from the
|
|
377
|
+
* AUTONOMY GATES (`autoBuild`/`autoTask`): the intake document PR-mode is now an
|
|
378
|
+
* operator/config choice, NOT a function of "may an agent act autonomously" —
|
|
379
|
+
* so a repo can have `autoBuild: true`/`autoTask: true` (autonomy) AND intake
|
|
380
|
+
* documents merging to `main` at the same time (ADR
|
|
381
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`). Untrusted safety is
|
|
382
|
+
* unchanged: it rests entirely on placement (`untrusted*LandIn`) + the
|
|
383
|
+
* build-time `originTrust: untrusted` stamp (the code PR), never a forced
|
|
384
|
+
* document PR. Resolved per-repo like {@link taskingIntegration}: flag
|
|
385
|
+
* (`--merge`/`--propose`) > env (`DORFL_INTAKE_INTEGRATION`) > per-repo >
|
|
386
|
+
* global > (fall back to) `integration` > default `propose`. The intake CLI's
|
|
387
|
+
* explicit `--merge-task`/`--merge-spec`/`--merge`/`--propose` flags still win
|
|
388
|
+
* (operator-present, top of precedence). DISTINCT from `taskingIntegration` (a
|
|
389
|
+
* DIFFERENT lifecycle transition) — each is its own `merge|propose` mode that
|
|
390
|
+
* falls back to `integration`, none tied to an autonomy gate.
|
|
391
|
+
*/
|
|
392
|
+
intakeIntegration?: IntegrationMode;
|
|
360
393
|
/**
|
|
361
394
|
* **Per-repo DEFAULT landing for the TASKER's emitted tasks** (spec
|
|
362
395
|
* `staging-pool-position-gate-and-trust-model` US #5, task
|
|
@@ -377,7 +410,7 @@ export interface Config {
|
|
|
377
410
|
* `staging-pool-position-gate-and-trust-model` US #2/#5/#6/#12). The SPEC twin
|
|
378
411
|
* of {@link tasksLandIn}: resolved per-repo EXACTLY like it (flag
|
|
379
412
|
* `--specs-land-in` > env `DORFL_SPECS_LAND_IN` > per-repo > global >
|
|
380
|
-
* built-in `'
|
|
413
|
+
* built-in `'proposed'`). `intake`'s spec dispatch reads it and passes it as
|
|
381
414
|
* the CONFIGURED-DEFAULT rung into the shared placement resolver
|
|
382
415
|
* (`src/placement.ts`); the resolver overlays an EXPLICIT operator flag
|
|
383
416
|
* (top) and the UNTRUSTED-ORIGIN force (staging) on top, in that order.
|
|
@@ -391,6 +424,48 @@ export interface Config {
|
|
|
391
424
|
* `prdsLandIn` config key + `--prds-land-in` flag are GONE (clean break).
|
|
392
425
|
*/
|
|
393
426
|
specsLandIn: SpecsLandIn;
|
|
427
|
+
/**
|
|
428
|
+
* **Per-repo DEFAULT landing for an UNTRUSTED-origin TASK** (spec
|
|
429
|
+
* `untrusted-origin-carries-via-stamp-intake-placement-symmetry-and-ci-gate-resolution`
|
|
430
|
+
* US #5/#6, governing ADR
|
|
431
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`). The exact TWIN of
|
|
432
|
+
* {@link tasksLandIn} for items whose `originTrust` stamp is `untrusted`:
|
|
433
|
+
* SAME value shape ({@link TasksLandIn}), SAME resolution chain (flag
|
|
434
|
+
* `--untrusted-tasks-land-in` > env `DORFL_UNTRUSTED_TASKS_LAND_IN` >
|
|
435
|
+
* per-repo > global > built-in `'backlog'`). The ADR removes the
|
|
436
|
+
* untrusted-forces-staging RUNG from the placement resolver; instead the
|
|
437
|
+
* CALLER reads the stamp and selects THIS key (vs the trusted
|
|
438
|
+
* {@link tasksLandIn}) as the configured-default rung. Governs EVERY
|
|
439
|
+
* untrusted-stamped task — intake-authored directly from an issue OR emitted
|
|
440
|
+
* by the tasker from an untrusted-origin spec (decision X: one policy, both
|
|
441
|
+
* call sites; no third knob). DEFAULTS to STAGING (`'backlog'`), the
|
|
442
|
+
* conservative human-admission landing: safety for an untrusted item that
|
|
443
|
+
* opts into the pool (`'ready'`) is then the carried build STAMP (a code PR),
|
|
444
|
+
* not the folder. CONSUMED at both task call sites: the tasker
|
|
445
|
+
* (`performTask`) selects it for an untrusted-origin spec's emitted tasks, and
|
|
446
|
+
* intake's direct task emit (`dispatchTask`, a sibling task) selects it for a
|
|
447
|
+
* task born straight from an issue.
|
|
448
|
+
*/
|
|
449
|
+
untrustedTasksLandIn: TasksLandIn;
|
|
450
|
+
/**
|
|
451
|
+
* **Per-repo DEFAULT landing for an UNTRUSTED-origin intake SPEC** (spec
|
|
452
|
+
* `untrusted-origin-carries-via-stamp-intake-placement-symmetry-and-ci-gate-resolution`
|
|
453
|
+
* US #7, governing ADR
|
|
454
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`). The exact TWIN of
|
|
455
|
+
* {@link specsLandIn} for untrusted-stamped intake specs: SAME value shape
|
|
456
|
+
* ({@link SpecsLandIn} — `proposed` staging vs `ready` pool), SAME
|
|
457
|
+
* resolution chain (flag `--untrusted-specs-land-in` > env
|
|
458
|
+
* `DORFL_UNTRUSTED_SPECS_LAND_IN` > per-repo > global > built-in
|
|
459
|
+
* `'proposed'`). The caller reads the `originTrust` stamp and selects THIS
|
|
460
|
+
* key (vs the trusted {@link specsLandIn}) as the placement resolver's
|
|
461
|
+
* configured-default rung. DEFAULTS to STAGING (`'proposed'`) — the
|
|
462
|
+
* conservative human-admission landing, preserving today's effective
|
|
463
|
+
* behaviour for a repo that configures nothing; a repo opts an untrusted spec
|
|
464
|
+
* into the pool (`'ready'`) explicitly, safety then via the carried stamp.
|
|
465
|
+
* CONSUMED at the intake SPEC call site (`dispatchSpec`), which selects it
|
|
466
|
+
* over {@link specsLandIn} when the intake stamp is `originTrust: untrusted`.
|
|
467
|
+
*/
|
|
468
|
+
untrustedSpecsLandIn: SpecsLandIn;
|
|
394
469
|
/**
|
|
395
470
|
* **The PR-INTENT axis** (ADR §6): on the `propose` path, do NOT open a review
|
|
396
471
|
* request even on a GitHub arbiter with auth — push the branch (the
|
|
@@ -825,14 +900,26 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
825
900
|
// The runner-deterministic resolver overlays explicit-flag + untrusted-origin
|
|
826
901
|
// force on top of this default (`src/placement.ts`).
|
|
827
902
|
tasksLandIn: 'backlog',
|
|
828
|
-
// `intake`-authored specs land STAGED (`
|
|
903
|
+
// `intake`-authored specs land STAGED (`proposed/`) by default — the
|
|
829
904
|
// conservative landing that mirrors `tasksLandIn`'s built-in floor: a spec is
|
|
830
905
|
// durable + readable but NOT in the auto-tasking POOL until a human/runner
|
|
831
906
|
// promotes it. A repo opts into the trusted fast-path with `specsLandIn: 'ready'`
|
|
832
907
|
// (or `--specs-land-in ready` / `DORFL_SPECS_LAND_IN=ready`). The same
|
|
833
908
|
// runner-deterministic resolver overlays explicit-flag + untrusted-origin
|
|
834
909
|
// force on top of this default (`src/placement.ts`).
|
|
835
|
-
specsLandIn: '
|
|
910
|
+
specsLandIn: 'proposed',
|
|
911
|
+
// The untrusted-side placement TWINS default to STAGING (`backlog` /
|
|
912
|
+
// `proposed`) — the conservative human-admission landing (ADR
|
|
913
|
+
// `untrusted-origin-carries-via-stamp-not-forced-staging`). A repo that trusts
|
|
914
|
+
// its stamp-based pipeline opts an untrusted item into the pool (`ready`)
|
|
915
|
+
// explicitly; safety is then the carried build STAMP (a code PR), not the
|
|
916
|
+
// folder. Unset ⇒ both resolve to staging, so a repo configuring nothing keeps
|
|
917
|
+
// today's effective behaviour. NO call site consumes these yet (resolver +
|
|
918
|
+
// intake/tasker wiring are later tasks); resolved flag > env
|
|
919
|
+
// (`DORFL_UNTRUSTED_TASKS_LAND_IN` / `DORFL_UNTRUSTED_SPECS_LAND_IN`) > per-repo
|
|
920
|
+
// > global > built-in, exactly like their trusted twins.
|
|
921
|
+
untrustedTasksLandIn: 'backlog',
|
|
922
|
+
untrustedSpecsLandIn: 'proposed',
|
|
836
923
|
agentCmd: '',
|
|
837
924
|
// Gate 2 (PR/code review) defaults OFF — it puts a model on the merge path, so
|
|
838
925
|
// it is opt-in (ADR §8). On an `approve` a resolved `merge` lands automatically
|
package/src/do.ts
CHANGED
|
@@ -416,11 +416,21 @@ export interface DoOptions {
|
|
|
416
416
|
* lifecycle concern).
|
|
417
417
|
*/
|
|
418
418
|
tasksLandIn?: 'backlog' | 'ready';
|
|
419
|
+
/**
|
|
420
|
+
* **The per-repo UNTRUSTED-side TASK-PLACEMENT default** (spec
|
|
421
|
+
* `untrusted-origin-carries-via-stamp-intake-placement-symmetry-and-ci-gate-resolution`,
|
|
422
|
+
* ADR `untrusted-origin-carries-via-stamp-not-forced-staging`). Threaded into
|
|
423
|
+
* {@link performTask}, which selects it (over {@link tasksLandIn}) as the
|
|
424
|
+
* configured-default rung when the tasked spec carries an
|
|
425
|
+
* `originTrust: untrusted` stamp. See {@link performTask}'s
|
|
426
|
+
* `untrustedTasksLandIn`.
|
|
427
|
+
*/
|
|
428
|
+
untrustedTasksLandIn?: 'backlog' | 'ready';
|
|
419
429
|
/**
|
|
420
430
|
* **The OPERATOR's EXPLICIT task-placement override** (the TOP precedence
|
|
421
431
|
* rung in the placement resolver). Set ONLY when the operator typed
|
|
422
432
|
* `--tasks-land-in <where>` on this invocation; never when the value came
|
|
423
|
-
* from config. Wins over
|
|
433
|
+
* from config. Wins over the configured default (the operator is present;
|
|
424
434
|
* CLI always wins, no special force-key) — the positional analogue of
|
|
425
435
|
* `explicitMerge` overriding the untrusted-origin build-propose rule.
|
|
426
436
|
*/
|
|
@@ -704,6 +714,11 @@ export interface DoRemoteOptions extends DoAgentLaunchOptions {
|
|
|
704
714
|
* configured-default rung. See {@link DoOptions.tasksLandIn}.
|
|
705
715
|
*/
|
|
706
716
|
tasksLandIn?: 'backlog' | 'ready';
|
|
717
|
+
/**
|
|
718
|
+
* **The per-repo UNTRUSTED-side TASK-PLACEMENT default** on the `do --remote
|
|
719
|
+
* spec:` path. See {@link DoOptions.untrustedTasksLandIn}.
|
|
720
|
+
*/
|
|
721
|
+
untrustedTasksLandIn?: 'backlog' | 'ready';
|
|
707
722
|
/**
|
|
708
723
|
* **The OPERATOR's EXPLICIT task-placement override** on the `do --remote
|
|
709
724
|
* spec:` path. See {@link DoOptions.explicitTasksLandIn}.
|
|
@@ -942,13 +957,16 @@ export async function performDo(options: DoOptions): Promise<DoResult> {
|
|
|
942
957
|
// same per-repo cap the build path uses (task
|
|
943
958
|
// `thread-merge-retries-cross-task-and-ratify-default`).
|
|
944
959
|
mergeRetries: options.mergeRetries,
|
|
945
|
-
// The per-repo TASK-PLACEMENT
|
|
946
|
-
// override (task `runner-deterministic-slice-
|
|
947
|
-
// precedence
|
|
948
|
-
//
|
|
949
|
-
// `originTrust
|
|
950
|
-
//
|
|
960
|
+
// The per-repo TASK-PLACEMENT defaults (trusted + untrusted twin) + the
|
|
961
|
+
// operator's explicit override (task `runner-deterministic-slice-
|
|
962
|
+
// placement-policy-and-precedence`; ADR `untrusted-origin-carries-via-
|
|
963
|
+
// stamp-not-forced-staging`). The tasker reads the spec's stamped
|
|
964
|
+
// `originTrust:` to SELECT the trusted-vs-untrusted configured default,
|
|
965
|
+
// then feeds it (with the explicit override on top) into the
|
|
966
|
+
// runner-deterministic placement resolver (which no longer has a trust
|
|
967
|
+
// rung).
|
|
951
968
|
tasksLandIn: options.tasksLandIn,
|
|
969
|
+
untrustedTasksLandIn: options.untrustedTasksLandIn,
|
|
952
970
|
explicitTasksLandIn: options.explicitTasksLandIn,
|
|
953
971
|
noPR: options.noPR,
|
|
954
972
|
providerInstance: options.providerInstance,
|
|
@@ -2372,10 +2390,12 @@ export async function performDoRemote(
|
|
|
2372
2390
|
// threaded on the no-checkout `do --remote spec:` tasking path too,
|
|
2373
2391
|
// mirroring the in-place site above.
|
|
2374
2392
|
mergeRetries: options.mergeRetries,
|
|
2375
|
-
// The per-repo TASK-PLACEMENT
|
|
2376
|
-
// override
|
|
2377
|
-
//
|
|
2393
|
+
// The per-repo TASK-PLACEMENT defaults (trusted + untrusted twin) + the
|
|
2394
|
+
// operator's explicit override. Same stamp-selected threading as the
|
|
2395
|
+
// in-place `do spec:` path (ADR `untrusted-origin-carries-via-stamp-
|
|
2396
|
+
// not-forced-staging`).
|
|
2378
2397
|
tasksLandIn: options.tasksLandIn,
|
|
2398
|
+
untrustedTasksLandIn: options.untrustedTasksLandIn,
|
|
2379
2399
|
explicitTasksLandIn: options.explicitTasksLandIn,
|
|
2380
2400
|
noPR: options.noPR,
|
|
2381
2401
|
providerInstance: options.providerInstance,
|
package/src/env-config.ts
CHANGED
|
@@ -98,6 +98,14 @@ const KEY_COERCIONS: {[K in keyof Config]?: Coercion} = {
|
|
|
98
98
|
// works and a typo FAILS LOUDLY. Unset ⇒ the tasking transition falls back to
|
|
99
99
|
// `integration` (the flat value). It NEVER touches the build transition or intake.
|
|
100
100
|
taskingIntegration: {enum: ['propose', 'merge']},
|
|
101
|
+
// `intakeIntegration` (the per-TRANSITION INTAKE-DOCUMENT override — the twin of
|
|
102
|
+
// `taskingIntegration` for the intake front door) coerces as the SAME
|
|
103
|
+
// `propose`/`merge` enum as `integration`, so `DORFL_INTAKE_INTEGRATION`
|
|
104
|
+
// works and a typo FAILS LOUDLY. Unset ⇒ the intake document emit falls back to
|
|
105
|
+
// `integration` (the flat value). Decoupled from the autonomy gates
|
|
106
|
+
// (`autoBuild`/`autoTask`) — the intake document PR-mode is operator/config, not
|
|
107
|
+
// a function of autonomy (ADR untrusted-origin-carries-via-stamp-not-forced-staging).
|
|
108
|
+
intakeIntegration: {enum: ['propose', 'merge']},
|
|
101
109
|
// `tasksLandIn` (the per-repo TASK-PLACEMENT default — spec
|
|
102
110
|
// `staging-pool-position-gate-and-trust-model` US #5) coerces as the
|
|
103
111
|
// `backlog`/`ready` enum, so `DORFL_TASKS_LAND_IN=ready` works and a
|
|
@@ -108,13 +116,27 @@ const KEY_COERCIONS: {[K in keyof Config]?: Coercion} = {
|
|
|
108
116
|
tasksLandIn: {enum: ['backlog', 'ready']},
|
|
109
117
|
// `specsLandIn` (the per-repo SPEC-PLACEMENT default — spec
|
|
110
118
|
// `staging-pool-position-gate-and-trust-model` US #2/#5) coerces as the
|
|
111
|
-
// `
|
|
119
|
+
// `proposed`/`ready` enum, so `DORFL_SPECS_LAND_IN=ready` works and a typo
|
|
112
120
|
// FAILS LOUDLY. Same precedence chain as `tasksLandIn` (flag > env > per-repo
|
|
113
|
-
// > global > built-in `
|
|
121
|
+
// > global > built-in `proposed`); fed into the shared placement resolver
|
|
114
122
|
// (`src/placement.ts`) as the configured-default rung for the spec lifecycle.
|
|
115
123
|
// The legacy `prdsLandIn` key / `DORFL_PRDS_LAND_IN` env are GONE after the
|
|
116
124
|
// ''prd'' → `spec` hard cutover (clean break).
|
|
117
|
-
specsLandIn: {enum: ['
|
|
125
|
+
specsLandIn: {enum: ['proposed', 'ready']},
|
|
126
|
+
// `untrustedTasksLandIn` (the UNTRUSTED-side TWIN of `tasksLandIn` — spec
|
|
127
|
+
// `untrusted-origin-carries-via-stamp-intake-placement-symmetry-and-ci-gate-resolution`
|
|
128
|
+
// US #5/#6, ADR `untrusted-origin-carries-via-stamp-not-forced-staging`)
|
|
129
|
+
// coerces as the SAME `backlog`/`ready` enum as `tasksLandIn`, so
|
|
130
|
+
// `DORFL_UNTRUSTED_TASKS_LAND_IN=ready` works and a typo FAILS LOUDLY. Same
|
|
131
|
+
// precedence chain (flag > env > per-repo > global > built-in `backlog`); the
|
|
132
|
+
// caller selects it (over the trusted `tasksLandIn`) by reading the
|
|
133
|
+
// `originTrust` stamp before feeding the placement resolver.
|
|
134
|
+
untrustedTasksLandIn: {enum: ['backlog', 'ready']},
|
|
135
|
+
// `untrustedSpecsLandIn` (the UNTRUSTED-side TWIN of `specsLandIn` — same spec
|
|
136
|
+
// US #7) coerces as the SAME `proposed`/`ready` enum as `specsLandIn`, so
|
|
137
|
+
// `DORFL_UNTRUSTED_SPECS_LAND_IN=ready` works and a typo FAILS LOUDLY. Same
|
|
138
|
+
// precedence chain (flag > env > per-repo > global > built-in `proposed`).
|
|
139
|
+
untrustedSpecsLandIn: {enum: ['proposed', 'ready']},
|
|
118
140
|
// `noPR` (the PR-INTENT axis) is a BOOLEAN coercion (like `review`), so
|
|
119
141
|
// `DORFL_NO_PR=true|false` works and a typo FAILS LOUDLY. The removed
|
|
120
142
|
// `provider` override has NO env var (a stale `DORFL_PROVIDER` is ignored
|
package/src/gc.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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
4
|
import {
|
|
@@ -261,11 +261,29 @@ export interface GcOptions {
|
|
|
261
261
|
env?: NodeJS.ProcessEnv;
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
/**
|
|
265
|
+
* An ORPHAN `<workspacesDir>/work/*` entry `gc` swept: a path git never
|
|
266
|
+
* registered as a worktree and that carries NO job record — a dangling symlink,
|
|
267
|
+
* or a bare directory left by a run that crashed BEFORE `git worktree add`
|
|
268
|
+
* registered it (an early `spawn git ENOENT`). It holds no durable work, so it is
|
|
269
|
+
* always safe to remove; sweeping it self-heals a half-set-up claim instead of
|
|
270
|
+
* leaving it to wedge the next `worktree add` ("already exists") until a human
|
|
271
|
+
* `rm`s it. Reported SEPARATELY from reaped jobs (it was never a real job).
|
|
272
|
+
*/
|
|
273
|
+
export interface SweptOrphan {
|
|
274
|
+
/** Absolute path to the orphan entry that was removed. */
|
|
275
|
+
dir: string;
|
|
276
|
+
/** Whether the orphan was a dangling symlink or an un-registered directory. */
|
|
277
|
+
kind: 'dangling-symlink' | 'orphan-dir';
|
|
278
|
+
}
|
|
279
|
+
|
|
264
280
|
export interface GcResult {
|
|
265
281
|
/** The worktrees reaped this sweep (provably safe, or forced). */
|
|
266
282
|
reaped: ReapedJob[];
|
|
267
283
|
/** The worktrees retained, each with a clear reason. */
|
|
268
284
|
retained: RetainedJob[];
|
|
285
|
+
/** Record-less orphan `work/*` entries swept (dangling symlinks / orphan dirs). */
|
|
286
|
+
sweptOrphans: SweptOrphan[];
|
|
269
287
|
}
|
|
270
288
|
|
|
271
289
|
/**
|
|
@@ -284,6 +302,11 @@ export function gc(options: GcOptions): GcResult {
|
|
|
284
302
|
const reaped: ReapedJob[] = [];
|
|
285
303
|
const retained: RetainedJob[] = [];
|
|
286
304
|
|
|
305
|
+
// First, self-heal any record-less ORPHAN `work/*` entry (a dangling symlink
|
|
306
|
+
// or a dir git never registered) so a half-set-up claim does not linger unseen
|
|
307
|
+
// by the job loop below and wedge the next `worktree add`.
|
|
308
|
+
const sweptOrphans = sweepOrphans(options.workspacesDir, note);
|
|
309
|
+
|
|
287
310
|
for (const job of discoverJobs(options.workspacesDir)) {
|
|
288
311
|
const mirrorPath = resolveMirrorPath(options.workspacesDir, job);
|
|
289
312
|
const result = reapJob({
|
|
@@ -310,7 +333,77 @@ export function gc(options: GcOptions): GcResult {
|
|
|
310
333
|
note(`Retained ${job.slug}: ${reasonText}.`);
|
|
311
334
|
}
|
|
312
335
|
|
|
313
|
-
return {reaped, retained};
|
|
336
|
+
return {reaped, retained, sweptOrphans};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Sweep record-less ORPHAN entries under `<workspacesDir>/work/*`: a DANGLING
|
|
341
|
+
* SYMLINK (its target gone) or a directory with NO job record at either the
|
|
342
|
+
* sibling or legacy in-tree location. These are the residue of a run that
|
|
343
|
+
* crashed BETWEEN creating the `work/<id>` path and registering it as a git
|
|
344
|
+
* worktree (or writing its record) — e.g. an early `spawn git ENOENT`. They hold
|
|
345
|
+
* no durable work, are invisible to {@link discoverJobs} (which requires a
|
|
346
|
+
* record), and block the next same-id `worktree add`. Removing them is a bounded
|
|
347
|
+
* `rmSync` of ONE path each (never a registered worktree — those carry a record
|
|
348
|
+
* and go through the reap predicate). Best-effort per entry.
|
|
349
|
+
*/
|
|
350
|
+
function sweepOrphans(
|
|
351
|
+
workspacesDir: string,
|
|
352
|
+
note: (message: string) => void,
|
|
353
|
+
): SweptOrphan[] {
|
|
354
|
+
const workDir = join(workspacesDir, 'work');
|
|
355
|
+
if (!existsSync(workDir)) {
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
const swept: SweptOrphan[] = [];
|
|
359
|
+
for (const entry of readdirSync(workDir)) {
|
|
360
|
+
if (entry.endsWith('.json')) {
|
|
361
|
+
continue; // a sibling record file, not a work-id entry
|
|
362
|
+
}
|
|
363
|
+
const dir = join(workDir, entry);
|
|
364
|
+
let link;
|
|
365
|
+
try {
|
|
366
|
+
link = lstatSync(dir);
|
|
367
|
+
} catch {
|
|
368
|
+
continue; // vanished under us
|
|
369
|
+
}
|
|
370
|
+
const isSymlink = link.isSymbolicLink();
|
|
371
|
+
const targetExists = existsSync(dir); // follows the link; false ⇒ dangling
|
|
372
|
+
const hasRecord =
|
|
373
|
+
existsSync(jobRecordPath(dir)) ||
|
|
374
|
+
(targetExists && existsSync(join(dir, JOB_RECORD_FILENAME)));
|
|
375
|
+
// Orphan iff: a dangling symlink (target gone), OR a record-less entry that
|
|
376
|
+
// is not a live directory git could own (a stray symlink-to-elsewhere, or a
|
|
377
|
+
// dir with no record). A record-bearing entry is a real job → leave it to the
|
|
378
|
+
// reap predicate above.
|
|
379
|
+
const danglingSymlink = isSymlink && !targetExists;
|
|
380
|
+
if (hasRecord) {
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
if (!danglingSymlink) {
|
|
384
|
+
// A record-less real directory: only sweep it if git does not track it as a
|
|
385
|
+
// worktree here. `discoverJobs` already skips it (no record), and a
|
|
386
|
+
// registered worktree always has our record, so a record-less dir is an
|
|
387
|
+
// orphan. But be conservative: skip a NON-symlink dir that is not empty of
|
|
388
|
+
// a `.git` pointer only when it looks like a crashed pre-register dir.
|
|
389
|
+
if (isSymlink && targetExists) {
|
|
390
|
+
// symlink to a live path but no record → still an orphan link
|
|
391
|
+
} else if (!link.isDirectory()) {
|
|
392
|
+
continue; // not a dir, not a dangling link — leave alone
|
|
393
|
+
} else if (existsSync(join(dir, '.git'))) {
|
|
394
|
+
continue; // has a git pointer but no record: leave for a human (rare)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
rmSync(dir, {recursive: true, force: true});
|
|
399
|
+
const kind = danglingSymlink ? 'dangling-symlink' : 'orphan-dir';
|
|
400
|
+
swept.push({dir, kind});
|
|
401
|
+
note(`Swept orphan work entry ${entry} (${kind}).`);
|
|
402
|
+
} catch {
|
|
403
|
+
// best-effort: a permission error surfaces on the next add attempt
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return swept;
|
|
314
407
|
}
|
|
315
408
|
|
|
316
409
|
/**
|