claude-alfred 0.1.0 → 0.2.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 (57) hide show
  1. package/content/hooks/hooks.json +12 -0
  2. package/dist/{epic-CdRKNGvP.mjs → audit-DM4QaS4L.mjs} +16 -3
  3. package/dist/cli.mjs +7 -7
  4. package/dist/directives-D2A8mdOs.mjs +37 -0
  5. package/dist/{dispatcher-BzOdcjaa.mjs → dispatcher-DNBIBPJS.mjs} +12 -5
  6. package/dist/{fts-BDdUbNfM.mjs → fts-WNIfvJ7g.mjs} +5 -5
  7. package/dist/{helpers-BsdW4kgn.mjs → helpers-BR57P8BX.mjs} +25 -23
  8. package/dist/{types-C3butmI8.mjs → knowledge-DACIiaOh.mjs} +210 -1
  9. package/dist/post-tool-DCMLZ8BU.mjs +291 -0
  10. package/dist/{pre-compact-Cmg9kprV.mjs → pre-compact-BtHyMSFR.mjs} +27 -16
  11. package/dist/{server-DF7CXxKi.mjs → server-BVBSt-GA.mjs} +202 -25
  12. package/dist/{server-Dsf47Pd4.mjs → server-HMIKVikr.mjs} +37 -15
  13. package/dist/{session-start-DUYF6E0V.mjs → session-start-DCp2_K0Z.mjs} +42 -9
  14. package/dist/user-prompt-BZ9VhKi6.mjs +276 -0
  15. package/dist/{vectors-DvuAqDeO.mjs → vectors-DByIf3R2.mjs} +1 -1
  16. package/package.json +1 -1
  17. package/web/dist/assets/activity-BQIta63V.js +1 -0
  18. package/web/dist/assets/api-Cq0fYMlI.js +1 -0
  19. package/web/dist/assets/card-BcSoTIRX.js +1 -0
  20. package/web/dist/assets/circle-BL_hGKys.js +1 -0
  21. package/web/dist/assets/circle-dot-BPxCviEE.js +1 -0
  22. package/web/dist/assets/createLucideIcon-BsT1VhJi.js +1 -0
  23. package/web/dist/assets/dist-BoQ-HzPG.js +1 -0
  24. package/web/dist/assets/index-CT2wUCM0.js +10 -0
  25. package/web/dist/assets/index-D3MHfJUi.css +1 -0
  26. package/web/dist/assets/knowledge-BVglB57u.js +10 -0
  27. package/web/dist/assets/lib-C3W60Lg0.js +58 -0
  28. package/web/dist/assets/{link-BSgD_zxQ.js → link-DqICqa6t.js} +1 -1
  29. package/web/dist/assets/{matchContext-CO01nzZ3.js → matchContext-Djmy2MvT.js} +1 -1
  30. package/web/dist/assets/{progress-DBmt_Ww6.js → progress-Dq-nvFJj.js} +1 -1
  31. package/web/dist/assets/routes-FGDdpj0K.js +1 -0
  32. package/web/dist/assets/{separator-5sy8HYz5.js → separator-CMQXCJRe.js} +1 -1
  33. package/web/dist/assets/{skeleton-D7GRd6oJ.js → skeleton-CG9s_VrV.js} +1 -1
  34. package/web/dist/assets/tasks-k0AEdG6F.js +1 -0
  35. package/web/dist/assets/tasks._slug-CDoTqYi5.js +42 -0
  36. package/web/dist/assets/utils-muoz4bGA.js +1 -0
  37. package/web/dist/index.html +6 -6
  38. package/dist/audit-DujZ6YAy.mjs +0 -18
  39. package/dist/knowledge-CCCixwb8.mjs +0 -156
  40. package/dist/post-tool-qemgso2b.mjs +0 -88
  41. package/dist/user-prompt-BDeST0mR.mjs +0 -144
  42. package/web/dist/assets/activity-UyW12k7Z.js +0 -1
  43. package/web/dist/assets/api-BI8AW-mC.js +0 -1
  44. package/web/dist/assets/dist-BHj_gZG8.js +0 -1
  45. package/web/dist/assets/dist-DDZSXOC-.js +0 -1
  46. package/web/dist/assets/index-B9C85vN2.js +0 -10
  47. package/web/dist/assets/index-bIyYMf1a.css +0 -1
  48. package/web/dist/assets/knowledge-DmvXTX67.js +0 -5
  49. package/web/dist/assets/routes-zEN1XNFl.js +0 -1
  50. package/web/dist/assets/scroll-area-DPCDB42s.js +0 -45
  51. package/web/dist/assets/tabs-VSkG1f0-.js +0 -1
  52. package/web/dist/assets/tasks-CKNc1U7M.js +0 -1
  53. package/web/dist/assets/tasks._slug-DPzi78wf.js +0 -8
  54. package/web/dist/assets/utils-Dw49HYRP.js +0 -1
  55. /package/dist/{embedder-BshPIMrW.mjs → embedder-qPO74Ar-.mjs} +0 -0
  56. /package/dist/{project-CpgK3fwQ.mjs → project-Djp8-Djz.mjs} +0 -0
  57. /package/dist/{store-Clcihees.mjs → store-wuybYYaO.mjs} +0 -0
@@ -36,6 +36,18 @@
36
36
  }
37
37
  ]
38
38
  }
39
+ ],
40
+ "PostToolUse": [
41
+ {
42
+ "hooks": [
43
+ {
44
+ "command": "alfred hook PostToolUse",
45
+ "statusMessage": "alfred: checking context...",
46
+ "timeout": 5,
47
+ "type": "command"
48
+ }
49
+ ]
50
+ }
39
51
  ]
40
52
  }
41
53
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { n as VALID_SLUG, p as require_dist } from "./types-C3butmI8.mjs";
3
- import { mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import { E as require_dist, S as rootDir, m as VALID_SLUG } from "./knowledge-DACIiaOh.mjs";
3
+ import { appendFileSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  //#region src/epic/index.ts
6
6
  var import_dist = require_dist();
@@ -224,4 +224,17 @@ function writeActiveEpics(projectPath, state) {
224
224
  renameSync(tmp, path);
225
225
  }
226
226
  //#endregion
227
- export { removeEpic as a, unlinkTaskFromAllEpics as c, nextActionable as i, initEpic as n, syncTaskStatus as o, listAllEpics as r, topologicalOrder as s, EpicDir as t };
227
+ //#region src/spec/audit.ts
228
+ function appendAudit(projectPath, entry) {
229
+ try {
230
+ const dir = rootDir(projectPath);
231
+ mkdirSync(dir, { recursive: true });
232
+ const line = JSON.stringify({
233
+ ...entry,
234
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
235
+ });
236
+ appendFileSync(join(dir, "audit.jsonl"), line + "\n");
237
+ } catch {}
238
+ }
239
+ //#endregion
240
+ export { nextActionable as a, topologicalOrder as c, listAllEpics as i, unlinkTaskFromAllEpics as l, EpicDir as n, removeEpic as o, initEpic as r, syncTaskStatus as s, appendAudit as t };
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-Clcihees.mjs");
373
- const { Embedder } = await import("./embedder-BshPIMrW.mjs");
374
- const { serveMCP } = await import("./server-Dsf47Pd4.mjs");
372
+ const { Store } = await import("./store-wuybYYaO.mjs");
373
+ const { Embedder } = await import("./embedder-qPO74Ar-.mjs");
374
+ const { serveMCP } = await import("./server-HMIKVikr.mjs");
375
375
  const store = Store.openDefault();
376
376
  let emb = null;
377
377
  try {
@@ -397,9 +397,9 @@ const main = defineCommand({
397
397
  }
398
398
  },
399
399
  async run({ args }) {
400
- const { Store } = await import("./store-Clcihees.mjs");
401
- const { Embedder } = await import("./embedder-BshPIMrW.mjs");
402
- const { startDashboard } = await import("./server-DF7CXxKi.mjs");
400
+ const { Store } = await import("./store-wuybYYaO.mjs");
401
+ const { Embedder } = await import("./embedder-qPO74Ar-.mjs");
402
+ const { startDashboard } = await import("./server-BVBSt-GA.mjs");
403
403
  const projectPath = process.cwd();
404
404
  const store = Store.openDefault();
405
405
  let emb = null;
@@ -422,7 +422,7 @@ const main = defineCommand({
422
422
  description: "Event name"
423
423
  } },
424
424
  async run({ args }) {
425
- const { runHook } = await import("./dispatcher-BzOdcjaa.mjs");
425
+ const { runHook } = await import("./dispatcher-DNBIBPJS.mjs").then((n) => n.t);
426
426
  await runHook(args.event);
427
427
  }
428
428
  }),
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { n as emitAdditionalContext } from "./dispatcher-DNBIBPJS.mjs";
3
+ //#region src/hooks/directives.ts
4
+ const LEVEL_ORDER = {
5
+ DIRECTIVE: 0,
6
+ WARNING: 1,
7
+ CONTEXT: 2
8
+ };
9
+ const MAX_DIRECTIVES = 3;
10
+ /**
11
+ * Build a single additionalContext string from directive items.
12
+ * Order: DIRECTIVE → WARNING → CONTEXT.
13
+ * Max 3 DIRECTIVE items (NFR-5). Excess DIRECTIVEs downgraded to WARNING.
14
+ */
15
+ function buildDirectiveOutput(items) {
16
+ if (items.length === 0) return "";
17
+ let directiveCount = 0;
18
+ return items.map((item) => {
19
+ if (item.level === "DIRECTIVE") {
20
+ directiveCount++;
21
+ if (directiveCount > MAX_DIRECTIVES) return {
22
+ level: "WARNING",
23
+ message: item.message
24
+ };
25
+ }
26
+ return item;
27
+ }).slice().sort((a, b) => LEVEL_ORDER[a.level] - LEVEL_ORDER[b.level]).map((item) => `[${item.level}] ${item.message}`).join("\n");
28
+ }
29
+ /**
30
+ * Emit directives via single emitAdditionalContext call (NFR-4).
31
+ */
32
+ function emitDirectives(eventName, items) {
33
+ const output = buildDirectiveOutput(items);
34
+ if (output) emitAdditionalContext(eventName, output);
35
+ }
36
+ //#endregion
37
+ export { emitDirectives as t };
@@ -1,6 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ import { O as __exportAll } from "./knowledge-DACIiaOh.mjs";
2
3
  import { resolve } from "node:path";
3
4
  //#region src/hooks/dispatcher.ts
5
+ var dispatcher_exports = /* @__PURE__ */ __exportAll({
6
+ emitAdditionalContext: () => emitAdditionalContext,
7
+ extractSection: () => extractSection,
8
+ notifyUser: () => notifyUser,
9
+ runHook: () => runHook
10
+ });
4
11
  function notifyUser(format, ...args) {
5
12
  let i = 0;
6
13
  const msg = format.replace(/%[svd]/g, () => i < args.length ? String(args[i++]) : "");
@@ -74,20 +81,20 @@ async function runHook(event) {
74
81
  }
75
82
  }
76
83
  async function handleSessionStart(ev, signal) {
77
- const { sessionStart } = await import("./session-start-DUYF6E0V.mjs");
84
+ const { sessionStart } = await import("./session-start-DCp2_K0Z.mjs");
78
85
  await sessionStart(ev, signal);
79
86
  }
80
87
  async function handlePreCompact(ev, signal) {
81
- const { preCompact } = await import("./pre-compact-Cmg9kprV.mjs");
88
+ const { preCompact } = await import("./pre-compact-BtHyMSFR.mjs");
82
89
  await preCompact(ev, signal);
83
90
  }
84
91
  async function handleUserPromptSubmit(ev, signal) {
85
- const { userPromptSubmit } = await import("./user-prompt-BDeST0mR.mjs");
92
+ const { userPromptSubmit } = await import("./user-prompt-BZ9VhKi6.mjs");
86
93
  await userPromptSubmit(ev, signal);
87
94
  }
88
95
  async function handlePostToolUse(ev, signal) {
89
- const { postToolUse } = await import("./post-tool-qemgso2b.mjs");
96
+ const { postToolUse } = await import("./post-tool-DCMLZ8BU.mjs");
90
97
  await postToolUse(ev, signal);
91
98
  }
92
99
  //#endregion
93
- export { emitAdditionalContext, extractSection, notifyUser, runHook };
100
+ export { notifyUser as i, emitAdditionalContext as n, extractSection as r, dispatcher_exports as t };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { c as mapRow, r as getKnowledgeByIDs, u as searchKnowledgeKeyword } from "./knowledge-CCCixwb8.mjs";
3
- import { n as deserializeFloat32, t as cosineSimilarity } from "./vectors-DvuAqDeO.mjs";
2
+ import { c as mapRow, r as getKnowledgeByIDs, u as searchKnowledgeKeyword } from "./knowledge-DACIiaOh.mjs";
3
+ import { n as deserializeFloat32, t as cosineSimilarity } from "./vectors-DByIf3R2.mjs";
4
4
  //#region src/store/fts.ts
5
5
  function subTypeHalfLife(subType) {
6
6
  switch (subType) {
@@ -92,13 +92,13 @@ function fuzzySearchKnowledge(store, queryWords, limit, exclude) {
92
92
  }
93
93
  return docs;
94
94
  }
95
- function detectKnowledgeConflicts(store, threshold = .7) {
95
+ function detectKnowledgeConflicts(store, threshold = .7, limit = 1e3) {
96
96
  const docs = store.db.prepare(`
97
97
  SELECT e.source_id, e.vector FROM embeddings e
98
98
  JOIN knowledge_index k ON k.id = e.source_id
99
99
  WHERE e.source = 'knowledge' AND k.enabled = 1
100
- LIMIT 1000
101
- `).all().map((r) => ({
100
+ LIMIT ?
101
+ `).all(limit).map((r) => ({
102
102
  id: r.source_id,
103
103
  vec: deserializeFloat32(r.vector)
104
104
  }));
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { o as incrementHitCount, r as getKnowledgeByIDs, u as searchKnowledgeKeyword } from "./knowledge-CCCixwb8.mjs";
3
- import { r as vectorSearchKnowledge } from "./vectors-DvuAqDeO.mjs";
4
- import { i as subTypeHalfLife, n as searchKnowledgeFTS, r as subTypeBoost } from "./fts-BDdUbNfM.mjs";
2
+ import { r as getKnowledgeByIDs, s as incrementHitCount, u as searchKnowledgeKeyword } from "./knowledge-DACIiaOh.mjs";
3
+ import { r as vectorSearchKnowledge } from "./vectors-DByIf3R2.mjs";
4
+ import { i as subTypeHalfLife, n as searchKnowledgeFTS, r as subTypeBoost } from "./fts-WNIfvJ7g.mjs";
5
5
  //#region src/mcp/helpers.ts
6
6
  const RECENCY_FLOOR = .5;
7
7
  function truncate(s, maxLen) {
@@ -19,28 +19,30 @@ function recencyFactor(createdAt, subType, now) {
19
19
  const factor = Math.exp(-Math.LN2 * ageDays / halfLife);
20
20
  return factor < RECENCY_FLOOR ? RECENCY_FLOOR : factor;
21
21
  }
22
- function applyRecencySignal(docs, now) {
23
- if (docs.length === 0) return docs;
22
+ function applyRecencySignal(docs, method, now) {
23
+ if (docs.length === 0) return [];
24
24
  const scored = docs.map((doc, i) => {
25
25
  const posScore = 1 / (i + 1);
26
26
  const rf = recencyFactor(doc.createdAt, doc.subType, now);
27
27
  const stb = subTypeBoost(doc.subType);
28
28
  return {
29
29
  doc,
30
- score: posScore * rf * stb
30
+ score: Math.round(posScore * rf * stb * 100) / 100,
31
+ matchReason: method
31
32
  };
32
33
  });
33
34
  if (scored.length > 1) scored.sort((a, b) => b.score - a.score);
34
- return scored.map((s) => s.doc);
35
+ return scored;
35
36
  }
36
- async function searchPipeline(store, emb, query, limit, overRetrieve) {
37
+ async function searchPipeline(store, emb, query, limit, overRetrieve, precomputedVec) {
37
38
  const res = {
38
- docs: [],
39
+ scoredDocs: [],
39
40
  searchMethod: "",
40
41
  warnings: []
41
42
  };
43
+ let rawDocs = [];
42
44
  if (emb) try {
43
- const matches = vectorSearchKnowledge(store, await emb.embedForSearch(query), overRetrieve);
45
+ const matches = vectorSearchKnowledge(store, precomputedVec ?? await emb.embedForSearch(query), overRetrieve);
44
46
  if (matches.length > 0) {
45
47
  const ids = matches.map((m) => m.sourceId);
46
48
  const docs = getKnowledgeByIDs(store, ids);
@@ -50,15 +52,15 @@ async function searchPipeline(store, emb, query, limit, overRetrieve) {
50
52
  const d = docMap.get(id);
51
53
  if (d) ordered.push(d);
52
54
  }
53
- res.docs = ordered;
55
+ rawDocs = ordered;
54
56
  res.searchMethod = "vector";
55
- if (res.docs.length > limit) try {
56
- const contents = res.docs.map((d) => d.title + "\n" + d.content);
57
+ if (rawDocs.length > limit) try {
58
+ const contents = rawDocs.map((d) => d.title + "\n" + d.content);
57
59
  const reranked = await emb.rerank(query, contents, limit);
58
60
  if (reranked.length > 0) {
59
61
  const reorderedDocs = [];
60
- for (const r of reranked) if (r.index >= 0 && r.index < res.docs.length) reorderedDocs.push(res.docs[r.index]);
61
- res.docs = reorderedDocs;
62
+ for (const r of reranked) if (r.index >= 0 && r.index < rawDocs.length) reorderedDocs.push(rawDocs[r.index]);
63
+ rawDocs = reorderedDocs;
62
64
  res.searchMethod = "vector+rerank";
63
65
  }
64
66
  } catch (err) {
@@ -68,27 +70,27 @@ async function searchPipeline(store, emb, query, limit, overRetrieve) {
68
70
  } catch (err) {
69
71
  res.warnings.push(`vector embedding failed: ${err}`);
70
72
  }
71
- if (res.docs.length === 0) {
73
+ if (rawDocs.length === 0) {
72
74
  res.searchMethod = "fts5";
73
75
  try {
74
- res.docs = searchKnowledgeFTS(store, query, limit);
76
+ rawDocs = searchKnowledgeFTS(store, query, limit);
75
77
  } catch (err) {
76
78
  res.warnings.push(`fts5 search failed: ${err}`);
77
79
  res.searchMethod = "keyword";
78
80
  try {
79
- res.docs = searchKnowledgeKeyword(store, query, limit);
81
+ rawDocs = searchKnowledgeKeyword(store, query, limit);
80
82
  } catch (err2) {
81
83
  res.warnings.push(`keyword search failed: ${err2}`);
82
84
  }
83
85
  }
84
86
  }
85
- res.docs = applyRecencySignal(res.docs, /* @__PURE__ */ new Date());
86
- if (res.docs.length > limit) res.docs = res.docs.slice(0, limit);
87
+ res.scoredDocs = applyRecencySignal(rawDocs, res.searchMethod, /* @__PURE__ */ new Date());
88
+ if (res.scoredDocs.length > limit) res.scoredDocs = res.scoredDocs.slice(0, limit);
87
89
  return res;
88
90
  }
89
- function trackHitCounts(store, docs) {
90
- if (docs.length === 0) return;
91
- incrementHitCount(store, docs.filter((d) => d.id > 0).map((d) => d.id));
91
+ function trackHitCounts(store, scoredDocs) {
92
+ if (scoredDocs.length === 0) return;
93
+ incrementHitCount(store, scoredDocs.filter((sd) => sd.doc.id > 0).map((sd) => sd.doc.id));
92
94
  }
93
95
  //#endregion
94
96
  export { trackHitCounts as n, truncate as r, searchPipeline as t };
@@ -2,6 +2,7 @@
2
2
  import { createRequire } from "node:module";
3
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { basename, join } from "node:path";
5
+ import { createHash } from "node:crypto";
5
6
  //#region \0rolldown/runtime.js
6
7
  var __create = Object.create;
7
8
  var __defProp = Object.defineProperty;
@@ -6799,6 +6800,58 @@ function reviewStatusFor(projectPath, taskSlug) {
6799
6800
  return "";
6800
6801
  }
6801
6802
  }
6803
+ /**
6804
+ * Verify that a valid review JSON file exists with status=approved and zero unresolved comments.
6805
+ * Does NOT read _active.md (no overlap with reviewStatusFor).
6806
+ *
6807
+ * Legacy mode: if reviews/ directory is absent → valid (backward compat).
6808
+ * If reviews/ exists but is empty → invalid.
6809
+ */
6810
+ function verifyReviewFile(projectPath, taskSlug) {
6811
+ const reviewsDir = join(specsDir(projectPath), taskSlug, "reviews");
6812
+ if (!existsSync(reviewsDir)) return {
6813
+ valid: true,
6814
+ reason: "legacy: no reviews/ directory"
6815
+ };
6816
+ let files;
6817
+ try {
6818
+ files = readdirSync(reviewsDir).filter((f) => f.startsWith("review-") && f.endsWith(".json")).sort().reverse();
6819
+ } catch {
6820
+ return {
6821
+ valid: false,
6822
+ reason: "failed to read reviews/ directory"
6823
+ };
6824
+ }
6825
+ if (files.length === 0) return {
6826
+ valid: false,
6827
+ reason: "no review JSON files found in reviews/"
6828
+ };
6829
+ const latestFile = files[0];
6830
+ let reviewData;
6831
+ try {
6832
+ reviewData = JSON.parse(readFileSync(join(reviewsDir, latestFile), "utf-8"));
6833
+ } catch {
6834
+ return {
6835
+ valid: false,
6836
+ reason: `failed to parse ${latestFile}`
6837
+ };
6838
+ }
6839
+ if (reviewData.status !== "approved") return {
6840
+ valid: false,
6841
+ reason: `latest review status is "${reviewData.status ?? "unknown"}", not "approved"`
6842
+ };
6843
+ if (Array.isArray(reviewData.comments)) {
6844
+ const unresolved = reviewData.comments.filter((c) => !c.resolved).length;
6845
+ if (unresolved > 0) return {
6846
+ valid: false,
6847
+ reason: `${unresolved} unresolved review comment(s) remain`
6848
+ };
6849
+ }
6850
+ return {
6851
+ valid: true,
6852
+ reason: `verified via ${latestFile}`
6853
+ };
6854
+ }
6802
6855
  function removeTask(projectPath, taskSlug) {
6803
6856
  const state = readActiveState(projectPath);
6804
6857
  const filtered = state.tasks.filter((t) => t.slug !== taskSlug);
@@ -6820,4 +6873,160 @@ function removeTask(projectPath, taskSlug) {
6820
6873
  return false;
6821
6874
  }
6822
6875
  //#endregion
6823
- export { filesForSize as a, removeTask as c, switchActive as d, writeActiveState as f, __toESM as g, __exportAll as h, detectSize as i, reviewStatusFor as l, __commonJSMin as m, VALID_SLUG as n, readActive as o, require_dist as p, completeTask as r, readActiveState as s, SpecDir as t, rootDir as u };
6876
+ //#region src/store/knowledge.ts
6877
+ function contentHash(content) {
6878
+ return createHash("sha256").update(content).digest("hex");
6879
+ }
6880
+ function upsertKnowledge(store, row) {
6881
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6882
+ if (!row.createdAt) row.createdAt = now;
6883
+ row.updatedAt = now;
6884
+ row.contentHash = contentHash(row.content);
6885
+ 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);
6886
+ if (existing && existing.content_hash === row.contentHash) {
6887
+ row.id = existing.id;
6888
+ return {
6889
+ id: existing.id,
6890
+ changed: false
6891
+ };
6892
+ }
6893
+ const result = store.db.prepare(`
6894
+ INSERT INTO knowledge_index
6895
+ (file_path, content_hash, title, content, sub_type,
6896
+ project_remote, project_path, project_name, branch,
6897
+ created_at, updated_at, hit_count, last_accessed, enabled)
6898
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, '', 1)
6899
+ ON CONFLICT(project_remote, project_path, file_path) DO UPDATE SET
6900
+ content_hash = excluded.content_hash,
6901
+ title = excluded.title,
6902
+ content = excluded.content,
6903
+ sub_type = excluded.sub_type,
6904
+ project_name = excluded.project_name,
6905
+ branch = excluded.branch,
6906
+ updated_at = excluded.updated_at
6907
+ `).run(row.filePath, row.contentHash, row.title, row.content, row.subType, row.projectRemote, row.projectPath, row.projectName, row.branch, row.createdAt, row.updatedAt);
6908
+ const id = Number(result.lastInsertRowid);
6909
+ row.id = id;
6910
+ return {
6911
+ id,
6912
+ changed: true
6913
+ };
6914
+ }
6915
+ function getKnowledgeByID(store, id) {
6916
+ const row = store.db.prepare(`
6917
+ SELECT id, file_path, content_hash, title, content, sub_type,
6918
+ project_remote, project_path, project_name, branch,
6919
+ created_at, updated_at, hit_count, last_accessed, enabled
6920
+ FROM knowledge_index WHERE id = ?
6921
+ `).get(id);
6922
+ return row ? mapRow(row) : void 0;
6923
+ }
6924
+ function getKnowledgeByIDs(store, ids) {
6925
+ if (ids.length === 0) return [];
6926
+ const placeholders = ids.map(() => "?").join(",");
6927
+ return store.db.prepare(`
6928
+ SELECT id, file_path, content_hash, title, content, sub_type,
6929
+ project_remote, project_path, project_name, branch,
6930
+ created_at, updated_at, hit_count, last_accessed, enabled
6931
+ FROM knowledge_index WHERE id IN (${placeholders})
6932
+ `).all(...ids).map(mapRow);
6933
+ }
6934
+ function setKnowledgeEnabled(store, id, enabled) {
6935
+ store.db.prepare("UPDATE knowledge_index SET enabled = ? WHERE id = ?").run(enabled ? 1 : 0, id);
6936
+ }
6937
+ function incrementHitCount(store, ids) {
6938
+ if (ids.length === 0) return;
6939
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6940
+ const placeholders = ids.map(() => "?").join(",");
6941
+ store.db.prepare(`UPDATE knowledge_index SET hit_count = hit_count + 1, last_accessed = ?
6942
+ WHERE id IN (${placeholders})`).run(now, ...ids);
6943
+ }
6944
+ function promoteSubType(store, id, newSubType) {
6945
+ const now = (/* @__PURE__ */ new Date()).toISOString();
6946
+ if (store.db.prepare("UPDATE knowledge_index SET sub_type = ?, updated_at = ? WHERE id = ? AND enabled = 1").run(newSubType, now, id).changes === 0) throw new Error(`store: promote sub_type: knowledge ${id} not found or disabled`);
6947
+ }
6948
+ function getPromotionCandidates(store) {
6949
+ return store.db.prepare(`
6950
+ SELECT id, file_path, content_hash, title, content, sub_type,
6951
+ project_remote, project_path, project_name, branch,
6952
+ created_at, updated_at, hit_count, last_accessed, enabled
6953
+ FROM knowledge_index
6954
+ WHERE enabled = 1
6955
+ AND ((sub_type = 'general' AND hit_count >= 5)
6956
+ OR (sub_type = 'pattern' AND hit_count >= 15))
6957
+ ORDER BY hit_count DESC
6958
+ `).all().map(mapRow);
6959
+ }
6960
+ function getKnowledgeStats(store) {
6961
+ const agg = store.db.prepare("SELECT COUNT(*) as total, COALESCE(AVG(hit_count), 0) as avg_hits FROM knowledge_index WHERE enabled = 1").get();
6962
+ const bySubType = {};
6963
+ const subtypeRows = store.db.prepare("SELECT sub_type, COUNT(*) as cnt FROM knowledge_index WHERE enabled = 1 GROUP BY sub_type").all();
6964
+ for (const r of subtypeRows) bySubType[r.sub_type] = r.cnt;
6965
+ const topRows = store.db.prepare(`
6966
+ SELECT id, file_path, content_hash, title, content, sub_type,
6967
+ project_remote, project_path, project_name, branch,
6968
+ created_at, updated_at, hit_count, last_accessed, enabled
6969
+ FROM knowledge_index WHERE enabled = 1
6970
+ ORDER BY hit_count DESC LIMIT 5
6971
+ `).all();
6972
+ return {
6973
+ total: agg?.total ?? 0,
6974
+ avgHitCount: agg?.avg_hits ?? 0,
6975
+ bySubType,
6976
+ topAccessed: topRows.map(mapRow)
6977
+ };
6978
+ }
6979
+ function searchKnowledgeKeyword(store, query, limit) {
6980
+ const escaped = escapeLIKEContains(query);
6981
+ return store.db.prepare(`
6982
+ SELECT id, file_path, content_hash, title, content, sub_type,
6983
+ project_remote, project_path, project_name, branch,
6984
+ created_at, updated_at, hit_count, last_accessed, enabled
6985
+ FROM knowledge_index
6986
+ WHERE enabled = 1 AND (content LIKE ? ESCAPE '\\' OR title LIKE ? ESCAPE '\\')
6987
+ ORDER BY hit_count DESC LIMIT ?
6988
+ `).all(escaped, escaped, limit).map(mapRow);
6989
+ }
6990
+ function getRecentDecisions(store, projectRemote, projectPath, sinceISO, limit) {
6991
+ return store.db.prepare(`
6992
+ SELECT title, content, created_at FROM knowledge_index
6993
+ WHERE sub_type = 'decision'
6994
+ AND project_remote = ? AND project_path = ?
6995
+ AND created_at > ? AND enabled = 1
6996
+ ORDER BY created_at DESC LIMIT ?
6997
+ `).all(projectRemote, projectPath, sinceISO, limit).map((r) => ({
6998
+ title: r.title,
6999
+ content: r.content,
7000
+ createdAt: r.created_at
7001
+ }));
7002
+ }
7003
+ function countKnowledge(store, projectRemote, projectPath) {
7004
+ return store.db.prepare("SELECT COUNT(*) as cnt FROM knowledge_index WHERE project_remote = ? AND project_path = ? AND enabled = 1").get(projectRemote, projectPath).cnt;
7005
+ }
7006
+ function escapeLIKEContains(s) {
7007
+ s = s.replaceAll("\\", "\\\\");
7008
+ s = s.replaceAll("%", "\\%");
7009
+ s = s.replaceAll("_", "\\_");
7010
+ return `%${s}%`;
7011
+ }
7012
+ function mapRow(r) {
7013
+ return {
7014
+ id: r.id,
7015
+ filePath: r.file_path,
7016
+ contentHash: r.content_hash,
7017
+ title: r.title,
7018
+ content: r.content,
7019
+ subType: r.sub_type,
7020
+ projectRemote: r.project_remote,
7021
+ projectPath: r.project_path,
7022
+ projectName: r.project_name,
7023
+ branch: r.branch,
7024
+ createdAt: r.created_at,
7025
+ updatedAt: r.updated_at,
7026
+ hitCount: r.hit_count,
7027
+ lastAccessed: r.last_accessed,
7028
+ enabled: r.enabled === 1
7029
+ };
7030
+ }
7031
+ //#endregion
7032
+ export { switchActive as C, __commonJSMin as D, require_dist as E, __exportAll as O, rootDir as S, writeActiveState as T, filesForSize as _, getPromotionCandidates as a, removeTask as b, mapRow as c, setKnowledgeEnabled as d, upsertKnowledge as f, detectSize as g, completeTask as h, getKnowledgeStats as i, __toESM as k, promoteSubType as l, VALID_SLUG as m, getKnowledgeByID as n, getRecentDecisions as o, SpecDir as p, getKnowledgeByIDs as r, incrementHitCount as s, countKnowledge as t, searchKnowledgeKeyword as u, readActive as v, verifyReviewFile as w, reviewStatusFor as x, readActiveState as y };