@theglitchking/semantic-memory 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +61 -0
  4. package/LICENSES/Apache-2.0.txt +202 -0
  5. package/README.md +19 -2
  6. package/THIRD_PARTY_NOTICES.md +17 -0
  7. package/commands/jev-mode.md +18 -0
  8. package/commands/skill-loop.md +23 -0
  9. package/dist/{chunk-TMMTOFHI.js → chunk-34VN47IW.js} +2 -2
  10. package/dist/chunk-3FPUBAEW.js +113 -0
  11. package/dist/chunk-3FPUBAEW.js.map +1 -0
  12. package/dist/{chunk-ZLIHQZ4C.js → chunk-6TIKWOY4.js} +5 -5
  13. package/dist/{chunk-KSH2QO6V.js → chunk-7UAVDOI6.js} +5 -5
  14. package/dist/{chunk-PGVIBUL3.js → chunk-AIXQFTSB.js} +5 -5
  15. package/dist/chunk-BEACL25M.js +224 -0
  16. package/dist/chunk-BEACL25M.js.map +1 -0
  17. package/dist/chunk-CATPGINC.js +186 -0
  18. package/dist/chunk-CATPGINC.js.map +1 -0
  19. package/dist/chunk-DBFD3MTT.js +130 -0
  20. package/dist/chunk-DBFD3MTT.js.map +1 -0
  21. package/dist/{chunk-HZ7B5KHY.js → chunk-EXAUVIGW.js} +4 -4
  22. package/dist/chunk-GYR7U6KA.js +80 -0
  23. package/dist/chunk-GYR7U6KA.js.map +1 -0
  24. package/dist/chunk-IEZVFKUT.js +157 -0
  25. package/dist/chunk-IEZVFKUT.js.map +1 -0
  26. package/dist/chunk-MQS7JEBD.js +137 -0
  27. package/dist/chunk-MQS7JEBD.js.map +1 -0
  28. package/dist/chunk-NCGM36NX.js +118 -0
  29. package/dist/chunk-NCGM36NX.js.map +1 -0
  30. package/dist/chunk-NIZLBNH4.js +165 -0
  31. package/dist/chunk-NIZLBNH4.js.map +1 -0
  32. package/dist/chunk-OT3MV7XF.js +123 -0
  33. package/dist/chunk-OT3MV7XF.js.map +1 -0
  34. package/dist/chunk-P3GLY42T.js +291 -0
  35. package/dist/chunk-P3GLY42T.js.map +1 -0
  36. package/dist/chunk-PJXYCCI2.js +177 -0
  37. package/dist/chunk-PJXYCCI2.js.map +1 -0
  38. package/dist/chunk-R6GNP7DS.js +87 -0
  39. package/dist/chunk-R6GNP7DS.js.map +1 -0
  40. package/dist/chunk-TKHWVISV.js +71 -0
  41. package/dist/chunk-TKHWVISV.js.map +1 -0
  42. package/dist/{chunk-GYBUBJYM.js → chunk-VBLCUWT4.js} +17 -8
  43. package/dist/chunk-VBLCUWT4.js.map +1 -0
  44. package/dist/chunk-VQCNWRTL.js +121 -0
  45. package/dist/chunk-VQCNWRTL.js.map +1 -0
  46. package/dist/{chunk-HUG7E25D.js → chunk-Y7H76Q7F.js} +1 -1
  47. package/dist/chunk-Y7H76Q7F.js.map +1 -0
  48. package/dist/chunk-ZSWI3Y4Y.js +232 -0
  49. package/dist/chunk-ZSWI3Y4Y.js.map +1 -0
  50. package/dist/cli/index.js +381 -52
  51. package/dist/cli/index.js.map +1 -1
  52. package/dist/core/features.d.ts +38 -0
  53. package/dist/core/features.js +28 -0
  54. package/dist/core/index.d.ts +285 -2
  55. package/dist/core/index.js +170 -21
  56. package/dist/core/index.js.map +1 -1
  57. package/dist/core/jev.d.ts +35 -0
  58. package/dist/core/jev.js +20 -0
  59. package/dist/core/skill-curator.d.ts +1 -0
  60. package/dist/core/skill-curator.js +41 -0
  61. package/dist/core/skill-evidence.d.ts +25 -0
  62. package/dist/core/skill-evidence.js +23 -0
  63. package/dist/core/skill-queue.d.ts +40 -0
  64. package/dist/core/skill-queue.js +38 -0
  65. package/dist/core/skill-review.d.ts +3 -0
  66. package/dist/core/skill-review.js +29 -0
  67. package/dist/core/skill-worker.d.ts +21 -0
  68. package/dist/core/skill-worker.js +70 -0
  69. package/dist/core/skill-worker.js.map +1 -0
  70. package/dist/{dossier-EN26WDKC.js → dossier-RXBLQOXC.js} +3 -3
  71. package/dist/failure-eval-IUJO47UC.js +18 -0
  72. package/dist/failure-eval-IUJO47UC.js.map +1 -0
  73. package/dist/{healthcheck-53OVXUQQ.js → healthcheck-S7D6YNTA.js} +7 -7
  74. package/dist/healthcheck-S7D6YNTA.js.map +1 -0
  75. package/dist/{lexicon-XNA5IFIA.js → lexicon-SEJSLBHO.js} +4 -4
  76. package/dist/lexicon-SEJSLBHO.js.map +1 -0
  77. package/dist/{lint-O4RQ47NX.js → lint-3JNLRM5U.js} +6 -6
  78. package/dist/lint-3JNLRM5U.js.map +1 -0
  79. package/dist/mcp/server.js +19 -17
  80. package/dist/mcp/server.js.map +1 -1
  81. package/dist/{profile-SIWAVD7O.js → profile-SNULTJ27.js} +4 -4
  82. package/dist/profile-SNULTJ27.js.map +1 -0
  83. package/dist/{schema-UJNYYRZZ.js → schema-RDYUK6CC.js} +2 -2
  84. package/dist/schema-RDYUK6CC.js.map +1 -0
  85. package/dist/skill-curator-D7HHZaUI.d.ts +71 -0
  86. package/dist/skill-land-XU2APPBJ.js +31 -0
  87. package/dist/skill-land-XU2APPBJ.js.map +1 -0
  88. package/dist/skill-ledger-AYXVNFX7.js +18 -0
  89. package/dist/skill-ledger-AYXVNFX7.js.map +1 -0
  90. package/dist/skill-merge-GXBQLBZA.js +24 -0
  91. package/dist/skill-merge-GXBQLBZA.js.map +1 -0
  92. package/dist/skill-review-BaW5n0Zg.d.ts +179 -0
  93. package/dist/skill-schema-RVB3EHEF.js +30 -0
  94. package/dist/skill-schema-RVB3EHEF.js.map +1 -0
  95. package/hooks/hooks.json +26 -1
  96. package/hooks/jev-bridge.py +109 -0
  97. package/hooks/session-start.js +5 -0
  98. package/hooks/skill-loop.js +65 -0
  99. package/hooks/vault-context.js +104 -6
  100. package/package.json +4 -2
  101. package/dist/chunk-5F5ZIAW2.js +0 -79
  102. package/dist/chunk-5F5ZIAW2.js.map +0 -1
  103. package/dist/chunk-GYBUBJYM.js.map +0 -1
  104. package/dist/chunk-HUG7E25D.js.map +0 -1
  105. package/dist/failure-eval-37JRNTIW.js +0 -10
  106. /package/dist/{chunk-TMMTOFHI.js.map → chunk-34VN47IW.js.map} +0 -0
  107. /package/dist/{chunk-ZLIHQZ4C.js.map → chunk-6TIKWOY4.js.map} +0 -0
  108. /package/dist/{chunk-KSH2QO6V.js.map → chunk-7UAVDOI6.js.map} +0 -0
  109. /package/dist/{chunk-PGVIBUL3.js.map → chunk-AIXQFTSB.js.map} +0 -0
  110. /package/dist/{chunk-HZ7B5KHY.js.map → chunk-EXAUVIGW.js.map} +0 -0
  111. /package/dist/{dossier-EN26WDKC.js.map → core/features.js.map} +0 -0
  112. /package/dist/{failure-eval-37JRNTIW.js.map → core/jev.js.map} +0 -0
  113. /package/dist/{healthcheck-53OVXUQQ.js.map → core/skill-curator.js.map} +0 -0
  114. /package/dist/{lexicon-XNA5IFIA.js.map → core/skill-evidence.js.map} +0 -0
  115. /package/dist/{lint-O4RQ47NX.js.map → core/skill-queue.js.map} +0 -0
  116. /package/dist/{profile-SIWAVD7O.js.map → core/skill-review.js.map} +0 -0
  117. /package/dist/{schema-UJNYYRZZ.js.map → dossier-RXBLQOXC.js.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -1,10 +1,34 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ tuneDescription
4
+ } from "../chunk-OT3MV7XF.js";
5
+ import {
6
+ buildEvalSet,
7
+ runTrigger
8
+ } from "../chunk-MQS7JEBD.js";
9
+ import {
10
+ runTaskEval
11
+ } from "../chunk-DBFD3MTT.js";
12
+ import "../chunk-IEZVFKUT.js";
13
+ import {
14
+ parseSkill
15
+ } from "../chunk-ZSWI3Y4Y.js";
16
+ import "../chunk-J7LYDRML.js";
17
+ import {
18
+ ENV,
19
+ describeFeature,
20
+ featureState,
21
+ setRepoConfig,
22
+ setSettingsEnv
23
+ } from "../chunk-VQCNWRTL.js";
24
+ import "../chunk-GYR7U6KA.js";
25
+ import "../chunk-H2BES6FU.js";
2
26
  import "../chunk-PZ5AY32C.js";
3
27
 
4
28
  // src/cli/index.ts
5
29
  import { program } from "commander";
6
- import { resolve as resolve2, join as join3, dirname, relative } from "path";
7
- import { existsSync as existsSync3, readFileSync, writeFileSync, rmSync } from "fs";
30
+ import { resolve as resolve4, join as join3, dirname, relative } from "path";
31
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, rmSync } from "fs";
8
32
  import { createRequire } from "module";
9
33
  import { spawnSync } from "child_process";
10
34
  import { pathToFileURL } from "url";
@@ -71,15 +95,15 @@ async function listShippedSkills(sourceRoot) {
71
95
  const skillsDir = join(sourceRoot, "skills");
72
96
  if (!existsSync(skillsDir)) return [];
73
97
  const entries = await readdir(skillsDir, { withFileTypes: true });
74
- const out = [];
98
+ const out2 = [];
75
99
  for (const e of entries) {
76
100
  if (!e.isDirectory()) continue;
77
101
  const dir = join(skillsDir, e.name);
78
102
  const skillFile = join(dir, "SKILL.md");
79
103
  if (!existsSync(skillFile)) continue;
80
- out.push({ name: e.name, dir });
104
+ out2.push({ name: e.name, dir });
81
105
  }
82
- return out;
106
+ return out2;
83
107
  }
84
108
  async function sha256OfFile(path) {
85
109
  const buf = await readFile(path);
@@ -305,6 +329,240 @@ function safeMtime(path) {
305
329
  }
306
330
  }
307
331
 
332
+ // src/cli/features.ts
333
+ var NEXT = "takes effect in the next Claude Code session (settings env is read at session start)";
334
+ function toggle(feature, on, opts) {
335
+ const cwd = process.cwd();
336
+ try {
337
+ if (opts.repo) {
338
+ const path = setRepoConfig(cwd, "skill_loop", opts.clear ? void 0 : on ? "on" : "off");
339
+ console.log(`\u2713 ${opts.clear ? "cleared" : "set"} skill_loop${opts.clear ? "" : `: ${on ? "on" : "off"}`} in ${path}`);
340
+ } else {
341
+ const { path, backup } = setSettingsEnv(ENV[feature], on ? "1" : void 0);
342
+ console.log(`\u2713 ${on ? "set" : "removed"} ${ENV[feature]}${on ? "=1" : ""} in ${path}${backup ? ` (previous file: ${backup})` : ""}`);
343
+ console.log(` ${NEXT}`);
344
+ }
345
+ } catch (e) {
346
+ console.error(`\u2717 ${e.message}`);
347
+ process.exit(1);
348
+ }
349
+ const env = { ...process.env };
350
+ if (!opts.repo) {
351
+ if (on) env[ENV[feature]] = "1";
352
+ else delete env[ENV[feature]];
353
+ }
354
+ console.log(` now: ${describeFeature(featureState(feature, cwd, env))}`);
355
+ }
356
+ function registerFeatureCommands(program2, extraStatus = {}) {
357
+ for (const [feature, name, what] of [
358
+ ["skill_loop", "skill-loop", "the skill loop (learned skills between sessions)"],
359
+ ["jev", "jev-mode", "jev mode (Jev through jev-loop for closed-set decisions)"]
360
+ ]) {
361
+ const cmd = program2.commands.find((c) => c.name() === name) ?? program2.command(name).description(`Turn ${what} on or off, or show its state.`);
362
+ const on = cmd.command("on").description(`Turn ${what} on (sets ${ENV[feature]}=1 in ~/.claude/settings.json env).`);
363
+ const off = cmd.command("off").description(`Turn ${what} off (removes ${ENV[feature]} from ~/.claude/settings.json env).`);
364
+ if (feature === "skill_loop") {
365
+ for (const c of [on, off]) {
366
+ c.option("--repo", "Write the per-repo override in .claude/.semantic-memory/config instead (beats the global switch)");
367
+ c.option("--clear", "With --repo: remove the override so the global switch applies");
368
+ }
369
+ }
370
+ on.action((opts) => toggle(feature, true, opts));
371
+ off.action((opts) => toggle(feature, false, opts));
372
+ cmd.command("status").description(`Show whether ${what} is on, and why.`).option("--json", "JSON output").action(async (opts) => {
373
+ const cwd = process.cwd();
374
+ const s = featureState(feature, cwd);
375
+ const extra = await extraStatus[feature]?.(cwd) ?? [];
376
+ if (opts.json) console.log(JSON.stringify({ ...s, details: extra }, null, 2));
377
+ else for (const line of [describeFeature(s), ...extra.map((l) => ` ${l}`)]) console.log(line);
378
+ process.exit(0);
379
+ });
380
+ }
381
+ }
382
+
383
+ // src/cli/skill-loop.ts
384
+ import { resolve as resolve2 } from "path";
385
+ function registerSkillLoopCommands(program2) {
386
+ const cmd = program2.commands.find((c) => c.name() === "skill-loop");
387
+ registerReviewCommands(cmd);
388
+ registerCuratorCommands(cmd);
389
+ cmd.command("worker").description("Drain this repo's review queue (started detached by the SessionEnd hook; safe to run by hand).").option("--repo <dir>", "Repo root (default: cwd)").action(async (opts) => {
390
+ const repo = resolve2(opts.repo ?? process.cwd());
391
+ const { featureOn } = await import("../core/features.js");
392
+ const { log } = await import("../core/skill-queue.js");
393
+ if (!featureOn("skill_loop", repo)) {
394
+ log(repo, "worker: skill loop is off \u2014 queue left as it is");
395
+ process.exit(0);
396
+ }
397
+ const { drain } = await import("../core/skill-worker.js");
398
+ const { reviewSession } = await import("../core/skill-review.js");
399
+ const r = await drain(repo, (job) => reviewSession(job));
400
+ console.log(r.locked ? "another worker holds this repo's lock" : `reviewed ${r.reviewed} session(s)${r.capped ? "; daily cap reached" : ""}${r.failed ? `; ${r.failed} set aside` : ""}`);
401
+ process.exit(0);
402
+ });
403
+ }
404
+ function registerReviewCommands(cmd) {
405
+ cmd.command("review").description("List skill drafts with their evidence (and diff, for patches); approve, reject or evaluate one.").option("--json", "JSON output").option("--approve <id>", "Land a draft (writes the skill and a ledger entry)").option("--promote", "With --approve: land it as a global skill replacing the project copies (needs a promotion)").option("--override <why>", "With --approve: land it although its eval gate did not adopt it (the reason is ledgered)").option("--reject <id>", "Drop a draft (ledgered)").option("--eval <id>", "Run the trigger eval on a draft and record the scores and the gate's verdict").option("--task", "With --eval: also replay the task with and without the skill (runs the draft's Verify command)").action(async (opts) => {
406
+ const { repoRoot } = await import("../core/skill-evidence.js");
407
+ const land = await import("../skill-land-XU2APPBJ.js");
408
+ const repo = repoRoot(process.cwd());
409
+ try {
410
+ if (opts.approve) {
411
+ const e = land.approveDraft(repo, opts.approve, { promote: opts.promote, override: opts.override });
412
+ console.log(`\u2713 ${e.action}: ${e.files.map((f) => f.path).join(", ")} (ledger ${e.id}; undo: skill-loop rollback ${e.id})`);
413
+ } else if (opts.reject) {
414
+ const e = land.rejectDraft(repo, opts.reject);
415
+ console.log(`\u2713 rejected ${e.skill} (ledger ${e.id})`);
416
+ } else if (opts.eval) {
417
+ const ev = await land.evalDraft(repo, opts.eval, { task: opts.task });
418
+ console.log(opts.json ? JSON.stringify(ev, null, 2) : `gate: ${ev.gate.verdict} \u2014 ${ev.gate.reasons.join("; ")} (${ev.prompts} prompts, ${ev.runs} claude runs)`);
419
+ } else {
420
+ const drafts = land.describeDrafts(repo);
421
+ if (opts.json) console.log(JSON.stringify(drafts, null, 2));
422
+ else if (!drafts.length) console.log("no skill drafts waiting");
423
+ else
424
+ for (const d of drafts) {
425
+ console.log(`
426
+ ${d.id} ${d.kind} ${d.name} \u2192 ${d.destination}`);
427
+ console.log(` scope: ${d.scope}${d.scope_why ? ` (${d.scope_why})` : ""}${d.promotion?.length ? `; also in ${d.promotion.map((p) => p.repo).join(", ")}: --promote offers one global skill` : ""}`);
428
+ console.log(` evidence: sessions ${d.provenance?.sessions.join(", ")}; failures ${d.provenance?.failures.join(", ") || "-"}; corrections ${d.provenance?.corrections.join(", ") || "-"}`);
429
+ if (d.verify.kind === "command") console.log(` verify command (a --task eval runs it): ${d.verify.command}`);
430
+ if (d.evals) console.log(` evals: ${JSON.stringify(d.evals.gate)}`);
431
+ for (const w of d.warnings) console.log(` ! ${w}`);
432
+ }
433
+ }
434
+ } catch (e) {
435
+ console.error(`\u2717 ${e.message}`);
436
+ process.exit(1);
437
+ }
438
+ process.exit(0);
439
+ });
440
+ cmd.command("rollback <ledger-id>").description("Undo one ledgered change exactly (refuses if a file changed since).").action(async (id) => {
441
+ const { repoRoot } = await import("../core/skill-evidence.js");
442
+ const { rollback } = await import("../skill-ledger-AYXVNFX7.js");
443
+ try {
444
+ const e = rollback(repoRoot(process.cwd()), id);
445
+ console.log(`\u2713 ${e.why} (ledger ${e.id})`);
446
+ } catch (err) {
447
+ console.error(`\u2717 ${err.message}`);
448
+ process.exit(1);
449
+ }
450
+ process.exit(0);
451
+ });
452
+ }
453
+ function registerCuratorCommands(cmd) {
454
+ const repoOf = async () => (await import("../core/skill-evidence.js")).repoRoot(process.cwd());
455
+ cmd.command("curate").description("Mark learned skills unused 14 days stale and archive those unused 30 days (pinned / cited / new ones skipped).").option("--dry-run", "Show what would change, change nothing").option("--merge", "Also propose merges of overlapping learned skills as review drafts (opt-in)").option("--auto", "Started by SessionStart: quiet, logs to skill-loop.log").action(async (opts) => {
456
+ const repo = await repoOf();
457
+ const { curate } = await import("../core/skill-curator.js");
458
+ const { log } = await import("../core/skill-queue.js");
459
+ const r = curate(repo, { dryRun: opts.dryRun });
460
+ const changes = r.actions.filter((a) => a.action !== "skip" && a.action !== "keep");
461
+ if (opts.auto) log(repo, `curator: ${changes.map((a) => `${a.action} ${a.skill} (${a.why})`).join(", ") || "nothing to do"}`);
462
+ else for (const a of r.actions) console.log(`${opts.dryRun ? "would " : ""}${a.action.padEnd(10)} ${a.skill} \u2014 ${a.why}`);
463
+ if (!opts.auto && !r.actions.length) console.log("no learned skills here");
464
+ const { consolidateOn, proposeMerges } = await import("../skill-merge-GXBQLBZA.js");
465
+ if (!opts.dryRun && (opts.merge || consolidateOn(repo))) {
466
+ const m = await proposeMerges(repo);
467
+ const line = `merge: ${m.drafted.length} draft(s) for review${m.skipped.length ? `; ${m.skipped.join("; ")}` : ""}`;
468
+ if (opts.auto) log(repo, line);
469
+ else console.log(line);
470
+ }
471
+ process.exit(0);
472
+ });
473
+ cmd.command("restore <name>").description("Bring an archived learned skill back, active.").action(async (name) => {
474
+ const { restore } = await import("../core/skill-curator.js");
475
+ try {
476
+ const e = restore(await repoOf(), name);
477
+ console.log(`\u2713 restored ${name} \u2192 ${e.files[0].path} (ledger ${e.id}). Pin it (skill_loop.pinned: true) to keep the curator away.`);
478
+ } catch (err) {
479
+ console.error(`\u2717 ${err.message}`);
480
+ process.exit(1);
481
+ }
482
+ process.exit(0);
483
+ });
484
+ cmd.command("verify").description("Run each landed learned skill's ## Verify command here; failures are flagged, nothing is archived.").action(async () => {
485
+ const { verifySkills } = await import("../core/skill-curator.js");
486
+ const r = verifySkills(await repoOf());
487
+ for (const x of r) console.log(`${x.result === "pass" ? "\u2713" : x.result === "fail" ? "\u2717" : "\xB7"} ${x.skill}: ${x.result} \u2014 ${x.detail}`);
488
+ if (!r.length) console.log("no learned skills here");
489
+ process.exit(r.some((x) => x.result === "fail") ? 1 : 0);
490
+ });
491
+ }
492
+ async function skillLoopStatus(cwd) {
493
+ const { repoRoot } = await import("../core/skill-evidence.js");
494
+ const { listDrafts } = await import("../core/skill-review.js");
495
+ const { runsToday, dailyRunCap, pendingJobs, lockHolder } = await import("../core/skill-queue.js");
496
+ const repo = repoRoot(cwd);
497
+ const lines = [
498
+ `drafts waiting: ${listDrafts(repo).length}${listDrafts(repo).length ? " (/skill-loop review)" : ""}`,
499
+ `queued sessions for this repo: ${pendingJobs(repo).length}${lockHolder(repo) ? ` (worker running, pid ${lockHolder(repo)})` : ""}`,
500
+ `reviews today (all repos): ${runsToday()} of ${dailyRunCap()} (SEMANTIC_MEMORY_SKILL_LOOP_DAILY_RUNS)`
501
+ ];
502
+ const { curatorState } = await import("../core/skill-curator.js");
503
+ const st = curatorState(repo);
504
+ lines.push(`curator: ${st.last_run ? `last run ${st.last_run.slice(0, 16)} \u2014 ${st.last_report}` : "not run yet (first run 7 days after the loop first sees a repo)"}`);
505
+ if (st.flagged?.length) lines.push(`verify failures flagged: ${st.flagged.map((f) => f.name).join(", ")}`);
506
+ return lines;
507
+ }
508
+
509
+ // src/cli/skill-eval.ts
510
+ import { readFileSync } from "fs";
511
+ import { resolve as resolve3 } from "path";
512
+ var pct = (n) => n === null ? "n/a" : `${Math.round(n * 100)}%`;
513
+ function load(file, promptsFile) {
514
+ const skillRaw = readFileSync(resolve3(file), "utf8");
515
+ const candidates = readFileSync(resolve3(promptsFile), "utf8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
516
+ return { skillRaw, prompts: buildEvalSet(parseSkill(skillRaw), candidates) };
517
+ }
518
+ var out = (json, data, text) => console.log(json ? JSON.stringify(data, null, 2) : text());
519
+ function registerSkillEvalCommands(program2) {
520
+ const ev = program2.command("skill-eval").description("Evaluate a learned skill: does it trigger, does it help, can its description be better (v1.7)");
521
+ const repoRoot = process.cwd();
522
+ const fail = (e) => {
523
+ console.error(`\u2717 ${e.message}`);
524
+ process.exit(1);
525
+ };
526
+ ev.command("trigger <skill-md>").description("Run the trigger eval; --prompts is JSONL of {session, text}").requiredOption("--prompts <jsonl>", "candidate prompts, one {session, text} per line").option("--json", "machine-readable output").action(async (file, opts) => {
527
+ try {
528
+ const { skillRaw, prompts } = load(file, opts.prompts);
529
+ const r = await runTrigger(prompts, { repoRoot, skillRaw });
530
+ out(
531
+ opts.json,
532
+ r,
533
+ () => [
534
+ `${r.skill}: ${prompts.length} prompts, ${r.runs} claude run(s)`,
535
+ ...["train", "heldout"].map((s) => ` ${s}: triggers on ${pct(r[s].positiveRate)} of positives, false-triggers on ${pct(r[s].falseTriggerRate)} of near-misses (n=${r[s].n})`)
536
+ ].join("\n")
537
+ );
538
+ } catch (e) {
539
+ fail(e);
540
+ }
541
+ });
542
+ ev.command("task <skill-md>").description("Replay a task with and without the skill and check its ## Verify").requiredOption("--prompt <text>", "the task prompt").option("--json", "machine-readable output").action(async (file, opts) => {
543
+ try {
544
+ const r = await runTaskEval({ repoRoot, skillRaw: readFileSync(resolve3(file), "utf8"), prompt: opts.prompt });
545
+ out(opts.json, r, () => `${r.skill}: with skill ${r.with.passed ? "PASS" : "FAIL"} (${r.with.evidence}); without ${r.without.passed ? "PASS" : "FAIL"} (${r.without.evidence})`);
546
+ } catch (e) {
547
+ fail(e);
548
+ }
549
+ });
550
+ ev.command("tune <skill-md>").description("Propose a better description (never writes the skill file)").requiredOption("--prompts <jsonl>", "candidate prompts, one {session, text} per line").option("--json", "machine-readable output").action(async (file, opts) => {
551
+ try {
552
+ const { skillRaw, prompts } = load(file, opts.prompts);
553
+ const r = await tuneDescription({ repoRoot, skillRaw, prompts });
554
+ out(
555
+ opts.json,
556
+ r,
557
+ () => r.improved ? `proposed (held-out ${pct(r.original.heldout)} -> ${pct(r.best.heldout)}):
558
+ ${r.best.description}` : `no better description found (held-out ${pct(r.original.heldout)}); ${r.candidates.length - 1} tried`
559
+ );
560
+ } catch (e) {
561
+ fail(e);
562
+ }
563
+ });
564
+ }
565
+
308
566
  // src/cli/index.ts
309
567
  var require_ = createRequire(import.meta.url);
310
568
  var { version } = require_("../../package.json");
@@ -314,7 +572,7 @@ function runRelink(cwd) {
314
572
  const candidates = [
315
573
  join3(cwd, "node_modules", "@theglitchking", "semantic-memory", "scripts", "link-skills.js"),
316
574
  join3(cwd, "node_modules", "@theglitchking", "semantic-sidekick", "scripts", "link-skills.js"),
317
- resolve2(process.cwd(), "scripts", "link-skills.js")
575
+ resolve4(process.cwd(), "scripts", "link-skills.js")
318
576
  ];
319
577
  const script = candidates.find((p) => existsSync3(p));
320
578
  if (!script) {
@@ -400,7 +658,7 @@ function localBinArg(cwd) {
400
658
  }
401
659
  function readJsonSafe(path) {
402
660
  try {
403
- const raw = readFileSync(path, "utf8");
661
+ const raw = readFileSync2(path, "utf8");
404
662
  return raw.trim() ? JSON.parse(raw) : null;
405
663
  } catch {
406
664
  return null;
@@ -636,38 +894,81 @@ program.command("search <query>").description("Run a vault search and print top
636
894
  process.exit(1);
637
895
  }
638
896
  });
639
- program.command("eval-failures").description("Replay failed tool calls through search and measure how often the top hit answers them (v1.6 failure lookup)").requiredOption("--cases <file>", "JSONL of {id, notes, error, command}").requiredOption("--judgments <file>", "JSONL of {case, path, relevant}").option("--limit <n>", "Hits per search", (v) => parseInt(v, 10), 5).option("--query <mode>", "raw (error + command as-is) or normalized (failureQuery)", "normalized").option("--target <p>", "Target precision for the recommended threshold", (v) => parseFloat(v), 0.95).option("--min-judged <n>", "Judged injections needed before a threshold can be recommended", (v) => parseInt(v, 10), 20).option("--unjudged-out <file>", "Write unlabelled top hits here (JSONL) for labelling").option("--hits-out <file>", "Write every case's hits here (JSONL) for analysis").option("--json", "Print the report as JSON").action(async (opts) => {
897
+ program.command("eval-failures").description("Replay failed tool calls through search and measure how often the top hit answers them (v1.6 failure lookup)").requiredOption("--cases <file>", "JSONL of {id, notes, error, command}").requiredOption("--judgments <file>", "JSONL of {case, path, relevant}").option("--limit <n>", "Hits per search", (v) => parseInt(v, 10), 5).option("--query <mode>", "raw (error + command as-is) or normalized (failureQuery)", "normalized").option("--target <p>", "Target precision for the recommended threshold", (v) => parseFloat(v), 0.95).option("--min-judged <n>", "Judged injections needed before a threshold can be recommended", (v) => parseInt(v, 10), 20).option("--unjudged-out <file>", "Write unlabelled top hits here (JSONL) for labelling").option("--hits-out <file>", "Write every case's hits here (JSONL) for analysis").option("--json", "Print the report as JSON").option("--hits-in <file>", "Reuse hits from an earlier --hits-out instead of searching").option("--gate <kind>", "Also replay the failure hook's gate: 'jev' (needs --jev-scores)").option("--jev-scores <files...>", "JSONL of {case, path, p} from scripts/eval-failures/judge-jev.mjs").option("--gate-batch-out <file>", "With --gate jev: write the candidates Jev has not scored as a judge batch (set B format)").action(async (opts) => {
640
898
  const { openSearch } = await import("../search-325ULY57.js");
641
- const { evaluateFailures, formatFailureEval } = await import("../failure-eval-37JRNTIW.js");
899
+ const { evaluateFailures, formatFailureEval } = await import("../failure-eval-IUJO47UC.js");
642
900
  const { failureQuery } = await import("../core/failure-query.js");
643
901
  const toQuery = (c) => opts.query === "raw" ? `${c.error}
644
902
  ${c.command}` : failureQuery(c.error, c.command);
645
- const readJsonl = (p) => readFileSync(resolve2(p), "utf-8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
903
+ const readJsonl = (p) => readFileSync2(resolve4(p), "utf-8").split("\n").filter(Boolean).map((l) => JSON.parse(l));
646
904
  const cases = readJsonl(opts.cases);
647
905
  const judgments = readJsonl(opts.judgments);
648
906
  const searchers = /* @__PURE__ */ new Map();
649
907
  const results = [];
650
- for (const c of cases) {
651
- if (!searchers.has(c.notes)) searchers.set(c.notes, await openSearch(c.notes));
652
- const hits = await searchers.get(c.notes).search(toQuery(c), opts.limit);
653
- results.push({ case: c.id, hits: hits.map((h) => ({ path: h.path, score: h.score })) });
908
+ if (opts.hitsIn) {
909
+ const ids = new Set(cases.map((c) => c.id));
910
+ results.push(...readJsonl(opts.hitsIn).filter((r) => ids.has(r.case)));
911
+ } else {
912
+ for (const c of cases) {
913
+ if (!searchers.has(c.notes)) searchers.set(c.notes, await openSearch(c.notes));
914
+ const hits = await searchers.get(c.notes).search(toQuery(c), opts.limit);
915
+ results.push({ case: c.id, hits: hits.map((h) => ({ path: h.path, score: h.score })) });
916
+ }
654
917
  }
655
918
  const report = evaluateFailures(results, judgments, { targetPrecision: opts.target, minJudged: opts.minJudged });
919
+ if (opts.gate) {
920
+ if (opts.gate !== "jev" || !opts.jevScores) {
921
+ console.error("--gate jev needs --jev-scores <file...>");
922
+ process.exit(2);
923
+ }
924
+ const { evaluateGate, formatGate } = await import("../failure-eval-IUJO47UC.js");
925
+ const jev = opts.jevScores.flatMap((f) => readJsonl(f));
926
+ const gate = evaluateGate(results, judgments, jev);
927
+ const veto = evaluateGate(results, judgments, jev, { mode: "veto", thresholds: [0.3, 0.35, 0.4, 0.45, 0.5] });
928
+ if (opts.gateBatchOut) {
929
+ const { default: matter } = await import("../matter-SNJ2WDL6.js");
930
+ const byId = new Map(cases.map((c) => [c.id, c]));
931
+ const missing = [...new Map([...gate.missing, ...veto.missing].map((m) => [`${m.case}\0${m.path}`, m])).values()];
932
+ const batch = missing.map((m) => {
933
+ const c = byId.get(m.case);
934
+ let title = m.path, excerpt = "";
935
+ try {
936
+ const doc = matter(readFileSync2(join3(c.notes, m.path), "utf-8"));
937
+ title = String(doc.data.title ?? m.path);
938
+ excerpt = doc.content.replace(/^#.*$/m, "").trim().slice(0, 600);
939
+ } catch {
940
+ }
941
+ return { case: m.case, path: m.path, command: c.command, error: c.error, doc_title: title, doc_excerpt: excerpt };
942
+ });
943
+ writeFileSync(resolve4(opts.gateBatchOut), JSON.stringify(batch, null, 1));
944
+ console.error(`${batch.length} unscored candidates \u2192 ${opts.gateBatchOut} (judge them with judge-jev.mjs, then re-run)`);
945
+ }
946
+ if (opts.json) {
947
+ console.log(JSON.stringify({ ...report, gate: gate.rows, veto: veto.rows }, null, 2));
948
+ process.exit(0);
949
+ }
950
+ console.log(formatFailureEval(report, `query: ${opts.query}`));
951
+ console.log("");
952
+ console.log(formatGate(gate.rows));
953
+ console.log("");
954
+ console.log(formatGate(veto.rows, "veto"));
955
+ process.exit(0);
956
+ }
656
957
  if (opts.hitsOut) {
657
- writeFileSync(resolve2(opts.hitsOut), results.map((r) => JSON.stringify(r)).join("\n") + "\n");
958
+ writeFileSync(resolve4(opts.hitsOut), results.map((r) => JSON.stringify(r)).join("\n") + "\n");
658
959
  }
659
960
  if (opts.unjudgedOut) {
660
- writeFileSync(resolve2(opts.unjudgedOut), report.unjudged.map((u) => JSON.stringify(u)).join("\n") + "\n");
961
+ writeFileSync(resolve4(opts.unjudgedOut), report.unjudged.map((u) => JSON.stringify(u)).join("\n") + "\n");
661
962
  }
662
963
  console.log(opts.json ? JSON.stringify(report, null, 2) : formatFailureEval(report, `query: ${opts.query}`));
663
964
  process.exit(0);
664
965
  });
665
966
  program.command("lint").description("Lint the vault against vault.schema.yml (schema violations + missing provenance + stale)").requiredOption("--notes <path>", "Path to markdown notes directory").option("--rule <name>", "Limit output to one rule: schema_violations | missing_provenance | stale").option("--json", "Emit JSON instead of the human-readable report").option("--strict", "Exit non-zero on warnings too (default: exit non-zero only on errors)").action(async (opts) => {
666
- const { lintVault, formatLintReport } = await import("../lint-O4RQ47NX.js");
667
- const report = await lintVault(resolve2(opts.notes));
967
+ const { lintVault, formatLintReport } = await import("../lint-3JNLRM5U.js");
968
+ const report = await lintVault(resolve4(opts.notes));
668
969
  if (opts.json) {
669
- const out = opts.rule ? report.byRule[opts.rule] : report;
670
- console.log(JSON.stringify(out, null, 2));
970
+ const out2 = opts.rule ? report.byRule[opts.rule] : report;
971
+ console.log(JSON.stringify(out2, null, 2));
671
972
  } else {
672
973
  console.log(formatLintReport(report, { rule: opts.rule }));
673
974
  }
@@ -687,7 +988,7 @@ program.command("log-event").description("Append a structured event to the vault
687
988
  }
688
989
  }
689
990
  try {
690
- const entry = await logEvent(resolve2(opts.notes), {
991
+ const entry = await logEvent(resolve4(opts.notes), {
691
992
  kind: opts.kind,
692
993
  summary: opts.summary,
693
994
  payload
@@ -701,7 +1002,7 @@ program.command("log-event").description("Append a structured event to the vault
701
1002
  program.command("log-query").description("Read structured log entries from log.md \u2014 filters by kind, date range, limit").requiredOption("--notes <path>", "Path to markdown notes directory").option("--kind <name>", "Filter by kind (ingest, synthesis, error, mode_change, etc.)").option("--after <iso>", "ISO timestamp \u2014 only entries on or after").option("--before <iso>", "ISO timestamp \u2014 only entries on or before").option("--limit <n>", "Max entries (most recent)", (v) => parseInt(v, 10)).action(async (opts) => {
702
1003
  const { logQuery } = await import("../log-RM2NDNPH.js");
703
1004
  try {
704
- const entries = await logQuery(resolve2(opts.notes), {
1005
+ const entries = await logQuery(resolve4(opts.notes), {
705
1006
  kind: opts.kind,
706
1007
  after: opts.after,
707
1008
  before: opts.before,
@@ -714,8 +1015,8 @@ program.command("log-query").description("Read structured log entries from log.m
714
1015
  }
715
1016
  });
716
1017
  program.command("install-schema").description("Bootstrap vault.schema.yml in the vault root with the default schema").requiredOption("--notes <path>", "Path to markdown notes directory").option("--force", "Overwrite existing schema file").action(async (opts) => {
717
- const { installDefaultSchema } = await import("../schema-UJNYYRZZ.js");
718
- const r = await installDefaultSchema(resolve2(opts.notes), opts.force);
1018
+ const { installDefaultSchema } = await import("../schema-RDYUK6CC.js");
1019
+ const r = await installDefaultSchema(resolve4(opts.notes), opts.force);
719
1020
  console.log(r.written ? `Installed default schema: ${r.path}` : `Schema already exists: ${r.path} (use --force to overwrite)`);
720
1021
  process.exit(0);
721
1022
  });
@@ -728,7 +1029,7 @@ program.command("tools [name]").description("List all MCP tools, or show details
728
1029
  process.exit(0);
729
1030
  });
730
1031
  program.command("serve", { isDefault: true }).description("Start the MCP server (default command)").requiredOption("--notes <path>", "Path to markdown notes directory").option("--reindex", "Force full reindex and exit").option("--stats", "Show vault statistics and exit").option("--wait-for-ready", "Block startup until index is fully built before serving (default: index in background; tools return 'Indexing in progress' until ready)").option("--read-only", "Suppress write tools (create_note, update_note, delete_note, move_note, update_frontmatter, manage_tags, rename_tag, log_event, ...) \u2014 use for shared docs vaults owned by another tool").option("--model <name>", "Embedding model to use (default: all-MiniLM-L6-v2, fast; use nomic-ai/nomic-embed-text-v1.5 for higher quality)").option("--workers <n>", "Number of worker threads for parallel embedding", parseInt).option("--batch-size <n>", "Texts per ONNX forward pass (default: 8)", parseInt).option("--no-quantized", "Use full-precision model instead of quantized (slower, slightly higher quality)").option("--no-watch", "Disable file watcher").action(async (opts) => {
731
- const notesPath = resolve2(opts.notes);
1032
+ const notesPath = resolve4(opts.notes);
732
1033
  if (!existsSync3(notesPath)) {
733
1034
  console.error(`Error: notes directory not found: ${notesPath}`);
734
1035
  process.exit(1);
@@ -824,7 +1125,7 @@ run 'npm install --save @theglitchking/semantic-memory' first, then re-run this
824
1125
  }
825
1126
  const bakPath = join3(dirname(mcpPath), ".mcp.json.bak");
826
1127
  try {
827
- writeFileSync(bakPath, readFileSync(mcpPath, "utf8"));
1128
+ writeFileSync(bakPath, readFileSync2(mcpPath, "utf8"));
828
1129
  console.log(`backup written: ${bakPath}`);
829
1130
  } catch (err) {
830
1131
  console.error(`could not write backup: ${err.message}`);
@@ -836,7 +1137,7 @@ run 'npm install --save @theglitchking/semantic-memory' first, then re-run this
836
1137
  console.error(`verification failed (exit ${verify.status}):
837
1138
  ${verify.stderr?.toString() || ""}`);
838
1139
  console.error(`rolling back from ${bakPath}`);
839
- writeFileSync(mcpPath, readFileSync(bakPath, "utf8"));
1140
+ writeFileSync(mcpPath, readFileSync2(bakPath, "utf8"));
840
1141
  process.exit(1);
841
1142
  }
842
1143
  console.log(`\u2713 .mcp.json normalized and verified. Toggle the MCPs in /mcp to reconnect.`);
@@ -895,7 +1196,7 @@ ${r2.stderr?.toString() || ""}`);
895
1196
  }
896
1197
  }
897
1198
  if (smokeOk && !opts.json) console.log(`\u2713 local install starts cleanly (v${smokeVersion})`);
898
- const { runHealthcheck, formatDriftBanner, filterToDrift } = await import("../healthcheck-53OVXUQQ.js");
1199
+ const { runHealthcheck, formatDriftBanner, filterToDrift } = await import("../healthcheck-S7D6YNTA.js");
899
1200
  const vaultCandidate = join3(cwd, ".claude", ".vault");
900
1201
  const vaultPath = existsSync3(vaultCandidate) ? vaultCandidate : void 0;
901
1202
  let result = await runHealthcheck({ projectRoot: cwd, vaultPath, tier: opts.fast ? "fast" : "all", force: true });
@@ -909,7 +1210,8 @@ ${r2.stderr?.toString() || ""}`);
909
1210
  }
910
1211
  }
911
1212
  if (opts.json) {
912
- console.log(JSON.stringify({ smoke: { ok: smokeOk, version: smokeVersion }, result, fix: opts.fix ? { plan, outcomes: fixOutcomes } : void 0 }, null, 2));
1213
+ const features = { skill_loop: featureState("skill_loop", cwd), jev: featureState("jev", cwd) };
1214
+ console.log(JSON.stringify({ smoke: { ok: smokeOk, version: smokeVersion }, result, features, fix: opts.fix ? { plan, outcomes: fixOutcomes } : void 0 }, null, 2));
913
1215
  } else {
914
1216
  if (opts.fix) {
915
1217
  if (fixOutcomes.length === 0 && plan.skipped.length === 0) {
@@ -930,6 +1232,9 @@ ${r2.stderr?.toString() || ""}`);
930
1232
  console.log(`
931
1233
  \u2713 no drift detected (${result.findings.length} checks ran in ${result.duration_ms}ms)`);
932
1234
  }
1235
+ console.log(`
1236
+ ${describeFeature(featureState("skill_loop", cwd))}
1237
+ ${describeFeature(featureState("jev", cwd))}`);
933
1238
  }
934
1239
  process.exit(smokeOk ? 0 : 1);
935
1240
  });
@@ -953,15 +1258,15 @@ function findSourceRoot() {
953
1258
  const installed = join3(process.cwd(), "node_modules", "@theglitchking", pkg);
954
1259
  if (existsSync3(installed)) return installed;
955
1260
  }
956
- const dev = resolve2(new URL("../../", import.meta.url).pathname);
1261
+ const dev = resolve4(new URL("../../", import.meta.url).pathname);
957
1262
  return dev;
958
1263
  }
959
1264
  var skillsCmd = program.command("skills").description("Install, target, or uninstall skill bundles for non-Claude agents (codex, copilot, pi). Claude installs continue to use the npm postinstall flow.");
960
1265
  skillsCmd.command("targets").description("Preview where skill bundles would be installed for one or more agents.").option("--agent <name...>", "One or more agents (default: all). Repeatable.", void 0).option("--scope <scope>", "global or local (default: local)", "local").option("--project <path>", "Project root for local-scope paths (default: cwd)", process.cwd()).action((opts) => {
961
1266
  const scope = parseScope(opts.scope);
962
1267
  const agents = (opts.agent && opts.agent.length > 0 ? opts.agent : SKILL_BUNDLER_AGENTS).map(parseAgent);
963
- const out = agents.map((a) => resolveTarget(a, scope, opts.project));
964
- console.log(JSON.stringify(out, null, 2));
1268
+ const out2 = agents.map((a) => resolveTarget(a, scope, opts.project));
1269
+ console.log(JSON.stringify(out2, null, 2));
965
1270
  });
966
1271
  skillsCmd.command("install").description("Install skill bundles into an agent's skill directory. Non-destructive by default; --force to overwrite.").requiredOption("--agent <name>", "Agent runtime (claude, codex, copilot, pi)").option("--scope <scope>", "global or local (default: local)", "local").option("--project <path>", "Project root for local-scope paths (default: cwd)", process.cwd()).option("--only <names...>", "Restrict to specific skill names (default: all shipped)").option("--force", "Overwrite even if drift is detected against an existing manifest", false).action(async (opts) => {
967
1272
  const agent = parseAgent(opts.agent);
@@ -1023,7 +1328,7 @@ ${result.conflicts.length} conflict${result.conflicts.length === 1 ? "" : "s"} d
1023
1328
  });
1024
1329
  program.command("decay-config").description("Print the active confidence-decay config (shipped defaults + any vault.schema.yml override).").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
1025
1330
  const { loadDecayConfig, DEFAULT_DECAY_CONFIG } = await import("../decay-ELJUKVQG.js");
1026
- const cfg = loadDecayConfig(resolve2(opts.notes));
1331
+ const cfg = loadDecayConfig(resolve4(opts.notes));
1027
1332
  const source = JSON.stringify(cfg) === JSON.stringify(DEFAULT_DECAY_CONFIG) ? "defaults" : "vault.schema.yml (overridden)";
1028
1333
  console.log(JSON.stringify({ source, config: cfg }, null, 2));
1029
1334
  process.exit(0);
@@ -1031,18 +1336,18 @@ program.command("decay-config").description("Print the active confidence-decay c
1031
1336
  program.command("decay-trace <notePath>").description("Show the decay calculation for one note: type, last_verified, age, half-life, evergreen, multiplier.").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (notePath, opts) => {
1032
1337
  const { loadDecayConfig, computeDecay, normalizeVerifiedDate } = await import("../decay-ELJUKVQG.js");
1033
1338
  const matter = (await import("../matter-SNJ2WDL6.js")).default;
1034
- const abs = resolve2(opts.notes, notePath);
1339
+ const abs = resolve4(opts.notes, notePath);
1035
1340
  if (!existsSync3(abs)) {
1036
1341
  console.error(`note not found: ${abs}`);
1037
1342
  process.exit(1);
1038
1343
  }
1039
- const { data: fm } = matter(readFileSync(abs, "utf8"));
1344
+ const { data: fm } = matter(readFileSync2(abs, "utf8"));
1040
1345
  const lastVerified = normalizeVerifiedDate(fm.last_verified);
1041
1346
  const d = computeDecay({
1042
1347
  type: typeof fm.type === "string" ? fm.type : void 0,
1043
1348
  last_verified: lastVerified,
1044
1349
  evergreen: fm.evergreen === true,
1045
- config: loadDecayConfig(resolve2(opts.notes))
1350
+ config: loadDecayConfig(resolve4(opts.notes))
1046
1351
  });
1047
1352
  console.log(
1048
1353
  JSON.stringify(
@@ -1064,55 +1369,55 @@ program.command("decay-trace <notePath>").description("Show the decay calculatio
1064
1369
  });
1065
1370
  var lexiconCmd = program.command("lexicon").description("Manage the learned human\u2192artifact lexicon (aliases).");
1066
1371
  lexiconCmd.command("list").description("List all aliases in the lexicon.").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
1067
- const { loadLexiconCache } = await import("../lexicon-XNA5IFIA.js");
1068
- console.log(JSON.stringify(await loadLexiconCache(resolve2(opts.notes)), null, 2));
1372
+ const { loadLexiconCache } = await import("../lexicon-SEJSLBHO.js");
1373
+ console.log(JSON.stringify(await loadLexiconCache(resolve4(opts.notes)), null, 2));
1069
1374
  process.exit(0);
1070
1375
  });
1071
1376
  lexiconCmd.command("compile").description("Rebuild the derived lexicon cache from the lexicon/ notes.").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
1072
- const { compileLexicon } = await import("../lexicon-XNA5IFIA.js");
1073
- const entries = await compileLexicon(resolve2(opts.notes));
1377
+ const { compileLexicon } = await import("../lexicon-SEJSLBHO.js");
1378
+ const entries = await compileLexicon(resolve4(opts.notes));
1074
1379
  console.log(`compiled ${entries.length} alias(es)`);
1075
1380
  process.exit(0);
1076
1381
  });
1077
1382
  lexiconCmd.command("add <canonical> <phrases...>").description("Upsert an alias: a canonical target and the human phrases that refer to it.").requiredOption("--notes <path>", "Path to markdown notes directory").option("--authored", "Mark as human-authored (default: learned)").action(async (canonical, phrases, opts) => {
1078
- const { addAlias } = await import("../lexicon-XNA5IFIA.js");
1079
- const r = await addAlias(resolve2(opts.notes), { canonical, phrases, source: opts.authored ? "authored" : "learned" });
1383
+ const { addAlias } = await import("../lexicon-SEJSLBHO.js");
1384
+ const r = await addAlias(resolve4(opts.notes), { canonical, phrases, source: opts.authored ? "authored" : "learned" });
1080
1385
  console.log(JSON.stringify(r, null, 2));
1081
1386
  process.exit(0);
1082
1387
  });
1083
1388
  var dossierCmd = program.command("dossier").description("Manage entity dossiers \u2014 per-component living notes (purpose / failure modes / knobs / incident log / current state).");
1084
1389
  dossierCmd.command("init <entity>").description("Scaffold a dossier for an entity (no-op if one already exists). Optionally seed aliases and provenance.").requiredOption("--notes <path>", "Path to markdown notes directory").option("--alias <phrase...>", "Human phrase(s) for this entity \u2014 fed to the lexicon compiler").option("--seeded-from <ref>", "Provenance of the scaffold (e.g. a babel-fish project-map entry)").action(async (entity, opts) => {
1085
- const { initDossier } = await import("../dossier-EN26WDKC.js");
1086
- const r = await initDossier(resolve2(opts.notes), entity, { aliases: opts.alias, seeded_from: opts.seededFrom });
1390
+ const { initDossier } = await import("../dossier-RXBLQOXC.js");
1391
+ const r = await initDossier(resolve4(opts.notes), entity, { aliases: opts.alias, seeded_from: opts.seededFrom });
1087
1392
  console.log(JSON.stringify(r, null, 2));
1088
1393
  process.exit(0);
1089
1394
  });
1090
1395
  dossierCmd.command("list").description("List all dossiers with their entity, aliases, and current state.").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
1091
- const { listDossiers } = await import("../dossier-EN26WDKC.js");
1092
- console.log(JSON.stringify(await listDossiers(resolve2(opts.notes)), null, 2));
1396
+ const { listDossiers } = await import("../dossier-RXBLQOXC.js");
1397
+ console.log(JSON.stringify(await listDossiers(resolve4(opts.notes)), null, 2));
1093
1398
  process.exit(0);
1094
1399
  });
1095
1400
  var profileCmd = program.command("profile").description("Manage the speaker profile \u2014 how THIS human communicates (severity calibration, chronic omissions, verbosity, shorthand).");
1096
1401
  profileCmd.command("init").description("Scaffold profile/speaker.md with the fixed sections (no-op if it exists).").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
1097
- const { initSpeakerProfile } = await import("../profile-SIWAVD7O.js");
1098
- const r = await initSpeakerProfile(resolve2(opts.notes));
1402
+ const { initSpeakerProfile } = await import("../profile-SNULTJ27.js");
1403
+ const r = await initSpeakerProfile(resolve4(opts.notes));
1099
1404
  console.log(JSON.stringify(r, null, 2));
1100
1405
  process.exit(0);
1101
1406
  });
1102
1407
  profileCmd.command("show").description("Print the speaker profile's injection head (the capped, placeholder-stripped view).").requiredOption("--notes <path>", "Path to markdown notes directory").action(async (opts) => {
1103
- const { readSpeakerProfile } = await import("../profile-SIWAVD7O.js");
1104
- const p = await readSpeakerProfile(resolve2(opts.notes));
1408
+ const { readSpeakerProfile } = await import("../profile-SNULTJ27.js");
1409
+ const p = await readSpeakerProfile(resolve4(opts.notes));
1105
1410
  console.log(p ? p.head || "(profile is empty \u2014 only placeholders)" : "(no speaker profile \u2014 run `profile init`)");
1106
1411
  process.exit(0);
1107
1412
  });
1108
1413
  program.command("selection-stats").description("Summarize the local selection log: searches, selections, most-cited notes, and retrieved-but-never-cited notes.").requiredOption("--notes <path>", "Path to markdown notes directory").option("--json", "Emit raw JSON instead of a human summary").action(async (opts) => {
1109
1414
  const { computeSelectionStats, selectionLogPath } = await import("../telemetry-YAG3VA4Y.js");
1110
- const stats = await computeSelectionStats(resolve2(opts.notes));
1415
+ const stats = await computeSelectionStats(resolve4(opts.notes));
1111
1416
  if (opts.json) {
1112
1417
  console.log(JSON.stringify(stats, null, 2));
1113
1418
  process.exit(0);
1114
1419
  }
1115
- console.log(`Selection log: ${selectionLogPath(resolve2(opts.notes))}`);
1420
+ console.log(`Selection log: ${selectionLogPath(resolve4(opts.notes))}`);
1116
1421
  console.log(` searches: ${stats.searches}`);
1117
1422
  console.log(` selections: ${stats.selections}`);
1118
1423
  const topCited = Object.entries(stats.citedPaths).sort((a, b) => b[1] - a[1]).slice(0, 10);
@@ -1125,5 +1430,29 @@ program.command("selection-stats").description("Summarize the local selection lo
1125
1430
  }
1126
1431
  process.exit(0);
1127
1432
  });
1433
+ skillsCmd.command("lint").description("Lint the skill loop's learned skills this repo loads (v1.7): format, description, evidence, sections, cites, applies_to, secrets, name clashes.").option("--root <dir>", "Repo root (default: cwd)").option("--json", "JSON output").action(async (opts) => {
1434
+ const { lintSkills } = await import("../skill-schema-RVB3EHEF.js");
1435
+ const root = resolve4(opts.root ?? process.cwd());
1436
+ const r = lintSkills(root);
1437
+ if (opts.json) console.log(JSON.stringify(r, null, 2));
1438
+ else {
1439
+ for (const f of r.findings) console.log(`${f.severity === "error" ? "\u2717" : "!"} ${relative(root, f.path)}: ${f.rule}: ${f.message}`);
1440
+ const errors = r.findings.filter((f) => f.severity === "error").length;
1441
+ console.log(`${r.files} learned skill(s), ${errors} error(s), ${r.findings.length - errors} warning(s)`);
1442
+ }
1443
+ process.exit(r.findings.some((f) => f.severity === "error") ? 1 : 0);
1444
+ });
1445
+ registerFeatureCommands(program, {
1446
+ skill_loop: skillLoopStatus,
1447
+ jev: async () => {
1448
+ const { jevSpendToday } = await import("../core/jev.js");
1449
+ const { jevLoopDir } = await import("../core/features.js");
1450
+ const spend = jevSpendToday(jevLoopDir());
1451
+ const total = Object.values(spend).reduce((a, b) => a + b, 0);
1452
+ return [`Jev today (semantic-memory callers): ${total.toLocaleString()} tokens${total ? ` \u2014 ${Object.entries(spend).map(([c, n]) => `${c} ${n.toLocaleString()}`).join(", ")}` : ""}`];
1453
+ }
1454
+ });
1455
+ registerSkillLoopCommands(program);
1456
+ registerSkillEvalCommands(program);
1128
1457
  program.parse();
1129
1458
  //# sourceMappingURL=index.js.map