@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
@@ -6,13 +6,13 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Single-plugin marketplace for semantic-memory — the unified memory-layer plugin. v1.1.0 absorbs brain's strengths: AGENTS.md contract artifact, hard-gated verification sessions, multi-agent skill bundler (codex/copilot/pi), and SessionStart drift detection. v1.3 adds confidence decay — age-aware retrieval ranking with verify_note to reset the clock. All backwards-compatible with v1.0.x via deprecation shims.",
9
- "version": "1.6.1"
9
+ "version": "1.7.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "semantic-memory",
14
14
  "description": "Unified memory-layer plugin for Claude Code. One MCP server, 44 tools across multi-corpus search/read/write/graph/lint/sessions/contract. v1.1.0 adds: AGENTS.md contract artifact, hard-gated verification sessions (session_start/run/finish/status), proposal-mode synthesize_note + synthesize_promote, multi-agent skill bundler (codex/copilot/pi via the new `skills` CLI), and SessionStart drift detection. v1.2 completes the hygiene line: `/healthcheck --fix` auto-remediation, `lint_vault({checks:['code_symbols']})` code-path drift, and a fresh-install CI smoke gate. v1.3 adds confidence decay: age-aware search ranking (0.5^(age/half-life), type-aware, floored, composes with load_priority), a `decay` block surfaced on decayed results, `verify_note` to reset the clock, `evergreen` opt-out, and `decay-config`/`decay-trace` CLI introspection. v1.3.1 adds local-only selection-logging telemetry (which note each search result actually got used), the `decay_candidates` lint, and a `selection-stats` CLI. v1.4.0 is the resident-expert bridge: a learned human→artifact lexicon (`manage_lexicon`) with propose-and-confirm alias capture, verbatim symptom capture + symptom-keyed indexing (terse symptom queries match the note), Tier-1 lexicon query expansion in the hook, archive path-class down-weight, injection hygiene (score-gate + dedup + top-3 + compact instructions), section-targeted `read_note`, and opt-in conditional tool registration. v1.5.0 is the expert-character layer: entity dossiers (`manage_dossier`) — per-component living notes (purpose / failure modes / knobs / incident log / current state) that accrete in place, with two-hop retrieval (utterance→entity→dossier injected first); usage-feedback ranking that boosts repeatedly-cited notes (capped, composes with decay/path-class) plus a `decoys` lint for retrieved-but-never-cited notes (surfaced, never auto-down-ranked); session paging (Stop drafts a durable session digest as a proposal, SessionStart pages in a curated digest instead of a broad sweep); and a speaker profile (`manage_profile`) that models how the human communicates — severity calibration, chronic omissions, verbosity, shorthand — injected at SessionStart. Backwards-compatible with v1.0.x — every previous tool name remains callable through deprecation shims (removed in v2.0.0).",
15
- "version": "1.6.1",
15
+ "version": "1.7.0",
16
16
  "author": {
17
17
  "name": "TheGlitchKing"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "semantic-memory",
3
3
  "description": "Unified memory-layer plugin for Claude Code. One MCP server, multiple typed corpora (vault / code / plans / docs / research / project-map), unified cross-corpus knowledge graph, conditional tool registration, in-process drift detection, and human↔code translation. Rebrand of `semantic-sidekick` at v1.0.0 — see CHANGELOG for migration.",
4
- "version": "1.6.1",
4
+ "version": "1.7.0",
5
5
  "author": {
6
6
  "name": "TheGlitchKing",
7
7
  "email": "theglitchking@users.noreply.github.com"
package/CHANGELOG.md CHANGED
@@ -2,6 +2,67 @@
2
2
 
3
3
  All notable changes to semantic-memory (formerly semantic-sidekick) will be documented here.
4
4
 
5
+ ## [1.7.0] - 2026-10-26 — The skill loop and jev mode
6
+
7
+ Two new features, **both off by default**, each with its own switch. With both off, 1.7.0 behaves like 1.6.1.
8
+
9
+ ### The skill loop (`SEMANTIC_MEMORY_SKILL_LOOP=1`, `/skill-loop on|off|status`)
10
+
11
+ semantic-memory now learns *procedures* as well as facts. After a session with something to learn from (a failed
12
+ command, a correction, a long session), a background review drafts a skill, or a patch to a learned skill that was
13
+ used and then failed. Nothing lands until you approve it.
14
+
15
+ - **Quiet.** `SessionEnd` only writes a job and starts a detached worker; the session never waits. One worker per
16
+ repo, at most 10 reviews a day across all repos (`SEMANTIC_MEMORY_SKILL_LOOP_DAILY_RUNS`).
17
+ - **Fenced reviewer.** Each review is one `claude -p` with Read/Grep/Glob only: no Bash, no MCP servers, no hooks,
18
+ no subagents, nothing outside the repo, no session file. It answers JSON; semantic-memory writes the draft after
19
+ redaction and lint. At most 5 skill-loop `claude` processes run at once on the machine
20
+ (`SEMANTIC_MEMORY_SKILL_LOOP_CONCURRENCY`, 1–5).
21
+ - **Evidence.** Hooks record skill use (PostToolUse `Skill`, and `/name`), failed commands and corrections to
22
+ redacted logs under `.claude/.semantic-memory/`.
23
+ - **Scope.** Directory or project by rule (the paths a skill names). Global only by promotion, when the same lesson
24
+ exists in two repos, and only on approval. Private repos (`SEMANTIC_MEMORY_PRIVATE_PATHS`, default
25
+ `~/workspace/kentro`) stay project and are never indexed.
26
+ - **Review.** `/skill-loop review` shows each draft's evidence, scope, Verify command and (for patches) diff:
27
+ approve, reject, or `--eval` it. Every change is in a ledger with exact bytes, so `skill-loop rollback <id>` undoes
28
+ one change.
29
+ - **Curator.** Weekly when the repo is quiet: learned skills unused 14 days go stale, 30 days are archived (never
30
+ deleted; `skill-loop restore <name>`). Pinned and hand-written skills are never touched. Merging is opt-in.
31
+ - **Skill format.** A normal `SKILL.md` plus a `skill_loop:` frontmatter block (scope, status, triggers, applies_to,
32
+ provenance, cites); `semantic-memory skills lint` and `lint_vault({checks: ["skills"]})` check it.
33
+ - **Skill evals.** `semantic-memory skill-eval trigger|task|tune`: does the skill trigger on its own prompts and
34
+ stay quiet on near-misses, does it beat doing the task without it, can its description be better. A TypeScript
35
+ rewrite of the method in Anthropic's skill-creator, with its prompts adapted (Apache-2.0, see
36
+ `THIRD_PARTY_NOTICES.md`).
37
+
38
+ ### Jev mode (`SEMANTIC_MEMORY_JEV=1`, `/jev-mode on|off|status`)
39
+
40
+ semantic-memory can use Jev (TypeSafe's yes/no model) for closed-set decisions, always through jev-loop's
41
+ `router.ask` (redaction, cache, ledger, budget): `hooks/jev-bridge.py`. Callers `semantic-memory-hook` (the failure hook, with its own
42
+ ceiling `SEMANTIC_MEMORY_JEV_DAILY_TOKENS`, default 500,000 a day), `semantic-memory-judge` and
43
+ `semantic-memory-skill-loop`. jev-loop counts all three as batch callers (capped by its daily batch cap, cached)
44
+ until it lists `semantic-memory-hook` among its live callers.
45
+
46
+ - **Failure hook veto.** In jev mode a fact the failure hook picked is suppressed when Jev is confident it does not
47
+ fit (<= 0.05), inside the hook's 1.5 s budget; an error injects nothing. Measured on 1,171 labelled pairs: Jev's
48
+ no's were 99.7% right, but it never said yes at >= 0.95, so a yes-gate would inject nothing. Replayed, the veto
49
+ removed only wrong facts (12 wrong, 0 right at score 0.30) and none at the shipped 0.50, where precision stays
50
+ 61%: well below the 95% bar, so jev mode does not turn the hook on by itself.
51
+ - **Failure-eval judge.** `scripts/eval-failures/judge-jev.mjs`, `calibrate-jev.mjs`, and
52
+ `eval-failures --gate jev`.
53
+ - **Skill loop.** The scope question (could this be general?) and duplicate checks before a merge.
54
+
55
+ ### Also
56
+
57
+ - Every semantic-memory hook exits at once under `SEMANTIC_MEMORY_REVIEWER=1` (set on every skill-loop `claude` run).
58
+ - `healthcheck` reports both switches.
59
+
60
+ ### Validation
61
+
62
+ - 664 tests (545 in 1.6.1). With both switches off, every hook's output matches 1.6.1 byte for byte. Golden eval and
63
+ failure eval unchanged. A real end-to-end run (sessions → review → draft → eval → approve → use) found three bugs,
64
+ fixed here. Details: `.documentation/changelog/v1-7-0-skill-loop-and-jev-mode.md`.
65
+
5
66
  ## [1.6.1] - 2026-10-24 — Release audit fixes (1.5.2 → 1.6.0)
6
67
 
7
68
  A full validation of 1.5.2, 1.5.3 and 1.6.0 through the published packages and real entry points (fresh install, plugin update, upgrade from 1.5.1 state, a stdio MCP server, the read-only docs server, the live watcher, the hooks as real processes, the offline tarball) found seven defects. Each has a regression test.
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md CHANGED
@@ -11,10 +11,11 @@ The original semantic-sidekick framing — activation / mode-routing / capture l
11
11
 
12
12
  ## Status
13
13
 
14
- **v1.6.1** (2026-10-24), on npm as `@theglitchking/semantic-memory`. Per-version notes are in [CHANGELOG.md](./CHANGELOG.md) and `.documentation/changelog/`. Recent releases:
14
+ **v1.7.0** (2026-10-26), on npm as `@theglitchking/semantic-memory`. Per-version notes are in [CHANGELOG.md](./CHANGELOG.md) and `.documentation/changelog/`. Recent releases:
15
15
 
16
16
  | Version | What it adds |
17
17
  |---|---|
18
+ | **1.7.0** | The skill loop (learned skills between sessions) and jev mode, both opt-in |
18
19
  | **1.6.1** | Release-audit fixes: invalid frontmatter stays body-only on every reindex, frontmatter `api_key:` values are masked, `--read-only` servers no longer expose `log_event` |
19
20
  | **1.6.0** | Opt-in fact lookup on failed commands (`PostToolUseFailure`), `eval-failures` |
20
21
  | **1.5.3** | Secret redaction at every write point, `lint_vault({checks: ["secrets"]})` |
@@ -85,6 +86,21 @@ How the layers fit: [.documentation/architecture/architecture-layers.md](.docume
85
86
 
86
87
  Set `SEMANTIC_MEMORY_FAILURE_HOOK=1` (e.g. in `~/.claude/settings.json` → `env`) and a failed Bash/MCP call gets the knowledge-base fact that most likely explains it, when one scores high enough. Measured on real failures it is right about 6 times in 10, so it is off by default. Facts with the literal error in `symptoms:` are found far more reliably (in a hit-em-with-the-docs tree, read the guide first: hewtd uses `symptoms:` for something else). If you already run another `PostToolUseFailure` hook, both can add a note to the same failure — see [the guide](.documentation/operational/failure-lookup-guide.md).
87
88
 
89
+ ## The skill loop (opt-in, v1.7+)
90
+
91
+ `semantic-memory skill-loop on` (or `/skill-loop on`). After a session that failed a command and found the fix, or
92
+ was corrected, a background review drafts a skill — a short playbook Claude Code loads the next time the same
93
+ thing happens. The session never waits; the reviewer is a fenced, read-only `claude -p`; nothing lands until you
94
+ approve it in `/skill-loop review`; unused learned skills go stale at 14 days and are archived at 30. Off (the
95
+ default) changes nothing. See [the guide](.documentation/operational/skill-loop.md).
96
+
97
+ ## Jev mode (opt-in, v1.7+)
98
+
99
+ `semantic-memory jev-mode on` (or `/jev-mode on`) lets semantic-memory use Jev, TypeSafe's yes/no model, through
100
+ [jev-loop](https://github.com/the-glitch-kingdom/jev-loop) for closed-set decisions — whether a fact fits a
101
+ failure before the failure hook shows it, whether a learned skill could be general, whether two skills are
102
+ duplicates. Needs jev-loop, `TYPESAFE_API_KEY` and python3. See [the guide](.documentation/operational/jev-mode.md).
103
+
88
104
  ## Privacy and secrets
89
105
 
90
106
  Everything stays on your machine (local embedding model, no network telemetry). Since v1.5.3 every write masks API keys, tokens, passwords and private keys before they reach disk, and `lint_vault({ checks: ["secrets"] })` finds ones already in notes. Details and limits: [SECURITY.md](SECURITY.md#secret-redaction-v153).
@@ -99,4 +115,5 @@ node dist/cli/index.js --version
99
115
 
100
116
  ## License
101
117
 
102
- MIT
118
+ MIT. The skill-eval prompts in `src/core/skill-eval/prompts/` are adapted from Anthropic's skill-creator
119
+ (Apache License 2.0): see [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) and [LICENSES/Apache-2.0.txt](LICENSES/Apache-2.0.txt).
@@ -0,0 +1,17 @@
1
+ # Third-party notices
2
+
3
+ semantic-memory is MIT-licensed (see `LICENSE`). The files below are adapted from other projects and keep
4
+ their original license.
5
+
6
+ ## Anthropic skill-creator (Apache License 2.0)
7
+
8
+ Source: https://github.com/anthropics/claude-plugins-official (`plugins/skill-creator`). The full license text
9
+ is in `LICENSES/Apache-2.0.txt`. Each file below starts with a header saying it was adapted and modified.
10
+
11
+ | File | Adapted from |
12
+ |------|--------------|
13
+ | `src/core/skill-eval/prompts/improve-description.ts` | `scripts/improve_description.py` (description-improvement and shorten prompts) |
14
+ | `src/core/skill-eval/prompts/grader.ts` | `agents/grader.md` (grader rules) |
15
+
16
+ The `evals/evals.json` reader and writer (`src/core/skill-eval/evals-json.ts`) follows the format in
17
+ skill-creator's `references/schemas.md`; no code or text is copied. The rest of the skill-eval code is new.
@@ -0,0 +1,18 @@
1
+ ---
2
+ description: Turn jev mode on or off, or show its state (Jev through jev-loop for semantic-memory's yes/no decisions)
3
+ argument-hint: "[on|off|status]"
4
+ allowed-tools: Bash(npx:*), Bash(node:*)
5
+ ---
6
+
7
+ The user ran `/jev-mode $ARGUMENTS`. Empty arguments mean `status`.
8
+
9
+ Run `node ./node_modules/@theglitchking/semantic-memory/bin/semantic-memory jev-mode <on|off|status>`
10
+ (or `npx --no @theglitchking/semantic-memory jev-mode …` when there is no local install) and relay the
11
+ output verbatim.
12
+
13
+ - `on` / `off` edit only `SEMANTIC_MEMORY_JEV` in `~/.claude/settings.json` `env` (a `.bak` of the previous
14
+ file is kept). The change applies from the next Claude Code session: say so.
15
+ - `status` prints on / off / on-but-unavailable with the reason (jev-loop, `TYPESAFE_API_KEY` or python3
16
+ missing) and today's Jev spend by semantic-memory.
17
+
18
+ Never print or echo `TYPESAFE_API_KEY`.
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: The skill loop — on/off/status, review drafted skills, run the curator, restore or roll back
3
+ argument-hint: "[on|off|status|review|curate|restore <name>|rollback <ledger-id>|verify]"
4
+ allowed-tools: Bash(npx:*), Bash(node:*), Read
5
+ ---
6
+
7
+ The user ran `/skill-loop $ARGUMENTS`. Empty arguments mean `status`.
8
+
9
+ Every subcommand is the CLI: `node ./node_modules/@theglitchking/semantic-memory/bin/semantic-memory skill-loop <args>`
10
+ (or `npx --no @theglitchking/semantic-memory skill-loop <args>` when there is no local install).
11
+
12
+ - `on` / `off` — edit only `SEMANTIC_MEMORY_SKILL_LOOP` in `~/.claude/settings.json` `env` (a `.bak` is kept);
13
+ applies from the next session — say so. `on --repo` / `off --repo` write the per-repo override in
14
+ `.claude/.semantic-memory/config` instead (it beats the global switch); `--repo --clear` removes it.
15
+ Off never deletes anything: skills already landed stay, as ordinary files.
16
+ - `status` — relay verbatim: on/off and why, drafts waiting, today's review budget, last curator run.
17
+ - `review` — run `skill-loop review --json`. For each draft show its name, scope, the evidence it cites
18
+ (sessions, failures, corrections), eval scores if any, and for a patch the diff. Ask the user, one draft at a
19
+ time: approve, reject, or edit. Then run `skill-loop review --approve <id>` / `--reject <id>`; for edit, change
20
+ the draft file it names, then approve it. Never approve on the user's behalf.
21
+ - `curate` — `skill-loop curate --dry-run` first, show what would go stale or be archived, and run without
22
+ `--dry-run` only when the user agrees. `curate --merge` proposes merges as review drafts.
23
+ - `restore <name>`, `rollback <ledger-id>`, `verify` — run them and relay the output.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  lintVault
3
- } from "./chunk-GYBUBJYM.js";
3
+ } from "./chunk-VBLCUWT4.js";
4
4
 
5
5
  // src/core/healthcheck.ts
6
6
  import { readFile, writeFile, mkdir } from "fs/promises";
@@ -338,4 +338,4 @@ export {
338
338
  filterToDrift,
339
339
  formatDriftBanner
340
340
  };
341
- //# sourceMappingURL=chunk-TMMTOFHI.js.map
341
+ //# sourceMappingURL=chunk-34VN47IW.js.map
@@ -0,0 +1,113 @@
1
+ import {
2
+ featureOn
3
+ } from "./chunk-VQCNWRTL.js";
4
+ import {
5
+ askJev,
6
+ jevVerdict
7
+ } from "./chunk-GYR7U6KA.js";
8
+ import {
9
+ smHome
10
+ } from "./chunk-NIZLBNH4.js";
11
+
12
+ // src/core/skill-scope.ts
13
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "fs";
14
+ import { homedir } from "os";
15
+ import { join, sep } from "path";
16
+ function privatePaths(env = process.env) {
17
+ const home = env.HOME || homedir();
18
+ return (env.SEMANTIC_MEMORY_PRIVATE_PATHS ?? "~/workspace/kentro").split(",").map((p) => p.trim().replace(/^~(?=$|\/)/, home)).filter(Boolean);
19
+ }
20
+ var isPrivateRepo = (repo, env = process.env) => privatePaths(env).some((p) => repo === p || repo.startsWith(p.endsWith(sep) ? p : p + sep));
21
+ function repoReferences(repo, text, paths = []) {
22
+ const tokens = new Set(paths);
23
+ for (const m of text.matchAll(/(?:^|[\s`'"(])((?:\.\/)?[\w.@-]+(?:\/[\w.@-]+)+\/?|[\w-]+\.(?:sh|js|mjs|ts|py|json|toml|ya?ml|md))(?=[\s`'"),:;]|$)/gm)) {
24
+ tokens.add(m[1].replace(/^\.\//, "").replace(/\/$/, ""));
25
+ }
26
+ return [...tokens].filter((p) => p && !p.startsWith("/") && !p.startsWith("~") && !p.includes("..") && existsSync(join(repo, p)));
27
+ }
28
+ function commonSubdir(repo, refs) {
29
+ const dirs = refs.map((r) => {
30
+ let isDir = false;
31
+ try {
32
+ isDir = statSync(join(repo, r)).isDirectory();
33
+ } catch {
34
+ }
35
+ const parts = r.split("/");
36
+ return (isDir ? parts : parts.slice(0, -1)).slice(0, 2).join("/");
37
+ });
38
+ if (!dirs.length || dirs.some((d) => !d)) return null;
39
+ const first = dirs[0];
40
+ return dirs.every((d) => d === first || d.startsWith(first + "/")) ? first : null;
41
+ }
42
+ var SCOPE_QUESTION = "Would these steps work unchanged in any software repository \u2014 they name no file, script, package, service or setting that belongs to one particular repository?";
43
+ async function decideScope(repo, draft, env = process.env) {
44
+ const refs = repoReferences(repo, `${draft.description}
45
+ ${draft.body}`, draft.applies_to?.paths ?? []);
46
+ if (refs.length) {
47
+ const sub = commonSubdir(repo, refs);
48
+ if (sub && (existsSync(join(repo, sub, "package.json")) || existsSync(join(repo, sub, ".claude")) || existsSync(join(repo, sub, "pyproject.toml")))) {
49
+ return { scope: "directory", directory: sub, globalCandidate: false, why: `every path it names is under ${sub}/` };
50
+ }
51
+ return { scope: "project", globalCandidate: false, why: `names this repo's ${refs.slice(0, 3).join(", ")}` };
52
+ }
53
+ if (isPrivateRepo(repo, env)) return { scope: "project", globalCandidate: false, why: "private repo: stays here" };
54
+ if (!featureOn("jev", repo, env)) return { scope: "project", globalCandidate: false, why: "names nothing in the repo; jev mode off \u2192 project" };
55
+ const a = await askJev(
56
+ {
57
+ cwd: repo,
58
+ caller: "semantic-memory-skill-loop",
59
+ state: `Skill ${draft.name}: ${draft.description}
60
+
61
+ ${draft.body}`.slice(0, 2e3),
62
+ questions: [{ key: "global", instructions: SCOPE_QUESTION }]
63
+ },
64
+ { env, timeoutMs: 3e4 }
65
+ );
66
+ const yes = jevVerdict(a.scores.global) === true;
67
+ return {
68
+ scope: "project",
69
+ globalCandidate: yes,
70
+ why: yes ? `Jev: holds in any repo (p=${a.scores.global?.toFixed(2)}); promotion needs a second repo` : `names nothing in the repo; Jev not sure it is general (${a.error ?? `p=${a.scores.global?.toFixed(2)}`}) \u2192 project`
71
+ };
72
+ }
73
+ var skillIndexPath = (env = process.env) => join(smHome(env), "skill-index.jsonl");
74
+ function indexSkill(e, env = process.env) {
75
+ if (isPrivateRepo(e.repo, env)) return;
76
+ mkdirSync(smHome(env), { recursive: true });
77
+ appendFileSync(skillIndexPath(env), JSON.stringify({ ...e, ts: (/* @__PURE__ */ new Date()).toISOString() }) + "\n");
78
+ }
79
+ function readIndex(env = process.env) {
80
+ const latest = /* @__PURE__ */ new Map();
81
+ let raw = "";
82
+ try {
83
+ raw = readFileSync(skillIndexPath(env), "utf8");
84
+ } catch {
85
+ return [];
86
+ }
87
+ for (const line of raw.split("\n")) {
88
+ try {
89
+ const e = JSON.parse(line);
90
+ latest.set(`${e.repo}\0${e.name}\0${e.path}`, e);
91
+ } catch {
92
+ }
93
+ }
94
+ return [...latest.values()];
95
+ }
96
+ var nameTokens = (n) => new Set(n.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 1));
97
+ function sameLesson(a, b) {
98
+ if (a === b) return true;
99
+ const x = nameTokens(a), y = nameTokens(b);
100
+ const shared = [...x].filter((t) => y.has(t)).length;
101
+ return shared / Math.max(x.size, y.size, 1) >= 0.75;
102
+ }
103
+ function promotionPeers(repo, name, env = process.env) {
104
+ if (isPrivateRepo(repo, env)) return [];
105
+ return readIndex(env).filter((e) => e.repo !== repo && e.status !== "archived" && sameLesson(e.name, name) && !isPrivateRepo(e.repo, env));
106
+ }
107
+
108
+ export {
109
+ decideScope,
110
+ indexSkill,
111
+ promotionPeers
112
+ };
113
+ //# sourceMappingURL=chunk-3FPUBAEW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/skill-scope.ts"],"sourcesContent":["/**\n * Where a learned skill lives (v1.7): directory, project, or — only by promotion — global.\n *\n * 1. Deterministic first: a draft that names this repo's paths is project-scoped; one whose paths all sit\n * under a single subdirectory is directory-scoped there.\n * 2. Unresolved (names nothing in the repo): in jev mode one batch Jev question, \"is this true in any\n * repository?\"; a yes at >= 0.95 only marks the draft a global *candidate*. Otherwise: project.\n * 3. Global only by promotion: when the cross-repo index shows the same skill name drafted or landed in\n * >= 2 repos, review offers one global skill that replaces the project copies. A person approves it.\n *\n * Private repos (SEMANTIC_MEMORY_PRIVATE_PATHS, default ~/workspace/kentro: a Claude-only boundary) are\n * always project, are never written to the cross-repo index, and never reach Jev.\n */\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join, sep } from \"node:path\";\nimport { featureOn } from \"./features.js\";\nimport { askJev, jevVerdict } from \"./jev.js\";\nimport { smHome } from \"./skill-queue.js\";\nimport type { SkillScope } from \"./skill-schema.js\";\n\nexport interface ScopeDecision {\n scope: Exclude<SkillScope, \"global\">;\n /** directory scope: the subdirectory (repo-relative) */\n directory?: string;\n /** Jev said this holds in any repo: offer promotion once another repo has it too */\n globalCandidate: boolean;\n why: string;\n}\n\nexport function privatePaths(env = process.env): string[] {\n const home = env.HOME || homedir();\n return (env.SEMANTIC_MEMORY_PRIVATE_PATHS ?? \"~/workspace/kentro\")\n .split(\",\")\n .map((p) => p.trim().replace(/^~(?=$|\\/)/, home))\n .filter(Boolean);\n}\n\nexport const isPrivateRepo = (repo: string, env = process.env) =>\n privatePaths(env).some((p) => repo === p || repo.startsWith(p.endsWith(sep) ? p : p + sep));\n\n/** Repo-relative paths a draft mentions that exist in the repo (from applies_to.paths and path-like tokens). */\nexport function repoReferences(repo: string, text: string, paths: string[] = []): string[] {\n const tokens = new Set(paths);\n for (const m of text.matchAll(/(?:^|[\\s`'\"(])((?:\\.\\/)?[\\w.@-]+(?:\\/[\\w.@-]+)+\\/?|[\\w-]+\\.(?:sh|js|mjs|ts|py|json|toml|ya?ml|md))(?=[\\s`'\"),:;]|$)/gm)) {\n tokens.add(m[1].replace(/^\\.\\//, \"\").replace(/\\/$/, \"\"));\n }\n return [...tokens].filter((p) => p && !p.startsWith(\"/\") && !p.startsWith(\"~\") && !p.includes(\"..\") && existsSync(join(repo, p)));\n}\n\n/** The one subdirectory every referenced path sits under (two levels at most), or null. */\nfunction commonSubdir(repo: string, refs: string[]): string | null {\n const dirs = refs.map((r) => {\n let isDir = false;\n try {\n isDir = statSync(join(repo, r)).isDirectory();\n } catch {}\n const parts = r.split(\"/\");\n return (isDir ? parts : parts.slice(0, -1)).slice(0, 2).join(\"/\");\n });\n if (!dirs.length || dirs.some((d) => !d)) return null;\n const first = dirs[0];\n return dirs.every((d) => d === first || d.startsWith(first + \"/\")) ? first : null;\n}\n\nexport const SCOPE_QUESTION =\n \"Would these steps work unchanged in any software repository — they name no file, script, package, service or \" +\n \"setting that belongs to one particular repository?\";\n\nexport async function decideScope(\n repo: string,\n draft: { name: string; description: string; body: string; applies_to?: { paths?: string[] } },\n env = process.env,\n): Promise<ScopeDecision> {\n const refs = repoReferences(repo, `${draft.description}\\n${draft.body}`, draft.applies_to?.paths ?? []);\n if (refs.length) {\n const sub = commonSubdir(repo, refs);\n // a subdir that holds its own package/skills is a real unit; a bare src/ or scripts/ is still the whole project\n if (sub && (existsSync(join(repo, sub, \"package.json\")) || existsSync(join(repo, sub, \".claude\")) || existsSync(join(repo, sub, \"pyproject.toml\")))) {\n return { scope: \"directory\", directory: sub, globalCandidate: false, why: `every path it names is under ${sub}/` };\n }\n return { scope: \"project\", globalCandidate: false, why: `names this repo's ${refs.slice(0, 3).join(\", \")}` };\n }\n if (isPrivateRepo(repo, env)) return { scope: \"project\", globalCandidate: false, why: \"private repo: stays here\" };\n if (!featureOn(\"jev\", repo, env)) return { scope: \"project\", globalCandidate: false, why: \"names nothing in the repo; jev mode off → project\" };\n const a = await askJev(\n {\n cwd: repo,\n caller: \"semantic-memory-skill-loop\",\n state: `Skill ${draft.name}: ${draft.description}\\n\\n${draft.body}`.slice(0, 2000),\n questions: [{ key: \"global\", instructions: SCOPE_QUESTION }],\n },\n { env, timeoutMs: 30_000 },\n );\n const yes = jevVerdict(a.scores.global) === true;\n return {\n scope: \"project\",\n globalCandidate: yes,\n why: yes ? `Jev: holds in any repo (p=${a.scores.global?.toFixed(2)}); promotion needs a second repo` : `names nothing in the repo; Jev not sure it is general (${a.error ?? `p=${a.scores.global?.toFixed(2)}`}) → project`,\n };\n}\n\n// --- the cross-repo index: paths and names only ---\n\nexport interface IndexEntry {\n repo: string;\n name: string;\n path: string;\n status: \"draft\" | \"active\" | \"archived\";\n global_candidate?: boolean;\n ts: string;\n}\n\nexport const skillIndexPath = (env = process.env) => join(smHome(env), \"skill-index.jsonl\");\n\nexport function indexSkill(e: Omit<IndexEntry, \"ts\">, env = process.env): void {\n if (isPrivateRepo(e.repo, env)) return;\n mkdirSync(smHome(env), { recursive: true });\n appendFileSync(skillIndexPath(env), JSON.stringify({ ...e, ts: new Date().toISOString() }) + \"\\n\");\n}\n\n/** Latest entry per (repo, name, path). */\nexport function readIndex(env = process.env): IndexEntry[] {\n const latest = new Map<string, IndexEntry>();\n let raw = \"\";\n try {\n raw = readFileSync(skillIndexPath(env), \"utf8\");\n } catch {\n return [];\n }\n for (const line of raw.split(\"\\n\")) {\n try {\n const e = JSON.parse(line) as IndexEntry;\n latest.set(`${e.repo}\\0${e.name}\\0${e.path}`, e);\n } catch {}\n }\n return [...latest.values()];\n}\n\nconst nameTokens = (n: string) => new Set(n.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 1));\n\n/** Same lesson: the same name, or names sharing >= 3/4 of their words. */\nexport function sameLesson(a: string, b: string): boolean {\n if (a === b) return true;\n const x = nameTokens(a), y = nameTokens(b);\n const shared = [...x].filter((t) => y.has(t)).length;\n return shared / Math.max(x.size, y.size, 1) >= 0.75;\n}\n\n/** Other repos holding the same lesson (not archived). Promotion is offered when this returns >= 1 repo. */\nexport function promotionPeers(repo: string, name: string, env = process.env): IndexEntry[] {\n if (isPrivateRepo(repo, env)) return [];\n return readIndex(env).filter((e) => e.repo !== repo && e.status !== \"archived\" && sameLesson(e.name, name) && !isPrivateRepo(e.repo, env));\n}\n"],"mappings":"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","names":[]}
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  appendToSection,
3
3
  replaceSectionBody
4
- } from "./chunk-HZ7B5KHY.js";
5
- import {
6
- redact
7
- } from "./chunk-H2BES6FU.js";
4
+ } from "./chunk-EXAUVIGW.js";
8
5
  import {
9
6
  matter_default
10
7
  } from "./chunk-J7LYDRML.js";
8
+ import {
9
+ redact
10
+ } from "./chunk-H2BES6FU.js";
11
11
 
12
12
  // src/core/profile.ts
13
13
  import { readFile, writeFile, mkdir } from "fs/promises";
@@ -108,4 +108,4 @@ export {
108
108
  initSpeakerProfile,
109
109
  updateProfileSection
110
110
  };
111
- //# sourceMappingURL=chunk-ZLIHQZ4C.js.map
111
+ //# sourceMappingURL=chunk-6TIKWOY4.js.map
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  listDossiers
3
- } from "./chunk-HZ7B5KHY.js";
3
+ } from "./chunk-EXAUVIGW.js";
4
4
  import {
5
5
  deriveSessionDir
6
6
  } from "./chunk-EYVFC745.js";
7
- import {
8
- redactDeep
9
- } from "./chunk-H2BES6FU.js";
10
7
  import {
11
8
  matter_default
12
9
  } from "./chunk-J7LYDRML.js";
10
+ import {
11
+ redactDeep
12
+ } from "./chunk-H2BES6FU.js";
13
13
 
14
14
  // src/core/lexicon.ts
15
15
  import { readFile, writeFile, mkdir, unlink } from "fs/promises";
@@ -180,4 +180,4 @@ export {
180
180
  findAliasConflicts,
181
181
  removeAlias
182
182
  };
183
- //# sourceMappingURL=chunk-KSH2QO6V.js.map
183
+ //# sourceMappingURL=chunk-7UAVDOI6.js.map
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  loadSchema,
3
3
  validateNote
4
- } from "./chunk-HUG7E25D.js";
4
+ } from "./chunk-Y7H76Q7F.js";
5
+ import {
6
+ matter_default
7
+ } from "./chunk-J7LYDRML.js";
5
8
  import {
6
9
  redact,
7
10
  redactDeep
8
11
  } from "./chunk-H2BES6FU.js";
9
- import {
10
- matter_default
11
- } from "./chunk-J7LYDRML.js";
12
12
 
13
13
  // src/core/crud.ts
14
14
  import { readFile, writeFile, unlink, rename, mkdir } from "fs/promises";
@@ -943,4 +943,4 @@ export {
943
943
  regenerateAgentsContract,
944
944
  inspectAgentsContract
945
945
  };
946
- //# sourceMappingURL=chunk-PGVIBUL3.js.map
946
+ //# sourceMappingURL=chunk-AIXQFTSB.js.map