bikky 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +323 -0
  3. package/bin/bikky.js +6 -0
  4. package/dist/cli.d.ts +12 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +51 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/config.d.ts +59 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +141 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/config.test.d.ts +8 -0
  13. package/dist/config.test.d.ts.map +1 -0
  14. package/dist/config.test.js +449 -0
  15. package/dist/config.test.js.map +1 -0
  16. package/dist/daemon/consolidation.d.ts +60 -0
  17. package/dist/daemon/consolidation.d.ts.map +1 -0
  18. package/dist/daemon/consolidation.js +448 -0
  19. package/dist/daemon/consolidation.js.map +1 -0
  20. package/dist/daemon/extraction.d.ts +17 -0
  21. package/dist/daemon/extraction.d.ts.map +1 -0
  22. package/dist/daemon/extraction.js +465 -0
  23. package/dist/daemon/extraction.js.map +1 -0
  24. package/dist/daemon/index.d.ts +3 -0
  25. package/dist/daemon/index.d.ts.map +1 -0
  26. package/dist/daemon/index.js +3 -0
  27. package/dist/daemon/index.js.map +1 -0
  28. package/dist/daemon/loop.d.ts +3 -0
  29. package/dist/daemon/loop.d.ts.map +1 -0
  30. package/dist/daemon/loop.js +93 -0
  31. package/dist/daemon/loop.js.map +1 -0
  32. package/dist/daemon/qdrant.d.ts +103 -0
  33. package/dist/daemon/qdrant.d.ts.map +1 -0
  34. package/dist/daemon/qdrant.js +273 -0
  35. package/dist/daemon/qdrant.js.map +1 -0
  36. package/dist/daemon/qdrant.test.d.ts +8 -0
  37. package/dist/daemon/qdrant.test.d.ts.map +1 -0
  38. package/dist/daemon/qdrant.test.js +209 -0
  39. package/dist/daemon/qdrant.test.js.map +1 -0
  40. package/dist/daemon/relations.d.ts +54 -0
  41. package/dist/daemon/relations.d.ts.map +1 -0
  42. package/dist/daemon/relations.js +290 -0
  43. package/dist/daemon/relations.js.map +1 -0
  44. package/dist/daemon/staleness.d.ts +24 -0
  45. package/dist/daemon/staleness.d.ts.map +1 -0
  46. package/dist/daemon/staleness.js +63 -0
  47. package/dist/daemon/staleness.js.map +1 -0
  48. package/dist/daemon/watcher.d.ts +11 -0
  49. package/dist/daemon/watcher.d.ts.map +1 -0
  50. package/dist/daemon/watcher.js +38 -0
  51. package/dist/daemon/watcher.js.map +1 -0
  52. package/dist/daemon/watcher.test.d.ts +8 -0
  53. package/dist/daemon/watcher.test.d.ts.map +1 -0
  54. package/dist/daemon/watcher.test.js +214 -0
  55. package/dist/daemon/watcher.test.js.map +1 -0
  56. package/dist/install.d.ts +5 -0
  57. package/dist/install.d.ts.map +1 -0
  58. package/dist/install.js +49 -0
  59. package/dist/install.js.map +1 -0
  60. package/dist/install.test.d.ts +9 -0
  61. package/dist/install.test.d.ts.map +1 -0
  62. package/dist/install.test.js +126 -0
  63. package/dist/install.test.js.map +1 -0
  64. package/dist/llm/embedding.d.ts +13 -0
  65. package/dist/llm/embedding.d.ts.map +1 -0
  66. package/dist/llm/embedding.js +127 -0
  67. package/dist/llm/embedding.js.map +1 -0
  68. package/dist/llm/embedding.test.d.ts +8 -0
  69. package/dist/llm/embedding.test.d.ts.map +1 -0
  70. package/dist/llm/embedding.test.js +117 -0
  71. package/dist/llm/embedding.test.js.map +1 -0
  72. package/dist/llm/index.d.ts +4 -0
  73. package/dist/llm/index.d.ts.map +1 -0
  74. package/dist/llm/index.js +3 -0
  75. package/dist/llm/index.js.map +1 -0
  76. package/dist/llm/inference.d.ts +12 -0
  77. package/dist/llm/inference.d.ts.map +1 -0
  78. package/dist/llm/inference.js +146 -0
  79. package/dist/llm/inference.js.map +1 -0
  80. package/dist/llm/inference.test.d.ts +8 -0
  81. package/dist/llm/inference.test.d.ts.map +1 -0
  82. package/dist/llm/inference.test.js +117 -0
  83. package/dist/llm/inference.test.js.map +1 -0
  84. package/dist/llm/types.d.ts +41 -0
  85. package/dist/llm/types.d.ts.map +1 -0
  86. package/dist/llm/types.js +5 -0
  87. package/dist/llm/types.js.map +1 -0
  88. package/dist/logger.d.ts +13 -0
  89. package/dist/logger.d.ts.map +1 -0
  90. package/dist/logger.js +41 -0
  91. package/dist/logger.js.map +1 -0
  92. package/dist/mcp/api.d.ts +30 -0
  93. package/dist/mcp/api.d.ts.map +1 -0
  94. package/dist/mcp/api.js +150 -0
  95. package/dist/mcp/api.js.map +1 -0
  96. package/dist/mcp/helpers.d.ts +35 -0
  97. package/dist/mcp/helpers.d.ts.map +1 -0
  98. package/dist/mcp/helpers.js +152 -0
  99. package/dist/mcp/helpers.js.map +1 -0
  100. package/dist/mcp/helpers.test.d.ts +5 -0
  101. package/dist/mcp/helpers.test.d.ts.map +1 -0
  102. package/dist/mcp/helpers.test.js +487 -0
  103. package/dist/mcp/helpers.test.js.map +1 -0
  104. package/dist/mcp/index.d.ts +9 -0
  105. package/dist/mcp/index.d.ts.map +1 -0
  106. package/dist/mcp/index.js +67 -0
  107. package/dist/mcp/index.js.map +1 -0
  108. package/dist/mcp/taxonomy.d.ts +38 -0
  109. package/dist/mcp/taxonomy.d.ts.map +1 -0
  110. package/dist/mcp/taxonomy.js +223 -0
  111. package/dist/mcp/taxonomy.js.map +1 -0
  112. package/dist/mcp/taxonomy.test.d.ts +5 -0
  113. package/dist/mcp/taxonomy.test.d.ts.map +1 -0
  114. package/dist/mcp/taxonomy.test.js +341 -0
  115. package/dist/mcp/taxonomy.test.js.map +1 -0
  116. package/dist/mcp/tools.d.ts +6 -0
  117. package/dist/mcp/tools.d.ts.map +1 -0
  118. package/dist/mcp/tools.js +958 -0
  119. package/dist/mcp/tools.js.map +1 -0
  120. package/dist/mcp/types.d.ts +118 -0
  121. package/dist/mcp/types.d.ts.map +1 -0
  122. package/dist/mcp/types.js +5 -0
  123. package/dist/mcp/types.js.map +1 -0
  124. package/package.json +56 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Relationship Inference
3
+ *
4
+ * Periodic task that infers typed relationships between entities by analysing
5
+ * their shared facts. Runs inside the consolidation tick cadence.
6
+ *
7
+ * Flow:
8
+ * 1. Scroll all non-superseded facts → build entity co-occurrence map
9
+ * 2. Filter out pairs that already have a `kind: "relation"` fact
10
+ * 3. For the top N pairs by shared-fact count, fetch the actual shared facts
11
+ * 4. Send to a cheap LLM for a 2-4 word relationship label
12
+ * 5. Store via storeFact with kind: "relation", source: "daemon"
13
+ */
14
+ import type { BikkyConfig } from "../config.js";
15
+ import type { LogFn } from "./qdrant.js";
16
+ declare const setLogger: (fn: LogFn) => void;
17
+ /** Canonical pair key — alphabetical so (a,b) === (b,a) */
18
+ declare const pairKey: (a: string, b: string) => string;
19
+ interface CoOccurrence {
20
+ entityA: string;
21
+ entityB: string;
22
+ count: number;
23
+ factIds: string[];
24
+ }
25
+ /**
26
+ * Scroll ALL non-superseded facts in batches and build a co-occurrence map.
27
+ * Returns pairs sorted by count descending.
28
+ */
29
+ declare const buildCoOccurrenceMap: () => Promise<CoOccurrence[]>;
30
+ /**
31
+ * Get the set of entity pairs that already have a daemon-inferred relation.
32
+ * Returns a Set of pairKeys.
33
+ */
34
+ declare const getExistingRelations: () => Promise<Set<string>>;
35
+ /**
36
+ * Use LLM to infer a relationship label from shared facts.
37
+ * Returns null if the LLM can't determine a meaningful relationship.
38
+ */
39
+ declare const inferRelation: (entityA: string, entityB: string, sharedFacts: Array<{
40
+ content: string;
41
+ category: string;
42
+ }>) => Promise<{
43
+ type: string;
44
+ content: string;
45
+ } | null>;
46
+ /**
47
+ * Store an inferred relation as a memory fact.
48
+ */
49
+ declare const storeRelation: (entityA: string, entityB: string, relationType: string, content: string, sharedFactIds: string[]) => Promise<string>;
50
+ declare const tick: (config: BikkyConfig) => Promise<void>;
51
+ /** Reset state (for testing). */
52
+ declare const _reset: () => void;
53
+ export { tick, setLogger, _reset, buildCoOccurrenceMap, getExistingRelations, inferRelation, storeRelation, pairKey, };
54
+ //# sourceMappingURL=relations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../../src/daemon/relations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAiB,MAAM,aAAa,CAAC;AAOxD,QAAA,MAAM,SAAS,GAAI,IAAI,KAAK,KAAG,IAAuB,CAAC;AAcvD,2DAA2D;AAC3D,QAAA,MAAM,OAAO,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,KAAG,MAGvC,CAAC;AAEF,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,QAAA,MAAM,oBAAoB,QAAa,OAAO,CAAC,YAAY,EAAE,CA2E5D,CAAC;AAEF;;;GAGG;AACH,QAAA,MAAM,oBAAoB,QAAa,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CA8CzD,CAAC;AAqBF;;;GAGG;AACH,QAAA,MAAM,aAAa,GACjB,SAAS,MAAM,EACf,SAAS,MAAM,EACf,aAAa,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,KACxD,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAwClD,CAAC;AAEF;;GAEG;AACH,QAAA,MAAM,aAAa,GACjB,SAAS,MAAM,EACf,SAAS,MAAM,EACf,cAAc,MAAM,EACpB,SAAS,MAAM,EACf,eAAe,MAAM,EAAE,KACtB,OAAO,CAAC,MAAM,CA4BhB,CAAC;AAIF,QAAA,MAAM,IAAI,GAAU,QAAQ,WAAW,KAAG,OAAO,CAAC,IAAI,CA+DrD,CAAC;AAEF,iCAAiC;AACjC,QAAA,MAAM,MAAM,QAAO,IAElB,CAAC;AAEF,OAAO,EACL,IAAI,EACJ,SAAS,EACT,MAAM,EAEN,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,OAAO,GACR,CAAC"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Relationship Inference
3
+ *
4
+ * Periodic task that infers typed relationships between entities by analysing
5
+ * their shared facts. Runs inside the consolidation tick cadence.
6
+ *
7
+ * Flow:
8
+ * 1. Scroll all non-superseded facts → build entity co-occurrence map
9
+ * 2. Filter out pairs that already have a `kind: "relation"` fact
10
+ * 3. For the top N pairs by shared-fact count, fetch the actual shared facts
11
+ * 4. Send to a cheap LLM for a 2-4 word relationship label
12
+ * 5. Store via storeFact with kind: "relation", source: "daemon"
13
+ */
14
+ import { createHash } from "node:crypto";
15
+ import * as qdrant from "./qdrant.js";
16
+ import { chatCompletion } from "../llm/index.js";
17
+ // ─── State ───────────────────────────────────────────────────────────────────
18
+ let logFn = () => { };
19
+ let internalTickCount = 0;
20
+ const setLogger = (fn) => { logFn = fn; };
21
+ // Run every 300 internal ticks. consolidation.tick calls us every 5 daemon
22
+ // ticks (≈25 s each), so effective period ≈ 300 × 25 s ≈ 2 hours.
23
+ const TICK_INTERVAL = 300;
24
+ // Max relations to infer per cycle (keeps LLM cost bounded)
25
+ const MAX_INFER_PER_CYCLE = 3;
26
+ // Minimum shared facts before we consider inferring a relation
27
+ const MIN_SHARED_FACTS = 2;
28
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
29
+ /** Canonical pair key — alphabetical so (a,b) === (b,a) */
30
+ const pairKey = (a, b) => {
31
+ const sorted = [a, b].sort();
32
+ return `${sorted[0]}::${sorted[1]}`;
33
+ };
34
+ /**
35
+ * Scroll ALL non-superseded facts in batches and build a co-occurrence map.
36
+ * Returns pairs sorted by count descending.
37
+ */
38
+ const buildCoOccurrenceMap = async () => {
39
+ const pairMap = new Map();
40
+ let offset = null;
41
+ const BATCH = 100;
42
+ let totalScrolled = 0;
43
+ // Scroll through all non-superseded, non-relation facts
44
+ for (;;) {
45
+ const body = {
46
+ filter: {
47
+ must: [
48
+ { is_null: { key: "superseded_by" } },
49
+ ],
50
+ must_not: [
51
+ { key: "kind", match: { value: "relation" } },
52
+ ],
53
+ },
54
+ limit: BATCH,
55
+ with_payload: { include: ["entities"] },
56
+ };
57
+ if (offset)
58
+ body.offset = offset;
59
+ const result = await qdrant.qdrantRequest("POST", `/collections/${qdrant.collection}/points/scroll`, body);
60
+ const points = result.result?.points || [];
61
+ if (points.length === 0)
62
+ break;
63
+ for (const pt of points) {
64
+ const entities = pt.payload?.entities || [];
65
+ if (entities.length < 2)
66
+ continue;
67
+ // Generate all pairs from this fact's entities
68
+ for (let i = 0; i < entities.length; i++) {
69
+ for (let j = i + 1; j < entities.length; j++) {
70
+ const eA = entities[i];
71
+ const eB = entities[j];
72
+ const key = pairKey(eA, eB);
73
+ const existing = pairMap.get(key);
74
+ if (existing) {
75
+ existing.count++;
76
+ if (existing.factIds.length < 10)
77
+ existing.factIds.push(pt.id);
78
+ }
79
+ else {
80
+ const sorted = [eA, eB].sort();
81
+ pairMap.set(key, {
82
+ entityA: sorted[0],
83
+ entityB: sorted[1],
84
+ count: 1,
85
+ factIds: [pt.id],
86
+ });
87
+ }
88
+ }
89
+ }
90
+ }
91
+ totalScrolled += points.length;
92
+ offset = result.result?.next_page_offset ?? null;
93
+ if (!offset)
94
+ break;
95
+ }
96
+ logFn("DEBUG", `Relations: scrolled ${totalScrolled} facts, found ${pairMap.size} co-occurrence pairs`);
97
+ // Sort by count descending
98
+ return Array.from(pairMap.values())
99
+ .filter(p => p.count >= MIN_SHARED_FACTS)
100
+ .sort((a, b) => b.count - a.count);
101
+ };
102
+ /**
103
+ * Get the set of entity pairs that already have a daemon-inferred relation.
104
+ * Returns a Set of pairKeys.
105
+ */
106
+ const getExistingRelations = async () => {
107
+ const existing = new Set();
108
+ let offset = null;
109
+ for (;;) {
110
+ const body = {
111
+ filter: {
112
+ must: [
113
+ { key: "kind", match: { value: "relation" } },
114
+ { key: "source", match: { value: "daemon" } },
115
+ { is_null: { key: "superseded_by" } },
116
+ ],
117
+ },
118
+ limit: 100,
119
+ with_payload: { include: ["from_entity", "to_entity"] },
120
+ };
121
+ if (offset)
122
+ body.offset = offset;
123
+ const result = await qdrant.qdrantRequest("POST", `/collections/${qdrant.collection}/points/scroll`, body);
124
+ const points = result.result?.points || [];
125
+ if (points.length === 0)
126
+ break;
127
+ for (const pt of points) {
128
+ const from = pt.payload?.from_entity;
129
+ const to = pt.payload?.to_entity;
130
+ if (from && to) {
131
+ existing.add(pairKey(from, to));
132
+ }
133
+ }
134
+ offset = result.result?.next_page_offset ?? null;
135
+ if (!offset)
136
+ break;
137
+ }
138
+ logFn("DEBUG", `Relations: ${existing.size} existing daemon-inferred relations`);
139
+ return existing;
140
+ };
141
+ /**
142
+ * Fetch the full content of specific fact IDs for the LLM prompt.
143
+ */
144
+ const fetchFactContents = async (factIds) => {
145
+ const result = await qdrant.qdrantRequest("POST", `/collections/${qdrant.collection}/points`, { ids: factIds, with_payload: { include: ["content", "category"] } });
146
+ return (result.result || []).map(pt => ({
147
+ id: pt.id,
148
+ content: pt.payload?.content ?? "",
149
+ category: pt.payload?.category ?? "",
150
+ }));
151
+ };
152
+ /**
153
+ * Use LLM to infer a relationship label from shared facts.
154
+ * Returns null if the LLM can't determine a meaningful relationship.
155
+ */
156
+ const inferRelation = async (entityA, entityB, sharedFacts) => {
157
+ const factsText = sharedFacts
158
+ .map((f, i) => `${i + 1}. [${f.category}] ${f.content}`)
159
+ .join("\n");
160
+ const raw = await chatCompletion({
161
+ messages: [
162
+ {
163
+ role: "system",
164
+ content: `You infer relationships between entities based on shared facts.
165
+ Output ONLY valid JSON: { "type": "verb-phrase", "content": "one sentence description" }
166
+ The "type" should be a 1-3 word verb phrase (e.g. "owns", "uses", "works-on", "manages", "depends-on").
167
+ The "content" should be a single sentence describing the relationship.
168
+ If the facts don't suggest a clear relationship, output: { "type": null }`,
169
+ },
170
+ {
171
+ role: "user",
172
+ content: `Entity A: "${entityA}"
173
+ Entity B: "${entityB}"
174
+ Shared facts (${sharedFacts.length}):
175
+ ${factsText}
176
+
177
+ Infer the primary relationship between these two entities.`,
178
+ },
179
+ ],
180
+ temperature: 0.1,
181
+ max_tokens: 150,
182
+ });
183
+ if (!raw)
184
+ return null;
185
+ try {
186
+ const jsonStr = raw.replace(/^```json?\n?/, "").replace(/\n?```$/, "").trim();
187
+ const parsed = JSON.parse(jsonStr);
188
+ if (!parsed.type)
189
+ return null;
190
+ return { type: parsed.type, content: parsed.content || `${entityA} ${parsed.type} ${entityB}` };
191
+ }
192
+ catch {
193
+ logFn("WARN", `Relations: failed to parse LLM response for ${entityA}↔${entityB}: ${raw.slice(0, 100)}`);
194
+ return null;
195
+ }
196
+ };
197
+ /**
198
+ * Store an inferred relation as a memory fact.
199
+ */
200
+ const storeRelation = async (entityA, entityB, relationType, content, sharedFactIds) => {
201
+ const hash = createHash("sha256")
202
+ .update(`daemon-relation:${pairKey(entityA, entityB)}:${relationType}`)
203
+ .digest("hex");
204
+ const id = await qdrant.storeFact({
205
+ content,
206
+ category: "team", // relations are about entity structure
207
+ domain: "work",
208
+ kind: "relation",
209
+ entities: [entityA, entityB],
210
+ source: "daemon",
211
+ confidence: 0.7,
212
+ importance: 0.6,
213
+ content_hash: hash,
214
+ metadata: {
215
+ inferred_from: sharedFactIds.slice(0, 5).join(","),
216
+ shared_fact_count: String(sharedFactIds.length),
217
+ },
218
+ relation: {
219
+ from: entityA,
220
+ type: relationType,
221
+ to: entityB,
222
+ },
223
+ });
224
+ logFn("INFO", `Relations: inferred ${entityA} —[${relationType}]→ ${entityB} (id: ${id})`);
225
+ return id;
226
+ };
227
+ // ─── Main Tick ───────────────────────────────────────────────────────────────
228
+ const tick = async (config) => {
229
+ if (!qdrant.isReady())
230
+ return;
231
+ if (config.daemon.relation_inference_enabled === false)
232
+ return;
233
+ internalTickCount++;
234
+ if (internalTickCount % TICK_INTERVAL !== 0)
235
+ return;
236
+ logFn("DEBUG", "Relations: starting inference cycle");
237
+ try {
238
+ // 1. Build co-occurrence map
239
+ const pairs = await buildCoOccurrenceMap();
240
+ if (pairs.length === 0) {
241
+ logFn("DEBUG", "Relations: no co-occurrence pairs above threshold");
242
+ return;
243
+ }
244
+ // 2. Get existing relations to avoid duplicates
245
+ const existing = await getExistingRelations();
246
+ // 3. Find candidate pairs (not yet inferred)
247
+ const candidates = pairs.filter(p => !existing.has(pairKey(p.entityA, p.entityB)));
248
+ logFn("DEBUG", `Relations: ${candidates.length} candidate pairs (${pairs.length} total above threshold)`);
249
+ if (candidates.length === 0)
250
+ return;
251
+ // 4. Infer relations for top N candidates
252
+ let inferred = 0;
253
+ for (const candidate of candidates.slice(0, MAX_INFER_PER_CYCLE)) {
254
+ try {
255
+ const facts = await fetchFactContents(candidate.factIds);
256
+ if (facts.length < MIN_SHARED_FACTS)
257
+ continue;
258
+ const result = await inferRelation(candidate.entityA, candidate.entityB, facts);
259
+ if (!result)
260
+ continue;
261
+ // Dedup check before storing
262
+ const hash = createHash("sha256")
263
+ .update(`daemon-relation:${pairKey(candidate.entityA, candidate.entityB)}:${result.type}`)
264
+ .digest("hex");
265
+ const dedup = await qdrant.dedupCheck(result.content, hash);
266
+ if (dedup.action === "skip") {
267
+ logFn("DEBUG", `Relations: skipping duplicate ${candidate.entityA}↔${candidate.entityB}`);
268
+ continue;
269
+ }
270
+ await storeRelation(candidate.entityA, candidate.entityB, result.type, result.content, candidate.factIds);
271
+ inferred++;
272
+ }
273
+ catch (e) {
274
+ logFn("WARN", `Relations: failed to infer ${candidate.entityA}↔${candidate.entityB}: ${e.message}`);
275
+ }
276
+ }
277
+ logFn("INFO", `Relations: inference cycle complete — ${inferred} new relations from ${candidates.length} candidates`);
278
+ }
279
+ catch (e) {
280
+ logFn("ERROR", `Relations: inference cycle failed: ${e.message}`);
281
+ }
282
+ };
283
+ /** Reset state (for testing). */
284
+ const _reset = () => {
285
+ internalTickCount = 0;
286
+ };
287
+ export { tick, setLogger, _reset,
288
+ // Exported for testing
289
+ buildCoOccurrenceMap, getExistingRelations, inferRelation, storeRelation, pairKey, };
290
+ //# sourceMappingURL=relations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relations.js","sourceRoot":"","sources":["../../src/daemon/relations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Staleness scanner — queries Qdrant for facts that haven't been verified
3
+ * within the configured threshold and logs stale facts.
4
+ *
5
+ * Deduplication: tracks the last set of stale fact IDs.
6
+ * If the same set is found again, skips to prevent log flooding.
7
+ */
8
+ import type { BikkyConfig } from "../config.js";
9
+ import type { LogFn, QdrantScrollFilters, QdrantScrollResult } from "./qdrant.js";
10
+ /** Dependencies injectable for testing. */
11
+ export interface StaleDeps {
12
+ isReady: () => boolean;
13
+ scrollFacts: (filters?: QdrantScrollFilters, limit?: number) => Promise<QdrantScrollResult[]>;
14
+ }
15
+ /**
16
+ * Scan for stale facts via direct Qdrant scroll query.
17
+ * Finds facts in configured categories whose last_reinforced_at is older than threshold.
18
+ * Logs stale facts instead of writing to inbox.
19
+ */
20
+ export declare const scanStaleFacts: (config: BikkyConfig, deps?: StaleDeps) => Promise<void>;
21
+ /** Reset dedup state (for testing). */
22
+ export declare const _resetDedup: () => void;
23
+ export declare const setLogger: (log: LogFn) => void;
24
+ //# sourceMappingURL=staleness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staleness.d.ts","sourceRoot":"","sources":["../../src/daemon/staleness.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAOlF,2CAA2C;AAC3C,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,OAAO,CAAC;IACvB,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,mBAAmB,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;CAC/F;AAOD;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAU,QAAQ,WAAW,EAAE,OAAM,SAAuB,KAAG,OAAO,CAAC,IAAI,CA0CrG,CAAC;AAEF,uCAAuC;AACvC,eAAO,MAAM,WAAW,QAAO,IAA8B,CAAC;AAE9D,eAAO,MAAM,SAAS,GAAI,KAAK,KAAK,KAAG,IAAwB,CAAC"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Staleness scanner — queries Qdrant for facts that haven't been verified
3
+ * within the configured threshold and logs stale facts.
4
+ *
5
+ * Deduplication: tracks the last set of stale fact IDs.
6
+ * If the same set is found again, skips to prevent log flooding.
7
+ */
8
+ import * as qdrantMod from "./qdrant.js";
9
+ let logFn = console.log;
10
+ /** Track the last stale fact IDs to avoid duplicate logging. */
11
+ let lastStaleIds = "";
12
+ const defaultDeps = {
13
+ isReady: qdrantMod.isReady,
14
+ scrollFacts: qdrantMod.scrollFacts,
15
+ };
16
+ /**
17
+ * Scan for stale facts via direct Qdrant scroll query.
18
+ * Finds facts in configured categories whose last_reinforced_at is older than threshold.
19
+ * Logs stale facts instead of writing to inbox.
20
+ */
21
+ export const scanStaleFacts = async (config, deps = defaultDeps) => {
22
+ const threshold = config.daemon.staleness_threshold_days || 30;
23
+ const categories = ["infrastructure", "projects", "decisions"];
24
+ const limit = 3;
25
+ if (!deps.isReady()) {
26
+ logFn("DEBUG", "Staleness scan: Qdrant client not ready, skipping");
27
+ return;
28
+ }
29
+ const cutoff = new Date();
30
+ cutoff.setDate(cutoff.getDate() - threshold);
31
+ const cutoffISO = cutoff.toISOString();
32
+ logFn("DEBUG", `Staleness scan: checking ${categories.join(",")} older than ${threshold} days`);
33
+ try {
34
+ const staleFacts = await deps.scrollFacts({
35
+ categories,
36
+ olderThan: cutoffISO,
37
+ }, limit);
38
+ if (staleFacts.length > 0) {
39
+ // Deduplicate: only log if the set of stale fact IDs has changed
40
+ const currentIds = staleFacts.map((f) => f.id).sort().join(",");
41
+ if (currentIds === lastStaleIds) {
42
+ logFn("DEBUG", `Staleness scan: same ${staleFacts.length} fact(s) still stale, skipping duplicate log`);
43
+ return;
44
+ }
45
+ lastStaleIds = currentIds;
46
+ for (const f of staleFacts) {
47
+ logFn("INFO", `Stale fact [${f.category}] (${f.id}): "${f.content.slice(0, 80)}" — last reinforced: ${f.last_reinforced_at}`);
48
+ }
49
+ logFn("INFO", `Staleness scan: found ${staleFacts.length} stale fact(s)`);
50
+ }
51
+ else {
52
+ lastStaleIds = "";
53
+ logFn("DEBUG", "Staleness scan: no stale facts found");
54
+ }
55
+ }
56
+ catch (e) {
57
+ logFn("WARN", `Staleness scan failed: ${e.message}`);
58
+ }
59
+ };
60
+ /** Reset dedup state (for testing). */
61
+ export const _resetDedup = () => { lastStaleIds = ""; };
62
+ export const setLogger = (log) => { logFn = log; };
63
+ //# sourceMappingURL=staleness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staleness.js","sourceRoot":"","sources":["../../src/daemon/staleness.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,SAAS,MAAM,aAAa,CAAC;AAIzC,IAAI,KAAK,GAAU,OAAO,CAAC,GAAuB,CAAC;AAEnD,gEAAgE;AAChE,IAAI,YAAY,GAAW,EAAE,CAAC;AAQ9B,MAAM,WAAW,GAAc;IAC7B,OAAO,EAAE,SAAS,CAAC,OAAO;IAC1B,WAAW,EAAE,SAAS,CAAC,WAAW;CACnC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EAAE,MAAmB,EAAE,OAAkB,WAAW,EAAiB,EAAE;IACxG,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,wBAAwB,IAAI,EAAE,CAAC;IAC/D,MAAM,UAAU,GAAG,CAAC,gBAAgB,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,CAAC,CAAC;IAEhB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;QACpB,KAAK,CAAC,OAAO,EAAE,mDAAmD,CAAC,CAAC;QACpE,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;IAC1B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,SAAS,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAEvC,KAAK,CAAC,OAAO,EAAE,4BAA4B,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,SAAS,OAAO,CAAC,CAAC;IAEhG,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC;YACxC,UAAU;YACV,SAAS,EAAE,SAAS;SACrB,EAAE,KAAK,CAAC,CAAC;QAEV,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,iEAAiE;YACjE,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChE,IAAI,UAAU,KAAK,YAAY,EAAE,CAAC;gBAChC,KAAK,CAAC,OAAO,EAAE,wBAAwB,UAAU,CAAC,MAAM,8CAA8C,CAAC,CAAC;gBACxG,OAAO;YACT,CAAC;YACD,YAAY,GAAG,UAAU,CAAC;YAE1B,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;gBAC3B,KAAK,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,wBAAwB,CAAC,CAAC,kBAAkB,EAAE,CAAC,CAAC;YAChI,CAAC;YACD,KAAK,CAAC,MAAM,EAAE,yBAAyB,UAAU,CAAC,MAAM,gBAAgB,CAAC,CAAC;QAC5E,CAAC;aAAM,CAAC;YACN,YAAY,GAAG,EAAE,CAAC;YAClB,KAAK,CAAC,OAAO,EAAE,sCAAsC,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,MAAM,EAAE,0BAA2B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAClE,CAAC;AACH,CAAC,CAAC;AAEF,uCAAuC;AACvC,MAAM,CAAC,MAAM,WAAW,GAAG,GAAS,EAAE,GAAG,YAAY,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,GAAU,EAAQ,EAAE,GAAG,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copilot session watcher — detects active sessions by scanning
3
+ * ~/.copilot/session-state/ for directories with events.jsonl files.
4
+ */
5
+ export interface WatchedSession {
6
+ uuid: string;
7
+ eventsPath: string;
8
+ active: boolean;
9
+ }
10
+ export declare function discoverSessions(): WatchedSession[];
11
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/daemon/watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,gBAAgB,IAAI,cAAc,EAAE,CA8BnD"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Copilot session watcher — detects active sessions by scanning
3
+ * ~/.copilot/session-state/ for directories with events.jsonl files.
4
+ */
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+ import { loadConfig } from "../config.js";
8
+ export function discoverSessions() {
9
+ const cfg = loadConfig();
10
+ const baseDir = cfg.watchers.copilot.path;
11
+ if (!fs.existsSync(baseDir))
12
+ return [];
13
+ const sessions = [];
14
+ for (const entry of fs.readdirSync(baseDir)) {
15
+ const sessionDir = path.join(baseDir, entry);
16
+ const eventsPath = path.join(sessionDir, "events.jsonl");
17
+ let isDir = false;
18
+ try {
19
+ isDir = fs.statSync(sessionDir).isDirectory();
20
+ }
21
+ catch {
22
+ continue;
23
+ }
24
+ if (!isDir)
25
+ continue;
26
+ if (!fs.existsSync(eventsPath))
27
+ continue;
28
+ // Check for active lock files
29
+ const lockFiles = fs.readdirSync(sessionDir).filter((f) => f.startsWith("inuse.") && f.endsWith(".lock"));
30
+ sessions.push({
31
+ uuid: entry,
32
+ eventsPath,
33
+ active: lockFiles.length > 0,
34
+ });
35
+ }
36
+ return sessions;
37
+ }
38
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/daemon/watcher.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAQ1C,MAAM,UAAU,gBAAgB;IAC9B,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;IAC1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAEvC,MAAM,QAAQ,GAAqB,EAAE,CAAC;IACtC,KAAK,MAAM,KAAK,IAAI,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QAEzD,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,IAAI,CAAC;YACH,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YAAC,SAAS;QAAC,CAAC;QACrB,IAAI,CAAC,KAAK;YAAE,SAAS;QAErB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,SAAS;QAEzC,8BAA8B;QAC9B,MAAM,SAAS,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CACjD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CACrD,CAAC;QAEF,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,KAAK;YACX,UAAU;YACV,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,CAAC;SAC7B,CAAC,CAAC;IACL,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tests for the session watcher.
3
+ *
4
+ * Creates a temporary directory structure mimicking ~/.copilot/session-state/
5
+ * and verifies discoverSessions() behavior.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=watcher.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.test.d.ts","sourceRoot":"","sources":["../../src/daemon/watcher.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}