@theglitchking/semantic-memory 1.0.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.
- package/.claude-plugin/marketplace.json +71 -0
- package/.claude-plugin/plugin.json +34 -0
- package/CHANGELOG.md +233 -0
- package/LICENSE +21 -0
- package/README.md +91 -0
- package/bin/semantic-memory +2 -0
- package/bin/semantic-sidekick +2 -0
- package/commands/healthcheck.md +6 -0
- package/commands/mode.md +65 -0
- package/commands/normalize-config.md +6 -0
- package/commands/policy.md +16 -0
- package/commands/relink.md +6 -0
- package/commands/status.md +6 -0
- package/commands/update.md +6 -0
- package/commands/vault.md +21 -0
- package/dist/chunk-25EIQWXL.js +651 -0
- package/dist/chunk-25EIQWXL.js.map +1 -0
- package/dist/chunk-CAN2GWUG.js +88 -0
- package/dist/chunk-CAN2GWUG.js.map +1 -0
- package/dist/chunk-CXTRTCIK.js +762 -0
- package/dist/chunk-CXTRTCIK.js.map +1 -0
- package/dist/chunk-E2Z4J4MK.js +180 -0
- package/dist/chunk-E2Z4J4MK.js.map +1 -0
- package/dist/chunk-HODDRXJK.js +169 -0
- package/dist/chunk-HODDRXJK.js.map +1 -0
- package/dist/chunk-JPQHNG3R.js +147 -0
- package/dist/chunk-JPQHNG3R.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +505 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/embed-worker.d.ts +2 -0
- package/dist/core/embed-worker.js +121 -0
- package/dist/core/embed-worker.js.map +1 -0
- package/dist/core/index.d.ts +486 -0
- package/dist/core/index.js +62 -0
- package/dist/core/index.js.map +1 -0
- package/dist/indexer-KD6UTXZB.js +8 -0
- package/dist/indexer-KD6UTXZB.js.map +1 -0
- package/dist/lint-GMXQF4RW.js +11 -0
- package/dist/lint-GMXQF4RW.js.map +1 -0
- package/dist/log-LQXIJXIA.js +10 -0
- package/dist/log-LQXIJXIA.js.map +1 -0
- package/dist/mcp/server.d.ts +16 -0
- package/dist/mcp/server.js +4925 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/schema-F3RTY6SQ.js +12 -0
- package/dist/schema-F3RTY6SQ.js.map +1 -0
- package/dist/search-27WYAI24.js +202 -0
- package/dist/search-27WYAI24.js.map +1 -0
- package/docs/compat-matrix.md +127 -0
- package/docs/corpora-json.md +150 -0
- package/docs/regression-snapshot.md +122 -0
- package/docs/smart-middle-activation.md +97 -0
- package/hooks/hooks.json +39 -0
- package/hooks/reconcile.js +171 -0
- package/hooks/session-start.js +81 -0
- package/hooks/vault-context.js +503 -0
- package/package.json +97 -0
- package/scripts/link-skills.js +25 -0
- package/scripts/pre-commit-lint.sh +42 -0
- package/skills/outage-silence/SKILL.md +72 -0
- package/skills/research-mode/SKILL.md +84 -0
- package/skills/semantic-first/SKILL.md +117 -0
- package/skills/semantic-first/evals/evals.json +37 -0
- package/skills/semantic-first/references/vault-frontmatter.md +133 -0
- package/skills/vault-first/SKILL.md +100 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Embedder,
|
|
3
|
+
GraphBuilder,
|
|
4
|
+
TextSearch,
|
|
5
|
+
VectorIndex
|
|
6
|
+
} from "./chunk-25EIQWXL.js";
|
|
7
|
+
import {
|
|
8
|
+
Indexer
|
|
9
|
+
} from "./chunk-HODDRXJK.js";
|
|
10
|
+
import "./chunk-PZ5AY32C.js";
|
|
11
|
+
|
|
12
|
+
// src/cli/search.ts
|
|
13
|
+
import { join, resolve } from "path";
|
|
14
|
+
import { existsSync } from "fs";
|
|
15
|
+
import { readFile } from "fs/promises";
|
|
16
|
+
async function runSearch(opts) {
|
|
17
|
+
const notesPath = resolve(opts.notes);
|
|
18
|
+
if (!existsSync(notesPath)) {
|
|
19
|
+
throw new Error(`notes directory not found: ${notesPath}`);
|
|
20
|
+
}
|
|
21
|
+
const indexer = new Indexer(notesPath);
|
|
22
|
+
const indexPath = join(notesPath, ".semantic-sidekick-index");
|
|
23
|
+
let documents = existsSync(indexPath) ? await indexer.loadDocsCache(indexPath) : null;
|
|
24
|
+
if (!documents) {
|
|
25
|
+
documents = await indexer.indexAll();
|
|
26
|
+
}
|
|
27
|
+
if (documents.length === 0) return [];
|
|
28
|
+
const limit = opts.limit ?? 8;
|
|
29
|
+
if (opts.textOnly) {
|
|
30
|
+
const textSearch = new TextSearch();
|
|
31
|
+
textSearch.setDocuments(documents);
|
|
32
|
+
const keywords = extractKeywords(opts.query);
|
|
33
|
+
if (keywords.length === 0) return [];
|
|
34
|
+
const scoreByPath = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const kw of keywords) {
|
|
36
|
+
const results = textSearch.search({ pattern: kw, limit: 50 });
|
|
37
|
+
for (const r of results) {
|
|
38
|
+
const existing = scoreByPath.get(r.path);
|
|
39
|
+
const doc = documents.find((d) => d.path === r.path);
|
|
40
|
+
if (existing) {
|
|
41
|
+
existing.hits += 1;
|
|
42
|
+
} else {
|
|
43
|
+
scoreByPath.set(r.path, {
|
|
44
|
+
hits: 1,
|
|
45
|
+
firstSnippet: r.snippet,
|
|
46
|
+
title: r.title,
|
|
47
|
+
mtime: doc?.mtime ?? ""
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const hits = Array.from(scoreByPath.entries()).map(([path, v]) => ({
|
|
53
|
+
path,
|
|
54
|
+
title: v.title,
|
|
55
|
+
score: v.hits / keywords.length,
|
|
56
|
+
snippet: v.firstSnippet,
|
|
57
|
+
mtime: v.mtime
|
|
58
|
+
})).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
59
|
+
return hits;
|
|
60
|
+
}
|
|
61
|
+
if (!existsSync(indexPath)) {
|
|
62
|
+
throw new Error(`index not built at ${indexPath} \u2014 run: semantic-sidekick --notes ${notesPath} --reindex`);
|
|
63
|
+
}
|
|
64
|
+
const embedder = new Embedder();
|
|
65
|
+
await embedder.init();
|
|
66
|
+
const metaPath = join(indexPath, "meta.json");
|
|
67
|
+
if (existsSync(metaPath)) {
|
|
68
|
+
try {
|
|
69
|
+
const meta = JSON.parse(await readFile(metaPath, "utf-8"));
|
|
70
|
+
if (meta.model && meta.model !== embedder.getModel()) {
|
|
71
|
+
throw new Error(`model mismatch (cached=${meta.model}, current=${embedder.getModel()}) \u2014 run --reindex`);
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
if (e instanceof Error && e.message.startsWith("model mismatch")) throw e;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const vectorIndex = new VectorIndex(embedder.getDimensions());
|
|
78
|
+
const loaded = await vectorIndex.load(indexPath);
|
|
79
|
+
if (!loaded) {
|
|
80
|
+
throw new Error(`could not load vector index at ${indexPath} \u2014 run --reindex`);
|
|
81
|
+
}
|
|
82
|
+
const graph = new GraphBuilder();
|
|
83
|
+
await graph.load(indexPath);
|
|
84
|
+
const docByPath = new Map(documents.map((d) => [d.path, d]));
|
|
85
|
+
const queryEmbed = await embedder.embed(opts.query);
|
|
86
|
+
const semantic = vectorIndex.search(queryEmbed, limit * 3);
|
|
87
|
+
const graphResults = graph.searchGraph(opts.query, 2);
|
|
88
|
+
const graphPaths = new Set(graphResults.map((r) => r.path));
|
|
89
|
+
const hybrid = semantic.map((r) => {
|
|
90
|
+
const doc = docByPath.get(r.path);
|
|
91
|
+
let score = graphPaths.has(r.path) ? r.score * 1.3 : r.score;
|
|
92
|
+
if (doc?.loadPriority !== void 0) {
|
|
93
|
+
score *= 1 + (doc.loadPriority - 5) * 0.04;
|
|
94
|
+
}
|
|
95
|
+
return { ...r, score };
|
|
96
|
+
}).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
97
|
+
return hybrid.map((r) => {
|
|
98
|
+
const doc = docByPath.get(r.path);
|
|
99
|
+
return {
|
|
100
|
+
path: r.path,
|
|
101
|
+
title: r.title ?? doc?.title,
|
|
102
|
+
score: r.score,
|
|
103
|
+
snippet: r.snippet,
|
|
104
|
+
mtime: doc?.mtime
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
109
|
+
"a",
|
|
110
|
+
"an",
|
|
111
|
+
"and",
|
|
112
|
+
"are",
|
|
113
|
+
"as",
|
|
114
|
+
"at",
|
|
115
|
+
"be",
|
|
116
|
+
"by",
|
|
117
|
+
"can",
|
|
118
|
+
"do",
|
|
119
|
+
"does",
|
|
120
|
+
"for",
|
|
121
|
+
"from",
|
|
122
|
+
"how",
|
|
123
|
+
"i",
|
|
124
|
+
"if",
|
|
125
|
+
"in",
|
|
126
|
+
"is",
|
|
127
|
+
"it",
|
|
128
|
+
"its",
|
|
129
|
+
"of",
|
|
130
|
+
"on",
|
|
131
|
+
"or",
|
|
132
|
+
"our",
|
|
133
|
+
"should",
|
|
134
|
+
"that",
|
|
135
|
+
"the",
|
|
136
|
+
"this",
|
|
137
|
+
"to",
|
|
138
|
+
"was",
|
|
139
|
+
"we",
|
|
140
|
+
"what",
|
|
141
|
+
"when",
|
|
142
|
+
"where",
|
|
143
|
+
"which",
|
|
144
|
+
"why",
|
|
145
|
+
"will",
|
|
146
|
+
"with",
|
|
147
|
+
"you",
|
|
148
|
+
"your",
|
|
149
|
+
"have",
|
|
150
|
+
"has",
|
|
151
|
+
"had",
|
|
152
|
+
"but",
|
|
153
|
+
"not",
|
|
154
|
+
"so",
|
|
155
|
+
"any",
|
|
156
|
+
"all",
|
|
157
|
+
"about",
|
|
158
|
+
"there",
|
|
159
|
+
"their",
|
|
160
|
+
"they",
|
|
161
|
+
"them",
|
|
162
|
+
"these",
|
|
163
|
+
"those",
|
|
164
|
+
"would",
|
|
165
|
+
"could",
|
|
166
|
+
"might",
|
|
167
|
+
"may",
|
|
168
|
+
"really",
|
|
169
|
+
"just",
|
|
170
|
+
"like",
|
|
171
|
+
"some",
|
|
172
|
+
"more",
|
|
173
|
+
"than",
|
|
174
|
+
"into",
|
|
175
|
+
"out",
|
|
176
|
+
"up",
|
|
177
|
+
"down",
|
|
178
|
+
"need",
|
|
179
|
+
"want",
|
|
180
|
+
"get",
|
|
181
|
+
"got",
|
|
182
|
+
"also",
|
|
183
|
+
"very",
|
|
184
|
+
"much"
|
|
185
|
+
]);
|
|
186
|
+
function extractKeywords(query) {
|
|
187
|
+
const tokens = query.toLowerCase().replace(/[^\w\s-]/g, " ").split(/\s+/).filter((t) => t.length > 2 && !STOPWORDS.has(t));
|
|
188
|
+
const seen = /* @__PURE__ */ new Set();
|
|
189
|
+
const out = [];
|
|
190
|
+
for (const t of tokens) {
|
|
191
|
+
if (!seen.has(t)) {
|
|
192
|
+
seen.add(t);
|
|
193
|
+
out.push(t);
|
|
194
|
+
}
|
|
195
|
+
if (out.length >= 6) break;
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
export {
|
|
200
|
+
runSearch
|
|
201
|
+
};
|
|
202
|
+
//# sourceMappingURL=search-27WYAI24.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/search.ts"],"sourcesContent":["import { join, resolve } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { Indexer } from \"../core/indexer.js\";\nimport { Embedder } from \"../core/embedder.js\";\nimport { GraphBuilder } from \"../core/graph.js\";\nimport { VectorIndex } from \"../core/vector.js\";\nimport { TextSearch } from \"../core/search-text.js\";\n\nexport interface SearchCliOptions {\n notes: string;\n query: string;\n limit?: number;\n textOnly?: boolean;\n json?: boolean;\n}\n\nexport interface SearchHit {\n path: string;\n title?: string;\n score: number;\n snippet?: string;\n mtime?: string;\n}\n\nexport async function runSearch(opts: SearchCliOptions): Promise<SearchHit[]> {\n const notesPath = resolve(opts.notes);\n if (!existsSync(notesPath)) {\n throw new Error(`notes directory not found: ${notesPath}`);\n }\n\n const indexer = new Indexer(notesPath);\n const indexPath = join(notesPath, \".semantic-sidekick-index\");\n\n // Prefer the parsed-doc cache written by reindex — avoids re-parsing 500+ files\n // on every hook call. Falls back to a full parse if the cache is missing.\n let documents = existsSync(indexPath) ? await indexer.loadDocsCache(indexPath) : null;\n if (!documents) {\n documents = await indexer.indexAll();\n }\n if (documents.length === 0) return [];\n\n const limit = opts.limit ?? 8;\n\n if (opts.textOnly) {\n const textSearch = new TextSearch();\n textSearch.setDocuments(documents);\n const keywords = extractKeywords(opts.query);\n if (keywords.length === 0) return [];\n const scoreByPath = new Map<string, { hits: number; firstSnippet?: string; title?: string; mtime: string }>();\n for (const kw of keywords) {\n const results = textSearch.search({ pattern: kw, limit: 50 });\n for (const r of results) {\n const existing = scoreByPath.get(r.path);\n const doc = documents.find((d) => d.path === r.path);\n if (existing) {\n existing.hits += 1;\n } else {\n scoreByPath.set(r.path, {\n hits: 1,\n firstSnippet: r.snippet,\n title: r.title,\n mtime: doc?.mtime ?? \"\",\n });\n }\n }\n }\n const hits: SearchHit[] = Array.from(scoreByPath.entries())\n .map(([path, v]) => ({\n path,\n title: v.title,\n score: v.hits / keywords.length,\n snippet: v.firstSnippet,\n mtime: v.mtime,\n }))\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n return hits;\n }\n\n // Hybrid path: load cached index, embed query, search + graph rerank.\n if (!existsSync(indexPath)) {\n throw new Error(`index not built at ${indexPath} — run: semantic-sidekick --notes ${notesPath} --reindex`);\n }\n\n const embedder = new Embedder();\n await embedder.init();\n\n const metaPath = join(indexPath, \"meta.json\");\n if (existsSync(metaPath)) {\n try {\n const meta = JSON.parse(await readFile(metaPath, \"utf-8\")) as { model?: string };\n if (meta.model && meta.model !== embedder.getModel()) {\n throw new Error(`model mismatch (cached=${meta.model}, current=${embedder.getModel()}) — run --reindex`);\n }\n } catch (e) {\n if (e instanceof Error && e.message.startsWith(\"model mismatch\")) throw e;\n }\n }\n\n const vectorIndex = new VectorIndex(embedder.getDimensions());\n const loaded = await vectorIndex.load(indexPath);\n if (!loaded) {\n throw new Error(`could not load vector index at ${indexPath} — run --reindex`);\n }\n\n const graph = new GraphBuilder();\n await graph.load(indexPath);\n\n const docByPath = new Map(documents.map((d) => [d.path, d]));\n const queryEmbed = await embedder.embed(opts.query);\n const semantic = vectorIndex.search(queryEmbed, limit * 3);\n const graphResults = graph.searchGraph(opts.query, 2);\n const graphPaths = new Set(graphResults.map((r) => r.path));\n\n const hybrid = semantic\n .map((r) => {\n const doc = docByPath.get(r.path);\n let score = graphPaths.has(r.path) ? r.score * 1.3 : r.score;\n if (doc?.loadPriority !== undefined) {\n score *= 1 + (doc.loadPriority - 5) * 0.04;\n }\n return { ...r, score };\n })\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n\n return hybrid.map((r) => {\n const doc = docByPath.get(r.path);\n return {\n path: r.path,\n title: (r as any).title ?? doc?.title,\n score: r.score,\n snippet: (r as any).snippet,\n mtime: doc?.mtime,\n };\n });\n}\n\nconst STOPWORDS = new Set([\n \"a\", \"an\", \"and\", \"are\", \"as\", \"at\", \"be\", \"by\", \"can\", \"do\", \"does\", \"for\", \"from\",\n \"how\", \"i\", \"if\", \"in\", \"is\", \"it\", \"its\", \"of\", \"on\", \"or\", \"our\", \"should\", \"that\",\n \"the\", \"this\", \"to\", \"was\", \"we\", \"what\", \"when\", \"where\", \"which\", \"why\", \"will\",\n \"with\", \"you\", \"your\", \"have\", \"has\", \"had\", \"but\", \"not\", \"so\", \"any\", \"all\",\n \"about\", \"there\", \"their\", \"they\", \"them\", \"these\", \"those\", \"would\", \"could\",\n \"might\", \"may\", \"really\", \"just\", \"like\", \"some\", \"more\", \"than\", \"into\", \"out\",\n \"up\", \"down\", \"need\", \"want\", \"get\", \"got\", \"also\", \"very\", \"much\",\n]);\n\nfunction extractKeywords(query: string): string[] {\n const tokens = query\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, \" \")\n .split(/\\s+/)\n .filter((t) => t.length > 2 && !STOPWORDS.has(t));\n const seen = new Set<string>();\n const out: string[] = [];\n for (const t of tokens) {\n if (!seen.has(t)) {\n seen.add(t);\n out.push(t);\n }\n if (out.length >= 6) break;\n }\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TheGlitchKing stack compatibility matrix
|
|
3
|
+
tier: reference
|
|
4
|
+
domains:
|
|
5
|
+
- architecture
|
|
6
|
+
- distribution
|
|
7
|
+
status: active
|
|
8
|
+
last_updated: 2026-05-07
|
|
9
|
+
version: 1.0.0
|
|
10
|
+
audience:
|
|
11
|
+
- developers
|
|
12
|
+
- admin
|
|
13
|
+
purpose: Canonical compatibility matrix for the TheGlitchKing memory-layer stack. Defines which combinations of underlying plugin versions are tested together and ship as a coherent set under each `dev-stack` meta-plugin version. This is the single source of truth referenced by every plugin's README.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# TheGlitchKing stack compatibility matrix
|
|
17
|
+
|
|
18
|
+
This is **the** compatibility matrix for the TheGlitchKing memory-layer + planning + docs + translation stack. It documents which combinations of underlying plugin versions are tested together and ship as a coherent set under each `dev-stack` meta-plugin version.
|
|
19
|
+
|
|
20
|
+
The matrix lives in `semantic-memory` (this repo) because semantic-memory is the runtime that consumes the other plugins as either:
|
|
21
|
+
- producers writing into corpora (persistent-planning → `plans`, babel-fish → `project-map`, hit-em-with-the-docs → `docs`)
|
|
22
|
+
- a frontmatter standard the runtime validates against (HEWTD)
|
|
23
|
+
|
|
24
|
+
Every other plugin's README cross-references this doc as the canonical source.
|
|
25
|
+
|
|
26
|
+
## Active matrix
|
|
27
|
+
|
|
28
|
+
### `dev-stack@1.0.x` (current)
|
|
29
|
+
|
|
30
|
+
| Plugin | Required range | Tested at |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `@theglitchking/semantic-memory` | `^1.0.0` | 1.0.0 (Phase 2.0.0a — rebrand mechanics, runtime equivalent to semantic-sidekick 0.2.5) |
|
|
33
|
+
| `@theglitchking/hit-em-with-the-docs` | `~2.2.0` | 2.2.0 (Phase 1.1.0 — `tier: "plan"` + conditional `version`) |
|
|
34
|
+
| `@theglitchking/persistent-planning` | `^3.0.0` | 3.0.0 (Phase 1.2.0/1.3.0 — sm/lg modes + layered phase/task/atom/notes) |
|
|
35
|
+
| `@theglitchking/babel-fish` | `^2.0.0` | 2.0.0 (existing release; integration with semantic-memory project-map corpus + glossary side-channel ships in Phase 3.1.0) |
|
|
36
|
+
|
|
37
|
+
#### Why these ranges
|
|
38
|
+
- **semantic-memory `^1.0.0`**: any 1.x is API-compatible. Multi-corpus refactor (Phase 2.0.0b) lands as 1.1.0; will not break the 1.0 tool surface (regression-snapshot suite gates this).
|
|
39
|
+
- **hit-em-with-the-docs `~2.2.0`**: the `tier: "plan"` extension is the precise feature persistent-planning depends on. Tilde-range allows patch-level updates (2.2.x) without accepting a 2.3.0 that might re-shape the schema. Will widen to `^2.2.0` once 2.3 is known to be additive.
|
|
40
|
+
- **persistent-planning `^3.0.0`**: any 3.x preserves the lg-mode contract (phase / task / atom / notes layers, HEWTD frontmatter, sm-mode preservation).
|
|
41
|
+
- **babel-fish `^2.0.0`**: existing 2.0 release. Integration uses babel-fish's existing 19-section project-map output unchanged — semantic-memory consumes the artifact, doesn't drive babel-fish runtime.
|
|
42
|
+
|
|
43
|
+
#### What's tested together
|
|
44
|
+
- Each underlying plugin has its own test suite (preserved bit-for-bit through the rebrand)
|
|
45
|
+
- semantic-memory regression-snapshot suite (`test/regression/`) gates the 33-tool surface across all of these
|
|
46
|
+
- Phase 4.4.0 (persistent-planning testing) verifies sm + lg flows with semantic-memory present and absent
|
|
47
|
+
- Phase 4.5.0 (babel-fish testing) verifies the glossary contract end-to-end with semantic-memory's translation verbs
|
|
48
|
+
|
|
49
|
+
#### What's NOT tested together (documented gaps)
|
|
50
|
+
- Multi-corpus runtime is preview-documented in semantic-memory 1.0.0a but not implemented; full integration tests with all six corpora active land in Phase 4.2.0 once 2.0.0b ships
|
|
51
|
+
- Drift detection and translation verbs are not part of the 1.0 surface; they ship in Phase 3.1.0 / 3.3.0
|
|
52
|
+
- pages-compat mode (1:1 surface mapping for semantic-pages migrators) ships in 2.0.0b
|
|
53
|
+
|
|
54
|
+
## Future matrix (planned)
|
|
55
|
+
|
|
56
|
+
### `dev-stack@1.1.x` (after semantic-memory 1.1.0 ships with multi-corpus)
|
|
57
|
+
|
|
58
|
+
| Plugin | Range |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `semantic-memory` | `^1.1.0` |
|
|
61
|
+
| `hit-em-with-the-docs` | `~2.2.0` (unchanged) |
|
|
62
|
+
| `persistent-planning` | `^3.0.0` (unchanged) |
|
|
63
|
+
| `babel-fish` | `^2.0.0` (unchanged) |
|
|
64
|
+
|
|
65
|
+
When 1.1 ships, dev-stack pin tightens to `^1.1.0` to ensure consumers get the multi-corpus runtime.
|
|
66
|
+
|
|
67
|
+
### `dev-stack@1.2.x` (after Phase 3.1.0 — babel-fish glossary integration)
|
|
68
|
+
|
|
69
|
+
When semantic-memory adds the `project-map` corpus + translation verbs (3.1.0 of the meta-plan = a minor of semantic-memory), babel-fish constraint may tighten to a specific minor that ships the glossary side-channel contract changes (TBD — could be 2.1.0 or 2.0.0 if the existing 19-section output already meets the contract).
|
|
70
|
+
|
|
71
|
+
### `dev-stack@2.0.x` (after Phase 3.2.0 / 3.3.0 — code corpus + drift)
|
|
72
|
+
|
|
73
|
+
Major bump if the drift-detection feature changes the way users author HEWTD docs (e.g. requires a new field). Otherwise stays at 1.x.
|
|
74
|
+
|
|
75
|
+
## Coexistence with deprecated plugins
|
|
76
|
+
|
|
77
|
+
| Plugin | Status | Coexists with dev-stack? |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `semantic-sidekick` | Renamed to `semantic-memory` at 1.0.0a (PR #9) | Don't install both — semantic-memory IS the new name. Migrate via uninstall + install. |
|
|
80
|
+
| `semantic-pages` | Deprecated as of 2026-05-07 (PR #5) | Yes — different MCP key (`semantic-vault` vs `semantic-memory`). Both can run side-by-side during the 12-month sunset. |
|
|
81
|
+
|
|
82
|
+
## How to verify your install matches this matrix
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Check the meta-plugin version
|
|
86
|
+
/plugin list --installed | grep dev-stack
|
|
87
|
+
|
|
88
|
+
# Check each underlying plugin's version
|
|
89
|
+
/plugin list --installed | grep -E "semantic-memory|hit-em-with-the-docs|persistent-planning|babel-fish"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Or via npm:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm ls @theglitchking/semantic-memory @theglitchking/hit-em-with-the-docs @theglitchking/persistent-planning @theglitchking/babel-fish
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
If any version falls outside the range listed above for your `dev-stack` version, you've drifted — either bump dev-stack or pin the underlying plugin manually in your `.mcp.json` / `package.json`.
|
|
99
|
+
|
|
100
|
+
## Inter-PR coordination history
|
|
101
|
+
|
|
102
|
+
This matrix was derived from the unified memory-layer plan tracked in `~/workspace/the-glitch-kingdom/persistent-planning/.planning/layered-planning-with-mcp-and-hewtd-frontmatter/task_plan.md`. Initial entries reflect the PRs landing in the May 2026 launch window:
|
|
103
|
+
|
|
104
|
+
| PR | Repo | Status | Lands |
|
|
105
|
+
|---|---|---|---|
|
|
106
|
+
| #7, #8 | semantic-sidekick | Merged to main | offline packaging, regression baseline |
|
|
107
|
+
| #3 | hit-em-with-the-docs | Open | HEWTD 2.2.0 — `tier: "plan"` + conditional version |
|
|
108
|
+
| #1 | persistent-planning | Open | persistent-planning 3.0.0 — sm/lg + layered model |
|
|
109
|
+
| #9 | semantic-sidekick | Open | semantic-memory 1.0.0a rebrand mechanics |
|
|
110
|
+
| #5 | semantic-pages | Open | deprecation announcement |
|
|
111
|
+
| (new repo) | dev-stack | Created private; pending visibility flip + publish | meta-plugin 1.0.0 |
|
|
112
|
+
|
|
113
|
+
## Updating this matrix
|
|
114
|
+
|
|
115
|
+
When a new minor of any underlying plugin ships:
|
|
116
|
+
1. Verify it satisfies the existing range (e.g. semantic-memory 1.1.0 satisfies `^1.0.0`)
|
|
117
|
+
2. If it does AND the integration is tested → no matrix change needed
|
|
118
|
+
3. If it changes behavior in a way users should opt into → bump dev-stack minor and tighten the constraint
|
|
119
|
+
4. If it requires breaking changes in another plugin → bump dev-stack major
|
|
120
|
+
|
|
121
|
+
The matrix is hand-maintained. Automation (a CI job that pins from npm latest) is a future improvement.
|
|
122
|
+
|
|
123
|
+
## See also
|
|
124
|
+
|
|
125
|
+
- `~/workspace/the-glitch-kingdom/dev-stack/README.md` — meta-plugin README with install / upgrade flow
|
|
126
|
+
- `~/workspace/the-glitch-kingdom/persistent-planning/.planning/layered-planning-with-mcp-and-hewtd-frontmatter/task_plan.md` — Phase 4.14.0 (cross-repo coordination)
|
|
127
|
+
- Each underlying plugin's CHANGELOG for what shipped in the bumps reflected here
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: corpora.json schema (preview, ships in 2.0.0b)
|
|
3
|
+
tier: standard
|
|
4
|
+
domains:
|
|
5
|
+
- architecture
|
|
6
|
+
- configuration
|
|
7
|
+
status: draft
|
|
8
|
+
last_updated: 2026-05-07
|
|
9
|
+
version: 1.0.0
|
|
10
|
+
audience:
|
|
11
|
+
- developers
|
|
12
|
+
purpose: Preview the multi-corpus configuration file that semantic-memory will read in v2.0.0b. Documents the schema, defaults, and smart-middle activation contract before the implementation lands.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# `corpora.json` schema (preview)
|
|
16
|
+
|
|
17
|
+
> **Status: planned for 2.0.0b.** This file documents the design of `corpora.json` so consumers and downstream plugins (persistent-planning, babel-fish) can plan against it. The implementation lands in the next release. v1.0.0 (this release) preserves the legacy single-vault behavior unchanged.
|
|
18
|
+
|
|
19
|
+
`corpora.json` is the configuration file that tells semantic-memory which corpora to index and how. It lives at `<project-root>/.semantic/corpora.json` and is auto-bootstrapped on first run by the **smart-middle activation** heuristic.
|
|
20
|
+
|
|
21
|
+
## Schema
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"schema_version": "1.0",
|
|
26
|
+
"corpora": [
|
|
27
|
+
{
|
|
28
|
+
"name": "vault",
|
|
29
|
+
"root": "./.claude/.vault",
|
|
30
|
+
"glob": "**/*.md",
|
|
31
|
+
"chunker": "markdown",
|
|
32
|
+
"enabled": true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "code",
|
|
36
|
+
"root": "./src",
|
|
37
|
+
"glob": "**/*.{ts,tsx,js,jsx}",
|
|
38
|
+
"chunker": "tree-sitter-ts",
|
|
39
|
+
"enabled": true
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "plans",
|
|
43
|
+
"root": "./.planning",
|
|
44
|
+
"glob": "**/*.md",
|
|
45
|
+
"chunker": "markdown",
|
|
46
|
+
"enabled": true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "docs",
|
|
50
|
+
"root": "./.documentation",
|
|
51
|
+
"glob": "**/*.md",
|
|
52
|
+
"chunker": "markdown",
|
|
53
|
+
"enabled": true
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "research",
|
|
57
|
+
"root": "./.research",
|
|
58
|
+
"glob": "**/*.md",
|
|
59
|
+
"chunker": "markdown",
|
|
60
|
+
"enabled": true
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "project-map",
|
|
64
|
+
"root": "./.babel-fish",
|
|
65
|
+
"glob": "**/*.md",
|
|
66
|
+
"chunker": "markdown",
|
|
67
|
+
"enabled": true,
|
|
68
|
+
"extras": {
|
|
69
|
+
"glossary_extractor": "babel-fish-vocabulary"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Field reference
|
|
77
|
+
|
|
78
|
+
### Top level
|
|
79
|
+
|
|
80
|
+
| Field | Type | Required | Description |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| `schema_version` | string | yes | Schema version of this file. Currently `"1.0"`. |
|
|
83
|
+
| `corpora` | array of corpus objects | yes | One entry per corpus to register. |
|
|
84
|
+
|
|
85
|
+
### Per corpus
|
|
86
|
+
|
|
87
|
+
| Field | Type | Required | Description |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| `name` | string | yes | Corpus identifier. Used as the suffix for per-corpus search verbs (`search_<name>`). Reserved names: `vault`, `code`, `plans`, `docs`, `research`, `project-map`. Custom names allowed. |
|
|
90
|
+
| `root` | string | yes | Directory to scan for content. Relative to project root. |
|
|
91
|
+
| `glob` | string | yes | File pattern (glob syntax) for files to index within `root`. |
|
|
92
|
+
| `chunker` | string | yes | Chunker to use. Built-in: `markdown`, `tree-sitter-ts`, `fixed-window`. |
|
|
93
|
+
| `enabled` | boolean | no (default `true`) | If `false`, the corpus is registered but not indexed; search verbs return `{status: "no_index", results: []}`. |
|
|
94
|
+
| `extras` | object | no | Chunker-specific or corpus-specific configuration. E.g. `glossary_extractor: "babel-fish-vocabulary"` activates the babel-fish glossary side-channel for the project-map corpus. |
|
|
95
|
+
|
|
96
|
+
## Smart-middle activation
|
|
97
|
+
|
|
98
|
+
On first run (when `<project-root>/.semantic/` doesn't exist), semantic-memory scans the project root for known markers:
|
|
99
|
+
|
|
100
|
+
| Marker | Activates corpus |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `.claude/.vault/` | `vault` |
|
|
103
|
+
| `src/` (or detected source dir) with TS/JS files | `code` |
|
|
104
|
+
| `.planning/` with `.meta/workspace.json` (lg mode) | `plans` |
|
|
105
|
+
| `.documentation/` | `docs` |
|
|
106
|
+
| `.research/` | `research` |
|
|
107
|
+
| `.babel-fish/` with `01-vocabulary.md` | `project-map` |
|
|
108
|
+
|
|
109
|
+
For each marker found, the corresponding corpus is added to `corpora.json` with `enabled: true`. Markers not found result in the corpus being omitted (or added with `enabled: false` for visibility — TBD).
|
|
110
|
+
|
|
111
|
+
A decision banner is printed on first activation listing what got enabled and why.
|
|
112
|
+
|
|
113
|
+
## Backwards compatibility
|
|
114
|
+
|
|
115
|
+
For projects that have an existing `.claude/.vault/` and never ran v1.0.0+ before, the smart-middle activation creates a `corpora.json` with just `vault` enabled, pointing at the existing `.claude/.vault/` directory. **The existing `.semantic-sidekick-index/` directory continues to be the index location** (preserved for backwards compatibility).
|
|
116
|
+
|
|
117
|
+
For projects that ARE v1.0.0a-only (no `corpora.json`), the legacy single-vault flow continues to work without any `corpora.json`. The auto-default reads `notes_path` from CLI args or environment, exactly as v0.x did.
|
|
118
|
+
|
|
119
|
+
## Per-corpus search verbs (planned for 2.0.0b)
|
|
120
|
+
|
|
121
|
+
Each registered corpus produces a corresponding `search_<name>` MCP verb:
|
|
122
|
+
|
|
123
|
+
- `search_vault(query, limit?, ...filters)` — equivalent to today's `search_semantic` for the vault corpus
|
|
124
|
+
- `search_code(query, language?, symbol_kind?, pathGlob?)` — when code corpus is active
|
|
125
|
+
- `search_plans(query, ...filters)` — when plans corpus is active
|
|
126
|
+
- `search_docs(query, ...filters)` — when docs corpus is active
|
|
127
|
+
- `search_research(query, ...filters)` — when research corpus is active
|
|
128
|
+
- `search_project_map(query, ...filters)` — when project-map corpus is active
|
|
129
|
+
|
|
130
|
+
Plus a cross-corpus verb:
|
|
131
|
+
|
|
132
|
+
- `search_all(query, corpus_filter?, limit?)` — searches across all enabled corpora and merges results
|
|
133
|
+
|
|
134
|
+
Empty / missing corpora always return `{status: "no_index", results: []}` rather than erroring (the `no_index` contract). This means the agent can always attempt a search and learn from the empty result.
|
|
135
|
+
|
|
136
|
+
## Conditional tool registration
|
|
137
|
+
|
|
138
|
+
Workflow tools register only when their target corpus has data:
|
|
139
|
+
|
|
140
|
+
- Plan workflow tools (`next_atom`, `update_atom_status`, ...) register iff `plans` corpus is active AND has data
|
|
141
|
+
- Drift detection tools (`detect_drift`) register iff `code` corpus + at least one doc-bearing corpus are both active
|
|
142
|
+
- Translation tools (`translate`, `reverse_translate`, `list_vocabulary`) register iff `project-map` corpus is active AND `glossary.json` is present
|
|
143
|
+
|
|
144
|
+
Search tools always register and use the `no_index` contract for empty corpora.
|
|
145
|
+
|
|
146
|
+
## See also
|
|
147
|
+
|
|
148
|
+
- `docs/smart-middle-activation.md` — full smart-middle resolution rules
|
|
149
|
+
- `docs/regression-snapshot.md` — the gate that protects existing tool surface during the multi-corpus refactor
|
|
150
|
+
- `~/workspace/the-glitch-kingdom/persistent-planning/.planning/layered-planning-with-mcp-and-hewtd-frontmatter/task_plan.md` — the meta-plan
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Regression-Snapshot Suite
|
|
3
|
+
tier: standard
|
|
4
|
+
domains:
|
|
5
|
+
- testing
|
|
6
|
+
- operational
|
|
7
|
+
status: active
|
|
8
|
+
last_updated: 2026-05-07
|
|
9
|
+
version: 1.0.0
|
|
10
|
+
audience:
|
|
11
|
+
- developers
|
|
12
|
+
purpose: Document the regression-snapshot suite, when it fails, how to interpret a failure, and the policy for updating snapshots intentionally.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Regression-Snapshot Suite
|
|
16
|
+
|
|
17
|
+
The regression suite under `test/regression/` is the **structural stability gate** for semantic-sidekick. It captures the shape of the MCP tool surface — every tool's name, description, input schema, and representative output structure — as vitest snapshots that fail loudly on any unintentional change.
|
|
18
|
+
|
|
19
|
+
This is the load-bearing baseline for the **semantic-memory rebrand + multi-corpus refactor** (Phase 1.0.0 of the unified memory layer plan). The contract: every snapshot in this suite must remain unchanged through the rebrand unless an intentional break is documented and signed off.
|
|
20
|
+
|
|
21
|
+
## What the suite covers
|
|
22
|
+
|
|
23
|
+
### `test/regression/tool-surface.test.ts` — input contract
|
|
24
|
+
|
|
25
|
+
Three snapshots:
|
|
26
|
+
|
|
27
|
+
| Snapshot | Captures | Size |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `tool-surface 1` | Full list of registered tools — name, description, complete JSON Schema for inputs | ~1000 lines |
|
|
30
|
+
| `tool-name list (compact) 1` | Sorted list of tool names in default mode (33 entries) | ~35 lines |
|
|
31
|
+
| `tool-name list in read-only mode 1` | Sorted list of tool names in `--read-only` mode (21 entries) | ~22 lines |
|
|
32
|
+
|
|
33
|
+
Plus structural assertions:
|
|
34
|
+
- Default mode exposes exactly 33 tools
|
|
35
|
+
- `--read-only` mode exposes exactly 21 tools
|
|
36
|
+
- Read-only mode never exposes any of the 12 write tools (explicit name list)
|
|
37
|
+
|
|
38
|
+
### `test/regression/tool-outputs.test.ts` — output contract
|
|
39
|
+
|
|
40
|
+
16 snapshots covering response shapes for:
|
|
41
|
+
- **Deterministic read tools**: `read_note`, `read_multiple_notes`, `list_notes`, `get_frontmatter`, `backlinks`, `forwardlinks`, `graph_path`, `graph_statistics`, `get_stats`
|
|
42
|
+
- **Lint tools**: `lint_vault` (rule-name structure)
|
|
43
|
+
- **Embedding-dependent tools** (structural only — see below): `search_semantic`, `search_text`, `search_hybrid`, `search_graph`
|
|
44
|
+
|
|
45
|
+
## Embedding-dependent vs deterministic snapshots
|
|
46
|
+
|
|
47
|
+
Search tools that route through the embedder produce slightly different scores across runs (within a tolerance). Snapshotting their full output would create false-positive failures.
|
|
48
|
+
|
|
49
|
+
The suite handles this with two strategies:
|
|
50
|
+
|
|
51
|
+
1. **Strip volatile fields**: `mtime`, `lastIndexed`, embedding scores are removed (or reduced to presence-checks) before snapshotting.
|
|
52
|
+
2. **Shape-only snapshots**: For `search_semantic` / `search_hybrid` / `search_graph`, the snapshot captures only the *keys present* in a result, not the values. The shape regresses if a field is added or removed; embedding score drift never breaks the test.
|
|
53
|
+
|
|
54
|
+
## When a snapshot fails
|
|
55
|
+
|
|
56
|
+
A regression-snapshot failure means the tool surface or output shape changed since the snapshot was last written. There are three responses:
|
|
57
|
+
|
|
58
|
+
### 1. The change is unintentional → fix the regression
|
|
59
|
+
|
|
60
|
+
This is the default assumption during the semantic-memory rebrand. Investigate what changed, restore the previous behavior. Do not update the snapshot.
|
|
61
|
+
|
|
62
|
+
### 2. The change is intentional and documented
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
- Adding a new MCP tool (the surface snapshot will gain an entry)
|
|
66
|
+
- Extending an existing tool's input schema (the surface snapshot will show the new field)
|
|
67
|
+
- Adding a field to a tool's output (the output snapshot will show the new key)
|
|
68
|
+
|
|
69
|
+
Process:
|
|
70
|
+
1. Confirm the change is intentional and matches the published plan
|
|
71
|
+
2. Update snapshots: `npm test -- -u test/regression/`
|
|
72
|
+
3. Review the diff in the snapshot file as part of the PR
|
|
73
|
+
4. Document the intentional break in the relevant CHANGELOG entry (semantic-memory CHANGELOG, probably under the version that introduces it)
|
|
74
|
+
5. Sign-off in the PR description: explicit acknowledgment that this is a deliberate break
|
|
75
|
+
|
|
76
|
+
### 3. The change reflects an upgrade to the embedding model or chunker
|
|
77
|
+
|
|
78
|
+
If the embedding model changes (e.g., MiniLM-L6 → BGE-m3) or the chunker changes (markdown → tree-sitter for some corpus), the deterministic snapshots may legitimately need to update. Treat as case 2 — explicit, documented, signed off. Note the model/chunker change in the CHANGELOG.
|
|
79
|
+
|
|
80
|
+
## Updating snapshots
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Update only the regression suite
|
|
84
|
+
npm test -- -u test/regression/
|
|
85
|
+
|
|
86
|
+
# Update everything (use sparingly)
|
|
87
|
+
npm test -- -u
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Always inspect the resulting diff before committing. A snapshot diff that touches more than the file you changed is a signal that something else regressed.
|
|
91
|
+
|
|
92
|
+
## Capture process (how the baseline was created)
|
|
93
|
+
|
|
94
|
+
Captured 2026-05-07 BEFORE any sidekick code changes for the semantic-memory rebrand. Three commits established the baseline:
|
|
95
|
+
|
|
96
|
+
1. `test(regression): golden snapshots for MCP tool surface` — input contracts (3 snapshots, 6 tests)
|
|
97
|
+
2. `test(regression): golden snapshots for MCP tool output shapes` — output contracts (16 snapshots, 16 tests)
|
|
98
|
+
3. `docs(regression): document the snapshot suite` — this file
|
|
99
|
+
|
|
100
|
+
Combined: 19 snapshots / 22 regression tests / 0 failures at baseline.
|
|
101
|
+
|
|
102
|
+
The full sidekick suite at baseline: 211 tests across 26 files, 21.85s.
|
|
103
|
+
|
|
104
|
+
## Performance baseline (separate, deferred)
|
|
105
|
+
|
|
106
|
+
A performance baseline (query latency p50/p95, index size per chunk count) is mentioned in the regression plan but is captured separately by `scripts/regression-perf-baseline.js` (TBD). It is **informational, not gated** — a 10% performance budget will be enforced in Phase 4.2.0, not now.
|
|
107
|
+
|
|
108
|
+
## Suite lifecycle
|
|
109
|
+
|
|
110
|
+
The regression suite is a **gate during refactoring**. After the semantic-memory rebrand and multi-corpus refactor are complete, the suite should be re-evaluated:
|
|
111
|
+
|
|
112
|
+
- Snapshots that captured `--read-only` 21-tool surface may need to evolve to reflect new conditional tool registration (workflow / drift / translation verbs).
|
|
113
|
+
- Output snapshots may need to evolve as new corpora come online and result shapes grow corpus tags.
|
|
114
|
+
- The suite itself should evolve from "lock current behavior" to "verify multi-corpus behavior remains stable as new corpora are added."
|
|
115
|
+
|
|
116
|
+
That evolution happens at Phase 4.2.0 (per the plan). Until then: this suite is the immutable baseline.
|
|
117
|
+
|
|
118
|
+
## Related
|
|
119
|
+
|
|
120
|
+
- Plan: `~/workspace/the-glitch-kingdom/persistent-planning/.planning/layered-planning-with-mcp-and-hewtd-frontmatter/task_plan.md`
|
|
121
|
+
- Companion behavior tests: `test/integration/mcp-server.test.ts`, `test/e2e/stdio-server.test.ts`
|
|
122
|
+
- Vitest snapshot docs: https://vitest.dev/guide/snapshot.html
|