bikky 0.3.1 → 0.3.3
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/README.md +124 -35
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +22 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +61 -6
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +17 -9
- package/dist/config.test.js.map +1 -1
- package/dist/daemon/capture-policy.d.ts +95 -0
- package/dist/daemon/capture-policy.d.ts.map +1 -0
- package/dist/daemon/capture-policy.js +139 -0
- package/dist/daemon/capture-policy.js.map +1 -0
- package/dist/daemon/capture-policy.test.d.ts +2 -0
- package/dist/daemon/capture-policy.test.d.ts.map +1 -0
- package/dist/daemon/capture-policy.test.js +46 -0
- package/dist/daemon/capture-policy.test.js.map +1 -0
- package/dist/daemon/consolidation.d.ts.map +1 -1
- package/dist/daemon/consolidation.js +84 -98
- package/dist/daemon/consolidation.js.map +1 -1
- package/dist/daemon/episode-summary.d.ts +72 -0
- package/dist/daemon/episode-summary.d.ts.map +1 -0
- package/dist/daemon/episode-summary.js +208 -0
- package/dist/daemon/episode-summary.js.map +1 -0
- package/dist/daemon/episode-summary.test.d.ts +2 -0
- package/dist/daemon/episode-summary.test.d.ts.map +1 -0
- package/dist/daemon/episode-summary.test.js +101 -0
- package/dist/daemon/episode-summary.test.js.map +1 -0
- package/dist/daemon/extraction.d.ts +25 -0
- package/dist/daemon/extraction.d.ts.map +1 -1
- package/dist/daemon/extraction.js +244 -124
- package/dist/daemon/extraction.js.map +1 -1
- package/dist/daemon/extraction.test.d.ts +2 -0
- package/dist/daemon/extraction.test.d.ts.map +1 -0
- package/dist/daemon/extraction.test.js +106 -0
- package/dist/daemon/extraction.test.js.map +1 -0
- package/dist/daemon/loop.d.ts.map +1 -1
- package/dist/daemon/loop.js +8 -6
- package/dist/daemon/loop.js.map +1 -1
- package/dist/daemon/qdrant.d.ts +59 -8
- package/dist/daemon/qdrant.d.ts.map +1 -1
- package/dist/daemon/qdrant.js +74 -23
- package/dist/daemon/qdrant.js.map +1 -1
- package/dist/daemon/qdrant.test.js +2 -2
- package/dist/daemon/qdrant.test.js.map +1 -1
- package/dist/daemon/relations.d.ts +6 -1
- package/dist/daemon/relations.d.ts.map +1 -1
- package/dist/daemon/relations.js +44 -63
- package/dist/daemon/relations.js.map +1 -1
- package/dist/daemon/session-index.d.ts +60 -0
- package/dist/daemon/session-index.d.ts.map +1 -0
- package/dist/daemon/session-index.js +136 -0
- package/dist/daemon/session-index.js.map +1 -0
- package/dist/daemon/session-index.test.d.ts +2 -0
- package/dist/daemon/session-index.test.d.ts.map +1 -0
- package/dist/daemon/session-index.test.js +54 -0
- package/dist/daemon/session-index.test.js.map +1 -0
- package/dist/daemon/session-summary.d.ts +69 -0
- package/dist/daemon/session-summary.d.ts.map +1 -0
- package/dist/daemon/session-summary.js +200 -0
- package/dist/daemon/session-summary.js.map +1 -0
- package/dist/daemon/session-summary.test.d.ts +2 -0
- package/dist/daemon/session-summary.test.d.ts.map +1 -0
- package/dist/daemon/session-summary.test.js +160 -0
- package/dist/daemon/session-summary.test.js.map +1 -0
- package/dist/daemon/staleness.test.d.ts +7 -0
- package/dist/daemon/staleness.test.d.ts.map +1 -0
- package/dist/daemon/staleness.test.js +128 -0
- package/dist/daemon/staleness.test.js.map +1 -0
- package/dist/daemon/workstream-summary.d.ts +61 -0
- package/dist/daemon/workstream-summary.d.ts.map +1 -0
- package/dist/daemon/workstream-summary.js +220 -0
- package/dist/daemon/workstream-summary.js.map +1 -0
- package/dist/daemon/workstream-summary.test.d.ts +2 -0
- package/dist/daemon/workstream-summary.test.d.ts.map +1 -0
- package/dist/daemon/workstream-summary.test.js +86 -0
- package/dist/daemon/workstream-summary.test.js.map +1 -0
- package/dist/lib/qdrant-client.d.ts +6 -1
- package/dist/lib/qdrant-client.d.ts.map +1 -1
- package/dist/lib/qdrant-client.js +3 -4
- package/dist/lib/qdrant-client.js.map +1 -1
- package/dist/lib/qdrant-client.test.js +21 -2
- package/dist/lib/qdrant-client.test.js.map +1 -1
- package/dist/lifecycle.test.d.ts +8 -0
- package/dist/lifecycle.test.d.ts.map +1 -0
- package/dist/lifecycle.test.js +74 -0
- package/dist/lifecycle.test.js.map +1 -0
- package/dist/llm/embedding/index.d.ts +42 -0
- package/dist/llm/embedding/index.d.ts.map +1 -0
- package/dist/llm/embedding/index.js +78 -0
- package/dist/llm/embedding/index.js.map +1 -0
- package/dist/llm/embedding/index.test.d.ts +8 -0
- package/dist/llm/embedding/index.test.d.ts.map +1 -0
- package/dist/llm/embedding/index.test.js +100 -0
- package/dist/llm/embedding/index.test.js.map +1 -0
- package/dist/llm/embedding/providers/bedrock.d.ts +16 -0
- package/dist/llm/embedding/providers/bedrock.d.ts.map +1 -0
- package/dist/llm/embedding/providers/bedrock.js +90 -0
- package/dist/llm/embedding/providers/bedrock.js.map +1 -0
- package/dist/llm/embedding/providers/bedrock.test.d.ts +2 -0
- package/dist/llm/embedding/providers/bedrock.test.d.ts.map +1 -0
- package/dist/llm/embedding/providers/bedrock.test.js +24 -0
- package/dist/llm/embedding/providers/bedrock.test.js.map +1 -0
- package/dist/llm/embedding/providers/index.d.ts +9 -0
- package/dist/llm/embedding/providers/index.d.ts.map +1 -0
- package/dist/llm/embedding/providers/index.js +9 -0
- package/dist/llm/embedding/providers/index.js.map +1 -0
- package/dist/llm/embedding/providers/ollama.d.ts +6 -0
- package/dist/llm/embedding/providers/ollama.d.ts.map +1 -0
- package/dist/llm/embedding/providers/ollama.js +39 -0
- package/dist/llm/embedding/providers/ollama.js.map +1 -0
- package/dist/llm/embedding/providers/ollama.test.d.ts +2 -0
- package/dist/llm/embedding/providers/ollama.test.d.ts.map +1 -0
- package/dist/llm/embedding/providers/ollama.test.js +54 -0
- package/dist/llm/embedding/providers/ollama.test.js.map +1 -0
- package/dist/llm/embedding/providers/openai.d.ts +6 -0
- package/dist/llm/embedding/providers/openai.d.ts.map +1 -0
- package/dist/llm/embedding/providers/openai.js +44 -0
- package/dist/llm/embedding/providers/openai.js.map +1 -0
- package/dist/llm/embedding/providers/openai.test.d.ts +2 -0
- package/dist/llm/embedding/providers/openai.test.d.ts.map +1 -0
- package/dist/llm/embedding/providers/openai.test.js +48 -0
- package/dist/llm/embedding/providers/openai.test.js.map +1 -0
- package/dist/llm/embedding/providers/portkey.d.ts +15 -0
- package/dist/llm/embedding/providers/portkey.d.ts.map +1 -0
- package/dist/llm/embedding/providers/portkey.js +58 -0
- package/dist/llm/embedding/providers/portkey.js.map +1 -0
- package/dist/llm/embedding/providers/portkey.test.d.ts +2 -0
- package/dist/llm/embedding/providers/portkey.test.d.ts.map +1 -0
- package/dist/llm/embedding/providers/portkey.test.js +56 -0
- package/dist/llm/embedding/providers/portkey.test.js.map +1 -0
- package/dist/llm/embedding/registry.d.ts +14 -0
- package/dist/llm/embedding/registry.d.ts.map +1 -0
- package/dist/llm/embedding/registry.js +27 -0
- package/dist/llm/embedding/registry.js.map +1 -0
- package/dist/llm/embedding/registry.test.d.ts +7 -0
- package/dist/llm/embedding/registry.test.d.ts.map +1 -0
- package/dist/llm/embedding/registry.test.js +68 -0
- package/dist/llm/embedding/registry.test.js.map +1 -0
- package/dist/llm/embedding/types.d.ts +55 -0
- package/dist/llm/embedding/types.d.ts.map +1 -0
- package/dist/llm/embedding/types.js +12 -0
- package/dist/llm/embedding/types.js.map +1 -0
- package/dist/llm/errors.d.ts +95 -0
- package/dist/llm/errors.d.ts.map +1 -0
- package/dist/llm/errors.js +164 -0
- package/dist/llm/errors.js.map +1 -0
- package/dist/llm/errors.test.d.ts +2 -0
- package/dist/llm/errors.test.d.ts.map +1 -0
- package/dist/llm/errors.test.js +103 -0
- package/dist/llm/errors.test.js.map +1 -0
- package/dist/llm/fetch.d.ts +39 -0
- package/dist/llm/fetch.d.ts.map +1 -0
- package/dist/llm/fetch.js +52 -0
- package/dist/llm/fetch.js.map +1 -0
- package/dist/llm/index.d.ts +6 -3
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +2 -2
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/inference/index.d.ts +39 -0
- package/dist/llm/inference/index.d.ts.map +1 -0
- package/dist/llm/inference/index.js +118 -0
- package/dist/llm/inference/index.js.map +1 -0
- package/dist/llm/inference/index.test.d.ts +6 -0
- package/dist/llm/inference/index.test.d.ts.map +1 -0
- package/dist/llm/inference/index.test.js +109 -0
- package/dist/llm/inference/index.test.js.map +1 -0
- package/dist/llm/inference/providers/bedrock.d.ts +18 -0
- package/dist/llm/inference/providers/bedrock.d.ts.map +1 -0
- package/dist/llm/inference/providers/bedrock.js +105 -0
- package/dist/llm/inference/providers/bedrock.js.map +1 -0
- package/dist/llm/inference/providers/bedrock.test.d.ts +2 -0
- package/dist/llm/inference/providers/bedrock.test.d.ts.map +1 -0
- package/dist/llm/inference/providers/bedrock.test.js +21 -0
- package/dist/llm/inference/providers/bedrock.test.js.map +1 -0
- package/dist/llm/inference/providers/index.d.ts +10 -0
- package/dist/llm/inference/providers/index.d.ts.map +1 -0
- package/dist/llm/inference/providers/index.js +10 -0
- package/dist/llm/inference/providers/index.js.map +1 -0
- package/dist/llm/inference/providers/ollama.d.ts +8 -0
- package/dist/llm/inference/providers/ollama.d.ts.map +1 -0
- package/dist/llm/inference/providers/ollama.js +63 -0
- package/dist/llm/inference/providers/ollama.js.map +1 -0
- package/dist/llm/inference/providers/ollama.test.d.ts +2 -0
- package/dist/llm/inference/providers/ollama.test.d.ts.map +1 -0
- package/dist/llm/inference/providers/ollama.test.js +57 -0
- package/dist/llm/inference/providers/ollama.test.js.map +1 -0
- package/dist/llm/inference/providers/openai.d.ts +11 -0
- package/dist/llm/inference/providers/openai.d.ts.map +1 -0
- package/dist/llm/inference/providers/openai.js +73 -0
- package/dist/llm/inference/providers/openai.js.map +1 -0
- package/dist/llm/inference/providers/openai.test.d.ts +2 -0
- package/dist/llm/inference/providers/openai.test.d.ts.map +1 -0
- package/dist/llm/inference/providers/openai.test.js +46 -0
- package/dist/llm/inference/providers/openai.test.js.map +1 -0
- package/dist/llm/inference/providers/portkey.d.ts +13 -0
- package/dist/llm/inference/providers/portkey.d.ts.map +1 -0
- package/dist/llm/inference/providers/portkey.js +80 -0
- package/dist/llm/inference/providers/portkey.js.map +1 -0
- package/dist/llm/inference/providers/portkey.test.d.ts +2 -0
- package/dist/llm/inference/providers/portkey.test.d.ts.map +1 -0
- package/dist/llm/inference/providers/portkey.test.js +48 -0
- package/dist/llm/inference/providers/portkey.test.js.map +1 -0
- package/dist/llm/inference/registry.d.ts +15 -0
- package/dist/llm/inference/registry.d.ts.map +1 -0
- package/dist/llm/inference/registry.js +28 -0
- package/dist/llm/inference/registry.js.map +1 -0
- package/dist/llm/inference/registry.test.d.ts +6 -0
- package/dist/llm/inference/registry.test.d.ts.map +1 -0
- package/dist/llm/inference/registry.test.js +63 -0
- package/dist/llm/inference/registry.test.js.map +1 -0
- package/dist/llm/inference/types.d.ts +84 -0
- package/dist/llm/inference/types.d.ts.map +1 -0
- package/dist/llm/inference/types.js +9 -0
- package/dist/llm/inference/types.js.map +1 -0
- package/dist/llm/telemetry.d.ts +25 -0
- package/dist/llm/telemetry.d.ts.map +1 -0
- package/dist/llm/telemetry.js +43 -0
- package/dist/llm/telemetry.js.map +1 -0
- package/dist/llm/telemetry.test.d.ts +5 -0
- package/dist/llm/telemetry.test.d.ts.map +1 -0
- package/dist/llm/telemetry.test.js +89 -0
- package/dist/llm/telemetry.test.js.map +1 -0
- package/dist/llm/types.d.ts +4 -37
- package/dist/llm/types.d.ts.map +1 -1
- package/dist/llm/types.js +4 -1
- package/dist/llm/types.js.map +1 -1
- package/dist/logger.d.ts +18 -3
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +102 -20
- package/dist/logger.js.map +1 -1
- package/dist/logger.test.d.ts +5 -0
- package/dist/logger.test.d.ts.map +1 -0
- package/dist/logger.test.js +103 -0
- package/dist/logger.test.js.map +1 -0
- package/dist/mcp/api.d.ts +15 -1
- package/dist/mcp/api.d.ts.map +1 -1
- package/dist/mcp/api.js +44 -19
- package/dist/mcp/api.js.map +1 -1
- package/dist/mcp/api.test.d.ts +6 -0
- package/dist/mcp/api.test.d.ts.map +1 -0
- package/dist/mcp/api.test.js +130 -0
- package/dist/mcp/api.test.js.map +1 -0
- package/dist/mcp/helpers.d.ts +1 -0
- package/dist/mcp/helpers.d.ts.map +1 -1
- package/dist/mcp/helpers.js +62 -6
- package/dist/mcp/helpers.js.map +1 -1
- package/dist/mcp/helpers.test.js +71 -10
- package/dist/mcp/helpers.test.js.map +1 -1
- package/dist/mcp/index.d.ts +7 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +46 -21
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/taxonomy.d.ts +251 -31
- package/dist/mcp/taxonomy.d.ts.map +1 -1
- package/dist/mcp/taxonomy.js +603 -171
- package/dist/mcp/taxonomy.js.map +1 -1
- package/dist/mcp/taxonomy.test.d.ts +1 -1
- package/dist/mcp/taxonomy.test.js +141 -302
- package/dist/mcp/taxonomy.test.js.map +1 -1
- package/dist/mcp/tools.d.ts +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.integration.itest.d.ts +23 -0
- package/dist/mcp/tools.integration.itest.d.ts.map +1 -0
- package/dist/mcp/tools.integration.itest.js +172 -0
- package/dist/mcp/tools.integration.itest.js.map +1 -0
- package/dist/mcp/tools.js +422 -357
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/tools.test.d.ts +16 -0
- package/dist/mcp/tools.test.d.ts.map +1 -0
- package/dist/mcp/tools.test.js +472 -0
- package/dist/mcp/tools.test.js.map +1 -0
- package/dist/mcp/types.d.ts +63 -8
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/prompts/brief.d.ts +19 -0
- package/dist/prompts/brief.d.ts.map +1 -0
- package/dist/prompts/brief.js +67 -0
- package/dist/prompts/brief.js.map +1 -0
- package/dist/prompts/contradiction.d.ts +24 -0
- package/dist/prompts/contradiction.d.ts.map +1 -0
- package/dist/prompts/contradiction.js +73 -0
- package/dist/prompts/contradiction.js.map +1 -0
- package/dist/prompts/distill.d.ts +21 -0
- package/dist/prompts/distill.d.ts.map +1 -0
- package/dist/prompts/distill.js +92 -0
- package/dist/prompts/distill.js.map +1 -0
- package/dist/prompts/episode-summary.d.ts +15 -0
- package/dist/prompts/episode-summary.d.ts.map +1 -0
- package/dist/prompts/episode-summary.js +60 -0
- package/dist/prompts/episode-summary.js.map +1 -0
- package/dist/prompts/extraction.d.ts +14 -0
- package/dist/prompts/extraction.d.ts.map +1 -0
- package/dist/prompts/extraction.js +110 -0
- package/dist/prompts/extraction.js.map +1 -0
- package/dist/prompts/index.d.ts +52 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +104 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/prompts.test.d.ts +8 -0
- package/dist/prompts/prompts.test.d.ts.map +1 -0
- package/dist/prompts/prompts.test.js +140 -0
- package/dist/prompts/prompts.test.js.map +1 -0
- package/dist/prompts/relations.d.ts +17 -0
- package/dist/prompts/relations.d.ts.map +1 -0
- package/dist/prompts/relations.js +72 -0
- package/dist/prompts/relations.js.map +1 -0
- package/dist/prompts/workstream-summary.d.ts +17 -0
- package/dist/prompts/workstream-summary.d.ts.map +1 -0
- package/dist/prompts/workstream-summary.js +72 -0
- package/dist/prompts/workstream-summary.js.map +1 -0
- package/dist/render.d.ts +41 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +185 -0
- package/dist/render.js.map +1 -0
- package/dist/render.test.d.ts +8 -0
- package/dist/render.test.d.ts.map +1 -0
- package/dist/render.test.js +243 -0
- package/dist/render.test.js.map +1 -0
- package/docs/diagrams/architecture.svg +87 -0
- package/docs/diagrams/team-memory.svg +250 -0
- package/docs/screenshots/dashboard.png +0 -0
- package/docs/screenshots/graph.png +0 -0
- package/docs/screenshots/memory.png +0 -0
- package/package.json +12 -3
- package/dist/llm/embedding.d.ts +0 -13
- package/dist/llm/embedding.d.ts.map +0 -1
- package/dist/llm/embedding.js +0 -127
- package/dist/llm/embedding.js.map +0 -1
- package/dist/llm/embedding.test.d.ts +0 -8
- package/dist/llm/embedding.test.d.ts.map +0 -1
- package/dist/llm/embedding.test.js +0 -117
- package/dist/llm/embedding.test.js.map +0 -1
- package/dist/llm/inference.d.ts +0 -12
- package/dist/llm/inference.d.ts.map +0 -1
- package/dist/llm/inference.js +0 -146
- package/dist/llm/inference.js.map +0 -1
- package/dist/llm/inference.test.d.ts +0 -8
- package/dist/llm/inference.test.d.ts.map +0 -1
- package/dist/llm/inference.test.js +0 -117
- package/dist/llm/inference.test.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"episode-summary.test.d.ts","sourceRoot":"","sources":["../../src/daemon/episode-summary.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { buildEpisodeSummaryFilter, buildEpisodeSummaryMessages, buildEpisodeSummaryPayload, parseEpisodeSummaryDraft, segmentTranscriptIntoEpisodes, } from "./episode-summary.js";
|
|
4
|
+
const defaultScope = {
|
|
5
|
+
workspaceId: "default",
|
|
6
|
+
includeLegacy: true,
|
|
7
|
+
};
|
|
8
|
+
describe("daemon/episode-summary", () => {
|
|
9
|
+
it("segments two unrelated user tasks in one session into two episodes", () => {
|
|
10
|
+
const transcript = `[USER] Implement ontology v2 in src/mcp/taxonomy.ts and add tests.
|
|
11
|
+
|
|
12
|
+
[ASSISTANT] Updated src/mcp/taxonomy.ts with software_engineering domains and memory_subtype validation. Ran npm test for taxonomy.
|
|
13
|
+
|
|
14
|
+
[USER] Now fix the UI smoke tests in packages/ui/tests/smoke.spec.ts.
|
|
15
|
+
|
|
16
|
+
[ASSISTANT] Updated packages/ui/tests/smoke.spec.ts to cover browse filters and ran npm run test:e2e.`;
|
|
17
|
+
const segments = segmentTranscriptIntoEpisodes({
|
|
18
|
+
sessionId: "uuid:test-session",
|
|
19
|
+
transcript,
|
|
20
|
+
eventCount: 14,
|
|
21
|
+
});
|
|
22
|
+
assert.equal(segments.length, 2);
|
|
23
|
+
assert.match(segments[0].episode_id, /^uuid:test-session:episode:/);
|
|
24
|
+
assert.match(segments[0].transcript, /ontology v2/);
|
|
25
|
+
assert.match(segments[1].transcript, /UI smoke tests/);
|
|
26
|
+
});
|
|
27
|
+
it("does not create episodes for small irrelevant batches", () => {
|
|
28
|
+
const segments = segmentTranscriptIntoEpisodes({
|
|
29
|
+
sessionId: "uuid:test-session",
|
|
30
|
+
transcript: "[USER] ok\n\n[ASSISTANT] done",
|
|
31
|
+
eventCount: 2,
|
|
32
|
+
});
|
|
33
|
+
assert.deepEqual(segments, []);
|
|
34
|
+
});
|
|
35
|
+
it("builds episode summary prompts", () => {
|
|
36
|
+
const messages = buildEpisodeSummaryMessages({
|
|
37
|
+
transcript: "[USER] update src/daemon/extraction.ts",
|
|
38
|
+
});
|
|
39
|
+
assert.match(messages[0].content, /one coherent work episode/);
|
|
40
|
+
assert.match(messages[1].content, /workstream_key/);
|
|
41
|
+
});
|
|
42
|
+
it("parses episode summary drafts", () => {
|
|
43
|
+
const draft = parseEpisodeSummaryDraft(`\`\`\`json
|
|
44
|
+
{
|
|
45
|
+
"content": "Implemented ontology v2 extraction for src/daemon/extraction.ts.",
|
|
46
|
+
"tasks_completed": ["ontology extraction"],
|
|
47
|
+
"decisions_made": ["Use memory_subtype on daemon facts"],
|
|
48
|
+
"open_questions": ["wire workstream summaries"],
|
|
49
|
+
"entities": ["Bikky", "src/daemon/extraction.ts"],
|
|
50
|
+
"workstream_key": "243-bikky-data-capture-policy",
|
|
51
|
+
"importance": 0.9
|
|
52
|
+
}
|
|
53
|
+
\`\`\``);
|
|
54
|
+
assert.equal(draft.content, "Implemented ontology v2 extraction for src/daemon/extraction.ts.");
|
|
55
|
+
assert.deepEqual(draft.entities, ["bikky", "src/daemon/extraction.ts"]);
|
|
56
|
+
assert.equal(draft.workstream_key, "243-bikky-data-capture-policy");
|
|
57
|
+
});
|
|
58
|
+
it("builds episode filters scoped to workspace and subtype", () => {
|
|
59
|
+
const filter = buildEpisodeSummaryFilter("episode-1", defaultScope);
|
|
60
|
+
assert.ok(filter.must.some((condition) => JSON.stringify(condition) === JSON.stringify({ key: "memory_subtype", match: { value: "episode" } })));
|
|
61
|
+
assert.deepEqual(filter.should, [
|
|
62
|
+
{ key: "workspace_id", match: { value: "default" } },
|
|
63
|
+
{ is_empty: { key: "workspace_id" } },
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
it("omits workspace filters when no workspace is supplied", () => {
|
|
67
|
+
const filter = buildEpisodeSummaryFilter("episode-1", {});
|
|
68
|
+
assert.equal(filter.should, undefined);
|
|
69
|
+
assert.equal(filter.must.some((condition) => JSON.stringify(condition).includes("workspace_id")), false);
|
|
70
|
+
});
|
|
71
|
+
it("builds ontology-v2 episode payloads", () => {
|
|
72
|
+
const { payload } = buildEpisodeSummaryPayload({
|
|
73
|
+
draft: {
|
|
74
|
+
content: "Implemented daemon episode summaries for src/daemon/episode-summary.ts.",
|
|
75
|
+
tasks_completed: ["episode summaries"],
|
|
76
|
+
decisions_made: ["Use episode summaries instead of one evolving session summary"],
|
|
77
|
+
open_questions: ["workstream updater"],
|
|
78
|
+
entities: ["bikky", "src/daemon/episode-summary.ts"],
|
|
79
|
+
workstream_key: "243-bikky-data-capture-policy",
|
|
80
|
+
importance: 0.85,
|
|
81
|
+
},
|
|
82
|
+
segment: {
|
|
83
|
+
episode_id: "episode-1",
|
|
84
|
+
ordinal: 0,
|
|
85
|
+
transcript: "[USER] implement episodes",
|
|
86
|
+
event_count: 8,
|
|
87
|
+
},
|
|
88
|
+
sessionId: "uuid:test-session",
|
|
89
|
+
scope: { workspaceId: "team-a", actorId: "agent-1", includeLegacy: false },
|
|
90
|
+
now: "2026-04-25T12:00:00.000Z",
|
|
91
|
+
redactionOptions: { enabled: true, redactPii: true },
|
|
92
|
+
});
|
|
93
|
+
assert.equal(payload.kind, "summary");
|
|
94
|
+
assert.equal(payload.memory_subtype, "episode");
|
|
95
|
+
assert.equal(payload.domain, "software_engineering");
|
|
96
|
+
assert.equal(payload.episode_id, "episode-1");
|
|
97
|
+
assert.equal(payload.workstream_key, "243-bikky-data-capture-policy");
|
|
98
|
+
assert.equal(payload.metadata.summary_subtype, "episode");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
//# sourceMappingURL=episode-summary.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"episode-summary.test.js","sourceRoot":"","sources":["../../src/daemon/episode-summary.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,31 @@
|
|
|
8
8
|
import type { BikkyConfig } from "../config.js";
|
|
9
9
|
import type { LogFn } from "./qdrant.js";
|
|
10
10
|
export declare const setLogger: (fn: LogFn) => void;
|
|
11
|
+
export declare const DEFAULT_EXTRACTION_PROMPT = "You are Bikky's memory extraction agent for open-source coding agents. Extract durable, reusable facts that help a future agent continue work without rereading the whole transcript.\n\n## Core rule\nExtract fewer, sharper memories. A candidate fact must be independently useful after the session is gone.\n\n## Quality gate\nEvery fact must pass at least one gate:\n1. GREPPABLE: names a file path, package, symbol, config key, CLI flag, issue/PR, service, or API a future agent can search for.\n2. RUNNABLE: contains a command, URL, setting, port, or procedure that can be executed or checked.\n3. NAVIGABLE: tells a future agent where to look and what that location means.\n4. DECISIVE: records a durable decision, rationale, constraint, convention, preference, or ownership rule.\n5. DIAGNOSTIC: captures a repeatable failure mode, root cause, or troubleshooting gotcha.\n\n## Ontology\n- domain is the activity profile. For coding-agent captures use \"software_engineering\".\n- category is subject matter: codebase | infrastructure | operations | decisions | product_domain | projects | people | preferences | observations.\n- kind is object shape. For this prompt, emit only kind=\"fact\".\n- memory_subtype must be one of:\n codebase_map | architecture_decision | infra_topology | access_pattern | deployment_procedure | operational_procedure | domain_rule | troubleshooting_gotcha | preference | ownership.\n\n## Examples\nGOOD:\n- \"The UI smoke tests live in packages/ui/tests/smoke.spec.ts and run through npm run test:e2e with mocked /api/memory/* responses.\"\n- \"Use workspace_id as the tenancy/access boundary; domain is reserved for activity profile such as software_engineering.\"\n- \"If Qdrant order_by fails with a missing index error, create a datetime payload index for the sorted field before retrying.\"\n- \"Prefer Node's built-in test runner for root tests; do not add Jest just for daemon unit tests.\"\n\nBAD:\n- \"The tests were fixed.\" (status only)\n- \"We reviewed the code.\" (session narration)\n- \"The deployment succeeded.\" (transient and not reusable)\n- \"The agent used npm.\" (tool narration)\n- \"There was an error.\" (no root cause or reusable detail)\n\n## Output format\nReturn strict JSON:\n{\"facts\":[\n {\n \"content\":\"One self-contained durable fact.\",\n \"category\":\"codebase\",\n \"memory_subtype\":\"codebase_map\",\n \"entities\":[\"repo-or-tool\",\"specific-module\"],\n \"confidence\":0.9,\n \"importance\":0.7,\n \"quality_score\":0.8,\n \"confidence_reason\":\"Explicitly stated in the transcript.\",\n \"repo\":\"optional/repo-or-package\",\n \"branch\":\"optional-branch\",\n \"task_key\":\"optional issue/PR/task key\",\n \"workstream_key\":\"optional stable workstream key\"\n }\n]}\n\nScoring:\n- confidence: 0.9 explicit, 0.7 strong inference, 0.55 weak but useful inference.\n- importance: 0.8+ for decisions, infra, procedures, access, recurring failures; 0.6+ for useful codebase maps/preferences.\n- quality_score: 0.8+ passes multiple gates, 0.6+ passes one strong gate, below 0.6 should usually be omitted.\n\nIf nothing passes the quality gate, return {\"facts\":[]}.";
|
|
12
|
+
export interface ExtractedFact {
|
|
13
|
+
content: string;
|
|
14
|
+
category: string;
|
|
15
|
+
memory_subtype?: string | null;
|
|
16
|
+
entities: string[];
|
|
17
|
+
confidence: number;
|
|
18
|
+
importance: number;
|
|
19
|
+
quality_score?: number | null;
|
|
20
|
+
confidence_reason?: string | null;
|
|
21
|
+
repo?: string | null;
|
|
22
|
+
branch?: string | null;
|
|
23
|
+
task_key?: string | null;
|
|
24
|
+
workstream_key?: string | null;
|
|
25
|
+
}
|
|
26
|
+
export interface FactQualitySignals {
|
|
27
|
+
wordCount: number;
|
|
28
|
+
hasDurableAnchor: boolean;
|
|
29
|
+
isStatusOnly: boolean;
|
|
30
|
+
isShortUseful: boolean;
|
|
31
|
+
computedQualityScore: number;
|
|
32
|
+
}
|
|
33
|
+
export declare const factQualitySignals: (fact: ExtractedFact) => FactQualitySignals;
|
|
34
|
+
export declare const normalizeExtractedFact: (raw: Record<string, unknown>) => ExtractedFact | null;
|
|
35
|
+
export declare const isHighQualityExtractedFact: (fact: ExtractedFact) => boolean;
|
|
11
36
|
/**
|
|
12
37
|
* Periodic extraction tick — called from the daemon tick loop.
|
|
13
38
|
* For each active Copilot session with events.jsonl, reads new events
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extraction.d.ts","sourceRoot":"","sources":["../../src/daemon/extraction.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"extraction.d.ts","sourceRoot":"","sources":["../../src/daemon/extraction.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD,OAAO,KAAK,EAAE,KAAK,EAAa,MAAM,aAAa,CAAC;AA2BpD,eAAO,MAAM,SAAS,GAAI,IAAI,KAAK,KAAG,IAErC,CAAC;AAUF,eAAO,MAAM,yBAAyB,unGA0DmB,CAAC;AAgO1D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AA2BD,eAAO,MAAM,kBAAkB,GAAI,MAAM,aAAa,KAAG,kBA2BxD,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,aAAa,GAAG,IAuCrF,CAAC;AAEF,eAAO,MAAM,0BAA0B,GAAI,MAAM,aAAa,KAAG,OAkBhE,CAAC;AAkLF;;;;GAIG;AACH,eAAO,MAAM,IAAI,GAAU,QAAQ,WAAW,KAAG,OAAO,CAAC,IAAI,CA8B5D,CAAC"}
|
|
@@ -14,6 +14,10 @@ import { loadConfig, STATE_DIR } from "../config.js";
|
|
|
14
14
|
import * as qdrant from "./qdrant.js";
|
|
15
15
|
import { chatCompletion } from "../llm/index.js";
|
|
16
16
|
import { detectContradiction } from "./consolidation.js";
|
|
17
|
+
import { normalizeCategory, normalizeEntities, normalizeMemorySubtype, validateMemorySubtype, } from "../mcp/taxonomy.js";
|
|
18
|
+
import { extractionPrompt, EXTRACTION_PROMPT_DESCRIPTOR, safeParseJson, } from "../prompts/index.js";
|
|
19
|
+
import { CAPTURE_POLICY_VERSION, CAPTURE_TRIGGERS, DEFAULT_CAPTURE_CONTEXT, QUALITY_THRESHOLDS, promptVersionForSubtype, subtypeForCategory, } from "./capture-policy.js";
|
|
20
|
+
import { shouldSummarizeEvents, updateSessionSummary } from "./session-summary.js";
|
|
17
21
|
// ── Module state ─────────────────────────────────────────────────────────────
|
|
18
22
|
let logFn = (() => { });
|
|
19
23
|
let lastTickAt = 0;
|
|
@@ -26,78 +30,65 @@ const EXTRACTABLE_TYPES = new Set([
|
|
|
26
30
|
"assistant.message",
|
|
27
31
|
"session.compaction_complete",
|
|
28
32
|
]);
|
|
29
|
-
const DEFAULT_EXTRACTION_PROMPT = `You are
|
|
33
|
+
export const DEFAULT_EXTRACTION_PROMPT = `You are Bikky's memory extraction agent for open-source coding agents. Extract durable, reusable facts that help a future agent continue work without rereading the whole transcript.
|
|
30
34
|
|
|
31
|
-
##
|
|
32
|
-
|
|
33
|
-
1. GREPPABLE — contains a file path, service name, config key, CLI flag, or symbol an engineer could search for
|
|
34
|
-
2. RUNNABLE — contains a command, URL, port, or procedure that could be executed
|
|
35
|
-
3. NAVIGABLE — tells you where to look for something specific (which repo, which module, which config)
|
|
36
|
-
4. DECISIVE — records a choice with enough rationale that a future engineer won't re-debate it
|
|
35
|
+
## Core rule
|
|
36
|
+
Extract fewer, sharper memories. A candidate fact must be independently useful after the session is gone.
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## Quality gate
|
|
39
|
+
Every fact must pass at least one gate:
|
|
40
|
+
1. GREPPABLE: names a file path, package, symbol, config key, CLI flag, issue/PR, service, or API a future agent can search for.
|
|
41
|
+
2. RUNNABLE: contains a command, URL, setting, port, or procedure that can be executed or checked.
|
|
42
|
+
3. NAVIGABLE: tells a future agent where to look and what that location means.
|
|
43
|
+
4. DECISIVE: records a durable decision, rationale, constraint, convention, preference, or ownership rule.
|
|
44
|
+
5. DIAGNOSTIC: captures a repeatable failure mode, root cause, or troubleshooting gotcha.
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
## Ontology
|
|
47
|
+
- domain is the activity profile. For coding-agent captures use "software_engineering".
|
|
48
|
+
- category is subject matter: codebase | infrastructure | operations | decisions | product_domain | projects | people | preferences | observations.
|
|
49
|
+
- kind is object shape. For this prompt, emit only kind="fact".
|
|
50
|
+
- memory_subtype must be one of:
|
|
51
|
+
codebase_map | architecture_decision | infra_topology | access_pattern | deployment_procedure | operational_procedure | domain_rule | troubleshooting_gotcha | preference | ownership.
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
## Examples
|
|
54
|
+
GOOD:
|
|
55
|
+
- "The UI smoke tests live in packages/ui/tests/smoke.spec.ts and run through npm run test:e2e with mocked /api/memory/* responses."
|
|
56
|
+
- "Use workspace_id as the tenancy/access boundary; domain is reserved for activity profile such as software_engineering."
|
|
57
|
+
- "If Qdrant order_by fails with a missing index error, create a datetime payload index for the sorted field before retrying."
|
|
58
|
+
- "Prefer Node's built-in test runner for root tests; do not add Jest just for daemon unit tests."
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Where secrets live, IAM roles, auth patterns, permission gotchas.
|
|
57
|
-
GOOD: "saber IAM user blocked by EnforceMFA for ECR/S3 — use --profile mfa. EC2 instance role agent00-cortex-ec2 has ECR pull+push scoped to arn:aws:ecr:ap-southeast-2:871829501674:repository/agent00-cortex"
|
|
58
|
-
BAD: "AWS credentials are configured" (useless)
|
|
59
|
-
|
|
60
|
-
### 5. Deployment & Shipping — "how do I release X?"
|
|
61
|
-
Build, release, CI/CD pipelines, verification steps.
|
|
62
|
-
GOOD: "EC2 cortex deploys via SSM: download repo tarball from GitHub API (needs saber-zrelli-private token) → build on EC2 (native amd64) → push to ECR → docker compose pull && up -d. SSM executionTimeout must be ≥600s"
|
|
63
|
-
BAD: "The deployment was successful" (not reusable)
|
|
64
|
-
|
|
65
|
-
### 6. Operational Procedure — "how do I do X on a live system?"
|
|
66
|
-
kubectl recipes, patching commands, rollout procedures, scaling ops, cleanup, troubleshooting.
|
|
67
|
-
GOOD: "To roll TG bot images across lloyds fleet: kubectl get deploy -l app=tg-bot --context lloyds-ctx, then patch each with image update. Wait 30s between batches to avoid message loss"
|
|
68
|
-
BAD: "Bot images were updated" (no procedure)
|
|
69
|
-
|
|
70
|
-
### 7. Business Logic Rule — "what are the domain rules?"
|
|
71
|
-
Data flow semantics, edge cases that affect code, domain-specific constraints.
|
|
72
|
-
GOOD: "SA bank accounts (Capitec branch 470010, TymeBank 678910, FNB 250655) are frequently misclassified as AU BSBs because branch codes are 6 digits. Recovery script stage 1b re-extracts with ZA country tagging"
|
|
73
|
-
BAD: "Bank accounts are extracted" (no specifics)
|
|
74
|
-
|
|
75
|
-
## What to ALWAYS SKIP
|
|
76
|
-
- Session narration: "the user asked to fix X, then we looked at Y" — that's a log, not a reference
|
|
77
|
-
- Meta-observations about tools: "bikky handles extraction" / "the agent used kubectl" — obvious from context
|
|
78
|
-
- Debugging state: "test 67 fails" / "got a 404" — transient unless it reveals a PERMANENT quirk
|
|
79
|
-
- Vague summaries: "WhatsApp bot was updated" — which bot? which update? which PR?
|
|
80
|
-
- Opinions without rationale: "Nova Lite is good" — good for what? compared to what?
|
|
81
|
-
- Anything you can't add specifics to: if you can't name a file, service, command, or decision — skip it
|
|
60
|
+
BAD:
|
|
61
|
+
- "The tests were fixed." (status only)
|
|
62
|
+
- "We reviewed the code." (session narration)
|
|
63
|
+
- "The deployment succeeded." (transient and not reusable)
|
|
64
|
+
- "The agent used npm." (tool narration)
|
|
65
|
+
- "There was an error." (no root cause or reusable detail)
|
|
82
66
|
|
|
83
67
|
## Output format
|
|
84
|
-
|
|
68
|
+
Return strict JSON:
|
|
69
|
+
{"facts":[
|
|
85
70
|
{
|
|
86
|
-
"content":
|
|
87
|
-
"category":
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
71
|
+
"content":"One self-contained durable fact.",
|
|
72
|
+
"category":"codebase",
|
|
73
|
+
"memory_subtype":"codebase_map",
|
|
74
|
+
"entities":["repo-or-tool","specific-module"],
|
|
75
|
+
"confidence":0.9,
|
|
76
|
+
"importance":0.7,
|
|
77
|
+
"quality_score":0.8,
|
|
78
|
+
"confidence_reason":"Explicitly stated in the transcript.",
|
|
79
|
+
"repo":"optional/repo-or-package",
|
|
80
|
+
"branch":"optional-branch",
|
|
81
|
+
"task_key":"optional issue/PR/task key",
|
|
82
|
+
"workstream_key":"optional stable workstream key"
|
|
91
83
|
}
|
|
92
84
|
]}
|
|
93
85
|
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
86
|
+
Scoring:
|
|
87
|
+
- confidence: 0.9 explicit, 0.7 strong inference, 0.55 weak but useful inference.
|
|
88
|
+
- importance: 0.8+ for decisions, infra, procedures, access, recurring failures; 0.6+ for useful codebase maps/preferences.
|
|
89
|
+
- quality_score: 0.8+ passes multiple gates, 0.6+ passes one strong gate, below 0.6 should usually be omitted.
|
|
98
90
|
|
|
99
|
-
|
|
100
|
-
If nothing passes the quality gate, return: {"facts": []}`;
|
|
91
|
+
If nothing passes the quality gate, return {"facts":[]}.`;
|
|
101
92
|
// ── JSON-file state persistence ──────────────────────────────────────────────
|
|
102
93
|
const EXTRACTION_STATE_PATH = join(STATE_DIR, "extraction-state.json");
|
|
103
94
|
const loadExtractionStates = () => {
|
|
@@ -272,75 +263,164 @@ const buildTranscript = (events) => {
|
|
|
272
263
|
}
|
|
273
264
|
return lines.join("\n\n");
|
|
274
265
|
};
|
|
266
|
+
const clamp01 = (value) => Math.max(0, Math.min(1, value));
|
|
267
|
+
const textWordCount = (text) => text.trim().split(/\s+/).filter(Boolean).length;
|
|
268
|
+
const hasDurableAnchor = (content, entities) => {
|
|
269
|
+
const text = content.trim();
|
|
270
|
+
return Boolean(/(?:^|\s)(?:[\w.-]+\/)+[\w./-]+/.test(text) ||
|
|
271
|
+
/`[^`]+`/.test(text) ||
|
|
272
|
+
/\b(?:npm|pnpm|yarn|node|git|gh|docker|kubectl|make|go|python|pip|cargo|terraform|aws|curl)\b/.test(text) ||
|
|
273
|
+
/https?:\/\/\S+/.test(text) ||
|
|
274
|
+
/\b[A-Z][A-Z0-9_]{2,}\b/.test(text) ||
|
|
275
|
+
/\b[\w.-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|rb|php|json|ya?ml|toml|md|sql|sh)\b/.test(text) ||
|
|
276
|
+
/\b(?:issue|pr|pull request)\s*#?\d+\b/i.test(text) ||
|
|
277
|
+
entities.length >= 2);
|
|
278
|
+
};
|
|
279
|
+
const isStatusOnlyContent = (content, entities) => {
|
|
280
|
+
const lower = content.trim().toLowerCase();
|
|
281
|
+
if (hasDurableAnchor(content, entities))
|
|
282
|
+
return false;
|
|
283
|
+
if (textWordCount(content) > 16)
|
|
284
|
+
return false;
|
|
285
|
+
return /\b(done|fixed|updated|implemented|reviewed|tested|works|passed|failed|succeeded|completed|started|looked|changed|deployed)\b/.test(lower);
|
|
286
|
+
};
|
|
287
|
+
export const factQualitySignals = (fact) => {
|
|
288
|
+
const content = fact.content.trim();
|
|
289
|
+
const entities = normalizeEntities(fact.entities ?? []);
|
|
290
|
+
const subtype = normalizeMemorySubtype("fact", fact.memory_subtype) ?? subtypeForCategory(normalizeCategory(fact.category));
|
|
291
|
+
const wordCount = textWordCount(content);
|
|
292
|
+
const durableAnchor = hasDurableAnchor(content, entities);
|
|
293
|
+
const statusOnly = isStatusOnlyContent(content, entities);
|
|
294
|
+
const isPreferenceLike = subtype === "preference" || subtype === "ownership" || subtype === "domain_rule";
|
|
295
|
+
const isDecisionLike = subtype === "architecture_decision" || subtype === "troubleshooting_gotcha";
|
|
296
|
+
const shortUseful = wordCount >= 7 && wordCount <= 22 && (isPreferenceLike || isDecisionLike) && (entities.length > 0 || durableAnchor);
|
|
297
|
+
let score = 0.25;
|
|
298
|
+
if (wordCount >= 8)
|
|
299
|
+
score += 0.1;
|
|
300
|
+
if (wordCount >= 14)
|
|
301
|
+
score += 0.1;
|
|
302
|
+
if (durableAnchor)
|
|
303
|
+
score += 0.25;
|
|
304
|
+
if (isPreferenceLike || isDecisionLike)
|
|
305
|
+
score += 0.15;
|
|
306
|
+
if ((fact.confidence ?? 0) >= 0.75)
|
|
307
|
+
score += 0.1;
|
|
308
|
+
if ((fact.importance ?? 0) >= 0.7)
|
|
309
|
+
score += 0.1;
|
|
310
|
+
if (statusOnly)
|
|
311
|
+
score -= 0.4;
|
|
312
|
+
return {
|
|
313
|
+
wordCount,
|
|
314
|
+
hasDurableAnchor: durableAnchor,
|
|
315
|
+
isStatusOnly: statusOnly,
|
|
316
|
+
isShortUseful: shortUseful,
|
|
317
|
+
computedQualityScore: Math.round(clamp01(score) * 100) / 100,
|
|
318
|
+
};
|
|
319
|
+
};
|
|
320
|
+
export const normalizeExtractedFact = (raw) => {
|
|
321
|
+
if (!raw.content || typeof raw.content !== "string")
|
|
322
|
+
return null;
|
|
323
|
+
const category = normalizeCategory(typeof raw.category === "string" ? raw.category : "observations");
|
|
324
|
+
const requestedSubtype = typeof raw.memory_subtype === "string" ? raw.memory_subtype : null;
|
|
325
|
+
let memorySubtype = requestedSubtype ? normalizeMemorySubtype("fact", requestedSubtype) : null;
|
|
326
|
+
if (!memorySubtype) {
|
|
327
|
+
memorySubtype = subtypeForCategory(category);
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
validateMemorySubtype("fact", memorySubtype);
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
memorySubtype = subtypeForCategory(category);
|
|
334
|
+
}
|
|
335
|
+
const confidence = typeof raw.confidence === "number" ? clamp01(raw.confidence) : 0.7;
|
|
336
|
+
const importance = typeof raw.importance === "number" ? clamp01(raw.importance) : 0.5;
|
|
337
|
+
const qualityScore = typeof raw.quality_score === "number" ? clamp01(raw.quality_score) : null;
|
|
338
|
+
const entities = Array.isArray(raw.entities)
|
|
339
|
+
? normalizeEntities(raw.entities.map((entity) => String(entity)))
|
|
340
|
+
: [];
|
|
341
|
+
const fact = {
|
|
342
|
+
content: raw.content.trim(),
|
|
343
|
+
category,
|
|
344
|
+
memory_subtype: memorySubtype,
|
|
345
|
+
entities,
|
|
346
|
+
confidence,
|
|
347
|
+
importance,
|
|
348
|
+
quality_score: qualityScore,
|
|
349
|
+
confidence_reason: typeof raw.confidence_reason === "string" ? raw.confidence_reason.trim() : null,
|
|
350
|
+
repo: typeof raw.repo === "string" ? raw.repo.trim() : null,
|
|
351
|
+
branch: typeof raw.branch === "string" ? raw.branch.trim() : null,
|
|
352
|
+
task_key: typeof raw.task_key === "string" ? raw.task_key.trim() : null,
|
|
353
|
+
workstream_key: typeof raw.workstream_key === "string" ? raw.workstream_key.trim() : null,
|
|
354
|
+
};
|
|
355
|
+
return isHighQualityExtractedFact(fact) ? fact : null;
|
|
356
|
+
};
|
|
357
|
+
export const isHighQualityExtractedFact = (fact) => {
|
|
358
|
+
const content = fact.content.trim();
|
|
359
|
+
if (content.length < 30)
|
|
360
|
+
return false;
|
|
361
|
+
const signals = factQualitySignals(fact);
|
|
362
|
+
if (QUALITY_THRESHOLDS.rejectStatusOnlyFacts && signals.isStatusOnly)
|
|
363
|
+
return false;
|
|
364
|
+
const confidence = fact.confidence ?? 0.7;
|
|
365
|
+
const importance = fact.importance ?? 0.5;
|
|
366
|
+
if (confidence < QUALITY_THRESHOLDS.minFactConfidence && importance < QUALITY_THRESHOLDS.minImportanceForLowConfidenceFact) {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
const qualityScore = fact.quality_score ?? signals.computedQualityScore;
|
|
370
|
+
if (qualityScore < QUALITY_THRESHOLDS.minFactQualityScore && !signals.isShortUseful)
|
|
371
|
+
return false;
|
|
372
|
+
if (!signals.hasDurableAnchor && !signals.isShortUseful)
|
|
373
|
+
return false;
|
|
374
|
+
return true;
|
|
375
|
+
};
|
|
275
376
|
/**
|
|
276
377
|
* Call the LLM to extract facts from a conversation transcript.
|
|
277
378
|
*/
|
|
278
379
|
const extractFacts = async (transcript, config) => {
|
|
279
380
|
if (!transcript.trim())
|
|
280
381
|
return [];
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
{ role: "system", content: prompt },
|
|
285
|
-
{ role: "user", content: transcript },
|
|
286
|
-
],
|
|
287
|
-
temperature: 0.1,
|
|
288
|
-
max_tokens: 4000,
|
|
289
|
-
response_format: { type: "json_object" },
|
|
382
|
+
const rendered = extractionPrompt({
|
|
383
|
+
transcript,
|
|
384
|
+
systemOverride: config?.daemon.extraction_prompt ?? null,
|
|
290
385
|
});
|
|
386
|
+
const result = await chatCompletion(rendered);
|
|
291
387
|
if (!result) {
|
|
292
388
|
logFn("WARN", "Extraction LLM call returned null");
|
|
293
389
|
return [];
|
|
294
390
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
391
|
+
// Parse — handle {facts: [...]}, raw array, or single-fact object
|
|
392
|
+
let parsed = safeParseJson(result);
|
|
393
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
394
|
+
const obj = parsed;
|
|
395
|
+
const unwrapped = obj.facts || obj.results || obj.items;
|
|
396
|
+
if (Array.isArray(unwrapped)) {
|
|
397
|
+
parsed = unwrapped;
|
|
300
398
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const obj = parsed;
|
|
305
|
-
const unwrapped = obj.facts || obj.results || obj.items;
|
|
306
|
-
if (Array.isArray(unwrapped)) {
|
|
307
|
-
parsed = unwrapped;
|
|
308
|
-
}
|
|
309
|
-
else if (obj.content && typeof obj.content === "string") {
|
|
310
|
-
// Single fact object — wrap in array
|
|
311
|
-
parsed = [obj];
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
// Try first array value from the object
|
|
315
|
-
const firstVal = Object.values(obj).find(v => Array.isArray(v));
|
|
316
|
-
parsed = firstVal || [obj];
|
|
317
|
-
}
|
|
399
|
+
else if (obj.content && typeof obj.content === "string") {
|
|
400
|
+
// Single fact object — wrap in array
|
|
401
|
+
parsed = [obj];
|
|
318
402
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
403
|
+
else {
|
|
404
|
+
// Try first array value from the object
|
|
405
|
+
const firstVal = Object.values(obj).find(v => Array.isArray(v));
|
|
406
|
+
parsed = firstVal || [obj];
|
|
322
407
|
}
|
|
323
|
-
return parsed
|
|
324
|
-
.filter(f => f.content && typeof f.content === "string" && f.category)
|
|
325
|
-
.map(f => ({
|
|
326
|
-
content: f.content,
|
|
327
|
-
category: f.category,
|
|
328
|
-
entities: Array.isArray(f.entities) ? f.entities.map(e => String(e).toLowerCase()) : [],
|
|
329
|
-
confidence: typeof f.confidence === "number" ? f.confidence : 0.7,
|
|
330
|
-
importance: typeof f.importance === "number" ? f.importance : 0.5,
|
|
331
|
-
}))
|
|
332
|
-
.filter(f => {
|
|
333
|
-
if (f.importance < 0.5) {
|
|
334
|
-
logFn("DEBUG", `Extraction: dropping low-importance fact (${f.importance}): "${f.content.slice(0, 80)}…"`);
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
return true;
|
|
338
|
-
});
|
|
339
408
|
}
|
|
340
|
-
|
|
341
|
-
logFn("WARN", `Extraction LLM
|
|
409
|
+
if (!Array.isArray(parsed)) {
|
|
410
|
+
logFn("WARN", `Extraction LLM returned non-array: ${result.slice(0, 300)}`);
|
|
342
411
|
return [];
|
|
343
412
|
}
|
|
413
|
+
return parsed
|
|
414
|
+
.map((raw) => {
|
|
415
|
+
const fact = normalizeExtractedFact(raw);
|
|
416
|
+
if (!fact) {
|
|
417
|
+
const content = typeof raw.content === "string" ? raw.content : JSON.stringify(raw).slice(0, 120);
|
|
418
|
+
logFn("DEBUG", `Extraction: dropping low-quality fact candidate: "${content.slice(0, 80)}…"`);
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
return fact;
|
|
422
|
+
})
|
|
423
|
+
.filter((fact) => fact !== null);
|
|
344
424
|
};
|
|
345
425
|
// ── Fact storage ─────────────────────────────────────────────────────────────
|
|
346
426
|
const contentHash = (text) => createHash("sha256").update(text).digest("hex");
|
|
@@ -353,12 +433,20 @@ const storeFacts = async (facts, sessionId, config) => {
|
|
|
353
433
|
logFn("WARN", "Extraction: Qdrant not ready, skipping store");
|
|
354
434
|
return 0;
|
|
355
435
|
}
|
|
356
|
-
const baseMeta = {
|
|
436
|
+
const baseMeta = {
|
|
437
|
+
extracted_from_session: sessionId,
|
|
438
|
+
capture_policy_version: CAPTURE_POLICY_VERSION,
|
|
439
|
+
extracted_by_prompt: `${EXTRACTION_PROMPT_DESCRIPTOR.id}@${EXTRACTION_PROMPT_DESCRIPTOR.version}`,
|
|
440
|
+
};
|
|
357
441
|
let stored = 0;
|
|
358
442
|
for (const fact of facts) {
|
|
359
443
|
const hash = contentHash(fact.content);
|
|
444
|
+
const sanitizedFact = {
|
|
445
|
+
...fact,
|
|
446
|
+
entities: fact.entities.map((entity) => entity.toLowerCase()),
|
|
447
|
+
};
|
|
360
448
|
try {
|
|
361
|
-
const dedup = await qdrant.dedupCheck(
|
|
449
|
+
const dedup = await qdrant.dedupCheck(sanitizedFact.content, hash);
|
|
362
450
|
if (dedup.action === "skip") {
|
|
363
451
|
// Reinforce existing fact
|
|
364
452
|
if (dedup.existingId) {
|
|
@@ -369,7 +457,7 @@ const storeFacts = async (facts, sessionId, config) => {
|
|
|
369
457
|
// Run contradiction detection for non-trivial facts
|
|
370
458
|
if (config && (fact.confidence ?? 0.5) >= 0.5) {
|
|
371
459
|
try {
|
|
372
|
-
const contradiction = await detectContradiction(
|
|
460
|
+
const contradiction = await detectContradiction(sanitizedFact, config);
|
|
373
461
|
if (contradiction.contradiction && contradiction.existingId) {
|
|
374
462
|
logFn("INFO", `Extraction: contradiction detected for "${fact.content.slice(0, 60)}..." vs ${contradiction.existingId}: ${contradiction.reason}`);
|
|
375
463
|
// Log contradiction instead of writing to inbox
|
|
@@ -379,15 +467,29 @@ const storeFacts = async (facts, sessionId, config) => {
|
|
|
379
467
|
logFn("WARN", `Extraction: contradiction check failed: ${e.message}`);
|
|
380
468
|
}
|
|
381
469
|
}
|
|
470
|
+
const subtype = validateMemorySubtype("fact", sanitizedFact.memory_subtype)
|
|
471
|
+
?? subtypeForCategory(normalizeCategory(sanitizedFact.category));
|
|
382
472
|
const storePayload = {
|
|
383
|
-
content:
|
|
384
|
-
category:
|
|
385
|
-
|
|
473
|
+
content: sanitizedFact.content,
|
|
474
|
+
category: sanitizedFact.category,
|
|
475
|
+
domain: DEFAULT_CAPTURE_CONTEXT.domain,
|
|
476
|
+
memory_subtype: subtype,
|
|
477
|
+
entities: sanitizedFact.entities,
|
|
386
478
|
source: "daemon",
|
|
387
479
|
kind: "fact",
|
|
388
480
|
confidence: fact.confidence,
|
|
389
481
|
importance: fact.importance,
|
|
390
482
|
content_hash: hash,
|
|
483
|
+
prompt_version: promptVersionForSubtype(subtype),
|
|
484
|
+
capture_policy_version: CAPTURE_POLICY_VERSION,
|
|
485
|
+
quality_score: fact.quality_score ?? factQualitySignals(fact).computedQualityScore,
|
|
486
|
+
confidence_reason: fact.confidence_reason,
|
|
487
|
+
review_status: DEFAULT_CAPTURE_CONTEXT.reviewStatus,
|
|
488
|
+
volatility: DEFAULT_CAPTURE_CONTEXT.volatility,
|
|
489
|
+
repo: fact.repo,
|
|
490
|
+
branch: fact.branch,
|
|
491
|
+
task_key: fact.task_key,
|
|
492
|
+
workstream_key: fact.workstream_key,
|
|
391
493
|
metadata: baseMeta,
|
|
392
494
|
};
|
|
393
495
|
if (dedup.action === "supersede" && dedup.existingId) {
|
|
@@ -406,6 +508,20 @@ const storeFacts = async (facts, sessionId, config) => {
|
|
|
406
508
|
}
|
|
407
509
|
return stored;
|
|
408
510
|
};
|
|
511
|
+
const updateSummaryForTranscript = async (sessionId, transcript, eventCount, config) => {
|
|
512
|
+
try {
|
|
513
|
+
const result = await updateSessionSummary({ sessionId, transcript, eventCount, config });
|
|
514
|
+
if (result.action === "stored" || result.action === "updated") {
|
|
515
|
+
logFn("INFO", `Summary: ${result.action} session summary ${result.factId} for ${sessionId}`);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
logFn("DEBUG", `Summary: skipped for ${sessionId} (${result.reason})`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch (e) {
|
|
522
|
+
logFn("WARN", `Summary: failed for ${sessionId}: ${e.message}`);
|
|
523
|
+
}
|
|
524
|
+
};
|
|
409
525
|
// ── Tick entry point ─────────────────────────────────────────────────────────
|
|
410
526
|
// Max transcript size per LLM call (~60K chars ≈ 15K tokens)
|
|
411
527
|
const MAX_TRANSCRIPT_CHARS = 60_000;
|
|
@@ -429,7 +545,7 @@ export const tick = async (config) => {
|
|
|
429
545
|
if (now - lastTickAt < intervalMs)
|
|
430
546
|
return;
|
|
431
547
|
lastTickAt = now;
|
|
432
|
-
const minEvents = config.daemon.extract_min_events ||
|
|
548
|
+
const minEvents = config.daemon.extract_min_events || CAPTURE_TRIGGERS.factExtraction.minEvents;
|
|
433
549
|
try {
|
|
434
550
|
// Extract from ALL active Copilot sessions with events.jsonl
|
|
435
551
|
const lockMappings = await resolveLockFiles();
|
|
@@ -465,7 +581,8 @@ const extractForUuid = async (mapping, minEvents, config) => {
|
|
|
465
581
|
}
|
|
466
582
|
// Read new events
|
|
467
583
|
const { events, newOffset, totalLines } = await readNewEvents(eventsPath, state.byte_offset);
|
|
468
|
-
|
|
584
|
+
const shouldSummarize = shouldSummarizeEvents(events, minEvents);
|
|
585
|
+
if (events.length < minEvents && !shouldSummarize) {
|
|
469
586
|
// Still update offset to avoid re-scanning non-extractable events
|
|
470
587
|
if (newOffset > state.byte_offset) {
|
|
471
588
|
state.byte_offset = newOffset;
|
|
@@ -480,11 +597,14 @@ const extractForUuid = async (mapping, minEvents, config) => {
|
|
|
480
597
|
for (let i = 0; i < chunks.length; i++) {
|
|
481
598
|
const transcript = buildTranscript(chunks[i]);
|
|
482
599
|
logFn("DEBUG", `Extraction: UUID ${uuid.slice(0, 8)} — chunk ${i + 1}/${chunks.length}, ${chunks[i].length} events, ${transcript.length} chars`);
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
600
|
+
if (events.length >= minEvents) {
|
|
601
|
+
const facts = await extractFacts(transcript, config);
|
|
602
|
+
if (facts.length > 0) {
|
|
603
|
+
const stored = await storeFacts(facts, stateKey, config);
|
|
604
|
+
totalFacts += stored;
|
|
605
|
+
}
|
|
487
606
|
}
|
|
607
|
+
await updateSummaryForTranscript(stateKey, transcript, chunks[i].length, config);
|
|
488
608
|
}
|
|
489
609
|
if (totalFacts > 0) {
|
|
490
610
|
logFn("INFO", `Extraction: UUID ${uuid.slice(0, 8)} — ${totalFacts} facts stored (${events.length} events, ${chunks.length} chunk(s))`);
|