sdtk-wiki-kit 0.1.2 → 0.1.4
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 +35 -23
- package/package.json +1 -1
- package/src/commands/enrich.js +51 -0
- package/src/commands/help.js +16 -11
- package/src/commands/lint.js +1 -0
- package/src/commands/operations.js +6 -5
- package/src/commands/search.js +5 -4
- package/src/commands/wiki.js +8 -7
- package/src/index.js +4 -0
- package/src/lib/wiki-compile.js +1201 -68
- package/src/lib/wiki-enrich.js +264 -0
- package/src/lib/wiki-extract.js +685 -9
- package/src/lib/wiki-lint.js +293 -11
- package/src/lib/wiki-paths.js +55 -0
- package/src/lib/wiki-search.js +17 -10
package/src/lib/wiki-compile.js
CHANGED
|
@@ -4,7 +4,10 @@ const fs = require("fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const { CliError, ValidationError } = require("./errors");
|
|
6
6
|
const {
|
|
7
|
+
assertCanonicalWikiWritePath,
|
|
7
8
|
assertWikiWorkspaceWritePath,
|
|
9
|
+
getCanonicalWikiPath,
|
|
10
|
+
getLegacyPersonalBrainPath,
|
|
8
11
|
getWikiReportsPath,
|
|
9
12
|
getWikiWorkspacePath,
|
|
10
13
|
isPathInsideOrEqual,
|
|
@@ -15,7 +18,7 @@ const REPORT_PREFIX = "compile-dry-run-preview";
|
|
|
15
18
|
const APPLY_PLAN_PREFIX = "compile-apply-plan";
|
|
16
19
|
const APPLY_PLAN_RECORD_TYPE = "sdtk_wiki_compile_apply_plan";
|
|
17
20
|
const APPLY_PLAN_SCHEMA_VERSION = 1;
|
|
18
|
-
const
|
|
21
|
+
const CANONICAL_WIKI_RELATIVE = "wiki";
|
|
19
22
|
const APPLY_MODE = "create_only_or_same_content_noop";
|
|
20
23
|
const ALLOWED_OPERATION_TYPES = new Set([
|
|
21
24
|
"append_section",
|
|
@@ -106,13 +109,609 @@ function mdList(values) {
|
|
|
106
109
|
return items.length > 0 ? items.map((item) => `- ${item}`).join("\n") : "- None recorded.";
|
|
107
110
|
}
|
|
108
111
|
|
|
112
|
+
function mdLinkedList(values) {
|
|
113
|
+
const items = asArray(values).filter(Boolean);
|
|
114
|
+
if (items.length === 0) return "- None recorded.";
|
|
115
|
+
return items.map((item) => {
|
|
116
|
+
const text = String(item);
|
|
117
|
+
if (/^https?:\/\//i.test(text)) return `- [${text}](${text})`;
|
|
118
|
+
return `- ${text}`;
|
|
119
|
+
}).join("\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function quoteBlock(value) {
|
|
123
|
+
const text = String(value || "").replace(/\r?\n/g, " ").trim();
|
|
124
|
+
return text ? `> ${text}` : "> No extracted snippet recorded in the local source.";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function topicLabelList(entity) {
|
|
128
|
+
const topics = [...new Set(asArray(entity.topics).filter(Boolean).map(String))];
|
|
129
|
+
return topics.length > 0 ? topics.map((topic) => `- ${topic}`).join("\n") : "- No topic labels extracted from local sources.";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function relatedRepoList(entity) {
|
|
133
|
+
const repos = asArray(entity.related_repos);
|
|
134
|
+
if (repos.length === 0) return "- No related repositories inferred from local topic overlap.";
|
|
135
|
+
return repos.map((repo) => {
|
|
136
|
+
const label = repo.name || repo.entity_id || "related repo";
|
|
137
|
+
const suffix = asArray(repo.shared_topics).length > 0 ? ` (shared topics: ${asArray(repo.shared_topics).join(", ")})` : "";
|
|
138
|
+
return repo.github_url ? `- [${label}](${repo.github_url})${suffix}` : `- ${label}${suffix}`;
|
|
139
|
+
}).join("\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function relativePersonalBrainLink(targetPagePath) {
|
|
143
|
+
const text = toPosix(targetPagePath || "");
|
|
144
|
+
if (text.startsWith("wiki/")) return text.slice("wiki/".length);
|
|
145
|
+
const marker = ".sdtk/wiki/personal-brain/";
|
|
146
|
+
const idx = text.indexOf(marker);
|
|
147
|
+
return idx >= 0 ? text.slice(idx + marker.length) : text;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function conceptAxisList(concept) {
|
|
151
|
+
const axes = asArray(concept.key_axes);
|
|
152
|
+
if (axes.length === 0) return "- No key axes were inferred from local evidence.";
|
|
153
|
+
return axes.map((axis) => `- ${axis.name}: ${axis.evidence_count || 1} local evidence reference(s).`).join("\n");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function conceptImplementationList(concept) {
|
|
157
|
+
const entities = asArray(concept.related_entity_details);
|
|
158
|
+
if (entities.length === 0) return "- No implementation examples were extracted from local sources.";
|
|
159
|
+
return entities.slice(0, 10).map((entity) => {
|
|
160
|
+
const label = entity.repo_owner && entity.repo_name ? `${entity.repo_owner}/${entity.repo_name}` : (entity.name || "tool candidate");
|
|
161
|
+
const link = entity.target_page_path ? relativePersonalBrainLink(entity.target_page_path) : "";
|
|
162
|
+
const target = link ? `[${label}](../${link})` : label;
|
|
163
|
+
const url = entity.github_url ? `; ${entity.github_url}` : "";
|
|
164
|
+
return `- ${target}: ${entity.summary || `Local candidate in ${entity.category || "uncategorized"}`}${url}`;
|
|
165
|
+
}).join("\n");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function conceptPatternList(concept) {
|
|
169
|
+
const patterns = asArray(concept.patterns);
|
|
170
|
+
if (patterns.length === 0) return "- No repeated pattern was inferred from local evidence.";
|
|
171
|
+
return patterns.map((pattern) => `- ${pattern.pattern}: ${pattern.evidence}`).join("\n");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function conceptRelatedPagesList(concept) {
|
|
175
|
+
const pages = asArray(concept.related_entity_details)
|
|
176
|
+
.map((entity) => entity.target_page_path)
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
.map((target) => `../${relativePersonalBrainLink(target)}`);
|
|
179
|
+
return mdList([...new Set(pages)].slice(0, 12));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function confidenceSummaryList(summary) {
|
|
183
|
+
if (!summary || typeof summary !== "object") return "- No source confidence summary recorded.";
|
|
184
|
+
const rows = Object.entries(summary).filter(([, count]) => Number(count) > 0);
|
|
185
|
+
return rows.length > 0 ? rows.map(([tier, count]) => `- ${tier}: ${count}`).join("\n") : "- No source confidence summary recorded.";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function comparisonMatrix(comparison) {
|
|
189
|
+
const rows = asArray(comparison.matrix_rows);
|
|
190
|
+
if (rows.length === 0) return "| Candidate | Category | Local evidence | Strengths | Caveats | Recommendation |\n|---|---|---:|---|---|---|\n| None recorded | - | 0 | - | - | - |";
|
|
191
|
+
const table = [
|
|
192
|
+
"| Candidate | Category | Local evidence | Strengths | Caveats | Recommendation |",
|
|
193
|
+
"|---|---|---:|---|---|---|",
|
|
194
|
+
];
|
|
195
|
+
for (const row of rows) {
|
|
196
|
+
const candidate = row.github_url ? `[${row.name || row.entity_id}](${row.github_url})` : (row.name || row.entity_id || "candidate");
|
|
197
|
+
table.push(`| ${candidate} | ${row.category || "uncategorized"} | ${row.source_ref_count || 0} | ${asArray(row.strengths).slice(0, 3).join("<br>") || "-"} | ${asArray(row.caveats).slice(0, 3).join("<br>") || "-"} | ${row.local_recommendation || "review"} |`);
|
|
198
|
+
}
|
|
199
|
+
return table.join("\n");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function candidateToolList(records) {
|
|
203
|
+
const rows = asArray(records);
|
|
204
|
+
if (rows.length === 0) return "- No candidate tools recorded.";
|
|
205
|
+
return rows.slice(0, 12).map((row) => {
|
|
206
|
+
const label = row.github_url ? `[${row.name || row.entity_id}](${row.github_url})` : (row.name || row.entity_id || "candidate");
|
|
207
|
+
return `- ${label}: ${row.local_recommendation || "review"}; confidence ${row.source_confidence || row.confidence_tier || "unknown"}.`;
|
|
208
|
+
}).join("\n");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function localCandidateLabel(record) {
|
|
212
|
+
if (!record) return "local candidate";
|
|
213
|
+
if (record.repo_owner && record.repo_name) return `${record.repo_owner}/${record.repo_name}`;
|
|
214
|
+
if (record.owner && (record.repo_name || record.repo)) return `${record.owner}/${record.repo_name || record.repo}`;
|
|
215
|
+
return record.name || record.entity_id || "local candidate";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function candidateEvidenceCount(record) {
|
|
219
|
+
const explicit = Number(record.source_ref_count || record.local_evidence_count || record.evidence_count || 0);
|
|
220
|
+
if (explicit > 0) return explicit;
|
|
221
|
+
const refs = sourceRefsFor(record);
|
|
222
|
+
return refs.length > 0 ? refs.length : 1;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function topLocalCandidates(records, limit = 3) {
|
|
226
|
+
return asArray(records)
|
|
227
|
+
.map((record) => ({
|
|
228
|
+
record,
|
|
229
|
+
label: localCandidateLabel(record),
|
|
230
|
+
evidence: candidateEvidenceCount(record),
|
|
231
|
+
category: record.category || record.local_category || "uncategorized",
|
|
232
|
+
confidence: record.source_confidence || record.confidence_tier || record.confidence || "unknown",
|
|
233
|
+
topics: asArray(record.topics || record.shared_topics).filter(Boolean).map(String),
|
|
234
|
+
recommendation: record.local_recommendation || record.recommendation || "review",
|
|
235
|
+
summary: record.summary || record.evidence || "",
|
|
236
|
+
}))
|
|
237
|
+
.sort((a, b) => (b.evidence - a.evidence) || a.label.localeCompare(b.label))
|
|
238
|
+
.slice(0, limit);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function refinedCandidateSentence(candidate) {
|
|
242
|
+
const topicText = candidate.topics.length > 0 ? `; local topics: ${candidate.topics.slice(0, 4).join(", ")}` : "";
|
|
243
|
+
return `${candidate.label} (${candidate.category}) has ${candidate.evidence} local evidence reference(s), confidence ${candidate.confidence}${topicText}.`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function conceptDecisionBrief(concept) {
|
|
247
|
+
const title = recordTitle(concept, "Concept");
|
|
248
|
+
const related = asArray(concept.related_entity_details);
|
|
249
|
+
const candidates = topLocalCandidates(related, 3);
|
|
250
|
+
const sourceCount = sourceRefCount(concept);
|
|
251
|
+
const axisNames = asArray(concept.key_axes).map((axis) => axis.name || axis).filter(Boolean).slice(0, 4);
|
|
252
|
+
const lines = [
|
|
253
|
+
`- Local evidence profile: ${related.length} related tool candidate(s), ${sourceCount} source reference(s), ${axisNames.length} decision axis/axes.`,
|
|
254
|
+
];
|
|
255
|
+
if (candidates.length > 0) {
|
|
256
|
+
lines.push(`- First review target: ${refinedCandidateSentence(candidates[0])}`);
|
|
257
|
+
if (candidates.length > 1) {
|
|
258
|
+
lines.push(`- Compare next: ${candidates.slice(1).map((candidate) => `${candidate.label} (${candidate.evidence} local ref(s))`).join("; ")}.`);
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
lines.push("- First review target: no concrete tool candidate was linked to this concept in local evidence.");
|
|
262
|
+
}
|
|
263
|
+
if (axisNames.length > 0) {
|
|
264
|
+
lines.push(`- Decision axes to preserve: ${axisNames.join(", ")}.`);
|
|
265
|
+
}
|
|
266
|
+
lines.push(`- Local-only boundary: treat ${title} as a decision queue from local notes, not as proof of current repository health, license, or adoption readiness.`);
|
|
267
|
+
return lines.join("\n");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function comparisonDecisionGuidance(comparison) {
|
|
271
|
+
const rows = asArray(comparison.matrix_rows);
|
|
272
|
+
const candidates = topLocalCandidates(rows, 4);
|
|
273
|
+
const axisNames = asArray(comparison.decision_axes).map((axis) => axis.name || axis).filter(Boolean).slice(0, 5);
|
|
274
|
+
const lines = [
|
|
275
|
+
`- Use this comparison to rank ${rows.length} local candidate(s) across ${axisNames.length || "the recorded"} decision axis/axes.`,
|
|
276
|
+
];
|
|
277
|
+
if (candidates.length > 0) {
|
|
278
|
+
lines.push(`- Local shortlist: ${candidates.map((candidate) => `${candidate.label} (${candidate.evidence} ref(s), ${candidate.recommendation})`).join("; ")}.`);
|
|
279
|
+
}
|
|
280
|
+
if (axisNames.length > 0) {
|
|
281
|
+
lines.push(`- Review order: score candidates first on ${axisNames.join(", ")}, then verify external facts separately.`);
|
|
282
|
+
}
|
|
283
|
+
lines.push("- Do not convert a local shortlist into an adoption recommendation until license, maintenance, security, and current repository identity are verified outside this local evidence set.");
|
|
284
|
+
return lines.join("\n");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function synthesisDecisionNarrative(synthesis) {
|
|
288
|
+
const rows = asArray(synthesis.candidate_tools);
|
|
289
|
+
const candidates = topLocalCandidates(rows, 4);
|
|
290
|
+
const axisNames = asArray(synthesis.landscape_axes).map((axis) => axis.name || axis).filter(Boolean).slice(0, 5);
|
|
291
|
+
const lines = [
|
|
292
|
+
`- This synthesis frames ${rows.length} local candidate(s) as a review backlog, not a final recommendation.`,
|
|
293
|
+
];
|
|
294
|
+
if (candidates.length > 0) {
|
|
295
|
+
lines.push(`- Best first inspection path: ${candidates.map((candidate) => `${candidate.label} (${candidate.evidence} ref(s), ${candidate.confidence})`).join("; ")}.`);
|
|
296
|
+
}
|
|
297
|
+
if (axisNames.length > 0) {
|
|
298
|
+
lines.push(`- Decision lens: ${axisNames.join(", ")}.`);
|
|
299
|
+
}
|
|
300
|
+
if (synthesis.related_comparison_path) {
|
|
301
|
+
lines.push(`- Use the related comparison page before making a tool choice: ../${relativePersonalBrainLink(synthesis.related_comparison_path)}.`);
|
|
302
|
+
}
|
|
303
|
+
lines.push("- Keep unverified claims labeled: local snippets can explain why a tool entered the queue, but not whether it is currently mature, licensed correctly, or maintained.");
|
|
304
|
+
return lines.join("\n");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function mdEscapeTable(value) {
|
|
308
|
+
return String(value ?? "").replace(/\|/g, "\\|").replace(/\r?\n/g, " ").trim();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function rootLink(targetPagePath) {
|
|
312
|
+
const rel = relativePersonalBrainLink(targetPagePath);
|
|
313
|
+
return rel ? toPosix(rel) : "";
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function dashboardLink(targetPagePath) {
|
|
317
|
+
const rel = relativePersonalBrainLink(targetPagePath);
|
|
318
|
+
return rel ? `../${toPosix(rel)}` : "";
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function sourceRefCount(record) {
|
|
322
|
+
return sourceRefsFor(record).length;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function highSignalTools(payload, limit = 12) {
|
|
326
|
+
return asArray(payload.tool_entities)
|
|
327
|
+
.map((tool) => ({
|
|
328
|
+
title: recordTitle(tool, "tool"),
|
|
329
|
+
path: tool.target_page_path,
|
|
330
|
+
github_url: tool.github_url,
|
|
331
|
+
category: tool.category || "uncategorized",
|
|
332
|
+
topics: asArray(tool.topics).filter(Boolean).map(String),
|
|
333
|
+
source_refs: sourceRefCount(tool),
|
|
334
|
+
confidence: tool.confidence_tier || tool.confidence || "medium",
|
|
335
|
+
summary: tool.summary || "",
|
|
336
|
+
}))
|
|
337
|
+
.sort((a, b) => (b.source_refs - a.source_refs) || a.title.localeCompare(b.title))
|
|
338
|
+
.slice(0, limit);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function topConcepts(payload, limit = 10) {
|
|
342
|
+
return asArray(payload.concepts)
|
|
343
|
+
.map((concept) => ({
|
|
344
|
+
title: recordTitle(concept, "concept"),
|
|
345
|
+
path: concept.target_page_path,
|
|
346
|
+
related_count: asArray(concept.related_entity_details).length || asArray(concept.related_entities).length,
|
|
347
|
+
source_refs: sourceRefCount(concept),
|
|
348
|
+
axes: asArray(concept.key_axes).map((axis) => axis.name || axis).filter(Boolean),
|
|
349
|
+
}))
|
|
350
|
+
.sort((a, b) => (b.related_count - a.related_count) || (b.source_refs - a.source_refs) || a.title.localeCompare(b.title))
|
|
351
|
+
.slice(0, limit);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function comparisonEntries(payload, limit = 10) {
|
|
355
|
+
return asArray(payload.comparisons)
|
|
356
|
+
.map((comparison) => ({
|
|
357
|
+
title: recordTitle(comparison, "comparison"),
|
|
358
|
+
topic: comparison.topic || comparison.comparison_id || recordTitle(comparison, "comparison"),
|
|
359
|
+
path: comparison.target_page_path,
|
|
360
|
+
rows: asArray(comparison.matrix_rows).length,
|
|
361
|
+
source_refs: sourceRefCount(comparison),
|
|
362
|
+
recommendations: asArray(comparison.recommendations),
|
|
363
|
+
}))
|
|
364
|
+
.sort((a, b) => (b.rows - a.rows) || (b.source_refs - a.source_refs) || a.title.localeCompare(b.title))
|
|
365
|
+
.slice(0, limit);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function synthesisEntries(payload, limit = 10) {
|
|
369
|
+
return asArray(payload.syntheses)
|
|
370
|
+
.map((synthesis) => ({
|
|
371
|
+
title: recordTitle(synthesis, "synthesis"),
|
|
372
|
+
topic: synthesis.topic || synthesis.synthesis_id || recordTitle(synthesis, "synthesis"),
|
|
373
|
+
path: synthesis.target_page_path,
|
|
374
|
+
candidates: asArray(synthesis.candidate_tools).length,
|
|
375
|
+
source_refs: sourceRefCount(synthesis),
|
|
376
|
+
recommendations: asArray(synthesis.recommendations),
|
|
377
|
+
}))
|
|
378
|
+
.sort((a, b) => (b.candidates - a.candidates) || (b.source_refs - a.source_refs) || a.title.localeCompare(b.title))
|
|
379
|
+
.slice(0, limit);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function sourceEntries(payload, limit = 20) {
|
|
383
|
+
return asArray(payload.sources)
|
|
384
|
+
.map((source) => ({
|
|
385
|
+
title: source.source_logical_path || source.source_relative_path || source.source_id || "source",
|
|
386
|
+
path: source.target_page_path,
|
|
387
|
+
source_id: source.source_id,
|
|
388
|
+
quality_flags: asArray(source.source_quality && source.source_quality.quality_flags),
|
|
389
|
+
source_url: source.source_url || "",
|
|
390
|
+
source_hash: source.source_hash || "",
|
|
391
|
+
}))
|
|
392
|
+
.slice(0, limit);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function groupToolsByCategory(tools) {
|
|
396
|
+
const byCategory = new Map();
|
|
397
|
+
for (const tool of tools) {
|
|
398
|
+
const key = tool.category || "uncategorized";
|
|
399
|
+
if (!byCategory.has(key)) byCategory.set(key, []);
|
|
400
|
+
byCategory.get(key).push(tool);
|
|
401
|
+
}
|
|
402
|
+
return [...byCategory.entries()]
|
|
403
|
+
.map(([category, rows]) => ({ category, rows }))
|
|
404
|
+
.sort((a, b) => (b.rows.length - a.rows.length) || a.category.localeCompare(b.category));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function linkedListFromRoot(entries) {
|
|
408
|
+
const rows = asArray(entries).filter((entry) => entry && entry.path);
|
|
409
|
+
if (rows.length === 0) return "- None recorded.";
|
|
410
|
+
return rows.map((entry) => `- [${entry.title}](${rootLink(entry.path)}): ${entry.source_refs || 0} source reference(s).`).join("\n");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function linkedListFromDashboard(entries) {
|
|
414
|
+
const rows = asArray(entries).filter((entry) => entry && entry.path);
|
|
415
|
+
if (rows.length === 0) return "- None recorded.";
|
|
416
|
+
return rows.map((entry) => `- [${entry.title}](${dashboardLink(entry.path)}): ${entry.source_refs || 0} source reference(s).`).join("\n");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function dashboardFrontmatter(id, title, payload, generatedAt, relatedPages = []) {
|
|
420
|
+
return {
|
|
421
|
+
id,
|
|
422
|
+
title,
|
|
423
|
+
type: "dashboard",
|
|
424
|
+
created_at: generatedAt,
|
|
425
|
+
updated_at: generatedAt,
|
|
426
|
+
aliases: [title],
|
|
427
|
+
tags: ["dashboard", "navigation"],
|
|
428
|
+
related_pages: relatedPages,
|
|
429
|
+
source_refs: asArray(payload.sources).map((source) => source.source_id).filter(Boolean).slice(0, 50),
|
|
430
|
+
confidence: "medium",
|
|
431
|
+
review_status: "needs_review",
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function renderResearchToolLandscapeDashboard(payload) {
|
|
436
|
+
const tools = highSignalTools(payload, 16);
|
|
437
|
+
const concepts = topConcepts(payload, 8);
|
|
438
|
+
const comparisons = comparisonEntries(payload, 6);
|
|
439
|
+
const syntheses = synthesisEntries(payload, 6);
|
|
440
|
+
const categoryRows = groupToolsByCategory(tools);
|
|
441
|
+
const table = [
|
|
442
|
+
"| Category | Candidate count | High-signal examples |",
|
|
443
|
+
"|---|---:|---|",
|
|
444
|
+
...categoryRows.map(({ category, rows }) => {
|
|
445
|
+
const examples = rows.slice(0, 4).map((tool) => tool.path ? `[${mdEscapeTable(tool.title)}](${dashboardLink(tool.path)})` : mdEscapeTable(tool.title)).join("<br>");
|
|
446
|
+
return `| ${mdEscapeTable(category)} | ${rows.length} | ${examples || "-"} |`;
|
|
447
|
+
}),
|
|
448
|
+
].join("\n");
|
|
449
|
+
const highSignalTable = [
|
|
450
|
+
"| Tool | Category | Local evidence | Topics | Confidence |",
|
|
451
|
+
"|---|---|---:|---|---|",
|
|
452
|
+
...tools.slice(0, 12).map((tool) => {
|
|
453
|
+
const label = tool.path ? `[${mdEscapeTable(tool.title)}](${dashboardLink(tool.path)})` : mdEscapeTable(tool.title);
|
|
454
|
+
return `| ${label} | ${mdEscapeTable(tool.category)} | ${tool.source_refs} | ${mdEscapeTable(tool.topics.slice(0, 4).join(", ") || "-")} | ${mdEscapeTable(tool.confidence)} |`;
|
|
455
|
+
}),
|
|
456
|
+
].join("\n");
|
|
457
|
+
const body = [
|
|
458
|
+
"# Research / Tool Landscape Dashboard",
|
|
459
|
+
"",
|
|
460
|
+
"## Purpose",
|
|
461
|
+
"",
|
|
462
|
+
"Use this page as the starting map for browsing locally extracted repository and tool evidence. The links stay inside the generated local wiki and do not imply external verification.",
|
|
463
|
+
"",
|
|
464
|
+
"## Top Concepts",
|
|
465
|
+
"",
|
|
466
|
+
linkedListFromDashboard(concepts),
|
|
467
|
+
"",
|
|
468
|
+
"## Tool Clusters",
|
|
469
|
+
"",
|
|
470
|
+
table,
|
|
471
|
+
"",
|
|
472
|
+
"## High-Signal Tools",
|
|
473
|
+
"",
|
|
474
|
+
highSignalTable,
|
|
475
|
+
"",
|
|
476
|
+
"## Comparison And Synthesis Entry Points",
|
|
477
|
+
"",
|
|
478
|
+
"Comparisons:",
|
|
479
|
+
"",
|
|
480
|
+
linkedListFromDashboard(comparisons),
|
|
481
|
+
"",
|
|
482
|
+
"Syntheses:",
|
|
483
|
+
"",
|
|
484
|
+
linkedListFromDashboard(syntheses),
|
|
485
|
+
"",
|
|
486
|
+
"## Review Notes",
|
|
487
|
+
"",
|
|
488
|
+
"- Prefer tools with multiple local source references before opening an external verification issue.",
|
|
489
|
+
"- Treat topic/category clustering as local evidence only.",
|
|
490
|
+
"- Use the decision-support dashboard before making adoption recommendations.",
|
|
491
|
+
"",
|
|
492
|
+
].join("\n");
|
|
493
|
+
return withPersonalBrainFrontmatter(
|
|
494
|
+
dashboardFrontmatter(
|
|
495
|
+
"dashboard_research_tool_landscape",
|
|
496
|
+
"Research / Tool Landscape Dashboard",
|
|
497
|
+
payload,
|
|
498
|
+
payload.generated_at || "",
|
|
499
|
+
["../index.md", ...concepts.map((entry) => dashboardLink(entry.path)).filter(Boolean)]
|
|
500
|
+
),
|
|
501
|
+
body
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function renderDecisionSupportDashboard(payload) {
|
|
506
|
+
const comparisons = comparisonEntries(payload, 8);
|
|
507
|
+
const syntheses = synthesisEntries(payload, 8);
|
|
508
|
+
const tools = highSignalTools(payload, 10);
|
|
509
|
+
const comparisonTable = [
|
|
510
|
+
"| Decision surface | Candidates | Local source refs | First recommendation cue |",
|
|
511
|
+
"|---|---:|---:|---|",
|
|
512
|
+
...comparisons.map((entry) => `| [${mdEscapeTable(entry.title)}](${dashboardLink(entry.path)}) | ${entry.rows} | ${entry.source_refs} | ${mdEscapeTable(entry.recommendations[0] || "review candidates")} |`),
|
|
513
|
+
].join("\n");
|
|
514
|
+
const shortlist = [
|
|
515
|
+
"| Candidate | Category | Local evidence | Decision note |",
|
|
516
|
+
"|---|---|---:|---|",
|
|
517
|
+
...tools.slice(0, 8).map((tool) => `| [${mdEscapeTable(tool.title)}](${dashboardLink(tool.path)}) | ${mdEscapeTable(tool.category)} | ${tool.source_refs} | Verify license, maintenance, security, and fit before adoption. |`),
|
|
518
|
+
].join("\n");
|
|
519
|
+
const body = [
|
|
520
|
+
"# Decision-Support Dashboard",
|
|
521
|
+
"",
|
|
522
|
+
"## Purpose",
|
|
523
|
+
"",
|
|
524
|
+
"Use this page when deciding which locally sourced candidates deserve deeper review. It links comparison, synthesis, and high-signal tool pages without fetching external metadata.",
|
|
525
|
+
"",
|
|
526
|
+
"## Comparison Starting Points",
|
|
527
|
+
"",
|
|
528
|
+
comparisons.length > 0 ? comparisonTable : "- No comparison pages were generated from the local evidence.",
|
|
529
|
+
"",
|
|
530
|
+
"## Synthesis Starting Points",
|
|
531
|
+
"",
|
|
532
|
+
linkedListFromDashboard(syntheses),
|
|
533
|
+
"",
|
|
534
|
+
"## Candidate Shortlist",
|
|
535
|
+
"",
|
|
536
|
+
shortlist,
|
|
537
|
+
"",
|
|
538
|
+
"## Decision Checklist",
|
|
539
|
+
"",
|
|
540
|
+
"- Confirm the page has multiple local source references or a strong extracted snippet.",
|
|
541
|
+
"- Check comparison caveats before adoption.",
|
|
542
|
+
"- Verify license, maintainer activity, security posture, and current repository identity outside this local-only report.",
|
|
543
|
+
"- Record human acceptance criteria before treating a tool as recommended.",
|
|
544
|
+
"",
|
|
545
|
+
].join("\n");
|
|
546
|
+
return withPersonalBrainFrontmatter(
|
|
547
|
+
dashboardFrontmatter(
|
|
548
|
+
"dashboard_decision_support",
|
|
549
|
+
"Decision-Support Dashboard",
|
|
550
|
+
payload,
|
|
551
|
+
payload.generated_at || "",
|
|
552
|
+
["../index.md", ...comparisons.map((entry) => dashboardLink(entry.path)).filter(Boolean)]
|
|
553
|
+
),
|
|
554
|
+
body
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function renderMaintenanceQualityDashboard(payload) {
|
|
559
|
+
const findings = asArray(payload.source_quality_findings);
|
|
560
|
+
const unsupported = asArray(payload.unsupported_items);
|
|
561
|
+
const sources = sourceEntries(payload, 10);
|
|
562
|
+
const maintenanceLinks = [];
|
|
563
|
+
if (findings.length > 0 || unsupported.length > 0) {
|
|
564
|
+
maintenanceLinks.push("[Extraction Quality Review](../maintenance/extraction-quality-review.md)");
|
|
565
|
+
}
|
|
566
|
+
const findingList = findings.slice(0, 12).map((finding) =>
|
|
567
|
+
`- ${finding.finding_id || finding.source_id || "finding"}: ${asArray(finding.quality_flags).join(", ") || finding.reason || "review required"}`
|
|
568
|
+
).join("\n") || "- No source-quality findings recorded.";
|
|
569
|
+
const unsupportedList = unsupported.slice(0, 12).map((item) =>
|
|
570
|
+
`- ${item.item_id || "unsupported"}: ${item.reason || item.raw_observation_summary || "review required"}`
|
|
571
|
+
).join("\n") || "- No unsupported extraction items recorded.";
|
|
572
|
+
const body = [
|
|
573
|
+
"# Maintenance / Quality Dashboard",
|
|
574
|
+
"",
|
|
575
|
+
"## Purpose",
|
|
576
|
+
"",
|
|
577
|
+
"Use this page to triage local extraction quality before relying on generated concept, comparison, synthesis, or tool pages.",
|
|
578
|
+
"",
|
|
579
|
+
"## Quality Snapshot",
|
|
580
|
+
"",
|
|
581
|
+
`- source-quality findings: ${findings.length}`,
|
|
582
|
+
`- unsupported items: ${unsupported.length}`,
|
|
583
|
+
`- source pages tracked: ${asArray(payload.sources).length}`,
|
|
584
|
+
`- tool candidates tracked: ${asArray(payload.tool_entities).length}`,
|
|
585
|
+
"",
|
|
586
|
+
"## Maintenance Entry Points",
|
|
587
|
+
"",
|
|
588
|
+
maintenanceLinks.length > 0 ? maintenanceLinks.map((link) => `- ${link}`).join("\n") : "- No dedicated maintenance review page was required for this extraction.",
|
|
589
|
+
"",
|
|
590
|
+
"## Source Findings",
|
|
591
|
+
"",
|
|
592
|
+
findingList,
|
|
593
|
+
"",
|
|
594
|
+
"## Unsupported Items",
|
|
595
|
+
"",
|
|
596
|
+
unsupportedList,
|
|
597
|
+
"",
|
|
598
|
+
"## Source Pages To Recheck",
|
|
599
|
+
"",
|
|
600
|
+
linkedListFromDashboard(sources.map((source) => ({ title: source.title, path: source.path, source_refs: source.quality_flags.length }))),
|
|
601
|
+
"",
|
|
602
|
+
"## Operating Notes",
|
|
603
|
+
"",
|
|
604
|
+
"- Fix source-quality warnings before treating dashboard rankings as final.",
|
|
605
|
+
"- Keep destructive cleanup, archive, and delete decisions outside this generated dashboard.",
|
|
606
|
+
"- Re-run lint after compile/apply to verify internal links and required fields.",
|
|
607
|
+
"",
|
|
608
|
+
].join("\n");
|
|
609
|
+
return withPersonalBrainFrontmatter(
|
|
610
|
+
dashboardFrontmatter("dashboard_maintenance_quality", "Maintenance / Quality Dashboard", payload, payload.generated_at || "", ["../index.md"]),
|
|
611
|
+
body
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function renderSourceCoverageDashboard(payload) {
|
|
616
|
+
const sources = sourceEntries(payload, 30);
|
|
617
|
+
const counts = payload.source_counts || {};
|
|
618
|
+
const sourceTable = [
|
|
619
|
+
"| Source | Quality flags | Source URL | Hash prefix |",
|
|
620
|
+
"|---|---|---|---|",
|
|
621
|
+
...sources.map((source) => {
|
|
622
|
+
const label = source.path ? `[${mdEscapeTable(source.title)}](${dashboardLink(source.path)})` : mdEscapeTable(source.title);
|
|
623
|
+
const sourceUrl = source.source_url ? `[source](${source.source_url})` : "-";
|
|
624
|
+
return `| ${label} | ${mdEscapeTable(source.quality_flags.join(", ") || "none")} | ${sourceUrl} | ${mdEscapeTable(source.source_hash.slice(0, 12) || "-")} |`;
|
|
625
|
+
}),
|
|
626
|
+
].join("\n");
|
|
627
|
+
const body = [
|
|
628
|
+
"# Source Coverage Dashboard",
|
|
629
|
+
"",
|
|
630
|
+
"## Purpose",
|
|
631
|
+
"",
|
|
632
|
+
"Use this page to understand which local files fed the generated local wiki and where source-level review should start.",
|
|
633
|
+
"",
|
|
634
|
+
"## Coverage Snapshot",
|
|
635
|
+
"",
|
|
636
|
+
`- sources scanned: ${counts.scanned ?? "(unknown)"}`,
|
|
637
|
+
`- sources indexed: ${counts.indexed ?? "(unknown)"}`,
|
|
638
|
+
`- source records: ${asArray(payload.sources).length}`,
|
|
639
|
+
`- tool candidates: ${asArray(payload.tool_entities).length}`,
|
|
640
|
+
`- concepts: ${asArray(payload.concepts).length}`,
|
|
641
|
+
`- comparisons: ${asArray(payload.comparisons).length}`,
|
|
642
|
+
`- syntheses: ${asArray(payload.syntheses).length}`,
|
|
643
|
+
"",
|
|
644
|
+
"## Source Pages",
|
|
645
|
+
"",
|
|
646
|
+
sourceTable,
|
|
647
|
+
"",
|
|
648
|
+
"## Coverage Notes",
|
|
649
|
+
"",
|
|
650
|
+
"- The dashboard is derived from local source registry and semantic extraction output only.",
|
|
651
|
+
"- Source hashes should remain stable across compile and enrichment/report-only flows.",
|
|
652
|
+
"- Investigate any lint provenance finding before using the generated graph as a decision record.",
|
|
653
|
+
"",
|
|
654
|
+
].join("\n");
|
|
655
|
+
return withPersonalBrainFrontmatter(
|
|
656
|
+
dashboardFrontmatter("dashboard_source_coverage", "Source Coverage Dashboard", payload, payload.generated_at || "", ["../index.md"]),
|
|
657
|
+
body
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function renderDashboardPage(name, payload) {
|
|
662
|
+
if (name === "research-tool-landscape") return renderResearchToolLandscapeDashboard(payload);
|
|
663
|
+
if (name === "decision-support") return renderDecisionSupportDashboard(payload);
|
|
664
|
+
if (name === "maintenance-quality") return renderMaintenanceQualityDashboard(payload);
|
|
665
|
+
if (name === "source-coverage") return renderSourceCoverageDashboard(payload);
|
|
666
|
+
return renderResearchToolLandscapeDashboard(payload);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function yamlScalar(value) {
|
|
670
|
+
return `"${String(value ?? "").replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\r?\n/g, " ").trim()}"`;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function yamlInlineList(values) {
|
|
674
|
+
const items = [...new Set(asArray(values).filter(Boolean).map(String))];
|
|
675
|
+
return `[${items.map(yamlScalar).join(", ")}]`;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function frontmatterBlock(fields) {
|
|
679
|
+
const lines = ["---"];
|
|
680
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
681
|
+
if (Array.isArray(value)) {
|
|
682
|
+
lines.push(`${key}: ${yamlInlineList(value)}`);
|
|
683
|
+
} else {
|
|
684
|
+
lines.push(`${key}: ${yamlScalar(value)}`);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
lines.push("---", "");
|
|
688
|
+
return lines.join("\n");
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function withPersonalBrainFrontmatter(fields, body) {
|
|
692
|
+
return `${frontmatterBlock({
|
|
693
|
+
schema_version: "1",
|
|
694
|
+
product: "SDTK-WIKI",
|
|
695
|
+
managed_by: "sdtk-wiki",
|
|
696
|
+
status: "generated",
|
|
697
|
+
aliases: [],
|
|
698
|
+
tags: [],
|
|
699
|
+
related_pages: [],
|
|
700
|
+
source_refs: [],
|
|
701
|
+
confidence: "medium",
|
|
702
|
+
review_status: "needs_review",
|
|
703
|
+
...fields,
|
|
704
|
+
})}${String(body || "").replace(/^\s+/, "")}`;
|
|
705
|
+
}
|
|
706
|
+
|
|
109
707
|
function recordTitle(record, fallback) {
|
|
110
708
|
return String(record.title || record.name || record.topic || record.entity_id || record.concept_id || record.source_id || fallback || "Generated page");
|
|
111
709
|
}
|
|
112
710
|
|
|
113
|
-
function renderSourcePage(source) {
|
|
114
|
-
|
|
115
|
-
|
|
711
|
+
function renderSourcePage(source, generatedAt) {
|
|
712
|
+
const title = recordTitle(source, "Source");
|
|
713
|
+
const body = [
|
|
714
|
+
`# ${title}`,
|
|
116
715
|
"",
|
|
117
716
|
"## Summary",
|
|
118
717
|
"",
|
|
@@ -131,112 +730,471 @@ function renderSourcePage(source) {
|
|
|
131
730
|
"",
|
|
132
731
|
mdList(source.source_quality && source.source_quality.quality_flags),
|
|
133
732
|
"",
|
|
733
|
+
"## Thinness Policy",
|
|
734
|
+
"",
|
|
735
|
+
"This page is an accepted source/provenance anchor. Its purpose is to preserve local source identity, hash, quality flags, and provenance rather than to carry semantic analysis. Strict stub quality is measured on tool, concept, comparison, and synthesis pages.",
|
|
736
|
+
"",
|
|
134
737
|
"## Provenance",
|
|
135
738
|
"",
|
|
136
739
|
mdList(provenanceRefsFor(source)),
|
|
137
740
|
"",
|
|
138
741
|
].join("\n");
|
|
742
|
+
return withPersonalBrainFrontmatter({
|
|
743
|
+
id: source.source_id,
|
|
744
|
+
title,
|
|
745
|
+
type: "source",
|
|
746
|
+
created_at: generatedAt,
|
|
747
|
+
updated_at: generatedAt,
|
|
748
|
+
aliases: source.source_logical_path ? [source.source_logical_path] : [],
|
|
749
|
+
tags: ["source", source.source_type || "local"],
|
|
750
|
+
thinness_policy: "accepted_source_provenance_anchor",
|
|
751
|
+
source_refs: sourceRefsFor(source),
|
|
752
|
+
confidence: source.source_quality && source.source_quality.low_confidence_extraction ? "low" : "medium",
|
|
753
|
+
review_status: source.source_quality && source.source_quality.low_confidence_extraction ? "needs_review" : "ready_for_review",
|
|
754
|
+
}, body);
|
|
139
755
|
}
|
|
140
756
|
|
|
141
|
-
function renderToolEntityPage(entity) {
|
|
142
|
-
|
|
143
|
-
|
|
757
|
+
function renderToolEntityPage(entity, generatedAt) {
|
|
758
|
+
const title = recordTitle(entity, "Tool entity");
|
|
759
|
+
const evidenceRecords = asArray(entity.evidence_records);
|
|
760
|
+
const firstSnippet = asArray(entity.evidence_snippets)[0] || (evidenceRecords[0] && evidenceRecords[0].snippet);
|
|
761
|
+
const discoverySources = [
|
|
762
|
+
...asArray(entity.source_links),
|
|
763
|
+
...asArray(entity.discovery_sources),
|
|
764
|
+
...evidenceRecords.map((record) => record.source_link || record.source_logical_path).filter(Boolean),
|
|
765
|
+
];
|
|
766
|
+
const sourceCount = sourceRefsFor(entity).length;
|
|
767
|
+
const whyItMatters = entity.summary
|
|
768
|
+
? `Local evidence presents ${title} as relevant to ${entity.category || "the extracted tool landscape"}. ${entity.summary}`
|
|
769
|
+
: `Local evidence links ${title} to ${entity.category || "the extracted tool landscape"} with ${sourceCount} source reference(s).`;
|
|
770
|
+
const whenToUse = asArray(entity.topics).length > 0
|
|
771
|
+
? `Review when exploring ${asArray(entity.topics).slice(0, 4).join(", ")} options from the local source set.`
|
|
772
|
+
: "Review when evaluating whether this repository should become a maintained tool note.";
|
|
773
|
+
const overlap = asArray(entity.related_repos).length > 0
|
|
774
|
+
? "Related repositories were inferred from shared local topic labels or category evidence. Treat this as a review cue, not external verification."
|
|
775
|
+
: "No overlap was inferred from local topic labels in this slice.";
|
|
776
|
+
const body = [
|
|
777
|
+
`# ${title}`,
|
|
144
778
|
"",
|
|
145
779
|
"## Summary",
|
|
146
780
|
"",
|
|
147
781
|
entity.summary || `Generated tool entity page for ${entity.name || entity.entity_id}.`,
|
|
148
782
|
"",
|
|
149
|
-
"##
|
|
783
|
+
"## Key Facts",
|
|
150
784
|
"",
|
|
151
|
-
`-
|
|
152
|
-
`-
|
|
153
|
-
`-
|
|
154
|
-
`-
|
|
785
|
+
`- Canonical URL: ${entity.github_url || "(missing)"}`,
|
|
786
|
+
`- Owner: ${entity.repo_owner || "(missing)"}`,
|
|
787
|
+
`- Repo name: ${entity.repo_name || "(missing)"}`,
|
|
788
|
+
`- Category: ${entity.category || "(unknown)"}`,
|
|
789
|
+
`- Confidence: ${entity.confidence_tier || entity.confidence || "medium"}`,
|
|
790
|
+
`- Local source refs: ${sourceCount}`,
|
|
155
791
|
"",
|
|
156
|
-
"##
|
|
792
|
+
"## Discovery Source",
|
|
793
|
+
"",
|
|
794
|
+
mdLinkedList([...new Set(discoverySources.filter(Boolean).map(String))]),
|
|
795
|
+
"",
|
|
796
|
+
"## Extracted Snippet",
|
|
797
|
+
"",
|
|
798
|
+
quoteBlock(firstSnippet),
|
|
799
|
+
"",
|
|
800
|
+
"## Topic Labels",
|
|
801
|
+
"",
|
|
802
|
+
topicLabelList(entity),
|
|
803
|
+
"",
|
|
804
|
+
"## Why It Matters",
|
|
157
805
|
"",
|
|
158
|
-
|
|
806
|
+
whyItMatters,
|
|
807
|
+
"",
|
|
808
|
+
"## When To Use",
|
|
809
|
+
"",
|
|
810
|
+
whenToUse,
|
|
811
|
+
"",
|
|
812
|
+
"## Related Repos",
|
|
813
|
+
"",
|
|
814
|
+
relatedRepoList(entity),
|
|
815
|
+
"",
|
|
816
|
+
"## Overlaps / Differences",
|
|
817
|
+
"",
|
|
818
|
+
overlap,
|
|
819
|
+
"",
|
|
820
|
+
"## Open Questions",
|
|
821
|
+
"",
|
|
822
|
+
"- Verify license, maintenance status, and project maturity before relying on this tool.",
|
|
823
|
+
"- Confirm whether the local topic labels match the repository's actual current scope.",
|
|
824
|
+
"- Decide whether this should remain a tool entity or be reclassified after review.",
|
|
825
|
+
"",
|
|
826
|
+
"## Local Evidence Records",
|
|
827
|
+
"",
|
|
828
|
+
evidenceRecords.length > 0
|
|
829
|
+
? evidenceRecords.map((record) => [
|
|
830
|
+
`- source_id: ${record.source_id || "(missing)"}`,
|
|
831
|
+
` - source: ${record.source_link || record.source_logical_path || "(missing)"}`,
|
|
832
|
+
` - topics: ${asArray(record.topics).join(", ") || "(none)"}`,
|
|
833
|
+
` - confidence: ${record.confidence_tier || record.confidence || "(unknown)"}`,
|
|
834
|
+
].join("\n")).join("\n")
|
|
835
|
+
: "- No structured evidence records recorded.",
|
|
159
836
|
"",
|
|
160
837
|
"## Provenance",
|
|
161
838
|
"",
|
|
162
839
|
mdList(provenanceRefsFor(entity)),
|
|
163
840
|
"",
|
|
164
841
|
].join("\n");
|
|
842
|
+
return withPersonalBrainFrontmatter({
|
|
843
|
+
id: entity.entity_id,
|
|
844
|
+
title,
|
|
845
|
+
type: "tool_entity",
|
|
846
|
+
created_at: generatedAt,
|
|
847
|
+
updated_at: generatedAt,
|
|
848
|
+
aliases: [entity.repo_name, entity.github_url].filter(Boolean),
|
|
849
|
+
tags: ["tool", entity.category, ...asArray(entity.topics)].filter(Boolean),
|
|
850
|
+
source_refs: sourceRefsFor(entity),
|
|
851
|
+
confidence: entity.confidence_tier || entity.confidence || "medium",
|
|
852
|
+
review_status: "ready_for_review",
|
|
853
|
+
}, body);
|
|
165
854
|
}
|
|
166
855
|
|
|
167
|
-
function renderConceptPage(concept) {
|
|
168
|
-
|
|
169
|
-
|
|
856
|
+
function renderConceptPage(concept, generatedAt) {
|
|
857
|
+
const title = recordTitle(concept, "Concept");
|
|
858
|
+
const relatedCount = asArray(concept.related_entity_details).length || asArray(concept.related_entities).length;
|
|
859
|
+
const sourceCount = sourceRefsFor(concept).length;
|
|
860
|
+
const summary = concept.definition
|
|
861
|
+
|| `Local sources contain ${relatedCount} tool candidate(s) and ${sourceCount} source reference(s) related to ${title}.`;
|
|
862
|
+
const body = [
|
|
863
|
+
`# ${title}`,
|
|
864
|
+
"",
|
|
865
|
+
"## Summary",
|
|
866
|
+
"",
|
|
867
|
+
summary,
|
|
868
|
+
"",
|
|
869
|
+
"## Decision Brief",
|
|
870
|
+
"",
|
|
871
|
+
conceptDecisionBrief(concept),
|
|
872
|
+
"",
|
|
873
|
+
"## Key Axes",
|
|
170
874
|
"",
|
|
171
|
-
|
|
875
|
+
conceptAxisList(concept),
|
|
172
876
|
"",
|
|
173
|
-
|
|
877
|
+
"## Implementations / Examples",
|
|
878
|
+
"",
|
|
879
|
+
conceptImplementationList(concept),
|
|
880
|
+
"",
|
|
881
|
+
"## Patterns",
|
|
882
|
+
"",
|
|
883
|
+
conceptPatternList(concept),
|
|
884
|
+
"",
|
|
885
|
+
"## Recommendations / Caveats",
|
|
886
|
+
"",
|
|
887
|
+
"- Treat this concept page as local-evidence synthesis, not external verification.",
|
|
888
|
+
"- Prioritize examples with multiple source references or stronger extraction confidence.",
|
|
889
|
+
"- Review source-quality warnings before making architecture or tool adoption decisions.",
|
|
890
|
+
"",
|
|
891
|
+
"## Open Questions",
|
|
892
|
+
"",
|
|
893
|
+
"- Which extracted implementation is mature enough for real project adoption?",
|
|
894
|
+
"- Are the local topic labels accurate after human review?",
|
|
895
|
+
"- Should this concept be split, merged, or promoted after comparison/synthesis enrichment?",
|
|
174
896
|
"",
|
|
175
897
|
"## Aliases",
|
|
176
898
|
"",
|
|
177
899
|
mdList(concept.aliases),
|
|
178
900
|
"",
|
|
179
|
-
"## Related
|
|
901
|
+
"## Related Pages",
|
|
180
902
|
"",
|
|
181
|
-
|
|
903
|
+
conceptRelatedPagesList(concept),
|
|
182
904
|
"",
|
|
183
|
-
"##
|
|
905
|
+
"## Source References",
|
|
184
906
|
"",
|
|
185
907
|
mdList(sourceRefsFor(concept)),
|
|
186
908
|
"",
|
|
909
|
+
"## Provenance",
|
|
910
|
+
"",
|
|
911
|
+
mdList(provenanceRefsFor(concept)),
|
|
912
|
+
"",
|
|
187
913
|
].join("\n");
|
|
914
|
+
return withPersonalBrainFrontmatter({
|
|
915
|
+
id: concept.concept_id,
|
|
916
|
+
title,
|
|
917
|
+
type: "concept",
|
|
918
|
+
created_at: generatedAt,
|
|
919
|
+
updated_at: generatedAt,
|
|
920
|
+
aliases: concept.aliases,
|
|
921
|
+
tags: ["concept"],
|
|
922
|
+
related_pages: asArray(concept.related_entity_details).map((entity) => entity.target_page_path ? `../${relativePersonalBrainLink(entity.target_page_path)}` : null).filter(Boolean).slice(0, 12),
|
|
923
|
+
source_refs: sourceRefsFor(concept),
|
|
924
|
+
confidence: concept.confidence_tier || concept.confidence || "medium",
|
|
925
|
+
review_status: "ready_for_review",
|
|
926
|
+
}, body);
|
|
188
927
|
}
|
|
189
928
|
|
|
190
|
-
function renderComparisonPage(comparison) {
|
|
191
|
-
|
|
192
|
-
|
|
929
|
+
function renderComparisonPage(comparison, generatedAt) {
|
|
930
|
+
const title = recordTitle(comparison, "Comparison");
|
|
931
|
+
const body = [
|
|
932
|
+
`# ${title}`,
|
|
933
|
+
"",
|
|
934
|
+
"## Summary",
|
|
935
|
+
"",
|
|
936
|
+
comparison.summary || `Local comparison for ${comparison.topic || comparison.comparison_id}.`,
|
|
937
|
+
"",
|
|
938
|
+
"## Decision Guidance",
|
|
939
|
+
"",
|
|
940
|
+
comparisonDecisionGuidance(comparison),
|
|
941
|
+
"",
|
|
942
|
+
"## Decision Axes",
|
|
193
943
|
"",
|
|
194
|
-
|
|
944
|
+
conceptAxisList({ key_axes: comparison.decision_axes }),
|
|
195
945
|
"",
|
|
196
|
-
|
|
946
|
+
"## Comparison Matrix",
|
|
197
947
|
"",
|
|
198
|
-
|
|
948
|
+
comparisonMatrix(comparison),
|
|
199
949
|
"",
|
|
200
|
-
|
|
950
|
+
"## Candidate Tools / Repos",
|
|
201
951
|
"",
|
|
202
|
-
|
|
952
|
+
candidateToolList(comparison.matrix_rows),
|
|
953
|
+
"",
|
|
954
|
+
"## Recommendations",
|
|
955
|
+
"",
|
|
956
|
+
mdList(comparison.recommendations),
|
|
957
|
+
"",
|
|
958
|
+
"## Caveats",
|
|
959
|
+
"",
|
|
960
|
+
mdList(comparison.caveats),
|
|
961
|
+
"",
|
|
962
|
+
"## Source Confidence",
|
|
963
|
+
"",
|
|
964
|
+
confidenceSummaryList(comparison.source_confidence_summary),
|
|
965
|
+
"",
|
|
966
|
+
"## Open Questions",
|
|
967
|
+
"",
|
|
968
|
+
"- Which candidate should be verified first with external source checks?",
|
|
969
|
+
"- Which criteria matter most for the target SDTK use case?",
|
|
970
|
+
"- Are any candidates duplicates, abandoned, or misclassified in the local source set?",
|
|
971
|
+
"",
|
|
972
|
+
"## Source References",
|
|
203
973
|
"",
|
|
204
974
|
mdList(sourceRefsFor(comparison)),
|
|
205
975
|
"",
|
|
976
|
+
"## Provenance",
|
|
977
|
+
"",
|
|
978
|
+
mdList(provenanceRefsFor(comparison)),
|
|
979
|
+
"",
|
|
206
980
|
].join("\n");
|
|
981
|
+
return withPersonalBrainFrontmatter({
|
|
982
|
+
id: comparison.comparison_id,
|
|
983
|
+
title,
|
|
984
|
+
type: "comparison",
|
|
985
|
+
created_at: generatedAt,
|
|
986
|
+
updated_at: generatedAt,
|
|
987
|
+
aliases: [comparison.topic].filter(Boolean),
|
|
988
|
+
tags: ["comparison"],
|
|
989
|
+
related_pages: asArray(comparison.compared_entity_details).map((entity) => entity.target_page_path ? `../${relativePersonalBrainLink(entity.target_page_path)}` : null).filter(Boolean).slice(0, 12),
|
|
990
|
+
source_refs: sourceRefsFor(comparison),
|
|
991
|
+
confidence: comparison.confidence_tier || comparison.confidence || "medium",
|
|
992
|
+
review_status: "needs_review",
|
|
993
|
+
}, body);
|
|
207
994
|
}
|
|
208
995
|
|
|
209
|
-
function renderSynthesisPage(synthesis) {
|
|
210
|
-
|
|
211
|
-
|
|
996
|
+
function renderSynthesisPage(synthesis, generatedAt) {
|
|
997
|
+
const title = recordTitle(synthesis, "Synthesis");
|
|
998
|
+
const body = [
|
|
999
|
+
`# ${title}`,
|
|
212
1000
|
"",
|
|
213
1001
|
"## Summary",
|
|
214
1002
|
"",
|
|
215
1003
|
synthesis.summary || `Generated synthesis page for ${synthesis.topic || synthesis.synthesis_id}.`,
|
|
216
1004
|
"",
|
|
217
|
-
"##
|
|
1005
|
+
"## Decision Narrative",
|
|
1006
|
+
"",
|
|
1007
|
+
synthesisDecisionNarrative(synthesis),
|
|
1008
|
+
"",
|
|
1009
|
+
"## Landscape Snapshot",
|
|
1010
|
+
"",
|
|
1011
|
+
candidateToolList(synthesis.candidate_tools),
|
|
1012
|
+
"",
|
|
1013
|
+
"## Key Patterns",
|
|
1014
|
+
"",
|
|
1015
|
+
conceptPatternList({ patterns: synthesis.patterns }),
|
|
1016
|
+
"",
|
|
1017
|
+
"## Decision Axes",
|
|
1018
|
+
"",
|
|
1019
|
+
conceptAxisList({ key_axes: synthesis.landscape_axes }),
|
|
1020
|
+
"",
|
|
1021
|
+
"## Recommended Review Path",
|
|
218
1022
|
"",
|
|
219
1023
|
mdList(synthesis.recommendations),
|
|
220
1024
|
"",
|
|
221
|
-
"##
|
|
1025
|
+
"## Caveats",
|
|
1026
|
+
"",
|
|
1027
|
+
mdList(synthesis.caveats),
|
|
1028
|
+
"",
|
|
1029
|
+
"## Source Confidence",
|
|
1030
|
+
"",
|
|
1031
|
+
confidenceSummaryList(synthesis.source_confidence_summary),
|
|
1032
|
+
"",
|
|
1033
|
+
"## Related Comparisons",
|
|
1034
|
+
"",
|
|
1035
|
+
synthesis.related_comparison_path ? mdList([`../${relativePersonalBrainLink(synthesis.related_comparison_path)}`]) : "- None recorded.",
|
|
1036
|
+
"",
|
|
1037
|
+
"## Open Questions",
|
|
1038
|
+
"",
|
|
1039
|
+
"- Which comparison criteria should become product acceptance gates?",
|
|
1040
|
+
"- Which local candidates need external verification before adoption?",
|
|
1041
|
+
"- Does this cluster need a dedicated deeper synthesis pass?",
|
|
1042
|
+
"",
|
|
1043
|
+
"## Source References",
|
|
222
1044
|
"",
|
|
223
1045
|
mdList(sourceRefsFor(synthesis)),
|
|
224
1046
|
"",
|
|
1047
|
+
"## Provenance",
|
|
1048
|
+
"",
|
|
1049
|
+
mdList(provenanceRefsFor(synthesis)),
|
|
1050
|
+
"",
|
|
225
1051
|
].join("\n");
|
|
1052
|
+
return withPersonalBrainFrontmatter({
|
|
1053
|
+
id: synthesis.synthesis_id,
|
|
1054
|
+
title,
|
|
1055
|
+
type: "synthesis",
|
|
1056
|
+
created_at: generatedAt,
|
|
1057
|
+
updated_at: generatedAt,
|
|
1058
|
+
aliases: [synthesis.topic].filter(Boolean),
|
|
1059
|
+
tags: ["synthesis"],
|
|
1060
|
+
related_pages: synthesis.related_comparison_path ? [`../${relativePersonalBrainLink(synthesis.related_comparison_path)}`] : [],
|
|
1061
|
+
source_refs: sourceRefsFor(synthesis),
|
|
1062
|
+
confidence: synthesis.confidence_tier || synthesis.confidence || "medium",
|
|
1063
|
+
review_status: "needs_review",
|
|
1064
|
+
}, body);
|
|
226
1065
|
}
|
|
227
1066
|
|
|
228
1067
|
function renderRootPage(name, payload) {
|
|
229
1068
|
const extractionCounts = payload && payload.source_counts ? payload.source_counts : {};
|
|
1069
|
+
const concepts = topConcepts(payload, 8);
|
|
1070
|
+
const comparisons = comparisonEntries(payload, 6);
|
|
1071
|
+
const syntheses = synthesisEntries(payload, 6);
|
|
1072
|
+
const tools = highSignalTools(payload, 10);
|
|
1073
|
+
const sources = sourceEntries(payload, 8);
|
|
1074
|
+
const dashboards = [
|
|
1075
|
+
["Research / Tool Landscape", "dashboards/research-tool-landscape.md"],
|
|
1076
|
+
["Decision Support", "dashboards/decision-support.md"],
|
|
1077
|
+
["Maintenance / Quality", "dashboards/maintenance-quality.md"],
|
|
1078
|
+
["Source Coverage", "dashboards/source-coverage.md"],
|
|
1079
|
+
];
|
|
230
1080
|
const titles = {
|
|
231
|
-
index: "
|
|
232
|
-
overview: "
|
|
233
|
-
ontology: "
|
|
234
|
-
taxonomy: "
|
|
235
|
-
graph: "
|
|
236
|
-
log: "
|
|
1081
|
+
index: "Local Wiki Index",
|
|
1082
|
+
overview: "Local Wiki Overview",
|
|
1083
|
+
ontology: "Local Wiki Ontology",
|
|
1084
|
+
taxonomy: "Local Wiki Taxonomy",
|
|
1085
|
+
graph: "Local Wiki Graph",
|
|
1086
|
+
log: "Local Wiki Generation Log",
|
|
237
1087
|
};
|
|
238
|
-
|
|
239
|
-
|
|
1088
|
+
const title = titles[name] || "Local Wiki";
|
|
1089
|
+
const dashboardLinks = dashboards.map(([label, target]) => `- [${label}](${target})`).join("\n");
|
|
1090
|
+
const topToolLinks = tools.length > 0
|
|
1091
|
+
? tools.slice(0, 8).map((tool) => `- [${tool.title}](${rootLink(tool.path)}): ${tool.category}; ${tool.source_refs} source reference(s).`).join("\n")
|
|
1092
|
+
: "- None recorded.";
|
|
1093
|
+
const sourceLinks = sources.length > 0
|
|
1094
|
+
? sources.map((source) => `- [${source.title}](${rootLink(source.path)}): ${source.quality_flags.length > 0 ? source.quality_flags.join(", ") : "no quality flags"}.`).join("\n")
|
|
1095
|
+
: "- None recorded.";
|
|
1096
|
+
const taxonomyRows = [
|
|
1097
|
+
"| Area | Count | Starting points |",
|
|
1098
|
+
"|---|---:|---|",
|
|
1099
|
+
`| Sources | ${asArray(payload.sources).length} | [Source coverage dashboard](dashboards/source-coverage.md) |`,
|
|
1100
|
+
`| Tool entities | ${asArray(payload.tool_entities).length} | [Research / Tool Landscape](dashboards/research-tool-landscape.md) |`,
|
|
1101
|
+
`| Concepts | ${asArray(payload.concepts).length} | ${concepts.slice(0, 4).map((entry) => entry.path ? `[${mdEscapeTable(entry.title)}](${rootLink(entry.path)})` : mdEscapeTable(entry.title)).join("<br>") || "-"} |`,
|
|
1102
|
+
`| Comparisons | ${asArray(payload.comparisons).length} | ${comparisons.slice(0, 4).map((entry) => entry.path ? `[${mdEscapeTable(entry.title)}](${rootLink(entry.path)})` : mdEscapeTable(entry.title)).join("<br>") || "-"} |`,
|
|
1103
|
+
`| Syntheses | ${asArray(payload.syntheses).length} | ${syntheses.slice(0, 4).map((entry) => entry.path ? `[${mdEscapeTable(entry.title)}](${rootLink(entry.path)})` : mdEscapeTable(entry.title)).join("<br>") || "-"} |`,
|
|
1104
|
+
].join("\n");
|
|
1105
|
+
const graphRows = [
|
|
1106
|
+
"| Graph surface | Count | Navigation |",
|
|
1107
|
+
"|---|---:|---|",
|
|
1108
|
+
`| Relations | ${asArray(payload.relations).length} | Review relation append sections below. |`,
|
|
1109
|
+
`| Concepts | ${asArray(payload.concepts).length} | ${linkedListFromRoot(concepts.slice(0, 5)).replace(/\n/g, "<br>")} |`,
|
|
1110
|
+
`| Comparisons | ${asArray(payload.comparisons).length} | ${linkedListFromRoot(comparisons.slice(0, 5)).replace(/\n/g, "<br>")} |`,
|
|
1111
|
+
`| Syntheses | ${asArray(payload.syntheses).length} | ${linkedListFromRoot(syntheses.slice(0, 5)).replace(/\n/g, "<br>")} |`,
|
|
1112
|
+
].join("\n");
|
|
1113
|
+
const pageSpecificSections = {
|
|
1114
|
+
index: [
|
|
1115
|
+
"## Start Here",
|
|
1116
|
+
"",
|
|
1117
|
+
dashboardLinks,
|
|
1118
|
+
"",
|
|
1119
|
+
"## High-Signal Tool Entry Points",
|
|
1120
|
+
"",
|
|
1121
|
+
topToolLinks,
|
|
1122
|
+
"",
|
|
1123
|
+
"## Concept / Comparison / Synthesis Entry Points",
|
|
1124
|
+
"",
|
|
1125
|
+
"Concepts:",
|
|
1126
|
+
"",
|
|
1127
|
+
linkedListFromRoot(concepts),
|
|
1128
|
+
"",
|
|
1129
|
+
"Comparisons:",
|
|
1130
|
+
"",
|
|
1131
|
+
linkedListFromRoot(comparisons),
|
|
1132
|
+
"",
|
|
1133
|
+
"Syntheses:",
|
|
1134
|
+
"",
|
|
1135
|
+
linkedListFromRoot(syntheses),
|
|
1136
|
+
],
|
|
1137
|
+
overview: [
|
|
1138
|
+
"## Session Brief",
|
|
1139
|
+
"",
|
|
1140
|
+
"This generated local wiki is organized for local review. Start with dashboards, inspect top concepts, then use comparison and synthesis pages before adopting any tool.",
|
|
1141
|
+
"",
|
|
1142
|
+
"## Current Landscape",
|
|
1143
|
+
"",
|
|
1144
|
+
`- high-signal tools listed: ${tools.length}`,
|
|
1145
|
+
`- top concepts listed: ${concepts.length}`,
|
|
1146
|
+
`- comparison pages listed: ${comparisons.length}`,
|
|
1147
|
+
`- synthesis pages listed: ${syntheses.length}`,
|
|
1148
|
+
"",
|
|
1149
|
+
"## Recommended Browsing Path",
|
|
1150
|
+
"",
|
|
1151
|
+
"- [Research / Tool Landscape](dashboards/research-tool-landscape.md)",
|
|
1152
|
+
"- [Decision Support](dashboards/decision-support.md)",
|
|
1153
|
+
"- [Source Coverage](dashboards/source-coverage.md)",
|
|
1154
|
+
"- [Maintenance / Quality](dashboards/maintenance-quality.md)",
|
|
1155
|
+
],
|
|
1156
|
+
taxonomy: [
|
|
1157
|
+
"## Taxonomy Snapshot",
|
|
1158
|
+
"",
|
|
1159
|
+
taxonomyRows,
|
|
1160
|
+
"",
|
|
1161
|
+
"## Concept Clusters",
|
|
1162
|
+
"",
|
|
1163
|
+
concepts.length > 0
|
|
1164
|
+
? concepts.map((concept) => `- [${concept.title}](${rootLink(concept.path)}): ${concept.related_count} related tool candidate(s); axes: ${concept.axes.slice(0, 4).join(", ") || "none inferred"}.`).join("\n")
|
|
1165
|
+
: "- None recorded.",
|
|
1166
|
+
],
|
|
1167
|
+
graph: [
|
|
1168
|
+
"## Navigation Graph",
|
|
1169
|
+
"",
|
|
1170
|
+
graphRows,
|
|
1171
|
+
"",
|
|
1172
|
+
"## Tool Cluster Hubs",
|
|
1173
|
+
"",
|
|
1174
|
+
groupToolsByCategory(tools).slice(0, 8).map(({ category, rows }) =>
|
|
1175
|
+
`- ${category}: ${rows.slice(0, 4).map((tool) => `[${tool.title}](${rootLink(tool.path)})`).join(", ")}`
|
|
1176
|
+
).join("\n") || "- None recorded.",
|
|
1177
|
+
],
|
|
1178
|
+
log: [
|
|
1179
|
+
"## Review Trail",
|
|
1180
|
+
"",
|
|
1181
|
+
"- Compile operations were derived from the semantic extraction report.",
|
|
1182
|
+
"- Dashboard and navigation pages are generated from the same local evidence as entity/concept/comparison/synthesis pages.",
|
|
1183
|
+
"- Re-run lint after compile/apply to verify internal links and quality gates.",
|
|
1184
|
+
],
|
|
1185
|
+
ontology: [
|
|
1186
|
+
"## Ontology Navigation",
|
|
1187
|
+
"",
|
|
1188
|
+
"- source: local file/source registry page.",
|
|
1189
|
+
"- tool_entity: extracted GitHub repository or tool candidate page.",
|
|
1190
|
+
"- concept: local thematic grouping across tools and source evidence.",
|
|
1191
|
+
"- comparison: decision matrix for a concept cluster.",
|
|
1192
|
+
"- synthesis: higher-level review path for a cluster.",
|
|
1193
|
+
"- dashboard: curated generated navigation page for browsing and triage.",
|
|
1194
|
+
],
|
|
1195
|
+
};
|
|
1196
|
+
const body = [
|
|
1197
|
+
`# ${title}`,
|
|
240
1198
|
"",
|
|
241
1199
|
"## Generation Context",
|
|
242
1200
|
"",
|
|
@@ -254,13 +1212,33 @@ function renderRootPage(name, payload) {
|
|
|
254
1212
|
"- concepts/",
|
|
255
1213
|
"- comparisons/",
|
|
256
1214
|
"- syntheses/",
|
|
1215
|
+
"- dashboards/",
|
|
257
1216
|
"- maintenance/",
|
|
258
1217
|
"",
|
|
1218
|
+
...(pageSpecificSections[name] || []),
|
|
1219
|
+
"",
|
|
1220
|
+
"## Source Coverage",
|
|
1221
|
+
"",
|
|
1222
|
+
sourceLinks,
|
|
1223
|
+
"",
|
|
259
1224
|
].join("\n");
|
|
1225
|
+
return withPersonalBrainFrontmatter({
|
|
1226
|
+
id: `personal_brain_${name}`,
|
|
1227
|
+
title,
|
|
1228
|
+
type: "root",
|
|
1229
|
+
created_at: payload.generated_at || "",
|
|
1230
|
+
updated_at: payload.generated_at || "",
|
|
1231
|
+
aliases: [name],
|
|
1232
|
+
tags: ["local-wiki", name],
|
|
1233
|
+
related_pages: dashboards.map(([, target]) => target),
|
|
1234
|
+
source_refs: asArray(payload.sources).map((source) => source.source_id).filter(Boolean).slice(0, 50),
|
|
1235
|
+
confidence: "medium",
|
|
1236
|
+
review_status: "needs_review",
|
|
1237
|
+
}, body);
|
|
260
1238
|
}
|
|
261
1239
|
|
|
262
1240
|
function renderMaintenancePage(payload) {
|
|
263
|
-
|
|
1241
|
+
const body = [
|
|
264
1242
|
"# Extraction Quality Review",
|
|
265
1243
|
"",
|
|
266
1244
|
"## Source Quality Findings",
|
|
@@ -272,6 +1250,18 @@ function renderMaintenancePage(payload) {
|
|
|
272
1250
|
mdList(asArray(payload.unsupported_items).map((item) => `${item.item_id || "unsupported"}: ${item.reason || item.raw_observation_summary || "review required"}`)),
|
|
273
1251
|
"",
|
|
274
1252
|
].join("\n");
|
|
1253
|
+
return withPersonalBrainFrontmatter({
|
|
1254
|
+
id: "maintenance_extraction_quality_review",
|
|
1255
|
+
title: "Extraction Quality Review",
|
|
1256
|
+
type: "maintenance",
|
|
1257
|
+
created_at: payload.generated_at || "",
|
|
1258
|
+
updated_at: payload.generated_at || "",
|
|
1259
|
+
aliases: ["source quality", "extraction quality"],
|
|
1260
|
+
tags: ["maintenance", "quality"],
|
|
1261
|
+
source_refs: asArray(payload.sources).map((source) => source.source_id).filter(Boolean).slice(0, 50),
|
|
1262
|
+
confidence: asArray(payload.unsupported_items).length > 0 ? "low" : "medium",
|
|
1263
|
+
review_status: "needs_review",
|
|
1264
|
+
}, body);
|
|
275
1265
|
}
|
|
276
1266
|
|
|
277
1267
|
function pushCreatePageOperation(operations, raw) {
|
|
@@ -308,8 +1298,8 @@ function operationsFromSemanticExtraction(payload) {
|
|
|
308
1298
|
source_id: source.source_id,
|
|
309
1299
|
source_hash: source.source_hash,
|
|
310
1300
|
target_page_path: source.target_page_path,
|
|
311
|
-
content: renderSourcePage(source),
|
|
312
|
-
proposed_content_summary: `Create
|
|
1301
|
+
content: renderSourcePage(source, payload.generated_at),
|
|
1302
|
+
proposed_content_summary: `Create local wiki source page for ${source.source_logical_path || source.source_relative_path || source.source_id}.`,
|
|
313
1303
|
source_refs: sourceRefsFor(source),
|
|
314
1304
|
provenance_refs: provenanceRefsFor(source),
|
|
315
1305
|
evidence_refs: refsFor(source),
|
|
@@ -325,7 +1315,7 @@ function operationsFromSemanticExtraction(payload) {
|
|
|
325
1315
|
source_id: sourceId,
|
|
326
1316
|
source_hash: sourceHashFor(sourceById, sourceId),
|
|
327
1317
|
target_page_path: entity.target_page_path,
|
|
328
|
-
content: renderToolEntityPage(entity),
|
|
1318
|
+
content: renderToolEntityPage(entity, payload.generated_at),
|
|
329
1319
|
proposed_content_summary: `Create tool entity page for ${entity.name || entity.entity_id}.`,
|
|
330
1320
|
source_refs: sourceRefsFor(entity),
|
|
331
1321
|
provenance_refs: provenanceRefsFor(entity),
|
|
@@ -361,7 +1351,7 @@ function operationsFromSemanticExtraction(payload) {
|
|
|
361
1351
|
source_id: sourceId,
|
|
362
1352
|
source_hash: sourceHashFor(sourceById, sourceId),
|
|
363
1353
|
target_page_path: concept.target_page_path,
|
|
364
|
-
content: renderConceptPage(concept),
|
|
1354
|
+
content: renderConceptPage(concept, payload.generated_at),
|
|
365
1355
|
proposed_content_summary: `Create concept page for ${concept.name || concept.concept_id}.`,
|
|
366
1356
|
source_refs: sourceRefsFor(concept),
|
|
367
1357
|
provenance_refs: provenanceRefsFor(concept),
|
|
@@ -378,7 +1368,7 @@ function operationsFromSemanticExtraction(payload) {
|
|
|
378
1368
|
source_id: sourceId,
|
|
379
1369
|
source_hash: sourceHashFor(sourceById, sourceId),
|
|
380
1370
|
target_page_path: comparison.target_page_path,
|
|
381
|
-
content: renderComparisonPage(comparison),
|
|
1371
|
+
content: renderComparisonPage(comparison, payload.generated_at),
|
|
382
1372
|
proposed_content_summary: `Create comparison page for ${comparison.topic || comparison.comparison_id}.`,
|
|
383
1373
|
source_refs: sourceRefsFor(comparison),
|
|
384
1374
|
provenance_refs: provenanceRefsFor(comparison),
|
|
@@ -395,7 +1385,7 @@ function operationsFromSemanticExtraction(payload) {
|
|
|
395
1385
|
source_id: sourceId,
|
|
396
1386
|
source_hash: sourceHashFor(sourceById, sourceId),
|
|
397
1387
|
target_page_path: synthesis.target_page_path,
|
|
398
|
-
content: renderSynthesisPage(synthesis),
|
|
1388
|
+
content: renderSynthesisPage(synthesis, payload.generated_at),
|
|
399
1389
|
proposed_content_summary: `Create synthesis page for ${synthesis.topic || synthesis.synthesis_id}.`,
|
|
400
1390
|
source_refs: sourceRefsFor(synthesis),
|
|
401
1391
|
provenance_refs: provenanceRefsFor(synthesis),
|
|
@@ -406,12 +1396,12 @@ function operationsFromSemanticExtraction(payload) {
|
|
|
406
1396
|
}
|
|
407
1397
|
|
|
408
1398
|
const rootPages = [
|
|
409
|
-
["index", "
|
|
410
|
-
["overview", "
|
|
411
|
-
["ontology", "
|
|
412
|
-
["taxonomy", "
|
|
413
|
-
["graph", "
|
|
414
|
-
["log", "
|
|
1399
|
+
["index", "wiki/index.md", "Create local wiki index from semantic extraction output."],
|
|
1400
|
+
["overview", "wiki/overview.md", "Create local wiki overview summarizing extracted local sources."],
|
|
1401
|
+
["ontology", "wiki/ontology.md", "Create ontology page describing source, tool_entity, concept, claim, relation, comparison, and synthesis records."],
|
|
1402
|
+
["taxonomy", "wiki/taxonomy.md", "Create taxonomy page for extracted categories and concepts."],
|
|
1403
|
+
["graph", "wiki/graph.md", "Create semantic graph summary page from extracted relations."],
|
|
1404
|
+
["log", "wiki/log.md", "Create generation log page for extraction and compile preview evidence."],
|
|
415
1405
|
];
|
|
416
1406
|
for (const [name, target, summary] of rootPages) {
|
|
417
1407
|
pushCreatePageOperation(operations, {
|
|
@@ -427,13 +1417,34 @@ function operationsFromSemanticExtraction(payload) {
|
|
|
427
1417
|
});
|
|
428
1418
|
}
|
|
429
1419
|
|
|
1420
|
+
const dashboardPages = [
|
|
1421
|
+
["research-tool-landscape", "wiki/dashboards/research-tool-landscape.md", "Create research/tool landscape dashboard from local semantic extraction output."],
|
|
1422
|
+
["decision-support", "wiki/dashboards/decision-support.md", "Create decision-support dashboard from local comparison and synthesis evidence."],
|
|
1423
|
+
["maintenance-quality", "wiki/dashboards/maintenance-quality.md", "Create maintenance/quality dashboard from local extraction findings."],
|
|
1424
|
+
["source-coverage", "wiki/dashboards/source-coverage.md", "Create source coverage dashboard from local source registry evidence."],
|
|
1425
|
+
];
|
|
1426
|
+
for (const [name, target, summary] of dashboardPages) {
|
|
1427
|
+
pushCreatePageOperation(operations, {
|
|
1428
|
+
operation_id: `pb-dashboard-${name}`,
|
|
1429
|
+
source_id: rootSourceId,
|
|
1430
|
+
source_hash: rootSourceHash,
|
|
1431
|
+
target_page_path: target,
|
|
1432
|
+
content: renderDashboardPage(name, payload),
|
|
1433
|
+
proposed_content_summary: summary,
|
|
1434
|
+
source_refs: asArray(payload.sources).map((source) => source.source_id).filter(Boolean).slice(0, 50),
|
|
1435
|
+
evidence_refs: [`record_type:${payload.record_type}`, `dashboard:${name}`],
|
|
1436
|
+
confidence: "medium",
|
|
1437
|
+
review_status: "needs_review",
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
|
|
430
1441
|
for (const relation of asArray(payload.relations)) {
|
|
431
1442
|
operations.push({
|
|
432
1443
|
operation_id: `pb-relation-${String(operations.length + 1).padStart(3, "0")}`,
|
|
433
1444
|
operation_type: "add_relation",
|
|
434
1445
|
source_id: relation.source_id || rootSourceId,
|
|
435
1446
|
source_hash: rootSourceHash,
|
|
436
|
-
target_page_path: "
|
|
1447
|
+
target_page_path: "wiki/graph.md",
|
|
437
1448
|
content_sections: [
|
|
438
1449
|
{
|
|
439
1450
|
title: `Relation: ${relation.relation_type || relation.relation_id}`,
|
|
@@ -463,7 +1474,7 @@ function operationsFromSemanticExtraction(payload) {
|
|
|
463
1474
|
operation_id: "pb-maintenance-extraction-quality-review",
|
|
464
1475
|
source_id: rootSourceId,
|
|
465
1476
|
source_hash: rootSourceHash,
|
|
466
|
-
target_page_path: "
|
|
1477
|
+
target_page_path: "wiki/maintenance/extraction-quality-review.md",
|
|
467
1478
|
content: renderMaintenancePage(payload),
|
|
468
1479
|
proposed_content_summary: `Create maintenance review page for ${qualityFindingCount} source-quality finding(s) and ${unsupportedCount} unsupported extraction item(s).`,
|
|
469
1480
|
evidence_refs: [`source_quality_findings:${qualityFindingCount}`, `unsupported_items:${unsupportedCount}`],
|
|
@@ -615,7 +1626,7 @@ function fallbackOperationContent(operation) {
|
|
|
615
1626
|
"",
|
|
616
1627
|
"## Generated Operation",
|
|
617
1628
|
"",
|
|
618
|
-
operation.proposed_content_summary || "Generated
|
|
1629
|
+
operation.proposed_content_summary || "Generated local wiki content.",
|
|
619
1630
|
"",
|
|
620
1631
|
"## Evidence",
|
|
621
1632
|
"",
|
|
@@ -625,16 +1636,21 @@ function fallbackOperationContent(operation) {
|
|
|
625
1636
|
}
|
|
626
1637
|
|
|
627
1638
|
function normalizeTargetPath(rawTargetPath, projectPath) {
|
|
628
|
-
const
|
|
1639
|
+
const canonicalWikiRoot = getCanonicalWikiPath(projectPath);
|
|
1640
|
+
const legacyPersonalBrainRoot = getLegacyPersonalBrainPath(projectPath);
|
|
629
1641
|
const targetValue = String(rawTargetPath || "");
|
|
630
1642
|
const resolvedTarget = targetValue ? path.resolve(projectPath, targetValue) : "";
|
|
631
1643
|
const withinProject = Boolean(resolvedTarget) && isPathInsideOrEqual(resolvedTarget, projectPath);
|
|
632
|
-
const
|
|
1644
|
+
const withinCanonicalWiki = Boolean(resolvedTarget) && isPathInsideOrEqual(resolvedTarget, canonicalWikiRoot);
|
|
1645
|
+
const withinLegacyPersonalBrain = Boolean(resolvedTarget) && isPathInsideOrEqual(resolvedTarget, legacyPersonalBrainRoot);
|
|
1646
|
+
const withinWikiContent = withinCanonicalWiki || withinLegacyPersonalBrain;
|
|
633
1647
|
return {
|
|
634
1648
|
resolvedTarget,
|
|
635
1649
|
targetPathNormalized: withinProject ? toPosix(path.relative(projectPath, resolvedTarget)) : toPosix(resolvedTarget),
|
|
636
1650
|
withinProject,
|
|
637
|
-
|
|
1651
|
+
withinCanonicalWiki,
|
|
1652
|
+
withinLegacyPersonalBrain,
|
|
1653
|
+
withinWikiContent,
|
|
638
1654
|
};
|
|
639
1655
|
}
|
|
640
1656
|
|
|
@@ -651,7 +1667,9 @@ function normalizeOperation(rawOperation, index, projectPath) {
|
|
|
651
1667
|
target_page_path: String(rawOperation.target_page_path || rawOperation.targetPagePath || ""),
|
|
652
1668
|
target_path_normalized: target.targetPathNormalized,
|
|
653
1669
|
target_path_within_project: target.withinProject,
|
|
654
|
-
target_path_within_personal_brain: target.
|
|
1670
|
+
target_path_within_personal_brain: target.withinWikiContent,
|
|
1671
|
+
target_path_within_canonical_wiki: target.withinCanonicalWiki,
|
|
1672
|
+
target_path_within_legacy_personal_brain: target.withinLegacyPersonalBrain,
|
|
655
1673
|
operation_type: operationType || "unsupported_operation",
|
|
656
1674
|
requested_operation_type: operationType || "(missing)",
|
|
657
1675
|
proposed_content_summary: String(rawOperation.proposed_content_summary || rawOperation.proposedContentSummary || ""),
|
|
@@ -768,6 +1786,8 @@ function renderApplyPlan({ projectPath, planPath, reportPath, generatedAt, opera
|
|
|
768
1786
|
target_path_normalized: operation.target_path_normalized,
|
|
769
1787
|
target_path_within_project: operation.target_path_within_project,
|
|
770
1788
|
target_path_within_personal_brain: operation.target_path_within_personal_brain,
|
|
1789
|
+
target_path_within_canonical_wiki: operation.target_path_within_canonical_wiki,
|
|
1790
|
+
target_path_within_legacy_personal_brain: operation.target_path_within_legacy_personal_brain,
|
|
771
1791
|
content: operation.content,
|
|
772
1792
|
content_sections: operation.content_sections,
|
|
773
1793
|
source_refs: operation.source_refs,
|
|
@@ -829,12 +1849,14 @@ function aggregateApplyTargets(operations, projectPath) {
|
|
|
829
1849
|
const targets = new Map();
|
|
830
1850
|
for (const operation of operations) {
|
|
831
1851
|
const target = normalizeTargetPath(operation.target_page_path, projectPath);
|
|
832
|
-
if (!target.withinProject || !target.
|
|
833
|
-
throw new ValidationError(`Unsafe
|
|
1852
|
+
if (!target.withinProject || !target.withinWikiContent) {
|
|
1853
|
+
throw new ValidationError(`Unsafe wiki target path: ${operation.target_page_path}. No project files were changed.`);
|
|
834
1854
|
}
|
|
835
1855
|
const existing = targets.get(target.resolvedTarget) || {
|
|
836
1856
|
path: target.resolvedTarget,
|
|
837
1857
|
normalized: target.targetPathNormalized,
|
|
1858
|
+
withinCanonicalWiki: target.withinCanonicalWiki,
|
|
1859
|
+
withinLegacyPersonalBrain: target.withinLegacyPersonalBrain,
|
|
838
1860
|
sections: [],
|
|
839
1861
|
operationIds: [],
|
|
840
1862
|
};
|
|
@@ -848,6 +1870,111 @@ function aggregateApplyTargets(operations, projectPath) {
|
|
|
848
1870
|
return [...targets.values()].sort((a, b) => a.normalized.localeCompare(b.normalized));
|
|
849
1871
|
}
|
|
850
1872
|
|
|
1873
|
+
function scaffoldFileIfMissing(filePath, content, projectPath, rootPath, created, skipped) {
|
|
1874
|
+
if (!isPathInsideOrEqual(filePath, rootPath)) {
|
|
1875
|
+
throw new ValidationError(`Unsafe scaffold target path: ${filePath}. No project files were changed.`);
|
|
1876
|
+
}
|
|
1877
|
+
if (fs.existsSync(filePath)) {
|
|
1878
|
+
skipped.push(toPosix(path.relative(projectPath, filePath)));
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1881
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
1882
|
+
fs.writeFileSync(filePath, ensureTrailingNewline(content), "utf-8");
|
|
1883
|
+
created.push(toPosix(path.relative(projectPath, filePath)));
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
function scaffoldWikiPageFrontmatter(fields, body) {
|
|
1887
|
+
const generatedAt = new Date().toISOString();
|
|
1888
|
+
return withPersonalBrainFrontmatter({
|
|
1889
|
+
created_at: generatedAt,
|
|
1890
|
+
updated_at: generatedAt,
|
|
1891
|
+
...fields,
|
|
1892
|
+
}, body);
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
function scaffoldCanonicalWikiStructure(projectPath) {
|
|
1896
|
+
const canonicalRoot = getCanonicalWikiPath(projectPath);
|
|
1897
|
+
const created = [];
|
|
1898
|
+
const skipped = [];
|
|
1899
|
+
const directories = [
|
|
1900
|
+
"timelines",
|
|
1901
|
+
"queries",
|
|
1902
|
+
"decisions",
|
|
1903
|
+
"meeting-notes",
|
|
1904
|
+
"templates",
|
|
1905
|
+
"maintenance",
|
|
1906
|
+
];
|
|
1907
|
+
fs.mkdirSync(canonicalRoot, { recursive: true });
|
|
1908
|
+
for (const dir of directories) {
|
|
1909
|
+
const dirPath = path.join(canonicalRoot, dir);
|
|
1910
|
+
if (!isPathInsideOrEqual(dirPath, canonicalRoot)) {
|
|
1911
|
+
throw new ValidationError(`Unsafe wiki directory path: ${dirPath}. No project files were changed.`);
|
|
1912
|
+
}
|
|
1913
|
+
if (!fs.existsSync(dirPath)) {
|
|
1914
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
1915
|
+
created.push(toPosix(path.relative(projectPath, dirPath)) + "/");
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
scaffoldFileIfMissing(
|
|
1919
|
+
path.join(canonicalRoot, "maintenance.md"),
|
|
1920
|
+
scaffoldWikiPageFrontmatter(
|
|
1921
|
+
{
|
|
1922
|
+
id: "root_maintenance",
|
|
1923
|
+
title: "Maintenance",
|
|
1924
|
+
type: "maintenance",
|
|
1925
|
+
aliases: ["Maintenance"],
|
|
1926
|
+
tags: ["maintenance", "local-wiki"],
|
|
1927
|
+
related_pages: ["index.md", "maintenance/extraction-quality-review.md"],
|
|
1928
|
+
source_refs: ["sdtk_wiki_scaffold"],
|
|
1929
|
+
confidence: "medium",
|
|
1930
|
+
review_status: "needs_review",
|
|
1931
|
+
},
|
|
1932
|
+
"# Maintenance\n\n## Purpose\n\nTrack local wiki maintenance tasks, quality checks, and review follow-ups.\n\n## Source Quality Findings\n\n- Review generated lint, discover, compile, and enrichment reports under `.sdtk/wiki/reports`.\n\n## Unsupported Items\n\n- None recorded in the scaffold.\n\n## Open Items\n\n- Promote maintenance notes into dedicated pages under `wiki/maintenance/` when review produces durable decisions."
|
|
1933
|
+
),
|
|
1934
|
+
projectPath,
|
|
1935
|
+
canonicalRoot,
|
|
1936
|
+
created,
|
|
1937
|
+
skipped
|
|
1938
|
+
);
|
|
1939
|
+
scaffoldFileIfMissing(
|
|
1940
|
+
path.join(canonicalRoot, "inbox.md"),
|
|
1941
|
+
scaffoldWikiPageFrontmatter(
|
|
1942
|
+
{
|
|
1943
|
+
id: "root_inbox",
|
|
1944
|
+
title: "Inbox",
|
|
1945
|
+
type: "root",
|
|
1946
|
+
aliases: ["Inbox"],
|
|
1947
|
+
tags: ["inbox", "local-wiki"],
|
|
1948
|
+
related_pages: ["index.md"],
|
|
1949
|
+
source_refs: [],
|
|
1950
|
+
confidence: "medium",
|
|
1951
|
+
review_status: "needs_review",
|
|
1952
|
+
},
|
|
1953
|
+
"# Inbox\n\n## Purpose\n\nCapture uncategorized local notes before they are promoted into source, decision, query, timeline, or maintenance pages.\n\n## Inbox\n\n- Add local notes here before structured review."
|
|
1954
|
+
),
|
|
1955
|
+
projectPath,
|
|
1956
|
+
canonicalRoot,
|
|
1957
|
+
created,
|
|
1958
|
+
skipped
|
|
1959
|
+
);
|
|
1960
|
+
|
|
1961
|
+
const rootDocs = [
|
|
1962
|
+
["README.md", "# Local Wiki\n\nThis project uses `wiki/` as the human-facing SDTK-WIKI output. Internal state, reports, provenance, raw registry, graph, and logs stay under `.sdtk/wiki`."],
|
|
1963
|
+
["AGENTS.md", "# Agent Operating Notes\n\nCodex agents should read `wiki/index.md` first, then use `wiki/dashboards/` for navigation. Do not mutate `.sdtk/wiki` internal state by hand."],
|
|
1964
|
+
["CLAUDE.md", "# Claude Operating Notes\n\nClaude agents should use `wiki/` as the canonical local wiki and treat `.sdtk/wiki` as SDTK-WIKI internal state."],
|
|
1965
|
+
];
|
|
1966
|
+
for (const [name, content] of rootDocs) {
|
|
1967
|
+
const filePath = path.join(projectPath, name);
|
|
1968
|
+
if (fs.existsSync(filePath)) {
|
|
1969
|
+
skipped.push(name);
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
fs.writeFileSync(filePath, ensureTrailingNewline(content), "utf-8");
|
|
1973
|
+
created.push(name);
|
|
1974
|
+
}
|
|
1975
|
+
return { created, skipped };
|
|
1976
|
+
}
|
|
1977
|
+
|
|
851
1978
|
function runWikiCompileApply({ projectPath, planArg }) {
|
|
852
1979
|
const resolvedProjectPath = resolveProjectPath(projectPath || process.cwd());
|
|
853
1980
|
if (!fs.existsSync(resolvedProjectPath) || !fs.statSync(resolvedProjectPath).isDirectory()) {
|
|
@@ -866,9 +1993,12 @@ function runWikiCompileApply({ projectPath, planArg }) {
|
|
|
866
1993
|
const toCreate = [];
|
|
867
1994
|
|
|
868
1995
|
for (const target of targets) {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
1996
|
+
if (target.withinCanonicalWiki) {
|
|
1997
|
+
assertCanonicalWikiWritePath(target.path, resolvedProjectPath);
|
|
1998
|
+
} else if (target.withinLegacyPersonalBrain) {
|
|
1999
|
+
assertWikiWorkspaceWritePath(target.path, resolvedProjectPath);
|
|
2000
|
+
} else {
|
|
2001
|
+
throw new ValidationError(`Refusing to write outside project wiki output: ${target.normalized}. No project files were changed.`);
|
|
872
2002
|
}
|
|
873
2003
|
if (!fs.existsSync(target.path)) {
|
|
874
2004
|
toCreate.push(target);
|
|
@@ -887,6 +2017,7 @@ function runWikiCompileApply({ projectPath, planArg }) {
|
|
|
887
2017
|
}
|
|
888
2018
|
|
|
889
2019
|
const created = [];
|
|
2020
|
+
const scaffold = scaffoldCanonicalWikiStructure(resolvedProjectPath);
|
|
890
2021
|
for (const target of toCreate) {
|
|
891
2022
|
fs.mkdirSync(path.dirname(target.path), { recursive: true });
|
|
892
2023
|
fs.writeFileSync(target.path, target.content, "utf-8");
|
|
@@ -898,6 +2029,8 @@ function runWikiCompileApply({ projectPath, planArg }) {
|
|
|
898
2029
|
planPath,
|
|
899
2030
|
created,
|
|
900
2031
|
unchanged,
|
|
2032
|
+
scaffoldCreated: scaffold.created,
|
|
2033
|
+
scaffoldSkipped: scaffold.skipped,
|
|
901
2034
|
sourceQualityWarningCount: Number(sourceQualitySummary.warning_count || sourceQualitySummary.findings_count || 0),
|
|
902
2035
|
};
|
|
903
2036
|
} catch (error) {
|