backthread 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-bundle/backthread.js +49 -10
- package/hooks/hooks.json +2 -2
- package/package.json +1 -1
|
@@ -7395,6 +7395,19 @@ function isForeignRelativePath(p) {
|
|
|
7395
7395
|
const stripped = p.replace(/^(?:\.[\\/])+/, "");
|
|
7396
7396
|
return /^\.\.(?:[\\/]|$)/.test(stripped);
|
|
7397
7397
|
}
|
|
7398
|
+
function normalizeRepoRelative(rel) {
|
|
7399
|
+
const out = [];
|
|
7400
|
+
for (const seg of rel.split(/[\\/]/)) {
|
|
7401
|
+
if (seg === "" || seg === ".") continue;
|
|
7402
|
+
if (seg === "..") {
|
|
7403
|
+
if (out.length === 0) return null;
|
|
7404
|
+
out.pop();
|
|
7405
|
+
continue;
|
|
7406
|
+
}
|
|
7407
|
+
out.push(seg);
|
|
7408
|
+
}
|
|
7409
|
+
return out.join("/");
|
|
7410
|
+
}
|
|
7398
7411
|
function relativizeUnder(abs, root) {
|
|
7399
7412
|
const trimmedRoot = root.replace(/\/+$/, "");
|
|
7400
7413
|
if (trimmedRoot.length === 0) return null;
|
|
@@ -7454,10 +7467,12 @@ function sessionPaths(records, repoRoot) {
|
|
|
7454
7467
|
if (root === null) continue;
|
|
7455
7468
|
const rel = relativizeUnder(p, root);
|
|
7456
7469
|
if (rel === null || rel.length === 0) continue;
|
|
7457
|
-
|
|
7470
|
+
const norm = normalizeRepoRelative(rel);
|
|
7471
|
+
if (norm === null || norm.length === 0) continue;
|
|
7472
|
+
seen.add(norm);
|
|
7458
7473
|
} else if (!isForeignRelativePath(p)) {
|
|
7459
|
-
const
|
|
7460
|
-
if (
|
|
7474
|
+
const norm = normalizeRepoRelative(p.replace(/^(?:\.\/)+/, ""));
|
|
7475
|
+
if (norm !== null && norm.length > 0) seen.add(norm);
|
|
7461
7476
|
}
|
|
7462
7477
|
}
|
|
7463
7478
|
}
|
|
@@ -7892,7 +7907,9 @@ var TRUST_COPY = [
|
|
|
7892
7907
|
" code blocks replaced with [code redacted] \u2014 to Backthread's Worker, never source.",
|
|
7893
7908
|
" Full details: https://app.backthread.dev/security"
|
|
7894
7909
|
].join("\n");
|
|
7895
|
-
var HOOK_COMMAND = "npx backthread capture";
|
|
7910
|
+
var HOOK_COMMAND = "npx backthread capture --from-hook --agent claude-code --detach";
|
|
7911
|
+
var LEGACY_HOOK_COMMANDS = ["npx backthread capture"];
|
|
7912
|
+
var OUR_HOOK_COMMANDS = /* @__PURE__ */ new Set([HOOK_COMMAND, ...LEGACY_HOOK_COMMANDS]);
|
|
7896
7913
|
async function registerHook(cwd, deps = {}) {
|
|
7897
7914
|
const doReadFile = deps.readFileImpl ?? ((p) => readFile3(p, "utf8"));
|
|
7898
7915
|
const doWriteFile = deps.writeFileImpl ?? ((p, d) => writeFile3(p, d));
|
|
@@ -7938,21 +7955,43 @@ function isNotFound2(err) {
|
|
|
7938
7955
|
function mergeSessionEndHook(settings) {
|
|
7939
7956
|
const hooks = settings.hooks && typeof settings.hooks === "object" && !Array.isArray(settings.hooks) ? { ...settings.hooks } : {};
|
|
7940
7957
|
const sessionEnd = Array.isArray(hooks.SessionEnd) ? [...hooks.SessionEnd] : [];
|
|
7941
|
-
if (sessionEnd.some(
|
|
7942
|
-
|
|
7943
|
-
|
|
7958
|
+
if (sessionEnd.some((g) => groupHasCommand(g, HOOK_COMMAND))) return null;
|
|
7959
|
+
let migrated = false;
|
|
7960
|
+
const nextSessionEnd = sessionEnd.map((group) => {
|
|
7961
|
+
const rewritten = rewriteLegacyCommand(group);
|
|
7962
|
+
if (rewritten !== group) migrated = true;
|
|
7963
|
+
return rewritten;
|
|
7944
7964
|
});
|
|
7945
|
-
|
|
7965
|
+
if (!migrated) {
|
|
7966
|
+
nextSessionEnd.push({ hooks: [{ type: "command", command: HOOK_COMMAND }] });
|
|
7967
|
+
}
|
|
7968
|
+
hooks.SessionEnd = nextSessionEnd;
|
|
7946
7969
|
return { ...settings, hooks };
|
|
7947
7970
|
}
|
|
7948
|
-
function
|
|
7971
|
+
function groupHasCommand(group, command) {
|
|
7949
7972
|
if (!group || typeof group !== "object") return false;
|
|
7950
7973
|
const inner = group.hooks;
|
|
7951
7974
|
if (!Array.isArray(inner)) return false;
|
|
7952
7975
|
return inner.some(
|
|
7953
|
-
(h) => h && typeof h === "object" && h.command ===
|
|
7976
|
+
(h) => h && typeof h === "object" && h.command === command
|
|
7954
7977
|
);
|
|
7955
7978
|
}
|
|
7979
|
+
function rewriteLegacyCommand(group) {
|
|
7980
|
+
if (!group || typeof group !== "object") return group;
|
|
7981
|
+
const inner = group.hooks;
|
|
7982
|
+
if (!Array.isArray(inner)) return group;
|
|
7983
|
+
let changed = false;
|
|
7984
|
+
const nextInner = inner.map((h) => {
|
|
7985
|
+
if (!h || typeof h !== "object") return h;
|
|
7986
|
+
const cmd = h.command;
|
|
7987
|
+
if (typeof cmd === "string" && cmd !== HOOK_COMMAND && OUR_HOOK_COMMANDS.has(cmd)) {
|
|
7988
|
+
changed = true;
|
|
7989
|
+
return { ...h, command: HOOK_COMMAND };
|
|
7990
|
+
}
|
|
7991
|
+
return h;
|
|
7992
|
+
});
|
|
7993
|
+
return changed ? { ...group, hooks: nextInner } : group;
|
|
7994
|
+
}
|
|
7956
7995
|
async function runInstall(opts = {}, deps = {}) {
|
|
7957
7996
|
const env = opts.env ?? process.env;
|
|
7958
7997
|
const log = opts.log ?? ((m) => console.error(m));
|
package/hooks/hooks.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "The SessionEnd capture hook, declared in the PLUGIN MANIFEST (referenced from .claude-plugin/plugin.json). When Backthread is installed as a Claude Code plugin, this registers the hook automatically — no mutation of the user's .claude/settings.json.
|
|
2
|
+
"$comment": "The SessionEnd capture hook, declared in the PLUGIN MANIFEST (referenced from .claude-plugin/plugin.json). When Backthread is installed as a Claude Code plugin, this registers the hook automatically — no mutation of the user's .claude/settings.json. The command routes through the shared `--from-hook` entrypoint with `--detach`: it reads the SessionEnd payload off stdin, then re-spawns a DETACHED worker that does the slow LOCALLY-redacted redact→infer→persist round-trip and returns immediately — so a ≥30s inference can't be SIGTERM'd by CC's SessionEnd hook timeout (or reaped on session exit). Best-effort, never blocks/delays the session, always exits 0 (ARP-682). `--agent claude-code` selects the CC payload shape. Mirrored by the .claude/settings.json fallback that `backthread install` writes for the bare-npx (non-plugin) path. We register ONLY SessionEnd (once per session) on purpose — `runCapture` also handles a Stop payload, but Stop fires on every turn-end, which would capture far too aggressively, so Stop is intentionally NOT registered here.",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"SessionEnd": [
|
|
5
5
|
{
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "npx backthread capture"
|
|
9
|
+
"command": "npx backthread capture --from-hook --agent claude-code --detach"
|
|
10
10
|
}
|
|
11
11
|
]
|
|
12
12
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "backthread",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Backthread CLI — capture the why behind your AI-coded changes from your Claude Code sessions, and ask how your codebase works without leaving the terminal. Source code and tool I/O are redacted locally before anything leaves your machine.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Backthread",
|