claude-alfred 0.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.
- package/README.ja.md +283 -0
- package/README.md +283 -0
- package/content/hooks/hooks.json +41 -0
- package/content/mcp/.mcp.json +12 -0
- package/dist/audit-DujZ6YAy.mjs +18 -0
- package/dist/cli.mjs +509 -0
- package/dist/dispatcher-BzOdcjaa.mjs +93 -0
- package/dist/embedder-BshPIMrW.mjs +215 -0
- package/dist/epic-CdRKNGvP.mjs +227 -0
- package/dist/fts-BDdUbNfM.mjs +195 -0
- package/dist/helpers-BsdW4kgn.mjs +94 -0
- package/dist/knowledge-CCCixwb8.mjs +156 -0
- package/dist/post-tool-qemgso2b.mjs +88 -0
- package/dist/postinstall.mjs +49 -0
- package/dist/pre-compact-Cmg9kprV.mjs +181 -0
- package/dist/project-CpgK3fwQ.mjs +79 -0
- package/dist/schema-CcIFwr_0.mjs +289 -0
- package/dist/server-DF7CXxKi.mjs +2635 -0
- package/dist/server-Dsf47Pd4.mjs +19220 -0
- package/dist/session-start-DUYF6E0V.mjs +209 -0
- package/dist/store-Clcihees.mjs +338 -0
- package/dist/types-C3butmI8.mjs +6823 -0
- package/dist/user-prompt-BDeST0mR.mjs +144 -0
- package/dist/vectors-DvuAqDeO.mjs +83 -0
- package/package.json +46 -0
- package/web/dist/assets/activity-UyW12k7Z.js +1 -0
- package/web/dist/assets/api-BI8AW-mC.js +1 -0
- package/web/dist/assets/dist-BHj_gZG8.js +1 -0
- package/web/dist/assets/dist-DDZSXOC-.js +1 -0
- package/web/dist/assets/index-B9C85vN2.js +10 -0
- package/web/dist/assets/index-bIyYMf1a.css +1 -0
- package/web/dist/assets/knowledge-DmvXTX67.js +5 -0
- package/web/dist/assets/link-BSgD_zxQ.js +1 -0
- package/web/dist/assets/matchContext-CO01nzZ3.js +1 -0
- package/web/dist/assets/progress-DBmt_Ww6.js +6 -0
- package/web/dist/assets/routes-zEN1XNFl.js +1 -0
- package/web/dist/assets/scroll-area-DPCDB42s.js +45 -0
- package/web/dist/assets/separator-5sy8HYz5.js +1 -0
- package/web/dist/assets/skeleton-D7GRd6oJ.js +1 -0
- package/web/dist/assets/tabs-VSkG1f0-.js +1 -0
- package/web/dist/assets/tasks-CKNc1U7M.js +1 -0
- package/web/dist/assets/tasks._slug-DPzi78wf.js +8 -0
- package/web/dist/assets/utils-Dw49HYRP.js +1 -0
- package/web/dist/index.html +17 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { o as readActive, s as readActiveState, t as SpecDir } from "./types-C3butmI8.mjs";
|
|
3
|
+
import { f as upsertKnowledge, t as countKnowledge } from "./knowledge-CCCixwb8.mjs";
|
|
4
|
+
import { t as detectProject } from "./project-CpgK3fwQ.mjs";
|
|
5
|
+
import { emitAdditionalContext, extractSection, notifyUser } from "./dispatcher-BzOdcjaa.mjs";
|
|
6
|
+
import { openDefaultCached } from "./store-Clcihees.mjs";
|
|
7
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
//#region src/hooks/session-start.ts
|
|
10
|
+
async function sessionStart(ev, _signal) {
|
|
11
|
+
if (!ev.cwd) return;
|
|
12
|
+
let store;
|
|
13
|
+
try {
|
|
14
|
+
store = openDefaultCached();
|
|
15
|
+
} catch (err) {
|
|
16
|
+
notifyUser("warning: store open failed: %s", err);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
ingestProjectClaudeMD(store, ev.cwd);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
notifyUser("warning: CLAUDE.md ingest failed: %s", err);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
syncKnowledgeIndex(store, ev.cwd);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
notifyUser("warning: knowledge sync failed: %s", err);
|
|
28
|
+
}
|
|
29
|
+
if (!existsSync(join(join(ev.cwd, ".alfred", "steering"), "product.md"))) notifyUser("tip: run `/alfred:init` to set up project steering docs, templates, and knowledge index");
|
|
30
|
+
suggestLedgerReflect(store);
|
|
31
|
+
injectSpecContext(ev.cwd, ev.source ?? "", store);
|
|
32
|
+
}
|
|
33
|
+
function ingestProjectClaudeMD(store, projectPath) {
|
|
34
|
+
const claudeMD = join(projectPath, "CLAUDE.md");
|
|
35
|
+
let content;
|
|
36
|
+
try {
|
|
37
|
+
content = readFileSync(claudeMD, "utf-8");
|
|
38
|
+
} catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const sections = splitMarkdownSections(content);
|
|
42
|
+
if (sections.length === 0) return;
|
|
43
|
+
const proj = detectProject(projectPath);
|
|
44
|
+
for (const sec of sections) upsertKnowledge(store, {
|
|
45
|
+
id: 0,
|
|
46
|
+
filePath: "CLAUDE.md",
|
|
47
|
+
contentHash: "",
|
|
48
|
+
title: sec.path,
|
|
49
|
+
content: sec.content,
|
|
50
|
+
subType: "project",
|
|
51
|
+
projectRemote: proj.remote,
|
|
52
|
+
projectPath: proj.path,
|
|
53
|
+
projectName: proj.name,
|
|
54
|
+
branch: proj.branch,
|
|
55
|
+
createdAt: "",
|
|
56
|
+
updatedAt: "",
|
|
57
|
+
hitCount: 0,
|
|
58
|
+
lastAccessed: "",
|
|
59
|
+
enabled: true
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function splitMarkdownSections(md) {
|
|
63
|
+
const lines = md.split("\n");
|
|
64
|
+
const sections = [];
|
|
65
|
+
let currentPath = "";
|
|
66
|
+
let buf = [];
|
|
67
|
+
const flush = () => {
|
|
68
|
+
const content = buf.join("\n").trim();
|
|
69
|
+
if (currentPath && content) sections.push({
|
|
70
|
+
path: currentPath,
|
|
71
|
+
content
|
|
72
|
+
});
|
|
73
|
+
buf = [];
|
|
74
|
+
};
|
|
75
|
+
for (const line of lines) if (line.startsWith("## ")) {
|
|
76
|
+
flush();
|
|
77
|
+
currentPath = line.slice(3).trim();
|
|
78
|
+
} else if (line.startsWith("# ") && !currentPath) currentPath = line.slice(2).trim();
|
|
79
|
+
else if (currentPath) buf.push(line);
|
|
80
|
+
flush();
|
|
81
|
+
return sections;
|
|
82
|
+
}
|
|
83
|
+
function syncKnowledgeIndex(store, projectPath) {
|
|
84
|
+
const knowledgeDir = join(projectPath, ".alfred", "knowledge");
|
|
85
|
+
let files;
|
|
86
|
+
try {
|
|
87
|
+
files = readdirSync(knowledgeDir).filter((f) => f.endsWith(".md"));
|
|
88
|
+
} catch {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (files.length === 0) return;
|
|
92
|
+
const proj = detectProject(projectPath);
|
|
93
|
+
let synced = 0;
|
|
94
|
+
for (const file of files) try {
|
|
95
|
+
const { frontmatter, body } = parseFrontmatter(readFileSync(join(knowledgeDir, file), "utf-8"));
|
|
96
|
+
const { changed } = upsertKnowledge(store, {
|
|
97
|
+
id: 0,
|
|
98
|
+
filePath: file,
|
|
99
|
+
contentHash: "",
|
|
100
|
+
title: frontmatter.id ?? file.replace(".md", ""),
|
|
101
|
+
content: body,
|
|
102
|
+
subType: frontmatter.type ?? "general",
|
|
103
|
+
projectRemote: proj.remote,
|
|
104
|
+
projectPath: proj.path,
|
|
105
|
+
projectName: proj.name,
|
|
106
|
+
branch: proj.branch,
|
|
107
|
+
createdAt: frontmatter.created_at ?? "",
|
|
108
|
+
updatedAt: "",
|
|
109
|
+
hitCount: 0,
|
|
110
|
+
lastAccessed: "",
|
|
111
|
+
enabled: true
|
|
112
|
+
});
|
|
113
|
+
if (changed) synced++;
|
|
114
|
+
} catch {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (synced > 0) notifyUser("synced %d knowledge files to index", synced);
|
|
118
|
+
}
|
|
119
|
+
function parseFrontmatter(content) {
|
|
120
|
+
const fm = {};
|
|
121
|
+
if (!content.startsWith("---")) return {
|
|
122
|
+
frontmatter: fm,
|
|
123
|
+
body: content
|
|
124
|
+
};
|
|
125
|
+
const end = content.indexOf("---", 3);
|
|
126
|
+
if (end === -1) return {
|
|
127
|
+
frontmatter: fm,
|
|
128
|
+
body: content
|
|
129
|
+
};
|
|
130
|
+
const fmBlock = content.slice(3, end).trim();
|
|
131
|
+
for (const line of fmBlock.split("\n")) {
|
|
132
|
+
const idx = line.indexOf(":");
|
|
133
|
+
if (idx > 0) {
|
|
134
|
+
const key = line.slice(0, idx).trim();
|
|
135
|
+
fm[key] = line.slice(idx + 1).trim();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
frontmatter: fm,
|
|
140
|
+
body: content.slice(end + 3).trim()
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function suggestLedgerReflect(store) {
|
|
144
|
+
try {
|
|
145
|
+
const count = countKnowledge(store, "", "");
|
|
146
|
+
if (count < 20) return;
|
|
147
|
+
notifyUser("knowledge health: %d memories. Consider `ledger action=reflect` for a health report.", count);
|
|
148
|
+
} catch {}
|
|
149
|
+
}
|
|
150
|
+
function injectSpecContext(projectPath, source, store) {
|
|
151
|
+
let taskSlug;
|
|
152
|
+
try {
|
|
153
|
+
taskSlug = readActive(projectPath);
|
|
154
|
+
} catch {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
if (readActiveState(projectPath).tasks.find((t) => t.slug === taskSlug)?.status === "completed") return;
|
|
159
|
+
} catch {}
|
|
160
|
+
const sd = new SpecDir(projectPath, taskSlug);
|
|
161
|
+
if (!sd.exists()) return;
|
|
162
|
+
let buf = "";
|
|
163
|
+
if (source === "compact") {
|
|
164
|
+
let session = "";
|
|
165
|
+
try {
|
|
166
|
+
session = sd.readFile("session.md");
|
|
167
|
+
} catch {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const compactCount = (session.match(/## Compact Marker \[/g) ?? []).length;
|
|
171
|
+
buf += `\n--- Alfred Protocol: Recovering Task '${taskSlug}' (post-compact #${compactCount}) ---\n`;
|
|
172
|
+
if (compactCount <= 1) {
|
|
173
|
+
buf += "Full context recovery (first compact):\n\n";
|
|
174
|
+
for (const section of sd.allSections()) if (section.content.trim()) buf += `### ${section.file}\n${section.content}\n\n`;
|
|
175
|
+
} else {
|
|
176
|
+
buf += "Lightweight recovery (use dossier action=status for full spec):\n\n";
|
|
177
|
+
buf += `### session.md\n${session}\n\n`;
|
|
178
|
+
}
|
|
179
|
+
buf += "--- End Alfred Protocol ---\n";
|
|
180
|
+
emitAdditionalContext("SessionStart", buf);
|
|
181
|
+
notifyUser("recovered task '%s' (compact #%d)", taskSlug, compactCount);
|
|
182
|
+
} else {
|
|
183
|
+
let session;
|
|
184
|
+
try {
|
|
185
|
+
session = sd.readFile("session.md");
|
|
186
|
+
} catch {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!session) return;
|
|
190
|
+
const proj = detectProject(projectPath);
|
|
191
|
+
const memoryCount = countKnowledge(store, proj.remote, proj.path);
|
|
192
|
+
buf += `\n--- Alfred Protocol: Active Task '${taskSlug}' ---\n`;
|
|
193
|
+
if (memoryCount <= 5) {
|
|
194
|
+
buf += "(Full context — new project)\n\n";
|
|
195
|
+
for (const section of sd.allSections()) if (section.content.trim()) buf += `### ${section.file}\n${section.content}\n\n`;
|
|
196
|
+
} else if (memoryCount <= 20) {
|
|
197
|
+
buf += session + "\n";
|
|
198
|
+
try {
|
|
199
|
+
const goal = extractSection(sd.readFile("requirements.md"), "## Goal");
|
|
200
|
+
if (goal) buf += "\nGoal: " + goal + "\n";
|
|
201
|
+
} catch {}
|
|
202
|
+
} else buf += session + "\n";
|
|
203
|
+
buf += "--- End Alfred Protocol ---\n";
|
|
204
|
+
emitAdditionalContext("SessionStart", buf);
|
|
205
|
+
notifyUser("injected context for task '%s' (memories: %d)", taskSlug, memoryCount);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
export { sessionStart };
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import Database from "better-sqlite3";
|
|
6
|
+
const DDL = `
|
|
7
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
8
|
+
version INTEGER NOT NULL
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
CREATE TABLE IF NOT EXISTS knowledge_index (
|
|
12
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
13
|
+
file_path TEXT NOT NULL,
|
|
14
|
+
content_hash TEXT NOT NULL,
|
|
15
|
+
title TEXT NOT NULL,
|
|
16
|
+
content TEXT NOT NULL,
|
|
17
|
+
sub_type TEXT NOT NULL DEFAULT 'general',
|
|
18
|
+
project_remote TEXT DEFAULT '',
|
|
19
|
+
project_path TEXT NOT NULL,
|
|
20
|
+
project_name TEXT NOT NULL DEFAULT '',
|
|
21
|
+
branch TEXT DEFAULT '',
|
|
22
|
+
created_at TEXT NOT NULL,
|
|
23
|
+
updated_at TEXT NOT NULL,
|
|
24
|
+
hit_count INTEGER DEFAULT 0,
|
|
25
|
+
last_accessed TEXT DEFAULT '',
|
|
26
|
+
enabled INTEGER DEFAULT 1,
|
|
27
|
+
UNIQUE(project_remote, project_path, file_path)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_ki_project ON knowledge_index(project_remote, project_path);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_ki_sub_type ON knowledge_index(sub_type);
|
|
32
|
+
CREATE INDEX IF NOT EXISTS idx_ki_updated ON knowledge_index(updated_at);
|
|
33
|
+
|
|
34
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS knowledge_fts USING fts5(
|
|
35
|
+
title,
|
|
36
|
+
content,
|
|
37
|
+
sub_type,
|
|
38
|
+
content='knowledge_index',
|
|
39
|
+
content_rowid='id'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE TRIGGER IF NOT EXISTS ki_fts_ai AFTER INSERT ON knowledge_index BEGIN
|
|
43
|
+
INSERT INTO knowledge_fts(rowid, title, content, sub_type)
|
|
44
|
+
VALUES (new.id, new.title, new.content, new.sub_type);
|
|
45
|
+
END;
|
|
46
|
+
CREATE TRIGGER IF NOT EXISTS ki_fts_ad AFTER DELETE ON knowledge_index BEGIN
|
|
47
|
+
INSERT INTO knowledge_fts(knowledge_fts, rowid, title, content, sub_type)
|
|
48
|
+
VALUES ('delete', old.id, old.title, old.content, old.sub_type);
|
|
49
|
+
END;
|
|
50
|
+
CREATE TRIGGER IF NOT EXISTS ki_fts_au AFTER UPDATE ON knowledge_index BEGIN
|
|
51
|
+
INSERT INTO knowledge_fts(knowledge_fts, rowid, title, content, sub_type)
|
|
52
|
+
VALUES ('delete', old.id, old.title, old.content, old.sub_type);
|
|
53
|
+
INSERT INTO knowledge_fts(rowid, title, content, sub_type)
|
|
54
|
+
VALUES (new.id, new.title, new.content, new.sub_type);
|
|
55
|
+
END;
|
|
56
|
+
|
|
57
|
+
CREATE TABLE IF NOT EXISTS tag_aliases (
|
|
58
|
+
tag TEXT NOT NULL,
|
|
59
|
+
alias TEXT NOT NULL,
|
|
60
|
+
PRIMARY KEY (tag, alias)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
CREATE TABLE IF NOT EXISTS embeddings (
|
|
64
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
65
|
+
source TEXT NOT NULL,
|
|
66
|
+
source_id INTEGER NOT NULL,
|
|
67
|
+
model TEXT NOT NULL,
|
|
68
|
+
dims INTEGER NOT NULL,
|
|
69
|
+
vector BLOB NOT NULL,
|
|
70
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
71
|
+
UNIQUE (source, source_id)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE IF NOT EXISTS session_links (
|
|
75
|
+
claude_session_id TEXT PRIMARY KEY,
|
|
76
|
+
master_session_id TEXT NOT NULL,
|
|
77
|
+
project_remote TEXT DEFAULT '',
|
|
78
|
+
project_path TEXT NOT NULL DEFAULT '',
|
|
79
|
+
task_slug TEXT NOT NULL DEFAULT '',
|
|
80
|
+
branch TEXT DEFAULT '',
|
|
81
|
+
linked_at TEXT NOT NULL
|
|
82
|
+
);
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_session_links_master ON session_links(master_session_id);
|
|
84
|
+
`;
|
|
85
|
+
const TAG_ALIASES = {
|
|
86
|
+
auth: [
|
|
87
|
+
"authentication",
|
|
88
|
+
"login",
|
|
89
|
+
"認証",
|
|
90
|
+
"ログイン"
|
|
91
|
+
],
|
|
92
|
+
db: [
|
|
93
|
+
"database",
|
|
94
|
+
"sqlite",
|
|
95
|
+
"データベース"
|
|
96
|
+
],
|
|
97
|
+
api: [
|
|
98
|
+
"endpoint",
|
|
99
|
+
"rest",
|
|
100
|
+
"graphql"
|
|
101
|
+
],
|
|
102
|
+
test: [
|
|
103
|
+
"testing",
|
|
104
|
+
"テスト",
|
|
105
|
+
"spec"
|
|
106
|
+
],
|
|
107
|
+
security: [
|
|
108
|
+
"セキュリティ",
|
|
109
|
+
"vulnerability",
|
|
110
|
+
"脆弱性"
|
|
111
|
+
],
|
|
112
|
+
config: [
|
|
113
|
+
"configuration",
|
|
114
|
+
"settings",
|
|
115
|
+
"設定"
|
|
116
|
+
],
|
|
117
|
+
deploy: [
|
|
118
|
+
"deployment",
|
|
119
|
+
"デプロイ",
|
|
120
|
+
"release"
|
|
121
|
+
],
|
|
122
|
+
perf: [
|
|
123
|
+
"performance",
|
|
124
|
+
"パフォーマンス",
|
|
125
|
+
"optimization",
|
|
126
|
+
"最適化"
|
|
127
|
+
],
|
|
128
|
+
error: [
|
|
129
|
+
"エラー",
|
|
130
|
+
"bug",
|
|
131
|
+
"バグ",
|
|
132
|
+
"failure"
|
|
133
|
+
],
|
|
134
|
+
hook: [
|
|
135
|
+
"hooks",
|
|
136
|
+
"フック",
|
|
137
|
+
"lifecycle"
|
|
138
|
+
],
|
|
139
|
+
memory: [
|
|
140
|
+
"メモリ",
|
|
141
|
+
"knowledge",
|
|
142
|
+
"ナレッジ"
|
|
143
|
+
],
|
|
144
|
+
spec: [
|
|
145
|
+
"specification",
|
|
146
|
+
"仕様",
|
|
147
|
+
"requirement"
|
|
148
|
+
],
|
|
149
|
+
embed: [
|
|
150
|
+
"embedding",
|
|
151
|
+
"埋め込み",
|
|
152
|
+
"vector",
|
|
153
|
+
"ベクトル"
|
|
154
|
+
],
|
|
155
|
+
search: [
|
|
156
|
+
"検索",
|
|
157
|
+
"query",
|
|
158
|
+
"クエリ"
|
|
159
|
+
],
|
|
160
|
+
refactor: [
|
|
161
|
+
"リファクタ",
|
|
162
|
+
"cleanup",
|
|
163
|
+
"restructure"
|
|
164
|
+
],
|
|
165
|
+
ci: [
|
|
166
|
+
"ci/cd",
|
|
167
|
+
"pipeline",
|
|
168
|
+
"github actions"
|
|
169
|
+
]
|
|
170
|
+
};
|
|
171
|
+
const LEGACY_TABLES = [
|
|
172
|
+
"records_fts",
|
|
173
|
+
"records",
|
|
174
|
+
"session_links",
|
|
175
|
+
"tag_aliases",
|
|
176
|
+
"docs",
|
|
177
|
+
"docs_fts",
|
|
178
|
+
"crawl_meta",
|
|
179
|
+
"doc_feedback",
|
|
180
|
+
"instincts",
|
|
181
|
+
"patterns",
|
|
182
|
+
"pattern_tags",
|
|
183
|
+
"pattern_files",
|
|
184
|
+
"patterns_fts",
|
|
185
|
+
"alerts",
|
|
186
|
+
"alert_events",
|
|
187
|
+
"suggestion_outcomes",
|
|
188
|
+
"failure_solutions",
|
|
189
|
+
"solution_chains",
|
|
190
|
+
"learned_episodes",
|
|
191
|
+
"feedbacks",
|
|
192
|
+
"coaching_cache",
|
|
193
|
+
"snr_history",
|
|
194
|
+
"signal_outcomes",
|
|
195
|
+
"user_pattern_effectiveness",
|
|
196
|
+
"user_profile",
|
|
197
|
+
"user_preferences",
|
|
198
|
+
"adaptive_baselines",
|
|
199
|
+
"workflow_sequences",
|
|
200
|
+
"file_co_changes",
|
|
201
|
+
"live_session_phases",
|
|
202
|
+
"live_session_files",
|
|
203
|
+
"global_tool_sequences",
|
|
204
|
+
"global_tool_trigrams",
|
|
205
|
+
"tags",
|
|
206
|
+
"preferences",
|
|
207
|
+
"sessions",
|
|
208
|
+
"events",
|
|
209
|
+
"compact_events",
|
|
210
|
+
"decisions",
|
|
211
|
+
"tool_failures"
|
|
212
|
+
];
|
|
213
|
+
const LEGACY_TRIGGERS = [
|
|
214
|
+
"records_fts_ai",
|
|
215
|
+
"records_fts_ad",
|
|
216
|
+
"records_fts_au",
|
|
217
|
+
"patterns_fts_ai",
|
|
218
|
+
"patterns_fts_ad",
|
|
219
|
+
"patterns_fts_au",
|
|
220
|
+
"decisions_fts_ai",
|
|
221
|
+
"decisions_fts_ad",
|
|
222
|
+
"decisions_fts_au",
|
|
223
|
+
"docs_fts_ai",
|
|
224
|
+
"docs_fts_ad",
|
|
225
|
+
"docs_fts_au",
|
|
226
|
+
"ki_fts_ai",
|
|
227
|
+
"ki_fts_ad",
|
|
228
|
+
"ki_fts_au"
|
|
229
|
+
];
|
|
230
|
+
const LEGACY_INDEXES = [
|
|
231
|
+
"idx_records_source_type",
|
|
232
|
+
"idx_records_crawled_at",
|
|
233
|
+
"idx_records_sub_type",
|
|
234
|
+
"idx_embeddings_source",
|
|
235
|
+
"idx_docs_source_type",
|
|
236
|
+
"idx_docs_crawled_at",
|
|
237
|
+
"idx_wseq_task",
|
|
238
|
+
"idx_cochange_a",
|
|
239
|
+
"idx_live_phases_session",
|
|
240
|
+
"idx_live_files_session",
|
|
241
|
+
"idx_gts_from",
|
|
242
|
+
"idx_gtt_t1t2",
|
|
243
|
+
"idx_decisions_session",
|
|
244
|
+
"idx_decisions_timestamp",
|
|
245
|
+
"idx_instincts_scope",
|
|
246
|
+
"idx_instincts_domain",
|
|
247
|
+
"idx_instincts_confidence",
|
|
248
|
+
"idx_ki_project",
|
|
249
|
+
"idx_ki_sub_type",
|
|
250
|
+
"idx_ki_updated"
|
|
251
|
+
];
|
|
252
|
+
const SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
253
|
+
function dropSafe(db, kind, name) {
|
|
254
|
+
if (!SAFE_IDENTIFIER.test(name)) throw new Error(`store: unsafe identifier in DROP ${kind}: "${name}"`);
|
|
255
|
+
db.exec(`DROP ${kind} IF EXISTS ${name}`);
|
|
256
|
+
}
|
|
257
|
+
function seedTagAliases(db) {
|
|
258
|
+
const stmt = db.prepare("INSERT OR IGNORE INTO tag_aliases (tag, alias) VALUES (?, ?)");
|
|
259
|
+
for (const [tag, aliases] of Object.entries(TAG_ALIASES)) for (const alias of aliases) stmt.run(tag, alias);
|
|
260
|
+
}
|
|
261
|
+
function rebuildFromScratch(db) {
|
|
262
|
+
for (const trigger of LEGACY_TRIGGERS) dropSafe(db, "TRIGGER", trigger);
|
|
263
|
+
for (const table of LEGACY_TABLES) dropSafe(db, "TABLE", table);
|
|
264
|
+
for (const table of [
|
|
265
|
+
"knowledge_fts",
|
|
266
|
+
"knowledge_index",
|
|
267
|
+
"embeddings",
|
|
268
|
+
"session_links",
|
|
269
|
+
"tag_aliases",
|
|
270
|
+
"schema_version"
|
|
271
|
+
]) dropSafe(db, "TABLE", table);
|
|
272
|
+
for (const idx of LEGACY_INDEXES) dropSafe(db, "INDEX", idx);
|
|
273
|
+
db.exec(DDL);
|
|
274
|
+
seedTagAliases(db);
|
|
275
|
+
}
|
|
276
|
+
function setSchemaVersion(db, ver) {
|
|
277
|
+
db.exec("DELETE FROM schema_version");
|
|
278
|
+
db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(ver);
|
|
279
|
+
db.pragma(`user_version = ${ver}`);
|
|
280
|
+
}
|
|
281
|
+
function migrate(db) {
|
|
282
|
+
let current = 0;
|
|
283
|
+
try {
|
|
284
|
+
const row = db.prepare("SELECT version FROM schema_version LIMIT 1").get();
|
|
285
|
+
if (row) current = row.version;
|
|
286
|
+
} catch {}
|
|
287
|
+
if (current === 8) return;
|
|
288
|
+
db.transaction(() => {
|
|
289
|
+
rebuildFromScratch(db);
|
|
290
|
+
setSchemaVersion(db, 8);
|
|
291
|
+
})();
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/store/index.ts
|
|
295
|
+
var Store = class Store {
|
|
296
|
+
db;
|
|
297
|
+
dbPath;
|
|
298
|
+
expectedDims = 0;
|
|
299
|
+
constructor(db, dbPath) {
|
|
300
|
+
this.db = db;
|
|
301
|
+
this.dbPath = dbPath;
|
|
302
|
+
}
|
|
303
|
+
static open(dbPath) {
|
|
304
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
305
|
+
const db = new Database(dbPath);
|
|
306
|
+
db.pragma("journal_mode = WAL");
|
|
307
|
+
db.pragma("foreign_keys = ON");
|
|
308
|
+
db.pragma("synchronous = NORMAL");
|
|
309
|
+
db.pragma("cache_size = -8000");
|
|
310
|
+
db.pragma("mmap_size = 268435456");
|
|
311
|
+
db.pragma("temp_store = MEMORY");
|
|
312
|
+
if (db.pragma("user_version", { simple: true }) !== 8) migrate(db);
|
|
313
|
+
return new Store(db, dbPath);
|
|
314
|
+
}
|
|
315
|
+
static openDefault() {
|
|
316
|
+
return Store.open(defaultDBPath());
|
|
317
|
+
}
|
|
318
|
+
close() {
|
|
319
|
+
this.db.close();
|
|
320
|
+
}
|
|
321
|
+
schemaVersionCurrent() {
|
|
322
|
+
try {
|
|
323
|
+
return this.db.prepare("SELECT version FROM schema_version LIMIT 1").get()?.version ?? 0;
|
|
324
|
+
} catch {
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
let cachedStore;
|
|
330
|
+
function openDefaultCached() {
|
|
331
|
+
if (!cachedStore) cachedStore = Store.openDefault();
|
|
332
|
+
return cachedStore;
|
|
333
|
+
}
|
|
334
|
+
function defaultDBPath() {
|
|
335
|
+
return join(homedir(), ".claude-alfred", "alfred.db");
|
|
336
|
+
}
|
|
337
|
+
//#endregion
|
|
338
|
+
export { Store, openDefaultCached };
|