@agentproto/worktree 0.5.2 → 0.5.4
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/bin/worktree-agent.mjs +2 -2
- package/dist/{chunk-PSNNWGAF.mjs → chunk-UC36T7F7.mjs} +10 -7
- package/dist/chunk-UC36T7F7.mjs.map +1 -0
- package/dist/{chunk-S5ARFUTK.mjs → chunk-Y2TW7PBC.mjs} +3 -3
- package/dist/chunk-Y2TW7PBC.mjs.map +1 -0
- package/dist/index.d.ts +84 -8
- package/dist/index.mjs +177 -10
- package/dist/index.mjs.map +1 -1
- package/dist/provider/index.mjs +1 -1
- package/package.json +7 -7
- package/dist/chunk-PSNNWGAF.mjs.map +0 -1
- package/dist/chunk-S5ARFUTK.mjs.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -996,6 +996,15 @@ type LivenessState = {
|
|
|
996
996
|
* `daemon-unreachable` fires only when the sessions snapshot itself is
|
|
997
997
|
* present but unreadable (corrupt/permission-denied), not merely absent —
|
|
998
998
|
* "no daemon has ever run" is a legitimate empty registry, not an error.
|
|
999
|
+
*
|
|
1000
|
+
* `options.sessionsPath` is passed straight to `readSessionsRegistry`
|
|
1001
|
+
* un-defaulted — same as `computeProvenance` — so an omitted path resolves
|
|
1002
|
+
* to the bucket-union-plus-legacy read (AIP-46 §State partitioning,
|
|
1003
|
+
* `provenance.ts`'s header), not just the frozen legacy file. Defaulting to
|
|
1004
|
+
* `SESSIONS_FILE_PATH()` here used to silently blind this check to every
|
|
1005
|
+
* session living in a per-workspace bucket — i.e. every session created
|
|
1006
|
+
* after the AIP-46 partitioning shipped — which is how a running session's
|
|
1007
|
+
* own cwd got classified `reclaim` by `gc` in production.
|
|
999
1008
|
*/
|
|
1000
1009
|
declare function computeLiveness(worktreePath: string, options?: ComputeProvenanceOptions): Promise<LivenessState>;
|
|
1001
1010
|
type GateState = {
|
|
@@ -1074,7 +1083,7 @@ interface ListWorktreeStatusesInput {
|
|
|
1074
1083
|
declare function listWorktreeStatuses(input: ListWorktreeStatusesInput): Promise<WorktreeStatusEntry[]>;
|
|
1075
1084
|
|
|
1076
1085
|
/** The subset of `HarnessClient` the host drives — narrowed for fake-client tests. */
|
|
1077
|
-
type DaemonClient = Pick<HarnessClient, "start" | "prompt" | "output" | "kill" | "waitForAny" | "close">;
|
|
1086
|
+
type DaemonClient = Pick<HarnessClient, "start" | "prompt" | "output" | "kill" | "waitForAny" | "currentEventsCursor" | "close">;
|
|
1078
1087
|
interface DaemonAgentSessionHost extends AgentSessionHost {
|
|
1079
1088
|
close(): Promise<void>;
|
|
1080
1089
|
/** Full `agent_start`, returning the raw descriptor and forwarding every
|
|
@@ -1099,7 +1108,11 @@ interface DaemonAgentSessionHost extends AgentSessionHost {
|
|
|
1099
1108
|
waitForAny(sessionIds: string[], opts?: {
|
|
1100
1109
|
timeoutMs?: number;
|
|
1101
1110
|
event?: TurnEvent;
|
|
1111
|
+
since?: number;
|
|
1102
1112
|
}): Promise<TurnResult>;
|
|
1113
|
+
/** Race-free cursor for a subsequent `waitForAny({ since })` — see
|
|
1114
|
+
* `HarnessClient.currentEventsCursor`. */
|
|
1115
|
+
currentEventsCursor(): Promise<number>;
|
|
1103
1116
|
}
|
|
1104
1117
|
/**
|
|
1105
1118
|
* Connect to the agentproto daemon's MCP endpoint and return a real,
|
|
@@ -1173,8 +1186,18 @@ interface ClassifyForGcOptions {
|
|
|
1173
1186
|
* also depends on.
|
|
1174
1187
|
*/
|
|
1175
1188
|
declare function classifyForGc(tree: TreeState, integration: IntegrationState, liveness: LivenessState, options?: ClassifyForGcOptions): GcClass;
|
|
1176
|
-
/**
|
|
1177
|
-
|
|
1189
|
+
/**
|
|
1190
|
+
* The reclaim reasons this module can attach. `dep-bump` promotes a `hold`
|
|
1191
|
+
* to `reclaim` (see above). `orphan` is different in kind — it never starts
|
|
1192
|
+
* from a `hold` verdict at all, because an orphan was never classified in
|
|
1193
|
+
* the first place (see the "orphan reclaim" section below): a directory
|
|
1194
|
+
* physically present under the repo's worktree pool that `git worktree
|
|
1195
|
+
* list` no longer knows about, so none of `classify`'s three axes can be
|
|
1196
|
+
* computed for it.
|
|
1197
|
+
*/
|
|
1198
|
+
type GcReclaimReason = "dep-bump" | "orphan";
|
|
1199
|
+
/** The hold reasons this module can attach, distinct from `classify`'s snapshot-based holds. */
|
|
1200
|
+
type GcHoldReason = "live-session-cwd";
|
|
1178
1201
|
/**
|
|
1179
1202
|
* Layer 3+4 of the exemption: EVERY commit ahead of `baseRef` must have a
|
|
1180
1203
|
* dep-bump subject (`git log --format=%s`, two-dot range — exactly the
|
|
@@ -1214,13 +1237,30 @@ interface GcPlanEntry {
|
|
|
1214
1237
|
path: string;
|
|
1215
1238
|
branch: string | null;
|
|
1216
1239
|
head: string;
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1240
|
+
/**
|
|
1241
|
+
* Absent only for an orphan entry (`orphan: true`) — git itself cannot
|
|
1242
|
+
* answer the tree/integration/liveness questions for a directory it no
|
|
1243
|
+
* longer recognizes as a worktree, so this module never fabricates a
|
|
1244
|
+
* value for them rather than reporting a fact it can't actually check.
|
|
1245
|
+
*/
|
|
1246
|
+
tree?: TreeState;
|
|
1247
|
+
integration?: IntegrationState;
|
|
1248
|
+
liveness?: LivenessState;
|
|
1220
1249
|
/** Classification per PLAN.md §5.1, after `--include-detached` (if set). */
|
|
1221
1250
|
class: GcClass;
|
|
1222
|
-
/** Set only when `class === "reclaim"` via the dep-bump exemption — see `resolveGcClass`. */
|
|
1251
|
+
/** Set only when `class === "reclaim"` via the dep-bump exemption or the orphan reclaim path — see `resolveGcClass` / `scanOrphanWorktreePaths`. */
|
|
1223
1252
|
reclaimReason?: GcReclaimReason;
|
|
1253
|
+
/** Set only when `class === "hold"` via `protectedPaths` (`isProtectedPath`) — distinguishes this from an ordinary snapshot-based hold. */
|
|
1254
|
+
holdReason?: GcHoldReason;
|
|
1255
|
+
/**
|
|
1256
|
+
* `true` only for a directory the orphan scan found: physically present
|
|
1257
|
+
* under the repo's worktree pool, but absent from `git worktree list`
|
|
1258
|
+
* (even after `git worktree prune`) — the interrupted-`worktree remove`
|
|
1259
|
+
* shape (module docblock's §5 note). Always paired with `class: "reclaim"`
|
|
1260
|
+
* and `reclaimReason: "orphan"`. Never set for a linked worktree, however
|
|
1261
|
+
* it classifies.
|
|
1262
|
+
*/
|
|
1263
|
+
orphan?: boolean;
|
|
1224
1264
|
}
|
|
1225
1265
|
interface PlanGcInput {
|
|
1226
1266
|
repoRoot: string;
|
|
@@ -1233,6 +1273,23 @@ interface PlanGcInput {
|
|
|
1233
1273
|
now?: () => string;
|
|
1234
1274
|
/** See `ComputeWorktreeStatusInput.nowMs` (status.ts) — kept separate from `now`. Defaults to `Date.now()`. */
|
|
1235
1275
|
nowMs?: number;
|
|
1276
|
+
/**
|
|
1277
|
+
* The bucket directory this repo's worktrees are provisioned under — e.g.
|
|
1278
|
+
* `<worktrees.root>/<repoLabel>` (see `resolveWorktreesRoot` / `repoLabel`,
|
|
1279
|
+
* both resolved by the caller; this module does no root resolution of its
|
|
1280
|
+
* own, see the orphan-reclaim section below). When omitted, orphan
|
|
1281
|
+
* scanning is skipped entirely and `planGc`/`applyGc` behave exactly as
|
|
1282
|
+
* before — every existing caller that doesn't pass this sees zero change.
|
|
1283
|
+
*/
|
|
1284
|
+
worktreesRoot?: string;
|
|
1285
|
+
/**
|
|
1286
|
+
* Absolute paths (typically live session cwds) that must never be
|
|
1287
|
+
* classified `reclaim`/`salvage`, exact-or-subdirectory (`isProtectedPath`)
|
|
1288
|
+
* — see the "live-session-cwd protection" section above. Applies to both
|
|
1289
|
+
* linked worktrees and orphan-scan entries. Omitted ⇒ no additional
|
|
1290
|
+
* protection beyond `classify`'s own snapshot-based liveness axis.
|
|
1291
|
+
*/
|
|
1292
|
+
protectedPaths?: string[];
|
|
1236
1293
|
}
|
|
1237
1294
|
/**
|
|
1238
1295
|
* The dry-run plan (PLAN.md §5.2 layer 1): classify every linked worktree,
|
|
@@ -1255,6 +1312,8 @@ interface ApplyGcOptions {
|
|
|
1255
1312
|
nowMs?: number;
|
|
1256
1313
|
/** Override for `~/.agentproto/worktree-salvage` — tests use a temp dir. */
|
|
1257
1314
|
salvageRoot?: string;
|
|
1315
|
+
/** See `PlanGcInput.protectedPaths` — re-checked here at apply time (layer 2), independent of what the plan entry says, so a stale plan can never remove a now-protected path. */
|
|
1316
|
+
protectedPaths?: string[];
|
|
1258
1317
|
}
|
|
1259
1318
|
type GcApplyOutcome = {
|
|
1260
1319
|
path: string;
|
|
@@ -1270,6 +1329,7 @@ type GcApplyOutcome = {
|
|
|
1270
1329
|
path: string;
|
|
1271
1330
|
branch: string | null;
|
|
1272
1331
|
result: "held";
|
|
1332
|
+
holdReason?: GcHoldReason;
|
|
1273
1333
|
}
|
|
1274
1334
|
/** Salvage-class, but `--salvage-dirty` was not passed: left untouched by design. */
|
|
1275
1335
|
| {
|
|
@@ -1284,6 +1344,7 @@ type GcApplyOutcome = {
|
|
|
1284
1344
|
result: "aborted-reclassified";
|
|
1285
1345
|
from: GcClass;
|
|
1286
1346
|
to: GcClass;
|
|
1347
|
+
holdReason?: GcHoldReason;
|
|
1287
1348
|
}
|
|
1288
1349
|
/** The worktree named in the plan no longer exists (already removed by something else since the plan was made). */
|
|
1289
1350
|
| {
|
|
@@ -1306,5 +1367,20 @@ type GcApplyOutcome = {
|
|
|
1306
1367
|
* `git worktree remove`.
|
|
1307
1368
|
*/
|
|
1308
1369
|
declare function applyGc(plan: readonly GcPlanEntry[], options: ApplyGcOptions): Promise<GcApplyOutcome[]>;
|
|
1370
|
+
/**
|
|
1371
|
+
* Attempt to reclaim exactly one worktree by path — the exit-time auto-
|
|
1372
|
+
* reclaim primitive `sessions.ts` calls (via the injected
|
|
1373
|
+
* `WorktreeAutoReclaimer` port) when a policy-provisioned session reaches a
|
|
1374
|
+
* terminal state. Classifies it fresh (never trusts a caller-supplied
|
|
1375
|
+
* verdict) and only ever removes it when that classification is `reclaim`
|
|
1376
|
+
* (merged-or-fresh, clean, idle) — a dirty or held worktree is returned
|
|
1377
|
+
* as-is, exactly like any other `gc` outcome, still visible via
|
|
1378
|
+
* `worktree_status` and still manually reclaimable. Returns `null` when the
|
|
1379
|
+
* path no longer names a linked worktree at all (already removed by
|
|
1380
|
+
* something else — nothing to do, not an error).
|
|
1381
|
+
*/
|
|
1382
|
+
declare function reclaimOneWorktree(worktreePath: string, options: ApplyGcOptions & {
|
|
1383
|
+
includeDetached?: boolean;
|
|
1384
|
+
}): Promise<GcApplyOutcome | null>;
|
|
1309
1385
|
|
|
1310
|
-
export { type AgentprotoConfig, type ApplyGcOptions, CONFIG_FILENAME, type Classification, type ClassifyForGcOptions, type ComputeProvenanceOptions, type ComputeWorktreeStatusInput, ConfigError, DEFAULT_PROXY_PORT, type DaemonAgentSessionHost, type DaemonClient, ENV_VARS, type ExecOptions, type ExecResult, FileVerdictMemoStore, type ForgeClient, type ForgePullRequestRef, ForgeUnavailableError, type GateState, type GcApplyOutcome, type GcClass, type GcPlanEntry, type GcReclaimReason, GhCliForgeClient, type GitWorktreeRef, HookError, type HookRun, type HostnameParts, InMemoryVerdictMemoStore, type IntegrationState, type ListWorktreeStatusesInput, type LivenessState, type NamedScript, type PeerService, type PlanGcInput, type ProvenanceConfidence, type ProvenanceInfo, ProxyTable, type ReconcileIntegrationInput, type ResolveGcClassOptions, type ResolveSupervisorInput, type ResolvedGcClass, type ResolvedWorktreeRuntime, RestForgeClient, type RunningProxy, SALVAGE_ROOT, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, type SalvageManifest, type SalvageResult, type SalvageWorktreeInput, type ScriptConfig, type ServiceRuntime, type ServiceStatus, ServiceSupervisor, type SessionRef, type SupervisorOptions, type TreeState, UnreachableForgeClient, VERDICT_MEMO_PATH, type VerdictMemoRecord, type VerdictMemoStore, WORKTREES_TURBO_CACHE_DIR_ENV, type WorktreeAgentInput, type WorktreeClass, type WorktreeEnvContext, type WorktreeMarker, WorktreeNotRemovableError, type WorktreeStatusEntry, allocatePort, applyGc, classify, classifyForGc, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, connectDaemonAgentSessionHost, createForgeClient, createProxyServer, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isMechanicalDepBumpRange, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, makeDaemonAgentSessionHost, normalizeHook, parseConfig, parseGithubOwnerRepo, peerEnv, peerEnvKey, planGc, readSessionsRegistry, readWorktreeMarker, reconcileIntegration, repoLabel, resolveGcClass, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, salvageWorktree, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, startProxy, stripPort, worktreeAgentInputSchema, worktreeAgentWorkflow, writeWorktreeMarker };
|
|
1386
|
+
export { type AgentprotoConfig, type ApplyGcOptions, CONFIG_FILENAME, type Classification, type ClassifyForGcOptions, type ComputeProvenanceOptions, type ComputeWorktreeStatusInput, ConfigError, DEFAULT_PROXY_PORT, type DaemonAgentSessionHost, type DaemonClient, ENV_VARS, type ExecOptions, type ExecResult, FileVerdictMemoStore, type ForgeClient, type ForgePullRequestRef, ForgeUnavailableError, type GateState, type GcApplyOutcome, type GcClass, type GcHoldReason, type GcPlanEntry, type GcReclaimReason, GhCliForgeClient, type GitWorktreeRef, HookError, type HookRun, type HostnameParts, InMemoryVerdictMemoStore, type IntegrationState, type ListWorktreeStatusesInput, type LivenessState, type NamedScript, type PeerService, type PlanGcInput, type ProvenanceConfidence, type ProvenanceInfo, ProxyTable, type ReconcileIntegrationInput, type ResolveGcClassOptions, type ResolveSupervisorInput, type ResolvedGcClass, type ResolvedWorktreeRuntime, RestForgeClient, type RunningProxy, SALVAGE_ROOT, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, type SalvageManifest, type SalvageResult, type SalvageWorktreeInput, type ScriptConfig, type ServiceRuntime, type ServiceStatus, ServiceSupervisor, type SessionRef, type SupervisorOptions, type TreeState, UnreachableForgeClient, VERDICT_MEMO_PATH, type VerdictMemoRecord, type VerdictMemoStore, WORKTREES_TURBO_CACHE_DIR_ENV, type WorktreeAgentInput, type WorktreeClass, type WorktreeEnvContext, type WorktreeMarker, WorktreeNotRemovableError, type WorktreeStatusEntry, allocatePort, applyGc, classify, classifyForGc, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, connectDaemonAgentSessionHost, createForgeClient, createProxyServer, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isMechanicalDepBumpRange, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, makeDaemonAgentSessionHost, normalizeHook, parseConfig, parseGithubOwnerRepo, peerEnv, peerEnvKey, planGc, readSessionsRegistry, readWorktreeMarker, reclaimOneWorktree, reconcileIntegration, repoLabel, resolveGcClass, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, salvageWorktree, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, startProxy, stripPort, worktreeAgentInputSchema, worktreeAgentWorkflow, writeWorktreeMarker };
|
package/dist/index.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow } from './chunk-
|
|
1
|
+
export { connectDaemonAgentSessionHost, makeDaemonAgentSessionHost, worktreeAgentInputSchema, worktreeAgentWorkflow } from './chunk-UC36T7F7.mjs';
|
|
2
2
|
import './chunk-E3EM24BS.mjs';
|
|
3
|
-
import { computeProvenance, execArgv, classify, computeWorktreeStatus, listGitWorktrees, worktreeProvider } from './chunk-
|
|
4
|
-
export { CONFIG_FILENAME, ConfigError, DEFAULT_PROXY_PORT, ENV_VARS, FileVerdictMemoStore, HookError, InMemoryVerdictMemoStore, ProxyTable, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, ServiceSupervisor, VERDICT_MEMO_PATH, WORKTREES_TURBO_CACHE_DIR_ENV, WorktreeNotRemovableError, allocatePort, classify, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, normalizeHook, parseConfig, peerEnv, peerEnvKey, readSessionsRegistry, readWorktreeMarker, reconcileIntegration, repoLabel, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, stripPort, worktreeProvider, writeWorktreeMarker } from './chunk-
|
|
3
|
+
import { computeProvenance, execArgv, classify, computeWorktreeStatus, listGitWorktrees, worktreeProvider } from './chunk-Y2TW7PBC.mjs';
|
|
4
|
+
export { CONFIG_FILENAME, ConfigError, DEFAULT_PROXY_PORT, ENV_VARS, FileVerdictMemoStore, HookError, InMemoryVerdictMemoStore, ProxyTable, SESSIONS_BUCKETS_ROOT, SESSIONS_FILE_PATH, ServiceSupervisor, VERDICT_MEMO_PATH, WORKTREES_TURBO_CACHE_DIR_ENV, WorktreeNotRemovableError, allocatePort, classify, computeLiveness, computeProvenance, computeTreeState, computeWorktreeStatus, detectDefaultBranch, disposeSupervisor, ephemeralPort, execArgv, execGit, execShell, expandGlob, getScript, getSupervisor, globToRegExp, hookEnv, isPortFree, listGitWorktrees, listScripts, listServices, listWorktreeStatuses, loadConfigFromBase, normalizeHook, parseConfig, peerEnv, peerEnvKey, readSessionsRegistry, readWorktreeMarker, reconcileIntegration, repoLabel, resolveSupervisor, resolveWorktreesTurboCacheDir, runSetup, runTeardown, serviceEnv, serviceEnvToken, serviceHostname, serviceUrl, sessionInWorktree, sharedProxyTable, slugify, stripPort, worktreeProvider, writeWorktreeMarker } from './chunk-Y2TW7PBC.mjs';
|
|
5
5
|
import { cleanupWorktreeTool } from './chunk-CM3XGWTY.mjs';
|
|
6
6
|
export { cleanupWorktreeTool, listServicesTool, provisionWorktreeTool, runGateTool, runScriptTool, serviceStatusSchema, startServiceTool, stopServiceTool } from './chunk-CM3XGWTY.mjs';
|
|
7
|
-
import { mkdir, copyFile, writeFile, open } from 'fs/promises';
|
|
7
|
+
import { mkdir, copyFile, writeFile, open, readdir, realpath, rm, readFile, stat } from 'fs/promises';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
|
-
import { resolve, join, dirname, basename } from 'path';
|
|
9
|
+
import { resolve, join, dirname, basename, sep, relative, isAbsolute } from 'path';
|
|
10
10
|
import http from 'http';
|
|
11
11
|
import { connect } from 'net';
|
|
12
12
|
import { z } from 'zod';
|
|
@@ -149,12 +149,12 @@ function startProxy(table, port = 0) {
|
|
|
149
149
|
sockets.add(socket);
|
|
150
150
|
socket.once("close", () => sockets.delete(socket));
|
|
151
151
|
});
|
|
152
|
-
return new Promise((
|
|
152
|
+
return new Promise((resolve3, reject) => {
|
|
153
153
|
server.once("error", reject);
|
|
154
154
|
server.listen(port, () => {
|
|
155
155
|
const address = server.address();
|
|
156
156
|
const boundPort = address && typeof address === "object" ? address.port : port;
|
|
157
|
-
|
|
157
|
+
resolve3({
|
|
158
158
|
port: boundPort,
|
|
159
159
|
server,
|
|
160
160
|
close: () => new Promise((res) => {
|
|
@@ -428,6 +428,10 @@ function classifyForGc(tree, integration, liveness, options = {}) {
|
|
|
428
428
|
}
|
|
429
429
|
return classify(tree, integration, liveness, options.nowMs).class;
|
|
430
430
|
}
|
|
431
|
+
function isProtectedPath(worktreePath, protectedPaths) {
|
|
432
|
+
if (!protectedPaths || protectedPaths.length === 0) return false;
|
|
433
|
+
return protectedPaths.some((p) => p === worktreePath || p.startsWith(worktreePath + sep));
|
|
434
|
+
}
|
|
431
435
|
var DEP_BUMP_SUBJECT_RE = /^(?:chore|fix)\(deps[\w-]*\):\s/;
|
|
432
436
|
function isDepBumpSubject(subject) {
|
|
433
437
|
return DEP_BUMP_SUBJECT_RE.test(subject);
|
|
@@ -462,7 +466,116 @@ async function linkedWorktreesOf(repoRoot) {
|
|
|
462
466
|
const worktrees = await listGitWorktrees(repoRoot);
|
|
463
467
|
return worktrees.filter((w) => w.path !== repoRoot);
|
|
464
468
|
}
|
|
465
|
-
async function
|
|
469
|
+
async function gitWorktreesDir(repoRoot) {
|
|
470
|
+
const res = await execArgv("git", ["-C", repoRoot, "rev-parse", "--git-common-dir"], repoRoot);
|
|
471
|
+
if (res.exitCode !== 0) {
|
|
472
|
+
throw new Error(
|
|
473
|
+
`git rev-parse --git-common-dir failed in ${repoRoot} (exit ${res.exitCode}): ${res.stderr.trim()}`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
return join(resolve(repoRoot, res.stdout.trim()), "worktrees");
|
|
477
|
+
}
|
|
478
|
+
async function readGitdirPointer(worktreePath) {
|
|
479
|
+
let raw;
|
|
480
|
+
try {
|
|
481
|
+
raw = await readFile(join(worktreePath, ".git"), "utf8");
|
|
482
|
+
} catch {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
const match = raw.match(/^gitdir:\s*(.+?)\s*$/m);
|
|
486
|
+
const target = match?.[1];
|
|
487
|
+
if (!target) return null;
|
|
488
|
+
return resolve(worktreePath, target);
|
|
489
|
+
}
|
|
490
|
+
async function belongsToRepo(repoRoot, worktreePath) {
|
|
491
|
+
const pointer = await readGitdirPointer(worktreePath);
|
|
492
|
+
if (!pointer) return false;
|
|
493
|
+
let worktreesDir;
|
|
494
|
+
try {
|
|
495
|
+
worktreesDir = await gitWorktreesDir(repoRoot);
|
|
496
|
+
} catch {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
const rel = relative(worktreesDir, pointer);
|
|
500
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
501
|
+
}
|
|
502
|
+
async function pathExists(path) {
|
|
503
|
+
try {
|
|
504
|
+
await stat(path);
|
|
505
|
+
return true;
|
|
506
|
+
} catch {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async function realOrResolved(path) {
|
|
511
|
+
try {
|
|
512
|
+
return await realpath(path);
|
|
513
|
+
} catch {
|
|
514
|
+
return resolve(path);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async function scanOrphanWorktreePaths(repoRoot, worktreesRoot) {
|
|
518
|
+
await execArgv("git", ["-C", repoRoot, "worktree", "prune"], repoRoot).catch(() => null);
|
|
519
|
+
const linkedRefs = await linkedWorktreesOf(repoRoot);
|
|
520
|
+
const linked = new Set(await Promise.all(linkedRefs.map((w) => realOrResolved(w.path))));
|
|
521
|
+
const entries = await readdir(worktreesRoot, { withFileTypes: true }).catch(() => []);
|
|
522
|
+
const orphans = [];
|
|
523
|
+
for (const entry of entries) {
|
|
524
|
+
if (!entry.isDirectory()) continue;
|
|
525
|
+
const candidate = resolve(worktreesRoot, entry.name);
|
|
526
|
+
if (linked.has(await realOrResolved(candidate))) continue;
|
|
527
|
+
if (await belongsToRepo(repoRoot, candidate)) orphans.push(candidate);
|
|
528
|
+
}
|
|
529
|
+
return orphans;
|
|
530
|
+
}
|
|
531
|
+
function makeOrphanPlanEntry(path, protectedPaths) {
|
|
532
|
+
if (isProtectedPath(path, protectedPaths)) {
|
|
533
|
+
return { path, branch: null, head: "", class: "hold", holdReason: "live-session-cwd" };
|
|
534
|
+
}
|
|
535
|
+
return { path, branch: null, head: "", class: "reclaim", reclaimReason: "orphan", orphan: true };
|
|
536
|
+
}
|
|
537
|
+
async function verifyOrphan(repoRoot, worktreePath) {
|
|
538
|
+
if (!await pathExists(worktreePath)) return "gone";
|
|
539
|
+
await execArgv("git", ["-C", repoRoot, "worktree", "prune"], repoRoot).catch(() => null);
|
|
540
|
+
const linked = await linkedWorktreesOf(repoRoot);
|
|
541
|
+
const realWorktreePath = await realOrResolved(worktreePath);
|
|
542
|
+
for (const w of linked) {
|
|
543
|
+
if (await realOrResolved(w.path) === realWorktreePath) return "not-orphan";
|
|
544
|
+
}
|
|
545
|
+
return await belongsToRepo(repoRoot, worktreePath) ? "confirmed" : "not-orphan";
|
|
546
|
+
}
|
|
547
|
+
async function reclaimOrphan(entry, options) {
|
|
548
|
+
const state = await verifyOrphan(options.repoRoot, entry.path);
|
|
549
|
+
if (state === "not-orphan") {
|
|
550
|
+
return { path: entry.path, branch: null, result: "aborted-reclassified", from: "reclaim", to: "hold" };
|
|
551
|
+
}
|
|
552
|
+
if (state === "confirmed") {
|
|
553
|
+
try {
|
|
554
|
+
await rm(entry.path, { recursive: true, force: true });
|
|
555
|
+
} catch (err) {
|
|
556
|
+
return {
|
|
557
|
+
path: entry.path,
|
|
558
|
+
branch: null,
|
|
559
|
+
result: "failed",
|
|
560
|
+
message: err instanceof Error ? err.message : String(err)
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return { path: entry.path, branch: null, result: "reclaimed", reclaimReason: "orphan" };
|
|
565
|
+
}
|
|
566
|
+
async function toPlanEntry(repoRoot, defaultBranchRef, worktree, status, includeDetached, nowMs, protectedPaths) {
|
|
567
|
+
if (isProtectedPath(worktree.path, protectedPaths)) {
|
|
568
|
+
return {
|
|
569
|
+
path: worktree.path,
|
|
570
|
+
branch: worktree.branch,
|
|
571
|
+
head: worktree.head,
|
|
572
|
+
tree: status.tree,
|
|
573
|
+
integration: status.integration,
|
|
574
|
+
liveness: status.liveness,
|
|
575
|
+
class: "hold",
|
|
576
|
+
holdReason: "live-session-cwd"
|
|
577
|
+
};
|
|
578
|
+
}
|
|
466
579
|
const resolved = await resolveGcClass(status.tree, status.integration, status.liveness, {
|
|
467
580
|
repoRoot,
|
|
468
581
|
tipSha: worktree.head,
|
|
@@ -497,7 +610,21 @@ async function planGc(input) {
|
|
|
497
610
|
sessionsPath: input.sessionsPath,
|
|
498
611
|
now: input.now
|
|
499
612
|
});
|
|
500
|
-
entries.push(
|
|
613
|
+
entries.push(
|
|
614
|
+
await toPlanEntry(
|
|
615
|
+
input.repoRoot,
|
|
616
|
+
input.defaultBranchRef,
|
|
617
|
+
worktree,
|
|
618
|
+
status,
|
|
619
|
+
includeDetached,
|
|
620
|
+
nowMs,
|
|
621
|
+
input.protectedPaths
|
|
622
|
+
)
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
if (input.worktreesRoot) {
|
|
626
|
+
const orphanPaths = await scanOrphanWorktreePaths(input.repoRoot, input.worktreesRoot);
|
|
627
|
+
for (const path of orphanPaths) entries.push(makeOrphanPlanEntry(path, input.protectedPaths));
|
|
501
628
|
}
|
|
502
629
|
return entries;
|
|
503
630
|
}
|
|
@@ -592,6 +719,22 @@ async function salvageOne(options, worktree) {
|
|
|
592
719
|
return { path: worktree.path, branch: worktree.branch, result: "salvaged", salvageDir };
|
|
593
720
|
}
|
|
594
721
|
async function applyOne(entry, options) {
|
|
722
|
+
if (isProtectedPath(entry.path, options.protectedPaths)) {
|
|
723
|
+
if (entry.class === "hold") {
|
|
724
|
+
return { path: entry.path, branch: entry.branch, result: "held", holdReason: "live-session-cwd" };
|
|
725
|
+
}
|
|
726
|
+
return {
|
|
727
|
+
path: entry.path,
|
|
728
|
+
branch: entry.branch,
|
|
729
|
+
result: "aborted-reclassified",
|
|
730
|
+
from: entry.class,
|
|
731
|
+
to: "hold",
|
|
732
|
+
holdReason: "live-session-cwd"
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
if (entry.orphan) {
|
|
736
|
+
return reclaimOrphan(entry, options);
|
|
737
|
+
}
|
|
595
738
|
if (entry.class === "hold") {
|
|
596
739
|
return { path: entry.path, branch: entry.branch, result: "held" };
|
|
597
740
|
}
|
|
@@ -638,7 +781,31 @@ async function applyGc(plan, options) {
|
|
|
638
781
|
}
|
|
639
782
|
return outcomes;
|
|
640
783
|
}
|
|
784
|
+
async function reclaimOneWorktree(worktreePath, options) {
|
|
785
|
+
const worktree = await findWorktree(options.repoRoot, worktreePath);
|
|
786
|
+
if (!worktree) return null;
|
|
787
|
+
const status = await computeWorktreeStatus({
|
|
788
|
+
repoRoot: options.repoRoot,
|
|
789
|
+
repoName: options.repoName,
|
|
790
|
+
worktree,
|
|
791
|
+
forge: options.forge,
|
|
792
|
+
memo: options.memo,
|
|
793
|
+
defaultBranchRef: options.defaultBranchRef,
|
|
794
|
+
sessionsPath: options.sessionsPath,
|
|
795
|
+
now: options.now
|
|
796
|
+
});
|
|
797
|
+
const entry = await toPlanEntry(
|
|
798
|
+
options.repoRoot,
|
|
799
|
+
options.defaultBranchRef,
|
|
800
|
+
worktree,
|
|
801
|
+
status,
|
|
802
|
+
Boolean(options.includeDetached),
|
|
803
|
+
options.nowMs ?? Date.now(),
|
|
804
|
+
options.protectedPaths
|
|
805
|
+
);
|
|
806
|
+
return applyOne(entry, options);
|
|
807
|
+
}
|
|
641
808
|
|
|
642
|
-
export { ForgeUnavailableError, GhCliForgeClient, RestForgeClient, SALVAGE_ROOT, UnreachableForgeClient, applyGc, classifyForGc, createForgeClient, createProxyServer, isMechanicalDepBumpRange, parseGithubOwnerRepo, planGc, resolveGcClass, salvageWorktree, startProxy };
|
|
809
|
+
export { ForgeUnavailableError, GhCliForgeClient, RestForgeClient, SALVAGE_ROOT, UnreachableForgeClient, applyGc, classifyForGc, createForgeClient, createProxyServer, isMechanicalDepBumpRange, parseGithubOwnerRepo, planGc, reclaimOneWorktree, resolveGcClass, salvageWorktree, startProxy };
|
|
643
810
|
//# sourceMappingURL=index.mjs.map
|
|
644
811
|
//# sourceMappingURL=index.mjs.map
|