project-librarian 0.2.0 → 0.3.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.ko.md +260 -120
- package/README.md +203 -114
- package/SKILL.md +38 -18
- package/dist/args.js +20 -2
- package/dist/code-index-file-policy.js +107 -2
- package/dist/code-index.js +77 -59
- package/dist/hooks.js +143 -7
- package/dist/init-project-wiki.js +237 -145
- package/dist/install-skill.js +24 -12
- package/dist/mcp-server.js +449 -0
- package/dist/migration.js +1064 -50
- package/dist/modes.js +324 -149
- package/dist/taxonomy.js +193 -0
- package/dist/templates.js +241 -42
- package/dist/wiki-files.js +24 -28
- package/dist/wiki-graph.js +141 -0
- package/package.json +11 -8
- package/README.ja.md +0 -227
- package/README.zh.md +0 -227
package/dist/migration.js
CHANGED
|
@@ -33,9 +33,18 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.generatedMigrationInboxFiles = void 0;
|
|
36
37
|
exports.classifyMarkdown = classifyMarkdown;
|
|
38
|
+
exports.formOnlyMigrationDocumentReason = formOnlyMigrationDocumentReason;
|
|
39
|
+
exports.extractMigrationUnits = extractMigrationUnits;
|
|
40
|
+
exports.collectMigrationCoverageDiagnostics = collectMigrationCoverageDiagnostics;
|
|
41
|
+
exports.collectMigrationUnitMapDiagnostics = collectMigrationUnitMapDiagnostics;
|
|
42
|
+
exports.collectMigrationSplitPlanDiagnostics = collectMigrationSplitPlanDiagnostics;
|
|
37
43
|
exports.markdownTableRows = markdownTableRows;
|
|
38
44
|
exports.buildInbox = buildInbox;
|
|
45
|
+
exports.isPrunableGeneratedMigrationInbox = isPrunableGeneratedMigrationInbox;
|
|
46
|
+
exports.migrationSemanticReviewComplete = migrationSemanticReviewComplete;
|
|
47
|
+
exports.buildMigrationBulkReviewPlan = buildMigrationBulkReviewPlan;
|
|
39
48
|
exports.timestampSuffix = timestampSuffix;
|
|
40
49
|
exports.prepareMigrationMode = prepareMigrationMode;
|
|
41
50
|
exports.migrationTargetForKind = migrationTargetForKind;
|
|
@@ -43,9 +52,11 @@ exports.runMigrationMode = runMigrationMode;
|
|
|
43
52
|
exports.normalizeMigrationStatus = normalizeMigrationStatus;
|
|
44
53
|
exports.isMigrationInboxStatus = isMigrationInboxStatus;
|
|
45
54
|
exports.migrationInboxStatusMap = migrationInboxStatusMap;
|
|
55
|
+
exports.migrationCoverageStatusMap = migrationCoverageStatusMap;
|
|
46
56
|
exports.semanticStatusForInboxStatus = semanticStatusForInboxStatus;
|
|
47
57
|
exports.runReviewMigrationMode = runReviewMigrationMode;
|
|
48
58
|
const fs = __importStar(require("node:fs"));
|
|
59
|
+
const taxonomy_1 = require("./taxonomy");
|
|
49
60
|
const workspace_1 = require("./workspace");
|
|
50
61
|
const templates_1 = require("./templates");
|
|
51
62
|
const wiki_files_1 = require("./wiki-files");
|
|
@@ -67,10 +78,489 @@ function classifyMarkdown(relativePath, text) {
|
|
|
67
78
|
function markdownTableCell(value) {
|
|
68
79
|
return String(value).replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
69
80
|
}
|
|
81
|
+
function plainMarkdownTableCell(value) {
|
|
82
|
+
return markdownTableCell(String(value).replace(/\[/g, "[").replace(/\]/g, "]"));
|
|
83
|
+
}
|
|
84
|
+
function slugPart(value) {
|
|
85
|
+
return value.toLowerCase().replace(/[^a-z0-9가-힣ぁ-んァ-ン一-龥]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "unit";
|
|
86
|
+
}
|
|
87
|
+
function unitSummary(value) {
|
|
88
|
+
return value.replace(/\s+/g, " ").trim().slice(0, 180);
|
|
89
|
+
}
|
|
90
|
+
function nextUnitId(legacyPath, index, summary) {
|
|
91
|
+
return `${legacyPath}#u${String(index).padStart(3, "0")}-${slugPart(summary)}`;
|
|
92
|
+
}
|
|
93
|
+
const generatedEmptyStarterPaths = new Map([
|
|
94
|
+
["canonical/project-brief.md", "wiki/canonical/project-brief.md"],
|
|
95
|
+
["canonical/open-questions.md", "wiki/canonical/open-questions.md"],
|
|
96
|
+
["canonical/assumptions.md", "wiki/canonical/assumptions.md"],
|
|
97
|
+
["canonical/risks.md", "wiki/canonical/risks.md"],
|
|
98
|
+
]);
|
|
99
|
+
function normalizedTemplateBody(value) {
|
|
100
|
+
return (0, workspace_1.stripMetadataHeader)(value).replace(/\r\n/g, "\n").trim().replace(/[ \t]+/g, " ");
|
|
101
|
+
}
|
|
102
|
+
function isGeneratedEmptyStarterDocument(legacyPath, text) {
|
|
103
|
+
const starterPath = generatedEmptyStarterPaths.get(legacyPath.replace(/^wiki\//, ""));
|
|
104
|
+
if (!starterPath)
|
|
105
|
+
return false;
|
|
106
|
+
const starter = templates_1.starterFiles[starterPath];
|
|
107
|
+
if (!starter)
|
|
108
|
+
return false;
|
|
109
|
+
return normalizedTemplateBody(text) === normalizedTemplateBody(starter);
|
|
110
|
+
}
|
|
111
|
+
function formOnlyMigrationDocumentReason(legacyPath, text) {
|
|
112
|
+
if ((0, workspace_1.metadataValue)(text, "status").toLowerCase() === "template")
|
|
113
|
+
return "metadata status is template";
|
|
114
|
+
if (isGeneratedEmptyStarterDocument(legacyPath, text))
|
|
115
|
+
return "matches generated empty starter page";
|
|
116
|
+
return "";
|
|
117
|
+
}
|
|
118
|
+
function isFormOnlyTemplateRouteUnit(legacyPath, value) {
|
|
119
|
+
if (legacyPath.replace(/^wiki\//, "") !== "index.md")
|
|
120
|
+
return false;
|
|
121
|
+
return /\bdecisions\/(?:decision-pack-template|full-adr-template)\b/.test(value);
|
|
122
|
+
}
|
|
123
|
+
function extractMigrationUnits(legacyPath, text) {
|
|
124
|
+
if (formOnlyMigrationDocumentReason(legacyPath, text))
|
|
125
|
+
return [];
|
|
126
|
+
const body = (0, workspace_1.stripMetadataHeader)(text);
|
|
127
|
+
const lines = body.split(/\r?\n/);
|
|
128
|
+
const units = [];
|
|
129
|
+
let heading = "";
|
|
130
|
+
let headingPath = [];
|
|
131
|
+
let paragraph = [];
|
|
132
|
+
let inCodeFence = false;
|
|
133
|
+
let codeBlock = [];
|
|
134
|
+
let sourceUnitIndex = 0;
|
|
135
|
+
const pushUnit = (type, value) => {
|
|
136
|
+
const summary = unitSummary(value);
|
|
137
|
+
if (!summary)
|
|
138
|
+
return;
|
|
139
|
+
sourceUnitIndex += 1;
|
|
140
|
+
if (isFormOnlyTemplateRouteUnit(legacyPath, value))
|
|
141
|
+
return;
|
|
142
|
+
const unitHeadingPath = [...headingPath];
|
|
143
|
+
units.push({
|
|
144
|
+
id: nextUnitId(legacyPath, sourceUnitIndex, summary),
|
|
145
|
+
legacyPath,
|
|
146
|
+
type,
|
|
147
|
+
heading,
|
|
148
|
+
headingPath: unitHeadingPath,
|
|
149
|
+
content: value,
|
|
150
|
+
summary,
|
|
151
|
+
classification: (0, taxonomy_1.classifyMigrationUnit)({ legacyPath, heading, headingPath: unitHeadingPath, content: value, summary }),
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
const flushParagraph = () => {
|
|
155
|
+
if (paragraph.length === 0)
|
|
156
|
+
return;
|
|
157
|
+
pushUnit("paragraph", paragraph.join(" "));
|
|
158
|
+
paragraph = [];
|
|
159
|
+
};
|
|
160
|
+
for (const line of lines) {
|
|
161
|
+
const trimmed = line.trim();
|
|
162
|
+
if (/^```/.test(trimmed)) {
|
|
163
|
+
if (inCodeFence) {
|
|
164
|
+
codeBlock.push(line);
|
|
165
|
+
pushUnit("code-block", codeBlock.join("\n"));
|
|
166
|
+
codeBlock = [];
|
|
167
|
+
inCodeFence = false;
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
flushParagraph();
|
|
171
|
+
inCodeFence = true;
|
|
172
|
+
codeBlock = [line];
|
|
173
|
+
}
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (inCodeFence) {
|
|
177
|
+
codeBlock.push(line);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (!trimmed) {
|
|
181
|
+
flushParagraph();
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
185
|
+
if (headingMatch?.[2]) {
|
|
186
|
+
flushParagraph();
|
|
187
|
+
heading = headingMatch[2].trim();
|
|
188
|
+
const level = headingMatch[1]?.length ?? 1;
|
|
189
|
+
headingPath = [...headingPath.slice(0, level - 1), heading];
|
|
190
|
+
pushUnit("heading", heading);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (/^\|.+\|$/.test(trimmed) && !/^\|\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(trimmed)) {
|
|
194
|
+
flushParagraph();
|
|
195
|
+
pushUnit("table-row", trimmed);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (/^([-*+]|\d+[.)])\s+/.test(trimmed)) {
|
|
199
|
+
flushParagraph();
|
|
200
|
+
pushUnit("list-item", trimmed);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
paragraph.push(trimmed);
|
|
204
|
+
}
|
|
205
|
+
if (inCodeFence && codeBlock.length > 0)
|
|
206
|
+
pushUnit("code-block", codeBlock.join("\n"));
|
|
207
|
+
flushParagraph();
|
|
208
|
+
return units;
|
|
209
|
+
}
|
|
210
|
+
function coverageTableRows(units) {
|
|
211
|
+
if (units.length === 0)
|
|
212
|
+
return "| none | - | - | - | - | pending | - | - | - | - | - |\n";
|
|
213
|
+
return units.map((unit) => {
|
|
214
|
+
const status = unit.classification.confidence === "low" ? "needs-human-review" : "pending";
|
|
215
|
+
return `| ${markdownTableCell(unit.id)} | ${markdownTableCell(unit.legacyPath)} | ${unit.type} | ${plainMarkdownTableCell(unit.heading || "-")} | ${plainMarkdownTableCell(unit.summary)} | ${status} | ${markdownTableCell(unit.classification.target)} | - | ${markdownTableCell(unit.classification.label)} | ${unit.classification.confidence} | ${plainMarkdownTableCell(unit.classification.reason)} |`;
|
|
216
|
+
}).join("\n") + "\n";
|
|
217
|
+
}
|
|
218
|
+
function coverageRowsFromUnits(units) {
|
|
219
|
+
return units.map((unit) => ({
|
|
220
|
+
unitId: unit.id,
|
|
221
|
+
legacySource: unit.legacyPath,
|
|
222
|
+
type: unit.type,
|
|
223
|
+
heading: unit.heading || "-",
|
|
224
|
+
summary: unit.summary,
|
|
225
|
+
status: unit.classification.confidence === "low" ? "needs-human-review" : "pending",
|
|
226
|
+
target: unit.classification.target,
|
|
227
|
+
note: "-",
|
|
228
|
+
area: unit.classification.label,
|
|
229
|
+
confidence: unit.classification.confidence,
|
|
230
|
+
reason: unit.classification.reason,
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
function normalizeCoverageStatus(value) {
|
|
234
|
+
const status = String(value || "").trim().toLowerCase();
|
|
235
|
+
if (isMigrationCoverageStatus(status))
|
|
236
|
+
return status;
|
|
237
|
+
return "pending";
|
|
238
|
+
}
|
|
239
|
+
function normalizeConfidence(value) {
|
|
240
|
+
const confidence = String(value || "").trim().toLowerCase();
|
|
241
|
+
if (isMigrationConfidence(confidence))
|
|
242
|
+
return confidence;
|
|
243
|
+
return "low";
|
|
244
|
+
}
|
|
245
|
+
function parseMigrationCoverageRows(text) {
|
|
246
|
+
return (0, wiki_files_1.parseMarkdownTableRows)(text, 11)
|
|
247
|
+
.filter((cells) => cells[0] !== "Unit ID")
|
|
248
|
+
.filter((cells) => !isMarkdownTableSeparatorRow(cells))
|
|
249
|
+
.map((cells) => ({
|
|
250
|
+
unitId: cells[0] ?? "",
|
|
251
|
+
legacySource: cells[1] ?? "",
|
|
252
|
+
type: cells[2] ?? "",
|
|
253
|
+
heading: cells[3] ?? "",
|
|
254
|
+
summary: cells[4] ?? "",
|
|
255
|
+
status: normalizeCoverageStatus(cells[5]),
|
|
256
|
+
target: cells[6] ?? "",
|
|
257
|
+
note: cells[7] ?? "",
|
|
258
|
+
area: cells[8] ?? "",
|
|
259
|
+
confidence: normalizeConfidence(cells[9]),
|
|
260
|
+
reason: cells[10] ?? "",
|
|
261
|
+
}))
|
|
262
|
+
.filter((row) => row.unitId);
|
|
263
|
+
}
|
|
264
|
+
function unitMapRows(units) {
|
|
265
|
+
if (units.length === 0)
|
|
266
|
+
return "| none | - | - | - | - | - | - | - | - | - |\n";
|
|
267
|
+
return units.map((unit) => {
|
|
268
|
+
const status = unit.classification.confidence === "low" ? "needs-human-review" : "pending";
|
|
269
|
+
return `| ${markdownTableCell(unit.id)} | ${markdownTableCell(unit.legacyPath)} | ${plainMarkdownTableCell(unit.headingPath.join(" > ") || "-")} | ${markdownTableCell(unit.classification.label)} | ${unit.classification.storage} | ${unit.classification.confidence} | ${markdownTableCell(unit.classification.target)} | ${plainMarkdownTableCell(unit.classification.reason)} | ${plainMarkdownTableCell(unit.summary)} | ${status} |`;
|
|
270
|
+
}).join("\n") + "\n";
|
|
271
|
+
}
|
|
272
|
+
function confidenceRank(value) {
|
|
273
|
+
if (value === "high")
|
|
274
|
+
return 3;
|
|
275
|
+
if (value === "medium")
|
|
276
|
+
return 2;
|
|
277
|
+
return 1;
|
|
278
|
+
}
|
|
279
|
+
function splitPlanRows(units) {
|
|
280
|
+
if (units.length === 0)
|
|
281
|
+
return "| none | - | - | - | 0 | - |\n";
|
|
282
|
+
const groups = new Map();
|
|
283
|
+
for (const unit of units) {
|
|
284
|
+
const key = unit.classification.target;
|
|
285
|
+
groups.set(key, [...(groups.get(key) ?? []), unit]);
|
|
286
|
+
}
|
|
287
|
+
return Array.from(groups.entries())
|
|
288
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
289
|
+
.map(([target, group]) => {
|
|
290
|
+
const first = group[0];
|
|
291
|
+
const confidence = group.map((unit) => unit.classification.confidence).sort((left, right) => confidenceRank(left) - confidenceRank(right))[0] ?? "low";
|
|
292
|
+
const unitIds = group.map((unit) => unit.id).join("<br>");
|
|
293
|
+
return `| ${markdownTableCell(target)} | ${markdownTableCell(first?.classification.label ?? "-")} | ${first?.classification.storage ?? "-"} | ${confidence} | ${group.length} | ${markdownTableCell(unitIds)} |`;
|
|
294
|
+
}).join("\n") + "\n";
|
|
295
|
+
}
|
|
296
|
+
function isMigrationCoverageStatus(value) {
|
|
297
|
+
return ["adopted", "merged", "superseded", "rejected", "resolved", "needs-human-review", "pending"].includes(value);
|
|
298
|
+
}
|
|
299
|
+
function legacyWikiRoots() {
|
|
300
|
+
if (!fs.existsSync(workspace_1.root))
|
|
301
|
+
return [];
|
|
302
|
+
return fs.readdirSync(workspace_1.root, { withFileTypes: true })
|
|
303
|
+
.filter((entry) => entry.isDirectory() && /^wiki_legacy(?:_|$)/.test(entry.name))
|
|
304
|
+
.map((entry) => entry.name)
|
|
305
|
+
.sort();
|
|
306
|
+
}
|
|
307
|
+
function migrationVerificationLegacyRoot() {
|
|
308
|
+
if (!(0, workspace_1.exists)("wiki/migration/verification.md"))
|
|
309
|
+
return null;
|
|
310
|
+
const legacyRoot = ((0, workspace_1.read)("wiki/migration/verification.md").match(/^- legacy root:\s*(.+)$/m) || [])[1]?.trim();
|
|
311
|
+
if (!legacyRoot || ["none", "unknown"].includes(legacyRoot))
|
|
312
|
+
return null;
|
|
313
|
+
return (0, workspace_1.exists)(legacyRoot) ? legacyRoot : null;
|
|
314
|
+
}
|
|
315
|
+
function activeMigrationLegacyRoots() {
|
|
316
|
+
const verifiedRoot = migrationVerificationLegacyRoot();
|
|
317
|
+
return verifiedRoot ? [verifiedRoot] : legacyWikiRoots();
|
|
318
|
+
}
|
|
319
|
+
function expectedMigrationUnits() {
|
|
320
|
+
return activeMigrationLegacyRoots()
|
|
321
|
+
.flatMap((legacyRoot) => (0, wiki_files_1.walkMarkdownFiles)((0, workspace_1.abs)(legacyRoot), [], (0, workspace_1.abs)(legacyRoot)))
|
|
322
|
+
.flatMap((file) => extractMigrationUnits(file.basePath, (0, workspace_1.read)(file.path)));
|
|
323
|
+
}
|
|
324
|
+
function isMigrationConfidence(value) {
|
|
325
|
+
return ["high", "medium", "low"].includes(value);
|
|
326
|
+
}
|
|
327
|
+
function isMigrationStorage(value) {
|
|
328
|
+
return ["canonical", "decisions", "sources", "meta"].includes(value);
|
|
329
|
+
}
|
|
330
|
+
exports.generatedMigrationInboxFiles = [
|
|
331
|
+
"wiki/canonical/migration-inbox.md",
|
|
332
|
+
"wiki/decisions/migration-inbox.md",
|
|
333
|
+
"wiki/sources/migration-inbox.md",
|
|
334
|
+
];
|
|
335
|
+
function isNewWikiTarget(value) {
|
|
336
|
+
return /^wiki\/(canonical|decisions|sources|meta)\//.test(value);
|
|
337
|
+
}
|
|
338
|
+
function expectedUnitMap(units) {
|
|
339
|
+
return new Map(units.map((unit) => [unit.id, unit]));
|
|
340
|
+
}
|
|
341
|
+
function splitLegacyUnitsCell(value) {
|
|
342
|
+
return value
|
|
343
|
+
.split(/<br\s*\/?>|,/i)
|
|
344
|
+
.map((item) => item.trim())
|
|
345
|
+
.filter(Boolean);
|
|
346
|
+
}
|
|
347
|
+
function isMarkdownTableSeparatorRow(cells) {
|
|
348
|
+
return cells.every((cell) => /^:?-+:?$/.test(cell.replace(/\s/g, "")));
|
|
349
|
+
}
|
|
350
|
+
function isReviewedCoverageRetarget(cells) {
|
|
351
|
+
const note = String(cells[7] || "").toLowerCase();
|
|
352
|
+
const reason = String(cells[10] || "").toLowerCase();
|
|
353
|
+
return note.includes("reviewed low-confidence content; retargeted")
|
|
354
|
+
|| reason.includes("reviewed source context; taxonomy target assigned");
|
|
355
|
+
}
|
|
356
|
+
function collectMigrationCoverageDiagnostics() {
|
|
357
|
+
const units = expectedMigrationUnits();
|
|
358
|
+
if (units.length === 0)
|
|
359
|
+
return [];
|
|
360
|
+
if (!(0, workspace_1.exists)("wiki/migration/coverage.md")) {
|
|
361
|
+
return [{
|
|
362
|
+
code: "migration-coverage-missing",
|
|
363
|
+
severity: "error",
|
|
364
|
+
file: "wiki/migration/coverage.md",
|
|
365
|
+
message: "migration unit coverage ledger is missing; run --migrate to account for legacy meaning units",
|
|
366
|
+
}];
|
|
367
|
+
}
|
|
368
|
+
const diagnostics = [];
|
|
369
|
+
const expectedIds = new Set(units.map((unit) => unit.id));
|
|
370
|
+
const unitsById = expectedUnitMap(units);
|
|
371
|
+
const seenIds = new Set();
|
|
372
|
+
const rows = (0, wiki_files_1.parseMarkdownTableRows)((0, workspace_1.read)("wiki/migration/coverage.md"), 8)
|
|
373
|
+
.filter((cells) => cells[0] !== "Unit ID")
|
|
374
|
+
.filter((cells) => !isMarkdownTableSeparatorRow(cells));
|
|
375
|
+
for (const cells of rows) {
|
|
376
|
+
const id = cells[0] || "";
|
|
377
|
+
const status = String(cells[5] || "").trim().toLowerCase();
|
|
378
|
+
const target = String(cells[6] || "").trim();
|
|
379
|
+
const confidence = String(cells[9] || "").trim().toLowerCase();
|
|
380
|
+
const expectedUnit = unitsById.get(id);
|
|
381
|
+
if (cells.length < 11) {
|
|
382
|
+
diagnostics.push({ code: "migration-coverage-schema-drift", severity: "error", file: "wiki/migration/coverage.md", message: `coverage row for ${id || "(blank)"} is missing Area, Confidence, or Reason columns; rerun --migrate` });
|
|
383
|
+
}
|
|
384
|
+
if (seenIds.has(id)) {
|
|
385
|
+
diagnostics.push({ code: "migration-duplicate-unit", severity: "error", file: "wiki/migration/coverage.md", message: `duplicate migration unit row: ${id}` });
|
|
386
|
+
}
|
|
387
|
+
seenIds.add(id);
|
|
388
|
+
if (!expectedIds.has(id)) {
|
|
389
|
+
diagnostics.push({ code: "migration-stale-unit", severity: "warn", file: "wiki/migration/coverage.md", message: `coverage row does not match current legacy units: ${id}` });
|
|
390
|
+
}
|
|
391
|
+
if (!isMigrationCoverageStatus(status)) {
|
|
392
|
+
diagnostics.push({ code: "migration-invalid-status", severity: "error", file: "wiki/migration/coverage.md", message: `unit ${id} has invalid status: ${status || "(blank)"}` });
|
|
393
|
+
}
|
|
394
|
+
if (confidence && !isMigrationConfidence(confidence)) {
|
|
395
|
+
diagnostics.push({ code: "migration-invalid-confidence", severity: "error", file: "wiki/migration/coverage.md", message: `unit ${id} has invalid confidence: ${confidence}` });
|
|
396
|
+
}
|
|
397
|
+
if (["adopted", "merged"].includes(status) && !isNewWikiTarget(target)) {
|
|
398
|
+
diagnostics.push({ code: "migration-missing-target", severity: "error", file: "wiki/migration/coverage.md", message: `unit ${id} is ${status} but target is not a new wiki page` });
|
|
399
|
+
}
|
|
400
|
+
if (status === "pending" && expectedUnit && target && target !== expectedUnit.classification.target && !isReviewedCoverageRetarget(cells)) {
|
|
401
|
+
diagnostics.push({ code: "migration-pending-target-drift", severity: "warn", file: "wiki/migration/coverage.md", message: `pending unit ${id} target differs from generated taxonomy target ${expectedUnit.classification.target}` });
|
|
402
|
+
}
|
|
403
|
+
if (["adopted", "merged"].includes(status) && confidence === "low") {
|
|
404
|
+
diagnostics.push({ code: "migration-low-confidence-adopted", severity: "warn", file: "wiki/migration/coverage.md", message: `unit ${id} is ${status} despite low confidence classification; verify the target manually` });
|
|
405
|
+
}
|
|
406
|
+
if (/\bwiki_legacy(?:_|\b|\/)/.test(target)) {
|
|
407
|
+
diagnostics.push({ code: "migration-legacy-target", severity: "error", file: "wiki/migration/coverage.md", message: `unit ${id} targets wiki_legacy* instead of migrated new-wiki truth` });
|
|
408
|
+
}
|
|
409
|
+
if (status === "pending") {
|
|
410
|
+
diagnostics.push({ code: "migration-pending-unit", severity: "warn", file: "wiki/migration/coverage.md", message: `unit ${id} is still pending migration review` });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
for (const unit of units) {
|
|
414
|
+
if (!seenIds.has(unit.id)) {
|
|
415
|
+
diagnostics.push({ code: "migration-unaccounted-unit", severity: "error", file: unit.legacyPath, message: `legacy meaning unit missing from coverage ledger: ${unit.id}` });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return diagnostics.sort((a, b) => a.file.localeCompare(b.file) || a.code.localeCompare(b.code) || a.message.localeCompare(b.message));
|
|
419
|
+
}
|
|
420
|
+
function collectMigrationUnitMapDiagnostics() {
|
|
421
|
+
const units = expectedMigrationUnits();
|
|
422
|
+
if (units.length === 0)
|
|
423
|
+
return [];
|
|
424
|
+
const file = "wiki/migration/unit-map.md";
|
|
425
|
+
if (!(0, workspace_1.exists)(file)) {
|
|
426
|
+
return [{
|
|
427
|
+
code: "migration-unit-map-missing",
|
|
428
|
+
severity: "error",
|
|
429
|
+
file,
|
|
430
|
+
message: "migration unit map is missing; run --migrate to classify legacy meaning units",
|
|
431
|
+
}];
|
|
432
|
+
}
|
|
433
|
+
const diagnostics = [];
|
|
434
|
+
const unitsById = expectedUnitMap(units);
|
|
435
|
+
const seenIds = new Set();
|
|
436
|
+
const rows = (0, wiki_files_1.parseMarkdownTableRows)((0, workspace_1.read)(file), 10)
|
|
437
|
+
.filter((cells) => cells[0] !== "Unit ID")
|
|
438
|
+
.filter((cells) => !isMarkdownTableSeparatorRow(cells));
|
|
439
|
+
for (const cells of rows) {
|
|
440
|
+
const id = cells[0] || "";
|
|
441
|
+
const area = String(cells[3] || "").trim();
|
|
442
|
+
const storage = String(cells[4] || "").trim();
|
|
443
|
+
const confidence = String(cells[5] || "").trim().toLowerCase();
|
|
444
|
+
const target = String(cells[6] || "").trim();
|
|
445
|
+
const status = String(cells[9] || "").trim().toLowerCase();
|
|
446
|
+
const expectedUnit = unitsById.get(id);
|
|
447
|
+
if (seenIds.has(id)) {
|
|
448
|
+
diagnostics.push({ code: "migration-unit-map-duplicate-unit", severity: "error", file, message: `duplicate unit-map row: ${id}` });
|
|
449
|
+
}
|
|
450
|
+
seenIds.add(id);
|
|
451
|
+
if (!expectedUnit) {
|
|
452
|
+
diagnostics.push({ code: "migration-unit-map-stale-unit", severity: "warn", file, message: `unit-map row does not match current migration batch: ${id}` });
|
|
453
|
+
}
|
|
454
|
+
if (!isMigrationStorage(storage)) {
|
|
455
|
+
diagnostics.push({ code: "migration-unit-map-invalid-storage", severity: "error", file, message: `unit ${id} has invalid storage: ${storage || "(blank)"}` });
|
|
456
|
+
}
|
|
457
|
+
if (!isMigrationConfidence(confidence)) {
|
|
458
|
+
diagnostics.push({ code: "migration-unit-map-invalid-confidence", severity: "error", file, message: `unit ${id} has invalid confidence: ${confidence || "(blank)"}` });
|
|
459
|
+
}
|
|
460
|
+
if (!isMigrationCoverageStatus(status)) {
|
|
461
|
+
diagnostics.push({ code: "migration-unit-map-invalid-status", severity: "error", file, message: `unit ${id} has invalid status: ${status || "(blank)"}` });
|
|
462
|
+
}
|
|
463
|
+
if (!isNewWikiTarget(target)) {
|
|
464
|
+
diagnostics.push({ code: "migration-unit-map-invalid-target", severity: "error", file, message: `unit ${id} target is not under wiki/canonical, wiki/decisions, wiki/sources, or wiki/meta` });
|
|
465
|
+
}
|
|
466
|
+
if (expectedUnit) {
|
|
467
|
+
if (area !== expectedUnit.classification.label) {
|
|
468
|
+
diagnostics.push({ code: "migration-unit-map-area-drift", severity: "warn", file, message: `unit ${id} area differs from generated classification ${expectedUnit.classification.label}` });
|
|
469
|
+
}
|
|
470
|
+
if (storage !== expectedUnit.classification.storage) {
|
|
471
|
+
diagnostics.push({ code: "migration-unit-map-storage-drift", severity: "warn", file, message: `unit ${id} storage differs from generated classification ${expectedUnit.classification.storage}` });
|
|
472
|
+
}
|
|
473
|
+
if (confidence !== expectedUnit.classification.confidence) {
|
|
474
|
+
diagnostics.push({ code: "migration-unit-map-confidence-drift", severity: "warn", file, message: `unit ${id} confidence differs from generated classification ${expectedUnit.classification.confidence}` });
|
|
475
|
+
}
|
|
476
|
+
if (target !== expectedUnit.classification.target) {
|
|
477
|
+
diagnostics.push({ code: "migration-unit-map-target-drift", severity: "warn", file, message: `unit ${id} target differs from generated classification ${expectedUnit.classification.target}` });
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
for (const unit of units) {
|
|
482
|
+
if (!seenIds.has(unit.id)) {
|
|
483
|
+
diagnostics.push({ code: "migration-unit-map-unaccounted-unit", severity: "error", file: unit.legacyPath, message: `legacy meaning unit missing from unit map: ${unit.id}` });
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return diagnostics.sort((a, b) => a.file.localeCompare(b.file) || a.code.localeCompare(b.code) || a.message.localeCompare(b.message));
|
|
487
|
+
}
|
|
488
|
+
function collectMigrationSplitPlanDiagnostics() {
|
|
489
|
+
const units = expectedMigrationUnits();
|
|
490
|
+
if (units.length === 0)
|
|
491
|
+
return [];
|
|
492
|
+
const file = "wiki/migration/split-plan.md";
|
|
493
|
+
if (!(0, workspace_1.exists)(file)) {
|
|
494
|
+
return [{
|
|
495
|
+
code: "migration-split-plan-missing",
|
|
496
|
+
severity: "error",
|
|
497
|
+
file,
|
|
498
|
+
message: "migration split plan is missing; run --migrate to group legacy units by target page",
|
|
499
|
+
}];
|
|
500
|
+
}
|
|
501
|
+
const diagnostics = [];
|
|
502
|
+
const unitsById = expectedUnitMap(units);
|
|
503
|
+
const seenTargets = new Set();
|
|
504
|
+
const seenUnitIds = new Set();
|
|
505
|
+
const rows = (0, wiki_files_1.parseMarkdownTableRows)((0, workspace_1.read)(file), 6)
|
|
506
|
+
.filter((cells) => cells[0] !== "Suggested Target")
|
|
507
|
+
.filter((cells) => !isMarkdownTableSeparatorRow(cells));
|
|
508
|
+
for (const cells of rows) {
|
|
509
|
+
const target = String(cells[0] || "").trim();
|
|
510
|
+
const area = String(cells[1] || "").trim();
|
|
511
|
+
const storage = String(cells[2] || "").trim();
|
|
512
|
+
const confidence = String(cells[3] || "").trim().toLowerCase();
|
|
513
|
+
const count = Number(String(cells[4] || "").trim());
|
|
514
|
+
const unitIds = splitLegacyUnitsCell(cells[5] || "");
|
|
515
|
+
const targetStorage = target.match(/^wiki\/([^/]+)\//)?.[1] ?? "";
|
|
516
|
+
if (seenTargets.has(target)) {
|
|
517
|
+
diagnostics.push({ code: "migration-split-plan-duplicate-target", severity: "error", file, message: `duplicate split-plan target row: ${target}` });
|
|
518
|
+
}
|
|
519
|
+
seenTargets.add(target);
|
|
520
|
+
if (!isNewWikiTarget(target)) {
|
|
521
|
+
diagnostics.push({ code: "migration-split-plan-invalid-target", severity: "error", file, message: `split-plan target is not under wiki/canonical, wiki/decisions, wiki/sources, or wiki/meta: ${target || "(blank)"}` });
|
|
522
|
+
}
|
|
523
|
+
if (!isMigrationStorage(storage)) {
|
|
524
|
+
diagnostics.push({ code: "migration-split-plan-invalid-storage", severity: "error", file, message: `target ${target || "(blank)"} has invalid storage: ${storage || "(blank)"}` });
|
|
525
|
+
}
|
|
526
|
+
if (isMigrationStorage(storage) && targetStorage && storage !== targetStorage) {
|
|
527
|
+
diagnostics.push({ code: "migration-split-plan-storage-target-mismatch", severity: "error", file, message: `target ${target} is under wiki/${targetStorage}/ but split-plan storage is ${storage}` });
|
|
528
|
+
}
|
|
529
|
+
if (!isMigrationConfidence(confidence)) {
|
|
530
|
+
diagnostics.push({ code: "migration-split-plan-invalid-confidence", severity: "error", file, message: `target ${target || "(blank)"} has invalid confidence: ${confidence || "(blank)"}` });
|
|
531
|
+
}
|
|
532
|
+
if (!Number.isInteger(count) || count !== unitIds.length) {
|
|
533
|
+
diagnostics.push({ code: "migration-split-plan-count-mismatch", severity: "error", file, message: `target ${target || "(blank)"} says ${Number.isFinite(count) ? count : "(blank)"} units but lists ${unitIds.length}` });
|
|
534
|
+
}
|
|
535
|
+
for (const unitId of unitIds) {
|
|
536
|
+
const expectedUnit = unitsById.get(unitId);
|
|
537
|
+
if (seenUnitIds.has(unitId)) {
|
|
538
|
+
diagnostics.push({ code: "migration-split-plan-duplicate-unit", severity: "error", file, message: `unit appears in more than one split-plan row: ${unitId}` });
|
|
539
|
+
}
|
|
540
|
+
seenUnitIds.add(unitId);
|
|
541
|
+
if (!expectedUnit) {
|
|
542
|
+
diagnostics.push({ code: "migration-split-plan-stale-unit", severity: "warn", file, message: `split-plan unit does not match current migration batch: ${unitId}` });
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (expectedUnit.classification.target !== target) {
|
|
546
|
+
diagnostics.push({ code: "migration-split-plan-target-drift", severity: "warn", file, message: `unit ${unitId} is listed under ${target} but generated classification targets ${expectedUnit.classification.target}` });
|
|
547
|
+
}
|
|
548
|
+
if (expectedUnit.classification.label !== area) {
|
|
549
|
+
diagnostics.push({ code: "migration-split-plan-area-drift", severity: "warn", file, message: `unit ${unitId} row area differs from generated classification ${expectedUnit.classification.label}` });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
for (const unit of units) {
|
|
554
|
+
if (!seenUnitIds.has(unit.id)) {
|
|
555
|
+
diagnostics.push({ code: "migration-split-plan-unaccounted-unit", severity: "error", file: unit.legacyPath, message: `legacy meaning unit missing from split plan: ${unit.id}` });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return diagnostics.sort((a, b) => a.file.localeCompare(b.file) || a.code.localeCompare(b.code) || a.message.localeCompare(b.message));
|
|
559
|
+
}
|
|
70
560
|
function markdownTableRows(items) {
|
|
71
561
|
if (items.length === 0)
|
|
72
562
|
return "| none | - | - | - |\n";
|
|
73
|
-
return items.map((item) => `| ${markdownTableCell(item.path)} | ${
|
|
563
|
+
return items.map((item) => `| ${markdownTableCell(item.path)} | ${plainMarkdownTableCell(item.title)} | ${plainMarkdownTableCell(item.summary)} | pending |`).join("\n") + "\n";
|
|
74
564
|
}
|
|
75
565
|
function buildInbox(title, description, items) {
|
|
76
566
|
return `${(0, templates_1.metadata)("migration-inbox", "medium", "wiki/meta/wiki-ops-v1-decisions.md", "migration candidates are adopted or rescanned")}
|
|
@@ -87,6 +577,369 @@ function buildInbox(title, description, items) {
|
|
|
87
577
|
| --- | --- | --- | --- |
|
|
88
578
|
${markdownTableRows(items)}`;
|
|
89
579
|
}
|
|
580
|
+
function wikiLinkForMigrationPath(relativePath) {
|
|
581
|
+
return `[[${relativePath.replace(/^wiki\//, "").replace(/\.(md|mdx)$/i, "")}]]`;
|
|
582
|
+
}
|
|
583
|
+
function isGeneratedFileLevelMigrationInboxText(text) {
|
|
584
|
+
if ((0, workspace_1.metadataValue)(text, "scope") !== "migration-inbox")
|
|
585
|
+
return false;
|
|
586
|
+
const body = (0, workspace_1.stripMetadataHeader)(text).trim();
|
|
587
|
+
if (!body.includes("| Source | Title | Summary | Status |"))
|
|
588
|
+
return false;
|
|
589
|
+
if (!body.includes("Status values: pending, adopted, rejected, resolved, needs-human-review."))
|
|
590
|
+
return false;
|
|
591
|
+
const headings = Array.from(body.matchAll(/^#{1,6}\s+(.+)$/gm)).map((match) => match[1]?.trim() ?? "");
|
|
592
|
+
if (headings.length !== 2)
|
|
593
|
+
return false;
|
|
594
|
+
return (headings[0]?.endsWith("Migration Inbox") ?? false) && headings[1] === "TL;DR";
|
|
595
|
+
}
|
|
596
|
+
function isPrunableGeneratedMigrationInbox(relativePath) {
|
|
597
|
+
if (!exports.generatedMigrationInboxFiles.includes(relativePath))
|
|
598
|
+
return false;
|
|
599
|
+
if (!(0, workspace_1.exists)(relativePath))
|
|
600
|
+
return false;
|
|
601
|
+
return isGeneratedFileLevelMigrationInboxText((0, workspace_1.read)(relativePath));
|
|
602
|
+
}
|
|
603
|
+
function migrationSemanticReviewComplete() {
|
|
604
|
+
if (!(0, workspace_1.exists)("wiki/migration/verification.md"))
|
|
605
|
+
return false;
|
|
606
|
+
return /^- semantic migration complete:\s*yes, for\b/m.test((0, workspace_1.read)("wiki/migration/verification.md"));
|
|
607
|
+
}
|
|
608
|
+
function migrationBatchScope(legacyRoot) {
|
|
609
|
+
return `${workspace_1.today} migration batch${legacyRoot && legacyRoot !== "none" ? ` from ${legacyRoot}` : ""}`;
|
|
610
|
+
}
|
|
611
|
+
function semanticCompletionValue(complete, batchScope) {
|
|
612
|
+
if (complete)
|
|
613
|
+
return `yes, for the ${batchScope} only`;
|
|
614
|
+
return `no, the ${batchScope} still has unresolved rows`;
|
|
615
|
+
}
|
|
616
|
+
function completionScopeSection(batchScope) {
|
|
617
|
+
return `## Completion Scope
|
|
618
|
+
|
|
619
|
+
- This page records the ${batchScope} only.
|
|
620
|
+
- It does not mean future requests to build a new wiki from the existing wiki should reuse current \`wiki/\` in place.
|
|
621
|
+
- For a fresh rebuild request, treat current \`wiki/\` as the legacy source unless the user says otherwise: preserve it as \`wiki_legacy*\`, create a fresh standard \`wiki/\`, migrate/adopt content from the preserved legacy source, then refresh routing and diagnostics.
|
|
622
|
+
`;
|
|
623
|
+
}
|
|
624
|
+
function completedMigrationStartupBlock(legacyRoot, remainingInboxes) {
|
|
625
|
+
const retainedInboxLine = remainingInboxes.length === 0
|
|
626
|
+
? "- Generated file-level migration inboxes were pruned after semantic completion."
|
|
627
|
+
: `- Generated file-level migration inboxes were pruned where safe; retained inbox-like pages: ${remainingInboxes.map((file) => `\`${file}\``).join(", ")}.`;
|
|
628
|
+
return `<!-- PROJECT-WIKI-MIGRATION:START -->
|
|
629
|
+
## Migration State
|
|
630
|
+
|
|
631
|
+
- ${workspace_1.today}: migration review is complete for \`${legacyRoot || "wiki_legacy"}\`; pending 0 and needs-human-review 0.
|
|
632
|
+
${retainedInboxLine}
|
|
633
|
+
- Migration ledgers under \`wiki/migration/\` remain as audit evidence.
|
|
634
|
+
- \`${legacyRoot || "wiki_legacy"}\` remains preserved as source/rollback archive and is not deleted automatically.
|
|
635
|
+
- Migration completion status is scoped to this batch only. For a future fresh rebuild request, treat current \`wiki/\` as the legacy source unless the user says otherwise.
|
|
636
|
+
<!-- PROJECT-WIKI-MIGRATION:END -->`;
|
|
637
|
+
}
|
|
638
|
+
function completedMigrationIndexBlock(remainingInboxes) {
|
|
639
|
+
const inboxLine = remainingInboxes.length === 0
|
|
640
|
+
? "- Generated file-level migration inboxes were pruned after semantic completion."
|
|
641
|
+
: `- Retained inbox-like pages: ${remainingInboxes.map(wikiLinkForMigrationPath).join(", ")}.`;
|
|
642
|
+
return `<!-- PROJECT-WIKI-MIGRATION:START -->
|
|
643
|
+
## Migration
|
|
644
|
+
|
|
645
|
+
- Core ledgers: [[migration/coverage]], [[migration/review]], [[migration/verification]], [[migration/bulk-review]].
|
|
646
|
+
- Planning ledgers: [[migration/plan]], [[migration/inventory]], [[migration/unit-map]], [[migration/split-plan]].
|
|
647
|
+
${inboxLine}
|
|
648
|
+
- Keep \`wiki_legacy*\` only as source/rollback archive after semantic completion; it is not deleted automatically.
|
|
649
|
+
<!-- PROJECT-WIKI-MIGRATION:END -->`;
|
|
650
|
+
}
|
|
651
|
+
function pruneCompletedMigrationJunk(legacyRoot) {
|
|
652
|
+
const results = [];
|
|
653
|
+
for (const file of exports.generatedMigrationInboxFiles) {
|
|
654
|
+
if (!isPrunableGeneratedMigrationInbox(file))
|
|
655
|
+
continue;
|
|
656
|
+
fs.unlinkSync((0, workspace_1.abs)(file));
|
|
657
|
+
results.push([file, "removed"]);
|
|
658
|
+
}
|
|
659
|
+
const remainingInboxes = exports.generatedMigrationInboxFiles.filter((file) => (0, workspace_1.exists)(file));
|
|
660
|
+
if ((0, workspace_1.exists)("wiki/startup.md")) {
|
|
661
|
+
results.push(["wiki/startup.md migration state", (0, workspace_1.upsertMarkedSection)("wiki/startup.md", "<!-- PROJECT-WIKI-MIGRATION:START -->", "<!-- PROJECT-WIKI-MIGRATION:END -->", completedMigrationStartupBlock(legacyRoot, remainingInboxes))]);
|
|
662
|
+
}
|
|
663
|
+
if ((0, workspace_1.exists)("wiki/index.md")) {
|
|
664
|
+
results.push(["wiki/index.md migration router", (0, workspace_1.upsertMarkedSection)("wiki/index.md", "<!-- PROJECT-WIKI-MIGRATION:START -->", "<!-- PROJECT-WIKI-MIGRATION:END -->", completedMigrationIndexBlock(remainingInboxes))]);
|
|
665
|
+
}
|
|
666
|
+
return results;
|
|
667
|
+
}
|
|
668
|
+
function isOpenMigrationRow(row) {
|
|
669
|
+
return row.inboxStatus === "pending" || row.inboxStatus === "needs-human-review" || row.semanticStatus === "pending semantic rewrite";
|
|
670
|
+
}
|
|
671
|
+
function sourceLooksGeneratedOrRouter(source) {
|
|
672
|
+
return /^(AGENTS|CLAUDE|GEMINI|README|startup|index)\.md$/.test(source)
|
|
673
|
+
|| /^meta\/(operating-model|decision-policy|wiki-ops-v1-decisions|document-taxonomy)\.md$/.test(source);
|
|
674
|
+
}
|
|
675
|
+
const structuralHumanReviewHeadings = new Set([
|
|
676
|
+
"Active",
|
|
677
|
+
"Audience",
|
|
678
|
+
"Claim Boundary",
|
|
679
|
+
"Code-Proven Behavior",
|
|
680
|
+
"Code-proven behavior",
|
|
681
|
+
"Constraints",
|
|
682
|
+
"Core Scenarios",
|
|
683
|
+
"Current State",
|
|
684
|
+
"Diagnostics And Search",
|
|
685
|
+
"Discovery Rules",
|
|
686
|
+
"Evidence",
|
|
687
|
+
"Generated Output Ownership",
|
|
688
|
+
"Incremental Update Behavior",
|
|
689
|
+
"Inference",
|
|
690
|
+
"Mode Surface",
|
|
691
|
+
"Parser Backend Registry",
|
|
692
|
+
"Product",
|
|
693
|
+
"Product Direction",
|
|
694
|
+
"Project State",
|
|
695
|
+
"Query And Staleness",
|
|
696
|
+
"Read On Demand",
|
|
697
|
+
"Related Canonical Pages",
|
|
698
|
+
"Resolved",
|
|
699
|
+
"Retired",
|
|
700
|
+
"Routing And Inbox",
|
|
701
|
+
"Skill Installation",
|
|
702
|
+
"Success Criteria",
|
|
703
|
+
"TL;DR",
|
|
704
|
+
"Token Discipline",
|
|
705
|
+
"Tree-Sitter Mode",
|
|
706
|
+
"Workspace And Ownership Adapters",
|
|
707
|
+
]);
|
|
708
|
+
function normalizedReviewText(value) {
|
|
709
|
+
return value.replace(/\\\|/g, "|").replace(/<br>/g, " ").replace(/\s+/g, " ").trim();
|
|
710
|
+
}
|
|
711
|
+
function isMarkdownTableDividerSummary(summary) {
|
|
712
|
+
return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?$/.test(summary);
|
|
713
|
+
}
|
|
714
|
+
function isMarkdownTableHeaderSummary(summary) {
|
|
715
|
+
if (!/^\|.+\|$/.test(summary))
|
|
716
|
+
return false;
|
|
717
|
+
if (summary.includes("`"))
|
|
718
|
+
return false;
|
|
719
|
+
if (isMarkdownTableDividerSummary(summary))
|
|
720
|
+
return true;
|
|
721
|
+
const cells = summary.split("|").map((cell) => cell.trim()).filter(Boolean);
|
|
722
|
+
return cells.length > 1 && cells.every((cell) => /^[A-Z가-힣][A-Za-z가-힣 /-]{0,40}$/.test(cell));
|
|
723
|
+
}
|
|
724
|
+
function isStructuralHumanReviewRow(row) {
|
|
725
|
+
const summary = normalizedReviewText(row.summary);
|
|
726
|
+
const heading = normalizedReviewText(row.heading);
|
|
727
|
+
if (row.unitType === "heading" && (summary === heading || structuralHumanReviewHeadings.has(summary)))
|
|
728
|
+
return true;
|
|
729
|
+
if (row.unitType === "paragraph" && /^(Code-proven facts:|Code-proven behavior:|Evidence:|Inference:|Product framing:)$/i.test(summary))
|
|
730
|
+
return true;
|
|
731
|
+
if (isMarkdownTableDividerSummary(summary) || isMarkdownTableHeaderSummary(summary))
|
|
732
|
+
return true;
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
function confidenceCounts(rows) {
|
|
736
|
+
return {
|
|
737
|
+
high: rows.filter((row) => row.confidence === "high").length,
|
|
738
|
+
medium: rows.filter((row) => row.confidence === "medium").length,
|
|
739
|
+
low: rows.filter((row) => row.confidence === "low").length,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
function groupBulkRows(rows, keyForRow) {
|
|
743
|
+
const groups = new Map();
|
|
744
|
+
for (const row of rows) {
|
|
745
|
+
const key = keyForRow(row);
|
|
746
|
+
groups.set(key, [...(groups.get(key) ?? []), row]);
|
|
747
|
+
}
|
|
748
|
+
return Array.from(groups.entries())
|
|
749
|
+
.map(([key, groupRows]) => {
|
|
750
|
+
const counts = confidenceCounts(groupRows);
|
|
751
|
+
return {
|
|
752
|
+
key,
|
|
753
|
+
rows: groupRows.length,
|
|
754
|
+
...counts,
|
|
755
|
+
sources: Array.from(new Set(groupRows.map((row) => row.legacySource))).sort(),
|
|
756
|
+
targets: Array.from(new Set(groupRows.map((row) => row.target))).sort(),
|
|
757
|
+
areas: Array.from(new Set(groupRows.map((row) => row.area))).sort(),
|
|
758
|
+
sampleUnitIds: groupRows.slice(0, 5).map((row) => row.unitId),
|
|
759
|
+
sampleSummaries: groupRows.slice(0, 3).map((row) => row.summary),
|
|
760
|
+
};
|
|
761
|
+
})
|
|
762
|
+
.sort((left, right) => right.rows - left.rows || left.key.localeCompare(right.key));
|
|
763
|
+
}
|
|
764
|
+
function groupList(values, limit = 4) {
|
|
765
|
+
const shown = values.slice(0, limit).map((value) => markdownTableCell(value));
|
|
766
|
+
const hidden = values.length - shown.length;
|
|
767
|
+
return hidden > 0 ? `${shown.join("<br>")}<br>+${hidden} more` : shown.join("<br>") || "-";
|
|
768
|
+
}
|
|
769
|
+
function sampleList(values, limit = 3) {
|
|
770
|
+
const shown = values.slice(0, limit).map((value) => plainMarkdownTableCell(value));
|
|
771
|
+
const hidden = values.length - shown.length;
|
|
772
|
+
return hidden > 0 ? `${shown.join("<br>")}<br>+${hidden} more` : shown.join("<br>") || "-";
|
|
773
|
+
}
|
|
774
|
+
function confidenceMix(group) {
|
|
775
|
+
return `high ${group.high}, medium ${group.medium}, low ${group.low}`;
|
|
776
|
+
}
|
|
777
|
+
function renderTargetGroupRows(groups, suggestedStatus, limit = Number.POSITIVE_INFINITY) {
|
|
778
|
+
const selected = groups.slice(0, limit);
|
|
779
|
+
if (selected.length === 0)
|
|
780
|
+
return "| none | - | 0 | - | - | - |\n";
|
|
781
|
+
return selected.map((group) => `| ${markdownTableCell(group.key)} | ${group.rows} | ${confidenceMix(group)} | ${groupList(group.sources)} | ${groupList(group.areas)} | ${markdownTableCell(suggestedStatus)} |`).join("\n") + "\n";
|
|
782
|
+
}
|
|
783
|
+
function renderSourceGroupRows(groups, suggestedStatus, limit = Number.POSITIVE_INFINITY) {
|
|
784
|
+
const selected = groups.slice(0, limit);
|
|
785
|
+
if (selected.length === 0)
|
|
786
|
+
return "| none | 0 | - | - | - | - |\n";
|
|
787
|
+
return selected.map((group) => `| ${markdownTableCell(group.key)} | ${group.rows} | ${confidenceMix(group)} | ${groupList(group.targets, 3)} | ${groupList(group.areas)} | ${markdownTableCell(suggestedStatus)} |`).join("\n") + "\n";
|
|
788
|
+
}
|
|
789
|
+
function renderHumanReviewGroupRows(groups, suggestedStatus, limit = Number.POSITIVE_INFINITY) {
|
|
790
|
+
const selected = groups.slice(0, limit);
|
|
791
|
+
if (selected.length === 0)
|
|
792
|
+
return "| none | 0 | - | - | - | - | - |\n";
|
|
793
|
+
return selected.map((group) => `| ${markdownTableCell(group.key)} | ${group.rows} | ${confidenceMix(group)} | ${groupList(group.targets, 3)} | ${sampleList(group.sampleSummaries)} | ${groupList(group.sampleUnitIds, 3)} | ${markdownTableCell(suggestedStatus)} |`).join("\n") + "\n";
|
|
794
|
+
}
|
|
795
|
+
function buildMigrationBulkReviewPlan(rows) {
|
|
796
|
+
const openRows = rows.filter(isOpenMigrationRow);
|
|
797
|
+
const humanRows = openRows.filter((row) => row.inboxStatus === "needs-human-review" || row.confidence === "low");
|
|
798
|
+
const humanStructuralRows = humanRows.filter(isStructuralHumanReviewRow);
|
|
799
|
+
const humanContentRows = humanRows.filter((row) => !isStructuralHumanReviewRow(row));
|
|
800
|
+
const highRows = openRows.filter((row) => row.inboxStatus === "pending" && row.confidence === "high");
|
|
801
|
+
const mediumRows = openRows.filter((row) => row.inboxStatus === "pending" && row.confidence === "medium");
|
|
802
|
+
const sourceGroups = groupBulkRows(openRows, (row) => row.legacySource);
|
|
803
|
+
const singleTargetSourceGroups = sourceGroups.filter((group) => group.targets.length === 1 && group.low === 0);
|
|
804
|
+
const generatedSourceGroups = sourceGroups.filter((group) => sourceLooksGeneratedOrRouter(group.key));
|
|
805
|
+
return {
|
|
806
|
+
totalRows: rows.length,
|
|
807
|
+
openRows: openRows.length,
|
|
808
|
+
completedRows: rows.length - openRows.length,
|
|
809
|
+
highConfidenceRows: highRows.length,
|
|
810
|
+
mediumConfidenceRows: mediumRows.length,
|
|
811
|
+
humanReviewRows: humanRows.length,
|
|
812
|
+
humanReviewStructuralRows: humanStructuralRows.length,
|
|
813
|
+
humanReviewContentRows: humanContentRows.length,
|
|
814
|
+
highTargetGroups: groupBulkRows(highRows, (row) => row.target),
|
|
815
|
+
mediumTargetGroups: groupBulkRows(mediumRows, (row) => row.target),
|
|
816
|
+
singleTargetSourceGroups,
|
|
817
|
+
humanReviewSourceGroups: groupBulkRows(humanRows, (row) => row.legacySource),
|
|
818
|
+
humanReviewStructuralSourceGroups: groupBulkRows(humanStructuralRows, (row) => row.legacySource),
|
|
819
|
+
humanReviewContentSourceGroups: groupBulkRows(humanContentRows, (row) => row.legacySource),
|
|
820
|
+
generatedSourceGroups,
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
function bulkReviewRowsFromCoverage(coverageRows, reviewedRows = []) {
|
|
824
|
+
const reviewedByUnit = new Map(reviewedRows.map((row) => [row.legacyPath, row]));
|
|
825
|
+
return coverageRows.map((row) => {
|
|
826
|
+
const reviewed = reviewedByUnit.get(row.unitId);
|
|
827
|
+
const inboxStatus = reviewed?.inboxStatus ?? normalizeCoverageStatusForReview(row.status);
|
|
828
|
+
const semanticStatus = reviewed?.semanticStatus ?? semanticStatusForInboxStatus(inboxStatus);
|
|
829
|
+
return {
|
|
830
|
+
unitId: row.unitId,
|
|
831
|
+
legacySource: row.legacySource,
|
|
832
|
+
unitType: row.type,
|
|
833
|
+
heading: row.heading,
|
|
834
|
+
summary: row.summary,
|
|
835
|
+
target: reviewed?.target || row.target,
|
|
836
|
+
area: row.area,
|
|
837
|
+
confidence: row.confidence,
|
|
838
|
+
inboxStatus,
|
|
839
|
+
semanticStatus,
|
|
840
|
+
reason: row.reason,
|
|
841
|
+
};
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
function bulkReviewSummarySection(plan) {
|
|
845
|
+
return `## Bulk Review Summary
|
|
846
|
+
|
|
847
|
+
- Do not review all ${plan.totalRows} rows one by one; use [[migration/bulk-review]] to process batches.
|
|
848
|
+
- Completed rows: ${plan.completedRows}
|
|
849
|
+
- Open rows: ${plan.openRows}
|
|
850
|
+
- High-confidence bulk candidates: ${plan.highConfidenceRows} rows across ${plan.highTargetGroups.length} target groups.
|
|
851
|
+
- Medium-confidence batch candidates: ${plan.mediumConfidenceRows} rows across ${plan.mediumTargetGroups.length} target groups.
|
|
852
|
+
- Human-review priority: ${plan.humanReviewContentSourceGroups.length} content-bearing source batches (${plan.humanReviewContentRows} rows), plus ${plan.humanReviewStructuralRows} structural/layout rows handled with the same source review.
|
|
853
|
+
|
|
854
|
+
| Queue | Rows | Groups | Recommended handling |
|
|
855
|
+
| --- | ---: | ---: | --- |
|
|
856
|
+
| High-confidence target batches | ${plan.highConfidenceRows} | ${plan.highTargetGroups.length} | Rewrite or confirm target page, then bulk mark rows adopted or merged. |
|
|
857
|
+
| Medium-confidence target batches | ${plan.mediumConfidenceRows} | ${plan.mediumTargetGroups.length} | Review by target page, sample source evidence, then bulk mark rows. |
|
|
858
|
+
| Human-review content batches | ${plan.humanReviewContentRows} | ${plan.humanReviewContentSourceGroups.length} | Inspect by source page; do not handle as isolated rows unless a batch remains ambiguous. |
|
|
859
|
+
| Human-review structural/layout rows | ${plan.humanReviewStructuralRows} | ${plan.humanReviewStructuralSourceGroups.length} | Close with the source rewrite when headings, table headers, or boilerplate carry no standalone project truth. |
|
|
860
|
+
| Single-target source shortcuts | ${plan.singleTargetSourceGroups.reduce((sum, group) => sum + group.rows, 0)} | ${plan.singleTargetSourceGroups.length} | One source maps to one target; a file-level inbox decision can close the batch. |
|
|
861
|
+
| Generated/router source candidates | ${plan.generatedSourceGroups.reduce((sum, group) => sum + group.rows, 0)} | ${plan.generatedSourceGroups.length} | Compare against regenerated operating docs; mark superseded or resolved only when no project truth remains. |
|
|
862
|
+
`;
|
|
863
|
+
}
|
|
864
|
+
function renderMigrationBulkReviewDocument(plan, batchScope) {
|
|
865
|
+
return `${(0, templates_1.metadata)("migration-bulk-review", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "migration coverage or confidence grouping changes")}
|
|
866
|
+
# Migration Bulk Review Plan
|
|
867
|
+
|
|
868
|
+
## TL;DR
|
|
869
|
+
|
|
870
|
+
- Scope: ${batchScope}
|
|
871
|
+
- Total rows: ${plan.totalRows}
|
|
872
|
+
- Open rows: ${plan.openRows}
|
|
873
|
+
- Completed rows: ${plan.completedRows}
|
|
874
|
+
- The low-confidence queue is ${plan.humanReviewContentSourceGroups.length} content-bearing source batches, not ${plan.humanReviewRows} isolated row decisions.
|
|
875
|
+
- Structural/layout low rows: ${plan.humanReviewStructuralRows}; content-bearing low rows: ${plan.humanReviewContentRows}.
|
|
876
|
+
- This page is advisory. It never changes \`wiki/migration/coverage.md\` statuses by itself.
|
|
877
|
+
|
|
878
|
+
${bulkReviewSummarySection(plan)}
|
|
879
|
+
|
|
880
|
+
## Human-Review Triage
|
|
881
|
+
|
|
882
|
+
Low-confidence rows are not intended to be reviewed one by one. Start with content-bearing source batches, then close structural/layout rows while rewriting or rejecting that same source.
|
|
883
|
+
|
|
884
|
+
| Queue | Rows | Source batches | What to do |
|
|
885
|
+
| --- | ---: | ---: | --- |
|
|
886
|
+
| Content-bearing low rows | ${plan.humanReviewContentRows} | ${plan.humanReviewContentSourceGroups.length} | Review the legacy source page once, adopt/retarget/reject the useful meaning, then mark its low rows together. |
|
|
887
|
+
| Structural/layout low rows | ${plan.humanReviewStructuralRows} | ${plan.humanReviewStructuralSourceGroups.length} | Treat headings, TL;DR labels, table headers, and separators as source-structure evidence; usually close them with the source page decision. |
|
|
888
|
+
| Total low/needs-human-review rows | ${plan.humanReviewRows} | ${plan.humanReviewSourceGroups.length} | Escalate to row-level review only when a source batch mixes unrelated truths that cannot be split safely. |
|
|
889
|
+
|
|
890
|
+
## Single-Target Source Shortcuts
|
|
891
|
+
|
|
892
|
+
Use these when a legacy source maps to one target and has no low-confidence rows. A compatible file-level inbox status can close the whole source batch.
|
|
893
|
+
|
|
894
|
+
| Legacy Source | Rows | Confidence Mix | Target | Areas | Suggested status |
|
|
895
|
+
| --- | ---: | --- | --- | --- | --- |
|
|
896
|
+
${renderSourceGroupRows(plan.singleTargetSourceGroups, "adopted or merged after target rewrite")}
|
|
897
|
+
|
|
898
|
+
## High-Confidence Target Batches
|
|
899
|
+
|
|
900
|
+
| Suggested Target | Rows | Confidence Mix | Sources | Areas | Suggested status |
|
|
901
|
+
| --- | ---: | --- | --- | --- | --- |
|
|
902
|
+
${renderTargetGroupRows(plan.highTargetGroups, "adopted or merged after target rewrite")}
|
|
903
|
+
|
|
904
|
+
## Medium-Confidence Target Batches
|
|
905
|
+
|
|
906
|
+
| Suggested Target | Rows | Confidence Mix | Sources | Areas | Suggested status |
|
|
907
|
+
| --- | ---: | --- | --- | --- | --- |
|
|
908
|
+
${renderTargetGroupRows(plan.mediumTargetGroups, "review by target page before adopting")}
|
|
909
|
+
|
|
910
|
+
## Content-Bearing Human-Review Batches
|
|
911
|
+
|
|
912
|
+
These are the practical manual review units. Review each legacy source page once; use the sample rows to find the relevant evidence quickly.
|
|
913
|
+
|
|
914
|
+
| Legacy Source | Rows | Confidence Mix | Targets | Sample summaries | Sample unit ids | Suggested status |
|
|
915
|
+
| --- | ---: | --- | --- | --- | --- | --- |
|
|
916
|
+
${renderHumanReviewGroupRows(plan.humanReviewContentSourceGroups, "source-batch adopt, retarget, resolve, or reject")}
|
|
917
|
+
|
|
918
|
+
## Structural Human-Review Batches
|
|
919
|
+
|
|
920
|
+
These rows are low-confidence because they are headings, table scaffolding, or boilerplate labels. They rarely require standalone decisions.
|
|
921
|
+
|
|
922
|
+
| Legacy Source | Rows | Confidence Mix | Targets | Sample summaries | Sample unit ids | Suggested status |
|
|
923
|
+
| --- | ---: | --- | --- | --- | --- | --- |
|
|
924
|
+
${renderHumanReviewGroupRows(plan.humanReviewStructuralSourceGroups, "close with the same source-page decision")}
|
|
925
|
+
|
|
926
|
+
## All Human-Review Source Groups
|
|
927
|
+
|
|
928
|
+
These rows have low confidence or are explicitly marked needs-human-review.
|
|
929
|
+
|
|
930
|
+
| Legacy Source | Rows | Confidence Mix | Targets | Areas | Suggested status |
|
|
931
|
+
| --- | ---: | --- | --- | --- | --- |
|
|
932
|
+
${renderSourceGroupRows(plan.humanReviewSourceGroups, "manual adopt, resolve, reject, or retarget")}
|
|
933
|
+
|
|
934
|
+
## Generated Or Router Source Candidates
|
|
935
|
+
|
|
936
|
+
These sources often overlap with regenerated bootstrap/wiki operating documents. Do not discard them automatically; compare for project-specific truth first.
|
|
937
|
+
|
|
938
|
+
| Legacy Source | Rows | Confidence Mix | Targets | Areas | Suggested status |
|
|
939
|
+
| --- | ---: | --- | --- | --- | --- |
|
|
940
|
+
${renderSourceGroupRows(plan.generatedSourceGroups, "superseded or resolved only after comparison")}
|
|
941
|
+
`;
|
|
942
|
+
}
|
|
90
943
|
function timestampSuffix() {
|
|
91
944
|
return new Date().toISOString().replace(/[-:]/g, "").replace(/\..+$/, "").replace("T", "_");
|
|
92
945
|
}
|
|
@@ -118,13 +971,19 @@ function migrationTargetForKind(kind) {
|
|
|
118
971
|
return "wiki/decisions/migration-inbox.md";
|
|
119
972
|
if (kind === "source")
|
|
120
973
|
return "wiki/sources/migration-inbox.md";
|
|
974
|
+
if (kind === "meta")
|
|
975
|
+
return "wiki/meta/migration-inbox.md";
|
|
121
976
|
return "wiki/canonical/migration-inbox.md";
|
|
122
977
|
}
|
|
123
978
|
function runMigrationMode(migrationState) {
|
|
124
979
|
const legacyPath = migrationState.legacyPath;
|
|
125
980
|
const markdownFiles = legacyPath && (0, workspace_1.exists)(legacyPath) ? (0, wiki_files_1.walkMarkdownFiles)((0, workspace_1.abs)(legacyPath), [], (0, workspace_1.abs)(legacyPath)) : [];
|
|
126
|
-
const
|
|
981
|
+
const fileRecords = markdownFiles.map((file) => {
|
|
127
982
|
const text = (0, workspace_1.read)(file.path);
|
|
983
|
+
return { file, text, formOnlyReason: formOnlyMigrationDocumentReason(file.basePath, text) };
|
|
984
|
+
});
|
|
985
|
+
const skippedFormOnlyFiles = fileRecords.filter((record) => record.formOnlyReason);
|
|
986
|
+
const items = fileRecords.filter((record) => !record.formOnlyReason).map(({ file, text }) => {
|
|
128
987
|
return {
|
|
129
988
|
path: file.path,
|
|
130
989
|
legacyPath: file.basePath,
|
|
@@ -138,11 +997,15 @@ function runMigrationMode(migrationState) {
|
|
|
138
997
|
canonical: items.filter((item) => item.kind === "canonical"),
|
|
139
998
|
decision: items.filter((item) => item.kind === "decision"),
|
|
140
999
|
source: items.filter((item) => item.kind === "source"),
|
|
1000
|
+
meta: items.filter((item) => item.kind === "meta"),
|
|
141
1001
|
other: items.filter((item) => item.kind === "other"),
|
|
142
1002
|
};
|
|
143
1003
|
const inventoryRows = items.length === 0
|
|
144
1004
|
? "| none | - | - | 0 | - |\n"
|
|
145
|
-
: items.map((item) => `| ${markdownTableCell(item.path)} | ${item.kind} | ${
|
|
1005
|
+
: items.map((item) => `| ${markdownTableCell(item.path)} | ${item.kind} | ${plainMarkdownTableCell(item.title)} | ${item.bytes} | ${plainMarkdownTableCell(item.summary)} |`).join("\n") + "\n";
|
|
1006
|
+
const skippedRows = skippedFormOnlyFiles.length === 0
|
|
1007
|
+
? "| none | - | - |\n"
|
|
1008
|
+
: skippedFormOnlyFiles.map((record) => `| ${markdownTableCell(record.file.path)} | ${markdownTableCell(record.formOnlyReason)} | ${plainMarkdownTableCell((0, wiki_files_1.firstHeading)(record.text, record.file.path))} |`).join("\n") + "\n";
|
|
146
1009
|
const inventory = `${(0, templates_1.metadata)("migration-inventory", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "migration scan is rerun")}
|
|
147
1010
|
# Migration Inventory
|
|
148
1011
|
|
|
@@ -150,12 +1013,71 @@ function runMigrationMode(migrationState) {
|
|
|
150
1013
|
|
|
151
1014
|
- Generated: ${workspace_1.today}
|
|
152
1015
|
- Legacy root: ${legacyPath || "none"}
|
|
153
|
-
-
|
|
1016
|
+
- Legacy markdown files: ${markdownFiles.length}
|
|
1017
|
+
- Migratable markdown files: ${items.length}
|
|
1018
|
+
- Skipped form-only/template files: ${skippedFormOnlyFiles.length}
|
|
154
1019
|
- Legacy files are not copied directly into the new wiki; they are mapped to rewrite inboxes.
|
|
155
1020
|
|
|
1021
|
+
## Migratable Files
|
|
1022
|
+
|
|
156
1023
|
| Legacy Source | Classification | Title | Size (bytes) | Summary |
|
|
157
1024
|
| --- | --- | --- | ---: | --- |
|
|
158
|
-
${inventoryRows}
|
|
1025
|
+
${inventoryRows}
|
|
1026
|
+
## Skipped Form-Only Files
|
|
1027
|
+
|
|
1028
|
+
| Legacy Source | Reason | Title |
|
|
1029
|
+
| --- | --- | --- |
|
|
1030
|
+
${skippedRows}`;
|
|
1031
|
+
const units = items.flatMap((item) => extractMigrationUnits(item.legacyPath, (0, workspace_1.read)(item.path)));
|
|
1032
|
+
const unitsByStorage = {
|
|
1033
|
+
canonical: units.filter((unit) => unit.classification.storage === "canonical"),
|
|
1034
|
+
decisions: units.filter((unit) => unit.classification.storage === "decisions"),
|
|
1035
|
+
sources: units.filter((unit) => unit.classification.storage === "sources"),
|
|
1036
|
+
meta: units.filter((unit) => unit.classification.storage === "meta"),
|
|
1037
|
+
};
|
|
1038
|
+
const batchScope = migrationBatchScope(legacyPath || "none");
|
|
1039
|
+
const initialBulkPlan = buildMigrationBulkReviewPlan(bulkReviewRowsFromCoverage(coverageRowsFromUnits(units)));
|
|
1040
|
+
const bulkReview = renderMigrationBulkReviewDocument(initialBulkPlan, batchScope);
|
|
1041
|
+
const coverage = `${(0, templates_1.metadata)("migration-coverage", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "migration unit coverage statuses change")}
|
|
1042
|
+
# Migration Coverage Ledger
|
|
1043
|
+
|
|
1044
|
+
## TL;DR
|
|
1045
|
+
|
|
1046
|
+
- Generated: ${workspace_1.today}
|
|
1047
|
+
- Legacy root: ${legacyPath || "none"}
|
|
1048
|
+
- Legacy meaning units: ${units.length}
|
|
1049
|
+
- Every legacy heading, paragraph, list item, table row, and code block should remain accounted for.
|
|
1050
|
+
- Status values: pending, adopted, merged, superseded, rejected, resolved, needs-human-review.
|
|
1051
|
+
- \`adopted\` and \`merged\` rows require a new-wiki target under \`wiki/canonical/\`, \`wiki/decisions/\`, \`wiki/sources/\`, or \`wiki/meta/\`.
|
|
1052
|
+
|
|
1053
|
+
| Unit ID | Legacy Source | Type | Heading | Summary | Status | Target | Note | Area | Confidence | Reason |
|
|
1054
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
1055
|
+
${coverageTableRows(units)}`;
|
|
1056
|
+
const unitMap = `${(0, templates_1.metadata)("migration-unit-map", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "migration classification or target suggestions change")}
|
|
1057
|
+
# Migration Unit Map
|
|
1058
|
+
|
|
1059
|
+
## TL;DR
|
|
1060
|
+
|
|
1061
|
+
- Generated: ${workspace_1.today}
|
|
1062
|
+
- Legacy meaning units: ${units.length}
|
|
1063
|
+
- Each row classifies one legacy meaning unit against the service/product document taxonomy.
|
|
1064
|
+
- Low-confidence rows start as needs-human-review.
|
|
1065
|
+
|
|
1066
|
+
| Unit ID | Legacy Source | Heading Path | Area | Storage | Confidence | Suggested Target | Reason | Summary | Status |
|
|
1067
|
+
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
1068
|
+
${unitMapRows(units)}`;
|
|
1069
|
+
const splitPlan = `${(0, templates_1.metadata)("migration-split-plan", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "migration split grouping changes")}
|
|
1070
|
+
# Migration Split Plan
|
|
1071
|
+
|
|
1072
|
+
## TL;DR
|
|
1073
|
+
|
|
1074
|
+
- Generated: ${workspace_1.today}
|
|
1075
|
+
- Suggested target pages: ${new Set(units.map((unit) => unit.classification.target)).size}
|
|
1076
|
+
- Use this as the rewrite plan when one legacy page mixes API specs, features, UX, policy, QA, operations, or source evidence.
|
|
1077
|
+
|
|
1078
|
+
| Suggested Target | Area | Storage | Lowest Confidence | Unit Count | Legacy Units |
|
|
1079
|
+
| --- | --- | --- | --- | ---: | --- |
|
|
1080
|
+
${splitPlanRows(units)}`;
|
|
159
1081
|
const plan = `${(0, templates_1.metadata)("migration-plan", "short", "wiki/meta/wiki-ops-v1-decisions.md", "migration procedure or status changes")}
|
|
160
1082
|
# Migration Plan
|
|
161
1083
|
|
|
@@ -164,6 +1086,7 @@ ${inventoryRows}`;
|
|
|
164
1086
|
- Generated: ${workspace_1.today}
|
|
165
1087
|
- Preparation: ${migrationState.note}
|
|
166
1088
|
- The new \`./wiki\` uses the standard structure.
|
|
1089
|
+
- Form-only/template files are recorded in inventory but excluded from meaning-unit migration.
|
|
167
1090
|
- Next step: review inbox items and absorb useful meaning into canonical, decisions, sources, or meta docs.
|
|
168
1091
|
|
|
169
1092
|
## Counts
|
|
@@ -173,75 +1096,92 @@ ${inventoryRows}`;
|
|
|
173
1096
|
| canonical candidates | ${byKind.canonical.length} |
|
|
174
1097
|
| decision candidates | ${byKind.decision.length} |
|
|
175
1098
|
| source candidates | ${byKind.source.length} |
|
|
1099
|
+
| meta candidates | ${byKind.meta.length} |
|
|
176
1100
|
| other candidates | ${byKind.other.length} |
|
|
1101
|
+
| skipped form-only/template files | ${skippedFormOnlyFiles.length} |
|
|
1102
|
+
| canonical units | ${unitsByStorage.canonical.length} |
|
|
1103
|
+
| decision units | ${unitsByStorage.decisions.length} |
|
|
1104
|
+
| source units | ${unitsByStorage.sources.length} |
|
|
1105
|
+
| meta units | ${unitsByStorage.meta.length} |
|
|
177
1106
|
`;
|
|
178
|
-
const verificationRows =
|
|
1107
|
+
const verificationRows = units.length === 0
|
|
179
1108
|
? "| none | - | - | pass | - |\n"
|
|
180
|
-
:
|
|
1109
|
+
: units.map((unit) => {
|
|
1110
|
+
const semanticStatus = unit.classification.confidence === "low" ? "needs-human-review" : "pending semantic rewrite";
|
|
1111
|
+
return `| ${markdownTableCell(unit.id)} | ${(0, taxonomy_1.storageToMigrationKind)(unit.classification.storage)} | ${markdownTableCell(unit.classification.target)} | mapped | ${semanticStatus} |`;
|
|
1112
|
+
}).join("\n") + "\n";
|
|
181
1113
|
const verification = `${(0, templates_1.metadata)("migration-verification", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "migration inbox items are adopted, rejected, or rescanned")}
|
|
182
1114
|
# Migration Verification
|
|
183
1115
|
|
|
184
1116
|
## TL;DR
|
|
185
1117
|
|
|
186
1118
|
- legacy root: ${legacyPath || "none"}
|
|
187
|
-
- legacy markdown files: ${
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
1119
|
+
- legacy markdown files: ${markdownFiles.length}
|
|
1120
|
+
- skipped form-only/template files: ${skippedFormOnlyFiles.length}
|
|
1121
|
+
- mapped units: ${units.length}
|
|
1122
|
+
- coverage: ${items.length + skippedFormOnlyFiles.length === markdownFiles.length ? "pass" : "fail"}
|
|
1123
|
+
- This verifies unit coverage. Semantic completeness is confirmed after coverage or inbox statuses are resolved.
|
|
1124
|
+
|
|
1125
|
+
${completionScopeSection(batchScope)}
|
|
191
1126
|
|
|
192
1127
|
| Legacy Source | Classification | New Wiki Target | Coverage | Semantic Status |
|
|
193
1128
|
| --- | --- | --- | --- | --- |
|
|
194
1129
|
${verificationRows}`;
|
|
1130
|
+
const review = `${(0, templates_1.metadata)("migration-review", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "migration inbox statuses change")}
|
|
1131
|
+
# Migration Review
|
|
1132
|
+
|
|
1133
|
+
## TL;DR
|
|
1134
|
+
|
|
1135
|
+
- generated: ${workspace_1.today}
|
|
1136
|
+
- total legacy rows: ${units.length}
|
|
1137
|
+
- skipped form-only/template files: ${skippedFormOnlyFiles.length}
|
|
1138
|
+
- semantic migration complete: no, the ${batchScope} still has unresolved rows
|
|
1139
|
+
- Run \`--review-migration\` after updating coverage or migration inbox statuses.
|
|
1140
|
+
|
|
1141
|
+
${completionScopeSection(batchScope)}
|
|
1142
|
+
|
|
1143
|
+
${bulkReviewSummarySection(initialBulkPlan)}
|
|
1144
|
+
|
|
1145
|
+
| Legacy Source | Classification | Inbox Status | Semantic Status | Evidence |
|
|
1146
|
+
| --- | --- | --- | --- | --- |
|
|
1147
|
+
${units.length === 0 ? "| none | - | - | - | - |\n" : units.map((unit) => `| ${markdownTableCell(unit.id)} | ${(0, taxonomy_1.storageToMigrationKind)(unit.classification.storage)} | ${unit.classification.confidence === "low" ? "needs-human-review" : "pending"} | ${unit.classification.confidence === "low" ? "needs-human-review" : "pending semantic rewrite"} | ${plainMarkdownTableCell(unit.classification.reason)} |`).join("\n") + "\n"}`;
|
|
195
1148
|
const migrationStartupBlock = `<!-- PROJECT-WIKI-MIGRATION:START -->
|
|
196
1149
|
## Migration State
|
|
197
1150
|
|
|
198
1151
|
- ${workspace_1.today}: preserved existing wiki at \`${legacyPath || "no wiki_legacy"}\` and regenerated the standard wiki structure.
|
|
199
|
-
- Scanned ${
|
|
1152
|
+
- Scanned ${markdownFiles.length} legacy markdown files, skipped ${skippedFormOnlyFiles.length} form-only/template files, and mapped ${units.length} meaning units; created inventory, unit map, split plan, coverage, verification, review, and inbox files.
|
|
200
1153
|
- Do not delete \`${legacyPath || "wiki_legacy"}\` until all migration inbox items are adopted/rejected/resolved and needs-human-review is 0.
|
|
1154
|
+
- Migration completion status is scoped to this batch only. For a future fresh rebuild request, treat current \`wiki/\` as the legacy source unless the user says otherwise.
|
|
201
1155
|
<!-- PROJECT-WIKI-MIGRATION:END -->`;
|
|
202
1156
|
const migrationIndexBlock = `<!-- PROJECT-WIKI-MIGRATION:START -->
|
|
203
1157
|
## Migration
|
|
204
1158
|
|
|
205
|
-
- [[migration/plan]]
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
- [[migration/
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
- [[migration/
|
|
214
|
-
- Read when: legacy file coverage or semantic migration status matters.
|
|
215
|
-
- Update when: migration inbox statuses change.
|
|
216
|
-
- Token budget: on-demand.
|
|
217
|
-
- [[migration/review]]
|
|
218
|
-
- Read when: semantic migration review status matters.
|
|
219
|
-
- Update when: \`--review-migration\` syncs migration state.
|
|
220
|
-
- Token budget: on-demand.
|
|
221
|
-
- [[canonical/migration-inbox]]
|
|
222
|
-
- Read when: absorbing legacy canonical candidates.
|
|
223
|
-
- Update when: candidates are adopted/rejected/resolved/needs-human-review.
|
|
224
|
-
- Token budget: medium.
|
|
225
|
-
- [[decisions/migration-inbox]]
|
|
226
|
-
- Read when: absorbing legacy decision candidates.
|
|
227
|
-
- Update when: candidates are adopted/rejected/resolved/needs-human-review.
|
|
228
|
-
- Token budget: medium.
|
|
229
|
-
- [[sources/migration-inbox]]
|
|
230
|
-
- Read when: absorbing legacy source candidates.
|
|
231
|
-
- Update when: candidates are adopted/rejected/resolved/needs-human-review.
|
|
232
|
-
- Token budget: medium.
|
|
1159
|
+
- [[migration/plan]]: migration procedure, fresh rebuild procedure, and counts.
|
|
1160
|
+
- [[migration/inventory]]: legacy file list and file-level classification.
|
|
1161
|
+
- [[migration/unit-map]]: meaning-unit taxonomy classification and suggested target pages.
|
|
1162
|
+
- [[migration/split-plan]]: target-page grouping for mixed legacy pages.
|
|
1163
|
+
- [[migration/coverage]]: editable unit status, target, and note ledger.
|
|
1164
|
+
- [[migration/verification]]: current unit coverage and semantic status.
|
|
1165
|
+
- [[migration/review]]: regenerated summary from coverage/inbox status.
|
|
1166
|
+
- [[migration/bulk-review]]: batch review queues so humans do not inspect every unit one by one.
|
|
1167
|
+
- [[canonical/migration-inbox]], [[decisions/migration-inbox]], [[sources/migration-inbox]]: file-level adoption inboxes.
|
|
233
1168
|
<!-- PROJECT-WIKI-MIGRATION:END -->`;
|
|
234
1169
|
const results = [];
|
|
235
1170
|
(0, workspace_1.mkdirp)("wiki/migration");
|
|
236
1171
|
results.push(["wiki/migration/inventory.md", (0, workspace_1.writeManaged)("wiki/migration/inventory.md", inventory)]);
|
|
1172
|
+
results.push(["wiki/migration/unit-map.md", (0, workspace_1.writeManaged)("wiki/migration/unit-map.md", unitMap)]);
|
|
1173
|
+
results.push(["wiki/migration/split-plan.md", (0, workspace_1.writeManaged)("wiki/migration/split-plan.md", splitPlan)]);
|
|
1174
|
+
results.push(["wiki/migration/coverage.md", (0, workspace_1.writeManaged)("wiki/migration/coverage.md", coverage)]);
|
|
237
1175
|
results.push(["wiki/migration/plan.md", (0, workspace_1.writeManaged)("wiki/migration/plan.md", plan)]);
|
|
1176
|
+
results.push(["wiki/migration/review.md", (0, workspace_1.writeManaged)("wiki/migration/review.md", review)]);
|
|
238
1177
|
results.push(["wiki/migration/verification.md", (0, workspace_1.writeManaged)("wiki/migration/verification.md", verification)]);
|
|
1178
|
+
results.push(["wiki/migration/bulk-review.md", (0, workspace_1.writeManaged)("wiki/migration/bulk-review.md", bulkReview)]);
|
|
239
1179
|
results.push(["wiki/canonical/migration-inbox.md", (0, workspace_1.writeManaged)("wiki/canonical/migration-inbox.md", buildInbox("Canonical Migration Inbox", "Legacy content that may belong in current project truth.", byKind.canonical.concat(byKind.other)))]);
|
|
240
1180
|
results.push(["wiki/decisions/migration-inbox.md", (0, workspace_1.writeManaged)("wiki/decisions/migration-inbox.md", buildInbox("Decision Migration Inbox", "Legacy content that may belong in project decision history.", byKind.decision))]);
|
|
241
1181
|
results.push(["wiki/sources/migration-inbox.md", (0, workspace_1.writeManaged)("wiki/sources/migration-inbox.md", buildInbox("Source Migration Inbox", "Legacy content that may belong in source summaries.", byKind.source))]);
|
|
242
1182
|
results.push(["wiki/startup.md migration state", (0, workspace_1.upsertMarkedSection)("wiki/startup.md", "<!-- PROJECT-WIKI-MIGRATION:START -->", "<!-- PROJECT-WIKI-MIGRATION:END -->", migrationStartupBlock)]);
|
|
243
1183
|
results.push(["wiki/index.md migration router", (0, workspace_1.upsertMarkedSection)("wiki/index.md", "<!-- PROJECT-WIKI-MIGRATION:START -->", "<!-- PROJECT-WIKI-MIGRATION:END -->", migrationIndexBlock)]);
|
|
244
|
-
return { results, total:
|
|
1184
|
+
return { results, total: markdownFiles.length, legacyPath };
|
|
245
1185
|
}
|
|
246
1186
|
function normalizeMigrationStatus(status) {
|
|
247
1187
|
const value = String(status || "").trim().toLowerCase();
|
|
@@ -270,11 +1210,69 @@ function migrationInboxStatusMap() {
|
|
|
270
1210
|
const source = cells[0];
|
|
271
1211
|
if (!source)
|
|
272
1212
|
continue;
|
|
273
|
-
|
|
1213
|
+
const entry = { status: normalizeMigrationStatus(cells[3]), inbox: file };
|
|
1214
|
+
statuses.set(source, entry);
|
|
1215
|
+
const rootStripped = source.replace(/^wiki_legacy(?:_[^/]+)?\//, "");
|
|
1216
|
+
if (rootStripped !== source)
|
|
1217
|
+
statuses.set(rootStripped, entry);
|
|
274
1218
|
}
|
|
275
1219
|
}
|
|
276
1220
|
return statuses;
|
|
277
1221
|
}
|
|
1222
|
+
function normalizeCoverageStatusForReview(status) {
|
|
1223
|
+
const value = String(status || "").trim().toLowerCase();
|
|
1224
|
+
if (value === "merged")
|
|
1225
|
+
return "adopted";
|
|
1226
|
+
if (value === "superseded")
|
|
1227
|
+
return "resolved";
|
|
1228
|
+
if (isMigrationInboxStatus(value))
|
|
1229
|
+
return value;
|
|
1230
|
+
return "pending";
|
|
1231
|
+
}
|
|
1232
|
+
function migrationCoverageStatusMap() {
|
|
1233
|
+
const statuses = new Map();
|
|
1234
|
+
if (!(0, workspace_1.exists)("wiki/migration/coverage.md"))
|
|
1235
|
+
return statuses;
|
|
1236
|
+
for (const cells of (0, wiki_files_1.parseMarkdownTableRows)((0, workspace_1.read)("wiki/migration/coverage.md"), 8).filter((row) => row[0] !== "Unit ID")) {
|
|
1237
|
+
const source = cells[0];
|
|
1238
|
+
if (!source)
|
|
1239
|
+
continue;
|
|
1240
|
+
statuses.set(source, { status: normalizeCoverageStatusForReview(cells[5]), inbox: "wiki/migration/coverage.md" });
|
|
1241
|
+
}
|
|
1242
|
+
return statuses;
|
|
1243
|
+
}
|
|
1244
|
+
function legacySourceFromUnitId(value) {
|
|
1245
|
+
return value.split("#u", 1)[0] ?? value;
|
|
1246
|
+
}
|
|
1247
|
+
function sourcesSafeForFileLevelFallback(rows) {
|
|
1248
|
+
const targetsBySource = new Map();
|
|
1249
|
+
for (const row of rows) {
|
|
1250
|
+
const source = legacySourceFromUnitId(row.legacyPath);
|
|
1251
|
+
const targets = targetsBySource.get(source) ?? new Set();
|
|
1252
|
+
targets.add(row.target);
|
|
1253
|
+
targetsBySource.set(source, targets);
|
|
1254
|
+
}
|
|
1255
|
+
return new Set(Array.from(targetsBySource.entries()).filter(([, targets]) => targets.size === 1).map(([source]) => source));
|
|
1256
|
+
}
|
|
1257
|
+
function migrationStatusForRow(row, coverageStatuses, inboxStatuses, fileLevelFallbackSources) {
|
|
1258
|
+
const coverage = coverageStatuses.get(row.legacyPath);
|
|
1259
|
+
if (coverage && coverage.status !== "pending")
|
|
1260
|
+
return coverage;
|
|
1261
|
+
const exactInbox = inboxStatuses.get(row.legacyPath);
|
|
1262
|
+
if (exactInbox)
|
|
1263
|
+
return exactInbox;
|
|
1264
|
+
const source = legacySourceFromUnitId(row.legacyPath);
|
|
1265
|
+
const sourceInbox = inboxStatuses.get(source);
|
|
1266
|
+
if (sourceInbox && fileLevelFallbackSources.has(source))
|
|
1267
|
+
return sourceInbox;
|
|
1268
|
+
if (sourceInbox) {
|
|
1269
|
+
const note = `file-level inbox row ignored for mixed-target legacy source: ${sourceInbox.inbox}`;
|
|
1270
|
+
return coverage ? { status: coverage.status, inbox: `${note}; using ${coverage.inbox}` } : { status: "needs-human-review", inbox: note };
|
|
1271
|
+
}
|
|
1272
|
+
if (coverage)
|
|
1273
|
+
return coverage;
|
|
1274
|
+
return { status: "needs-human-review", inbox: "missing migration coverage or inbox row" };
|
|
1275
|
+
}
|
|
278
1276
|
function semanticStatusForInboxStatus(status) {
|
|
279
1277
|
if (["adopted", "rejected", "resolved", "needs-human-review"].includes(status))
|
|
280
1278
|
return status;
|
|
@@ -292,11 +1290,12 @@ function runReviewMigrationMode() {
|
|
|
292
1290
|
target: cells[2] ?? "",
|
|
293
1291
|
coverage: cells[3] ?? "",
|
|
294
1292
|
}));
|
|
1293
|
+
const coverageStatuses = migrationCoverageStatusMap();
|
|
295
1294
|
const inboxStatuses = migrationInboxStatusMap();
|
|
1295
|
+
const fileLevelFallbackSources = sourcesSafeForFileLevelFallback(verificationRows);
|
|
296
1296
|
const reviewedRows = verificationRows.map((row) => {
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
return { ...row, inboxStatus: status, semanticStatus: semanticStatusForInboxStatus(status), note: inbox ? inbox.inbox : "missing migration inbox row" };
|
|
1297
|
+
const statusEntry = migrationStatusForRow(row, coverageStatuses, inboxStatuses, fileLevelFallbackSources);
|
|
1298
|
+
return { ...row, inboxStatus: statusEntry.status, semanticStatus: semanticStatusForInboxStatus(statusEntry.status), note: statusEntry.inbox };
|
|
300
1299
|
});
|
|
301
1300
|
const counts = reviewedRows.reduce((acc, row) => {
|
|
302
1301
|
acc[row.inboxStatus] = (acc[row.inboxStatus] || 0) + 1;
|
|
@@ -305,6 +1304,12 @@ function runReviewMigrationMode() {
|
|
|
305
1304
|
const pending = counts.pending || 0;
|
|
306
1305
|
const needsHuman = counts["needs-human-review"] || 0;
|
|
307
1306
|
const complete = pending === 0 && needsHuman === 0;
|
|
1307
|
+
const legacyRoot = (verificationText.match(/^- legacy root:\s*(.+)$/m) || [])[1] || "unknown";
|
|
1308
|
+
const batchScope = migrationBatchScope(legacyRoot);
|
|
1309
|
+
const completionValue = semanticCompletionValue(complete, batchScope);
|
|
1310
|
+
const coverageRows = (0, workspace_1.exists)("wiki/migration/coverage.md") ? parseMigrationCoverageRows((0, workspace_1.read)("wiki/migration/coverage.md")) : [];
|
|
1311
|
+
const bulkPlan = buildMigrationBulkReviewPlan(bulkReviewRowsFromCoverage(coverageRows, reviewedRows));
|
|
1312
|
+
const bulkReview = renderMigrationBulkReviewDocument(bulkPlan, batchScope);
|
|
308
1313
|
const reviewRows = reviewedRows.length === 0
|
|
309
1314
|
? "| none | - | - | - | - |\n"
|
|
310
1315
|
: reviewedRows.map((row) => `| ${markdownTableCell(row.legacyPath)} | ${row.kind} | ${row.inboxStatus} | ${row.semanticStatus} | ${markdownTableCell(row.note)} |`).join("\n") + "\n";
|
|
@@ -320,7 +1325,11 @@ function runReviewMigrationMode() {
|
|
|
320
1325
|
- resolved: ${counts.resolved || 0}
|
|
321
1326
|
- pending: ${pending}
|
|
322
1327
|
- needs-human-review: ${needsHuman}
|
|
323
|
-
- semantic migration complete: ${
|
|
1328
|
+
- semantic migration complete: ${completionValue}
|
|
1329
|
+
|
|
1330
|
+
${completionScopeSection(batchScope)}
|
|
1331
|
+
|
|
1332
|
+
${bulkReviewSummarySection(bulkPlan)}
|
|
324
1333
|
|
|
325
1334
|
| Legacy Source | Classification | Inbox Status | Semantic Status | Evidence |
|
|
326
1335
|
| --- | --- | --- | --- | --- |
|
|
@@ -328,27 +1337,32 @@ ${reviewRows}`;
|
|
|
328
1337
|
const verificationRowsText = reviewedRows.length === 0
|
|
329
1338
|
? "| none | - | - | pass | - |\n"
|
|
330
1339
|
: reviewedRows.map((row) => `| ${markdownTableCell(row.legacyPath)} | ${row.kind} | ${row.target} | ${row.coverage} | ${row.semanticStatus} |`).join("\n") + "\n";
|
|
331
|
-
const legacyRoot = (verificationText.match(/^- legacy root:\s*(.+)$/m) || [])[1] || "unknown";
|
|
332
1340
|
const verification = `${(0, templates_1.metadata)("migration-verification", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "migration inbox items are adopted, rejected, resolved, or marked needs-human-review")}
|
|
333
1341
|
# Migration Verification
|
|
334
1342
|
|
|
335
1343
|
## TL;DR
|
|
336
1344
|
|
|
337
1345
|
- legacy root: ${legacyRoot}
|
|
338
|
-
- legacy
|
|
339
|
-
- mapped
|
|
1346
|
+
- legacy rows: ${reviewedRows.length}
|
|
1347
|
+
- mapped rows: ${reviewedRows.filter((row) => row.coverage === "mapped").length}
|
|
340
1348
|
- coverage: ${reviewedRows.every((row) => row.coverage === "mapped") ? "pass" : "fail"}
|
|
341
|
-
- semantic migration complete: ${
|
|
1349
|
+
- semantic migration complete: ${completionValue}
|
|
342
1350
|
- pending: ${pending}
|
|
343
1351
|
- needs-human-review: ${needsHuman}
|
|
344
1352
|
|
|
1353
|
+
${completionScopeSection(batchScope)}
|
|
1354
|
+
|
|
345
1355
|
| Legacy Source | Classification | New Wiki Target | Coverage | Semantic Status |
|
|
346
1356
|
| --- | --- | --- | --- | --- |
|
|
347
1357
|
${verificationRowsText}`;
|
|
348
1358
|
const results = [
|
|
349
1359
|
["wiki/migration/review.md", (0, workspace_1.writeManaged)("wiki/migration/review.md", review)],
|
|
350
1360
|
["wiki/migration/verification.md", (0, workspace_1.writeManaged)("wiki/migration/verification.md", verification)],
|
|
1361
|
+
["wiki/migration/bulk-review.md", (0, workspace_1.writeManaged)("wiki/migration/bulk-review.md", bulkReview)],
|
|
351
1362
|
];
|
|
1363
|
+
if (complete) {
|
|
1364
|
+
results.push(...pruneCompletedMigrationJunk(legacyRoot));
|
|
1365
|
+
}
|
|
352
1366
|
console.log("Project wiki migration review complete.");
|
|
353
1367
|
for (const [relativePath, status] of results)
|
|
354
1368
|
console.log(`${String(status).padEnd(7)} ${relativePath}`);
|