@whittlelabs/sifter 0.13.0 → 0.15.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/bin.js +64 -71
- package/bin.js.map +3 -3
- package/package.json +1 -1
package/bin.js
CHANGED
|
@@ -3538,7 +3538,7 @@ var require_init = __commonJS({
|
|
|
3538
3538
|
var store_1 = require_store();
|
|
3539
3539
|
var spend_store_1 = require_spend_store();
|
|
3540
3540
|
function buildInitCommand(brand) {
|
|
3541
|
-
return new commander_1.Command("init").description(`Pair this ${brand.product.title} with Keep and seed local state`).option("--pairing-code <code>", "Pairing code from the product UI").option("--keep-url <url>", "Override the Keep API URL from BrandConfig").option("--jobs-url <url>", "Override the Jobs API URL from BrandConfig").option("--executor <type>", `Executor to write into ${brand.product.id}.yaml (e.g. claude-code). Must be in the brand's executor allowlist.`).option("--poll-interval <ms>", "Run-loop poll interval in milliseconds").option("--daily-cap <tokens>", "Daily token cap; overrides the brand default").option("--monthly-cap <tokens>", "Monthly token cap; overrides the brand default").option("--
|
|
3541
|
+
return new commander_1.Command("init").description(`Pair this ${brand.product.title} with Keep and seed local state`).option("--pairing-code <code>", "Pairing code from the product UI").option("--keep-url <url>", "Override the Keep API URL from BrandConfig").option("--jobs-url <url>", "Override the Jobs API URL from BrandConfig").option("--executor <type>", `Executor to write into ${brand.product.id}.yaml (e.g. claude-code). Must be in the brand's executor allowlist.`).option("--poll-interval <ms>", "Run-loop poll interval in milliseconds").option("--daily-cap <tokens>", "Daily token cap; overrides the brand default").option("--monthly-cap <tokens>", "Monthly token cap; overrides the brand default").option("--tmp-dir <path>", "Base directory for each job's throwaway checkout (defaults to the OS temp dir; set only when that is unsuitable)").option("-p, --profile <name>", 'Config profile to pair into (isolates this pairing under its own dir; default "default")').action(async (options) => {
|
|
3542
3542
|
try {
|
|
3543
3543
|
const profile = (0, apply_1.resolveProfile)(brand, options.profile);
|
|
3544
3544
|
const setupOptions = parseSetupOptions(brand, options);
|
|
@@ -3621,8 +3621,8 @@ var require_init = __commonJS({
|
|
|
3621
3621
|
if (raw.monthlyCap !== void 0) {
|
|
3622
3622
|
out.monthlyCap = requireNonNegativeInt("--monthly-cap", raw.monthlyCap);
|
|
3623
3623
|
}
|
|
3624
|
-
if (raw.
|
|
3625
|
-
out.
|
|
3624
|
+
if (raw.tmpDir !== void 0) {
|
|
3625
|
+
out.tmpDir = raw.tmpDir;
|
|
3626
3626
|
}
|
|
3627
3627
|
return out;
|
|
3628
3628
|
}
|
|
@@ -18896,10 +18896,11 @@ var require_workspace = __commonJS({
|
|
|
18896
18896
|
var path_1 = __importDefault(require("path"));
|
|
18897
18897
|
var DEFAULT_GIT_TIMEOUT_MS = 12e4;
|
|
18898
18898
|
var DEFAULT_GIT_PROTOCOLS = "https:ssh:git";
|
|
18899
|
-
async function prepareWorkspace(
|
|
18899
|
+
async function prepareWorkspace(workspace, options = {}) {
|
|
18900
|
+
const tmpBase = options.tmpDir ?? os_1.default.tmpdir();
|
|
18900
18901
|
const resolved = readGitWorkspace(workspace);
|
|
18901
18902
|
if ("warning" in resolved) {
|
|
18902
|
-
return degradedWorkspace(resolved.warning);
|
|
18903
|
+
return degradedWorkspace(resolved.warning, tmpBase);
|
|
18903
18904
|
}
|
|
18904
18905
|
const gitWorkspace = resolved.git;
|
|
18905
18906
|
const timeoutMs = options.commandTimeoutMs ?? DEFAULT_GIT_TIMEOUT_MS;
|
|
@@ -18907,7 +18908,6 @@ var require_workspace = __commonJS({
|
|
|
18907
18908
|
const signal = options.signal;
|
|
18908
18909
|
const authEnv = options.gitToken !== void 0 ? buildGitAuthEnv(gitWorkspace.remote, options.gitToken) : void 0;
|
|
18909
18910
|
const git = (args, cwd) => runGit(args, cwd, { timeoutMs, signal, allowedProtocols, extraEnv: authEnv });
|
|
18910
|
-
const gitCleanup = (args, cwd) => runGit(args, cwd, { timeoutMs, allowedProtocols });
|
|
18911
18911
|
const hasCommit = (dir) => git(["cat-file", "-e", `${gitWorkspace.commit}^{commit}`], dir).then((r) => r.code === 0);
|
|
18912
18912
|
const fetchCommit = async (dir, opts) => {
|
|
18913
18913
|
if (await hasCommit(dir))
|
|
@@ -18915,9 +18915,6 @@ var require_workspace = __commonJS({
|
|
|
18915
18915
|
const depth = opts.shallow ? ["--depth=1"] : [];
|
|
18916
18916
|
const attempts = [];
|
|
18917
18917
|
for (const ref of gitWorkspace.fetchRefs ?? []) {
|
|
18918
|
-
if (opts.includeOrigin) {
|
|
18919
|
-
attempts.push({ source: `origin ${ref}`, args: ["fetch", "--no-tags", ...depth, "origin", ref] });
|
|
18920
|
-
}
|
|
18921
18918
|
attempts.push({
|
|
18922
18919
|
source: `${gitWorkspace.remote} ${ref}`,
|
|
18923
18920
|
args: ["fetch", "--no-tags", ...depth, gitWorkspace.remote, ref]
|
|
@@ -18941,31 +18938,7 @@ var require_workspace = __commonJS({
|
|
|
18941
18938
|
return failures2;
|
|
18942
18939
|
};
|
|
18943
18940
|
const notMaterialized = (failures2) => `could not materialize ${gitWorkspace.commit} from ${gitWorkspace.remote}` + (failures2.length > 0 ? ` (${failures2.join("; ")})` : "");
|
|
18944
|
-
const
|
|
18945
|
-
if (isRepo) {
|
|
18946
|
-
const failures2 = await fetchCommit(repoDir, { includeOrigin: true, shallow: false });
|
|
18947
|
-
if (!await hasCommit(repoDir))
|
|
18948
|
-
return degradedWorkspace(notMaterialized(failures2));
|
|
18949
|
-
const tempRoot2 = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-ws-"));
|
|
18950
|
-
const worktreeDir = path_1.default.join(tempRoot2, "checkout");
|
|
18951
|
-
const add = await git(["worktree", "add", "--detach", worktreeDir, gitWorkspace.commit], repoDir);
|
|
18952
|
-
if (add.code !== 0) {
|
|
18953
|
-
await fs_1.promises.rm(tempRoot2, { recursive: true, force: true }).catch(() => {
|
|
18954
|
-
});
|
|
18955
|
-
return degradedWorkspace(`git worktree add failed: ${firstLine(add.stderr)}`);
|
|
18956
|
-
}
|
|
18957
|
-
return {
|
|
18958
|
-
cwd: worktreeDir,
|
|
18959
|
-
materialized: true,
|
|
18960
|
-
cleanup: async () => {
|
|
18961
|
-
await gitCleanup(["worktree", "remove", "--force", worktreeDir], repoDir);
|
|
18962
|
-
await gitCleanup(["worktree", "prune"], repoDir);
|
|
18963
|
-
await fs_1.promises.rm(tempRoot2, { recursive: true, force: true }).catch(() => {
|
|
18964
|
-
});
|
|
18965
|
-
}
|
|
18966
|
-
};
|
|
18967
|
-
}
|
|
18968
|
-
const tempRoot = await fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), "shuttle-ws-"));
|
|
18941
|
+
const tempRoot = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-ws-"));
|
|
18969
18942
|
const checkoutDir = path_1.default.join(tempRoot, "checkout");
|
|
18970
18943
|
const removeTempRoot = async () => {
|
|
18971
18944
|
await fs_1.promises.rm(tempRoot, { recursive: true, force: true }).catch(() => {
|
|
@@ -18975,17 +18948,17 @@ var require_workspace = __commonJS({
|
|
|
18975
18948
|
const init = await git(["init", "-q"], checkoutDir);
|
|
18976
18949
|
if (init.code !== 0) {
|
|
18977
18950
|
await removeTempRoot();
|
|
18978
|
-
return degradedWorkspace(`git init failed: ${firstLine(init.stderr)}
|
|
18951
|
+
return degradedWorkspace(`git init failed: ${firstLine(init.stderr)}`, tmpBase);
|
|
18979
18952
|
}
|
|
18980
|
-
const failures = await fetchCommit(checkoutDir, {
|
|
18953
|
+
const failures = await fetchCommit(checkoutDir, { shallow: true });
|
|
18981
18954
|
if (!await hasCommit(checkoutDir)) {
|
|
18982
18955
|
await removeTempRoot();
|
|
18983
|
-
return degradedWorkspace(notMaterialized(failures));
|
|
18956
|
+
return degradedWorkspace(notMaterialized(failures), tmpBase);
|
|
18984
18957
|
}
|
|
18985
18958
|
const checkout = await git(["checkout", "--detach", gitWorkspace.commit], checkoutDir);
|
|
18986
18959
|
if (checkout.code !== 0) {
|
|
18987
18960
|
await removeTempRoot();
|
|
18988
|
-
return degradedWorkspace(`git checkout failed: ${firstLine(checkout.stderr)}
|
|
18961
|
+
return degradedWorkspace(`git checkout failed: ${firstLine(checkout.stderr)}`, tmpBase);
|
|
18989
18962
|
}
|
|
18990
18963
|
return { cwd: checkoutDir, materialized: true, cleanup: removeTempRoot };
|
|
18991
18964
|
}
|
|
@@ -19033,8 +19006,8 @@ var require_workspace = __commonJS({
|
|
|
19033
19006
|
}
|
|
19034
19007
|
return { git };
|
|
19035
19008
|
}
|
|
19036
|
-
async function degradedWorkspace(warning) {
|
|
19037
|
-
const scratchDir = await fs_1.promises.mkdtemp(path_1.default.join(
|
|
19009
|
+
async function degradedWorkspace(warning, tmpBase = os_1.default.tmpdir()) {
|
|
19010
|
+
const scratchDir = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-ws-degraded-"));
|
|
19038
19011
|
return {
|
|
19039
19012
|
cwd: scratchDir,
|
|
19040
19013
|
materialized: false,
|
|
@@ -19329,12 +19302,15 @@ var require_claude_code = __commonJS({
|
|
|
19329
19302
|
configDir = path_1.default.join(jobScratch, "claude-config");
|
|
19330
19303
|
await fs_1.promises.mkdir(configDir);
|
|
19331
19304
|
}
|
|
19305
|
+
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
19332
19306
|
let workspace = null;
|
|
19307
|
+
let promptOnlyScratch = null;
|
|
19333
19308
|
let overlayResult = null;
|
|
19334
19309
|
try {
|
|
19335
19310
|
if (spec.workspace) {
|
|
19336
|
-
workspace = await (0, workspace_1.prepareWorkspace)(
|
|
19311
|
+
workspace = await (0, workspace_1.prepareWorkspace)(spec.workspace, {
|
|
19337
19312
|
signal,
|
|
19313
|
+
tmpDir: tmpBase,
|
|
19338
19314
|
...credentials?.githubToken ? { gitToken: credentials.githubToken } : {}
|
|
19339
19315
|
});
|
|
19340
19316
|
if (spec.workspace.overlay) {
|
|
@@ -19347,7 +19323,10 @@ var require_claude_code = __commonJS({
|
|
|
19347
19323
|
const childEnv = buildChildEnv(credentials, configDir);
|
|
19348
19324
|
const claudeHints = (0, loom_1.pickProviderHints)(spec, "claude-code");
|
|
19349
19325
|
const hintedModel = typeof claudeHints.model === "string" && claudeHints.model.length > 0 ? claudeHints.model : void 0;
|
|
19350
|
-
|
|
19326
|
+
if (!workspace) {
|
|
19327
|
+
promptOnlyScratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-run-"));
|
|
19328
|
+
}
|
|
19329
|
+
const runCwd = workspace?.cwd ?? promptOnlyScratch;
|
|
19351
19330
|
const { response, readPaths } = await this.runClaude(prompt, spec, runCwd, childEnv, signal, hintedModel);
|
|
19352
19331
|
if (workspace) {
|
|
19353
19332
|
response.outputs.push({
|
|
@@ -19372,6 +19351,9 @@ var require_claude_code = __commonJS({
|
|
|
19372
19351
|
} finally {
|
|
19373
19352
|
if (workspace)
|
|
19374
19353
|
await workspace.cleanup();
|
|
19354
|
+
if (promptOnlyScratch)
|
|
19355
|
+
await fs_1.promises.rm(promptOnlyScratch, { recursive: true, force: true }).catch(() => {
|
|
19356
|
+
});
|
|
19375
19357
|
if (jobScratch)
|
|
19376
19358
|
await fs_1.promises.rm(jobScratch, { recursive: true, force: true }).catch(() => {
|
|
19377
19359
|
});
|
|
@@ -19477,12 +19459,13 @@ var require_claude_code = __commonJS({
|
|
|
19477
19459
|
}
|
|
19478
19460
|
};
|
|
19479
19461
|
function createClaudeCodeExecutor(config, capabilityId = "claude-code", deps = {}) {
|
|
19480
|
-
const
|
|
19481
|
-
if (
|
|
19482
|
-
|
|
19462
|
+
const validated = { capabilityId };
|
|
19463
|
+
if (config.tmpDir !== void 0) {
|
|
19464
|
+
if (typeof config.tmpDir !== "string" || config.tmpDir.length === 0) {
|
|
19465
|
+
throw new Error('"tmpDir" must be a non-empty string');
|
|
19466
|
+
}
|
|
19467
|
+
validated.tmpDir = config.tmpDir;
|
|
19483
19468
|
}
|
|
19484
|
-
const cwd = (0, apply_1.expandHome)(cwdRaw);
|
|
19485
|
-
const validated = { cwd, capabilityId };
|
|
19486
19469
|
if (config.model !== void 0) {
|
|
19487
19470
|
if (typeof config.model !== "string")
|
|
19488
19471
|
throw new Error('"model" must be a string');
|
|
@@ -20747,6 +20730,7 @@ var require_shuttle = __commonJS({
|
|
|
20747
20730
|
"use strict";
|
|
20748
20731
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
20749
20732
|
exports2.Shuttle = void 0;
|
|
20733
|
+
exports2.mergeExecutorDefaults = mergeExecutorDefaults;
|
|
20750
20734
|
var os_1 = require("os");
|
|
20751
20735
|
var jobs_1 = require_dist2();
|
|
20752
20736
|
var self_update_1 = require_self_update();
|
|
@@ -20806,7 +20790,7 @@ var require_shuttle = __commonJS({
|
|
|
20806
20790
|
});
|
|
20807
20791
|
const registry = new registry_1.ExecutorRegistry(this.brand.executorAllowlist);
|
|
20808
20792
|
registerBuiltInExecutors(registry, this.brand.executorAllowlist);
|
|
20809
|
-
const executors = registry.build(this.config.executors);
|
|
20793
|
+
const executors = registry.build(mergeExecutorDefaults(this.brand.executorDefaults, this.config.executors));
|
|
20810
20794
|
const byStrategy = /* @__PURE__ */ new Map();
|
|
20811
20795
|
for (const executor of executors) {
|
|
20812
20796
|
for (const strategy of executor.strategies)
|
|
@@ -20965,6 +20949,15 @@ var require_shuttle = __commonJS({
|
|
|
20965
20949
|
}
|
|
20966
20950
|
};
|
|
20967
20951
|
exports2.Shuttle = Shuttle;
|
|
20952
|
+
function mergeExecutorDefaults(defaults, userExecutors) {
|
|
20953
|
+
if (!defaults)
|
|
20954
|
+
return userExecutors;
|
|
20955
|
+
const merged = {};
|
|
20956
|
+
for (const [name, userConfig] of Object.entries(userExecutors)) {
|
|
20957
|
+
merged[name] = { ...defaults[name] ?? {}, ...userConfig };
|
|
20958
|
+
}
|
|
20959
|
+
return merged;
|
|
20960
|
+
}
|
|
20968
20961
|
function registerBuiltInExecutors(registry, allowlist) {
|
|
20969
20962
|
const all = [
|
|
20970
20963
|
["claude-code", claude_code_1.createClaudeCodeExecutor],
|
|
@@ -21532,14 +21525,13 @@ var import_shuttle2 = __toESM(require_dist5());
|
|
|
21532
21525
|
// src/brand.ts
|
|
21533
21526
|
var import_path = require("path");
|
|
21534
21527
|
var import_promises = require("fs/promises");
|
|
21535
|
-
var import_readline = require("readline");
|
|
21536
21528
|
var import_yaml = __toESM(require_dist());
|
|
21537
21529
|
var import_shuttle = __toESM(require_dist5());
|
|
21538
21530
|
|
|
21539
21531
|
// package.json
|
|
21540
21532
|
var package_default = {
|
|
21541
21533
|
name: "@whittlelabs/sifter",
|
|
21542
|
-
version: "0.
|
|
21534
|
+
version: "0.15.0",
|
|
21543
21535
|
description: "Whittle Sifter: paired AI reviewer for Whittle Sift job pools.",
|
|
21544
21536
|
bin: {
|
|
21545
21537
|
"whittle-sifter": "./dist/bin.js"
|
|
@@ -21598,6 +21590,19 @@ var sifterBrand = {
|
|
|
21598
21590
|
configSearchPaths: ["~/.sifter/sifter.yaml"]
|
|
21599
21591
|
},
|
|
21600
21592
|
executorAllowlist: ["claude-code", "anthropic-api", "http-api", "webhook", "custom-script"],
|
|
21593
|
+
// Overlay-fetch origins for the signed standards-content URLs each Sift
|
|
21594
|
+
// tier signs (SIFT_PUBLIC_API_URL). Without these, the executor's SSRF
|
|
21595
|
+
// guard silently skips the standards overlay and every review runs
|
|
21596
|
+
// standards-blind (2026-07-13). User config overrides per key.
|
|
21597
|
+
executorDefaults: {
|
|
21598
|
+
"claude-code": {
|
|
21599
|
+
overlayAllowedHosts: [
|
|
21600
|
+
"https://api.sift.whittlelabs.com",
|
|
21601
|
+
"https://api.sift.stage.whittlelabs.com",
|
|
21602
|
+
"http://localhost:3008"
|
|
21603
|
+
]
|
|
21604
|
+
}
|
|
21605
|
+
},
|
|
21601
21606
|
keepRegistration: {
|
|
21602
21607
|
keepApiUrl: process.env.KEEP_API_URL ?? "https://keep.whittlelabs.com",
|
|
21603
21608
|
jobsApiUrl: process.env.JOBS_API_URL ?? "https://jobs.whittlelabs.com",
|
|
@@ -21623,12 +21628,11 @@ var sifterBrand = {
|
|
|
21623
21628
|
// A re-pair must refresh the pairing-derived fields (Jobs URL, service
|
|
21624
21629
|
// identity, pools) even when sifter.yaml already exists — otherwise a
|
|
21625
21630
|
// stale file silently shadows the new pairing (exactly how a leftover
|
|
21626
|
-
// `localhost:3005`
|
|
21627
|
-
//
|
|
21628
|
-
//
|
|
21629
|
-
//
|
|
21630
|
-
//
|
|
21631
|
-
// isn't one to preserve.
|
|
21631
|
+
// `localhost:3005` once survived a fresh pair). So we merge rather than
|
|
21632
|
+
// skip: rebuild the pairing fields from `pairing`, carry over the user's
|
|
21633
|
+
// executor block (e.g. a claude-code `tmpDir`) and polling unless a fresh
|
|
21634
|
+
// flag overrides them, and log what happened. We only (re)scaffold an
|
|
21635
|
+
// executor block when there isn't one to preserve.
|
|
21632
21636
|
run: async (ctx) => {
|
|
21633
21637
|
const yamlPath = (0, import_path.join)(ctx.configDir, "sifter.yaml");
|
|
21634
21638
|
const pairing = new import_shuttle.PairingConfigStore((0, import_path.join)(ctx.configDir, "config.json")).read();
|
|
@@ -21683,8 +21687,10 @@ var sifterBrand = {
|
|
|
21683
21687
|
async function buildExecutorBlock(executor, ctx) {
|
|
21684
21688
|
switch (executor) {
|
|
21685
21689
|
case "claude-code": {
|
|
21686
|
-
|
|
21687
|
-
|
|
21690
|
+
return {
|
|
21691
|
+
type: "claude-code",
|
|
21692
|
+
...ctx.setupOptions.tmpDir ? { tmpDir: ctx.setupOptions.tmpDir } : {}
|
|
21693
|
+
};
|
|
21688
21694
|
}
|
|
21689
21695
|
case "anthropic-api": {
|
|
21690
21696
|
return {
|
|
@@ -21696,19 +21702,6 @@ async function buildExecutorBlock(executor, ctx) {
|
|
|
21696
21702
|
return { type: executor };
|
|
21697
21703
|
}
|
|
21698
21704
|
}
|
|
21699
|
-
async function promptForCwd(prompt, fallback) {
|
|
21700
|
-
if (!process.stdin.isTTY) return fallback;
|
|
21701
|
-
const rl = (0, import_readline.createInterface)({ input: process.stdin, output: process.stdout });
|
|
21702
|
-
try {
|
|
21703
|
-
const answer = await new Promise((resolve) => {
|
|
21704
|
-
rl.question(prompt, (input) => resolve(input));
|
|
21705
|
-
});
|
|
21706
|
-
const trimmed = answer.trim();
|
|
21707
|
-
return trimmed.length === 0 ? fallback : trimmed;
|
|
21708
|
-
} finally {
|
|
21709
|
-
rl.close();
|
|
21710
|
-
}
|
|
21711
|
-
}
|
|
21712
21705
|
|
|
21713
21706
|
// src/bin.ts
|
|
21714
21707
|
(0, import_shuttle2.createCli)(sifterBrand).parseAsync(process.argv).catch((err) => {
|