@garygentry/system1 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle/chunks/{bin-3EWFISFQ.mjs → bin-NJIZOMS4.mjs} +18 -14
- package/dist/bundle/chunks/{chunk-BGL272OK.mjs → chunk-A7OWKSHE.mjs} +6 -2
- package/dist/bundle/chunks/{chunk-4ZZ3ZSJN.mjs → chunk-B62DSXC3.mjs} +6 -2
- package/dist/bundle/chunks/{chunk-5TUBFH7M.mjs → chunk-CUKOJ7OM.mjs} +939 -153
- package/dist/bundle/chunks/{chunk-5LVOFZF3.mjs → chunk-FU3D2MBS.mjs} +33 -4
- package/dist/bundle/chunks/{config-UUPD2JL4.mjs → config-7OESN6WC.mjs} +3 -3
- package/dist/bundle/chunks/{decide-AAXD3ITT.mjs → decide-QXDMZDNT.mjs} +4 -4
- package/dist/bundle/chunks/{doctor-KPJ5KVLS.mjs → doctor-EXJQQ7NY.mjs} +3 -3
- package/dist/bundle/chunks/{misc-IW4I3SO6.mjs → misc-2SNIZHU6.mjs} +3 -3
- package/dist/bundle/chunks/opportunities-I6VON6X7.mjs +144 -0
- package/dist/bundle/chunks/{route-7N3J5NP6.mjs → route-JQO5EDAN.mjs} +3 -3
- package/dist/bundle/chunks/{spec-RKA3K6ZZ.mjs → spec-56ZXLEYW.mjs} +42 -7
- 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-B62DSXC3.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,14 +9106,14 @@ 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
|
|
|
@@ -9415,7 +9415,8 @@ var RETRY_STATUSES = /* @__PURE__ */ new Set([
|
|
|
9415
9415
|
520,
|
|
9416
9416
|
521,
|
|
9417
9417
|
522,
|
|
9418
|
-
524
|
|
9418
|
+
524,
|
|
9419
|
+
529
|
|
9419
9420
|
]);
|
|
9420
9421
|
var defaultSleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
9421
9422
|
function createOpenRouterTransport(options) {
|
|
@@ -9569,7 +9570,7 @@ function loadConfig(options = {}) {
|
|
|
9569
9570
|
{ layer: user, file: userFile }
|
|
9570
9571
|
];
|
|
9571
9572
|
const pickFrom = (path, rule) => pick(path, rule, layers2, warnings);
|
|
9572
|
-
const
|
|
9573
|
+
const merged2 = {
|
|
9573
9574
|
model: pickFrom("model", TEXT) ?? DEFAULTS.model,
|
|
9574
9575
|
endpoint: pickFrom("endpoint", URL_TEXT) ?? DEFAULTS.endpoint,
|
|
9575
9576
|
concurrency: pickFrom("concurrency", WHOLE) ?? DEFAULTS.concurrency,
|
|
@@ -9591,12 +9592,13 @@ function loadConfig(options = {}) {
|
|
|
9591
9592
|
const key = resolveApiKey(env, userDir);
|
|
9592
9593
|
const session = detectSession(env);
|
|
9593
9594
|
return {
|
|
9594
|
-
...
|
|
9595
|
-
model: nonEmpty2(env.SYSTEM1_MODEL) ??
|
|
9596
|
-
endpoint: nonEmpty2(env.SYSTEM1_ENDPOINT) ??
|
|
9595
|
+
...merged2,
|
|
9596
|
+
model: nonEmpty2(env.SYSTEM1_MODEL) ?? merged2.model,
|
|
9597
|
+
endpoint: nonEmpty2(env.SYSTEM1_ENDPOINT) ?? merged2.endpoint,
|
|
9597
9598
|
repoRoot,
|
|
9598
9599
|
apiKey: key?.value,
|
|
9599
9600
|
apiKeySource: key?.source,
|
|
9601
|
+
apiKeyQuoted: key?.quoted ?? false,
|
|
9600
9602
|
replay: /^(1|true|yes)$/i.test(env.SYSTEM1_REPLAY ?? ""),
|
|
9601
9603
|
session: session?.id,
|
|
9602
9604
|
sessionOrigin: session?.origin,
|
|
@@ -9840,18 +9842,36 @@ function triggerList(value, file) {
|
|
|
9840
9842
|
});
|
|
9841
9843
|
}
|
|
9842
9844
|
function resolveApiKey(env, userDir) {
|
|
9843
|
-
const fromEnv =
|
|
9845
|
+
const fromEnv = unquoteKey(env.OPENROUTER_API_KEY);
|
|
9844
9846
|
if (fromEnv)
|
|
9845
|
-
return {
|
|
9847
|
+
return { ...fromEnv, source: "env" };
|
|
9846
9848
|
const file = join(userDir, "credentials");
|
|
9847
9849
|
if (!existsSync(file))
|
|
9848
9850
|
return void 0;
|
|
9849
9851
|
if (process.platform !== "win32" && (statSync(file).mode & 63) !== 0) {
|
|
9850
9852
|
throw new DecisionsError("config-error", `${file} is readable by other users. Run \`chmod 600 ${file}\`.`, { file });
|
|
9851
9853
|
}
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9854
|
+
let parsed;
|
|
9855
|
+
try {
|
|
9856
|
+
parsed = readLayer(file);
|
|
9857
|
+
} catch {
|
|
9858
|
+
throw new DecisionsError("config-error", `${file} is not a valid YAML mapping; it should hold one line, \`openrouter_api_key: <key>\``, { file });
|
|
9859
|
+
}
|
|
9860
|
+
const key = unquoteKey(typeof parsed?.openrouter_api_key === "string" ? parsed.openrouter_api_key : void 0, `openrouter_api_key in ${file}`);
|
|
9861
|
+
return key ? { ...key, source: "credentials", file } : void 0;
|
|
9862
|
+
}
|
|
9863
|
+
function unquoteKey(raw, source = "OPENROUTER_API_KEY") {
|
|
9864
|
+
const value = nonEmpty2(raw);
|
|
9865
|
+
if (!value)
|
|
9866
|
+
return void 0;
|
|
9867
|
+
const quoted = /^(["']).*\1$/s.test(value);
|
|
9868
|
+
const key = quoted ? nonEmpty2(value.slice(1, -1)) : value;
|
|
9869
|
+
if (!key)
|
|
9870
|
+
return void 0;
|
|
9871
|
+
if (/[^\x21-\x7e]/.test(key)) {
|
|
9872
|
+
throw new DecisionsError("config-error", `${source} contains a space, line break or other character an API key can't have; set it again`);
|
|
9873
|
+
}
|
|
9874
|
+
return { value: key, quoted };
|
|
9855
9875
|
}
|
|
9856
9876
|
function nonEmpty2(value) {
|
|
9857
9877
|
return value === void 0 || value.trim() === "" ? void 0 : value.trim();
|
|
@@ -9926,6 +9946,9 @@ function applyExcludes(items, extra = []) {
|
|
|
9926
9946
|
}
|
|
9927
9947
|
return { items: kept, excluded: [...excluded.values()] };
|
|
9928
9948
|
}
|
|
9949
|
+
function isExcluded(path, extra = []) {
|
|
9950
|
+
return [...DEFAULT_EXCLUDES, ...extra].find((p) => (0, import_picomatch.default)(p, { dot: true })(path));
|
|
9951
|
+
}
|
|
9929
9952
|
|
|
9930
9953
|
// ../core/dist/ping.js
|
|
9931
9954
|
function probeUrl(config) {
|
|
@@ -13109,8 +13132,8 @@ function Compare(left, right) {
|
|
|
13109
13132
|
// ../../node_modules/.pnpm/typebox@1.3.34/node_modules/typebox/build/type/engine/evaluate/broaden.mjs
|
|
13110
13133
|
function BroadenFilter(type, types, result = [], all = types) {
|
|
13111
13134
|
return guard_exports.ShiftLeft(types, (left, right) => {
|
|
13112
|
-
const
|
|
13113
|
-
return guard_exports.IsEqual(
|
|
13135
|
+
const compare2 = Compare(type, left);
|
|
13136
|
+
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);
|
|
13114
13137
|
}, () => [...result, type]);
|
|
13115
13138
|
}
|
|
13116
13139
|
function BroadenType(type, types, result) {
|
|
@@ -13556,8 +13579,8 @@ function CollapseIntersectProperties(left, right) {
|
|
|
13556
13579
|
const leftProperties = leftKeys.reduce((result, key) => ({ ...result, [key]: left[key] }), {});
|
|
13557
13580
|
const rightProperties = rightKeys.reduce((result, key) => ({ ...result, [key]: right[key] }), {});
|
|
13558
13581
|
const sharedProperties = sharedKeys.reduce((result, key) => ({ ...result, [key]: EvaluateIntersect([left[key], right[key]]) }), {});
|
|
13559
|
-
const
|
|
13560
|
-
const shared = memory_exports.Assign(
|
|
13582
|
+
const unique2 = memory_exports.Assign(leftProperties, rightProperties);
|
|
13583
|
+
const shared = memory_exports.Assign(unique2, sharedProperties);
|
|
13561
13584
|
return shared;
|
|
13562
13585
|
}
|
|
13563
13586
|
function FromIntersect(types) {
|
|
@@ -14993,15 +15016,15 @@ function NextPendingResource(stack, schema) {
|
|
|
14993
15016
|
return IsId(schema) ? false : stack.pendingResource;
|
|
14994
15017
|
}
|
|
14995
15018
|
function NextLexicalBase(stack, schema) {
|
|
14996
|
-
const
|
|
14997
|
-
if (
|
|
14998
|
-
return
|
|
15019
|
+
const entry2 = ResourceEntry(stack, schema);
|
|
15020
|
+
if (entry2)
|
|
15021
|
+
return entry2.base;
|
|
14999
15022
|
return IsId(schema) ? NextUri(schema.$id, stack.lexicalBase).href : stack.lexicalBase;
|
|
15000
15023
|
}
|
|
15001
15024
|
function NextResourceBase(stack, schema) {
|
|
15002
|
-
const
|
|
15003
|
-
if (
|
|
15004
|
-
return
|
|
15025
|
+
const entry2 = ResourceEntry(stack, schema);
|
|
15026
|
+
if (entry2)
|
|
15027
|
+
return entry2.base;
|
|
15005
15028
|
return IsId(schema) && stack.pendingResource ? NextUri(schema.$id, stack.resourceBase).href : stack.resourceBase;
|
|
15006
15029
|
}
|
|
15007
15030
|
function NextUseResourceBaseForReference(stack, schema) {
|
|
@@ -15013,9 +15036,9 @@ function NextReferenceBase(stack, schema) {
|
|
|
15013
15036
|
return isRetrieved || useResourceBaseForReference ? NextResourceBase(stack, schema) : NextLexicalBase(stack, schema);
|
|
15014
15037
|
}
|
|
15015
15038
|
function NextLexicalSchema(stack, schema) {
|
|
15016
|
-
const
|
|
15017
|
-
if (
|
|
15018
|
-
return
|
|
15039
|
+
const entry2 = ResourceEntry(stack, schema);
|
|
15040
|
+
if (entry2)
|
|
15041
|
+
return entry2.root;
|
|
15019
15042
|
return IsId(schema) ? schema : stack.lexicalSchema;
|
|
15020
15043
|
}
|
|
15021
15044
|
function HasStackKeywords(stack, schema) {
|
|
@@ -17975,10 +17998,10 @@ function FromArray13(context, type, value) {
|
|
|
17975
17998
|
const repaired = maximum.map((value2) => FromType26(context, type.items, value2));
|
|
17976
17999
|
if (!IsUniqueItems(type) || IsUniqueItems(type) && !guard_exports.IsEqual(type.uniqueItems, true))
|
|
17977
18000
|
return repaired;
|
|
17978
|
-
const
|
|
17979
|
-
if (!Check2(context, type,
|
|
18001
|
+
const unique2 = MakeUnique(repaired);
|
|
18002
|
+
if (!Check2(context, type, unique2))
|
|
17980
18003
|
throw new RepairError(context, type, value, "Failed to repair Array due to uniqueItems constraint");
|
|
17981
|
-
return
|
|
18004
|
+
return unique2;
|
|
17982
18005
|
}
|
|
17983
18006
|
|
|
17984
18007
|
// ../../node_modules/.pnpm/typebox@1.3.34/node_modules/typebox/build/value/repair/from_enum.mjs
|
|
@@ -18122,14 +18145,14 @@ function FromUnknown2(context, type, value) {
|
|
|
18122
18145
|
|
|
18123
18146
|
// ../../node_modules/.pnpm/typebox@1.3.34/node_modules/typebox/build/value/repair/from_type.mjs
|
|
18124
18147
|
function AssertRepairableValue(context, type, value) {
|
|
18125
|
-
const
|
|
18126
|
-
if (
|
|
18148
|
+
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);
|
|
18149
|
+
if (unsupported2) {
|
|
18127
18150
|
throw new RepairError(context, type, value, "Value is not repairable");
|
|
18128
18151
|
}
|
|
18129
18152
|
}
|
|
18130
18153
|
function AssertRepairableType(context, type, value) {
|
|
18131
|
-
const
|
|
18132
|
-
if (
|
|
18154
|
+
const unsupported2 = IsConstructor2(type) || IsFunction2(type) || IsNever(type);
|
|
18155
|
+
if (unsupported2) {
|
|
18133
18156
|
throw new RepairError(context, type, value, "Type is not repairable");
|
|
18134
18157
|
}
|
|
18135
18158
|
}
|
|
@@ -18186,16 +18209,34 @@ var import_yaml3 = __toESM(require_dist(), 1);
|
|
|
18186
18209
|
|
|
18187
18210
|
// ../core/dist/project/project.js
|
|
18188
18211
|
var FILTER = /^\s*([A-Za-z_][\w-]*)((?:\.[\w-]+)*)\s*(>=|<=|!=|>|<|=|\s+in\s+)\s*(.+?)\s*$/;
|
|
18189
|
-
function
|
|
18212
|
+
function tokenizeFilter(text) {
|
|
18190
18213
|
const m = FILTER.exec(text);
|
|
18191
|
-
if (!m?.[1] || !m[3] || m[4] === void 0)
|
|
18192
|
-
|
|
18193
|
-
}
|
|
18194
|
-
const question = m[1];
|
|
18214
|
+
if (!m?.[1] || !m[3] || m[4] === void 0)
|
|
18215
|
+
return void 0;
|
|
18195
18216
|
const op = m[3].trim();
|
|
18196
|
-
const field = m[2] ? m[2].slice(1) : defaultField(question, questions);
|
|
18197
18217
|
const raw = m[4];
|
|
18198
18218
|
const value = op === "in" ? raw.split(",").map((s) => s.trim()).filter(Boolean) : isNumeric(raw) ? Number(raw) : raw;
|
|
18219
|
+
return { name: m[1], path: m[2] ? m[2].slice(1) : "", op, value };
|
|
18220
|
+
}
|
|
18221
|
+
function tokenizeSort(text) {
|
|
18222
|
+
const m = /^\s*([A-Za-z_][\w-]*)((?:\.[\w-]+)*)(?::(asc|desc))?\s*$/.exec(text);
|
|
18223
|
+
if (!m?.[1])
|
|
18224
|
+
return void 0;
|
|
18225
|
+
return {
|
|
18226
|
+
name: m[1],
|
|
18227
|
+
path: m[2] ? m[2].slice(1) : "",
|
|
18228
|
+
direction: m[3] ?? "desc"
|
|
18229
|
+
};
|
|
18230
|
+
}
|
|
18231
|
+
function parseFilter(text, questions) {
|
|
18232
|
+
const t = tokenizeFilter(text);
|
|
18233
|
+
if (!t) {
|
|
18234
|
+
throw new DecisionsError("invalid-request", `Cannot parse --keep "${text}". Use <question>[.<field>]<op><value>, e.g. relevant>=0.7 or kind in fix,feature`);
|
|
18235
|
+
}
|
|
18236
|
+
const question = t.name;
|
|
18237
|
+
const op = t.op;
|
|
18238
|
+
const field = t.path || defaultField(question, questions);
|
|
18239
|
+
const value = t.value;
|
|
18199
18240
|
const filter = { question, field, op, value, source: text };
|
|
18200
18241
|
if (questions)
|
|
18201
18242
|
assertKnown(question, questions, text);
|
|
@@ -18205,14 +18246,14 @@ function parseFilter(text, questions) {
|
|
|
18205
18246
|
return filter;
|
|
18206
18247
|
}
|
|
18207
18248
|
function parseSort(text, questions) {
|
|
18208
|
-
const
|
|
18209
|
-
if (!
|
|
18249
|
+
const t = tokenizeSort(text);
|
|
18250
|
+
if (!t) {
|
|
18210
18251
|
throw new DecisionsError("invalid-request", `Cannot parse --sort "${text}". Use <question>[.<field>][:asc|desc]`);
|
|
18211
18252
|
}
|
|
18212
18253
|
if (questions)
|
|
18213
|
-
assertKnown(
|
|
18214
|
-
const field =
|
|
18215
|
-
return { question:
|
|
18254
|
+
assertKnown(t.name, questions, text);
|
|
18255
|
+
const field = t.path || sortField(t.name, questions);
|
|
18256
|
+
return { question: t.name, field, direction: t.direction };
|
|
18216
18257
|
}
|
|
18217
18258
|
function matches(answers, filter) {
|
|
18218
18259
|
const actual = read(answers[filter.question], filter.field);
|
|
@@ -18231,18 +18272,33 @@ function matches(answers, filter) {
|
|
|
18231
18272
|
}
|
|
18232
18273
|
}
|
|
18233
18274
|
}
|
|
18275
|
+
function verdictOf(answers, flat, keep, keepAny = [], sortQuestion) {
|
|
18276
|
+
const acted = [...keep.map((f) => f.question), ...sortQuestion ? [sortQuestion] : []];
|
|
18277
|
+
const all = acted.length === 0 && keepAny.length === 0;
|
|
18278
|
+
const blocking = flat.filter((q) => all || acted.includes(q));
|
|
18279
|
+
if (blocking.length > 0)
|
|
18280
|
+
return { verdict: "undecided", questions: [...blocking] };
|
|
18281
|
+
if (!keep.every((f) => matches(answers, f)))
|
|
18282
|
+
return { verdict: "dropped", questions: [] };
|
|
18283
|
+
if (keepAny.length === 0)
|
|
18284
|
+
return { verdict: "kept", questions: [] };
|
|
18285
|
+
const decided = keepAny.filter((f) => !flat.includes(f.question));
|
|
18286
|
+
if (decided.some((f) => matches(answers, f)))
|
|
18287
|
+
return { verdict: "kept", questions: [] };
|
|
18288
|
+
const open = [...new Set(keepAny.filter((f) => flat.includes(f.question)).map((f) => f.question))];
|
|
18289
|
+
return open.length > 0 ? { verdict: "undecided", questions: open } : { verdict: "dropped", questions: [] };
|
|
18290
|
+
}
|
|
18234
18291
|
function project(input) {
|
|
18235
18292
|
const keep = input.keep ?? [];
|
|
18236
|
-
const
|
|
18237
|
-
const relevant = acted.length > 0 ? new Set(acted) : void 0;
|
|
18293
|
+
const keepAny = input.keepAny ?? [];
|
|
18238
18294
|
const kept = [];
|
|
18239
18295
|
const undecided = [];
|
|
18240
18296
|
let dropped = 0;
|
|
18241
18297
|
for (const row2 of input.rows) {
|
|
18242
|
-
const
|
|
18243
|
-
if (
|
|
18244
|
-
undecided.push({ row: row2, questions:
|
|
18245
|
-
else if (
|
|
18298
|
+
const v = verdictOf(row2.answers, input.undecidedOf(row2), keep, keepAny, input.sort?.question);
|
|
18299
|
+
if (v.verdict === "undecided")
|
|
18300
|
+
undecided.push({ row: row2, questions: v.questions });
|
|
18301
|
+
else if (v.verdict === "kept")
|
|
18246
18302
|
kept.push(row2);
|
|
18247
18303
|
else
|
|
18248
18304
|
dropped += 1;
|
|
@@ -18625,21 +18681,21 @@ var Walker = class {
|
|
|
18625
18681
|
if (controller.aborted || signal && signal.aborted || maxFiles && paths.length > maxFiles) return;
|
|
18626
18682
|
const files = this.getArray(this.state.paths);
|
|
18627
18683
|
for (let i = 0; i < entries.length; ++i) {
|
|
18628
|
-
const
|
|
18629
|
-
if (
|
|
18630
|
-
const filename = this.joinPath(
|
|
18684
|
+
const entry2 = entries[i];
|
|
18685
|
+
if (entry2.isFile() || entry2.isSymbolicLink() && !resolveSymlinks$1 && !excludeSymlinks) {
|
|
18686
|
+
const filename = this.joinPath(entry2.name, directoryPath);
|
|
18631
18687
|
this.pushFile(filename, files, this.state.counts, filters);
|
|
18632
|
-
} else if (
|
|
18633
|
-
let path = joinDirectoryPath(
|
|
18634
|
-
if (exclude && exclude(
|
|
18688
|
+
} else if (entry2.isDirectory()) {
|
|
18689
|
+
let path = joinDirectoryPath(entry2.name, directoryPath, this.state.options.pathSeparator);
|
|
18690
|
+
if (exclude && exclude(entry2.name, path)) continue;
|
|
18635
18691
|
this.pushDirectory(path, paths, filters);
|
|
18636
18692
|
this.walkDirectory(this.state, path, path, depth - 1, this.walk);
|
|
18637
|
-
} else if (this.resolveSymlink &&
|
|
18638
|
-
let path = joinPathWithBasePath(
|
|
18693
|
+
} else if (this.resolveSymlink && entry2.isSymbolicLink()) {
|
|
18694
|
+
let path = joinPathWithBasePath(entry2.name, directoryPath);
|
|
18639
18695
|
this.resolveSymlink(path, this.state, (stat2, resolvedPath) => {
|
|
18640
18696
|
if (stat2.isDirectory()) {
|
|
18641
18697
|
resolvedPath = normalizePath(resolvedPath, this.state.options);
|
|
18642
|
-
if (exclude && exclude(
|
|
18698
|
+
if (exclude && exclude(entry2.name, useRealPaths ? resolvedPath : path + pathSeparator)) return;
|
|
18643
18699
|
this.walkDirectory(this.state, resolvedPath, useRealPaths ? resolvedPath : path + pathSeparator, depth - 1, this.walk);
|
|
18644
18700
|
} else {
|
|
18645
18701
|
resolvedPath = useRealPaths ? resolvedPath : path;
|
|
@@ -19073,6 +19129,26 @@ async function glob(globInput, options) {
|
|
|
19073
19129
|
return crawler ? formatPaths(await crawler.withPromise(), relative5) : [];
|
|
19074
19130
|
}
|
|
19075
19131
|
|
|
19132
|
+
// ../core/dist/sources/filter.js
|
|
19133
|
+
var import_picomatch3 = __toESM(require_picomatch2(), 1);
|
|
19134
|
+
function applyFilter(items, patterns = []) {
|
|
19135
|
+
if (patterns.length === 0)
|
|
19136
|
+
return { items: [...items], filtered: [] };
|
|
19137
|
+
const matchers = patterns.map((p) => ({ p, test: (0, import_picomatch3.default)(p, { dot: true }) }));
|
|
19138
|
+
const kept = [];
|
|
19139
|
+
const filtered = /* @__PURE__ */ new Map();
|
|
19140
|
+
for (const item of items) {
|
|
19141
|
+
const paths = [item.path, item.realPath].filter((p) => p !== void 0);
|
|
19142
|
+
const hit = item.path === void 0 ? void 0 : matchers.find((m) => paths.some((p) => m.test(p)));
|
|
19143
|
+
if (hit && item.path !== void 0) {
|
|
19144
|
+
filtered.set(item.path, { path: item.path, reason: "filtered", detail: hit.p });
|
|
19145
|
+
} else {
|
|
19146
|
+
kept.push(item);
|
|
19147
|
+
}
|
|
19148
|
+
}
|
|
19149
|
+
return { items: kept, filtered: [...filtered.values()] };
|
|
19150
|
+
}
|
|
19151
|
+
|
|
19076
19152
|
// ../core/dist/sources/read.js
|
|
19077
19153
|
function resolvePath(cwd, path) {
|
|
19078
19154
|
const absolute = resolve4(cwd, path);
|
|
@@ -19187,12 +19263,15 @@ async function readGlob(patterns, options) {
|
|
|
19187
19263
|
});
|
|
19188
19264
|
matches2.sort();
|
|
19189
19265
|
const ignored = new Set(gitIgnored(options.cwd, matches2));
|
|
19190
|
-
const
|
|
19191
|
-
const
|
|
19266
|
+
const candidates = matches2.filter((m) => !ignored.has(m));
|
|
19267
|
+
const withheld = candidates.filter((m) => isExcluded(m, options.withhold));
|
|
19268
|
+
const unfiltered = applyFilter(candidates.filter((m) => !withheld.includes(m)).map((path) => ({ path })), options.filter);
|
|
19269
|
+
const result = readFiles([...withheld, ...unfiltered.items.map((m) => m.path)].sort(), options);
|
|
19192
19270
|
return {
|
|
19193
19271
|
documents: result.documents,
|
|
19194
19272
|
skipped: [
|
|
19195
19273
|
...[...ignored].map((path) => ({ path, reason: "gitignored" })),
|
|
19274
|
+
...unfiltered.filtered,
|
|
19196
19275
|
...result.skipped
|
|
19197
19276
|
]
|
|
19198
19277
|
};
|
|
@@ -19535,6 +19614,8 @@ var SpecSchema = typebox_exports.Object({
|
|
|
19535
19614
|
description: typebox_exports.String({ minLength: 1 }),
|
|
19536
19615
|
questions: typebox_exports.Record(typebox_exports.String(), typebox_exports.Unknown()),
|
|
19537
19616
|
keep: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
19617
|
+
/** Kept when any of these matches, as well as every `keep`. */
|
|
19618
|
+
keepAny: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
19538
19619
|
sort: typebox_exports.Optional(typebox_exports.String()),
|
|
19539
19620
|
policy: typebox_exports.Optional(typebox_exports.Object({ thresholds: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), Threshold)) }, CLOSED)),
|
|
19540
19621
|
source: typebox_exports.Optional(typebox_exports.Object({
|
|
@@ -19581,7 +19662,7 @@ function parseSpec(text, file, origin) {
|
|
|
19581
19662
|
throw new DecisionsError("invalid-request", `${file}: ${error.message}`, { file });
|
|
19582
19663
|
}
|
|
19583
19664
|
const questions = spec.questions;
|
|
19584
|
-
for (const text2 of spec.keep ?? [])
|
|
19665
|
+
for (const text2 of [...spec.keep ?? [], ...spec.keepAny ?? []])
|
|
19585
19666
|
wrap(file, () => parseFilter(text2, questions));
|
|
19586
19667
|
if (spec.sort)
|
|
19587
19668
|
wrap(file, () => parseSort(spec.sort, questions));
|
|
@@ -19684,11 +19765,11 @@ function listSpecs(dirs) {
|
|
|
19684
19765
|
for (const [origin, dir] of layers(dirs)) {
|
|
19685
19766
|
if (!existsSync4(dir))
|
|
19686
19767
|
continue;
|
|
19687
|
-
for (const
|
|
19688
|
-
if (!/\.(ya?ml|json)$/.test(
|
|
19768
|
+
for (const entry2 of readdirSync2(dir).sort()) {
|
|
19769
|
+
if (!/\.(ya?ml|json)$/.test(entry2))
|
|
19689
19770
|
continue;
|
|
19690
|
-
const file = join2(dir,
|
|
19691
|
-
const name = basename2(
|
|
19771
|
+
const file = join2(dir, entry2);
|
|
19772
|
+
const name = basename2(entry2, extname(entry2));
|
|
19692
19773
|
const shadowed = seen.has(name);
|
|
19693
19774
|
seen.add(name);
|
|
19694
19775
|
try {
|
|
@@ -19728,6 +19809,360 @@ function wrap(file, fn) {
|
|
|
19728
19809
|
}
|
|
19729
19810
|
}
|
|
19730
19811
|
|
|
19812
|
+
// ../core/dist/model/schema.js
|
|
19813
|
+
var QuestionSchema = typebox_exports.Union([
|
|
19814
|
+
typebox_exports.Object({
|
|
19815
|
+
type: typebox_exports.Literal("noul"),
|
|
19816
|
+
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19817
|
+
/** Optional `true`/`false` criteria that sharpen an ambiguous statement. */
|
|
19818
|
+
criteria: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), typebox_exports.String()))
|
|
19819
|
+
}, { additionalProperties: false }),
|
|
19820
|
+
typebox_exports.Object({
|
|
19821
|
+
type: typebox_exports.Literal("choice"),
|
|
19822
|
+
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19823
|
+
/** Option key to when it applies. At least two, including a way out. */
|
|
19824
|
+
criteria: typebox_exports.Record(typebox_exports.String(), typebox_exports.String(), { minProperties: 2 })
|
|
19825
|
+
}, { additionalProperties: false }),
|
|
19826
|
+
typebox_exports.Object({
|
|
19827
|
+
type: typebox_exports.Literal("score"),
|
|
19828
|
+
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19829
|
+
/** Levels, lowest first and 0-indexed. */
|
|
19830
|
+
criteria: typebox_exports.Array(typebox_exports.String(), { minItems: 2 })
|
|
19831
|
+
}, { additionalProperties: false })
|
|
19832
|
+
]);
|
|
19833
|
+
|
|
19834
|
+
// ../core/dist/opportunities/backlog.js
|
|
19835
|
+
import { createHash } from "node:crypto";
|
|
19836
|
+
import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync5, renameSync, rmSync, statSync as statSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
19837
|
+
import { dirname as dirname4, join as join3 } from "node:path";
|
|
19838
|
+
var BACKLOG_FORMAT = 1;
|
|
19839
|
+
var CLOSED2 = { additionalProperties: false };
|
|
19840
|
+
var Text = typebox_exports.String({ minLength: 1 });
|
|
19841
|
+
var Usd = typebox_exports.Number({ minimum: 0, maximum: 1e6 });
|
|
19842
|
+
var Status = typebox_exports.Union([
|
|
19843
|
+
typebox_exports.Literal("new"),
|
|
19844
|
+
typebox_exports.Literal("stale"),
|
|
19845
|
+
typebox_exports.Literal("adopted"),
|
|
19846
|
+
typebox_exports.Literal("rejected")
|
|
19847
|
+
]);
|
|
19848
|
+
var candidateFields = {
|
|
19849
|
+
/** `code`: a codebase (Mode A). `agents`: skills, hooks, agent configuration (Mode B). */
|
|
19850
|
+
mode: typebox_exports.Union([typebox_exports.Literal("code"), typebox_exports.Literal("agents")]),
|
|
19851
|
+
location: typebox_exports.Object({
|
|
19852
|
+
path: Text,
|
|
19853
|
+
lines: typebox_exports.Optional(typebox_exports.Object({ start: typebox_exports.Integer({ minimum: 1 }), end: typebox_exports.Integer({ minimum: 1 }) }, CLOSED2))
|
|
19854
|
+
}, CLOSED2),
|
|
19855
|
+
/** What does the job today, e.g. "gpt-4o call parsed to an enum". */
|
|
19856
|
+
mechanism: Text,
|
|
19857
|
+
shape: typebox_exports.Union([
|
|
19858
|
+
typebox_exports.Literal("single"),
|
|
19859
|
+
typebox_exports.Literal("fanout"),
|
|
19860
|
+
typebox_exports.Literal("cascade"),
|
|
19861
|
+
typebox_exports.Literal("pairwise")
|
|
19862
|
+
]),
|
|
19863
|
+
/**
|
|
19864
|
+
* The text that triggered it, taken verbatim from the screened item. The id is
|
|
19865
|
+
* derived from it, so it must be copied, not paraphrased.
|
|
19866
|
+
*/
|
|
19867
|
+
evidence: typebox_exports.String({ pattern: "\\S" }),
|
|
19868
|
+
/** A draft question set that would replace the mechanism. */
|
|
19869
|
+
questions: typebox_exports.Record(typebox_exports.String(), QuestionSchema, { minProperties: 1 }),
|
|
19870
|
+
/**
|
|
19871
|
+
* What replacing the mechanism would mainly buy. `cost`: cheaper calls.
|
|
19872
|
+
* `quality`: better or more consistent verdicts (a regex or heuristic that
|
|
19873
|
+
* costs nothing today, so its `savingUsd` can be negative). `latency`:
|
|
19874
|
+
* faster answers. Rank within a benefit, not across them.
|
|
19875
|
+
*/
|
|
19876
|
+
benefit: typebox_exports.Union([typebox_exports.Literal("cost"), typebox_exports.Literal("quality"), typebox_exports.Literal("latency")]),
|
|
19877
|
+
/** The inputs of the projected saving. The saving itself is computed here. */
|
|
19878
|
+
projected: typebox_exports.Object({
|
|
19879
|
+
volume: typebox_exports.Number({ minimum: 0, maximum: 1e12 }),
|
|
19880
|
+
/** What `volume` counts per, e.g. "day", "PR", "run". */
|
|
19881
|
+
per: Text,
|
|
19882
|
+
currentCostPerItemUsd: Usd,
|
|
19883
|
+
decisionCostPerItemUsd: Usd,
|
|
19884
|
+
/** Where the figures came from. */
|
|
19885
|
+
note: typebox_exports.Optional(typebox_exports.String())
|
|
19886
|
+
}, CLOSED2),
|
|
19887
|
+
risk: typebox_exports.Object({
|
|
19888
|
+
level: typebox_exports.Union([typebox_exports.Literal("low"), typebox_exports.Literal("medium"), typebox_exports.Literal("high")]),
|
|
19889
|
+
/** What would go wrong if the replacement erred. */
|
|
19890
|
+
note: Text
|
|
19891
|
+
}, CLOSED2),
|
|
19892
|
+
/** The next step, e.g. "save the spec with design, then adopt". */
|
|
19893
|
+
next: Text,
|
|
19894
|
+
/** Defaults to `new`. `rejected` needs a `statusReason`. */
|
|
19895
|
+
status: typebox_exports.Optional(Status),
|
|
19896
|
+
statusReason: typebox_exports.Optional(Text),
|
|
19897
|
+
/** Which sweep produced it, and whether its screening answers were live or replayed. */
|
|
19898
|
+
source: typebox_exports.Object({
|
|
19899
|
+
sweep: Text,
|
|
19900
|
+
answers: typebox_exports.Union([typebox_exports.Literal("live"), typebox_exports.Literal("replay")])
|
|
19901
|
+
}, CLOSED2)
|
|
19902
|
+
};
|
|
19903
|
+
var CandidateSchema = typebox_exports.Object(candidateFields, CLOSED2);
|
|
19904
|
+
var OpportunitySchema = typebox_exports.Object({
|
|
19905
|
+
id: typebox_exports.String({ pattern: "^op-[0-9a-f]{12}$" }),
|
|
19906
|
+
...candidateFields,
|
|
19907
|
+
status: Status,
|
|
19908
|
+
projected: typebox_exports.Object({
|
|
19909
|
+
...candidateFields.projected.properties,
|
|
19910
|
+
basis: typebox_exports.Literal("projected"),
|
|
19911
|
+
/** volume × (current − decision), per `per`. Negative means it would cost more. */
|
|
19912
|
+
savingUsd: typebox_exports.Number()
|
|
19913
|
+
}, CLOSED2),
|
|
19914
|
+
firstSeenAt: typebox_exports.String(),
|
|
19915
|
+
seenAt: typebox_exports.String()
|
|
19916
|
+
}, CLOSED2);
|
|
19917
|
+
var BacklogSchema = typebox_exports.Object({
|
|
19918
|
+
version: typebox_exports.Literal(BACKLOG_FORMAT),
|
|
19919
|
+
opportunities: typebox_exports.Array(OpportunitySchema)
|
|
19920
|
+
}, CLOSED2);
|
|
19921
|
+
function backlogPath(repoRoot) {
|
|
19922
|
+
return join3(stateDir(repoRoot), "opportunities.json");
|
|
19923
|
+
}
|
|
19924
|
+
function opportunityId(mode, evidence) {
|
|
19925
|
+
const normal = evidence.replace(/\s+/g, " ").trim();
|
|
19926
|
+
return `op-${createHash("sha256").update(`${mode}
|
|
19927
|
+
${normal}`).digest("hex").slice(0, 12)}`;
|
|
19928
|
+
}
|
|
19929
|
+
function projectedSaving(p) {
|
|
19930
|
+
return round(p.volume * (p.currentCostPerItemUsd - p.decisionCostPerItemUsd));
|
|
19931
|
+
}
|
|
19932
|
+
function backlogProblems(value) {
|
|
19933
|
+
const problems = [...value_exports.Errors(BacklogSchema, value)].map((e) => `${e.instancePath || "/"} ${e.message}`);
|
|
19934
|
+
if (problems.length)
|
|
19935
|
+
return problems;
|
|
19936
|
+
const backlog = value;
|
|
19937
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19938
|
+
backlog.opportunities.forEach((o, i) => {
|
|
19939
|
+
if (seen.has(o.id))
|
|
19940
|
+
problems.push(`/opportunities/${i}/id ${o.id} appears twice`);
|
|
19941
|
+
seen.add(o.id);
|
|
19942
|
+
if (o.id !== opportunityId(o.mode, o.evidence)) {
|
|
19943
|
+
problems.push(`/opportunities/${i}/id does not match its mode and evidence`);
|
|
19944
|
+
}
|
|
19945
|
+
if (o.projected.savingUsd !== projectedSaving(o.projected)) {
|
|
19946
|
+
problems.push(`/opportunities/${i}/projected/savingUsd does not match its inputs`);
|
|
19947
|
+
}
|
|
19948
|
+
problems.push(...candidateProblems(o, `/opportunities/${i}`));
|
|
19949
|
+
});
|
|
19950
|
+
return problems;
|
|
19951
|
+
}
|
|
19952
|
+
function candidateProblems(c, at) {
|
|
19953
|
+
const problems = [];
|
|
19954
|
+
if (c.status === "rejected" && !c.statusReason) {
|
|
19955
|
+
problems.push(`${at}/statusReason is required when status is rejected`);
|
|
19956
|
+
}
|
|
19957
|
+
if (!Number.isFinite(projectedSaving(c.projected))) {
|
|
19958
|
+
problems.push(`${at}/projected gives a saving that isn't a finite number`);
|
|
19959
|
+
}
|
|
19960
|
+
if (c.location.lines && c.location.lines.end < c.location.lines.start) {
|
|
19961
|
+
problems.push(`${at}/location/lines end is before start`);
|
|
19962
|
+
}
|
|
19963
|
+
try {
|
|
19964
|
+
assertQuestionSet(c.questions);
|
|
19965
|
+
} catch (error) {
|
|
19966
|
+
problems.push(`${at}/questions ${error.message}`);
|
|
19967
|
+
}
|
|
19968
|
+
return problems;
|
|
19969
|
+
}
|
|
19970
|
+
function readBacklog(file) {
|
|
19971
|
+
if (!existsSync5(file))
|
|
19972
|
+
return { version: BACKLOG_FORMAT, opportunities: [] };
|
|
19973
|
+
let text;
|
|
19974
|
+
try {
|
|
19975
|
+
text = readFileSync5(file, "utf8");
|
|
19976
|
+
} catch (error) {
|
|
19977
|
+
throw malformed2(file, [`cannot read it: ${error.message}`]);
|
|
19978
|
+
}
|
|
19979
|
+
let value;
|
|
19980
|
+
try {
|
|
19981
|
+
value = JSON.parse(text);
|
|
19982
|
+
} catch (error) {
|
|
19983
|
+
throw malformed2(file, [`not JSON: ${error.message}`]);
|
|
19984
|
+
}
|
|
19985
|
+
const problems = backlogProblems(value);
|
|
19986
|
+
if (problems.length)
|
|
19987
|
+
throw malformed2(file, problems);
|
|
19988
|
+
return value;
|
|
19989
|
+
}
|
|
19990
|
+
function malformed2(file, problems) {
|
|
19991
|
+
return new DecisionsError("invalid-request", `${file} is not a valid backlog (it was left as it is):
|
|
19992
|
+
${problems.join("\n ")}`, { file, problems });
|
|
19993
|
+
}
|
|
19994
|
+
var LOCK_WAIT_MS = 5e3;
|
|
19995
|
+
var LOCK_STALE_MS = 3e4;
|
|
19996
|
+
function withBacklogLock(file, body) {
|
|
19997
|
+
mkdirSync2(dirname4(file), { recursive: true });
|
|
19998
|
+
const lock = `${file}.lock`;
|
|
19999
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
20000
|
+
for (; ; ) {
|
|
20001
|
+
try {
|
|
20002
|
+
closeSync(openSync(lock, "wx"));
|
|
20003
|
+
break;
|
|
20004
|
+
} catch (error) {
|
|
20005
|
+
if (error.code !== "EEXIST")
|
|
20006
|
+
throw error;
|
|
20007
|
+
try {
|
|
20008
|
+
if (Date.now() - statSync4(lock).mtimeMs > LOCK_STALE_MS)
|
|
20009
|
+
rmSync(lock, { force: true });
|
|
20010
|
+
} catch {
|
|
20011
|
+
}
|
|
20012
|
+
if (Date.now() > deadline) {
|
|
20013
|
+
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 });
|
|
20014
|
+
}
|
|
20015
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
20016
|
+
}
|
|
20017
|
+
}
|
|
20018
|
+
try {
|
|
20019
|
+
return body();
|
|
20020
|
+
} finally {
|
|
20021
|
+
rmSync(lock, { force: true });
|
|
20022
|
+
}
|
|
20023
|
+
}
|
|
20024
|
+
function writeBacklog(file, backlog) {
|
|
20025
|
+
mkdirSync2(dirname4(file), { recursive: true });
|
|
20026
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
20027
|
+
writeFileSync2(tmp, `${JSON.stringify(backlog, null, 2)}
|
|
20028
|
+
`);
|
|
20029
|
+
renameSync(tmp, file);
|
|
20030
|
+
}
|
|
20031
|
+
function mergeCandidates(backlog, candidates, now) {
|
|
20032
|
+
const problems = candidates.flatMap((c, i) => candidateProblems(c, `/candidates/${i}`));
|
|
20033
|
+
if (problems.length) {
|
|
20034
|
+
throw new DecisionsError("invalid-request", `Invalid candidates:
|
|
20035
|
+
${problems.join("\n ")}`, {
|
|
20036
|
+
problems
|
|
20037
|
+
});
|
|
20038
|
+
}
|
|
20039
|
+
const byId = new Map(backlog.opportunities.map((o) => [o.id, o]));
|
|
20040
|
+
const existed = new Set(byId.keys());
|
|
20041
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20042
|
+
for (const { statusReason: reason, ...c } of candidates) {
|
|
20043
|
+
const id = opportunityId(c.mode, c.evidence);
|
|
20044
|
+
seen.add(id);
|
|
20045
|
+
const before = byId.get(id);
|
|
20046
|
+
const statusReason = reason ?? (c.status ? void 0 : before?.statusReason);
|
|
20047
|
+
byId.set(id, {
|
|
20048
|
+
id,
|
|
20049
|
+
...c,
|
|
20050
|
+
// Seeing the evidence again disproves "stale"; any other status stands.
|
|
20051
|
+
status: c.status ?? (before?.status === "stale" ? "new" : before?.status) ?? "new",
|
|
20052
|
+
...statusReason ? { statusReason } : {},
|
|
20053
|
+
projected: { ...c.projected, basis: "projected", savingUsd: projectedSaving(c.projected) },
|
|
20054
|
+
firstSeenAt: before?.firstSeenAt ?? now,
|
|
20055
|
+
seenAt: now
|
|
20056
|
+
});
|
|
20057
|
+
}
|
|
20058
|
+
const added = [...seen].filter((id) => !existed.has(id));
|
|
20059
|
+
const updated = [...seen].filter((id) => existed.has(id));
|
|
20060
|
+
const key = (o) => `${o.mode}
|
|
20061
|
+
${o.location.path}
|
|
20062
|
+
${o.source.sweep}`;
|
|
20063
|
+
const covered = new Set(candidates.map(key));
|
|
20064
|
+
const staled = [];
|
|
20065
|
+
for (const o of byId.values()) {
|
|
20066
|
+
if (o.status === "new" && !seen.has(o.id) && covered.has(key(o))) {
|
|
20067
|
+
byId.set(o.id, { ...o, status: "stale" });
|
|
20068
|
+
staled.push(o.id);
|
|
20069
|
+
}
|
|
20070
|
+
}
|
|
20071
|
+
return {
|
|
20072
|
+
backlog: { version: BACKLOG_FORMAT, opportunities: [...byId.values()] },
|
|
20073
|
+
added,
|
|
20074
|
+
updated,
|
|
20075
|
+
staled
|
|
20076
|
+
};
|
|
20077
|
+
}
|
|
20078
|
+
var FIELDS = {
|
|
20079
|
+
id: {},
|
|
20080
|
+
mode: {},
|
|
20081
|
+
benefit: {},
|
|
20082
|
+
status: {},
|
|
20083
|
+
statusReason: {},
|
|
20084
|
+
shape: {},
|
|
20085
|
+
mechanism: {},
|
|
20086
|
+
evidence: {},
|
|
20087
|
+
next: {},
|
|
20088
|
+
firstSeenAt: {},
|
|
20089
|
+
seenAt: {},
|
|
20090
|
+
risk: { default: "level", subs: ["level", "note"] },
|
|
20091
|
+
projected: {
|
|
20092
|
+
default: "savingUsd",
|
|
20093
|
+
subs: [
|
|
20094
|
+
"savingUsd",
|
|
20095
|
+
"volume",
|
|
20096
|
+
"per",
|
|
20097
|
+
"currentCostPerItemUsd",
|
|
20098
|
+
"decisionCostPerItemUsd",
|
|
20099
|
+
"basis",
|
|
20100
|
+
"note"
|
|
20101
|
+
]
|
|
20102
|
+
},
|
|
20103
|
+
location: { default: "path", subs: ["path", "lines.start", "lines.end"] },
|
|
20104
|
+
source: { default: "sweep", subs: ["sweep", "answers"] }
|
|
20105
|
+
};
|
|
20106
|
+
var ENTRY_FIELDS = [...Object.keys(OpportunitySchema.properties)];
|
|
20107
|
+
function parseRecordFilter(text) {
|
|
20108
|
+
const t = tokenizeFilter(text);
|
|
20109
|
+
if (!t) {
|
|
20110
|
+
throw new DecisionsError("invalid-request", `Cannot parse --keep "${text}". Use <field>[.<sub>]<op><value>, e.g. status=new or projected>=0.01`);
|
|
20111
|
+
}
|
|
20112
|
+
const field = recordField(t.name, t.path, text);
|
|
20113
|
+
if ((t.op === ">" || t.op === ">=" || t.op === "<" || t.op === "<=") && typeof t.value !== "number") {
|
|
20114
|
+
throw new DecisionsError("invalid-request", `--keep "${text}": ${t.op} needs a number`);
|
|
20115
|
+
}
|
|
20116
|
+
return { field, op: t.op, value: t.value, source: text };
|
|
20117
|
+
}
|
|
20118
|
+
function parseRecordSort(text) {
|
|
20119
|
+
const t = tokenizeSort(text);
|
|
20120
|
+
if (!t) {
|
|
20121
|
+
throw new DecisionsError("invalid-request", `Cannot parse --sort "${text}". Use <field>[.<sub>][:asc|desc]`);
|
|
20122
|
+
}
|
|
20123
|
+
return { field: recordField(t.name, t.path, text), direction: t.direction };
|
|
20124
|
+
}
|
|
20125
|
+
function recordField(name, path, text) {
|
|
20126
|
+
const spec = Object.hasOwn(FIELDS, name) ? FIELDS[name] : void 0;
|
|
20127
|
+
if (!spec) {
|
|
20128
|
+
throw new DecisionsError("invalid-request", `"${text}": no field "${name}". Fields: ${Object.keys(FIELDS).join(", ")}`);
|
|
20129
|
+
}
|
|
20130
|
+
if (path && !spec.subs?.includes(path)) {
|
|
20131
|
+
throw new DecisionsError("invalid-request", spec.subs ? `"${text}": ${name} has no "${path}". Use one of: ${spec.subs.join(", ")}` : `"${text}": ${name} has no sub-fields`);
|
|
20132
|
+
}
|
|
20133
|
+
const sub = path || spec.default;
|
|
20134
|
+
return sub ? `${name}.${sub}` : name;
|
|
20135
|
+
}
|
|
20136
|
+
function readField(o, field) {
|
|
20137
|
+
let value = o;
|
|
20138
|
+
for (const key of field.split(".")) {
|
|
20139
|
+
if (value === null || typeof value !== "object")
|
|
20140
|
+
return void 0;
|
|
20141
|
+
value = value[key];
|
|
20142
|
+
}
|
|
20143
|
+
return value;
|
|
20144
|
+
}
|
|
20145
|
+
function recordMatches(o, f) {
|
|
20146
|
+
const actual = readField(o, f.field);
|
|
20147
|
+
switch (f.op) {
|
|
20148
|
+
case "in":
|
|
20149
|
+
return f.value.includes(String(actual));
|
|
20150
|
+
case "=":
|
|
20151
|
+
return String(actual) === String(f.value);
|
|
20152
|
+
case "!=":
|
|
20153
|
+
return String(actual) !== String(f.value);
|
|
20154
|
+
default: {
|
|
20155
|
+
if (typeof actual !== "number")
|
|
20156
|
+
return false;
|
|
20157
|
+
const v = f.value;
|
|
20158
|
+
return f.op === ">=" ? actual >= v : f.op === ">" ? actual > v : f.op === "<=" ? actual <= v : actual < v;
|
|
20159
|
+
}
|
|
20160
|
+
}
|
|
20161
|
+
}
|
|
20162
|
+
function round(n) {
|
|
20163
|
+
return Math.round(n * 1e9) / 1e9;
|
|
20164
|
+
}
|
|
20165
|
+
|
|
19731
20166
|
// ../core/dist/tools/schemas.js
|
|
19732
20167
|
var Source = typebox_exports.Union([
|
|
19733
20168
|
typebox_exports.Object({
|
|
@@ -19750,26 +20185,6 @@ var Source = typebox_exports.Union([
|
|
|
19750
20185
|
paths: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String()))
|
|
19751
20186
|
})
|
|
19752
20187
|
]);
|
|
19753
|
-
var Question = typebox_exports.Union([
|
|
19754
|
-
typebox_exports.Object({
|
|
19755
|
-
type: typebox_exports.Literal("noul"),
|
|
19756
|
-
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19757
|
-
/** Optional `true`/`false` criteria that sharpen an ambiguous statement. */
|
|
19758
|
-
criteria: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), typebox_exports.String()))
|
|
19759
|
-
}, { additionalProperties: false }),
|
|
19760
|
-
typebox_exports.Object({
|
|
19761
|
-
type: typebox_exports.Literal("choice"),
|
|
19762
|
-
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19763
|
-
/** Option key to when it applies. At least two, including a way out. */
|
|
19764
|
-
criteria: typebox_exports.Record(typebox_exports.String(), typebox_exports.String(), { minProperties: 2 })
|
|
19765
|
-
}, { additionalProperties: false }),
|
|
19766
|
-
typebox_exports.Object({
|
|
19767
|
-
type: typebox_exports.Literal("score"),
|
|
19768
|
-
instructions: typebox_exports.String({ minLength: 1 }),
|
|
19769
|
-
/** Levels, lowest first and 0-indexed. */
|
|
19770
|
-
criteria: typebox_exports.Array(typebox_exports.String(), { minItems: 2 })
|
|
19771
|
-
}, { additionalProperties: false })
|
|
19772
|
-
]);
|
|
19773
20188
|
var Mode = typebox_exports.Union([
|
|
19774
20189
|
typebox_exports.Literal("auto"),
|
|
19775
20190
|
typebox_exports.Literal("live"),
|
|
@@ -19780,13 +20195,20 @@ var Common = {
|
|
|
19780
20195
|
/** A saved spec: name (repo → user → bundled) or path. */
|
|
19781
20196
|
spec: typebox_exports.Optional(typebox_exports.String()),
|
|
19782
20197
|
/** An inline question set; mutually exclusive with `spec`. */
|
|
19783
|
-
questions: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(),
|
|
20198
|
+
questions: typebox_exports.Optional(typebox_exports.Record(typebox_exports.String(), QuestionSchema)),
|
|
19784
20199
|
/** Where the content comes from. Defaults to the spec's `source`. */
|
|
19785
20200
|
sources: typebox_exports.Optional(typebox_exports.Array(Source)),
|
|
20201
|
+
/**
|
|
20202
|
+
* Leave out paths matching these globs (repo-relative, like `glob`). A per-call
|
|
20203
|
+
* choice, reported as `filtered`; egress excludes still apply on top.
|
|
20204
|
+
*/
|
|
20205
|
+
exclude: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String({ minLength: 1 }))),
|
|
19786
20206
|
/** `file` | `hunk` | `row` | `lines:N[/overlap]`. */
|
|
19787
20207
|
split: typebox_exports.Optional(typebox_exports.String()),
|
|
19788
20208
|
/** Filters, ANDed: `relevant>=0.7`, `kind in fix,feature`. */
|
|
19789
20209
|
keep: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
20210
|
+
/** Kept when any of these matches (and every `keep` does): `a>=0.3`, `b>=0.3`. */
|
|
20211
|
+
keepAny: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
19790
20212
|
mode: typebox_exports.Optional(Mode),
|
|
19791
20213
|
model: typebox_exports.Optional(typebox_exports.String()),
|
|
19792
20214
|
/**
|
|
@@ -19824,6 +20246,24 @@ var SpecCheckInput = typebox_exports.Object({
|
|
|
19824
20246
|
/** Proceed past the spend guard. */
|
|
19825
20247
|
confirm: typebox_exports.Optional(typebox_exports.Boolean())
|
|
19826
20248
|
}, strict);
|
|
20249
|
+
var SpecLintInput = typebox_exports.Object({
|
|
20250
|
+
/** Spec name (repo → user → bundled) or path. Omitted: every spec in reach. */
|
|
20251
|
+
spec: typebox_exports.Optional(typebox_exports.String({ minLength: 1 }))
|
|
20252
|
+
}, strict);
|
|
20253
|
+
var OpportunitiesAddInput = typebox_exports.Object({
|
|
20254
|
+
/** Candidates to merge into `.system1/opportunities.json`. */
|
|
20255
|
+
candidates: typebox_exports.Array(CandidateSchema, { minItems: 1 })
|
|
20256
|
+
}, strict);
|
|
20257
|
+
var OpportunitiesListInput = typebox_exports.Object({
|
|
20258
|
+
/** Record filters, ANDed: `status=new`, `risk in medium,high`, `projected>=0.01`. */
|
|
20259
|
+
keep: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String())),
|
|
20260
|
+
/** `projected:desc` (the default), `seenAt:asc`, … */
|
|
20261
|
+
sort: typebox_exports.Optional(typebox_exports.String()),
|
|
20262
|
+
limit: typebox_exports.Optional(typebox_exports.Integer({ minimum: 0 })),
|
|
20263
|
+
/** Top-level fields to include per entry; `id` always is. */
|
|
20264
|
+
fields: typebox_exports.Optional(typebox_exports.Array(typebox_exports.String()))
|
|
20265
|
+
}, strict);
|
|
20266
|
+
var OpportunitiesCheckInput = typebox_exports.Object({}, strict);
|
|
19827
20267
|
var RouteInput = typebox_exports.Object({
|
|
19828
20268
|
/** The user's prompt, as the harness hook received it. */
|
|
19829
20269
|
prompt: typebox_exports.String()
|
|
@@ -19833,11 +20273,15 @@ var TOOL_SCHEMAS = {
|
|
|
19833
20273
|
many: ManyInput,
|
|
19834
20274
|
usage: UsageInput,
|
|
19835
20275
|
"spec-check": SpecCheckInput,
|
|
20276
|
+
"spec-lint": SpecLintInput,
|
|
20277
|
+
"opportunities-add": OpportunitiesAddInput,
|
|
20278
|
+
"opportunities-list": OpportunitiesListInput,
|
|
20279
|
+
"opportunities-check": OpportunitiesCheckInput,
|
|
19836
20280
|
route: RouteInput
|
|
19837
20281
|
};
|
|
19838
20282
|
|
|
19839
20283
|
// ../core/dist/tools/context.js
|
|
19840
|
-
import { isAbsolute as isAbsolute4, join as
|
|
20284
|
+
import { isAbsolute as isAbsolute4, join as join5, relative as relative4 } from "node:path";
|
|
19841
20285
|
|
|
19842
20286
|
// ../core/dist/egress/scrub.js
|
|
19843
20287
|
var SECRET_NAME = "[A-Za-z0-9_.-]*(?:api[_-]?key|secret|token|passw(?:or)?d|pwd|credential|private[_-]?key|auth)[A-Za-z0-9_.-]*";
|
|
@@ -19857,6 +20301,13 @@ var RULES = [
|
|
|
19857
20301
|
{ kind: "slack-token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
19858
20302
|
{ kind: "stripe-key", pattern: /\b[rs]k_(?:live|test)_[A-Za-z0-9]{16,}\b/g },
|
|
19859
20303
|
{ kind: "google-api-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
20304
|
+
// Service tokens with a known prefix and separator, whatever they are
|
|
20305
|
+
// assigned to (MCP server env blocks name them freely: NOTION_INTEGRATION,
|
|
20306
|
+
// HONEYCOMB_WRITEKEY).
|
|
20307
|
+
{
|
|
20308
|
+
kind: "service-token",
|
|
20309
|
+
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
|
|
20310
|
+
},
|
|
19860
20311
|
{ kind: "jwt", pattern: /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g },
|
|
19861
20312
|
{ kind: "url-credentials", pattern: /(?<=\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s@/]{3,}(?=@)/gi },
|
|
19862
20313
|
// NAME = "literal": a quoted value of 8+ chars assigned to a secret-sounding name.
|
|
@@ -19870,6 +20321,15 @@ var RULES = [
|
|
|
19870
20321
|
{
|
|
19871
20322
|
kind: "assigned-secret",
|
|
19872
20323
|
pattern: new RegExp(`(?<=\\b${SECRET_NAME}\\s*[:=]\\s*)(?!\\$\\{|<|\\[REDACTED)(?=[A-Za-z_\\-+/=~]*\\d)[A-Za-z0-9_\\-+/=~]{8,}(?![\\w(.])`, "gi")
|
|
20324
|
+
},
|
|
20325
|
+
// ENV_NAME: "value" in upper-case environment style, ending in a credential
|
|
20326
|
+
// word (API_WRITEKEY, SERVICE_PAT). Case-sensitive, and the value must look
|
|
20327
|
+
// like a credential (12+ characters, letters and digits, no dots or colons),
|
|
20328
|
+
// so UI constants (`PROBE_KEY = "pulse:probe"`) and code are left alone.
|
|
20329
|
+
// Last, so a value an earlier rule already caught keeps that rule's name.
|
|
20330
|
+
{
|
|
20331
|
+
kind: "env-secret",
|
|
20332
|
+
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
|
|
19873
20333
|
}
|
|
19874
20334
|
];
|
|
19875
20335
|
function scrubText(text, counts2 = {}) {
|
|
@@ -19927,13 +20387,13 @@ function assertStateFits(id, state, questions, profile) {
|
|
|
19927
20387
|
}
|
|
19928
20388
|
|
|
19929
20389
|
// ../core/dist/fixtures/store.js
|
|
19930
|
-
import { createHash } from "node:crypto";
|
|
19931
|
-
import { existsSync as
|
|
19932
|
-
import { dirname as
|
|
20390
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
20391
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
20392
|
+
import { dirname as dirname5, join as join4 } from "node:path";
|
|
19933
20393
|
var FIXTURE_VERSION = 1;
|
|
19934
20394
|
function fixtureKey(request) {
|
|
19935
20395
|
const { model, state, questions } = request;
|
|
19936
|
-
return
|
|
20396
|
+
return createHash2("sha256").update(canonicalJson({ model, state, questions })).digest("hex");
|
|
19937
20397
|
}
|
|
19938
20398
|
function canonicalJson(value) {
|
|
19939
20399
|
return JSON.stringify(sortKeys(value));
|
|
@@ -19956,13 +20416,13 @@ var FixtureStore = class {
|
|
|
19956
20416
|
if (!NAMESPACE.test(namespace)) {
|
|
19957
20417
|
throw new Error(`Invalid fixture namespace "${namespace}" (lowercase letters, digits, . _ -)`);
|
|
19958
20418
|
}
|
|
19959
|
-
return
|
|
20419
|
+
return join4(this.dir, namespace, `${key}.json`);
|
|
19960
20420
|
}
|
|
19961
20421
|
lookup(namespace, request) {
|
|
19962
20422
|
const file = this.path(namespace, fixtureKey(request));
|
|
19963
|
-
if (!
|
|
20423
|
+
if (!existsSync6(file))
|
|
19964
20424
|
return void 0;
|
|
19965
|
-
const record = JSON.parse(
|
|
20425
|
+
const record = JSON.parse(readFileSync6(file, "utf8"));
|
|
19966
20426
|
return record.v === FIXTURE_VERSION ? record : void 0;
|
|
19967
20427
|
}
|
|
19968
20428
|
record(namespace, request, response, now = /* @__PURE__ */ new Date()) {
|
|
@@ -19975,11 +20435,11 @@ var FixtureStore = class {
|
|
|
19975
20435
|
response
|
|
19976
20436
|
};
|
|
19977
20437
|
const file = this.path(namespace, key);
|
|
19978
|
-
|
|
20438
|
+
mkdirSync3(dirname5(file), { recursive: true });
|
|
19979
20439
|
const temp = `${file}.${process.pid}.tmp`;
|
|
19980
|
-
|
|
20440
|
+
writeFileSync3(temp, `${JSON.stringify(record, null, 2)}
|
|
19981
20441
|
`);
|
|
19982
|
-
|
|
20442
|
+
renameSync2(temp, file);
|
|
19983
20443
|
return record;
|
|
19984
20444
|
}
|
|
19985
20445
|
};
|
|
@@ -20057,8 +20517,8 @@ function createDecider(options) {
|
|
|
20057
20517
|
}
|
|
20058
20518
|
|
|
20059
20519
|
// ../core/dist/run/spend.js
|
|
20060
|
-
import { appendFileSync, existsSync as
|
|
20061
|
-
import { dirname as
|
|
20520
|
+
import { appendFileSync, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7 } from "node:fs";
|
|
20521
|
+
import { dirname as dirname6 } from "node:path";
|
|
20062
20522
|
function sumUsage(usages) {
|
|
20063
20523
|
const total = usages.reduce((sum, u) => ({
|
|
20064
20524
|
input_tokens: sum.input_tokens + (u?.input_tokens ?? 0),
|
|
@@ -20072,15 +20532,15 @@ var SpendLedger = class {
|
|
|
20072
20532
|
constructor(file) {
|
|
20073
20533
|
this.file = file;
|
|
20074
20534
|
}
|
|
20075
|
-
append(
|
|
20076
|
-
|
|
20077
|
-
appendFileSync(this.file, `${JSON.stringify(
|
|
20535
|
+
append(entry2) {
|
|
20536
|
+
mkdirSync4(dirname6(this.file), { recursive: true });
|
|
20537
|
+
appendFileSync(this.file, `${JSON.stringify(entry2)}
|
|
20078
20538
|
`);
|
|
20079
20539
|
}
|
|
20080
20540
|
entries() {
|
|
20081
|
-
if (!
|
|
20541
|
+
if (!existsSync7(this.file))
|
|
20082
20542
|
return [];
|
|
20083
|
-
return
|
|
20543
|
+
return readFileSync7(this.file, "utf8").split("\n").filter((line) => line.trim() !== "").flatMap((line) => {
|
|
20084
20544
|
try {
|
|
20085
20545
|
return [JSON.parse(line)];
|
|
20086
20546
|
} catch {
|
|
@@ -20145,15 +20605,33 @@ function resolveRequest(ctx, input) {
|
|
|
20145
20605
|
sources,
|
|
20146
20606
|
split: parseSplit(input.split ?? spec?.source?.split ?? "file"),
|
|
20147
20607
|
keep: keepText.map((k) => parseFilter(k, questions)),
|
|
20608
|
+
keepAny: (input.keepAny ?? spec?.keepAny ?? []).map((k) => parseFilter(k, questions)),
|
|
20148
20609
|
...sortText ? { sort: parseSort(sortText, questions) } : {},
|
|
20149
|
-
profile
|
|
20610
|
+
profile,
|
|
20611
|
+
exclude: (input.exclude ?? []).map((p) => rebasePattern(p, ctx.cwd, ctx.config.repoRoot))
|
|
20150
20612
|
};
|
|
20151
20613
|
}
|
|
20614
|
+
function rebasePattern(pattern, cwd, repoRoot) {
|
|
20615
|
+
if (pattern.startsWith("!")) {
|
|
20616
|
+
throw new DecisionsError("invalid-request", `--exclude "${pattern}": negation isn't supported; name what to leave out`);
|
|
20617
|
+
}
|
|
20618
|
+
if (isAbsolute4(pattern)) {
|
|
20619
|
+
const rel = relative4(repoRoot, pattern);
|
|
20620
|
+
if (rel.startsWith("..") || isAbsolute4(rel)) {
|
|
20621
|
+
throw new DecisionsError("invalid-request", `--exclude "${pattern}" is outside the repo (${repoRoot}), so it would match nothing`);
|
|
20622
|
+
}
|
|
20623
|
+
return rel;
|
|
20624
|
+
}
|
|
20625
|
+
const prefix = relative4(repoRoot, cwd);
|
|
20626
|
+
if (prefix === "" || prefix.startsWith("..") || isAbsolute4(prefix) || pattern.startsWith("**/"))
|
|
20627
|
+
return pattern;
|
|
20628
|
+
return join5(prefix, pattern);
|
|
20629
|
+
}
|
|
20152
20630
|
function rebase(source, cwd, repoRoot) {
|
|
20153
20631
|
const prefix = relative4(repoRoot, cwd);
|
|
20154
20632
|
if (prefix === "" || prefix.startsWith("..") || isAbsolute4(prefix))
|
|
20155
20633
|
return source;
|
|
20156
|
-
const move = (path) => isAbsolute4(path) ? path :
|
|
20634
|
+
const move = (path) => isAbsolute4(path) ? path : join5(prefix, path);
|
|
20157
20635
|
switch (source.kind) {
|
|
20158
20636
|
case "file":
|
|
20159
20637
|
return { ...source, path: move(source.path) };
|
|
@@ -20193,14 +20671,14 @@ function deciderFor(ctx, profile, mode = "auto") {
|
|
|
20193
20671
|
...ctx.fetch ? { fetch: ctx.fetch } : {}
|
|
20194
20672
|
})
|
|
20195
20673
|
} : {},
|
|
20196
|
-
fixtures: new FixtureStore(
|
|
20197
|
-
ledger: new SpendLedger(
|
|
20674
|
+
fixtures: new FixtureStore(join5(dir, "fixtures")),
|
|
20675
|
+
ledger: new SpendLedger(join5(dir, "usage.jsonl")),
|
|
20198
20676
|
mode: config.replay ? "replay" : mode,
|
|
20199
20677
|
...config.session ? { session: config.session } : {}
|
|
20200
20678
|
});
|
|
20201
20679
|
}
|
|
20202
20680
|
function ledgerFor(ctx) {
|
|
20203
|
-
return new SpendLedger(
|
|
20681
|
+
return new SpendLedger(join5(stateDir(ctx.config.repoRoot), "usage.jsonl"));
|
|
20204
20682
|
}
|
|
20205
20683
|
|
|
20206
20684
|
// ../core/dist/run/budget.js
|
|
@@ -20245,7 +20723,8 @@ async function prepare(input) {
|
|
|
20245
20723
|
const read2 = await readSources(input.sources, {
|
|
20246
20724
|
cwd: input.cwd,
|
|
20247
20725
|
...input.maxFileBytes ? { maxFileBytes: input.maxFileBytes } : {},
|
|
20248
|
-
...input.allowOutside ? { allowOutside: true } : {}
|
|
20726
|
+
...input.allowOutside ? { allowOutside: true } : {},
|
|
20727
|
+
...input.filter?.length ? { filter: input.filter, withhold: input.exclude ?? [] } : {}
|
|
20249
20728
|
});
|
|
20250
20729
|
const byKind = {};
|
|
20251
20730
|
let redactedSources = 0;
|
|
@@ -20256,7 +20735,8 @@ async function prepare(input) {
|
|
|
20256
20735
|
return found2;
|
|
20257
20736
|
};
|
|
20258
20737
|
const allowed = applyExcludes(read2.documents, input.exclude);
|
|
20259
|
-
const
|
|
20738
|
+
const chosen = applyFilter(allowed.items, input.filter);
|
|
20739
|
+
const documents = chosen.items.map((doc) => {
|
|
20260
20740
|
const counts2 = {};
|
|
20261
20741
|
const scrubbed = {
|
|
20262
20742
|
...doc,
|
|
@@ -20270,17 +20750,36 @@ async function prepare(input) {
|
|
|
20270
20750
|
const filtered = applyExcludes(split(documents, input.split), input.exclude);
|
|
20271
20751
|
const kept = input.split.kind === "join" ? joinItems(filtered.items) : filtered.items;
|
|
20272
20752
|
const tokens = [];
|
|
20273
|
-
const
|
|
20753
|
+
const oversize = [];
|
|
20754
|
+
const items = kept.flatMap((item) => {
|
|
20274
20755
|
const counts2 = {};
|
|
20275
20756
|
const state = scrubState(item.state, counts2);
|
|
20276
20757
|
if (count(counts2) > 0)
|
|
20277
20758
|
redactedSources += 1;
|
|
20278
|
-
|
|
20279
|
-
|
|
20759
|
+
try {
|
|
20760
|
+
tokens.push(assertStateFits(item.id, state, input.questions, input.profile));
|
|
20761
|
+
} catch (error) {
|
|
20762
|
+
if (input.oversize !== "skip" || !isDecisionsError(error))
|
|
20763
|
+
throw error;
|
|
20764
|
+
const estimated = error.details.estimatedTokens;
|
|
20765
|
+
oversize.push({
|
|
20766
|
+
path: item.path ?? item.id,
|
|
20767
|
+
reason: "too-large",
|
|
20768
|
+
detail: `about ${estimated} tokens with its questions (limit ${input.profile.maxStateTokens}); re-run it with --split lines:400/40`
|
|
20769
|
+
});
|
|
20770
|
+
return [];
|
|
20771
|
+
}
|
|
20772
|
+
return [{ ...item, state }];
|
|
20280
20773
|
});
|
|
20281
20774
|
return {
|
|
20282
20775
|
items,
|
|
20283
|
-
skipped: [
|
|
20776
|
+
skipped: unique([
|
|
20777
|
+
...read2.skipped,
|
|
20778
|
+
...allowed.excluded,
|
|
20779
|
+
...chosen.filtered,
|
|
20780
|
+
...filtered.excluded,
|
|
20781
|
+
...oversize
|
|
20782
|
+
]),
|
|
20284
20783
|
redactions: {
|
|
20285
20784
|
total: Object.values(byKind).reduce((a, b) => a + b, 0),
|
|
20286
20785
|
byKind,
|
|
@@ -20299,6 +20798,16 @@ function assertChoicesFit(questions, profile) {
|
|
|
20299
20798
|
}
|
|
20300
20799
|
}
|
|
20301
20800
|
}
|
|
20801
|
+
function unique(skipped) {
|
|
20802
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20803
|
+
return skipped.filter((s) => {
|
|
20804
|
+
const key = `${s.path}\0${s.reason}`;
|
|
20805
|
+
if (seen.has(key))
|
|
20806
|
+
return false;
|
|
20807
|
+
seen.add(key);
|
|
20808
|
+
return true;
|
|
20809
|
+
});
|
|
20810
|
+
}
|
|
20302
20811
|
|
|
20303
20812
|
// ../core/dist/run/pool.js
|
|
20304
20813
|
async function mapWithConcurrency(items, limit, worker, onProgress) {
|
|
@@ -20339,6 +20848,8 @@ async function runMany(ctx, rawInput) {
|
|
|
20339
20848
|
profile: req.profile,
|
|
20340
20849
|
cwd: ctx.config.repoRoot,
|
|
20341
20850
|
exclude: ctx.config.egress.exclude,
|
|
20851
|
+
filter: req.exclude,
|
|
20852
|
+
oversize: "skip",
|
|
20342
20853
|
...input.allowOutside ? { allowOutside: true } : {}
|
|
20343
20854
|
});
|
|
20344
20855
|
const base2 = {
|
|
@@ -20390,6 +20901,7 @@ async function runMany(ctx, rawInput) {
|
|
|
20390
20901
|
const projected = project({
|
|
20391
20902
|
rows,
|
|
20392
20903
|
keep: req.keep,
|
|
20904
|
+
keepAny: req.keepAny,
|
|
20393
20905
|
...req.sort ? { sort: req.sort } : {},
|
|
20394
20906
|
...input.limit !== void 0 ? { limit: input.limit } : {},
|
|
20395
20907
|
...input.fields ? { fields: input.fields } : {},
|
|
@@ -20455,11 +20967,14 @@ async function runAsk(ctx, rawInput) {
|
|
|
20455
20967
|
profile: req.profile,
|
|
20456
20968
|
cwd: ctx.config.repoRoot,
|
|
20457
20969
|
exclude: ctx.config.egress.exclude,
|
|
20970
|
+
filter: req.exclude,
|
|
20458
20971
|
...input.allowOutside ? { allowOutside: true } : {}
|
|
20459
20972
|
});
|
|
20460
20973
|
const [item, ...rest] = prepared.items;
|
|
20461
20974
|
if (!item || rest.length > 0) {
|
|
20462
|
-
const
|
|
20975
|
+
const held = prepared.skipped.filter((s) => s.reason !== "filtered");
|
|
20976
|
+
const left = prepared.skipped.length - held.length;
|
|
20977
|
+
const withheld = (held.length ? ` (${held.length} withheld: ${held.map((s) => `${s.path} ${s.reason}`).join(", ")})` : "") + (left ? ` (${left} left out by --exclude)` : "");
|
|
20463
20978
|
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 });
|
|
20464
20979
|
}
|
|
20465
20980
|
const result = await deciderFor(ctx, req.profile, input.mode ?? "auto").decide({
|
|
@@ -20467,11 +20982,7 @@ async function runAsk(ctx, rawInput) {
|
|
|
20467
20982
|
questions: req.questions,
|
|
20468
20983
|
namespace: req.namespace
|
|
20469
20984
|
});
|
|
20470
|
-
|
|
20471
|
-
if (req.keep.length > 0) {
|
|
20472
|
-
const referenced = new Set(req.keep.map((f) => f.question));
|
|
20473
|
-
verdict = result.undecided.some((q) => referenced.has(q)) ? "undecided" : req.keep.every((f) => matches(result.answers, f)) ? "kept" : "dropped";
|
|
20474
|
-
}
|
|
20985
|
+
const verdict = req.keep.length > 0 || req.keepAny.length > 0 ? verdictOf(result.answers, result.undecided, req.keep, req.keepAny).verdict : void 0;
|
|
20475
20986
|
return {
|
|
20476
20987
|
...req.spec ? { spec: req.spec.name } : {},
|
|
20477
20988
|
id: item.id,
|
|
@@ -20491,8 +21002,8 @@ async function runAsk(ctx, rawInput) {
|
|
|
20491
21002
|
}
|
|
20492
21003
|
|
|
20493
21004
|
// ../core/dist/tools/doctor.js
|
|
20494
|
-
import { accessSync, constants, statSync as
|
|
20495
|
-
import { delimiter, join as
|
|
21005
|
+
import { accessSync, constants, existsSync as existsSync8, statSync as statSync5 } from "node:fs";
|
|
21006
|
+
import { delimiter, join as join6 } from "node:path";
|
|
20496
21007
|
function routeCheck(config) {
|
|
20497
21008
|
try {
|
|
20498
21009
|
route("", config);
|
|
@@ -20518,6 +21029,32 @@ function routeCheck(config) {
|
|
|
20518
21029
|
};
|
|
20519
21030
|
}
|
|
20520
21031
|
}
|
|
21032
|
+
function backlogProblem(error) {
|
|
21033
|
+
const problems = isDecisionsError(error) ? error.details?.problems : void 0;
|
|
21034
|
+
const first = Array.isArray(problems) ? problems[0] : void 0;
|
|
21035
|
+
const count = Array.isArray(problems) && problems.length > 1 ? ` (+${problems.length - 1} more)` : "";
|
|
21036
|
+
return `the backlog doesn't validate: ${first ?? (error instanceof Error ? error.message : String(error))}${count}`;
|
|
21037
|
+
}
|
|
21038
|
+
function backlogCheck(repoRoot) {
|
|
21039
|
+
const file = backlogPath(repoRoot);
|
|
21040
|
+
if (!existsSync8(file))
|
|
21041
|
+
return { name: "backlog", status: "ok", detail: "no scout backlog yet" };
|
|
21042
|
+
try {
|
|
21043
|
+
const { opportunities } = readBacklog(file);
|
|
21044
|
+
return {
|
|
21045
|
+
name: "backlog",
|
|
21046
|
+
status: "ok",
|
|
21047
|
+
detail: `${opportunities.length} opportunit${opportunities.length === 1 ? "y" : "ies"} in ${file}`
|
|
21048
|
+
};
|
|
21049
|
+
} catch (error) {
|
|
21050
|
+
return {
|
|
21051
|
+
name: "backlog",
|
|
21052
|
+
status: "warn",
|
|
21053
|
+
detail: backlogProblem(error),
|
|
21054
|
+
fix: "`decide opportunities check` lists every problem; correct the file by hand or move it aside (decide never repairs it)"
|
|
21055
|
+
};
|
|
21056
|
+
}
|
|
21057
|
+
}
|
|
20521
21058
|
var CODEX_RULE = 'prefix_rule(pattern = ["decide"], decision = "allow")';
|
|
20522
21059
|
async function runDoctor(options) {
|
|
20523
21060
|
const { env } = options;
|
|
@@ -20564,12 +21101,23 @@ async function runDoctor(options) {
|
|
|
20564
21101
|
fix: "correct or remove these keys (docs/configuration.md lists every key)"
|
|
20565
21102
|
});
|
|
20566
21103
|
}
|
|
20567
|
-
|
|
21104
|
+
const credentials = join6(userConfigDir(env, options.home), "credentials");
|
|
21105
|
+
checks.push(config.apiKey && config.apiKeyQuoted ? {
|
|
21106
|
+
name: "key",
|
|
21107
|
+
status: "warn",
|
|
21108
|
+
detail: `OPENROUTER_API_KEY present (${config.apiKeySource}), but wrapped in quotes; they were removed`,
|
|
21109
|
+
fix: config.apiKeySource === "env" ? "set OPENROUTER_API_KEY without the surrounding quotes" : `remove the extra quotes around openrouter_api_key in ${credentials}`,
|
|
21110
|
+
advisory: true
|
|
21111
|
+
} : config.apiKey ? {
|
|
21112
|
+
name: "key",
|
|
21113
|
+
status: "ok",
|
|
21114
|
+
detail: `OPENROUTER_API_KEY present (${config.apiKeySource})`
|
|
21115
|
+
} : {
|
|
20568
21116
|
name: "key",
|
|
20569
21117
|
status: "warn",
|
|
20570
21118
|
detail: "no API key: only replay works",
|
|
20571
21119
|
// The file this shell would read: XDG_CONFIG_HOME moves it.
|
|
20572
|
-
fix: `set OPENROUTER_API_KEY, or write \`openrouter_api_key: <key>\` to ${
|
|
21120
|
+
fix: `set OPENROUTER_API_KEY, or write \`openrouter_api_key: <key>\` to ${credentials} (create its directory first; chmod 600)`
|
|
20573
21121
|
});
|
|
20574
21122
|
checks.push(config.egress.consent.granted ? { name: "consent", status: "ok", detail: `egress consent granted for ${config.repoRoot}` } : {
|
|
20575
21123
|
name: "consent",
|
|
@@ -20578,6 +21126,7 @@ async function runDoctor(options) {
|
|
|
20578
21126
|
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"
|
|
20579
21127
|
});
|
|
20580
21128
|
checks.push(routeCheck(config.route));
|
|
21129
|
+
checks.push(backlogCheck(config.repoRoot));
|
|
20581
21130
|
let reach;
|
|
20582
21131
|
try {
|
|
20583
21132
|
reach = await ping({
|
|
@@ -20622,8 +21171,8 @@ function pathCheck(harness, env) {
|
|
|
20622
21171
|
return {
|
|
20623
21172
|
name: "path",
|
|
20624
21173
|
status: "warn",
|
|
20625
|
-
detail: "decide is not on PATH, so the
|
|
20626
|
-
fix:
|
|
21174
|
+
detail: "decide is not on PATH, so the System 1 plugin's bin/ is not loaded",
|
|
21175
|
+
fix: `npm i -g ${CLI_PACKAGE}@${VERSION} (also puts decide in your own terminal), or install the System 1 plugin from its marketplace`
|
|
20627
21176
|
};
|
|
20628
21177
|
}
|
|
20629
21178
|
return {
|
|
@@ -20687,10 +21236,10 @@ function which(name, path) {
|
|
|
20687
21236
|
for (const dir of path.split(delimiter)) {
|
|
20688
21237
|
if (!dir)
|
|
20689
21238
|
continue;
|
|
20690
|
-
const candidate =
|
|
21239
|
+
const candidate = join6(dir, name);
|
|
20691
21240
|
try {
|
|
20692
21241
|
accessSync(candidate, constants.X_OK);
|
|
20693
|
-
if (
|
|
21242
|
+
if (statSync5(candidate).isFile())
|
|
20694
21243
|
return candidate;
|
|
20695
21244
|
} catch {
|
|
20696
21245
|
}
|
|
@@ -20698,6 +21247,72 @@ function which(name, path) {
|
|
|
20698
21247
|
return void 0;
|
|
20699
21248
|
}
|
|
20700
21249
|
|
|
21250
|
+
// ../core/dist/tools/opportunities.js
|
|
21251
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
21252
|
+
function runOpportunitiesAdd(ctx, rawInput, now = () => /* @__PURE__ */ new Date()) {
|
|
21253
|
+
const input = checkInput("opportunities-add", rawInput);
|
|
21254
|
+
const file = backlogPath(ctx.config.repoRoot);
|
|
21255
|
+
const merged2 = withBacklogLock(file, () => {
|
|
21256
|
+
const result = mergeCandidates(readBacklog(file), input.candidates, now().toISOString());
|
|
21257
|
+
writeBacklog(file, result.backlog);
|
|
21258
|
+
return result;
|
|
21259
|
+
});
|
|
21260
|
+
return {
|
|
21261
|
+
file,
|
|
21262
|
+
added: merged2.added,
|
|
21263
|
+
updated: merged2.updated,
|
|
21264
|
+
staled: merged2.staled,
|
|
21265
|
+
total: merged2.backlog.opportunities.length
|
|
21266
|
+
};
|
|
21267
|
+
}
|
|
21268
|
+
function runOpportunitiesList(ctx, rawInput = {}) {
|
|
21269
|
+
const input = checkInput("opportunities-list", rawInput);
|
|
21270
|
+
const filters = (input.keep ?? []).map(parseRecordFilter);
|
|
21271
|
+
const sort = parseRecordSort(input.sort ?? "projected:desc");
|
|
21272
|
+
assertFields(input.fields);
|
|
21273
|
+
const file = backlogPath(ctx.config.repoRoot);
|
|
21274
|
+
const { opportunities } = readBacklog(file);
|
|
21275
|
+
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));
|
|
21276
|
+
const shown = input.limit === void 0 ? matched : matched.slice(0, input.limit);
|
|
21277
|
+
return {
|
|
21278
|
+
file,
|
|
21279
|
+
total: opportunities.length,
|
|
21280
|
+
matched: matched.length,
|
|
21281
|
+
basis: "projected",
|
|
21282
|
+
opportunities: shown.map((o) => pick2(o, input.fields))
|
|
21283
|
+
};
|
|
21284
|
+
}
|
|
21285
|
+
function runOpportunitiesCheck(ctx, rawInput = {}) {
|
|
21286
|
+
checkInput("opportunities-check", rawInput);
|
|
21287
|
+
const file = backlogPath(ctx.config.repoRoot);
|
|
21288
|
+
const backlog = readBacklog(file);
|
|
21289
|
+
const byStatus = {};
|
|
21290
|
+
for (const o of backlog.opportunities)
|
|
21291
|
+
byStatus[o.status] = (byStatus[o.status] ?? 0) + 1;
|
|
21292
|
+
return { file, exists: existsSync9(file), entries: backlog.opportunities.length, byStatus };
|
|
21293
|
+
}
|
|
21294
|
+
function compare(a, b, direction) {
|
|
21295
|
+
if (a === void 0 || b === void 0)
|
|
21296
|
+
return a === b ? 0 : a === void 0 ? 1 : -1;
|
|
21297
|
+
const order = typeof a === "number" && typeof b === "number" ? a - b : String(a).localeCompare(String(b));
|
|
21298
|
+
return direction === "asc" ? order : -order;
|
|
21299
|
+
}
|
|
21300
|
+
function pick2(o, fields) {
|
|
21301
|
+
if (!fields)
|
|
21302
|
+
return o;
|
|
21303
|
+
const out = { id: o.id };
|
|
21304
|
+
for (const f of fields)
|
|
21305
|
+
if (Object.hasOwn(o, f))
|
|
21306
|
+
out[f] = o[f];
|
|
21307
|
+
return out;
|
|
21308
|
+
}
|
|
21309
|
+
function assertFields(fields = []) {
|
|
21310
|
+
const unknown = fields.filter((f) => !ENTRY_FIELDS.includes(f));
|
|
21311
|
+
if (unknown.length) {
|
|
21312
|
+
throw new DecisionsError("invalid-request", `--fields: no field ${unknown.join(", ")}. Fields: ${ENTRY_FIELDS.join(", ")}`);
|
|
21313
|
+
}
|
|
21314
|
+
}
|
|
21315
|
+
|
|
20701
21316
|
// ../core/dist/tools/route.js
|
|
20702
21317
|
function runRoute(options, rawInput) {
|
|
20703
21318
|
const problems = checkRouteInput(rawInput);
|
|
@@ -20725,10 +21340,132 @@ function checkRouteInput(input) {
|
|
|
20725
21340
|
];
|
|
20726
21341
|
}
|
|
20727
21342
|
|
|
21343
|
+
// ../core/dist/spec/lint.js
|
|
21344
|
+
var LINT_SEVERITY = {
|
|
21345
|
+
"no-way-out": "warning",
|
|
21346
|
+
"abstract-levels": "warning",
|
|
21347
|
+
"unsupported-task": "warning",
|
|
21348
|
+
"merged-question": "warning",
|
|
21349
|
+
"unjustified-threshold": "warning",
|
|
21350
|
+
"unknown-threshold": "error"
|
|
21351
|
+
};
|
|
21352
|
+
var WAY_OUT_KEY = /^(none|other|others|unclear|unknown|neither|na|nomatch|unsure|mixed|else|nothing|irrelevant|unrelated)$/i;
|
|
21353
|
+
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;
|
|
21354
|
+
var UNSUPPORTED = [
|
|
21355
|
+
{
|
|
21356
|
+
kind: "counting or arithmetic",
|
|
21357
|
+
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
|
|
21358
|
+
},
|
|
21359
|
+
{
|
|
21360
|
+
kind: "dates",
|
|
21361
|
+
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
|
|
21362
|
+
},
|
|
21363
|
+
{
|
|
21364
|
+
kind: "images",
|
|
21365
|
+
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
|
|
21366
|
+
},
|
|
21367
|
+
{
|
|
21368
|
+
kind: "an exact fact to read or run, not judge",
|
|
21369
|
+
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
|
|
21370
|
+
}
|
|
21371
|
+
];
|
|
21372
|
+
function lintQuestions(questions) {
|
|
21373
|
+
const out = [];
|
|
21374
|
+
for (const [name, q] of Object.entries(questions)) {
|
|
21375
|
+
out.push(...lintQuestion(name, q));
|
|
21376
|
+
}
|
|
21377
|
+
return out;
|
|
21378
|
+
}
|
|
21379
|
+
function lintSpec(spec) {
|
|
21380
|
+
const out = lintQuestions(spec.questions);
|
|
21381
|
+
const thresholds = spec.policy?.thresholds ?? {};
|
|
21382
|
+
for (const name of Object.keys(thresholds)) {
|
|
21383
|
+
if (!(name in spec.questions)) {
|
|
21384
|
+
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.`));
|
|
21385
|
+
}
|
|
21386
|
+
}
|
|
21387
|
+
const justified = new Set(Object.keys(thresholds));
|
|
21388
|
+
const reported = /* @__PURE__ */ new Set();
|
|
21389
|
+
const filters = [
|
|
21390
|
+
...(spec.keep ?? []).map((text) => ({ text, list: "keep" })),
|
|
21391
|
+
...(spec.keepAny ?? []).map((text) => ({ text, list: "keepAny" }))
|
|
21392
|
+
];
|
|
21393
|
+
for (const { text, list } of filters) {
|
|
21394
|
+
const { question, op } = parseFilter(text, spec.questions);
|
|
21395
|
+
if (!NUMERIC.has(op))
|
|
21396
|
+
continue;
|
|
21397
|
+
if (justified.has(question) || reported.has(question))
|
|
21398
|
+
continue;
|
|
21399
|
+
reported.add(question);
|
|
21400
|
+
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));
|
|
21401
|
+
}
|
|
21402
|
+
return out;
|
|
21403
|
+
}
|
|
21404
|
+
var NUMERIC = /* @__PURE__ */ new Set([">=", ">", "<=", "<"]);
|
|
21405
|
+
function lintQuestion(name, q) {
|
|
21406
|
+
const out = [];
|
|
21407
|
+
if (q.type === "choice") {
|
|
21408
|
+
const options = Object.entries(q.criteria);
|
|
21409
|
+
const wayOut = ([key, desc]) => key.split(/[_\-\s/]+/).some((t) => WAY_OUT_KEY.test(t)) || WAY_OUT_TEXT.test(desc);
|
|
21410
|
+
if (!options.some(wayOut)) {
|
|
21411
|
+
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));
|
|
21412
|
+
}
|
|
21413
|
+
}
|
|
21414
|
+
if (q.type === "score" && q.criteria.every((level) => words(level) <= 2)) {
|
|
21415
|
+
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));
|
|
21416
|
+
}
|
|
21417
|
+
out.push(...unsupported([q.instructions, ...criteriaTexts(q).map(unquoted)].join("\n"), name));
|
|
21418
|
+
if (merged(q.instructions)) {
|
|
21419
|
+
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));
|
|
21420
|
+
}
|
|
21421
|
+
return dedupe(out);
|
|
21422
|
+
}
|
|
21423
|
+
function unsupported(text, question) {
|
|
21424
|
+
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));
|
|
21425
|
+
}
|
|
21426
|
+
function merged(instructions) {
|
|
21427
|
+
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));
|
|
21428
|
+
return claims.length > 1 || /\band\/or\b/i.test(instructions) || /\beither\b.+\bor\b/i.test(instructions);
|
|
21429
|
+
}
|
|
21430
|
+
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;
|
|
21431
|
+
function unquoted(text) {
|
|
21432
|
+
return text.replace(/"[^"\n]*"|“[^”\n]*”/g, '""');
|
|
21433
|
+
}
|
|
21434
|
+
function criteriaTexts(q) {
|
|
21435
|
+
if (q.type === "noul")
|
|
21436
|
+
return q.criteria ? Object.values(q.criteria) : [];
|
|
21437
|
+
if (q.type === "choice")
|
|
21438
|
+
return Object.values(q.criteria);
|
|
21439
|
+
return q.criteria;
|
|
21440
|
+
}
|
|
21441
|
+
function words(text) {
|
|
21442
|
+
return text.trim().split(/\s+/).filter(Boolean).length;
|
|
21443
|
+
}
|
|
21444
|
+
function finding(check, message, fix, question) {
|
|
21445
|
+
return {
|
|
21446
|
+
check,
|
|
21447
|
+
severity: LINT_SEVERITY[check],
|
|
21448
|
+
...question ? { question } : {},
|
|
21449
|
+
message,
|
|
21450
|
+
fix
|
|
21451
|
+
};
|
|
21452
|
+
}
|
|
21453
|
+
function dedupe(findings) {
|
|
21454
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21455
|
+
return findings.filter((f) => {
|
|
21456
|
+
const key = `${f.check}\0${f.question ?? ""}\0${f.message}`;
|
|
21457
|
+
if (seen.has(key))
|
|
21458
|
+
return false;
|
|
21459
|
+
seen.add(key);
|
|
21460
|
+
return true;
|
|
21461
|
+
});
|
|
21462
|
+
}
|
|
21463
|
+
|
|
20728
21464
|
// ../core/dist/tools/spec-check.js
|
|
20729
21465
|
async function runSpecCheck(ctx, rawInput) {
|
|
20730
21466
|
const input = checkInput("spec-check", rawInput);
|
|
20731
21467
|
const spec = loadSpec(input.spec, ctx.specDirs, ctx.cwd);
|
|
21468
|
+
const lint = lintSpec(spec);
|
|
20732
21469
|
const examples = spec.examples ?? [];
|
|
20733
21470
|
if (examples.length === 0) {
|
|
20734
21471
|
throw new DecisionsError("invalid-request", `Spec "${spec.name}" has no examples to check. Add some under examples: (id, state or file, expect).`);
|
|
@@ -20826,7 +21563,8 @@ async function runSpecCheck(ctx, rawInput) {
|
|
|
20826
21563
|
counts: counts2,
|
|
20827
21564
|
examples: results,
|
|
20828
21565
|
usage: sumUsage(usages),
|
|
20829
|
-
skipped: summariseSkipped(prepared.flatMap((p) => p.prep?.skipped ?? []))
|
|
21566
|
+
skipped: summariseSkipped(prepared.flatMap((p) => p.prep?.skipped ?? [])),
|
|
21567
|
+
lint
|
|
20830
21568
|
};
|
|
20831
21569
|
}
|
|
20832
21570
|
function judge(id, answers, flat, expect) {
|
|
@@ -20858,6 +21596,43 @@ function judge(id, answers, flat, expect) {
|
|
|
20858
21596
|
};
|
|
20859
21597
|
}
|
|
20860
21598
|
|
|
21599
|
+
// ../core/dist/tools/spec-lint.js
|
|
21600
|
+
import { basename as basename3, extname as extname2 } from "node:path";
|
|
21601
|
+
function runSpecLint(ctx, rawInput = {}) {
|
|
21602
|
+
const input = checkInput("spec-lint", rawInput);
|
|
21603
|
+
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 } : (
|
|
21604
|
+
// Loaded by path, so keep the layer it was listed from.
|
|
21605
|
+
{ ...entry(loadSpec(s.file, ctx.specDirs, ctx.cwd)), origin: s.origin }
|
|
21606
|
+
));
|
|
21607
|
+
const all = specs.flatMap((s) => s.findings);
|
|
21608
|
+
const counts2 = {
|
|
21609
|
+
specs: specs.length,
|
|
21610
|
+
errors: all.filter((f) => f.severity === "error").length,
|
|
21611
|
+
warnings: all.filter((f) => f.severity === "warning").length,
|
|
21612
|
+
invalid: specs.filter((s) => s.invalid).length
|
|
21613
|
+
};
|
|
21614
|
+
return { specs, counts: counts2, passed: counts2.errors === 0 && counts2.invalid === 0 };
|
|
21615
|
+
}
|
|
21616
|
+
function named(ctx, ref) {
|
|
21617
|
+
try {
|
|
21618
|
+
return entry(loadSpec(ref, ctx.specDirs, ctx.cwd));
|
|
21619
|
+
} catch (error) {
|
|
21620
|
+
const file = isDecisionsError(error) ? error.details?.file : void 0;
|
|
21621
|
+
if (typeof file !== "string")
|
|
21622
|
+
throw error;
|
|
21623
|
+
return {
|
|
21624
|
+
name: basename3(file, extname2(file)),
|
|
21625
|
+
file,
|
|
21626
|
+
origin: "path",
|
|
21627
|
+
findings: [],
|
|
21628
|
+
invalid: error.message
|
|
21629
|
+
};
|
|
21630
|
+
}
|
|
21631
|
+
}
|
|
21632
|
+
function entry(spec) {
|
|
21633
|
+
return { name: spec.name, file: spec.file, origin: spec.origin, findings: lintSpec(spec) };
|
|
21634
|
+
}
|
|
21635
|
+
|
|
20861
21636
|
// ../core/dist/tools/usage.js
|
|
20862
21637
|
function runUsage(ctx, rawInput = {}) {
|
|
20863
21638
|
const input = checkInput("usage", rawInput);
|
|
@@ -20890,10 +21665,17 @@ function resolveConnection(env = process.env) {
|
|
|
20890
21665
|
return {
|
|
20891
21666
|
endpoint: nonEmpty3(env.SYSTEM1_ENDPOINT) ?? DEFAULT_ENDPOINT,
|
|
20892
21667
|
model: nonEmpty3(env.SYSTEM1_MODEL) ?? DEFAULT_MODEL,
|
|
20893
|
-
apiKey:
|
|
21668
|
+
apiKey: envKey(env),
|
|
20894
21669
|
replay: /^(1|true|yes)$/i.test(env.SYSTEM1_REPLAY ?? "")
|
|
20895
21670
|
};
|
|
20896
21671
|
}
|
|
21672
|
+
function envKey(env) {
|
|
21673
|
+
try {
|
|
21674
|
+
return unquoteKey(env.OPENROUTER_API_KEY)?.value;
|
|
21675
|
+
} catch {
|
|
21676
|
+
return void 0;
|
|
21677
|
+
}
|
|
21678
|
+
}
|
|
20897
21679
|
function nonEmpty3(value) {
|
|
20898
21680
|
return value === void 0 || value.trim() === "" ? void 0 : value.trim();
|
|
20899
21681
|
}
|
|
@@ -20918,8 +21700,12 @@ export {
|
|
|
20918
21700
|
runMany,
|
|
20919
21701
|
runAsk,
|
|
20920
21702
|
runDoctor,
|
|
21703
|
+
runOpportunitiesAdd,
|
|
21704
|
+
runOpportunitiesList,
|
|
21705
|
+
runOpportunitiesCheck,
|
|
20921
21706
|
runRoute,
|
|
20922
21707
|
runSpecCheck,
|
|
21708
|
+
runSpecLint,
|
|
20923
21709
|
runUsage,
|
|
20924
21710
|
resolveConnection
|
|
20925
21711
|
};
|