index-enrich.mjs
171 lines 5.7 KB
Raw
sha256:0d530f9ef27b8b75547d1db7701a74bc77b77aa8f3d7fa3a8672cf2af36e63bb reconcile: import GitHub-direct RBAC/OAuth/companion and ho… Human minor ⚠ breaking 6 hours ago
1 /**
2 * Issue #1 Phase F3 — post-index enrichment via sampling.
3 * Generates per-note summaries and writes them as frontmatter metadata.
4 * Opt-in only (expensive: one sampling call per note).
5 *
6 * Phase 6 migration (D6.6.2): all persistence now routes through DerivedArtifactWriter.
7 * The direct writeNote call is removed. A writer must be supplied by the caller or built
8 * internally from config (convenience/self-partition default).
9 */
10
11 import { readNote, resolveVaultRelativePath } from '../../lib/vault.mjs';
12 import { writeNote } from '../../lib/write.mjs';
13 import { trySampling } from '../sampling.mjs';
14 import { completeChat } from '../../lib/llm-complete.mjs';
15 import {
16 createDerivedArtifactWriter,
17 } from '../../lib/companion-artifact-writer.mjs';
18 import {
19 buildConvenienceProvenance,
20 PROVENANCE_SCHEMA_VERSION,
21 } from '../../lib/companion-provenance-validator.mjs';
22
23 const SUMMARY_SYSTEM = 'Summarize the following note in 1-2 sentences. Be factual and concise. Output only the summary text, nothing else.';
24 const MAX_NOTE_CHARS = 16000;
25 const INTER_NOTE_DELAY_MS = 200;
26
27 /**
28 * Build a DerivedArtifactWriter from a config object for the convenience/self-partition default.
29 * Used when the caller does not supply an explicit writer.
30 *
31 * @param {object} config - loadConfig() result
32 * @returns {import('../../lib/companion-artifact-writer.mjs').DerivedArtifactWriter}
33 */
34 function _buildDefaultWriter(config) {
35 return createDerivedArtifactWriter({
36 writeNoteFn: writeNote,
37 vaultPath: config.vault_path,
38 vaultRegistryAvailable: false, // convenience-only until vault registry exists
39 });
40 }
41
42 /**
43 * Build the write context for the convenience/self-partition default.
44 * All enrichment here is self-partition (no delegation).
45 *
46 * @returns {import('../../lib/companion-artifact-writer.mjs').WriteContext}
47 */
48 function _defaultWriteContext() {
49 return {
50 lane: 'local',
51 containsPrivateData: false,
52 isDelegate: false,
53 delegatedManagedAllowed: false,
54 enrichesDelegatedPartition: false,
55 delegatedEnrichmentAllowed: false,
56 };
57 }
58
59 /**
60 * Enrich recently indexed notes by generating short summaries via sampling.
61 * Summaries are written to frontmatter field `ai_summary` via DerivedArtifactWriter.
62 *
63 * Phase 6: persistence is routed through the writer (D6.6.2).
64 * The writer performs provenance validation, tier resolution, consent gate, and
65 * encryption routing before any write occurs. For the convenience/self-partition
66 * default the behavior is unchanged — the writer passes all gates and stores
67 * host-readable frontmatter exactly as before.
68 *
69 * @param {import('@modelcontextprotocol/sdk/server/mcp.js').McpServer} mcpServer
70 * @param {object} config - loadConfig() result
71 * @param {{
72 * limit?: number,
73 * onProgress?: (done: number, total: number) => Promise<void>,
74 * writer?: import('../../lib/companion-artifact-writer.mjs').DerivedArtifactWriter,
75 * writerContext?: import('../../lib/companion-artifact-writer.mjs').WriteContext,
76 * generatedBy?: string,
77 * model?: string,
78 * modelVersion?: string,
79 * runtimeVersion?: string,
80 * }} [opts]
81 * @returns {Promise<number>} count of notes enriched
82 */
83 export async function enrichIndexedNotes(mcpServer, config, opts = {}) {
84 const { runListNotes } = await import('../../lib/list-notes.mjs');
85 const limit = Math.min(opts.limit ?? 50, 200);
86
87 const listing = runListNotes(config, {
88 limit,
89 offset: 0,
90 order: 'date',
91 fields: 'full',
92 });
93
94 const notes = (listing.notes || []).filter((n) => {
95 if (!n.path || !n.body) return false;
96 if (n.frontmatter?.ai_summary) return false;
97 return true;
98 });
99
100 const writer = opts.writer ?? _buildDefaultWriter(config);
101 const context = opts.writerContext ?? _defaultWriteContext();
102 const generatedBy = opts.generatedBy || config.vault_id || 'system';
103 const model = opts.model || config.llm?.model || 'unknown';
104 // D6.2.1: one of model_version|runtime_version MUST be a concrete value.
105 // When no version is configured, use 'unknown' rather than null to satisfy this.
106 const modelVersion = opts.modelVersion || config.llm?.model_version || 'unknown';
107 const runtimeVersion = opts.runtimeVersion || null;
108
109 let enriched = 0;
110 for (let i = 0; i < notes.length; i++) {
111 const note = notes[i];
112 try {
113 const body = note.body.slice(0, MAX_NOTE_CHARS);
114 const userPrompt = `Note path: ${note.path}\n\n${body}`;
115
116 let summary = await trySampling(mcpServer, {
117 system: SUMMARY_SYSTEM,
118 user: userPrompt,
119 maxTokens: 200,
120 });
121
122 if (!summary) {
123 try {
124 summary = await completeChat(config, {
125 system: SUMMARY_SYSTEM,
126 user: userPrompt,
127 maxTokens: 200,
128 });
129 } catch (_) {
130 continue;
131 }
132 }
133
134 if (summary) {
135 const truncatedSummary = summary.slice(0, 500);
136 const sourceEventId = `enrich-${note.path}-${Date.now()}`;
137
138 const provenance = buildConvenienceProvenance({
139 generatedBy,
140 source: 'companion',
141 model,
142 modelVersion: modelVersion ?? undefined,
143 runtimeVersion: runtimeVersion ?? undefined,
144 lane: context.lane,
145 artifactType: 'ai_summary',
146 sourceNotePath: note.path,
147 sourceEventId,
148 });
149
150 const artifact = { summary: truncatedSummary };
151 const result = await writer.write(artifact, provenance, context);
152
153 if (result.ok) {
154 enriched++;
155 }
156 }
157 } catch (_) {
158 // Skip individual note failures — same as before
159 }
160
161 if (opts.onProgress) {
162 await opts.onProgress(i + 1, notes.length);
163 }
164
165 if (i < notes.length - 1 && INTER_NOTE_DELAY_MS > 0) {
166 await new Promise((r) => setTimeout(r, INTER_NOTE_DELAY_MS));
167 }
168 }
169
170 return enriched;
171 }
File History 1 commit
sha256:0d530f9ef27b8b75547d1db7701a74bc77b77aa8f3d7fa3a8672cf2af36e63bb reconcile: import GitHub-direct RBAC/OAuth/companion and ho… Human minor 6 hours ago