@garygentry/system1 0.3.1 → 0.4.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/dist/bundle/chunks/{bin-FN3DZQV3.mjs → bin-TIEUYIP3.mjs} +21 -46
- package/dist/bundle/chunks/{chunk-5LVOFZF3.mjs → chunk-FU3D2MBS.mjs} +33 -4
- package/dist/bundle/chunks/{chunk-O6BSAJIH.mjs → chunk-QYUSJUFX.mjs} +1117 -257
- package/dist/bundle/chunks/{chunk-JD7HVXW5.mjs → chunk-WZIVOY5Q.mjs} +8 -2
- package/dist/bundle/chunks/{chunk-X2CYN5PB.mjs → chunk-XNRV73UE.mjs} +6 -2
- package/dist/bundle/chunks/{config-QMTA6YOP.mjs → config-KXGUNAJR.mjs} +36 -9
- package/dist/bundle/chunks/{decide-4QGC5ZEB.mjs → decide-HI5CUH3C.mjs} +4 -4
- package/dist/bundle/chunks/{doctor-JJIUAYN7.mjs → doctor-NDV3Y6T7.mjs} +2 -2
- package/dist/bundle/chunks/{misc-U5ZFC6EC.mjs → misc-6SDKH2PI.mjs} +39 -6
- package/dist/bundle/chunks/opportunities-ONEIYAG3.mjs +144 -0
- package/dist/bundle/chunks/{route-L3LIB4DZ.mjs → route-BF5J2NLU.mjs} +3 -3
- package/dist/bundle/chunks/{spec-Y52UMFEH.mjs → spec-GLTPEEON.mjs} +62 -16
- package/dist/bundle/decide.mjs +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
__require,
|
|
10
10
|
__toESM,
|
|
11
11
|
isDecisionsError
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WZIVOY5Q.mjs";
|
|
13
13
|
|
|
14
14
|
// ../../node_modules/.pnpm/yaml@2.9.1/node_modules/yaml/dist/nodes/identity.js
|
|
15
15
|
var require_identity = __commonJS({
|
|
@@ -8969,9 +8969,9 @@ var require_picomatch = __commonJS({
|
|
|
8969
8969
|
var utils = require_utils();
|
|
8970
8970
|
var constants2 = require_constants();
|
|
8971
8971
|
var isObject2 = (val) => val && typeof val === "object" && !Array.isArray(val);
|
|
8972
|
-
var
|
|
8972
|
+
var picomatch4 = (glob2, options, returnState = false) => {
|
|
8973
8973
|
if (Array.isArray(glob2)) {
|
|
8974
|
-
const fns = glob2.map((input) =>
|
|
8974
|
+
const fns = glob2.map((input) => picomatch4(input, options, returnState));
|
|
8975
8975
|
const arrayMatcher = (str) => {
|
|
8976
8976
|
for (const isMatch of fns) {
|
|
8977
8977
|
const state2 = isMatch(str);
|
|
@@ -8987,16 +8987,16 @@ var require_picomatch = __commonJS({
|
|
|
8987
8987
|
}
|
|
8988
8988
|
const opts = options || {};
|
|
8989
8989
|
const posix2 = opts.windows;
|
|
8990
|
-
const regex = isState ?
|
|
8990
|
+
const regex = isState ? picomatch4.compileRe(glob2, options) : picomatch4.makeRe(glob2, options, false, true);
|
|
8991
8991
|
const state = regex.state;
|
|
8992
8992
|
delete regex.state;
|
|
8993
8993
|
let isIgnored = () => false;
|
|
8994
8994
|
if (opts.ignore) {
|
|
8995
8995
|
const ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null };
|
|
8996
|
-
isIgnored =
|
|
8996
|
+
isIgnored = picomatch4(opts.ignore, ignoreOpts, returnState);
|
|
8997
8997
|
}
|
|
8998
8998
|
const matcher = (input, returnObject = false) => {
|
|
8999
|
-
const { isMatch, match, output } =
|
|
8999
|
+
const { isMatch, match, output } = picomatch4.test(input, regex, options, { glob: glob2, posix: posix2 });
|
|
9000
9000
|
const result = { glob: glob2, state, regex, posix: posix2, input, output, match, isMatch };
|
|
9001
9001
|
if (typeof opts.onResult === "function") {
|
|
9002
9002
|
opts.onResult(result);
|
|
@@ -9022,7 +9022,7 @@ var require_picomatch = __commonJS({
|
|
|
9022
9022
|
}
|
|
9023
9023
|
return matcher;
|
|
9024
9024
|
};
|
|
9025
|
-
|
|
9025
|
+
picomatch4.test = (input, regex, options, { glob: glob2, posix: posix2 } = {}) => {
|
|
9026
9026
|
if (typeof input !== "string") {
|
|
9027
9027
|
throw new TypeError("Expected input to be a string");
|
|
9028
9028
|
}
|
|
@@ -9039,24 +9039,24 @@ var require_picomatch = __commonJS({
|
|
|
9039
9039
|
}
|
|
9040
9040
|
if (match === false || opts.capture === true) {
|
|
9041
9041
|
if (opts.matchBase === true || opts.basename === true) {
|
|
9042
|
-
match =
|
|
9042
|
+
match = picomatch4.matchBase(input, regex, options, posix2);
|
|
9043
9043
|
} else {
|
|
9044
9044
|
match = regex.exec(output);
|
|
9045
9045
|
}
|
|
9046
9046
|
}
|
|
9047
9047
|
return { isMatch: Boolean(match), match, output };
|
|
9048
9048
|
};
|
|
9049
|
-
|
|
9050
|
-
const regex = glob2 instanceof RegExp ? glob2 :
|
|
9049
|
+
picomatch4.matchBase = (input, glob2, options, posix2 = options && options.windows) => {
|
|
9050
|
+
const regex = glob2 instanceof RegExp ? glob2 : picomatch4.makeRe(glob2, options);
|
|
9051
9051
|
return regex.test(utils.basename(input, { windows: posix2 }));
|
|
9052
9052
|
};
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
if (Array.isArray(pattern)) return pattern.map((p) =>
|
|
9053
|
+
picomatch4.isMatch = (str, patterns, options) => picomatch4(patterns, options)(str);
|
|
9054
|
+
picomatch4.parse = (pattern, options) => {
|
|
9055
|
+
if (Array.isArray(pattern)) return pattern.map((p) => picomatch4.parse(p, options));
|
|
9056
9056
|
return parse3(pattern, { ...options, fastpaths: false });
|
|
9057
9057
|
};
|
|
9058
|
-
|
|
9059
|
-
|
|
9058
|
+
picomatch4.scan = (input, options) => scan(input, options);
|
|
9059
|
+
picomatch4.compileRe = (state, options, returnOutput = false, returnState = false) => {
|
|
9060
9060
|
if (returnOutput === true) {
|
|
9061
9061
|
return state.output;
|
|
9062
9062
|
}
|
|
@@ -9067,13 +9067,13 @@ var require_picomatch = __commonJS({
|
|
|
9067
9067
|
if (state && state.negated === true) {
|
|
9068
9068
|
source = `^(?!${source}).*$`;
|
|
9069
9069
|
}
|
|
9070
|
-
const regex =
|
|
9070
|
+
const regex = picomatch4.toRegex(source, options);
|
|
9071
9071
|
if (returnState === true) {
|
|
9072
9072
|
regex.state = state;
|
|
9073
9073
|
}
|
|
9074
9074
|
return regex;
|
|
9075
9075
|
};
|
|
9076
|
-
|
|
9076
|
+
picomatch4.makeRe = (input, options = {}, returnOutput = false, returnState = false) => {
|
|
9077
9077
|
if (!input || typeof input !== "string") {
|
|
9078
9078
|
throw new TypeError("Expected a non-empty string");
|
|
9079
9079
|
}
|
|
@@ -9084,9 +9084,9 @@ var require_picomatch = __commonJS({
|
|
|
9084
9084
|
if (!parsed.output) {
|
|
9085
9085
|
parsed = parse3(input, options);
|
|
9086
9086
|
}
|
|
9087
|
-
return
|
|
9087
|
+
return picomatch4.compileRe(parsed, options, returnOutput, returnState);
|
|
9088
9088
|
};
|
|
9089
|
-
|
|
9089
|
+
picomatch4.toRegex = (source, options) => {
|
|
9090
9090
|
try {
|
|
9091
9091
|
const opts = options || {};
|
|
9092
9092
|
return new RegExp(source, opts.flags || (opts.nocase ? "i" : ""));
|
|
@@ -9095,8 +9095,8 @@ var require_picomatch = __commonJS({
|
|
|
9095
9095
|
return /$^/;
|
|
9096
9096
|
}
|
|
9097
9097
|
};
|
|
9098
|
-
|
|
9099
|
-
module.exports =
|
|
9098
|
+
picomatch4.constants = constants2;
|
|
9099
|
+
module.exports = picomatch4;
|
|
9100
9100
|
}
|
|
9101
9101
|
});
|
|
9102
9102
|
|
|
@@ -9106,65 +9106,17 @@ var require_picomatch2 = __commonJS({
|
|
|
9106
9106
|
"use strict";
|
|
9107
9107
|
var pico = require_picomatch();
|
|
9108
9108
|
var utils = require_utils();
|
|
9109
|
-
function
|
|
9109
|
+
function picomatch4(glob2, options, returnState = false) {
|
|
9110
9110
|
if (options && (options.windows === null || options.windows === void 0)) {
|
|
9111
9111
|
options = { ...options, windows: utils.isWindows() };
|
|
9112
9112
|
}
|
|
9113
9113
|
return pico(glob2, options, returnState);
|
|
9114
9114
|
}
|
|
9115
|
-
Object.assign(
|
|
9116
|
-
module.exports =
|
|
9115
|
+
Object.assign(picomatch4, pico);
|
|
9116
|
+
module.exports = picomatch4;
|
|
9117
9117
|
}
|
|
9118
9118
|
});
|
|
9119
9119
|
|
|
9120
|
-
// ../core/dist/config/load.js
|
|
9121
|
-
var import_yaml = __toESM(require_dist(), 1);
|
|
9122
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
9123
|
-
import { homedir } from "node:os";
|
|
9124
|
-
import { dirname, join, resolve } from "node:path";
|
|
9125
|
-
|
|
9126
|
-
// ../core/dist/model/answers.js
|
|
9127
|
-
var DEFAULT_UNDECIDED_FLOOR = 0.15;
|
|
9128
|
-
function noulConfidence(answer) {
|
|
9129
|
-
return Math.abs(answer.noul - 0.5) * 2;
|
|
9130
|
-
}
|
|
9131
|
-
function confidenceOf(answer) {
|
|
9132
|
-
return answer.type === "noul" ? noulConfidence(answer) : answer.confidence;
|
|
9133
|
-
}
|
|
9134
|
-
function undecidedNames(answers, floor = DEFAULT_UNDECIDED_FLOOR) {
|
|
9135
|
-
return Object.entries(answers).filter(([, answer]) => confidenceOf(answer) <= floor).map(([name]) => name);
|
|
9136
|
-
}
|
|
9137
|
-
|
|
9138
|
-
// ../core/dist/model/profiles.js
|
|
9139
|
-
var PROFILES = [
|
|
9140
|
-
{
|
|
9141
|
-
id: "typesafe/jev-1.13",
|
|
9142
|
-
displayName: "TypeSafe Jev 1.13",
|
|
9143
|
-
transport: "openrouter-decisions",
|
|
9144
|
-
maxStateTokens: 32e3,
|
|
9145
|
-
// Measured 2026-09-22: 400 options is refused upstream with "at most 255 choices".
|
|
9146
|
-
maxChoices: 255,
|
|
9147
|
-
usdPerInputToken: 0.042 / 1e6,
|
|
9148
|
-
usdPerOutputToken: 0,
|
|
9149
|
-
priceAsOf: "2026-09-19",
|
|
9150
|
-
undecidedFloor: DEFAULT_UNDECIDED_FLOOR,
|
|
9151
|
-
calibrated: true
|
|
9152
|
-
}
|
|
9153
|
-
];
|
|
9154
|
-
var DEFAULT_MODEL_ID = "typesafe/jev-1.13";
|
|
9155
|
-
function resolveProfile(id, profiles = PROFILES) {
|
|
9156
|
-
const exact = profiles.find((p) => p.id === id);
|
|
9157
|
-
if (exact)
|
|
9158
|
-
return exact;
|
|
9159
|
-
const family = profiles.find((p) => new RegExp(`^${escapeRegExp(p.id)}-\\d{8}$`).test(id));
|
|
9160
|
-
if (family)
|
|
9161
|
-
return { ...family, id };
|
|
9162
|
-
throw new DecisionsError("unknown-model", `No profile for model "${id}". Known: ${profiles.map((p) => p.id).join(", ")}.`, { model: id, known: profiles.map((p) => p.id) });
|
|
9163
|
-
}
|
|
9164
|
-
function escapeRegExp(text) {
|
|
9165
|
-
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9166
|
-
}
|
|
9167
|
-
|
|
9168
9120
|
// ../core/dist/route/route.js
|
|
9169
9121
|
var ROUTE_DEFAULTS = {
|
|
9170
9122
|
enabled: true,
|
|
@@ -9266,7 +9218,58 @@ function route(prompt, config) {
|
|
|
9266
9218
|
};
|
|
9267
9219
|
}
|
|
9268
9220
|
|
|
9221
|
+
// ../core/dist/config/load.js
|
|
9222
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
9223
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
9224
|
+
import { homedir } from "node:os";
|
|
9225
|
+
import { dirname, join, resolve } from "node:path";
|
|
9226
|
+
|
|
9227
|
+
// ../core/dist/model/answers.js
|
|
9228
|
+
var DEFAULT_UNDECIDED_FLOOR = 0.15;
|
|
9229
|
+
function noulConfidence(answer) {
|
|
9230
|
+
return Math.abs(answer.noul - 0.5) * 2;
|
|
9231
|
+
}
|
|
9232
|
+
function confidenceOf(answer) {
|
|
9233
|
+
return answer.type === "noul" ? noulConfidence(answer) : answer.confidence;
|
|
9234
|
+
}
|
|
9235
|
+
function undecidedNames(answers, floor = DEFAULT_UNDECIDED_FLOOR) {
|
|
9236
|
+
return Object.entries(answers).filter(([, answer]) => confidenceOf(answer) <= floor).map(([name]) => name);
|
|
9237
|
+
}
|
|
9238
|
+
|
|
9239
|
+
// ../core/dist/model/profiles.js
|
|
9240
|
+
var JEV_CALL_OVERHEAD_TOKENS = 300;
|
|
9241
|
+
var PROFILES = [
|
|
9242
|
+
{
|
|
9243
|
+
id: "typesafe/jev-1.13",
|
|
9244
|
+
displayName: "TypeSafe Jev 1.13",
|
|
9245
|
+
transport: "openrouter-decisions",
|
|
9246
|
+
maxStateTokens: 32e3,
|
|
9247
|
+
// Measured 2026-09-22: 400 options is refused upstream with "at most 255 choices".
|
|
9248
|
+
maxChoices: 255,
|
|
9249
|
+
callOverheadTokens: JEV_CALL_OVERHEAD_TOKENS,
|
|
9250
|
+
usdPerInputToken: 0.042 / 1e6,
|
|
9251
|
+
usdPerOutputToken: 0,
|
|
9252
|
+
priceAsOf: "2026-09-19",
|
|
9253
|
+
undecidedFloor: DEFAULT_UNDECIDED_FLOOR,
|
|
9254
|
+
calibrated: true
|
|
9255
|
+
}
|
|
9256
|
+
];
|
|
9257
|
+
var DEFAULT_MODEL_ID = "typesafe/jev-1.13";
|
|
9258
|
+
function resolveProfile(id, profiles = PROFILES) {
|
|
9259
|
+
const exact = profiles.find((p) => p.id === id);
|
|
9260
|
+
if (exact)
|
|
9261
|
+
return exact;
|
|
9262
|
+
const family = profiles.find((p) => new RegExp(`^${escapeRegExp(p.id)}-\\d{8}$`).test(id));
|
|
9263
|
+
if (family)
|
|
9264
|
+
return { ...family, id };
|
|
9265
|
+
throw new DecisionsError("unknown-model", `No profile for model "${id}". Known: ${profiles.map((p) => p.id).join(", ")}.`, { model: id, known: profiles.map((p) => p.id) });
|
|
9266
|
+
}
|
|
9267
|
+
function escapeRegExp(text) {
|
|
9268
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9269
|
+
}
|
|
9270
|
+
|
|
9269
9271
|
// ../core/dist/model/validate.js
|
|
9272
|
+
var QUESTION_KEYS = ["type", "instructions", "criteria"];
|
|
9270
9273
|
function assertQuestionSet(questions) {
|
|
9271
9274
|
const problems = [];
|
|
9272
9275
|
if (!isObject(questions) || Object.keys(questions).length === 0) {
|
|
@@ -9277,6 +9280,10 @@ function assertQuestionSet(questions) {
|
|
|
9277
9280
|
problems.push(`${name}: must be an object`);
|
|
9278
9281
|
continue;
|
|
9279
9282
|
}
|
|
9283
|
+
const extra = Object.keys(q).filter((k) => !QUESTION_KEYS.includes(k));
|
|
9284
|
+
if (extra.length > 0) {
|
|
9285
|
+
problems.push(`${name}: unknown key(s) ${extra.join(", ")} (a question has type, instructions, criteria)`);
|
|
9286
|
+
}
|
|
9280
9287
|
if (typeof q.instructions !== "string" || q.instructions.trim() === "") {
|
|
9281
9288
|
problems.push(`${name}: instructions must be a non-empty string`);
|
|
9282
9289
|
}
|
|
@@ -9408,7 +9415,8 @@ var RETRY_STATUSES = /* @__PURE__ */ new Set([
|
|
|
9408
9415
|
520,
|
|
9409
9416
|
521,
|
|
9410
9417
|
522,
|
|
9411
|
-
524
|
|
9418
|
+
524,
|
|
9419
|
+
529
|
|
9412
9420
|
]);
|
|
9413
9421
|
var defaultSleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
9414
9422
|
function createOpenRouterTransport(options) {
|
|
@@ -9556,14 +9564,20 @@ function loadConfig(options = {}) {
|
|
|
9556
9564
|
const repoFile = repoConfigPath(repoRoot);
|
|
9557
9565
|
const user = readLayer(userFile);
|
|
9558
9566
|
const repo = readLayer(repoFile);
|
|
9559
|
-
const
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9567
|
+
const warnings = [...unknownKeys(user, userFile, "user"), ...unknownKeys(repo, repoFile, "repo")];
|
|
9568
|
+
const layers2 = [
|
|
9569
|
+
{ layer: repo, file: repoFile },
|
|
9570
|
+
{ layer: user, file: userFile }
|
|
9571
|
+
];
|
|
9572
|
+
const pickFrom = (path, rule) => pick(path, rule, layers2, warnings);
|
|
9573
|
+
const merged2 = {
|
|
9574
|
+
model: pickFrom("model", TEXT) ?? DEFAULTS.model,
|
|
9575
|
+
endpoint: pickFrom("endpoint", URL_TEXT) ?? DEFAULTS.endpoint,
|
|
9576
|
+
concurrency: pickFrom("concurrency", WHOLE) ?? DEFAULTS.concurrency,
|
|
9577
|
+
timeoutMs: pickFrom("timeoutMs", POSITIVE) ?? DEFAULTS.timeoutMs,
|
|
9564
9578
|
budget: {
|
|
9565
|
-
maxCalls:
|
|
9566
|
-
maxUsd:
|
|
9579
|
+
maxCalls: pickFrom("budget.maxCalls", NON_NEGATIVE) ?? DEFAULTS.budget.maxCalls,
|
|
9580
|
+
maxUsd: pickFrom("budget.maxUsd", NON_NEGATIVE) ?? DEFAULTS.budget.maxUsd
|
|
9567
9581
|
},
|
|
9568
9582
|
egress: {
|
|
9569
9583
|
consent: readConsent(repo, repoFile),
|
|
@@ -9572,15 +9586,15 @@ function loadConfig(options = {}) {
|
|
|
9572
9586
|
...stringList(repo, "egress.exclude", repoFile)
|
|
9573
9587
|
]
|
|
9574
9588
|
},
|
|
9575
|
-
profiles: [...profileList(user, userFile), ...profileList(repo, repoFile)],
|
|
9589
|
+
profiles: [...profileList(user, userFile, warnings), ...profileList(repo, repoFile, warnings)],
|
|
9576
9590
|
route: readRoute(user, userFile, repo, repoFile, env)
|
|
9577
9591
|
};
|
|
9578
9592
|
const key = resolveApiKey(env, userDir);
|
|
9579
9593
|
const session = detectSession(env);
|
|
9580
9594
|
return {
|
|
9581
|
-
...
|
|
9582
|
-
model: nonEmpty2(env.SYSTEM1_MODEL) ??
|
|
9583
|
-
endpoint: nonEmpty2(env.SYSTEM1_ENDPOINT) ??
|
|
9595
|
+
...merged2,
|
|
9596
|
+
model: nonEmpty2(env.SYSTEM1_MODEL) ?? merged2.model,
|
|
9597
|
+
endpoint: nonEmpty2(env.SYSTEM1_ENDPOINT) ?? merged2.endpoint,
|
|
9584
9598
|
repoRoot,
|
|
9585
9599
|
apiKey: key?.value,
|
|
9586
9600
|
apiKeySource: key?.source,
|
|
@@ -9591,11 +9605,15 @@ function loadConfig(options = {}) {
|
|
|
9591
9605
|
...user ? { user: userFile } : {},
|
|
9592
9606
|
...repo ? { repo: repoFile } : {},
|
|
9593
9607
|
...key?.file ? { credentials: key.file } : {}
|
|
9594
|
-
}
|
|
9608
|
+
},
|
|
9609
|
+
warnings
|
|
9595
9610
|
};
|
|
9596
9611
|
}
|
|
9597
9612
|
function allProfiles(config) {
|
|
9598
|
-
|
|
9613
|
+
const byId = /* @__PURE__ */ new Map();
|
|
9614
|
+
for (const profile of [...PROFILES, ...config.profiles])
|
|
9615
|
+
byId.set(profile.id, profile);
|
|
9616
|
+
return [...byId.values()];
|
|
9599
9617
|
}
|
|
9600
9618
|
function readLayer(file) {
|
|
9601
9619
|
if (!existsSync(file))
|
|
@@ -9615,25 +9633,73 @@ function readLayer(file) {
|
|
|
9615
9633
|
}
|
|
9616
9634
|
return parsed;
|
|
9617
9635
|
}
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
|
|
9621
|
-
|
|
9622
|
-
|
|
9636
|
+
var KNOWN = {
|
|
9637
|
+
"": ["model", "endpoint", "concurrency", "timeoutMs", "budget", "egress", "profiles", "route"],
|
|
9638
|
+
budget: ["maxCalls", "maxUsd"],
|
|
9639
|
+
egress: ["consent", "exclude"]
|
|
9640
|
+
};
|
|
9641
|
+
function unknownKeys(layer, file, which2) {
|
|
9642
|
+
if (!layer)
|
|
9643
|
+
return [];
|
|
9644
|
+
const warnings = Object.keys(layer).filter((k) => !KNOWN[""]?.includes(k)).map((key) => `${file}: unknown key ${key}`);
|
|
9645
|
+
for (const section2 of ["budget", "egress"]) {
|
|
9646
|
+
const value = layer[section2];
|
|
9647
|
+
if (value === void 0 || value === null)
|
|
9648
|
+
continue;
|
|
9649
|
+
if (!isMapping(value)) {
|
|
9650
|
+
warnings.push(`${file}: ${section2} must be a mapping (ignored)`);
|
|
9651
|
+
continue;
|
|
9652
|
+
}
|
|
9653
|
+
for (const key of Object.keys(value)) {
|
|
9654
|
+
if (!KNOWN[section2]?.includes(key))
|
|
9655
|
+
warnings.push(`${file}: unknown key ${section2}.${key}`);
|
|
9656
|
+
}
|
|
9623
9657
|
}
|
|
9624
|
-
|
|
9658
|
+
const egress = layer.egress;
|
|
9659
|
+
if (which2 === "user" && isMapping(egress) && egress.consent !== void 0) {
|
|
9660
|
+
warnings.push(`${file}: egress.consent is read only from the repo file (ignored)`);
|
|
9661
|
+
}
|
|
9662
|
+
return warnings;
|
|
9625
9663
|
}
|
|
9626
|
-
|
|
9627
|
-
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9664
|
+
var TEXT = { ok: (v) => typeof v === "string", expected: "text" };
|
|
9665
|
+
var URL_TEXT = {
|
|
9666
|
+
ok: (v) => typeof v === "string" && URL.canParse(v) && /^https?:$/.test(new URL(v).protocol),
|
|
9667
|
+
expected: "an http(s) URL"
|
|
9668
|
+
};
|
|
9669
|
+
var WHOLE = {
|
|
9670
|
+
ok: (v) => Number.isInteger(v) && v >= 1,
|
|
9671
|
+
expected: "a whole number of at least 1"
|
|
9672
|
+
};
|
|
9673
|
+
var POSITIVE = {
|
|
9674
|
+
ok: (v) => typeof v === "number" && Number.isFinite(v) && v > 0,
|
|
9675
|
+
expected: "a number above 0"
|
|
9676
|
+
};
|
|
9677
|
+
var NON_NEGATIVE = {
|
|
9678
|
+
ok: (v) => typeof v === "number" && Number.isFinite(v) && v >= 0,
|
|
9679
|
+
expected: "a number of at least 0"
|
|
9680
|
+
};
|
|
9681
|
+
function pick(path, rule, layers2, warnings) {
|
|
9682
|
+
let found2;
|
|
9683
|
+
for (const { layer, file } of layers2) {
|
|
9684
|
+
const [head, key] = path.split(".");
|
|
9685
|
+
let value = layer?.[head];
|
|
9686
|
+
if (key !== void 0)
|
|
9687
|
+
value = isMapping(value) ? value[key] : void 0;
|
|
9688
|
+
if (value === void 0 || value === null)
|
|
9689
|
+
continue;
|
|
9690
|
+
if (!rule.ok(value))
|
|
9691
|
+
warnings.push(`${file}: ${path} must be ${rule.expected} (ignored)`);
|
|
9692
|
+
else if (found2 === void 0)
|
|
9693
|
+
found2 = value;
|
|
9631
9694
|
}
|
|
9632
|
-
return
|
|
9695
|
+
return found2;
|
|
9696
|
+
}
|
|
9697
|
+
function isMapping(value) {
|
|
9698
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9633
9699
|
}
|
|
9634
9700
|
function readConsent(repo, file) {
|
|
9635
9701
|
const consent = repo?.egress?.consent;
|
|
9636
|
-
if (consent === void 0)
|
|
9702
|
+
if (consent === void 0 || consent === null)
|
|
9637
9703
|
return { granted: false };
|
|
9638
9704
|
if (typeof consent !== "object" || consent === null || typeof consent.granted !== "boolean") {
|
|
9639
9705
|
throw new DecisionsError("config-error", `${file}: egress.consent must be {granted: true|false, at?, by?}`, { file });
|
|
@@ -9647,7 +9713,7 @@ function readConsent(repo, file) {
|
|
|
9647
9713
|
}
|
|
9648
9714
|
function stringList(layer, path, file) {
|
|
9649
9715
|
const value = layer?.egress?.exclude;
|
|
9650
|
-
if (value === void 0)
|
|
9716
|
+
if (value === void 0 || value === null)
|
|
9651
9717
|
return [];
|
|
9652
9718
|
if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
|
|
9653
9719
|
throw new DecisionsError("config-error", `${file}: ${path} must be a list of glob patterns`, {
|
|
@@ -9656,29 +9722,57 @@ function stringList(layer, path, file) {
|
|
|
9656
9722
|
}
|
|
9657
9723
|
return value;
|
|
9658
9724
|
}
|
|
9659
|
-
|
|
9725
|
+
var PROFILE_FIELDS = [
|
|
9726
|
+
"id",
|
|
9727
|
+
"displayName",
|
|
9728
|
+
"transport",
|
|
9729
|
+
"maxStateTokens",
|
|
9730
|
+
"maxChoices",
|
|
9731
|
+
"callOverheadTokens",
|
|
9732
|
+
"usdPerInputToken",
|
|
9733
|
+
"usdPerOutputToken",
|
|
9734
|
+
"priceAsOf",
|
|
9735
|
+
"undecidedFloor",
|
|
9736
|
+
"calibrated"
|
|
9737
|
+
];
|
|
9738
|
+
function profileList(layer, file, warnings) {
|
|
9660
9739
|
const value = layer?.profiles;
|
|
9661
|
-
if (value === void 0)
|
|
9740
|
+
if (value === void 0 || value === null)
|
|
9662
9741
|
return [];
|
|
9663
9742
|
if (!Array.isArray(value))
|
|
9664
9743
|
throw new DecisionsError("config-error", `${file}: profiles must be a list`, { file });
|
|
9665
9744
|
return value.map((p, i) => {
|
|
9666
|
-
const profile = p;
|
|
9745
|
+
const profile = { ...p };
|
|
9746
|
+
for (const [key, v] of Object.entries(profile)) {
|
|
9747
|
+
if (v === null && PROFILE_FIELDS.includes(key)) {
|
|
9748
|
+
delete profile[key];
|
|
9749
|
+
}
|
|
9750
|
+
}
|
|
9667
9751
|
const ok = typeof profile.id === "string" && typeof profile.maxStateTokens === "number" && typeof profile.usdPerInputToken === "number" && typeof profile.undecidedFloor === "number";
|
|
9668
9752
|
if (!ok) {
|
|
9669
9753
|
throw new DecisionsError("config-error", `${file}: profiles[${i}] needs id, maxStateTokens, usdPerInputToken and undecidedFloor`, { file });
|
|
9670
9754
|
}
|
|
9755
|
+
if (profile.callOverheadTokens !== void 0 && !(Number.isInteger(profile.callOverheadTokens) && profile.callOverheadTokens >= 0)) {
|
|
9756
|
+
throw new DecisionsError("config-error", `${file}: profiles[${i}].callOverheadTokens must be a whole number of at least 0`, { file });
|
|
9757
|
+
}
|
|
9671
9758
|
if (profile.maxChoices !== void 0 && !(Number.isInteger(profile.maxChoices) && profile.maxChoices >= 2)) {
|
|
9672
9759
|
throw new DecisionsError("config-error", `${file}: profiles[${i}].maxChoices must be a whole number of at least 2`, { file });
|
|
9673
9760
|
}
|
|
9761
|
+
const extra = Object.keys(profile).filter((k) => !PROFILE_FIELDS.includes(k));
|
|
9762
|
+
for (const key of extra) {
|
|
9763
|
+
warnings.push(`${file}: unknown key profiles[${i}].${key} (ignored)`);
|
|
9764
|
+
delete profile[key];
|
|
9765
|
+
}
|
|
9674
9766
|
return {
|
|
9675
9767
|
displayName: profile.id,
|
|
9676
9768
|
transport: "openrouter-decisions",
|
|
9677
9769
|
usdPerOutputToken: 0,
|
|
9678
9770
|
priceAsOf: "unknown",
|
|
9679
9771
|
calibrated: true,
|
|
9680
|
-
// Same wire contract as Jev unless the config says otherwise.
|
|
9772
|
+
// Same wire contract as Jev unless the config says otherwise. The overhead is
|
|
9773
|
+
// Jev's measured one: unknown for another model, but it errs toward stopping.
|
|
9681
9774
|
maxChoices: 255,
|
|
9775
|
+
callOverheadTokens: JEV_CALL_OVERHEAD_TOKENS,
|
|
9682
9776
|
...profile
|
|
9683
9777
|
};
|
|
9684
9778
|
});
|
|
@@ -9784,7 +9878,7 @@ function setConsent(repoRoot, granted, by, now = /* @__PURE__ */ new Date()) {
|
|
|
9784
9878
|
function assertConsent(consent, repoRoot) {
|
|
9785
9879
|
if (consent.granted)
|
|
9786
9880
|
return;
|
|
9787
|
-
throw new DecisionsError("egress-refused", `This repo has not agreed to send content to the decision model's provider. Consent is the user's:
|
|
9881
|
+
throw new DecisionsError("egress-refused", `This repo has not agreed to send content to the decision model's provider. Consent is the user's, and an agent must not grant it: the user runs \`decide config egress allow\` themselves in a terminal in ${repoRoot}, or through their agent prompt's shell escape (not as a chat message) with --confirm added. The setup skill explains what gets sent. Replay of recorded answers works without consent.`, { repoRoot });
|
|
9788
9882
|
}
|
|
9789
9883
|
|
|
9790
9884
|
// ../core/dist/egress/exclude.js
|
|
@@ -9833,6 +9927,9 @@ function applyExcludes(items, extra = []) {
|
|
|
9833
9927
|
}
|
|
9834
9928
|
return { items: kept, excluded: [...excluded.values()] };
|
|
9835
9929
|
}
|
|
9930
|
+
function isExcluded(path, extra = []) {
|
|
9931
|
+
return [...DEFAULT_EXCLUDES, ...extra].find((p) => (0, import_picomatch.default)(p, { dot: true })(path));
|
|
9932
|
+
}
|
|
9836
9933
|
|
|
9837
9934
|
// ../core/dist/ping.js
|
|
9838
9935
|
function probeUrl(config) {
|
|
@@ -13016,8 +13113,8 @@ function Compare(left, right) {
|
|
|
13016
13113
|
// ../../node_modules/.pnpm/typebox@1.3.34/node_modules/typebox/build/type/engine/evaluate/broaden.mjs
|
|
13017
13114
|
function BroadenFilter(type, types, result = [], all = types) {
|
|
13018
13115
|
return guard_exports.ShiftLeft(types, (left, right) => {
|
|
13019
|
-
const
|
|
13020
|
-
return guard_exports.IsEqual(
|
|
13116
|
+
const compare2 = Compare(type, left);
|
|
13117
|
+
return guard_exports.IsEqual(compare2, CompareResultLeftInside) || guard_exports.IsEqual(compare2, CompareResultEqual) ? all : guard_exports.IsEqual(compare2, CompareResultDisjoint) ? BroadenFilter(type, right, [...result, left], all) : BroadenFilter(type, right, result, all);
|
|
13021
13118
|
}, () => [...result, type]);
|
|
13022
13119
|
}
|
|
13023
13120
|
function BroadenType(type, types, result) {
|
|
@@ -13463,8 +13560,8 @@ function CollapseIntersectProperties(left, right) {
|
|
|
13463
13560
|
const leftProperties = leftKeys.reduce((result, key) => ({ ...result, [key]: left[key] }), {});
|
|
13464
13561
|
const rightProperties = rightKeys.reduce((result, key) => ({ ...result, [key]: right[key] }), {});
|
|
13465
13562
|
const sharedProperties = sharedKeys.reduce((result, key) => ({ ...result, [key]: EvaluateIntersect([left[key], right[key]]) }), {});
|
|
13466
|
-
const
|
|
13467
|
-
const shared = memory_exports.Assign(
|
|
13563
|
+
const unique2 = memory_exports.Assign(leftProperties, rightProperties);
|
|
13564
|
+
const shared = memory_exports.Assign(unique2, sharedProperties);
|
|
13468
13565
|
return shared;
|
|
13469
13566
|
}
|
|
13470
13567
|
function FromIntersect(types) {
|
|
@@ -14900,15 +14997,15 @@ function NextPendingResource(stack, schema) {
|
|
|
14900
14997
|
return IsId(schema) ? false : stack.pendingResource;
|
|
14901
14998
|
}
|
|
14902
14999
|
function NextLexicalBase(stack, schema) {
|
|
14903
|
-
const
|
|
14904
|
-
if (
|
|
14905
|
-
return
|
|
15000
|
+
const entry2 = ResourceEntry(stack, schema);
|
|
15001
|
+
if (entry2)
|
|
15002
|
+
return entry2.base;
|
|
14906
15003
|
return IsId(schema) ? NextUri(schema.$id, stack.lexicalBase).href : stack.lexicalBase;
|
|
14907
15004
|
}
|
|
14908
15005
|
function NextResourceBase(stack, schema) {
|
|
14909
|
-
const
|
|
14910
|
-
if (
|
|
14911
|
-
return
|
|
15006
|
+
const entry2 = ResourceEntry(stack, schema);
|
|
15007
|
+
if (entry2)
|
|
15008
|
+
return entry2.base;
|
|
14912
15009
|
return IsId(schema) && stack.pendingResource ? NextUri(schema.$id, stack.resourceBase).href : stack.resourceBase;
|
|
14913
15010
|
}
|
|
14914
15011
|
function NextUseResourceBaseForReference(stack, schema) {
|
|
@@ -14920,9 +15017,9 @@ function NextReferenceBase(stack, schema) {
|
|
|
14920
15017
|
return isRetrieved || useResourceBaseForReference ? NextResourceBase(stack, schema) : NextLexicalBase(stack, schema);
|
|
14921
15018
|
}
|
|
14922
15019
|
function NextLexicalSchema(stack, schema) {
|
|
14923
|
-
const
|
|
14924
|
-
if (
|
|
14925
|
-
return
|
|
15020
|
+
const entry2 = ResourceEntry(stack, schema);
|
|
15021
|
+
if (entry2)
|
|
15022
|
+
return entry2.root;
|
|
14926
15023
|
return IsId(schema) ? schema : stack.lexicalSchema;
|
|
14927
15024
|
}
|
|
14928
15025
|
function HasStackKeywords(stack, schema) {
|
|
@@ -17882,10 +17979,10 @@ function FromArray13(context, type, value) {
|
|
|
17882
17979
|
const repaired = maximum.map((value2) => FromType26(context, type.items, value2));
|
|
17883
17980
|
if (!IsUniqueItems(type) || IsUniqueItems(type) && !guard_exports.IsEqual(type.uniqueItems, true))
|
|
17884
17981
|
return repaired;
|
|
17885
|
-
const
|
|
17886
|
-
if (!Check2(context, type,
|
|
17982
|
+
const unique2 = MakeUnique(repaired);
|
|
17983
|
+
if (!Check2(context, type, unique2))
|
|
17887
17984
|
throw new RepairError(context, type, value, "Failed to repair Array due to uniqueItems constraint");
|
|
17888
|
-
return
|
|
17985
|
+
return unique2;
|
|
17889
17986
|
}
|
|
17890
17987
|
|
|
17891
17988
|
// ../../node_modules/.pnpm/typebox@1.3.34/node_modules/typebox/build/value/repair/from_enum.mjs
|
|
@@ -18029,14 +18126,14 @@ function FromUnknown2(context, type, value) {
|
|
|
18029
18126
|
|
|
18030
18127
|
// ../../node_modules/.pnpm/typebox@1.3.34/node_modules/typebox/build/value/repair/from_type.mjs
|
|
18031
18128
|
function AssertRepairableValue(context, type, value) {
|
|
18032
|
-
const
|
|
18033
|
-
if (
|
|
18129
|
+
const unsupported2 = globals_exports.IsDate(value) || globals_exports.IsMap(value) || globals_exports.IsSet(value) || globals_exports.IsTypeArray(value) || guard_exports.IsConstructor(value) || guard_exports.IsFunction(value);
|
|
18130
|
+
if (unsupported2) {
|
|
18034
18131
|
throw new RepairError(context, type, value, "Value is not repairable");
|
|
18035
18132
|
}
|
|
18036
18133
|
}
|
|
18037
18134
|
function AssertRepairableType(context, type, value) {
|
|
18038
|
-
const
|
|
18039
|
-
if (
|
|
18135
|
+
const unsupported2 = IsConstructor2(type) || IsFunction2(type) || IsNever(type);
|
|
18136
|
+
if (unsupported2) {
|
|
18040
18137
|
throw new RepairError(context, type, value, "Type is not repairable");
|
|
18041
18138
|
}
|
|
18042
18139
|
}
|
|
@@ -18093,16 +18190,34 @@ var import_yaml3 = __toESM(require_dist(), 1);
|
|
|
18093
18190
|
|
|
18094
18191
|
// ../core/dist/project/project.js
|
|
18095
18192
|
var FILTER = /^\s*([A-Za-z_][\w-]*)((?:\.[\w-]+)*)\s*(>=|<=|!=|>|<|=|\s+in\s+)\s*(.+?)\s*$/;
|
|
18096
|
-
function
|
|
18193
|
+
function tokenizeFilter(text) {
|
|
18097
18194
|
const m = FILTER.exec(text);
|
|
18098
|
-
if (!m?.[1] || !m[3] || m[4] === void 0)
|
|
18099
|
-
|
|
18100
|
-
}
|
|
18101
|
-
const question = m[1];
|
|
18195
|
+
if (!m?.[1] || !m[3] || m[4] === void 0)
|
|
18196
|
+
return void 0;
|
|
18102
18197
|
const op = m[3].trim();
|
|
18103
|
-
const field = m[2] ? m[2].slice(1) : defaultField(question, questions);
|
|
18104
18198
|
const raw = m[4];
|
|
18105
18199
|
const value = op === "in" ? raw.split(",").map((s) => s.trim()).filter(Boolean) : isNumeric(raw) ? Number(raw) : raw;
|
|
18200
|
+
return { name: m[1], path: m[2] ? m[2].slice(1) : "", op, value };
|
|
18201
|
+
}
|
|
18202
|
+
function tokenizeSort(text) {
|
|
18203
|
+
const m = /^\s*([A-Za-z_][\w-]*)((?:\.[\w-]+)*)(?::(asc|desc))?\s*$/.exec(text);
|
|
18204
|
+
if (!m?.[1])
|
|
18205
|
+
return void 0;
|
|
18206
|
+
return {
|
|
18207
|
+
name: m[1],
|
|
18208
|
+
path: m[2] ? m[2].slice(1) : "",
|
|
18209
|
+
direction: m[3] ?? "desc"
|
|
18210
|
+
};
|
|
18211
|
+
}
|
|
18212
|
+
function parseFilter(text, questions) {
|
|
18213
|
+
const t = tokenizeFilter(text);
|
|
18214
|
+
if (!t) {
|
|
18215
|
+
throw new DecisionsError("invalid-request", `Cannot parse --keep "${text}". Use <question>[.<field>]<op><value>, e.g. relevant>=0.7 or kind in fix,feature`);
|
|
18216
|
+
}
|
|
18217
|
+
const question = t.name;
|
|
18218
|
+
const op = t.op;
|
|
18219
|
+
const field = t.path || defaultField(question, questions);
|
|
18220
|
+
const value = t.value;
|
|
18106
18221
|
const filter = { question, field, op, value, source: text };
|
|
18107
18222
|
if (questions)
|
|
18108
18223
|
assertKnown(question, questions, text);
|
|
@@ -18112,14 +18227,14 @@ function parseFilter(text, questions) {
|
|
|
18112
18227
|
return filter;
|
|
18113
18228
|
}
|
|
18114
18229
|
function parseSort(text, questions) {
|
|
18115
|
-
const
|
|
18116
|
-
if (!
|
|
18230
|
+
const t = tokenizeSort(text);
|
|
18231
|
+
if (!t) {
|
|
18117
18232
|
throw new DecisionsError("invalid-request", `Cannot parse --sort "${text}". Use <question>[.<field>][:asc|desc]`);
|
|
18118
18233
|
}
|
|
18119
18234
|
if (questions)
|
|
18120
|
-
assertKnown(
|
|
18121
|
-
const field =
|
|
18122
|
-
return { question:
|
|
18235
|
+
assertKnown(t.name, questions, text);
|
|
18236
|
+
const field = t.path || sortField(t.name, questions);
|
|
18237
|
+
return { question: t.name, field, direction: t.direction };
|
|
18123
18238
|
}
|
|
18124
18239
|
function matches(answers, filter) {
|
|
18125
18240
|
const actual = read(answers[filter.question], filter.field);
|
|
@@ -18138,18 +18253,33 @@ function matches(answers, filter) {
|
|
|
18138
18253
|
}
|
|
18139
18254
|
}
|
|
18140
18255
|
}
|
|
18256
|
+
function verdictOf(answers, flat, keep, keepAny = [], sortQuestion) {
|
|
18257
|
+
const acted = [...keep.map((f) => f.question), ...sortQuestion ? [sortQuestion] : []];
|
|
18258
|
+
const all = acted.length === 0 && keepAny.length === 0;
|
|
18259
|
+
const blocking = flat.filter((q) => all || acted.includes(q));
|
|
18260
|
+
if (blocking.length > 0)
|
|
18261
|
+
return { verdict: "undecided", questions: [...blocking] };
|
|
18262
|
+
if (!keep.every((f) => matches(answers, f)))
|
|
18263
|
+
return { verdict: "dropped", questions: [] };
|
|
18264
|
+
if (keepAny.length === 0)
|
|
18265
|
+
return { verdict: "kept", questions: [] };
|
|
18266
|
+
const decided = keepAny.filter((f) => !flat.includes(f.question));
|
|
18267
|
+
if (decided.some((f) => matches(answers, f)))
|
|
18268
|
+
return { verdict: "kept", questions: [] };
|
|
18269
|
+
const open = [...new Set(keepAny.filter((f) => flat.includes(f.question)).map((f) => f.question))];
|
|
18270
|
+
return open.length > 0 ? { verdict: "undecided", questions: open } : { verdict: "dropped", questions: [] };
|
|
18271
|
+
}
|
|
18141
18272
|
function project(input) {
|
|
18142
18273
|
const keep = input.keep ?? [];
|
|
18143
|
-
const
|
|
18144
|
-
const relevant = acted.length > 0 ? new Set(acted) : void 0;
|
|
18274
|
+
const keepAny = input.keepAny ?? [];
|
|
18145
18275
|
const kept = [];
|
|
18146
18276
|
const undecided = [];
|
|
18147
18277
|
let dropped = 0;
|
|
18148
18278
|
for (const row2 of input.rows) {
|
|
18149
|
-
const
|
|
18150
|
-
if (
|
|
18151
|
-
undecided.push({ row: row2, questions:
|
|
18152
|
-
else if (
|
|
18279
|
+
const v = verdictOf(row2.answers, input.undecidedOf(row2), keep, keepAny, input.sort?.question);
|
|
18280
|
+
if (v.verdict === "undecided")
|
|
18281
|
+
undecided.push({ row: row2, questions: v.questions });
|
|
18282
|
+
else if (v.verdict === "kept")
|
|
18153
18283
|
kept.push(row2);
|
|
18154
18284
|
else
|
|
18155
18285
|
dropped += 1;
|
|
@@ -18532,21 +18662,21 @@ var Walker = class {
|
|
|
18532
18662
|
if (controller.aborted || signal && signal.aborted || maxFiles && paths.length > maxFiles) return;
|
|
18533
18663
|
const files = this.getArray(this.state.paths);
|
|
18534
18664
|
for (let i = 0; i < entries.length; ++i) {
|
|
18535
|
-
const
|
|
18536
|
-
if (
|
|
18537
|
-
const filename = this.joinPath(
|
|
18665
|
+
const entry2 = entries[i];
|
|
18666
|
+
if (entry2.isFile() || entry2.isSymbolicLink() && !resolveSymlinks$1 && !excludeSymlinks) {
|
|
18667
|
+
const filename = this.joinPath(entry2.name, directoryPath);
|
|
18538
18668
|
this.pushFile(filename, files, this.state.counts, filters);
|
|
18539
|
-
} else if (
|
|
18540
|
-
let path = joinDirectoryPath(
|
|
18541
|
-
if (exclude && exclude(
|
|
18669
|
+
} else if (entry2.isDirectory()) {
|
|
18670
|
+
let path = joinDirectoryPath(entry2.name, directoryPath, this.state.options.pathSeparator);
|
|
18671
|
+
if (exclude && exclude(entry2.name, path)) continue;
|
|
18542
18672
|
this.pushDirectory(path, paths, filters);
|
|
18543
18673
|
this.walkDirectory(this.state, path, path, depth - 1, this.walk);
|
|
18544
|
-
} else if (this.resolveSymlink &&
|
|
18545
|
-
let path = joinPathWithBasePath(
|
|
18674
|
+
} else if (this.resolveSymlink && entry2.isSymbolicLink()) {
|
|
18675
|
+
let path = joinPathWithBasePath(entry2.name, directoryPath);
|
|
18546
18676
|
this.resolveSymlink(path, this.state, (stat2, resolvedPath) => {
|
|
18547
18677
|
if (stat2.isDirectory()) {
|
|
18548
18678
|
resolvedPath = normalizePath(resolvedPath, this.state.options);
|
|
18549
|
-
if (exclude && exclude(
|
|
18679
|
+
if (exclude && exclude(entry2.name, useRealPaths ? resolvedPath : path + pathSeparator)) return;
|
|
18550
18680
|
this.walkDirectory(this.state, resolvedPath, useRealPaths ? resolvedPath : path + pathSeparator, depth - 1, this.walk);
|
|
18551
18681
|
} else {
|
|
18552
18682
|
resolvedPath = useRealPaths ? resolvedPath : path;
|
|
@@ -18980,6 +19110,26 @@ async function glob(globInput, options) {
|
|
|
18980
19110
|
return crawler ? formatPaths(await crawler.withPromise(), relative5) : [];
|
|
18981
19111
|
}
|
|
18982
19112
|
|
|
19113
|
+
// ../core/dist/sources/filter.js
|
|
19114
|
+
var import_picomatch3 = __toESM(require_picomatch2(), 1);
|
|
19115
|
+
function applyFilter(items, patterns = []) {
|
|
19116
|
+
if (patterns.length === 0)
|
|
19117
|
+
return { items: [...items], filtered: [] };
|
|
19118
|
+
const matchers = patterns.map((p) => ({ p, test: (0, import_picomatch3.default)(p, { dot: true }) }));
|
|
19119
|
+
const kept = [];
|
|
19120
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
19121
|
+
for (const item of items) {
|
|
19122
|
+
const paths = [item.path, item.realPath].filter((p) => p !== void 0);
|
|
19123
|
+
const hit = item.path === void 0 ? void 0 : matchers.find((m) => paths.some((p) => m.test(p)));
|
|
19124
|
+
if (hit && item.path !== void 0) {
|
|
19125
|
+
filtered.set(item.path, { path: item.path, reason: "filtered", detail: hit.p });
|
|
19126
|
+
} else {
|
|
19127
|
+
kept.push(item);
|
|
19128
|
+
}
|
|
19129
|
+
}
|
|
19130
|
+
return { items: kept, filtered: [...filtered.values()] };
|
|
19131
|
+
}
|
|
19132
|
+
|
|
18983
19133
|
// ../core/dist/sources/read.js
|
|
18984
19134
|
function resolvePath(cwd, path) {
|
|
18985
19135
|
const absolute = resolve4(cwd, path);
|
|
@@ -19094,12 +19244,15 @@ async function readGlob(patterns, options) {
|
|
|
19094
19244
|
});
|
|
19095
19245
|
matches2.sort();
|
|
19096
19246
|
const ignored = new Set(gitIgnored(options.cwd, matches2));
|
|
19097
|
-
const
|
|
19098
|
-
const
|
|
19247
|
+
const candidates = matches2.filter((m) => !ignored.has(m));
|
|
19248
|
+
const withheld = candidates.filter((m) => isExcluded(m, options.withhold));
|
|
19249
|
+
const unfiltered = applyFilter(candidates.filter((m) => !withheld.includes(m)).map((path) => ({ path })), options.filter);
|
|
19250
|
+
const result = readFiles([...withheld, ...unfiltered.items.map((m) => m.path)].sort(), options);
|
|
19099
19251
|
return {
|
|
19100
19252
|
documents: result.documents,
|
|
19101
19253
|
skipped: [
|
|
19102
19254
|
...[...ignored].map((path) => ({ path, reason: "gitignored" })),
|
|
19255
|
+
...unfiltered.filtered,
|
|
19103
19256
|
...result.skipped
|
|
19104
19257
|
]
|
|
19105
19258
|
};
|
|
@@ -19433,40 +19586,38 @@ function describeExpectation(expectation) {
|
|
|
19433
19586
|
}
|
|
19434
19587
|
|
|
19435
19588
|
// ../core/dist/spec/spec.js
|
|
19436
|
-
var
|
|
19589
|
+
var CLOSED = { additionalProperties: false };
|
|
19590
|
+
var Threshold = typebox_exports.Object({ value: typebox_exports.Number(), why: typebox_exports.String({ minLength: 1 }) }, CLOSED);
|
|
19437
19591
|
var SPEC_FORMAT = 1;
|
|
19438
|
-
var SpecSchema = typebox_exports.Object(
|
|
19439
|
-
|
|
19440
|
-
|
|
19441
|
-
|
|
19442
|
-
|
|
19443
|
-
|
|
19444
|
-
|
|
19445
|
-
|
|
19446
|
-
|
|
19447
|
-
|
|
19448
|
-
|
|
19449
|
-
|
|
19450
|
-
|
|
19451
|
-
|
|
19452
|
-
|
|
19453
|
-
|
|
19454
|
-
|
|
19455
|
-
|
|
19456
|
-
|
|
19457
|
-
|
|
19458
|
-
|
|
19459
|
-
|
|
19460
|
-
|
|
19461
|
-
|
|
19462
|
-
|
|
19463
|
-
|
|
19464
|
-
|
|
19465
|
-
|
|
19466
|
-
|
|
19467
|
-
// becoming no policy at all.
|
|
19468
|
-
{ additionalProperties: false }
|
|
19469
|
-
);
|
|
19592
|
+
var SpecSchema = typebox_exports.Object({
|
|
19593
|
+
/** Spec file format. Absent means 1; a newer one is refused, not guessed. */
|
|
19594
|
+
version: typebox_exports.Optional(typebox_exports.Integer({ minimum: 1 })),
|
|
19595
|
+
description: typebox_exports.String({ minLength: 1 }),
|
|
19596
|
+
questions: typebox_exports.Record(typebox_exports.String(), typebox_exports.Unknown()),
|
|
19597
|
+
keep: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
19598
|
+
/** Kept when any of these matches, as well as every `keep`. */
|
|
19599
|
+
keepAny: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
19600
|
+
sort: typebox_exports.Optional(typebox_exports.String()),
|
|
19601
|
+
policy: typebox_exports.Optional(typebox_exports.Object({ thresholds: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), Threshold)) }, CLOSED)),
|
|
19602
|
+
source: typebox_exports.Optional(typebox_exports.Object({
|
|
19603
|
+
glob: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
19604
|
+
file: typebox_exports.Optional(typebox_exports.String()),
|
|
19605
|
+
jsonl: typebox_exports.Optional(typebox_exports.String()),
|
|
19606
|
+
diff: typebox_exports.Optional(typebox_exports.String()),
|
|
19607
|
+
split: typebox_exports.Optional(typebox_exports.String())
|
|
19608
|
+
}, CLOSED)),
|
|
19609
|
+
examples: typebox_exports.Optional(typebox_exports.Array(typebox_exports.Object({
|
|
19610
|
+
id: typebox_exports.String({ minLength: 1 }),
|
|
19611
|
+
/** The text (or a JSON value, sent as its JSON text) to judge. Give this or `file`. */
|
|
19612
|
+
state: typebox_exports.Optional(typebox_exports.Unknown()),
|
|
19613
|
+
/** A repo file to judge, `path` or `path:START-END`. Give this or `state`. */
|
|
19614
|
+
file: typebox_exports.Optional(typebox_exports.String()),
|
|
19615
|
+
expect: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), typebox_exports.Unknown()))
|
|
19616
|
+
}, CLOSED))),
|
|
19617
|
+
provenance: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), typebox_exports.Unknown())),
|
|
19618
|
+
/** Anything else a team wants to carry, ignored by the engine. */
|
|
19619
|
+
meta: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), typebox_exports.Unknown()))
|
|
19620
|
+
}, CLOSED);
|
|
19470
19621
|
var NAME = /^[a-z0-9][a-z0-9._-]*$/;
|
|
19471
19622
|
function parseSpec(text, file, origin) {
|
|
19472
19623
|
let raw;
|
|
@@ -19492,7 +19643,7 @@ function parseSpec(text, file, origin) {
|
|
|
19492
19643
|
throw new DecisionsError("invalid-request", `${file}: ${error.message}`, { file });
|
|
19493
19644
|
}
|
|
19494
19645
|
const questions = spec.questions;
|
|
19495
|
-
for (const text2 of spec.keep ?? [])
|
|
19646
|
+
for (const text2 of [...spec.keep ?? [], ...spec.keepAny ?? []])
|
|
19496
19647
|
wrap(file, () => parseFilter(text2, questions));
|
|
19497
19648
|
if (spec.sort)
|
|
19498
19649
|
wrap(file, () => parseSort(spec.sort, questions));
|
|
@@ -19595,11 +19746,11 @@ function listSpecs(dirs) {
|
|
|
19595
19746
|
for (const [origin, dir] of layers(dirs)) {
|
|
19596
19747
|
if (!existsSync4(dir))
|
|
19597
19748
|
continue;
|
|
19598
|
-
for (const
|
|
19599
|
-
if (!/\.(ya?ml|json)$/.test(
|
|
19749
|
+
for (const entry2 of readdirSync2(dir).sort()) {
|
|
19750
|
+
if (!/\.(ya?ml|json)$/.test(entry2))
|
|
19600
19751
|
continue;
|
|
19601
|
-
const file = join2(dir,
|
|
19602
|
-
const name = basename2(
|
|
19752
|
+
const file = join2(dir, entry2);
|
|
19753
|
+
const name = basename2(entry2, extname(entry2));
|
|
19603
19754
|
const shadowed = seen.has(name);
|
|
19604
19755
|
seen.add(name);
|
|
19605
19756
|
try {
|
|
@@ -19639,6 +19790,360 @@ function wrap(file, fn) {
|
|
|
19639
19790
|
}
|
|
19640
19791
|
}
|
|
19641
19792
|
|
|
19793
|
+
// ../core/dist/model/schema.js
|
|
19794
|
+
var QuestionSchema = typebox_exports.Union([
|
|
19795
|
+
typebox_exports.Object({
|
|
19796
|
+
type: typebox_exports.Literal("noul"),
|
|
19797
|
+
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19798
|
+
/** Optional `true`/`false` criteria that sharpen an ambiguous statement. */
|
|
19799
|
+
criteria: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), typebox_exports.String()))
|
|
19800
|
+
}, { additionalProperties: false }),
|
|
19801
|
+
typebox_exports.Object({
|
|
19802
|
+
type: typebox_exports.Literal("choice"),
|
|
19803
|
+
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19804
|
+
/** Option key to when it applies. At least two, including a way out. */
|
|
19805
|
+
criteria: typebox_exports.Record(typebox_exports.String(), typebox_exports.String(), { minProperties: 2 })
|
|
19806
|
+
}, { additionalProperties: false }),
|
|
19807
|
+
typebox_exports.Object({
|
|
19808
|
+
type: typebox_exports.Literal("score"),
|
|
19809
|
+
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19810
|
+
/** Levels, lowest first and 0-indexed. */
|
|
19811
|
+
criteria: typebox_exports.Array(typebox_exports.String(), { minItems: 2 })
|
|
19812
|
+
}, { additionalProperties: false })
|
|
19813
|
+
]);
|
|
19814
|
+
|
|
19815
|
+
// ../core/dist/opportunities/backlog.js
|
|
19816
|
+
import { createHash } from "node:crypto";
|
|
19817
|
+
import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync5, renameSync, rmSync, statSync as statSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
19818
|
+
import { dirname as dirname4, join as join3 } from "node:path";
|
|
19819
|
+
var BACKLOG_FORMAT = 1;
|
|
19820
|
+
var CLOSED2 = { additionalProperties: false };
|
|
19821
|
+
var Text = typebox_exports.String({ minLength: 1 });
|
|
19822
|
+
var Usd = typebox_exports.Number({ minimum: 0, maximum: 1e6 });
|
|
19823
|
+
var Status = typebox_exports.Union([
|
|
19824
|
+
typebox_exports.Literal("new"),
|
|
19825
|
+
typebox_exports.Literal("stale"),
|
|
19826
|
+
typebox_exports.Literal("adopted"),
|
|
19827
|
+
typebox_exports.Literal("rejected")
|
|
19828
|
+
]);
|
|
19829
|
+
var candidateFields = {
|
|
19830
|
+
/** `code`: a codebase (Mode A). `agents`: skills, hooks, agent configuration (Mode B). */
|
|
19831
|
+
mode: typebox_exports.Union([typebox_exports.Literal("code"), typebox_exports.Literal("agents")]),
|
|
19832
|
+
location: typebox_exports.Object({
|
|
19833
|
+
path: Text,
|
|
19834
|
+
lines: typebox_exports.Optional(typebox_exports.Object({ start: typebox_exports.Integer({ minimum: 1 }), end: typebox_exports.Integer({ minimum: 1 }) }, CLOSED2))
|
|
19835
|
+
}, CLOSED2),
|
|
19836
|
+
/** What does the job today, e.g. "gpt-4o call parsed to an enum". */
|
|
19837
|
+
mechanism: Text,
|
|
19838
|
+
shape: typebox_exports.Union([
|
|
19839
|
+
typebox_exports.Literal("single"),
|
|
19840
|
+
typebox_exports.Literal("fanout"),
|
|
19841
|
+
typebox_exports.Literal("cascade"),
|
|
19842
|
+
typebox_exports.Literal("pairwise")
|
|
19843
|
+
]),
|
|
19844
|
+
/**
|
|
19845
|
+
* The text that triggered it, taken verbatim from the screened item. The id is
|
|
19846
|
+
* derived from it, so it must be copied, not paraphrased.
|
|
19847
|
+
*/
|
|
19848
|
+
evidence: typebox_exports.String({ pattern: "\\S" }),
|
|
19849
|
+
/** A draft question set that would replace the mechanism. */
|
|
19850
|
+
questions: typebox_exports.Record(typebox_exports.String(), QuestionSchema, { minProperties: 1 }),
|
|
19851
|
+
/**
|
|
19852
|
+
* What replacing the mechanism would mainly buy. `cost`: cheaper calls.
|
|
19853
|
+
* `quality`: better or more consistent verdicts (a regex or heuristic that
|
|
19854
|
+
* costs nothing today, so its `savingUsd` can be negative). `latency`:
|
|
19855
|
+
* faster answers. Rank within a benefit, not across them.
|
|
19856
|
+
*/
|
|
19857
|
+
benefit: typebox_exports.Union([typebox_exports.Literal("cost"), typebox_exports.Literal("quality"), typebox_exports.Literal("latency")]),
|
|
19858
|
+
/** The inputs of the projected saving. The saving itself is computed here. */
|
|
19859
|
+
projected: typebox_exports.Object({
|
|
19860
|
+
volume: typebox_exports.Number({ minimum: 0, maximum: 1e12 }),
|
|
19861
|
+
/** What `volume` counts per, e.g. "day", "PR", "run". */
|
|
19862
|
+
per: Text,
|
|
19863
|
+
currentCostPerItemUsd: Usd,
|
|
19864
|
+
decisionCostPerItemUsd: Usd,
|
|
19865
|
+
/** Where the figures came from. */
|
|
19866
|
+
note: typebox_exports.Optional(typebox_exports.String())
|
|
19867
|
+
}, CLOSED2),
|
|
19868
|
+
risk: typebox_exports.Object({
|
|
19869
|
+
level: typebox_exports.Union([typebox_exports.Literal("low"), typebox_exports.Literal("medium"), typebox_exports.Literal("high")]),
|
|
19870
|
+
/** What would go wrong if the replacement erred. */
|
|
19871
|
+
note: Text
|
|
19872
|
+
}, CLOSED2),
|
|
19873
|
+
/** The next step, e.g. "save the spec with design, then adopt". */
|
|
19874
|
+
next: Text,
|
|
19875
|
+
/** Defaults to `new`. `rejected` needs a `statusReason`. */
|
|
19876
|
+
status: typebox_exports.Optional(Status),
|
|
19877
|
+
statusReason: typebox_exports.Optional(Text),
|
|
19878
|
+
/** Which sweep produced it, and whether its screening answers were live or replayed. */
|
|
19879
|
+
source: typebox_exports.Object({
|
|
19880
|
+
sweep: Text,
|
|
19881
|
+
answers: typebox_exports.Union([typebox_exports.Literal("live"), typebox_exports.Literal("replay")])
|
|
19882
|
+
}, CLOSED2)
|
|
19883
|
+
};
|
|
19884
|
+
var CandidateSchema = typebox_exports.Object(candidateFields, CLOSED2);
|
|
19885
|
+
var OpportunitySchema = typebox_exports.Object({
|
|
19886
|
+
id: typebox_exports.String({ pattern: "^op-[0-9a-f]{12}$" }),
|
|
19887
|
+
...candidateFields,
|
|
19888
|
+
status: Status,
|
|
19889
|
+
projected: typebox_exports.Object({
|
|
19890
|
+
...candidateFields.projected.properties,
|
|
19891
|
+
basis: typebox_exports.Literal("projected"),
|
|
19892
|
+
/** volume × (current − decision), per `per`. Negative means it would cost more. */
|
|
19893
|
+
savingUsd: typebox_exports.Number()
|
|
19894
|
+
}, CLOSED2),
|
|
19895
|
+
firstSeenAt: typebox_exports.String(),
|
|
19896
|
+
seenAt: typebox_exports.String()
|
|
19897
|
+
}, CLOSED2);
|
|
19898
|
+
var BacklogSchema = typebox_exports.Object({
|
|
19899
|
+
version: typebox_exports.Literal(BACKLOG_FORMAT),
|
|
19900
|
+
opportunities: typebox_exports.Array(OpportunitySchema)
|
|
19901
|
+
}, CLOSED2);
|
|
19902
|
+
function backlogPath(repoRoot) {
|
|
19903
|
+
return join3(stateDir(repoRoot), "opportunities.json");
|
|
19904
|
+
}
|
|
19905
|
+
function opportunityId(mode, evidence) {
|
|
19906
|
+
const normal = evidence.replace(/\s+/g, " ").trim();
|
|
19907
|
+
return `op-${createHash("sha256").update(`${mode}
|
|
19908
|
+
${normal}`).digest("hex").slice(0, 12)}`;
|
|
19909
|
+
}
|
|
19910
|
+
function projectedSaving(p) {
|
|
19911
|
+
return round(p.volume * (p.currentCostPerItemUsd - p.decisionCostPerItemUsd));
|
|
19912
|
+
}
|
|
19913
|
+
function backlogProblems(value) {
|
|
19914
|
+
const problems = [...value_exports.Errors(BacklogSchema, value)].map((e) => `${e.instancePath || "/"} ${e.message}`);
|
|
19915
|
+
if (problems.length)
|
|
19916
|
+
return problems;
|
|
19917
|
+
const backlog = value;
|
|
19918
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19919
|
+
backlog.opportunities.forEach((o, i) => {
|
|
19920
|
+
if (seen.has(o.id))
|
|
19921
|
+
problems.push(`/opportunities/${i}/id ${o.id} appears twice`);
|
|
19922
|
+
seen.add(o.id);
|
|
19923
|
+
if (o.id !== opportunityId(o.mode, o.evidence)) {
|
|
19924
|
+
problems.push(`/opportunities/${i}/id does not match its mode and evidence`);
|
|
19925
|
+
}
|
|
19926
|
+
if (o.projected.savingUsd !== projectedSaving(o.projected)) {
|
|
19927
|
+
problems.push(`/opportunities/${i}/projected/savingUsd does not match its inputs`);
|
|
19928
|
+
}
|
|
19929
|
+
problems.push(...candidateProblems(o, `/opportunities/${i}`));
|
|
19930
|
+
});
|
|
19931
|
+
return problems;
|
|
19932
|
+
}
|
|
19933
|
+
function candidateProblems(c, at) {
|
|
19934
|
+
const problems = [];
|
|
19935
|
+
if (c.status === "rejected" && !c.statusReason) {
|
|
19936
|
+
problems.push(`${at}/statusReason is required when status is rejected`);
|
|
19937
|
+
}
|
|
19938
|
+
if (!Number.isFinite(projectedSaving(c.projected))) {
|
|
19939
|
+
problems.push(`${at}/projected gives a saving that isn't a finite number`);
|
|
19940
|
+
}
|
|
19941
|
+
if (c.location.lines && c.location.lines.end < c.location.lines.start) {
|
|
19942
|
+
problems.push(`${at}/location/lines end is before start`);
|
|
19943
|
+
}
|
|
19944
|
+
try {
|
|
19945
|
+
assertQuestionSet(c.questions);
|
|
19946
|
+
} catch (error) {
|
|
19947
|
+
problems.push(`${at}/questions ${error.message}`);
|
|
19948
|
+
}
|
|
19949
|
+
return problems;
|
|
19950
|
+
}
|
|
19951
|
+
function readBacklog(file) {
|
|
19952
|
+
if (!existsSync5(file))
|
|
19953
|
+
return { version: BACKLOG_FORMAT, opportunities: [] };
|
|
19954
|
+
let text;
|
|
19955
|
+
try {
|
|
19956
|
+
text = readFileSync5(file, "utf8");
|
|
19957
|
+
} catch (error) {
|
|
19958
|
+
throw malformed2(file, [`cannot read it: ${error.message}`]);
|
|
19959
|
+
}
|
|
19960
|
+
let value;
|
|
19961
|
+
try {
|
|
19962
|
+
value = JSON.parse(text);
|
|
19963
|
+
} catch (error) {
|
|
19964
|
+
throw malformed2(file, [`not JSON: ${error.message}`]);
|
|
19965
|
+
}
|
|
19966
|
+
const problems = backlogProblems(value);
|
|
19967
|
+
if (problems.length)
|
|
19968
|
+
throw malformed2(file, problems);
|
|
19969
|
+
return value;
|
|
19970
|
+
}
|
|
19971
|
+
function malformed2(file, problems) {
|
|
19972
|
+
return new DecisionsError("invalid-request", `${file} is not a valid backlog (it was left as it is):
|
|
19973
|
+
${problems.join("\n ")}`, { file, problems });
|
|
19974
|
+
}
|
|
19975
|
+
var LOCK_WAIT_MS = 5e3;
|
|
19976
|
+
var LOCK_STALE_MS = 3e4;
|
|
19977
|
+
function withBacklogLock(file, body) {
|
|
19978
|
+
mkdirSync2(dirname4(file), { recursive: true });
|
|
19979
|
+
const lock = `${file}.lock`;
|
|
19980
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
19981
|
+
for (; ; ) {
|
|
19982
|
+
try {
|
|
19983
|
+
closeSync(openSync(lock, "wx"));
|
|
19984
|
+
break;
|
|
19985
|
+
} catch (error) {
|
|
19986
|
+
if (error.code !== "EEXIST")
|
|
19987
|
+
throw error;
|
|
19988
|
+
try {
|
|
19989
|
+
if (Date.now() - statSync4(lock).mtimeMs > LOCK_STALE_MS)
|
|
19990
|
+
rmSync(lock, { force: true });
|
|
19991
|
+
} catch {
|
|
19992
|
+
}
|
|
19993
|
+
if (Date.now() > deadline) {
|
|
19994
|
+
throw new DecisionsError("invalid-request", `${file} is locked by another \`decide opportunities add\` (${lock}). Retry, or delete the lock file if no add is running.`, { file, lock });
|
|
19995
|
+
}
|
|
19996
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
19997
|
+
}
|
|
19998
|
+
}
|
|
19999
|
+
try {
|
|
20000
|
+
return body();
|
|
20001
|
+
} finally {
|
|
20002
|
+
rmSync(lock, { force: true });
|
|
20003
|
+
}
|
|
20004
|
+
}
|
|
20005
|
+
function writeBacklog(file, backlog) {
|
|
20006
|
+
mkdirSync2(dirname4(file), { recursive: true });
|
|
20007
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
20008
|
+
writeFileSync2(tmp, `${JSON.stringify(backlog, null, 2)}
|
|
20009
|
+
`);
|
|
20010
|
+
renameSync(tmp, file);
|
|
20011
|
+
}
|
|
20012
|
+
function mergeCandidates(backlog, candidates, now) {
|
|
20013
|
+
const problems = candidates.flatMap((c, i) => candidateProblems(c, `/candidates/${i}`));
|
|
20014
|
+
if (problems.length) {
|
|
20015
|
+
throw new DecisionsError("invalid-request", `Invalid candidates:
|
|
20016
|
+
${problems.join("\n ")}`, {
|
|
20017
|
+
problems
|
|
20018
|
+
});
|
|
20019
|
+
}
|
|
20020
|
+
const byId = new Map(backlog.opportunities.map((o) => [o.id, o]));
|
|
20021
|
+
const existed = new Set(byId.keys());
|
|
20022
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20023
|
+
for (const { statusReason: reason, ...c } of candidates) {
|
|
20024
|
+
const id = opportunityId(c.mode, c.evidence);
|
|
20025
|
+
seen.add(id);
|
|
20026
|
+
const before = byId.get(id);
|
|
20027
|
+
const statusReason = reason ?? (c.status ? void 0 : before?.statusReason);
|
|
20028
|
+
byId.set(id, {
|
|
20029
|
+
id,
|
|
20030
|
+
...c,
|
|
20031
|
+
// Seeing the evidence again disproves "stale"; any other status stands.
|
|
20032
|
+
status: c.status ?? (before?.status === "stale" ? "new" : before?.status) ?? "new",
|
|
20033
|
+
...statusReason ? { statusReason } : {},
|
|
20034
|
+
projected: { ...c.projected, basis: "projected", savingUsd: projectedSaving(c.projected) },
|
|
20035
|
+
firstSeenAt: before?.firstSeenAt ?? now,
|
|
20036
|
+
seenAt: now
|
|
20037
|
+
});
|
|
20038
|
+
}
|
|
20039
|
+
const added = [...seen].filter((id) => !existed.has(id));
|
|
20040
|
+
const updated = [...seen].filter((id) => existed.has(id));
|
|
20041
|
+
const key = (o) => `${o.mode}
|
|
20042
|
+
${o.location.path}
|
|
20043
|
+
${o.source.sweep}`;
|
|
20044
|
+
const covered = new Set(candidates.map(key));
|
|
20045
|
+
const staled = [];
|
|
20046
|
+
for (const o of byId.values()) {
|
|
20047
|
+
if (o.status === "new" && !seen.has(o.id) && covered.has(key(o))) {
|
|
20048
|
+
byId.set(o.id, { ...o, status: "stale" });
|
|
20049
|
+
staled.push(o.id);
|
|
20050
|
+
}
|
|
20051
|
+
}
|
|
20052
|
+
return {
|
|
20053
|
+
backlog: { version: BACKLOG_FORMAT, opportunities: [...byId.values()] },
|
|
20054
|
+
added,
|
|
20055
|
+
updated,
|
|
20056
|
+
staled
|
|
20057
|
+
};
|
|
20058
|
+
}
|
|
20059
|
+
var FIELDS = {
|
|
20060
|
+
id: {},
|
|
20061
|
+
mode: {},
|
|
20062
|
+
benefit: {},
|
|
20063
|
+
status: {},
|
|
20064
|
+
statusReason: {},
|
|
20065
|
+
shape: {},
|
|
20066
|
+
mechanism: {},
|
|
20067
|
+
evidence: {},
|
|
20068
|
+
next: {},
|
|
20069
|
+
firstSeenAt: {},
|
|
20070
|
+
seenAt: {},
|
|
20071
|
+
risk: { default: "level", subs: ["level", "note"] },
|
|
20072
|
+
projected: {
|
|
20073
|
+
default: "savingUsd",
|
|
20074
|
+
subs: [
|
|
20075
|
+
"savingUsd",
|
|
20076
|
+
"volume",
|
|
20077
|
+
"per",
|
|
20078
|
+
"currentCostPerItemUsd",
|
|
20079
|
+
"decisionCostPerItemUsd",
|
|
20080
|
+
"basis",
|
|
20081
|
+
"note"
|
|
20082
|
+
]
|
|
20083
|
+
},
|
|
20084
|
+
location: { default: "path", subs: ["path", "lines.start", "lines.end"] },
|
|
20085
|
+
source: { default: "sweep", subs: ["sweep", "answers"] }
|
|
20086
|
+
};
|
|
20087
|
+
var ENTRY_FIELDS = [...Object.keys(OpportunitySchema.properties)];
|
|
20088
|
+
function parseRecordFilter(text) {
|
|
20089
|
+
const t = tokenizeFilter(text);
|
|
20090
|
+
if (!t) {
|
|
20091
|
+
throw new DecisionsError("invalid-request", `Cannot parse --keep "${text}". Use <field>[.<sub>]<op><value>, e.g. status=new or projected>=0.01`);
|
|
20092
|
+
}
|
|
20093
|
+
const field = recordField(t.name, t.path, text);
|
|
20094
|
+
if ((t.op === ">" || t.op === ">=" || t.op === "<" || t.op === "<=") && typeof t.value !== "number") {
|
|
20095
|
+
throw new DecisionsError("invalid-request", `--keep "${text}": ${t.op} needs a number`);
|
|
20096
|
+
}
|
|
20097
|
+
return { field, op: t.op, value: t.value, source: text };
|
|
20098
|
+
}
|
|
20099
|
+
function parseRecordSort(text) {
|
|
20100
|
+
const t = tokenizeSort(text);
|
|
20101
|
+
if (!t) {
|
|
20102
|
+
throw new DecisionsError("invalid-request", `Cannot parse --sort "${text}". Use <field>[.<sub>][:asc|desc]`);
|
|
20103
|
+
}
|
|
20104
|
+
return { field: recordField(t.name, t.path, text), direction: t.direction };
|
|
20105
|
+
}
|
|
20106
|
+
function recordField(name, path, text) {
|
|
20107
|
+
const spec = Object.hasOwn(FIELDS, name) ? FIELDS[name] : void 0;
|
|
20108
|
+
if (!spec) {
|
|
20109
|
+
throw new DecisionsError("invalid-request", `"${text}": no field "${name}". Fields: ${Object.keys(FIELDS).join(", ")}`);
|
|
20110
|
+
}
|
|
20111
|
+
if (path && !spec.subs?.includes(path)) {
|
|
20112
|
+
throw new DecisionsError("invalid-request", spec.subs ? `"${text}": ${name} has no "${path}". Use one of: ${spec.subs.join(", ")}` : `"${text}": ${name} has no sub-fields`);
|
|
20113
|
+
}
|
|
20114
|
+
const sub = path || spec.default;
|
|
20115
|
+
return sub ? `${name}.${sub}` : name;
|
|
20116
|
+
}
|
|
20117
|
+
function readField(o, field) {
|
|
20118
|
+
let value = o;
|
|
20119
|
+
for (const key of field.split(".")) {
|
|
20120
|
+
if (value === null || typeof value !== "object")
|
|
20121
|
+
return void 0;
|
|
20122
|
+
value = value[key];
|
|
20123
|
+
}
|
|
20124
|
+
return value;
|
|
20125
|
+
}
|
|
20126
|
+
function recordMatches(o, f) {
|
|
20127
|
+
const actual = readField(o, f.field);
|
|
20128
|
+
switch (f.op) {
|
|
20129
|
+
case "in":
|
|
20130
|
+
return f.value.includes(String(actual));
|
|
20131
|
+
case "=":
|
|
20132
|
+
return String(actual) === String(f.value);
|
|
20133
|
+
case "!=":
|
|
20134
|
+
return String(actual) !== String(f.value);
|
|
20135
|
+
default: {
|
|
20136
|
+
if (typeof actual !== "number")
|
|
20137
|
+
return false;
|
|
20138
|
+
const v = f.value;
|
|
20139
|
+
return f.op === ">=" ? actual >= v : f.op === ">" ? actual > v : f.op === "<=" ? actual <= v : actual < v;
|
|
20140
|
+
}
|
|
20141
|
+
}
|
|
20142
|
+
}
|
|
20143
|
+
function round(n) {
|
|
20144
|
+
return Math.round(n * 1e9) / 1e9;
|
|
20145
|
+
}
|
|
20146
|
+
|
|
19642
20147
|
// ../core/dist/tools/schemas.js
|
|
19643
20148
|
var Source = typebox_exports.Union([
|
|
19644
20149
|
typebox_exports.Object({
|
|
@@ -19661,26 +20166,6 @@ var Source = typebox_exports.Union([
|
|
|
19661
20166
|
paths: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String()))
|
|
19662
20167
|
})
|
|
19663
20168
|
]);
|
|
19664
|
-
var Question = typebox_exports.Union([
|
|
19665
|
-
typebox_exports.Object({
|
|
19666
|
-
type: typebox_exports.Literal("noul"),
|
|
19667
|
-
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19668
|
-
/** Optional `true`/`false` criteria that sharpen an ambiguous statement. */
|
|
19669
|
-
criteria: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), typebox_exports.String()))
|
|
19670
|
-
}, { additionalProperties: false }),
|
|
19671
|
-
typebox_exports.Object({
|
|
19672
|
-
type: typebox_exports.Literal("choice"),
|
|
19673
|
-
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19674
|
-
/** Option key to when it applies. At least two, including a way out. */
|
|
19675
|
-
criteria: typebox_exports.Record(typebox_exports.String(), typebox_exports.String(), { minProperties: 2 })
|
|
19676
|
-
}, { additionalProperties: false }),
|
|
19677
|
-
typebox_exports.Object({
|
|
19678
|
-
type: typebox_exports.Literal("score"),
|
|
19679
|
-
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19680
|
-
/** Levels, lowest first and 0-indexed. */
|
|
19681
|
-
criteria: typebox_exports.Array(typebox_exports.String(), { minItems: 2 })
|
|
19682
|
-
}, { additionalProperties: false })
|
|
19683
|
-
]);
|
|
19684
20169
|
var Mode = typebox_exports.Union([
|
|
19685
20170
|
typebox_exports.Literal("auto"),
|
|
19686
20171
|
typebox_exports.Literal("live"),
|
|
@@ -19691,13 +20176,20 @@ var Common = {
|
|
|
19691
20176
|
/** A saved spec: name (repo → user → bundled) or path. */
|
|
19692
20177
|
spec: typebox_exports.Optional(typebox_exports.String()),
|
|
19693
20178
|
/** An inline question set; mutually exclusive with `spec`. */
|
|
19694
|
-
questions: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(),
|
|
20179
|
+
questions: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), QuestionSchema)),
|
|
19695
20180
|
/** Where the content comes from. Defaults to the spec's `source`. */
|
|
19696
20181
|
sources: typebox_exports.Optional(typebox_exports.Array(Source)),
|
|
20182
|
+
/**
|
|
20183
|
+
* Leave out paths matching these globs (repo-relative, like `glob`). A per-call
|
|
20184
|
+
* choice, reported as `filtered`; egress excludes still apply on top.
|
|
20185
|
+
*/
|
|
20186
|
+
exclude: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String({ minLength: 1 }))),
|
|
19697
20187
|
/** `file` | `hunk` | `row` | `lines:N[/overlap]`. */
|
|
19698
20188
|
split: typebox_exports.Optional(typebox_exports.String()),
|
|
19699
20189
|
/** Filters, ANDed: `relevant>=0.7`, `kind in fix,feature`. */
|
|
19700
20190
|
keep: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
20191
|
+
/** Kept when any of these matches (and every `keep` does): `a>=0.3`, `b>=0.3`. */
|
|
20192
|
+
keepAny: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
19701
20193
|
mode: typebox_exports.Optional(Mode),
|
|
19702
20194
|
model: typebox_exports.Optional(typebox_exports.String()),
|
|
19703
20195
|
/**
|
|
@@ -19735,6 +20227,24 @@ var SpecCheckInput = typebox_exports.Object({
|
|
|
19735
20227
|
/** Proceed past the spend guard. */
|
|
19736
20228
|
confirm: typebox_exports.Optional(typebox_exports.Boolean())
|
|
19737
20229
|
}, strict);
|
|
20230
|
+
var SpecLintInput = typebox_exports.Object({
|
|
20231
|
+
/** Spec name (repo → user → bundled) or path. Omitted: every spec in reach. */
|
|
20232
|
+
spec: typebox_exports.Optional(typebox_exports.String({ minLength: 1 }))
|
|
20233
|
+
}, strict);
|
|
20234
|
+
var OpportunitiesAddInput = typebox_exports.Object({
|
|
20235
|
+
/** Candidates to merge into `.system1/opportunities.json`. */
|
|
20236
|
+
candidates: typebox_exports.Array(CandidateSchema, { minItems: 1 })
|
|
20237
|
+
}, strict);
|
|
20238
|
+
var OpportunitiesListInput = typebox_exports.Object({
|
|
20239
|
+
/** Record filters, ANDed: `status=new`, `risk in medium,high`, `projected>=0.01`. */
|
|
20240
|
+
keep: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
20241
|
+
/** `projected:desc` (the default), `seenAt:asc`, … */
|
|
20242
|
+
sort: typebox_exports.Optional(typebox_exports.String()),
|
|
20243
|
+
limit: typebox_exports.Optional(typebox_exports.Integer({ minimum: 0 })),
|
|
20244
|
+
/** Top-level fields to include per entry; `id` always is. */
|
|
20245
|
+
fields: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String()))
|
|
20246
|
+
}, strict);
|
|
20247
|
+
var OpportunitiesCheckInput = typebox_exports.Object({}, strict);
|
|
19738
20248
|
var RouteInput = typebox_exports.Object({
|
|
19739
20249
|
/** The user's prompt, as the harness hook received it. */
|
|
19740
20250
|
prompt: typebox_exports.String()
|
|
@@ -19744,11 +20254,15 @@ var TOOL_SCHEMAS = {
|
|
|
19744
20254
|
many: ManyInput,
|
|
19745
20255
|
usage: UsageInput,
|
|
19746
20256
|
"spec-check": SpecCheckInput,
|
|
20257
|
+
"spec-lint": SpecLintInput,
|
|
20258
|
+
"opportunities-add": OpportunitiesAddInput,
|
|
20259
|
+
"opportunities-list": OpportunitiesListInput,
|
|
20260
|
+
"opportunities-check": OpportunitiesCheckInput,
|
|
19747
20261
|
route: RouteInput
|
|
19748
20262
|
};
|
|
19749
20263
|
|
|
19750
20264
|
// ../core/dist/tools/context.js
|
|
19751
|
-
import { isAbsolute as isAbsolute4, join as
|
|
20265
|
+
import { isAbsolute as isAbsolute4, join as join5, relative as relative4 } from "node:path";
|
|
19752
20266
|
|
|
19753
20267
|
// ../core/dist/egress/scrub.js
|
|
19754
20268
|
var SECRET_NAME = "[A-Za-z0-9_.-]*(?:api[_-]?key|secret|token|passw(?:or)?d|pwd|credential|private[_-]?key|auth)[A-Za-z0-9_.-]*";
|
|
@@ -19768,6 +20282,13 @@ var RULES = [
|
|
|
19768
20282
|
{ kind: "slack-token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
19769
20283
|
{ kind: "stripe-key", pattern: /\b[rs]k_(?:live|test)_[A-Za-z0-9]{16,}\b/g },
|
|
19770
20284
|
{ kind: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
20285
|
+
// Service tokens with a known prefix and separator, whatever they are
|
|
20286
|
+
// assigned to (MCP server env blocks name them freely: NOTION_INTEGRATION,
|
|
20287
|
+
// HONEYCOMB_WRITEKEY).
|
|
20288
|
+
{
|
|
20289
|
+
kind: "service-token",
|
|
20290
|
+
pattern: /\b(?:secret_|ntn_|glpat-|npm_|pypi-|shpat_|shpss_|dop_v1_|hcaik_|hcxik_|sntrys_|lin_api_|figd_|xapp-)[A-Za-z0-9_]{20,}\b/g
|
|
20291
|
+
},
|
|
19771
20292
|
{ kind: "jwt", pattern: /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g },
|
|
19772
20293
|
{ kind: "url-credentials", pattern: /(?<=\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s@/]{3,}(?=@)/gi },
|
|
19773
20294
|
// NAME = "literal": a quoted value of 8+ chars assigned to a secret-sounding name.
|
|
@@ -19781,6 +20302,15 @@ var RULES = [
|
|
|
19781
20302
|
{
|
|
19782
20303
|
kind: "assigned-secret",
|
|
19783
20304
|
pattern: new RegExp(`(?<=\\b${SECRET_NAME}\\s*[:=]\\s*)(?!\\$\\{|<|\\[REDACTED)(?=[A-Za-z_\\-+/=~]*\\d)[A-Za-z0-9_\\-+/=~]{8,}(?![\\w(.])`, "gi")
|
|
20305
|
+
},
|
|
20306
|
+
// ENV_NAME: "value" in upper-case environment style, ending in a credential
|
|
20307
|
+
// word (API_WRITEKEY, SERVICE_PAT). Case-sensitive, and the value must look
|
|
20308
|
+
// like a credential (12+ characters, letters and digits, no dots or colons),
|
|
20309
|
+
// so UI constants (`PROBE_KEY = "pulse:probe"`) and code are left alone.
|
|
20310
|
+
// Last, so a value an earlier rule already caught keeps that rule's name.
|
|
20311
|
+
{
|
|
20312
|
+
kind: "env-secret",
|
|
20313
|
+
pattern: /(?<=\b[A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASS|PAT|CREDENTIALS?)["']?\s*[:=]\s*["']?)(?!\$\{|<|\[REDACTED)(?=[A-Za-z0-9_\-+/=~]*\d)(?=[A-Za-z0-9_\-+/=~]*[A-Za-z])[A-Za-z0-9_\-+/=~]{12,}(?=["'\s,}]|$)/gm
|
|
19784
20314
|
}
|
|
19785
20315
|
];
|
|
19786
20316
|
function scrubText(text, counts2 = {}) {
|
|
@@ -19838,13 +20368,13 @@ function assertStateFits(id, state, questions, profile) {
|
|
|
19838
20368
|
}
|
|
19839
20369
|
|
|
19840
20370
|
// ../core/dist/fixtures/store.js
|
|
19841
|
-
import { createHash } from "node:crypto";
|
|
19842
|
-
import { existsSync as
|
|
19843
|
-
import { dirname as
|
|
20371
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
20372
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
20373
|
+
import { dirname as dirname5, join as join4 } from "node:path";
|
|
19844
20374
|
var FIXTURE_VERSION = 1;
|
|
19845
20375
|
function fixtureKey(request) {
|
|
19846
20376
|
const { model, state, questions } = request;
|
|
19847
|
-
return
|
|
20377
|
+
return createHash2("sha256").update(canonicalJson({ model, state, questions })).digest("hex");
|
|
19848
20378
|
}
|
|
19849
20379
|
function canonicalJson(value) {
|
|
19850
20380
|
return JSON.stringify(sortKeys(value));
|
|
@@ -19867,13 +20397,13 @@ var FixtureStore = class {
|
|
|
19867
20397
|
if (!NAMESPACE.test(namespace)) {
|
|
19868
20398
|
throw new Error(`Invalid fixture namespace "${namespace}" (lowercase letters, digits, . _ -)`);
|
|
19869
20399
|
}
|
|
19870
|
-
return
|
|
20400
|
+
return join4(this.dir, namespace, `${key}.json`);
|
|
19871
20401
|
}
|
|
19872
20402
|
lookup(namespace, request) {
|
|
19873
20403
|
const file = this.path(namespace, fixtureKey(request));
|
|
19874
|
-
if (!
|
|
20404
|
+
if (!existsSync6(file))
|
|
19875
20405
|
return void 0;
|
|
19876
|
-
const record = JSON.parse(
|
|
20406
|
+
const record = JSON.parse(readFileSync6(file, "utf8"));
|
|
19877
20407
|
return record.v === FIXTURE_VERSION ? record : void 0;
|
|
19878
20408
|
}
|
|
19879
20409
|
record(namespace, request, response, now = /* @__PURE__ */ new Date()) {
|
|
@@ -19886,11 +20416,11 @@ var FixtureStore = class {
|
|
|
19886
20416
|
response
|
|
19887
20417
|
};
|
|
19888
20418
|
const file = this.path(namespace, key);
|
|
19889
|
-
|
|
20419
|
+
mkdirSync3(dirname5(file), { recursive: true });
|
|
19890
20420
|
const temp = `${file}.${process.pid}.tmp`;
|
|
19891
|
-
|
|
20421
|
+
writeFileSync3(temp, `${JSON.stringify(record, null, 2)}
|
|
19892
20422
|
`);
|
|
19893
|
-
|
|
20423
|
+
renameSync2(temp, file);
|
|
19894
20424
|
return record;
|
|
19895
20425
|
}
|
|
19896
20426
|
};
|
|
@@ -19968,8 +20498,8 @@ function createDecider(options) {
|
|
|
19968
20498
|
}
|
|
19969
20499
|
|
|
19970
20500
|
// ../core/dist/run/spend.js
|
|
19971
|
-
import { appendFileSync, existsSync as
|
|
19972
|
-
import { dirname as
|
|
20501
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7 } from "node:fs";
|
|
20502
|
+
import { dirname as dirname6 } from "node:path";
|
|
19973
20503
|
function sumUsage(usages) {
|
|
19974
20504
|
const total = usages.reduce((sum, u) => ({
|
|
19975
20505
|
input_tokens: sum.input_tokens + (u?.input_tokens ?? 0),
|
|
@@ -19983,15 +20513,15 @@ var SpendLedger = class {
|
|
|
19983
20513
|
constructor(file) {
|
|
19984
20514
|
this.file = file;
|
|
19985
20515
|
}
|
|
19986
|
-
append(
|
|
19987
|
-
|
|
19988
|
-
appendFileSync(this.file, `${JSON.stringify(
|
|
20516
|
+
append(entry2) {
|
|
20517
|
+
mkdirSync4(dirname6(this.file), { recursive: true });
|
|
20518
|
+
appendFileSync(this.file, `${JSON.stringify(entry2)}
|
|
19989
20519
|
`);
|
|
19990
20520
|
}
|
|
19991
20521
|
entries() {
|
|
19992
|
-
if (!
|
|
20522
|
+
if (!existsSync7(this.file))
|
|
19993
20523
|
return [];
|
|
19994
|
-
return
|
|
20524
|
+
return readFileSync7(this.file, "utf8").split("\n").filter((line) => line.trim() !== "").flatMap((line) => {
|
|
19995
20525
|
try {
|
|
19996
20526
|
return [JSON.parse(line)];
|
|
19997
20527
|
} catch {
|
|
@@ -20056,15 +20586,33 @@ function resolveRequest(ctx, input) {
|
|
|
20056
20586
|
sources,
|
|
20057
20587
|
split: parseSplit(input.split ?? spec?.source?.split ?? "file"),
|
|
20058
20588
|
keep: keepText.map((k) => parseFilter(k, questions)),
|
|
20589
|
+
keepAny: (input.keepAny ?? spec?.keepAny ?? []).map((k) => parseFilter(k, questions)),
|
|
20059
20590
|
...sortText ? { sort: parseSort(sortText, questions) } : {},
|
|
20060
|
-
profile
|
|
20591
|
+
profile,
|
|
20592
|
+
exclude: (input.exclude ?? []).map((p) => rebasePattern(p, ctx.cwd, ctx.config.repoRoot))
|
|
20061
20593
|
};
|
|
20062
20594
|
}
|
|
20595
|
+
function rebasePattern(pattern, cwd, repoRoot) {
|
|
20596
|
+
if (pattern.startsWith("!")) {
|
|
20597
|
+
throw new DecisionsError("invalid-request", `--exclude "${pattern}": negation isn't supported; name what to leave out`);
|
|
20598
|
+
}
|
|
20599
|
+
if (isAbsolute4(pattern)) {
|
|
20600
|
+
const rel = relative4(repoRoot, pattern);
|
|
20601
|
+
if (rel.startsWith("..") || isAbsolute4(rel)) {
|
|
20602
|
+
throw new DecisionsError("invalid-request", `--exclude "${pattern}" is outside the repo (${repoRoot}), so it would match nothing`);
|
|
20603
|
+
}
|
|
20604
|
+
return rel;
|
|
20605
|
+
}
|
|
20606
|
+
const prefix = relative4(repoRoot, cwd);
|
|
20607
|
+
if (prefix === "" || prefix.startsWith("..") || isAbsolute4(prefix) || pattern.startsWith("**/"))
|
|
20608
|
+
return pattern;
|
|
20609
|
+
return join5(prefix, pattern);
|
|
20610
|
+
}
|
|
20063
20611
|
function rebase(source, cwd, repoRoot) {
|
|
20064
20612
|
const prefix = relative4(repoRoot, cwd);
|
|
20065
20613
|
if (prefix === "" || prefix.startsWith("..") || isAbsolute4(prefix))
|
|
20066
20614
|
return source;
|
|
20067
|
-
const move = (path) => isAbsolute4(path) ? path :
|
|
20615
|
+
const move = (path) => isAbsolute4(path) ? path : join5(prefix, path);
|
|
20068
20616
|
switch (source.kind) {
|
|
20069
20617
|
case "file":
|
|
20070
20618
|
return { ...source, path: move(source.path) };
|
|
@@ -20104,19 +20652,19 @@ function deciderFor(ctx, profile, mode = "auto") {
|
|
|
20104
20652
|
...ctx.fetch ? { fetch: ctx.fetch } : {}
|
|
20105
20653
|
})
|
|
20106
20654
|
} : {},
|
|
20107
|
-
fixtures: new FixtureStore(
|
|
20108
|
-
ledger: new SpendLedger(
|
|
20655
|
+
fixtures: new FixtureStore(join5(dir, "fixtures")),
|
|
20656
|
+
ledger: new SpendLedger(join5(dir, "usage.jsonl")),
|
|
20109
20657
|
mode: config.replay ? "replay" : mode,
|
|
20110
20658
|
...config.session ? { session: config.session } : {}
|
|
20111
20659
|
});
|
|
20112
20660
|
}
|
|
20113
20661
|
function ledgerFor(ctx) {
|
|
20114
|
-
return new SpendLedger(
|
|
20662
|
+
return new SpendLedger(join5(stateDir(ctx.config.repoRoot), "usage.jsonl"));
|
|
20115
20663
|
}
|
|
20116
20664
|
|
|
20117
20665
|
// ../core/dist/run/budget.js
|
|
20118
20666
|
function project2(profile, tokensPerCall) {
|
|
20119
|
-
const estimatedInputTokens = tokensPerCall.reduce((
|
|
20667
|
+
const estimatedInputTokens = tokensPerCall.reduce((sum, tokens) => sum + tokens + profile.callOverheadTokens, 0);
|
|
20120
20668
|
return {
|
|
20121
20669
|
basis: "projected",
|
|
20122
20670
|
calls: tokensPerCall.length,
|
|
@@ -20125,6 +20673,16 @@ function project2(profile, tokensPerCall) {
|
|
|
20125
20673
|
priceAsOf: profile.priceAsOf
|
|
20126
20674
|
};
|
|
20127
20675
|
}
|
|
20676
|
+
function combineProjections(profile, parts) {
|
|
20677
|
+
const estimatedInputTokens = parts.reduce((sum, p) => sum + p.estimatedInputTokens, 0);
|
|
20678
|
+
return {
|
|
20679
|
+
basis: "projected",
|
|
20680
|
+
calls: parts.reduce((sum, p) => sum + p.calls, 0),
|
|
20681
|
+
estimatedInputTokens,
|
|
20682
|
+
projectedUsd: estimatedInputTokens * profile.usdPerInputToken,
|
|
20683
|
+
priceAsOf: profile.priceAsOf
|
|
20684
|
+
};
|
|
20685
|
+
}
|
|
20128
20686
|
function checkBudget(projection, budget, confirmed) {
|
|
20129
20687
|
if (confirmed)
|
|
20130
20688
|
return;
|
|
@@ -20146,7 +20704,8 @@ async function prepare(input) {
|
|
|
20146
20704
|
const read2 = await readSources(input.sources, {
|
|
20147
20705
|
cwd: input.cwd,
|
|
20148
20706
|
...input.maxFileBytes ? { maxFileBytes: input.maxFileBytes } : {},
|
|
20149
|
-
...input.allowOutside ? { allowOutside: true } : {}
|
|
20707
|
+
...input.allowOutside ? { allowOutside: true } : {},
|
|
20708
|
+
...input.filter?.length ? { filter: input.filter, withhold: input.exclude ?? [] } : {}
|
|
20150
20709
|
});
|
|
20151
20710
|
const byKind = {};
|
|
20152
20711
|
let redactedSources = 0;
|
|
@@ -20157,7 +20716,8 @@ async function prepare(input) {
|
|
|
20157
20716
|
return found2;
|
|
20158
20717
|
};
|
|
20159
20718
|
const allowed = applyExcludes(read2.documents, input.exclude);
|
|
20160
|
-
const
|
|
20719
|
+
const chosen = applyFilter(allowed.items, input.filter);
|
|
20720
|
+
const documents = chosen.items.map((doc) => {
|
|
20161
20721
|
const counts2 = {};
|
|
20162
20722
|
const scrubbed = {
|
|
20163
20723
|
...doc,
|
|
@@ -20171,17 +20731,36 @@ async function prepare(input) {
|
|
|
20171
20731
|
const filtered = applyExcludes(split(documents, input.split), input.exclude);
|
|
20172
20732
|
const kept = input.split.kind === "join" ? joinItems(filtered.items) : filtered.items;
|
|
20173
20733
|
const tokens = [];
|
|
20174
|
-
const
|
|
20734
|
+
const oversize = [];
|
|
20735
|
+
const items = kept.flatMap((item) => {
|
|
20175
20736
|
const counts2 = {};
|
|
20176
20737
|
const state = scrubState(item.state, counts2);
|
|
20177
20738
|
if (count(counts2) > 0)
|
|
20178
20739
|
redactedSources += 1;
|
|
20179
|
-
|
|
20180
|
-
|
|
20740
|
+
try {
|
|
20741
|
+
tokens.push(assertStateFits(item.id, state, input.questions, input.profile));
|
|
20742
|
+
} catch (error) {
|
|
20743
|
+
if (input.oversize !== "skip" || !isDecisionsError(error))
|
|
20744
|
+
throw error;
|
|
20745
|
+
const estimated = error.details.estimatedTokens;
|
|
20746
|
+
oversize.push({
|
|
20747
|
+
path: item.path ?? item.id,
|
|
20748
|
+
reason: "too-large",
|
|
20749
|
+
detail: `about ${estimated} tokens with its questions (limit ${input.profile.maxStateTokens}); re-run it with --split lines:400/40`
|
|
20750
|
+
});
|
|
20751
|
+
return [];
|
|
20752
|
+
}
|
|
20753
|
+
return [{ ...item, state }];
|
|
20181
20754
|
});
|
|
20182
20755
|
return {
|
|
20183
20756
|
items,
|
|
20184
|
-
skipped: [
|
|
20757
|
+
skipped: unique([
|
|
20758
|
+
...read2.skipped,
|
|
20759
|
+
...allowed.excluded,
|
|
20760
|
+
...chosen.filtered,
|
|
20761
|
+
...filtered.excluded,
|
|
20762
|
+
...oversize
|
|
20763
|
+
]),
|
|
20185
20764
|
redactions: {
|
|
20186
20765
|
total: Object.values(byKind).reduce((a, b) => a + b, 0),
|
|
20187
20766
|
byKind,
|
|
@@ -20200,6 +20779,16 @@ function assertChoicesFit(questions, profile) {
|
|
|
20200
20779
|
}
|
|
20201
20780
|
}
|
|
20202
20781
|
}
|
|
20782
|
+
function unique(skipped) {
|
|
20783
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20784
|
+
return skipped.filter((s) => {
|
|
20785
|
+
const key = `${s.path}\0${s.reason}`;
|
|
20786
|
+
if (seen.has(key))
|
|
20787
|
+
return false;
|
|
20788
|
+
seen.add(key);
|
|
20789
|
+
return true;
|
|
20790
|
+
});
|
|
20791
|
+
}
|
|
20203
20792
|
|
|
20204
20793
|
// ../core/dist/run/pool.js
|
|
20205
20794
|
async function mapWithConcurrency(items, limit, worker, onProgress) {
|
|
@@ -20240,6 +20829,8 @@ async function runMany(ctx, rawInput) {
|
|
|
20240
20829
|
profile: req.profile,
|
|
20241
20830
|
cwd: ctx.config.repoRoot,
|
|
20242
20831
|
exclude: ctx.config.egress.exclude,
|
|
20832
|
+
filter: req.exclude,
|
|
20833
|
+
oversize: "skip",
|
|
20243
20834
|
...input.allowOutside ? { allowOutside: true } : {}
|
|
20244
20835
|
});
|
|
20245
20836
|
const base2 = {
|
|
@@ -20291,6 +20882,7 @@ async function runMany(ctx, rawInput) {
|
|
|
20291
20882
|
const projected = project({
|
|
20292
20883
|
rows,
|
|
20293
20884
|
keep: req.keep,
|
|
20885
|
+
keepAny: req.keepAny,
|
|
20294
20886
|
...req.sort ? { sort: req.sort } : {},
|
|
20295
20887
|
...input.limit !== void 0 ? { limit: input.limit } : {},
|
|
20296
20888
|
...input.fields ? { fields: input.fields } : {},
|
|
@@ -20356,11 +20948,14 @@ async function runAsk(ctx, rawInput) {
|
|
|
20356
20948
|
profile: req.profile,
|
|
20357
20949
|
cwd: ctx.config.repoRoot,
|
|
20358
20950
|
exclude: ctx.config.egress.exclude,
|
|
20951
|
+
filter: req.exclude,
|
|
20359
20952
|
...input.allowOutside ? { allowOutside: true } : {}
|
|
20360
20953
|
});
|
|
20361
20954
|
const [item, ...rest] = prepared.items;
|
|
20362
20955
|
if (!item || rest.length > 0) {
|
|
20363
|
-
const
|
|
20956
|
+
const held = prepared.skipped.filter((s) => s.reason !== "filtered");
|
|
20957
|
+
const left = prepared.skipped.length - held.length;
|
|
20958
|
+
const withheld = (held.length ? ` (${held.length} withheld: ${held.map((s) => `${s.path} ${s.reason}`).join(", ")})` : "") + (left ? ` (${left} left out by --exclude)` : "");
|
|
20364
20959
|
throw new DecisionsError("invalid-request", item ? `ask needs exactly one state, but the source gave ${prepared.items.length}. Use \`decide many\` to fan out.` : `The source gave nothing to ask about${withheld}.`, { items: prepared.items.length });
|
|
20365
20960
|
}
|
|
20366
20961
|
const result = await deciderFor(ctx, req.profile, input.mode ?? "auto").decide({
|
|
@@ -20368,11 +20963,7 @@ async function runAsk(ctx, rawInput) {
|
|
|
20368
20963
|
questions: req.questions,
|
|
20369
20964
|
namespace: req.namespace
|
|
20370
20965
|
});
|
|
20371
|
-
|
|
20372
|
-
if (req.keep.length > 0) {
|
|
20373
|
-
const referenced = new Set(req.keep.map((f) => f.question));
|
|
20374
|
-
verdict = result.undecided.some((q) => referenced.has(q)) ? "undecided" : req.keep.every((f) => matches(result.answers, f)) ? "kept" : "dropped";
|
|
20375
|
-
}
|
|
20966
|
+
const verdict = req.keep.length > 0 || req.keepAny.length > 0 ? verdictOf(result.answers, result.undecided, req.keep, req.keepAny).verdict : void 0;
|
|
20376
20967
|
return {
|
|
20377
20968
|
...req.spec ? { spec: req.spec.name } : {},
|
|
20378
20969
|
id: item.id,
|
|
@@ -20392,8 +20983,8 @@ async function runAsk(ctx, rawInput) {
|
|
|
20392
20983
|
}
|
|
20393
20984
|
|
|
20394
20985
|
// ../core/dist/tools/doctor.js
|
|
20395
|
-
import { accessSync, constants, statSync as
|
|
20396
|
-
import { delimiter, join as
|
|
20986
|
+
import { accessSync, constants, existsSync as existsSync8, statSync as statSync5 } from "node:fs";
|
|
20987
|
+
import { delimiter, join as join6 } from "node:path";
|
|
20397
20988
|
function routeCheck(config) {
|
|
20398
20989
|
try {
|
|
20399
20990
|
route("", config);
|
|
@@ -20419,6 +21010,32 @@ function routeCheck(config) {
|
|
|
20419
21010
|
};
|
|
20420
21011
|
}
|
|
20421
21012
|
}
|
|
21013
|
+
function backlogProblem(error) {
|
|
21014
|
+
const problems = isDecisionsError(error) ? error.details?.problems : void 0;
|
|
21015
|
+
const first = Array.isArray(problems) ? problems[0] : void 0;
|
|
21016
|
+
const count = Array.isArray(problems) && problems.length > 1 ? ` (+${problems.length - 1} more)` : "";
|
|
21017
|
+
return `the backlog doesn't validate: ${first ?? (error instanceof Error ? error.message : String(error))}${count}`;
|
|
21018
|
+
}
|
|
21019
|
+
function backlogCheck(repoRoot) {
|
|
21020
|
+
const file = backlogPath(repoRoot);
|
|
21021
|
+
if (!existsSync8(file))
|
|
21022
|
+
return { name: "backlog", status: "ok", detail: "no scout backlog yet" };
|
|
21023
|
+
try {
|
|
21024
|
+
const { opportunities } = readBacklog(file);
|
|
21025
|
+
return {
|
|
21026
|
+
name: "backlog",
|
|
21027
|
+
status: "ok",
|
|
21028
|
+
detail: `${opportunities.length} opportunit${opportunities.length === 1 ? "y" : "ies"} in ${file}`
|
|
21029
|
+
};
|
|
21030
|
+
} catch (error) {
|
|
21031
|
+
return {
|
|
21032
|
+
name: "backlog",
|
|
21033
|
+
status: "warn",
|
|
21034
|
+
detail: backlogProblem(error),
|
|
21035
|
+
fix: "`decide opportunities check` lists every problem; correct the file by hand or move it aside (decide never repairs it)"
|
|
21036
|
+
};
|
|
21037
|
+
}
|
|
21038
|
+
}
|
|
20422
21039
|
var CODEX_RULE = 'prefix_rule(pattern = ["decide"], decision = "allow")';
|
|
20423
21040
|
async function runDoctor(options) {
|
|
20424
21041
|
const { env } = options;
|
|
@@ -20457,20 +21074,29 @@ async function runDoctor(options) {
|
|
|
20457
21074
|
return report2(void 0, false);
|
|
20458
21075
|
}
|
|
20459
21076
|
const { config } = ctx;
|
|
21077
|
+
if (config.warnings.length > 0) {
|
|
21078
|
+
checks.push({
|
|
21079
|
+
name: "config-keys",
|
|
21080
|
+
status: "warn",
|
|
21081
|
+
detail: `ignored: ${config.warnings.join("; ")}`,
|
|
21082
|
+
fix: "correct or remove these keys (docs/configuration.md lists every key)"
|
|
21083
|
+
});
|
|
21084
|
+
}
|
|
20460
21085
|
checks.push(config.apiKey ? { name: "key", status: "ok", detail: `OPENROUTER_API_KEY present (${config.apiKeySource})` } : {
|
|
20461
21086
|
name: "key",
|
|
20462
21087
|
status: "warn",
|
|
20463
21088
|
detail: "no API key: only replay works",
|
|
20464
21089
|
// The file this shell would read: XDG_CONFIG_HOME moves it.
|
|
20465
|
-
fix: `set OPENROUTER_API_KEY, or write \`openrouter_api_key: <key>\` to ${
|
|
21090
|
+
fix: `set OPENROUTER_API_KEY, or write \`openrouter_api_key: <key>\` to ${join6(userConfigDir(env, options.home), "credentials")} (create its directory first; chmod 600)`
|
|
20466
21091
|
});
|
|
20467
21092
|
checks.push(config.egress.consent.granted ? { name: "consent", status: "ok", detail: `egress consent granted for ${config.repoRoot}` } : {
|
|
20468
21093
|
name: "consent",
|
|
20469
21094
|
status: "warn",
|
|
20470
21095
|
detail: `no egress consent for ${config.repoRoot}: live calls are refused`,
|
|
20471
|
-
fix: "the user
|
|
21096
|
+
fix: "if the user agrees, they grant it themselves (an agent must not): `decide config egress allow` in a terminal in this repo, or through their agent prompt's shell escape (not as a chat message) with --confirm added"
|
|
20472
21097
|
});
|
|
20473
21098
|
checks.push(routeCheck(config.route));
|
|
21099
|
+
checks.push(backlogCheck(config.repoRoot));
|
|
20474
21100
|
let reach;
|
|
20475
21101
|
try {
|
|
20476
21102
|
reach = await ping({
|
|
@@ -20580,10 +21206,10 @@ function which(name, path) {
|
|
|
20580
21206
|
for (const dir of path.split(delimiter)) {
|
|
20581
21207
|
if (!dir)
|
|
20582
21208
|
continue;
|
|
20583
|
-
const candidate =
|
|
21209
|
+
const candidate = join6(dir, name);
|
|
20584
21210
|
try {
|
|
20585
21211
|
accessSync(candidate, constants.X_OK);
|
|
20586
|
-
if (
|
|
21212
|
+
if (statSync5(candidate).isFile())
|
|
20587
21213
|
return candidate;
|
|
20588
21214
|
} catch {
|
|
20589
21215
|
}
|
|
@@ -20591,6 +21217,72 @@ function which(name, path) {
|
|
|
20591
21217
|
return void 0;
|
|
20592
21218
|
}
|
|
20593
21219
|
|
|
21220
|
+
// ../core/dist/tools/opportunities.js
|
|
21221
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
21222
|
+
function runOpportunitiesAdd(ctx, rawInput, now = () => /* @__PURE__ */ new Date()) {
|
|
21223
|
+
const input = checkInput("opportunities-add", rawInput);
|
|
21224
|
+
const file = backlogPath(ctx.config.repoRoot);
|
|
21225
|
+
const merged2 = withBacklogLock(file, () => {
|
|
21226
|
+
const result = mergeCandidates(readBacklog(file), input.candidates, now().toISOString());
|
|
21227
|
+
writeBacklog(file, result.backlog);
|
|
21228
|
+
return result;
|
|
21229
|
+
});
|
|
21230
|
+
return {
|
|
21231
|
+
file,
|
|
21232
|
+
added: merged2.added,
|
|
21233
|
+
updated: merged2.updated,
|
|
21234
|
+
staled: merged2.staled,
|
|
21235
|
+
total: merged2.backlog.opportunities.length
|
|
21236
|
+
};
|
|
21237
|
+
}
|
|
21238
|
+
function runOpportunitiesList(ctx, rawInput = {}) {
|
|
21239
|
+
const input = checkInput("opportunities-list", rawInput);
|
|
21240
|
+
const filters = (input.keep ?? []).map(parseRecordFilter);
|
|
21241
|
+
const sort = parseRecordSort(input.sort ?? "projected:desc");
|
|
21242
|
+
assertFields(input.fields);
|
|
21243
|
+
const file = backlogPath(ctx.config.repoRoot);
|
|
21244
|
+
const { opportunities } = readBacklog(file);
|
|
21245
|
+
const matched = opportunities.filter((o) => filters.every((f) => recordMatches(o, f))).sort((a, b) => compare(readField(a, sort.field), readField(b, sort.field), sort.direction));
|
|
21246
|
+
const shown = input.limit === void 0 ? matched : matched.slice(0, input.limit);
|
|
21247
|
+
return {
|
|
21248
|
+
file,
|
|
21249
|
+
total: opportunities.length,
|
|
21250
|
+
matched: matched.length,
|
|
21251
|
+
basis: "projected",
|
|
21252
|
+
opportunities: shown.map((o) => pick2(o, input.fields))
|
|
21253
|
+
};
|
|
21254
|
+
}
|
|
21255
|
+
function runOpportunitiesCheck(ctx, rawInput = {}) {
|
|
21256
|
+
checkInput("opportunities-check", rawInput);
|
|
21257
|
+
const file = backlogPath(ctx.config.repoRoot);
|
|
21258
|
+
const backlog = readBacklog(file);
|
|
21259
|
+
const byStatus = {};
|
|
21260
|
+
for (const o of backlog.opportunities)
|
|
21261
|
+
byStatus[o.status] = (byStatus[o.status] ?? 0) + 1;
|
|
21262
|
+
return { file, exists: existsSync9(file), entries: backlog.opportunities.length, byStatus };
|
|
21263
|
+
}
|
|
21264
|
+
function compare(a, b, direction) {
|
|
21265
|
+
if (a === void 0 || b === void 0)
|
|
21266
|
+
return a === b ? 0 : a === void 0 ? 1 : -1;
|
|
21267
|
+
const order = typeof a === "number" && typeof b === "number" ? a - b : String(a).localeCompare(String(b));
|
|
21268
|
+
return direction === "asc" ? order : -order;
|
|
21269
|
+
}
|
|
21270
|
+
function pick2(o, fields) {
|
|
21271
|
+
if (!fields)
|
|
21272
|
+
return o;
|
|
21273
|
+
const out = { id: o.id };
|
|
21274
|
+
for (const f of fields)
|
|
21275
|
+
if (Object.hasOwn(o, f))
|
|
21276
|
+
out[f] = o[f];
|
|
21277
|
+
return out;
|
|
21278
|
+
}
|
|
21279
|
+
function assertFields(fields = []) {
|
|
21280
|
+
const unknown = fields.filter((f) => !ENTRY_FIELDS.includes(f));
|
|
21281
|
+
if (unknown.length) {
|
|
21282
|
+
throw new DecisionsError("invalid-request", `--fields: no field ${unknown.join(", ")}. Fields: ${ENTRY_FIELDS.join(", ")}`);
|
|
21283
|
+
}
|
|
21284
|
+
}
|
|
21285
|
+
|
|
20594
21286
|
// ../core/dist/tools/route.js
|
|
20595
21287
|
function runRoute(options, rawInput) {
|
|
20596
21288
|
const problems = checkRouteInput(rawInput);
|
|
@@ -20618,10 +21310,133 @@ function checkRouteInput(input) {
|
|
|
20618
21310
|
];
|
|
20619
21311
|
}
|
|
20620
21312
|
|
|
21313
|
+
// ../core/dist/spec/lint.js
|
|
21314
|
+
var LINT_SEVERITY = {
|
|
21315
|
+
"no-way-out": "warning",
|
|
21316
|
+
"abstract-levels": "warning",
|
|
21317
|
+
"unsupported-task": "warning",
|
|
21318
|
+
"merged-question": "warning",
|
|
21319
|
+
"unjustified-threshold": "warning",
|
|
21320
|
+
"unknown-threshold": "error"
|
|
21321
|
+
};
|
|
21322
|
+
var WAY_OUT_KEY = /^(none|other|others|unclear|unknown|neither|na|nomatch|unsure|mixed|else|nothing|irrelevant|unrelated)$/i;
|
|
21323
|
+
var WAY_OUT_TEXT = /\b(none of|any other|anything else|something else|neither|unclear|not (?:clear|sure|applicable|enough)|no match|does(?:n't| not) (?:fit|apply)|other than (?:these|the above))\b/i;
|
|
21324
|
+
var UNSUPPORTED = [
|
|
21325
|
+
{
|
|
21326
|
+
kind: "counting or arithmetic",
|
|
21327
|
+
pattern: /(^\s*count\b|\bhow many\b|\bthe number of\b|\bcount (?:all|every|each|how|the number)\b|\b(?:more|fewer|less|greater|longer|shorter|larger|smaller|higher|lower) than \d|\b(?:over|under|above|below|at least|at most|exactly|up to) \d|\b\d+(?:\.\d+)?\s?(?:%|percent)|\bsum of\b|\btotal of\b|\baverage of\b|\bon average\b)/i
|
|
21328
|
+
},
|
|
21329
|
+
{
|
|
21330
|
+
kind: "dates",
|
|
21331
|
+
pattern: /(\b(?:older|newer|younger) than \d+ (?:days?|weeks?|months?|years?)\b|\b\d+ (?:days?|weeks?|months?|years?) (?:ago|old)\b|\b(?:within|in) the (?:last|past|next) \d+ (?:days?|weeks?|months?|years?)\b|\b(?:yesterday|tomorrow)\b|\b(?:this|last|next) (?:week|month|quarter|year)\b|\b(?:before|after|since|until|by) (?:monday|tuesday|wednesday|thursday|friday|saturday|sunday|january|february|march|april|may|june|july|august|september|october|november|december|(?:19|20)\d\d)\b|\b(?:has|is|was) (?:expired|overdue)\b)/i
|
|
21332
|
+
},
|
|
21333
|
+
{
|
|
21334
|
+
kind: "images",
|
|
21335
|
+
pattern: /(\bscreenshot|\bphoto(?:graph)?s?\b|\b(?:the|this|an?) (?:image|picture) (?:shows?|contains?|depicts?)\b|\b(?:visible|shown|appears?|seen) in (?:the|this|an?) (?:image|picture)\b)/i
|
|
21336
|
+
},
|
|
21337
|
+
{
|
|
21338
|
+
kind: "an exact fact to read or run, not judge",
|
|
21339
|
+
pattern: /(\b(?:all|every|the) tests? (?:pass(?:es|ed)?|fail(?:s|ed)?|succeed(?:s|ed)?)\b|\btests? (?:pass(?:es|ed)?|are (?:passing|green))\b|\b(?:the )?(?:build|ci|pipeline) (?:passes|passed|succeeds|succeeded|is green)\b|\b(?:it )?compiles without errors\b)/i
|
|
21340
|
+
}
|
|
21341
|
+
];
|
|
21342
|
+
function lintQuestions(questions) {
|
|
21343
|
+
const out = [];
|
|
21344
|
+
for (const [name, q] of Object.entries(questions)) {
|
|
21345
|
+
out.push(...lintQuestion(name, q));
|
|
21346
|
+
}
|
|
21347
|
+
return out;
|
|
21348
|
+
}
|
|
21349
|
+
function lintSpec(spec) {
|
|
21350
|
+
const out = lintQuestions(spec.questions);
|
|
21351
|
+
const thresholds = spec.policy?.thresholds ?? {};
|
|
21352
|
+
for (const name of Object.keys(thresholds)) {
|
|
21353
|
+
if (!(name in spec.questions)) {
|
|
21354
|
+
out.push(finding("unknown-threshold", `policy.thresholds.${name} names no question in this spec`, `Rename it to one of: ${Object.keys(spec.questions).join(", ")}, or remove it.`));
|
|
21355
|
+
}
|
|
21356
|
+
}
|
|
21357
|
+
const justified = new Set(Object.keys(thresholds));
|
|
21358
|
+
const reported = /* @__PURE__ */ new Set();
|
|
21359
|
+
const filters = [
|
|
21360
|
+
...(spec.keep ?? []).map((text) => ({ text, list: "keep" })),
|
|
21361
|
+
...(spec.keepAny ?? []).map((text) => ({ text, list: "keepAny" }))
|
|
21362
|
+
];
|
|
21363
|
+
for (const { text, list } of filters) {
|
|
21364
|
+
const { question, op } = parseFilter(text, spec.questions);
|
|
21365
|
+
if (!NUMERIC.has(op))
|
|
21366
|
+
continue;
|
|
21367
|
+
if (justified.has(question) || reported.has(question))
|
|
21368
|
+
continue;
|
|
21369
|
+
reported.add(question);
|
|
21370
|
+
out.push(finding("unjustified-threshold", `${list} "${text}" has no policy.thresholds.${question} saying why`, `Add policy.thresholds.${question}: {value, why}, with the cost of a wrong keep and a wrong drop (see thresholds.md).`, question));
|
|
21371
|
+
}
|
|
21372
|
+
return out;
|
|
21373
|
+
}
|
|
21374
|
+
var NUMERIC = /* @__PURE__ */ new Set([">=", ">", "<=", "<"]);
|
|
21375
|
+
function lintQuestion(name, q) {
|
|
21376
|
+
const out = [];
|
|
21377
|
+
const texts = [q.instructions, ...criteriaTexts(q)];
|
|
21378
|
+
if (q.type === "choice") {
|
|
21379
|
+
const options = Object.entries(q.criteria);
|
|
21380
|
+
const wayOut = ([key, desc]) => key.split(/[_\-\s/]+/).some((t) => WAY_OUT_KEY.test(t)) || WAY_OUT_TEXT.test(desc);
|
|
21381
|
+
if (!options.some(wayOut)) {
|
|
21382
|
+
out.push(finding("no-way-out", `choice "${name}" has no none/other/unclear option`, "Add an option such as `none: None of these apply`, so text that fits nothing isn't forced onto an option.", name));
|
|
21383
|
+
}
|
|
21384
|
+
}
|
|
21385
|
+
if (q.type === "score" && q.criteria.every((level) => words(level) <= 2)) {
|
|
21386
|
+
out.push(finding("abstract-levels", `score "${name}" has one- or two-word levels (${q.criteria.join(" | ")})`, "Describe each level as a concrete situation someone could spot in the text, lowest first.", name));
|
|
21387
|
+
}
|
|
21388
|
+
out.push(...unsupported([q.instructions, ...criteriaTexts(q).map(unquoted)].join("\n"), name));
|
|
21389
|
+
if (merged(q.instructions)) {
|
|
21390
|
+
out.push(finding("merged-question", `"${name}" looks like two questions in one`, "Split it into one question per idea and combine the answers with --keep.", name));
|
|
21391
|
+
}
|
|
21392
|
+
return dedupe(out);
|
|
21393
|
+
}
|
|
21394
|
+
function unsupported(text, question) {
|
|
21395
|
+
return UNSUPPORTED.filter(({ pattern }) => pattern.test(text)).map(({ kind, pattern }) => finding("unsupported-task", `${question ? `"${question}" ` : ""}asks for ${kind} ("${pattern.exec(text)?.[0]}")`, "Do that part in code (grep, a date check, a count) and ask the model only about what's left.", question));
|
|
21396
|
+
}
|
|
21397
|
+
function merged(instructions) {
|
|
21398
|
+
const claims = instructions.replace(/\bhttps?:\/\/\S+/gi, "URL").replace(/\b(e\.g|i\.e|etc|vs|cf|mr|mrs|ms|dr|st|no|approx)\./gi, "$1").split(/[.?!](?:\s+|$)/).map((s) => s.trim()).filter((s) => words(s) >= 3 && !FRAMING.test(s));
|
|
21399
|
+
return claims.length > 1 || /\band\/or\b/i.test(instructions) || /\beither\b.+\bor\b/i.test(instructions);
|
|
21400
|
+
}
|
|
21401
|
+
var FRAMING = /^(consider|only|ignore|answer|note|treat|assume|look|focus|read|judge|use|do not|don't|exclude|include|given|for example|e\.g|if|when|here|this question|count only|disregard)\b/i;
|
|
21402
|
+
function unquoted(text) {
|
|
21403
|
+
return text.replace(/"[^"\n]*"|“[^”\n]*”/g, '""');
|
|
21404
|
+
}
|
|
21405
|
+
function criteriaTexts(q) {
|
|
21406
|
+
if (q.type === "noul")
|
|
21407
|
+
return q.criteria ? Object.values(q.criteria) : [];
|
|
21408
|
+
if (q.type === "choice")
|
|
21409
|
+
return Object.values(q.criteria);
|
|
21410
|
+
return q.criteria;
|
|
21411
|
+
}
|
|
21412
|
+
function words(text) {
|
|
21413
|
+
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
21414
|
+
}
|
|
21415
|
+
function finding(check, message, fix, question) {
|
|
21416
|
+
return {
|
|
21417
|
+
check,
|
|
21418
|
+
severity: LINT_SEVERITY[check],
|
|
21419
|
+
...question ? { question } : {},
|
|
21420
|
+
message,
|
|
21421
|
+
fix
|
|
21422
|
+
};
|
|
21423
|
+
}
|
|
21424
|
+
function dedupe(findings) {
|
|
21425
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21426
|
+
return findings.filter((f) => {
|
|
21427
|
+
const key = `${f.check}\0${f.question ?? ""}\0${f.message}`;
|
|
21428
|
+
if (seen.has(key))
|
|
21429
|
+
return false;
|
|
21430
|
+
seen.add(key);
|
|
21431
|
+
return true;
|
|
21432
|
+
});
|
|
21433
|
+
}
|
|
21434
|
+
|
|
20621
21435
|
// ../core/dist/tools/spec-check.js
|
|
20622
21436
|
async function runSpecCheck(ctx, rawInput) {
|
|
20623
21437
|
const input = checkInput("spec-check", rawInput);
|
|
20624
21438
|
const spec = loadSpec(input.spec, ctx.specDirs, ctx.cwd);
|
|
21439
|
+
const lint = lintSpec(spec);
|
|
20625
21440
|
const examples = spec.examples ?? [];
|
|
20626
21441
|
if (examples.length === 0) {
|
|
20627
21442
|
throw new DecisionsError("invalid-request", `Spec "${spec.name}" has no examples to check. Add some under examples: (id, state or file, expect).`);
|
|
@@ -20669,8 +21484,8 @@ async function runSpecCheck(ctx, rawInput) {
|
|
|
20669
21484
|
const decider = deciderFor(ctx, profile, input.mode ?? "replay");
|
|
20670
21485
|
if (decider.mode !== "replay") {
|
|
20671
21486
|
assertConsent(ctx.config.egress.consent, root);
|
|
20672
|
-
const
|
|
20673
|
-
checkBudget(
|
|
21487
|
+
const parts = prepared.flatMap((p) => p.prep ? [p.prep.projection] : []);
|
|
21488
|
+
checkBudget(combineProjections(profile, parts), ctx.config.budget, input.confirm ?? false);
|
|
20674
21489
|
}
|
|
20675
21490
|
const results = [];
|
|
20676
21491
|
const usages = [];
|
|
@@ -20719,7 +21534,8 @@ async function runSpecCheck(ctx, rawInput) {
|
|
|
20719
21534
|
counts: counts2,
|
|
20720
21535
|
examples: results,
|
|
20721
21536
|
usage: sumUsage(usages),
|
|
20722
|
-
skipped: summariseSkipped(prepared.flatMap((p) => p.prep?.skipped ?? []))
|
|
21537
|
+
skipped: summariseSkipped(prepared.flatMap((p) => p.prep?.skipped ?? [])),
|
|
21538
|
+
lint
|
|
20723
21539
|
};
|
|
20724
21540
|
}
|
|
20725
21541
|
function judge(id, answers, flat, expect) {
|
|
@@ -20751,6 +21567,43 @@ function judge(id, answers, flat, expect) {
|
|
|
20751
21567
|
};
|
|
20752
21568
|
}
|
|
20753
21569
|
|
|
21570
|
+
// ../core/dist/tools/spec-lint.js
|
|
21571
|
+
import { basename as basename3, extname as extname2 } from "node:path";
|
|
21572
|
+
function runSpecLint(ctx, rawInput = {}) {
|
|
21573
|
+
const input = checkInput("spec-lint", rawInput);
|
|
21574
|
+
const specs = input.spec ? [named(ctx, input.spec)] : listSpecs(ctx.specDirs).filter((s) => !s.shadowed).map((s) => s.error ? { name: s.name, file: s.file, origin: s.origin, findings: [], invalid: s.error } : (
|
|
21575
|
+
// Loaded by path, so keep the layer it was listed from.
|
|
21576
|
+
{ ...entry(loadSpec(s.file, ctx.specDirs, ctx.cwd)), origin: s.origin }
|
|
21577
|
+
));
|
|
21578
|
+
const all = specs.flatMap((s) => s.findings);
|
|
21579
|
+
const counts2 = {
|
|
21580
|
+
specs: specs.length,
|
|
21581
|
+
errors: all.filter((f) => f.severity === "error").length,
|
|
21582
|
+
warnings: all.filter((f) => f.severity === "warning").length,
|
|
21583
|
+
invalid: specs.filter((s) => s.invalid).length
|
|
21584
|
+
};
|
|
21585
|
+
return { specs, counts: counts2, passed: counts2.errors === 0 && counts2.invalid === 0 };
|
|
21586
|
+
}
|
|
21587
|
+
function named(ctx, ref) {
|
|
21588
|
+
try {
|
|
21589
|
+
return entry(loadSpec(ref, ctx.specDirs, ctx.cwd));
|
|
21590
|
+
} catch (error) {
|
|
21591
|
+
const file = isDecisionsError(error) ? error.details?.file : void 0;
|
|
21592
|
+
if (typeof file !== "string")
|
|
21593
|
+
throw error;
|
|
21594
|
+
return {
|
|
21595
|
+
name: basename3(file, extname2(file)),
|
|
21596
|
+
file,
|
|
21597
|
+
origin: "path",
|
|
21598
|
+
findings: [],
|
|
21599
|
+
invalid: error.message
|
|
21600
|
+
};
|
|
21601
|
+
}
|
|
21602
|
+
}
|
|
21603
|
+
function entry(spec) {
|
|
21604
|
+
return { name: spec.name, file: spec.file, origin: spec.origin, findings: lintSpec(spec) };
|
|
21605
|
+
}
|
|
21606
|
+
|
|
20754
21607
|
// ../core/dist/tools/usage.js
|
|
20755
21608
|
function runUsage(ctx, rawInput = {}) {
|
|
20756
21609
|
const input = checkInput("usage", rawInput);
|
|
@@ -20792,10 +21645,13 @@ function nonEmpty3(value) {
|
|
|
20792
21645
|
}
|
|
20793
21646
|
|
|
20794
21647
|
export {
|
|
21648
|
+
activeTriggers,
|
|
20795
21649
|
repoConfigPath,
|
|
20796
21650
|
loadConfig,
|
|
21651
|
+
allProfiles,
|
|
20797
21652
|
setConsent,
|
|
20798
21653
|
DEFAULT_EXCLUDES,
|
|
21654
|
+
probeUrl,
|
|
20799
21655
|
ping,
|
|
20800
21656
|
parseFileRef,
|
|
20801
21657
|
exampleProblems,
|
|
@@ -20808,8 +21664,12 @@ export {
|
|
|
20808
21664
|
runMany,
|
|
20809
21665
|
runAsk,
|
|
20810
21666
|
runDoctor,
|
|
21667
|
+
runOpportunitiesAdd,
|
|
21668
|
+
runOpportunitiesList,
|
|
21669
|
+
runOpportunitiesCheck,
|
|
20811
21670
|
runRoute,
|
|
20812
21671
|
runSpecCheck,
|
|
21672
|
+
runSpecLint,
|
|
20813
21673
|
runUsage,
|
|
20814
21674
|
resolveConnection
|
|
20815
21675
|
};
|