@whittlelabs/sifter 0.39.0 → 0.40.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.
- package/README.md +1 -0
- package/bin.js +208 -32
- package/bin.js.map +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
|
|
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
|
-
|
|
11096
|
-
|
|
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,
|
|
@@ -12013,24 +12045,43 @@ var require_copilot = __commonJS({
|
|
|
12013
12045
|
var json_answer_1 = require_json_answer();
|
|
12014
12046
|
var models_1 = require_models();
|
|
12015
12047
|
var workspace_telemetry_1 = require_workspace_telemetry();
|
|
12048
|
+
var cli_version_1 = require_cli_version();
|
|
12049
|
+
var version_check_1 = require_version_check();
|
|
12016
12050
|
var apply_1 = require_apply();
|
|
12017
12051
|
var REVIEW_TOOLS = ["view", "grep", "glob"];
|
|
12018
12052
|
var DENIED_TOOLS = ["shell", "write"];
|
|
12019
|
-
function copilotReviewArgs() {
|
|
12053
|
+
function copilotReviewArgs(version, outputDir) {
|
|
12054
|
+
const permissions = outputDir ? [
|
|
12055
|
+
`--available-tools=${[...REVIEW_TOOLS, "create"].join(",")}`,
|
|
12056
|
+
...REVIEW_TOOLS.map((t) => `--allow-tool=${t}`),
|
|
12057
|
+
`--allow-tool=write(${path_1.default.join(outputDir, "*")})`,
|
|
12058
|
+
...DENIED_TOOLS.filter((t) => t !== "write").map((t) => `--deny-tool=${t}`)
|
|
12059
|
+
] : [
|
|
12060
|
+
"--allow-all-tools",
|
|
12061
|
+
`--available-tools=${REVIEW_TOOLS.join(",")}`,
|
|
12062
|
+
...DENIED_TOOLS.map((t) => `--deny-tool=${t}`)
|
|
12063
|
+
];
|
|
12020
12064
|
return [
|
|
12021
12065
|
"--output-format",
|
|
12022
12066
|
"json",
|
|
12023
12067
|
"--no-color",
|
|
12024
|
-
|
|
12025
|
-
`--available-tools=${REVIEW_TOOLS.join(",")}`,
|
|
12026
|
-
...DENIED_TOOLS.map((t) => `--deny-tool=${t}`),
|
|
12068
|
+
...permissions,
|
|
12027
12069
|
"--disable-builtin-mcps",
|
|
12028
12070
|
"--disallow-temp-dir",
|
|
12029
12071
|
"--no-ask-user",
|
|
12030
12072
|
"--no-auto-update",
|
|
12031
|
-
|
|
12073
|
+
...sessionStaysLocal(version)
|
|
12032
12074
|
];
|
|
12033
12075
|
}
|
|
12076
|
+
var REMOTE_FLAG_RETIRED = "1.0.103";
|
|
12077
|
+
function sessionStaysLocal(version) {
|
|
12078
|
+
if (version === null)
|
|
12079
|
+
return [];
|
|
12080
|
+
return (0, version_check_1.compareSemver)(version, REMOTE_FLAG_RETIRED) >= 0 ? ["--sync-session=off"] : ["--no-remote"];
|
|
12081
|
+
}
|
|
12082
|
+
function copilotVersion(home) {
|
|
12083
|
+
return (0, cli_version_1.cliVersion)("copilot", runEnv(home));
|
|
12084
|
+
}
|
|
12034
12085
|
var RUN_SETTINGS = { disableAllHooks: true, autoUpdate: false };
|
|
12035
12086
|
var COPILOT_FAULTS = {
|
|
12036
12087
|
installRemedy: "install GitHub Copilot CLI (brew install --cask copilot-cli, or npm install -g @github/copilot) where this process can find it on PATH",
|
|
@@ -12065,7 +12116,7 @@ var require_copilot = __commonJS({
|
|
|
12065
12116
|
};
|
|
12066
12117
|
}
|
|
12067
12118
|
async prepareEnvironment(dispatch) {
|
|
12068
|
-
return (0, agent_common_1.agentEnvironment)(dispatch, this.config);
|
|
12119
|
+
return { ...(0, agent_common_1.agentEnvironment)(dispatch, this.config), writesAnswerFiles: true };
|
|
12069
12120
|
}
|
|
12070
12121
|
async execute(dispatch, signal, ctx) {
|
|
12071
12122
|
(0, agent_common_1.refuseHosted)("copilot", Boolean(ctx?.credentials));
|
|
@@ -12088,8 +12139,9 @@ var require_copilot = __commonJS({
|
|
|
12088
12139
|
await fs_1.promises.mkdir(runCwd);
|
|
12089
12140
|
}
|
|
12090
12141
|
const sessionId = (0, crypto_1.randomUUID)();
|
|
12142
|
+
const outputDir = ctx?.outputDir ? await fs_1.promises.realpath(ctx.outputDir) : void 0;
|
|
12091
12143
|
const { response, reads, toolInputs } = await this.runCopilot({
|
|
12092
|
-
args: [...copilotReviewArgs(), `--session-id=${sessionId}`, "-C", runCwd],
|
|
12144
|
+
args: [...copilotReviewArgs(await copilotVersion(home), outputDir), `--session-id=${sessionId}`, "-C", runCwd],
|
|
12093
12145
|
prompt: withSchema(prompt, spec),
|
|
12094
12146
|
spec,
|
|
12095
12147
|
cwd: runCwd,
|
|
@@ -12136,7 +12188,7 @@ var require_copilot = __commonJS({
|
|
|
12136
12188
|
outputSchema: handle.outputSchema
|
|
12137
12189
|
};
|
|
12138
12190
|
const { response } = await this.runCopilot({
|
|
12139
|
-
args: [...copilotReviewArgs(), `--resume=${handle.sessionId}`, "-C", cwd],
|
|
12191
|
+
args: [...copilotReviewArgs(await copilotVersion(handle.home)), `--resume=${handle.sessionId}`, "-C", cwd],
|
|
12140
12192
|
prompt: followUp,
|
|
12141
12193
|
spec,
|
|
12142
12194
|
cwd,
|
|
@@ -12156,11 +12208,12 @@ var require_copilot = __commonJS({
|
|
|
12156
12208
|
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(this.tmpBase(), "shuttle-copilot-ready-"));
|
|
12157
12209
|
try {
|
|
12158
12210
|
const home = await makeRunHome(scratch);
|
|
12211
|
+
const version = await copilotVersion(home);
|
|
12159
12212
|
let run;
|
|
12160
12213
|
try {
|
|
12161
12214
|
run = await withOperatorLogin(home, () => (0, agent_process_1.runAgentProcess)({
|
|
12162
12215
|
bin: "copilot",
|
|
12163
|
-
args: [...copilotReviewArgs(), "-C", scratch, "-p", READINESS_PROMPT],
|
|
12216
|
+
args: [...copilotReviewArgs(version), "-C", scratch, "-p", READINESS_PROMPT],
|
|
12164
12217
|
cwd: scratch,
|
|
12165
12218
|
env: runEnv(home),
|
|
12166
12219
|
idleTimeoutMs: READINESS_TIMEOUT_MS,
|
|
@@ -27192,6 +27245,96 @@ var require_workspace = __commonJS({
|
|
|
27192
27245
|
}
|
|
27193
27246
|
});
|
|
27194
27247
|
|
|
27248
|
+
// ../../packages/shuttle/dist/core/answer-files.js
|
|
27249
|
+
var require_answer_files = __commonJS({
|
|
27250
|
+
"../../packages/shuttle/dist/core/answer-files.js"(exports2) {
|
|
27251
|
+
"use strict";
|
|
27252
|
+
var __importDefault = exports2 && exports2.__importDefault || function(mod) {
|
|
27253
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
27254
|
+
};
|
|
27255
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
27256
|
+
exports2.OUTPUT_DIR = void 0;
|
|
27257
|
+
exports2.makeOutputDir = makeOutputDir;
|
|
27258
|
+
exports2.collectAnswerFiles = collectAnswerFiles;
|
|
27259
|
+
exports2.spliceAnswerFiles = spliceAnswerFiles;
|
|
27260
|
+
var node_fs_1 = require("node:fs");
|
|
27261
|
+
var node_path_1 = __importDefault(require("node:path"));
|
|
27262
|
+
var output_gates_1 = require_output_gates();
|
|
27263
|
+
exports2.OUTPUT_DIR = "out";
|
|
27264
|
+
var FINDING_GATE = "answer-files";
|
|
27265
|
+
async function makeOutputDir(runRoot) {
|
|
27266
|
+
const dir = node_path_1.default.join(runRoot, exports2.OUTPUT_DIR);
|
|
27267
|
+
await node_fs_1.promises.mkdir(dir);
|
|
27268
|
+
return dir;
|
|
27269
|
+
}
|
|
27270
|
+
async function collectAnswerFiles(dir, declaration) {
|
|
27271
|
+
const files = /* @__PURE__ */ new Map();
|
|
27272
|
+
let entries;
|
|
27273
|
+
try {
|
|
27274
|
+
entries = await node_fs_1.promises.readdir(dir, { withFileTypes: true });
|
|
27275
|
+
} catch {
|
|
27276
|
+
return files;
|
|
27277
|
+
}
|
|
27278
|
+
for (const entry of entries) {
|
|
27279
|
+
if (!entry.isFile())
|
|
27280
|
+
continue;
|
|
27281
|
+
if (!declaration.extensions.includes(node_path_1.default.extname(entry.name).toLowerCase()))
|
|
27282
|
+
continue;
|
|
27283
|
+
const full = node_path_1.default.join(dir, entry.name);
|
|
27284
|
+
const { size } = await node_fs_1.promises.stat(full);
|
|
27285
|
+
if (size > declaration.maxBytes) {
|
|
27286
|
+
files.set(entry.name, { problem: `is ${size} bytes, over the ${declaration.maxBytes}-byte limit` });
|
|
27287
|
+
continue;
|
|
27288
|
+
}
|
|
27289
|
+
files.set(entry.name, { text: await node_fs_1.promises.readFile(full, "utf8") });
|
|
27290
|
+
}
|
|
27291
|
+
return files;
|
|
27292
|
+
}
|
|
27293
|
+
function spliceAnswerFiles(response, outputLabel, declaration, files) {
|
|
27294
|
+
if (!response.parsed)
|
|
27295
|
+
return { response, findings: [] };
|
|
27296
|
+
const answer = structuredClone(response.parsed);
|
|
27297
|
+
const findings = [];
|
|
27298
|
+
const selected = (0, output_gates_1.resolvePath)(answer, declaration.at);
|
|
27299
|
+
for (const target of Array.isArray(selected) ? selected : [selected]) {
|
|
27300
|
+
if (typeof target !== "object" || target === null || Array.isArray(target))
|
|
27301
|
+
continue;
|
|
27302
|
+
const holder = target;
|
|
27303
|
+
const name = holder[declaration.reference];
|
|
27304
|
+
if (name === void 0)
|
|
27305
|
+
continue;
|
|
27306
|
+
const refused = (why) => findings.push({
|
|
27307
|
+
gate: FINDING_GATE,
|
|
27308
|
+
message: `${declaration.at}.${declaration.reference} ${JSON.stringify(name)} ${why}. Put the content inline as \`${declaration.into}\` and remove \`${declaration.reference}\`.`
|
|
27309
|
+
});
|
|
27310
|
+
if (typeof name !== "string" || name !== node_path_1.default.basename(name) || name.startsWith(".")) {
|
|
27311
|
+
refused(`is not a plain file name in ${exports2.OUTPUT_DIR}/`);
|
|
27312
|
+
continue;
|
|
27313
|
+
}
|
|
27314
|
+
const file = files.get(name);
|
|
27315
|
+
if (!file) {
|
|
27316
|
+
refused(`names no ${declaration.extensions.join(" or ")} file written to ${exports2.OUTPUT_DIR}/`);
|
|
27317
|
+
continue;
|
|
27318
|
+
}
|
|
27319
|
+
if ("problem" in file) {
|
|
27320
|
+
refused(file.problem);
|
|
27321
|
+
continue;
|
|
27322
|
+
}
|
|
27323
|
+
holder[declaration.into] = file.text;
|
|
27324
|
+
delete holder[declaration.reference];
|
|
27325
|
+
}
|
|
27326
|
+
return {
|
|
27327
|
+
response: {
|
|
27328
|
+
...response,
|
|
27329
|
+
parsed: answer,
|
|
27330
|
+
outputs: response.outputs.map((o) => o.label === outputLabel ? { ...o, content: answer } : o)
|
|
27331
|
+
},
|
|
27332
|
+
findings
|
|
27333
|
+
};
|
|
27334
|
+
}
|
|
27335
|
+
}
|
|
27336
|
+
});
|
|
27337
|
+
|
|
27195
27338
|
// ../../packages/shuttle/dist/core/run-dispatch.js
|
|
27196
27339
|
var require_run_dispatch = __commonJS({
|
|
27197
27340
|
"../../packages/shuttle/dist/core/run-dispatch.js"(exports2) {
|
|
@@ -27205,6 +27348,7 @@ var require_run_dispatch = __commonJS({
|
|
|
27205
27348
|
var output_gates_1 = require_output_gates();
|
|
27206
27349
|
var logger_1 = require_logger();
|
|
27207
27350
|
var workspace_1 = require_workspace();
|
|
27351
|
+
var answer_files_1 = require_answer_files();
|
|
27208
27352
|
exports2.GATE_REPAIR_BUDGET = 2;
|
|
27209
27353
|
async function runDispatch(dispatch, executor, options) {
|
|
27210
27354
|
const { credentials, signal } = options;
|
|
@@ -27225,46 +27369,78 @@ var require_run_dispatch = __commonJS({
|
|
|
27225
27369
|
workspace = null;
|
|
27226
27370
|
}
|
|
27227
27371
|
}
|
|
27228
|
-
const
|
|
27372
|
+
const declaration = answerFilesDeclaration(dispatch);
|
|
27373
|
+
let outputDir;
|
|
27374
|
+
if (declaration && workspace && environment?.writesAnswerFiles) {
|
|
27375
|
+
try {
|
|
27376
|
+
outputDir = await (0, answer_files_1.makeOutputDir)(workspace.cwd);
|
|
27377
|
+
} catch (err) {
|
|
27378
|
+
(0, logger_1.getLogger)().warn("could not make the output directory; the answer will inline its files", {
|
|
27379
|
+
jobId: dispatch.job.id,
|
|
27380
|
+
error: err instanceof Error ? err.message : String(err)
|
|
27381
|
+
});
|
|
27382
|
+
}
|
|
27383
|
+
}
|
|
27384
|
+
const repairCtx = {
|
|
27229
27385
|
...options.reportProgress ? { reportProgress: options.reportProgress } : {},
|
|
27230
27386
|
workspace,
|
|
27231
27387
|
credentials
|
|
27232
27388
|
};
|
|
27389
|
+
const ctx = { ...repairCtx, ...outputDir ? { outputDir } : {} };
|
|
27233
27390
|
let response;
|
|
27391
|
+
let files = /* @__PURE__ */ new Map();
|
|
27234
27392
|
try {
|
|
27235
27393
|
response = await executor.execute(dispatch, signal, ctx);
|
|
27394
|
+
if (outputDir && declaration)
|
|
27395
|
+
files = await (0, answer_files_1.collectAnswerFiles)(outputDir, declaration);
|
|
27236
27396
|
} catch (error) {
|
|
27237
27397
|
return { kind: "executor-failed", error };
|
|
27238
27398
|
} finally {
|
|
27239
27399
|
if (workspace)
|
|
27240
27400
|
await workspace.cleanup();
|
|
27241
27401
|
}
|
|
27242
|
-
const gated = await runOutputGates(dispatch, response, executor, signal,
|
|
27402
|
+
const gated = await runOutputGates(dispatch, response, executor, signal, repairCtx, files);
|
|
27243
27403
|
if (!gated.ok)
|
|
27244
27404
|
return { kind: "gate-failed", reason: gated.reason };
|
|
27245
27405
|
return { kind: "completed", response: gated.response };
|
|
27246
27406
|
}
|
|
27247
|
-
|
|
27407
|
+
function answerFilesDeclaration(dispatch) {
|
|
27408
|
+
try {
|
|
27409
|
+
return (0, loom_1.readPromptExecutionSpec)(dispatch.inputs).answerFiles ?? null;
|
|
27410
|
+
} catch {
|
|
27411
|
+
return null;
|
|
27412
|
+
}
|
|
27413
|
+
}
|
|
27414
|
+
async function runOutputGates(dispatch, response, executor, signal, ctx, files = /* @__PURE__ */ new Map()) {
|
|
27248
27415
|
let spec;
|
|
27249
27416
|
try {
|
|
27250
27417
|
spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
27251
27418
|
} catch {
|
|
27252
27419
|
return { ok: true, response };
|
|
27253
27420
|
}
|
|
27254
|
-
const gates = spec.gates;
|
|
27255
|
-
|
|
27421
|
+
const gates = spec.gates ?? [];
|
|
27422
|
+
const declaration = spec.answerFiles;
|
|
27423
|
+
if (gates.length === 0 && !declaration)
|
|
27256
27424
|
return { ok: true, response };
|
|
27257
|
-
|
|
27258
|
-
|
|
27425
|
+
const check = (candidate) => {
|
|
27426
|
+
const spliced = declaration ? (0, answer_files_1.spliceAnswerFiles)(candidate, spec.outputLabel, declaration, files) : { response: candidate, findings: [] };
|
|
27427
|
+
const gated = (0, output_gates_1.evaluateOutputGates)(gates, spliced.response.parsed ?? {}, dispatch.inputs);
|
|
27428
|
+
const findings = [...spliced.findings, ...gated.findings];
|
|
27429
|
+
return { response: spliced.response, result: { ok: findings.length === 0, findings } };
|
|
27430
|
+
};
|
|
27431
|
+
let checked = check(response);
|
|
27432
|
+
let current = checked.response;
|
|
27433
|
+
let result = checked.result;
|
|
27259
27434
|
let attempts = 0;
|
|
27260
27435
|
while (!result.ok && attempts < exports2.GATE_REPAIR_BUDGET && current.continuation !== void 0 && typeof executor.continue === "function") {
|
|
27261
27436
|
try {
|
|
27262
27437
|
const repaired = await executor.continue(current.continuation, (0, output_gates_1.renderRepairPrompt)(result.findings), signal, ctx);
|
|
27263
|
-
|
|
27438
|
+
checked = check(mergeRepairedResponse(current, repaired));
|
|
27264
27439
|
} catch {
|
|
27265
27440
|
break;
|
|
27266
27441
|
}
|
|
27267
|
-
|
|
27442
|
+
current = checked.response;
|
|
27443
|
+
result = checked.result;
|
|
27268
27444
|
attempts += 1;
|
|
27269
27445
|
}
|
|
27270
27446
|
if (result.ok)
|
|
@@ -29469,7 +29645,7 @@ var import_path = require("path");
|
|
|
29469
29645
|
var import_promises = require("fs/promises");
|
|
29470
29646
|
var import_yaml = __toESM(require_dist3());
|
|
29471
29647
|
var import_shuttle = __toESM(require_dist5());
|
|
29472
|
-
var buildVersion = true ? "0.
|
|
29648
|
+
var buildVersion = true ? "0.40.1" : pkg.version;
|
|
29473
29649
|
var AGENT_EXECUTORS = ["claude-code", "codex", "copilot"];
|
|
29474
29650
|
var sifterBrand = {
|
|
29475
29651
|
product: {
|