@whittlelabs/sifter 0.16.0 → 0.17.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 +259 -6
- package/bin.js.map +4 -4
- package/package.json +1 -1
package/bin.js
CHANGED
|
@@ -15803,6 +15803,17 @@ var require_prompt_execution = __commonJS({
|
|
|
15803
15803
|
}
|
|
15804
15804
|
}
|
|
15805
15805
|
}
|
|
15806
|
+
if (spec.gates !== void 0) {
|
|
15807
|
+
if (!Array.isArray(spec.gates)) {
|
|
15808
|
+
throw new Error("prompt-execution: spec.gates must be an array when present");
|
|
15809
|
+
}
|
|
15810
|
+
for (const gate of spec.gates) {
|
|
15811
|
+
const g = gate;
|
|
15812
|
+
if (typeof gate !== "object" || gate === null || typeof g.name !== "string" || typeof g.primitive !== "string" || typeof g.params !== "object" || g.params === null) {
|
|
15813
|
+
throw new Error("prompt-execution: each spec.gates entry must be { name, primitive, params }");
|
|
15814
|
+
}
|
|
15815
|
+
}
|
|
15816
|
+
}
|
|
15806
15817
|
return spec;
|
|
15807
15818
|
}
|
|
15808
15819
|
function renderPromptExecution(inputs) {
|
|
@@ -16022,6 +16033,8 @@ var require_prompt_execution2 = __commonJS({
|
|
|
16022
16033
|
spec.costCapHint = options.costCapHint;
|
|
16023
16034
|
if (options.progressChannel !== void 0)
|
|
16024
16035
|
spec.progressChannel = options.progressChannel;
|
|
16036
|
+
if (options.outputGates && options.outputGates.length > 0)
|
|
16037
|
+
spec.gates = options.outputGates;
|
|
16025
16038
|
if (options.rendering === "producer" && !options.prompt) {
|
|
16026
16039
|
throw new Error("rendering='producer' requires `prompt`");
|
|
16027
16040
|
}
|
|
@@ -18081,6 +18094,7 @@ var require_canonicalise = __commonJS({
|
|
|
18081
18094
|
promptTemplate: templateText,
|
|
18082
18095
|
promptTemplatePath: promptOptions.promptTemplate,
|
|
18083
18096
|
outputSchema: toJsonSchema(promptOptions.outputSchema),
|
|
18097
|
+
...promptOptions.outputGates ? { outputGates: promptOptions.outputGates } : {},
|
|
18084
18098
|
outputLabel,
|
|
18085
18099
|
rendering: promptOptions.rendering,
|
|
18086
18100
|
...promptOptions.maxAttempts !== void 0 ? { maxAttempts: promptOptions.maxAttempts } : {},
|
|
@@ -18363,6 +18377,128 @@ var require_chain = __commonJS({
|
|
|
18363
18377
|
}
|
|
18364
18378
|
});
|
|
18365
18379
|
|
|
18380
|
+
// ../../packages/shuttle/dist/executors/output-gates.js
|
|
18381
|
+
var require_output_gates = __commonJS({
|
|
18382
|
+
"../../packages/shuttle/dist/executors/output-gates.js"(exports2) {
|
|
18383
|
+
"use strict";
|
|
18384
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
18385
|
+
exports2.resolvePath = resolvePath;
|
|
18386
|
+
exports2.evaluateOutputGates = evaluateOutputGates;
|
|
18387
|
+
exports2.renderRepairPrompt = renderRepairPrompt;
|
|
18388
|
+
var INPUT_REF = "input:";
|
|
18389
|
+
function resolvePath(root, path) {
|
|
18390
|
+
let ctx = root;
|
|
18391
|
+
for (const seg of path.split(".")) {
|
|
18392
|
+
const project = seg.endsWith("[*]");
|
|
18393
|
+
const key = project ? seg.slice(0, -3) : seg;
|
|
18394
|
+
if (Array.isArray(ctx)) {
|
|
18395
|
+
ctx = ctx.map((el) => key ? el?.[key] : el);
|
|
18396
|
+
} else {
|
|
18397
|
+
ctx = key ? ctx?.[key] : ctx;
|
|
18398
|
+
}
|
|
18399
|
+
if (project && !Array.isArray(ctx)) {
|
|
18400
|
+
ctx = ctx == null ? [] : [ctx];
|
|
18401
|
+
}
|
|
18402
|
+
}
|
|
18403
|
+
return ctx;
|
|
18404
|
+
}
|
|
18405
|
+
function resolveRef(ref, output, inputs) {
|
|
18406
|
+
if (ref.startsWith(INPUT_REF)) {
|
|
18407
|
+
const label = ref.slice(INPUT_REF.length);
|
|
18408
|
+
const row = inputs.find((i) => i.label === label) ?? inputs.find((i) => i.label === "artifacts" && i.payload.label === label);
|
|
18409
|
+
if (!row)
|
|
18410
|
+
return void 0;
|
|
18411
|
+
return Object.prototype.hasOwnProperty.call(row.payload, "value") ? row.payload.value : row.payload;
|
|
18412
|
+
}
|
|
18413
|
+
return resolvePath(output, ref);
|
|
18414
|
+
}
|
|
18415
|
+
function asStrings(value) {
|
|
18416
|
+
if (Array.isArray(value))
|
|
18417
|
+
return value.map((v) => String(v));
|
|
18418
|
+
if (value == null)
|
|
18419
|
+
return [];
|
|
18420
|
+
return [String(value)];
|
|
18421
|
+
}
|
|
18422
|
+
var PRIMITIVES = {
|
|
18423
|
+
/**
|
|
18424
|
+
* `coverage` — the values at `got` must exactly cover the set at `want`:
|
|
18425
|
+
* every wanted value present, nothing invented, none repeated. Params:
|
|
18426
|
+
* `{ got: <output path>, want: <ref>, subject?: <noun> }`.
|
|
18427
|
+
*/
|
|
18428
|
+
coverage(params, output, inputs) {
|
|
18429
|
+
const subject = typeof params.subject === "string" ? params.subject : "item";
|
|
18430
|
+
const got = asStrings(resolveRef(String(params.got), output, inputs));
|
|
18431
|
+
const want = asStrings(resolveRef(String(params.want), output, inputs));
|
|
18432
|
+
const gotSet = new Set(got);
|
|
18433
|
+
const wantSet = new Set(want);
|
|
18434
|
+
const missing = want.filter((v) => !gotSet.has(v));
|
|
18435
|
+
const unknown = [...new Set(got.filter((v) => !wantSet.has(v)))];
|
|
18436
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18437
|
+
const duplicated = [];
|
|
18438
|
+
for (const v of got) {
|
|
18439
|
+
if (seen.has(v))
|
|
18440
|
+
duplicated.push(v);
|
|
18441
|
+
else
|
|
18442
|
+
seen.add(v);
|
|
18443
|
+
}
|
|
18444
|
+
if (missing.length === 0 && unknown.length === 0 && duplicated.length === 0)
|
|
18445
|
+
return [];
|
|
18446
|
+
const parts = [];
|
|
18447
|
+
if (missing.length)
|
|
18448
|
+
parts.push(`missing ${subject}(s): [${missing.join(", ")}]`);
|
|
18449
|
+
if (unknown.length)
|
|
18450
|
+
parts.push(`not a known ${subject}: [${unknown.join(", ")}]`);
|
|
18451
|
+
if (duplicated.length)
|
|
18452
|
+
parts.push(`assessed more than once: [${[...new Set(duplicated)].join(", ")}]`);
|
|
18453
|
+
return [
|
|
18454
|
+
`every ${subject} must be covered exactly once \u2014 ${parts.join("; ")}. Return one entry per ${subject}, using its exact id.`
|
|
18455
|
+
];
|
|
18456
|
+
},
|
|
18457
|
+
/**
|
|
18458
|
+
* `non_empty_when` — the array at `path` must be non-empty, optionally only
|
|
18459
|
+
* when `when.path` equals `when.equals`. Params:
|
|
18460
|
+
* `{ path: <output path>, when?: { path: <output path>, equals: <value> } }`.
|
|
18461
|
+
*/
|
|
18462
|
+
non_empty_when(params, output) {
|
|
18463
|
+
const when = params.when;
|
|
18464
|
+
if (when && typeof when.path === "string") {
|
|
18465
|
+
if (resolvePath(output, when.path) !== when.equals)
|
|
18466
|
+
return [];
|
|
18467
|
+
}
|
|
18468
|
+
const path = String(params.path);
|
|
18469
|
+
const value = resolvePath(output, path);
|
|
18470
|
+
if (Array.isArray(value) && value.length > 0)
|
|
18471
|
+
return [];
|
|
18472
|
+
const cond = when && typeof when.path === "string" ? ` when ${when.path} is ${JSON.stringify(when.equals)}` : "";
|
|
18473
|
+
return [
|
|
18474
|
+
`'${path}' must be a non-empty array${cond}; it was ${Array.isArray(value) ? "empty" : "absent"}.`
|
|
18475
|
+
];
|
|
18476
|
+
}
|
|
18477
|
+
};
|
|
18478
|
+
function evaluateOutputGates(gates, output, inputs) {
|
|
18479
|
+
const findings = [];
|
|
18480
|
+
for (const gate of gates) {
|
|
18481
|
+
const primitive = PRIMITIVES[gate.primitive];
|
|
18482
|
+
if (!primitive) {
|
|
18483
|
+
findings.push({ gate: gate.name, message: `unknown gate primitive '${gate.primitive}'` });
|
|
18484
|
+
continue;
|
|
18485
|
+
}
|
|
18486
|
+
for (const message of primitive(gate.params, output, inputs)) {
|
|
18487
|
+
findings.push({ gate: gate.name, message });
|
|
18488
|
+
}
|
|
18489
|
+
}
|
|
18490
|
+
return { ok: findings.length === 0, findings };
|
|
18491
|
+
}
|
|
18492
|
+
function renderRepairPrompt(findings) {
|
|
18493
|
+
return [
|
|
18494
|
+
"Your previous answer did not satisfy these output checks. Return the corrected, complete answer that resolves every item below. Produce the same output shape with the problems fixed, and nothing else \u2014 no commentary, no explanation of the changes.",
|
|
18495
|
+
"",
|
|
18496
|
+
...findings.map((f) => `- [${f.gate}] ${f.message}`)
|
|
18497
|
+
].join("\n");
|
|
18498
|
+
}
|
|
18499
|
+
}
|
|
18500
|
+
});
|
|
18501
|
+
|
|
18366
18502
|
// ../../packages/keep/dist/client.js
|
|
18367
18503
|
var require_client2 = __commonJS({
|
|
18368
18504
|
"../../packages/keep/dist/client.js"(exports2) {
|
|
@@ -19533,11 +19669,40 @@ var require_claude_code = __commonJS({
|
|
|
19533
19669
|
});
|
|
19534
19670
|
}
|
|
19535
19671
|
}
|
|
19536
|
-
|
|
19672
|
+
/**
|
|
19673
|
+
* In-session gate repair (ADR 0017): resume the session `execute()` returned a
|
|
19674
|
+
* handle for and correct its output. The repair turn works from the resumed
|
|
19675
|
+
* session's own context (no checkout needed), under the same `--json-schema`
|
|
19676
|
+
* constraint. A throwaway cwd hosts the run; the same handle rides back out so
|
|
19677
|
+
* a second repair round can resume again.
|
|
19678
|
+
*/
|
|
19679
|
+
async continue(continuation, followUp, signal, _ctx) {
|
|
19680
|
+
const handle = continuation;
|
|
19681
|
+
if (!handle || typeof handle.sessionId !== "string") {
|
|
19682
|
+
throw new Error("claude-code continue: missing session to resume");
|
|
19683
|
+
}
|
|
19684
|
+
const tmpBase = this.config.tmpDir ? (0, apply_1.expandHome)(this.config.tmpDir) : os_1.default.tmpdir();
|
|
19685
|
+
const scratch = await fs_1.promises.mkdtemp(path_1.default.join(tmpBase, "shuttle-cc-resume-"));
|
|
19686
|
+
try {
|
|
19687
|
+
const spec = {
|
|
19688
|
+
rendering: "producer",
|
|
19689
|
+
outputLabel: handle.outputLabel,
|
|
19690
|
+
outputSchema: handle.outputSchema
|
|
19691
|
+
};
|
|
19692
|
+
const { response } = await this.runClaude(followUp, spec, scratch, handle.childEnv, signal, handle.model, null, handle.sessionId);
|
|
19693
|
+
return response;
|
|
19694
|
+
} finally {
|
|
19695
|
+
await fs_1.promises.rm(scratch, { recursive: true, force: true }).catch(() => {
|
|
19696
|
+
});
|
|
19697
|
+
}
|
|
19698
|
+
}
|
|
19699
|
+
runClaude(prompt, spec, cwd, childEnv, signal, modelOverride, reporter, resumeSessionId) {
|
|
19537
19700
|
const outputLabel = spec.outputLabel;
|
|
19538
19701
|
const model = modelOverride ?? this.config.model;
|
|
19539
19702
|
return new Promise((resolve, reject) => {
|
|
19540
19703
|
const args = ["--print", "--output-format", "stream-json", "--verbose"];
|
|
19704
|
+
if (resumeSessionId)
|
|
19705
|
+
args.push("--resume", resumeSessionId);
|
|
19541
19706
|
if (spec.outputSchema && typeof spec.outputSchema === "object" && Object.keys(spec.outputSchema).length > 0) {
|
|
19542
19707
|
args.push("--json-schema", JSON.stringify(spec.outputSchema));
|
|
19543
19708
|
}
|
|
@@ -19632,7 +19797,18 @@ var require_claude_code = __commonJS({
|
|
|
19632
19797
|
aiProvider: "claude-code",
|
|
19633
19798
|
...model ? { aiModel: model } : {},
|
|
19634
19799
|
...stream.usage ?? {}
|
|
19635
|
-
}
|
|
19800
|
+
},
|
|
19801
|
+
// In-session repair handle (ADR 0017): present only when the run
|
|
19802
|
+
// reported a session id, so the runner can `--resume` it.
|
|
19803
|
+
...stream.sessionId ? {
|
|
19804
|
+
continuation: {
|
|
19805
|
+
sessionId: stream.sessionId,
|
|
19806
|
+
model,
|
|
19807
|
+
childEnv,
|
|
19808
|
+
outputLabel,
|
|
19809
|
+
outputSchema: spec.outputSchema
|
|
19810
|
+
}
|
|
19811
|
+
} : {}
|
|
19636
19812
|
},
|
|
19637
19813
|
readPaths: stream.readPaths,
|
|
19638
19814
|
toolText: stream.toolText
|
|
@@ -19732,6 +19908,7 @@ var require_claude_code = __commonJS({
|
|
|
19732
19908
|
let usage = null;
|
|
19733
19909
|
let resultMeta = null;
|
|
19734
19910
|
let structuredOutput = null;
|
|
19911
|
+
let sessionId = null;
|
|
19735
19912
|
let toolText = "";
|
|
19736
19913
|
for (const line of stdout.split("\n")) {
|
|
19737
19914
|
const trimmed = line.trim();
|
|
@@ -19743,6 +19920,8 @@ var require_claude_code = __commonJS({
|
|
|
19743
19920
|
} catch {
|
|
19744
19921
|
continue;
|
|
19745
19922
|
}
|
|
19923
|
+
if (typeof event.session_id === "string")
|
|
19924
|
+
sessionId = event.session_id;
|
|
19746
19925
|
if (event.type === "assistant") {
|
|
19747
19926
|
const content = event.message?.content;
|
|
19748
19927
|
if (!Array.isArray(content))
|
|
@@ -19779,7 +19958,7 @@ var require_claude_code = __commonJS({
|
|
|
19779
19958
|
};
|
|
19780
19959
|
}
|
|
19781
19960
|
}
|
|
19782
|
-
return { finalText, readPaths, usage, resultMeta, structuredOutput, toolText };
|
|
19961
|
+
return { finalText, readPaths, usage, resultMeta, structuredOutput, sessionId, toolText };
|
|
19783
19962
|
}
|
|
19784
19963
|
var MAX_REPORTED_READS = 2e3;
|
|
19785
19964
|
function relativeWorkspaceReads(readPaths, cwd) {
|
|
@@ -20043,7 +20222,6 @@ var require_anthropic_api = __commonJS({
|
|
|
20043
20222
|
}
|
|
20044
20223
|
async execute(dispatch, signal) {
|
|
20045
20224
|
const { prompt, spec } = (0, loom_1.renderPromptExecution)(dispatch.inputs);
|
|
20046
|
-
const outputLabel = spec.outputLabel;
|
|
20047
20225
|
const providerHints = (0, loom_1.pickProviderHints)(spec, this.instance.capabilityId);
|
|
20048
20226
|
const apiKey = await this.instance.resolveKey(providerHints);
|
|
20049
20227
|
const hintedModel = providerHints.model;
|
|
@@ -20052,6 +20230,33 @@ var require_anthropic_api = __commonJS({
|
|
|
20052
20230
|
max_tokens: this.instance.maxTokens,
|
|
20053
20231
|
messages: [{ role: "user", content: prompt }]
|
|
20054
20232
|
};
|
|
20233
|
+
return this.callApi(apiKey, body, spec, spec.outputLabel, signal);
|
|
20234
|
+
}
|
|
20235
|
+
/**
|
|
20236
|
+
* In-session gate repair (ADR 0017): re-call the Messages API with the prior
|
|
20237
|
+
* conversation plus the model's own last turn plus the repair turn, under the
|
|
20238
|
+
* same output contract. A fresh conversation is not started — the appended
|
|
20239
|
+
* assistant turn gives the model its previous answer to correct.
|
|
20240
|
+
*/
|
|
20241
|
+
async continue(continuation, followUp, signal, _ctx) {
|
|
20242
|
+
const handle = continuation;
|
|
20243
|
+
if (!handle || !Array.isArray(handle.messages)) {
|
|
20244
|
+
throw new Error("anthropic-api continue: missing conversation to resume");
|
|
20245
|
+
}
|
|
20246
|
+
const spec = {
|
|
20247
|
+
rendering: "producer",
|
|
20248
|
+
outputLabel: handle.outputLabel,
|
|
20249
|
+
outputSchema: handle.outputSchema
|
|
20250
|
+
};
|
|
20251
|
+
const body = {
|
|
20252
|
+
model: handle.model,
|
|
20253
|
+
max_tokens: handle.maxTokens,
|
|
20254
|
+
messages: [...handle.messages, { role: "user", content: followUp }]
|
|
20255
|
+
};
|
|
20256
|
+
return this.callApi(handle.apiKey, body, spec, handle.outputLabel, signal);
|
|
20257
|
+
}
|
|
20258
|
+
/** Shared Messages-API call + parse + validate, used by execute and continue. */
|
|
20259
|
+
async callApi(apiKey, body, spec, outputLabel, signal) {
|
|
20055
20260
|
const timeoutController = new AbortController();
|
|
20056
20261
|
const timer = setTimeout(() => timeoutController.abort(), this.instance.timeoutMs);
|
|
20057
20262
|
function onAbort() {
|
|
@@ -20099,7 +20304,17 @@ var require_anthropic_api = __commonJS({
|
|
|
20099
20304
|
outputs: [{ label: outputLabel, content: parsed ?? { raw: rawText } }],
|
|
20100
20305
|
rawText,
|
|
20101
20306
|
parsed,
|
|
20102
|
-
...usage ? { usage } : {}
|
|
20307
|
+
...usage ? { usage } : {},
|
|
20308
|
+
// In-session repair handle (ADR 0017): the conversation with the model's
|
|
20309
|
+
// answer appended, ready for `continue()` to add the repair turn.
|
|
20310
|
+
continuation: {
|
|
20311
|
+
apiKey,
|
|
20312
|
+
model: body.model,
|
|
20313
|
+
maxTokens: body.max_tokens,
|
|
20314
|
+
messages: [...body.messages, { role: "assistant", content: rawText }],
|
|
20315
|
+
outputLabel,
|
|
20316
|
+
outputSchema: spec.outputSchema
|
|
20317
|
+
}
|
|
20103
20318
|
};
|
|
20104
20319
|
} finally {
|
|
20105
20320
|
clearTimeout(timer);
|
|
@@ -20932,6 +21147,7 @@ var require_shuttle = __commonJS({
|
|
|
20932
21147
|
var decommission_1 = require_decommission();
|
|
20933
21148
|
var loom_1 = require_dist3();
|
|
20934
21149
|
var chain_1 = require_chain();
|
|
21150
|
+
var output_gates_1 = require_output_gates();
|
|
20935
21151
|
var keep_1 = require_dist4();
|
|
20936
21152
|
var apply_1 = require_apply();
|
|
20937
21153
|
var version_check_1 = require_version_check();
|
|
@@ -20946,6 +21162,37 @@ var require_shuttle = __commonJS({
|
|
|
20946
21162
|
var audit_log_1 = require_audit_log();
|
|
20947
21163
|
var spend_tracker_1 = require_spend_tracker();
|
|
20948
21164
|
var store_1 = require_store();
|
|
21165
|
+
var GATE_REPAIR_BUDGET = 2;
|
|
21166
|
+
async function runOutputGates(dispatch, response, executor, signal, ctx) {
|
|
21167
|
+
let spec;
|
|
21168
|
+
try {
|
|
21169
|
+
spec = (0, loom_1.readPromptExecutionSpec)(dispatch.inputs);
|
|
21170
|
+
} catch {
|
|
21171
|
+
return { ok: true, response };
|
|
21172
|
+
}
|
|
21173
|
+
const gates = spec.gates;
|
|
21174
|
+
if (!gates || gates.length === 0)
|
|
21175
|
+
return { ok: true, response };
|
|
21176
|
+
let current = response;
|
|
21177
|
+
let result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
|
|
21178
|
+
let attempts = 0;
|
|
21179
|
+
while (!result.ok && attempts < GATE_REPAIR_BUDGET && current.continuation !== void 0 && typeof executor.continue === "function") {
|
|
21180
|
+
try {
|
|
21181
|
+
current = await executor.continue(current.continuation, (0, output_gates_1.renderRepairPrompt)(result.findings), signal, ctx);
|
|
21182
|
+
} catch {
|
|
21183
|
+
break;
|
|
21184
|
+
}
|
|
21185
|
+
result = (0, output_gates_1.evaluateOutputGates)(gates, current.parsed ?? {}, dispatch.inputs);
|
|
21186
|
+
attempts += 1;
|
|
21187
|
+
}
|
|
21188
|
+
if (result.ok)
|
|
21189
|
+
return { ok: true, response: current };
|
|
21190
|
+
const detail = result.findings.map((f) => `[${f.gate}] ${f.message}`).join(" | ");
|
|
21191
|
+
return {
|
|
21192
|
+
ok: false,
|
|
21193
|
+
reason: `output gate(s) failed${attempts > 0 ? ` after ${attempts} repair attempt(s)` : ""}: ${detail}`
|
|
21194
|
+
};
|
|
21195
|
+
}
|
|
20949
21196
|
var Shuttle = class {
|
|
20950
21197
|
brand;
|
|
20951
21198
|
config;
|
|
@@ -21056,6 +21303,12 @@ var require_shuttle = __commonJS({
|
|
|
21056
21303
|
await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
|
|
21057
21304
|
return { status: "failed", reason };
|
|
21058
21305
|
}
|
|
21306
|
+
const gated = await runOutputGates(dispatch, response, executor, signal, executionCtx);
|
|
21307
|
+
if (!gated.ok) {
|
|
21308
|
+
await observerChain.notify({ kind: "attendance.closed", jobId, attendanceId, status: "failed", reason: gated.reason, totalDurationMs: Date.now() - startedAt }, onObserverError);
|
|
21309
|
+
return { status: "failed", reason: gated.reason };
|
|
21310
|
+
}
|
|
21311
|
+
response = gated.response;
|
|
21059
21312
|
await observerChain.notify({
|
|
21060
21313
|
kind: "executor.dispatch.completed",
|
|
21061
21314
|
jobId,
|
|
@@ -21728,7 +21981,7 @@ var import_path = require("path");
|
|
|
21728
21981
|
var import_promises = require("fs/promises");
|
|
21729
21982
|
var import_yaml = __toESM(require_dist());
|
|
21730
21983
|
var import_shuttle = __toESM(require_dist5());
|
|
21731
|
-
var buildVersion = true ? "0.
|
|
21984
|
+
var buildVersion = true ? "0.17.0" : pkg.version;
|
|
21732
21985
|
var sifterBrand = {
|
|
21733
21986
|
product: {
|
|
21734
21987
|
id: "sifter",
|