@zosmaai/pi-llm-wiki 0.10.9 → 0.11.1
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/CHANGELOG.md +6 -0
- package/README.de.md +35 -4
- package/README.es.md +260 -170
- package/README.fr.md +35 -4
- package/README.hi.md +35 -4
- package/README.ja.md +35 -4
- package/README.ko.md +35 -4
- package/README.md +41 -12
- package/README.pt.md +35 -4
- package/README.ru.md +35 -4
- package/README.zh.md +260 -170
- package/assets/demo.gif +0 -0
- package/dist/extensions/llm-wiki/lib/bootstrap.js +74 -0
- package/dist/extensions/llm-wiki/lib/embeddings.js +401 -0
- package/dist/extensions/llm-wiki/lib/guardrails.js +232 -0
- package/dist/extensions/llm-wiki/lib/indexing.js +78 -0
- package/dist/extensions/llm-wiki/lib/ingest-worker.js +410 -0
- package/dist/extensions/llm-wiki/lib/inject.js +65 -0
- package/dist/extensions/llm-wiki/lib/knowledge-document.js +442 -0
- package/dist/extensions/llm-wiki/lib/knowledge-links.js +206 -0
- package/dist/extensions/llm-wiki/lib/legacy-repair.js +443 -0
- package/dist/extensions/llm-wiki/lib/metadata.js +505 -0
- package/dist/extensions/llm-wiki/lib/model-command.js +85 -0
- package/dist/extensions/llm-wiki/lib/observation.js +283 -0
- package/dist/extensions/llm-wiki/lib/recall.js +875 -0
- package/dist/extensions/llm-wiki/lib/retro.js +158 -0
- package/dist/extensions/llm-wiki/lib/runtime.js +187 -0
- package/dist/extensions/llm-wiki/lib/source-extractors.js +426 -0
- package/dist/extensions/llm-wiki/lib/source-packet.js +229 -0
- package/dist/extensions/llm-wiki/lib/subagent.js +41 -0
- package/dist/extensions/llm-wiki/lib/task-config.js +201 -0
- package/dist/extensions/llm-wiki/lib/tools.js +1199 -0
- package/dist/extensions/llm-wiki/lib/trajectories-command.js +51 -0
- package/dist/extensions/llm-wiki/lib/trajectory.js +467 -0
- package/dist/extensions/llm-wiki/lib/utils.js +353 -0
- package/dist/extensions/llm-wiki/lib/vault-format.js +247 -0
- package/dist/extensions/llm-wiki/lib/visible-status.js +31 -0
- package/dist/extensions/llm-wiki/lib/wiki-service.js +128 -0
- package/dist/mcp/exec.js +121 -0
- package/dist/mcp/index.js +229 -0
- package/dist/mcp/operations.js +130 -0
- package/dist/package.json +1 -0
- package/docs/api.md +5 -2
- package/docs/architecture.md +5 -2
- package/docs/configuration.md +25 -0
- package/docs/superpowers/plans/2026-08-02-okf-foundation.md +1579 -0
- package/docs/superpowers/plans/2026-08-03-okf-foundation-remediation.md +3005 -0
- package/docs/superpowers/plans/2026-08-06-authoritative-event-history-phase-1-foundation-hardening.md +937 -0
- package/docs/superpowers/plans/2026-08-06-okf-foundation-release-remediation.md +1174 -0
- package/docs/superpowers/plans/2026-08-07-synthesis-language.md +98 -0
- package/docs/superpowers/specs/2026-08-02-okf-foundation-design.md +593 -0
- package/docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md +542 -0
- package/docs/superpowers/specs/2026-08-07-synthesis-language-design.md +94 -0
- package/extensions/llm-wiki/index.ts +22 -36
- package/extensions/llm-wiki/lib/bootstrap.ts +87 -0
- package/extensions/llm-wiki/lib/embeddings.ts +9 -3
- package/extensions/llm-wiki/lib/guardrails.ts +26 -18
- package/extensions/llm-wiki/lib/indexing.ts +2 -1
- package/extensions/llm-wiki/lib/ingest-worker.ts +304 -28
- package/extensions/llm-wiki/lib/knowledge-document.ts +663 -0
- package/extensions/llm-wiki/lib/knowledge-links.ts +282 -0
- package/extensions/llm-wiki/lib/legacy-repair.ts +572 -0
- package/extensions/llm-wiki/lib/metadata.ts +550 -128
- package/extensions/llm-wiki/lib/model-command.ts +0 -1
- package/extensions/llm-wiki/lib/observation.ts +37 -43
- package/extensions/llm-wiki/lib/recall.ts +61 -33
- package/extensions/llm-wiki/lib/retro.ts +65 -41
- package/extensions/llm-wiki/lib/runtime.ts +0 -3
- package/extensions/llm-wiki/lib/source-extractors.ts +12 -17
- package/extensions/llm-wiki/lib/source-packet.ts +45 -32
- package/extensions/llm-wiki/lib/task-config.ts +36 -0
- package/extensions/llm-wiki/lib/tools.ts +413 -342
- package/extensions/llm-wiki/lib/trajectory.ts +15 -1
- package/extensions/llm-wiki/lib/utils.ts +127 -131
- package/extensions/llm-wiki/lib/vault-format.ts +363 -0
- package/extensions/llm-wiki/lib/wiki-service.ts +183 -0
- package/mcp/exec.ts +122 -0
- package/mcp/index.ts +60 -250
- package/mcp/operations.ts +176 -0
- package/package.json +8 -2
- package/scripts/migrate-llm-wiki.js +801 -0
- package/skills/llm-wiki/SKILL.md +12 -8
|
@@ -4,9 +4,19 @@ import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
|
4
4
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import type { Static } from "typebox";
|
|
7
|
+
import {
|
|
8
|
+
type KnowledgeDiagnostic,
|
|
9
|
+
type KnowledgeDocument,
|
|
10
|
+
createKnowledgeDocument,
|
|
11
|
+
patchKnowledgeDocument,
|
|
12
|
+
readKnowledgeDocumentFile,
|
|
13
|
+
serializeKnowledgeDocument,
|
|
14
|
+
writeKnowledgeDocumentFile,
|
|
15
|
+
} from "./knowledge-document.js";
|
|
7
16
|
import { appendEvent, rebuildMetadataLight } from "./metadata.js";
|
|
8
17
|
import { runSubAgent } from "./subagent.js";
|
|
9
18
|
import { type VaultPaths, fmtDate, slugify } from "./utils.js";
|
|
19
|
+
import { VaultWriteError, assertWritableVault } from "./vault-format.js";
|
|
10
20
|
|
|
11
21
|
/**
|
|
12
22
|
* Background ingest synthesis (issue #65, part of epic #63).
|
|
@@ -76,64 +86,239 @@ export interface CommitResult {
|
|
|
76
86
|
contradictions: number;
|
|
77
87
|
}
|
|
78
88
|
|
|
89
|
+
export type CommitSynthesisOutcome =
|
|
90
|
+
| ({ ok: true } & CommitResult)
|
|
91
|
+
| { ok: false; sourceId: string; diagnostics: KnowledgeDiagnostic[] };
|
|
92
|
+
|
|
79
93
|
// ── deterministic persistence (no LLM) ────────────────────
|
|
80
94
|
|
|
81
|
-
|
|
95
|
+
/** Localized headings for generated pages (issue #124). */
|
|
96
|
+
const HEADINGS: Record<string, Record<string, string>> = {
|
|
97
|
+
ru: {
|
|
98
|
+
summary: "Резюме",
|
|
99
|
+
keyTakeaways: "Ключевые выводы",
|
|
100
|
+
entitiesMentioned: "Упомянутые сущности",
|
|
101
|
+
conceptsMentioned: "Упомянутые концепции",
|
|
102
|
+
notableQuotes: "Заметные цитаты",
|
|
103
|
+
contradictions: "Противоречия",
|
|
104
|
+
sourcePacket: "Пакет источника",
|
|
105
|
+
overview: "Обзор",
|
|
106
|
+
definition: "Определение",
|
|
107
|
+
noneRecorded: "[Не записано]",
|
|
108
|
+
none: "[Нет]",
|
|
109
|
+
id: "ID",
|
|
110
|
+
extracted: "Извлечено",
|
|
111
|
+
manifest: "Манифест",
|
|
112
|
+
contradiction: "⚠️ **Противоречие**",
|
|
113
|
+
},
|
|
114
|
+
fr: {
|
|
115
|
+
summary: "Résumé",
|
|
116
|
+
keyTakeaways: "Points clés",
|
|
117
|
+
entitiesMentioned: "Entités mentionnées",
|
|
118
|
+
conceptsMentioned: "Concepts mentionnés",
|
|
119
|
+
notableQuotes: "Citations notables",
|
|
120
|
+
contradictions: "Contradictions",
|
|
121
|
+
sourcePacket: "Paquet source",
|
|
122
|
+
overview: "Aperçu",
|
|
123
|
+
definition: "Définition",
|
|
124
|
+
noneRecorded: "[Aucun enregistré]",
|
|
125
|
+
none: "[Aucun]",
|
|
126
|
+
id: "ID",
|
|
127
|
+
extracted: "Extrait",
|
|
128
|
+
manifest: "Manifeste",
|
|
129
|
+
contradiction: "⚠️ **Contradiction**",
|
|
130
|
+
},
|
|
131
|
+
de: {
|
|
132
|
+
summary: "Zusammenfassung",
|
|
133
|
+
keyTakeaways: "Wichtige Erkenntnisse",
|
|
134
|
+
entitiesMentioned: "Erwähnte Entitäten",
|
|
135
|
+
conceptsMentioned: "Erwähnte Konzepte",
|
|
136
|
+
notableQuotes: "Bemerkenswerte Zitate",
|
|
137
|
+
contradictions: "Widersprüche",
|
|
138
|
+
sourcePacket: "Quellenpaket",
|
|
139
|
+
overview: "Übersicht",
|
|
140
|
+
definition: "Definition",
|
|
141
|
+
noneRecorded: "[Keine aufgezeichnet]",
|
|
142
|
+
none: "[Keine]",
|
|
143
|
+
id: "ID",
|
|
144
|
+
extracted: "Extrahiert",
|
|
145
|
+
manifest: "Manifest",
|
|
146
|
+
contradiction: "⚠️ **Widerspruch**",
|
|
147
|
+
},
|
|
148
|
+
ja: {
|
|
149
|
+
summary: "要約",
|
|
150
|
+
keyTakeaways: "主な要点",
|
|
151
|
+
entitiesMentioned: "言及されたエンティティ",
|
|
152
|
+
conceptsMentioned: "言及された概念",
|
|
153
|
+
notableQuotes: "注目すべき引用",
|
|
154
|
+
contradictions: "矛盾",
|
|
155
|
+
sourcePacket: "ソースパケット",
|
|
156
|
+
overview: "概要",
|
|
157
|
+
definition: "定義",
|
|
158
|
+
noneRecorded: "[記録なし]",
|
|
159
|
+
none: "[なし]",
|
|
160
|
+
id: "ID",
|
|
161
|
+
extracted: "抽出済み",
|
|
162
|
+
manifest: "マニフェスト",
|
|
163
|
+
contradiction: "⚠️ **矛盾**",
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function getHeadings(lang?: string): Record<string, string> {
|
|
168
|
+
if (lang && HEADINGS[lang]) return HEADINGS[lang];
|
|
169
|
+
// English defaults
|
|
170
|
+
return {
|
|
171
|
+
summary: "Summary",
|
|
172
|
+
keyTakeaways: "Key Takeaways",
|
|
173
|
+
entitiesMentioned: "Entities Mentioned",
|
|
174
|
+
conceptsMentioned: "Concepts Mentioned",
|
|
175
|
+
notableQuotes: "Notable Quotes",
|
|
176
|
+
contradictions: "Contradictions",
|
|
177
|
+
sourcePacket: "Source Packet",
|
|
178
|
+
overview: "Overview",
|
|
179
|
+
definition: "Definition",
|
|
180
|
+
noneRecorded: "[None recorded]",
|
|
181
|
+
none: "[None]",
|
|
182
|
+
id: "ID",
|
|
183
|
+
extracted: "Extracted",
|
|
184
|
+
manifest: "Manifest",
|
|
185
|
+
contradiction: "⚠️ **Contradiction**",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function buildEntityPageBody(
|
|
82
190
|
title: string,
|
|
83
191
|
description: string,
|
|
84
|
-
date: string,
|
|
85
192
|
sourceId: string,
|
|
193
|
+
lang?: string,
|
|
86
194
|
): string {
|
|
87
195
|
const desc = description.trim() || "One-line description.";
|
|
88
|
-
|
|
196
|
+
const h = getHeadings(lang);
|
|
197
|
+
return `# ${title}
|
|
198
|
+
|
|
199
|
+
${desc}
|
|
200
|
+
|
|
201
|
+
## ${h.overview}
|
|
202
|
+
|
|
203
|
+
[Key facts]
|
|
204
|
+
|
|
205
|
+
## Links
|
|
206
|
+
|
|
207
|
+
- [${sourceId}](/sources/${sourceId}.md)`;
|
|
89
208
|
}
|
|
90
209
|
|
|
91
|
-
function
|
|
210
|
+
function buildConceptPageBody(
|
|
92
211
|
title: string,
|
|
93
212
|
definition: string,
|
|
94
|
-
date: string,
|
|
95
213
|
sourceId: string,
|
|
214
|
+
lang?: string,
|
|
96
215
|
): string {
|
|
97
216
|
const def = definition.trim() || "One-line definition.";
|
|
98
|
-
|
|
217
|
+
const h = getHeadings(lang);
|
|
218
|
+
return `# ${title}
|
|
219
|
+
|
|
220
|
+
${def}
|
|
221
|
+
|
|
222
|
+
## ${h.definition}
|
|
223
|
+
|
|
224
|
+
[Clear explanation]
|
|
225
|
+
|
|
226
|
+
## Links
|
|
227
|
+
|
|
228
|
+
- [${sourceId}](/sources/${sourceId}.md)`;
|
|
99
229
|
}
|
|
100
230
|
|
|
101
|
-
/** Rebuild the source page from synthesis data, marking it ingested. */
|
|
102
|
-
export function
|
|
231
|
+
/** Rebuild the source page body from synthesis data, marking it ingested. */
|
|
232
|
+
export function buildIngestedSourcePageBody(
|
|
103
233
|
manifest: Record<string, unknown>,
|
|
104
234
|
data: SynthesisData,
|
|
105
|
-
|
|
235
|
+
_date: string,
|
|
236
|
+
lang?: string,
|
|
106
237
|
): string {
|
|
107
238
|
const id = String(manifest.id);
|
|
108
239
|
const title = String(manifest.title || id);
|
|
109
240
|
const url = manifest.url ? `\n> _Original: [${manifest.url}](${manifest.url})_` : "";
|
|
110
|
-
const
|
|
111
|
-
const captured = String(manifest.captured || date);
|
|
241
|
+
const h = getHeadings(lang);
|
|
112
242
|
|
|
113
243
|
const takeaways =
|
|
114
244
|
data.key_takeaways.length > 0
|
|
115
245
|
? data.key_takeaways.map((t) => `- ${t.trim()}`).join("\n")
|
|
116
|
-
:
|
|
246
|
+
: `- ${h.noneRecorded}`;
|
|
117
247
|
const entities =
|
|
118
248
|
data.entities.length > 0
|
|
119
|
-
? data.entities.map((e) => `- [
|
|
120
|
-
:
|
|
249
|
+
? data.entities.map((e) => `- [${e.title}](/entities/${slugify(e.title)}.md)`).join("\n")
|
|
250
|
+
: `- ${h.none}`;
|
|
121
251
|
const concepts =
|
|
122
252
|
data.concepts.length > 0
|
|
123
|
-
? data.concepts.map((c) => `- [
|
|
124
|
-
:
|
|
253
|
+
? data.concepts.map((c) => `- [${c.title}](/concepts/${slugify(c.title)}.md)`).join("\n")
|
|
254
|
+
: `- ${h.none}`;
|
|
125
255
|
const quotes =
|
|
126
256
|
data.quotes && data.quotes.length > 0
|
|
127
257
|
? data.quotes
|
|
128
258
|
.map((q) => `> ${q.text.trim()}${q.attribution ? ` — ${q.attribution}` : ""}`)
|
|
129
259
|
.join("\n\n")
|
|
130
|
-
:
|
|
260
|
+
: `> ${h.noneRecorded}`;
|
|
131
261
|
const contradictions =
|
|
132
262
|
data.contradictions && data.contradictions.length > 0
|
|
133
|
-
? `\n##
|
|
263
|
+
? `\n## ${h.contradictions}\n\n${data.contradictions.map((c) => `${h.contradiction}: ${c.trim()}`).join("\n")}\n`
|
|
134
264
|
: "";
|
|
135
265
|
|
|
136
|
-
return
|
|
266
|
+
return `# ${title}${url}
|
|
267
|
+
|
|
268
|
+
## ${h.summary}
|
|
269
|
+
|
|
270
|
+
${data.summary.trim()}
|
|
271
|
+
|
|
272
|
+
## ${h.keyTakeaways}
|
|
273
|
+
|
|
274
|
+
${takeaways}
|
|
275
|
+
|
|
276
|
+
## ${h.entitiesMentioned}
|
|
277
|
+
|
|
278
|
+
${entities}
|
|
279
|
+
|
|
280
|
+
## ${h.conceptsMentioned}
|
|
281
|
+
|
|
282
|
+
${concepts}
|
|
283
|
+
|
|
284
|
+
## ${h.notableQuotes}
|
|
285
|
+
|
|
286
|
+
${quotes}
|
|
287
|
+
${contradictions}## ${h.sourcePacket}
|
|
288
|
+
|
|
289
|
+
- **${h.id}:** \`sources/${id}\`
|
|
290
|
+
- **${h.extracted}:** \`raw/sources/${id}/extracted.md\`
|
|
291
|
+
- **${h.manifest}:** \`raw/sources/${id}/manifest.json\`
|
|
292
|
+
`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Rebuild the source page from synthesis data, marking it ingested. */
|
|
296
|
+
export function buildIngestedSourcePage(
|
|
297
|
+
manifest: Record<string, unknown>,
|
|
298
|
+
data: SynthesisData,
|
|
299
|
+
date: string,
|
|
300
|
+
lang?: string,
|
|
301
|
+
): string {
|
|
302
|
+
const id = String(manifest.id);
|
|
303
|
+
const title = String(manifest.title || id);
|
|
304
|
+
const format = String(manifest.format || "unknown");
|
|
305
|
+
const captured = String(manifest.captured || date);
|
|
306
|
+
const body = buildIngestedSourcePageBody(manifest, data, date, lang);
|
|
307
|
+
const doc = createKnowledgeDocument(
|
|
308
|
+
`sources/${id}.md`,
|
|
309
|
+
{
|
|
310
|
+
type: "source",
|
|
311
|
+
title,
|
|
312
|
+
format,
|
|
313
|
+
source_id: id,
|
|
314
|
+
raw_path: `raw/sources/${id}/extracted.md`,
|
|
315
|
+
captured,
|
|
316
|
+
status: "ingested",
|
|
317
|
+
updated: date,
|
|
318
|
+
},
|
|
319
|
+
body,
|
|
320
|
+
);
|
|
321
|
+
return serializeKnowledgeDocument(doc);
|
|
137
322
|
}
|
|
138
323
|
|
|
139
324
|
/**
|
|
@@ -147,7 +332,8 @@ export function commitSynthesis(
|
|
|
147
332
|
manifest: Record<string, unknown>,
|
|
148
333
|
data: SynthesisData,
|
|
149
334
|
date: string = fmtDate(),
|
|
150
|
-
|
|
335
|
+
lang?: string,
|
|
336
|
+
): CommitSynthesisOutcome {
|
|
151
337
|
const result: CommitResult = {
|
|
152
338
|
sourceId,
|
|
153
339
|
sourcePage: join(paths.wiki, "sources", `${sourceId}.md`),
|
|
@@ -158,9 +344,42 @@ export function commitSynthesis(
|
|
|
158
344
|
contradictions: data.contradictions?.length ?? 0,
|
|
159
345
|
};
|
|
160
346
|
|
|
161
|
-
|
|
347
|
+
try {
|
|
348
|
+
assertWritableVault(paths);
|
|
349
|
+
} catch (error: unknown) {
|
|
350
|
+
if (error instanceof VaultWriteError) {
|
|
351
|
+
return { ok: false, sourceId, diagnostics: error.diagnostics };
|
|
352
|
+
}
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Patch existing documents so unknown fields, legacy sources, and titles survive.
|
|
357
|
+
let sourceDocument: KnowledgeDocument;
|
|
358
|
+
if (existsSync(result.sourcePage)) {
|
|
359
|
+
const parsed = readKnowledgeDocumentFile(result.sourcePage, `sources/${sourceId}.md`);
|
|
360
|
+
if (!parsed.ok) return { ok: false, sourceId, diagnostics: parsed.diagnostics };
|
|
361
|
+
sourceDocument = patchKnowledgeDocument(parsed.document, {
|
|
362
|
+
fields: { status: "ingested", updated: date },
|
|
363
|
+
body: buildIngestedSourcePageBody(manifest, data, date, lang),
|
|
364
|
+
});
|
|
365
|
+
} else {
|
|
366
|
+
sourceDocument = createKnowledgeDocument(
|
|
367
|
+
`sources/${sourceId}.md`,
|
|
368
|
+
{
|
|
369
|
+
type: "source",
|
|
370
|
+
title: String(manifest.title || sourceId),
|
|
371
|
+
format: String(manifest.format || "unknown"),
|
|
372
|
+
source_id: sourceId,
|
|
373
|
+
raw_path: `raw/sources/${sourceId}/extracted.md`,
|
|
374
|
+
captured: String(manifest.captured || date),
|
|
375
|
+
status: "ingested",
|
|
376
|
+
updated: date,
|
|
377
|
+
},
|
|
378
|
+
buildIngestedSourcePageBody(manifest, data, date, lang),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
162
381
|
mkdirSync(join(paths.wiki, "sources"), { recursive: true });
|
|
163
|
-
|
|
382
|
+
writeKnowledgeDocumentFile(result.sourcePage, sourceDocument);
|
|
164
383
|
|
|
165
384
|
// Entity pages — create if absent, link if present.
|
|
166
385
|
mkdirSync(join(paths.wiki, "entities"), { recursive: true });
|
|
@@ -171,7 +390,19 @@ export function commitSynthesis(
|
|
|
171
390
|
if (existsSync(pagePath)) {
|
|
172
391
|
result.entitiesLinked.push(slug);
|
|
173
392
|
} else {
|
|
174
|
-
|
|
393
|
+
const entityDoc = createKnowledgeDocument(
|
|
394
|
+
`entities/${slug}.md`,
|
|
395
|
+
{
|
|
396
|
+
type: "entity",
|
|
397
|
+
title: e.title,
|
|
398
|
+
description: e.description.trim() || "One-line description.",
|
|
399
|
+
created: date,
|
|
400
|
+
updated: date,
|
|
401
|
+
},
|
|
402
|
+
buildEntityPageBody(e.title, e.description, sourceId, lang),
|
|
403
|
+
[{ id: sourceId, resource: `/sources/${sourceId}.md` }],
|
|
404
|
+
);
|
|
405
|
+
writeKnowledgeDocumentFile(pagePath, entityDoc);
|
|
175
406
|
result.entitiesCreated.push(slug);
|
|
176
407
|
}
|
|
177
408
|
}
|
|
@@ -185,7 +416,19 @@ export function commitSynthesis(
|
|
|
185
416
|
if (existsSync(pagePath)) {
|
|
186
417
|
result.conceptsLinked.push(slug);
|
|
187
418
|
} else {
|
|
188
|
-
|
|
419
|
+
const conceptDoc = createKnowledgeDocument(
|
|
420
|
+
`concepts/${slug}.md`,
|
|
421
|
+
{
|
|
422
|
+
type: "concept",
|
|
423
|
+
title: c.title,
|
|
424
|
+
description: c.definition.trim() || "One-line definition.",
|
|
425
|
+
created: date,
|
|
426
|
+
updated: date,
|
|
427
|
+
},
|
|
428
|
+
buildConceptPageBody(c.title, c.definition, sourceId, lang),
|
|
429
|
+
[{ id: sourceId, resource: `/sources/${sourceId}.md` }],
|
|
430
|
+
);
|
|
431
|
+
writeKnowledgeDocumentFile(pagePath, conceptDoc);
|
|
189
432
|
result.conceptsCreated.push(slug);
|
|
190
433
|
}
|
|
191
434
|
}
|
|
@@ -199,7 +442,7 @@ export function commitSynthesis(
|
|
|
199
442
|
background: true,
|
|
200
443
|
});
|
|
201
444
|
|
|
202
|
-
return result;
|
|
445
|
+
return { ok: true, ...result };
|
|
203
446
|
}
|
|
204
447
|
|
|
205
448
|
// ── sub-agent synthesis (LLM) ─────────────────────────────
|
|
@@ -230,6 +473,8 @@ export interface RunIngestSynthesisArgs {
|
|
|
230
473
|
/** Cap on extracted chars fed to the model (avoid huge prompts). Default 24k. */
|
|
231
474
|
maxChars?: number;
|
|
232
475
|
signal?: AbortSignal;
|
|
476
|
+
/** BCP 47 language tag for narrative content (issue #124). */
|
|
477
|
+
synthesisLanguage?: string;
|
|
233
478
|
}
|
|
234
479
|
|
|
235
480
|
/**
|
|
@@ -240,10 +485,26 @@ export interface RunIngestSynthesisArgs {
|
|
|
240
485
|
export async function runIngestSynthesis(
|
|
241
486
|
args: RunIngestSynthesisArgs,
|
|
242
487
|
): Promise<CommitResult | undefined> {
|
|
243
|
-
const {
|
|
488
|
+
const {
|
|
489
|
+
model,
|
|
490
|
+
apiKey,
|
|
491
|
+
headers,
|
|
492
|
+
paths,
|
|
493
|
+
sourceId,
|
|
494
|
+
manifest,
|
|
495
|
+
extracted,
|
|
496
|
+
maxChars,
|
|
497
|
+
signal,
|
|
498
|
+
synthesisLanguage,
|
|
499
|
+
} = args;
|
|
244
500
|
const content = extracted.slice(0, maxChars ?? 24_000);
|
|
245
501
|
if (!content.trim()) return undefined;
|
|
246
502
|
|
|
503
|
+
const languageInstruction = synthesisLanguage
|
|
504
|
+
? `\n\nWrite all generated content in ${synthesisLanguage}, including titles, headings, summaries, descriptions, and concept/entity names. Only preserve code, API names, file paths, commands, exact technical identifiers, and verbatim quotations in their original form.`
|
|
505
|
+
: "";
|
|
506
|
+
const systemPrompt = INGEST_SYSTEM + languageInstruction;
|
|
507
|
+
|
|
247
508
|
let committed: CommitResult | undefined;
|
|
248
509
|
|
|
249
510
|
const commitTool: AgentTool<typeof CommitSynthesisSchema> = {
|
|
@@ -253,7 +514,22 @@ export async function runIngestSynthesis(
|
|
|
253
514
|
"Persist the structured synthesis of this source into wiki pages. Call exactly once.",
|
|
254
515
|
parameters: CommitSynthesisSchema,
|
|
255
516
|
execute: async (_id, params) => {
|
|
256
|
-
|
|
517
|
+
const outcome = commitSynthesis(
|
|
518
|
+
paths,
|
|
519
|
+
sourceId,
|
|
520
|
+
manifest,
|
|
521
|
+
params,
|
|
522
|
+
undefined,
|
|
523
|
+
synthesisLanguage,
|
|
524
|
+
);
|
|
525
|
+
if (!outcome.ok) {
|
|
526
|
+
return {
|
|
527
|
+
content: [{ type: "text", text: `Failed: ${outcome.diagnostics[0].message}` }],
|
|
528
|
+
details: { sourceId },
|
|
529
|
+
isError: true,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
committed = outcome;
|
|
257
533
|
const ack = `Committed: source page + ${committed.entitiesCreated.length} new entit${
|
|
258
534
|
committed.entitiesCreated.length === 1 ? "y" : "ies"
|
|
259
535
|
}, ${committed.conceptsCreated.length} new concept${
|
|
@@ -270,7 +546,7 @@ export async function runIngestSynthesis(
|
|
|
270
546
|
model,
|
|
271
547
|
apiKey,
|
|
272
548
|
headers,
|
|
273
|
-
systemPrompt
|
|
549
|
+
systemPrompt,
|
|
274
550
|
userPrompt,
|
|
275
551
|
tools: [commitTool as AgentTool],
|
|
276
552
|
signal,
|