@theglitchking/semantic-memory 1.0.1 → 1.1.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.
@@ -5,14 +5,14 @@
5
5
  "email": "theglitchking@users.noreply.github.com"
6
6
  },
7
7
  "metadata": {
8
- "description": "Single-plugin marketplace for semantic-memory — the unified memory-layer plugin (rebrand of semantic-sidekick at v1.0.0). One MCP server, multiple typed corpora, unified cross-corpus knowledge graph, conditional tool registration, drift detection, and human↔code translation.",
9
- "version": "1.0.1"
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 — all backwards-compatible with v1.0.x via deprecation shims.",
9
+ "version": "1.1.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "semantic-memory",
14
- "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 based on active corpora, in-process drift detection, and human↔code translation. Rebrand of semantic-sidekick at v1.0.0; existing semantic-sidekick plugins continue to work under the legacy name during the transition period.",
15
- "version": "1.0.1",
14
+ "description": "Unified memory-layer plugin for Claude Code. One MCP server, 40 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. Backwards-compatible with v1.0.x — every previous tool name remains callable through deprecation shims (removed in v2.0.0).",
15
+ "version": "1.1.0",
16
16
  "author": {
17
17
  "name": "TheGlitchKing"
18
18
  },
package/CHANGELOG.md CHANGED
@@ -2,6 +2,72 @@
2
2
 
3
3
  All notable changes to semantic-memory (formerly semantic-sidekick) will be documented here.
4
4
 
5
+ ## [1.1.0] - 2026-05-08 — brain-absorption
6
+
7
+ Adapts four targeted strengths from `JimmyMcBride/brain` into semantic-memory: AGENTS.md contract artifact, hard-gated verification sessions, multi-agent skill bundler, distill/synthesize unification — plus a server.ts refactor that makes future additions cheap, a deprecation-shimmed tool consolidation, and an opt-in SessionStart drift-detection layer.
8
+
9
+ ### Added
10
+
11
+ - **AGENTS.md contract artifact** at the project root with managed-block markers (`<!-- semantic-memory:begin contract -->`...`<!-- end -->`). Frontmatter records `generated_by`, `version`, `last_generated`. The block lists Active Modes, Required Workflow, Tool Surface (live + deprecated), and Memory Policy. Local Notes tail outside the markers is preserved verbatim across regenerations. New tools: `regenerate_contract`, `inspect_contract`. New slash command: `/contract`.
12
+ - **Hard-gated verification sessions.** New module `src/core/session.ts` owns `<project>/.claude/.semantic-memory/session.json`. New MCP tools: `session_start`, `session_run`, `session_finish`, `session_status`. `session_finish` refuses without recorded verifications unless `verified: false` AND a non-empty `reason` is provided. State is opt-in; sessions auto-record `notes_touched` from `apply_patch` / `synthesize_note` / `synthesize_promote` operations.
13
+ - **Distill ↔ synthesize_note unification.** `synthesize_note` gains `proposal: boolean` (writes to `proposals/<date>-<slug>.md` with `status: proposal`), `proposal_subdir` (override), and `from_session: boolean` (auto-pulls task context, verifications, and touched notes from the active session). New tool `synthesize_promote` atomically moves a reviewed proposal to its canonical destination.
14
+ - **Multi-agent skill bundler.** New CLI subcommand tree `bin/semantic-memory skills {targets,install,uninstall,list}` for codex, copilot, and pi. Each install writes a `.semantic-memory-skill-manifest.json` with sha256s for stale/drift detection. Non-destructive by default; `--force` overrides. Existing Claude flow via `claude-plugin-runtime` postinstall is unchanged.
15
+ - **SessionStart drift detection.** Fast-tier checks (file-system probes only) for `.mcp.json` server entry, hook registration, AGENTS.md managed-block presence, and session staleness. Inline JS in `hooks/vault-context.js` — no spawn cost. Healthy installs see nothing; drifted installs see a single `<vault-drift>` block at session start with a pointer to `/healthcheck`.
16
+ - **Manual `/healthcheck` deep audit.** New module `src/core/healthcheck.ts` provides fast and slow tiers (slow includes full `lint_vault`). 5-minute result cache. CLI command `semantic-memory healthcheck` accepts `--fast` and `--json`. The `--fix` flag is reserved for v1.2.
17
+
18
+ ### Changed
19
+
20
+ - `src/mcp/server.ts` (1039 lines, 33 tools in one file) split into per-domain modules under `src/mcp/tools/*.ts` with a shared `src/mcp/context.ts`. server.ts shrinks to 96 lines (composition root + lifecycle). **Tool surface unchanged** — every tool name, schema, and handler is preserved bit-for-bit, gated by the existing regression snapshot suite.
21
+ - `lint_vault` accepts a new optional `checks: ("schema"|"provenance"|"stale"|"broken_links")[]` filter. Omit for the existing full report. Additive schema change.
22
+ - `manage_tags` `action` enum widens to include `"rename"`, with new optional `from` / `to` fields. `action='rename'` calls the existing `tagManager.renameVaultWide`. Additive schema change.
23
+ - `.claude/CLAUDE.md` now opens with a pointer to `AGENTS.md` as the primary contract; CLAUDE.md remains for repo-specific augmentation.
24
+
25
+ ### Deprecated (callable through v1.x via shim, removed in v2.0.0)
26
+
27
+ - `find_schema_violations` → use `lint_vault({checks: ["schema"]})`
28
+ - `find_missing_provenance` → use `lint_vault({checks: ["provenance"]})`
29
+ - `find_stale` → use `lint_vault({checks: ["stale"]})`
30
+ - `find_broken_links` → use `lint_vault({checks: ["broken_links"]})`
31
+ - `read_multiple_notes` → call `read_note` in a loop or via batched MCP calls
32
+ - `rename_tag` → use `manage_tags({action: "rename", from, to})`
33
+
34
+ All shims emit `[DEPRECATED — removed in v2.0.0; use ...]` prefix in their tool descriptions, visible to agents in the MCP tool list.
35
+
36
+ ### Tool-surface delta
37
+
38
+ ```
39
+ v1.0.1 → v1.1.0
40
+ +regenerate_contract +inspect_contract (Phase 3)
41
+ +synthesize_promote (Phase 4)
42
+ +session_start +session_run +session_finish
43
+ +session_status (Phase 5)
44
+ = 33 → 40 tools (write mode)
45
+ = 21 → 21 tools (read-only mode, unchanged)
46
+ ```
47
+
48
+ ### Backwards-compatibility contract
49
+
50
+ Four statements that remain true after v1.1.0 ships:
51
+
52
+ 1. **Every existing MCP tool name remains callable.** Eliminated tools become deprecation shims that delegate to their survivors. No automation needs to change in v1.x.
53
+ 2. **Hook output is unchanged when no session is active and no drift exists.** The Stop hook only adds new prompts when a session is open; the SessionStart hook only adds drift output when actual drift is detected.
54
+ 3. **AGENTS.md generation is opt-in.** Existing repos see no new files until they call `regenerate_contract` or run `/contract`.
55
+ 4. **Drift detection is silent on healthy installs.** Auto-check at SessionStart adds <100ms latency and emits zero output when no drift is present.
56
+
57
+ Storage layout (`.semantic-sidekick-index/`, `~/.semantic-sidekick/models/`) preserved per the v1.0 promise. No re-index, no model re-download required when upgrading from v1.0.x.
58
+
59
+ ### Tests
60
+
61
+ Suite expanded from 211 (v1.0.1) to 277 tests across 31 files. New coverage:
62
+ - `test/unit/agents-contract.test.ts` (7 tests)
63
+ - Extended `test/unit/synthesize.test.ts` (6 new tests for proposal mode)
64
+ - `test/unit/session.test.ts` (24 tests)
65
+ - `test/phase5/stop-hook-session.test.ts` (5 tests)
66
+ - `test/unit/skills-bundler.test.ts` (12 tests)
67
+ - `test/unit/healthcheck.test.ts` (12 tests)
68
+
69
+ All pre-existing tests pass unchanged. Snapshot diffs are verified additive-only across all phases.
70
+
5
71
  ## [1.0.1] - 2026-05-08 — Republish (no functional changes)
6
72
 
7
73
  ### Republish only
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: Generate or refresh AGENTS.md at the project root — the versionable agent contract. Preserves Local Notes across regenerations.
3
+ allowed-tools: mcp__semantic-vault__regenerate_contract, mcp__semantic-vault__inspect_contract
4
+ ---
5
+
6
+ When the user runs `/contract`, dispatch by argument:
7
+
8
+ - `/contract` (no arg) or `/contract refresh` — call `regenerate_contract` to write or refresh `AGENTS.md` at the project root. Report whether the file was written, whether hand-edits were detected, and how many characters of Local Notes were preserved.
9
+ - `/contract inspect` — call `inspect_contract` (read-only) and report whether `AGENTS.md` exists, whether managed-block markers are present, and the size of the Local Notes tail.
10
+ - `/contract force` — call `regenerate_contract` with `force=true` to overwrite hand-edited managed-block content. Use only after the user has confirmed they accept losing in-block hand-edits.
11
+
12
+ If the call returns `hand_edit_detected: true`, do NOT pass `force=true` automatically — surface the warning to the user and let them decide whether to move their changes to the Local Notes section or overwrite.
13
+
14
+ `AGENTS.md` is the canonical agent contract for this project. The managed block is regenerable from the current tool surface and active modes; the Local Notes section is user-owned and preserved verbatim across regenerations.
@@ -1,6 +1,24 @@
1
1
  ---
2
- description: Verify the local semantic-sidekick install starts cleanly; self-heals npx-cache ERR_MODULE_NOT_FOUND
3
- allowed-tools: Bash(npx:*)
2
+ description: Verify the local install + run drift detection. Self-heals npx-cache ERR_MODULE_NOT_FOUND. Pass --fast to skip the slow tier (full vault lint).
3
+ allowed-tools: Bash(npx:*), Bash(node:*)
4
4
  ---
5
5
 
6
- Run `npx --no @theglitchking/semantic-sidekick healthcheck` and relay the output. If it reports a fragile `.mcp.json` form, suggest running `/semantic-sidekick:normalize-config`.
6
+ When the user runs `/healthcheck`:
7
+
8
+ - `/healthcheck` (no arg) — Run the local install verification AND the full drift check (fast tier + slow tier). Report the results: install status, drift findings (if any), and remediation pointers (e.g. `/relink`, `/normalize-config`, `regenerate_contract`).
9
+ - `/healthcheck --fast` — Run the install verification + ONLY the fast-tier drift checks. Skips the full vault lint (slow tier). Use when the agent only wants quick install/manifest checks without the multi-second vault scan.
10
+
11
+ The fast-tier auto-check also runs on every `SessionStart` via `vault-context.js` — healthy installs see nothing at session start, drifted installs see a one-block warning with a pointer to this command.
12
+
13
+ Implementation: shell out to `node ./node_modules/@theglitchking/semantic-sidekick/bin/semantic-sidekick healthcheck [--fast]` (or `npx --no @theglitchking/semantic-sidekick healthcheck [--fast]` if no local install).
14
+
15
+ Drift findings are categorized by check:
16
+ - `mcp_json_entry` — `.mcp.json` missing the semantic-memory server entry. Fix: `/normalize-config`.
17
+ - `hook_registration` — `.claude/settings.json` missing one of SessionStart/UserPromptSubmit/Stop. Fix: `/relink` or reinstall.
18
+ - `agents_contract` — `AGENTS.md` exists but lacks managed-block markers. Fix: move custom content to a `Local Notes` section, or delete and re-run `regenerate_contract`.
19
+ - `session_staleness` — A session has been open >24h with no activity. Fix: resume (run `session_run` / `session_finish`) or remove the stale session file.
20
+ - `index_freshness` — No vector index built yet. Fix: call `reindex`.
21
+ - `skill_manifest:*` — Per-agent skill install state. Informational unless missing.
22
+ - `lint_vault` (slow tier only) — Vault lint findings. Many require human review (stale notes, broken wikilinks).
23
+
24
+ `--fix` flag is reserved for v1.2 — auto-applying safe fixes (re-link skills, reconcile `.mcp.json`, regenerate contract) is the planned v1.2 work. For v1.1, drift detection is read-only and points the user at the right manual command.
@@ -0,0 +1,273 @@
1
+ import {
2
+ lintVault
3
+ } from "./chunk-CAN2GWUG.js";
4
+
5
+ // src/core/healthcheck.ts
6
+ import { readFile, writeFile, mkdir } from "fs/promises";
7
+ import { existsSync } from "fs";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
10
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
11
+ async function runHealthcheck(opts) {
12
+ const start = opts.nowMs ?? Date.now();
13
+ const tier = opts.tier ?? "fast";
14
+ const cachePath = opts.cachePath ?? join(opts.projectRoot, ".claude", ".semantic-memory", "healthcheck-cache.json");
15
+ if (!opts.force && existsSync(cachePath)) {
16
+ try {
17
+ const raw = await readFile(cachePath, "utf-8");
18
+ const cached = JSON.parse(raw);
19
+ if (cached.tier_run === tier && start - cached.ran_at_ms < CACHE_TTL_MS) {
20
+ return { ...cached, cached: true };
21
+ }
22
+ } catch {
23
+ }
24
+ }
25
+ const findings = tier === "fast" ? await runFastTier(opts) : tier === "slow" ? await runSlowTier(opts) : [...await runFastTier(opts), ...await runSlowTier(opts)];
26
+ const counts = countBySeverity(findings);
27
+ const ranAt = new Date(start).toISOString();
28
+ const result = {
29
+ tier_run: tier,
30
+ ran_at: ranAt,
31
+ duration_ms: Date.now() - start,
32
+ findings,
33
+ counts
34
+ };
35
+ try {
36
+ await mkdir(join(opts.projectRoot, ".claude", ".semantic-memory"), { recursive: true });
37
+ await writeFile(cachePath, JSON.stringify({ ...result, ran_at_ms: start }, null, 2), "utf-8");
38
+ } catch {
39
+ }
40
+ return result;
41
+ }
42
+ function filterToDrift(result) {
43
+ return result.findings.filter((f) => f.severity !== "ok");
44
+ }
45
+ function countBySeverity(findings) {
46
+ const counts = { ok: 0, warn: 0, error: 0 };
47
+ for (const f of findings) counts[f.severity]++;
48
+ return counts;
49
+ }
50
+ async function runFastTier(opts) {
51
+ const checks = [
52
+ checkPluginVersion(opts.projectRoot),
53
+ checkMcpJsonEntry(opts.projectRoot),
54
+ checkHookRegistration(opts.projectRoot),
55
+ checkIndexFreshness(opts.projectRoot, opts.vaultPath),
56
+ checkSessionStaleness(opts.projectRoot),
57
+ checkAgentsContract(opts.projectRoot),
58
+ ...await skillManifestChecks(opts.projectRoot)
59
+ ];
60
+ const results = await Promise.all(checks.map((p) => p.catch((err) => failOpenWarn(err))));
61
+ return results.filter((r) => r !== null);
62
+ }
63
+ async function runSlowTier(opts) {
64
+ const findings = [];
65
+ if (opts.vaultPath && existsSync(opts.vaultPath)) {
66
+ try {
67
+ const report = await lintVault(opts.vaultPath);
68
+ const total = report.findings.length;
69
+ if (total === 0) {
70
+ findings.push({ check: "lint_vault", severity: "ok", summary: `vault lint clean (${report.counts.filesLinted} files)` });
71
+ } else {
72
+ findings.push({
73
+ check: "lint_vault",
74
+ severity: report.counts.errors > 0 ? "error" : "warn",
75
+ summary: `vault lint: ${total} findings (errors=${report.counts.errors}, warnings=${report.counts.warnings})`,
76
+ detail: `Run \`lint_vault\` for the full report. Many lint findings need human review (stale notes, broken wikilinks).`
77
+ });
78
+ }
79
+ } catch (err) {
80
+ findings.push({ check: "lint_vault", severity: "warn", summary: `lint_vault threw: ${err?.message ?? err}` });
81
+ }
82
+ }
83
+ return findings;
84
+ }
85
+ function failOpenWarn(err) {
86
+ return {
87
+ check: "internal",
88
+ severity: "warn",
89
+ summary: `healthcheck internal error: ${err instanceof Error ? err.message : String(err)}`
90
+ };
91
+ }
92
+ async function checkPluginVersion(projectRoot) {
93
+ const pkgPaths = [
94
+ join(projectRoot, "node_modules", "@theglitchking", "semantic-memory", "package.json"),
95
+ join(projectRoot, "node_modules", "@theglitchking", "semantic-sidekick", "package.json")
96
+ ];
97
+ for (const p of pkgPaths) {
98
+ if (!existsSync(p)) continue;
99
+ try {
100
+ const installed = JSON.parse(await readFile(p, "utf-8"));
101
+ if (typeof installed?.version === "string") {
102
+ return { check: "plugin_version", severity: "ok", summary: `installed=${installed.version}` };
103
+ }
104
+ } catch {
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+ async function checkMcpJsonEntry(projectRoot) {
110
+ const mcpPath = join(projectRoot, ".mcp.json");
111
+ if (!existsSync(mcpPath)) return null;
112
+ const data = JSON.parse(await readFile(mcpPath, "utf-8"));
113
+ const servers = data?.mcpServers ?? {};
114
+ const hasEntry = Object.keys(servers).some((k) => k === "semantic-vault" || k === "semantic-sidekick" || k === "semantic-memory");
115
+ if (!hasEntry) {
116
+ return {
117
+ check: "mcp_json_entry",
118
+ severity: "warn",
119
+ summary: ".mcp.json present but no semantic-memory/sidekick/vault server entry",
120
+ fixable_via: "mcp-reconcile"
121
+ };
122
+ }
123
+ return { check: "mcp_json_entry", severity: "ok", summary: ".mcp.json server entry present" };
124
+ }
125
+ async function checkHookRegistration(projectRoot) {
126
+ const settings = join(projectRoot, ".claude", "settings.json");
127
+ if (!existsSync(settings)) return null;
128
+ try {
129
+ const data = JSON.parse(await readFile(settings, "utf-8"));
130
+ const hooks = data?.hooks ?? {};
131
+ const required = ["SessionStart", "UserPromptSubmit", "Stop"];
132
+ const missing = [];
133
+ for (const event of required) {
134
+ const arr = hooks[event];
135
+ if (!Array.isArray(arr) || arr.length === 0) missing.push(event);
136
+ }
137
+ if (missing.length > 0) {
138
+ return {
139
+ check: "hook_registration",
140
+ severity: "warn",
141
+ summary: `missing hook registrations: ${missing.join(", ")}`,
142
+ detail: `Run \`/relink\` or reinstall the plugin to refresh hook registrations.`
143
+ };
144
+ }
145
+ return { check: "hook_registration", severity: "ok", summary: "all 3 hook events registered" };
146
+ } catch (err) {
147
+ return failOpenWarn(err);
148
+ }
149
+ }
150
+ async function checkIndexFreshness(projectRoot, vaultPath) {
151
+ const vault = vaultPath ?? defaultVaultPath(projectRoot);
152
+ if (!vault || !existsSync(vault)) return null;
153
+ const indexDir = join(vault, ".semantic-sidekick-index");
154
+ const meta = join(indexDir, "meta.json");
155
+ if (!existsSync(meta)) {
156
+ return {
157
+ check: "index_freshness",
158
+ severity: "warn",
159
+ summary: "no index built yet",
160
+ detail: "Run `reindex` to build the vector index.",
161
+ fixable_via: "reindex"
162
+ };
163
+ }
164
+ try {
165
+ const data = JSON.parse(await readFile(meta, "utf-8"));
166
+ if (typeof data.indexedAt === "string") {
167
+ return { check: "index_freshness", severity: "ok", summary: `last reindex ${data.indexedAt}` };
168
+ }
169
+ } catch {
170
+ }
171
+ return null;
172
+ }
173
+ async function checkSessionStaleness(projectRoot) {
174
+ const sessionPath = join(projectRoot, ".claude", ".semantic-memory", "session.json");
175
+ if (!existsSync(sessionPath)) return null;
176
+ try {
177
+ const state = JSON.parse(await readFile(sessionPath, "utf-8"));
178
+ if (state?.closed_at) return null;
179
+ const last = state?.last_activity_at;
180
+ if (typeof last !== "string") return null;
181
+ const ageMs = Date.now() - new Date(last).getTime();
182
+ if (ageMs > 24 * 60 * 60 * 1e3) {
183
+ return {
184
+ check: "session_staleness",
185
+ severity: "warn",
186
+ summary: `session ${state.id} stale (>${Math.round(ageMs / (60 * 60 * 1e3))}h since activity)`,
187
+ detail: `Either resume the session (run session_run / session_finish) or remove .claude/.semantic-memory/session.json to abandon it.`
188
+ };
189
+ }
190
+ return { check: "session_staleness", severity: "ok", summary: `session ${state.id} active` };
191
+ } catch {
192
+ return null;
193
+ }
194
+ }
195
+ async function checkAgentsContract(projectRoot) {
196
+ const path = join(projectRoot, "AGENTS.md");
197
+ if (!existsSync(path)) return null;
198
+ try {
199
+ const raw = await readFile(path, "utf-8");
200
+ const begin = raw.indexOf("<!-- semantic-memory:begin contract -->");
201
+ const end = raw.indexOf("<!-- semantic-memory:end contract -->");
202
+ if (begin === -1 || end === -1) {
203
+ return {
204
+ check: "agents_contract",
205
+ severity: "warn",
206
+ summary: "AGENTS.md exists but lacks managed-block markers",
207
+ detail: "Either move custom content to a Local Notes section, or delete AGENTS.md and run `regenerate_contract` to bootstrap.",
208
+ fixable_via: "regenerate-contract"
209
+ };
210
+ }
211
+ return { check: "agents_contract", severity: "ok", summary: "AGENTS.md managed block intact" };
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+ async function skillManifestChecks(projectRoot) {
217
+ const home = homedir();
218
+ const targets = [
219
+ { agent: "claude", path: join(home, ".claude", "skills") },
220
+ { agent: "codex", path: join(home, ".codex", "skills") },
221
+ { agent: "copilot", path: join(home, ".copilot", "skills") },
222
+ { agent: "pi", path: join(home, ".pi", "agent", "skills") },
223
+ { agent: "claude-local", path: join(projectRoot, ".claude", "skills") }
224
+ ];
225
+ const out = [];
226
+ for (const t of targets) {
227
+ out.push(checkOneSkillManifest(t.agent, t.path));
228
+ }
229
+ return out;
230
+ }
231
+ async function checkOneSkillManifest(agentLabel, skillsRoot) {
232
+ const manifestPath = join(skillsRoot, ".semantic-memory-skill-manifest.json");
233
+ if (!existsSync(manifestPath)) return null;
234
+ try {
235
+ const raw = await readFile(manifestPath, "utf-8");
236
+ const manifest = JSON.parse(raw);
237
+ if (typeof manifest?.installed_at !== "string") return null;
238
+ const age = Date.now() - new Date(manifest.installed_at).getTime();
239
+ return {
240
+ check: `skill_manifest:${agentLabel}`,
241
+ severity: "ok",
242
+ summary: `${agentLabel} skills installed v${manifest.version} (${Math.round(age / (24 * 60 * 60 * 1e3))}d ago)`
243
+ };
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
248
+ function defaultVaultPath(projectRoot) {
249
+ return join(projectRoot, ".claude", ".vault");
250
+ }
251
+ function formatDriftBanner(result) {
252
+ const drift = filterToDrift(result);
253
+ if (drift.length === 0) return "";
254
+ const lines = [
255
+ `<vault-drift count="${drift.length}">`,
256
+ `\u26A0\uFE0F semantic-memory: ${drift.length} drift issue${drift.length === 1 ? "" : "s"} detected`
257
+ ];
258
+ for (const f of drift.slice(0, 5)) {
259
+ const sev = f.severity === "error" ? "\u2717" : "\u26A0";
260
+ lines.push(` ${sev} ${f.check}: ${f.summary}`);
261
+ }
262
+ if (drift.length > 5) lines.push(` \u2026and ${drift.length - 5} more`);
263
+ lines.push(`Run /healthcheck for details, or /healthcheck --fix to auto-fix safe items.`);
264
+ lines.push(`</vault-drift>`);
265
+ return lines.join("\n");
266
+ }
267
+
268
+ export {
269
+ runHealthcheck,
270
+ filterToDrift,
271
+ formatDriftBanner
272
+ };
273
+ //# sourceMappingURL=chunk-CCSX3BHF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/healthcheck.ts"],"sourcesContent":["import { readFile, writeFile, mkdir, stat } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport { homedir } from \"node:os\";\nimport { lintVault } from \"./lint.js\";\nimport { deriveProjectRoot } from \"./session.js\";\n\nexport type Tier = \"fast\" | \"slow\" | \"all\";\nexport type Severity = \"ok\" | \"warn\" | \"error\";\n\nexport interface DriftFinding {\n check: string;\n severity: Severity;\n summary: string;\n detail?: string;\n fixable_via?: \"skill-link\" | \"mcp-reconcile\" | \"regenerate-contract\" | \"reindex\" | \"none\";\n}\n\nexport interface HealthcheckResult {\n tier_run: Tier;\n ran_at: string;\n duration_ms: number;\n findings: DriftFinding[];\n counts: { ok: number; warn: number; error: number };\n cached?: boolean;\n}\n\nexport interface HealthcheckOptions {\n projectRoot: string;\n /** Vault path. When omitted, derived from projectRoot's .claude/.vault. */\n vaultPath?: string;\n tier?: Tier;\n /** Cache file path. Default: <projectRoot>/.claude/.semantic-memory/healthcheck-cache.json */\n cachePath?: string;\n /** Force re-run even if cache is <5 minutes old. */\n force?: boolean;\n /** Override \"now\" — used by tests. */\n nowMs?: number;\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000;\n\n/**\n * Run the drift-detection healthcheck.\n *\n * - Fast tier: cheap file-system probes (~ms each). Plugin version, skill manifests,\n * .mcp.json registration, hook registration, index freshness, session staleness,\n * AGENTS.md managed-block sha. All independent, run in parallel. Total budget: <100ms\n * on a representative install.\n * - Slow tier: full vault lint. Adds tens of seconds on large vaults — invoked by\n * explicit /healthcheck, never by the SessionStart auto path.\n * - Cache: when force=false and the cache file is <5min old, return the cached result.\n *\n * Fail-open: any individual check that throws produces a `severity: \"warn\"` finding\n * with the error message; never propagates.\n */\nexport async function runHealthcheck(opts: HealthcheckOptions): Promise<HealthcheckResult> {\n const start = (opts.nowMs ?? Date.now());\n const tier: Tier = opts.tier ?? \"fast\";\n const cachePath = opts.cachePath ?? join(opts.projectRoot, \".claude\", \".semantic-memory\", \"healthcheck-cache.json\");\n\n if (!opts.force && existsSync(cachePath)) {\n try {\n const raw = await readFile(cachePath, \"utf-8\");\n const cached = JSON.parse(raw) as HealthcheckResult & { ran_at_ms: number };\n if (cached.tier_run === tier && start - cached.ran_at_ms < CACHE_TTL_MS) {\n return { ...cached, cached: true };\n }\n } catch {\n // ignore corrupt cache; will overwrite below\n }\n }\n\n const findings = tier === \"fast\" ? await runFastTier(opts) : tier === \"slow\" ? await runSlowTier(opts) : [...(await runFastTier(opts)), ...(await runSlowTier(opts))];\n\n const counts = countBySeverity(findings);\n const ranAt = new Date(start).toISOString();\n const result: HealthcheckResult = {\n tier_run: tier,\n ran_at: ranAt,\n duration_ms: Date.now() - start,\n findings,\n counts,\n };\n\n // Persist cache (best-effort; failure is non-fatal).\n try {\n await mkdir(join(opts.projectRoot, \".claude\", \".semantic-memory\"), { recursive: true });\n await writeFile(cachePath, JSON.stringify({ ...result, ran_at_ms: start }, null, 2), \"utf-8\");\n } catch {\n // never let cache write block the result\n }\n\n return result;\n}\n\n/**\n * Surface filter — return findings with severity >= warn. Used by the SessionStart hook\n * to decide whether to emit anything at all.\n */\nexport function filterToDrift(result: HealthcheckResult): DriftFinding[] {\n return result.findings.filter((f) => f.severity !== \"ok\");\n}\n\nfunction countBySeverity(findings: DriftFinding[]): { ok: number; warn: number; error: number } {\n const counts = { ok: 0, warn: 0, error: 0 };\n for (const f of findings) counts[f.severity]++;\n return counts;\n}\n\nasync function runFastTier(opts: HealthcheckOptions): Promise<DriftFinding[]> {\n const checks: Promise<DriftFinding | null>[] = [\n checkPluginVersion(opts.projectRoot),\n checkMcpJsonEntry(opts.projectRoot),\n checkHookRegistration(opts.projectRoot),\n checkIndexFreshness(opts.projectRoot, opts.vaultPath),\n checkSessionStaleness(opts.projectRoot),\n checkAgentsContract(opts.projectRoot),\n ...(await skillManifestChecks(opts.projectRoot)),\n ];\n const results = await Promise.all(checks.map((p) => p.catch((err) => failOpenWarn(err))));\n return results.filter((r): r is DriftFinding => r !== null);\n}\n\nasync function runSlowTier(opts: HealthcheckOptions): Promise<DriftFinding[]> {\n const findings: DriftFinding[] = [];\n if (opts.vaultPath && existsSync(opts.vaultPath)) {\n try {\n const report = await lintVault(opts.vaultPath);\n const total = report.findings.length;\n if (total === 0) {\n findings.push({ check: \"lint_vault\", severity: \"ok\", summary: `vault lint clean (${report.counts.filesLinted} files)` });\n } else {\n findings.push({\n check: \"lint_vault\",\n severity: report.counts.errors > 0 ? \"error\" : \"warn\",\n summary: `vault lint: ${total} findings (errors=${report.counts.errors}, warnings=${report.counts.warnings})`,\n detail: `Run \\`lint_vault\\` for the full report. Many lint findings need human review (stale notes, broken wikilinks).`,\n });\n }\n } catch (err: any) {\n findings.push({ check: \"lint_vault\", severity: \"warn\", summary: `lint_vault threw: ${err?.message ?? err}` });\n }\n }\n return findings;\n}\n\nfunction failOpenWarn(err: unknown): DriftFinding {\n return {\n check: \"internal\",\n severity: \"warn\",\n summary: `healthcheck internal error: ${err instanceof Error ? err.message : String(err)}`,\n };\n}\n\n// --- individual checks ---\n\nasync function checkPluginVersion(projectRoot: string): Promise<DriftFinding | null> {\n const pkgPaths = [\n join(projectRoot, \"node_modules\", \"@theglitchking\", \"semantic-memory\", \"package.json\"),\n join(projectRoot, \"node_modules\", \"@theglitchking\", \"semantic-sidekick\", \"package.json\"),\n ];\n for (const p of pkgPaths) {\n if (!existsSync(p)) continue;\n try {\n const installed = JSON.parse(await readFile(p, \"utf-8\"));\n if (typeof installed?.version === \"string\") {\n return { check: \"plugin_version\", severity: \"ok\", summary: `installed=${installed.version}` };\n }\n } catch {\n // continue to next candidate\n }\n }\n return null; // no installation present — not a drift event for non-npm dev environments\n}\n\nasync function checkMcpJsonEntry(projectRoot: string): Promise<DriftFinding | null> {\n const mcpPath = join(projectRoot, \".mcp.json\");\n if (!existsSync(mcpPath)) return null; // no .mcp.json — fine for dev\n const data = JSON.parse(await readFile(mcpPath, \"utf-8\"));\n const servers = data?.mcpServers ?? {};\n const hasEntry = Object.keys(servers).some((k) => k === \"semantic-vault\" || k === \"semantic-sidekick\" || k === \"semantic-memory\");\n if (!hasEntry) {\n return {\n check: \"mcp_json_entry\",\n severity: \"warn\",\n summary: \".mcp.json present but no semantic-memory/sidekick/vault server entry\",\n fixable_via: \"mcp-reconcile\",\n };\n }\n return { check: \"mcp_json_entry\", severity: \"ok\", summary: \".mcp.json server entry present\" };\n}\n\nasync function checkHookRegistration(projectRoot: string): Promise<DriftFinding | null> {\n const settings = join(projectRoot, \".claude\", \"settings.json\");\n if (!existsSync(settings)) return null;\n try {\n const data = JSON.parse(await readFile(settings, \"utf-8\"));\n const hooks = data?.hooks ?? {};\n const required = [\"SessionStart\", \"UserPromptSubmit\", \"Stop\"];\n const missing: string[] = [];\n for (const event of required) {\n const arr = hooks[event];\n if (!Array.isArray(arr) || arr.length === 0) missing.push(event);\n }\n if (missing.length > 0) {\n return {\n check: \"hook_registration\",\n severity: \"warn\",\n summary: `missing hook registrations: ${missing.join(\", \")}`,\n detail: `Run \\`/relink\\` or reinstall the plugin to refresh hook registrations.`,\n };\n }\n return { check: \"hook_registration\", severity: \"ok\", summary: \"all 3 hook events registered\" };\n } catch (err: any) {\n return failOpenWarn(err);\n }\n}\n\nasync function checkIndexFreshness(projectRoot: string, vaultPath?: string): Promise<DriftFinding | null> {\n const vault = vaultPath ?? defaultVaultPath(projectRoot);\n if (!vault || !existsSync(vault)) return null;\n const indexDir = join(vault, \".semantic-sidekick-index\");\n const meta = join(indexDir, \"meta.json\");\n if (!existsSync(meta)) {\n return {\n check: \"index_freshness\",\n severity: \"warn\",\n summary: \"no index built yet\",\n detail: \"Run `reindex` to build the vector index.\",\n fixable_via: \"reindex\",\n };\n }\n try {\n const data = JSON.parse(await readFile(meta, \"utf-8\"));\n if (typeof data.indexedAt === \"string\") {\n return { check: \"index_freshness\", severity: \"ok\", summary: `last reindex ${data.indexedAt}` };\n }\n } catch {\n // ignore\n }\n return null;\n}\n\nasync function checkSessionStaleness(projectRoot: string): Promise<DriftFinding | null> {\n const sessionPath = join(projectRoot, \".claude\", \".semantic-memory\", \"session.json\");\n if (!existsSync(sessionPath)) return null;\n try {\n const state = JSON.parse(await readFile(sessionPath, \"utf-8\"));\n if (state?.closed_at) return null;\n const last = state?.last_activity_at;\n if (typeof last !== \"string\") return null;\n const ageMs = Date.now() - new Date(last).getTime();\n if (ageMs > 24 * 60 * 60 * 1000) {\n return {\n check: \"session_staleness\",\n severity: \"warn\",\n summary: `session ${state.id} stale (>${Math.round(ageMs / (60 * 60 * 1000))}h since activity)`,\n detail: `Either resume the session (run session_run / session_finish) or remove .claude/.semantic-memory/session.json to abandon it.`,\n };\n }\n return { check: \"session_staleness\", severity: \"ok\", summary: `session ${state.id} active` };\n } catch {\n return null;\n }\n}\n\nasync function checkAgentsContract(projectRoot: string): Promise<DriftFinding | null> {\n const path = join(projectRoot, \"AGENTS.md\");\n if (!existsSync(path)) return null; // optional artifact\n try {\n const raw = await readFile(path, \"utf-8\");\n const begin = raw.indexOf(\"<!-- semantic-memory:begin contract -->\");\n const end = raw.indexOf(\"<!-- semantic-memory:end contract -->\");\n if (begin === -1 || end === -1) {\n return {\n check: \"agents_contract\",\n severity: \"warn\",\n summary: \"AGENTS.md exists but lacks managed-block markers\",\n detail: \"Either move custom content to a Local Notes section, or delete AGENTS.md and run `regenerate_contract` to bootstrap.\",\n fixable_via: \"regenerate-contract\",\n };\n }\n return { check: \"agents_contract\", severity: \"ok\", summary: \"AGENTS.md managed block intact\" };\n } catch {\n return null;\n }\n}\n\nasync function skillManifestChecks(projectRoot: string): Promise<Promise<DriftFinding | null>[]> {\n const home = homedir();\n const targets = [\n { agent: \"claude\", path: join(home, \".claude\", \"skills\") },\n { agent: \"codex\", path: join(home, \".codex\", \"skills\") },\n { agent: \"copilot\", path: join(home, \".copilot\", \"skills\") },\n { agent: \"pi\", path: join(home, \".pi\", \"agent\", \"skills\") },\n { agent: \"claude-local\", path: join(projectRoot, \".claude\", \"skills\") },\n ];\n const out: Promise<DriftFinding | null>[] = [];\n for (const t of targets) {\n out.push(checkOneSkillManifest(t.agent, t.path));\n }\n return out;\n}\n\nasync function checkOneSkillManifest(agentLabel: string, skillsRoot: string): Promise<DriftFinding | null> {\n const manifestPath = join(skillsRoot, \".semantic-memory-skill-manifest.json\");\n if (!existsSync(manifestPath)) return null; // not installed for this agent — fine\n try {\n const raw = await readFile(manifestPath, \"utf-8\");\n const manifest = JSON.parse(raw);\n if (typeof manifest?.installed_at !== \"string\") return null;\n const age = Date.now() - new Date(manifest.installed_at).getTime();\n // Manifest itself is the source of truth; deeper sha-recheck happens via slow tier\n return {\n check: `skill_manifest:${agentLabel}`,\n severity: \"ok\",\n summary: `${agentLabel} skills installed v${manifest.version} (${Math.round(age / (24 * 60 * 60 * 1000))}d ago)`,\n };\n } catch {\n return null;\n }\n}\n\nfunction defaultVaultPath(projectRoot: string): string {\n return join(projectRoot, \".claude\", \".vault\");\n}\n\n/**\n * Format a drift summary for the SessionStart hook. Returns the text to inject as\n * additionalContext, or an empty string when there's nothing to surface.\n */\nexport function formatDriftBanner(result: HealthcheckResult): string {\n const drift = filterToDrift(result);\n if (drift.length === 0) return \"\";\n const lines = [\n `<vault-drift count=\"${drift.length}\">`,\n `⚠️ semantic-memory: ${drift.length} drift issue${drift.length === 1 ? \"\" : \"s\"} detected`,\n ];\n for (const f of drift.slice(0, 5)) {\n const sev = f.severity === \"error\" ? \"✗\" : \"⚠\";\n lines.push(` ${sev} ${f.check}: ${f.summary}`);\n }\n if (drift.length > 5) lines.push(` …and ${drift.length - 5} more`);\n lines.push(`Run /healthcheck for details, or /healthcheck --fix to auto-fix safe items.`);\n lines.push(`</vault-drift>`);\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}