claude-alfred 0.3.22 → 0.4.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.
Files changed (72) hide show
  1. package/dist/{audit-DmNpxVt4.mjs → audit-BUfNnVyn.mjs} +1 -1
  2. package/dist/cli.mjs +14 -8
  3. package/dist/{directives-Drv78LF2.mjs → directives-1LA3fP9f.mjs} +4 -4
  4. package/dist/{dispatcher-Bm43S1GQ.mjs → dispatcher-CN_L4EfX.mjs} +6 -6
  5. package/dist/{drift-PySAVqkr.mjs → drift-B5k7Dh4l.mjs} +3 -3
  6. package/dist/{epic-DJZgRHFr.mjs → epic-BHZ71Dpf.mjs} +1 -1
  7. package/dist/{fts-DICqcpG_.mjs → fts-DsQAcHWr.mjs} +96 -9
  8. package/dist/{knowledge-C7rEfFSX.mjs → knowledge-BAxf0Ool.mjs} +31 -38
  9. package/dist/{living-spec-C5kafrYy.mjs → living-spec-CljcxM8t.mjs} +2 -2
  10. package/dist/post-tool-CR0jUX_B.mjs +11 -0
  11. package/dist/{post-tool-CXbKGLOa.mjs → post-tool-RuXx9ji5.mjs} +84 -22
  12. package/dist/postinstall.mjs +1 -1
  13. package/dist/{pre-compact-p_eqH4vL.mjs → pre-compact-Br_oHNYF.mjs} +10 -14
  14. package/dist/{pre-tool-CnoF_sta.mjs → pre-tool-PQfosCOP.mjs} +2 -2
  15. package/dist/project-BPGWTiJo.mjs +166 -0
  16. package/dist/{review-gate-P_eycqEX.mjs → review-gate-BpjBk-63.mjs} +1 -1
  17. package/dist/{schema-BoqQZmCK.mjs → schema-CkIoZpfo.mjs} +74 -11
  18. package/dist/{server-BJ1jUEm2.mjs → server-ceH-yuDk.mjs} +205 -113
  19. package/dist/{server-AL3qcEqy.mjs → server-xyaK5L2j.mjs} +219 -72
  20. package/dist/{session-start-CS4gyHMH.mjs → session-start-CAzUdhbx.mjs} +14 -19
  21. package/dist/spec-sync-B3ffXG6c.mjs +147 -0
  22. package/dist/{stop-DJgb2wrf.mjs → stop-DOflmlLk.mjs} +2 -2
  23. package/dist/{store-BffM-bi-.mjs → store-CvwAL2ah.mjs} +75 -12
  24. package/dist/{types-DyBixMZ0.mjs → types-zr3tpkx_.mjs} +7 -2
  25. package/dist/{user-prompt-vSVUeux8.mjs → user-prompt-BKGvFhwe.mjs} +5 -5
  26. package/dist/{vectors-DHZGQ096.mjs → vectors-CC4__7vH.mjs} +39 -24
  27. package/package.json +1 -1
  28. package/web/dist/assets/activity-DMDkxCo7.js +1 -0
  29. package/web/dist/assets/alert-dialog-CM_ZhUdU.js +20 -0
  30. package/web/dist/assets/{badge-LeU66vxM.js → api-BmhJIAol.js} +1935 -1935
  31. package/web/dist/assets/badge-BQs79r1b.js +1 -0
  32. package/web/dist/assets/butler-empty-DXQML1e-.js +1 -0
  33. package/web/dist/assets/button-CyEMTYOZ.js +1 -0
  34. package/web/dist/assets/card-BwA6Hc_6.js +1 -0
  35. package/web/dist/assets/es2015-CipziuBh.js +41 -0
  36. package/web/dist/assets/index-3eR8NKmm.css +1 -0
  37. package/web/dist/assets/index-VGK-jnob.js +10 -0
  38. package/web/dist/assets/knowledge-utPtfMSM.js +53 -0
  39. package/web/dist/assets/link-BbVn19YR.js +1 -0
  40. package/web/dist/assets/motion-Glmw_2_q.js +1 -0
  41. package/web/dist/assets/preload-helper-B9M90MvK.js +1 -0
  42. package/web/dist/assets/progress-DC2lctnl.js +6 -0
  43. package/web/dist/assets/projects-Dq5vAnIw.js +1 -0
  44. package/web/dist/assets/routes-Dwc3R2gp.js +1 -0
  45. package/web/dist/assets/separator-CPkkHGC7.js +5 -0
  46. package/web/dist/assets/skeleton-CNWArsdx.js +1 -0
  47. package/web/dist/assets/{stagger-container-GyT5nb39.js → stagger-container-BA1--Skd.js} +2 -2
  48. package/web/dist/assets/tasks-BmNVv-XH.js +1 -0
  49. package/web/dist/assets/{tasks._slug-BccdO-CH.js → tasks._slug-oWBBUpKP.js} +10 -10
  50. package/web/dist/assets/tooltip-BnisAe87.js +1 -0
  51. package/web/dist/index.html +10 -9
  52. package/dist/post-tool-CiV4IiYH.mjs +0 -11
  53. package/dist/project-DCKke4_Q.mjs +0 -81
  54. package/web/dist/assets/activity-CXCppRFo.js +0 -1
  55. package/web/dist/assets/alert-dialog-EwyZzvus.js +0 -20
  56. package/web/dist/assets/butler-empty-DDWZ5SlJ.js +0 -1
  57. package/web/dist/assets/button-9A03BFB6.js +0 -1
  58. package/web/dist/assets/card-C6aK9m3p.js +0 -1
  59. package/web/dist/assets/es2015-DCAVxrl_.js +0 -41
  60. package/web/dist/assets/index-BWInN_tm.css +0 -1
  61. package/web/dist/assets/index-uOCUo7Eo.js +0 -10
  62. package/web/dist/assets/knowledge-CB_kjUsk.js +0 -53
  63. package/web/dist/assets/link-DBmH_08w.js +0 -1
  64. package/web/dist/assets/motion-BYjuf98F.js +0 -1
  65. package/web/dist/assets/preload-helper-BmUhu0Y-.js +0 -1
  66. package/web/dist/assets/progress-qFNeU9jW.js +0 -6
  67. package/web/dist/assets/routes-Dgq6wgCk.js +0 -1
  68. package/web/dist/assets/separator-mtcJeU26.js +0 -5
  69. package/web/dist/assets/skeleton-Cwiwlof_.js +0 -1
  70. package/web/dist/assets/tasks-CVVDa650.js +0 -1
  71. package/web/dist/assets/tooltip-CAuNczLe.js +0 -1
  72. /package/web/dist/assets/{types-Gr5neTyu.js → types-CvyMsJWC.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as __esmMin } from "./chunk-CAm0Jl7e.mjs";
3
- import { f as rootDir, s as init_types } from "./types-DyBixMZ0.mjs";
3
+ import { f as rootDir, s as init_types } from "./types-zr3tpkx_.mjs";
4
4
  import { appendFileSync, mkdirSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  //#region src/spec/audit.ts
package/dist/cli.mjs CHANGED
@@ -369,9 +369,9 @@ const main = defineCommand({
369
369
  serve: defineCommand({
370
370
  meta: { description: "Start MCP server (stdio)" },
371
371
  async run() {
372
- const { Store } = await import("./store-BffM-bi-.mjs").then((n) => (n.t(), n.r));
372
+ const { Store } = await import("./store-CvwAL2ah.mjs").then((n) => (n.t(), n.r));
373
373
  const { Embedder } = await import("./embedder-D3hJoryD.mjs");
374
- const { serveMCP } = await import("./server-BJ1jUEm2.mjs");
374
+ const { serveMCP } = await import("./server-ceH-yuDk.mjs");
375
375
  const store = Store.openDefault();
376
376
  let emb = null;
377
377
  try {
@@ -383,7 +383,7 @@ const main = defineCommand({
383
383
  }
384
384
  }),
385
385
  dashboard: defineCommand({
386
- meta: { description: "Open browser dashboard" },
386
+ meta: { description: "Open browser dashboard (cross-project)" },
387
387
  args: {
388
388
  port: {
389
389
  type: "string",
@@ -397,18 +397,24 @@ const main = defineCommand({
397
397
  }
398
398
  },
399
399
  async run({ args }) {
400
- const { Store } = await import("./store-BffM-bi-.mjs").then((n) => (n.t(), n.r));
400
+ const { existsSync } = await import("node:fs");
401
+ const { join } = await import("node:path");
402
+ const { Store } = await import("./store-CvwAL2ah.mjs").then((n) => (n.t(), n.r));
401
403
  const { Embedder } = await import("./embedder-D3hJoryD.mjs");
402
- const { startDashboard } = await import("./server-AL3qcEqy.mjs");
403
- const projectPath = process.cwd();
404
+ const { startDashboard } = await import("./server-xyaK5L2j.mjs");
405
+ const { resolveOrRegisterProject } = await import("./project-BPGWTiJo.mjs").then((n) => (n.n(), n.a));
406
+ const { syncAllProjectSpecs } = await import("./spec-sync-B3ffXG6c.mjs");
407
+ const cwd = process.cwd();
404
408
  const store = Store.openDefault();
405
409
  let emb = null;
406
410
  try {
407
411
  emb = Embedder.create();
408
412
  } catch {}
409
413
  if (emb) store.expectedDims = emb.dims;
414
+ if (existsSync(join(cwd, ".alfred"))) resolveOrRegisterProject(store, cwd);
415
+ await syncAllProjectSpecs(store, emb);
410
416
  const version = await resolveVersion();
411
- await startDashboard(projectPath, store, emb, {
417
+ await startDashboard(cwd, store, emb, {
412
418
  port: parseInt(args.port, 10),
413
419
  urlOnly: args["url-only"],
414
420
  version
@@ -422,7 +428,7 @@ const main = defineCommand({
422
428
  description: "Event name"
423
429
  } },
424
430
  async run({ args }) {
425
- const { runHook } = await import("./dispatcher-Bm43S1GQ.mjs").then((n) => (n.i(), n.t));
431
+ const { runHook } = await import("./dispatcher-CN_L4EfX.mjs").then((n) => (n.i(), n.t));
426
432
  await runHook(args.event);
427
433
  }
428
434
  }),
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as __esmMin } from "./chunk-CAm0Jl7e.mjs";
3
- import { c as incrementHitCount, f as searchKnowledgeKeyword, i as getKnowledgeByIDs, l as init_knowledge } from "./knowledge-C7rEfFSX.mjs";
4
- import { r as vectorSearchKnowledge, t as init_vectors } from "./vectors-DHZGQ096.mjs";
5
- import { a as subTypeBoost, i as searchKnowledgeFTS, o as subTypeHalfLife, r as init_fts } from "./fts-DICqcpG_.mjs";
6
- import { i as init_dispatcher, n as emitAdditionalContext } from "./dispatcher-Bm43S1GQ.mjs";
3
+ import { c as incrementHitCount, f as searchKnowledgeKeyword, i as getKnowledgeByIDs, l as init_knowledge } from "./knowledge-BAxf0Ool.mjs";
4
+ import { i as vectorSearchKnowledge, t as init_vectors } from "./vectors-CC4__7vH.mjs";
5
+ import { i as searchKnowledgeFTS, o as subTypeBoost, r as init_fts, s as subTypeHalfLife } from "./fts-DsQAcHWr.mjs";
6
+ import { i as init_dispatcher, n as emitAdditionalContext } from "./dispatcher-CN_L4EfX.mjs";
7
7
  //#region src/mcp/helpers.ts
8
8
  function truncate(s, maxLen) {
9
9
  const runes = [...s];
@@ -89,27 +89,27 @@ async function runHook(event) {
89
89
  }
90
90
  }
91
91
  async function handleSessionStart(ev, signal) {
92
- const { sessionStart } = await import("./session-start-CS4gyHMH.mjs");
92
+ const { sessionStart } = await import("./session-start-CAzUdhbx.mjs");
93
93
  await sessionStart(ev, signal);
94
94
  }
95
95
  async function handlePreCompact(ev, signal) {
96
- const { preCompact } = await import("./pre-compact-p_eqH4vL.mjs");
96
+ const { preCompact } = await import("./pre-compact-Br_oHNYF.mjs");
97
97
  await preCompact(ev, signal);
98
98
  }
99
99
  async function handleUserPromptSubmit(ev, signal) {
100
- const { userPromptSubmit } = await import("./user-prompt-vSVUeux8.mjs");
100
+ const { userPromptSubmit } = await import("./user-prompt-BKGvFhwe.mjs");
101
101
  await userPromptSubmit(ev, signal);
102
102
  }
103
103
  async function handlePostToolUse(ev, signal) {
104
- const { postToolUse } = await import("./post-tool-CiV4IiYH.mjs");
104
+ const { postToolUse } = await import("./post-tool-CR0jUX_B.mjs");
105
105
  await postToolUse(ev, signal);
106
106
  }
107
107
  async function handlePreToolUse(ev, _signal) {
108
- const { preToolUse } = await import("./pre-tool-CnoF_sta.mjs");
108
+ const { preToolUse } = await import("./pre-tool-PQfosCOP.mjs");
109
109
  await preToolUse(ev);
110
110
  }
111
111
  async function handleStop(ev, _signal) {
112
- const { stop } = await import("./stop-DJgb2wrf.mjs");
112
+ const { stop } = await import("./stop-DOflmlLk.mjs");
113
113
  await stop(ev);
114
114
  }
115
115
  var init_dispatcher = __esmMin((() => {}));
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as __esmMin } from "./chunk-CAm0Jl7e.mjs";
3
- import { c as readActive, s as init_types, t as SpecDir } from "./types-DyBixMZ0.mjs";
4
- import { n as init_audit, t as appendAudit } from "./audit-DmNpxVt4.mjs";
5
- import { extractChangedFiles, n as init_lang_filter, parseDesignFileRefs, r as shouldAutoAppend, t as init_living_spec } from "./living-spec-C5kafrYy.mjs";
3
+ import { c as readActive, s as init_types, t as SpecDir } from "./types-zr3tpkx_.mjs";
4
+ import { n as init_audit, t as appendAudit } from "./audit-BUfNnVyn.mjs";
5
+ import { extractChangedFiles, n as init_lang_filter, parseDesignFileRefs, r as shouldAutoAppend, t as init_living_spec } from "./living-spec-CljcxM8t.mjs";
6
6
  //#region src/hooks/drift.ts
7
7
  /**
8
8
  * Detect source files changed in the last commit that are not referenced in the active spec.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as __esmMin } from "./chunk-CAm0Jl7e.mjs";
3
- import { _ as require_dist, n as VALID_SLUG, s as init_types } from "./types-DyBixMZ0.mjs";
3
+ import { _ as require_dist, n as VALID_SLUG, s as init_types } from "./types-zr3tpkx_.mjs";
4
4
  import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
5
5
  import { join } from "node:path";
6
6
  //#region src/epic/index.ts
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as __esmMin } from "./chunk-CAm0Jl7e.mjs";
3
- import { f as searchKnowledgeKeyword, i as getKnowledgeByIDs, l as init_knowledge, u as mapRow } from "./knowledge-C7rEfFSX.mjs";
4
- import { n as pairwiseSimilarity, t as init_vectors } from "./vectors-DHZGQ096.mjs";
3
+ import { f as searchKnowledgeKeyword, i as getKnowledgeByIDs, l as init_knowledge, u as mapRow } from "./knowledge-BAxf0Ool.mjs";
4
+ import { r as pairwiseSimilarity, t as init_vectors } from "./vectors-CC4__7vH.mjs";
5
5
  //#region src/store/fts.ts
6
6
  function subTypeHalfLife(subType) {
7
7
  switch (subType) {
@@ -54,9 +54,8 @@ function searchKnowledgeFTS(store, query, limit) {
54
54
  }
55
55
  function searchFTSKnowledge(store, ftsQuery, limit) {
56
56
  return store.db.prepare(`
57
- SELECT k.id, k.file_path, k.content_hash, k.title, k.content, k.sub_type,
58
- k.project_remote, k.project_path, k.project_name, k.branch,
59
- k.created_at, k.updated_at, k.hit_count, k.last_accessed, k.enabled,
57
+ SELECT k.id, k.project_id, k.file_path, k.content_hash, k.title, k.content, k.sub_type,
58
+ k.branch, k.created_at, k.updated_at, k.hit_count, k.last_accessed, k.enabled,
60
59
  bm25(knowledge_fts, 3.0, 1.0, 1.0) AS rank
61
60
  FROM knowledge_fts f
62
61
  JOIN knowledge_index k ON k.id = f.rowid
@@ -65,13 +64,101 @@ function searchFTSKnowledge(store, ftsQuery, limit) {
65
64
  LIMIT ?
66
65
  `).all(ftsQuery, limit).map(mapRow);
67
66
  }
67
+ function searchUnified(store, query, options) {
68
+ const limit = options?.limit ?? 20;
69
+ const sources = options?.sources ?? ["knowledge", "spec"];
70
+ const projectId = options?.projectId;
71
+ const q = query.trim();
72
+ if (!q) return [];
73
+ const words = q.split(/\s+/);
74
+ let expanded;
75
+ try {
76
+ expanded = expandAliases(store, words);
77
+ } catch {
78
+ expanded = words;
79
+ }
80
+ const ftsTerms = [];
81
+ for (const w of expanded) {
82
+ const sanitized = sanitizeFTSTerm(w);
83
+ if (sanitized) ftsTerms.push(`"${sanitized}"`);
84
+ }
85
+ if (ftsTerms.length === 0) return [];
86
+ const ftsQuery = ftsTerms.join(" OR ");
87
+ const results = [];
88
+ if (sources.includes("knowledge")) {
89
+ const projectFilter = projectId ? "AND k.project_id = ?" : "";
90
+ const params = [ftsQuery];
91
+ if (projectId) params.push(projectId);
92
+ params.push(limit);
93
+ try {
94
+ const rows = store.db.prepare(`
95
+ SELECT k.id, k.project_id, k.title, k.content, k.sub_type, k.hit_count,
96
+ COALESCE(p.name, '') as project_name,
97
+ bm25(knowledge_fts, 3.0, 1.0, 1.0) AS rank
98
+ FROM knowledge_fts f
99
+ JOIN knowledge_index k ON k.id = f.rowid
100
+ LEFT JOIN projects p ON p.id = k.project_id
101
+ WHERE knowledge_fts MATCH ? AND k.enabled = 1 ${projectFilter}
102
+ ORDER BY rank
103
+ LIMIT ?
104
+ `).all(...params);
105
+ for (const r of rows) results.push({
106
+ id: r.id,
107
+ source: "knowledge",
108
+ title: r.title,
109
+ content: r.content,
110
+ projectId: r.project_id,
111
+ projectName: r.project_name,
112
+ score: -r.rank,
113
+ subType: r.sub_type,
114
+ hitCount: r.hit_count
115
+ });
116
+ } catch (e) {
117
+ process.stderr.write(`alfred: FTS knowledge search error: ${e}\n`);
118
+ }
119
+ }
120
+ if (sources.includes("spec")) {
121
+ const projectFilter = projectId ? "AND s.project_id = ?" : "";
122
+ const params = [ftsQuery];
123
+ if (projectId) params.push(projectId);
124
+ params.push(limit);
125
+ try {
126
+ const rows = store.db.prepare(`
127
+ SELECT s.id, s.project_id, s.title, s.content, s.slug, s.file_name, s.status,
128
+ COALESCE(p.name, '') as project_name,
129
+ bm25(spec_fts, 3.0, 1.0, 1.0) AS rank
130
+ FROM spec_fts f
131
+ JOIN spec_index s ON s.id = f.rowid
132
+ LEFT JOIN projects p ON p.id = s.project_id
133
+ WHERE spec_fts MATCH ? ${projectFilter}
134
+ ORDER BY rank
135
+ LIMIT ?
136
+ `).all(...params);
137
+ for (const r of rows) results.push({
138
+ id: r.id,
139
+ source: "spec",
140
+ title: r.title,
141
+ content: r.content,
142
+ projectId: r.project_id,
143
+ projectName: r.project_name,
144
+ score: -r.rank,
145
+ slug: r.slug,
146
+ fileName: r.file_name,
147
+ specStatus: r.status
148
+ });
149
+ } catch (e) {
150
+ process.stderr.write(`alfred: FTS spec search error: ${e}\n`);
151
+ }
152
+ }
153
+ results.sort((a, b) => b.score - a.score);
154
+ return results.slice(0, limit);
155
+ }
68
156
  function fuzzySearchKnowledge(store, queryWords, limit, exclude) {
69
157
  if (limit <= 0) return [];
70
158
  const excludeIds = new Set(exclude.map((d) => d.id));
71
159
  const rows = store.db.prepare(`
72
- SELECT id, file_path, content_hash, title, content, sub_type,
73
- project_remote, project_path, project_name, branch,
74
- created_at, updated_at, hit_count, last_accessed, enabled
160
+ SELECT id, project_id, file_path, content_hash, title, content, sub_type,
161
+ branch, created_at, updated_at, hit_count, last_accessed, enabled
75
162
  FROM knowledge_index WHERE enabled = 1 LIMIT 500
76
163
  `).all();
77
164
  const docs = [];
@@ -189,4 +276,4 @@ var init_fts = __esmMin((() => {
189
276
  ];
190
277
  }));
191
278
  //#endregion
192
- export { subTypeBoost as a, searchKnowledgeFTS as i, expandAliases as n, subTypeHalfLife as o, init_fts as r, detectKnowledgeConflicts as t };
279
+ export { searchUnified as a, searchKnowledgeFTS as i, expandAliases as n, subTypeBoost as o, init_fts as r, subTypeHalfLife as s, detectKnowledgeConflicts as t };
@@ -10,7 +10,7 @@ function upsertKnowledge(store, row) {
10
10
  if (!row.createdAt) row.createdAt = now;
11
11
  row.updatedAt = now;
12
12
  row.contentHash = contentHash(row.content);
13
- const existing = store.db.prepare("SELECT id, content_hash FROM knowledge_index WHERE project_remote = ? AND project_path = ? AND file_path = ?").get(row.projectRemote, row.projectPath, row.filePath);
13
+ const existing = store.db.prepare("SELECT id, content_hash FROM knowledge_index WHERE project_id = ? AND file_path = ?").get(row.projectId, row.filePath);
14
14
  if (existing && existing.content_hash === row.contentHash) {
15
15
  row.id = existing.id;
16
16
  return {
@@ -20,19 +20,17 @@ function upsertKnowledge(store, row) {
20
20
  }
21
21
  const result = store.db.prepare(`
22
22
  INSERT INTO knowledge_index
23
- (file_path, content_hash, title, content, sub_type,
24
- project_remote, project_path, project_name, branch,
25
- created_at, updated_at, hit_count, last_accessed, enabled)
26
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, '', 1)
27
- ON CONFLICT(project_remote, project_path, file_path) DO UPDATE SET
23
+ (project_id, file_path, content_hash, title, content, sub_type,
24
+ branch, created_at, updated_at, hit_count, last_accessed, enabled)
25
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, '', 1)
26
+ ON CONFLICT(project_id, file_path) DO UPDATE SET
28
27
  content_hash = excluded.content_hash,
29
28
  title = excluded.title,
30
29
  content = excluded.content,
31
30
  sub_type = excluded.sub_type,
32
- project_name = excluded.project_name,
33
31
  branch = excluded.branch,
34
32
  updated_at = excluded.updated_at
35
- `).run(row.filePath, row.contentHash, row.title, row.content, row.subType, row.projectRemote, row.projectPath, row.projectName, row.branch, row.createdAt, row.updatedAt);
33
+ `).run(row.projectId, row.filePath, row.contentHash, row.title, row.content, row.subType, row.branch, row.createdAt, row.updatedAt);
36
34
  const id = Number(result.lastInsertRowid);
37
35
  row.id = id;
38
36
  return {
@@ -42,9 +40,8 @@ function upsertKnowledge(store, row) {
42
40
  }
43
41
  function getKnowledgeByID(store, id) {
44
42
  const row = store.db.prepare(`
45
- SELECT id, file_path, content_hash, title, content, sub_type,
46
- project_remote, project_path, project_name, branch,
47
- created_at, updated_at, hit_count, last_accessed, enabled
43
+ SELECT id, project_id, file_path, content_hash, title, content, sub_type,
44
+ branch, created_at, updated_at, hit_count, last_accessed, enabled
48
45
  FROM knowledge_index WHERE id = ?
49
46
  `).get(id);
50
47
  return row ? mapRow(row) : void 0;
@@ -53,9 +50,8 @@ function getKnowledgeByIDs(store, ids) {
53
50
  if (ids.length === 0) return [];
54
51
  const placeholders = ids.map(() => "?").join(",");
55
52
  return store.db.prepare(`
56
- SELECT id, file_path, content_hash, title, content, sub_type,
57
- project_remote, project_path, project_name, branch,
58
- created_at, updated_at, hit_count, last_accessed, enabled
53
+ SELECT id, project_id, file_path, content_hash, title, content, sub_type,
54
+ branch, created_at, updated_at, hit_count, last_accessed, enabled
59
55
  FROM knowledge_index WHERE id IN (${placeholders})
60
56
  `).all(...ids).map(mapRow);
61
57
  }
@@ -75,27 +71,27 @@ function promoteSubType(store, id, newSubType) {
75
71
  }
76
72
  function getPromotionCandidates(store) {
77
73
  return store.db.prepare(`
78
- SELECT id, file_path, content_hash, title, content, sub_type,
79
- project_remote, project_path, project_name, branch,
80
- created_at, updated_at, hit_count, last_accessed, enabled
74
+ SELECT id, project_id, file_path, content_hash, title, content, sub_type,
75
+ branch, created_at, updated_at, hit_count, last_accessed, enabled
81
76
  FROM knowledge_index
82
77
  WHERE enabled = 1
83
78
  AND (sub_type = 'pattern' AND hit_count >= 15)
84
79
  ORDER BY hit_count DESC
85
80
  `).all().map(mapRow);
86
81
  }
87
- function getKnowledgeStats(store) {
88
- const agg = store.db.prepare("SELECT COUNT(*) as total, COALESCE(AVG(hit_count), 0) as avg_hits FROM knowledge_index WHERE enabled = 1").get();
82
+ function getKnowledgeStats(store, projectId) {
83
+ const projectFilter = projectId ? "AND project_id = ?" : "";
84
+ const params = projectId ? [projectId] : [];
85
+ const agg = store.db.prepare(`SELECT COUNT(*) as total, COALESCE(AVG(hit_count), 0) as avg_hits FROM knowledge_index WHERE enabled = 1 ${projectFilter}`).get(...params);
89
86
  const bySubType = {};
90
- const subtypeRows = store.db.prepare("SELECT sub_type, COUNT(*) as cnt FROM knowledge_index WHERE enabled = 1 GROUP BY sub_type").all();
87
+ const subtypeRows = store.db.prepare(`SELECT sub_type, COUNT(*) as cnt FROM knowledge_index WHERE enabled = 1 ${projectFilter} GROUP BY sub_type`).all(...params);
91
88
  for (const r of subtypeRows) bySubType[r.sub_type] = r.cnt;
92
89
  const topRows = store.db.prepare(`
93
- SELECT id, file_path, content_hash, title, content, sub_type,
94
- project_remote, project_path, project_name, branch,
95
- created_at, updated_at, hit_count, last_accessed, enabled
96
- FROM knowledge_index WHERE enabled = 1
90
+ SELECT id, project_id, file_path, content_hash, title, content, sub_type,
91
+ branch, created_at, updated_at, hit_count, last_accessed, enabled
92
+ FROM knowledge_index WHERE enabled = 1 ${projectFilter}
97
93
  ORDER BY hit_count DESC LIMIT 5
98
- `).all();
94
+ `).all(...params);
99
95
  return {
100
96
  total: agg?.total ?? 0,
101
97
  avgHitCount: agg?.avg_hits ?? 0,
@@ -106,29 +102,28 @@ function getKnowledgeStats(store) {
106
102
  function searchKnowledgeKeyword(store, query, limit) {
107
103
  const escaped = escapeLIKEContains(query);
108
104
  return store.db.prepare(`
109
- SELECT id, file_path, content_hash, title, content, sub_type,
110
- project_remote, project_path, project_name, branch,
111
- created_at, updated_at, hit_count, last_accessed, enabled
105
+ SELECT id, project_id, file_path, content_hash, title, content, sub_type,
106
+ branch, created_at, updated_at, hit_count, last_accessed, enabled
112
107
  FROM knowledge_index
113
108
  WHERE enabled = 1 AND (content LIKE ? ESCAPE '\\' OR title LIKE ? ESCAPE '\\')
114
109
  ORDER BY hit_count DESC LIMIT ?
115
110
  `).all(escaped, escaped, limit).map(mapRow);
116
111
  }
117
- function getRecentDecisions(store, projectRemote, projectPath, sinceISO, limit) {
112
+ function getRecentDecisions(store, projectId, sinceISO, limit) {
118
113
  return store.db.prepare(`
119
114
  SELECT title, content, created_at FROM knowledge_index
120
115
  WHERE sub_type = 'decision'
121
- AND project_remote = ? AND project_path = ?
116
+ AND project_id = ?
122
117
  AND created_at > ? AND enabled = 1
123
118
  ORDER BY created_at DESC LIMIT ?
124
- `).all(projectRemote, projectPath, sinceISO, limit).map((r) => ({
119
+ `).all(projectId, sinceISO, limit).map((r) => ({
125
120
  title: r.title,
126
121
  content: r.content,
127
122
  createdAt: r.created_at
128
123
  }));
129
124
  }
130
- function deleteOrphanKnowledge(store, projectRemote, projectPath, branch, validFilePaths) {
131
- const rows = store.db.prepare("SELECT id, file_path FROM knowledge_index WHERE project_remote = ? AND project_path = ? AND branch = ?").all(projectRemote, projectPath, branch);
125
+ function deleteOrphanKnowledge(store, projectId, branch, validFilePaths) {
126
+ const rows = store.db.prepare("SELECT id, file_path FROM knowledge_index WHERE project_id = ? AND branch = ?").all(projectId, branch);
132
127
  let deleted = 0;
133
128
  const delEmbed = store.db.prepare("DELETE FROM embeddings WHERE source = 'knowledge' AND source_id = ?");
134
129
  const delKnowledge = store.db.prepare("DELETE FROM knowledge_index WHERE id = ?");
@@ -141,8 +136,8 @@ function deleteOrphanKnowledge(store, projectRemote, projectPath, branch, validF
141
136
  })();
142
137
  return deleted;
143
138
  }
144
- function countKnowledge(store, projectRemote, projectPath) {
145
- return store.db.prepare("SELECT COUNT(*) as cnt FROM knowledge_index WHERE project_remote = ? AND project_path = ? AND enabled = 1").get(projectRemote, projectPath).cnt;
139
+ function countKnowledge(store, projectId) {
140
+ return store.db.prepare("SELECT COUNT(*) as cnt FROM knowledge_index WHERE project_id = ? AND enabled = 1").get(projectId).cnt;
146
141
  }
147
142
  function escapeLIKEContains(s) {
148
143
  s = s.replaceAll("\\", "\\\\");
@@ -153,14 +148,12 @@ function escapeLIKEContains(s) {
153
148
  function mapRow(r) {
154
149
  return {
155
150
  id: r.id,
151
+ projectId: r.project_id,
156
152
  filePath: r.file_path,
157
153
  contentHash: r.content_hash,
158
154
  title: r.title,
159
155
  content: r.content,
160
156
  subType: r.sub_type,
161
- projectRemote: r.project_remote,
162
- projectPath: r.project_path,
163
- projectName: r.project_name,
164
157
  branch: r.branch,
165
158
  createdAt: r.created_at,
166
159
  updatedAt: r.updated_at,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as __esmMin } from "./chunk-CAm0Jl7e.mjs";
3
- import { c as readActive, s as init_types, t as SpecDir } from "./types-DyBixMZ0.mjs";
4
- import { n as init_audit, t as appendAudit } from "./audit-DmNpxVt4.mjs";
3
+ import { c as readActive, s as init_types, t as SpecDir } from "./types-zr3tpkx_.mjs";
4
+ import { n as init_audit, t as appendAudit } from "./audit-BUfNnVyn.mjs";
5
5
  import { basename, dirname, extname } from "node:path";
6
6
  import { execSync } from "node:child_process";
7
7
  //#region src/hooks/lang-filter.ts
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import "./types-zr3tpkx_.mjs";
3
+ import "./audit-BUfNnVyn.mjs";
4
+ import "./dispatcher-CN_L4EfX.mjs";
5
+ import "./directives-1LA3fP9f.mjs";
6
+ import { a as postToolUse, n as init_post_tool } from "./post-tool-RuXx9ji5.mjs";
7
+ import "./store-CvwAL2ah.mjs";
8
+ import "./review-gate-BpjBk-63.mjs";
9
+ import "./state-Cih-8_Zc.mjs";
10
+ init_post_tool();
11
+ export { postToolUse };
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as __esmMin, r as __exportAll } from "./chunk-CAm0Jl7e.mjs";
3
- import { a as effectiveStatus, c as readActive, g as writeActiveState, l as readActiveState, m as transitionStatus, s as init_types$1, t as SpecDir } from "./types-DyBixMZ0.mjs";
4
- import { n as init_audit, t as appendAudit } from "./audit-DmNpxVt4.mjs";
5
- import { a as getKnowledgeStats, d as promoteSubType, l as init_knowledge, m as upsertKnowledge, o as getPromotionCandidates, r as getKnowledgeByID } from "./knowledge-C7rEfFSX.mjs";
6
- import { i as searchKnowledgeFTS, r as init_fts, t as detectKnowledgeConflicts } from "./fts-DICqcpG_.mjs";
7
- import { n as init_project, t as detectProject } from "./project-DCKke4_Q.mjs";
8
- import { a as notifyUser, i as init_dispatcher } from "./dispatcher-Bm43S1GQ.mjs";
9
- import { a as trackHitCounts, i as searchPipeline, n as init_directives, o as truncate, r as init_helpers, t as emitDirectives } from "./directives-Drv78LF2.mjs";
10
- import { n as openDefaultCached, t as init_store } from "./store-BffM-bi-.mjs";
11
- import { a as writeReviewGate, d as init_spec_guard, n as init_review_gate, p as isSpecFilePath } from "./review-gate-P_eycqEX.mjs";
3
+ import { a as effectiveStatus, c as readActive, g as writeActiveState, l as readActiveState, m as transitionStatus, s as init_types$1, t as SpecDir } from "./types-zr3tpkx_.mjs";
4
+ import { n as init_audit, t as appendAudit } from "./audit-BUfNnVyn.mjs";
5
+ import { a as getKnowledgeStats, d as promoteSubType, l as init_knowledge, m as upsertKnowledge, o as getPromotionCandidates, r as getKnowledgeByID } from "./knowledge-BAxf0Ool.mjs";
6
+ import { i as searchKnowledgeFTS, r as init_fts, t as detectKnowledgeConflicts } from "./fts-DsQAcHWr.mjs";
7
+ import { n as init_project, s as resolveOrRegisterProject } from "./project-BPGWTiJo.mjs";
8
+ import { a as notifyUser, i as init_dispatcher } from "./dispatcher-CN_L4EfX.mjs";
9
+ import { a as trackHitCounts, i as searchPipeline, n as init_directives, o as truncate, r as init_helpers, t as emitDirectives } from "./directives-1LA3fP9f.mjs";
10
+ import { n as openDefaultCached, t as init_store } from "./store-CvwAL2ah.mjs";
11
+ import { a as writeReviewGate, d as init_spec_guard, n as init_review_gate, p as isSpecFilePath } from "./review-gate-BpjBk-63.mjs";
12
12
  import { f as writeStateText, i as parseWaveProgress, o as readStateText, p as writeWaveProgress, r as init_state, s as readWaveProgress, t as addWorkedSlug } from "./state-Cih-8_Zc.mjs";
13
13
  import { mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
14
14
  import { join } from "node:path";
@@ -155,14 +155,76 @@ function writeKnowledgeFile(projectPath, subType, id, entry) {
155
155
  atomicWriteSync(join(projectPath, ".alfred", "knowledge", filePath), `${JSON.stringify(entry, null, 2)}\n`);
156
156
  return filePath;
157
157
  }
158
+ /**
159
+ * Build embedding text optimized per sub_type for vector search.
160
+ * Truncated to MAX_EMBEDDING_LENGTH to avoid model token limits.
161
+ */
162
+ function buildEmbeddingText(subType, params) {
163
+ let parts;
164
+ switch (subType) {
165
+ case "decision":
166
+ parts = [
167
+ params.title ?? "",
168
+ params.context_text ?? "",
169
+ params.decision ?? "",
170
+ params.alternatives ?? ""
171
+ ];
172
+ break;
173
+ case "pattern":
174
+ parts = [
175
+ params.title ?? "",
176
+ params.context_text ?? "",
177
+ params.pattern ?? "",
178
+ params.application_conditions ?? ""
179
+ ];
180
+ break;
181
+ case "rule":
182
+ parts = [
183
+ params.title ?? "",
184
+ params.text ?? "",
185
+ params.rationale ?? "",
186
+ params.category ?? ""
187
+ ];
188
+ break;
189
+ default: parts = [params.title ?? "", params.context_text ?? ""];
190
+ }
191
+ const text = parts.filter(Boolean).join(" ");
192
+ return text.length > MAX_EMBEDDING_LENGTH ? text.slice(0, MAX_EMBEDDING_LENGTH) : text;
193
+ }
158
194
  function parseTags(tagsStr) {
159
195
  return (tagsStr ?? "").split(",").map((t) => t.trim()).filter(Boolean);
160
196
  }
197
+ /**
198
+ * Detect serialized JSON objects/arrays dumped into text fields.
199
+ */
200
+ function looksLikeJSON(s) {
201
+ const t = s.trim();
202
+ return t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]");
203
+ }
204
+ /**
205
+ * Validate knowledge fields before saving. Rejects garbage data early.
206
+ */
207
+ function validateKnowledgeFields(params) {
208
+ if (params.title && looksLikeJSON(params.title)) return "title must be natural language, not JSON. Provide a concise summary sentence.";
209
+ if (params.title && params.title.length > MAX_TITLE_LENGTH) return `title must be ${MAX_TITLE_LENGTH} characters or less (got ${params.title.length})`;
210
+ if (params.label && looksLikeJSON(params.label)) return "label must be natural language, not JSON.";
211
+ for (const [field, value] of [
212
+ ["decision", params.decision],
213
+ ["pattern", params.pattern],
214
+ ["text", params.text],
215
+ ["reasoning", params.reasoning]
216
+ ]) if (value && looksLikeJSON(value)) return `${field} must be natural language, not JSON. Describe the ${field} in plain text.`;
217
+ if (params.title?.trim() === "") return "title must not be empty or whitespace-only";
218
+ if (params.label?.trim() === "") return "label must not be empty or whitespace-only";
219
+ return null;
220
+ }
161
221
  async function ledgerSave(store, emb, params) {
162
222
  const subType = params.sub_type;
163
223
  if (!subType || !VALID_SUB_TYPES.includes(subType)) return errorResult("sub_type must be decision, pattern, or rule");
164
224
  if (!params.title) return errorResult("title is required for save");
165
225
  if (!params.label) return errorResult("label is required for save");
226
+ const validationError = validateKnowledgeFields(params);
227
+ if (validationError) return errorResult(validationError);
166
228
  const now = (/* @__PURE__ */ new Date()).toISOString();
167
229
  const lang = toLang();
168
230
  const tags = parseTags(params.tags);
@@ -231,17 +293,15 @@ async function ledgerSave(store, emb, params) {
231
293
  }
232
294
  const projectPath = params.project_path ?? process.cwd();
233
295
  const filePath = writeKnowledgeFile(projectPath, subType, id, entry);
234
- const projInfo = detectProject(projectPath);
296
+ const projInfo = resolveOrRegisterProject(store, projectPath);
235
297
  const { id: dbId, changed } = upsertKnowledge(store, {
236
298
  id: 0,
299
+ projectId: projInfo.id,
237
300
  filePath,
238
301
  contentHash: "",
239
302
  title: params.title,
240
303
  content: JSON.stringify(entry),
241
304
  subType,
242
- projectRemote: projInfo.remote,
243
- projectPath: projInfo.path,
244
- projectName: projInfo.name,
245
305
  branch: projInfo.branch,
246
306
  createdAt: "",
247
307
  updatedAt: "",
@@ -252,9 +312,9 @@ async function ledgerSave(store, emb, params) {
252
312
  let embeddingStatus = "none";
253
313
  if (emb && changed) {
254
314
  const model = emb.model;
255
- const embText = `${params.title} ${params.context_text ?? ""} ${params.decision ?? params.pattern ?? params.text ?? ""}`;
315
+ const embText = buildEmbeddingText(subType, params);
256
316
  emb.embedForStorage(embText).then(async (vec) => {
257
- const { insertEmbedding } = await import("./vectors-DHZGQ096.mjs").then((n) => (n.t(), n.i));
317
+ const { insertEmbedding } = await import("./vectors-CC4__7vH.mjs").then((n) => (n.t(), n.a));
258
318
  insertEmbedding(store, "knowledge", dbId, model, vec);
259
319
  }).catch((err) => {
260
320
  console.error(`[alfred] embedding failed for ${dbId}: ${err}`);
@@ -348,12 +408,15 @@ async function ledgerReflect(store, emb, _params) {
348
408
  lang
349
409
  });
350
410
  }
411
+ var MAX_EMBEDDING_LENGTH, MAX_TITLE_LENGTH;
351
412
  var init_ledger = __esmMin((() => {
352
413
  init_fts();
353
414
  init_knowledge();
354
415
  init_project();
355
416
  init_types();
356
417
  init_helpers();
418
+ MAX_EMBEDDING_LENGTH = 1500;
419
+ MAX_TITLE_LENGTH = 200;
357
420
  }));
358
421
  //#endregion
359
422
  //#region src/mcp/knowledge-extractor.ts
@@ -398,19 +461,18 @@ function stringifyResponse(response) {
398
461
  }
399
462
  }
400
463
  function saveKnowledgeEntries(store, projectPath, entries, subType) {
401
- const proj = detectProject(projectPath);
464
+ const proj = resolveOrRegisterProject(store, projectPath);
402
465
  let saved = 0;
403
466
  for (const entry of entries) try {
467
+ const filePath = writeKnowledgeFile(projectPath, subType, entry.id, entry);
404
468
  if (upsertKnowledge(store, {
405
469
  id: 0,
406
- filePath: writeKnowledgeFile(projectPath, subType, entry.id, entry),
470
+ projectId: proj.id,
471
+ filePath,
407
472
  contentHash: "",
408
473
  title: entry.title,
409
474
  content: JSON.stringify(entry),
410
475
  subType,
411
- projectRemote: proj.remote,
412
- projectPath: proj.path,
413
- projectName: proj.name,
414
476
  branch: proj.branch,
415
477
  createdAt: "",
416
478
  updatedAt: "",
@@ -575,11 +637,11 @@ async function handleBashResult(ev, items, signal) {
575
637
  if (isGitCommit(response.stdout ?? "") && !signal.aborted) {
576
638
  let appendedFiles = /* @__PURE__ */ new Set();
577
639
  try {
578
- const { handleLivingSpec } = await import("./living-spec-C5kafrYy.mjs");
640
+ const { handleLivingSpec } = await import("./living-spec-CljcxM8t.mjs");
579
641
  appendedFiles = handleLivingSpec(ev.cwd);
580
642
  } catch {}
581
643
  try {
582
- const { detectDrift } = await import("./drift-PySAVqkr.mjs");
644
+ const { detectDrift } = await import("./drift-B5k7Dh4l.mjs");
583
645
  detectDrift(ev.cwd, appendedFiles, items);
584
646
  } catch {}
585
647
  await checkKnowledgeConflicts(items);
@@ -10,7 +10,7 @@ async function main() {
10
10
  } catch {}
11
11
  try {
12
12
  const Database = (await import("better-sqlite3")).default;
13
- const { migrate } = await import("./schema-BoqQZmCK.mjs");
13
+ const { migrate } = await import("./schema-CkIoZpfo.mjs");
14
14
  const dbPath = join(dbDir, "alfred.db");
15
15
  const db = new Database(dbPath);
16
16
  db.pragma("journal_mode = WAL");