@whittlelabs/sifter 0.38.1 → 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.
- package/README.md +1 -0
- package/bin.js +264 -34
- 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 } : {},
|
|
@@ -9963,6 +9973,10 @@ var require_progress = __commonJS({
|
|
|
9963
9973
|
textBuf = "";
|
|
9964
9974
|
/** The last two characters of reasoning emitted since the last tool; null when none. */
|
|
9965
9975
|
reasoningTail = null;
|
|
9976
|
+
/** A streamed block has begun and none of its text has been emitted yet. */
|
|
9977
|
+
blockStart = false;
|
|
9978
|
+
/** The open streamed block's trailing whitespace, held until text follows it. */
|
|
9979
|
+
heldSpace = "";
|
|
9966
9980
|
timer = null;
|
|
9967
9981
|
closed = false;
|
|
9968
9982
|
flushMs;
|
|
@@ -9996,15 +10010,42 @@ var require_progress = __commonJS({
|
|
|
9996
10010
|
const block = text.trim();
|
|
9997
10011
|
if (this.closed || !block)
|
|
9998
10012
|
return;
|
|
9999
|
-
|
|
10000
|
-
|
|
10001
|
-
|
|
10013
|
+
this.reasoning(this.gap() + block);
|
|
10014
|
+
}
|
|
10015
|
+
/**
|
|
10016
|
+
* A block of reasoning begins that will arrive in pieces through
|
|
10017
|
+
* `reasoningDelta`, as the model writes it. Set off and trimmed exactly as
|
|
10018
|
+
* `reasoningBlock` sets off and trims a whole one, so a streamed transcript
|
|
10019
|
+
* reads the same as one reported block by block; only its timing differs.
|
|
10020
|
+
*/
|
|
10021
|
+
beginBlock() {
|
|
10022
|
+
this.blockStart = true;
|
|
10023
|
+
this.heldSpace = "";
|
|
10024
|
+
}
|
|
10025
|
+
/** The next piece of the block `beginBlock` opened. */
|
|
10026
|
+
reasoningDelta(text) {
|
|
10027
|
+
if (this.closed || !text)
|
|
10028
|
+
return;
|
|
10029
|
+
let body = this.heldSpace + text;
|
|
10030
|
+
if (this.blockStart)
|
|
10031
|
+
body = body.trimStart();
|
|
10032
|
+
const kept = body.trimEnd();
|
|
10033
|
+
this.heldSpace = body.slice(kept.length);
|
|
10034
|
+
if (!kept)
|
|
10035
|
+
return;
|
|
10036
|
+
if (this.blockStart) {
|
|
10037
|
+
this.blockStart = false;
|
|
10038
|
+
this.reasoning(this.gap() + kept);
|
|
10039
|
+
} else {
|
|
10040
|
+
this.reasoning(kept);
|
|
10041
|
+
}
|
|
10002
10042
|
}
|
|
10003
10043
|
tool(name, target) {
|
|
10004
10044
|
if (this.closed || !name)
|
|
10005
10045
|
return;
|
|
10006
10046
|
this.drainText();
|
|
10007
10047
|
this.reasoningTail = null;
|
|
10048
|
+
this.heldSpace = "";
|
|
10008
10049
|
this.push({ kind: "tool", tool: target ? { name, target } : { name } });
|
|
10009
10050
|
this.flush();
|
|
10010
10051
|
}
|
|
@@ -10029,6 +10070,11 @@ var require_progress = __commonJS({
|
|
|
10029
10070
|
this.closed = true;
|
|
10030
10071
|
this.clearTimer();
|
|
10031
10072
|
}
|
|
10073
|
+
/** The break that sets a new block off from the reasoning before it. */
|
|
10074
|
+
gap() {
|
|
10075
|
+
const tail = this.reasoningTail;
|
|
10076
|
+
return tail === null || tail.endsWith("\n\n") ? "" : tail.endsWith("\n") ? "\n" : "\n\n";
|
|
10077
|
+
}
|
|
10032
10078
|
drainText() {
|
|
10033
10079
|
if (this.textBuf.length === 0)
|
|
10034
10080
|
return;
|
|
@@ -10058,6 +10104,8 @@ var require_progress = __commonJS({
|
|
|
10058
10104
|
var StreamJsonTap = class {
|
|
10059
10105
|
handlers;
|
|
10060
10106
|
buf = "";
|
|
10107
|
+
/** The messages whose reasoning arrived as deltas, by API message id. */
|
|
10108
|
+
streamed = /* @__PURE__ */ new Set();
|
|
10061
10109
|
constructor(handlers) {
|
|
10062
10110
|
this.handlers = handlers;
|
|
10063
10111
|
}
|
|
@@ -10087,12 +10135,18 @@ var require_progress = __commonJS({
|
|
|
10087
10135
|
} catch {
|
|
10088
10136
|
return;
|
|
10089
10137
|
}
|
|
10090
|
-
if (event.type === "
|
|
10091
|
-
|
|
10138
|
+
if (event.type === "stream_event") {
|
|
10139
|
+
this.consumeStreamEvent(event.event);
|
|
10140
|
+
} else if (event.type === "assistant") {
|
|
10141
|
+
const message = event.message;
|
|
10142
|
+
const content = message?.content;
|
|
10092
10143
|
if (!Array.isArray(content))
|
|
10093
10144
|
return;
|
|
10145
|
+
const streamed = typeof message?.id === "string" && this.streamed.has(message.id);
|
|
10094
10146
|
for (const block of content) {
|
|
10095
10147
|
const b = block;
|
|
10148
|
+
if (streamed && b?.type !== "tool_use")
|
|
10149
|
+
continue;
|
|
10096
10150
|
if (b?.type === "text" && typeof b.text === "string" && b.text.length > 0) {
|
|
10097
10151
|
this.handlers.onReasoning?.(b.text);
|
|
10098
10152
|
} else if (b?.type === "thinking" && typeof b.thinking === "string" && b.thinking.length > 0) {
|
|
@@ -10105,6 +10159,24 @@ var require_progress = __commonJS({
|
|
|
10105
10159
|
this.handlers.onResult?.();
|
|
10106
10160
|
}
|
|
10107
10161
|
}
|
|
10162
|
+
consumeStreamEvent(event) {
|
|
10163
|
+
if (!event)
|
|
10164
|
+
return;
|
|
10165
|
+
if (event.type === "message_start") {
|
|
10166
|
+
const id = event.message?.id;
|
|
10167
|
+
if (typeof id === "string")
|
|
10168
|
+
this.streamed.add(id);
|
|
10169
|
+
} else if (event.type === "content_block_start") {
|
|
10170
|
+
const kind = event.content_block?.type;
|
|
10171
|
+
if (kind === "thinking" || kind === "text")
|
|
10172
|
+
this.handlers.onReasoningStart?.();
|
|
10173
|
+
} else if (event.type === "content_block_delta") {
|
|
10174
|
+
const delta = event.delta;
|
|
10175
|
+
const text = delta?.type === "thinking_delta" ? delta.thinking : delta?.type === "text_delta" ? delta.text : void 0;
|
|
10176
|
+
if (typeof text === "string" && text.length > 0)
|
|
10177
|
+
this.handlers.onReasoningDelta?.(text);
|
|
10178
|
+
}
|
|
10179
|
+
}
|
|
10108
10180
|
};
|
|
10109
10181
|
exports2.StreamJsonTap = StreamJsonTap;
|
|
10110
10182
|
function toolTarget(input) {
|
|
@@ -10764,19 +10836,25 @@ var require_claude_code = __commonJS({
|
|
|
10764
10836
|
var DEFAULT_TIMEOUT = 6e5;
|
|
10765
10837
|
var REVIEW_TOOLS = ["Read", "Grep", "Glob"];
|
|
10766
10838
|
var THINKING_DISPLAY_FLOOR = "2.1.94";
|
|
10767
|
-
function reviewRunArgs(settingSources) {
|
|
10839
|
+
function reviewRunArgs(settingSources, writesAnswerFiles = false) {
|
|
10768
10840
|
return [
|
|
10769
10841
|
"--setting-sources",
|
|
10770
10842
|
settingSources,
|
|
10771
10843
|
"--strict-mcp-config",
|
|
10772
10844
|
"--tools",
|
|
10773
|
-
REVIEW_TOOLS.join(","),
|
|
10845
|
+
(writesAnswerFiles ? [...REVIEW_TOOLS, "Write"] : REVIEW_TOOLS).join(","),
|
|
10774
10846
|
"--permission-mode",
|
|
10775
10847
|
"default",
|
|
10776
10848
|
"--settings",
|
|
10777
10849
|
JSON.stringify({ disableAllHooks: true })
|
|
10778
10850
|
];
|
|
10779
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
|
+
}
|
|
10780
10858
|
var RETIRED_READ_TOOLS = ["LS"];
|
|
10781
10859
|
var HOSTED_ENV_PASSTHROUGH = [
|
|
10782
10860
|
"PATH",
|
|
@@ -10865,7 +10943,11 @@ var require_claude_code = __commonJS({
|
|
|
10865
10943
|
materializeOptions: {
|
|
10866
10944
|
...tmpBase ? { tmpDir: tmpBase } : {},
|
|
10867
10945
|
overlayAllowedHosts: this.config.overlayAllowedHosts ?? []
|
|
10868
|
-
}
|
|
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"
|
|
10869
10951
|
};
|
|
10870
10952
|
}
|
|
10871
10953
|
async execute(dispatch, signal, ctx) {
|
|
@@ -10917,7 +10999,8 @@ var require_claude_code = __commonJS({
|
|
|
10917
10999
|
// machine's, and the runner pauses on it. With a revealed key it is one
|
|
10918
11000
|
// customer's, and only their job fails.
|
|
10919
11001
|
modelEnv === null,
|
|
10920
|
-
effort
|
|
11002
|
+
effort,
|
|
11003
|
+
ctx?.outputDir
|
|
10921
11004
|
);
|
|
10922
11005
|
if (workspace) {
|
|
10923
11006
|
response.outputs.push(...(0, workspace_telemetry_1.workspaceOutputs)(workspace, runCwd, readPaths, toolInputs));
|
|
@@ -11003,7 +11086,7 @@ var require_claude_code = __commonJS({
|
|
|
11003
11086
|
async listModels() {
|
|
11004
11087
|
return CLAUDE_CODE_MODELS;
|
|
11005
11088
|
}
|
|
11006
|
-
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) {
|
|
11007
11090
|
const outputLabel = spec.outputLabel;
|
|
11008
11091
|
const model = modelOverride ?? this.config.model;
|
|
11009
11092
|
const effort = effortOverride ?? this.config.reasoningEffort;
|
|
@@ -11012,9 +11095,18 @@ var require_claude_code = __commonJS({
|
|
|
11012
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");
|
|
11013
11096
|
}
|
|
11014
11097
|
const settingSources = local && this.config.claudeSettings === "user" ? "user" : "";
|
|
11015
|
-
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
|
+
];
|
|
11016
11106
|
if (resumeSessionId)
|
|
11017
11107
|
args.push("--resume", resumeSessionId);
|
|
11108
|
+
if (reporter)
|
|
11109
|
+
args.push("--include-partial-messages");
|
|
11018
11110
|
if (spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0) {
|
|
11019
11111
|
args.push("--json-schema", JSON.stringify(spec.outputSchema));
|
|
11020
11112
|
}
|
|
@@ -11028,11 +11120,14 @@ var require_claude_code = __commonJS({
|
|
|
11028
11120
|
}
|
|
11029
11121
|
if (this.config.maxTurns !== void 0)
|
|
11030
11122
|
args.push("--max-turns", String(this.config.maxTurns));
|
|
11031
|
-
|
|
11032
|
-
|
|
11123
|
+
const allowRules = [...allowedTools ?? [], ...writeRule ? [writeRule] : []];
|
|
11124
|
+
if (allowRules.length > 0) {
|
|
11125
|
+
args.push("--allowedTools", allowRules.join(","));
|
|
11033
11126
|
}
|
|
11034
11127
|
const tap = reporter ? new progress_1.StreamJsonTap({
|
|
11035
11128
|
onReasoning: (t) => reporter.reasoningBlock(t),
|
|
11129
|
+
onReasoningStart: () => reporter.beginBlock(),
|
|
11130
|
+
onReasoningDelta: (t) => reporter.reasoningDelta(t),
|
|
11036
11131
|
onTool: (name, target) => reporter.tool(name, target),
|
|
11037
11132
|
onResult: () => reporter.flush()
|
|
11038
11133
|
}) : null;
|
|
@@ -11464,8 +11559,10 @@ var require_codex = __commonJS({
|
|
|
11464
11559
|
var workspace_telemetry_1 = require_workspace_telemetry();
|
|
11465
11560
|
var apply_1 = require_apply();
|
|
11466
11561
|
var CODEX_PROFILE = "sift-review";
|
|
11467
|
-
function reviewProfile(installDir, platform = process.platform) {
|
|
11562
|
+
function reviewProfile(installDir, platform = process.platform, outputDir) {
|
|
11468
11563
|
const rules = ['":root"="deny"', '":minimal"="read"', '":workspace_roots"="read"', `${JSON.stringify(installDir)}="read"`];
|
|
11564
|
+
if (outputDir)
|
|
11565
|
+
rules.push(`${JSON.stringify(outputDir)}="write"`);
|
|
11469
11566
|
if (platform === "linux") {
|
|
11470
11567
|
rules.push(`${JSON.stringify(path_1.default.join(codexHome(), "tmp", "arg0"))}="read"`);
|
|
11471
11568
|
} else {
|
|
@@ -11516,14 +11613,14 @@ var require_codex = __commonJS({
|
|
|
11516
11613
|
}
|
|
11517
11614
|
return codex;
|
|
11518
11615
|
}
|
|
11519
|
-
function codexReviewArgs(installDir, platform = process.platform) {
|
|
11616
|
+
function codexReviewArgs(installDir, platform = process.platform, outputDir) {
|
|
11520
11617
|
return [
|
|
11521
11618
|
"--json",
|
|
11522
11619
|
"--skip-git-repo-check",
|
|
11523
11620
|
"--ignore-user-config",
|
|
11524
11621
|
"--ignore-rules",
|
|
11525
11622
|
"-c",
|
|
11526
|
-
reviewProfile(installDir, platform),
|
|
11623
|
+
reviewProfile(installDir, platform, outputDir),
|
|
11527
11624
|
"-c",
|
|
11528
11625
|
`default_permissions="${CODEX_PROFILE}"`,
|
|
11529
11626
|
"-c",
|
|
@@ -11566,7 +11663,7 @@ var require_codex = __commonJS({
|
|
|
11566
11663
|
};
|
|
11567
11664
|
}
|
|
11568
11665
|
async prepareEnvironment(dispatch) {
|
|
11569
|
-
return (0, agent_common_1.agentEnvironment)(dispatch, this.config);
|
|
11666
|
+
return { ...(0, agent_common_1.agentEnvironment)(dispatch, this.config), writesAnswerFiles: true };
|
|
11570
11667
|
}
|
|
11571
11668
|
async execute(dispatch, signal, ctx) {
|
|
11572
11669
|
(0, agent_common_1.refuseHosted)("codex", Boolean(ctx?.credentials));
|
|
@@ -11584,9 +11681,10 @@ var require_codex = __commonJS({
|
|
|
11584
11681
|
runCwd = path_1.default.join(scratch, "run");
|
|
11585
11682
|
await fs_1.promises.mkdir(runCwd);
|
|
11586
11683
|
}
|
|
11684
|
+
const outputDir = ctx?.outputDir ? await fs_1.promises.realpath(ctx.outputDir) : void 0;
|
|
11587
11685
|
const { response, commands } = await this.runCodex({
|
|
11588
11686
|
codex,
|
|
11589
|
-
args: ["exec", ...codexReviewArgs(codex.installDir), "-C", runCwd],
|
|
11687
|
+
args: ["exec", ...codexReviewArgs(codex.installDir, process.platform, outputDir), "-C", runCwd],
|
|
11590
11688
|
prompt,
|
|
11591
11689
|
spec,
|
|
11592
11690
|
cwd: runCwd,
|
|
@@ -11950,14 +12048,22 @@ var require_copilot = __commonJS({
|
|
|
11950
12048
|
var apply_1 = require_apply();
|
|
11951
12049
|
var REVIEW_TOOLS = ["view", "grep", "glob"];
|
|
11952
12050
|
var DENIED_TOOLS = ["shell", "write"];
|
|
11953
|
-
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
|
+
];
|
|
11954
12062
|
return [
|
|
11955
12063
|
"--output-format",
|
|
11956
12064
|
"json",
|
|
11957
12065
|
"--no-color",
|
|
11958
|
-
|
|
11959
|
-
`--available-tools=${REVIEW_TOOLS.join(",")}`,
|
|
11960
|
-
...DENIED_TOOLS.map((t) => `--deny-tool=${t}`),
|
|
12066
|
+
...permissions,
|
|
11961
12067
|
"--disable-builtin-mcps",
|
|
11962
12068
|
"--disallow-temp-dir",
|
|
11963
12069
|
"--no-ask-user",
|
|
@@ -11999,7 +12105,7 @@ var require_copilot = __commonJS({
|
|
|
11999
12105
|
};
|
|
12000
12106
|
}
|
|
12001
12107
|
async prepareEnvironment(dispatch) {
|
|
12002
|
-
return (0, agent_common_1.agentEnvironment)(dispatch, this.config);
|
|
12108
|
+
return { ...(0, agent_common_1.agentEnvironment)(dispatch, this.config), writesAnswerFiles: true };
|
|
12003
12109
|
}
|
|
12004
12110
|
async execute(dispatch, signal, ctx) {
|
|
12005
12111
|
(0, agent_common_1.refuseHosted)("copilot", Boolean(ctx?.credentials));
|
|
@@ -12022,8 +12128,9 @@ var require_copilot = __commonJS({
|
|
|
12022
12128
|
await fs_1.promises.mkdir(runCwd);
|
|
12023
12129
|
}
|
|
12024
12130
|
const sessionId = (0, crypto_1.randomUUID)();
|
|
12131
|
+
const outputDir = ctx?.outputDir ? await fs_1.promises.realpath(ctx.outputDir) : void 0;
|
|
12025
12132
|
const { response, reads, toolInputs } = await this.runCopilot({
|
|
12026
|
-
args: [...copilotReviewArgs(), `--session-id=${sessionId}`, "-C", runCwd],
|
|
12133
|
+
args: [...copilotReviewArgs(outputDir), `--session-id=${sessionId}`, "-C", runCwd],
|
|
12027
12134
|
prompt: withSchema(prompt, spec),
|
|
12028
12135
|
spec,
|
|
12029
12136
|
cwd: runCwd,
|
|
@@ -27126,6 +27233,96 @@ var require_workspace = __commonJS({
|
|
|
27126
27233
|
}
|
|
27127
27234
|
});
|
|
27128
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
|
+
|
|
27129
27326
|
// ../../packages/shuttle/dist/core/run-dispatch.js
|
|
27130
27327
|
var require_run_dispatch = __commonJS({
|
|
27131
27328
|
"../../packages/shuttle/dist/core/run-dispatch.js"(exports2) {
|
|
@@ -27139,6 +27336,7 @@ var require_run_dispatch = __commonJS({
|
|
|
27139
27336
|
var output_gates_1 = require_output_gates();
|
|
27140
27337
|
var logger_1 = require_logger();
|
|
27141
27338
|
var workspace_1 = require_workspace();
|
|
27339
|
+
var answer_files_1 = require_answer_files();
|
|
27142
27340
|
exports2.GATE_REPAIR_BUDGET = 2;
|
|
27143
27341
|
async function runDispatch(dispatch, executor, options) {
|
|
27144
27342
|
const { credentials, signal } = options;
|
|
@@ -27159,46 +27357,78 @@ var require_run_dispatch = __commonJS({
|
|
|
27159
27357
|
workspace = null;
|
|
27160
27358
|
}
|
|
27161
27359
|
}
|
|
27162
|
-
const
|
|
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 = {
|
|
27163
27373
|
...options.reportProgress ? { reportProgress: options.reportProgress } : {},
|
|
27164
27374
|
workspace,
|
|
27165
27375
|
credentials
|
|
27166
27376
|
};
|
|
27377
|
+
const ctx = { ...repairCtx, ...outputDir ? { outputDir } : {} };
|
|
27167
27378
|
let response;
|
|
27379
|
+
let files = /* @__PURE__ */ new Map();
|
|
27168
27380
|
try {
|
|
27169
27381
|
response = await executor.execute(dispatch, signal, ctx);
|
|
27382
|
+
if (outputDir && declaration)
|
|
27383
|
+
files = await (0, answer_files_1.collectAnswerFiles)(outputDir, declaration);
|
|
27170
27384
|
} catch (error) {
|
|
27171
27385
|
return { kind: "executor-failed", error };
|
|
27172
27386
|
} finally {
|
|
27173
27387
|
if (workspace)
|
|
27174
27388
|
await workspace.cleanup();
|
|
27175
27389
|
}
|
|
27176
|
-
const gated = await runOutputGates(dispatch, response, executor, signal,
|
|
27390
|
+
const gated = await runOutputGates(dispatch, response, executor, signal, repairCtx, files);
|
|
27177
27391
|
if (!gated.ok)
|
|
27178
27392
|
return { kind: "gate-failed", reason: gated.reason };
|
|
27179
27393
|
return { kind: "completed", response: gated.response };
|
|
27180
27394
|
}
|
|
27181
|
-
|
|
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()) {
|
|
27182
27403
|
let spec;
|
|
27183
27404
|
try {
|
|
27184
27405
|
spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
27185
27406
|
} catch {
|
|
27186
27407
|
return { ok: true, response };
|
|
27187
27408
|
}
|
|
27188
|
-
const gates = spec.gates;
|
|
27189
|
-
|
|
27409
|
+
const gates = spec.gates ?? [];
|
|
27410
|
+
const declaration = spec.answerFiles;
|
|
27411
|
+
if (gates.length === 0 && !declaration)
|
|
27190
27412
|
return { ok: true, response };
|
|
27191
|
-
|
|
27192
|
-
|
|
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;
|
|
27193
27422
|
let attempts = 0;
|
|
27194
27423
|
while (!result.ok && attempts < exports2.GATE_REPAIR_BUDGET && current.continuation !== void 0 && typeof executor.continue === "function") {
|
|
27195
27424
|
try {
|
|
27196
27425
|
const repaired = await executor.continue(current.continuation, (0, output_gates_1.renderRepairPrompt)(result.findings), signal, ctx);
|
|
27197
|
-
|
|
27426
|
+
checked = check(mergeRepairedResponse(current, repaired));
|
|
27198
27427
|
} catch {
|
|
27199
27428
|
break;
|
|
27200
27429
|
}
|
|
27201
|
-
|
|
27430
|
+
current = checked.response;
|
|
27431
|
+
result = checked.result;
|
|
27202
27432
|
attempts += 1;
|
|
27203
27433
|
}
|
|
27204
27434
|
if (result.ok)
|
|
@@ -29403,7 +29633,7 @@ var import_path = require("path");
|
|
|
29403
29633
|
var import_promises = require("fs/promises");
|
|
29404
29634
|
var import_yaml = __toESM(require_dist3());
|
|
29405
29635
|
var import_shuttle = __toESM(require_dist5());
|
|
29406
|
-
var buildVersion = true ? "0.
|
|
29636
|
+
var buildVersion = true ? "0.40.0" : pkg.version;
|
|
29407
29637
|
var AGENT_EXECUTORS = ["claude-code", "codex", "copilot"];
|
|
29408
29638
|
var sifterBrand = {
|
|
29409
29639
|
product: {
|