agent-method 1.5.12
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 +343 -0
- package/bin/wwa.js +115 -0
- package/docs/internal/cli-commands.yaml +259 -0
- package/docs/internal/doc-tokens.yaml +1103 -0
- package/docs/internal/feature-registry.yaml +1643 -0
- package/lib/boundaries.js +247 -0
- package/lib/cli/add.js +170 -0
- package/lib/cli/casestudy.js +1000 -0
- package/lib/cli/check.js +323 -0
- package/lib/cli/close.js +838 -0
- package/lib/cli/completion.js +735 -0
- package/lib/cli/deps.js +234 -0
- package/lib/cli/digest.js +73 -0
- package/lib/cli/doc-review.js +486 -0
- package/lib/cli/docs.js +315 -0
- package/lib/cli/helpers.js +198 -0
- package/lib/cli/implement.js +169 -0
- package/lib/cli/init.js +280 -0
- package/lib/cli/pipeline.js +206 -0
- package/lib/cli/plan.js +140 -0
- package/lib/cli/record.js +98 -0
- package/lib/cli/refine.js +202 -0
- package/lib/cli/report-helpers.js +113 -0
- package/lib/cli/review.js +76 -0
- package/lib/cli/routable.js +109 -0
- package/lib/cli/route.js +101 -0
- package/lib/cli/scan.js +133 -0
- package/lib/cli/serve.js +23 -0
- package/lib/cli/status.js +65 -0
- package/lib/cli/update-docs.js +574 -0
- package/lib/cli/upgrade.js +222 -0
- package/lib/cli/watch.js +32 -0
- package/lib/dependencies.js +196 -0
- package/lib/init.js +692 -0
- package/lib/mcp-server.js +612 -0
- package/lib/pipeline.js +907 -0
- package/lib/registry.js +132 -0
- package/lib/watcher.js +165 -0
- package/package.json +54 -0
- package/templates/README.md +363 -0
- package/templates/entry-points/.cursorrules +90 -0
- package/templates/entry-points/AGENT.md +90 -0
- package/templates/entry-points/CLAUDE.md +88 -0
- package/templates/extensions/MANIFEST.md +110 -0
- package/templates/extensions/analytical-system.md +96 -0
- package/templates/extensions/code-project.md +77 -0
- package/templates/extensions/data-exploration.md +117 -0
- package/templates/full/.context/BASE.md +101 -0
- package/templates/full/.context/COMPOSITION.md +47 -0
- package/templates/full/.context/INDEX.yaml +56 -0
- package/templates/full/.context/METHODOLOGY.md +246 -0
- package/templates/full/.context/PROTOCOL.yaml +169 -0
- package/templates/full/.context/REGISTRY.md +75 -0
- package/templates/full/.cursorrules +90 -0
- package/templates/full/AGENT.md +90 -0
- package/templates/full/CLAUDE.md +90 -0
- package/templates/full/Management/DIGEST.md +23 -0
- package/templates/full/Management/STATUS.md +46 -0
- package/templates/full/PLAN.md +67 -0
- package/templates/full/PROJECT-PROFILE.md +61 -0
- package/templates/full/PROJECT.md +80 -0
- package/templates/full/REQUIREMENTS.md +30 -0
- package/templates/full/ROADMAP.md +39 -0
- package/templates/full/Reviews/INDEX.md +41 -0
- package/templates/full/Reviews/backlog.md +52 -0
- package/templates/full/Reviews/plan.md +43 -0
- package/templates/full/Reviews/project.md +41 -0
- package/templates/full/Reviews/requirements.md +42 -0
- package/templates/full/Reviews/roadmap.md +41 -0
- package/templates/full/Reviews/state.md +56 -0
- package/templates/full/SESSION-LOG.md +102 -0
- package/templates/full/STATE.md +42 -0
- package/templates/full/SUMMARY.md +27 -0
- package/templates/full/agentWorkflows/INDEX.md +42 -0
- package/templates/full/agentWorkflows/observations.md +65 -0
- package/templates/full/agentWorkflows/patterns.md +68 -0
- package/templates/full/agentWorkflows/sessions.md +92 -0
- package/templates/full/intro/README.md +39 -0
- package/templates/full/registry/feature-registry.yaml +25 -0
- package/templates/full/registry/features/catalog.yaml +743 -0
- package/templates/full/registry/features/protocol.yaml +121 -0
- package/templates/full/registry/features/routing.yaml +358 -0
- package/templates/full/registry/features/workflows.yaml +404 -0
- package/templates/full/todos/backlog.md +19 -0
- package/templates/starter/.context/BASE.md +66 -0
- package/templates/starter/.context/INDEX.yaml +51 -0
- package/templates/starter/.context/METHODOLOGY.md +228 -0
- package/templates/starter/.context/PROTOCOL.yaml +165 -0
- package/templates/starter/.cursorrules +90 -0
- package/templates/starter/AGENT.md +90 -0
- package/templates/starter/CLAUDE.md +90 -0
- package/templates/starter/Management/DIGEST.md +23 -0
- package/templates/starter/Management/STATUS.md +46 -0
- package/templates/starter/PLAN.md +67 -0
- package/templates/starter/PROJECT-PROFILE.md +44 -0
- package/templates/starter/PROJECT.md +80 -0
- package/templates/starter/ROADMAP.md +39 -0
- package/templates/starter/Reviews/INDEX.md +75 -0
- package/templates/starter/SESSION-LOG.md +102 -0
- package/templates/starter/STATE.md +42 -0
- package/templates/starter/SUMMARY.md +27 -0
- package/templates/starter/agentWorkflows/INDEX.md +61 -0
- package/templates/starter/intro/README.md +37 -0
- package/templates/starter/registry/feature-registry.yaml +25 -0
- package/templates/starter/registry/features/catalog.yaml +743 -0
- package/templates/starter/registry/features/protocol.yaml +121 -0
- package/templates/starter/registry/features/routing.yaml +358 -0
- package/templates/starter/registry/features/workflows.yaml +404 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/** wwa doc-review — generate docs/internal/ registry from doc-tokens.yaml and DOCS-MAP.md. */
|
|
2
|
+
|
|
3
|
+
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
4
|
+
import { resolve, join } from "node:path";
|
|
5
|
+
import { safeWriteFile } from "./helpers.js";
|
|
6
|
+
import { resolveTokensPath, resolveDocsMapPath, resolveOutputDir } from "../boundaries.js";
|
|
7
|
+
|
|
8
|
+
export function register(program) {
|
|
9
|
+
program
|
|
10
|
+
.command("doc-review [directory]")
|
|
11
|
+
.description(
|
|
12
|
+
"Generate docs/internal/ document registry (doc-registry.yaml + doc-registry.md)"
|
|
13
|
+
)
|
|
14
|
+
.option("--yaml-only", "Only generate doc-registry.yaml")
|
|
15
|
+
.option("--md-only", "Only generate doc-registry.md")
|
|
16
|
+
.option("--json", "Output registry data as JSON (no file write)")
|
|
17
|
+
.action(async (directory, opts) => {
|
|
18
|
+
directory = directory || ".";
|
|
19
|
+
const d = resolve(directory);
|
|
20
|
+
|
|
21
|
+
const data = await gatherRegistryData(d);
|
|
22
|
+
|
|
23
|
+
if (data.errors.length > 0) {
|
|
24
|
+
console.error("Cannot generate document registry:");
|
|
25
|
+
for (const e of data.errors) console.error(` - ${e}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (opts.json) {
|
|
30
|
+
console.log(JSON.stringify(data, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const internalDir = await resolveOutputDir(d);
|
|
35
|
+
if (!existsSync(internalDir)) {
|
|
36
|
+
mkdirSync(internalDir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const results = [];
|
|
40
|
+
|
|
41
|
+
if (!opts.mdOnly) {
|
|
42
|
+
const yamlPath = join(internalDir, "doc-registry.yaml");
|
|
43
|
+
const wrote = await generateRegistryYaml(yamlPath, data);
|
|
44
|
+
if (wrote) results.push(yamlPath);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!opts.yamlOnly) {
|
|
48
|
+
const mdPath = join(internalDir, "doc-registry.md");
|
|
49
|
+
generateRegistryMd(mdPath, data);
|
|
50
|
+
results.push(mdPath);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (results.length > 0) {
|
|
54
|
+
console.log("\n Document registry generated:\n");
|
|
55
|
+
for (const r of results) {
|
|
56
|
+
console.log(` - ${r}`);
|
|
57
|
+
}
|
|
58
|
+
console.log(
|
|
59
|
+
`\n Nodes: ${data.dependencyGraph.nodes.length} Edges: ${data.dependencyGraph.edges.length} Orphans: ${data.health.orphanNodes}\n`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Data gathering
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
async function gatherRegistryData(dir) {
|
|
70
|
+
const data = {
|
|
71
|
+
generated: new Date().toISOString().slice(0, 10),
|
|
72
|
+
generator: "wwa doc-review",
|
|
73
|
+
dependencyGraph: { nodes: [], edges: [], orphans: [], readOrder: [] },
|
|
74
|
+
terminology: { modules: [], apis: [], entities: [], conventions: [] },
|
|
75
|
+
health: {
|
|
76
|
+
totalDocs: 0,
|
|
77
|
+
docsOnDisk: 0,
|
|
78
|
+
docsMissing: 0,
|
|
79
|
+
orphanNodes: 0,
|
|
80
|
+
staleDocs: 0,
|
|
81
|
+
dependencyCoverage: "0%",
|
|
82
|
+
},
|
|
83
|
+
docsInventory: [],
|
|
84
|
+
componentMappings: [],
|
|
85
|
+
errors: [],
|
|
86
|
+
warnings: [],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// --- Load doc_graph from doc-tokens.yaml ---
|
|
90
|
+
const tokensPath = await resolveTokensPath(dir);
|
|
91
|
+
if (!existsSync(tokensPath)) {
|
|
92
|
+
data.errors.push(
|
|
93
|
+
"No doc-tokens.yaml found — run wwa init or complete brownfield onboarding first"
|
|
94
|
+
);
|
|
95
|
+
return data;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let parsed;
|
|
99
|
+
try {
|
|
100
|
+
const yaml = (await import("js-yaml")).default;
|
|
101
|
+
const raw = readFileSync(tokensPath, "utf-8");
|
|
102
|
+
parsed = yaml.load(raw);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
data.errors.push(`Failed to parse doc-tokens.yaml: ${e.message}`);
|
|
105
|
+
return data;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Dependency graph
|
|
109
|
+
const docGraph = parsed?.doc_graph;
|
|
110
|
+
if (docGraph) {
|
|
111
|
+
const nodes = docGraph.nodes || [];
|
|
112
|
+
const edges = docGraph.edges || [];
|
|
113
|
+
data.dependencyGraph.nodes = nodes;
|
|
114
|
+
data.dependencyGraph.edges = edges;
|
|
115
|
+
|
|
116
|
+
// Compute orphans
|
|
117
|
+
const connected = new Set();
|
|
118
|
+
for (const e of edges) {
|
|
119
|
+
connected.add(e.from);
|
|
120
|
+
connected.add(e.to);
|
|
121
|
+
}
|
|
122
|
+
data.dependencyGraph.orphans = nodes
|
|
123
|
+
.filter((n) => !connected.has(n.path))
|
|
124
|
+
.map((n) => n.path);
|
|
125
|
+
|
|
126
|
+
// Topological read order (Kahn's algorithm on reads_from edges)
|
|
127
|
+
data.dependencyGraph.readOrder = computeReadOrder(nodes, edges);
|
|
128
|
+
} else {
|
|
129
|
+
data.warnings.push("No doc_graph section in doc-tokens.yaml — graph will be empty");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Terminology
|
|
133
|
+
const projectNames = parsed?.project_names;
|
|
134
|
+
if (projectNames) {
|
|
135
|
+
data.terminology.modules = projectNames.modules || [];
|
|
136
|
+
data.terminology.apis = projectNames.apis || [];
|
|
137
|
+
data.terminology.entities = projectNames.entities || [];
|
|
138
|
+
data.terminology.conventions = projectNames.conventions || [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- Load DOCS-MAP.md ---
|
|
142
|
+
const docsMapPath = await resolveDocsMapPath(dir);
|
|
143
|
+
if (existsSync(docsMapPath)) {
|
|
144
|
+
const content = readFileSync(docsMapPath, "utf-8");
|
|
145
|
+
const mapData = parseDocsMap(content);
|
|
146
|
+
data.docsInventory = mapData.inventory;
|
|
147
|
+
data.componentMappings = mapData.mappings;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// --- Health: check nodes against disk ---
|
|
151
|
+
const nodes = data.dependencyGraph.nodes;
|
|
152
|
+
let onDisk = 0;
|
|
153
|
+
let missing = 0;
|
|
154
|
+
const lastScan = docGraph?.last_scan || null;
|
|
155
|
+
|
|
156
|
+
for (const n of nodes) {
|
|
157
|
+
const fullPath = join(dir, n.path);
|
|
158
|
+
if (existsSync(fullPath)) {
|
|
159
|
+
onDisk++;
|
|
160
|
+
} else {
|
|
161
|
+
missing++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const totalEdgeNodes = new Set();
|
|
166
|
+
for (const e of data.dependencyGraph.edges) {
|
|
167
|
+
totalEdgeNodes.add(e.from);
|
|
168
|
+
totalEdgeNodes.add(e.to);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
data.health = {
|
|
172
|
+
totalDocs: nodes.length,
|
|
173
|
+
docsOnDisk: onDisk,
|
|
174
|
+
docsMissing: missing,
|
|
175
|
+
orphanNodes: data.dependencyGraph.orphans.length,
|
|
176
|
+
staleDocs: 0, // Could enhance with mtime comparison
|
|
177
|
+
dependencyCoverage:
|
|
178
|
+
nodes.length > 0
|
|
179
|
+
? Math.round((totalEdgeNodes.size / nodes.length) * 100) + "%"
|
|
180
|
+
: "0%",
|
|
181
|
+
lastScan,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return data;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function computeReadOrder(nodes, edges) {
|
|
188
|
+
const readsFrom = edges.filter((e) => e.type === "reads_from");
|
|
189
|
+
if (readsFrom.length === 0) return nodes.map((n) => n.path).sort();
|
|
190
|
+
|
|
191
|
+
const pathSet = new Set(nodes.map((n) => n.path));
|
|
192
|
+
const inDegree = {};
|
|
193
|
+
const adj = {};
|
|
194
|
+
for (const p of pathSet) {
|
|
195
|
+
inDegree[p] = 0;
|
|
196
|
+
adj[p] = [];
|
|
197
|
+
}
|
|
198
|
+
for (const e of readsFrom) {
|
|
199
|
+
if (pathSet.has(e.from) && pathSet.has(e.to)) {
|
|
200
|
+
adj[e.to].push(e.from); // to must be read before from
|
|
201
|
+
inDegree[e.from] = (inDegree[e.from] || 0) + 1;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const queue = [];
|
|
206
|
+
for (const p of pathSet) {
|
|
207
|
+
if (inDegree[p] === 0) queue.push(p);
|
|
208
|
+
}
|
|
209
|
+
queue.sort();
|
|
210
|
+
|
|
211
|
+
const result = [];
|
|
212
|
+
while (queue.length > 0) {
|
|
213
|
+
const node = queue.shift();
|
|
214
|
+
result.push(node);
|
|
215
|
+
for (const neighbor of (adj[node] || []).sort()) {
|
|
216
|
+
inDegree[neighbor]--;
|
|
217
|
+
if (inDegree[neighbor] === 0) queue.push(neighbor);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Add any remaining nodes (cycles) at the end
|
|
222
|
+
for (const p of pathSet) {
|
|
223
|
+
if (!result.includes(p)) result.push(p);
|
|
224
|
+
}
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function parseDocsMap(content) {
|
|
229
|
+
const inventory = [];
|
|
230
|
+
const mappings = [];
|
|
231
|
+
|
|
232
|
+
const parseTable = (sectionName) => {
|
|
233
|
+
const re = new RegExp(
|
|
234
|
+
`## ${sectionName}[ \\t]*\\n\\n?\\|[^\\n]+\\n\\|[-| :]+\\n((?:\\|[^\\n]+\\n)*)`,
|
|
235
|
+
);
|
|
236
|
+
const m = content.match(re);
|
|
237
|
+
if (!m) return [];
|
|
238
|
+
return m[1]
|
|
239
|
+
.trim()
|
|
240
|
+
.split("\n")
|
|
241
|
+
.map((row) =>
|
|
242
|
+
row
|
|
243
|
+
.split("|")
|
|
244
|
+
.map((c) => c.trim())
|
|
245
|
+
.filter((c) => c && !c.startsWith("<!--"))
|
|
246
|
+
)
|
|
247
|
+
.filter((cols) => cols.length >= 2);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
for (const cols of parseTable("Docs inventory")) {
|
|
251
|
+
inventory.push({
|
|
252
|
+
path: cols[0],
|
|
253
|
+
purpose: cols[1],
|
|
254
|
+
sources: cols[2] || "",
|
|
255
|
+
status: cols[3] || "",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
for (const cols of parseTable("Component-to-docs mapping")) {
|
|
259
|
+
mappings.push({
|
|
260
|
+
component: cols[0],
|
|
261
|
+
documentedIn: cols[1],
|
|
262
|
+
trigger: cols[2] || "",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return { inventory, mappings };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
// YAML output
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
async function generateRegistryYaml(yamlPath, data) {
|
|
274
|
+
try {
|
|
275
|
+
const yaml = (await import("js-yaml")).default;
|
|
276
|
+
|
|
277
|
+
const structured = {
|
|
278
|
+
generated: data.generated,
|
|
279
|
+
generator: data.generator,
|
|
280
|
+
dependency_graph: {
|
|
281
|
+
nodes: data.dependencyGraph.nodes,
|
|
282
|
+
edges: data.dependencyGraph.edges,
|
|
283
|
+
orphans: data.dependencyGraph.orphans,
|
|
284
|
+
read_order: data.dependencyGraph.readOrder,
|
|
285
|
+
},
|
|
286
|
+
terminology: data.terminology,
|
|
287
|
+
health: data.health,
|
|
288
|
+
docs_inventory: data.docsInventory,
|
|
289
|
+
component_mappings: data.componentMappings,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const yamlOutput = yaml.dump(structured, {
|
|
293
|
+
lineWidth: 120,
|
|
294
|
+
noRefs: true,
|
|
295
|
+
quotingType: '"',
|
|
296
|
+
});
|
|
297
|
+
const header = [
|
|
298
|
+
"# Document Registry — dependency graph, terminology, and health metrics",
|
|
299
|
+
`# Generated: ${data.generated} by wwa doc-review`,
|
|
300
|
+
"# Source: doc-tokens.yaml (doc_graph, project_names) + DOCS-MAP.md (resolved via boundaries)",
|
|
301
|
+
"",
|
|
302
|
+
].join("\n");
|
|
303
|
+
|
|
304
|
+
safeWriteFile(yamlPath, header + yamlOutput, "utf-8");
|
|
305
|
+
return true;
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
// Markdown output
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
|
|
315
|
+
function generateRegistryMd(mdPath, data) {
|
|
316
|
+
const lines = [];
|
|
317
|
+
|
|
318
|
+
lines.push(
|
|
319
|
+
"# Document Registry",
|
|
320
|
+
"",
|
|
321
|
+
`<!-- Generated: ${data.generated} by wwa doc-review -->`,
|
|
322
|
+
""
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
// Dependency tree
|
|
326
|
+
lines.push("## Dependency tree", "");
|
|
327
|
+
if (data.dependencyGraph.readOrder.length > 0) {
|
|
328
|
+
lines.push("Read order (foundational files first):", "");
|
|
329
|
+
let i = 1;
|
|
330
|
+
for (const p of data.dependencyGraph.readOrder) {
|
|
331
|
+
const node = data.dependencyGraph.nodes.find((n) => n.path === p);
|
|
332
|
+
const role = node ? ` — ${node.role}` : "";
|
|
333
|
+
lines.push(`${i}. \`${p}\`${role}`);
|
|
334
|
+
i++;
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
lines.push("No dependency data available.");
|
|
338
|
+
}
|
|
339
|
+
lines.push("");
|
|
340
|
+
|
|
341
|
+
// Terminology glossary
|
|
342
|
+
lines.push("## Terminology glossary", "");
|
|
343
|
+
|
|
344
|
+
const term = data.terminology;
|
|
345
|
+
const hasTerminology =
|
|
346
|
+
term.modules.length > 0 ||
|
|
347
|
+
term.apis.length > 0 ||
|
|
348
|
+
term.entities.length > 0 ||
|
|
349
|
+
term.conventions.length > 0;
|
|
350
|
+
|
|
351
|
+
if (hasTerminology) {
|
|
352
|
+
if (term.modules.length > 0) {
|
|
353
|
+
lines.push(
|
|
354
|
+
"### Modules",
|
|
355
|
+
"",
|
|
356
|
+
"| Canonical | Aliases | Location |",
|
|
357
|
+
"|-----------|---------|----------|"
|
|
358
|
+
);
|
|
359
|
+
for (const m of term.modules) {
|
|
360
|
+
const aliases = Array.isArray(m.aliases) ? m.aliases.join(", ") : "";
|
|
361
|
+
lines.push(
|
|
362
|
+
`| ${m.canonical || m} | ${aliases} | ${m.location || ""} |`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
lines.push("");
|
|
366
|
+
}
|
|
367
|
+
if (term.apis.length > 0) {
|
|
368
|
+
lines.push(
|
|
369
|
+
"### APIs",
|
|
370
|
+
"",
|
|
371
|
+
"| Endpoint | Methods | Documented in |",
|
|
372
|
+
"|----------|---------|---------------|"
|
|
373
|
+
);
|
|
374
|
+
for (const a of term.apis) {
|
|
375
|
+
const methods = Array.isArray(a.methods) ? a.methods.join(", ") : "";
|
|
376
|
+
lines.push(
|
|
377
|
+
`| ${a.canonical || a} | ${methods} | ${a.documented_in || ""} |`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
lines.push("");
|
|
381
|
+
}
|
|
382
|
+
if (term.entities.length > 0) {
|
|
383
|
+
lines.push(
|
|
384
|
+
"### Entities",
|
|
385
|
+
"",
|
|
386
|
+
"| Entity | Schema location |",
|
|
387
|
+
"|--------|-----------------|"
|
|
388
|
+
);
|
|
389
|
+
for (const e of term.entities) {
|
|
390
|
+
lines.push(
|
|
391
|
+
`| ${e.canonical || e} | ${e.schema_location || ""} |`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
lines.push("");
|
|
395
|
+
}
|
|
396
|
+
if (term.conventions.length > 0) {
|
|
397
|
+
lines.push("### Conventions", "");
|
|
398
|
+
for (const c of term.conventions) {
|
|
399
|
+
if (typeof c === "string") {
|
|
400
|
+
lines.push(`- ${c}`);
|
|
401
|
+
} else {
|
|
402
|
+
lines.push(`- **${c.name}**: ${c.pattern || ""}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
lines.push("");
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
lines.push("No project terminology captured yet.", "");
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Health dashboard
|
|
412
|
+
const h = data.health;
|
|
413
|
+
lines.push(
|
|
414
|
+
"## Health dashboard",
|
|
415
|
+
"",
|
|
416
|
+
"| Metric | Value |",
|
|
417
|
+
"|--------|-------|",
|
|
418
|
+
`| Total nodes | ${h.totalDocs} |`,
|
|
419
|
+
`| Nodes on disk | ${h.docsOnDisk} |`,
|
|
420
|
+
`| Nodes missing | ${h.docsMissing} |`,
|
|
421
|
+
`| Orphan nodes | ${h.orphanNodes} |`,
|
|
422
|
+
`| Dependency coverage | ${h.dependencyCoverage} |`,
|
|
423
|
+
`| Last scan | ${h.lastScan || "unknown"} |`,
|
|
424
|
+
""
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
if (data.dependencyGraph.orphans.length > 0) {
|
|
428
|
+
lines.push("### Orphan nodes (no edges)", "");
|
|
429
|
+
for (const o of data.dependencyGraph.orphans) {
|
|
430
|
+
lines.push(`- \`${o}\``);
|
|
431
|
+
}
|
|
432
|
+
lines.push("");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Cross-reference index
|
|
436
|
+
lines.push("## Cross-reference index", "");
|
|
437
|
+
if (data.dependencyGraph.nodes.length > 0) {
|
|
438
|
+
for (const node of data.dependencyGraph.nodes) {
|
|
439
|
+
const upstream = data.dependencyGraph.edges
|
|
440
|
+
.filter((e) => e.to === node.path)
|
|
441
|
+
.map((e) => `\`${e.from}\` (${e.type})`);
|
|
442
|
+
const downstream = data.dependencyGraph.edges
|
|
443
|
+
.filter((e) => e.from === node.path)
|
|
444
|
+
.map((e) => `\`${e.to}\` (${e.type})`);
|
|
445
|
+
|
|
446
|
+
if (upstream.length > 0 || downstream.length > 0) {
|
|
447
|
+
lines.push(`### \`${node.path}\``, "");
|
|
448
|
+
if (upstream.length > 0) {
|
|
449
|
+
lines.push(`- **Upstream**: ${upstream.join(", ")}`);
|
|
450
|
+
}
|
|
451
|
+
if (downstream.length > 0) {
|
|
452
|
+
lines.push(`- **Downstream**: ${downstream.join(", ")}`);
|
|
453
|
+
}
|
|
454
|
+
lines.push("");
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
} else {
|
|
458
|
+
lines.push("No cross-references available.", "");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
lines.push(
|
|
462
|
+
"---",
|
|
463
|
+
"",
|
|
464
|
+
"*Generated by `wwa doc-review` from doc-tokens.yaml and DOCS-MAP.md (resolved via boundaries).*",
|
|
465
|
+
""
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
safeWriteFile(mdPath, lines.join("\n"), "utf-8");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ---------------------------------------------------------------------------
|
|
472
|
+
// Public: load registry data for --internal-registry consumers
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
|
|
475
|
+
export async function loadInternalRegistry(dir) {
|
|
476
|
+
const outputDir = await resolveOutputDir(dir);
|
|
477
|
+
const registryPath = join(outputDir, "doc-registry.yaml");
|
|
478
|
+
if (!existsSync(registryPath)) return null;
|
|
479
|
+
try {
|
|
480
|
+
const yaml = (await import("js-yaml")).default;
|
|
481
|
+
const raw = readFileSync(registryPath, "utf-8");
|
|
482
|
+
return yaml.load(raw);
|
|
483
|
+
} catch {
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
}
|