@whittlelabs/sifter 0.10.0 → 0.12.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.
Files changed (3) hide show
  1. package/bin.js +99 -9
  2. package/bin.js.map +2 -2
  3. package/package.json +1 -1
package/bin.js CHANGED
@@ -19256,6 +19256,8 @@ var require_claude_code = __commonJS({
19256
19256
  };
19257
19257
  Object.defineProperty(exports2, "__esModule", { value: true });
19258
19258
  exports2.createClaudeCodeExecutor = createClaudeCodeExecutor;
19259
+ exports2.parseStreamJson = parseStreamJson;
19260
+ exports2.relativeWorkspaceReads = relativeWorkspaceReads;
19259
19261
  exports2.extractJsonObject = extractJsonObject;
19260
19262
  var child_process_1 = require("child_process");
19261
19263
  var fs_1 = require("fs");
@@ -19323,8 +19325,13 @@ var require_claude_code = __commonJS({
19323
19325
  const childEnv = buildChildEnv(credentials, configDir);
19324
19326
  const claudeHints = (0, loom_1.pickProviderHints)(spec, "claude-code");
19325
19327
  const hintedModel = typeof claudeHints.model === "string" && claudeHints.model.length > 0 ? claudeHints.model : void 0;
19326
- const response = await this.runClaude(prompt, spec, workspace?.cwd ?? this.config.cwd, childEnv, signal, hintedModel);
19328
+ const runCwd = workspace?.cwd ?? this.config.cwd;
19329
+ const { response, readPaths } = await this.runClaude(prompt, spec, runCwd, childEnv, signal, hintedModel);
19327
19330
  if (workspace) {
19331
+ response.outputs.push({
19332
+ label: "workspace_reads",
19333
+ content: { paths: relativeWorkspaceReads(readPaths, runCwd) }
19334
+ });
19328
19335
  response.outputs.push({
19329
19336
  label: "workspace",
19330
19337
  content: {
@@ -19350,9 +19357,12 @@ var require_claude_code = __commonJS({
19350
19357
  }
19351
19358
  runClaude(prompt, spec, cwd, childEnv, signal, modelOverride) {
19352
19359
  const outputLabel = spec.outputLabel;
19360
+ const model = modelOverride ?? this.config.model;
19353
19361
  return new Promise((resolve, reject) => {
19354
- const args = ["--print"];
19355
- const model = modelOverride ?? this.config.model;
19362
+ const args = ["--print", "--output-format", "stream-json", "--verbose"];
19363
+ if (spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0) {
19364
+ args.push("--json-schema", JSON.stringify(spec.outputSchema));
19365
+ }
19356
19366
  if (model)
19357
19367
  args.push("--model", model);
19358
19368
  if (this.config.maxTurns !== void 0)
@@ -19411,13 +19421,28 @@ var require_claude_code = __commonJS({
19411
19421
  const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
19412
19422
  if (code === 0) {
19413
19423
  try {
19414
- const parsed = extractJsonObject(stdout);
19424
+ const stream = parseStreamJson(stdout);
19425
+ if (stream.resultMeta?.isError) {
19426
+ throw new Error(`Claude Code reported ${stream.resultMeta.subtype ?? "an error"} on its terminal event`);
19427
+ }
19428
+ const answerText = stream.finalText ?? stdout;
19429
+ const parsed = stream.structuredOutput ?? extractJsonObject(answerText);
19430
+ if (parsed === void 0 && stream.resultMeta) {
19431
+ throw new Error(`claude's final message contained no JSON object (result subtype=${stream.resultMeta.subtype ?? "unknown"}; message head: ${JSON.stringify(answerText.slice(0, 160))})`);
19432
+ }
19415
19433
  (0, validate_output_1.assertOutputValid)(spec, parsed);
19416
19434
  settle(() => resolve({
19417
- outputs: [{ label: outputLabel, content: parsed ?? { raw: stdout } }],
19418
- rawText: stdout,
19419
- parsed,
19420
- usage: this.config.model ? { aiProvider: "claude-code", aiModel: this.config.model } : void 0
19435
+ response: {
19436
+ outputs: [{ label: outputLabel, content: parsed ?? { raw: answerText } }],
19437
+ rawText: answerText,
19438
+ parsed,
19439
+ usage: {
19440
+ aiProvider: "claude-code",
19441
+ ...model ? { aiModel: model } : {},
19442
+ ...stream.usage ?? {}
19443
+ }
19444
+ },
19445
+ readPaths: stream.readPaths
19421
19446
  }));
19422
19447
  } catch (err) {
19423
19448
  settle(() => reject(err instanceof Error ? err : new Error(String(err))));
@@ -19507,6 +19532,71 @@ var require_claude_code = __commonJS({
19507
19532
  return credentials;
19508
19533
  };
19509
19534
  }
19535
+ function parseStreamJson(stdout) {
19536
+ let finalText = null;
19537
+ const readPaths = [];
19538
+ let usage = null;
19539
+ let resultMeta = null;
19540
+ let structuredOutput = null;
19541
+ for (const line of stdout.split("\n")) {
19542
+ const trimmed = line.trim();
19543
+ if (!trimmed.startsWith("{"))
19544
+ continue;
19545
+ let event;
19546
+ try {
19547
+ event = JSON.parse(trimmed);
19548
+ } catch {
19549
+ continue;
19550
+ }
19551
+ if (event.type === "assistant") {
19552
+ const content = event.message?.content;
19553
+ if (!Array.isArray(content))
19554
+ continue;
19555
+ for (const block of content) {
19556
+ const b = block;
19557
+ if (b?.type === "tool_use" && b.name === "Read" && typeof b.input?.file_path === "string") {
19558
+ readPaths.push(b.input.file_path);
19559
+ }
19560
+ }
19561
+ } else if (event.type === "result") {
19562
+ if (typeof event.result === "string" && event.result.trim().length > 0) {
19563
+ finalText = event.result;
19564
+ }
19565
+ resultMeta = {
19566
+ subtype: typeof event.subtype === "string" ? event.subtype : null,
19567
+ isError: event.is_error === true
19568
+ };
19569
+ const so = event.structured_output;
19570
+ if (so && typeof so === "object" && !Array.isArray(so)) {
19571
+ structuredOutput = so;
19572
+ }
19573
+ const u = event.usage;
19574
+ const cost = event.total_cost_usd;
19575
+ usage = {
19576
+ ...typeof u?.input_tokens === "number" ? { inputTokens: u.input_tokens } : {},
19577
+ ...typeof u?.output_tokens === "number" ? { outputTokens: u.output_tokens } : {},
19578
+ ...typeof cost === "number" ? { costUsd: cost } : {}
19579
+ };
19580
+ }
19581
+ }
19582
+ return { finalText, readPaths, usage, resultMeta, structuredOutput };
19583
+ }
19584
+ var MAX_REPORTED_READS = 2e3;
19585
+ function relativeWorkspaceReads(readPaths, cwd) {
19586
+ const root = path_1.default.resolve(cwd);
19587
+ const out = /* @__PURE__ */ new Set();
19588
+ for (const p of readPaths) {
19589
+ const resolved = path_1.default.resolve(root, p);
19590
+ if (resolved === root)
19591
+ continue;
19592
+ if (!resolved.startsWith(root + path_1.default.sep))
19593
+ continue;
19594
+ out.add(resolved.slice(root.length + 1));
19595
+ if (out.size >= MAX_REPORTED_READS)
19596
+ break;
19597
+ }
19598
+ return [...out].sort();
19599
+ }
19510
19600
  function extractJsonObject(s) {
19511
19601
  const exact = tryParseObject(s);
19512
19602
  if (exact)
@@ -21414,7 +21504,7 @@ var import_shuttle = __toESM(require_dist5());
21414
21504
  // package.json
21415
21505
  var package_default = {
21416
21506
  name: "@whittlelabs/sifter",
21417
- version: "0.10.0",
21507
+ version: "0.12.0",
21418
21508
  description: "Whittle Sifter: paired AI reviewer for Whittle Sift job pools.",
21419
21509
  bin: {
21420
21510
  "whittle-sifter": "./dist/bin.js"