@andyqiu/codeforge 0.5.11 → 0.5.13

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/bin/codeforge.mjs CHANGED
@@ -95,7 +95,7 @@ function parseArgs(argv) {
95
95
  // ────────────────────────────────────────────────────────────────────
96
96
  // opencode installer:薄壳,调现成的 install.ps1 / install.sh
97
97
  // ────────────────────────────────────────────────────────────────────
98
- function installOpencode({ scope, dryRun, extraArgs }) {
98
+ function installOpencode({ scope, dryRun, extraArgs, quiet = false }) {
99
99
  const isWin = process.platform === "win32"
100
100
  const script = isWin ? path.join(REPO_ROOT, "install.ps1") : path.join(REPO_ROOT, "install.sh")
101
101
  if (!existsSync(script)) {
@@ -116,7 +116,11 @@ function installOpencode({ scope, dryRun, extraArgs }) {
116
116
  }
117
117
  const all = [...baseArgs, ...flagArgs, ...(extraArgs ?? [])]
118
118
  log(`opencode: ${cmd} ${all.join(" ")}`)
119
- const r = spawnSync(cmd, all, { stdio: "inherit", cwd: REPO_ROOT })
119
+ const r = spawnSync(cmd, all, { stdio: quiet ? "pipe" : "inherit", cwd: REPO_ROOT })
120
+ if (quiet && r.status !== 0) {
121
+ if (r.stderr) process.stderr.write(r.stderr)
122
+ if (r.stdout) process.stderr.write(r.stdout)
123
+ }
120
124
  return r.status ?? 1
121
125
  }
122
126
 
@@ -288,46 +292,86 @@ function cmdRollback(args) {
288
292
  // ────────────────────────────────────────────────────────────────────
289
293
  // 子命令:upgrade —— 升级到 npm latest 并重新 install --global
290
294
  // ────────────────────────────────────────────────────────────────────
291
- function cmdUpgrade(args) {
295
+
296
+ async function cmdUpgrade(args) {
292
297
  const dryRun = !!args.flags["dry-run"] || !!args.flags.dryRun
293
298
  const currentVersion = getVersion()
294
299
 
295
- log(`CodeForge upgrade`)
296
- log(` 当前版本 : ${currentVersion}`)
297
- log(` dry-run : ${dryRun}`)
298
- hr()
300
+ log(`检查版本...`)
299
301
 
300
- const npmCmd = "npm"
301
- const npmArgs = ["install", "-g", "@andyqiu/codeforge@latest"]
302
- log(`步骤 1/2:${npmCmd} ${npmArgs.join(" ")}`)
303
-
304
- if (!dryRun) {
305
- const r = spawnSync(npmCmd, npmArgs, { stdio: "inherit", shell: true })
306
- if (r.status !== 0) {
307
- err(`npm install 失败 (exit=${r.status ?? 1})`)
308
- err(`提示:可手动跑 npm install -g @andyqiu/codeforge@latest`)
309
- return 1
310
- }
302
+ // 查询 npm registry 最新版本
303
+ let latestVersion = null
304
+ try {
305
+ latestVersion = await new Promise((resolve) => {
306
+ import("node:https").then((mod) => {
307
+ const https = mod.default ?? mod // node:https ESM default export
308
+ const options = {
309
+ hostname: "registry.npmjs.org",
310
+ path: "/@andyqiu%2Fcodeforge/latest",
311
+ method: "GET",
312
+ headers: { "User-Agent": "codeforge-cli" },
313
+ timeout: 5000,
314
+ }
315
+ const req = https.get(options, (res) => {
316
+ let data = ""
317
+ res.on("data", (chunk) => { data += chunk })
318
+ res.on("end", () => {
319
+ try { resolve(JSON.parse(data).version ?? null) } catch { resolve(null) }
320
+ })
321
+ })
322
+ req.on("timeout", () => { req.destroy(); resolve(null) })
323
+ req.on("error", () => resolve(null))
324
+ }).catch(() => resolve(null))
325
+ })
326
+ } catch {
327
+ latestVersion = null
328
+ }
329
+
330
+ if (latestVersion === null) {
331
+ warn(`版本检查失败,继续升级流程`)
332
+ } else if (latestVersion === currentVersion) {
333
+ ok(`当前已是最新版本 v${currentVersion},无需升级`)
334
+ return 0
311
335
  } else {
312
- log(`[dry-run] 跳过 npm install`)
336
+ log(`当前版本:${currentVersion} 最新版本:${latestVersion}`)
313
337
  }
314
338
 
315
339
  hr()
316
- log(`步骤 2/2:codeforge install --global`)
340
+ log(`正在升级...`)
317
341
 
318
- if (!dryRun) {
319
- const skipBuildFlag = process.platform === "win32" ? "-SkipBuild" : "--skip-build"
320
- const code = installOpencode({ scope: "global", dryRun: false, extraArgs: [skipBuildFlag] })
321
- if (code !== 0) {
322
- err(`install --global 失败 (exit=${code})`)
323
- return 1
324
- }
325
- } else {
326
- log(`[dry-run] 跳过 install --global`)
342
+ const npmCmd = "npm"
343
+ // 有明确版本号时用精确版本,绕过 @latest dist-tag 缓存
344
+ const installTarget = latestVersion
345
+ ? `@andyqiu/codeforge@${latestVersion}`
346
+ : "@andyqiu/codeforge@latest"
347
+ const npmArgs = ["install", "-g", installTarget]
348
+
349
+ if (dryRun) {
350
+ const displayNew = latestVersion ?? "未知(版本查询失败)"
351
+ ok(`[dry-run] 演示升级:${currentVersion} → ${displayNew}`)
352
+ ok(`[dry-run] 实际执行时将自动重启生效提示`)
353
+ return 0
327
354
  }
328
355
 
329
- hr()
330
- ok(`升级完成!运行 codeforge -v 查看新版本`)
356
+ const r = spawnSync(npmCmd, npmArgs, { stdio: "pipe", shell: true })
357
+ if (r.status !== 0) {
358
+ if (r.stderr) process.stderr.write(r.stderr)
359
+ if (r.stdout) process.stderr.write(r.stdout)
360
+ err(`npm install 失败 (exit=${r.status ?? 1})`)
361
+ err(`提示:可手动跑 npm install -g ${installTarget}`)
362
+ return 1
363
+ }
364
+
365
+ const skipBuildFlag = process.platform === "win32" ? "-SkipBuild" : "--skip-build"
366
+ const code = installOpencode({ scope: "global", dryRun: false, extraArgs: [skipBuildFlag], quiet: true })
367
+ if (code !== 0) {
368
+ err(`install --global 失败 (exit=${code})`)
369
+ return 1
370
+ }
371
+
372
+ // 升级后读取新版本(npm install 成功后本地 package.json 已更新)
373
+ const newVersion = latestVersion ?? getVersion()
374
+ ok(`升级完成:${currentVersion} → ${newVersion}`)
331
375
  ok(`重启 opencode 后新版本生效`)
332
376
  return 0
333
377
  }
@@ -437,7 +481,7 @@ function cmdHelp() {
437
481
  codeforge list
438
482
  codeforge version
439
483
  codeforge rollback [--target=<path>] [--dry-run] # 恢复最近 backup(auto_install 失败救场)
440
- codeforge upgrade [--dry-run] # 升级到 npm latest 并重新 install --global
484
+ codeforge upgrade|update [--dry-run] # 升级到 npm latest 并重新 install --global
441
485
  codeforge runtime where [<path>] # 打印当前项目的全局运行时目录
442
486
  codeforge adr-init [--force] [--dry-run] [--write-prepare] [--no-pre-push]
443
487
  # 把 ADR 校验体系(hooks + scripts + 模板)下发到当前 git 项目
@@ -502,6 +546,7 @@ async function main() {
502
546
  case "rollback":
503
547
  return cmdRollback(args)
504
548
  case "upgrade":
549
+ case "update":
505
550
  return cmdUpgrade(args)
506
551
  case "runtime":
507
552
  return cmdRuntime(args)
package/dist/index.js CHANGED
@@ -13228,6 +13228,15 @@ async function markPlanReadOk(opts) {
13228
13228
  return true;
13229
13229
  });
13230
13230
  }
13231
+ async function touchEntryUpdatedAt(opts) {
13232
+ return await mutateRegistry(opts.mainRoot, (reg) => {
13233
+ const entry = reg.entries.find((e) => e.sessionId === opts.sessionId);
13234
+ if (!entry || entry.status !== "active")
13235
+ return false;
13236
+ entry.updatedAt = new Date().toISOString();
13237
+ return true;
13238
+ });
13239
+ }
13231
13240
  async function mergeSessionBack(opts) {
13232
13241
  const mainRoot = path13.resolve(opts.mainRoot);
13233
13242
  const entry = await getSessionWorktree(opts.sessionId, mainRoot);
@@ -13470,6 +13479,7 @@ Codeforge-Base: ${baseSha.slice(0, 12)}`;
13470
13479
  return subject + body + footer;
13471
13480
  }
13472
13481
  var ORPHAN_GRACE_MS = 60000;
13482
+ var SEMANTIC_ORPHAN_MIN_AGE_MS = 24 * 60 * 60000;
13473
13483
  async function pruneDiscardedRegistryEntries(mainRoot, opts = {}) {
13474
13484
  const keepRecent = opts.keepRecent ?? 50;
13475
13485
  if (keepRecent < 0) {
@@ -13491,7 +13501,7 @@ async function pruneDiscardedRegistryEntries(mainRoot, opts = {}) {
13491
13501
  return { pruned, kept: kept.length };
13492
13502
  });
13493
13503
  }
13494
- async function pruneOrphanWorktrees(mainRoot) {
13504
+ async function pruneOrphanWorktrees(mainRoot, opts = {}) {
13495
13505
  const resolved = path13.resolve(mainRoot);
13496
13506
  const cleaned = [];
13497
13507
  const failed = [];
@@ -13551,6 +13561,60 @@ async function pruneOrphanWorktrees(mainRoot) {
13551
13561
  }
13552
13562
  }
13553
13563
  });
13564
+ if (opts.isSessionAlive) {
13565
+ const minAge = opts.semanticOrphanMinAgeMs ?? SEMANTIC_ORPHAN_MIN_AGE_MS;
13566
+ const probe = opts.isSessionAlive;
13567
+ await mutateRegistry(resolved, async (reg2) => {
13568
+ const now = Date.now();
13569
+ for (const entry of reg2.entries) {
13570
+ if (entry.status !== "active")
13571
+ continue;
13572
+ const updatedMs = Date.parse(entry.updatedAt);
13573
+ if (!Number.isFinite(updatedMs) || now - updatedMs < minAge) {
13574
+ skipped++;
13575
+ continue;
13576
+ }
13577
+ let aliveResult;
13578
+ try {
13579
+ aliveResult = await probe(entry.sessionId);
13580
+ } catch {
13581
+ skipped++;
13582
+ continue;
13583
+ }
13584
+ if (aliveResult.source === "unknown") {
13585
+ skipped++;
13586
+ continue;
13587
+ }
13588
+ if (aliveResult.alive) {
13589
+ skipped++;
13590
+ continue;
13591
+ }
13592
+ try {
13593
+ await removeWorktree({
13594
+ root: resolved,
13595
+ worktree_path: entry.worktreePath,
13596
+ force: true
13597
+ });
13598
+ } catch (err) {
13599
+ failed.push({
13600
+ worktreePath: entry.worktreePath,
13601
+ error: `D 类 removeWorktree 失败: ${err instanceof Error ? err.message : String(err)}`
13602
+ });
13603
+ continue;
13604
+ }
13605
+ try {
13606
+ await runGit2(resolved, ["branch", "-D", entry.branch]);
13607
+ } catch {}
13608
+ entry.status = "discarded";
13609
+ entry.updatedAt = new Date().toISOString();
13610
+ cleaned.push({
13611
+ sessionId: entry.sessionId,
13612
+ worktreePath: entry.worktreePath,
13613
+ reason: `D 类语义孤儿 (opencode session ${aliveResult.source}: dead)`
13614
+ });
13615
+ }
13616
+ });
13617
+ }
13554
13618
  const codeforgeWorktreeRoot = path13.resolve(path13.join(resolved, DEFAULT_WORKTREE_SUBDIR));
13555
13619
  const fsWorktreePaths = [];
13556
13620
  try {
@@ -21710,7 +21774,7 @@ import * as zlib from "node:zlib";
21710
21774
  // lib/version-injected.ts
21711
21775
  function getInjectedVersion() {
21712
21776
  try {
21713
- const v = "0.5.11";
21777
+ const v = "0.5.13";
21714
21778
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
21715
21779
  return v;
21716
21780
  }
@@ -22990,9 +23054,9 @@ var handler24 = workflowEngineServer;
22990
23054
  import path27 from "node:path";
22991
23055
  var PLUGIN_NAME25 = "session-worktree-guard";
22992
23056
  logLifecycle(PLUGIN_NAME25, "import", {});
22993
- var WRITE_INTENT_RE = />(?!=)|\btee\b|\brm\b|\bmv\b|\bcp\b|\bmkdir\b|\btouch\b|\bchmod\b|\bchown\b|\bln\b/;
23057
+ var WRITE_INTENT_RE = />(?![=&])|\btee\b|\brm\b|\bmv\b|\bcp\b|\bmkdir\b|\btouch\b|\bchmod\b|\bchown\b|\bln\b/;
22994
23058
  var READ_ONLY_COMMANDS = /^\s*(?:ls|cat|head|tail|grep|rg|find|fd|wc|stat|file|which|whereis|echo|pwd|cd|pushd|popd|env|printenv|type|less|more|sort|uniq|awk|tr|cut|jq|date|whoami|id|uname|git(?:\s+-C\s+\S+)?\s+(?:log|show|diff|status|branch|tag|remote|config\s+--get|rev-parse|rev-list|ls-files|ls-tree|cat-file|describe|reflog|blame|shortlog|name-rev|symbolic-ref|merge-base|worktree\s+list|stash\s+list|stash\s+show))\b/;
22995
- var SIDE_EFFECT_TOKEN_RE = />(?!=)|\|\s*tee\b|\btee\b/;
23059
+ var SIDE_EFFECT_TOKEN_RE = />(?![=&])|\|\s*tee\b|\btee\b/;
22996
23060
  function isReadOnlyBashCommand(command) {
22997
23061
  if (!READ_ONLY_COMMANDS.test(command))
22998
23062
  return false;
@@ -23013,11 +23077,27 @@ function buildGitVcsWriteRegex(mainRoot) {
23013
23077
  return new RegExp(`git\\b[^\\n]*(?:-C\\s+|--work-tree[=\\s])${esc}`);
23014
23078
  }
23015
23079
  var WRITE_TOOLS = new Set(["write", "edit", "ast_edit"]);
23080
+ var TOUCH_THROTTLE_MS = 5 * 60000;
23081
+ var _touchCache = new Map;
23016
23082
  var CLASS_B_CALLER_WHITELIST = new Set([
23017
23083
  "codeforge",
23018
23084
  "reviewer",
23085
+ "reviewer-lite",
23019
23086
  "general"
23020
23087
  ]);
23088
+ var CODEFORGE_WORKTREE_DIR_NAME = path27.join(".git", "codeforge-worktrees");
23089
+ function worktreesRoot(mainRoot) {
23090
+ return path27.join(mainRoot, CODEFORGE_WORKTREE_DIR_NAME);
23091
+ }
23092
+ function isInsideAnyWorktreeDir(absPath, mainRoot) {
23093
+ if (!path27.isAbsolute(absPath))
23094
+ return false;
23095
+ const root = worktreesRoot(mainRoot);
23096
+ if (absPath === root)
23097
+ return false;
23098
+ const prefix = root.endsWith(path27.sep) ? root : root + path27.sep;
23099
+ return absPath.startsWith(prefix);
23100
+ }
23021
23101
  function rewritePath(value, mainRoot, worktreeRoot) {
23022
23102
  if (!value)
23023
23103
  return null;
@@ -23026,6 +23106,9 @@ function rewritePath(value, mainRoot, worktreeRoot) {
23026
23106
  if (resolved === worktreeRoot || resolved.startsWith(wtPrefix2)) {
23027
23107
  return null;
23028
23108
  }
23109
+ if (isInsideAnyWorktreeDir(resolved, mainRoot)) {
23110
+ return null;
23111
+ }
23029
23112
  if (resolved === mainRoot)
23030
23113
  return worktreeRoot;
23031
23114
  const prefix = mainRoot.endsWith("/") ? mainRoot : mainRoot + "/";
@@ -23055,7 +23138,20 @@ function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePat
23055
23138
  return true;
23056
23139
  }
23057
23140
  }
23058
- const sanitized = command.split(worktreePath).join("");
23141
+ const wtRoot = worktreesRoot(mainRoot);
23142
+ const wtRootPrefix = wtRoot + path27.sep;
23143
+ const escapedWtRootPrefix = escapeRegex2(wtRootPrefix);
23144
+ const wtPathPattern = escapedWtRootPrefix + `[^\\s'"\\x60)]*`;
23145
+ const allWorktreePathsReForEscape = new RegExp(wtPathPattern, "g");
23146
+ const allWorktreePathsReForReplace = new RegExp(wtPathPattern, "g");
23147
+ for (const match of command.matchAll(allWorktreePathsReForEscape)) {
23148
+ const matchedPath = match[0];
23149
+ const tail = matchedPath.slice(wtRootPrefix.length);
23150
+ if (/(?:^|[/\\])\.\.(?:[/\\]|$)/.test(tail)) {
23151
+ return true;
23152
+ }
23153
+ }
23154
+ const sanitized = command.split(worktreePath).join("").replace(allWorktreePathsReForReplace, "");
23059
23155
  return commandContainsMainRoot(sanitized, mainRoot);
23060
23156
  }
23061
23157
  function detectBashWriteIntent(command, mainRoot) {
@@ -23197,6 +23293,22 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23197
23293
  }
23198
23294
  }
23199
23295
  const worktreePath = entry.worktreePath;
23296
+ if (entry.status === "active" && isWriteOperation(toolName, argsObj, mainRoot)) {
23297
+ const last = _touchCache.get(entry.sessionId) ?? 0;
23298
+ const nowMs = Date.now();
23299
+ if (nowMs - last > TOUCH_THROTTLE_MS) {
23300
+ _touchCache.set(entry.sessionId, nowMs);
23301
+ touchEntryUpdatedAt({
23302
+ sessionId: entry.sessionId,
23303
+ mainRoot
23304
+ }).catch((err) => {
23305
+ log14.warn("touchEntryUpdatedAt 失败 (已忽略)", {
23306
+ sessionId: entry?.sessionId,
23307
+ error: err instanceof Error ? err.message : String(err)
23308
+ });
23309
+ });
23310
+ }
23311
+ }
23200
23312
  if (toolName === "session_merge") {
23201
23313
  const action = argsObj["action"];
23202
23314
  if (action === "merge") {
@@ -23425,6 +23537,103 @@ var sessionWorktreeGuardPlugin = async (ctx) => {
23425
23537
  };
23426
23538
  var handler25 = sessionWorktreeGuardPlugin;
23427
23539
 
23540
+ // lib/opencode-session-probe.ts
23541
+ import * as path28 from "node:path";
23542
+ import * as os6 from "node:os";
23543
+ import { createRequire as createRequire2 } from "node:module";
23544
+ var requireFromHere = createRequire2(import.meta.url);
23545
+ var DEFAULT_LIVENESS_MS = 6 * 60 * 60000;
23546
+ var DEFAULT_DB_PATH = path28.join(os6.homedir(), ".local/share/opencode/opencode.db");
23547
+ function createSessionProbe(opts = {}) {
23548
+ const dbPath = opts.dbPath ?? DEFAULT_DB_PATH;
23549
+ const httpBaseUrl = opts.httpBaseUrl ?? process.env["OPENCODE_SERVER_URL"];
23550
+ const livenessWindowMs = opts.livenessWindowMs ?? DEFAULT_LIVENESS_MS;
23551
+ const timeoutMs = opts.timeoutMs ?? 1500;
23552
+ let db = null;
23553
+ let dbInitTried = false;
23554
+ let dbStmt = null;
23555
+ async function tryOpenDb() {
23556
+ if (dbInitTried)
23557
+ return db != null;
23558
+ dbInitTried = true;
23559
+ try {
23560
+ const mod = requireFromHere("node:sqlite");
23561
+ try {
23562
+ db = new mod.DatabaseSync(dbPath, { readOnly: true });
23563
+ } catch {
23564
+ db = null;
23565
+ dbStmt = null;
23566
+ return false;
23567
+ }
23568
+ dbStmt = db.prepare("SELECT time_updated, time_archived FROM session WHERE id = ? LIMIT 1");
23569
+ return true;
23570
+ } catch {
23571
+ db = null;
23572
+ dbStmt = null;
23573
+ return false;
23574
+ }
23575
+ }
23576
+ async function probeHttp(sessionId) {
23577
+ if (!httpBaseUrl)
23578
+ return null;
23579
+ try {
23580
+ const ctrl = new AbortController;
23581
+ const t = setTimeout(() => ctrl.abort(), timeoutMs);
23582
+ const res = await fetch(`${httpBaseUrl.replace(/\/$/, "")}/session`, {
23583
+ signal: ctrl.signal
23584
+ }).finally(() => clearTimeout(t));
23585
+ if (!res.ok)
23586
+ return null;
23587
+ const list = await res.json();
23588
+ const hit = Array.isArray(list) && list.some((s) => s.id === sessionId);
23589
+ return { alive: hit, source: "http" };
23590
+ } catch {
23591
+ return null;
23592
+ }
23593
+ }
23594
+ async function probeSqlite(sessionId) {
23595
+ if (!await tryOpenDb() || !dbStmt)
23596
+ return null;
23597
+ try {
23598
+ const row = dbStmt.get(sessionId);
23599
+ if (!row) {
23600
+ return { alive: false, source: "sqlite" };
23601
+ }
23602
+ if (row.time_archived != null) {
23603
+ return {
23604
+ alive: false,
23605
+ source: "sqlite",
23606
+ time_archived: row.time_archived
23607
+ };
23608
+ }
23609
+ const now = Date.now();
23610
+ const tu = Number(row.time_updated) || 0;
23611
+ const alive = now - tu < livenessWindowMs;
23612
+ return { alive, source: "sqlite", time_updated: tu, time_archived: null };
23613
+ } catch {
23614
+ return null;
23615
+ }
23616
+ }
23617
+ return {
23618
+ async isSessionAlive(sessionId) {
23619
+ const http = await probeHttp(sessionId);
23620
+ if (http)
23621
+ return http;
23622
+ const sql = await probeSqlite(sessionId);
23623
+ if (sql)
23624
+ return sql;
23625
+ return { alive: true, source: "unknown" };
23626
+ },
23627
+ close() {
23628
+ try {
23629
+ db?.close?.();
23630
+ } catch {}
23631
+ db = null;
23632
+ dbStmt = null;
23633
+ }
23634
+ };
23635
+ }
23636
+
23428
23637
  // plugins/worktree-lifecycle.ts
23429
23638
  var PLUGIN_NAME26 = "worktree-lifecycle";
23430
23639
  logLifecycle(PLUGIN_NAME26, "import", {});
@@ -23435,6 +23644,7 @@ var PRUNE_INTERVAL_MS = 30 * 60000;
23435
23644
  var lastIdleToastAt = new Map;
23436
23645
  var pruneRunning = false;
23437
23646
  var _pruneTimer;
23647
+ var _probe = null;
23438
23648
  var log15 = makePluginLogger(PLUGIN_NAME26);
23439
23649
  var worktreeLifecyclePlugin = async (ctx) => {
23440
23650
  const mainRoot = ctx.directory;
@@ -23446,9 +23656,17 @@ var worktreeLifecyclePlugin = async (ctx) => {
23446
23656
  return {};
23447
23657
  }
23448
23658
  const client = ctx.client;
23659
+ if (_probe) {
23660
+ try {
23661
+ _probe.close();
23662
+ } catch {}
23663
+ }
23664
+ _probe = createSessionProbe();
23449
23665
  setImmediate(() => {
23450
23666
  safeAsync(PLUGIN_NAME26, "activate.pruneOrphan", async () => {
23451
- const result = await pruneOrphanWorktrees(mainRoot);
23667
+ const result = await pruneOrphanWorktrees(mainRoot, {
23668
+ isSessionAlive: _probe.isSessionAlive
23669
+ });
23452
23670
  if (result.cleaned.length > 0 || result.failed.length > 0) {
23453
23671
  log15.info(`[pruneOrphan] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
23454
23672
  safeWriteLog(PLUGIN_NAME26, {
@@ -23474,7 +23692,9 @@ var worktreeLifecyclePlugin = async (ctx) => {
23474
23692
  pruneRunning = true;
23475
23693
  safeAsync(PLUGIN_NAME26, "interval.pruneOrphan", async () => {
23476
23694
  try {
23477
- const result = await pruneOrphanWorktrees(mainRoot);
23695
+ const result = await pruneOrphanWorktrees(mainRoot, {
23696
+ isSessionAlive: _probe.isSessionAlive
23697
+ });
23478
23698
  if (result.cleaned.length > 0 || result.failed.length > 0) {
23479
23699
  log15.info(`[pruneOrphan interval] cleaned=${result.cleaned.length} failed=${result.failed.length} skipped=${result.skipped}`);
23480
23700
  safeWriteLog(PLUGIN_NAME26, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andyqiu/codeforge",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "description": "CodeForge — opencode 的零侵入扩展包",
5
5
  "type": "module",
6
6
  "private": false,