@agentproto/corpus 0.1.0-alpha.1 → 0.1.0-alpha.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/dist/chunk-3P23OX5X.mjs +93 -0
- package/dist/chunk-3P23OX5X.mjs.map +1 -0
- package/dist/fs.port-BHzctsoT.d.ts +60 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.mjs +16 -82
- package/dist/index.mjs.map +1 -1
- package/dist/model-CtAaBJn4.d.ts +24 -0
- package/dist/ports/index.d.ts +3 -60
- package/dist/report/index.d.ts +505 -0
- package/dist/report/index.mjs +633 -0
- package/dist/report/index.mjs.map +1 -0
- package/package.json +8 -3
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import { resolveKnowledge } from '../chunk-3P23OX5X.mjs';
|
|
2
|
+
import matter from 'gray-matter';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @agentproto/corpus v0.1.0-alpha
|
|
7
|
+
* Composition of AIP-10/12/18/9/15/41 — autonomous knowledge-improvement kit.
|
|
8
|
+
*/
|
|
9
|
+
var flat = (v) => Array.isArray(v) ? v.join(" ") : v == null ? "" : String(v);
|
|
10
|
+
var asTags = (v) => Array.isArray(v) ? v : v ? [String(v)] : [];
|
|
11
|
+
var firstSentence = (body) => {
|
|
12
|
+
const txt = body.replace(/^#.*$/gm, "").replace(/\s+/g, " ").trim();
|
|
13
|
+
return (txt.split(/(?<=[.!?])\s/)[0] || txt).slice(0, 260);
|
|
14
|
+
};
|
|
15
|
+
async function walkMd(dataset, dir) {
|
|
16
|
+
let rels;
|
|
17
|
+
try {
|
|
18
|
+
rels = await dataset.walk(dir);
|
|
19
|
+
} catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
return rels.filter((r) => r.endsWith(".md")).map((r) => `${dir}/${r}`);
|
|
23
|
+
}
|
|
24
|
+
async function buildPacks(opts) {
|
|
25
|
+
const { dataset, config } = opts;
|
|
26
|
+
const viewsDir = opts.viewsDir ?? "views";
|
|
27
|
+
const facetOrder = [...new Set(config.chapters.flatMap((c) => c.facets ?? []))];
|
|
28
|
+
const sources = [];
|
|
29
|
+
for (const path of await walkMd(dataset, "sources")) {
|
|
30
|
+
let raw;
|
|
31
|
+
try {
|
|
32
|
+
raw = await dataset.readFile(path);
|
|
33
|
+
} catch {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
let fm = {};
|
|
37
|
+
try {
|
|
38
|
+
fm = matter(raw).data;
|
|
39
|
+
} catch {
|
|
40
|
+
fm = {};
|
|
41
|
+
}
|
|
42
|
+
const id = flat(fm.id).trim();
|
|
43
|
+
if (!id) continue;
|
|
44
|
+
const corpusMeta = fm.metadata?.corpus ?? {};
|
|
45
|
+
const url = String(
|
|
46
|
+
corpusMeta.originalUrl ?? corpusMeta.importerSourceUrl ?? ""
|
|
47
|
+
).replace(/\s+/g, "").trim();
|
|
48
|
+
sources.push({
|
|
49
|
+
id,
|
|
50
|
+
title: flat(fm.title).replace(/\s+/g, " ").trim(),
|
|
51
|
+
url,
|
|
52
|
+
tags: asTags(fm.tags)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const facetOf = (s) => facetOrder.find((f) => s.tags.includes(f)) ?? "other";
|
|
56
|
+
sources.sort((a, b) => {
|
|
57
|
+
const fa = facetOrder.indexOf(facetOf(a));
|
|
58
|
+
const fb = facetOrder.indexOf(facetOf(b));
|
|
59
|
+
if (fa !== fb) return (fa < 0 ? 99 : fa) - (fb < 0 ? 99 : fb);
|
|
60
|
+
return a.title.localeCompare(b.title);
|
|
61
|
+
});
|
|
62
|
+
const idToN = /* @__PURE__ */ new Map();
|
|
63
|
+
sources.forEach((s, i) => {
|
|
64
|
+
s.n = i + 1;
|
|
65
|
+
idToN.set(s.id, i + 1);
|
|
66
|
+
});
|
|
67
|
+
const entries = [];
|
|
68
|
+
for (const path of await walkMd(dataset, "entries")) {
|
|
69
|
+
let raw;
|
|
70
|
+
try {
|
|
71
|
+
raw = await dataset.readFile(path);
|
|
72
|
+
} catch {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = matter(raw);
|
|
78
|
+
} catch {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const fm = parsed.data;
|
|
82
|
+
const title = flat(fm.title).replace(/\s+/g, " ").trim();
|
|
83
|
+
if (!title) continue;
|
|
84
|
+
const segs = path.replace(/^entries\//, "").split("/");
|
|
85
|
+
const kind = typeof fm.kind === "string" && fm.kind ? fm.kind : segs.slice(-2)[0] ?? "unknown";
|
|
86
|
+
const srcs = asTags(fm.sources).map((s) => flat(s).replace(/\s+/g, "").trim());
|
|
87
|
+
const ns = [
|
|
88
|
+
...new Set(srcs.map((id) => idToN.get(id)).filter((n) => !!n))
|
|
89
|
+
].sort((a, b) => a - b);
|
|
90
|
+
entries.push({
|
|
91
|
+
kind,
|
|
92
|
+
title,
|
|
93
|
+
conf: fm.confidence ?? "",
|
|
94
|
+
tags: asTags(fm.tags),
|
|
95
|
+
ns,
|
|
96
|
+
gist: firstSentence(parsed.content)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const files = [];
|
|
100
|
+
files.push({
|
|
101
|
+
path: `${viewsDir}/_bibliography.json`,
|
|
102
|
+
content: JSON.stringify({ sources }, null, 2)
|
|
103
|
+
});
|
|
104
|
+
files.push({
|
|
105
|
+
path: `${viewsDir}/_bibliography.md`,
|
|
106
|
+
content: "# Bibliography (global citation index)\n\n" + sources.map((s) => `${s.n}. ${s.title || s.id} \u2014 ${s.url}`).join("\n") + "\n"
|
|
107
|
+
});
|
|
108
|
+
const chapters = [];
|
|
109
|
+
for (const ch of config.chapters) {
|
|
110
|
+
const facets = ch.facets ?? [];
|
|
111
|
+
const kw = (ch.kw ?? []).map((s) => s.toLowerCase());
|
|
112
|
+
const seen = /* @__PURE__ */ new Set();
|
|
113
|
+
const scored = [];
|
|
114
|
+
for (const e of entries) {
|
|
115
|
+
if (!facets.some((f) => e.tags.includes(f))) continue;
|
|
116
|
+
const hay = `${e.title} ${e.tags.join(" ")} ${e.gist}`.toLowerCase();
|
|
117
|
+
const hits = kw.filter((k) => hay.includes(k)).length;
|
|
118
|
+
if (kw.length && hits === 0) continue;
|
|
119
|
+
const key = e.title.toLowerCase().slice(0, 50);
|
|
120
|
+
if (seen.has(key)) continue;
|
|
121
|
+
seen.add(key);
|
|
122
|
+
scored.push({ e, hits });
|
|
123
|
+
}
|
|
124
|
+
scored.sort(
|
|
125
|
+
(a, b) => b.hits - a.hits || String(b.e.conf).localeCompare(String(a.e.conf))
|
|
126
|
+
);
|
|
127
|
+
const picks = scored.slice(0, ch.cap ?? 28).map((x) => x.e);
|
|
128
|
+
const srcFiles = [...new Set(facets.map((f) => `sources.${f}.md`))];
|
|
129
|
+
const lines = picks.map(
|
|
130
|
+
(e) => `- **[${e.kind}]** ${e.title}${e.conf ? ` _(conf ${e.conf})_` : ""} ${e.ns.length ? `[${e.ns.join(",")}]` : "[uncited]"}
|
|
131
|
+
${e.gist}`
|
|
132
|
+
);
|
|
133
|
+
files.push({
|
|
134
|
+
path: `${viewsDir}/${ch.id}.md`,
|
|
135
|
+
content: `# Pack \u2014 ${ch.title}
|
|
136
|
+
|
|
137
|
+
**Facets:** ${facets.join(", ")} \xB7 **${picks.length} distilled claims** (each tagged with its global source number [n]).
|
|
138
|
+
|
|
139
|
+
**Also read for full context + exact quotes/URLs:** ${srcFiles.join(", ")} (in the corpus root), and ${viewsDir}/_bibliography.md.
|
|
140
|
+
|
|
141
|
+
## Distilled claims for this chapter
|
|
142
|
+
|
|
143
|
+
${lines.join("\n")}
|
|
144
|
+
`
|
|
145
|
+
});
|
|
146
|
+
chapters.push({ id: ch.id, title: ch.title, entryCount: picks.length });
|
|
147
|
+
}
|
|
148
|
+
return { files, bibliography: sources.length, chapters };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/report/cites.ts
|
|
152
|
+
function citesOf(s) {
|
|
153
|
+
return [...s.matchAll(/\[(\d{1,3}(?:,\s*\d{1,3})*)\]/g)].flatMap(
|
|
154
|
+
(m) => m[1].split(",").map((n) => parseInt(n.trim(), 10))
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
function outOfRangeCites(s, bibMax) {
|
|
158
|
+
return [...new Set(citesOf(s).filter((n) => n < 1 || n > bibMax))];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/report/assemble.ts
|
|
162
|
+
var PREAMBLE = /^\s*(?:I have|I'll|Here is|Here's|Writing|Let me|Okay|Done)/i;
|
|
163
|
+
var CROSSREF = /\[(\d{1,3})\s*(?:→|->|—>|to see|see)\s*(?:see\s*)?(\d{1,3})\]/g;
|
|
164
|
+
function cleanDraft(draft) {
|
|
165
|
+
if (!draft) return "";
|
|
166
|
+
let s = draft.replace(/\r/g, "");
|
|
167
|
+
const i = s.indexOf("## ");
|
|
168
|
+
if (i > 0) s = s.slice(i);
|
|
169
|
+
s = s.replace(CROSSREF, "[$2]");
|
|
170
|
+
return s.replace(/\n{3,}/g, "\n\n").trim();
|
|
171
|
+
}
|
|
172
|
+
function assembleChapters(opts) {
|
|
173
|
+
const chaptersDir = opts.chaptersDir ?? "chapters";
|
|
174
|
+
const files = [];
|
|
175
|
+
let preamblesStripped = 0;
|
|
176
|
+
const outOfRange = [];
|
|
177
|
+
for (const c of opts.chapters) {
|
|
178
|
+
const body = cleanDraft(c.draft);
|
|
179
|
+
if (PREAMBLE.test(c.draft || "")) preamblesStripped++;
|
|
180
|
+
if (body) files.push({ path: `${chaptersDir}/${c.ch}.md`, content: body + "\n" });
|
|
181
|
+
const oor = outOfRangeCites(body, opts.bibMax);
|
|
182
|
+
if (oor.length) outOfRange.push({ ch: c.ch, cites: oor });
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
files,
|
|
186
|
+
stats: {
|
|
187
|
+
chapters: opts.chapters.length,
|
|
188
|
+
preamblesStripped,
|
|
189
|
+
withOutOfRangeCites: outOfRange.length,
|
|
190
|
+
outOfRange
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
var reportSectionKindSchema = z.enum([
|
|
195
|
+
"front",
|
|
196
|
+
"part",
|
|
197
|
+
"chapter",
|
|
198
|
+
"annexes",
|
|
199
|
+
"sources"
|
|
200
|
+
]);
|
|
201
|
+
var reportSectionSchema = z.object({
|
|
202
|
+
/** Stable id — chapter id, or `_front`/`_annexes`/`_sources` for the glue. */
|
|
203
|
+
id: z.string(),
|
|
204
|
+
/** Human label (heading text, leading `#`s stripped). May be "". */
|
|
205
|
+
title: z.string(),
|
|
206
|
+
/** The section's raw markdown, exactly as it appears in REPORT.md. */
|
|
207
|
+
markdown: z.string(),
|
|
208
|
+
kind: reportSectionKindSchema
|
|
209
|
+
});
|
|
210
|
+
var bibEntrySchema = z.object({
|
|
211
|
+
n: z.number(),
|
|
212
|
+
id: z.string(),
|
|
213
|
+
title: z.string(),
|
|
214
|
+
url: z.string()
|
|
215
|
+
});
|
|
216
|
+
var reportContentSchema = z.object({
|
|
217
|
+
title: z.string(),
|
|
218
|
+
sections: z.array(reportSectionSchema),
|
|
219
|
+
bibliography: z.object({
|
|
220
|
+
mode: z.literal("numbered"),
|
|
221
|
+
entries: z.array(bibEntrySchema)
|
|
222
|
+
}).optional(),
|
|
223
|
+
/** Document metadata (brand · subtitle · tag · custodian) — presentation-free. */
|
|
224
|
+
meta: z.record(z.string(), z.string()).optional()
|
|
225
|
+
});
|
|
226
|
+
var stripHeading = (s) => s.replace(/^#+\s*/, "").trim();
|
|
227
|
+
async function collectReportSections(opts) {
|
|
228
|
+
const { config, report } = opts;
|
|
229
|
+
const chaptersDir = opts.chaptersDir ?? "chapters";
|
|
230
|
+
const viewsDir = opts.viewsDir ?? "views";
|
|
231
|
+
const rd = async (p) => (await report.readFile(p)).trim();
|
|
232
|
+
const rdOpt = async (p) => {
|
|
233
|
+
try {
|
|
234
|
+
return await rd(p);
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const titleOf = (id) => config.chapters.find((c) => c.id === id)?.title ?? id;
|
|
240
|
+
const sections = [];
|
|
241
|
+
const front = await rdOpt(config.frontFile ?? `${chaptersDir}/_front.md`);
|
|
242
|
+
if (front) sections.push({ id: "_front", title: "", markdown: front, kind: "front" });
|
|
243
|
+
if (config.parts && config.parts.length) {
|
|
244
|
+
for (const part of config.parts) {
|
|
245
|
+
sections.push({
|
|
246
|
+
id: stripHeading(part.heading).toLowerCase().replace(/[^a-z0-9]+/g, "-"),
|
|
247
|
+
title: stripHeading(part.heading),
|
|
248
|
+
markdown: part.heading,
|
|
249
|
+
kind: "part"
|
|
250
|
+
});
|
|
251
|
+
for (const id of part.chapters ?? []) {
|
|
252
|
+
sections.push({
|
|
253
|
+
id,
|
|
254
|
+
title: titleOf(id),
|
|
255
|
+
markdown: await rd(`${chaptersDir}/${id}.md`),
|
|
256
|
+
kind: "chapter"
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
for (const c of config.chapters) {
|
|
262
|
+
sections.push({
|
|
263
|
+
id: c.id,
|
|
264
|
+
title: c.title,
|
|
265
|
+
markdown: await rd(`${chaptersDir}/${c.id}.md`),
|
|
266
|
+
kind: "chapter"
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (config.annexesFile) {
|
|
271
|
+
const annexes = await rdOpt(config.annexesFile);
|
|
272
|
+
if (annexes)
|
|
273
|
+
sections.push({ id: "_annexes", title: "Annexes", markdown: annexes, kind: "annexes" });
|
|
274
|
+
}
|
|
275
|
+
const bib = (await rd(`${viewsDir}/_bibliography.md`)).replace(/^# Bibliography.*$/m, "").trim();
|
|
276
|
+
sections.push({
|
|
277
|
+
id: "_sources",
|
|
278
|
+
title: "Sources",
|
|
279
|
+
markdown: "## Sources\n\n" + bib,
|
|
280
|
+
kind: "sources"
|
|
281
|
+
});
|
|
282
|
+
return sections;
|
|
283
|
+
}
|
|
284
|
+
function reportContentToMarkdown(sections) {
|
|
285
|
+
return sections.map((s) => s.markdown).join("\n\n") + "\n";
|
|
286
|
+
}
|
|
287
|
+
async function buildReportContent(opts) {
|
|
288
|
+
const { config, report } = opts;
|
|
289
|
+
const viewsDir = opts.viewsDir ?? "views";
|
|
290
|
+
const sections = await collectReportSections(opts);
|
|
291
|
+
let title = config.title?.trim() ?? "";
|
|
292
|
+
if (!title) {
|
|
293
|
+
const front = sections.find((s) => s.kind === "front");
|
|
294
|
+
title = front?.markdown.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? "Report";
|
|
295
|
+
}
|
|
296
|
+
let bibliography;
|
|
297
|
+
try {
|
|
298
|
+
const raw = await report.readFile(`${viewsDir}/_bibliography.json`);
|
|
299
|
+
const parsed = JSON.parse(raw);
|
|
300
|
+
const src = Array.isArray(parsed.sources) ? parsed.sources : [];
|
|
301
|
+
const entries = src.map((s) => {
|
|
302
|
+
const o = s ?? {};
|
|
303
|
+
return {
|
|
304
|
+
n: typeof o.n === "number" ? o.n : 0,
|
|
305
|
+
id: typeof o.id === "string" ? o.id : "",
|
|
306
|
+
title: typeof o.title === "string" ? o.title : "",
|
|
307
|
+
url: typeof o.url === "string" ? o.url : ""
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
if (entries.length) bibliography = { mode: "numbered", entries };
|
|
311
|
+
} catch {
|
|
312
|
+
}
|
|
313
|
+
const meta = {};
|
|
314
|
+
const cover = config.cover;
|
|
315
|
+
if (cover) {
|
|
316
|
+
for (const k of ["brand", "subtitle", "tag", "meta"]) {
|
|
317
|
+
const v = cover[k];
|
|
318
|
+
if (typeof v === "string" && v.trim()) meta[k] = v.trim();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (config.profile) meta.profile = config.profile;
|
|
322
|
+
return {
|
|
323
|
+
title,
|
|
324
|
+
sections,
|
|
325
|
+
...bibliography ? { bibliography } : {},
|
|
326
|
+
...Object.keys(meta).length ? { meta } : {}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/report/stitch.ts
|
|
331
|
+
async function stitchReport(opts) {
|
|
332
|
+
const sections = await collectReportSections(opts);
|
|
333
|
+
const content = reportContentToMarkdown(sections);
|
|
334
|
+
const wordCount = sections.map((s) => s.markdown).join(" ").split(/\s+/).length;
|
|
335
|
+
return { content, wordCount };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/report/apply-edits.ts
|
|
339
|
+
function occ(hay, n) {
|
|
340
|
+
let c = 0;
|
|
341
|
+
let i = 0;
|
|
342
|
+
while ((i = hay.indexOf(n, i)) !== -1) {
|
|
343
|
+
c++;
|
|
344
|
+
i += n.length;
|
|
345
|
+
}
|
|
346
|
+
return c;
|
|
347
|
+
}
|
|
348
|
+
async function applyEdits(opts) {
|
|
349
|
+
const chaptersDir = opts.chaptersDir ?? "chapters";
|
|
350
|
+
const files = [];
|
|
351
|
+
let md = "# Fix-apply report (edit-based)\n\nEach edit lands only if `find` matches exactly once and `replace` adds no out-of-range [n].\n\n";
|
|
352
|
+
let applied = 0;
|
|
353
|
+
let skipped = 0;
|
|
354
|
+
let filesChanged = 0;
|
|
355
|
+
for (const r of opts.results) {
|
|
356
|
+
const path = `${chaptersDir}/${r.id}.md`;
|
|
357
|
+
let text = await opts.report.readFile(path);
|
|
358
|
+
const edits = r.edits ?? [];
|
|
359
|
+
let n = 0;
|
|
360
|
+
md += `## ${r.id} \u2014 ${edits.length} proposed edit(s)
|
|
361
|
+
`;
|
|
362
|
+
for (const e of edits) {
|
|
363
|
+
const find = e.find ?? "";
|
|
364
|
+
const replace = e.replace ?? "";
|
|
365
|
+
const oor = outOfRangeCites(replace, opts.bibMax);
|
|
366
|
+
const count = find ? occ(text, find) : 0;
|
|
367
|
+
let status;
|
|
368
|
+
if (!find) status = "SKIP (empty find)";
|
|
369
|
+
else if (count === 0) status = "SKIP (find not matched verbatim)";
|
|
370
|
+
else if (count > 1) status = `SKIP (find matches ${count}\xD7 \u2014 ambiguous)`;
|
|
371
|
+
else if (oor.length)
|
|
372
|
+
status = `SKIP (replace adds out-of-range cite ${oor.join(",")})`;
|
|
373
|
+
else {
|
|
374
|
+
text = text.replace(find, replace);
|
|
375
|
+
n++;
|
|
376
|
+
status = "\u2705 applied";
|
|
377
|
+
}
|
|
378
|
+
md += `- ${status} \u2014 _${e.reason || ""}_
|
|
379
|
+
`;
|
|
380
|
+
if (status.startsWith("\u2705")) {
|
|
381
|
+
md += ` find: ${JSON.stringify(find.slice(0, 200))}
|
|
382
|
+
replace: ${JSON.stringify(replace.slice(0, 200))}
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const post = [];
|
|
387
|
+
if (!text.startsWith("## ")) post.push("no longer starts at '##'");
|
|
388
|
+
const oorAll = outOfRangeCites(text, opts.bibMax);
|
|
389
|
+
if (oorAll.length) post.push(`out-of-range cites present: ${oorAll.join(",")}`);
|
|
390
|
+
if (post.length)
|
|
391
|
+
md += `- \u26A0\uFE0F POST-CHECK FAILED (${post.join("; ")}) \u2192 NOT written
|
|
392
|
+
`;
|
|
393
|
+
else if (n > 0) {
|
|
394
|
+
files.push({ path, content: text });
|
|
395
|
+
filesChanged++;
|
|
396
|
+
}
|
|
397
|
+
applied += n;
|
|
398
|
+
skipped += edits.length - n;
|
|
399
|
+
md += `- result: ${n}/${edits.length} applied${post.length ? " (reverted)" : ""}
|
|
400
|
+
|
|
401
|
+
`;
|
|
402
|
+
}
|
|
403
|
+
return { files, report: md, stats: { filesChanged, applied, skipped } };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/report/model.ts
|
|
407
|
+
async function runModel(model, input) {
|
|
408
|
+
const { result } = await model.complete(input);
|
|
409
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/report/analyze.ts
|
|
413
|
+
function buildFacetAnalysisPrompt(input) {
|
|
414
|
+
return {
|
|
415
|
+
system: "You are a research analyst. Your output is a structured markdown analysis document that will be used as input to a long-form research bible chapter. Write in clear, dense prose with H2 headers. No filler, no meta-commentary.",
|
|
416
|
+
prompt: `Synthesize these knowledge entries about the facet "${input.facet}" into a structured markdown analysis document for a research bible.
|
|
417
|
+
|
|
418
|
+
Include:
|
|
419
|
+
- ## Key Themes: recurring patterns across entries
|
|
420
|
+
- ## Major Findings: the most credible, high-confidence claims
|
|
421
|
+
- ## Notable Examples: concrete worked instances worth citing
|
|
422
|
+
- ## Gaps & Contradictions: what is unknown, disputed, or missing
|
|
423
|
+
|
|
424
|
+
ENTRIES:
|
|
425
|
+
|
|
426
|
+
${input.entriesText}`
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
async function analyzeDataset(opts) {
|
|
430
|
+
const files = [];
|
|
431
|
+
const skipped = [];
|
|
432
|
+
for (const facet of opts.facets) {
|
|
433
|
+
const entries = await resolveKnowledge({
|
|
434
|
+
fs: opts.dataset,
|
|
435
|
+
query: { tags: [facet] }
|
|
436
|
+
});
|
|
437
|
+
if (entries.length === 0) {
|
|
438
|
+
skipped.push(facet);
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const entriesText = entries.map(
|
|
442
|
+
(e) => `### ${e.title} (${e.kind}, confidence ${e.confidence ?? "?"})
|
|
443
|
+
|
|
444
|
+
${e.body}`
|
|
445
|
+
).join("\n\n---\n\n");
|
|
446
|
+
const content = await runModel(
|
|
447
|
+
opts.model,
|
|
448
|
+
buildFacetAnalysisPrompt({ facet, entriesText })
|
|
449
|
+
);
|
|
450
|
+
files.push({
|
|
451
|
+
path: `sources.${facet}.md`,
|
|
452
|
+
content: `# Analysis: ${facet}
|
|
453
|
+
|
|
454
|
+
${content}`
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
return { files, analyzed: files.length, skipped };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/report/write.ts
|
|
461
|
+
function buildChapterWritePrompt(ctx) {
|
|
462
|
+
const words = ctx.chapter.words ?? "800-1500";
|
|
463
|
+
return {
|
|
464
|
+
system: `You are an expert research writer. Write in clear, dense, well-structured prose. Every non-obvious claim must be cited inline as [n] from the Bibliography. Use Markdown H2 and H3 subheaders within the chapter. No meta-commentary or filler. Aim for ${words} words.`,
|
|
465
|
+
prompt: `Write the chapter "${ctx.chapter.title}" for the research document "${ctx.title}".
|
|
466
|
+
|
|
467
|
+
## Distilled claims for this chapter (with citation numbers):
|
|
468
|
+
|
|
469
|
+
${ctx.packContent}
|
|
470
|
+
|
|
471
|
+
` + (ctx.analysisContext ? `## Per-facet analysis (for deeper context):
|
|
472
|
+
|
|
473
|
+
${ctx.analysisContext}
|
|
474
|
+
|
|
475
|
+
` : "") + `## Bibliography (for citation verification):
|
|
476
|
+
|
|
477
|
+
${ctx.bibliography ?? ""}
|
|
478
|
+
|
|
479
|
+
Write the chapter now. Start with the "## ${ctx.chapter.title}" heading, then the content.`
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
async function writeChapter(ctx, model) {
|
|
483
|
+
const draft = await runModel(model, buildChapterWritePrompt(ctx));
|
|
484
|
+
return { ch: ctx.chapter.id, draft };
|
|
485
|
+
}
|
|
486
|
+
function buildReviewPrompt(ctx) {
|
|
487
|
+
return {
|
|
488
|
+
system: "You are a meticulous fact + citation reviewer for a long-form research report. Propose ONLY targeted, exact-match edits \u2014 never a rewrite. Each edit's `find` MUST be a verbatim substring of the chapter that occurs EXACTLY ONCE, and `replace` must keep every citation [n] within the valid range. Fix wrong/missing citations, unsupported or overstated claims, and factual errors against the analysis + bibliography. Output ONLY a JSON object \u2014 no prose, no fences.",
|
|
489
|
+
prompt: `Review the chapter "${ctx.chapter.title}". Valid citations are [1]..[${ctx.bibMax}].
|
|
490
|
+
|
|
491
|
+
## Chapter (current text):
|
|
492
|
+
|
|
493
|
+
${ctx.chapterText}
|
|
494
|
+
|
|
495
|
+
` + (ctx.analysisContext ? `## Per-facet analysis (ground truth):
|
|
496
|
+
|
|
497
|
+
${ctx.analysisContext}
|
|
498
|
+
|
|
499
|
+
` : "") + `## Bibliography:
|
|
500
|
+
|
|
501
|
+
${ctx.bibliography ?? ""}
|
|
502
|
+
|
|
503
|
+
Return JSON: { "edits": [ { "find": "<verbatim once-occurring span>", "replace": "<corrected span>", "reason": "<short why>" } ] }. Return { "edits": [] } if the chapter is clean.`
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
var reviewSchema = z.object({
|
|
507
|
+
edits: z.array(
|
|
508
|
+
z.object({
|
|
509
|
+
find: z.string().optional(),
|
|
510
|
+
replace: z.string().optional(),
|
|
511
|
+
reason: z.string().optional()
|
|
512
|
+
})
|
|
513
|
+
).default([])
|
|
514
|
+
});
|
|
515
|
+
function extractJson(text) {
|
|
516
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
517
|
+
const body = fenced ? fenced[1] : text;
|
|
518
|
+
const start = body.indexOf("{");
|
|
519
|
+
const end = body.lastIndexOf("}");
|
|
520
|
+
if (start === -1 || end === -1) return { edits: [] };
|
|
521
|
+
try {
|
|
522
|
+
return JSON.parse(body.slice(start, end + 1));
|
|
523
|
+
} catch {
|
|
524
|
+
return { edits: [] };
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async function reviewChapter(ctx, model) {
|
|
528
|
+
const text = await runModel(model, buildReviewPrompt(ctx));
|
|
529
|
+
const parsed = reviewSchema.safeParse(extractJson(text));
|
|
530
|
+
const edits = parsed.success ? parsed.data.edits : [];
|
|
531
|
+
return { id: ctx.chapter.id, edits };
|
|
532
|
+
}
|
|
533
|
+
var reportCoverFactSchema = z.object({ k: z.string(), v: z.string() }).passthrough();
|
|
534
|
+
var reportCoverPageSchema = z.object({
|
|
535
|
+
kicker: z.string().optional(),
|
|
536
|
+
title: z.string().optional(),
|
|
537
|
+
meta: z.string().optional(),
|
|
538
|
+
tag: z.string().optional(),
|
|
539
|
+
align: z.string().optional(),
|
|
540
|
+
facts: z.array(reportCoverFactSchema).optional()
|
|
541
|
+
}).passthrough();
|
|
542
|
+
var reportCoverSchema = z.object({
|
|
543
|
+
brand: z.string().optional(),
|
|
544
|
+
subtitle: z.string().optional(),
|
|
545
|
+
tag: z.string().optional(),
|
|
546
|
+
meta: z.string().optional(),
|
|
547
|
+
brandLetterSpacing: z.string().optional(),
|
|
548
|
+
brandFontSize: z.string().optional()
|
|
549
|
+
}).passthrough();
|
|
550
|
+
var reportPartSchema = z.object({
|
|
551
|
+
heading: z.string(),
|
|
552
|
+
chapters: z.array(z.string()).default([])
|
|
553
|
+
}).passthrough();
|
|
554
|
+
var reportChapterSchema = z.object({
|
|
555
|
+
/** = view filename + chapter source filename. */
|
|
556
|
+
id: z.string(),
|
|
557
|
+
/** Exact "## " heading the writer must use. */
|
|
558
|
+
title: z.string(),
|
|
559
|
+
/** Target length hint for the writer (e.g. "700–900"). */
|
|
560
|
+
words: z.string().optional(),
|
|
561
|
+
/** Analysis files (sources.<facet>.md) the writer/reviewer reads. */
|
|
562
|
+
src: z.array(z.string()).optional(),
|
|
563
|
+
/** The writer brief — what this chapter must cover. */
|
|
564
|
+
cover: z.string().optional(),
|
|
565
|
+
/** build-packs: which distilled-entry tags route into this view. */
|
|
566
|
+
facets: z.array(z.string()).default([]),
|
|
567
|
+
/** build-packs: keyword needles that rank (and gate) entries. */
|
|
568
|
+
kw: z.array(z.string()).optional(),
|
|
569
|
+
/** build-packs: max distilled claims in the view (default 28). */
|
|
570
|
+
cap: z.number().optional()
|
|
571
|
+
}).passthrough();
|
|
572
|
+
var reportConfigSchema = z.object({
|
|
573
|
+
/** Dataset path (repo-relative). Legacy `bible.config.json` key. */
|
|
574
|
+
workspace: z.string().optional(),
|
|
575
|
+
/** Pointer to the dataset this report consumes (the portable form). */
|
|
576
|
+
dataset: z.string().optional(),
|
|
577
|
+
/** Multiple datasets feeding one report (1 report → M datasets). */
|
|
578
|
+
corpora: z.array(z.string()).optional(),
|
|
579
|
+
/** Render/prompt profile preset (bible | brief | memo | deck). */
|
|
580
|
+
profile: z.string().optional(),
|
|
581
|
+
/** Highest valid citation [n]; out-of-range = error (used downstream). */
|
|
582
|
+
bibMax: z.number().optional(),
|
|
583
|
+
title: z.string().optional(),
|
|
584
|
+
cover: reportCoverSchema.optional(),
|
|
585
|
+
frontCovers: z.array(reportCoverPageSchema).optional(),
|
|
586
|
+
backCovers: z.array(reportCoverPageSchema).optional(),
|
|
587
|
+
runningHeader: z.string().optional(),
|
|
588
|
+
runningFooter: z.string().optional(),
|
|
589
|
+
frontFile: z.string().optional(),
|
|
590
|
+
annexesFile: z.string().optional(),
|
|
591
|
+
parts: z.array(reportPartSchema).optional(),
|
|
592
|
+
rulesText: z.string().optional(),
|
|
593
|
+
factsText: z.string().optional(),
|
|
594
|
+
briefText: z.string().optional(),
|
|
595
|
+
chapters: z.array(reportChapterSchema)
|
|
596
|
+
}).passthrough();
|
|
597
|
+
|
|
598
|
+
// src/report/plan.ts
|
|
599
|
+
var planSchema = z.object({
|
|
600
|
+
title: z.string().optional(),
|
|
601
|
+
chapters: z.array(reportChapterSchema).min(1)
|
|
602
|
+
});
|
|
603
|
+
function extractJson2(text) {
|
|
604
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
605
|
+
const body = fenced ? fenced[1] : text;
|
|
606
|
+
const start = body.indexOf("{");
|
|
607
|
+
const end = body.lastIndexOf("}");
|
|
608
|
+
if (start === -1 || end === -1) throw new Error("plan: no JSON object in completion");
|
|
609
|
+
return JSON.parse(body.slice(start, end + 1));
|
|
610
|
+
}
|
|
611
|
+
async function planOutline(opts) {
|
|
612
|
+
const profile = opts.profile ?? "bible";
|
|
613
|
+
const target = opts.targetChapters ?? 8;
|
|
614
|
+
const text = await runModel(opts.model, {
|
|
615
|
+
system: "You are a research editor planning a long-form, citation-backed report. Output ONLY a JSON object \u2014 no prose, no fences.",
|
|
616
|
+
prompt: `Plan a "${profile}" report from this brief and the available dataset facets.
|
|
617
|
+
|
|
618
|
+
BRIEF:
|
|
619
|
+
${opts.brief}
|
|
620
|
+
|
|
621
|
+
DATASET FACETS (chapters route against these tags):
|
|
622
|
+
${opts.facets.join(", ")}
|
|
623
|
+
|
|
624
|
+
Return JSON: { "title": string, "chapters": [ { "id": kebab-id, "title": "N. Heading", "facets": [subset of the facets above], "kw": [keyword needles], "cap": number, "words": "min-max", "cover": "1-2 sentence writer brief" } ] }.
|
|
625
|
+
Aim for about ${target} chapters. Every chapter MUST list at least one facet from the list.`
|
|
626
|
+
});
|
|
627
|
+
const parsed = planSchema.parse(extractJson2(text));
|
|
628
|
+
return { ...parsed.title ? { title: parsed.title } : {}, chapters: parsed.chapters };
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export { analyzeDataset, applyEdits, assembleChapters, bibEntrySchema, buildChapterWritePrompt, buildFacetAnalysisPrompt, buildPacks, buildReportContent, buildReviewPrompt, citesOf, cleanDraft, collectReportSections, outOfRangeCites, planOutline, reportChapterSchema, reportConfigSchema, reportContentSchema, reportContentToMarkdown, reportCoverFactSchema, reportCoverPageSchema, reportCoverSchema, reportPartSchema, reportSectionKindSchema, reportSectionSchema, reviewChapter, runModel, stitchReport, writeChapter };
|
|
632
|
+
//# sourceMappingURL=index.mjs.map
|
|
633
|
+
//# sourceMappingURL=index.mjs.map
|