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.
- package/LICENSE +661 -0
- package/README.md +323 -0
- package/bin/bikky.js +6 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +51 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +59 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +141 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.d.ts +8 -0
- package/dist/config.test.d.ts.map +1 -0
- package/dist/config.test.js +449 -0
- package/dist/config.test.js.map +1 -0
- package/dist/daemon/consolidation.d.ts +60 -0
- package/dist/daemon/consolidation.d.ts.map +1 -0
- package/dist/daemon/consolidation.js +448 -0
- package/dist/daemon/consolidation.js.map +1 -0
- package/dist/daemon/extraction.d.ts +17 -0
- package/dist/daemon/extraction.d.ts.map +1 -0
- package/dist/daemon/extraction.js +465 -0
- package/dist/daemon/extraction.js.map +1 -0
- package/dist/daemon/index.d.ts +3 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +3 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/loop.d.ts +3 -0
- package/dist/daemon/loop.d.ts.map +1 -0
- package/dist/daemon/loop.js +93 -0
- package/dist/daemon/loop.js.map +1 -0
- package/dist/daemon/qdrant.d.ts +103 -0
- package/dist/daemon/qdrant.d.ts.map +1 -0
- package/dist/daemon/qdrant.js +273 -0
- package/dist/daemon/qdrant.js.map +1 -0
- package/dist/daemon/qdrant.test.d.ts +8 -0
- package/dist/daemon/qdrant.test.d.ts.map +1 -0
- package/dist/daemon/qdrant.test.js +209 -0
- package/dist/daemon/qdrant.test.js.map +1 -0
- package/dist/daemon/relations.d.ts +54 -0
- package/dist/daemon/relations.d.ts.map +1 -0
- package/dist/daemon/relations.js +290 -0
- package/dist/daemon/relations.js.map +1 -0
- package/dist/daemon/staleness.d.ts +24 -0
- package/dist/daemon/staleness.d.ts.map +1 -0
- package/dist/daemon/staleness.js +63 -0
- package/dist/daemon/staleness.js.map +1 -0
- package/dist/daemon/watcher.d.ts +11 -0
- package/dist/daemon/watcher.d.ts.map +1 -0
- package/dist/daemon/watcher.js +38 -0
- package/dist/daemon/watcher.js.map +1 -0
- package/dist/daemon/watcher.test.d.ts +8 -0
- package/dist/daemon/watcher.test.d.ts.map +1 -0
- package/dist/daemon/watcher.test.js +214 -0
- package/dist/daemon/watcher.test.js.map +1 -0
- package/dist/install.d.ts +5 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +49 -0
- package/dist/install.js.map +1 -0
- package/dist/install.test.d.ts +9 -0
- package/dist/install.test.d.ts.map +1 -0
- package/dist/install.test.js +126 -0
- package/dist/install.test.js.map +1 -0
- package/dist/llm/embedding.d.ts +13 -0
- package/dist/llm/embedding.d.ts.map +1 -0
- package/dist/llm/embedding.js +127 -0
- package/dist/llm/embedding.js.map +1 -0
- package/dist/llm/embedding.test.d.ts +8 -0
- package/dist/llm/embedding.test.d.ts.map +1 -0
- package/dist/llm/embedding.test.js +117 -0
- package/dist/llm/embedding.test.js.map +1 -0
- package/dist/llm/index.d.ts +4 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +3 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/inference.d.ts +12 -0
- package/dist/llm/inference.d.ts.map +1 -0
- package/dist/llm/inference.js +146 -0
- package/dist/llm/inference.js.map +1 -0
- package/dist/llm/inference.test.d.ts +8 -0
- package/dist/llm/inference.test.d.ts.map +1 -0
- package/dist/llm/inference.test.js +117 -0
- package/dist/llm/inference.test.js.map +1 -0
- package/dist/llm/types.d.ts +41 -0
- package/dist/llm/types.d.ts.map +1 -0
- package/dist/llm/types.js +5 -0
- package/dist/llm/types.js.map +1 -0
- package/dist/logger.d.ts +13 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +41 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp/api.d.ts +30 -0
- package/dist/mcp/api.d.ts.map +1 -0
- package/dist/mcp/api.js +150 -0
- package/dist/mcp/api.js.map +1 -0
- package/dist/mcp/helpers.d.ts +35 -0
- package/dist/mcp/helpers.d.ts.map +1 -0
- package/dist/mcp/helpers.js +152 -0
- package/dist/mcp/helpers.js.map +1 -0
- package/dist/mcp/helpers.test.d.ts +5 -0
- package/dist/mcp/helpers.test.d.ts.map +1 -0
- package/dist/mcp/helpers.test.js +487 -0
- package/dist/mcp/helpers.test.js.map +1 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +67 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/taxonomy.d.ts +38 -0
- package/dist/mcp/taxonomy.d.ts.map +1 -0
- package/dist/mcp/taxonomy.js +223 -0
- package/dist/mcp/taxonomy.js.map +1 -0
- package/dist/mcp/taxonomy.test.d.ts +5 -0
- package/dist/mcp/taxonomy.test.d.ts.map +1 -0
- package/dist/mcp/taxonomy.test.js +341 -0
- package/dist/mcp/taxonomy.test.js.map +1 -0
- package/dist/mcp/tools.d.ts +6 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +958 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/types.d.ts +118 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +5 -0
- package/dist/mcp/types.js.map +1 -0
- 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 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.test.d.ts","sourceRoot":"","sources":["../../src/daemon/watcher.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|