@whittlelabs/sifter 0.39.0 → 0.40.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 (4) hide show
  1. package/README.md +1 -0
  2. package/bin.js +193 -29
  3. package/bin.js.map +4 -4
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -98,6 +98,7 @@ The Sifter subscribes to your job pool and waits for review jobs. Leave it runni
98
98
  A review checks out the pull request and runs Claude Code on it. The pull request is someone else's code, so the review is held to reading:
99
99
 
100
100
  - It is offered three tools, Read, Grep and Glob, and nothing else: no shell, no edits, no network tools, no subagents.
101
+ - When the review draws a guided tour, it may also write files into one empty folder of its own (`out/`, beside the checkout), and nowhere else. The Sifter puts each drawing written there back into the review and deletes the folder with the checkout. A review that loads your Claude Code user settings writes nothing, because your allow rules could widen where it writes.
101
102
  - It loads no Claude Code settings file, neither the repository's nor yours. Your allow rules, permission mode, hooks and plugins do not apply to reviews, and no MCP server starts.
102
103
 
103
104
  Your login still works: Claude Code keeps it in your keychain or credentials file, or reads `ANTHROPIC_API_KEY`. If instead your login or provider is configured in your Claude Code settings (an `apiKeyHelper`, or an `env` block naming Bedrock, Vertex or a custom base URL), reviews will fail as signed out. Tell the Sifter to load your user settings in its `sifter.yaml` (`~/.sifter/sifter.yaml`, or `~/.sifter/<profile>/sifter.yaml` for a named profile):
package/bin.js CHANGED
@@ -3214,6 +3214,13 @@ var require_prompt_execution = __commonJS({
3214
3214
  }
3215
3215
  }
3216
3216
  }
3217
+ if (spec.answerFiles !== void 0) {
3218
+ const f = spec.answerFiles;
3219
+ const isName = (v) => typeof v === "string" && v.length > 0;
3220
+ if (typeof f !== "object" || f === null || Array.isArray(f) || !isName(f.at) || !isName(f.reference) || !isName(f.into) || !Array.isArray(f.extensions) || f.extensions.length === 0 || !f.extensions.every((e) => typeof e === "string" && /^\.[a-z0-9]+$/.test(e)) || typeof f.maxBytes !== "number" || !Number.isInteger(f.maxBytes) || f.maxBytes <= 0) {
3221
+ throw new Error('prompt-execution: spec.answerFiles must be { at, reference, into, extensions: [".ext", \u2026], maxBytes }');
3222
+ }
3223
+ }
3217
3224
  return spec;
3218
3225
  }
3219
3226
  function renderPromptExecution(inputs) {
@@ -3454,6 +3461,8 @@ var require_prompt_execution2 = __commonJS({
3454
3461
  spec.progressChannel = options.progressChannel;
3455
3462
  if (options.outputGates && options.outputGates.length > 0)
3456
3463
  spec.gates = options.outputGates;
3464
+ if (options.answerFiles)
3465
+ spec.answerFiles = options.answerFiles;
3457
3466
  if (options.rendering === "producer" && !options.prompt) {
3458
3467
  throw new Error("rendering='producer' requires `prompt`");
3459
3468
  }
@@ -9648,6 +9657,7 @@ var require_canonicalise = __commonJS({
9648
9657
  promptTemplatePath: promptOptions.promptTemplate,
9649
9658
  outputSchema: toJsonSchema(promptOptions.outputSchema),
9650
9659
  ...promptOptions.outputGates ? { outputGates: promptOptions.outputGates } : {},
9660
+ ...promptOptions.answerFiles ? { answerFiles: promptOptions.answerFiles } : {},
9651
9661
  outputLabel,
9652
9662
  rendering: promptOptions.rendering,
9653
9663
  ...promptOptions.maxAttempts !== void 0 ? { maxAttempts: promptOptions.maxAttempts } : {},
@@ -10826,19 +10836,25 @@ var require_claude_code = __commonJS({
10826
10836
  var DEFAULT_TIMEOUT = 6e5;
10827
10837
  var REVIEW_TOOLS = ["Read", "Grep", "Glob"];
10828
10838
  var THINKING_DISPLAY_FLOOR = "2.1.94";
10829
- function reviewRunArgs(settingSources) {
10839
+ function reviewRunArgs(settingSources, writesAnswerFiles = false) {
10830
10840
  return [
10831
10841
  "--setting-sources",
10832
10842
  settingSources,
10833
10843
  "--strict-mcp-config",
10834
10844
  "--tools",
10835
- REVIEW_TOOLS.join(","),
10845
+ (writesAnswerFiles ? [...REVIEW_TOOLS, "Write"] : REVIEW_TOOLS).join(","),
10836
10846
  "--permission-mode",
10837
10847
  "default",
10838
10848
  "--settings",
10839
10849
  JSON.stringify({ disableAllHooks: true })
10840
10850
  ];
10841
10851
  }
10852
+ function outputDirRule(cwd, outputDir) {
10853
+ const relative = path_1.default.relative(cwd, outputDir);
10854
+ if (relative !== path_1.default.basename(outputDir) || relative.startsWith("."))
10855
+ return null;
10856
+ return `Edit(./${relative}/**)`;
10857
+ }
10842
10858
  var RETIRED_READ_TOOLS = ["LS"];
10843
10859
  var HOSTED_ENV_PASSTHROUGH = [
10844
10860
  "PATH",
@@ -10927,7 +10943,11 @@ var require_claude_code = __commonJS({
10927
10943
  materializeOptions: {
10928
10944
  ...tmpBase ? { tmpDir: tmpBase } : {},
10929
10945
  overlayAllowedHosts: this.config.overlayAllowedHosts ?? []
10930
- }
10946
+ },
10947
+ // Writes are confined by one allow rule (`outputDirRule`), which an
10948
+ // operator's own settings could widen: a run that loads them writes
10949
+ // nothing, and its answer inlines (ADR 0133).
10950
+ writesAnswerFiles: this.config.claudeSettings !== "user"
10931
10951
  };
10932
10952
  }
10933
10953
  async execute(dispatch, signal, ctx) {
@@ -10979,7 +10999,8 @@ var require_claude_code = __commonJS({
10979
10999
  // machine's, and the runner pauses on it. With a revealed key it is one
10980
11000
  // customer's, and only their job fails.
10981
11001
  modelEnv === null,
10982
- effort
11002
+ effort,
11003
+ ctx?.outputDir
10983
11004
  );
10984
11005
  if (workspace) {
10985
11006
  response.outputs.push(...(0, workspace_telemetry_1.workspaceOutputs)(workspace, runCwd, readPaths, toolInputs));
@@ -11065,7 +11086,7 @@ var require_claude_code = __commonJS({
11065
11086
  async listModels() {
11066
11087
  return CLAUDE_CODE_MODELS;
11067
11088
  }
11068
- async runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId, transcriptName, local = false, effortOverride) {
11089
+ async runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId, transcriptName, local = false, effortOverride, outputDir) {
11069
11090
  const outputLabel = spec.outputLabel;
11070
11091
  const model = modelOverride ?? this.config.model;
11071
11092
  const effort = effortOverride ?? this.config.reasoningEffort;
@@ -11074,7 +11095,14 @@ var require_claude_code = __commonJS({
11074
11095
  throw new Error("claude-code: `claudeSettings: user` is for an installed Sifter whose login lives in Claude Code settings; a hosted run authenticates with the key it is handed and loads no settings");
11075
11096
  }
11076
11097
  const settingSources = local && this.config.claudeSettings === "user" ? "user" : "";
11077
- const args = ["--print", "--output-format", "stream-json", "--verbose", ...reviewRunArgs(settingSources)];
11098
+ const writeRule = outputDir ? outputDirRule(cwd, outputDir) : null;
11099
+ const args = [
11100
+ "--print",
11101
+ "--output-format",
11102
+ "stream-json",
11103
+ "--verbose",
11104
+ ...reviewRunArgs(settingSources, writeRule !== null)
11105
+ ];
11078
11106
  if (resumeSessionId)
11079
11107
  args.push("--resume", resumeSessionId);
11080
11108
  if (reporter)
@@ -11092,8 +11120,9 @@ var require_claude_code = __commonJS({
11092
11120
  }
11093
11121
  if (this.config.maxTurns !== void 0)
11094
11122
  args.push("--max-turns", String(this.config.maxTurns));
11095
- if (allowedTools && allowedTools.length > 0) {
11096
- args.push("--allowedTools", allowedTools.join(","));
11123
+ const allowRules = [...allowedTools ?? [], ...writeRule ? [writeRule] : []];
11124
+ if (allowRules.length > 0) {
11125
+ args.push("--allowedTools", allowRules.join(","));
11097
11126
  }
11098
11127
  const tap = reporter ? new progress_1.StreamJsonTap({
11099
11128
  onReasoning: (t) => reporter.reasoningBlock(t),
@@ -11530,8 +11559,10 @@ var require_codex = __commonJS({
11530
11559
  var workspace_telemetry_1 = require_workspace_telemetry();
11531
11560
  var apply_1 = require_apply();
11532
11561
  var CODEX_PROFILE = "sift-review";
11533
- function reviewProfile(installDir, platform = process.platform) {
11562
+ function reviewProfile(installDir, platform = process.platform, outputDir) {
11534
11563
  const rules = ['":root"="deny"', '":minimal"="read"', '":workspace_roots"="read"', `${JSON.stringify(installDir)}="read"`];
11564
+ if (outputDir)
11565
+ rules.push(`${JSON.stringify(outputDir)}="write"`);
11535
11566
  if (platform === "linux") {
11536
11567
  rules.push(`${JSON.stringify(path_1.default.join(codexHome(), "tmp", "arg0"))}="read"`);
11537
11568
  } else {
@@ -11582,14 +11613,14 @@ var require_codex = __commonJS({
11582
11613
  }
11583
11614
  return codex;
11584
11615
  }
11585
- function codexReviewArgs(installDir, platform = process.platform) {
11616
+ function codexReviewArgs(installDir, platform = process.platform, outputDir) {
11586
11617
  return [
11587
11618
  "--json",
11588
11619
  "--skip-git-repo-check",
11589
11620
  "--ignore-user-config",
11590
11621
  "--ignore-rules",
11591
11622
  "-c",
11592
- reviewProfile(installDir, platform),
11623
+ reviewProfile(installDir, platform, outputDir),
11593
11624
  "-c",
11594
11625
  `default_permissions="${CODEX_PROFILE}"`,
11595
11626
  "-c",
@@ -11632,7 +11663,7 @@ var require_codex = __commonJS({
11632
11663
  };
11633
11664
  }
11634
11665
  async prepareEnvironment(dispatch) {
11635
- return (0, agent_common_1.agentEnvironment)(dispatch, this.config);
11666
+ return { ...(0, agent_common_1.agentEnvironment)(dispatch, this.config), writesAnswerFiles: true };
11636
11667
  }
11637
11668
  async execute(dispatch, signal, ctx) {
11638
11669
  (0, agent_common_1.refuseHosted)("codex", Boolean(ctx?.credentials));
@@ -11650,9 +11681,10 @@ var require_codex = __commonJS({
11650
11681
  runCwd = path_1.default.join(scratch, "run");
11651
11682
  await fs_1.promises.mkdir(runCwd);
11652
11683
  }
11684
+ const outputDir = ctx?.outputDir ? await fs_1.promises.realpath(ctx.outputDir) : void 0;
11653
11685
  const { response, commands } = await this.runCodex({
11654
11686
  codex,
11655
- args: ["exec", ...codexReviewArgs(codex.installDir), "-C", runCwd],
11687
+ args: ["exec", ...codexReviewArgs(codex.installDir, process.platform, outputDir), "-C", runCwd],
11656
11688
  prompt,
11657
11689
  spec,
11658
11690
  cwd: runCwd,
@@ -12016,14 +12048,22 @@ var require_copilot = __commonJS({
12016
12048
  var apply_1 = require_apply();
12017
12049
  var REVIEW_TOOLS = ["view", "grep", "glob"];
12018
12050
  var DENIED_TOOLS = ["shell", "write"];
12019
- function copilotReviewArgs() {
12051
+ function copilotReviewArgs(outputDir) {
12052
+ const permissions = outputDir ? [
12053
+ `--available-tools=${[...REVIEW_TOOLS, "create"].join(",")}`,
12054
+ ...REVIEW_TOOLS.map((t) => `--allow-tool=${t}`),
12055
+ `--allow-tool=write(${path_1.default.join(outputDir, "*")})`,
12056
+ ...DENIED_TOOLS.filter((t) => t !== "write").map((t) => `--deny-tool=${t}`)
12057
+ ] : [
12058
+ "--allow-all-tools",
12059
+ `--available-tools=${REVIEW_TOOLS.join(",")}`,
12060
+ ...DENIED_TOOLS.map((t) => `--deny-tool=${t}`)
12061
+ ];
12020
12062
  return [
12021
12063
  "--output-format",
12022
12064
  "json",
12023
12065
  "--no-color",
12024
- "--allow-all-tools",
12025
- `--available-tools=${REVIEW_TOOLS.join(",")}`,
12026
- ...DENIED_TOOLS.map((t) => `--deny-tool=${t}`),
12066
+ ...permissions,
12027
12067
  "--disable-builtin-mcps",
12028
12068
  "--disallow-temp-dir",
12029
12069
  "--no-ask-user",
@@ -12065,7 +12105,7 @@ var require_copilot = __commonJS({
12065
12105
  };
12066
12106
  }
12067
12107
  async prepareEnvironment(dispatch) {
12068
- return (0, agent_common_1.agentEnvironment)(dispatch, this.config);
12108
+ return { ...(0, agent_common_1.agentEnvironment)(dispatch, this.config), writesAnswerFiles: true };
12069
12109
  }
12070
12110
  async execute(dispatch, signal, ctx) {
12071
12111
  (0, agent_common_1.refuseHosted)("copilot", Boolean(ctx?.credentials));
@@ -12088,8 +12128,9 @@ var require_copilot = __commonJS({
12088
12128
  await fs_1.promises.mkdir(runCwd);
12089
12129
  }
12090
12130
  const sessionId = (0, crypto_1.randomUUID)();
12131
+ const outputDir = ctx?.outputDir ? await fs_1.promises.realpath(ctx.outputDir) : void 0;
12091
12132
  const { response, reads, toolInputs } = await this.runCopilot({
12092
- args: [...copilotReviewArgs(), `--session-id=${sessionId}`, "-C", runCwd],
12133
+ args: [...copilotReviewArgs(outputDir), `--session-id=${sessionId}`, "-C", runCwd],
12093
12134
  prompt: withSchema(prompt, spec),
12094
12135
  spec,
12095
12136
  cwd: runCwd,
@@ -27192,6 +27233,96 @@ var require_workspace = __commonJS({
27192
27233
  }
27193
27234
  });
27194
27235
 
27236
+ // ../../packages/shuttle/dist/core/answer-files.js
27237
+ var require_answer_files = __commonJS({
27238
+ "../../packages/shuttle/dist/core/answer-files.js"(exports2) {
27239
+ "use strict";
27240
+ var __importDefault = exports2 && exports2.__importDefault || function(mod) {
27241
+ return mod && mod.__esModule ? mod : { "default": mod };
27242
+ };
27243
+ Object.defineProperty(exports2, "__esModule", { value: true });
27244
+ exports2.OUTPUT_DIR = void 0;
27245
+ exports2.makeOutputDir = makeOutputDir;
27246
+ exports2.collectAnswerFiles = collectAnswerFiles;
27247
+ exports2.spliceAnswerFiles = spliceAnswerFiles;
27248
+ var node_fs_1 = require("node:fs");
27249
+ var node_path_1 = __importDefault(require("node:path"));
27250
+ var output_gates_1 = require_output_gates();
27251
+ exports2.OUTPUT_DIR = "out";
27252
+ var FINDING_GATE = "answer-files";
27253
+ async function makeOutputDir(runRoot) {
27254
+ const dir = node_path_1.default.join(runRoot, exports2.OUTPUT_DIR);
27255
+ await node_fs_1.promises.mkdir(dir);
27256
+ return dir;
27257
+ }
27258
+ async function collectAnswerFiles(dir, declaration) {
27259
+ const files = /* @__PURE__ */ new Map();
27260
+ let entries;
27261
+ try {
27262
+ entries = await node_fs_1.promises.readdir(dir, { withFileTypes: true });
27263
+ } catch {
27264
+ return files;
27265
+ }
27266
+ for (const entry of entries) {
27267
+ if (!entry.isFile())
27268
+ continue;
27269
+ if (!declaration.extensions.includes(node_path_1.default.extname(entry.name).toLowerCase()))
27270
+ continue;
27271
+ const full = node_path_1.default.join(dir, entry.name);
27272
+ const { size } = await node_fs_1.promises.stat(full);
27273
+ if (size > declaration.maxBytes) {
27274
+ files.set(entry.name, { problem: `is ${size} bytes, over the ${declaration.maxBytes}-byte limit` });
27275
+ continue;
27276
+ }
27277
+ files.set(entry.name, { text: await node_fs_1.promises.readFile(full, "utf8") });
27278
+ }
27279
+ return files;
27280
+ }
27281
+ function spliceAnswerFiles(response, outputLabel, declaration, files) {
27282
+ if (!response.parsed)
27283
+ return { response, findings: [] };
27284
+ const answer = structuredClone(response.parsed);
27285
+ const findings = [];
27286
+ const selected = (0, output_gates_1.resolvePath)(answer, declaration.at);
27287
+ for (const target of Array.isArray(selected) ? selected : [selected]) {
27288
+ if (typeof target !== "object" || target === null || Array.isArray(target))
27289
+ continue;
27290
+ const holder = target;
27291
+ const name = holder[declaration.reference];
27292
+ if (name === void 0)
27293
+ continue;
27294
+ const refused = (why) => findings.push({
27295
+ gate: FINDING_GATE,
27296
+ message: `${declaration.at}.${declaration.reference} ${JSON.stringify(name)} ${why}. Put the content inline as \`${declaration.into}\` and remove \`${declaration.reference}\`.`
27297
+ });
27298
+ if (typeof name !== "string" || name !== node_path_1.default.basename(name) || name.startsWith(".")) {
27299
+ refused(`is not a plain file name in ${exports2.OUTPUT_DIR}/`);
27300
+ continue;
27301
+ }
27302
+ const file = files.get(name);
27303
+ if (!file) {
27304
+ refused(`names no ${declaration.extensions.join(" or ")} file written to ${exports2.OUTPUT_DIR}/`);
27305
+ continue;
27306
+ }
27307
+ if ("problem" in file) {
27308
+ refused(file.problem);
27309
+ continue;
27310
+ }
27311
+ holder[declaration.into] = file.text;
27312
+ delete holder[declaration.reference];
27313
+ }
27314
+ return {
27315
+ response: {
27316
+ ...response,
27317
+ parsed: answer,
27318
+ outputs: response.outputs.map((o) => o.label === outputLabel ? { ...o, content: answer } : o)
27319
+ },
27320
+ findings
27321
+ };
27322
+ }
27323
+ }
27324
+ });
27325
+
27195
27326
  // ../../packages/shuttle/dist/core/run-dispatch.js
27196
27327
  var require_run_dispatch = __commonJS({
27197
27328
  "../../packages/shuttle/dist/core/run-dispatch.js"(exports2) {
@@ -27205,6 +27336,7 @@ var require_run_dispatch = __commonJS({
27205
27336
  var output_gates_1 = require_output_gates();
27206
27337
  var logger_1 = require_logger();
27207
27338
  var workspace_1 = require_workspace();
27339
+ var answer_files_1 = require_answer_files();
27208
27340
  exports2.GATE_REPAIR_BUDGET = 2;
27209
27341
  async function runDispatch(dispatch, executor, options) {
27210
27342
  const { credentials, signal } = options;
@@ -27225,46 +27357,78 @@ var require_run_dispatch = __commonJS({
27225
27357
  workspace = null;
27226
27358
  }
27227
27359
  }
27228
- const ctx = {
27360
+ const declaration = answerFilesDeclaration(dispatch);
27361
+ let outputDir;
27362
+ if (declaration && workspace && environment?.writesAnswerFiles) {
27363
+ try {
27364
+ outputDir = await (0, answer_files_1.makeOutputDir)(workspace.cwd);
27365
+ } catch (err) {
27366
+ (0, logger_1.getLogger)().warn("could not make the output directory; the answer will inline its files", {
27367
+ jobId: dispatch.job.id,
27368
+ error: err instanceof Error ? err.message : String(err)
27369
+ });
27370
+ }
27371
+ }
27372
+ const repairCtx = {
27229
27373
  ...options.reportProgress ? { reportProgress: options.reportProgress } : {},
27230
27374
  workspace,
27231
27375
  credentials
27232
27376
  };
27377
+ const ctx = { ...repairCtx, ...outputDir ? { outputDir } : {} };
27233
27378
  let response;
27379
+ let files = /* @__PURE__ */ new Map();
27234
27380
  try {
27235
27381
  response = await executor.execute(dispatch, signal, ctx);
27382
+ if (outputDir && declaration)
27383
+ files = await (0, answer_files_1.collectAnswerFiles)(outputDir, declaration);
27236
27384
  } catch (error) {
27237
27385
  return { kind: "executor-failed", error };
27238
27386
  } finally {
27239
27387
  if (workspace)
27240
27388
  await workspace.cleanup();
27241
27389
  }
27242
- const gated = await runOutputGates(dispatch, response, executor, signal, ctx);
27390
+ const gated = await runOutputGates(dispatch, response, executor, signal, repairCtx, files);
27243
27391
  if (!gated.ok)
27244
27392
  return { kind: "gate-failed", reason: gated.reason };
27245
27393
  return { kind: "completed", response: gated.response };
27246
27394
  }
27247
- async function runOutputGates(dispatch, response, executor, signal, ctx) {
27395
+ function answerFilesDeclaration(dispatch) {
27396
+ try {
27397
+ return (0, loom_1.readPromptExecutionSpec)(dispatch.inputs).answerFiles ?? null;
27398
+ } catch {
27399
+ return null;
27400
+ }
27401
+ }
27402
+ async function runOutputGates(dispatch, response, executor, signal, ctx, files = /* @__PURE__ */ new Map()) {
27248
27403
  let spec;
27249
27404
  try {
27250
27405
  spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
27251
27406
  } catch {
27252
27407
  return { ok: true, response };
27253
27408
  }
27254
- const gates = spec.gates;
27255
- if (!gates || gates.length === 0)
27409
+ const gates = spec.gates ?? [];
27410
+ const declaration = spec.answerFiles;
27411
+ if (gates.length === 0 && !declaration)
27256
27412
  return { ok: true, response };
27257
- let current = response;
27258
- let result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
27413
+ const check = (candidate) => {
27414
+ const spliced = declaration ? (0, answer_files_1.spliceAnswerFiles)(candidate, spec.outputLabel, declaration, files) : { response: candidate, findings: [] };
27415
+ const gated = (0, output_gates_1.evaluateOutputGates)(gates, spliced.response.parsed ?? {}, dispatch.inputs);
27416
+ const findings = [...spliced.findings, ...gated.findings];
27417
+ return { response: spliced.response, result: { ok: findings.length === 0, findings } };
27418
+ };
27419
+ let checked = check(response);
27420
+ let current = checked.response;
27421
+ let result = checked.result;
27259
27422
  let attempts = 0;
27260
27423
  while (!result.ok && attempts < exports2.GATE_REPAIR_BUDGET && current.continuation !== void 0 && typeof executor.continue === "function") {
27261
27424
  try {
27262
27425
  const repaired = await executor.continue(current.continuation, (0, output_gates_1.renderRepairPrompt)(result.findings), signal, ctx);
27263
- current = mergeRepairedResponse(current, repaired);
27426
+ checked = check(mergeRepairedResponse(current, repaired));
27264
27427
  } catch {
27265
27428
  break;
27266
27429
  }
27267
- result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
27430
+ current = checked.response;
27431
+ result = checked.result;
27268
27432
  attempts += 1;
27269
27433
  }
27270
27434
  if (result.ok)
@@ -29469,7 +29633,7 @@ var import_path = require("path");
29469
29633
  var import_promises = require("fs/promises");
29470
29634
  var import_yaml = __toESM(require_dist3());
29471
29635
  var import_shuttle = __toESM(require_dist5());
29472
- var buildVersion = true ? "0.39.0" : pkg.version;
29636
+ var buildVersion = true ? "0.40.0" : pkg.version;
29473
29637
  var AGENT_EXECUTORS = ["claude-code", "codex", "copilot"];
29474
29638
  var sifterBrand = {
29475
29639
  product: {