@doquflow/server 1.6.0 → 2.0.0
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 +24 -72
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -328
- package/package.json +5 -7
- package/dist/category-dir.js +0 -12
- package/dist/extractor-rules.js +0 -166
- package/dist/extractor-stoplist.js +0 -89
- package/dist/extractor.js +0 -171
- package/dist/filesystem.js +0 -132
- package/dist/language-map.js +0 -58
- package/dist/tools/answer-synthesis.js +0 -189
- package/dist/tools/build-graph.js +0 -111
- package/dist/tools/generate-dependency-graph.js +0 -162
- package/dist/tools/get-schema-guidance.js +0 -213
- package/dist/tools/ingest-source.js +0 -272
- package/dist/tools/lint-wiki.js +0 -342
- package/dist/tools/list-modules.js +0 -50
- package/dist/tools/list-wiki.js +0 -198
- package/dist/tools/preview-generation.js +0 -228
- package/dist/tools/query-wiki.js +0 -67
- package/dist/tools/read-module.js +0 -53
- package/dist/tools/read-specs.js +0 -39
- package/dist/tools/save-answer-as-page.js +0 -91
- package/dist/tools/update-index.js +0 -157
- package/dist/tools/wiki-search.js +0 -157
- package/dist/tools/write-spec.js +0 -55
- package/dist/types.js +0 -2
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildGraph = buildGraph;
|
|
4
|
-
const list_wiki_1 = require("./list-wiki");
|
|
5
|
-
const SEP = "\u0000";
|
|
6
|
-
async function buildGraph(input) {
|
|
7
|
-
const generatedAt = new Date().toISOString();
|
|
8
|
-
try {
|
|
9
|
-
const wiki = await (0, list_wiki_1.listWiki)({ project_path: input.project_path });
|
|
10
|
-
if (wiki.error) {
|
|
11
|
-
return {
|
|
12
|
-
nodes: [],
|
|
13
|
-
edges: [],
|
|
14
|
-
meta: {
|
|
15
|
-
total_pages: 0, total_edges: 0, orphans: 0, isolated: 0,
|
|
16
|
-
dangling_refs: [], generated_at: generatedAt,
|
|
17
|
-
},
|
|
18
|
-
error: wiki.error,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
const pages = wiki.pages;
|
|
22
|
-
const nodeIds = new Set(pages.map((p) => p.id));
|
|
23
|
-
const edgeMap = new Map();
|
|
24
|
-
const dangling = new Set();
|
|
25
|
-
// Pass 1: outbound
|
|
26
|
-
for (const page of pages) {
|
|
27
|
-
const src = page.id;
|
|
28
|
-
for (const tgt of page.outbound_links ?? []) {
|
|
29
|
-
if (tgt === src)
|
|
30
|
-
continue;
|
|
31
|
-
if (!nodeIds.has(tgt)) {
|
|
32
|
-
dangling.add(tgt);
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
const key = `${src}${SEP}${tgt}`;
|
|
36
|
-
if (!edgeMap.has(key)) {
|
|
37
|
-
edgeMap.set(key, { source: src, target: tgt, kind: "outbound" });
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// Pass 2: inbound — `n.inbound_links` lists pages that point AT n.
|
|
42
|
-
for (const page of pages) {
|
|
43
|
-
const tgt = page.id;
|
|
44
|
-
for (const src of page.inbound_links ?? []) {
|
|
45
|
-
if (src === tgt)
|
|
46
|
-
continue;
|
|
47
|
-
if (!nodeIds.has(src)) {
|
|
48
|
-
dangling.add(src);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
const key = `${src}${SEP}${tgt}`;
|
|
52
|
-
const existing = edgeMap.get(key);
|
|
53
|
-
if (existing) {
|
|
54
|
-
if (existing.kind === "outbound")
|
|
55
|
-
existing.kind = "both";
|
|
56
|
-
// already 'inbound' or 'both' → keep
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
edgeMap.set(key, { source: src, target: tgt, kind: "inbound" });
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const edges = Array.from(edgeMap.values());
|
|
64
|
-
// Compute resolved in/out degree from final edge list.
|
|
65
|
-
const inDeg = new Map();
|
|
66
|
-
const outDeg = new Map();
|
|
67
|
-
for (const e of edges) {
|
|
68
|
-
outDeg.set(e.source, (outDeg.get(e.source) ?? 0) + 1);
|
|
69
|
-
inDeg.set(e.target, (inDeg.get(e.target) ?? 0) + 1);
|
|
70
|
-
}
|
|
71
|
-
const nodes = pages.map((p) => {
|
|
72
|
-
const out_degree = outDeg.get(p.id) ?? 0;
|
|
73
|
-
const in_degree = inDeg.get(p.id) ?? 0;
|
|
74
|
-
return {
|
|
75
|
-
id: p.id,
|
|
76
|
-
title: p.title,
|
|
77
|
-
category: p.category,
|
|
78
|
-
degree: in_degree + out_degree,
|
|
79
|
-
in_degree,
|
|
80
|
-
out_degree,
|
|
81
|
-
stale: p.stale,
|
|
82
|
-
updated_at: p.updated_at,
|
|
83
|
-
};
|
|
84
|
-
});
|
|
85
|
-
const orphans = nodes.filter((n) => n.in_degree === 0).length;
|
|
86
|
-
const isolated = nodes.filter((n) => n.degree === 0).length;
|
|
87
|
-
return {
|
|
88
|
-
nodes,
|
|
89
|
-
edges,
|
|
90
|
-
meta: {
|
|
91
|
-
total_pages: nodes.length,
|
|
92
|
-
total_edges: edges.length,
|
|
93
|
-
orphans,
|
|
94
|
-
isolated,
|
|
95
|
-
dangling_refs: Array.from(dangling).sort(),
|
|
96
|
-
generated_at: generatedAt,
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
catch (e) {
|
|
101
|
-
return {
|
|
102
|
-
nodes: [],
|
|
103
|
-
edges: [],
|
|
104
|
-
meta: {
|
|
105
|
-
total_pages: 0, total_edges: 0, orphans: 0, isolated: 0,
|
|
106
|
-
dangling_refs: [], generated_at: generatedAt,
|
|
107
|
-
},
|
|
108
|
-
error: e?.message ?? String(e),
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
}
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.generateDependencyGraph = generateDependencyGraph;
|
|
7
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
-
const list_modules_1 = require("./list-modules");
|
|
9
|
-
async function generateDependencyGraph(input) {
|
|
10
|
-
try {
|
|
11
|
-
const projectPath = node_path_1.default.resolve(input.project_path);
|
|
12
|
-
// Scan all modules
|
|
13
|
-
const listResult = await (0, list_modules_1.listModules)({
|
|
14
|
-
path: projectPath,
|
|
15
|
-
extensions: input.extensions,
|
|
16
|
-
});
|
|
17
|
-
const modules = listResult.modules ?? [];
|
|
18
|
-
if (modules.length === 0) {
|
|
19
|
-
return {
|
|
20
|
-
nodes: [],
|
|
21
|
-
edges: [],
|
|
22
|
-
shared_tables: {},
|
|
23
|
-
shared_endpoints: {},
|
|
24
|
-
most_connected: [],
|
|
25
|
-
summary: "No source files found in the project.",
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
// Build id from relative path (normalised for use as node id)
|
|
29
|
-
const relPath = (filePath) => node_path_1.default.relative(projectPath, filePath).replace(/\\/g, "/");
|
|
30
|
-
// Build nodes map
|
|
31
|
-
const nodeMap = new Map();
|
|
32
|
-
for (const mod of modules) {
|
|
33
|
-
const id = relPath(mod.path);
|
|
34
|
-
nodeMap.set(id, {
|
|
35
|
-
id,
|
|
36
|
-
label: node_path_1.default.basename(mod.path),
|
|
37
|
-
language: mod.language,
|
|
38
|
-
classes: mod.classes.length,
|
|
39
|
-
functions: mod.functions.length,
|
|
40
|
-
db_tables: mod.db_tables,
|
|
41
|
-
endpoints: mod.endpoints,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
// Build import edges: for each module's dependency list, look for matches in node IDs
|
|
45
|
-
const edges = [];
|
|
46
|
-
const edgeSet = new Set(); // avoid duplicates
|
|
47
|
-
for (const mod of modules) {
|
|
48
|
-
const fromId = relPath(mod.path);
|
|
49
|
-
for (const dep of mod.dependencies) {
|
|
50
|
-
// Try to resolve dep to a known node (relative path match or basename match)
|
|
51
|
-
for (const [nodeId] of nodeMap) {
|
|
52
|
-
// Match if the dependency string is contained in the node path
|
|
53
|
-
// (e.g., dep = "./user-service" matches "src/user-service.ts")
|
|
54
|
-
const depNorm = dep.replace(/^[./]+/, "").replace(/\\/g, "/").toLowerCase();
|
|
55
|
-
const nodeNorm = nodeId.replace(/\\/g, "/").toLowerCase();
|
|
56
|
-
if (depNorm.length > 2 &&
|
|
57
|
-
(nodeNorm.includes(depNorm) || nodeNorm.replace(/\.\w+$/, "").endsWith(depNorm))) {
|
|
58
|
-
const key = `${fromId}->${nodeId}`;
|
|
59
|
-
if (fromId !== nodeId && !edgeSet.has(key)) {
|
|
60
|
-
edgeSet.add(key);
|
|
61
|
-
edges.push({ from: fromId, to: nodeId, type: "imports" });
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
// Shared DB tables
|
|
68
|
-
const tableToModules = new Map();
|
|
69
|
-
for (const mod of modules) {
|
|
70
|
-
for (const table of mod.db_tables) {
|
|
71
|
-
const key = table.toLowerCase();
|
|
72
|
-
if (!tableToModules.has(key))
|
|
73
|
-
tableToModules.set(key, []);
|
|
74
|
-
tableToModules.get(key).push(relPath(mod.path));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
const shared_tables = {};
|
|
78
|
-
for (const [table, mods] of tableToModules) {
|
|
79
|
-
if (mods.length > 1) {
|
|
80
|
-
shared_tables[table] = mods;
|
|
81
|
-
// Add shared_table edges
|
|
82
|
-
for (let i = 0; i < mods.length; i++) {
|
|
83
|
-
for (let j = i + 1; j < mods.length; j++) {
|
|
84
|
-
const key = `${mods[i]}<>${mods[j]}:${table}`;
|
|
85
|
-
if (!edgeSet.has(key)) {
|
|
86
|
-
edgeSet.add(key);
|
|
87
|
-
edges.push({ from: mods[i], to: mods[j], type: "shared_table" });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
// Shared endpoints
|
|
94
|
-
const endpointToModules = new Map();
|
|
95
|
-
for (const mod of modules) {
|
|
96
|
-
for (const ep of mod.endpoints) {
|
|
97
|
-
if (!endpointToModules.has(ep))
|
|
98
|
-
endpointToModules.set(ep, []);
|
|
99
|
-
endpointToModules.get(ep).push(relPath(mod.path));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
const shared_endpoints = {};
|
|
103
|
-
for (const [ep, mods] of endpointToModules) {
|
|
104
|
-
if (mods.length > 1)
|
|
105
|
-
shared_endpoints[ep] = mods;
|
|
106
|
-
}
|
|
107
|
-
// Most connected nodes (by total edge count)
|
|
108
|
-
const connectionCount = new Map();
|
|
109
|
-
for (const edge of edges) {
|
|
110
|
-
connectionCount.set(edge.from, (connectionCount.get(edge.from) ?? 0) + 1);
|
|
111
|
-
connectionCount.set(edge.to, (connectionCount.get(edge.to) ?? 0) + 1);
|
|
112
|
-
}
|
|
113
|
-
const most_connected = Array.from(connectionCount.entries())
|
|
114
|
-
.map(([id, count]) => ({
|
|
115
|
-
id,
|
|
116
|
-
label: nodeMap.get(id)?.label ?? id,
|
|
117
|
-
connection_count: count,
|
|
118
|
-
}))
|
|
119
|
-
.sort((a, b) => b.connection_count - a.connection_count)
|
|
120
|
-
.slice(0, 10);
|
|
121
|
-
// Apply focus filter: if focus is set, only include nodes/edges reachable from it
|
|
122
|
-
let nodes = Array.from(nodeMap.values());
|
|
123
|
-
let filteredEdges = edges;
|
|
124
|
-
if (input.focus) {
|
|
125
|
-
const focusNorm = input.focus.toLowerCase();
|
|
126
|
-
const focusId = nodes.find((n) => n.id.toLowerCase().includes(focusNorm))?.id;
|
|
127
|
-
if (focusId) {
|
|
128
|
-
const reachable = new Set([focusId]);
|
|
129
|
-
// BFS: include direct neighbours
|
|
130
|
-
for (const e of edges) {
|
|
131
|
-
if (e.from === focusId)
|
|
132
|
-
reachable.add(e.to);
|
|
133
|
-
if (e.to === focusId)
|
|
134
|
-
reachable.add(e.from);
|
|
135
|
-
}
|
|
136
|
-
nodes = nodes.filter((n) => reachable.has(n.id));
|
|
137
|
-
filteredEdges = edges.filter((e) => reachable.has(e.from) && reachable.has(e.to));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const sharedTableCount = Object.keys(shared_tables).length;
|
|
141
|
-
const summary = [
|
|
142
|
-
`${nodes.length} modules, ${filteredEdges.length} dependencies`,
|
|
143
|
-
sharedTableCount > 0 ? `${sharedTableCount} shared DB table(s)` : null,
|
|
144
|
-
most_connected.length > 0
|
|
145
|
-
? `Most connected: ${most_connected[0].label} (${most_connected[0].connection_count} links)`
|
|
146
|
-
: null,
|
|
147
|
-
]
|
|
148
|
-
.filter(Boolean)
|
|
149
|
-
.join(" · ");
|
|
150
|
-
return {
|
|
151
|
-
nodes,
|
|
152
|
-
edges: filteredEdges,
|
|
153
|
-
shared_tables,
|
|
154
|
-
shared_endpoints,
|
|
155
|
-
most_connected,
|
|
156
|
-
summary,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
catch (e) {
|
|
160
|
-
return { error: e?.message ?? String(e) };
|
|
161
|
-
}
|
|
162
|
-
}
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getSchemataGuidance = getSchemataGuidance;
|
|
7
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
-
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
-
/**
|
|
10
|
-
* get_schema_guidance
|
|
11
|
-
*
|
|
12
|
-
* Analyzes what documents should exist based on the project schema and current wiki state.
|
|
13
|
-
* Helps users understand what to create next.
|
|
14
|
-
*
|
|
15
|
-
* Input:
|
|
16
|
-
* - project_path: string
|
|
17
|
-
* - domain?: string (optional; auto-detected from schema if not provided)
|
|
18
|
-
*
|
|
19
|
-
* Output:
|
|
20
|
-
* - domain: detected domain
|
|
21
|
-
* - recommended_pages: list of pages that should exist with reasons
|
|
22
|
-
* - existing_pages: what's already in the wiki
|
|
23
|
-
* - missing_pages: high-priority missing documents
|
|
24
|
-
* - recommendations: actionable next steps
|
|
25
|
-
*/
|
|
26
|
-
async function getSchemataGuidance(input) {
|
|
27
|
-
const projectPath = node_path_1.default.resolve(input.project_path);
|
|
28
|
-
const docuDir = node_path_1.default.join(projectPath, ".docuflow");
|
|
29
|
-
const wikiDir = node_path_1.default.join(docuDir, "wiki");
|
|
30
|
-
const schemaPath = node_path_1.default.join(docuDir, "schema.md");
|
|
31
|
-
// Read schema to understand domain
|
|
32
|
-
let domain = input.domain || "General";
|
|
33
|
-
try {
|
|
34
|
-
const schemaContent = await promises_1.default.readFile(schemaPath, "utf-8");
|
|
35
|
-
if (schemaContent.includes("Research"))
|
|
36
|
-
domain = "Research";
|
|
37
|
-
else if (schemaContent.includes("Business"))
|
|
38
|
-
domain = "Business";
|
|
39
|
-
else if (schemaContent.includes("Architecture"))
|
|
40
|
-
domain = "Code/Architecture";
|
|
41
|
-
else if (schemaContent.includes("Personal"))
|
|
42
|
-
domain = "Personal";
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Use default if schema not readable
|
|
46
|
-
}
|
|
47
|
-
// Scan wiki for existing pages
|
|
48
|
-
const indexPath = node_path_1.default.join(docuDir, "index.md");
|
|
49
|
-
const indexContent = await promises_1.default
|
|
50
|
-
.readFile(indexPath, "utf-8")
|
|
51
|
-
.catch(() => "");
|
|
52
|
-
// Count pages by category
|
|
53
|
-
const existingPages = [];
|
|
54
|
-
const categories = ["entities", "concepts", "syntheses", "timelines"];
|
|
55
|
-
for (const cat of categories) {
|
|
56
|
-
const catDir = node_path_1.default.join(wikiDir, cat);
|
|
57
|
-
try {
|
|
58
|
-
const files = await promises_1.default.readdir(catDir);
|
|
59
|
-
for (const file of files.filter((f) => f.endsWith(".md"))) {
|
|
60
|
-
existingPages.push({
|
|
61
|
-
name: file.replace(".md", ""),
|
|
62
|
-
category: cat,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
// Directory may not exist
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
// Define recommended pages by domain
|
|
71
|
-
const recommendedByDomain = {
|
|
72
|
-
"Code/Architecture": [
|
|
73
|
-
{
|
|
74
|
-
name: "architecture_overview",
|
|
75
|
-
category: "syntheses",
|
|
76
|
-
suggested_title: "System Architecture Overview",
|
|
77
|
-
reason: "High-level view of how all components fit together",
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: "core_patterns",
|
|
81
|
-
category: "concepts",
|
|
82
|
-
suggested_title: "Core Architectural Patterns",
|
|
83
|
-
reason: "Design patterns used throughout the codebase",
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: "module_dependencies",
|
|
87
|
-
category: "concepts",
|
|
88
|
-
suggested_title: "Module Dependencies",
|
|
89
|
-
reason: "How modules depend on and integrate with each other",
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: "data_flow",
|
|
93
|
-
category: "syntheses",
|
|
94
|
-
suggested_title: "Data Flow Diagram",
|
|
95
|
-
reason: "How data moves through the system",
|
|
96
|
-
},
|
|
97
|
-
],
|
|
98
|
-
Research: [
|
|
99
|
-
{
|
|
100
|
-
name: "research_overview",
|
|
101
|
-
category: "syntheses",
|
|
102
|
-
suggested_title: "Research Domain Overview",
|
|
103
|
-
reason: "Big picture of the research area",
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: "key_findings",
|
|
107
|
-
category: "syntheses",
|
|
108
|
-
suggested_title: "Key Findings & Synthesis",
|
|
109
|
-
reason: "Major discoveries and insights",
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
name: "contradictions",
|
|
113
|
-
category: "syntheses",
|
|
114
|
-
suggested_title: "Areas of Contradiction",
|
|
115
|
-
reason: "Where researchers disagree",
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: "open_questions",
|
|
119
|
-
category: "concepts",
|
|
120
|
-
suggested_title: "Open Research Questions",
|
|
121
|
-
reason: "What's still unknown in this domain",
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
Business: [
|
|
125
|
-
{
|
|
126
|
-
name: "market_overview",
|
|
127
|
-
category: "syntheses",
|
|
128
|
-
suggested_title: "Market Overview",
|
|
129
|
-
reason: "Big picture of the market and competitive landscape",
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
name: "competitive_analysis",
|
|
133
|
-
category: "syntheses",
|
|
134
|
-
suggested_title: "Competitive Analysis",
|
|
135
|
-
reason: "Comparison of key competitors",
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
name: "opportunities",
|
|
139
|
-
category: "syntheses",
|
|
140
|
-
suggested_title: "Market Opportunities",
|
|
141
|
-
reason: "Gaps and growth areas",
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
name: "risks",
|
|
145
|
-
category: "concepts",
|
|
146
|
-
suggested_title: "Market Risks",
|
|
147
|
-
reason: "Threats and challenges",
|
|
148
|
-
},
|
|
149
|
-
],
|
|
150
|
-
Personal: [
|
|
151
|
-
{
|
|
152
|
-
name: "learning_goals",
|
|
153
|
-
category: "concepts",
|
|
154
|
-
suggested_title: "Learning Goals",
|
|
155
|
-
reason: "What you want to learn in this domain",
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
name: "key_insights",
|
|
159
|
-
category: "syntheses",
|
|
160
|
-
suggested_title: "Key Personal Insights",
|
|
161
|
-
reason: "Major learnings and takeaways",
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
name: "action_items",
|
|
165
|
-
category: "concepts",
|
|
166
|
-
suggested_title: "Action Items & Next Steps",
|
|
167
|
-
reason: "What to do with this knowledge",
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
name: "resources",
|
|
171
|
-
category: "concepts",
|
|
172
|
-
suggested_title: "Key Resources",
|
|
173
|
-
reason: "Important links, books, people",
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
};
|
|
177
|
-
const recommended = recommendedByDomain[domain] || recommendedByDomain.General;
|
|
178
|
-
// Find missing pages
|
|
179
|
-
const missingPages = [];
|
|
180
|
-
for (const rec of recommended) {
|
|
181
|
-
const found = existingPages.find((p) => p.name === rec.name);
|
|
182
|
-
if (!found) {
|
|
183
|
-
missingPages.push(rec.suggested_title);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
// Generate recommendations
|
|
187
|
-
const recommendations = [];
|
|
188
|
-
if (existingPages.length === 0) {
|
|
189
|
-
recommendations.push("🌱 Start by ingesting your first source");
|
|
190
|
-
recommendations.push("📝 Create an overview/synthesis page");
|
|
191
|
-
}
|
|
192
|
-
else if (existingPages.length < 10) {
|
|
193
|
-
recommendations.push("📚 Add more sources to deepen understanding");
|
|
194
|
-
if (missingPages.length > 0) {
|
|
195
|
-
recommendations.push(`⚠️ Consider creating: ${missingPages[0]}`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
else if (existingPages.length > 50) {
|
|
199
|
-
recommendations.push("✅ You have a solid wiki foundation");
|
|
200
|
-
recommendations.push("🔍 Run lint_wiki to health-check for gaps");
|
|
201
|
-
recommendations.push("🔗 Verify cross-references are accurate");
|
|
202
|
-
}
|
|
203
|
-
if (missingPages.length > 2) {
|
|
204
|
-
recommendations.push(`💡 Biggest gap: ${missingPages[0]} could improve wiki significantly`);
|
|
205
|
-
}
|
|
206
|
-
return {
|
|
207
|
-
domain,
|
|
208
|
-
recommended_pages: recommended,
|
|
209
|
-
existing_pages: existingPages,
|
|
210
|
-
missing_pages: missingPages,
|
|
211
|
-
recommendations,
|
|
212
|
-
};
|
|
213
|
-
}
|