@macintacos/caret 0.8.2 → 0.10.0

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.
Files changed (101) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +59 -47
  3. package/commands/discovery.md +4 -4
  4. package/dist/cli.js +341 -114
  5. package/opencode/caret.plugin.ts +91 -30
  6. package/opencode/commands/discovery.md +4 -4
  7. package/package.json +3 -2
  8. package/ui/dist/assets/{angular-html-BpJgIORc.js → angular-html-166Zp_qr.js} +1 -1
  9. package/ui/dist/assets/{angular-ts-Dw7jt6H3.js → angular-ts-DJf9y5RJ.js} +1 -1
  10. package/ui/dist/assets/{apl-DscWKpqG.js → apl-BRtDRKgK.js} +1 -1
  11. package/ui/dist/assets/{astro-M2TfAY-4.js → astro-B9_p1ZlX.js} +1 -1
  12. package/ui/dist/assets/{blade-BKZbiCTN.js → blade-DUQgH5UI.js} +1 -1
  13. package/ui/dist/assets/{c-vSGY_oaW.js → c-DkmnqVR5.js} +1 -1
  14. package/ui/dist/assets/{cobol-D_b85OID.js → cobol-D_DeO3_Y.js} +1 -1
  15. package/ui/dist/assets/{coffee-yR0vOcDv.js → coffee-BlbmSk0s.js} +1 -1
  16. package/ui/dist/assets/{cpp-B9n-PBog.js → cpp-C-2HRkcc.js} +1 -1
  17. package/ui/dist/assets/{crystal-DlMW4K68.js → crystal-CpLflAjJ.js} +1 -1
  18. package/ui/dist/assets/{css-eR_OGyvz.js → css-BVuUbqzr.js} +1 -1
  19. package/ui/dist/assets/{dist-Dw6gzrLi.js → dist-B9dAYtn2.js} +1 -1
  20. package/ui/dist/assets/{dist-BOlD4h0w.js → dist-BtZ4f0sy.js} +1 -1
  21. package/ui/dist/assets/{dist-BDTKIP5U.js → dist-BxKUx0iz.js} +1 -1
  22. package/ui/dist/assets/{dist-C-9GQ1Zp.js → dist-CmhY3KNJ.js} +1 -1
  23. package/ui/dist/assets/{dist-BVU_DRtk.js → dist-D-vK_12F.js} +1 -1
  24. package/ui/dist/assets/{dist-z5ayJUeC.js → dist-D1hccSXB.js} +1 -1
  25. package/ui/dist/assets/{dist-fegIt7sh.js → dist-DWjpoB5Q.js} +1 -1
  26. package/ui/dist/assets/{dist-Cu14OUmD.js → dist-Dd0x4Lgi.js} +1 -1
  27. package/ui/dist/assets/{dist-Bq4ybaGS.js → dist-Deoj7mVd.js} +1 -1
  28. package/ui/dist/assets/{dist-CA0N05NE.js → dist-DtJjv6C-.js} +1 -1
  29. package/ui/dist/assets/{dist-9nsJ_jfT.js → dist-DyJyAkU7.js} +1 -1
  30. package/ui/dist/assets/{dist-YE2Uj3eD.js → dist-DzbZtok6.js} +1 -1
  31. package/ui/dist/assets/{dist-CLxm4mmq.js → dist-Jn2c79bF.js} +1 -1
  32. package/ui/dist/assets/{dist--nCimyFJ.js → dist-WBctQMJH.js} +1 -1
  33. package/ui/dist/assets/{dist-DpjGqY_j.js → dist-nPPt0-3f.js} +1 -1
  34. package/ui/dist/assets/{dist-ZIUMZKKQ.js → dist-qtw8Ijdq.js} +1 -1
  35. package/ui/dist/assets/{dist-EFvLx_Vg.js → dist-vj9fsPbi.js} +1 -1
  36. package/ui/dist/assets/{edge-CIm6BqT_.js → edge-Bvaq3vgu.js} +1 -1
  37. package/ui/dist/assets/{elixir-CGFSlFw_.js → elixir-AtYS8b3T.js} +1 -1
  38. package/ui/dist/assets/{elm-BSew7n5O.js → elm-DiPfkLEI.js} +1 -1
  39. package/ui/dist/assets/{erb-BkgqIZze.js → erb-DFPsOHl7.js} +1 -1
  40. package/ui/dist/assets/{git-rebase-BUYBBmxS.js → git-rebase-DOQhjkkB.js} +1 -1
  41. package/ui/dist/assets/{glimmer-js-BGag6-lb.js → glimmer-js-DnsoKoXj.js} +1 -1
  42. package/ui/dist/assets/{glimmer-ts-DDMkKLl0.js → glimmer-ts-efCSUcGf.js} +1 -1
  43. package/ui/dist/assets/{glsl-DORy1f5o.js → glsl-8mp3VUjq.js} +1 -1
  44. package/ui/dist/assets/{graphql-CQdH4wHz.js → graphql-NvuaWD7i.js} +1 -1
  45. package/ui/dist/assets/{hack-DEMoudQX.js → hack-BJ5H84Wq.js} +1 -1
  46. package/ui/dist/assets/{haml-5F6bZL9O.js → haml-Dzosfq8Y.js} +1 -1
  47. package/ui/dist/assets/{handlebars-CJrxVb1S.js → handlebars-tKuTe0db.js} +1 -1
  48. package/ui/dist/assets/{html-DnBxHgUN.js → html-JqbgrVrs.js} +1 -1
  49. package/ui/dist/assets/{html-derivative-oO1_x7AL.js → html-derivative-0a3abtuv.js} +1 -1
  50. package/ui/dist/assets/{http-x7FjaFyJ.js → http-DMJqqRWL.js} +1 -1
  51. package/ui/dist/assets/{hurl-C3DS1OMk.js → hurl-N2MzWbSO.js} +1 -1
  52. package/ui/dist/assets/index-BMSMV2hN.css +1 -0
  53. package/ui/dist/assets/index-C_IKB4Ku.js +2501 -0
  54. package/ui/dist/assets/{java-C2C6Komk.js → java-B3EVVFFi.js} +1 -1
  55. package/ui/dist/assets/{javascript-DEpYVqmG.js → javascript-Y0fZ_N_R.js} +1 -1
  56. package/ui/dist/assets/{jinja-BNfhmgIR.js → jinja-BD-QmqJ0.js} +1 -1
  57. package/ui/dist/assets/{jison-ooxyiW2g.js → jison-BK-X5L2R.js} +1 -1
  58. package/ui/dist/assets/{json-Dx2gR8Lp.js → json-DlHOOSIb.js} +1 -1
  59. package/ui/dist/assets/{jsx-BZCBHgZQ.js → jsx-XohAHRMn.js} +1 -1
  60. package/ui/dist/assets/{julia-kkZGM_a9.js → julia-BBLrPT8A.js} +1 -1
  61. package/ui/dist/assets/{just-Cjde5dAJ.js → just-DEry_8sa.js} +1 -1
  62. package/ui/dist/assets/{latex-D3gXuVSc.js → latex-BuCB4qwR.js} +1 -1
  63. package/ui/dist/assets/{liquid-DmlvH9NM.js → liquid-ZtUmbkDf.js} +1 -1
  64. package/ui/dist/assets/{lua-pCB9FV-x.js → lua-BmOQjHgA.js} +1 -1
  65. package/ui/dist/assets/{marko-DwItdpjh.js → marko-BXMGyPGo.js} +1 -1
  66. package/ui/dist/assets/{mdc-SSlXAY7R.js → mdc-Byn32K04.js} +1 -1
  67. package/ui/dist/assets/{nginx-DwgR7vzY.js → nginx-BrQiZd4C.js} +1 -1
  68. package/ui/dist/assets/{nim-BB87Orsk.js → nim-Ceo1IkbE.js} +1 -1
  69. package/ui/dist/assets/{perl-BEwuOnSP.js → perl-Bum057xI.js} +1 -1
  70. package/ui/dist/assets/{php-B7dC7_E_.js → php-DcJ8ynWT.js} +1 -1
  71. package/ui/dist/assets/{pug-cCzod0Ou.js → pug-CA9Zezth.js} +1 -1
  72. package/ui/dist/assets/{qml-DlvhYl5u.js → qml-BHS1pXn9.js} +1 -1
  73. package/ui/dist/assets/{r-gkifu9rO.js → r-B4aRkCWL.js} +1 -1
  74. package/ui/dist/assets/{razor-CQ3nK7LT.js → razor-BXH-K44T.js} +1 -1
  75. package/ui/dist/assets/{regexp-BfKjRa3b.js → regexp-DUs7aoKy.js} +1 -1
  76. package/ui/dist/assets/{rst-CaTCPdHP.js → rst-DCrs7vP-.js} +1 -1
  77. package/ui/dist/assets/{ruby-Qi28elVh.js → ruby-0-lN-lO6.js} +1 -1
  78. package/ui/dist/assets/{sas-C0-zyShz.js → sas-B-GmRmDQ.js} +1 -1
  79. package/ui/dist/assets/{scss-B3v_ewrQ.js → scss-oB6C5dt1.js} +1 -1
  80. package/ui/dist/assets/{shellscript-CAX3EBQC.js → shellscript-BDueQ3Pr.js} +1 -1
  81. package/ui/dist/assets/{shellsession-BbCaojs-.js → shellsession-snwU-yjZ.js} +1 -1
  82. package/ui/dist/assets/{soy-C_L1i27o.js → soy-1UzSzCS_.js} +1 -1
  83. package/ui/dist/assets/{sql-DfQzhZMD.js → sql-_8rHrLe0.js} +1 -1
  84. package/ui/dist/assets/{stata-wKRkbYfj.js → stata-BFBQv4-R.js} +1 -1
  85. package/ui/dist/assets/{surrealql-r1cCvWUu.js → surrealql-C-hPyRoi.js} +1 -1
  86. package/ui/dist/assets/{svelte-QE15YUDB.js → svelte-COzcYWbs.js} +1 -1
  87. package/ui/dist/assets/{templ-DPDxNWwC.js → templ-CIF5RvVU.js} +1 -1
  88. package/ui/dist/assets/{tex-BFuhjY1V.js → tex-DiIjyX_E.js} +1 -1
  89. package/ui/dist/assets/{ts-tags-BUMMUMV3.js → ts-tags-Bb-cll5a.js} +1 -1
  90. package/ui/dist/assets/{tsx-BL1rdCtn.js → tsx-B_TJQczU.js} +1 -1
  91. package/ui/dist/assets/{twig-CFLDJ71e.js → twig-B2bev-dI.js} +1 -1
  92. package/ui/dist/assets/{typescript-Duv2diAv.js → typescript-Ii543U_f.js} +1 -1
  93. package/ui/dist/assets/{vue-DkO776Yn.js → vue-CGYOL_ei.js} +1 -1
  94. package/ui/dist/assets/{vue-html-9RYUFMtL.js → vue-html-DJiTs0pH.js} +1 -1
  95. package/ui/dist/assets/{vue-vine-C3meWGQa.js → vue-vine-BWgBzdhj.js} +1 -1
  96. package/ui/dist/assets/{xml-ClHTQj3q.js → xml-wwBhpto-.js} +1 -1
  97. package/ui/dist/assets/{xsl-D1Lt0UF-.js → xsl-ia_QpSvH.js} +1 -1
  98. package/ui/dist/assets/{yaml-BhUwnbwF.js → yaml-DPR0vpXc.js} +1 -1
  99. package/ui/dist/index.html +2 -2
  100. package/ui/dist/assets/index-CDxgCCFg.js +0 -1164
  101. package/ui/dist/assets/index-FuUBXrz0.css +0 -1
package/dist/cli.js CHANGED
@@ -1990,7 +1990,7 @@ var require_indexes = __commonJS((exports, module) => {
1990
1990
 
1991
1991
  // node_modules/.bun/thread-stream@4.2.0/node_modules/thread-stream/index.js
1992
1992
  var require_thread_stream = __commonJS((exports, module) => {
1993
- var __dirname = "/Users/juliantorres/GitLocal/Play/caret/node_modules/.bun/thread-stream@4.2.0/node_modules/thread-stream";
1993
+ var __dirname = "/Users/juliantorres/GitLocal/Play/caret/trunk/node_modules/.bun/thread-stream@4.2.0/node_modules/thread-stream";
1994
1994
  var { version: version2 } = require_package();
1995
1995
  var { EventEmitter } = __require("events");
1996
1996
  var { Worker } = __require("worker_threads");
@@ -2506,7 +2506,7 @@ var require_thread_stream = __commonJS((exports, module) => {
2506
2506
 
2507
2507
  // node_modules/.bun/pino@10.3.1/node_modules/pino/lib/transport.js
2508
2508
  var require_transport = __commonJS((exports, module) => {
2509
- var __dirname = "/Users/juliantorres/GitLocal/Play/caret/node_modules/.bun/pino@10.3.1/node_modules/pino/lib";
2509
+ var __dirname = "/Users/juliantorres/GitLocal/Play/caret/trunk/node_modules/.bun/pino@10.3.1/node_modules/pino/lib";
2510
2510
  var { createRequire } = __require("module");
2511
2511
  var { existsSync: existsSync4 } = __require("fs");
2512
2512
  var getCallers = require_caller();
@@ -5803,8 +5803,8 @@ __export(exports_dist, {
5803
5803
  });
5804
5804
  import { styleText as styleText2, stripVTControlCharacters } from "util";
5805
5805
  import process$1 from "process";
5806
- import { existsSync as existsSync10, lstatSync, readdirSync as readdirSync6 } from "fs";
5807
- import { dirname as dirname8, join as join13 } from "path";
5806
+ import { existsSync as existsSync10, lstatSync, readdirSync as readdirSync5 } from "fs";
5807
+ import { dirname as dirname8, join as join14 } from "path";
5808
5808
  function isUnicodeSupported() {
5809
5809
  if (process$1.platform !== "win32") {
5810
5810
  return process$1.env.TERM !== "linux";
@@ -6517,8 +6517,8 @@ ${n3}
6517
6517
  let i;
6518
6518
  existsSync10(t2) ? lstatSync(t2).isDirectory() && (!e.directory || t2.endsWith("/")) ? i = t2 : i = dirname8(t2) : i = dirname8(t2);
6519
6519
  const c2 = t2.length > 1 && t2.endsWith("/") ? t2.slice(0, -1) : t2;
6520
- return readdirSync6(i).map((r2) => {
6521
- const n3 = join13(i, r2), m3 = lstatSync(n3);
6520
+ return readdirSync5(i).map((r2) => {
6521
+ const n3 = join14(i, r2), m3 = lstatSync(n3);
6522
6522
  return {
6523
6523
  name: r2,
6524
6524
  path: n3,
@@ -24178,6 +24178,63 @@ config(en_default());
24178
24178
  var DEFAULT_PORT = 42718;
24179
24179
  var VANITY_HOST = "caret.localhost";
24180
24180
  var HOOK_TIMEOUT_S = 3900;
24181
+ var EXCERPT_RADIUS = 30;
24182
+ var KNOWN_FILE_EXTENSIONS = new Set([
24183
+ "ts",
24184
+ "tsx",
24185
+ "mts",
24186
+ "cts",
24187
+ "js",
24188
+ "jsx",
24189
+ "mjs",
24190
+ "cjs",
24191
+ "svelte",
24192
+ "vue",
24193
+ "json",
24194
+ "jsonc",
24195
+ "css",
24196
+ "scss",
24197
+ "less",
24198
+ "html",
24199
+ "htm",
24200
+ "xml",
24201
+ "svg",
24202
+ "md",
24203
+ "mdx",
24204
+ "py",
24205
+ "rb",
24206
+ "rs",
24207
+ "go",
24208
+ "java",
24209
+ "kt",
24210
+ "c",
24211
+ "h",
24212
+ "cc",
24213
+ "cpp",
24214
+ "hpp",
24215
+ "sh",
24216
+ "bash",
24217
+ "zsh",
24218
+ "toml",
24219
+ "yaml",
24220
+ "yml",
24221
+ "ini",
24222
+ "sql",
24223
+ "graphql",
24224
+ "gql",
24225
+ "php",
24226
+ "swift",
24227
+ "dart",
24228
+ "txt",
24229
+ "lock",
24230
+ "cfg",
24231
+ "conf"
24232
+ ]);
24233
+ function hasKnownFileExtension(path) {
24234
+ const base = path.split("/").pop() ?? "";
24235
+ const dot = base.lastIndexOf(".");
24236
+ return dot > 0 && KNOWN_FILE_EXTENSIONS.has(base.slice(dot + 1).toLowerCase());
24237
+ }
24181
24238
  var BUN_SOCKET_IDLE_CAP_S = 255;
24182
24239
  var IDLE_TIMEOUT_HEADROOM_S = 5;
24183
24240
  var MAX_HEARTBEAT_MS = (BUN_SOCKET_IDLE_CAP_S - IDLE_TIMEOUT_HEADROOM_S) * 1000;
@@ -24294,7 +24351,7 @@ async function loadUiAssets() {
24294
24351
  // package.json
24295
24352
  var package_default = {
24296
24353
  name: "@macintacos/caret",
24297
- version: "0.8.2",
24354
+ version: "0.10.0",
24298
24355
  license: "MIT",
24299
24356
  type: "module",
24300
24357
  description: "A Claude Code plugin that replaces the terminal plan-approval prompt with a local web UI for inline review, annotation, and approval.",
@@ -24323,7 +24380,7 @@ var package_default = {
24323
24380
  ".claude-plugin/plugin.json"
24324
24381
  ],
24325
24382
  scripts: {
24326
- test: "bun test --conditions browser",
24383
+ test: "bun test --conditions browser --timeout 30000",
24327
24384
  lint: "mise run lint",
24328
24385
  format: "mise run format",
24329
24386
  build: "mise run build"
@@ -24359,6 +24416,7 @@ var package_default = {
24359
24416
  "@lezer/highlight": "^1.2.3",
24360
24417
  "@opencode-ai/plugin": "^1.16.2",
24361
24418
  "@pierre/diffs": "1.2.7",
24419
+ "@pierre/trees": "1.0.0-beta.6",
24362
24420
  "bits-ui": "^2.18.1",
24363
24421
  clsx: "^2.1.1",
24364
24422
  commander: "^15.0.0",
@@ -25160,9 +25218,10 @@ var PlanInputSchema = exports_external.object({
25160
25218
  cwd: exports_external.string().optional(),
25161
25219
  title: exports_external.string().optional(),
25162
25220
  plan: exports_external.string().optional(),
25163
- planFilePath: exports_external.string().optional()
25221
+ planFilePath: exports_external.string().optional(),
25222
+ cmux: exports_external.object({ workspaceId: exports_external.string().min(1), surfaceId: exports_external.string().min(1) }).optional().catch(undefined)
25164
25223
  }).catch({});
25165
- var MAX_FILE_REFS = 200;
25224
+ var MAX_FILE_REFS = 1000;
25166
25225
  var FileRefsBodySchema = exports_external.object({ paths: exports_external.array(exports_external.string()).catch([]) }).catch({ paths: [] });
25167
25226
  var BehaviorSchema = exports_external.enum(["allow", "deny"]);
25168
25227
  var ResolveBodySchema = exports_external.object({
@@ -25226,12 +25285,41 @@ async function parseBody(req, schema) {
25226
25285
  return schema.parse(await req.json().catch(() => ({})));
25227
25286
  }
25228
25287
 
25288
+ // src/lib/cmux.ts
25289
+ function readCmuxPane(env = process.env) {
25290
+ const workspaceId = env.CMUX_WORKSPACE_ID;
25291
+ const surfaceId = env.CMUX_SURFACE_ID;
25292
+ if (!workspaceId || !surfaceId)
25293
+ return;
25294
+ return { workspaceId, surfaceId };
25295
+ }
25296
+ function markPaneRead(pane, deps = {}) {
25297
+ const spawn = deps.spawn ?? Bun.spawn;
25298
+ const log = deps.log ?? noopLogger;
25299
+ try {
25300
+ spawn([
25301
+ "cmux",
25302
+ "mark-notification-read",
25303
+ "--workspace",
25304
+ pane.workspaceId,
25305
+ "--surface",
25306
+ pane.surfaceId
25307
+ ], { stdio: ["ignore", "ignore", "ignore"] }).unref();
25308
+ } catch (err) {
25309
+ log.warn("spawn", "unread mark clear failed", { reason: String(err) });
25310
+ }
25311
+ }
25312
+
25313
+ // src/plan/directory.ts
25314
+ import { readdir as readdir2 } from "fs/promises";
25315
+ import { join as join8, relative as relative3, sep as sep2 } from "path";
25316
+
25229
25317
  // src/plan/excerpt.ts
25230
- import { readdirSync as readdirSync3, readFileSync as readFileSync7, realpathSync, statSync as statSync2 } from "fs";
25318
+ import { readFileSync as readFileSync7 } from "fs";
25319
+ import { readdir, realpath, stat } from "fs/promises";
25231
25320
  import { extname, isAbsolute, join as join7, relative as relative2, resolve, sep } from "path";
25232
- var EXCERPT_RADIUS = 6;
25233
- var EXCERPT_HEAD_LINES = 13;
25234
- var MAX_EXCERPT_BYTES = 2 * 1024 * 1024;
25321
+ var EXCERPT_HEAD_LINES = 60;
25322
+ var MAX_EXCERPT_BYTES = 10 * 1024 * 1024;
25235
25323
  var MAX_SCAN_ENTRIES = 5000;
25236
25324
  var SKIP_DIRS = new Set(["node_modules", "dist", "build", "coverage", "out"]);
25237
25325
  var EXT_LANGUAGE = {
@@ -25285,36 +25373,32 @@ function languageForPath(path) {
25285
25373
  return EXT_LANGUAGE[extname(path).toLowerCase()] ?? "text";
25286
25374
  }
25287
25375
  function safeRealpath(path) {
25288
- try {
25289
- return realpathSync(path);
25290
- } catch {
25291
- return null;
25292
- }
25376
+ return realpath(path).catch(() => null);
25293
25377
  }
25294
- function containedFile(cwdReal, abs) {
25295
- const real = safeRealpath(abs);
25378
+ function safeStat(path) {
25379
+ return stat(path).catch(() => null);
25380
+ }
25381
+ async function contained(cwdReal, abs) {
25382
+ const real = await safeRealpath(abs);
25296
25383
  if (real === null)
25297
25384
  return null;
25298
25385
  if (real !== cwdReal && !real.startsWith(cwdReal + sep))
25299
25386
  return null;
25300
- try {
25301
- if (!statSync2(real).isFile())
25302
- return null;
25303
- } catch {
25387
+ const stats = await safeStat(real);
25388
+ if (stats === null)
25304
25389
  return null;
25305
- }
25306
- return real;
25390
+ if (stats.isFile())
25391
+ return { path: real, kind: "file" };
25392
+ return stats.isDirectory() ? { path: real, kind: "directory" } : null;
25307
25393
  }
25308
- function basenameSearch(cwdReal, name) {
25309
- if (name === "")
25310
- return null;
25394
+ async function basenameSearch(cwdReal, name) {
25311
25395
  let scanned = 0;
25312
25396
  const queue = [cwdReal];
25313
25397
  while (queue.length > 0) {
25314
25398
  const currentDir = queue.shift();
25315
25399
  let entries;
25316
25400
  try {
25317
- entries = readdirSync3(currentDir, { withFileTypes: true });
25401
+ entries = await readdir(currentDir, { withFileTypes: true });
25318
25402
  } catch {
25319
25403
  continue;
25320
25404
  }
@@ -25331,49 +25415,73 @@ function basenameSearch(cwdReal, name) {
25331
25415
  }
25332
25416
  return null;
25333
25417
  }
25334
- function resolveFileInCwd(cwd, candidate) {
25418
+ async function resolveInCwd(cwd, candidate) {
25335
25419
  if (cwd === "" || !isAbsolute(cwd))
25336
25420
  return null;
25337
- const cwdReal = safeRealpath(cwd);
25421
+ const cwdReal = await safeRealpath(cwd);
25338
25422
  if (cwdReal === null)
25339
25423
  return null;
25340
25424
  const cleaned = candidate.trim();
25341
25425
  if (cleaned === "")
25342
25426
  return null;
25343
- const direct = containedFile(cwdReal, resolve(cwdReal, cleaned));
25427
+ const direct = await contained(cwdReal, resolve(cwdReal, cleaned));
25344
25428
  if (direct !== null)
25345
25429
  return direct;
25346
- const name = cleaned.split("/").pop() ?? "";
25347
- return basenameSearch(cwdReal, name);
25348
- }
25349
- function readFileExcerpt(cwd, candidate, line) {
25350
- const abs = resolveFileInCwd(cwd, candidate);
25351
- if (abs === null)
25352
- return null;
25353
- let size;
25354
- try {
25355
- size = statSync2(abs).size;
25356
- } catch {
25357
- return null;
25358
- }
25359
- if (size > MAX_EXCERPT_BYTES)
25430
+ if (cleaned.includes("/") || !hasKnownFileExtension(cleaned))
25360
25431
  return null;
25432
+ const found = await basenameSearch(cwdReal, cleaned);
25433
+ return found === null ? null : { path: found, kind: "file" };
25434
+ }
25435
+ async function isFileTooLargeToPreview(cwd, candidate) {
25436
+ const hit = await resolveInCwd(cwd, candidate);
25437
+ if (hit === null || hit.kind !== "file")
25438
+ return false;
25439
+ const stats = await safeStat(hit.path);
25440
+ return stats !== null && stats.size > MAX_EXCERPT_BYTES;
25441
+ }
25442
+ var memo = null;
25443
+ function statIdentity(stats) {
25444
+ return `${stats.size}:${stats.mtimeMs}:${stats.ino}`;
25445
+ }
25446
+ function splitLines(content) {
25447
+ const lines = content.split(`
25448
+ `);
25449
+ if (lines.length > 1 && lines[lines.length - 1] === "")
25450
+ lines.pop();
25451
+ return lines;
25452
+ }
25453
+ function readLines(abs, stats) {
25454
+ const identity = statIdentity(stats);
25455
+ if (memo !== null && memo.path === abs && memo.identity === identity)
25456
+ return memo.lines;
25361
25457
  let content;
25362
25458
  try {
25363
25459
  content = readFileSync7(abs, "utf-8");
25364
25460
  } catch {
25365
25461
  return null;
25366
25462
  }
25367
- if (content.includes("\x00"))
25463
+ const lines = content.includes("\x00") ? null : splitLines(content);
25464
+ memo = { path: abs, identity, lines };
25465
+ return lines;
25466
+ }
25467
+ async function readFileExcerpt(cwd, candidate, line, range) {
25468
+ const hit = await resolveInCwd(cwd, candidate);
25469
+ if (hit === null || hit.kind !== "file")
25470
+ return null;
25471
+ const abs = hit.path;
25472
+ const stats = await safeStat(abs);
25473
+ if (stats === null || stats.size > MAX_EXCERPT_BYTES)
25474
+ return null;
25475
+ const allLines = readLines(abs, stats);
25476
+ if (allLines === null)
25368
25477
  return null;
25369
- const allLines = content.split(`
25370
- `);
25371
- if (allLines.length > 1 && allLines[allLines.length - 1] === "")
25372
- allLines.pop();
25373
25478
  const totalLines = allLines.length;
25374
25479
  let startLine;
25375
25480
  let endLine;
25376
- if (line !== undefined && line >= 1) {
25481
+ if (range !== undefined) {
25482
+ startLine = Math.min(Math.max(1, range.start), totalLines);
25483
+ endLine = Math.min(totalLines, Math.max(range.end, startLine));
25484
+ } else if (line !== undefined && line >= 1) {
25377
25485
  const target = Math.min(line, totalLines);
25378
25486
  startLine = Math.max(1, target - EXCERPT_RADIUS);
25379
25487
  endLine = Math.min(totalLines, target + EXCERPT_RADIUS);
@@ -25381,7 +25489,7 @@ function readFileExcerpt(cwd, candidate, line) {
25381
25489
  startLine = 1;
25382
25490
  endLine = Math.min(totalLines, EXCERPT_HEAD_LINES);
25383
25491
  }
25384
- const cwdReal = safeRealpath(cwd) ?? cwd;
25492
+ const cwdReal = await safeRealpath(cwd) ?? cwd;
25385
25493
  return {
25386
25494
  path: relative2(cwdReal, abs),
25387
25495
  language: languageForPath(abs),
@@ -25392,6 +25500,62 @@ function readFileExcerpt(cwd, candidate, line) {
25392
25500
  };
25393
25501
  }
25394
25502
 
25503
+ // src/plan/directory.ts
25504
+ var MAX_DIR_ENTRIES = 500;
25505
+ var MAX_DIR_DEPTH = 10;
25506
+ function byKindThenName(a, b) {
25507
+ if (a.kind !== b.kind)
25508
+ return a.kind === "directory" ? -1 : 1;
25509
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
25510
+ }
25511
+ function entryFor(name, kind) {
25512
+ if (kind === "file")
25513
+ return { name, kind };
25514
+ return SKIP_DIRS.has(name) || name.startsWith(".") ? { name, kind, skipped: true } : { name, kind };
25515
+ }
25516
+ async function listDirectory(cwd, root, path) {
25517
+ const rootHit = await resolveInCwd(cwd, root);
25518
+ if (rootHit === null || rootHit.kind !== "directory")
25519
+ return null;
25520
+ const target = path === "" ? rootHit : await resolveInCwd(cwd, path);
25521
+ if (target === null || target.kind !== "directory")
25522
+ return null;
25523
+ const rel = relative3(rootHit.path, target.path);
25524
+ if (rel === ".." || rel.startsWith(`..${sep2}`))
25525
+ return null;
25526
+ if (rel !== "" && rel.split(sep2).length > MAX_DIR_DEPTH)
25527
+ return null;
25528
+ let dirents;
25529
+ try {
25530
+ dirents = await readdir2(target.path, { withFileTypes: true });
25531
+ } catch {
25532
+ return null;
25533
+ }
25534
+ const entries = [];
25535
+ const links = [];
25536
+ for (const d of dirents) {
25537
+ if (d.isFile())
25538
+ entries.push(entryFor(d.name, "file"));
25539
+ else if (d.isDirectory())
25540
+ entries.push(entryFor(d.name, "directory"));
25541
+ else if (d.isSymbolicLink())
25542
+ links.push(d);
25543
+ }
25544
+ const targets = await Promise.all(links.map((d) => resolveInCwd(cwd, join8(target.path, d.name))));
25545
+ targets.forEach((hit, i) => {
25546
+ const link = links[i];
25547
+ if (hit !== null && link !== undefined)
25548
+ entries.push(entryFor(link.name, hit.kind));
25549
+ });
25550
+ entries.sort(byKindThenName);
25551
+ const cwdReal = await safeRealpath(cwd) ?? cwd;
25552
+ return {
25553
+ path: relative3(cwdReal, target.path),
25554
+ entries: entries.slice(0, MAX_DIR_ENTRIES),
25555
+ total: entries.length
25556
+ };
25557
+ }
25558
+
25395
25559
  // src/review/decisions.ts
25396
25560
  function createDecisions(log = noopLogger) {
25397
25561
  const pending = new Map;
@@ -25438,12 +25602,12 @@ function createDecisions(log = noopLogger) {
25438
25602
  import { randomBytes } from "crypto";
25439
25603
 
25440
25604
  // src/plan/canonical-file.ts
25441
- import { appendFileSync, existsSync as existsSync4, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
25605
+ import { appendFileSync, existsSync as existsSync4, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
25442
25606
  function guardedPlanFileWrite(planFilePath, log, failMsg, write) {
25443
25607
  try {
25444
25608
  if (!planFilePath.endsWith(".md"))
25445
25609
  return;
25446
- if (!existsSync4(planFilePath) || !statSync3(planFilePath).isFile())
25610
+ if (!existsSync4(planFilePath) || !statSync2(planFilePath).isFile())
25447
25611
  return;
25448
25612
  write(planFilePath);
25449
25613
  } catch (err) {
@@ -25468,24 +25632,24 @@ import { createHash as createHash2 } from "crypto";
25468
25632
  import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync2, renameSync, writeFileSync as writeFileSync3 } from "fs";
25469
25633
  import { mkdtemp, rm } from "fs/promises";
25470
25634
  import { tmpdir } from "os";
25471
- import { dirname as dirname5, join as join8 } from "path";
25472
- var RUMDL_VERSION = "0.2.37";
25635
+ import { dirname as dirname5, join as join9 } from "path";
25636
+ var RUMDL_VERSION = "0.2.47";
25473
25637
  var ASSETS = {
25474
25638
  "darwin-arm64": {
25475
25639
  url: `https://github.com/rvben/rumdl/releases/download/v${RUMDL_VERSION}/rumdl-v${RUMDL_VERSION}-aarch64-apple-darwin.tar.gz`,
25476
- sha256: "ac5f31077c492c3303d27264d8d8840b1279cb5a8cd62f863a2045e1427a6c79"
25640
+ sha256: "10dc23e994327ecc6e98d285802fcad1174608f203062cb18bac55b51976e0cb"
25477
25641
  },
25478
25642
  "darwin-x64": {
25479
25643
  url: `https://github.com/rvben/rumdl/releases/download/v${RUMDL_VERSION}/rumdl-v${RUMDL_VERSION}-x86_64-apple-darwin.tar.gz`,
25480
- sha256: "7cdfbc6ac896bfe433938a050e0f7b4dfc9ebc78d17ec8f4f872580b1fe06eba"
25644
+ sha256: "9a2bf0c692310e0469fdc66fa83f74579cf0331cc1f9b306b25849cffd146c0f"
25481
25645
  },
25482
25646
  "linux-arm64": {
25483
25647
  url: `https://github.com/rvben/rumdl/releases/download/v${RUMDL_VERSION}/rumdl-v${RUMDL_VERSION}-aarch64-unknown-linux-musl.tar.gz`,
25484
- sha256: "2cee94dbc4e577c54869723245be548589e80a5e5a93fe25a3cc535d51e1ac6d"
25648
+ sha256: "b5ab4207b92c0bf0ea6d8946f15545c787e8cf6ecf2176d9e7f26c43aead86a8"
25485
25649
  },
25486
25650
  "linux-x64": {
25487
25651
  url: `https://github.com/rvben/rumdl/releases/download/v${RUMDL_VERSION}/rumdl-v${RUMDL_VERSION}-x86_64-unknown-linux-musl.tar.gz`,
25488
- sha256: "0999d31c6f1429f0b3b5ed86d3ebb2768e5b4a16b373a51a41a7af2d7eb43b7c"
25652
+ sha256: "bd4049763e93d6c25420ec086dee6690af31efc85af463c3ecf5d686a5fcf762"
25489
25653
  }
25490
25654
  };
25491
25655
  function rumdlAsset(platform = process.platform, arch = process.arch) {
@@ -25501,6 +25665,8 @@ code-blocks = false
25501
25665
  tables = false
25502
25666
  reflow = true
25503
25667
  reflow-mode = "normalize"
25668
+ reflow-length-exemptions = true
25669
+ ignore-link-urls = true
25504
25670
  `;
25505
25671
  async function downloadRumdl(binPath, asset, fetchImpl = fetch) {
25506
25672
  const res = await fetchImpl(asset.url);
@@ -25509,14 +25675,14 @@ async function downloadRumdl(binPath, asset, fetchImpl = fetch) {
25509
25675
  if (actual !== asset.sha256) {
25510
25676
  throw new Error(`rumdl: checksum mismatch for ${asset.url} (expected ${asset.sha256}, got ${actual})`);
25511
25677
  }
25512
- const work = await mkdtemp(join8(tmpdir(), "caret-rumdl-"));
25678
+ const work = await mkdtemp(join9(tmpdir(), "caret-rumdl-"));
25513
25679
  try {
25514
- const archive = join8(work, "rumdl.tar.gz");
25680
+ const archive = join9(work, "rumdl.tar.gz");
25515
25681
  await Bun.write(archive, bytes);
25516
25682
  const tar = Bun.spawn(["tar", "-xzf", archive, "-C", work], { stderr: "pipe" });
25517
25683
  if (await tar.exited !== 0)
25518
25684
  throw new Error(`rumdl: failed to extract ${asset.url}`);
25519
- const extracted = join8(work, "rumdl");
25685
+ const extracted = join9(work, "rumdl");
25520
25686
  if (!existsSync5(extracted))
25521
25687
  throw new Error(`rumdl: binary not found in ${asset.url}`);
25522
25688
  mkdirSync2(dirname5(binPath), { recursive: true });
@@ -25549,8 +25715,9 @@ async function ensureRumdl(install = installPinnedRumdl) {
25549
25715
  const config2 = rumdlConfig();
25550
25716
  writeFileSync3(config2, RUMDL_CONFIG);
25551
25717
  const override = process.env.CARET_RUMDL_BIN?.trim();
25552
- if (override)
25718
+ if (override && await installedVersion(override) === RUMDL_VERSION) {
25553
25719
  return { bin: override, config: config2, installed: false };
25720
+ }
25554
25721
  const bin = rumdlBin();
25555
25722
  if (await installedVersion(bin) === RUMDL_VERSION)
25556
25723
  return { bin, config: config2, installed: false };
@@ -25633,6 +25800,7 @@ async function routeIncomingPlan(input, store, log = noopLogger) {
25633
25800
  await store.update(latest.id, (r) => {
25634
25801
  r.versions.push({ version: version2, plan, annotations: [], createdAt: now });
25635
25802
  r.status = "pending";
25803
+ r.cmux = input.cmux ?? r.cmux;
25636
25804
  r.decision = undefined;
25637
25805
  });
25638
25806
  log.info("review", `review appended: ${shortId(latest.id)} v${version2}`, {
@@ -25659,6 +25827,7 @@ async function routeIncomingPlan(input, store, log = noopLogger) {
25659
25827
  title: input.title?.trim() || deriveTitle(plan),
25660
25828
  status: "pending",
25661
25829
  planEpoch,
25830
+ cmux: input.cmux,
25662
25831
  versions: [{ version: 1, plan, annotations: [], createdAt: now }],
25663
25832
  createdAt: now,
25664
25833
  updatedAt: now
@@ -25745,10 +25914,11 @@ function resolveOptions(opts) {
25745
25914
  approveVariants: opts.approveVariants,
25746
25915
  source: opts.source,
25747
25916
  diagnostics: opts.diagnostics,
25917
+ markPaneRead: opts.markPaneRead ?? ((pane) => markPaneRead(pane, { log: opts.log })),
25748
25918
  log: opts.log ?? noopLogger
25749
25919
  };
25750
25920
  }
25751
- var ID_ROUTE_RE = /^\/api\/reviews\/([^/]+)(\/decision|\/resolve|\/draft|\/expire|\/file-refs|\/file)?$/;
25921
+ var ID_ROUTE_RE = /^\/api\/reviews\/([^/]+)(\/decision|\/resolve|\/draft|\/expire|\/seen|\/file-refs|\/file|\/dir)?$/;
25752
25922
  function matchIdRoute(path) {
25753
25923
  const m = path.match(ID_ROUTE_RE);
25754
25924
  if (!m)
@@ -25807,6 +25977,9 @@ function createServer(opts) {
25807
25977
  function notFound() {
25808
25978
  return new Response("not found", { status: 404 });
25809
25979
  }
25980
+ function tooLarge() {
25981
+ return new Response("too large", { status: 413 });
25982
+ }
25810
25983
  function handleHealth() {
25811
25984
  const body = {
25812
25985
  ...IDENTITY,
@@ -25911,27 +26084,59 @@ function createServer(opts) {
25911
26084
  return notFound();
25912
26085
  const { paths } = await parseBody(req, FileRefsBodySchema);
25913
26086
  const seen = new Set;
25914
- const resolved = [];
25915
- for (const p of paths.slice(0, MAX_FILE_REFS)) {
26087
+ const unique = [];
26088
+ for (const p of paths) {
25916
26089
  if (seen.has(p))
25917
26090
  continue;
25918
26091
  seen.add(p);
25919
- if (resolveFileInCwd(r.cwd, p) !== null)
25920
- resolved.push(p);
26092
+ if (unique.push(p) === MAX_FILE_REFS)
26093
+ break;
25921
26094
  }
25922
- log.debug("request", `file-refs resolved: ${resolved.length}/${seen.size}`, { reviewId: id });
26095
+ const entries = await Promise.all(unique.map(async (p) => {
26096
+ const hit = await resolveInCwd(r.cwd, p);
26097
+ return hit === null ? null : [p, hit.kind];
26098
+ }));
26099
+ const resolved = Object.fromEntries(entries.filter((e) => e !== null));
26100
+ log.debug("request", `file-refs resolved: ${Object.keys(resolved).length}/${unique.length}`, {
26101
+ reviewId: id,
26102
+ requested: paths.length,
26103
+ directories: Object.values(resolved).filter((k) => k === "directory").length
26104
+ });
25923
26105
  return Response.json({ resolved });
25924
26106
  }
25925
- function handleFileExcerpt(req, id) {
26107
+ async function handleFileExcerpt(req, id) {
25926
26108
  const r = store.get(id);
25927
26109
  if (!r)
25928
26110
  return notFound();
25929
26111
  const params = new URL(req.url).searchParams;
25930
26112
  const path = params.get("path") ?? "";
25931
- const lineRaw = params.get("line");
25932
- const line = lineRaw !== null && /^\d+$/.test(lineRaw) ? Number(lineRaw) : undefined;
25933
- const excerpt = readFileExcerpt(r.cwd, path, line);
25934
- return excerpt ? Response.json(excerpt) : notFound();
26113
+ const num = (key) => {
26114
+ const raw = params.get(key);
26115
+ return raw !== null && /^\d+$/.test(raw) ? Number(raw) : undefined;
26116
+ };
26117
+ const line = num("line");
26118
+ const start = num("start");
26119
+ const end = num("end");
26120
+ const range = start !== undefined && end !== undefined ? { start, end } : undefined;
26121
+ const excerpt = await readFileExcerpt(r.cwd, path, line, range);
26122
+ if (excerpt)
26123
+ return Response.json(excerpt);
26124
+ return await isFileTooLargeToPreview(r.cwd, path) ? tooLarge() : notFound();
26125
+ }
26126
+ async function handleDirListing(req, id) {
26127
+ const r = store.get(id);
26128
+ if (!r)
26129
+ return notFound();
26130
+ const params = new URL(req.url).searchParams;
26131
+ const listing = await listDirectory(r.cwd, params.get("root") ?? "", params.get("path") ?? "");
26132
+ if (listing === null)
26133
+ return notFound();
26134
+ log.debug("request", "dir listed", {
26135
+ reviewId: id,
26136
+ total: listing.total,
26137
+ returned: listing.entries.length
26138
+ });
26139
+ return Response.json(listing);
25935
26140
  }
25936
26141
  async function handleDecision(id) {
25937
26142
  const inMem = store.get(id);
@@ -25989,6 +26194,10 @@ function createServer(opts) {
25989
26194
  await store.update(id, (r) => {
25990
26195
  r.decision = decision;
25991
26196
  r.status = decision.behavior === "allow" ? "approved" : "rejected";
26197
+ const general = r.generalCommentDraft?.trim();
26198
+ if (general && decision.behavior === "deny" && decision.feedback?.includes(general)) {
26199
+ currentVersion(r).generalComment = general;
26200
+ }
25992
26201
  r.generalCommentDraft = "";
25993
26202
  currentVersion(r).composerScratches = [];
25994
26203
  });
@@ -26001,6 +26210,8 @@ function createServer(opts) {
26001
26210
  });
26002
26211
  }
26003
26212
  }
26213
+ if (existing.cmux)
26214
+ cfg.markPaneRead(existing.cmux);
26004
26215
  setTimeout(() => resolveDecision(id, decision), 0);
26005
26216
  log.info("resolve", `review ${shortId(id)} resolved: ${decision.behavior}`, {
26006
26217
  reviewId: id,
@@ -26021,6 +26232,14 @@ function createServer(opts) {
26021
26232
  });
26022
26233
  return Response.json({ ok: true });
26023
26234
  }
26235
+ function handleSeen(id) {
26236
+ const existing = store.get(id);
26237
+ if (!existing)
26238
+ return notFound();
26239
+ if (existing.cmux)
26240
+ cfg.markPaneRead(existing.cmux);
26241
+ return new Response(null, { status: 204 });
26242
+ }
26024
26243
  async function dispatch(req, method, path) {
26025
26244
  if (method === "GET" && path === "/api/health")
26026
26245
  return handleHealth();
@@ -26047,6 +26266,8 @@ function createServer(opts) {
26047
26266
  return handleGetReview(id);
26048
26267
  if (method === "GET" && sub === "/file")
26049
26268
  return handleFileExcerpt(req, id);
26269
+ if (method === "GET" && sub === "/dir")
26270
+ return handleDirListing(req, id);
26050
26271
  if (method === "POST" && sub === "/file-refs")
26051
26272
  return handleFileRefs(req, id);
26052
26273
  if (method === "GET" && sub === "/decision")
@@ -26057,6 +26278,8 @@ function createServer(opts) {
26057
26278
  return handleResolve(req, id);
26058
26279
  if (method === "POST" && sub === "/expire")
26059
26280
  return handleExpire(id);
26281
+ if (method === "POST" && sub === "/seen")
26282
+ return handleSeen(id);
26060
26283
  }
26061
26284
  if (method === "GET") {
26062
26285
  const asset = handleAsset(path);
@@ -26138,8 +26361,8 @@ function createServer(opts) {
26138
26361
  }
26139
26362
 
26140
26363
  // src/review/store.ts
26141
- import { readdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
26142
- import { join as join9 } from "path";
26364
+ import { readdir as readdir3, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
26365
+ import { join as join10 } from "path";
26143
26366
  function createStore(dir, log = noopLogger) {
26144
26367
  const reviews = new Map;
26145
26368
  const epochs = new Map;
@@ -26148,7 +26371,7 @@ function createStore(dir, log = noopLogger) {
26148
26371
  const prev = writeChains.get(review.id) ?? Promise.resolve();
26149
26372
  const next = prev.catch(() => {}).then(async () => {
26150
26373
  ensureStateDir(dir);
26151
- await writeFile2(join9(dir, `${review.id}.json`), JSON.stringify(review, null, 2), {
26374
+ await writeFile2(join10(dir, `${review.id}.json`), JSON.stringify(review, null, 2), {
26152
26375
  mode: 384
26153
26376
  });
26154
26377
  log.debug("store", `review persisted: ${shortId(review.id)}`, { reviewId: review.id });
@@ -26198,7 +26421,7 @@ function createStore(dir, log = noopLogger) {
26198
26421
  return review;
26199
26422
  },
26200
26423
  async persisted(id) {
26201
- return await readJsonFile(join9(dir, `${id}.json`)) ?? undefined;
26424
+ return await readJsonFile(join10(dir, `${id}.json`)) ?? undefined;
26202
26425
  },
26203
26426
  size() {
26204
26427
  return reviews.size;
@@ -26215,7 +26438,7 @@ function createStore(dir, log = noopLogger) {
26215
26438
  async rehydrate() {
26216
26439
  let files;
26217
26440
  try {
26218
- files = await readdir(dir);
26441
+ files = await readdir3(dir);
26219
26442
  } catch {
26220
26443
  log.debug("store", "no reviews dir; nothing to rehydrate");
26221
26444
  return;
@@ -26225,7 +26448,7 @@ function createStore(dir, log = noopLogger) {
26225
26448
  if (!file2.endsWith(".json"))
26226
26449
  continue;
26227
26450
  try {
26228
- const review = JSON.parse(await readFile3(join9(dir, file2), "utf-8"));
26451
+ const review = JSON.parse(await readFile3(join10(dir, file2), "utf-8"));
26229
26452
  if (isUnresolved(review.status)) {
26230
26453
  reviews.set(review.id, review);
26231
26454
  loaded++;
@@ -26324,8 +26547,8 @@ import { existsSync as existsSync7 } from "fs";
26324
26547
  import { release } from "os";
26325
26548
 
26326
26549
  // src/discovery.ts
26327
- import { readdirSync as readdirSync4, statSync as statSync4 } from "fs";
26328
- import { basename as basename2, join as join10 } from "path";
26550
+ import { readdirSync as readdirSync3, statSync as statSync3 } from "fs";
26551
+ import { basename as basename2, join as join11 } from "path";
26329
26552
  var CARET_BIN = "caret-native";
26330
26553
  async function safe(build) {
26331
26554
  try {
@@ -26525,7 +26748,7 @@ function listReviewFiles() {
26525
26748
  const dir = reviewsDir();
26526
26749
  let files;
26527
26750
  try {
26528
- files = readdirSync4(dir);
26751
+ files = readdirSync3(dir);
26529
26752
  } catch {
26530
26753
  return [];
26531
26754
  }
@@ -26535,7 +26758,7 @@ function listReviewFiles() {
26535
26758
  continue;
26536
26759
  if (out.length >= 5000)
26537
26760
  break;
26538
- const raw = readJsonFileSync(join10(dir, file2));
26761
+ const raw = readJsonFileSync(join11(dir, file2));
26539
26762
  if (raw && typeof raw.id === "string" && typeof raw.status === "string") {
26540
26763
  out.push({ id: raw.id, status: raw.status });
26541
26764
  }
@@ -26546,7 +26769,7 @@ var TAIL_BYTES = 256 * 1024;
26546
26769
  async function logStats(path) {
26547
26770
  let size;
26548
26771
  try {
26549
- size = statSync4(path).size;
26772
+ size = statSync3(path).size;
26550
26773
  } catch {
26551
26774
  return { path, exists: false, size: 0, errors: 0, warns: 0 };
26552
26775
  }
@@ -26639,33 +26862,33 @@ import {
26639
26862
  existsSync as existsSync9,
26640
26863
  mkdirSync as mkdirSync3,
26641
26864
  rmSync as rmSync2,
26642
- statSync as statSync5,
26865
+ statSync as statSync4,
26643
26866
  symlinkSync,
26644
26867
  writeFileSync as writeFileSync5
26645
26868
  } from "fs";
26646
- import { join as join12, resolve as resolve2 } from "path";
26869
+ import { join as join13, resolve as resolve2 } from "path";
26647
26870
 
26648
26871
  // src/adapters/opencode/packaging.ts
26649
- import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync8 } from "fs";
26650
- import { dirname as dirname7, join as join11 } from "path";
26872
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
26873
+ import { dirname as dirname7, join as join12 } from "path";
26651
26874
  function resolveCaretRoot() {
26652
26875
  const candidates = [];
26653
26876
  const script = process.argv[1];
26654
26877
  if (script)
26655
- candidates.push(join11(dirname7(script), ".."));
26656
- candidates.push(join11(dirname7(process.execPath), ".."));
26878
+ candidates.push(join12(dirname7(script), ".."));
26879
+ candidates.push(join12(dirname7(process.execPath), ".."));
26657
26880
  for (const root of candidates) {
26658
- if (existsSync8(join11(root, "opencode", "caret.plugin.ts")))
26881
+ if (existsSync8(join12(root, "opencode", "caret.plugin.ts")))
26659
26882
  return root;
26660
26883
  }
26661
26884
  throw new Error(`caret OpenCode packaging not found (no opencode/caret.plugin.ts under: ${candidates.join(", ") || "(none)"}). The OpenCode plugin source must ship with caret.`);
26662
26885
  }
26663
26886
  function loadOpencodePackaging(root = resolveCaretRoot()) {
26664
- const binPath = join11(root, "bin", "caret");
26665
- const commandsDir = join11(root, "opencode", "commands");
26887
+ const binPath = join12(root, "bin", "caret");
26888
+ const commandsDir = join12(root, "opencode", "commands");
26666
26889
  let commands = [];
26667
26890
  try {
26668
- commands = readdirSync5(commandsDir).filter((f) => f.endsWith(".md")).sort().map((f) => ({ name: f, contents: readFileSync8(join11(commandsDir, f), "utf-8") }));
26891
+ commands = readdirSync4(commandsDir).filter((f) => f.endsWith(".md")).sort().map((f) => ({ name: f, contents: readFileSync8(join12(commandsDir, f), "utf-8") }));
26669
26892
  } catch {
26670
26893
  commands = [];
26671
26894
  }
@@ -26674,17 +26897,17 @@ function loadOpencodePackaging(root = resolveCaretRoot()) {
26674
26897
 
26675
26898
  // src/commands/install/local.ts
26676
26899
  function devMarketplaceDir() {
26677
- return join12(stateDir(), "dev-marketplace");
26900
+ return join13(stateDir(), "dev-marketplace");
26678
26901
  }
26679
26902
  function resolveLocalCheckout(opts = {}, deps = {}) {
26680
26903
  const repoDir = resolve2(caretRoot(deps));
26681
- if (!existsSync9(join12(repoDir, ".claude-plugin", "marketplace.json"))) {
26904
+ if (!existsSync9(join13(repoDir, ".claude-plugin", "marketplace.json"))) {
26682
26905
  throw new Error(`--from-local must run from a caret checkout (no .claude-plugin/marketplace.json at ${repoDir}). A published caret installs with a plain \`caret install\`.`);
26683
26906
  }
26684
26907
  if (opts.requireArtifacts !== false) {
26685
26908
  const missing = [
26686
- isExecutable(join12(repoDir, "bin", "caret-native")) ? null : "bin/caret-native",
26687
- isDirectory(join12(repoDir, "bin", "ui")) ? null : "bin/ui"
26909
+ isExecutable(join13(repoDir, "bin", "caret-native")) ? null : "bin/caret-native",
26910
+ isDirectory(join13(repoDir, "bin", "ui")) ? null : "bin/ui"
26688
26911
  ].filter((m) => m !== null);
26689
26912
  if (missing.length > 0) {
26690
26913
  throw new Error(`--from-local reuses the build artifacts bin/caret-native + bin/ui, and ${repoDir} is missing ${missing.join(" + ")} \u2014 run \`mise run build\` first.`);
@@ -26703,9 +26926,9 @@ function caretRoot(deps) {
26703
26926
  }
26704
26927
  function writeDevMarketplace(repoDir, outDir) {
26705
26928
  rmSync2(outDir, { recursive: true, force: true });
26706
- mkdirSync3(join12(outDir, ".claude-plugin"), { recursive: true });
26707
- symlinkSync(resolve2(repoDir), join12(outDir, "caret"));
26708
- writeFileSync5(join12(outDir, ".claude-plugin", "marketplace.json"), `${JSON.stringify({
26929
+ mkdirSync3(join13(outDir, ".claude-plugin"), { recursive: true });
26930
+ symlinkSync(resolve2(repoDir), join13(outDir, "caret"));
26931
+ writeFileSync5(join13(outDir, ".claude-plugin", "marketplace.json"), `${JSON.stringify({
26709
26932
  name: "caret",
26710
26933
  description: "Local caret dev build (generated by caret install --from-local).",
26711
26934
  owner: { name: "macintacos" },
@@ -26716,20 +26939,20 @@ function writeDevMarketplace(repoDir, outDir) {
26716
26939
  async function prewarmLocalBuild(repoDir, deps = {}) {
26717
26940
  const spawn = deps.spawn ?? runCaptured;
26718
26941
  try {
26719
- await spawn([join12(repoDir, "bin", "caret-native"), "prewarm"]);
26942
+ await spawn([join13(repoDir, "bin", "caret-native"), "prewarm"]);
26720
26943
  } catch {}
26721
26944
  }
26722
26945
  function isExecutable(path) {
26723
26946
  try {
26724
26947
  accessSync(path, constants.X_OK);
26725
- return statSync5(path).isFile();
26948
+ return statSync4(path).isFile();
26726
26949
  } catch {
26727
26950
  return false;
26728
26951
  }
26729
26952
  }
26730
26953
  function isDirectory(path) {
26731
26954
  try {
26732
- return statSync5(path).isDirectory();
26955
+ return statSync4(path).isDirectory();
26733
26956
  } catch {
26734
26957
  return false;
26735
26958
  }
@@ -26991,7 +27214,7 @@ async function runInstallClaudeTarget(opts, deps = {}) {
26991
27214
 
26992
27215
  // src/commands/install/opencode.ts
26993
27216
  import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
26994
- import { basename as basename3, join as join14 } from "path";
27217
+ import { basename as basename3, join as join15 } from "path";
26995
27218
 
26996
27219
  // src/adapters/opencode/deploy.ts
26997
27220
  import { existsSync as existsSync11, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
@@ -27094,7 +27317,7 @@ async function promptUpgrade(verdict, clack) {
27094
27317
 
27095
27318
  // src/commands/install/opencode.ts
27096
27319
  function isCaretCheckout(dir) {
27097
- return existsSync13(join14(dir, "opencode", "caret.plugin.ts"));
27320
+ return existsSync13(join15(dir, "opencode", "caret.plugin.ts"));
27098
27321
  }
27099
27322
  function caretEntries(text2, isCheckout) {
27100
27323
  return pluginEntries(text2).filter((entry) => {
@@ -27120,7 +27343,7 @@ async function runInstallOpencodeTarget(opts, deps = {}) {
27120
27343
  const ui = deps.ui ?? silentUI;
27121
27344
  const isCheckout = deps.isCheckout ?? isCaretCheckout;
27122
27345
  const configFile2 = resolveConfigFile(dir);
27123
- const commandPaths = pkg.commands.map((c3) => join14(commandDir(dir), namespacedCommandFilename(c3.name)));
27346
+ const commandPaths = pkg.commands.map((c3) => join15(commandDir(dir), namespacedCommandFilename(c3.name)));
27124
27347
  const legacy = existingLegacyInstallFiles(dir);
27125
27348
  const specifier = opts.local ? localPluginSpecifier(opts.local.repoDir) : CARET_PACKAGE;
27126
27349
  if (opts.dryRun) {
@@ -27143,7 +27366,7 @@ async function runInstallOpencodeTarget(opts, deps = {}) {
27143
27366
  await upgradeStep(configFile2, opts, deps, ui);
27144
27367
  await sweepLegacy(legacy, dir, ui);
27145
27368
  const files = pkg.commands.map((c3) => ({
27146
- path: join14(commandDir(dir), namespacedCommandFilename(c3.name)),
27369
+ path: join15(commandDir(dir), namespacedCommandFilename(c3.name)),
27147
27370
  contents: renderPlugin(c3.contents, { version: VERSION, binPath: pkg.binPath })
27148
27371
  }));
27149
27372
  await ui.step("Deploying the /caret:* command files", async () => deployFiles(files, { dryRun: false }), (deployed) => `Deployed ${deployed.paths.length} command file(s) to ${dir}`);
@@ -28813,7 +29036,10 @@ async function runReview(stdin2, deps) {
28813
29036
  step = "ensureDaemon";
28814
29037
  baseUrl = await deps.ensureDaemon();
28815
29038
  step = "postReview";
28816
- const { id, hasLiveClient } = await deps.postReview(baseUrl, input);
29039
+ const { id, hasLiveClient } = await deps.postReview(baseUrl, {
29040
+ ...input,
29041
+ cmux: deps.readPane?.()
29042
+ });
28817
29043
  ctx.reviewId = id;
28818
29044
  deps.onPosted?.(baseUrl, id);
28819
29045
  logDebug("review", `review created: ${shortId(id)}`, { ...ctx });
@@ -28880,6 +29106,7 @@ function prodReviewDeps(s, adapter) {
28880
29106
  postReview,
28881
29107
  longPoll,
28882
29108
  openBrowser,
29109
+ readPane: readCmuxPane,
28883
29110
  timeoutMs: reviewTimeoutMs(s),
28884
29111
  expire: expireReview
28885
29112
  };