@whittlelabs/sifter 0.30.0 → 0.30.1

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 (3) hide show
  1. package/bin.js +139 -17
  2. package/bin.js.map +2 -2
  3. package/package.json +1 -1
package/bin.js CHANGED
@@ -10008,6 +10008,8 @@ var require_claude_code = __commonJS({
10008
10008
  exports2.createClaudeCodeExecutor = createClaudeCodeExecutor;
10009
10009
  exports2.parseStreamJson = parseStreamJson;
10010
10010
  exports2.relativeWorkspaceReads = relativeWorkspaceReads;
10011
+ exports2.globToRegExp = globToRegExp;
10012
+ exports2.creditOverlayReads = creditOverlayReads;
10011
10013
  exports2.extractJsonObject = extractJsonObject;
10012
10014
  var child_process_1 = require("child_process");
10013
10015
  var fs_1 = require("fs");
@@ -10092,12 +10094,11 @@ var require_claude_code = __commonJS({
10092
10094
  promptOnlyScratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-run-"));
10093
10095
  }
10094
10096
  const runCwd = workspace?.cwd ?? promptOnlyScratch;
10095
- const { response, readPaths, toolText } = await this.runClaude(prompt, spec, runCwd, childEnv, signal, hintedModel, reporter);
10097
+ const { response, readPaths, toolInputs } = await this.runClaude(prompt, spec, runCwd, childEnv, signal, hintedModel, reporter, void 0, dispatch.job.id);
10096
10098
  if (workspace) {
10097
10099
  const reads = [...readPaths];
10098
- for (const rel of workspace.overlay?.writtenPaths ?? []) {
10099
- if (toolText.includes(rel))
10100
- reads.push(path_1.default.join(runCwd, rel));
10100
+ for (const rel of creditOverlayReads(toolInputs, workspace.overlay?.writtenPaths ?? [])) {
10101
+ reads.push(path_1.default.join(runCwd, rel));
10101
10102
  }
10102
10103
  response.outputs.push({
10103
10104
  label: "workspace_reads",
@@ -10155,14 +10156,14 @@ var require_claude_code = __commonJS({
10155
10156
  outputLabel: handle.outputLabel,
10156
10157
  outputSchema: handle.outputSchema
10157
10158
  };
10158
- const { response } = await this.runClaude(followUp, spec, scratch, handle.childEnv, signal, handle.model, null, handle.sessionId);
10159
+ const { response } = await this.runClaude(followUp, spec, scratch, handle.childEnv, signal, handle.model, null, handle.sessionId, `${handle.sessionId}-repair`);
10159
10160
  return response;
10160
10161
  } finally {
10161
10162
  await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
10162
10163
  });
10163
10164
  }
10164
10165
  }
10165
- runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId) {
10166
+ runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId, transcriptName) {
10166
10167
  const outputLabel = spec.outputLabel;
10167
10168
  const model = modelOverride ?? this.config.model;
10168
10169
  return new Promise((resolve, reject) => {
@@ -10251,6 +10252,9 @@ var require_claude_code = __commonJS({
10251
10252
  tap.end();
10252
10253
  }
10253
10254
  const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
10255
+ if (this.config.keepTranscripts && transcriptName) {
10256
+ void keepTranscript((0, apply_1.expandHome)(this.config.keepTranscripts), transcriptName, stdout);
10257
+ }
10254
10258
  if (code === 0) {
10255
10259
  try {
10256
10260
  const stream = parseStreamJson(stdout);
@@ -10286,7 +10290,7 @@ var require_claude_code = __commonJS({
10286
10290
  } : {}
10287
10291
  },
10288
10292
  readPaths: stream.readPaths,
10289
- toolText: stream.toolText
10293
+ toolInputs: stream.toolInputs
10290
10294
  }));
10291
10295
  } catch (err) {
10292
10296
  settle(() => reject(err instanceof Error ? err : new Error(String(err))));
@@ -10334,6 +10338,12 @@ var require_claude_code = __commonJS({
10334
10338
  }
10335
10339
  validated.timeout = config.timeout;
10336
10340
  }
10341
+ if (config.keepTranscripts !== void 0) {
10342
+ if (typeof config.keepTranscripts !== "string" || config.keepTranscripts.length === 0) {
10343
+ throw new Error('"keepTranscripts" must be a non-empty directory path');
10344
+ }
10345
+ validated.keepTranscripts = config.keepTranscripts;
10346
+ }
10337
10347
  if (config.overlayAllowedHosts !== void 0) {
10338
10348
  if (!Array.isArray(config.overlayAllowedHosts) || !config.overlayAllowedHosts.every((h) => typeof h === "string")) {
10339
10349
  throw new Error('"overlayAllowedHosts" must be an array of strings');
@@ -10358,7 +10368,7 @@ var require_claude_code = __commonJS({
10358
10368
  let resultMeta = null;
10359
10369
  let structuredOutput = null;
10360
10370
  let sessionId = null;
10361
- let toolText = "";
10371
+ const toolInputs = [];
10362
10372
  for (const line of stdout.split("\n")) {
10363
10373
  const trimmed = line.trim();
10364
10374
  if (!trimmed.startsWith("{"))
@@ -10384,7 +10394,7 @@ var require_claude_code = __commonJS({
10384
10394
  readPaths.push(fp);
10385
10395
  }
10386
10396
  if (b.input && typeof b.input === "object")
10387
- toolText += JSON.stringify(b.input);
10397
+ toolInputs.push(JSON.stringify(b.input));
10388
10398
  }
10389
10399
  } else if (event.type === "result") {
10390
10400
  if (typeof event.result === "string" && event.result.trim().length > 0) {
@@ -10412,24 +10422,131 @@ var require_claude_code = __commonJS({
10412
10422
  };
10413
10423
  }
10414
10424
  }
10415
- return { finalText, readPaths, usage, resultMeta, structuredOutput, sessionId, toolText };
10425
+ return { finalText, readPaths, usage, resultMeta, structuredOutput, sessionId, toolInputs };
10416
10426
  }
10417
10427
  var MAX_REPORTED_READS = 2e3;
10418
10428
  function relativeWorkspaceReads(readPaths, cwd) {
10419
10429
  const root = path_1.default.resolve(cwd);
10430
+ const roots = [root];
10431
+ try {
10432
+ const real = (0, fs_1.realpathSync)(root);
10433
+ if (real !== root)
10434
+ roots.push(real);
10435
+ } catch {
10436
+ }
10420
10437
  const out = /* @__PURE__ */ new Set();
10421
10438
  for (const p of readPaths) {
10422
10439
  const resolved = path_1.default.resolve(root, p);
10423
- if (resolved === root)
10424
- continue;
10425
- if (!resolved.startsWith(root + path_1.default.sep))
10426
- continue;
10427
- out.add(resolved.slice(root.length + 1));
10440
+ for (const r of roots) {
10441
+ if (resolved === r)
10442
+ break;
10443
+ if (!resolved.startsWith(r + path_1.default.sep))
10444
+ continue;
10445
+ out.add(resolved.slice(r.length + 1));
10446
+ break;
10447
+ }
10428
10448
  if (out.size >= MAX_REPORTED_READS)
10429
10449
  break;
10430
10450
  }
10431
10451
  return [...out].sort();
10432
10452
  }
10453
+ var GLOB_CHARS = /[*?[{]/;
10454
+ function escapeRegExp(s) {
10455
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10456
+ }
10457
+ function globToRegExp(glob) {
10458
+ let re = "^";
10459
+ for (let i = 0; i < glob.length; i++) {
10460
+ const ch = glob[i];
10461
+ if (ch === "*") {
10462
+ if (glob[i + 1] === "*") {
10463
+ if (glob[i + 2] === "/") {
10464
+ re += "(?:.*/)?";
10465
+ i += 2;
10466
+ } else {
10467
+ re += ".*";
10468
+ i += 1;
10469
+ }
10470
+ } else {
10471
+ re += "[^/]*";
10472
+ }
10473
+ } else if (ch === "?") {
10474
+ re += "[^/]";
10475
+ } else if (ch === "[") {
10476
+ const end = glob.indexOf("]", i + 1);
10477
+ if (end === -1)
10478
+ return null;
10479
+ let cls = glob.slice(i + 1, end);
10480
+ if (cls.startsWith("!"))
10481
+ cls = `^${cls.slice(1)}`;
10482
+ re += `[${cls.replace(/\\/g, "\\\\")}]`;
10483
+ i = end;
10484
+ } else if (ch === "{") {
10485
+ const end = glob.indexOf("}", i + 1);
10486
+ if (end === -1)
10487
+ return null;
10488
+ re += `(?:${glob.slice(i + 1, end).split(",").map(escapeRegExp).join("|")})`;
10489
+ i = end;
10490
+ } else {
10491
+ re += escapeRegExp(ch);
10492
+ }
10493
+ }
10494
+ return new RegExp(`${re}$`);
10495
+ }
10496
+ function creditOverlayReads(toolInputs, writtenPaths) {
10497
+ if (writtenPaths.length === 0 || toolInputs.length === 0)
10498
+ return [];
10499
+ const dirs = /* @__PURE__ */ new Set();
10500
+ for (const rel of writtenPaths) {
10501
+ const parts = rel.split("/");
10502
+ for (let i = 1; i < parts.length; i++)
10503
+ dirs.add(`${parts.slice(0, i).join("/")}/`);
10504
+ }
10505
+ const dirAlt = [...dirs].map(escapeRegExp).join("|");
10506
+ const wordRe = dirs.size > 0 ? new RegExp(`(?:${dirAlt})[^\\s"'\`;|&<>(),\\\\]*`, "g") : null;
10507
+ const findRe = dirs.size > 0 ? new RegExp(`\\bfind\\s+(?:\\./)?(${[...dirs].map((d) => escapeRegExp(d.slice(0, -1))).join("|")})/?(?=\\s|$)`, "g") : null;
10508
+ const readerRe = /(?:^|[\s"])(?:-exec|-execdir|xargs)\b[^;|&]*\b(?:cat|head|tail|sed|awk|less|more|bat)\b/;
10509
+ const credited = /* @__PURE__ */ new Set();
10510
+ for (const input of toolInputs) {
10511
+ for (const rel of writtenPaths) {
10512
+ if (input.includes(rel))
10513
+ credited.add(rel);
10514
+ }
10515
+ if (!wordRe)
10516
+ continue;
10517
+ for (const match of input.matchAll(wordRe)) {
10518
+ const word = match[0];
10519
+ if (!GLOB_CHARS.test(word))
10520
+ continue;
10521
+ const re = globToRegExp(word);
10522
+ if (!re)
10523
+ continue;
10524
+ for (const rel of writtenPaths) {
10525
+ if (re.test(rel))
10526
+ credited.add(rel);
10527
+ }
10528
+ }
10529
+ if (findRe && readerRe.test(input)) {
10530
+ for (const match of input.matchAll(findRe)) {
10531
+ const dir = `${match[1]}/`;
10532
+ for (const rel of writtenPaths) {
10533
+ if (rel.startsWith(dir))
10534
+ credited.add(rel);
10535
+ }
10536
+ }
10537
+ }
10538
+ }
10539
+ return [...credited].sort();
10540
+ }
10541
+ async function keepTranscript(dir, name, stdout) {
10542
+ try {
10543
+ await fs_1.promises.mkdir(dir, { recursive: true });
10544
+ const safe = name.replace(/[^A-Za-z0-9._-]/g, "_");
10545
+ await fs_1.promises.writeFile(path_1.default.join(dir, `${safe}.jsonl`), stdout);
10546
+ } catch (err) {
10547
+ console.warn(`[shuttle] could not keep the transcript for ${name} under ${dir}: ${err instanceof Error ? err.message : String(err)}`);
10548
+ }
10549
+ }
10433
10550
  function extractJsonObject(s) {
10434
10551
  const exact = tryParseObject(s);
10435
10552
  if (exact)
@@ -25422,7 +25539,7 @@ var import_path = require("path");
25422
25539
  var import_promises = require("fs/promises");
25423
25540
  var import_yaml = __toESM(require_dist4());
25424
25541
  var import_shuttle = __toESM(require_dist5());
25425
- var buildVersion = true ? "0.30.0" : pkg.version;
25542
+ var buildVersion = true ? "0.30.1" : pkg.version;
25426
25543
  var sifterBrand = {
25427
25544
  product: {
25428
25545
  id: "sifter",
@@ -25467,7 +25584,12 @@ var sifterBrand = {
25467
25584
  "https://api.sift.whittlelabs.com",
25468
25585
  "https://api.sift.stage.whittlelabs.com",
25469
25586
  "http://localhost:3008"
25470
- ]
25587
+ ],
25588
+ // Transcript retention is a diagnostic switch a person flips for one
25589
+ // session, so it rides the environment rather than a file they would
25590
+ // have to remember to unset. The shared package reads no env of its
25591
+ // own (BE-005); this brand is where the variable becomes config.
25592
+ ...process.env.SHUTTLE_KEEP_TRANSCRIPTS ? { keepTranscripts: process.env.SHUTTLE_KEEP_TRANSCRIPTS } : {}
25471
25593
  }
25472
25594
  },
25473
25595
  // The environments Whittle Labs operates, keyed by the profile name that