@imenam/mcp-documentor 1.0.62 → 1.0.63
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/shared/schemas/doc.schema.d.ts +4 -0
- package/dist/shared/schemas/doc.schema.js +3 -1
- package/dist/src/index.js +12 -32
- package/dist/src/tools/check-freshness.js +32 -18
- package/dist/src/tools/get-doc-metadata.js +2 -0
- package/dist/src/tools/get-doc.js +16 -4
- package/dist/src/tools/git-diff-doc.js +3 -1
- package/dist/src/tools/list-docs.js +3 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
|
@@ -65,6 +65,8 @@ export interface DocSummary {
|
|
|
65
65
|
updatedAt: string;
|
|
66
66
|
tags: string[];
|
|
67
67
|
isStale: boolean;
|
|
68
|
+
/** True when tracked entries have no snapshot and freshness cannot be computed. */
|
|
69
|
+
isBroken?: boolean;
|
|
68
70
|
folder?: string;
|
|
69
71
|
}
|
|
70
72
|
export interface DocMatch {
|
|
@@ -81,11 +83,13 @@ export declare const FreshnessReportSchema: z.ZodObject<{
|
|
|
81
83
|
stale: "stale";
|
|
82
84
|
no_tracking: "no_tracking";
|
|
83
85
|
unverifiable: "unverifiable";
|
|
86
|
+
broken: "broken";
|
|
84
87
|
}>;
|
|
85
88
|
modified: z.ZodArray<z.ZodString>;
|
|
86
89
|
missing: z.ZodArray<z.ZodString>;
|
|
87
90
|
unchanged: z.ZodArray<z.ZodString>;
|
|
88
91
|
anchors_lost: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
92
|
+
broken: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
89
93
|
total: z.ZodNumber;
|
|
90
94
|
}, z.core.$strip>;
|
|
91
95
|
export type FreshnessReport = z.infer<typeof FreshnessReportSchema>;
|
|
@@ -36,11 +36,13 @@ export const DocSchema = z.object({
|
|
|
36
36
|
updatedAt: z.string(),
|
|
37
37
|
});
|
|
38
38
|
export const FreshnessReportSchema = z.object({
|
|
39
|
-
status: z.enum(["up_to_date", "stale", "no_tracking", "unverifiable"]),
|
|
39
|
+
status: z.enum(["up_to_date", "stale", "no_tracking", "unverifiable", "broken"]),
|
|
40
40
|
modified: z.array(z.string()),
|
|
41
41
|
missing: z.array(z.string()),
|
|
42
42
|
unchanged: z.array(z.string()),
|
|
43
43
|
anchors_lost: z.array(z.string()).default([]),
|
|
44
|
+
/** Entries with neither sections nor contentHash (pre hash-based tracking) — cannot be checked until re-tracked. */
|
|
45
|
+
broken: z.array(z.string()).default([]),
|
|
44
46
|
total: z.number(),
|
|
45
47
|
});
|
|
46
48
|
export const CreateDocArgsSchema = z.object({
|
package/dist/src/index.js
CHANGED
|
@@ -159,15 +159,9 @@ async function handleGetInitialState(msg) {
|
|
|
159
159
|
for (const doc of docs) {
|
|
160
160
|
const full = await storageEngine.getDoc(doc.id); // doc.id is now the full path
|
|
161
161
|
if (full && full.tracked_files.length > 0) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const report = computeFreshness(full.tracked_files, config.currentProject);
|
|
166
|
-
doc.isStale = report.status === "stale";
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
doc.isStale = true;
|
|
170
|
-
}
|
|
162
|
+
const report = computeFreshness(full.tracked_files, config.currentProject);
|
|
163
|
+
doc.isStale = report.status === "stale" || report.status === "broken";
|
|
164
|
+
doc.isBroken = report.status === "broken";
|
|
171
165
|
}
|
|
172
166
|
}
|
|
173
167
|
const bundles = storageEngine.getBundles();
|
|
@@ -204,13 +198,9 @@ async function handleUpdateProject(msg) {
|
|
|
204
198
|
for (const doc of docs) {
|
|
205
199
|
const full = await storageEngine.getDoc(doc.id);
|
|
206
200
|
if (full && full.tracked_files.length > 0) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
catch {
|
|
212
|
-
doc.isStale = true;
|
|
213
|
-
}
|
|
201
|
+
const report = computeFreshness(full.tracked_files, project);
|
|
202
|
+
doc.isStale = report.status === "stale" || report.status === "broken";
|
|
203
|
+
doc.isBroken = report.status === "broken";
|
|
214
204
|
}
|
|
215
205
|
}
|
|
216
206
|
broadcastToGUI({
|
|
@@ -250,13 +240,9 @@ async function handleListDocs(msg) {
|
|
|
250
240
|
for (const doc of docs) {
|
|
251
241
|
const full = await storageEngine.getDoc(doc.id);
|
|
252
242
|
if (full && full.tracked_files.length > 0) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
doc.isStale = true;
|
|
259
|
-
}
|
|
243
|
+
const report = computeFreshness(full.tracked_files, currentProject);
|
|
244
|
+
doc.isStale = report.status === "stale" || report.status === "broken";
|
|
245
|
+
doc.isBroken = report.status === "broken";
|
|
260
246
|
}
|
|
261
247
|
}
|
|
262
248
|
broadcastToGUI({
|
|
@@ -368,15 +354,9 @@ async function handleCreateDocIPC(msg) {
|
|
|
368
354
|
for (const doc of docs) {
|
|
369
355
|
const full = await storageEngine.getDoc(doc.id);
|
|
370
356
|
if (full && full.tracked_files.length > 0) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const report = computeFreshness(full.tracked_files, config.currentProject);
|
|
375
|
-
doc.isStale = report.status === "stale";
|
|
376
|
-
}
|
|
377
|
-
catch {
|
|
378
|
-
doc.isStale = true;
|
|
379
|
-
}
|
|
357
|
+
const report = computeFreshness(full.tracked_files, config.currentProject);
|
|
358
|
+
doc.isStale = report.status === "stale" || report.status === "broken";
|
|
359
|
+
doc.isBroken = report.status === "broken";
|
|
380
360
|
}
|
|
381
361
|
}
|
|
382
362
|
broadcastToGUI({
|
|
@@ -8,12 +8,13 @@ export function computeFileHash(absolutePath) {
|
|
|
8
8
|
}
|
|
9
9
|
export function computeFreshness(trackedFiles, project) {
|
|
10
10
|
if (!trackedFiles || trackedFiles.length === 0) {
|
|
11
|
-
return { status: "no_tracking", modified: [], missing: [], unchanged: [], anchors_lost: [], total: 0 };
|
|
11
|
+
return { status: "no_tracking", modified: [], missing: [], unchanged: [], anchors_lost: [], broken: [], total: 0 };
|
|
12
12
|
}
|
|
13
13
|
const modified = [];
|
|
14
14
|
const missing = [];
|
|
15
15
|
const unchanged = [];
|
|
16
16
|
const anchors_lost = [];
|
|
17
|
+
const broken = [];
|
|
17
18
|
for (const tracked of trackedFiles) {
|
|
18
19
|
// A relative path with no code root configured on this machine is not
|
|
19
20
|
// resolvable here — count it as absent so we end up "unverifiable" rather
|
|
@@ -61,8 +62,10 @@ export function computeFreshness(trackedFiles, project) {
|
|
|
61
62
|
}
|
|
62
63
|
else {
|
|
63
64
|
// Hash comparison is the only freshness system. An entry with neither
|
|
64
|
-
// sections nor contentHash predates it and cannot be checked
|
|
65
|
-
|
|
65
|
+
// sections nor contentHash predates it and cannot be checked — it is
|
|
66
|
+
// reported as broken (never checked by any other means) so readers can
|
|
67
|
+
// still expose the doc and its tracked list to the agent for re-tracking.
|
|
68
|
+
broken.push(tracked.path);
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
// When every tracked file is absent, the source code simply isn't present on
|
|
@@ -72,15 +75,18 @@ export function computeFreshness(trackedFiles, project) {
|
|
|
72
75
|
// change signal (likely deletions) and still counts as stale.
|
|
73
76
|
const allMissing = missing.length === trackedFiles.length;
|
|
74
77
|
if (allMissing) {
|
|
75
|
-
return { status: "unverifiable", modified, missing, unchanged, anchors_lost, total: trackedFiles.length };
|
|
78
|
+
return { status: "unverifiable", modified, missing, unchanged, anchors_lost, broken, total: trackedFiles.length };
|
|
76
79
|
}
|
|
77
80
|
const isStale = modified.length > 0 || missing.length > 0 || anchors_lost.length > 0;
|
|
81
|
+
// Broken tracking outranks everything: no freshness verdict is trustworthy
|
|
82
|
+
// until the broken entries are re-tracked.
|
|
78
83
|
return {
|
|
79
|
-
status: isStale ? "stale" : "up_to_date",
|
|
84
|
+
status: broken.length > 0 ? "broken" : isStale ? "stale" : "up_to_date",
|
|
80
85
|
modified,
|
|
81
86
|
missing,
|
|
82
87
|
unchanged,
|
|
83
88
|
anchors_lost,
|
|
89
|
+
broken,
|
|
84
90
|
total: trackedFiles.length,
|
|
85
91
|
};
|
|
86
92
|
}
|
|
@@ -91,11 +97,23 @@ export function formatFreshnessReport(title, report) {
|
|
|
91
97
|
if (report.status === "unverifiable") {
|
|
92
98
|
return `UNVERIFIABLE: "${title}" — none of the ${report.total} tracked file(s) are present on this machine, so freshness cannot be checked here. This is expected when the documentation is cloned without its source codebase.`;
|
|
93
99
|
}
|
|
94
|
-
let text
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
let text;
|
|
101
|
+
if (report.status === "broken") {
|
|
102
|
+
text = `BROKEN TRACKING: "${title}" — some tracked entries have no content snapshot and cannot be checked.\n`;
|
|
103
|
+
}
|
|
104
|
+
else if (report.status === "stale") {
|
|
105
|
+
text = `STALE: "${title}" — tracked files have changed since the documentation was last updated.\n`;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
text = `UP TO DATE: "${title}" — no tracked files have been modified.\n`;
|
|
109
|
+
}
|
|
97
110
|
const anchorLostCount = report.anchors_lost?.length ?? 0;
|
|
98
|
-
|
|
111
|
+
const brokenCount = report.broken?.length ?? 0;
|
|
112
|
+
text += `\nSummary: ${report.modified.length} modified, ${report.missing.length} missing, ${anchorLostCount} with lost anchors, ${brokenCount} with broken tracking, ${report.unchanged.length} unchanged out of ${report.total} tracked file(s).`;
|
|
113
|
+
if (brokenCount > 0) {
|
|
114
|
+
text += `\n\n⚠ Files with broken tracking — no snapshot to compare against:\n${report.broken.map((f) => ` - ${f}`).join("\n")}`;
|
|
115
|
+
text += `\n → These entries predate hash-based tracking. Re-read the source files and re-declare them in tracked_files via update_doc, or call refresh_file_snapshot if the doc content is still accurate.`;
|
|
116
|
+
}
|
|
99
117
|
if (report.modified.length > 0) {
|
|
100
118
|
text += `\n\nModified files (call refresh_file_snapshot to acknowledge):\n${report.modified.map((f) => ` - ${f}`).join("\n")}`;
|
|
101
119
|
}
|
|
@@ -125,16 +143,12 @@ async function scanProjectFreshness(storageEngine, project) {
|
|
|
125
143
|
const doc = await storageEngine.getDoc(summary.id, project);
|
|
126
144
|
if (!doc)
|
|
127
145
|
continue;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
catch (err) {
|
|
133
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
134
|
-
scan.broken.push(` - **${doc.title}** [${doc.id}]: ${message}`);
|
|
135
|
-
continue;
|
|
146
|
+
const report = computeFreshness(doc.tracked_files, project);
|
|
147
|
+
if (report.status === "broken") {
|
|
148
|
+
const files = report.broken.map((f) => ` • no snapshot: ${f}`).join("\n");
|
|
149
|
+
scan.broken.push(` - **${doc.title}** [${doc.id}]\n${files}`);
|
|
136
150
|
}
|
|
137
|
-
if (report.status === "no_tracking") {
|
|
151
|
+
else if (report.status === "no_tracking") {
|
|
138
152
|
scan.noTracking.push(doc.title);
|
|
139
153
|
}
|
|
140
154
|
else if (report.status === "unverifiable") {
|
|
@@ -13,6 +13,8 @@ export async function getDocMetadata(storageEngine, args) {
|
|
|
13
13
|
const { doc, project } = resolved;
|
|
14
14
|
const report = computeFreshness(doc.tracked_files, project);
|
|
15
15
|
const fileStatusIcon = (filePath) => {
|
|
16
|
+
if (report.broken.includes(filePath))
|
|
17
|
+
return "⚠ broken tracking (no snapshot — re-declare via update_doc or run refresh_file_snapshot)";
|
|
16
18
|
if (report.modified.includes(filePath))
|
|
17
19
|
return "✗ modified";
|
|
18
20
|
if (report.missing.includes(filePath))
|
|
@@ -29,8 +29,20 @@ export async function getDoc(storageEngine, args) {
|
|
|
29
29
|
}).join("\n")
|
|
30
30
|
: " (none)";
|
|
31
31
|
const freshnessReport = computeFreshness(doc.tracked_files, resolvedProject);
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
let stalenessBanner = "";
|
|
33
|
+
if (freshnessReport.status === "broken") {
|
|
34
|
+
stalenessBanner = [
|
|
35
|
+
`> [!WARNING]`,
|
|
36
|
+
`> **Le suivi de fichiers de cette documentation est cassé.**`,
|
|
37
|
+
`> Les entrées suivantes n'ont aucun snapshot de contenu et ne peuvent pas être vérifiées :`,
|
|
38
|
+
...freshnessReport.broken.map((f) => `> - \`${f}\` *(aucun snapshot)*`),
|
|
39
|
+
`>`,
|
|
40
|
+
`> La fraîcheur de ce document est invérifiable tant que ces entrées ne sont pas re-suivies. Relisez les fichiers sources et re-déclarez-les via \`update_doc\` (tracked_files), ou appelez \`refresh_file_snapshot\` si le contenu ci-dessous est encore exact.`,
|
|
41
|
+
``,
|
|
42
|
+
].join("\n");
|
|
43
|
+
}
|
|
44
|
+
else if (freshnessReport.status === "stale") {
|
|
45
|
+
stalenessBanner = [
|
|
34
46
|
`> [!WARNING]`,
|
|
35
47
|
`> **Cette documentation est potentiellement obsolète.**`,
|
|
36
48
|
`> Les fichiers suivants ont été modifiés depuis la dernière mise à jour de ce document :`,
|
|
@@ -41,8 +53,8 @@ export async function getDoc(storageEngine, args) {
|
|
|
41
53
|
`>`,
|
|
42
54
|
`> Vérifiez si le contenu ci-dessous est toujours valide. Si nécessaire, utilisez \`git_diff_doc\` pour voir les changements, puis mettez à jour la documentation avec \`patch_doc\` ou \`update_doc\`.`,
|
|
43
55
|
``,
|
|
44
|
-
].join("\n")
|
|
45
|
-
|
|
56
|
+
].join("\n");
|
|
57
|
+
}
|
|
46
58
|
const text = [
|
|
47
59
|
stalenessBanner,
|
|
48
60
|
`# ${doc.title}`,
|
|
@@ -62,7 +62,9 @@ export async function gitDiffDoc(storageEngine, args) {
|
|
|
62
62
|
}
|
|
63
63
|
else {
|
|
64
64
|
const freshness = computeFreshness(doc.tracked_files, project);
|
|
65
|
-
|
|
65
|
+
// Broken entries (no snapshot) can't be hash-compared, but the git diff
|
|
66
|
+
// is still the best signal the agent has to re-assess them — include them.
|
|
67
|
+
const changedPaths = new Set([...freshness.modified, ...freshness.missing, ...freshness.broken]);
|
|
66
68
|
filesToDiff = doc.tracked_files.filter((tf) => changedPaths.has(tf.path));
|
|
67
69
|
}
|
|
68
70
|
if (filesToDiff.length === 0) {
|
|
@@ -21,7 +21,7 @@ function renderProjectDocs(summaries, totalLabel) {
|
|
|
21
21
|
if (key)
|
|
22
22
|
lines.push(`\n 📁 ${key}`);
|
|
23
23
|
for (const s of groups.get(key)) {
|
|
24
|
-
const staleTag = s.isStale ? " [STALE]" : "";
|
|
24
|
+
const staleTag = s.isBroken ? " [BROKEN TRACKING]" : s.isStale ? " [STALE]" : "";
|
|
25
25
|
const tags = s.tags.length > 0 ? ` (${s.tags.join(", ")})` : "";
|
|
26
26
|
const indent = key ? " " : " ";
|
|
27
27
|
lines.push(`${indent}- ${s.title} [${s.id}]${tags}${staleTag} — updated ${s.updatedAt}`);
|
|
@@ -43,6 +43,7 @@ export async function listDocs(storageEngine, args) {
|
|
|
43
43
|
if (doc && doc.tracked_files.length > 0) {
|
|
44
44
|
const report = computeFreshness(doc.tracked_files, proj);
|
|
45
45
|
summary.isStale = report.status === "stale";
|
|
46
|
+
summary.isBroken = report.status === "broken";
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
if (summaries.length === 0) {
|
|
@@ -61,6 +62,7 @@ export async function listDocs(storageEngine, args) {
|
|
|
61
62
|
if (doc && doc.tracked_files.length > 0) {
|
|
62
63
|
const report = computeFreshness(doc.tracked_files, proj);
|
|
63
64
|
summary.isStale = report.status === "stale";
|
|
65
|
+
summary.isBroken = report.status === "broken";
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
grandTotal += summaries.length;
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const APP_VERSION = "1.0.
|
|
1
|
+
export declare const APP_VERSION = "1.0.63";
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated version file - do not edit manually
|
|
2
|
-
export const APP_VERSION = '1.0.
|
|
2
|
+
export const APP_VERSION = '1.0.63';
|