@agentproto/worktree 0.5.2 → 0.5.5
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-R6UERXV4.mjs} +15 -7
- package/dist/chunk-R6UERXV4.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 +94 -9
- 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
|
@@ -3,7 +3,7 @@ export { worktreeProvider } from './provider/index.js';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import http from 'node:http';
|
|
5
5
|
import { RuntimeWorkflow, AgentSessionHost } from '@agentproto/workflow-runtime';
|
|
6
|
-
import { StartAgentArgs, SessionDescriptor, TurnEvent, TurnResult, HarnessClient, ConnectHarnessOptions } from '@agentproto/harness';
|
|
6
|
+
import { StartAgentArgs, SessionDescriptor, TurnEvent, TurnResult, SessionUsageSnapshot, HarnessClient, ConnectHarnessOptions } from '@agentproto/harness';
|
|
7
7
|
import '@agentproto/tool';
|
|
8
8
|
import '@agentproto/driver';
|
|
9
9
|
|
|
@@ -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,11 @@ 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"> & {
|
|
1087
|
+
/** `session_usage` — optional so pre-existing fakes/clients without it still
|
|
1088
|
+
* satisfy the type; the host simply exposes no `usage` then. */
|
|
1089
|
+
usage?: HarnessClient["usage"];
|
|
1090
|
+
};
|
|
1078
1091
|
interface DaemonAgentSessionHost extends AgentSessionHost {
|
|
1079
1092
|
close(): Promise<void>;
|
|
1080
1093
|
/** Full `agent_start`, returning the raw descriptor and forwarding every
|
|
@@ -1099,7 +1112,16 @@ interface DaemonAgentSessionHost extends AgentSessionHost {
|
|
|
1099
1112
|
waitForAny(sessionIds: string[], opts?: {
|
|
1100
1113
|
timeoutMs?: number;
|
|
1101
1114
|
event?: TurnEvent;
|
|
1115
|
+
since?: number;
|
|
1102
1116
|
}): Promise<TurnResult>;
|
|
1117
|
+
/** Race-free cursor for a subsequent `waitForAny({ since })` — see
|
|
1118
|
+
* `HarnessClient.currentEventsCursor`. */
|
|
1119
|
+
currentEventsCursor(): Promise<number>;
|
|
1120
|
+
/** Usage accounting for a session on this daemon (`session_usage`) — present
|
|
1121
|
+
* when the underlying client can answer it. `@agentproto/sandbox`'s host
|
|
1122
|
+
* inherits it, which is how a sandboxed session's cost/tokens/model reach
|
|
1123
|
+
* the HOST daemon's descriptor (the proxy's text stream carries none). */
|
|
1124
|
+
usage?(sessionId: string): Promise<SessionUsageSnapshot>;
|
|
1103
1125
|
}
|
|
1104
1126
|
/**
|
|
1105
1127
|
* Connect to the agentproto daemon's MCP endpoint and return a real,
|
|
@@ -1173,8 +1195,18 @@ interface ClassifyForGcOptions {
|
|
|
1173
1195
|
* also depends on.
|
|
1174
1196
|
*/
|
|
1175
1197
|
declare function classifyForGc(tree: TreeState, integration: IntegrationState, liveness: LivenessState, options?: ClassifyForGcOptions): GcClass;
|
|
1176
|
-
/**
|
|
1177
|
-
|
|
1198
|
+
/**
|
|
1199
|
+
* The reclaim reasons this module can attach. `dep-bump` promotes a `hold`
|
|
1200
|
+
* to `reclaim` (see above). `orphan` is different in kind — it never starts
|
|
1201
|
+
* from a `hold` verdict at all, because an orphan was never classified in
|
|
1202
|
+
* the first place (see the "orphan reclaim" section below): a directory
|
|
1203
|
+
* physically present under the repo's worktree pool that `git worktree
|
|
1204
|
+
* list` no longer knows about, so none of `classify`'s three axes can be
|
|
1205
|
+
* computed for it.
|
|
1206
|
+
*/
|
|
1207
|
+
type GcReclaimReason = "dep-bump" | "orphan";
|
|
1208
|
+
/** The hold reasons this module can attach, distinct from `classify`'s snapshot-based holds. */
|
|
1209
|
+
type GcHoldReason = "live-session-cwd";
|
|
1178
1210
|
/**
|
|
1179
1211
|
* Layer 3+4 of the exemption: EVERY commit ahead of `baseRef` must have a
|
|
1180
1212
|
* dep-bump subject (`git log --format=%s`, two-dot range — exactly the
|
|
@@ -1214,13 +1246,30 @@ interface GcPlanEntry {
|
|
|
1214
1246
|
path: string;
|
|
1215
1247
|
branch: string | null;
|
|
1216
1248
|
head: string;
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1249
|
+
/**
|
|
1250
|
+
* Absent only for an orphan entry (`orphan: true`) — git itself cannot
|
|
1251
|
+
* answer the tree/integration/liveness questions for a directory it no
|
|
1252
|
+
* longer recognizes as a worktree, so this module never fabricates a
|
|
1253
|
+
* value for them rather than reporting a fact it can't actually check.
|
|
1254
|
+
*/
|
|
1255
|
+
tree?: TreeState;
|
|
1256
|
+
integration?: IntegrationState;
|
|
1257
|
+
liveness?: LivenessState;
|
|
1220
1258
|
/** Classification per PLAN.md §5.1, after `--include-detached` (if set). */
|
|
1221
1259
|
class: GcClass;
|
|
1222
|
-
/** Set only when `class === "reclaim"` via the dep-bump exemption — see `resolveGcClass`. */
|
|
1260
|
+
/** Set only when `class === "reclaim"` via the dep-bump exemption or the orphan reclaim path — see `resolveGcClass` / `scanOrphanWorktreePaths`. */
|
|
1223
1261
|
reclaimReason?: GcReclaimReason;
|
|
1262
|
+
/** Set only when `class === "hold"` via `protectedPaths` (`isProtectedPath`) — distinguishes this from an ordinary snapshot-based hold. */
|
|
1263
|
+
holdReason?: GcHoldReason;
|
|
1264
|
+
/**
|
|
1265
|
+
* `true` only for a directory the orphan scan found: physically present
|
|
1266
|
+
* under the repo's worktree pool, but absent from `git worktree list`
|
|
1267
|
+
* (even after `git worktree prune`) — the interrupted-`worktree remove`
|
|
1268
|
+
* shape (module docblock's §5 note). Always paired with `class: "reclaim"`
|
|
1269
|
+
* and `reclaimReason: "orphan"`. Never set for a linked worktree, however
|
|
1270
|
+
* it classifies.
|
|
1271
|
+
*/
|
|
1272
|
+
orphan?: boolean;
|
|
1224
1273
|
}
|
|
1225
1274
|
interface PlanGcInput {
|
|
1226
1275
|
repoRoot: string;
|
|
@@ -1233,6 +1282,23 @@ interface PlanGcInput {
|
|
|
1233
1282
|
now?: () => string;
|
|
1234
1283
|
/** See `ComputeWorktreeStatusInput.nowMs` (status.ts) — kept separate from `now`. Defaults to `Date.now()`. */
|
|
1235
1284
|
nowMs?: number;
|
|
1285
|
+
/**
|
|
1286
|
+
* The bucket directory this repo's worktrees are provisioned under — e.g.
|
|
1287
|
+
* `<worktrees.root>/<repoLabel>` (see `resolveWorktreesRoot` / `repoLabel`,
|
|
1288
|
+
* both resolved by the caller; this module does no root resolution of its
|
|
1289
|
+
* own, see the orphan-reclaim section below). When omitted, orphan
|
|
1290
|
+
* scanning is skipped entirely and `planGc`/`applyGc` behave exactly as
|
|
1291
|
+
* before — every existing caller that doesn't pass this sees zero change.
|
|
1292
|
+
*/
|
|
1293
|
+
worktreesRoot?: string;
|
|
1294
|
+
/**
|
|
1295
|
+
* Absolute paths (typically live session cwds) that must never be
|
|
1296
|
+
* classified `reclaim`/`salvage`, exact-or-subdirectory (`isProtectedPath`)
|
|
1297
|
+
* — see the "live-session-cwd protection" section above. Applies to both
|
|
1298
|
+
* linked worktrees and orphan-scan entries. Omitted ⇒ no additional
|
|
1299
|
+
* protection beyond `classify`'s own snapshot-based liveness axis.
|
|
1300
|
+
*/
|
|
1301
|
+
protectedPaths?: string[];
|
|
1236
1302
|
}
|
|
1237
1303
|
/**
|
|
1238
1304
|
* The dry-run plan (PLAN.md §5.2 layer 1): classify every linked worktree,
|
|
@@ -1255,6 +1321,8 @@ interface ApplyGcOptions {
|
|
|
1255
1321
|
nowMs?: number;
|
|
1256
1322
|
/** Override for `~/.agentproto/worktree-salvage` — tests use a temp dir. */
|
|
1257
1323
|
salvageRoot?: string;
|
|
1324
|
+
/** 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. */
|
|
1325
|
+
protectedPaths?: string[];
|
|
1258
1326
|
}
|
|
1259
1327
|
type GcApplyOutcome = {
|
|
1260
1328
|
path: string;
|
|
@@ -1270,6 +1338,7 @@ type GcApplyOutcome = {
|
|
|
1270
1338
|
path: string;
|
|
1271
1339
|
branch: string | null;
|
|
1272
1340
|
result: "held";
|
|
1341
|
+
holdReason?: GcHoldReason;
|
|
1273
1342
|
}
|
|
1274
1343
|
/** Salvage-class, but `--salvage-dirty` was not passed: left untouched by design. */
|
|
1275
1344
|
| {
|
|
@@ -1284,6 +1353,7 @@ type GcApplyOutcome = {
|
|
|
1284
1353
|
result: "aborted-reclassified";
|
|
1285
1354
|
from: GcClass;
|
|
1286
1355
|
to: GcClass;
|
|
1356
|
+
holdReason?: GcHoldReason;
|
|
1287
1357
|
}
|
|
1288
1358
|
/** The worktree named in the plan no longer exists (already removed by something else since the plan was made). */
|
|
1289
1359
|
| {
|
|
@@ -1306,5 +1376,20 @@ type GcApplyOutcome = {
|
|
|
1306
1376
|
* `git worktree remove`.
|
|
1307
1377
|
*/
|
|
1308
1378
|
declare function applyGc(plan: readonly GcPlanEntry[], options: ApplyGcOptions): Promise<GcApplyOutcome[]>;
|
|
1379
|
+
/**
|
|
1380
|
+
* Attempt to reclaim exactly one worktree by path — the exit-time auto-
|
|
1381
|
+
* reclaim primitive `sessions.ts` calls (via the injected
|
|
1382
|
+
* `WorktreeAutoReclaimer` port) when a policy-provisioned session reaches a
|
|
1383
|
+
* terminal state. Classifies it fresh (never trusts a caller-supplied
|
|
1384
|
+
* verdict) and only ever removes it when that classification is `reclaim`
|
|
1385
|
+
* (merged-or-fresh, clean, idle) — a dirty or held worktree is returned
|
|
1386
|
+
* as-is, exactly like any other `gc` outcome, still visible via
|
|
1387
|
+
* `worktree_status` and still manually reclaimable. Returns `null` when the
|
|
1388
|
+
* path no longer names a linked worktree at all (already removed by
|
|
1389
|
+
* something else — nothing to do, not an error).
|
|
1390
|
+
*/
|
|
1391
|
+
declare function reclaimOneWorktree(worktreePath: string, options: ApplyGcOptions & {
|
|
1392
|
+
includeDetached?: boolean;
|
|
1393
|
+
}): Promise<GcApplyOutcome | null>;
|
|
1309
1394
|
|
|
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 };
|
|
1395
|
+
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-R6UERXV4.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
|