@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,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Smart-middle corpus activation (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: Document the first-run heuristic semantic-memory uses to auto-detect which corpora to enable. Designed to give zero-friction onboarding for solo users and full-stack visibility for teams.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Smart-middle activation
|
|
16
|
+
|
|
17
|
+
> **Status: planned for 2.0.0b.** This document describes the auto-detection heuristic that runs the first time `semantic-memory` is invoked in a project (i.e., when `<project-root>/.semantic/corpora.json` doesn't exist). The implementation lands with the multi-corpus refactor.
|
|
18
|
+
|
|
19
|
+
## The three approaches considered
|
|
20
|
+
|
|
21
|
+
When this design was being scoped, three default-corpora strategies were on the table:
|
|
22
|
+
|
|
23
|
+
1. **All-on**: enable all six corpora (vault, code, plans, docs, research, project-map) by default. Heavy footprint; lots of empty `no_index` returns for projects that don't have all six markers.
|
|
24
|
+
2. **Vault-only**: enable just `vault` by default; require explicit opt-in for everything else. Lightest footprint; requires manual config for any non-trivial project.
|
|
25
|
+
3. **Smart-middle**: scan the project for known markers (`.planning/`, `src/`, etc.) and auto-enable corresponding corpora. Friction-free onboarding without paying for empty corpora.
|
|
26
|
+
|
|
27
|
+
The user picked smart-middle. This doc specifies the resolution.
|
|
28
|
+
|
|
29
|
+
## Marker → corpus mapping
|
|
30
|
+
|
|
31
|
+
| Marker (file or directory) | Activates | Default `root` | Default `chunker` |
|
|
32
|
+
|---|---|---|---|
|
|
33
|
+
| `.claude/.vault/` | `vault` | `./.claude/.vault` | `markdown` |
|
|
34
|
+
| `.planning/.meta/workspace.json` with `mode: "lg"` | `plans` | `./.planning` | `markdown` |
|
|
35
|
+
| `.planning/` (without lg-mode workspace.json) | (skipped — sm-mode plans aren't multi-corpus material) | — | — |
|
|
36
|
+
| `.documentation/` | `docs` | `./.documentation` | `markdown` |
|
|
37
|
+
| `.research/` | `research` | `./.research` | `markdown` |
|
|
38
|
+
| `.babel-fish/01-vocabulary.md` | `project-map` (with glossary extractor enabled) | `./.babel-fish` | `markdown` |
|
|
39
|
+
| Detected source dir with TS/JS files | `code` | `<detected>` | `tree-sitter-ts` |
|
|
40
|
+
|
|
41
|
+
### Source-dir detection for `code`
|
|
42
|
+
|
|
43
|
+
The `code` corpus is the only one that requires content sniffing rather than a single marker. The detection logic:
|
|
44
|
+
|
|
45
|
+
1. If `package.json` exists at project root: read `main` / `module` / `exports` to find canonical source paths
|
|
46
|
+
2. If `tsconfig.json` exists: read `compilerOptions.rootDir` and `include`
|
|
47
|
+
3. If `src/` directory exists at project root with at least one `.ts` / `.tsx` / `.js` / `.jsx` file: use `src/`
|
|
48
|
+
4. If multiple candidates: pick the one with the most TS/JS files; tie-break by depth (shallower wins)
|
|
49
|
+
5. If none found: skip the `code` corpus (don't activate)
|
|
50
|
+
|
|
51
|
+
When activated, the default `glob` is `**/*.{ts,tsx,js,jsx}` (Python deferred to a future minor; see Phase 3.2.0 in the meta-plan).
|
|
52
|
+
|
|
53
|
+
## Decision banner
|
|
54
|
+
|
|
55
|
+
After scanning, semantic-memory prints a decision banner to stderr explaining what it activated and why:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
[semantic-memory] First-run corpus activation:
|
|
59
|
+
[semantic-memory] ✓ vault from .claude/.vault/ (markdown chunker)
|
|
60
|
+
[semantic-memory] ✓ plans from .planning/ (markdown chunker, lg-mode workspace detected)
|
|
61
|
+
[semantic-memory] ✓ code from src/ (tree-sitter-ts chunker, 47 files)
|
|
62
|
+
[semantic-memory] - docs (no .documentation/ found)
|
|
63
|
+
[semantic-memory] - research (no .research/ found)
|
|
64
|
+
[semantic-memory] - project-map (no .babel-fish/01-vocabulary.md found)
|
|
65
|
+
[semantic-memory]
|
|
66
|
+
[semantic-memory] Generated .semantic/corpora.json. Edit to override.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Banner intent: NO MAGIC. The user sees what got turned on, what didn't, and why. Editing `corpora.json` directly is always the override path.
|
|
70
|
+
|
|
71
|
+
## Override and force-rerun
|
|
72
|
+
|
|
73
|
+
After the first run, `corpora.json` is the source of truth. To re-run smart-middle activation (after adding new markers, for example), delete `<project-root>/.semantic/corpora.json` and re-invoke semantic-memory.
|
|
74
|
+
|
|
75
|
+
A `--re-detect` CLI flag may be added in a later minor for the case where the user wants to merge new markers into an existing `corpora.json` without losing manual customizations. (TBD — design open.)
|
|
76
|
+
|
|
77
|
+
## Why this design
|
|
78
|
+
|
|
79
|
+
- **Solo devs win**: a fresh project with just `.claude/.vault/` gets the legacy semantic-sidekick experience. No new config to learn.
|
|
80
|
+
- **Teams win**: a project with `.planning/` (lg mode) + `src/` + `.babel-fish/` gets all three corpora wired automatically — no need to read a multi-page config doc to get value out of the new architecture.
|
|
81
|
+
- **Banner is the trust mechanism**: users see what got auto-activated and can disable corpora they don't want by editing `corpora.json`. The decision is never invisible.
|
|
82
|
+
- **Backwards compatibility is free**: projects without ANY of the markers fall back to legacy single-vault behavior (which is what semantic-sidekick 0.2.5 does today). No `corpora.json` is even written in that case.
|
|
83
|
+
|
|
84
|
+
## Inter-corpus interactions (when multiple corpora are active)
|
|
85
|
+
|
|
86
|
+
- **Unified knowledge graph**: nodes from all active corpora share one graph. A `related_docs:` edge in a vault note can resolve to a code symbol in the `code` corpus.
|
|
87
|
+
- **Cross-corpus search**: `search_all` queries all enabled corpora and merges results.
|
|
88
|
+
- **Drift detection**: registered iff `code` + at least one doc-bearing corpus (vault, plans, docs) are both active.
|
|
89
|
+
- **Translation**: registered iff `project-map` corpus is active AND its `glossary.json` was extracted successfully.
|
|
90
|
+
|
|
91
|
+
These behaviors are detailed in `docs/corpora-json.md` (the schema reference).
|
|
92
|
+
|
|
93
|
+
## See also
|
|
94
|
+
|
|
95
|
+
- `docs/corpora-json.md` — `corpora.json` schema reference
|
|
96
|
+
- `docs/regression-snapshot.md` — the gate that protects existing single-vault behavior during the refactor
|
|
97
|
+
- `~/workspace/the-glitch-kingdom/persistent-planning/.planning/layered-planning-with-mcp-and-hewtd-frontmatter/task_plan.md` — meta-plan; smart-middle activation is decision C3
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "semantic-sidekick activation + mode hooks. SessionStart reconciles .mcp.json + resets mode + preloads vault context. UserPromptSubmit injects vault search results (mode-aware, fingerprinted). Stop emits mode-specific transition-capture prompts (research→synthesis, outage→postmortem, vault-first→generic capture).",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.js\""
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "command",
|
|
13
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/vault-context.js\""
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"UserPromptSubmit": [
|
|
19
|
+
{
|
|
20
|
+
"hooks": [
|
|
21
|
+
{
|
|
22
|
+
"type": "command",
|
|
23
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/vault-context.js\""
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"Stop": [
|
|
29
|
+
{
|
|
30
|
+
"hooks": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/vault-context.js\""
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// Plugin-specific SessionStart reconciliation for semantic-sidekick.
|
|
2
|
+
//
|
|
3
|
+
// Responsibilities (all conservative — the user's config always wins):
|
|
4
|
+
// 1. Ensure <project>/.claude/.vault exists.
|
|
5
|
+
// 2. If .mcp.json is missing a "semantic-vault" entry AND the package is
|
|
6
|
+
// installed locally, add one in the node-against-node_modules form.
|
|
7
|
+
// Existing entries (any shape) are preserved — if you want to rewrite
|
|
8
|
+
// a stale entry, run `npx --no @theglitchking/semantic-sidekick
|
|
9
|
+
// normalize-config`.
|
|
10
|
+
// 3. If hit-em-with-the-docs is enabled AND ./.documentation exists,
|
|
11
|
+
// conditionally add a read-only "semantic-sidekick" entry pointed at
|
|
12
|
+
// ./.documentation (same preserve-existing rule). Remove it ONLY if
|
|
13
|
+
// the existing entry matches a shape we recognize as ours.
|
|
14
|
+
//
|
|
15
|
+
// Any write is preceded by a backup to .mcp.json.bak. Never writes the
|
|
16
|
+
// `npx @latest` form — that shape is fragile (npx cache corruption causes
|
|
17
|
+
// ERR_MODULE_NOT_FOUND) and was the root cause of the 0.10.0 bugfix.
|
|
18
|
+
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { dirname, join, relative } from "node:path";
|
|
22
|
+
|
|
23
|
+
const PKG = "@theglitchking/semantic-sidekick";
|
|
24
|
+
const VAULT_KEY = "semantic-vault";
|
|
25
|
+
const DOCS_KEY = "semantic-sidekick";
|
|
26
|
+
|
|
27
|
+
function readJson(path, fallback = null) {
|
|
28
|
+
try {
|
|
29
|
+
const raw = readFileSync(path, "utf8");
|
|
30
|
+
return raw.trim() ? JSON.parse(raw) : fallback;
|
|
31
|
+
} catch {
|
|
32
|
+
return fallback;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function hewtdEnabled(projectRoot) {
|
|
37
|
+
// Check user-level AND project-level settings. Plugin enablement is per-scope,
|
|
38
|
+
// and hewtd might be enabled at either. Project-scope was previously ignored,
|
|
39
|
+
// which caused the docs MCP to get silently removed by the reconcile pass on
|
|
40
|
+
// every SessionStart for users who'd enabled hewtd only per-project.
|
|
41
|
+
const candidates = [
|
|
42
|
+
join(homedir(), ".claude", "settings.json"),
|
|
43
|
+
join(projectRoot, ".claude", "settings.json"),
|
|
44
|
+
join(projectRoot, ".claude", "settings.local.json"),
|
|
45
|
+
];
|
|
46
|
+
for (const path of candidates) {
|
|
47
|
+
const settings = readJson(path);
|
|
48
|
+
const enabled = settings?.enabledPlugins || {};
|
|
49
|
+
for (const key of Object.keys(enabled)) {
|
|
50
|
+
if (key.startsWith("hit-em-with-the-docs@") && enabled[key] === true) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Absolute path to the locally-installed bin script, or null. */
|
|
59
|
+
export function findLocalBin(projectRoot) {
|
|
60
|
+
const p = join(projectRoot, "node_modules", "@theglitchking", "semantic-sidekick", "bin", "semantic-sidekick");
|
|
61
|
+
return existsSync(p) ? p : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Returns the arg form that goes into .mcp.json for this project. */
|
|
65
|
+
function binArg(projectRoot) {
|
|
66
|
+
const abs = findLocalBin(projectRoot);
|
|
67
|
+
if (!abs) return null;
|
|
68
|
+
const rel = relative(projectRoot, abs);
|
|
69
|
+
// Always use ./-prefixed relative form so it's machine-portable within the repo.
|
|
70
|
+
return rel.startsWith("..") ? abs : `./${rel}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildEntry(binArgStr, notesPath, extraArgs = []) {
|
|
74
|
+
return {
|
|
75
|
+
type: "stdio",
|
|
76
|
+
command: "node",
|
|
77
|
+
args: [binArgStr, "--notes", notesPath, ...extraArgs],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Detect an entry as "one of ours" so we can safely remove it when the
|
|
83
|
+
* condition that created it no longer holds. Matches both shapes we've ever
|
|
84
|
+
* written: the legacy npx-form and the new node-against-node_modules form.
|
|
85
|
+
*/
|
|
86
|
+
function isOurEntry(entry, notesPath) {
|
|
87
|
+
if (!entry || typeof entry !== "object") return false;
|
|
88
|
+
const cmd = entry.command;
|
|
89
|
+
const args = Array.isArray(entry.args) ? entry.args : [];
|
|
90
|
+
const argStr = args.map((a) => String(a)).join(" ");
|
|
91
|
+
|
|
92
|
+
// Legacy npx form we shipped in 0.8.0 and prior SessionStart hooks.
|
|
93
|
+
if (cmd === "npx" && argStr.includes(PKG) && argStr.includes(notesPath)) return true;
|
|
94
|
+
// Current node-against-node_modules form (0.10.0+).
|
|
95
|
+
if (
|
|
96
|
+
cmd === "node" &&
|
|
97
|
+
args.some((a) => typeof a === "string" && a.includes("node_modules/@theglitchking/semantic-sidekick")) &&
|
|
98
|
+
argStr.includes(notesPath)
|
|
99
|
+
) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function backup(mcpPath) {
|
|
106
|
+
try {
|
|
107
|
+
if (existsSync(mcpPath)) {
|
|
108
|
+
writeFileSync(join(dirname(mcpPath), ".mcp.json.bak"), readFileSync(mcpPath, "utf8"));
|
|
109
|
+
}
|
|
110
|
+
} catch {}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function reconcile(projectRoot) {
|
|
114
|
+
const vaultDir = join(projectRoot, ".claude", ".vault");
|
|
115
|
+
const docsDir = join(projectRoot, ".documentation");
|
|
116
|
+
const mcpPath = join(projectRoot, ".mcp.json");
|
|
117
|
+
|
|
118
|
+
try { mkdirSync(vaultDir, { recursive: true }); } catch {}
|
|
119
|
+
|
|
120
|
+
const docsWired = hewtdEnabled(projectRoot) && existsSync(docsDir);
|
|
121
|
+
|
|
122
|
+
let data = { mcpServers: {} };
|
|
123
|
+
if (existsSync(mcpPath)) {
|
|
124
|
+
const parsed = readJson(mcpPath, null);
|
|
125
|
+
if (parsed === null) {
|
|
126
|
+
let raw = "";
|
|
127
|
+
try { raw = readFileSync(mcpPath, "utf8"); } catch {}
|
|
128
|
+
if (raw.trim()) {
|
|
129
|
+
process.stderr.write(`semantic-sidekick hook: could not parse ${mcpPath}; leaving untouched\n`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
} else if (parsed && typeof parsed === "object") {
|
|
133
|
+
data = parsed;
|
|
134
|
+
if (!data.mcpServers || typeof data.mcpServers !== "object") {
|
|
135
|
+
data.mcpServers = {};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const before = JSON.stringify(data);
|
|
141
|
+
|
|
142
|
+
const bin = binArg(projectRoot);
|
|
143
|
+
|
|
144
|
+
// VAULT entry: create if missing AND we have a local install. Never
|
|
145
|
+
// overwrite an existing entry (even if it looks "wrong" — the user
|
|
146
|
+
// controls their .mcp.json).
|
|
147
|
+
if (!data.mcpServers[VAULT_KEY]) {
|
|
148
|
+
if (bin) {
|
|
149
|
+
data.mcpServers[VAULT_KEY] = buildEntry(bin, "./.claude/.vault");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// DOCS entry: same preserve-existing rule. Conditional on hewtd +
|
|
154
|
+
// .documentation. When removing, only remove shapes we recognize as ours.
|
|
155
|
+
if (docsWired) {
|
|
156
|
+
if (!data.mcpServers[DOCS_KEY]) {
|
|
157
|
+
if (bin) {
|
|
158
|
+
data.mcpServers[DOCS_KEY] = buildEntry(bin, "./.documentation", ["--read-only"]);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} else if (data.mcpServers[DOCS_KEY] && isOurEntry(data.mcpServers[DOCS_KEY], ".documentation")) {
|
|
162
|
+
delete data.mcpServers[DOCS_KEY];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const after = JSON.stringify(data);
|
|
166
|
+
if (after === before) return;
|
|
167
|
+
|
|
168
|
+
backup(mcpPath);
|
|
169
|
+
mkdirSync(dirname(mcpPath), { recursive: true });
|
|
170
|
+
writeFileSync(mcpPath, JSON.stringify(data, null, 2) + "\n");
|
|
171
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// semantic-sidekick SessionStart hook.
|
|
3
|
+
//
|
|
4
|
+
// Critical path (zero external deps): run reconcile.js to wire .mcp.json +
|
|
5
|
+
// create .claude/.vault. This ALWAYS runs, even if the plugin is installed
|
|
6
|
+
// in a location where @theglitchking/claude-plugin-runtime isn't resolvable
|
|
7
|
+
// (which happens when Claude Code extracts the plugin tarball without running
|
|
8
|
+
// npm install afterward — the default for `/plugin install`).
|
|
9
|
+
//
|
|
10
|
+
// Optional: if claude-plugin-runtime IS resolvable, delegate to
|
|
11
|
+
// runSessionStart which also does policy-gated auto-update checks. Otherwise
|
|
12
|
+
// just emit the minimum-valid SessionStart response.
|
|
13
|
+
//
|
|
14
|
+
// Either way, this hook MUST NOT throw — a broken SessionStart hook is worse
|
|
15
|
+
// than an idle one.
|
|
16
|
+
|
|
17
|
+
import { reconcile } from "./reconcile.js";
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
21
|
+
|
|
22
|
+
// 1. Reconcile — no-deps, always runs.
|
|
23
|
+
try {
|
|
24
|
+
process.chdir(projectRoot);
|
|
25
|
+
} catch {
|
|
26
|
+
// If we can't chdir, emit an empty response and bail. Don't let this kill
|
|
27
|
+
// the session.
|
|
28
|
+
emit();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await reconcile(projectRoot);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
process.stderr.write(`[semantic-sidekick] reconcile failed: ${err?.message || err}\n`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Optional runtime-backed update check. Skip silently if the runtime
|
|
39
|
+
// isn't installed in this plugin's extraction dir (the common case when
|
|
40
|
+
// installed via /plugin install without a subsequent npm install).
|
|
41
|
+
try {
|
|
42
|
+
const mod = await import("@theglitchking/claude-plugin-runtime");
|
|
43
|
+
if (typeof mod.runSessionStart === "function") {
|
|
44
|
+
// runSessionStart emits its own response. It also re-runs reconcile —
|
|
45
|
+
// that's idempotent (reconcile writes only what's missing), so the
|
|
46
|
+
// double-invocation is harmless. We return early to avoid double-emit.
|
|
47
|
+
await mod.runSessionStart({
|
|
48
|
+
packageName: "@theglitchking/semantic-sidekick",
|
|
49
|
+
pluginName: "semantic-sidekick",
|
|
50
|
+
configFile: "semantic-sidekick.json",
|
|
51
|
+
reconcile,
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
// ERR_MODULE_NOT_FOUND is the expected case when the runtime isn't
|
|
57
|
+
// resolvable. Any other error gets logged but doesn't block the session.
|
|
58
|
+
const msg = String(err?.message || err);
|
|
59
|
+
if (!msg.includes("Cannot find package") && !msg.includes("ERR_MODULE_NOT_FOUND")) {
|
|
60
|
+
process.stderr.write(`[semantic-sidekick] runtime import failed: ${msg}\n`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Fallback emit — minimum-valid SessionStart response with empty context.
|
|
65
|
+
// vault-context.js (the other SessionStart hook) provides the actual preload.
|
|
66
|
+
emit();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function emit(additionalContext) {
|
|
70
|
+
const out = { hookSpecificOutput: { hookEventName: "SessionStart" } };
|
|
71
|
+
if (additionalContext && String(additionalContext).trim()) {
|
|
72
|
+
out.hookSpecificOutput.additionalContext = String(additionalContext);
|
|
73
|
+
}
|
|
74
|
+
process.stdout.write(JSON.stringify(out) + "\n");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main().catch((err) => {
|
|
78
|
+
process.stderr.write(`[semantic-sidekick] session-start.js fatal: ${err?.message || err}\n`);
|
|
79
|
+
// Still emit — never block the session.
|
|
80
|
+
emit();
|
|
81
|
+
});
|