@kynver-app/runtime 0.1.108 → 0.1.112

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.
@@ -3,5 +3,9 @@ export interface EnsureGitHubCliAuthResult {
3
3
  configured: boolean;
4
4
  reason: string;
5
5
  }
6
- /** Mirror of server ready-pr-landing.cli-auth — resolve gh token from env or local CLI. */
6
+ /**
7
+ * Intentional duplicate of `src/modules/agent-os/ready-pr-landing/ready-pr-landing.cli-auth.ts`.
8
+ * The runtime package cannot import `@/src` server modules (standalone esbuild bundle), so gh
9
+ * token resolution is mirrored here. Keep both copies in sync when auth probe logic changes.
10
+ */
7
11
  export declare function ensureGitHubTokenFromCliAuth(): EnsureGitHubCliAuthResult;
@@ -14,6 +14,14 @@ export interface ExecuteLandPrMergeInput {
14
14
  skipNotReady?: boolean;
15
15
  exec?: PrHandoffExec;
16
16
  }
17
+ /** Parse `owner/repo` from a GitHub PR URL, e.g. https://github.com/acme/app/pull/42 -> "acme/app". Returns null on non-match. */
18
+ export declare function repoFromPrUrl(prUrl: string): string | null;
19
+ /** Pure predicate form of the runtime verification-evidence gate (for tests). */
20
+ export declare function runtimeVerificationEvidenceSufficient(input: {
21
+ title?: string | null;
22
+ body?: string | null;
23
+ vercelCheckSuccess?: boolean;
24
+ }): boolean;
17
25
  /**
18
26
  * Daemon-local PR merge — live readiness, squash merge, branch delete, worktree cleanup.
19
27
  */
@@ -21,6 +21,10 @@ function fail(message) {
21
21
  console.error(message);
22
22
  process.exit(1);
23
23
  }
24
+ function hiddenSpawnOptions(opts) {
25
+ if (process.platform !== "win32") return opts;
26
+ return { windowsHide: true, ...opts };
27
+ }
24
28
  function safeJson(line) {
25
29
  try {
26
30
  return JSON.parse(line);
@@ -126,7 +130,11 @@ var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
126
130
 
127
131
  // src/git.ts
128
132
  function git(cwd, args, options = {}) {
129
- const res = spawnSync("git", args, { cwd, encoding: "utf8" });
133
+ const res = spawnSync(
134
+ "git",
135
+ args,
136
+ hiddenSpawnOptions({ cwd, encoding: "utf8" })
137
+ );
130
138
  if (res.status !== 0 && !options.allowFailure) {
131
139
  const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
132
140
  if (options.throwError) throw new Error(message);
@@ -139,7 +147,11 @@ function gitStatusShort(worktreePath) {
139
147
  }
140
148
  function gitCapture(cwd, args) {
141
149
  try {
142
- const res = spawnSync("git", args, { cwd, encoding: "utf8" });
150
+ const res = spawnSync(
151
+ "git",
152
+ args,
153
+ hiddenSpawnOptions({ cwd, encoding: "utf8" })
154
+ );
143
155
  return {
144
156
  status: res.status,
145
157
  stdout: res.stdout || "",
@@ -1735,6 +1747,32 @@ function materialWorktreeChanges(changedFiles) {
1735
1747
  });
1736
1748
  }
1737
1749
 
1750
+ // src/cleanup-worktree-salvage.ts
1751
+ function prUrlFromFinalResult(finalResult) {
1752
+ if (typeof finalResult === "string") {
1753
+ const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
1754
+ return match?.[0] ?? null;
1755
+ }
1756
+ if (finalResult && typeof finalResult === "object") {
1757
+ const obj = finalResult;
1758
+ for (const key of ["prUrl", "pr_url", "pullRequestUrl"]) {
1759
+ const value = obj[key];
1760
+ if (typeof value === "string" && value.trim()) return value.trim();
1761
+ }
1762
+ }
1763
+ return null;
1764
+ }
1765
+ function isPrOrUnmergedWork(status) {
1766
+ const relation = status.gitAncestry?.relation;
1767
+ if (relation === "merged" || relation === "synced") {
1768
+ return materialWorktreeChanges(status.changedFiles).length > 0;
1769
+ }
1770
+ if (prUrlFromFinalResult(status.finalResult)) return true;
1771
+ if (relation === "ahead" || relation === "diverged") return true;
1772
+ if (status.changedFiles.length > 0 && status.finalResult) return true;
1773
+ return false;
1774
+ }
1775
+
1738
1776
  // src/cleanup-index-status.ts
1739
1777
  function indexedWorktreeStatus(entry) {
1740
1778
  if (!entry.status) {
@@ -1745,11 +1783,71 @@ function indexedWorktreeStatus(entry) {
1745
1783
  }
1746
1784
  return entry.status;
1747
1785
  }
1748
- function indexedWorktreeHasMaterialChanges(entry) {
1786
+ function indexedWorktreeHasMaterialChanges(entry, gitStatusCache) {
1749
1787
  if (entry.status) {
1750
1788
  return materialWorktreeChanges(entry.status.changedFiles).length > 0;
1751
1789
  }
1752
- return materialWorktreeChanges(gitStatusShort(entry.worktreePath)).length > 0;
1790
+ const porcelain = gitStatusCache ? gitStatusCache.porcelain(entry.worktreePath) : gitStatusShort(entry.worktreePath);
1791
+ return materialWorktreeChanges(porcelain).length > 0;
1792
+ }
1793
+ function finalResultFromWorkerJson(entry) {
1794
+ const snapshot = entry.worker.completionSnapshot?.finalResult;
1795
+ if (snapshot !== void 0 && snapshot !== null) return snapshot;
1796
+ if (entry.worker.taskPrUrl) {
1797
+ return { prUrl: entry.worker.taskPrUrl };
1798
+ }
1799
+ return null;
1800
+ }
1801
+ function resolveWorktreeGuardStatus(entry, ctx) {
1802
+ if (entry.status) return entry.status;
1803
+ const worker = entry.worker;
1804
+ const completionAcknowledged = typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim().length > 0;
1805
+ const workerJsonTerminal = Boolean(worker.status && ["done", "exited", "blocked", "failed", "abandoned"].includes(worker.status)) || completionAcknowledged;
1806
+ const finalResult = finalResultFromWorkerJson(entry);
1807
+ if (workerJsonTerminal && !isPidAlive(worker.pid)) {
1808
+ const changedFiles = ctx?.gitStatusCache ? ctx.gitStatusCache.porcelain(entry.worktreePath) : gitStatusShort(entry.worktreePath);
1809
+ const baseLabel = entry.run.baseCommit?.trim() || entry.run.base?.trim() || "origin/main";
1810
+ const ahead = ctx?.gitRevCache?.countAheadOfMain(entry.worktreePath, baseLabel);
1811
+ const gitAncestry = ahead === 0 ? {
1812
+ checked: true,
1813
+ base: baseLabel,
1814
+ relation: "synced"
1815
+ } : computeGitAncestry(entry.worktreePath, {
1816
+ base: entry.run.base,
1817
+ baseCommit: entry.run.baseCommit
1818
+ });
1819
+ const status = {
1820
+ runId: entry.runId,
1821
+ worker: entry.workerName,
1822
+ pid: worker.pid,
1823
+ alive: false,
1824
+ status: worker.status ?? (completionAcknowledged ? "done" : "exited"),
1825
+ attention: { state: completionAcknowledged ? "done" : "stale" },
1826
+ branch: worker.branch,
1827
+ worktreePath: entry.worktreePath,
1828
+ ownedPaths: worker.ownedPaths,
1829
+ stdoutBytes: 0,
1830
+ stderrBytes: 0,
1831
+ heartbeatBytes: 0,
1832
+ firstEventAt: null,
1833
+ lastEventAt: null,
1834
+ lastActivityAt: worker.completionReportedAt ?? null,
1835
+ currentTool: null,
1836
+ heartbeatCount: 0,
1837
+ lastHeartbeatAt: null,
1838
+ lastHeartbeatPhase: null,
1839
+ lastHeartbeatSummary: null,
1840
+ heartbeatBlocker: null,
1841
+ changedFiles,
1842
+ gitAncestry,
1843
+ finalResult,
1844
+ completionBlocker: typeof worker.completionBlocker === "string" ? worker.completionBlocker.trim() || null : null,
1845
+ prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? prUrlFromFinalResult(finalResult)
1846
+ };
1847
+ entry.status = status;
1848
+ return status;
1849
+ }
1850
+ return indexedWorktreeStatus(entry);
1753
1851
  }
1754
1852
 
1755
1853
  // src/finalize.ts
@@ -1810,32 +1908,6 @@ function finalizeStaleRuns() {
1810
1908
  return finalized;
1811
1909
  }
1812
1910
 
1813
- // src/cleanup-worktree-salvage.ts
1814
- function prUrlFromFinalResult(finalResult) {
1815
- if (typeof finalResult === "string") {
1816
- const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
1817
- return match?.[0] ?? null;
1818
- }
1819
- if (finalResult && typeof finalResult === "object") {
1820
- const obj = finalResult;
1821
- for (const key of ["prUrl", "pr_url", "pullRequestUrl"]) {
1822
- const value = obj[key];
1823
- if (typeof value === "string" && value.trim()) return value.trim();
1824
- }
1825
- }
1826
- return null;
1827
- }
1828
- function isPrOrUnmergedWork(status) {
1829
- const relation = status.gitAncestry?.relation;
1830
- if (relation === "merged" || relation === "synced") {
1831
- return materialWorktreeChanges(status.changedFiles).length > 0;
1832
- }
1833
- if (prUrlFromFinalResult(status.finalResult)) return true;
1834
- if (relation === "ahead" || relation === "diverged") return true;
1835
- if (status.changedFiles.length > 0 && status.finalResult) return true;
1836
- return false;
1837
- }
1838
-
1839
1911
  // src/cleanup-completion-blocker.ts
1840
1912
  function completionBlockerBlocksWorktreeRemoval(indexed, status) {
1841
1913
  const blocker = typeof indexed.worker.completionBlocker === "string" ? indexed.worker.completionBlocker.trim() : "";
@@ -1856,13 +1928,28 @@ function completionBlockerBlocksWorktreeRemoval(indexed, status) {
1856
1928
  }
1857
1929
 
1858
1930
  // src/cleanup-run-liveness.ts
1931
+ var TERMINAL_WORKER_JSON_STATUSES = /* @__PURE__ */ new Set([
1932
+ "done",
1933
+ "exited",
1934
+ "blocked",
1935
+ "failed",
1936
+ "abandoned"
1937
+ ]);
1859
1938
  function deriveRunTerminal(indexed, ctx) {
1860
1939
  if (ctx) return ctx.runTerminalCache.derive(indexed.run);
1861
1940
  return deriveTerminalRunStatus(indexed.run);
1862
1941
  }
1863
1942
  function isWorkerProcessLive(indexed) {
1864
1943
  if (isPidAlive(indexed.worker.pid)) return true;
1865
- if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
1944
+ if (typeof indexed.worker.completionReportedAt === "string" && indexed.worker.completionReportedAt.trim().length > 0) {
1945
+ return false;
1946
+ }
1947
+ const workerStatus = indexed.worker.status;
1948
+ if (workerStatus && TERMINAL_WORKER_JSON_STATUSES.has(workerStatus)) return false;
1949
+ if (!indexed.worker.pid) {
1950
+ if (workerStatus !== "running") return false;
1951
+ return indexedWorktreeStatus(indexed).alive;
1952
+ }
1866
1953
  return false;
1867
1954
  }
1868
1955
  function isRunStaleActive(indexed, ctx) {
@@ -1871,6 +1958,10 @@ function isRunStaleActive(indexed, ctx) {
1871
1958
  }
1872
1959
  function runBlocksWorktreeRemoval(indexed, ctx) {
1873
1960
  if (isWorkerProcessLive(indexed)) return true;
1961
+ const workerStatus = indexed.worker.status;
1962
+ if (workerStatus && TERMINAL_WORKER_JSON_STATUSES.has(workerStatus) && !indexed.worker.completionBlocker) {
1963
+ return false;
1964
+ }
1874
1965
  const status = indexedWorktreeStatus(indexed);
1875
1966
  if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return true;
1876
1967
  if (isFinishedWorkerStatus(status)) return false;
@@ -1889,7 +1980,7 @@ function effectiveWorktreeAgeMs(input) {
1889
1980
  if (input.liveness && isRunStaleActive(indexed, input.liveness)) {
1890
1981
  return terminalWorktreesAgeMs;
1891
1982
  }
1892
- if (input.liveness && isFinishedWorkerStatus(indexedWorktreeStatus(indexed)) && !isWorkerProcessLive(indexed)) {
1983
+ if (input.liveness && isFinishedWorkerStatus(resolveWorktreeGuardStatus(indexed, input.liveness)) && !isWorkerProcessLive(indexed)) {
1893
1984
  return terminalWorktreesAgeMs;
1894
1985
  }
1895
1986
  return worktreesAgeMs;
@@ -1903,8 +1994,13 @@ function skipWorktreeRemoval(input) {
1903
1994
  const ageThresholdMs = effectiveWorktreeAgeMs(input);
1904
1995
  if (worktreesAgeMs <= 0 && !includeOrphans && ageThresholdMs <= 0) return "worktrees_disabled";
1905
1996
  if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
1906
- const status = indexedWorktreeStatus(indexed);
1907
1997
  if (isWorkerProcessLive(indexed)) return "active_worker";
1998
+ if (indexedWorktreeHasMaterialChanges(indexed, input.liveness?.gitStatusCache)) {
1999
+ return "dirty_worktree";
2000
+ }
2001
+ const ahead = input.liveness?.gitRevCache?.countAheadOfMain(input.worktreePath);
2002
+ if (ahead !== null && ahead !== void 0 && ahead > 0) return "pr_or_unmerged_commits";
2003
+ const status = resolveWorktreeGuardStatus(indexed, input.liveness);
1908
2004
  if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return "completion_blocked";
1909
2005
  if (runBlocksWorktreeRemoval(indexed, input.liveness)) return "run_still_active";
1910
2006
  if (!isFinishedWorkerStatus(status)) return "run_still_active";
@@ -1935,7 +2031,9 @@ function skipDependencyCacheRemoval(input) {
1935
2031
  if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
1936
2032
  if (activeWorktreePaths.has(path8.resolve(worktreePath))) return "active_worker";
1937
2033
  if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
1938
- if (indexed && indexedWorktreeHasMaterialChanges(indexed)) return "dirty_worktree";
2034
+ if (indexed && indexedWorktreeHasMaterialChanges(indexed, input.gitStatusCache)) {
2035
+ return "dirty_worktree";
2036
+ }
1939
2037
  return null;
1940
2038
  }
1941
2039
  function skipBuildCacheRemoval(input) {
@@ -3119,6 +3217,37 @@ function emitCleanupProgress(phase, detail) {
3119
3217
  console.error(`[kynver cleanup] ${phase}${suffix}`);
3120
3218
  }
3121
3219
 
3220
+ // src/cleanup-git-rev-cache.ts
3221
+ var CleanupGitRevCache = class {
3222
+ aheadOfMain = /* @__PURE__ */ new Map();
3223
+ countAheadOfMain(worktreePath, base = "origin/main") {
3224
+ const key = `${worktreePath}\0${base}`;
3225
+ if (this.aheadOfMain.has(key)) return this.aheadOfMain.get(key) ?? null;
3226
+ const result = gitCapture(worktreePath, ["rev-list", "--count", `${base}..HEAD`]);
3227
+ if (result.status !== 0) {
3228
+ this.aheadOfMain.set(key, null);
3229
+ return null;
3230
+ }
3231
+ const count = Number(result.stdout.trim());
3232
+ const parsed = Number.isFinite(count) ? count : null;
3233
+ this.aheadOfMain.set(key, parsed);
3234
+ return parsed;
3235
+ }
3236
+ };
3237
+
3238
+ // src/cleanup-git-status-cache.ts
3239
+ var CleanupGitStatusCache = class {
3240
+ cache = /* @__PURE__ */ new Map();
3241
+ porcelain(worktreePath) {
3242
+ const resolved = worktreePath;
3243
+ const cached = this.cache.get(resolved);
3244
+ if (cached !== void 0) return cached;
3245
+ const lines = gitStatusShort(resolved);
3246
+ this.cache.set(resolved, lines);
3247
+ return lines;
3248
+ }
3249
+ };
3250
+
3122
3251
  // src/cleanup-run-terminal-cache.ts
3123
3252
  var CleanupRunTerminalCache = class {
3124
3253
  cache = /* @__PURE__ */ new Map();
@@ -3236,7 +3365,11 @@ function runHarnessCleanup(options = {}) {
3236
3365
  emitCleanupProgress("index", "building worktree index");
3237
3366
  const index = mergeWorktreeIndexes(paths.scanRoots);
3238
3367
  emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
3239
- const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
3368
+ const liveness = {
3369
+ runTerminalCache: new CleanupRunTerminalCache(),
3370
+ gitStatusCache: new CleanupGitStatusCache(),
3371
+ gitRevCache: new CleanupGitRevCache()
3372
+ };
3240
3373
  const skips = [];
3241
3374
  const actions = [];
3242
3375
  const processedPaths = /* @__PURE__ */ new Set();
@@ -3256,8 +3389,15 @@ function runHarnessCleanup(options = {}) {
3256
3389
  index,
3257
3390
  now: paths.now
3258
3391
  };
3259
- for (const raw of scanDependencyCacheCandidates(scanOpts)) {
3392
+ const dependencyCandidates = scanDependencyCacheCandidates(scanOpts);
3393
+ emitCleanupProgress("dependency", `${dependencyCandidates.length} cache candidate(s) at ${harnessRoot}`);
3394
+ let dependencyProcessed = 0;
3395
+ for (const raw of dependencyCandidates) {
3260
3396
  if (atSweepCap()) break;
3397
+ dependencyProcessed += 1;
3398
+ if (dependencyProcessed % 50 === 0) {
3399
+ emitCleanupProgress("dependency", `${dependencyProcessed}/${dependencyCandidates.length} evaluated`);
3400
+ }
3261
3401
  const resolved = path23.resolve(raw.path);
3262
3402
  if (processedPaths.has(resolved)) continue;
3263
3403
  processedPaths.add(resolved);
@@ -3277,7 +3417,8 @@ function runHarnessCleanup(options = {}) {
3277
3417
  ageMs: candidate.ageMs,
3278
3418
  worktreePath,
3279
3419
  activeWorktreePaths: activeGuards.activeWorktreePaths,
3280
- diskPressure: retention.diskPressure
3420
+ diskPressure: retention.diskPressure,
3421
+ gitStatusCache: liveness.gitStatusCache
3281
3422
  });
3282
3423
  if (guardReason) {
3283
3424
  recordSkip(skips, candidate.path, guardReason);
@@ -3312,7 +3453,8 @@ function runHarnessCleanup(options = {}) {
3312
3453
  ageMs: candidate.ageMs,
3313
3454
  worktreePath,
3314
3455
  activeWorktreePaths: activeGuards.activeWorktreePaths,
3315
- diskPressure: retention.diskPressure
3456
+ diskPressure: retention.diskPressure,
3457
+ gitStatusCache: liveness.gitStatusCache
3316
3458
  });
3317
3459
  if (guardReason) {
3318
3460
  recordSkip(skips, candidate.path, guardReason);
@@ -3332,8 +3474,13 @@ function runHarnessCleanup(options = {}) {
3332
3474
  ];
3333
3475
  emitCleanupProgress("worktrees", `${worktreeCandidates.length} candidate(s) at ${harnessRoot}`);
3334
3476
  const worktreeSeen = /* @__PURE__ */ new Set();
3477
+ let worktreeProcessed = 0;
3335
3478
  for (const raw of worktreeCandidates) {
3336
3479
  if (atSweepCap()) break;
3480
+ worktreeProcessed += 1;
3481
+ if (worktreeProcessed % 50 === 0) {
3482
+ emitCleanupProgress("worktrees", `${worktreeProcessed}/${worktreeCandidates.length} evaluated`);
3483
+ }
3337
3484
  const resolved = path23.resolve(raw.path);
3338
3485
  if (worktreeSeen.has(resolved)) continue;
3339
3486
  worktreeSeen.add(resolved);