md4ai 0.9.5 → 0.9.7
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/dist/index.bundled.js +73 -6
- package/package.json +1 -1
package/dist/index.bundled.js
CHANGED
|
@@ -233,6 +233,7 @@ import { resolve as resolve2, dirname, join as join2 } from "node:path";
|
|
|
233
233
|
import { homedir as homedir2 } from "node:os";
|
|
234
234
|
async function parseFileReferences(filePath, projectRoot) {
|
|
235
235
|
const refs = [];
|
|
236
|
+
const brokenRefs = [];
|
|
236
237
|
const content = await readFile2(filePath, "utf-8");
|
|
237
238
|
const relPath = filePath.startsWith(projectRoot) ? filePath.slice(projectRoot.length + 1) : filePath;
|
|
238
239
|
const markdownLinkPattern = /\[.*?\]\(([^)]+)\)/g;
|
|
@@ -251,23 +252,40 @@ async function parseFileReferences(filePath, projectRoot) {
|
|
|
251
252
|
const target = match[1];
|
|
252
253
|
if (!target || target.startsWith("http") || target.startsWith("#"))
|
|
253
254
|
continue;
|
|
255
|
+
const baseName = target.split("/").pop() ?? target;
|
|
256
|
+
if (!target.includes("/") && /^[A-Z]/.test(baseName))
|
|
257
|
+
continue;
|
|
254
258
|
const resolved = resolveTarget(target, filePath, projectRoot);
|
|
255
259
|
if (resolved && existsSync2(resolved)) {
|
|
256
260
|
addRef(refs, relPath, resolved, projectRoot);
|
|
261
|
+
} else if (resolved) {
|
|
262
|
+
const targetRel = resolved.startsWith(projectRoot) ? resolved.slice(projectRoot.length + 1) : resolved;
|
|
263
|
+
if (targetRel !== relPath) {
|
|
264
|
+
brokenRefs.push({ from: relPath, to: targetRel, rawRef: target });
|
|
265
|
+
}
|
|
257
266
|
}
|
|
258
267
|
}
|
|
259
268
|
}
|
|
260
269
|
if (filePath.endsWith(".json")) {
|
|
261
|
-
parseJsonPathReferences(content, relPath, projectRoot, refs);
|
|
270
|
+
parseJsonPathReferences(content, relPath, projectRoot, refs, brokenRefs);
|
|
262
271
|
}
|
|
263
272
|
const seen = /* @__PURE__ */ new Set();
|
|
264
|
-
|
|
273
|
+
const dedupedRefs = refs.filter((r) => {
|
|
265
274
|
const key = `${r.from}->${r.to}`;
|
|
266
275
|
if (seen.has(key))
|
|
267
276
|
return false;
|
|
268
277
|
seen.add(key);
|
|
269
278
|
return true;
|
|
270
279
|
});
|
|
280
|
+
const brokenSeen = /* @__PURE__ */ new Set();
|
|
281
|
+
const dedupedBroken = brokenRefs.filter((r) => {
|
|
282
|
+
const key = `${r.from}->${r.to}`;
|
|
283
|
+
if (brokenSeen.has(key))
|
|
284
|
+
return false;
|
|
285
|
+
brokenSeen.add(key);
|
|
286
|
+
return true;
|
|
287
|
+
});
|
|
288
|
+
return { refs: dedupedRefs, brokenRefs: dedupedBroken };
|
|
271
289
|
}
|
|
272
290
|
function resolveTarget(target, sourceFilePath, projectRoot) {
|
|
273
291
|
if (target.startsWith("~")) {
|
|
@@ -279,6 +297,11 @@ function resolveTarget(target, sourceFilePath, projectRoot) {
|
|
|
279
297
|
if (target.startsWith(".claude/") || target.startsWith("docs/")) {
|
|
280
298
|
return join2(projectRoot, target);
|
|
281
299
|
}
|
|
300
|
+
if (target.includes("/")) {
|
|
301
|
+
const fromRoot = join2(projectRoot, target);
|
|
302
|
+
if (existsSync2(fromRoot))
|
|
303
|
+
return fromRoot;
|
|
304
|
+
}
|
|
282
305
|
return resolve2(dirname(sourceFilePath), target);
|
|
283
306
|
}
|
|
284
307
|
function addRef(refs, fromRel, resolvedAbsolute, projectRoot) {
|
|
@@ -287,7 +310,7 @@ function addRef(refs, fromRel, resolvedAbsolute, projectRoot) {
|
|
|
287
310
|
refs.push({ from: fromRel, to: targetRel });
|
|
288
311
|
}
|
|
289
312
|
}
|
|
290
|
-
function parseJsonPathReferences(content, fromRel, projectRoot, refs) {
|
|
313
|
+
function parseJsonPathReferences(content, fromRel, projectRoot, refs, brokenRefs) {
|
|
291
314
|
let parsed;
|
|
292
315
|
try {
|
|
293
316
|
parsed = JSON.parse(content);
|
|
@@ -304,6 +327,8 @@ function parseJsonPathReferences(content, fromRel, projectRoot, refs) {
|
|
|
304
327
|
const resolved = join2(projectRoot, relTarget);
|
|
305
328
|
if (existsSync2(resolved)) {
|
|
306
329
|
addRef(refs, fromRel, resolved, projectRoot);
|
|
330
|
+
} else {
|
|
331
|
+
brokenRefs.push({ from: fromRel, to: relTarget, rawRef: relTarget });
|
|
307
332
|
}
|
|
308
333
|
}
|
|
309
334
|
}
|
|
@@ -316,6 +341,8 @@ function parseJsonPathReferences(content, fromRel, projectRoot, refs) {
|
|
|
316
341
|
const resolved = join2(projectRoot, target);
|
|
317
342
|
if (existsSync2(resolved)) {
|
|
318
343
|
addRef(refs, fromRel, resolved, projectRoot);
|
|
344
|
+
} else {
|
|
345
|
+
brokenRefs.push({ from: fromRel, to: target, rawRef: target });
|
|
319
346
|
}
|
|
320
347
|
}
|
|
321
348
|
});
|
|
@@ -1259,11 +1286,13 @@ async function scanProject(projectRoot) {
|
|
|
1259
1286
|
const allFiles = await discoverFiles(projectRoot);
|
|
1260
1287
|
const rootFiles = identifyRoots(allFiles, projectRoot);
|
|
1261
1288
|
const allRefs = [];
|
|
1289
|
+
const allBrokenRefs = [];
|
|
1262
1290
|
for (const file of allFiles) {
|
|
1263
1291
|
const fullPath = file.startsWith("/") ? file : join10(projectRoot, file);
|
|
1264
1292
|
try {
|
|
1265
|
-
const refs = await parseFileReferences(fullPath, projectRoot);
|
|
1293
|
+
const { refs, brokenRefs: brokenRefs2 } = await parseFileReferences(fullPath, projectRoot);
|
|
1266
1294
|
allRefs.push(...refs);
|
|
1295
|
+
allBrokenRefs.push(...brokenRefs2);
|
|
1267
1296
|
} catch {
|
|
1268
1297
|
}
|
|
1269
1298
|
}
|
|
@@ -1273,11 +1302,32 @@ async function scanProject(projectRoot) {
|
|
|
1273
1302
|
const skills = await parseSkills(projectRoot);
|
|
1274
1303
|
const toolings = await detectToolings(projectRoot);
|
|
1275
1304
|
const envManifest = await scanEnvManifest(projectRoot);
|
|
1276
|
-
const
|
|
1305
|
+
const depthMap = /* @__PURE__ */ new Map();
|
|
1306
|
+
const queue = [...rootFiles];
|
|
1307
|
+
for (const r of queue)
|
|
1308
|
+
depthMap.set(r, 0);
|
|
1309
|
+
while (queue.length > 0) {
|
|
1310
|
+
const current = queue.shift();
|
|
1311
|
+
const currentDepth = depthMap.get(current);
|
|
1312
|
+
for (const ref of allRefs) {
|
|
1313
|
+
if (ref.from === current && !depthMap.has(ref.to)) {
|
|
1314
|
+
depthMap.set(ref.to, currentDepth + 1);
|
|
1315
|
+
queue.push(ref.to);
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
const filteredBroken = allBrokenRefs.filter((br) => classifyFileType(br.from) !== "plan");
|
|
1320
|
+
const brokenRefs = filteredBroken.map((br) => ({
|
|
1321
|
+
...br,
|
|
1322
|
+
depth: depthMap.get(br.from) ?? 999
|
|
1323
|
+
}));
|
|
1324
|
+
brokenRefs.sort((a, b) => a.depth - b.depth || a.from.localeCompare(b.from));
|
|
1325
|
+
const scanData = JSON.stringify({ graph, orphans, brokenRefs, skills, staleFiles, toolings, envManifest });
|
|
1277
1326
|
const dataHash = createHash("sha256").update(scanData).digest("hex");
|
|
1278
1327
|
return {
|
|
1279
1328
|
graph,
|
|
1280
1329
|
orphans,
|
|
1330
|
+
brokenRefs,
|
|
1281
1331
|
skills,
|
|
1282
1332
|
staleFiles,
|
|
1283
1333
|
toolings,
|
|
@@ -1595,7 +1645,7 @@ var CURRENT_VERSION;
|
|
|
1595
1645
|
var init_check_update = __esm({
|
|
1596
1646
|
"dist/check-update.js"() {
|
|
1597
1647
|
"use strict";
|
|
1598
|
-
CURRENT_VERSION = true ? "0.9.
|
|
1648
|
+
CURRENT_VERSION = true ? "0.9.7" : "0.0.0-dev";
|
|
1599
1649
|
}
|
|
1600
1650
|
});
|
|
1601
1651
|
|
|
@@ -1764,12 +1814,23 @@ async function mapCommand(path, options) {
|
|
|
1764
1814
|
const result = await scanProject(projectRoot);
|
|
1765
1815
|
console.log(` Files found: ${result.graph.nodes.length}`);
|
|
1766
1816
|
console.log(` References: ${result.graph.edges.length}`);
|
|
1817
|
+
console.log(` Broken refs: ${result.brokenRefs.length}`);
|
|
1767
1818
|
console.log(` Orphans: ${result.orphans.length}`);
|
|
1768
1819
|
console.log(` Stale files: ${result.staleFiles.length}`);
|
|
1769
1820
|
console.log(` Skills: ${result.skills.length}`);
|
|
1770
1821
|
console.log(` Toolings: ${result.toolings.length}`);
|
|
1771
1822
|
console.log(` Env Vars: ${result.envManifest?.variables.length ?? 0} (${result.envManifest ? "manifest found" : "no manifest"})`);
|
|
1772
1823
|
console.log(` Data hash: ${result.dataHash.slice(0, 12)}...`);
|
|
1824
|
+
if (result.brokenRefs.length > 0) {
|
|
1825
|
+
console.log(chalk11.red(`
|
|
1826
|
+
Warning: ${result.brokenRefs.length} broken reference(s) found:`));
|
|
1827
|
+
for (const br of result.brokenRefs.slice(0, 5)) {
|
|
1828
|
+
console.log(chalk11.red(` ${br.from} -> ${br.to}`));
|
|
1829
|
+
}
|
|
1830
|
+
if (result.brokenRefs.length > 5) {
|
|
1831
|
+
console.log(chalk11.red(` ... and ${result.brokenRefs.length - 5} more`));
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1773
1834
|
const outputDir = resolve3(projectRoot, "output");
|
|
1774
1835
|
if (!existsSync7(outputDir)) {
|
|
1775
1836
|
await mkdir2(outputDir, { recursive: true });
|
|
@@ -1823,6 +1884,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1823
1884
|
const { error } = await supabase.from("claude_folders").update({
|
|
1824
1885
|
graph_json: result.graph,
|
|
1825
1886
|
orphans_json: result.orphans,
|
|
1887
|
+
broken_refs_json: result.brokenRefs,
|
|
1826
1888
|
skills_table_json: result.skills,
|
|
1827
1889
|
stale_files_json: result.staleFiles,
|
|
1828
1890
|
env_manifest_json: result.envManifest,
|
|
@@ -1914,6 +1976,7 @@ ${proposedFiles.length} file(s) proposed for deletion:
|
|
|
1914
1976
|
await sb.from("claude_folders").update({
|
|
1915
1977
|
graph_json: result.graph,
|
|
1916
1978
|
orphans_json: result.orphans,
|
|
1979
|
+
broken_refs_json: result.brokenRefs,
|
|
1917
1980
|
skills_table_json: result.skills,
|
|
1918
1981
|
stale_files_json: result.staleFiles,
|
|
1919
1982
|
env_manifest_json: result.envManifest,
|
|
@@ -1989,6 +2052,7 @@ async function syncCommand(options) {
|
|
|
1989
2052
|
await supabase.from("claude_folders").update({
|
|
1990
2053
|
graph_json: result.graph,
|
|
1991
2054
|
orphans_json: result.orphans,
|
|
2055
|
+
broken_refs_json: result.brokenRefs,
|
|
1992
2056
|
skills_table_json: result.skills,
|
|
1993
2057
|
stale_files_json: result.staleFiles,
|
|
1994
2058
|
env_manifest_json: result.envManifest,
|
|
@@ -2024,6 +2088,7 @@ async function syncCommand(options) {
|
|
|
2024
2088
|
await supabase.from("claude_folders").update({
|
|
2025
2089
|
graph_json: result.graph,
|
|
2026
2090
|
orphans_json: result.orphans,
|
|
2091
|
+
broken_refs_json: result.brokenRefs,
|
|
2027
2092
|
skills_table_json: result.skills,
|
|
2028
2093
|
stale_files_json: result.staleFiles,
|
|
2029
2094
|
last_scanned: result.scannedAt,
|
|
@@ -2651,6 +2716,7 @@ async function checkPendingRescans(supabase, deviceId, deviceName) {
|
|
|
2651
2716
|
await supabase.from("claude_folders").update({
|
|
2652
2717
|
graph_json: result.graph,
|
|
2653
2718
|
orphans_json: result.orphans,
|
|
2719
|
+
broken_refs_json: result.brokenRefs,
|
|
2654
2720
|
skills_table_json: result.skills,
|
|
2655
2721
|
stale_files_json: result.staleFiles,
|
|
2656
2722
|
env_manifest_json: result.envManifest,
|
|
@@ -3066,6 +3132,7 @@ Linking "${folder.name}" to this device...
|
|
|
3066
3132
|
const { error: scanErr } = await supabase.from("claude_folders").update({
|
|
3067
3133
|
graph_json: result.graph,
|
|
3068
3134
|
orphans_json: result.orphans,
|
|
3135
|
+
broken_refs_json: result.brokenRefs,
|
|
3069
3136
|
skills_table_json: result.skills,
|
|
3070
3137
|
stale_files_json: result.staleFiles,
|
|
3071
3138
|
env_manifest_json: result.envManifest,
|