@wrongstack/cli 1.0.17 → 1.0.24
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/{acp-INCNOH42.js → acp-AJWSKWE5.js} +18 -7
- package/dist/arg-parser.d.ts +1 -0
- package/dist/{auth-YXK3EDUL.js → auth-GRBZ7Y3D.js} +61 -15
- package/dist/auth-menu/direct.d.ts +1 -0
- package/dist/auth-menu/local.d.ts +2 -0
- package/dist/auth-menu/oauth-menu.d.ts +3 -1
- package/dist/auth-menu/panel-service.d.ts +20 -0
- package/dist/{bench-YBU6BVQP.js → bench-N5K7Z5O3.js} +2 -2
- package/dist/boot/config-backup-recovery.d.ts +2 -1
- package/dist/boot/dispatch-webui.d.ts +11 -0
- package/dist/boot/short-circuit-desktop.d.ts +1 -0
- package/dist/boot/tui-project-picker-callback.d.ts +1 -1
- package/dist/boot/tui-settings-adapter.d.ts +15 -2
- package/dist/{boot-auto-thin-MANHOYHE.js → boot-auto-thin-6Q56FY3F.js} +3 -2
- package/dist/{chunk-6WRKAACA.js → chunk-2BKFWIER.js} +4 -2
- package/dist/{chunk-JY52HWL6.js → chunk-2EBVTYJR.js} +2 -2
- package/dist/chunk-3OCUS3VN.js +46 -0
- package/dist/chunk-424ORSE7.js +56 -0
- package/dist/{chunk-KEPABQYT.js → chunk-5SEYGOCH.js} +8 -82
- package/dist/chunk-BBHWW6LR.js +549 -0
- package/dist/{chunk-CJTBLTS4.js → chunk-BLD5DSVA.js} +4 -3
- package/dist/{chunk-K5PAL6KR.js → chunk-DRM7PSW7.js} +75 -29
- package/dist/{chunk-XJ7K67PP.js → chunk-JF2MULWF.js} +17 -10
- package/dist/{chunk-SIS37L3W.js → chunk-L2RVEQWO.js} +211 -193
- package/dist/{chunk-RDHFXOJM.js → chunk-LFMVHT3R.js} +106 -57
- package/dist/chunk-LUMETJFO.js +171 -0
- package/dist/{chunk-C3Z4N6A6.js → chunk-MHEBWVXL.js} +6 -2
- package/dist/{chunk-VGER3HYZ.js → chunk-PGKJ2OQP.js} +5 -5
- package/dist/{chunk-672UXYL3.js → chunk-QBBF7GIK.js} +2 -2
- package/dist/{chunk-DE7BZDNK.js → chunk-QFPQXKCG.js} +11 -3
- package/dist/chunk-R4QGL6GB.js +10 -0
- package/dist/chunk-S4FFHDJG.js +93 -0
- package/dist/chunk-SFBFYIOW.js +271 -0
- package/dist/{chunk-JX6KL7OF.js → chunk-SKPDE62W.js} +4 -3
- package/dist/{chunk-RMQNCHGO.js → chunk-U46QR4WR.js} +5 -9
- package/dist/{chunk-B4JLSXZB.js → chunk-WSTVGC2C.js} +73 -45
- package/dist/chunk-YBUCXXYZ.js +36 -0
- package/dist/{chunk-2VMJ3E5Z.js → chunk-YTEOAWOJ.js} +6 -2
- package/dist/{chunk-GPPRNAKW.js → chunk-ZMX5HRLI.js} +2 -2
- package/dist/{chunk-YLQEGYV4.js → chunk-ZZRSG7VK.js} +14 -4
- package/dist/{cli-context-4PTWEK52.js → cli-context-34Q5IJRA.js} +121 -89
- package/dist/cli-entry-point.d.ts +2 -0
- package/dist/{cli-main-DQ2GBY7M.js → cli-main-AELJDBGU.js} +1471 -1293
- package/dist/config-doctor.d.ts +10 -2
- package/dist/{config-transfer-2YF3CRTN.js → config-transfer-RUWLDCNK.js} +34 -13
- package/dist/{diag-doctor-ANPRUYFM.js → diag-doctor-WIFJH3XH.js} +2 -2
- package/dist/execute-deps.d.ts +2 -1
- package/dist/{execution-A6WJLSOC.js → execution-PYC5FCS4.js} +303 -256
- package/dist/{hq-NU75W3SE.js → hq-JTUTFI4L.js} +274 -258
- package/dist/hq-command-controller.d.ts +1 -1
- package/dist/hq-server/project-root.d.ts +35 -1
- package/dist/{hq-server-M2JWVW4D.js → hq-server-PC7GZMPT.js} +2 -2
- package/dist/{hq-service-L5INQFM4.js → hq-service-NW56MUP2.js} +11 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -12
- package/dist/kanban-supervisor-67ECABTD.js +7 -0
- package/dist/live-settings-input.d.ts +7 -0
- package/dist/{mailbox-serve-3GFOK2I7.js → mailbox-serve-WDOCH6KR.js} +3 -3
- package/dist/{mcp-DC5WSBOO.js → mcp-LBS45HWZ.js} +4 -4
- package/dist/modeldiag-36OGAL5C.js +828 -0
- package/dist/{per-subcommand-help-DHTYU524.js → per-subcommand-help-XE77MTGJ.js} +3 -3
- package/dist/permission-prompt-mirror.d.ts +1 -1
- package/dist/permission-prompt.d.ts +2 -2
- package/dist/plugin-config.d.ts +12 -0
- package/dist/plugin-management-types.d.ts +31 -0
- package/dist/plugin-management.d.ts +2 -30
- package/dist/plugin-package-install.d.ts +3 -0
- package/dist/{plugin-usage-M6KET4OZ.js → plugin-usage-S5LVGMBW.js} +6 -5
- package/dist/{plugins-WJLW6ULV.js → plugins-RHBIVLI7.js} +4 -3
- package/dist/provider-config-utils.d.ts +1 -6
- package/dist/{providers-models-LFNZ5WI7.js → providers-models-2CFG46RD.js} +41 -31
- package/dist/release-asset-download.d.ts +3 -0
- package/dist/services/dispatch-classifier.d.ts +39 -5
- package/dist/{sessions-config-UL45XKX7.js → sessions-config-IQUH2GXT.js} +13 -5
- package/dist/settings-menu.d.ts +2 -0
- package/dist/short-circuit-flags-SDT53OML.js +10 -0
- package/dist/{simpleui-dist-CXSIDAXZ.js → simpleui-dist-7BQISSEP.js} +7 -3
- package/dist/skill-suggest-L2EVWB6R.js +290 -0
- package/dist/slash-commands/effort.d.ts +1 -1
- package/dist/slash-commands/fallback.d.ts +1 -1
- package/dist/slash-commands/gitid.d.ts +1 -1
- package/dist/slash-commands/jev.d.ts +4 -0
- package/dist/slash-commands/openai-quota.d.ts +15 -0
- package/dist/slash-commands/sdd-task-mutations.d.ts +4 -0
- package/dist/slash-commands/settings-keys.d.ts +2 -0
- package/dist/slash-commands/settings-reset.d.ts +6 -0
- package/dist/standalone-update-MW47J3AK.js +164 -0
- package/dist/standalone-update.d.ts +19 -0
- package/dist/subcommands/handlers/hq-alerts.d.ts +3 -0
- package/dist/subcommands/handlers/hq-audit.d.ts +46 -0
- package/dist/subcommands/handlers/hq-data-dir.d.ts +13 -0
- package/dist/subcommands/handlers/skill-suggest.d.ts +21 -0
- package/dist/subcommands/handlers/typesafe-brain-replay.d.ts +26 -0
- package/dist/subcommands/handlers/typesafe-judgment-check.d.ts +3 -0
- package/dist/subcommands/handlers/typesafe-topic-replay.d.ts +19 -0
- package/dist/subcommands/handlers/typesafe-triage-replay.d.ts +18 -0
- package/dist/subcommands/handlers/typesafe.d.ts +23 -0
- package/dist/{subcommands-ERDDIJ3L.js → subcommands-ZTSIN7LG.js} +2 -2
- package/dist/typesafe-HVEVPA6R.js +316 -0
- package/dist/typesafe-brain-replay-C5276FVR.js +222 -0
- package/dist/typesafe-judgment-check-GGYYV5UF.js +29 -0
- package/dist/typesafe-topic-replay-4W73BOLV.js +218 -0
- package/dist/typesafe-triage-replay-FGAW7QM7.js +215 -0
- package/dist/{update-5DPB4YD4.js → update-7XIOEXTD.js} +4 -3
- package/dist/{update-check-BS3DHTSI.js → update-check-2POMBHFB.js} +3 -2
- package/dist/update-check.d.ts +1 -1
- package/dist/version-help-WZ623NWM.js +10 -0
- package/dist/version.d.ts +3 -2
- package/dist/webui-client-observability.d.ts +7 -0
- package/dist/{webui-server-MRPGTTKL.js → webui-server-L2SICPTB.js} +158 -94
- package/dist/webui-server-options.d.ts +11 -0
- package/dist/webui-server.d.ts +15 -1
- package/dist/wiring/domain-glossary.d.ts +5 -4
- package/dist/wiring/plugins.d.ts +1 -1
- package/dist/wiring/provider-persisters.d.ts +24 -0
- package/dist/wiring/provider-runtime.d.ts +9 -1
- package/dist/wiring/runtime-dispatch-state.d.ts +3 -1
- package/package.json +28 -28
- package/dist/chunk-DR2OXU46.js +0 -174
- package/dist/chunk-JACOMTIH.js +0 -275
- package/dist/chunk-M4W3QLQW.js +0 -54
- package/dist/chunk-XJXDOF63.js +0 -28
- package/dist/modeldiag-CJSFHZ3J.js +0 -1356
- package/dist/short-circuit-flags-SJQBJ33I.js +0 -10
- package/dist/version-help-WXI42PZ3.js +0 -10
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
|
|
2
|
+
// src/subcommands/handlers/skill-suggest.ts
|
|
3
|
+
import * as fs from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
buildSuggesterFromConfig,
|
|
7
|
+
parseEvalJsonl,
|
|
8
|
+
scoreSuggestions,
|
|
9
|
+
sweepThresholds,
|
|
10
|
+
unknownGoldLabels
|
|
11
|
+
} from "@wrongstack/core/skills";
|
|
12
|
+
import { estimateTypeSafeCostUsd } from "@wrongstack/core/typesafe";
|
|
13
|
+
import { color } from "@wrongstack/core/utils";
|
|
14
|
+
var SWEEP_STEPS = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6];
|
|
15
|
+
var EVAL_WORKERS = 4;
|
|
16
|
+
var USAGE = [
|
|
17
|
+
"Usage:",
|
|
18
|
+
' wstack skill-suggest "<request>" preview one request',
|
|
19
|
+
" wstack skill-suggest --eval <file.jsonl> score a labeled set",
|
|
20
|
+
" wstack skill-suggest --eval <file.jsonl> --sweep \u2026and sweep thresholds",
|
|
21
|
+
"",
|
|
22
|
+
"Eval file: one JSON object per line.",
|
|
23
|
+
' {"text": "cut a release branch for 1.2.0", "gold": "git-flow"}',
|
|
24
|
+
' {"text": "explain what a monad is"} // nothing covers it',
|
|
25
|
+
"",
|
|
26
|
+
"Both modes send request text to the configured TypeSafe/OpenRouter route.",
|
|
27
|
+
"Configure its key with `wstack typesafe login` and verify with `wstack typesafe test`."
|
|
28
|
+
].join("\n");
|
|
29
|
+
var skillSuggestCmd = async (args, deps) => {
|
|
30
|
+
const write = (line) => deps.renderer.write(`${line}
|
|
31
|
+
`);
|
|
32
|
+
const flags = deps.flags ?? {};
|
|
33
|
+
const evalFile = typeof flags["eval"] === "string" ? flags["eval"] : void 0;
|
|
34
|
+
const sweep = flags["sweep"] === true || flags["sweep"] === "true";
|
|
35
|
+
const positional = args.filter((a) => !a.startsWith("-"));
|
|
36
|
+
if (flags["help"] === true || flags["h"] === true) {
|
|
37
|
+
write(USAGE);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
if (!evalFile && positional.length === 0) {
|
|
41
|
+
write(USAGE);
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
const built = buildSuggesterFromConfig({
|
|
45
|
+
config: deps.config,
|
|
46
|
+
skillLoader: deps.skillLoader
|
|
47
|
+
});
|
|
48
|
+
if ("error" in built) {
|
|
49
|
+
write(color.red(`skill-suggest: ${built.error}`));
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
const { suggester } = built;
|
|
53
|
+
if (evalFile) {
|
|
54
|
+
return runEval({
|
|
55
|
+
file: path.resolve(deps.cwd, evalFile),
|
|
56
|
+
sweep,
|
|
57
|
+
suggester,
|
|
58
|
+
rosterNames: (await deps.skillLoader?.list())?.map((s) => s.name) ?? [],
|
|
59
|
+
write
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const request = positional.join(" ").trim();
|
|
63
|
+
if (!request) {
|
|
64
|
+
write(color.red("skill-suggest: give a request to preview, or --eval <file>"));
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
return runPreview({ request, suggester, write, config: deps.config });
|
|
68
|
+
};
|
|
69
|
+
async function runPreview(input) {
|
|
70
|
+
const { write } = input;
|
|
71
|
+
const gateThreshold = input.config.skills?.suggest?.gateThreshold ?? 0.3;
|
|
72
|
+
const fitsThreshold = input.config.skills?.suggest?.fitsThreshold ?? 0.3;
|
|
73
|
+
const started = Date.now();
|
|
74
|
+
const trace = await input.suggester.explain(input.request);
|
|
75
|
+
const elapsed = Date.now() - started;
|
|
76
|
+
write("");
|
|
77
|
+
write(color.dim(` "${truncate(input.request, 72)}"`));
|
|
78
|
+
write("");
|
|
79
|
+
if (trace.stop === "error") {
|
|
80
|
+
write(color.red(" the suggester failed \u2014 no answer to show"));
|
|
81
|
+
write(color.dim(" check the API key, the endpoint, and network reachability"));
|
|
82
|
+
return 1;
|
|
83
|
+
}
|
|
84
|
+
if (trace.stop === "roster-too-small") {
|
|
85
|
+
write(color.yellow(" roster has fewer than 2 visible skills \u2014 nothing to choose between"));
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
write(
|
|
89
|
+
` ${label("gate")} ${bar(trace.gate)} ${pct(trace.gate)} ${verdictWord(trace.gate >= gateThreshold, gateThreshold)}`
|
|
90
|
+
);
|
|
91
|
+
for (const [key, value] of Object.entries(trace.gateValues)) {
|
|
92
|
+
const inverted = key === "prose_suffices";
|
|
93
|
+
write(
|
|
94
|
+
color.dim(` ${key.padEnd(34)} ${pct(value)}${inverted ? " (counts inverted)" : ""}`)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
write("");
|
|
98
|
+
if (trace.ranked.length > 0) {
|
|
99
|
+
write(` ${label("pass 1 \u2014 ranked")}`);
|
|
100
|
+
for (const entry of trace.ranked.slice(0, 5)) {
|
|
101
|
+
const shortlisted = trace.shortlist.includes(entry.name);
|
|
102
|
+
const line = ` ${pct(entry.probability).padStart(4)} ${entry.name}`;
|
|
103
|
+
write(shortlisted ? line : color.dim(line));
|
|
104
|
+
}
|
|
105
|
+
write("");
|
|
106
|
+
}
|
|
107
|
+
if (trace.shortlist.length > 0 && Object.keys(trace.fits).length > 0) {
|
|
108
|
+
write(` ${label("pass 2 \u2014 fits")}`);
|
|
109
|
+
for (const name of trace.shortlist) {
|
|
110
|
+
const value = trace.fits[name];
|
|
111
|
+
const marker = name === trace.winner ? color.cyan(" \u2190 choice") : "";
|
|
112
|
+
write(` ${(value === void 0 ? "\u2014" : pct(value)).padStart(4)} ${name}${marker}`);
|
|
113
|
+
}
|
|
114
|
+
write("");
|
|
115
|
+
}
|
|
116
|
+
const suggestion = trace.suggestion;
|
|
117
|
+
if (suggestion) {
|
|
118
|
+
write(` ${color.green("suggests")} ${suggestion.name}`);
|
|
119
|
+
} else {
|
|
120
|
+
write(
|
|
121
|
+
` ${color.yellow("suggests nothing")} ${color.dim(`(${stopReason(trace, gateThreshold, fitsThreshold)})`)}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
write(
|
|
125
|
+
color.dim(
|
|
126
|
+
` ${trace.requests} request(s), ${elapsed}ms, ${trace.inputTokens} input tokens (~$${estimateTypeSafeCostUsd(trace.inputTokens).toFixed(6)})` + (trace.models.length > 0 ? ` \xB7 answered by ${trace.models.join(", ")}` : "")
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
write("");
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
function stopReason(trace, gateThreshold, fitsThreshold) {
|
|
133
|
+
switch (trace.stop) {
|
|
134
|
+
case "gate":
|
|
135
|
+
return `gate ${pct(trace.gate)} below ${pct(gateThreshold)}`;
|
|
136
|
+
case "fits": {
|
|
137
|
+
const best = Math.max(...Object.values(trace.fits), 0);
|
|
138
|
+
return `best fit ${pct(best)} below ${pct(fitsThreshold)}`;
|
|
139
|
+
}
|
|
140
|
+
case "wide-failed":
|
|
141
|
+
case "rerank-failed":
|
|
142
|
+
return "the model returned no usable answer for that pass";
|
|
143
|
+
default:
|
|
144
|
+
return trace.stop;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function runEval(input) {
|
|
148
|
+
const { write } = input;
|
|
149
|
+
let source;
|
|
150
|
+
try {
|
|
151
|
+
source = await fs.readFile(input.file, "utf8");
|
|
152
|
+
} catch {
|
|
153
|
+
write(color.red(`skill-suggest: cannot read ${input.file}`));
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
const { requests, errors } = parseEvalJsonl(source);
|
|
157
|
+
for (const error of errors) {
|
|
158
|
+
write(color.yellow(` line ${error.line}: ${error.reason}`));
|
|
159
|
+
}
|
|
160
|
+
if (requests.length === 0) {
|
|
161
|
+
write(color.red("skill-suggest: no usable cases in that file"));
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
const unknown = unknownGoldLabels(requests, input.rosterNames);
|
|
165
|
+
if (unknown.length > 0) {
|
|
166
|
+
write(color.yellow(` gold labels not in this roster: ${unknown.join(", ")}`));
|
|
167
|
+
write(color.yellow(" those cases can never be scored correct \u2014 fix or remove them"));
|
|
168
|
+
write("");
|
|
169
|
+
}
|
|
170
|
+
const covered = requests.filter((r) => r.gold).length;
|
|
171
|
+
write(
|
|
172
|
+
color.dim(
|
|
173
|
+
` ${requests.length} cases (${covered} covered, ${requests.length - covered} uncovered) \u2192 up to ${requests.length * 2} TypeSafe requests`
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
write("");
|
|
177
|
+
const traces = await mapWithConcurrency(requests, EVAL_WORKERS, async (request) => ({
|
|
178
|
+
request,
|
|
179
|
+
// A sweep has to know what pass 2 WOULD have said below the configured
|
|
180
|
+
// gate, and a trace that stopped at the gate cannot answer that.
|
|
181
|
+
trace: await input.suggester.explain(request.text, void 0, { alwaysRerank: input.sweep })
|
|
182
|
+
}));
|
|
183
|
+
const inputTokens = traces.reduce((sum, row) => sum + row.trace.inputTokens, 0);
|
|
184
|
+
const models = [...new Set(traces.flatMap((row) => row.trace.models))];
|
|
185
|
+
write(
|
|
186
|
+
color.dim(
|
|
187
|
+
` spent ${inputTokens} input tokens (~$${estimateTypeSafeCostUsd(inputTokens).toFixed(4)})` + (models.length > 0 ? ` \xB7 answered by ${models.join(", ")}` : "")
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
if (models.length > 1) {
|
|
191
|
+
write(
|
|
192
|
+
color.yellow(
|
|
193
|
+
" Two model versions answered this run \u2014 the alias moved mid-run; re-run before trusting the sweep."
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
write("");
|
|
198
|
+
const failed = traces.filter((row) => row.trace.stop === "error").length;
|
|
199
|
+
if (failed > 0) {
|
|
200
|
+
write(color.yellow(` ${failed} case(s) failed outright and count as "suggested nothing"`));
|
|
201
|
+
write("");
|
|
202
|
+
}
|
|
203
|
+
const byText = new Map(traces.map((row) => [row.request.text, row.trace]));
|
|
204
|
+
const { score, cases } = scoreSuggestions(
|
|
205
|
+
requests,
|
|
206
|
+
(request) => byText.get(request.text)?.suggestion?.name
|
|
207
|
+
);
|
|
208
|
+
writeScore(write, score);
|
|
209
|
+
const misses = cases.filter((c) => c.outcome === "wrong" || c.outcome === "needless");
|
|
210
|
+
if (misses.length > 0) {
|
|
211
|
+
write("");
|
|
212
|
+
write(` ${label("worst cases")}`);
|
|
213
|
+
for (const miss of misses.slice(0, 10)) {
|
|
214
|
+
const wanted = miss.request.gold ?? "(nothing)";
|
|
215
|
+
write(color.dim(` "${truncate(miss.request.text, 52)}"`));
|
|
216
|
+
write(` wanted ${wanted}, got ${color.red(miss.suggested ?? "(nothing)")}`);
|
|
217
|
+
}
|
|
218
|
+
if (misses.length > 10) write(color.dim(` \u2026and ${misses.length - 10} more`));
|
|
219
|
+
}
|
|
220
|
+
if (input.sweep) {
|
|
221
|
+
write("");
|
|
222
|
+
write(` ${label("threshold sweep")}`);
|
|
223
|
+
write(
|
|
224
|
+
color.dim(" gate fits correct wrong missed needless (of covered / uncovered)")
|
|
225
|
+
);
|
|
226
|
+
const rows = sweepThresholds(traces, SWEEP_STEPS, SWEEP_STEPS);
|
|
227
|
+
for (const row of rows) {
|
|
228
|
+
const s = row.score;
|
|
229
|
+
write(
|
|
230
|
+
` ${row.gateThreshold.toFixed(1)} ${row.fitsThreshold.toFixed(1)} ${pct(s.correct).padStart(6)} ${pct(s.wrongSuggestion).padStart(6)} ${pct(s.missed).padStart(6)} ${pct(s.needless).padStart(8)}`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
write("");
|
|
234
|
+
write(
|
|
235
|
+
color.dim(
|
|
236
|
+
" These score the SUGGESTER against your labels, not what the agent then loads."
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
write(
|
|
240
|
+
color.dim(
|
|
241
|
+
" Raising gate cuts needless suggestions and adds misses. Pick the one that costs you more."
|
|
242
|
+
)
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
write("");
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
function writeScore(write, score) {
|
|
249
|
+
write(` ${label("covered")} ${color.dim(`(${score.covered} cases)`)}`);
|
|
250
|
+
write(` correct ${pct(score.correct)}`);
|
|
251
|
+
write(` wrong suggestion ${pct(score.wrongSuggestion)}`);
|
|
252
|
+
write(` suggested nothing ${pct(score.missed)}`);
|
|
253
|
+
write(` ${label("uncovered")} ${color.dim(`(${score.uncovered} cases)`)}`);
|
|
254
|
+
write(` needless ${pct(score.needless)}`);
|
|
255
|
+
}
|
|
256
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
257
|
+
const results = new Array(items.length);
|
|
258
|
+
let cursor = 0;
|
|
259
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
260
|
+
for (; ; ) {
|
|
261
|
+
const index = cursor++;
|
|
262
|
+
const item = items[index];
|
|
263
|
+
if (item === void 0) return;
|
|
264
|
+
results[index] = await fn(item);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
await Promise.all(workers);
|
|
268
|
+
return results;
|
|
269
|
+
}
|
|
270
|
+
function pct(value) {
|
|
271
|
+
return `${(value * 100).toFixed(0)}%`;
|
|
272
|
+
}
|
|
273
|
+
function bar(value) {
|
|
274
|
+
const filled = Math.round(value * 10);
|
|
275
|
+
return color.dim(`[${"#".repeat(filled)}${".".repeat(10 - filled)}]`);
|
|
276
|
+
}
|
|
277
|
+
function label(text) {
|
|
278
|
+
return color.bold(text);
|
|
279
|
+
}
|
|
280
|
+
function verdictWord(passed, threshold) {
|
|
281
|
+
return passed ? color.green(`\u2265 ${pct(threshold)} \u2014 continue`) : color.yellow(`< ${pct(threshold)} \u2014 stop here`);
|
|
282
|
+
}
|
|
283
|
+
function truncate(text, max) {
|
|
284
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
285
|
+
return flat.length <= max ? flat : `${flat.slice(0, max - 1)}\u2026`;
|
|
286
|
+
}
|
|
287
|
+
export {
|
|
288
|
+
skillSuggestCmd
|
|
289
|
+
};
|
|
290
|
+
//# sourceMappingURL=skill-suggest-L2EVWB6R.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReasoningEffort, type SlashCommand } from '@wrongstack/core/types';
|
|
2
|
-
import type { SlashCommandContext } from './command-context.js';
|
|
3
2
|
import type { WstackPaths } from '@wrongstack/core/utils';
|
|
3
|
+
import type { SlashCommandContext } from './command-context.js';
|
|
4
4
|
/**
|
|
5
5
|
* `/effort` — view or set the SESSION-WIDE reasoning effort applied to the
|
|
6
6
|
* active leader model. This is the quick, discoverable front-end for
|
|
@@ -28,4 +28,19 @@ import type { SlashCommand } from '@wrongstack/core/types';
|
|
|
28
28
|
*/
|
|
29
29
|
export declare function renderQuotaSnapshot(snapshot: ProviderQuotaSnapshot): string[];
|
|
30
30
|
export declare function buildOpenAIQuotaCommand(): SlashCommand;
|
|
31
|
+
/**
|
|
32
|
+
* `/provider-quota` — every metered subscription this session has heard from.
|
|
33
|
+
*
|
|
34
|
+
* The narrow `/openai-quota` above stays because it answers one question
|
|
35
|
+
* without making the user read past other providers; this one exists because
|
|
36
|
+
* the reporters are no longer Codex-only. Claude Pro/Max and GitHub Copilot
|
|
37
|
+
* report into the same store, and a session that is signed into two metered
|
|
38
|
+
* plans has no other way to see which one is about to run out.
|
|
39
|
+
*
|
|
40
|
+
* The name says `provider` on purpose. A bare `/quota` would read as "what
|
|
41
|
+
* this project costs" — an account-wide spend view that does not exist here —
|
|
42
|
+
* and would occupy the name if one is ever built. What this shows is exactly
|
|
43
|
+
* the provider plane's readings, so that is what it is called.
|
|
44
|
+
*/
|
|
45
|
+
export declare function buildProviderQuotaCommand(): SlashCommand;
|
|
31
46
|
//# sourceMappingURL=openai-quota.d.ts.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { persistConfigSetting } from '../settings-menu.js';
|
|
2
|
+
import type { SlashCommandContext } from './command-context.js';
|
|
3
|
+
export declare function executeSettingsReset(rest: string[], persistDeps: Parameters<typeof persistConfigSetting>[0], opts: SlashCommandContext, profileName: string): Promise<{
|
|
4
|
+
message: string;
|
|
5
|
+
}>;
|
|
6
|
+
//# sourceMappingURL=settings-reset.d.ts.map
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import {
|
|
2
|
+
STANDALONE_TARGET
|
|
3
|
+
} from "./chunk-YBUCXXYZ.js";
|
|
4
|
+
|
|
5
|
+
// src/standalone-update.ts
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import { FetchError } from "@wrongstack/core/types";
|
|
10
|
+
|
|
11
|
+
// src/release-asset-download.ts
|
|
12
|
+
async function downloadReleaseAsset(url, timeoutMs, maxBytes) {
|
|
13
|
+
const res = await fetch(url, {
|
|
14
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
15
|
+
headers: { "User-Agent": "wrongstack-cli" },
|
|
16
|
+
redirect: "follow"
|
|
17
|
+
});
|
|
18
|
+
if (!res.ok) throw new Error(`download ${url} responded ${res.status}`);
|
|
19
|
+
const declared = Number(res.headers.get("content-length") ?? "0");
|
|
20
|
+
if (declared > maxBytes) throw new Error(`download ${url} is too large`);
|
|
21
|
+
if (!res.body) return Buffer.alloc(0);
|
|
22
|
+
const chunks = [];
|
|
23
|
+
let received = 0;
|
|
24
|
+
const reader = res.body.getReader();
|
|
25
|
+
try {
|
|
26
|
+
while (true) {
|
|
27
|
+
const { done, value } = await reader.read();
|
|
28
|
+
if (done) break;
|
|
29
|
+
received += value.byteLength;
|
|
30
|
+
if (received > maxBytes) {
|
|
31
|
+
await reader.cancel();
|
|
32
|
+
throw new Error(`download ${url} is too large`);
|
|
33
|
+
}
|
|
34
|
+
chunks.push(Buffer.from(value.buffer, value.byteOffset, value.byteLength));
|
|
35
|
+
}
|
|
36
|
+
} finally {
|
|
37
|
+
reader.releaseLock();
|
|
38
|
+
}
|
|
39
|
+
return Buffer.concat(chunks, received);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/standalone-update.ts
|
|
43
|
+
var RELEASES_REPO = "WrongStack/WrongStack";
|
|
44
|
+
var API_LATEST = `https://api.github.com/repos/${RELEASES_REPO}/releases/latest`;
|
|
45
|
+
var MAX_BINARY_BYTES = 512 * 1024 * 1024;
|
|
46
|
+
var MAX_CHECKSUM_BYTES = 1024 * 1024;
|
|
47
|
+
function standaloneAssetName(target) {
|
|
48
|
+
const name = `wstack-${target.replace(/^bun-/, "")}`;
|
|
49
|
+
return target.startsWith("bun-windows") ? `${name}.exe` : name;
|
|
50
|
+
}
|
|
51
|
+
async function fetchLatestRelease(timeoutMs, signal) {
|
|
52
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
53
|
+
const res = await fetch(API_LATEST, {
|
|
54
|
+
signal: signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal,
|
|
55
|
+
headers: { Accept: "application/vnd.github+json", "User-Agent": "wrongstack-cli" }
|
|
56
|
+
});
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new FetchError({
|
|
59
|
+
message: `GitHub releases responded ${res.status}`,
|
|
60
|
+
status: res.status,
|
|
61
|
+
context: { op: "checkForUpdate", registry: "github-releases", url: API_LATEST }
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const data = await res.json();
|
|
65
|
+
if (typeof data.tag_name !== "string") throw new Error("Latest release has no tag");
|
|
66
|
+
const assets = Array.isArray(data.assets) ? data.assets.filter(
|
|
67
|
+
(a) => typeof a?.name === "string" && typeof a?.browser_download_url === "string"
|
|
68
|
+
) : [];
|
|
69
|
+
return { version: data.tag_name.replace(/^v/, ""), assets };
|
|
70
|
+
}
|
|
71
|
+
async function fetchLatestStandaloneVersion(timeoutMs = 3e3, signal) {
|
|
72
|
+
return (await fetchLatestRelease(timeoutMs, signal)).version;
|
|
73
|
+
}
|
|
74
|
+
function parseSha256Sums(text) {
|
|
75
|
+
const sums = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const line of text.split(/\r?\n/)) {
|
|
77
|
+
const match = line.trim().match(/^([0-9a-f]{64})\s+\*?(.+)$/i);
|
|
78
|
+
if (match?.[1] && match[2]) {
|
|
79
|
+
const name = match[2].trim();
|
|
80
|
+
if (sums.has(name)) throw new Error(`Duplicate SHA256SUMS entry: ${name}`);
|
|
81
|
+
sums.set(name, match[1].toLowerCase());
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return sums;
|
|
85
|
+
}
|
|
86
|
+
function replaceExecutable(target, bytes, platform = process.platform) {
|
|
87
|
+
const staged = `${target}.new-${process.pid}`;
|
|
88
|
+
fs.writeFileSync(staged, bytes, { mode: 493 });
|
|
89
|
+
try {
|
|
90
|
+
if (platform === "win32") {
|
|
91
|
+
const aside = `${target}.old`;
|
|
92
|
+
fs.rmSync(aside, { force: true });
|
|
93
|
+
fs.renameSync(target, aside);
|
|
94
|
+
try {
|
|
95
|
+
fs.renameSync(staged, target);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
fs.renameSync(aside, target);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
fs.renameSync(staged, target);
|
|
102
|
+
}
|
|
103
|
+
} finally {
|
|
104
|
+
fs.rmSync(staged, { force: true });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function updateStandaloneBinary(options) {
|
|
108
|
+
const { renderer } = options;
|
|
109
|
+
if (!STANDALONE_TARGET) {
|
|
110
|
+
renderer.write("Update failed: this executable does not record its build target.\n");
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
const assetName = standaloneAssetName(STANDALONE_TARGET);
|
|
114
|
+
try {
|
|
115
|
+
const release = await fetchLatestRelease(15e3);
|
|
116
|
+
const asset = release.assets.find((a) => a.name === assetName);
|
|
117
|
+
const sumsAsset = release.assets.find((a) => a.name === "SHA256SUMS");
|
|
118
|
+
if (!asset || !sumsAsset) {
|
|
119
|
+
renderer.write(`Update failed: release v${release.version} has no ${assetName} build.
|
|
120
|
+
`);
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
renderer.write(`Updating wrongstack from v${options.current} to v${release.version}...
|
|
124
|
+
`);
|
|
125
|
+
renderer.write(`Downloading ${assetName}
|
|
126
|
+
`);
|
|
127
|
+
const [bytes, sumsText] = await Promise.all([
|
|
128
|
+
downloadReleaseAsset(asset.browser_download_url, 6e5, MAX_BINARY_BYTES),
|
|
129
|
+
downloadReleaseAsset(sumsAsset.browser_download_url, 3e4, MAX_CHECKSUM_BYTES).then(
|
|
130
|
+
(b) => b.toString("utf8")
|
|
131
|
+
)
|
|
132
|
+
]);
|
|
133
|
+
const expected = parseSha256Sums(sumsText).get(assetName);
|
|
134
|
+
const actual = createHash("sha256").update(bytes).digest("hex");
|
|
135
|
+
if (!expected || expected !== actual) {
|
|
136
|
+
renderer.write("Update failed: checksum does not match the release SHA256SUMS.\n");
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
const executable = fs.realpathSync(process.execPath);
|
|
140
|
+
replaceExecutable(executable, bytes);
|
|
141
|
+
renderer.write(
|
|
142
|
+
`
|
|
143
|
+
Updated ${path.basename(executable)} to v${release.version}. Restart wrongstack to use it.
|
|
144
|
+
`
|
|
145
|
+
);
|
|
146
|
+
return 0;
|
|
147
|
+
} catch (err) {
|
|
148
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
149
|
+
const permission = /EACCES|EPERM/.test(msg) ? "\nThe executable is not writable by this user; rerun with the permissions it was installed with." : "";
|
|
150
|
+
renderer.write(`
|
|
151
|
+
Update failed: ${msg}${permission}
|
|
152
|
+
`);
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
RELEASES_REPO,
|
|
158
|
+
fetchLatestStandaloneVersion,
|
|
159
|
+
parseSha256Sums,
|
|
160
|
+
replaceExecutable,
|
|
161
|
+
standaloneAssetName,
|
|
162
|
+
updateStandaloneBinary
|
|
163
|
+
};
|
|
164
|
+
//# sourceMappingURL=standalone-update-MW47J3AK.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TerminalRenderer } from './renderer.js';
|
|
2
|
+
export declare const RELEASES_REPO = "WrongStack/WrongStack";
|
|
3
|
+
/** Asset name a build target publishes under (mirrors build-binaries.mjs `outputName`). */
|
|
4
|
+
export declare function standaloneAssetName(target: string): string;
|
|
5
|
+
/** Latest released version, for the update check. */
|
|
6
|
+
export declare function fetchLatestStandaloneVersion(timeoutMs?: number, signal?: AbortSignal): Promise<string>;
|
|
7
|
+
/** `<sha256> <name>` lines → map. */
|
|
8
|
+
export declare function parseSha256Sums(text: string): Map<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* Replace `target` with `bytes`. Staged next to the target so the final step
|
|
11
|
+
* is a same-filesystem rename.
|
|
12
|
+
*/
|
|
13
|
+
export declare function replaceExecutable(target: string, bytes: Buffer, platform?: NodeJS.Platform): void;
|
|
14
|
+
/** `wstack update` for the standalone binary. Returns the process exit code. */
|
|
15
|
+
export declare function updateStandaloneBinary(options: {
|
|
16
|
+
current: string;
|
|
17
|
+
renderer: Pick<TerminalRenderer, 'write'>;
|
|
18
|
+
}): Promise<number>;
|
|
19
|
+
//# sourceMappingURL=standalone-update.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SubcommandDeps } from '../contracts.js';
|
|
2
|
+
/**
|
|
3
|
+
* `wstack hq audit verify` — re-derive the contentHash from the current
|
|
4
|
+
* on-disk `auth.json` and print it so an operator can compare against a
|
|
5
|
+
* `contentHash` field in an audit entry. This closes the forensic
|
|
6
|
+
* tie-back loop without requiring the operator to write a script: copy
|
|
7
|
+
* the hash from the audit log, run this command, eyeball (or `diff`)
|
|
8
|
+
* the two values.
|
|
9
|
+
*
|
|
10
|
+
* Prints:
|
|
11
|
+
* - the resolved `auth.json` path (so the operator knows which file
|
|
12
|
+
* was hashed)
|
|
13
|
+
* - the SHA-256 contentHash, or `(unavailable)` if the file is
|
|
14
|
+
* missing/unreadable
|
|
15
|
+
* - the `auth-audit.jsonl` path (so the operator knows where to look
|
|
16
|
+
* for entries to compare against)
|
|
17
|
+
*
|
|
18
|
+
* Exit codes:
|
|
19
|
+
* 0 — hash computed and printed
|
|
20
|
+
* 1 — `auth.json` missing or unreadable (hash unavailable); the
|
|
21
|
+
* audit-log path is still printed so the operator can inspect
|
|
22
|
+
* historical entries
|
|
23
|
+
*/
|
|
24
|
+
export declare function hqAuditCmd(args: string[], deps: SubcommandDeps): Promise<number>;
|
|
25
|
+
/**
|
|
26
|
+
* Re-read the persisted `auth.json` and return a conditional
|
|
27
|
+
* `contentHash` field for embedding in an audit entry. This closes the
|
|
28
|
+
* forensic tie-back loop: an operator reviewing `auth-audit.jsonl` can
|
|
29
|
+
* compare the `contentHash` against the current on-disk file via
|
|
30
|
+
* `wstack hq audit verify`.
|
|
31
|
+
*
|
|
32
|
+
* Re-reads rather than hashing the in-memory post-mutation snapshot
|
|
33
|
+
* because `writeHqAuthFile` re-stamps `updatedAt` on the persisted
|
|
34
|
+
* payload — hashing the in-memory `next` would diverge from what
|
|
35
|
+
* `verify` computes. Matches the pattern established by the `first-run`
|
|
36
|
+
* and `expired-prune` emitters in `auth-store.ts` / `hq-server.ts`.
|
|
37
|
+
*
|
|
38
|
+
* Returns `{}` (no `contentHash` key) when the file is unreadable, so
|
|
39
|
+
* the audit entry still records the event without a tie-back — the
|
|
40
|
+
* absence itself is meaningful. Conditional spread preserves
|
|
41
|
+
* `exactOptionalPropertyTypes`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function computeAuditHashField(dataDir: string, warn: (msg: string) => void): Promise<{
|
|
44
|
+
contentHash?: string;
|
|
45
|
+
}>;
|
|
46
|
+
//# sourceMappingURL=hq-audit.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SubcommandDeps } from '../contracts.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build a free-form actor string for the auth audit log. Combines the OS
|
|
4
|
+
* username (best-effort) with the hostname so an operator reviewing the
|
|
5
|
+
* log can see "who minted this token" without parsing environment
|
|
6
|
+
* variables. Never includes the token string.
|
|
7
|
+
*
|
|
8
|
+
* Shared by the `wstack hq token` create/revoke handlers and by
|
|
9
|
+
* `startHqServer`'s first-run bootstrap so every audit entry point
|
|
10
|
+
* produces the same actor shape.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveDataDir(deps: SubcommandDeps): string;
|
|
13
|
+
//# sourceMappingURL=hq-data-dir.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `wstack skill-suggest` — inspect and tune the TypeSafe skill suggester.
|
|
3
|
+
*
|
|
4
|
+
* The suggester runs inside a turn and its output is one line of system prompt,
|
|
5
|
+
* which makes it almost impossible to observe in place: you see the agent load
|
|
6
|
+
* a skill, not why. Two modes make it inspectable:
|
|
7
|
+
*
|
|
8
|
+
* wstack skill-suggest "<request>" one request, both passes, all numbers
|
|
9
|
+
* wstack skill-suggest --eval cases.jsonl labeled set, scored
|
|
10
|
+
* wstack skill-suggest --eval cases.jsonl --sweep …plus a threshold table
|
|
11
|
+
*
|
|
12
|
+
* Both work whether or not `skills.suggest.enabled` is on — previewing is how
|
|
13
|
+
* you decide whether to turn it on. Both need a TypeSafe API key, and both send
|
|
14
|
+
* request text to TypeSafe, so `--eval` prints what it is about to spend before
|
|
15
|
+
* it spends it.
|
|
16
|
+
*/
|
|
17
|
+
import { type LabeledRequest, type ScoredCase } from '@wrongstack/core/skills';
|
|
18
|
+
import type { SubcommandHandler } from '../contracts.js';
|
|
19
|
+
export declare const skillSuggestCmd: SubcommandHandler;
|
|
20
|
+
export type { LabeledRequest, ScoredCase };
|
|
21
|
+
//# sourceMappingURL=skill-suggest.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `wstack typesafe replay-brain-ledger` — calibrate the Brain's System One
|
|
3
|
+
* tier on decisions this project already made.
|
|
4
|
+
*
|
|
5
|
+
* The Brain ledger records every decision (question, chosen option, which tier
|
|
6
|
+
* or council settled it) and, for interventions, whether the same signal fired
|
|
7
|
+
* again afterwards. It does not record the option list or the full context —
|
|
8
|
+
* but the BrainMonitor signals (file churn, tool-failure streak, stall, error
|
|
9
|
+
* storm) always ask the same steer/continue question with a context the
|
|
10
|
+
* question text already carries, so those requests can be rebuilt exactly.
|
|
11
|
+
*
|
|
12
|
+
* Each rebuilt request goes through `probeSystemOneBrain` (the tier's real
|
|
13
|
+
* questions, no thresholds), and the report compares Jev's pick with the
|
|
14
|
+
* recorded one: overall agreement, how much the current thresholds would
|
|
15
|
+
* settle and how often those settled picks agree, a threshold sweep, and how
|
|
16
|
+
* often Jev said `continue` on a steer the ledger shows was futile (the same
|
|
17
|
+
* signal re-fired).
|
|
18
|
+
*
|
|
19
|
+
* The recorded pick is a reference, not ground truth: most were made by the
|
|
20
|
+
* council or a policy, and the outcome record only exists for steers. The
|
|
21
|
+
* decision digest the live tier sees is omitted — rebuilding it from the
|
|
22
|
+
* ledger would leak later outcomes into earlier decisions.
|
|
23
|
+
*/
|
|
24
|
+
import type { SubcommandDeps } from '../contracts.js';
|
|
25
|
+
export declare function replayBrainLedger(deps: SubcommandDeps, write: (line: string) => void): Promise<number>;
|
|
26
|
+
//# sourceMappingURL=typesafe-brain-replay.d.ts.map
|