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.
- package/content/hooks/hooks.json +12 -0
- package/dist/{epic-CdRKNGvP.mjs → audit-DM4QaS4L.mjs} +16 -3
- package/dist/cli.mjs +7 -7
- package/dist/directives-D2A8mdOs.mjs +37 -0
- package/dist/{dispatcher-BzOdcjaa.mjs → dispatcher-DNBIBPJS.mjs} +12 -5
- package/dist/{fts-BDdUbNfM.mjs → fts-WNIfvJ7g.mjs} +5 -5
- package/dist/{helpers-BsdW4kgn.mjs → helpers-BR57P8BX.mjs} +25 -23
- package/dist/{types-C3butmI8.mjs → knowledge-DACIiaOh.mjs} +210 -1
- package/dist/post-tool-DCMLZ8BU.mjs +291 -0
- package/dist/{pre-compact-Cmg9kprV.mjs → pre-compact-BtHyMSFR.mjs} +27 -16
- package/dist/{server-DF7CXxKi.mjs → server-BVBSt-GA.mjs} +202 -25
- package/dist/{server-Dsf47Pd4.mjs → server-HMIKVikr.mjs} +37 -15
- package/dist/{session-start-DUYF6E0V.mjs → session-start-DCp2_K0Z.mjs} +42 -9
- package/dist/user-prompt-BZ9VhKi6.mjs +276 -0
- package/dist/{vectors-DvuAqDeO.mjs → vectors-DByIf3R2.mjs} +1 -1
- package/package.json +1 -1
- package/web/dist/assets/activity-BQIta63V.js +1 -0
- package/web/dist/assets/api-Cq0fYMlI.js +1 -0
- package/web/dist/assets/card-BcSoTIRX.js +1 -0
- package/web/dist/assets/circle-BL_hGKys.js +1 -0
- package/web/dist/assets/circle-dot-BPxCviEE.js +1 -0
- package/web/dist/assets/createLucideIcon-BsT1VhJi.js +1 -0
- package/web/dist/assets/dist-BoQ-HzPG.js +1 -0
- package/web/dist/assets/index-CT2wUCM0.js +10 -0
- package/web/dist/assets/index-D3MHfJUi.css +1 -0
- package/web/dist/assets/knowledge-BVglB57u.js +10 -0
- package/web/dist/assets/lib-C3W60Lg0.js +58 -0
- package/web/dist/assets/{link-BSgD_zxQ.js → link-DqICqa6t.js} +1 -1
- package/web/dist/assets/{matchContext-CO01nzZ3.js → matchContext-Djmy2MvT.js} +1 -1
- package/web/dist/assets/{progress-DBmt_Ww6.js → progress-Dq-nvFJj.js} +1 -1
- package/web/dist/assets/routes-FGDdpj0K.js +1 -0
- package/web/dist/assets/{separator-5sy8HYz5.js → separator-CMQXCJRe.js} +1 -1
- package/web/dist/assets/{skeleton-D7GRd6oJ.js → skeleton-CG9s_VrV.js} +1 -1
- package/web/dist/assets/tasks-k0AEdG6F.js +1 -0
- package/web/dist/assets/tasks._slug-CDoTqYi5.js +42 -0
- package/web/dist/assets/utils-muoz4bGA.js +1 -0
- package/web/dist/index.html +6 -6
- package/dist/audit-DujZ6YAy.mjs +0 -18
- package/dist/knowledge-CCCixwb8.mjs +0 -156
- package/dist/post-tool-qemgso2b.mjs +0 -88
- package/dist/user-prompt-BDeST0mR.mjs +0 -144
- package/web/dist/assets/activity-UyW12k7Z.js +0 -1
- package/web/dist/assets/api-BI8AW-mC.js +0 -1
- package/web/dist/assets/dist-BHj_gZG8.js +0 -1
- package/web/dist/assets/dist-DDZSXOC-.js +0 -1
- package/web/dist/assets/index-B9C85vN2.js +0 -10
- package/web/dist/assets/index-bIyYMf1a.css +0 -1
- package/web/dist/assets/knowledge-DmvXTX67.js +0 -5
- package/web/dist/assets/routes-zEN1XNFl.js +0 -1
- package/web/dist/assets/scroll-area-DPCDB42s.js +0 -45
- package/web/dist/assets/tabs-VSkG1f0-.js +0 -1
- package/web/dist/assets/tasks-CKNc1U7M.js +0 -1
- package/web/dist/assets/tasks._slug-DPzi78wf.js +0 -8
- package/web/dist/assets/utils-Dw49HYRP.js +0 -1
- /package/dist/{embedder-BshPIMrW.mjs → embedder-qPO74Ar-.mjs} +0 -0
- /package/dist/{project-CpgK3fwQ.mjs → project-Djp8-Djz.mjs} +0 -0
- /package/dist/{store-Clcihees.mjs → store-wuybYYaO.mjs} +0 -0
package/content/hooks/hooks.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
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
|
-
|
|
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-
|
|
373
|
-
const { Embedder } = await import("./embedder-
|
|
374
|
-
const { serveMCP } = await import("./server-
|
|
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-
|
|
401
|
-
const { Embedder } = await import("./embedder-
|
|
402
|
-
const { startDashboard } = await import("./server-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
96
|
+
const { postToolUse } = await import("./post-tool-DCMLZ8BU.mjs");
|
|
90
97
|
await postToolUse(ev, signal);
|
|
91
98
|
}
|
|
92
99
|
//#endregion
|
|
93
|
-
export { emitAdditionalContext, extractSection
|
|
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-
|
|
3
|
-
import { n as deserializeFloat32, t as cosineSimilarity } from "./vectors-
|
|
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
|
|
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 {
|
|
3
|
-
import { r as vectorSearchKnowledge } from "./vectors-
|
|
4
|
-
import { i as subTypeHalfLife, n as searchKnowledgeFTS, r as subTypeBoost } from "./fts-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
55
|
+
rawDocs = ordered;
|
|
54
56
|
res.searchMethod = "vector";
|
|
55
|
-
if (
|
|
56
|
-
const contents =
|
|
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 <
|
|
61
|
-
|
|
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 (
|
|
73
|
+
if (rawDocs.length === 0) {
|
|
72
74
|
res.searchMethod = "fts5";
|
|
73
75
|
try {
|
|
74
|
-
|
|
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
|
-
|
|
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.
|
|
86
|
-
if (res.
|
|
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,
|
|
90
|
-
if (
|
|
91
|
-
incrementHitCount(store,
|
|
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
|
-
|
|
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 };
|