@macintacos/caret 0.1.1 → 0.2.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.
- package/.claude-plugin/plugin.json +1 -1
- package/commands/demo.md +5 -4
- package/dist/cli.js +354 -53
- package/hooks/hooks.json +10 -0
- package/opencode/caret.plugin.ts +138 -20
- package/package.json +1 -1
- package/ui/dist/assets/{angular-html-Cg_qO4OQ.js → angular-html-Qbpn1MTX.js} +1 -1
- package/ui/dist/assets/{angular-ts-BmV-Wd4K.js → angular-ts-DOJ1C-2P.js} +1 -1
- package/ui/dist/assets/{apl-DDDJQLEy.js → apl-Y5-ddbi_.js} +1 -1
- package/ui/dist/assets/{astro-Cq94xJ2F.js → astro-CT31GiG6.js} +1 -1
- package/ui/dist/assets/{blade-DZCNRrZB.js → blade-DstC0Y7u.js} +1 -1
- package/ui/dist/assets/{c-CwNkiPnO.js → c-1h9wmDaJ.js} +1 -1
- package/ui/dist/assets/{cobol-CgSNuNV-.js → cobol-BJsqFKRW.js} +1 -1
- package/ui/dist/assets/{coffee-DWlIdtuG.js → coffee-lAZsrYrG.js} +1 -1
- package/ui/dist/assets/{cpp-CmiYyU8y.js → cpp-BwPvHcji.js} +1 -1
- package/ui/dist/assets/{crystal-BiyOLiea.js → crystal-C9nhMRLM.js} +1 -1
- package/ui/dist/assets/{css-DGiwxBdC.js → css-zV5baxCq.js} +1 -1
- package/ui/dist/assets/{dist-B-SHQUSo.js → dist-B2afH7tP.js} +1 -1
- package/ui/dist/assets/{dist-CCQGXHXf.js → dist-BL3PTdx3.js} +1 -1
- package/ui/dist/assets/{dist-B3mK9QMK.js → dist-Ba7qEsX8.js} +1 -1
- package/ui/dist/assets/{dist-C9DszYV1.js → dist-BcfPYvB6.js} +1 -1
- package/ui/dist/assets/{dist-24WuufPm.js → dist-C94yZ6__.js} +1 -1
- package/ui/dist/assets/{dist-C1Qk3ApG.js → dist-CBdstVS8.js} +1 -1
- package/ui/dist/assets/{dist-Cr9g-IkY.js → dist-CWdEcyiK.js} +1 -1
- package/ui/dist/assets/{dist-CbS4AEdX.js → dist-CnTQMJTD.js} +1 -1
- package/ui/dist/assets/{dist-CfztD2mm.js → dist-Cuw-4MzZ.js} +1 -1
- package/ui/dist/assets/{dist-CNR9H9hI.js → dist-D1Xb0cdg.js} +1 -1
- package/ui/dist/assets/{dist-C-hQPg8K.js → dist-D5aJA_uS.js} +1 -1
- package/ui/dist/assets/{dist-D_I8tzqw.js → dist-DGObAN16.js} +1 -1
- package/ui/dist/assets/{dist-BXtMxuI7.js → dist-DTwEdaPW.js} +1 -1
- package/ui/dist/assets/{dist-BViEvwEG.js → dist-EeYINWCK.js} +1 -1
- package/ui/dist/assets/{dist-CKVYvLDM.js → dist-ZPJnos5t.js} +1 -1
- package/ui/dist/assets/{dist-BkSlyoLl.js → dist-ap3lqXWD.js} +1 -1
- package/ui/dist/assets/{dist-C_IYb7Qh.js → dist-eJbkdDpi.js} +1 -1
- package/ui/dist/assets/{edge-CZXzS9pm.js → edge-DtPr1ved.js} +1 -1
- package/ui/dist/assets/{elixir-BTnUmaBf.js → elixir-DxdQpZmO.js} +1 -1
- package/ui/dist/assets/{elm-Bq5po3Zd.js → elm-PyFaNcFd.js} +1 -1
- package/ui/dist/assets/{erb-BOVmqWJf.js → erb-4d0ztAHA.js} +1 -1
- package/ui/dist/assets/{git-rebase-DLQM-CxK.js → git-rebase-CF959Xij.js} +1 -1
- package/ui/dist/assets/{glimmer-js-DzMlcKY7.js → glimmer-js-DOoPHQhz.js} +1 -1
- package/ui/dist/assets/{glimmer-ts-Dz691XKF.js → glimmer-ts-B-wO1BbM.js} +1 -1
- package/ui/dist/assets/{glsl-CMQZ1AkF.js → glsl-DhUIrRGp.js} +1 -1
- package/ui/dist/assets/{graphql-DBKIWtF7.js → graphql-DHm0YGdI.js} +1 -1
- package/ui/dist/assets/{hack-DZbb_fkF.js → hack-KmeknXZp.js} +1 -1
- package/ui/dist/assets/{haml-CSjdx5hj.js → haml-CqH8UVt6.js} +1 -1
- package/ui/dist/assets/{handlebars-Mu9SxQrl.js → handlebars-xVW6rqZc.js} +1 -1
- package/ui/dist/assets/{html-D4wtNI0H.js → html-Dm0ku22r.js} +1 -1
- package/ui/dist/assets/{html-derivative-DHrkr74L.js → html-derivative-ByxJ1rMF.js} +1 -1
- package/ui/dist/assets/{http-DtBk1Y64.js → http--f7CCugj.js} +1 -1
- package/ui/dist/assets/{hurl-1qu6SWMo.js → hurl-hbsn4uwO.js} +1 -1
- package/ui/dist/assets/index-2kPU99Wr.css +1 -0
- package/ui/dist/assets/{index-Dqm3ZFVQ.js → index-CIXjaWlx.js} +285 -95
- package/ui/dist/assets/{java-CLLSyNOn.js → java-DIuHCmu6.js} +1 -1
- package/ui/dist/assets/{javascript-B-_kYMSx.js → javascript-gLvwRdLL.js} +1 -1
- package/ui/dist/assets/{jinja-BtHF51nI.js → jinja-CllNBGTW.js} +1 -1
- package/ui/dist/assets/{jison-C93_a--j.js → jison--nGCTsNX.js} +1 -1
- package/ui/dist/assets/{json-C4FWYbGR.js → json-C56ryB74.js} +1 -1
- package/ui/dist/assets/{jsx-BdkuTVvH.js → jsx-4Pk1mTn6.js} +1 -1
- package/ui/dist/assets/{julia-BmboAp4o.js → julia-C0bepdRl.js} +1 -1
- package/ui/dist/assets/{just-E1Wftakf.js → just-DZULmPRj.js} +1 -1
- package/ui/dist/assets/{latex-DZFvvGDu.js → latex-D16Jgu4V.js} +1 -1
- package/ui/dist/assets/{liquid-BUTRgRVi.js → liquid-C8coUMRS.js} +1 -1
- package/ui/dist/assets/{lua-Dl1ciOfH.js → lua-Cf5MxbTu.js} +1 -1
- package/ui/dist/assets/{marko-Bh-A3HQZ.js → marko-Cb70KBLA.js} +1 -1
- package/ui/dist/assets/{mdc-C2XCGsSV.js → mdc-C7MhKLIm.js} +1 -1
- package/ui/dist/assets/{nginx-igqyMv_F.js → nginx-BXtCcpzS.js} +1 -1
- package/ui/dist/assets/{nim-DelsCcX1.js → nim-DGCttsC9.js} +1 -1
- package/ui/dist/assets/{perl-Cgh7mkol.js → perl-QjSbxj8-.js} +1 -1
- package/ui/dist/assets/{php-DXn-4Rf4.js → php-C3F5od73.js} +1 -1
- package/ui/dist/assets/{pug-BNWZhj7f.js → pug-C5_Ubyev.js} +1 -1
- package/ui/dist/assets/{qml-CHLXijrJ.js → qml-ClOBZo_4.js} +1 -1
- package/ui/dist/assets/{r-g949Oasl.js → r-CRs34ME2.js} +1 -1
- package/ui/dist/assets/{razor-BUv8GTQg.js → razor-Da5e4iwK.js} +1 -1
- package/ui/dist/assets/{regexp-Dxi3LqhO.js → regexp-CIgnY5KL.js} +1 -1
- package/ui/dist/assets/{rst-CMQNxthU.js → rst-B8IBFGFC.js} +1 -1
- package/ui/dist/assets/{ruby-CDBQomnV.js → ruby-DCWmsWCN.js} +1 -1
- package/ui/dist/assets/{sas-uuqXrFx7.js → sas-C6G9MYN0.js} +1 -1
- package/ui/dist/assets/{scss-CniJSQ3f.js → scss-CNd_k2F5.js} +1 -1
- package/ui/dist/assets/{shellscript-CuBMk1iS.js → shellscript-CwXf9FTq.js} +1 -1
- package/ui/dist/assets/{shellsession-BzDlSDO2.js → shellsession-BJzcxmip.js} +1 -1
- package/ui/dist/assets/{soy-e0u5_VID.js → soy-KzIeqqPW.js} +1 -1
- package/ui/dist/assets/{sql-Cbg060Y9.js → sql-DBDZ7dWg.js} +1 -1
- package/ui/dist/assets/{stata-n_N_Lnpt.js → stata-SsEZ25vz.js} +1 -1
- package/ui/dist/assets/{surrealql-Sa2566xv.js → surrealql-C7tSrOv6.js} +1 -1
- package/ui/dist/assets/{svelte-B0zgpD2X.js → svelte-BHETRygr.js} +1 -1
- package/ui/dist/assets/{templ-BMfZts2f.js → templ-mCQ9V1Fb.js} +1 -1
- package/ui/dist/assets/{tex-7PyiPoxe.js → tex-DgKGHlQ1.js} +1 -1
- package/ui/dist/assets/{ts-tags-nHnZDDzD.js → ts-tags-D-31aQRg.js} +1 -1
- package/ui/dist/assets/{tsx-BgvqLYni.js → tsx-CKRZAYYe.js} +1 -1
- package/ui/dist/assets/{twig-BqJ1gUqf.js → twig-COT1S6WR.js} +1 -1
- package/ui/dist/assets/{typescript-C2nbCIiA.js → typescript-IBoewa4A.js} +1 -1
- package/ui/dist/assets/{vue-B_l4_yyQ.js → vue-44M0YEZN.js} +1 -1
- package/ui/dist/assets/{vue-html-qalJD3cY.js → vue-html-C1gtv8Ck.js} +1 -1
- package/ui/dist/assets/{vue-vine-Cle_s8sp.js → vue-vine-DEW0Tzyn.js} +1 -1
- package/ui/dist/assets/{xml-CnKUcNAY.js → xml-CSnYabNH.js} +1 -1
- package/ui/dist/assets/{xsl-D5cPGaiC.js → xsl-DrkZvWNe.js} +1 -1
- package/ui/dist/assets/{yaml-BcZKwY9y.js → yaml-D6QWa51n.js} +1 -1
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-DT45eblI.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -4438,9 +4438,12 @@ function setModeFor(id) {
|
|
|
4438
4438
|
}
|
|
4439
4439
|
|
|
4440
4440
|
// src/adapters/claude/feedback.ts
|
|
4441
|
-
function toHookOutput(input) {
|
|
4441
|
+
function toHookOutput(input, updatedInput) {
|
|
4442
4442
|
if (input.behavior === "allow") {
|
|
4443
4443
|
const decision = { behavior: "allow" };
|
|
4444
|
+
if (updatedInput) {
|
|
4445
|
+
decision.updatedInput = updatedInput;
|
|
4446
|
+
}
|
|
4444
4447
|
const mode = setModeFor(input.acceptMode);
|
|
4445
4448
|
if (mode) {
|
|
4446
4449
|
decision.updatedPermissions = [{ type: "setMode", mode, destination: "session" }];
|
|
@@ -4544,6 +4547,16 @@ function readHookInUserSettings(path) {
|
|
|
4544
4547
|
}
|
|
4545
4548
|
|
|
4546
4549
|
// src/adapters/claude/index.ts
|
|
4550
|
+
function toolInputEcho(input) {
|
|
4551
|
+
if (!input)
|
|
4552
|
+
return;
|
|
4553
|
+
const echo = {};
|
|
4554
|
+
if (input.plan !== undefined)
|
|
4555
|
+
echo.plan = input.plan;
|
|
4556
|
+
if (input.planFilePath !== undefined)
|
|
4557
|
+
echo.planFilePath = input.planFilePath;
|
|
4558
|
+
return Object.keys(echo).length > 0 ? echo : undefined;
|
|
4559
|
+
}
|
|
4547
4560
|
var claudeAdapter = {
|
|
4548
4561
|
approveVariants: APPROVE_VARIANTS,
|
|
4549
4562
|
parseHookInput(stdin) {
|
|
@@ -4560,8 +4573,8 @@ var claudeAdapter = {
|
|
|
4560
4573
|
planFilePath: hook.tool_input?.planFilePath
|
|
4561
4574
|
};
|
|
4562
4575
|
},
|
|
4563
|
-
emitDecision(decision) {
|
|
4564
|
-
return JSON.stringify(toHookOutput(decision));
|
|
4576
|
+
emitDecision(decision, input) {
|
|
4577
|
+
return JSON.stringify(toHookOutput(decision, toolInputEcho(input)));
|
|
4565
4578
|
},
|
|
4566
4579
|
fatalDenyLine(reason) {
|
|
4567
4580
|
return fatalDenyLine(reason);
|
|
@@ -5777,7 +5790,7 @@ import { createHash } from "crypto";
|
|
|
5777
5790
|
// package.json
|
|
5778
5791
|
var package_default = {
|
|
5779
5792
|
name: "@macintacos/caret",
|
|
5780
|
-
version: "0.
|
|
5793
|
+
version: "0.2.0",
|
|
5781
5794
|
license: "MIT",
|
|
5782
5795
|
type: "module",
|
|
5783
5796
|
description: "A Claude Code plugin that replaces the terminal plan-approval prompt with a local web UI for inline review, annotation, and approval.",
|
|
@@ -5967,7 +5980,7 @@ function currentCommit() {
|
|
|
5967
5980
|
}
|
|
5968
5981
|
|
|
5969
5982
|
// src/commands/daemon.ts
|
|
5970
|
-
import { randomUUID
|
|
5983
|
+
import { randomUUID } from "crypto";
|
|
5971
5984
|
import { existsSync as existsSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
5972
5985
|
|
|
5973
5986
|
// src/daemon-lifecycle.ts
|
|
@@ -6013,6 +6026,22 @@ async function longPoll(baseUrl, id) {
|
|
|
6013
6026
|
throw new Error(`decision long-poll failed: ${res.status}`);
|
|
6014
6027
|
return await res.json();
|
|
6015
6028
|
}
|
|
6029
|
+
async function listReviews(baseUrl) {
|
|
6030
|
+
const res = await fetch(`${baseUrl}/api/reviews`, { signal: AbortSignal.timeout(1000) });
|
|
6031
|
+
if (!res.ok)
|
|
6032
|
+
throw new Error(`GET /api/reviews failed: ${res.status}`);
|
|
6033
|
+
return await res.json();
|
|
6034
|
+
}
|
|
6035
|
+
async function resolveReview(baseUrl, id, body) {
|
|
6036
|
+
const res = await fetch(`${baseUrl}/api/reviews/${id}/resolve`, {
|
|
6037
|
+
method: "POST",
|
|
6038
|
+
headers: { "Content-Type": "application/json" },
|
|
6039
|
+
body: JSON.stringify(body),
|
|
6040
|
+
signal: AbortSignal.timeout(1000)
|
|
6041
|
+
});
|
|
6042
|
+
if (!res.ok)
|
|
6043
|
+
throw new Error(`POST /resolve failed: ${res.status}`);
|
|
6044
|
+
}
|
|
6016
6045
|
|
|
6017
6046
|
// src/log.ts
|
|
6018
6047
|
var import_pino = __toESM(require_pino(), 1);
|
|
@@ -6189,7 +6218,7 @@ function toClientReview(review) {
|
|
|
6189
6218
|
annotations: cur.annotations,
|
|
6190
6219
|
versions: review.versions,
|
|
6191
6220
|
generalCommentDraft: review.generalCommentDraft ?? "",
|
|
6192
|
-
composerScratches:
|
|
6221
|
+
composerScratches: cur.composerScratches ?? [],
|
|
6193
6222
|
createdAt: review.createdAt,
|
|
6194
6223
|
updatedAt: review.updatedAt,
|
|
6195
6224
|
decision: review.decision
|
|
@@ -20941,6 +20970,172 @@ function createDecisions(log = noopLogger) {
|
|
|
20941
20970
|
};
|
|
20942
20971
|
}
|
|
20943
20972
|
|
|
20973
|
+
// src/plan-files.ts
|
|
20974
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync2 } from "fs";
|
|
20975
|
+
import { extname, isAbsolute, join as join6, relative as relative2, resolve, sep } from "path";
|
|
20976
|
+
var EXCERPT_RADIUS = 6;
|
|
20977
|
+
var EXCERPT_HEAD_LINES = 13;
|
|
20978
|
+
var MAX_EXCERPT_BYTES = 2 * 1024 * 1024;
|
|
20979
|
+
var MAX_SCAN_ENTRIES = 5000;
|
|
20980
|
+
var SKIP_DIRS = new Set(["node_modules", "dist", "build", "coverage", "out"]);
|
|
20981
|
+
var EXT_LANGUAGE = {
|
|
20982
|
+
".ts": "typescript",
|
|
20983
|
+
".mts": "typescript",
|
|
20984
|
+
".cts": "typescript",
|
|
20985
|
+
".tsx": "tsx",
|
|
20986
|
+
".js": "javascript",
|
|
20987
|
+
".mjs": "javascript",
|
|
20988
|
+
".cjs": "javascript",
|
|
20989
|
+
".jsx": "jsx",
|
|
20990
|
+
".svelte": "svelte",
|
|
20991
|
+
".vue": "vue",
|
|
20992
|
+
".json": "json",
|
|
20993
|
+
".jsonc": "jsonc",
|
|
20994
|
+
".css": "css",
|
|
20995
|
+
".scss": "scss",
|
|
20996
|
+
".less": "less",
|
|
20997
|
+
".html": "html",
|
|
20998
|
+
".htm": "html",
|
|
20999
|
+
".xml": "xml",
|
|
21000
|
+
".svg": "xml",
|
|
21001
|
+
".md": "markdown",
|
|
21002
|
+
".mdx": "mdx",
|
|
21003
|
+
".py": "python",
|
|
21004
|
+
".rb": "ruby",
|
|
21005
|
+
".rs": "rust",
|
|
21006
|
+
".go": "go",
|
|
21007
|
+
".java": "java",
|
|
21008
|
+
".kt": "kotlin",
|
|
21009
|
+
".c": "c",
|
|
21010
|
+
".h": "c",
|
|
21011
|
+
".cc": "cpp",
|
|
21012
|
+
".cpp": "cpp",
|
|
21013
|
+
".hpp": "cpp",
|
|
21014
|
+
".sh": "shellscript",
|
|
21015
|
+
".bash": "shellscript",
|
|
21016
|
+
".zsh": "shellscript",
|
|
21017
|
+
".toml": "toml",
|
|
21018
|
+
".yaml": "yaml",
|
|
21019
|
+
".yml": "yaml",
|
|
21020
|
+
".ini": "ini",
|
|
21021
|
+
".sql": "sql",
|
|
21022
|
+
".graphql": "graphql",
|
|
21023
|
+
".gql": "graphql",
|
|
21024
|
+
".php": "php",
|
|
21025
|
+
".swift": "swift",
|
|
21026
|
+
".dart": "dart"
|
|
21027
|
+
};
|
|
21028
|
+
function languageForPath(path) {
|
|
21029
|
+
return EXT_LANGUAGE[extname(path).toLowerCase()] ?? "text";
|
|
21030
|
+
}
|
|
21031
|
+
function safeRealpath(path) {
|
|
21032
|
+
try {
|
|
21033
|
+
return realpathSync(path);
|
|
21034
|
+
} catch {
|
|
21035
|
+
return null;
|
|
21036
|
+
}
|
|
21037
|
+
}
|
|
21038
|
+
function containedFile(cwdReal, abs) {
|
|
21039
|
+
const real = safeRealpath(abs);
|
|
21040
|
+
if (real === null)
|
|
21041
|
+
return null;
|
|
21042
|
+
if (real !== cwdReal && !real.startsWith(cwdReal + sep))
|
|
21043
|
+
return null;
|
|
21044
|
+
try {
|
|
21045
|
+
if (!statSync2(real).isFile())
|
|
21046
|
+
return null;
|
|
21047
|
+
} catch {
|
|
21048
|
+
return null;
|
|
21049
|
+
}
|
|
21050
|
+
return real;
|
|
21051
|
+
}
|
|
21052
|
+
function basenameSearch(cwdReal, name) {
|
|
21053
|
+
if (name === "")
|
|
21054
|
+
return null;
|
|
21055
|
+
let scanned = 0;
|
|
21056
|
+
const queue = [cwdReal];
|
|
21057
|
+
while (queue.length > 0) {
|
|
21058
|
+
const currentDir = queue.shift();
|
|
21059
|
+
let entries;
|
|
21060
|
+
try {
|
|
21061
|
+
entries = readdirSync2(currentDir, { withFileTypes: true });
|
|
21062
|
+
} catch {
|
|
21063
|
+
continue;
|
|
21064
|
+
}
|
|
21065
|
+
for (const entry of entries) {
|
|
21066
|
+
if (++scanned > MAX_SCAN_ENTRIES)
|
|
21067
|
+
return null;
|
|
21068
|
+
if (entry.isFile()) {
|
|
21069
|
+
if (entry.name === name)
|
|
21070
|
+
return join6(currentDir, entry.name);
|
|
21071
|
+
} else if (entry.isDirectory() && !SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
|
|
21072
|
+
queue.push(join6(currentDir, entry.name));
|
|
21073
|
+
}
|
|
21074
|
+
}
|
|
21075
|
+
}
|
|
21076
|
+
return null;
|
|
21077
|
+
}
|
|
21078
|
+
function resolveFileInCwd(cwd, candidate) {
|
|
21079
|
+
if (cwd === "" || !isAbsolute(cwd))
|
|
21080
|
+
return null;
|
|
21081
|
+
const cwdReal = safeRealpath(cwd);
|
|
21082
|
+
if (cwdReal === null)
|
|
21083
|
+
return null;
|
|
21084
|
+
const cleaned = candidate.trim();
|
|
21085
|
+
if (cleaned === "")
|
|
21086
|
+
return null;
|
|
21087
|
+
const direct = containedFile(cwdReal, resolve(cwdReal, cleaned));
|
|
21088
|
+
if (direct !== null)
|
|
21089
|
+
return direct;
|
|
21090
|
+
const name = cleaned.split("/").pop() ?? "";
|
|
21091
|
+
return basenameSearch(cwdReal, name);
|
|
21092
|
+
}
|
|
21093
|
+
function readFileExcerpt(cwd, candidate, line) {
|
|
21094
|
+
const abs = resolveFileInCwd(cwd, candidate);
|
|
21095
|
+
if (abs === null)
|
|
21096
|
+
return null;
|
|
21097
|
+
let size;
|
|
21098
|
+
try {
|
|
21099
|
+
size = statSync2(abs).size;
|
|
21100
|
+
} catch {
|
|
21101
|
+
return null;
|
|
21102
|
+
}
|
|
21103
|
+
if (size > MAX_EXCERPT_BYTES)
|
|
21104
|
+
return null;
|
|
21105
|
+
let content;
|
|
21106
|
+
try {
|
|
21107
|
+
content = readFileSync6(abs, "utf-8");
|
|
21108
|
+
} catch {
|
|
21109
|
+
return null;
|
|
21110
|
+
}
|
|
21111
|
+
if (content.includes("\x00"))
|
|
21112
|
+
return null;
|
|
21113
|
+
const allLines = content.split(`
|
|
21114
|
+
`);
|
|
21115
|
+
if (allLines.length > 1 && allLines[allLines.length - 1] === "")
|
|
21116
|
+
allLines.pop();
|
|
21117
|
+
const totalLines = allLines.length;
|
|
21118
|
+
let startLine;
|
|
21119
|
+
let endLine;
|
|
21120
|
+
if (line !== undefined && line >= 1) {
|
|
21121
|
+
const target = Math.min(line, totalLines);
|
|
21122
|
+
startLine = Math.max(1, target - EXCERPT_RADIUS);
|
|
21123
|
+
endLine = Math.min(totalLines, target + EXCERPT_RADIUS);
|
|
21124
|
+
} else {
|
|
21125
|
+
startLine = 1;
|
|
21126
|
+
endLine = Math.min(totalLines, EXCERPT_HEAD_LINES);
|
|
21127
|
+
}
|
|
21128
|
+
const cwdReal = safeRealpath(cwd) ?? cwd;
|
|
21129
|
+
return {
|
|
21130
|
+
path: relative2(cwdReal, abs),
|
|
21131
|
+
language: languageForPath(abs),
|
|
21132
|
+
startLine,
|
|
21133
|
+
endLine,
|
|
21134
|
+
lines: allLines.slice(startLine - 1, endLine),
|
|
21135
|
+
totalLines
|
|
21136
|
+
};
|
|
21137
|
+
}
|
|
21138
|
+
|
|
20944
21139
|
// src/prefs.ts
|
|
20945
21140
|
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
20946
21141
|
import { dirname as dirname3 } from "path";
|
|
@@ -20970,17 +21165,17 @@ async function writeApproveMode(mode, file2 = prefsFile(), log = noopLogger, set
|
|
|
20970
21165
|
}
|
|
20971
21166
|
|
|
20972
21167
|
// src/reviews.ts
|
|
20973
|
-
import {
|
|
21168
|
+
import { randomBytes } from "crypto";
|
|
20974
21169
|
|
|
20975
21170
|
// src/plan-file.ts
|
|
20976
|
-
import { existsSync as existsSync2, statSync as
|
|
21171
|
+
import { existsSync as existsSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
20977
21172
|
function writeCanonicalPlanFile(planFilePath, canonical, log) {
|
|
20978
21173
|
if (!planFilePath)
|
|
20979
21174
|
return;
|
|
20980
21175
|
try {
|
|
20981
21176
|
if (!planFilePath.endsWith(".md"))
|
|
20982
21177
|
return;
|
|
20983
|
-
if (!existsSync2(planFilePath) || !
|
|
21178
|
+
if (!existsSync2(planFilePath) || !statSync3(planFilePath).isFile())
|
|
20984
21179
|
return;
|
|
20985
21180
|
writeFileSync2(planFilePath, canonical);
|
|
20986
21181
|
} catch (err) {
|
|
@@ -27751,6 +27946,9 @@ async function formatPlanMarkdown(plan, log = noopLogger, doFormat = prettierFor
|
|
|
27751
27946
|
}
|
|
27752
27947
|
|
|
27753
27948
|
// src/reviews.ts
|
|
27949
|
+
function newReviewId() {
|
|
27950
|
+
return randomBytes(8).toString("base64url");
|
|
27951
|
+
}
|
|
27754
27952
|
function deriveTitle(plan) {
|
|
27755
27953
|
for (const line of plan.split(`
|
|
27756
27954
|
`)) {
|
|
@@ -27799,7 +27997,7 @@ async function routeIncomingPlan(input, store, log = noopLogger) {
|
|
|
27799
27997
|
expired
|
|
27800
27998
|
};
|
|
27801
27999
|
}
|
|
27802
|
-
const id =
|
|
28000
|
+
const id = newReviewId();
|
|
27803
28001
|
const planEpoch = store.epochOf(sessionId);
|
|
27804
28002
|
const review = {
|
|
27805
28003
|
id,
|
|
@@ -27908,6 +28106,8 @@ var PlanInputSchema = exports_external.object({
|
|
|
27908
28106
|
plan: exports_external.string().optional(),
|
|
27909
28107
|
planFilePath: exports_external.string().optional()
|
|
27910
28108
|
}).catch({});
|
|
28109
|
+
var MAX_FILE_REFS = 200;
|
|
28110
|
+
var FileRefsBodySchema = exports_external.object({ paths: exports_external.array(exports_external.string()).catch([]) }).catch({ paths: [] });
|
|
27911
28111
|
var BehaviorSchema = exports_external.enum(["allow", "deny"]);
|
|
27912
28112
|
var ResolveBodySchema = exports_external.object({
|
|
27913
28113
|
behavior: BehaviorSchema.catch("allow"),
|
|
@@ -27958,11 +28158,13 @@ var nullToUndefined = (v2) => v2 ?? undefined;
|
|
|
27958
28158
|
var DraftBodySchema = exports_external.object({
|
|
27959
28159
|
annotations: exports_external.array(AnnotationSchema).nullish().transform(nullToUndefined),
|
|
27960
28160
|
generalCommentDraft: exports_external.string().nullish().transform(nullToUndefined),
|
|
27961
|
-
composerScratches: exports_external.array(PersistedScratchSchema).nullish().transform(nullToUndefined).catch(undefined)
|
|
28161
|
+
composerScratches: exports_external.array(PersistedScratchSchema).nullish().transform(nullToUndefined).catch(undefined),
|
|
28162
|
+
version: exports_external.number().int().min(1).nullish().transform(nullToUndefined).catch(undefined)
|
|
27962
28163
|
}).catch({
|
|
27963
28164
|
annotations: undefined,
|
|
27964
28165
|
generalCommentDraft: undefined,
|
|
27965
|
-
composerScratches: undefined
|
|
28166
|
+
composerScratches: undefined,
|
|
28167
|
+
version: undefined
|
|
27966
28168
|
});
|
|
27967
28169
|
async function parseBody(req, schema) {
|
|
27968
28170
|
return schema.parse(await req.json().catch(() => ({})));
|
|
@@ -27985,7 +28187,7 @@ function resolveOptions(opts) {
|
|
|
27985
28187
|
log: opts.log ?? noopLogger
|
|
27986
28188
|
};
|
|
27987
28189
|
}
|
|
27988
|
-
var ID_ROUTE_RE = /^\/api\/reviews\/([^/]+)(\/decision|\/resolve|\/draft|\/expire)?$/;
|
|
28190
|
+
var ID_ROUTE_RE = /^\/api\/reviews\/([^/]+)(\/decision|\/resolve|\/draft|\/expire|\/file-refs|\/file)?$/;
|
|
27989
28191
|
function matchIdRoute(path) {
|
|
27990
28192
|
const m = path.match(ID_ROUTE_RE);
|
|
27991
28193
|
if (!m)
|
|
@@ -27999,11 +28201,11 @@ function createServer(opts) {
|
|
|
27999
28201
|
const { awaitDecision, resolveDecision, clearDecision, openDecisionCount } = createDecisions(log);
|
|
28000
28202
|
const approveModeSet = approveVariants && approveVariants.length > 0 ? { valid: approveVariants.map((v2) => v2.id), fallback: approveVariants[0]?.id ?? "default" } : { valid: ["default"], fallback: "default" };
|
|
28001
28203
|
function raceDecision(id, ms2) {
|
|
28002
|
-
return new Promise((
|
|
28003
|
-
const t = setTimeout(() =>
|
|
28204
|
+
return new Promise((resolve2) => {
|
|
28205
|
+
const t = setTimeout(() => resolve2(null), ms2);
|
|
28004
28206
|
awaitDecision(id).then((d) => {
|
|
28005
28207
|
clearTimeout(t);
|
|
28006
|
-
|
|
28208
|
+
resolve2(d);
|
|
28007
28209
|
});
|
|
28008
28210
|
});
|
|
28009
28211
|
}
|
|
@@ -28135,6 +28337,34 @@ function createServer(opts) {
|
|
|
28135
28337
|
const r = store.get(id);
|
|
28136
28338
|
return r ? Response.json(toClientReview(r)) : notFound();
|
|
28137
28339
|
}
|
|
28340
|
+
async function handleFileRefs(req, id) {
|
|
28341
|
+
const r = store.get(id);
|
|
28342
|
+
if (!r)
|
|
28343
|
+
return notFound();
|
|
28344
|
+
const { paths } = await parseBody(req, FileRefsBodySchema);
|
|
28345
|
+
const seen = new Set;
|
|
28346
|
+
const resolved = [];
|
|
28347
|
+
for (const p of paths.slice(0, MAX_FILE_REFS)) {
|
|
28348
|
+
if (seen.has(p))
|
|
28349
|
+
continue;
|
|
28350
|
+
seen.add(p);
|
|
28351
|
+
if (resolveFileInCwd(r.cwd, p) !== null)
|
|
28352
|
+
resolved.push(p);
|
|
28353
|
+
}
|
|
28354
|
+
log.debug("request", `file-refs resolved: ${resolved.length}/${seen.size}`, { reviewId: id });
|
|
28355
|
+
return Response.json({ resolved });
|
|
28356
|
+
}
|
|
28357
|
+
function handleFileExcerpt(req, id) {
|
|
28358
|
+
const r = store.get(id);
|
|
28359
|
+
if (!r)
|
|
28360
|
+
return notFound();
|
|
28361
|
+
const params = new URL(req.url).searchParams;
|
|
28362
|
+
const path = params.get("path") ?? "";
|
|
28363
|
+
const lineRaw = params.get("line");
|
|
28364
|
+
const line = lineRaw !== null && /^\d+$/.test(lineRaw) ? Number(lineRaw) : undefined;
|
|
28365
|
+
const excerpt = readFileExcerpt(r.cwd, path, line);
|
|
28366
|
+
return excerpt ? Response.json(excerpt) : notFound();
|
|
28367
|
+
}
|
|
28138
28368
|
async function handleDecision(id) {
|
|
28139
28369
|
const inMem = store.get(id);
|
|
28140
28370
|
if (inMem?.decision) {
|
|
@@ -28168,8 +28398,9 @@ function createServer(opts) {
|
|
|
28168
28398
|
if (body.generalCommentDraft != null) {
|
|
28169
28399
|
r.generalCommentDraft = body.generalCommentDraft;
|
|
28170
28400
|
}
|
|
28171
|
-
|
|
28172
|
-
|
|
28401
|
+
const cur = currentVersion(r);
|
|
28402
|
+
if (body.composerScratches != null && (body.version == null || body.version === cur.version)) {
|
|
28403
|
+
cur.composerScratches = body.composerScratches;
|
|
28173
28404
|
}
|
|
28174
28405
|
});
|
|
28175
28406
|
if (updated)
|
|
@@ -28191,7 +28422,7 @@ function createServer(opts) {
|
|
|
28191
28422
|
r.decision = decision;
|
|
28192
28423
|
r.status = decision.behavior === "allow" ? "approved" : "rejected";
|
|
28193
28424
|
r.generalCommentDraft = "";
|
|
28194
|
-
r.composerScratches = [];
|
|
28425
|
+
currentVersion(r).composerScratches = [];
|
|
28195
28426
|
});
|
|
28196
28427
|
if (decision.behavior === "allow") {
|
|
28197
28428
|
store.bumpEpoch(existing.sessionId);
|
|
@@ -28244,6 +28475,10 @@ function createServer(opts) {
|
|
|
28244
28475
|
const { id, sub } = route;
|
|
28245
28476
|
if (method === "GET" && !sub)
|
|
28246
28477
|
return handleGetReview(id);
|
|
28478
|
+
if (method === "GET" && sub === "/file")
|
|
28479
|
+
return handleFileExcerpt(req, id);
|
|
28480
|
+
if (method === "POST" && sub === "/file-refs")
|
|
28481
|
+
return handleFileRefs(req, id);
|
|
28247
28482
|
if (method === "GET" && sub === "/decision")
|
|
28248
28483
|
return handleDecision(id);
|
|
28249
28484
|
if (method === "PUT" && sub === "/draft")
|
|
@@ -28334,7 +28569,7 @@ function createServer(opts) {
|
|
|
28334
28569
|
|
|
28335
28570
|
// src/store.ts
|
|
28336
28571
|
import { readdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
28337
|
-
import { join as
|
|
28572
|
+
import { join as join7 } from "path";
|
|
28338
28573
|
function createStore(dir, log = noopLogger) {
|
|
28339
28574
|
const reviews = new Map;
|
|
28340
28575
|
const epochs = new Map;
|
|
@@ -28343,7 +28578,7 @@ function createStore(dir, log = noopLogger) {
|
|
|
28343
28578
|
const prev = writeChains.get(review.id) ?? Promise.resolve();
|
|
28344
28579
|
const next = prev.catch(() => {}).then(async () => {
|
|
28345
28580
|
ensureStateDir(dir);
|
|
28346
|
-
await writeFile2(
|
|
28581
|
+
await writeFile2(join7(dir, `${review.id}.json`), JSON.stringify(review, null, 2), {
|
|
28347
28582
|
mode: 384
|
|
28348
28583
|
});
|
|
28349
28584
|
log.debug("store", `review persisted: ${shortId(review.id)}`, { reviewId: review.id });
|
|
@@ -28386,14 +28621,14 @@ function createStore(dir, log = noopLogger) {
|
|
|
28386
28621
|
return;
|
|
28387
28622
|
review.status = "expired";
|
|
28388
28623
|
review.generalCommentDraft = "";
|
|
28389
|
-
review.composerScratches = [];
|
|
28624
|
+
currentVersion(review).composerScratches = [];
|
|
28390
28625
|
review.updatedAt = Math.max(Date.now(), review.updatedAt + 1);
|
|
28391
28626
|
reviews.delete(id);
|
|
28392
28627
|
await persist(review);
|
|
28393
28628
|
return review;
|
|
28394
28629
|
},
|
|
28395
28630
|
async persisted(id) {
|
|
28396
|
-
return await readJsonFile(
|
|
28631
|
+
return await readJsonFile(join7(dir, `${id}.json`)) ?? undefined;
|
|
28397
28632
|
},
|
|
28398
28633
|
size() {
|
|
28399
28634
|
return reviews.size;
|
|
@@ -28420,7 +28655,7 @@ function createStore(dir, log = noopLogger) {
|
|
|
28420
28655
|
if (!file2.endsWith(".json"))
|
|
28421
28656
|
continue;
|
|
28422
28657
|
try {
|
|
28423
|
-
const review = JSON.parse(await readFile3(
|
|
28658
|
+
const review = JSON.parse(await readFile3(join7(dir, file2), "utf-8"));
|
|
28424
28659
|
if (isUnresolved(review.status)) {
|
|
28425
28660
|
reviews.set(review.id, review);
|
|
28426
28661
|
loaded++;
|
|
@@ -28468,7 +28703,7 @@ async function runDaemon(opts) {
|
|
|
28468
28703
|
buildId: await currentBuildId(),
|
|
28469
28704
|
commit: currentCommit(),
|
|
28470
28705
|
stateDir: stateDir(),
|
|
28471
|
-
instanceId:
|
|
28706
|
+
instanceId: randomUUID().slice(0, 8),
|
|
28472
28707
|
approveVariants: adapter.approveVariants,
|
|
28473
28708
|
log
|
|
28474
28709
|
});
|
|
@@ -28510,8 +28745,8 @@ import { existsSync as existsSync4 } from "fs";
|
|
|
28510
28745
|
import { release } from "os";
|
|
28511
28746
|
|
|
28512
28747
|
// src/discovery.ts
|
|
28513
|
-
import { readdirSync as
|
|
28514
|
-
import { basename, join as
|
|
28748
|
+
import { readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
28749
|
+
import { basename, join as join8 } from "path";
|
|
28515
28750
|
var CARET_BIN = "caret-native";
|
|
28516
28751
|
async function safe(build) {
|
|
28517
28752
|
try {
|
|
@@ -28711,7 +28946,7 @@ function listReviewFiles() {
|
|
|
28711
28946
|
const dir = reviewsDir();
|
|
28712
28947
|
let files;
|
|
28713
28948
|
try {
|
|
28714
|
-
files =
|
|
28949
|
+
files = readdirSync3(dir);
|
|
28715
28950
|
} catch {
|
|
28716
28951
|
return [];
|
|
28717
28952
|
}
|
|
@@ -28721,7 +28956,7 @@ function listReviewFiles() {
|
|
|
28721
28956
|
continue;
|
|
28722
28957
|
if (out.length >= 5000)
|
|
28723
28958
|
break;
|
|
28724
|
-
const raw = readJsonFileSync(
|
|
28959
|
+
const raw = readJsonFileSync(join8(dir, file2));
|
|
28725
28960
|
if (raw && typeof raw.id === "string" && typeof raw.status === "string") {
|
|
28726
28961
|
out.push({ id: raw.id, status: raw.status });
|
|
28727
28962
|
}
|
|
@@ -28732,7 +28967,7 @@ var TAIL_BYTES = 256 * 1024;
|
|
|
28732
28967
|
async function logStats(path) {
|
|
28733
28968
|
let size;
|
|
28734
28969
|
try {
|
|
28735
|
-
size =
|
|
28970
|
+
size = statSync4(path).size;
|
|
28736
28971
|
} catch {
|
|
28737
28972
|
return { path, exists: false, size: 0, errors: 0, warns: 0 };
|
|
28738
28973
|
}
|
|
@@ -28819,8 +29054,8 @@ async function runDiscoverySubcommand(opts) {
|
|
|
28819
29054
|
}
|
|
28820
29055
|
|
|
28821
29056
|
// src/commands/install-opencode.ts
|
|
28822
|
-
import { existsSync as existsSync7, readFileSync as
|
|
28823
|
-
import { join as
|
|
29057
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
29058
|
+
import { join as join10 } from "path";
|
|
28824
29059
|
|
|
28825
29060
|
// src/adapters/opencode/deploy.ts
|
|
28826
29061
|
import { existsSync as existsSync5, mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
@@ -28879,27 +29114,27 @@ function removeFiles(targets, opts) {
|
|
|
28879
29114
|
}
|
|
28880
29115
|
|
|
28881
29116
|
// src/adapters/opencode/packaging.ts
|
|
28882
|
-
import { existsSync as existsSync6, readFileSync as
|
|
28883
|
-
import { dirname as dirname6, join as
|
|
29117
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "fs";
|
|
29118
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
28884
29119
|
function resolveCaretRoot() {
|
|
28885
29120
|
const candidates = [];
|
|
28886
29121
|
const script = process.argv[1];
|
|
28887
29122
|
if (script)
|
|
28888
|
-
candidates.push(
|
|
28889
|
-
candidates.push(
|
|
29123
|
+
candidates.push(join9(dirname6(script), ".."));
|
|
29124
|
+
candidates.push(join9(dirname6(process.execPath), ".."));
|
|
28890
29125
|
for (const root of candidates) {
|
|
28891
|
-
if (existsSync6(
|
|
29126
|
+
if (existsSync6(join9(root, "opencode", "caret.plugin.ts")))
|
|
28892
29127
|
return root;
|
|
28893
29128
|
}
|
|
28894
29129
|
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.`);
|
|
28895
29130
|
}
|
|
28896
29131
|
function loadOpencodePackaging(root = resolveCaretRoot()) {
|
|
28897
|
-
const pluginSource =
|
|
28898
|
-
const binPath =
|
|
28899
|
-
const commandsDir =
|
|
29132
|
+
const pluginSource = readFileSync7(join9(root, "opencode", "caret.plugin.ts"), "utf-8");
|
|
29133
|
+
const binPath = join9(root, "bin", "caret");
|
|
29134
|
+
const commandsDir = join9(root, "opencode", "commands");
|
|
28900
29135
|
let commands = [];
|
|
28901
29136
|
try {
|
|
28902
|
-
commands =
|
|
29137
|
+
commands = readdirSync4(commandsDir).filter((f) => f.endsWith(".md")).sort().map((f) => ({ name: f, contents: readFileSync7(join9(commandsDir, f), "utf-8") }));
|
|
28903
29138
|
} catch {
|
|
28904
29139
|
commands = [];
|
|
28905
29140
|
}
|
|
@@ -28911,7 +29146,7 @@ function runInstallOpencodeSubcommand(opts, deps = {}) {
|
|
|
28911
29146
|
const dir = deps.configDir ?? opencodeConfigDir();
|
|
28912
29147
|
const pkg = deps.packaging ?? loadOpencodePackaging();
|
|
28913
29148
|
const pluginPath = pluginFilePath(dir);
|
|
28914
|
-
const commandPaths = pkg.commands.map((c) =>
|
|
29149
|
+
const commandPaths = pkg.commands.map((c) => join10(commandDir(dir), namespacedCommandFilename(c.name)));
|
|
28915
29150
|
if (opts.uninstall) {
|
|
28916
29151
|
const result2 = removeFiles([pluginPath, ...commandPaths], { dryRun: opts.dryRun });
|
|
28917
29152
|
const manifest2 = uninstallManifest(dir, opts.dryRun);
|
|
@@ -28924,7 +29159,7 @@ function runInstallOpencodeSubcommand(opts, deps = {}) {
|
|
|
28924
29159
|
contents: stripNonDefaultExports(renderPlugin(pkg.pluginSource, { version: VERSION, binPath: pkg.binPath }))
|
|
28925
29160
|
},
|
|
28926
29161
|
...pkg.commands.map((c) => ({
|
|
28927
|
-
path:
|
|
29162
|
+
path: join10(commandDir(dir), namespacedCommandFilename(c.name)),
|
|
28928
29163
|
contents: renderPlugin(c.contents, { version: VERSION, binPath: pkg.binPath })
|
|
28929
29164
|
}))
|
|
28930
29165
|
];
|
|
@@ -28961,7 +29196,7 @@ var bunInstall = (configDir2) => {
|
|
|
28961
29196
|
};
|
|
28962
29197
|
function installManifest(dir, dryRun) {
|
|
28963
29198
|
const path = packageJsonPath(dir);
|
|
28964
|
-
const existing = existsSync7(path) ?
|
|
29199
|
+
const existing = existsSync7(path) ? readFileSync8(path, "utf-8") : null;
|
|
28965
29200
|
let next;
|
|
28966
29201
|
try {
|
|
28967
29202
|
next = addPluginDependency(existing, OPENCODE_PLUGIN_DEP, OPENCODE_PLUGIN_DEP_VERSION);
|
|
@@ -28978,7 +29213,7 @@ function uninstallManifest(dir, dryRun) {
|
|
|
28978
29213
|
const path = packageJsonPath(dir);
|
|
28979
29214
|
if (!existsSync7(path))
|
|
28980
29215
|
return [];
|
|
28981
|
-
const existing =
|
|
29216
|
+
const existing = readFileSync8(path, "utf-8");
|
|
28982
29217
|
let next;
|
|
28983
29218
|
try {
|
|
28984
29219
|
next = removePluginDependency(existing, OPENCODE_PLUGIN_DEP);
|
|
@@ -29016,6 +29251,51 @@ async function runPrewarm() {
|
|
|
29016
29251
|
process.exit(0);
|
|
29017
29252
|
}
|
|
29018
29253
|
|
|
29254
|
+
// src/reconcile.ts
|
|
29255
|
+
async function runReconcile(stdin, deps) {
|
|
29256
|
+
const ctx = {};
|
|
29257
|
+
try {
|
|
29258
|
+
const input = deps.parseHookInput(stdin);
|
|
29259
|
+
ctx.sessionId = input.sessionId;
|
|
29260
|
+
if (!input.sessionId)
|
|
29261
|
+
return;
|
|
29262
|
+
const match = (await deps.listReviews()).find((r) => r.sessionId === input.sessionId);
|
|
29263
|
+
if (!match) {
|
|
29264
|
+
logDebug("reconcile", "no pending review for session; nothing to reconcile", { ...ctx });
|
|
29265
|
+
return;
|
|
29266
|
+
}
|
|
29267
|
+
ctx.reviewId = match.id;
|
|
29268
|
+
await deps.resolveReview(match.id);
|
|
29269
|
+
logInfo("reconcile", "terminal approval reconciled", { ...ctx });
|
|
29270
|
+
} catch (err) {
|
|
29271
|
+
logDebug("reconcile", "reconcile skipped", { ...ctx, err: String(err) });
|
|
29272
|
+
}
|
|
29273
|
+
}
|
|
29274
|
+
|
|
29275
|
+
// src/commands/reconcile.ts
|
|
29276
|
+
function prodReconcileDeps(baseUrl, adapter) {
|
|
29277
|
+
return {
|
|
29278
|
+
parseHookInput: (stdin) => adapter.parseHookInput(stdin),
|
|
29279
|
+
listReviews: () => listReviews(baseUrl),
|
|
29280
|
+
resolveReview: (id) => resolveReview(baseUrl, id, { behavior: "allow" })
|
|
29281
|
+
};
|
|
29282
|
+
}
|
|
29283
|
+
async function runReconcileSubcommand() {
|
|
29284
|
+
try {
|
|
29285
|
+
const loaded = loadSettings();
|
|
29286
|
+
setLogLevel(loaded.logging.level);
|
|
29287
|
+
setRedact(loaded.logging.redact);
|
|
29288
|
+
warnInvalidEnvVars((msg) => logWarn("env", msg));
|
|
29289
|
+
const adapter = selectAdapter();
|
|
29290
|
+
const baseUrl = `http://localhost:${getPort(loaded)}`;
|
|
29291
|
+
const stdin = await Bun.stdin.text();
|
|
29292
|
+
await runReconcile(stdin, prodReconcileDeps(baseUrl, adapter));
|
|
29293
|
+
} catch (err) {
|
|
29294
|
+
logDebug("reconcile", "reconcile subcommand skipped", { err: String(err) });
|
|
29295
|
+
}
|
|
29296
|
+
process.exit(0);
|
|
29297
|
+
}
|
|
29298
|
+
|
|
29019
29299
|
// src/commands/redact.ts
|
|
29020
29300
|
function runRedactSubcommand() {
|
|
29021
29301
|
try {
|
|
@@ -30391,17 +30671,24 @@ function denyDecision(reason) {
|
|
|
30391
30671
|
class TimeoutError extends Error {
|
|
30392
30672
|
}
|
|
30393
30673
|
function withTimeout(p, ms2, message) {
|
|
30394
|
-
return new Promise((
|
|
30674
|
+
return new Promise((resolve2, reject) => {
|
|
30395
30675
|
const t = setTimeout(() => reject(new TimeoutError(message)), ms2);
|
|
30396
30676
|
p.then((v3) => {
|
|
30397
30677
|
clearTimeout(t);
|
|
30398
|
-
|
|
30678
|
+
resolve2(v3);
|
|
30399
30679
|
}, (e) => {
|
|
30400
30680
|
clearTimeout(t);
|
|
30401
30681
|
reject(e);
|
|
30402
30682
|
});
|
|
30403
30683
|
});
|
|
30404
30684
|
}
|
|
30685
|
+
async function expireAbandoned(expire, handle) {
|
|
30686
|
+
if (!handle)
|
|
30687
|
+
return;
|
|
30688
|
+
try {
|
|
30689
|
+
await expire(handle.baseUrl, handle.id);
|
|
30690
|
+
} catch {}
|
|
30691
|
+
}
|
|
30405
30692
|
async function runReview(stdin, deps) {
|
|
30406
30693
|
let step = "parse";
|
|
30407
30694
|
const ctx = {};
|
|
@@ -30421,6 +30708,7 @@ async function runReview(stdin, deps) {
|
|
|
30421
30708
|
step = "postReview";
|
|
30422
30709
|
const { id, hasLiveClient } = await deps.postReview(baseUrl, input);
|
|
30423
30710
|
ctx.reviewId = id;
|
|
30711
|
+
deps.onPosted?.(baseUrl, id);
|
|
30424
30712
|
logDebug("review", `review created: ${shortId(id)}`, { ...ctx });
|
|
30425
30713
|
const open = new URL(baseUrl);
|
|
30426
30714
|
open.hostname = VANITY_HOST;
|
|
@@ -30495,24 +30783,36 @@ async function runReviewSubcommand() {
|
|
|
30495
30783
|
setRedact(loaded.logging.redact);
|
|
30496
30784
|
warnInvalidEnvVars((msg) => logWarn("env", msg));
|
|
30497
30785
|
const adapter = selectAdapter();
|
|
30786
|
+
let hookInput;
|
|
30787
|
+
let posted;
|
|
30498
30788
|
let responded = false;
|
|
30499
30789
|
const respond = (decision) => {
|
|
30500
30790
|
if (responded)
|
|
30501
30791
|
return;
|
|
30502
30792
|
responded = true;
|
|
30503
|
-
process.stdout.write(`${adapter.emitDecision(decision)}
|
|
30793
|
+
process.stdout.write(`${adapter.emitDecision(decision, hookInput)}
|
|
30504
30794
|
`);
|
|
30505
30795
|
};
|
|
30506
|
-
const denyAndExit = (reason) => {
|
|
30796
|
+
const denyAndExit = async (reason) => {
|
|
30507
30797
|
if (!responded)
|
|
30508
30798
|
logError("signal", new Error(reason));
|
|
30509
30799
|
respond({ behavior: "deny", feedback: `${reason} See ${logFile()}.`, decidedAt: Date.now() });
|
|
30800
|
+
await expireAbandoned(expireReview, posted);
|
|
30510
30801
|
process.exit(0);
|
|
30511
30802
|
};
|
|
30512
|
-
process.once("SIGINT", () => denyAndExit("caret: interrupted (SIGINT) \u2014 denying to fail safe."));
|
|
30513
|
-
process.once("SIGTERM", () => denyAndExit("caret: terminated (SIGTERM) \u2014 denying to fail safe."));
|
|
30803
|
+
process.once("SIGINT", () => void denyAndExit("caret: interrupted (SIGINT) \u2014 denying to fail safe."));
|
|
30804
|
+
process.once("SIGTERM", () => void denyAndExit("caret: terminated (SIGTERM) \u2014 denying to fail safe."));
|
|
30514
30805
|
const stdin = await Bun.stdin.text();
|
|
30515
|
-
|
|
30806
|
+
try {
|
|
30807
|
+
hookInput = adapter.parseHookInput(stdin);
|
|
30808
|
+
} catch {
|
|
30809
|
+
hookInput = undefined;
|
|
30810
|
+
}
|
|
30811
|
+
const deps = prodReviewDeps(loaded, adapter);
|
|
30812
|
+
deps.onPosted = (baseUrl, id) => {
|
|
30813
|
+
posted = { baseUrl, id };
|
|
30814
|
+
};
|
|
30815
|
+
const out = await runReview(stdin, deps);
|
|
30516
30816
|
respond(out);
|
|
30517
30817
|
process.exit(0);
|
|
30518
30818
|
}
|
|
@@ -32570,10 +32870,11 @@ function runProgram(program2, onError) {
|
|
|
32570
32870
|
|
|
32571
32871
|
// src/cli.ts
|
|
32572
32872
|
function buildProgram() {
|
|
32573
|
-
const program2 = createProgram("caret", "caret hook CLI: daemon | prewarm | review | redact | discovery | install-opencode").version(VERSION);
|
|
32873
|
+
const program2 = createProgram("caret", "caret hook CLI: daemon | prewarm | review | reconcile | redact | discovery | install-opencode").version(VERSION);
|
|
32574
32874
|
program2.command("daemon").description("run the review daemon").option("--ephemeral", "bind an OS-assigned port instead of the configured one").action((opts) => runDaemon({ ephemeral: opts.ephemeral ?? false }));
|
|
32575
32875
|
program2.command("prewarm").description("warm-start the daemon").action(() => runPrewarm());
|
|
32576
32876
|
program2.command("review").description("review a plan from stdin (ExitPlanMode hook)").action(() => runReviewSubcommand());
|
|
32877
|
+
program2.command("reconcile").description("reconcile a terminal plan approval into the daemon (ExitPlanMode PostToolUse hook)").action(() => runReconcileSubcommand());
|
|
32577
32878
|
program2.command("redact").description("write redacted copies of the logs").action(() => runRedactSubcommand());
|
|
32578
32879
|
program2.command("discovery").description("print a diagnostics report").option("--json", "emit the machine-readable JSON document").action((opts) => runDiscoverySubcommand({ json: opts.json ?? false }));
|
|
32579
32880
|
program2.command("install-opencode").description("install (or with --uninstall, remove) caret's OpenCode plugin + commands").option("--uninstall", "remove caret's OpenCode files instead of installing them").option("--dry-run", "print what would change without writing").action((opts) => runInstallOpencodeSubcommand({
|