@rubytech/create-maxy-code 0.1.132 → 0.1.138

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 (128) hide show
  1. package/dist/__tests__/installer-specialist-registration.test.js +5 -40
  2. package/dist/index.js +24 -28
  3. package/dist/specialist-registration.js +0 -15
  4. package/package.json +1 -1
  5. package/payload/platform/lib/graph-search/dist/boosts.d.ts +39 -0
  6. package/payload/platform/lib/graph-search/dist/boosts.d.ts.map +1 -0
  7. package/payload/platform/lib/graph-search/dist/boosts.js +57 -0
  8. package/payload/platform/lib/graph-search/dist/boosts.js.map +1 -0
  9. package/payload/platform/lib/graph-search/dist/dedup.d.ts +29 -0
  10. package/payload/platform/lib/graph-search/dist/dedup.d.ts.map +1 -0
  11. package/payload/platform/lib/graph-search/dist/dedup.js +97 -0
  12. package/payload/platform/lib/graph-search/dist/dedup.js.map +1 -0
  13. package/payload/platform/lib/graph-search/dist/index.d.ts +70 -8
  14. package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -1
  15. package/payload/platform/lib/graph-search/dist/index.js +323 -62
  16. package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
  17. package/payload/platform/lib/graph-search/dist/query-expansion.d.ts +37 -0
  18. package/payload/platform/lib/graph-search/dist/query-expansion.d.ts.map +1 -0
  19. package/payload/platform/lib/graph-search/dist/query-expansion.js +101 -0
  20. package/payload/platform/lib/graph-search/dist/query-expansion.js.map +1 -0
  21. package/payload/platform/lib/graph-search/dist/route.d.ts +29 -0
  22. package/payload/platform/lib/graph-search/dist/route.d.ts.map +1 -0
  23. package/payload/platform/lib/graph-search/dist/route.js +53 -0
  24. package/payload/platform/lib/graph-search/dist/route.js.map +1 -0
  25. package/payload/platform/lib/graph-search/dist/rrf-fusion.d.ts +31 -0
  26. package/payload/platform/lib/graph-search/dist/rrf-fusion.d.ts.map +1 -0
  27. package/payload/platform/lib/graph-search/dist/rrf-fusion.js +57 -0
  28. package/payload/platform/lib/graph-search/dist/rrf-fusion.js.map +1 -0
  29. package/payload/platform/lib/graph-search/src/__tests__/boosts.test.ts +59 -0
  30. package/payload/platform/lib/graph-search/src/__tests__/dedup.test.ts +83 -0
  31. package/payload/platform/lib/graph-search/src/__tests__/query-expansion.test.ts +96 -0
  32. package/payload/platform/lib/graph-search/src/__tests__/route.test.ts +62 -0
  33. package/payload/platform/lib/graph-search/src/__tests__/rrf-fusion.test.ts +39 -0
  34. package/payload/platform/lib/graph-search/src/boosts.ts +61 -0
  35. package/payload/platform/lib/graph-search/src/dedup.ts +108 -0
  36. package/payload/platform/lib/graph-search/src/index.ts +407 -65
  37. package/payload/platform/lib/graph-search/src/query-expansion.ts +129 -0
  38. package/payload/platform/lib/graph-search/src/route.ts +70 -0
  39. package/payload/platform/lib/graph-search/src/rrf-fusion.ts +62 -0
  40. package/payload/platform/neo4j/schema.cypher +80 -1
  41. package/payload/platform/plugins/admin/hooks/session-end-retrospective.sh +4 -2
  42. package/payload/platform/plugins/admin/mcp/dist/index.js +6 -4
  43. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  44. package/payload/platform/plugins/docs/references/internals.md +36 -10
  45. package/payload/platform/plugins/docs/references/neo4j.md +63 -0
  46. package/payload/platform/plugins/docs/references/session-retrospective.md +11 -4
  47. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +2 -2
  48. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -1
  49. package/payload/platform/plugins/memory/mcp/dist/index.js +114 -5
  50. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  51. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.d.ts +2 -0
  52. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.d.ts.map +1 -0
  53. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.js +25 -0
  54. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.js.map +1 -0
  55. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.d.ts +2 -0
  56. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.d.ts.map +1 -0
  57. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js +70 -0
  58. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js.map +1 -0
  59. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.d.ts +2 -0
  60. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.d.ts.map +1 -0
  61. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.js +50 -0
  62. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.js.map +1 -0
  63. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts +33 -0
  64. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts.map +1 -0
  65. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js +99 -0
  66. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js.map +1 -0
  67. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +21 -0
  68. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -1
  69. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +24 -0
  70. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -1
  71. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts +13 -0
  72. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts.map +1 -0
  73. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js +151 -0
  74. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js.map +1 -0
  75. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts +55 -0
  76. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts.map +1 -0
  77. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js +290 -0
  78. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js.map +1 -0
  79. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts +19 -0
  80. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts.map +1 -0
  81. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js +102 -0
  82. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js.map +1 -0
  83. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js +18 -1
  84. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js.map +1 -1
  85. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +5 -0
  86. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js.map +1 -1
  87. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js +2 -0
  88. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js.map +1 -1
  89. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
  90. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +14 -0
  91. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
  92. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts +29 -0
  93. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts.map +1 -0
  94. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js +22 -0
  95. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js.map +1 -0
  96. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +31 -1
  97. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
  98. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +182 -50
  99. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
  100. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  101. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +76 -1
  102. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  103. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts +30 -0
  104. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts.map +1 -0
  105. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js +129 -0
  106. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js.map +1 -0
  107. package/payload/platform/plugins/memory/mcp/scripts/backfill-typed-edges.ts +72 -0
  108. package/payload/platform/plugins/memory/mcp/scripts/generate-edge-docs.ts +75 -0
  109. package/payload/platform/plugins/memory/mcp/vitest.config.ts +5 -0
  110. package/payload/platform/plugins/memory/references/schema-base.md +44 -0
  111. package/payload/platform/scripts/seed-neo4j.sh +49 -0
  112. package/payload/platform/templates/agents/admin/IDENTITY.md +4 -3
  113. package/payload/platform/templates/specialists/agents/database-operator.md +9 -1
  114. package/payload/server/{adminuser-self-heal-QAWOZ3JV.js → adminuser-self-heal-YC47O34W.js} +2 -1
  115. package/payload/server/{chunk-JSBRDJBE.js → chunk-HYQNUVGO.js} +9 -1
  116. package/payload/server/{chunk-HCYM5FLU.js → chunk-T3DJD5QR.js} +3 -1
  117. package/payload/server/maxy-edge.js +3 -2
  118. package/payload/server/public/assets/{admin-FcRHAL-3.js → admin-BM9aXUUh.js} +1 -1
  119. package/payload/server/public/assets/{data-Ds37mflX.js → data-Ds2ECtfP.js} +1 -1
  120. package/payload/server/public/assets/{graph-CmWRhaiS.js → graph-BzxObKMM.js} +1 -1
  121. package/payload/server/public/assets/{graph-labels-Ch2r00Gt.js → graph-labels-Dwgep_nr.js} +1 -1
  122. package/payload/server/public/assets/{page-BcHhJXUt.js → page-BlkBsjP2.js} +1 -1
  123. package/payload/server/public/assets/page-h_SA5SVQ.js +51 -0
  124. package/payload/server/public/data.html +3 -3
  125. package/payload/server/public/graph.html +3 -3
  126. package/payload/server/public/index.html +4 -4
  127. package/payload/server/server.js +803 -76
  128. package/payload/server/public/assets/page-BOtNny_4.js +0 -51
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Multi-query expansion via Haiku (Task 308).
3
+ *
4
+ * Generates 3-5 paraphrases of the user query so the hybrid pipeline runs
5
+ * each paraphrase against vector + BM25 in parallel and unions the
6
+ * candidate sets before fusion. Recovers recall on terse queries where the
7
+ * embedding's nearest-neighbour is unstable.
8
+ *
9
+ * The expansion runs through `@rubytech/oauth-llm` against Claude Haiku —
10
+ * the same OAuth-bearer path the admin classifiers use; no new SDK
11
+ * dependency and no Anthropic-API-key path. Cache is in-process, keyed by
12
+ * (accountId, normalisedQuery, retrievalClass), with a 1-hour TTL.
13
+ *
14
+ * Graceful degrade: if the LLM call fails, returns just the original
15
+ * query. The caller's hybrid pipeline still runs — just on one query
16
+ * instead of N — so missing paraphrases are a recall regression, not a
17
+ * fault.
18
+ */
19
+
20
+ import { callOauthLlm } from "../../oauth-llm/dist/index.js";
21
+
22
+ const CACHE_TTL_MS = 60 * 60 * 1000;
23
+ const HAIKU_MODEL = "claude-haiku-4-5";
24
+ const MAX_PARAPHRASES = 5;
25
+
26
+ interface CacheEntry {
27
+ expansions: string[];
28
+ expiresAt: number;
29
+ }
30
+
31
+ const cache = new Map<string, CacheEntry>();
32
+
33
+ export function clearExpansionCache(): void {
34
+ cache.clear();
35
+ }
36
+
37
+ export interface ExpandQueryParams {
38
+ query: string;
39
+ retrievalClass?: string;
40
+ accountId?: string;
41
+ /**
42
+ * Override the cache or skip it entirely (useful in tests). When
43
+ * `false`, the cache is read but never written; when `true` (default),
44
+ * both reads and writes are active.
45
+ */
46
+ useCache?: boolean;
47
+ }
48
+
49
+ /**
50
+ * Return `[query, ...paraphrases]` (original always first). On LLM
51
+ * failure returns `[query]` only and writes a single stderr line so the
52
+ * operator can see expansion is degrading without breaking search.
53
+ */
54
+ export async function expandQuery(
55
+ params: ExpandQueryParams,
56
+ ): Promise<string[]> {
57
+ const { query, retrievalClass = "general", accountId = "", useCache = true } = params;
58
+ const normalised = query.trim().toLowerCase();
59
+ const cacheKey = `${accountId}::${retrievalClass}::${normalised}`;
60
+
61
+ if (useCache) {
62
+ const hit = cache.get(cacheKey);
63
+ if (hit && hit.expiresAt > Date.now()) {
64
+ return [query, ...hit.expansions];
65
+ }
66
+ }
67
+
68
+ const systemPrompt = [
69
+ "You generate paraphrases of a user query for hybrid graph search.",
70
+ "Return 3-5 alternative phrasings that preserve the user's intent but vary the surface form (synonyms, word order, entity-vs-attribute framing).",
71
+ "Do not change the entity names. Do not invent constraints the user did not state.",
72
+ "Output ONLY a JSON object via the provided tool; never free text.",
73
+ ].join("\n");
74
+
75
+ const classHint = retrievalClass === "general"
76
+ ? ""
77
+ : `The retrieval class is '${retrievalClass}' — tailor paraphrases accordingly.`;
78
+ const userMessage = [
79
+ `Original query: ${query}`,
80
+ classHint,
81
+ ].filter((s) => s.length > 0).join("\n");
82
+
83
+ const result = await callOauthLlm({
84
+ model: HAIKU_MODEL,
85
+ system: systemPrompt,
86
+ userMessage,
87
+ maxTokens: 512,
88
+ timeoutMs: 15_000,
89
+ tools: [
90
+ {
91
+ name: "emit_paraphrases",
92
+ description: "Emit 3-5 paraphrases of the user query.",
93
+ input_schema: {
94
+ type: "object",
95
+ properties: {
96
+ paraphrases: {
97
+ type: "array",
98
+ items: { type: "string" },
99
+ minItems: 3,
100
+ maxItems: MAX_PARAPHRASES,
101
+ },
102
+ },
103
+ required: ["paraphrases"],
104
+ },
105
+ },
106
+ ],
107
+ toolChoiceName: "emit_paraphrases",
108
+ });
109
+
110
+ if (result.kind !== "ok-tool") {
111
+ process.stderr.write(
112
+ `[graph-search:expansion-failed] cause=${result.kind === "fallback" ? result.cause : "unexpected"} query=${query.slice(0, 60).replace(/\s+/g, " ")}\n`,
113
+ );
114
+ return [query];
115
+ }
116
+
117
+ const raw = (result.input as { paraphrases?: unknown }).paraphrases;
118
+ if (!Array.isArray(raw)) return [query];
119
+ const expansions = raw
120
+ .filter((p): p is string => typeof p === "string" && p.trim().length > 0)
121
+ .map((p) => p.trim())
122
+ .slice(0, MAX_PARAPHRASES);
123
+
124
+ if (useCache) {
125
+ cache.set(cacheKey, { expansions, expiresAt: Date.now() + CACHE_TTL_MS });
126
+ }
127
+
128
+ return [query, ...expansions];
129
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Retrieval-class routing (Task 308).
3
+ *
4
+ * Consumes the `retrievalClass` field produced by Task 304's
5
+ * gateway-classifier (landed) and returns a per-class index plan: which
6
+ * labels to filter to, which fusion weights to favour, and whether to
7
+ * skip the lookup entirely.
8
+ *
9
+ * No new Haiku call here — this module is a pure deterministic mapping
10
+ * over the gateway's existing classification.
11
+ */
12
+
13
+ export type RetrievalClass =
14
+ | "entity"
15
+ | "temporal"
16
+ | "event"
17
+ | "general"
18
+ | "none";
19
+
20
+ export interface IndexPlan {
21
+ /** When true, the caller short-circuits and returns no results. */
22
+ skip: boolean;
23
+ /** Restrict vector + BM25 to these labels. Undefined means no filter. */
24
+ labelFilter?: string[];
25
+ /** Weight on the vector half of the (weighted-sum) fusion. */
26
+ vectorWeight: number;
27
+ /** Weight on the BM25 half of the (weighted-sum) fusion. */
28
+ bm25Weight: number;
29
+ }
30
+
31
+ /**
32
+ * Return the index plan for a retrieval class. Unknown values fall back
33
+ * to `general` rather than throwing — the routing is a soft hint and the
34
+ * caller's old behaviour is the safe default.
35
+ */
36
+ export function pickIndexMix(retrievalClass: string | undefined): IndexPlan {
37
+ switch (retrievalClass) {
38
+ case "entity":
39
+ return {
40
+ skip: false,
41
+ labelFilter: ["Person", "Company", "Concept"],
42
+ vectorWeight: 0.85,
43
+ bm25Weight: 0.15,
44
+ };
45
+ case "temporal":
46
+ return {
47
+ skip: false,
48
+ labelFilter: ["Event"],
49
+ vectorWeight: 0.2,
50
+ bm25Weight: 0.8,
51
+ };
52
+ case "event":
53
+ return {
54
+ skip: false,
55
+ labelFilter: ["Event"],
56
+ vectorWeight: 0,
57
+ bm25Weight: 1,
58
+ };
59
+ case "none":
60
+ return { skip: true, vectorWeight: 0, bm25Weight: 0 };
61
+ case "general":
62
+ default:
63
+ if (retrievalClass && retrievalClass !== "general") {
64
+ process.stderr.write(
65
+ `[graph-search:unknown-class] rc=${retrievalClass} fallback=general\n`,
66
+ );
67
+ }
68
+ return { skip: false, vectorWeight: 0.7, bm25Weight: 0.3 };
69
+ }
70
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Reciprocal Rank Fusion (Task 308).
3
+ *
4
+ * Replaces the weighted-sum fusion (`0.7 * vector + 0.3 * bm25_norm`) when
5
+ * the `MAXY_GS_RRF` flag is on. RRF is robust to score-distribution drift
6
+ * between the vector and BM25 indexes — it sums `1 / (k + rank_i)` for each
7
+ * node across the ranked lists it appears in. Higher rank (lower index) →
8
+ * larger contribution; the constant `k` (default 60, per the gbrain
9
+ * default) smooths the long tail.
10
+ *
11
+ * The weighted-sum baseline stays as the fallback so flag flips are
12
+ * reversible without code edits.
13
+ */
14
+
15
+ export interface FusionCandidate {
16
+ nodeId: string;
17
+ }
18
+
19
+ /**
20
+ * Fuse multiple ranked lists by Reciprocal Rank Fusion.
21
+ *
22
+ * Each input list is treated as already sorted (best first). For each
23
+ * candidate, score = sum over lists of `1 / (k + rank)`. Returns the union
24
+ * sorted by descending RRF score.
25
+ *
26
+ * Ties on RRF score are broken by the candidate's earliest appearance
27
+ * across the input lists (first-seen wins), which is deterministic given
28
+ * a deterministic input order.
29
+ */
30
+ export function fuseRrf<T extends FusionCandidate>(
31
+ rankedLists: T[][],
32
+ k = 60,
33
+ ): Array<T & { rrfScore: number }> {
34
+ const scores = new Map<string, number>();
35
+ const representatives = new Map<string, T>();
36
+ const firstSeen = new Map<string, number>();
37
+ let order = 0;
38
+
39
+ for (const list of rankedLists) {
40
+ for (let rank = 0; rank < list.length; rank++) {
41
+ const candidate = list[rank];
42
+ const contribution = 1 / (k + rank);
43
+ const prior = scores.get(candidate.nodeId) ?? 0;
44
+ scores.set(candidate.nodeId, prior + contribution);
45
+ if (!representatives.has(candidate.nodeId)) {
46
+ representatives.set(candidate.nodeId, candidate);
47
+ firstSeen.set(candidate.nodeId, order++);
48
+ }
49
+ }
50
+ }
51
+
52
+ const out: Array<T & { rrfScore: number }> = [];
53
+ for (const [nodeId, rrfScore] of scores) {
54
+ const rep = representatives.get(nodeId)!;
55
+ out.push({ ...rep, rrfScore });
56
+ }
57
+ out.sort((a, b) => {
58
+ if (b.rrfScore !== a.rrfScore) return b.rrfScore - a.rrfScore;
59
+ return (firstSeen.get(a.nodeId)! - firstSeen.get(b.nodeId)!);
60
+ });
61
+ return out;
62
+ }
@@ -2,6 +2,14 @@
2
2
  // Maxy Phase 0 — Neo4j Graph Schema
3
3
  // Schema.org compliant node types
4
4
  // ============================================================
5
+ //
6
+ // This file declares INDEXES and CONSTRAINTS only. The legal
7
+ // (sourceLabel, EDGE_TYPE, targetLabel) triples for the typed-edge
8
+ // auto-extraction pass (Task 305) live in
9
+ // `platform/plugins/memory/mcp/src/lib/typed-edge-schema.ts`
10
+ // as the TYPED_EDGE_ALLOWLIST constant. The Haiku prompt, the inline
11
+ // validator, and the public docs are all generated from that one
12
+ // constant; do not duplicate the enumeration here.
5
13
 
6
14
  // ----------------------------------------------------------
7
15
  // Constraints
@@ -361,7 +369,12 @@ ON EACH [n.name, n.firstName, n.lastName, n.givenName, n.familyName,
361
369
  n.email, n.note, n.label, n.value, n.message, n.preview, n.tagline,
362
370
  n.subject, n.bodyPreview, n.fromName, n.fromAddress, n.agentAddress, n.screeningReason,
363
371
  n.authority, n.contactValue, n.toolName,
364
- n.displayName, n.slug, n.role];
372
+ n.displayName, n.slug, n.role,
373
+ n.compiledTruth];
374
+ // `compiledTruth` (Task 306) is the authoritative-summary property the
375
+ // compiled-truth + timeline knowledge model writes on Person/Company/Concept.
376
+ // Indexed here so Task 308's compiledTruth boost path also benefits from BM25
377
+ // hits against the summary text. No-op until 306 writes the property.
365
378
 
366
379
  // Project node — a standalone creative-output node distinct from
367
380
  // :Section. Anchored via (:UserProfile)-[:CREATED]->(:Project), with optional
@@ -1345,3 +1358,69 @@ OPTIONS {
1345
1358
  `vector.similarity_function`: 'cosine'
1346
1359
  }
1347
1360
  };
1361
+
1362
+ // ----------------------------------------------------------
1363
+ // Compiled-truth + timeline knowledge model (Task 306) —
1364
+ // gbrain's "compiled truth above the rule, append-only timeline
1365
+ // below" pattern. Every :Person / :Organization / :Concept carries
1366
+ // an agent-rewritten summary (`compiledTruth`, ≤2000 chars) plus
1367
+ // a history of dated `:TimelineEvent` rows path-anchored via
1368
+ // `(entity)-[:HAS_TIMELINE_EVENT]->(:TimelineEvent)`. The
1369
+ // compiledTruth is what the agent reads when it needs a summary;
1370
+ // the timeline is what it cites and appends to.
1371
+ //
1372
+ // Naming:
1373
+ // :TimelineEvent — distinct from :Event (scheduling plugin's
1374
+ // schema:Event with eventId-unique). Collisions were the reason
1375
+ // to fork; do not blend.
1376
+ // :HAS_TIMELINE_EVENT — distinct from :HAS_EVENT (visitor analytics
1377
+ // Session→PageView/Click/ScrollMilestone).
1378
+ //
1379
+ // MERGE doctrine: writers MUST path-anchor from the entity to defend
1380
+ // cross-account collisions, e.g.
1381
+ // MATCH (e) WHERE elementId(e) = $entityId AND e.accountId = $accountId
1382
+ // MERGE (e)-[:HAS_TIMELINE_EVENT]->(t:TimelineEvent {timelineEventId: $id})
1383
+ // Never label-only MERGE on :TimelineEvent.
1384
+ //
1385
+ // `kind` enum (5 values, no catch-all):
1386
+ // mentioned | met | quoted | attended | transacted
1387
+ // Extractor refuses on ambiguity rather than collapsing to "other".
1388
+ //
1389
+ // `compiledTruth` source:
1390
+ // compiledTruthSource = "rewriter" (default) — compiled-truth-rewriter
1391
+ // library wrote it.
1392
+ // compiledTruthSource = "operator" — operator overrode via the graph UI.
1393
+ // Rewriter refuses to overwrite for 7 days unless forceRewrite=true.
1394
+ // ----------------------------------------------------------
1395
+
1396
+ CREATE CONSTRAINT timeline_event_id_unique IF NOT EXISTS
1397
+ FOR (t:TimelineEvent) REQUIRE t.timelineEventId IS UNIQUE;
1398
+
1399
+ CREATE INDEX timeline_event_account IF NOT EXISTS
1400
+ FOR (t:TimelineEvent) ON (t.accountId);
1401
+
1402
+ CREATE INDEX timeline_event_occurred_at IF NOT EXISTS
1403
+ FOR (t:TimelineEvent) ON (t.occurredAt);
1404
+
1405
+ CREATE INDEX timeline_event_kind IF NOT EXISTS
1406
+ FOR (t:TimelineEvent) ON (t.kind);
1407
+
1408
+ CREATE VECTOR INDEX timeline_event_embedding IF NOT EXISTS
1409
+ FOR (t:TimelineEvent) ON (t.embedding)
1410
+ OPTIONS {
1411
+ indexConfig: {
1412
+ `vector.dimensions`: 768,
1413
+ `vector.similarity_function`: 'cosine'
1414
+ }
1415
+ };
1416
+
1417
+ // `compiledTruthUpdatedAt` indices for stale-detection scans
1418
+ // (e.g. "find :Person with truth older than 7 days that has new events").
1419
+ CREATE INDEX person_compiled_truth_updated_at IF NOT EXISTS
1420
+ FOR (p:Person) ON (p.compiledTruthUpdatedAt);
1421
+
1422
+ CREATE INDEX organization_compiled_truth_updated_at IF NOT EXISTS
1423
+ FOR (o:Organization) ON (o.compiledTruthUpdatedAt);
1424
+
1425
+ CREATE INDEX concept_compiled_truth_updated_at IF NOT EXISTS
1426
+ FOR (c:Concept) ON (c.compiledTruthUpdatedAt);
@@ -254,7 +254,7 @@ fi
254
254
  emit_log "gate-blocked sessionId=${SESSION_ID} reason=sentinel-absent"
255
255
 
256
256
  cat >&2 <<'INSTRUCTION'
257
- The operator signalled session end. Before this session can close, run the three-pass retrospective inside this same admin session — do not spawn a new session, do not background anything. Use the tools you already have.
257
+ The operator signalled session end. Before this session can close, run the four-pass retrospective inside this same admin session — do not spawn a new session, do not background anything. Use the tools you already have.
258
258
 
259
259
  Pass 1 — Technical learnings. Walk this session for operator corrections and self-detected mistakes. For each one, delegate one graph write to `database-operator` via the Task tool, persisting a Learning or Correction node per the schema. Count what you write.
260
260
 
@@ -262,7 +262,9 @@ Pass 2 — Operator-relationship updates. Walk this session for tonal signals, r
262
262
 
263
263
  Pass 3 — Graph completeness sweep. Walk this session for any node, edge, or commitment that was discussed but not written to the graph in-flight. For each missing write, delegate to `database-operator` via the Task tool. Count what you write.
264
264
 
265
- Then call `session-retrospective-mark-complete` with the three counts and reply with one short paragraph in your house voice summarising what you wrote.
265
+ Pass 4 — Typed-edge auto-extraction. Delegate to `database-operator` via the Task tool with a prompt naming the typed-edge pass and the session-start timestamp. The specialist calls `memory-typed-edge-pass` with that timestamp and returns the counters. Capture `nodesProcessed` and the accepted count from its reply.
266
+
267
+ Then call `session-retrospective-mark-complete` with all five counts (learningsCount, preferencesCount, graphWriteCount, typedEdgesCount, nodesProcessed) and reply with one short paragraph in your house voice summarising what you wrote.
266
268
  INSTRUCTION
267
269
 
268
270
  exit 2
@@ -3094,13 +3094,15 @@ eagerTool(server, "action-edit", "Edit a pending action's input and then execute
3094
3094
  // trigger → gate-blocked → complete → gate-released
3095
3095
  // against one operator sessionId. The release decision itself is the
3096
3096
  // JSONL grep — never the log line.
3097
- server.tool("session-retrospective-mark-complete", "Marks the end-of-session retrospective complete. Call this AFTER walking the session for technical learnings, operator-relationship updates, and missing graph writes and persisting each one (Task-tool delegations to database-operator for graph writes, Write to SOUL.md for tonal updates). The counts you pass are the number of items you actually persisted in each pass. The session-end Stop hook gate releases on the presence of this tool_use in the transcript — calling it is the deterministic signal that the retrospective ran.", {
3097
+ server.tool("session-retrospective-mark-complete", "Marks the end-of-session retrospective complete. Call this AFTER walking the session for technical learnings, operator-relationship updates, missing graph writes, and the typed-edge auto-extraction pass — and persisting each one (Task-tool delegations to database-operator for graph writes and the typed-edge pass, Write to SOUL.md for tonal updates). The counts you pass are the number of items you actually persisted in each pass. The session-end Stop hook gate releases on the presence of this tool_use in the transcript — calling it is the deterministic signal that the retrospective ran.", {
3098
3098
  learningsCount: z.number().int().nonnegative().describe("Technical-learning / correction nodes you delegated to database-operator in this retrospective."),
3099
3099
  preferencesCount: z.number().int().nonnegative().describe("SOUL.md writes you made for tonal / working-style observations in this retrospective."),
3100
3100
  graphWriteCount: z.number().int().nonnegative().describe("Graph nodes/edges/commitments you delegated to database-operator for content that surfaced in the session but was not written in-flight."),
3101
- }, async ({ learningsCount, preferencesCount, graphWriteCount }) => {
3101
+ typedEdgesCount: z.number().int().nonnegative().describe("Typed edges accepted (MERGEd) by the typed-edge auto-extraction pass that database-operator ran in this retrospective. 0 is valid (no prose nodes touched or none yielded edges)."),
3102
+ nodesProcessed: z.number().int().nonnegative().describe("Prose-bearing nodes the typed-edge pass enumerated and scanned in this retrospective. 0 is valid (no prose nodes since the session started)."),
3103
+ }, async ({ learningsCount, preferencesCount, graphWriteCount, typedEdgesCount, nodesProcessed }) => {
3102
3104
  const sessionId = process.env.SESSION_ID || "unknown";
3103
- const line = `complete sessionId=${sessionId} learningsCount=${learningsCount} preferencesCount=${preferencesCount} graphWriteCount=${graphWriteCount}`;
3105
+ const line = `complete sessionId=${sessionId} learningsCount=${learningsCount} preferencesCount=${preferencesCount} graphWriteCount=${graphWriteCount} typedEdgesCount=${typedEdgesCount} nodesProcessed=${nodesProcessed}`;
3104
3106
  if (!PLATFORM_PORT) {
3105
3107
  // Module-init deliberately tolerates a missing PLATFORM_PORT
3106
3108
  // (lines ~62-71) so the MCP server does not loud-fail on plugin-
@@ -3131,7 +3133,7 @@ server.tool("session-retrospective-mark-complete", "Marks the end-of-session ret
3131
3133
  return {
3132
3134
  content: [{
3133
3135
  type: "text",
3134
- text: `Retrospective marked complete (learnings=${learningsCount} preferences=${preferencesCount} graphWrites=${graphWriteCount}). Session is now free to end.`,
3136
+ text: `Retrospective marked complete (learnings=${learningsCount} preferences=${preferencesCount} graphWrites=${graphWriteCount} typedEdges=${typedEdgesCount} nodesProcessed=${nodesProcessed}). Session is now free to end.`,
3135
3137
  }],
3136
3138
  };
3137
3139
  });