project-librarian 0.2.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.
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runInstallSkillMode = runInstallSkillMode;
37
+ const fs = __importStar(require("node:fs"));
38
+ const os = __importStar(require("node:os"));
39
+ const path = __importStar(require("node:path"));
40
+ const args_1 = require("./args");
41
+ const skillName = "project-librarian";
42
+ const packageFiles = [
43
+ "SKILL.md",
44
+ "dist",
45
+ "README.md",
46
+ "README.ko.md",
47
+ "README.ja.md",
48
+ "README.zh.md",
49
+ "LICENSE",
50
+ "package.json",
51
+ "agents",
52
+ ];
53
+ function fail(message) {
54
+ console.error(message);
55
+ process.exit(1);
56
+ }
57
+ function installScope() {
58
+ const scope = (0, args_1.argValue)("--scope") || "user";
59
+ if (scope === "user" || scope === "project")
60
+ return scope;
61
+ return fail(`invalid --scope: ${scope}; expected user or project`);
62
+ }
63
+ function installAgents() {
64
+ const value = (0, args_1.argValue)("--agents") || "both";
65
+ const parts = value.split(",").map((item) => item.trim()).filter(Boolean);
66
+ const agents = new Set();
67
+ for (const part of parts) {
68
+ if (part === "both" || part === "all") {
69
+ agents.add("codex");
70
+ agents.add("claude");
71
+ }
72
+ else if (part === "codex" || part === "claude") {
73
+ agents.add(part);
74
+ }
75
+ else {
76
+ return fail(`invalid --agents entry: ${part}; expected codex, claude, or both`);
77
+ }
78
+ }
79
+ return Array.from(agents);
80
+ }
81
+ function packageRoot() {
82
+ return path.resolve(__dirname, "..");
83
+ }
84
+ function userAgentRoot(agent) {
85
+ const home = os.homedir();
86
+ if (agent === "codex")
87
+ return process.env.CODEX_HOME || path.join(home, ".codex");
88
+ return process.env.CLAUDE_HOME || path.join(home, ".claude");
89
+ }
90
+ function installTarget(agent, scope) {
91
+ const base = scope === "user" ? userAgentRoot(agent) : path.join(process.cwd(), agent === "codex" ? ".codex" : ".claude");
92
+ return path.join(base, "skills", skillName);
93
+ }
94
+ function sameFile(source, target) {
95
+ if (!fs.existsSync(target) || !fs.statSync(target).isFile())
96
+ return false;
97
+ return fs.readFileSync(source).equals(fs.readFileSync(target));
98
+ }
99
+ function copyPath(source, target, dryRun) {
100
+ if (!fs.existsSync(source))
101
+ fail(`missing package file: ${source}`);
102
+ const existed = fs.existsSync(target);
103
+ if (dryRun)
104
+ return "dry-run";
105
+ const sourceStat = fs.statSync(source);
106
+ if (sourceStat.isDirectory()) {
107
+ fs.mkdirSync(target, { recursive: true });
108
+ for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
109
+ copyPath(path.join(source, entry.name), path.join(target, entry.name), false);
110
+ }
111
+ return existed ? "updated" : "created";
112
+ }
113
+ fs.mkdirSync(path.dirname(target), { recursive: true });
114
+ if (sameFile(source, target))
115
+ return "exists";
116
+ fs.copyFileSync(source, target);
117
+ fs.chmodSync(target, sourceStat.mode);
118
+ return existed ? "updated" : "created";
119
+ }
120
+ function runInstallSkillMode() {
121
+ const scope = installScope();
122
+ const agents = installAgents();
123
+ const dryRun = args_1.args.has("--dry-run");
124
+ const root = packageRoot();
125
+ const rows = [];
126
+ for (const agent of agents) {
127
+ const targetRoot = installTarget(agent, scope);
128
+ for (const relativePath of packageFiles) {
129
+ const source = path.join(root, relativePath);
130
+ const target = path.join(targetRoot, relativePath);
131
+ rows.push([`${agent}:${scope}:${path.join(targetRoot, relativePath)}`, copyPath(source, target, dryRun)]);
132
+ }
133
+ }
134
+ console.log(`Project Librarian skill ${dryRun ? "install dry-run" : "install"} complete.`);
135
+ console.log(`scope: ${scope}`);
136
+ console.log(`agents: ${agents.join(", ")}`);
137
+ console.log("note: install-skill only installs the reusable skill files; it does not create or update AGENTS.md, CLAUDE.md, wiki/, .codex/hooks.json, or .claude/settings.json.");
138
+ console.log("next: agents should run the installed local project-librarian runner from the target project root; direct shell users can still run `npx project-librarian` when registry access is available.");
139
+ for (const [label, status] of rows) {
140
+ console.log(`${status.padEnd(7)} ${label}`);
141
+ }
142
+ }
@@ -0,0 +1,356 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.classifyMarkdown = classifyMarkdown;
37
+ exports.markdownTableRows = markdownTableRows;
38
+ exports.buildInbox = buildInbox;
39
+ exports.timestampSuffix = timestampSuffix;
40
+ exports.prepareMigrationMode = prepareMigrationMode;
41
+ exports.migrationTargetForKind = migrationTargetForKind;
42
+ exports.runMigrationMode = runMigrationMode;
43
+ exports.normalizeMigrationStatus = normalizeMigrationStatus;
44
+ exports.isMigrationInboxStatus = isMigrationInboxStatus;
45
+ exports.migrationInboxStatusMap = migrationInboxStatusMap;
46
+ exports.semanticStatusForInboxStatus = semanticStatusForInboxStatus;
47
+ exports.runReviewMigrationMode = runReviewMigrationMode;
48
+ const fs = __importStar(require("node:fs"));
49
+ const workspace_1 = require("./workspace");
50
+ const templates_1 = require("./templates");
51
+ const wiki_files_1 = require("./wiki-files");
52
+ function classifyMarkdown(relativePath, text) {
53
+ const haystack = `${relativePath}\n${text.slice(0, 8000)}`.toLowerCase();
54
+ const hasDecisionSignal = /\b(adr|decision|decisions|rejected|alternative|tradeoff|rationale)\b|결정|기각|대안|재검토/.test(haystack);
55
+ const hasSourceSignal = /\b(source|sources|reference|references|bibliography|citation|citations|research|paper|article|link)\b|출처|참고|자료|링크/.test(haystack);
56
+ const hasCanonicalSignal = /\b(prd|brief|spec|requirements|roadmap|architecture|api|data model|policy|scope|goal|goals|user|users|persona|scenario|success)\b|정본|요구사항|기획|범위|목표|사용자|시나리오|성공/.test(haystack);
57
+ if (hasDecisionSignal)
58
+ return "decision";
59
+ if (hasSourceSignal)
60
+ return "source";
61
+ if (hasCanonicalSignal)
62
+ return "canonical";
63
+ if (/^(docs|documentation|wiki|notes|knowledge|specs)\//.test(relativePath))
64
+ return "canonical";
65
+ return "other";
66
+ }
67
+ function markdownTableCell(value) {
68
+ return String(value).replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
69
+ }
70
+ function markdownTableRows(items) {
71
+ if (items.length === 0)
72
+ return "| none | - | - | - |\n";
73
+ return items.map((item) => `| ${markdownTableCell(item.path)} | ${markdownTableCell(item.title)} | ${markdownTableCell(item.summary)} | pending |`).join("\n") + "\n";
74
+ }
75
+ function buildInbox(title, description, items) {
76
+ return `${(0, templates_1.metadata)("migration-inbox", "medium", "wiki/meta/wiki-ops-v1-decisions.md", "migration candidates are adopted or rescanned")}
77
+ # ${title}
78
+
79
+ ## TL;DR
80
+
81
+ - ${description}
82
+ - Original files are preserved under a \`wiki_legacy\` directory.
83
+ - Review each item, rewrite useful meaning into canonical/decision/source/meta docs, then set status to adopted/rejected/resolved/needs-human-review.
84
+ - Status values: pending, adopted, rejected, resolved, needs-human-review.
85
+
86
+ | Source | Title | Summary | Status |
87
+ | --- | --- | --- | --- |
88
+ ${markdownTableRows(items)}`;
89
+ }
90
+ function timestampSuffix() {
91
+ return new Date().toISOString().replace(/[-:]/g, "").replace(/\..+$/, "").replace("T", "_");
92
+ }
93
+ function nextLegacyPath() {
94
+ if (!(0, workspace_1.exists)("wiki_legacy"))
95
+ return "wiki_legacy";
96
+ const base = `wiki_legacy_${timestampSuffix()}`;
97
+ if (!(0, workspace_1.exists)(base))
98
+ return base;
99
+ for (let counter = 2; counter < 1000; counter += 1) {
100
+ const candidate = `${base}_${counter}`;
101
+ if (!(0, workspace_1.exists)(candidate))
102
+ return candidate;
103
+ }
104
+ throw new Error(`could not find an available wiki_legacy path for ${base}`);
105
+ }
106
+ function prepareMigrationMode() {
107
+ if ((0, workspace_1.exists)("wiki")) {
108
+ const legacyPath = nextLegacyPath();
109
+ fs.renameSync((0, workspace_1.abs)("wiki"), (0, workspace_1.abs)(legacyPath));
110
+ return { legacyPath, note: `moved wiki to ${legacyPath}` };
111
+ }
112
+ if ((0, workspace_1.exists)("wiki_legacy"))
113
+ return { legacyPath: "wiki_legacy", note: "using existing wiki_legacy" };
114
+ return { legacyPath: "", note: "no existing wiki directory to migrate" };
115
+ }
116
+ function migrationTargetForKind(kind) {
117
+ if (kind === "decision")
118
+ return "wiki/decisions/migration-inbox.md";
119
+ if (kind === "source")
120
+ return "wiki/sources/migration-inbox.md";
121
+ return "wiki/canonical/migration-inbox.md";
122
+ }
123
+ function runMigrationMode(migrationState) {
124
+ const legacyPath = migrationState.legacyPath;
125
+ 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 items = markdownFiles.map((file) => {
127
+ const text = (0, workspace_1.read)(file.path);
128
+ return {
129
+ path: file.path,
130
+ legacyPath: file.basePath,
131
+ kind: classifyMarkdown(file.path, text),
132
+ title: (0, wiki_files_1.firstHeading)(text, file.path),
133
+ summary: (0, wiki_files_1.compactSummary)(text),
134
+ bytes: Buffer.byteLength(text, "utf8"),
135
+ };
136
+ });
137
+ const byKind = {
138
+ canonical: items.filter((item) => item.kind === "canonical"),
139
+ decision: items.filter((item) => item.kind === "decision"),
140
+ source: items.filter((item) => item.kind === "source"),
141
+ other: items.filter((item) => item.kind === "other"),
142
+ };
143
+ const inventoryRows = items.length === 0
144
+ ? "| none | - | - | 0 | - |\n"
145
+ : items.map((item) => `| ${markdownTableCell(item.path)} | ${item.kind} | ${markdownTableCell(item.title)} | ${item.bytes} | ${markdownTableCell(item.summary)} |`).join("\n") + "\n";
146
+ const inventory = `${(0, templates_1.metadata)("migration-inventory", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "migration scan is rerun")}
147
+ # Migration Inventory
148
+
149
+ ## TL;DR
150
+
151
+ - Generated: ${workspace_1.today}
152
+ - Legacy root: ${legacyPath || "none"}
153
+ - Markdown files: ${items.length}
154
+ - Legacy files are not copied directly into the new wiki; they are mapped to rewrite inboxes.
155
+
156
+ | Legacy Source | Classification | Title | Size (bytes) | Summary |
157
+ | --- | --- | --- | ---: | --- |
158
+ ${inventoryRows}`;
159
+ const plan = `${(0, templates_1.metadata)("migration-plan", "short", "wiki/meta/wiki-ops-v1-decisions.md", "migration procedure or status changes")}
160
+ # Migration Plan
161
+
162
+ ## TL;DR
163
+
164
+ - Generated: ${workspace_1.today}
165
+ - Preparation: ${migrationState.note}
166
+ - The new \`./wiki\` uses the standard structure.
167
+ - Next step: review inbox items and absorb useful meaning into canonical, decisions, sources, or meta docs.
168
+
169
+ ## Counts
170
+
171
+ | Classification | Count |
172
+ | --- | ---: |
173
+ | canonical candidates | ${byKind.canonical.length} |
174
+ | decision candidates | ${byKind.decision.length} |
175
+ | source candidates | ${byKind.source.length} |
176
+ | other candidates | ${byKind.other.length} |
177
+ `;
178
+ const verificationRows = items.length === 0
179
+ ? "| none | - | - | pass | - |\n"
180
+ : items.map((item) => `| ${markdownTableCell(item.path)} | ${item.kind} | ${migrationTargetForKind(item.kind)} | mapped | pending semantic rewrite |`).join("\n") + "\n";
181
+ 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
+ # Migration Verification
183
+
184
+ ## TL;DR
185
+
186
+ - legacy root: ${legacyPath || "none"}
187
+ - legacy markdown files: ${items.length}
188
+ - mapped files: ${items.length}
189
+ - coverage: ${items.length === markdownFiles.length ? "pass" : "fail"}
190
+ - This verifies file coverage only. Semantic completeness is confirmed after inbox statuses are resolved.
191
+
192
+ | Legacy Source | Classification | New Wiki Target | Coverage | Semantic Status |
193
+ | --- | --- | --- | --- | --- |
194
+ ${verificationRows}`;
195
+ const migrationStartupBlock = `<!-- PROJECT-WIKI-MIGRATION:START -->
196
+ ## Migration State
197
+
198
+ - ${workspace_1.today}: preserved existing wiki at \`${legacyPath || "no wiki_legacy"}\` and regenerated the standard wiki structure.
199
+ - Scanned ${items.length} legacy markdown files and created migration inventory, plan, verification, and inbox files.
200
+ - Do not delete \`${legacyPath || "wiki_legacy"}\` until all migration inbox items are adopted/rejected/resolved and needs-human-review is 0.
201
+ <!-- PROJECT-WIKI-MIGRATION:END -->`;
202
+ const migrationIndexBlock = `<!-- PROJECT-WIKI-MIGRATION:START -->
203
+ ## Migration
204
+
205
+ - [[migration/plan]]
206
+ - Read when: migration procedure or status matters.
207
+ - Update when: migration procedure or state changes.
208
+ - Token budget: short.
209
+ - [[migration/inventory]]
210
+ - Read when: legacy markdown file list and classification matter.
211
+ - Update when: migration is rescanned.
212
+ - Token budget: on-demand.
213
+ - [[migration/verification]]
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.
233
+ <!-- PROJECT-WIKI-MIGRATION:END -->`;
234
+ const results = [];
235
+ (0, workspace_1.mkdirp)("wiki/migration");
236
+ results.push(["wiki/migration/inventory.md", (0, workspace_1.writeManaged)("wiki/migration/inventory.md", inventory)]);
237
+ results.push(["wiki/migration/plan.md", (0, workspace_1.writeManaged)("wiki/migration/plan.md", plan)]);
238
+ results.push(["wiki/migration/verification.md", (0, workspace_1.writeManaged)("wiki/migration/verification.md", verification)]);
239
+ 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
+ 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
+ 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
+ results.push(["wiki/startup.md migration state", (0, workspace_1.upsertMarkedSection)("wiki/startup.md", "<!-- PROJECT-WIKI-MIGRATION:START -->", "<!-- PROJECT-WIKI-MIGRATION:END -->", migrationStartupBlock)]);
243
+ 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: items.length, legacyPath };
245
+ }
246
+ function normalizeMigrationStatus(status) {
247
+ const value = String(status || "").trim().toLowerCase();
248
+ if (isMigrationInboxStatus(value))
249
+ return value;
250
+ if (value.includes("adopt"))
251
+ return "adopted";
252
+ if (value.includes("reject"))
253
+ return "rejected";
254
+ if (value.includes("resolve"))
255
+ return "resolved";
256
+ if (value.includes("human"))
257
+ return "needs-human-review";
258
+ return "pending";
259
+ }
260
+ function isMigrationInboxStatus(value) {
261
+ return ["adopted", "rejected", "resolved", "needs-human-review", "pending"].includes(value);
262
+ }
263
+ function migrationInboxStatusMap() {
264
+ const inboxFiles = ["wiki/canonical/migration-inbox.md", "wiki/decisions/migration-inbox.md", "wiki/sources/migration-inbox.md"];
265
+ const statuses = new Map();
266
+ for (const file of inboxFiles) {
267
+ if (!(0, workspace_1.exists)(file))
268
+ continue;
269
+ for (const cells of (0, wiki_files_1.parseMarkdownTableRows)((0, workspace_1.read)(file), 4)) {
270
+ const source = cells[0];
271
+ if (!source)
272
+ continue;
273
+ statuses.set(source, { status: normalizeMigrationStatus(cells[3]), inbox: file });
274
+ }
275
+ }
276
+ return statuses;
277
+ }
278
+ function semanticStatusForInboxStatus(status) {
279
+ if (["adopted", "rejected", "resolved", "needs-human-review"].includes(status))
280
+ return status;
281
+ return "pending semantic rewrite";
282
+ }
283
+ function runReviewMigrationMode() {
284
+ if (!(0, workspace_1.exists)("wiki/migration/verification.md")) {
285
+ console.error("missing wiki/migration/verification.md; run --migrate first");
286
+ process.exit(1);
287
+ }
288
+ const verificationText = (0, workspace_1.read)("wiki/migration/verification.md");
289
+ const verificationRows = (0, wiki_files_1.parseMarkdownTableRows)(verificationText, 5).map((cells) => ({
290
+ legacyPath: cells[0] ?? "",
291
+ kind: cells[1] ?? "",
292
+ target: cells[2] ?? "",
293
+ coverage: cells[3] ?? "",
294
+ }));
295
+ const inboxStatuses = migrationInboxStatusMap();
296
+ const reviewedRows = verificationRows.map((row) => {
297
+ const inbox = inboxStatuses.get(row.legacyPath);
298
+ const status = inbox ? inbox.status : "needs-human-review";
299
+ return { ...row, inboxStatus: status, semanticStatus: semanticStatusForInboxStatus(status), note: inbox ? inbox.inbox : "missing migration inbox row" };
300
+ });
301
+ const counts = reviewedRows.reduce((acc, row) => {
302
+ acc[row.inboxStatus] = (acc[row.inboxStatus] || 0) + 1;
303
+ return acc;
304
+ }, {});
305
+ const pending = counts.pending || 0;
306
+ const needsHuman = counts["needs-human-review"] || 0;
307
+ const complete = pending === 0 && needsHuman === 0;
308
+ const reviewRows = reviewedRows.length === 0
309
+ ? "| none | - | - | - | - |\n"
310
+ : reviewedRows.map((row) => `| ${markdownTableCell(row.legacyPath)} | ${row.kind} | ${row.inboxStatus} | ${row.semanticStatus} | ${markdownTableCell(row.note)} |`).join("\n") + "\n";
311
+ const review = `${(0, templates_1.metadata)("migration-review", "on-demand", "wiki/meta/wiki-ops-v1-decisions.md", "migration inbox statuses change")}
312
+ # Migration Review
313
+
314
+ ## TL;DR
315
+
316
+ - generated: ${workspace_1.today}
317
+ - total legacy rows: ${reviewedRows.length}
318
+ - adopted: ${counts.adopted || 0}
319
+ - rejected: ${counts.rejected || 0}
320
+ - resolved: ${counts.resolved || 0}
321
+ - pending: ${pending}
322
+ - needs-human-review: ${needsHuman}
323
+ - semantic migration complete: ${complete ? "yes" : "no"}
324
+
325
+ | Legacy Source | Classification | Inbox Status | Semantic Status | Evidence |
326
+ | --- | --- | --- | --- | --- |
327
+ ${reviewRows}`;
328
+ const verificationRowsText = reviewedRows.length === 0
329
+ ? "| none | - | - | pass | - |\n"
330
+ : 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
+ 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
+ # Migration Verification
334
+
335
+ ## TL;DR
336
+
337
+ - legacy root: ${legacyRoot}
338
+ - legacy markdown files: ${reviewedRows.length}
339
+ - mapped files: ${reviewedRows.filter((row) => row.coverage === "mapped").length}
340
+ - coverage: ${reviewedRows.every((row) => row.coverage === "mapped") ? "pass" : "fail"}
341
+ - semantic migration complete: ${complete ? "yes" : "no"}
342
+ - pending: ${pending}
343
+ - needs-human-review: ${needsHuman}
344
+
345
+ | Legacy Source | Classification | New Wiki Target | Coverage | Semantic Status |
346
+ | --- | --- | --- | --- | --- |
347
+ ${verificationRowsText}`;
348
+ const results = [
349
+ ["wiki/migration/review.md", (0, workspace_1.writeManaged)("wiki/migration/review.md", review)],
350
+ ["wiki/migration/verification.md", (0, workspace_1.writeManaged)("wiki/migration/verification.md", verification)],
351
+ ];
352
+ console.log("Project wiki migration review complete.");
353
+ for (const [relativePath, status] of results)
354
+ console.log(`${String(status).padEnd(7)} ${relativePath}`);
355
+ console.log(`summary pending=${pending} needs-human-review=${needsHuman} complete=${complete ? "yes" : "no"}`);
356
+ }