@theglitchking/semantic-memory 1.2.0 → 1.2.2

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.
@@ -6,18 +6,18 @@
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 — all backwards-compatible with v1.0.x via deprecation shims.",
9
- "version": "1.2.0"
9
+ "version": "1.2.2"
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, 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.2.0",
15
+ "version": "1.2.2",
16
16
  "author": {
17
17
  "name": "TheGlitchKing"
18
18
  },
19
- "homepage": "https://github.com/TheGlitchKing/semantic-sidekick",
20
- "repository": "https://github.com/TheGlitchKing/semantic-sidekick.git",
19
+ "homepage": "https://github.com/TheGlitchKing/semantic-memory",
20
+ "repository": "https://github.com/TheGlitchKing/semantic-memory.git",
21
21
  "license": "MIT",
22
22
  "keywords": [
23
23
  "mcp",
@@ -1,14 +1,14 @@
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.0.1",
4
+ "version": "1.2.2",
5
5
  "author": {
6
6
  "name": "TheGlitchKing",
7
7
  "email": "theglitchking@users.noreply.github.com"
8
8
  },
9
9
  "license": "MIT",
10
- "homepage": "https://github.com/TheGlitchKing/semantic-sidekick",
11
- "repository": "https://github.com/TheGlitchKing/semantic-sidekick",
10
+ "homepage": "https://github.com/TheGlitchKing/semantic-memory",
11
+ "repository": "https://github.com/TheGlitchKing/semantic-memory",
12
12
  "keywords": [
13
13
  "mcp",
14
14
  "memory-layer",
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  All notable changes to semantic-memory (formerly semantic-sidekick) will be documented here.
4
4
 
5
+ ## [1.2.2] - 2026-06-27 — Plugin-aware hook-registration check + manifest version sync
6
+
7
+ ### Fixed
8
+
9
+ - **`hook_registration` drift no longer false-positives on plugin-style installs.** The check (both the SessionStart fast path in `hooks/vault-context.js` and the CLI `checkHookRegistration` in `src/core/healthcheck.ts`) only inspected the project's `.claude/settings.json`. When semantic-memory is installed as a Claude Code plugin, its SessionStart/UserPromptSubmit/Stop hooks live in the plugin's own `hooks/hooks.json` (resolved via `CLAUDE_PLUGIN_ROOT`) and fire correctly, so the check warned about "missing" hooks that were in fact registered. Both sites now treat events declared in the plugin's `hooks/hooks.json` as registered. Npm-dependency installs (no `CLAUDE_PLUGIN_ROOT`) are unaffected and still require the hooks in `settings.json`.
10
+ - **Plugin manifest version sync.** `.claude-plugin/plugin.json` was frozen at `1.0.1` while `package.json`, git tags, and `marketplace.json` advanced — Claude Code reads the plugin version from `plugin.json`, so `claude plugin update` always reported "already at latest (1.0.1)" and never pulled newer code. Bumped `plugin.json` to match, and corrected its stale `homepage`/`repository` URLs (they pointed at the old `semantic-sidekick` repo).
11
+
12
+ ## [1.2.1] - 2026-06-11 — Capture-cue false-positive fixes
13
+
14
+ Fixes two classes of false positives in the Stop-hook capture nudge (`hooks/vault-context.js`), where the cue scanner over-fired and asked to synthesize moments that carried no new knowledge.
15
+
16
+ ### Fixed
17
+
18
+ - **Conversational "gotcha" no longer fires the cue.** The `gotcha` cue was a bare-word match (`\bgotcha\b`), so filler acknowledgments like "Gotcha, ok…" tripped it. It now requires noun-context (`a/the gotcha`, `gotcha:`, `gotchas`, `gotcha is/was/here/with`). `workaround`/`hack` are unchanged.
19
+ - **Quoted machinery no longer re-primes the cue.** Cue detection ran over the entire raw prompt, so pasting tool output back — especially this hook's own `<vault-capture-prompt>` block, which contains the words "gotcha"/"workaround"/"hack" and the cue regexes themselves — re-primed `capture-pending` in a self-referential loop. New `stripQuotedMachinery()` removes self-emitted `<vault-*>` blocks, fenced code, and inline-code spans before matching, so only the user's own prose is scanned.
20
+
21
+ ### Tests
22
+
23
+ - Extended `test/phase4/mode-hook.test.ts` with four regression cases: conversational "gotcha" does not prime; noun-context "the gotcha is…" does; pasting a `<vault-capture-prompt>` block does not re-prime; and a real cue alongside fenced machinery still primes (the strip is surgical, not total).
24
+
5
25
  ## [1.2.0] - 2026-05-09 — State consolidation under .claude/.semantic-memory/
6
26
 
7
27
  Consolidates every transient state file under one namespace. Renames the legacy `.sidekick-*` files to drop the obsolete prefix. All legacy paths remain readable through v1.x via fallback; v2.0 will remove the fallback.
@@ -4,7 +4,7 @@ import {
4
4
 
5
5
  // src/core/healthcheck.ts
6
6
  import { readFile, writeFile, mkdir } from "fs/promises";
7
- import { existsSync } from "fs";
7
+ import { existsSync, readFileSync } from "fs";
8
8
  import { join } from "path";
9
9
  import { homedir } from "os";
10
10
  var CACHE_TTL_MS = 5 * 60 * 1e3;
@@ -123,6 +123,19 @@ async function checkMcpJsonEntry(projectRoot) {
123
123
  }
124
124
  return { check: "mcp_json_entry", severity: "ok", summary: ".mcp.json server entry present" };
125
125
  }
126
+ function pluginRegisteredHookEvents() {
127
+ try {
128
+ const root = process.env.CLAUDE_PLUGIN_ROOT;
129
+ if (!root) return [];
130
+ const hooksPath = join(root, "hooks", "hooks.json");
131
+ if (!existsSync(hooksPath)) return [];
132
+ const data = JSON.parse(readFileSync(hooksPath, "utf-8"));
133
+ const hooks = data?.hooks ?? {};
134
+ return Object.keys(hooks).filter((e) => Array.isArray(hooks[e]) && hooks[e].length > 0);
135
+ } catch {
136
+ return [];
137
+ }
138
+ }
126
139
  async function checkHookRegistration(projectRoot) {
127
140
  const settings = join(projectRoot, ".claude", "settings.json");
128
141
  if (!existsSync(settings)) return null;
@@ -130,10 +143,12 @@ async function checkHookRegistration(projectRoot) {
130
143
  const data = JSON.parse(await readFile(settings, "utf-8"));
131
144
  const hooks = data?.hooks ?? {};
132
145
  const required = ["SessionStart", "UserPromptSubmit", "Stop"];
146
+ const pluginEvents = pluginRegisteredHookEvents();
133
147
  const missing = [];
134
148
  for (const event of required) {
135
149
  const arr = hooks[event];
136
- if (!Array.isArray(arr) || arr.length === 0) missing.push(event);
150
+ const inSettings = Array.isArray(arr) && arr.length > 0;
151
+ if (!inSettings && !pluginEvents.includes(event)) missing.push(event);
137
152
  }
138
153
  if (missing.length > 0) {
139
154
  return {
@@ -294,4 +309,4 @@ export {
294
309
  filterToDrift,
295
310
  formatDriftBanner
296
311
  };
297
- //# sourceMappingURL=chunk-EVAJGOCB.js.map
312
+ //# sourceMappingURL=chunk-UQSASB7Q.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, readFileSync } 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 checkLegacyStateFiles(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\n// Events the plugin registers through its own hooks/hooks.json (resolved via\n// CLAUDE_PLUGIN_ROOT). On a plugin-style install these live in the plugin, not\n// the project's .claude/settings.json, so they must count as registered.\nfunction pluginRegisteredHookEvents(): string[] {\n try {\n const root = process.env.CLAUDE_PLUGIN_ROOT;\n if (!root) return [];\n const hooksPath = join(root, \"hooks\", \"hooks.json\");\n if (!existsSync(hooksPath)) return [];\n const data = JSON.parse(readFileSync(hooksPath, \"utf-8\"));\n const hooks = data?.hooks ?? {};\n return Object.keys(hooks).filter((e) => Array.isArray(hooks[e]) && hooks[e].length > 0);\n } catch {\n return [];\n }\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 pluginEvents = pluginRegisteredHookEvents();\n const missing: string[] = [];\n for (const event of required) {\n const arr = hooks[event];\n const inSettings = Array.isArray(arr) && arr.length > 0;\n if (!inSettings && !pluginEvents.includes(event)) 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 checkLegacyStateFiles(projectRoot: string): Promise<DriftFinding | null> {\n // Detect v1.1-and-earlier state files at .claude/.sidekick-*. v1.2 moved these to\n // .claude/.semantic-memory/. The hook still reads them via fallback for backwards-\n // compat, but they should be migrated explicitly so the legacy paths stop existing.\n const legacy = [\n \".sidekick-mode\",\n \".sidekick-fingerprints.json\",\n \".sidekick-capture-pending.json\",\n ];\n const present: string[] = [];\n for (const name of legacy) {\n if (existsSync(join(projectRoot, \".claude\", name))) {\n present.push(name);\n }\n }\n if (present.length === 0) {\n return { check: \"legacy_state_files\", severity: \"ok\", summary: \"no legacy state files\" };\n }\n return {\n check: \"legacy_state_files\",\n severity: \"warn\",\n summary: `${present.length} legacy state file${present.length === 1 ? \"\" : \"s\"} at .claude/.sidekick-*`,\n detail:\n `Files: ${present.join(\", \")}. Run \\`bin/semantic-memory migrate-state\\` to move them under .claude/.semantic-memory/. ` +\n `These files are still being read for backwards-compat through v1.x; the legacy fallback is removed in v2.0.`,\n fixable_via: \"none\",\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":[]}
package/dist/cli/index.js CHANGED
@@ -778,7 +778,7 @@ ${r2.stderr?.toString() || ""}`);
778
778
  });
779
779
  var driftHealthcheckActionPostprocess = async (opts) => {
780
780
  try {
781
- const { runHealthcheck, formatDriftBanner } = await import("../healthcheck-7YOPNDXN.js");
781
+ const { runHealthcheck, formatDriftBanner } = await import("../healthcheck-E4GI5RMZ.js");
782
782
  const projectRoot = process.cwd();
783
783
  const vaultCandidate = join3(projectRoot, ".claude", ".vault");
784
784
  const vaultPath = existsSync3(vaultCandidate) ? vaultCandidate : void 0;
@@ -2,7 +2,7 @@ import {
2
2
  filterToDrift,
3
3
  formatDriftBanner,
4
4
  runHealthcheck
5
- } from "../chunk-EVAJGOCB.js";
5
+ } from "../chunk-UQSASB7Q.js";
6
6
  import {
7
7
  FrontmatterManager,
8
8
  INDEX_FILE,
@@ -2,7 +2,7 @@ import {
2
2
  filterToDrift,
3
3
  formatDriftBanner,
4
4
  runHealthcheck
5
- } from "./chunk-EVAJGOCB.js";
5
+ } from "./chunk-UQSASB7Q.js";
6
6
  import "./chunk-CAN2GWUG.js";
7
7
  import "./chunk-E2Z4J4MK.js";
8
8
  import "./chunk-PZ5AY32C.js";
@@ -11,4 +11,4 @@ export {
11
11
  formatDriftBanner,
12
12
  runHealthcheck
13
13
  };
14
- //# sourceMappingURL=healthcheck-7YOPNDXN.js.map
14
+ //# sourceMappingURL=healthcheck-E4GI5RMZ.js.map
@@ -267,13 +267,29 @@ const CAPTURE_CUES = [
267
267
  /\bbecause\b/i,
268
268
  /\bdecided\b|\bwe chose\b|\bthe decision\b/i,
269
269
  /\bturned out to be\b|\bthe bug was\b|\bthe fix was\b/i,
270
- /\bgotcha\b|\bworkaround\b|\bhack\b/i,
270
+ // "gotcha" is also a common acknowledgment ("Gotcha, ok…") — require noun-context
271
+ // (a/the gotcha, gotcha:, gotchas, gotcha is/was/here/with) so bare filler doesn't misfire.
272
+ /\b(?:a|the|one|another)\s+gotcha\b|\bgotcha[:s]\b|\bgotcha (?:is|was|here|with)\b|\bworkaround\b|\bhack\b/i,
271
273
  /\bnew convention\b|\bfrom now on\b|\bgoing forward\b/i,
272
274
  ];
273
275
 
276
+ // Cue detection must scan the user's own prose, NOT quoted machinery. Pasting
277
+ // tool output back into the prompt — especially this hook's own <vault-*> blocks,
278
+ // which literally contain the words "gotcha"/"workaround"/"hack" and the cue
279
+ // regexes themselves — would otherwise re-prime capture-pending in a self-
280
+ // referential loop. Strip self-emitted blocks, fenced code, and inline-code spans
281
+ // before matching. (Unterminated blocks/fences from truncated pastes strip to EOL.)
282
+ function stripQuotedMachinery(prompt) {
283
+ return prompt
284
+ .replace(/<vault-[a-z-]+[\s\S]*?(?:<\/vault-[a-z-]+>|$)/gi, " ")
285
+ .replace(/```[\s\S]*?(?:```|$)/g, " ")
286
+ .replace(/`[^`]*`/g, " ");
287
+ }
288
+
274
289
  function detectCaptureCue(prompt) {
290
+ const prose = stripQuotedMachinery(prompt);
275
291
  for (const re of CAPTURE_CUES) {
276
- if (re.test(prompt)) return re.source;
292
+ if (re.test(prose)) return re.source;
277
293
  }
278
294
  return null;
279
295
  }
@@ -390,6 +406,25 @@ async function handleSessionStart(projectRoot, vaultPath, cliBin) {
390
406
  * Heavy work (full vault lint, code-symbol drift) is reserved for the manual
391
407
  * `/healthcheck` command; this auto path stays cheap.
392
408
  */
409
+
410
+ // Events the plugin registers through its own hooks/hooks.json (resolved via
411
+ // CLAUDE_PLUGIN_ROOT). On a plugin-style install these live in the plugin, not
412
+ // the project's .claude/settings.json, so they must count as registered.
413
+ function pluginRegisteredHookEvents() {
414
+ try {
415
+ const root = process.env.CLAUDE_PLUGIN_ROOT;
416
+ if (!root) return [];
417
+ const hooksPath = join(root, "hooks", "hooks.json");
418
+ if (!existsSync(hooksPath)) return [];
419
+ const data = JSON.parse(readFileSync(hooksPath, "utf8"));
420
+ const hooks = (data && data.hooks) || {};
421
+ return Object.keys(hooks).filter((e) => Array.isArray(hooks[e]) && hooks[e].length > 0);
422
+ } catch (e) {
423
+ debug(`pluginRegisteredHookEvents threw: ${e.message}`);
424
+ return [];
425
+ }
426
+ }
427
+
393
428
  async function fastDriftCheck(projectRoot) {
394
429
  const findings = [];
395
430
  // .mcp.json
@@ -410,7 +445,13 @@ async function fastDriftCheck(projectRoot) {
410
445
  const data = JSON.parse(readFileSync(settings, "utf8"));
411
446
  const hooks = (data && data.hooks) || {};
412
447
  const required = ["SessionStart", "UserPromptSubmit", "Stop"];
413
- const missing = required.filter((e) => !Array.isArray(hooks[e]) || hooks[e].length === 0);
448
+ // Plugin-style installs register these via the plugin's own hooks/hooks.json
449
+ // (resolved through CLAUDE_PLUGIN_ROOT), not the project settings.json. Count
450
+ // those as registered so the check doesn't false-positive on plugin installs.
451
+ const pluginEvents = pluginRegisteredHookEvents();
452
+ const missing = required.filter(
453
+ (e) => (!Array.isArray(hooks[e]) || hooks[e].length === 0) && !pluginEvents.includes(e)
454
+ );
414
455
  if (missing.length > 0) findings.push({ check: "hook_registration", summary: `missing hook events: ${missing.join(", ")}` });
415
456
  }
416
457
  } catch (e) { debug(`hook check threw: ${e.message}`); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theglitchking/semantic-memory",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "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 @theglitchking/semantic-sidekick at v1.0 — see CHANGELOG for migration.",
5
5
  "type": "module",
6
6
  "main": "./dist/core/index.js",
@@ -1 +0,0 @@
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 checkLegacyStateFiles(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 checkLegacyStateFiles(projectRoot: string): Promise<DriftFinding | null> {\n // Detect v1.1-and-earlier state files at .claude/.sidekick-*. v1.2 moved these to\n // .claude/.semantic-memory/. The hook still reads them via fallback for backwards-\n // compat, but they should be migrated explicitly so the legacy paths stop existing.\n const legacy = [\n \".sidekick-mode\",\n \".sidekick-fingerprints.json\",\n \".sidekick-capture-pending.json\",\n ];\n const present: string[] = [];\n for (const name of legacy) {\n if (existsSync(join(projectRoot, \".claude\", name))) {\n present.push(name);\n }\n }\n if (present.length === 0) {\n return { check: \"legacy_state_files\", severity: \"ok\", summary: \"no legacy state files\" };\n }\n return {\n check: \"legacy_state_files\",\n severity: \"warn\",\n summary: `${present.length} legacy state file${present.length === 1 ? \"\" : \"s\"} at .claude/.sidekick-*`,\n detail:\n `Files: ${present.join(\", \")}. Run \\`bin/semantic-memory migrate-state\\` to move them under .claude/.semantic-memory/. ` +\n `These files are still being read for backwards-compat through v1.x; the legacy fallback is removed in v2.0.`,\n fixable_via: \"none\",\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":[]}