backthread 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -14,7 +14,7 @@ sessions, so you can ask *"how does X work?"* and stay oriented without
14
14
  spelunking through PRs. The decisions become a live **"How it works"** diagram
15
15
  and changelog at [backthread.dev](https://backthread.dev).
16
16
 
17
- ## Your code never leaves your machine
17
+ ## Your source code never leaves your machine
18
18
 
19
19
  Backthread reads your agent **transcripts**, not your repo. Before anything is
20
20
  sent, the CLI redacts every transcript **locally**:
@@ -23,9 +23,18 @@ sent, the CLI redacts every transcript **locally**:
23
23
  - **Keeps** only natural-language prompts and the agent's reasoning.
24
24
  - **Redacts** any fenced code block to `[code redacted]`.
25
25
 
26
- Only the derived, natural-language *decisions* ever leave your machine. The
27
- redaction fence is open source ([`@backthread/redact`](https://www.npmjs.com/package/@backthread/redact))
28
- so you can verify it read more at [backthread.dev/security](https://backthread.dev/security).
26
+ So no source code and no tool I/O ever leave your machine. Because the default
27
+ path runs inference on our servers, what *does* leave is the **redacted
28
+ transcript** natural-language prose only. The Worker re-runs the fenced-code
29
+ scrub server-side as a fail-closed backstop, derives the **decisions**, and
30
+ discards the transcript right after — processed in memory, never stored. Only
31
+ the decisions are persisted.
32
+
33
+ That's a weaker claim than the bring-your-own-key path — where nothing but the
34
+ derived decisions ever leaves your machine — which is designed and coming. We'd
35
+ rather say so than paper over it. The redaction fence is open source
36
+ ([`@backthread/redact`](https://www.npmjs.com/package/@backthread/redact)) so you
37
+ can verify it — read more at [backthread.dev/security](https://backthread.dev/security).
29
38
 
30
39
  ## Quick start
31
40
 
@@ -7380,6 +7380,89 @@ function sessionTimestamp(records) {
7380
7380
  }
7381
7381
  return latestIso;
7382
7382
  }
7383
+ function stripLeadingSlashes(p) {
7384
+ let i = 0;
7385
+ while (i < p.length && p[i] === "/") i += 1;
7386
+ return p.slice(i);
7387
+ }
7388
+ function isAbsolute(p) {
7389
+ return p.startsWith("/");
7390
+ }
7391
+ function isForeignRelativePath(p) {
7392
+ if (p.startsWith("~")) return true;
7393
+ if (p.startsWith("\\")) return true;
7394
+ if (/^[A-Za-z]:[\\/]/.test(p)) return true;
7395
+ const stripped = p.replace(/^(?:\.[\\/])+/, "");
7396
+ return /^\.\.(?:[\\/]|$)/.test(stripped);
7397
+ }
7398
+ function relativizeUnder(abs, root) {
7399
+ const trimmedRoot = root.replace(/\/+$/, "");
7400
+ if (trimmedRoot.length === 0) return null;
7401
+ if (abs === trimmedRoot) return "";
7402
+ const prefix = trimmedRoot + "/";
7403
+ if (!abs.startsWith(prefix)) return null;
7404
+ return stripLeadingSlashes(abs.slice(trimmedRoot.length));
7405
+ }
7406
+ function pathsFromRecord(rec) {
7407
+ if (!rec || typeof rec !== "object") return [];
7408
+ const out = [];
7409
+ const r = rec;
7410
+ const pushFromInput = (input) => {
7411
+ if (!input || typeof input !== "object") return;
7412
+ const i = input;
7413
+ for (const v of [i.file_path, i.path, i.notebook_path, i.cwd]) {
7414
+ if (typeof v === "string" && v.trim().length > 0) out.push(v.trim());
7415
+ }
7416
+ };
7417
+ const content = r.message?.content;
7418
+ if (Array.isArray(content)) {
7419
+ for (const raw of content) {
7420
+ if (!raw || typeof raw !== "object") continue;
7421
+ const block = raw;
7422
+ if (block.type === "tool_use") pushFromInput(block.input);
7423
+ }
7424
+ }
7425
+ if (r.payload && typeof r.payload === "object" && r.payload.type === "function_call") {
7426
+ const args = r.payload.arguments;
7427
+ if (typeof args === "string") {
7428
+ try {
7429
+ pushFromInput(JSON.parse(args));
7430
+ } catch {
7431
+ }
7432
+ } else {
7433
+ pushFromInput(args);
7434
+ }
7435
+ }
7436
+ return out;
7437
+ }
7438
+ function codexSessionCwd(records) {
7439
+ for (const raw of records) {
7440
+ if (!raw || typeof raw !== "object") continue;
7441
+ const rec = raw;
7442
+ if (rec.type !== "session_meta") continue;
7443
+ const cwd = rec.payload?.cwd;
7444
+ if (typeof cwd === "string" && cwd.trim().length > 0) return cwd.trim();
7445
+ }
7446
+ return null;
7447
+ }
7448
+ function sessionPaths(records, repoRoot) {
7449
+ const root = (repoRoot && repoRoot.trim().length > 0 ? repoRoot.trim() : codexSessionCwd(records)) ?? null;
7450
+ const seen = /* @__PURE__ */ new Set();
7451
+ for (const rec of records) {
7452
+ for (const p of pathsFromRecord(rec)) {
7453
+ if (isAbsolute(p)) {
7454
+ if (root === null) continue;
7455
+ const rel = relativizeUnder(p, root);
7456
+ if (rel === null || rel.length === 0) continue;
7457
+ seen.add(rel);
7458
+ } else if (!isForeignRelativePath(p)) {
7459
+ const rel = p.replace(/^(?:\.\/)+/, "");
7460
+ if (rel.length > 0) seen.add(rel);
7461
+ }
7462
+ }
7463
+ }
7464
+ return Array.from(seen).sort();
7465
+ }
7383
7466
 
7384
7467
  // src/repo.ts
7385
7468
  import { execFileSync } from "node:child_process";
@@ -7459,6 +7542,7 @@ async function serverInfer(transcript, config2, opts = {}) {
7459
7542
  body.persist = true;
7460
7543
  body.repo = { owner: opts.repo.owner, name: opts.repo.name };
7461
7544
  if (opts.decidedAt) body.decidedAt = opts.decidedAt;
7545
+ if (opts.filePaths && opts.filePaths.length > 0) body.filePaths = opts.filePaths;
7462
7546
  }
7463
7547
  let res;
7464
7548
  try {
@@ -8263,6 +8347,7 @@ async function runCapture(input, deps = {}) {
8263
8347
  const records = parseJsonl(rawTranscript);
8264
8348
  const redacted = redactTranscript(records);
8265
8349
  const decidedAt = sessionTimestamp(records) ?? void 0;
8350
+ const filePaths = sessionPaths(records, input.cwd);
8266
8351
  const sessionId = redacted.sessionId ?? input.session_id ?? null;
8267
8352
  if (redacted.turns.length === 0) {
8268
8353
  return {
@@ -8281,6 +8366,7 @@ async function runCapture(input, deps = {}) {
8281
8366
  env,
8282
8367
  fetchImpl: deps.fetchImpl,
8283
8368
  decidedAt,
8369
+ filePaths,
8284
8370
  ...repo ? { persist: true, repo } : {}
8285
8371
  });
8286
8372
  if (!result.ok) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "backthread",
3
- "version": "0.1.1",
4
- "description": "Backthread CLI — capture the *why* of your AI-coded changes from your Claude Code sessions, and query your codebase's architectural memory without leaving the terminal. Source code and tool I/O are redacted locally before anything leaves your machine.",
3
+ "version": "0.1.3",
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",
7
7
  "homepage": "https://backthread.dev",