backthread 0.1.2 → 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.
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "backthread",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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",