@klhapp/skillmux 1.6.0 → 1.7.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/CHANGELOG.md +21 -0
- package/README.md +7 -7
- package/config.example.toml +5 -0
- package/config.remote.example.toml +4 -7
- package/docs/README.md +4 -4
- package/docs/assets/architecture.svg +1 -1
- package/docs/cli.md +4 -57
- package/docs/concepts.md +12 -14
- package/docs/configuration.md +10 -11
- package/docs/deployment.md +11 -8
- package/docs/getting-started.md +1 -1
- package/docs/mcp-routing.md +28 -23
- package/docs/ranked-shortlist-migration.md +160 -0
- package/docs/schema.json +37 -139
- package/docs/skill-management.md +7 -2
- package/docs/troubleshooting.md +12 -14
- package/package.json +1 -1
- package/src/adapters.ts +1 -536
- package/src/audit.ts +1 -3
- package/src/cli.ts +19 -254
- package/src/completions.ts +0 -4
- package/src/config-service.ts +19 -32
- package/src/config-watcher.ts +2 -6
- package/src/config.ts +61 -60
- package/src/db.ts +32 -18
- package/src/doctor.ts +1 -84
- package/src/eval.ts +143 -60
- package/src/init.ts +4 -5
- package/src/metrics.ts +1 -13
- package/src/router-core.ts +42 -149
- package/src/server.ts +13 -27
- package/src/stats.ts +126 -57
- package/src/types.ts +8 -39
- package/docs/calibration.md +0 -198
- package/src/calibrate.ts +0 -1775
- package/src/config-mutation.ts +0 -65
- package/src/dataset-generator.ts +0 -119
- package/src/decision.ts +0 -45
package/src/cli.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { Database } from "bun:sqlite";
|
|
|
4
4
|
import { existsSync, lstatSync, mkdirSync, rmSync } from "node:fs";
|
|
5
5
|
import { hostname } from "node:os";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
-
import { generateDataset } from "./dataset-generator";
|
|
8
7
|
|
|
9
8
|
import { createClients } from "./clients";
|
|
10
9
|
import {
|
|
@@ -116,7 +115,6 @@ import { runTarget } from "./commands/target";
|
|
|
116
115
|
const KNOWN_COMMANDS = [
|
|
117
116
|
"context",
|
|
118
117
|
"config",
|
|
119
|
-
"calibrate",
|
|
120
118
|
"completions",
|
|
121
119
|
"serve",
|
|
122
120
|
"index",
|
|
@@ -147,7 +145,6 @@ function isDockerHostManagementCommand(command: string, subCommand: string): boo
|
|
|
147
145
|
"local-vault",
|
|
148
146
|
"models",
|
|
149
147
|
"context",
|
|
150
|
-
"calibrate",
|
|
151
148
|
"eval",
|
|
152
149
|
].includes(command)
|
|
153
150
|
) {
|
|
@@ -210,7 +207,7 @@ async function main() {
|
|
|
210
207
|
return;
|
|
211
208
|
}
|
|
212
209
|
|
|
213
|
-
// Parse global flags for context/config
|
|
210
|
+
// Parse global flags for context/config
|
|
214
211
|
for (let i = 0; i < rawArgv.length; i++) {
|
|
215
212
|
const arg = rawArgv[i];
|
|
216
213
|
if (arg === "--json") isJson = true;
|
|
@@ -235,10 +232,10 @@ async function main() {
|
|
|
235
232
|
return;
|
|
236
233
|
}
|
|
237
234
|
|
|
238
|
-
// Only resolve target if command is target-aware or context/config
|
|
235
|
+
// Only resolve target if command is target-aware or context/config
|
|
239
236
|
const isLocalConfigInit = command === "config" && rawArgv[1] === "init";
|
|
240
237
|
if (
|
|
241
|
-
(["context", "config"
|
|
238
|
+
(["context", "config"].includes(command) &&
|
|
242
239
|
!isLocalConfigInit) ||
|
|
243
240
|
flagContext ||
|
|
244
241
|
flagServer
|
|
@@ -272,11 +269,9 @@ async function main() {
|
|
|
272
269
|
});
|
|
273
270
|
break;
|
|
274
271
|
case "calibrate":
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
});
|
|
279
|
-
break;
|
|
272
|
+
throw new Error(
|
|
273
|
+
'skillmux calibrate was removed. Threshold calibration was removed; use "skillmux eval" for ranking evaluation.',
|
|
274
|
+
);
|
|
280
275
|
case "completions":
|
|
281
276
|
await handleCompletionsCommand(subCommand);
|
|
282
277
|
break;
|
|
@@ -364,7 +359,7 @@ async function main() {
|
|
|
364
359
|
const suggestion = suggestCorrection(command, KNOWN_COMMANDS);
|
|
365
360
|
const msg = suggestion
|
|
366
361
|
? `Unknown command "${command}". Did you mean "${suggestion}"?`
|
|
367
|
-
: `usage: skillmux <serve|index|sync|init|project|target|core pin/unpin|report|scan|install|eval|doctor|skill which|local-vault init|config show|models download
|
|
362
|
+
: `usage: skillmux <serve|index|sync|init|project|target|core pin/unpin|report|scan|install|eval|doctor|skill which|local-vault init|config show|models download>`;
|
|
368
363
|
throw new Error(msg);
|
|
369
364
|
}
|
|
370
365
|
}
|
|
@@ -463,188 +458,6 @@ async function handleContextCommand(
|
|
|
463
458
|
throw new Error("usage: skillmux context <add|list|current|use|remove>");
|
|
464
459
|
}
|
|
465
460
|
|
|
466
|
-
async function handleCalibrateCommand(
|
|
467
|
-
adapter: TargetAdapter,
|
|
468
|
-
sub: string,
|
|
469
|
-
args: string[],
|
|
470
|
-
ctx: { target: ResolvedTarget; isJson: boolean },
|
|
471
|
-
) {
|
|
472
|
-
if (sub === "run") {
|
|
473
|
-
let datasetPath: string | undefined;
|
|
474
|
-
let minAutoMatchPrecision: number | undefined;
|
|
475
|
-
let minRetrievalRecallAtK: number | undefined;
|
|
476
|
-
let minDeliveredShortlistRecallAtK: number | undefined;
|
|
477
|
-
let minAutoMatchCount: number | undefined;
|
|
478
|
-
let tuneAutoMatchPrecisionBuffer: number | undefined;
|
|
479
|
-
let tuneAutoMatchCountBuffer: number | undefined;
|
|
480
|
-
let tuneDeliveredShortlistRecallBuffer: number | undefined;
|
|
481
|
-
let concurrency: number | undefined;
|
|
482
|
-
let resumeRunId: string | undefined;
|
|
483
|
-
let timing = false;
|
|
484
|
-
const readNumber = (flag: string, raw: string | undefined): number => {
|
|
485
|
-
if (raw === undefined) throw new Error(`${flag} requires a value`);
|
|
486
|
-
const value = Number(raw);
|
|
487
|
-
if (!Number.isFinite(value)) throw new Error(`${flag} must be a number`);
|
|
488
|
-
return value;
|
|
489
|
-
};
|
|
490
|
-
for (let i = 1; i < args.length; i++) {
|
|
491
|
-
const option = args[i];
|
|
492
|
-
if (option === "--dataset") {
|
|
493
|
-
datasetPath = args[++i];
|
|
494
|
-
if (!datasetPath) throw new Error("--dataset requires a path value");
|
|
495
|
-
} else if (option === "--min-auto-match-precision") {
|
|
496
|
-
minAutoMatchPrecision = readNumber(option, args[++i]);
|
|
497
|
-
} else if (option === "--min-retrieval-recall-at-k") {
|
|
498
|
-
minRetrievalRecallAtK = readNumber(option, args[++i]);
|
|
499
|
-
} else if (option === "--min-delivered-shortlist-recall-at-k") {
|
|
500
|
-
minDeliveredShortlistRecallAtK = readNumber(option, args[++i]);
|
|
501
|
-
} else if (option === "--min-auto-match-count") {
|
|
502
|
-
minAutoMatchCount = readNumber(option, args[++i]);
|
|
503
|
-
if (!Number.isInteger(minAutoMatchCount) || minAutoMatchCount < 1) {
|
|
504
|
-
throw new Error("--min-auto-match-count must be a positive integer");
|
|
505
|
-
}
|
|
506
|
-
} else if (option === "--tune-auto-match-precision-buffer") {
|
|
507
|
-
tuneAutoMatchPrecisionBuffer = readNumber(option, args[++i]);
|
|
508
|
-
} else if (option === "--tune-auto-match-count-buffer") {
|
|
509
|
-
tuneAutoMatchCountBuffer = readNumber(option, args[++i]);
|
|
510
|
-
if (!Number.isInteger(tuneAutoMatchCountBuffer) || tuneAutoMatchCountBuffer < 0) {
|
|
511
|
-
throw new Error("--tune-auto-match-count-buffer must be a non-negative integer");
|
|
512
|
-
}
|
|
513
|
-
} else if (option === "--tune-delivered-shortlist-recall-buffer") {
|
|
514
|
-
tuneDeliveredShortlistRecallBuffer = readNumber(option, args[++i]);
|
|
515
|
-
} else if (option === "--concurrency") {
|
|
516
|
-
const raw = args[++i];
|
|
517
|
-
if (raw === undefined) throw new Error("--concurrency requires a value");
|
|
518
|
-
const val = Number(raw);
|
|
519
|
-
if (!Number.isInteger(val) || val < 1) {
|
|
520
|
-
throw new Error("--concurrency must be a positive integer");
|
|
521
|
-
}
|
|
522
|
-
concurrency = val;
|
|
523
|
-
} else if (option === "--resume") {
|
|
524
|
-
resumeRunId = args[++i];
|
|
525
|
-
if (!resumeRunId) throw new Error("--resume requires a run_id value");
|
|
526
|
-
} else if (option === "--timing") {
|
|
527
|
-
timing = true;
|
|
528
|
-
} else if (option === "--json") {
|
|
529
|
-
// Global flag accepted in the documented subcommand position
|
|
530
|
-
} else {
|
|
531
|
-
throw new Error(`unknown calibrate run option: ${option}`);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
for (const [flag, value] of [
|
|
535
|
-
["--min-auto-match-precision", minAutoMatchPrecision],
|
|
536
|
-
["--min-retrieval-recall-at-k", minRetrievalRecallAtK],
|
|
537
|
-
["--min-delivered-shortlist-recall-at-k", minDeliveredShortlistRecallAtK],
|
|
538
|
-
["--tune-auto-match-precision-buffer", tuneAutoMatchPrecisionBuffer],
|
|
539
|
-
["--tune-delivered-shortlist-recall-buffer", tuneDeliveredShortlistRecallBuffer],
|
|
540
|
-
] as const) {
|
|
541
|
-
if (value !== undefined && (value < 0 || value > 1)) {
|
|
542
|
-
throw new Error(`${flag} must be between 0 and 1`);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
const res = await adapter.calibrateRun({
|
|
546
|
-
datasetPath,
|
|
547
|
-
minAutoMatchPrecision,
|
|
548
|
-
minRetrievalRecallAtK,
|
|
549
|
-
minDeliveredShortlistRecallAtK,
|
|
550
|
-
minAutoMatchCount,
|
|
551
|
-
tuneAutoMatchPrecisionBuffer,
|
|
552
|
-
tuneAutoMatchCountBuffer,
|
|
553
|
-
tuneDeliveredShortlistRecallBuffer,
|
|
554
|
-
concurrency,
|
|
555
|
-
resumeRunId,
|
|
556
|
-
timing,
|
|
557
|
-
onTimingSummary: timing
|
|
558
|
-
? (summary) => {
|
|
559
|
-
// Write timing report to stderr only — stdout remains valid JSON under --json.
|
|
560
|
-
// Cumulative fields are total worker time across concurrent queries and may
|
|
561
|
-
// exceed wall_ms. They do not sum to wall time.
|
|
562
|
-
process.stderr.write(
|
|
563
|
-
[
|
|
564
|
-
"--- calibrate run timing ---",
|
|
565
|
-
`cases_total: ${summary.cases_total}`,
|
|
566
|
-
`cases_executed: ${summary.cases_executed} (retrieved in this invocation)`,
|
|
567
|
-
`cases_reused: ${summary.cases_reused} (loaded from prior interrupted run)`,
|
|
568
|
-
`wall_ms: ${summary.wall_ms.toFixed(1)}`,
|
|
569
|
-
`vault_sync_ms: ${summary.vault_sync_ms.toFixed(1)}`,
|
|
570
|
-
"cumulative worker time (concurrent totals; may exceed wall_ms):",
|
|
571
|
-
` cumulative_embedding_ms: ${summary.cumulative_embedding_ms.toFixed(1)}`,
|
|
572
|
-
` cumulative_lexical_ms: ${summary.cumulative_lexical_ms.toFixed(1)}`,
|
|
573
|
-
` cumulative_vector_ms: ${summary.cumulative_vector_ms.toFixed(1)}`,
|
|
574
|
-
` cumulative_reranker_ms: ${summary.cumulative_reranker_ms.toFixed(1)}`,
|
|
575
|
-
` cumulative_checkpoint_ms: ${summary.cumulative_checkpoint_ms.toFixed(1)}`,
|
|
576
|
-
`policy_evaluation_ms: ${summary.policy_evaluation_ms.toFixed(1)}`,
|
|
577
|
-
"",
|
|
578
|
-
].join("\n"),
|
|
579
|
-
);
|
|
580
|
-
}
|
|
581
|
-
: undefined,
|
|
582
|
-
});
|
|
583
|
-
emitSuccess({ isJson: ctx.isJson, target: ctx.target }, res, () => {
|
|
584
|
-
renderCalibrationTarget(ctx.target);
|
|
585
|
-
console.log(`Calibration run complete.`);
|
|
586
|
-
if (res.result) console.log(JSON.stringify(res.result, null, 2));
|
|
587
|
-
});
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if (sub === "list") {
|
|
593
|
-
const res = await adapter.calibrateList();
|
|
594
|
-
emitSuccess({ isJson: ctx.isJson, target: ctx.target }, res, () => {
|
|
595
|
-
renderCalibrationTarget(ctx.target);
|
|
596
|
-
renderTable(
|
|
597
|
-
[
|
|
598
|
-
{ key: "run_id", header: "RUN_ID" },
|
|
599
|
-
{ key: "created_at", header: "CREATED_AT" },
|
|
600
|
-
{ key: "status", header: "STATUS" },
|
|
601
|
-
],
|
|
602
|
-
res,
|
|
603
|
-
);
|
|
604
|
-
});
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
if (sub === "show") {
|
|
609
|
-
const runId = args[1];
|
|
610
|
-
if (!runId) throw new Error("usage: skillmux calibrate show <run_id>");
|
|
611
|
-
const res = await adapter.calibrateShow(runId);
|
|
612
|
-
emitSuccess({ isJson: ctx.isJson, target: ctx.target }, res, () => {
|
|
613
|
-
renderCalibrationTarget(ctx.target);
|
|
614
|
-
console.log(JSON.stringify(res, null, 2));
|
|
615
|
-
});
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
if (sub === "apply") {
|
|
620
|
-
const runId = args[1];
|
|
621
|
-
if (!runId) throw new Error("usage: skillmux calibrate apply <run_id>");
|
|
622
|
-
const res = await adapter.calibrateApply(runId);
|
|
623
|
-
emitSuccess({ isJson: ctx.isJson, target: ctx.target }, res, () => {
|
|
624
|
-
renderCalibrationTarget(ctx.target);
|
|
625
|
-
console.log(`Applied calibration run "${runId}"`);
|
|
626
|
-
});
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
if (sub === "generate-dataset") {
|
|
631
|
-
await runCalibrateGenerateDataset(args.slice(1));
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
throw new Error(
|
|
636
|
-
"usage: skillmux calibrate generate-dataset [--vault <path>] [--out <file>]",
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
function renderCalibrationTarget(target: ResolvedTarget): void {
|
|
641
|
-
if (target.type === "local") {
|
|
642
|
-
console.log("Target: local");
|
|
643
|
-
} else {
|
|
644
|
-
console.log(`Target: remote (${target.name} -> ${target.server})`);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
461
|
async function handleCompletionsCommand(shell: string) {
|
|
649
462
|
if (shell !== "bash" && shell !== "zsh" && shell !== "fish") {
|
|
650
463
|
throw new Error("usage: skillmux completions <bash|zsh|fish>");
|
|
@@ -720,17 +533,6 @@ Setup:
|
|
|
720
533
|
skillmux core <pin|unpin> <skill_id>... [--yes] [--dry-run] [--json]
|
|
721
534
|
skillmux skill which <skill_id>
|
|
722
535
|
|
|
723
|
-
Calibration:
|
|
724
|
-
skillmux calibrate run [--dataset <path>] [--concurrency <n>] [--resume <run_id>]
|
|
725
|
-
[--min-auto-match-precision <0..1>] [--min-auto-match-count <n>]
|
|
726
|
-
[--min-retrieval-recall-at-k <0..1>]
|
|
727
|
-
[--min-delivered-shortlist-recall-at-k <0..1>]
|
|
728
|
-
[--tune-auto-match-precision-buffer <0..1>]
|
|
729
|
-
[--tune-auto-match-count-buffer <n>]
|
|
730
|
-
[--tune-delivered-shortlist-recall-buffer <0..1>]
|
|
731
|
-
[--timing] [--json]
|
|
732
|
-
skillmux calibrate <list|show|apply|generate-dataset>
|
|
733
|
-
|
|
734
536
|
Init clients:
|
|
735
537
|
claude-code, codex, gemini-cli, opencode, github-copilot, windsurf,
|
|
736
538
|
antigravity, goose, hermes, skillmux-mcp
|
|
@@ -740,7 +542,7 @@ Init targets:
|
|
|
740
542
|
|
|
741
543
|
Commands:
|
|
742
544
|
serve, index, sync, init, project, target, core, report, scan, install, eval, doctor, skill,
|
|
743
|
-
local-vault, config, models,
|
|
545
|
+
local-vault, config, models, context, completions`);
|
|
744
546
|
}
|
|
745
547
|
|
|
746
548
|
// ---------------------------------------------------------------------------
|
|
@@ -810,13 +612,17 @@ async function runEval(options: { isJson: boolean }): Promise<void> {
|
|
|
810
612
|
throw new Error(`eval requires local embeddings: ${String(error)}`);
|
|
811
613
|
});
|
|
812
614
|
emitSuccess({ isJson: options.isJson }, report, () => {
|
|
813
|
-
console.log(`holdout queries:
|
|
814
|
-
console.log(`
|
|
815
|
-
console.log(`
|
|
816
|
-
console.log(`lexical
|
|
817
|
-
console.log(`
|
|
818
|
-
console.log(`
|
|
819
|
-
console.log(`
|
|
615
|
+
console.log(`holdout queries: ${report.queries}`);
|
|
616
|
+
console.log(`judged queries: ${report.judged_queries}`);
|
|
617
|
+
console.log(`unjudged queries: ${report.unjudged_queries}`);
|
|
618
|
+
console.log(`lexical recall@5: ${report.lexical.recall_at_5.toFixed(3)}`);
|
|
619
|
+
console.log(`lexical recall@10: ${report.lexical.recall_at_10.toFixed(3)}`);
|
|
620
|
+
console.log(`lexical MRR: ${report.lexical.mrr.toFixed(3)}`);
|
|
621
|
+
console.log(`lexical nDCG@10: ${report.lexical.ndcg_at_10.toFixed(3)}`);
|
|
622
|
+
console.log(`hybrid recall@5: ${report.hybrid.recall_at_5.toFixed(3)}`);
|
|
623
|
+
console.log(`hybrid recall@10: ${report.hybrid.recall_at_10.toFixed(3)}`);
|
|
624
|
+
console.log(`hybrid MRR: ${report.hybrid.mrr.toFixed(3)}`);
|
|
625
|
+
console.log(`hybrid nDCG@10: ${report.hybrid.ndcg_at_10.toFixed(3)}`);
|
|
820
626
|
});
|
|
821
627
|
}
|
|
822
628
|
|
|
@@ -1802,47 +1608,6 @@ async function runInstall(
|
|
|
1802
1608
|
}
|
|
1803
1609
|
}
|
|
1804
1610
|
|
|
1805
|
-
function parseCalibrateGenerateDatasetArgs(args: string[]): {
|
|
1806
|
-
vault?: string;
|
|
1807
|
-
out?: string;
|
|
1808
|
-
} {
|
|
1809
|
-
let vault: string | undefined;
|
|
1810
|
-
let out: string | undefined;
|
|
1811
|
-
for (let i = 0; i < args.length; i++) {
|
|
1812
|
-
const option = args[i];
|
|
1813
|
-
if (option === "--vault") {
|
|
1814
|
-
const value = args[++i];
|
|
1815
|
-
if (!value) throw new Error("--vault requires a path value");
|
|
1816
|
-
vault = value;
|
|
1817
|
-
} else if (option === "--out") {
|
|
1818
|
-
const value = args[++i];
|
|
1819
|
-
if (!value) throw new Error("--out requires a file path value");
|
|
1820
|
-
out = value;
|
|
1821
|
-
} else {
|
|
1822
|
-
throw new Error(`unknown calibrate option: ${option}`);
|
|
1823
|
-
}
|
|
1824
|
-
}
|
|
1825
|
-
return { vault, out };
|
|
1826
|
-
}
|
|
1827
|
-
|
|
1828
|
-
async function runCalibrateGenerateDataset(args: string[]): Promise<void> {
|
|
1829
|
-
const { vault: vaultArg, out: outArg } =
|
|
1830
|
-
parseCalibrateGenerateDatasetArgs(args);
|
|
1831
|
-
const config = await loadConfig();
|
|
1832
|
-
const vaultPath = expandHome(vaultArg ?? config.vault_path);
|
|
1833
|
-
const outPath = expandHome(outArg ?? join(config.state_dir, "queries.json"));
|
|
1834
|
-
|
|
1835
|
-
const skills = await scanVault(vaultPath);
|
|
1836
|
-
const dataset = generateDataset(skills);
|
|
1837
|
-
|
|
1838
|
-
const parentDir = join(outPath, "..");
|
|
1839
|
-
mkdirSync(parentDir, { recursive: true });
|
|
1840
|
-
await Bun.write(outPath, JSON.stringify(dataset, null, 2) + "\n");
|
|
1841
|
-
console.log(
|
|
1842
|
-
`generated synthetic dataset with ${dataset.length} cases at ${outPath}`,
|
|
1843
|
-
);
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
1611
|
if (import.meta.main) {
|
|
1847
1612
|
await main();
|
|
1848
1613
|
}
|
package/src/completions.ts
CHANGED
|
@@ -3,7 +3,6 @@ export type ShellType = "bash" | "zsh" | "fish";
|
|
|
3
3
|
const TOP_LEVEL_COMMANDS: { name: string; description: string }[] = [
|
|
4
4
|
{ name: "context", description: "Manage connection contexts" },
|
|
5
5
|
{ name: "config", description: "Manage configuration" },
|
|
6
|
-
{ name: "calibrate", description: "Manage policy calibration" },
|
|
7
6
|
{ name: "serve", description: "Start MCP server" },
|
|
8
7
|
{ name: "index", description: "Rebuild local search index" },
|
|
9
8
|
{ name: "sync", description: "Synchronize vault skills" },
|
|
@@ -45,9 +44,6 @@ _skillmux_completions() {
|
|
|
45
44
|
config)
|
|
46
45
|
COMPREPLY=( $(compgen -W "init show get validate diff set status" -- "$cur") )
|
|
47
46
|
;;
|
|
48
|
-
calibrate)
|
|
49
|
-
COMPREPLY=( $(compgen -W "run list show apply generate-dataset" -- "$cur") )
|
|
50
|
-
;;
|
|
51
47
|
completions)
|
|
52
48
|
COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
|
|
53
49
|
;;
|
package/src/config-service.ts
CHANGED
|
@@ -60,11 +60,8 @@ export const RELOADABLE_KEYS = [
|
|
|
60
60
|
"recall.k_lexical",
|
|
61
61
|
"recall.k_vector",
|
|
62
62
|
"recall.k_rerank",
|
|
63
|
-
"output.
|
|
64
|
-
"
|
|
65
|
-
"thresholds.match_score",
|
|
66
|
-
"thresholds.match_margin",
|
|
67
|
-
"thresholds.candidate_floor",
|
|
63
|
+
"output.top_k",
|
|
64
|
+
"output.max_top_k",
|
|
68
65
|
"inference.embedding.endpoint",
|
|
69
66
|
"inference.embedding.api_key_env",
|
|
70
67
|
"inference.reranker.adapter",
|
|
@@ -123,7 +120,7 @@ export async function getEffectiveConfig(configPath?: string): Promise<{
|
|
|
123
120
|
sources: ConfigSourceMap;
|
|
124
121
|
rawToml: Record<string, unknown>;
|
|
125
122
|
}> {
|
|
126
|
-
const path = configPath ?? DEFAULT_CONFIG_PATH;
|
|
123
|
+
const path = configPath ?? process.env.SKILLMUX_CONFIG ?? DEFAULT_CONFIG_PATH;
|
|
127
124
|
const effective = await loadConfig(path);
|
|
128
125
|
const rawToml: Record<string, unknown> = {};
|
|
129
126
|
|
|
@@ -150,11 +147,8 @@ export async function getEffectiveConfig(configPath?: string): Promise<{
|
|
|
150
147
|
"recall.k_lexical",
|
|
151
148
|
"recall.k_vector",
|
|
152
149
|
"recall.k_rerank",
|
|
153
|
-
"output.
|
|
154
|
-
"
|
|
155
|
-
"thresholds.match_score",
|
|
156
|
-
"thresholds.match_margin",
|
|
157
|
-
"thresholds.candidate_floor",
|
|
150
|
+
"output.top_k",
|
|
151
|
+
"output.max_top_k",
|
|
158
152
|
"inference.mode",
|
|
159
153
|
"inference.bundle",
|
|
160
154
|
"inference.models_dir",
|
|
@@ -200,15 +194,8 @@ export function isEnvMasked(key: string, allowEnvOverrides: boolean = true): boo
|
|
|
200
194
|
if (key === "recall.k_lexical" && (process.env.SKILLMUX_RECALL_K_LEXICAL || process.env.RECALL_K_LEXICAL)) return true;
|
|
201
195
|
if (key === "recall.k_vector" && (process.env.SKILLMUX_RECALL_K_VECTOR || process.env.RECALL_K_VECTOR)) return true;
|
|
202
196
|
if (key === "recall.k_rerank" && (process.env.SKILLMUX_RECALL_K_RERANK || process.env.RECALL_K_RERANK)) return true;
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
(process.env.SKILLMUX_OUTPUT_AMBIGUOUS_CANDIDATE_LIMIT ||
|
|
206
|
-
process.env.AMBIGUOUS_CANDIDATE_LIMIT ||
|
|
207
|
-
process.env.SKILLMUX_CANDIDATE_LIMIT ||
|
|
208
|
-
process.env.CANDIDATE_LIMIT)
|
|
209
|
-
) {
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
197
|
+
if (key === "output.top_k" && (process.env.SKILLMUX_OUTPUT_TOP_K || process.env.OUTPUT_TOP_K)) return true;
|
|
198
|
+
if (key === "output.max_top_k" && (process.env.SKILLMUX_OUTPUT_MAX_TOP_K || process.env.OUTPUT_MAX_TOP_K)) return true;
|
|
212
199
|
if (key === "inference.models_dir" && (process.env.SKILLMUX_MODELS_DIR || process.env.SKILL_ROUTER_MODELS_DIR)) return true;
|
|
213
200
|
if (key === "inference.embedding.device" && (process.env.SKILLMUX_EMBED_DEVICE || process.env.EMBED_DEVICE)) return true;
|
|
214
201
|
if (key === "inference.embedding.dtype" && (process.env.SKILLMUX_EMBED_DTYPE || process.env.EMBED_DTYPE)) return true;
|
|
@@ -229,6 +216,12 @@ export function isEnvMasked(key: string, allowEnvOverrides: boolean = true): boo
|
|
|
229
216
|
}
|
|
230
217
|
|
|
231
218
|
export function validateDottedKey(key: string): void {
|
|
219
|
+
if (key === "output.ambiguous_candidate_limit") {
|
|
220
|
+
throw new Error("output.ambiguous_candidate_limit is obsolete. Use output.top_k instead.");
|
|
221
|
+
}
|
|
222
|
+
if (key.startsWith("thresholds.") || key.startsWith("inference.thresholds.")) {
|
|
223
|
+
throw new Error("thresholds are obsolete. Threshold calibration was removed; use output.top_k.");
|
|
224
|
+
}
|
|
232
225
|
const allowed = new Set([
|
|
233
226
|
"config.environment_overrides",
|
|
234
227
|
"vault_path",
|
|
@@ -236,11 +229,8 @@ export function validateDottedKey(key: string): void {
|
|
|
236
229
|
"recall.k_lexical",
|
|
237
230
|
"recall.k_vector",
|
|
238
231
|
"recall.k_rerank",
|
|
239
|
-
"output.
|
|
240
|
-
"
|
|
241
|
-
"thresholds.match_score",
|
|
242
|
-
"thresholds.match_margin",
|
|
243
|
-
"thresholds.candidate_floor",
|
|
232
|
+
"output.top_k",
|
|
233
|
+
"output.max_top_k",
|
|
244
234
|
"inference.mode",
|
|
245
235
|
"inference.bundle",
|
|
246
236
|
"inference.models_dir",
|
|
@@ -279,11 +269,8 @@ export function parseDottedValue(key: string, valueStr: string): unknown {
|
|
|
279
269
|
"recall.k_lexical",
|
|
280
270
|
"recall.k_vector",
|
|
281
271
|
"recall.k_rerank",
|
|
282
|
-
"output.
|
|
283
|
-
"
|
|
284
|
-
"thresholds.match_score",
|
|
285
|
-
"thresholds.match_margin",
|
|
286
|
-
"thresholds.candidate_floor",
|
|
272
|
+
"output.top_k",
|
|
273
|
+
"output.max_top_k",
|
|
287
274
|
"inference.embedding.dimension",
|
|
288
275
|
"inference.timeout_ms",
|
|
289
276
|
"server.rate_limit.requests_per_minute",
|
|
@@ -328,7 +315,7 @@ export async function setDottedKey(
|
|
|
328
315
|
throw new Error(`Cannot set environment-masked configuration key "${key}"`);
|
|
329
316
|
}
|
|
330
317
|
|
|
331
|
-
const path = opts?.configPath ?? DEFAULT_CONFIG_PATH;
|
|
318
|
+
const path = opts?.configPath ?? process.env.SKILLMUX_CONFIG ?? DEFAULT_CONFIG_PATH;
|
|
332
319
|
const targetName = opts?.targetName ?? "local";
|
|
333
320
|
|
|
334
321
|
const { effective: priorEffective, rawToml } = await getEffectiveConfig(path);
|
|
@@ -445,7 +432,7 @@ export async function getLocalConfigStatus(configPath?: string): Promise<ConfigS
|
|
|
445
432
|
|
|
446
433
|
return {
|
|
447
434
|
target: "local",
|
|
448
|
-
desired_source: configPath ?? DEFAULT_CONFIG_PATH,
|
|
435
|
+
desired_source: configPath ?? process.env.SKILLMUX_CONFIG ?? DEFAULT_CONFIG_PATH,
|
|
449
436
|
desired_source_hash: hash,
|
|
450
437
|
active_revision: hash,
|
|
451
438
|
active_source_hash: hash,
|
package/src/config-watcher.ts
CHANGED
|
@@ -12,15 +12,11 @@ import type { Config } from "./types";
|
|
|
12
12
|
|
|
13
13
|
export const LIVE_RELOAD_KEYS = new Set([
|
|
14
14
|
"config.environment_overrides",
|
|
15
|
-
"inference.thresholds.match_score",
|
|
16
|
-
"inference.thresholds.match_margin",
|
|
17
|
-
"inference.thresholds.candidate_floor",
|
|
18
|
-
"inference.calibration.run_id",
|
|
19
15
|
"recall.k_lexical",
|
|
20
16
|
"recall.k_vector",
|
|
21
17
|
"recall.k_rerank",
|
|
22
|
-
"output.
|
|
23
|
-
"
|
|
18
|
+
"output.top_k",
|
|
19
|
+
"output.max_top_k",
|
|
24
20
|
"inference.embedding.endpoint",
|
|
25
21
|
"inference.embedding.api_key_env",
|
|
26
22
|
"inference.reranker.adapter",
|