@fenglimg/fabric-server 1.0.0 → 1.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.
@@ -9,398 +9,19 @@ import {
9
9
  readHumanLock,
10
10
  readHumanLockDocument,
11
11
  readHumanLockEntry,
12
- readLedger
13
- } from "./chunk-KZO24GUQ.js";
12
+ readLedger,
13
+ runDoctorReport
14
+ } from "./chunk-U3IQH5H6.js";
14
15
 
15
16
  // src/http.ts
16
17
  import { randomUUID as randomUUID2 } from "crypto";
17
- import { appendFile, readFile as readFile3 } from "fs/promises";
18
- import { join as join4 } from "path";
18
+ import { appendFile, readFile as readFile2 } from "fs/promises";
19
+ import { join as join3 } from "path";
19
20
  import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
20
21
  import {
21
22
  StreamableHTTPServerTransport
22
23
  } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
23
24
 
24
- // src/services/doctor.ts
25
- import { createHash } from "crypto";
26
- import { existsSync, readFileSync, readdirSync, statSync } from "fs";
27
- import { readFile } from "fs/promises";
28
- import { isAbsolute, join, posix, resolve } from "path";
29
- import { detectFramework, forensicReportSchema } from "@fenglimg/fabric-shared";
30
- var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx"]);
31
- var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
32
- ".fabric",
33
- ".git",
34
- ".next",
35
- ".turbo",
36
- "Library",
37
- "Temp",
38
- "build",
39
- "coverage",
40
- "dist",
41
- "node_modules"
42
- ]);
43
- var LEDGER_WARN_AFTER_MS = 3 * 24 * 60 * 60 * 1e3;
44
- var LEDGER_ERROR_AFTER_MS = 7 * 24 * 60 * 60 * 1e3;
45
- async function runDoctorReport(target) {
46
- const projectRoot = normalizeTarget(target);
47
- const framework = detectFramework(projectRoot);
48
- const entryPoints = collectEntryPoints(projectRoot);
49
- const [savedForensic, metaSnapshot, humanLockSnapshot, ledgerSnapshot] = await Promise.all([
50
- readSavedForensic(projectRoot),
51
- inspectMetaRevision(projectRoot),
52
- inspectHumanLock(projectRoot),
53
- inspectLedger(projectRoot)
54
- ]);
55
- const checks = [
56
- createForensicCheck(savedForensic, framework, entryPoints),
57
- createFrameworkCheck(savedForensic, framework, entryPoints),
58
- createMetaRevisionCheck(metaSnapshot),
59
- createProtectedPathsCheck(humanLockSnapshot),
60
- createLedgerCheck(ledgerSnapshot)
61
- ];
62
- return {
63
- status: reduceStatus(checks.map((check) => check.status)),
64
- checks,
65
- summary: {
66
- target: projectRoot,
67
- framework: {
68
- kind: framework.kind,
69
- version: framework.version,
70
- subkind: framework.subkind
71
- },
72
- entryPoints,
73
- driftCount: humanLockSnapshot.driftCount,
74
- protectedPathCount: humanLockSnapshot.protectedPathCount,
75
- protectedPathsIntact: humanLockSnapshot.present && humanLockSnapshot.driftCount === 0,
76
- lastLedgerEntryTs: ledgerSnapshot.lastEntryTs,
77
- lastLedgerEntryAgeMs: ledgerSnapshot.lastEntryAgeMs,
78
- metaRevision: metaSnapshot.revision
79
- }
80
- };
81
- }
82
- function createForensicCheck(forensic, framework, entryPoints) {
83
- if (!forensic.present) {
84
- return {
85
- name: "Forensic snapshot",
86
- status: "error",
87
- message: `${forensic.reason} Live scan detects ${formatFramework(framework)} with ${entryPoints.length} entry point${entryPoints.length === 1 ? "" : "s"}.`
88
- };
89
- }
90
- return {
91
- name: "Forensic snapshot",
92
- status: "ok",
93
- message: `Loaded .fabric/forensic.json for ${formatFramework(forensic.report.framework)} with ${forensic.report.entry_points.length} recorded entry point${forensic.report.entry_points.length === 1 ? "" : "s"}.`
94
- };
95
- }
96
- function createFrameworkCheck(forensic, framework, entryPoints) {
97
- if (framework.kind === "unknown") {
98
- return {
99
- name: "Framework fingerprint",
100
- status: "warn",
101
- message: "Unable to identify the project framework from current files."
102
- };
103
- }
104
- if (!forensic.present) {
105
- return {
106
- name: "Framework fingerprint",
107
- status: "warn",
108
- message: `Live detection sees ${formatFramework(framework)} and ${entryPoints.length} entry point${entryPoints.length === 1 ? "" : "s"}, but no forensic baseline exists yet.`
109
- };
110
- }
111
- const matches = forensic.report.framework.kind === framework.kind && forensic.report.framework.version === framework.version && forensic.report.framework.subkind === framework.subkind;
112
- if (!matches) {
113
- return {
114
- name: "Framework fingerprint",
115
- status: "warn",
116
- message: `Forensic baseline says ${formatFramework(forensic.report.framework)}; live scan says ${formatFramework(framework)}.`
117
- };
118
- }
119
- return {
120
- name: "Framework fingerprint",
121
- status: "ok",
122
- message: `Framework baseline matches live scan: ${formatFramework(framework)} \xB7 ${entryPoints.length} current entry point${entryPoints.length === 1 ? "" : "s"}.`
123
- };
124
- }
125
- function createMetaRevisionCheck(snapshot) {
126
- if (!snapshot.present) {
127
- return {
128
- name: "Meta revision",
129
- status: "error",
130
- message: snapshot.unexpectedError ?? "agents.meta.json is missing."
131
- };
132
- }
133
- if (snapshot.driftCount > 0 || snapshot.missingFiles.length > 0) {
134
- const parts = [
135
- `${snapshot.driftCount} tracked AGENTS file drift`,
136
- snapshot.missingFiles.length > 0 ? `${snapshot.missingFiles.length} missing tracked file` : null
137
- ].filter((part) => part !== null);
138
- return {
139
- name: "Meta revision",
140
- status: "error",
141
- message: `agents.meta.json revision ${snapshot.revision} is stale: ${parts.join(" \xB7 ")}.`
142
- };
143
- }
144
- return {
145
- name: "Meta revision",
146
- status: "ok",
147
- message: `agents.meta.json revision ${snapshot.revision} matches ${snapshot.nodeCount} tracked AGENTS file${snapshot.nodeCount === 1 ? "" : "s"}.`
148
- };
149
- }
150
- function createProtectedPathsCheck(snapshot) {
151
- if (!snapshot.present) {
152
- return {
153
- name: "Protected paths",
154
- status: "warn",
155
- message: snapshot.reason
156
- };
157
- }
158
- if (snapshot.driftCount > 0) {
159
- return {
160
- name: "Protected paths",
161
- status: "warn",
162
- message: `${snapshot.driftCount} of ${snapshot.protectedPathCount} protected path${snapshot.protectedPathCount === 1 ? "" : "s"} drifted from approved hashes.`
163
- };
164
- }
165
- return {
166
- name: "Protected paths",
167
- status: "ok",
168
- message: `${snapshot.protectedPathCount} protected path${snapshot.protectedPathCount === 1 ? "" : "s"} intact with zero hash drift.`
169
- };
170
- }
171
- function createLedgerCheck(snapshot) {
172
- if (snapshot.lastEntryTs === null || snapshot.lastEntryAgeMs === null) {
173
- return {
174
- name: "Intent ledger",
175
- status: "warn",
176
- message: "No ledger entries recorded yet."
177
- };
178
- }
179
- if (snapshot.lastEntryAgeMs >= LEDGER_ERROR_AFTER_MS) {
180
- return {
181
- name: "Intent ledger",
182
- status: "error",
183
- message: `Last ledger entry is ${formatAge(snapshot.lastEntryAgeMs)} old (${new Date(snapshot.lastEntryTs).toISOString()}).`
184
- };
185
- }
186
- if (snapshot.lastEntryAgeMs >= LEDGER_WARN_AFTER_MS) {
187
- return {
188
- name: "Intent ledger",
189
- status: "warn",
190
- message: `Last ledger entry is ${formatAge(snapshot.lastEntryAgeMs)} old (${new Date(snapshot.lastEntryTs).toISOString()}).`
191
- };
192
- }
193
- return {
194
- name: "Intent ledger",
195
- status: "ok",
196
- message: `Last ledger entry is ${formatAge(snapshot.lastEntryAgeMs)} old (${snapshot.count} total entr${snapshot.count === 1 ? "y" : "ies"}).`
197
- };
198
- }
199
- async function readSavedForensic(projectRoot) {
200
- const forensicPath = join(projectRoot, ".fabric", "forensic.json");
201
- try {
202
- const raw = await readFile(forensicPath, "utf8");
203
- const parsed = forensicReportSchema.safeParse(JSON.parse(raw));
204
- if (!parsed.success) {
205
- return {
206
- present: false,
207
- reason: "forensic.json is invalid."
208
- };
209
- }
210
- return {
211
- present: true,
212
- report: parsed.data
213
- };
214
- } catch (error) {
215
- if (isMissingFileError(error)) {
216
- return {
217
- present: false,
218
- reason: ".fabric/forensic.json is missing."
219
- };
220
- }
221
- return {
222
- present: false,
223
- reason: error instanceof Error ? error.message : String(error)
224
- };
225
- }
226
- }
227
- async function inspectMetaRevision(projectRoot) {
228
- try {
229
- const meta = readAgentsMeta(projectRoot);
230
- const entries = Object.entries(meta.nodes).sort(([left], [right]) => left.localeCompare(right));
231
- const missingFiles = [];
232
- let driftCount = 0;
233
- const revisionSource = entries.map(([, node]) => {
234
- const absolutePath = join(projectRoot, node.file);
235
- if (!existsSync(absolutePath)) {
236
- missingFiles.push(node.file);
237
- driftCount += 1;
238
- return "missing";
239
- }
240
- const actualHash = sha256(readFileSync(absolutePath, "utf8"));
241
- if (actualHash !== node.hash) {
242
- driftCount += 1;
243
- }
244
- return actualHash;
245
- }).join("");
246
- const revision = sha256(revisionSource);
247
- return {
248
- present: true,
249
- revision: meta.revision,
250
- nodeCount: entries.length,
251
- driftCount: revision === meta.revision ? driftCount : Math.max(driftCount, 1),
252
- missingFiles
253
- };
254
- } catch (error) {
255
- return {
256
- present: false,
257
- revision: null,
258
- nodeCount: 0,
259
- driftCount: 0,
260
- missingFiles: [],
261
- unexpectedError: error instanceof Error ? error.message : String(error)
262
- };
263
- }
264
- }
265
- async function inspectHumanLock(projectRoot) {
266
- try {
267
- const entries = await readHumanLock(projectRoot);
268
- return {
269
- present: true,
270
- driftCount: entries.filter((entry) => entry.drift).length,
271
- protectedPathCount: entries.length
272
- };
273
- } catch (error) {
274
- if (isMissingFileError(error)) {
275
- return {
276
- present: false,
277
- driftCount: 0,
278
- protectedPathCount: 0,
279
- reason: ".fabric/human-lock.json is missing; no protected paths are being tracked."
280
- };
281
- }
282
- return {
283
- present: false,
284
- driftCount: 0,
285
- protectedPathCount: 0,
286
- reason: error instanceof Error ? error.message : String(error)
287
- };
288
- }
289
- }
290
- async function inspectLedger(projectRoot) {
291
- const entries = await readLedger(projectRoot);
292
- const lastEntry = entries.reduce(
293
- (latest, entry) => latest === null || entry.ts > latest ? entry.ts : latest,
294
- null
295
- );
296
- return {
297
- count: entries.length,
298
- lastEntryTs: lastEntry,
299
- lastEntryAgeMs: lastEntry === null ? null : Math.max(Date.now() - lastEntry, 0)
300
- };
301
- }
302
- function normalizeTarget(targetInput) {
303
- return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
304
- }
305
- function collectEntryPoints(root) {
306
- if (!existsSync(root) || !statSync(root).isDirectory()) {
307
- return [];
308
- }
309
- const entries = [];
310
- const stack = [root];
311
- while (stack.length > 0) {
312
- const current = stack.pop();
313
- if (current === void 0) {
314
- continue;
315
- }
316
- for (const entry of readdirSync(current, { withFileTypes: true })) {
317
- const absolutePath = join(current, entry.name);
318
- const relativePath = posix.normalize(absolutePath.slice(root.length + 1).split("\\").join("/"));
319
- if (relativePath.length === 0) {
320
- continue;
321
- }
322
- if (entry.isDirectory()) {
323
- if (IGNORED_DIRECTORIES.has(entry.name)) {
324
- continue;
325
- }
326
- stack.push(absolutePath);
327
- continue;
328
- }
329
- if (!entry.isFile()) {
330
- continue;
331
- }
332
- const reason = getEntryPointReason(relativePath);
333
- if (reason !== null) {
334
- entries.push({
335
- path: relativePath,
336
- reason
337
- });
338
- }
339
- }
340
- }
341
- return entries.sort((left, right) => left.path.localeCompare(right.path));
342
- }
343
- function getEntryPointReason(relativePath) {
344
- const extension = relativePath.slice(relativePath.lastIndexOf("."));
345
- if (!SCRIPT_EXTENSIONS.has(extension)) {
346
- return null;
347
- }
348
- const directory = posix.dirname(relativePath);
349
- const fileName = posix.basename(relativePath);
350
- const fileBase = fileName.slice(0, Math.max(fileName.lastIndexOf("."), 0));
351
- if (directory === "assets/scripts" || directory === "scripts") {
352
- return "top-level script";
353
- }
354
- if (directory === "src" && /^(App|app|index|main)$/.test(fileBase)) {
355
- return "application entry";
356
- }
357
- if ((directory === "app" || directory.startsWith("app/")) && /^(layout|page|route)$/.test(fileBase)) {
358
- return "next app route";
359
- }
360
- if ((directory === "pages" || directory.startsWith("pages/")) && fileName !== "_app.d.ts") {
361
- return "next page route";
362
- }
363
- return null;
364
- }
365
- function reduceStatus(statuses) {
366
- if (statuses.includes("error")) {
367
- return "error";
368
- }
369
- if (statuses.includes("warn")) {
370
- return "warn";
371
- }
372
- return "ok";
373
- }
374
- function formatFramework(framework) {
375
- const pieces = [framework.kind, framework.version !== "unknown" ? framework.version : null, framework.subkind].filter((piece) => piece !== null && piece !== "unknown");
376
- return pieces.length > 0 ? pieces.join(" \xB7 ") : "unknown";
377
- }
378
- function formatAge(ageMs) {
379
- const seconds = Math.floor(ageMs / 1e3);
380
- if (seconds < 60) {
381
- return `${seconds}s`;
382
- }
383
- const minutes = Math.floor(seconds / 60);
384
- if (minutes < 60) {
385
- return `${minutes}m`;
386
- }
387
- const hours = Math.floor(minutes / 60);
388
- if (hours < 48) {
389
- return `${hours}h`;
390
- }
391
- const days = Math.floor(hours / 24);
392
- if (days < 14) {
393
- return `${days}d`;
394
- }
395
- return `${Math.floor(days / 7)}w`;
396
- }
397
- function sha256(content) {
398
- return `sha256:${createHash("sha256").update(content).digest("hex")}`;
399
- }
400
- function isMissingFileError(error) {
401
- return error instanceof Error && "code" in error && error.code === "ENOENT";
402
- }
403
-
404
25
  // src/api/_error.ts
405
26
  function sendError(res, status, code, message, details) {
406
27
  const payload = {
@@ -490,13 +111,13 @@ function registerDoctorApi(app, projectRoot) {
490
111
  }
491
112
 
492
113
  // src/api/events.ts
493
- import { createHash as createHash2, randomUUID } from "crypto";
494
- import { open, readFile as readFile2, stat } from "fs/promises";
495
- import { join as join2 } from "path";
114
+ import { createHash, randomUUID } from "crypto";
115
+ import { open, readFile, stat } from "fs/promises";
116
+ import { join } from "path";
496
117
  import {
497
118
  agentsMetaSchema,
498
119
  fabricEventSchema,
499
- forensicReportSchema as forensicReportSchema2,
120
+ forensicReportSchema,
500
121
  humanLockFileSchema,
501
122
  ledgerEntrySchema
502
123
  } from "@fenglimg/fabric-shared";
@@ -576,7 +197,7 @@ async function ensureWatcher(state, projectRoot) {
576
197
  if (state.watcher !== void 0) {
577
198
  return;
578
199
  }
579
- state.ledgerOffset = await readFileSize(join2(projectRoot, LEDGER_PATH));
200
+ state.ledgerOffset = await readFileSize(join(projectRoot, LEDGER_PATH));
580
201
  state.ledgerRemainder = "";
581
202
  state.humanLockSnapshot = await readHumanLockSnapshot(projectRoot);
582
203
  const watcher = chokidar.watch([...WATCHED_PATHS], {
@@ -645,7 +266,7 @@ async function readEventsForFile(state, projectRoot, relativePath) {
645
266
  return [];
646
267
  }
647
268
  async function readMetaUpdatedEvent(projectRoot) {
648
- const filePath = join2(projectRoot, AGENTS_META_PATH);
269
+ const filePath = join(projectRoot, AGENTS_META_PATH);
649
270
  const raw = await readUtf8File(filePath);
650
271
  if (raw === null) {
651
272
  return null;
@@ -657,12 +278,12 @@ async function readMetaUpdatedEvent(projectRoot) {
657
278
  };
658
279
  }
659
280
  async function readDriftDetectedEvent(projectRoot) {
660
- const filePath = join2(projectRoot, FORENSIC_PATH);
281
+ const filePath = join(projectRoot, FORENSIC_PATH);
661
282
  const raw = await readUtf8File(filePath);
662
283
  if (raw === null) {
663
284
  return null;
664
285
  }
665
- const parsed = forensicReportSchema2.parse(JSON.parse(raw));
286
+ const parsed = forensicReportSchema.parse(JSON.parse(raw));
666
287
  return {
667
288
  type: "drift:detected",
668
289
  payload: parsed
@@ -703,7 +324,7 @@ async function readHumanLockEvents(state, projectRoot) {
703
324
  return events;
704
325
  }
705
326
  async function readLedgerAppendedEvents(state, projectRoot) {
706
- const ledgerPath = join2(projectRoot, LEDGER_PATH);
327
+ const ledgerPath = join(projectRoot, LEDGER_PATH);
707
328
  const nextSize = await readFileSize(ledgerPath);
708
329
  if (nextSize < state.ledgerOffset) {
709
330
  state.ledgerOffset = 0;
@@ -772,7 +393,7 @@ data: ${JSON.stringify(payload)}
772
393
  }
773
394
  }
774
395
  async function readHumanLockSnapshot(projectRoot) {
775
- const humanLockPath = join2(projectRoot, HUMAN_LOCK_PATH);
396
+ const humanLockPath = join(projectRoot, HUMAN_LOCK_PATH);
776
397
  const raw = await readUtf8File(humanLockPath);
777
398
  if (raw === null) {
778
399
  return createEmptyHumanLockSnapshot();
@@ -793,7 +414,7 @@ async function readActualHumanLockHashes(projectRoot, locked) {
793
414
  const uniqueFiles = Array.from(new Set(locked.map((entry) => entry.file)));
794
415
  const fileContents = await Promise.all(
795
416
  uniqueFiles.map(async (file) => {
796
- const raw = await readUtf8File(join2(projectRoot, file));
417
+ const raw = await readUtf8File(join(projectRoot, file));
797
418
  return [file, raw];
798
419
  })
799
420
  );
@@ -808,7 +429,7 @@ async function readActualHumanLockHashes(projectRoot, locked) {
808
429
  function hashLockedContent(content, entry) {
809
430
  const lines = content.split(/\r?\n/);
810
431
  const slice = lines.slice(Math.max(entry.start_line - 1, 0), Math.max(entry.end_line, 0)).join("\n");
811
- return `sha256:${createHash2("sha256").update(slice).digest("hex")}`;
432
+ return `sha256:${createHash("sha256").update(slice).digest("hex")}`;
812
433
  }
813
434
  function getHumanLockKey(entry) {
814
435
  return `${entry.file}:${entry.start_line}:${entry.end_line}`;
@@ -838,7 +459,7 @@ function normalizePath(value) {
838
459
  }
839
460
  async function readUtf8File(path) {
840
461
  try {
841
- return await readFile2(path, "utf8");
462
+ return await readFile(path, "utf8");
842
463
  } catch (error) {
843
464
  if (isNodeError(error) && error.code === "ENOENT") {
844
465
  return null;
@@ -985,6 +606,8 @@ function buildLedgerFallbackMeta(entries) {
985
606
  scope_glob: affectedPath,
986
607
  deps: [],
987
608
  priority: "medium",
609
+ layer: "L2",
610
+ topology_type: "mirror",
988
611
  hash: `replayed:${hashBase ?? entry.id}`
989
612
  };
990
613
  }
@@ -1241,9 +864,9 @@ function registerRulesApi(app, projectRoot) {
1241
864
  }
1242
865
 
1243
866
  // src/api/scan.ts
1244
- import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
1245
- import { isAbsolute as isAbsolute2, join as join3, relative, resolve as resolve2, sep } from "path";
1246
- import { detectFramework as detectFramework2 } from "@fenglimg/fabric-shared";
867
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
868
+ import { isAbsolute, join as join2, relative, resolve, sep } from "path";
869
+ import { detectFramework } from "@fenglimg/fabric-shared/node";
1247
870
  var DEFAULT_IGNORES = [
1248
871
  "**/*.meta",
1249
872
  "library/**",
@@ -1266,11 +889,11 @@ function registerScanApi(app, projectRoot) {
1266
889
  });
1267
890
  }
1268
891
  function createScanReport(targetInput = process.cwd()) {
1269
- const target = normalizeTarget2(targetInput);
1270
- const framework = detectFramework2(target);
892
+ const target = normalizeTarget(targetInput);
893
+ const framework = detectFramework(target);
1271
894
  const readmeQuality = getReadmeQuality(target);
1272
- const hasContributing = existsSync2(join3(target, "CONTRIBUTING.md"));
1273
- const hasExistingFabric = existsSync2(join3(target, "AGENTS.md")) || existsSync2(join3(target, ".fabric"));
895
+ const hasContributing = existsSync(join2(target, "CONTRIBUTING.md"));
896
+ const hasExistingFabric = existsSync(join2(target, "AGENTS.md")) || existsSync(join2(target, ".fabric"));
1274
897
  const walkResult = walkFiles(target, DEFAULT_IGNORES);
1275
898
  return {
1276
899
  target,
@@ -1288,19 +911,19 @@ function createScanReport(targetInput = process.cwd()) {
1288
911
  })
1289
912
  };
1290
913
  }
1291
- function normalizeTarget2(targetInput) {
1292
- return isAbsolute2(targetInput) ? targetInput : resolve2(process.cwd(), targetInput);
914
+ function normalizeTarget(targetInput) {
915
+ return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
1293
916
  }
1294
917
  function getReadmeQuality(target) {
1295
- const readmePath = join3(target, "README.md");
1296
- if (!existsSync2(readmePath)) {
918
+ const readmePath = join2(target, "README.md");
919
+ if (!existsSync(readmePath)) {
1297
920
  return "stub";
1298
921
  }
1299
- const wordCount = readFileSync2(readmePath, "utf8").trim().split(/\s+/).filter(Boolean).length;
922
+ const wordCount = readFileSync(readmePath, "utf8").trim().split(/\s+/).filter(Boolean).length;
1300
923
  return wordCount >= 200 ? "ok" : "stub";
1301
924
  }
1302
925
  function walkFiles(root, ignorePatterns) {
1303
- if (!existsSync2(root) || !statSync2(root).isDirectory()) {
926
+ if (!existsSync(root) || !statSync(root).isDirectory()) {
1304
927
  throw new Error(`Target must be an existing directory: ${root}`);
1305
928
  }
1306
929
  let fileCount = 0;
@@ -1311,8 +934,8 @@ function walkFiles(root, ignorePatterns) {
1311
934
  if (current === void 0) {
1312
935
  continue;
1313
936
  }
1314
- for (const entry of readdirSync2(current, { withFileTypes: true })) {
1315
- const absolutePath = join3(current, entry.name);
937
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
938
+ const absolutePath = join2(current, entry.name);
1316
939
  const relativePath = toPosixPath(relative(root, absolutePath));
1317
940
  if (shouldIgnore(relativePath, entry.isDirectory(), ignorePatterns)) {
1318
941
  ignoredCount += 1;
@@ -1364,18 +987,18 @@ function buildRecommendations(input) {
1364
987
  }
1365
988
 
1366
989
  // src/api/static.ts
1367
- import { existsSync as existsSync3 } from "fs";
1368
- import { dirname, resolve as resolve3 } from "path";
990
+ import { existsSync as existsSync2 } from "fs";
991
+ import { dirname, resolve as resolve2 } from "path";
1369
992
  import { fileURLToPath } from "url";
1370
993
  import express from "express";
1371
- var DEFAULT_STATIC_DIR = resolve3(dirname(fileURLToPath(import.meta.url)), "static");
994
+ var DEFAULT_STATIC_DIR = resolve2(dirname(fileURLToPath(import.meta.url)), "static");
1372
995
  function registerDashboardStatic(app, options = {}) {
1373
996
  if (options.dev ?? process.env.NODE_ENV === "development") {
1374
997
  return;
1375
998
  }
1376
- const staticDir = resolve3(options.dashboardDistPath ?? DEFAULT_STATIC_DIR);
1377
- const indexPath = resolve3(staticDir, "index.html");
1378
- if (!existsSync3(indexPath)) {
999
+ const staticDir = resolve2(options.dashboardDistPath ?? DEFAULT_STATIC_DIR);
1000
+ const indexPath = resolve2(staticDir, "index.html");
1001
+ if (!existsSync2(indexPath)) {
1379
1002
  warnMissingDashboard(staticDir);
1380
1003
  app.get("/", (_req, res) => {
1381
1004
  res.status(404).json({
@@ -1400,7 +1023,7 @@ function warnMissingDashboard(staticDir) {
1400
1023
  }
1401
1024
 
1402
1025
  // src/middleware/bearer-auth.ts
1403
- import { createHash as createHash3, timingSafeEqual } from "crypto";
1026
+ import { createHash as createHash2, timingSafeEqual } from "crypto";
1404
1027
  function createBearerAuthMiddleware(token) {
1405
1028
  const expectedDigest = hashToken(token);
1406
1029
  return function bearerAuthMiddleware(req, res, next) {
@@ -1433,7 +1056,7 @@ function tokensMatch(token, expectedDigest) {
1433
1056
  return timingSafeEqual(hashToken(token), expectedDigest);
1434
1057
  }
1435
1058
  function hashToken(token) {
1436
- return createHash3("sha256").update(token, "utf8").digest();
1059
+ return createHash2("sha256").update(token, "utf8").digest();
1437
1060
  }
1438
1061
 
1439
1062
  // src/http.ts
@@ -1481,7 +1104,7 @@ var JsonlEventStore = class {
1481
1104
  async readEvents() {
1482
1105
  let raw;
1483
1106
  try {
1484
- raw = await readFile3(this.ledgerPath, "utf8");
1107
+ raw = await readFile2(this.ledgerPath, "utf8");
1485
1108
  } catch (error) {
1486
1109
  if (isNodeError2(error) && error.code === "ENOENT") {
1487
1110
  return [];
@@ -1494,7 +1117,7 @@ var JsonlEventStore = class {
1494
1117
  function createFabricHttpApp(options) {
1495
1118
  const { projectRoot, host = DEFAULT_HOST, authToken, dashboardDistPath, dev } = options;
1496
1119
  const app = createMcpExpressApp({ host });
1497
- const ledgerPath = join4(projectRoot, LEDGER_FILE);
1120
+ const ledgerPath = join3(projectRoot, LEDGER_FILE);
1498
1121
  const eventStore = new JsonlEventStore(ledgerPath);
1499
1122
  const sessions = /* @__PURE__ */ new Map();
1500
1123
  process.env.FABRIC_PROJECT_ROOT = projectRoot;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,65 @@
1
1
  import { Server } from 'node:http';
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { AuditMode } from '@fenglimg/fabric-shared';
4
+
5
+ type DoctorStatus = "ok" | "warn" | "error";
6
+ type DoctorCheck = {
7
+ name: string;
8
+ status: DoctorStatus;
9
+ message: string;
10
+ };
11
+ type DoctorSummary = {
12
+ target: string;
13
+ framework: {
14
+ kind: string;
15
+ version: string;
16
+ subkind: string;
17
+ };
18
+ entryPoints: Array<{
19
+ path: string;
20
+ reason: string;
21
+ }>;
22
+ driftCount: number;
23
+ protectedPathCount: number;
24
+ protectedPathsIntact: boolean;
25
+ lastLedgerEntryTs: number | null;
26
+ lastLedgerEntryAgeMs: number | null;
27
+ metaRevision: string | null;
28
+ audit: {
29
+ enabled: boolean;
30
+ mode: AuditMode;
31
+ checkedPathCount: number;
32
+ violationCount: number;
33
+ windowMs: number;
34
+ } | null;
35
+ };
36
+ type DoctorReport = {
37
+ status: DoctorStatus;
38
+ checks: DoctorCheck[];
39
+ summary: DoctorSummary;
40
+ audit: DoctorAuditReport | null;
41
+ };
42
+ type DoctorAuditViolation = {
43
+ editTs: number;
44
+ entryId: string;
45
+ intent: string;
46
+ lastGetRulesTs: number | null;
47
+ path: string;
48
+ };
49
+ type DoctorAuditReport = {
50
+ mode: AuditMode;
51
+ skipped: boolean;
52
+ windowMs: number;
53
+ checkedPathCount: number;
54
+ violationCount: number;
55
+ violations: DoctorAuditViolation[];
56
+ };
57
+ declare function runDoctorReport(target: string): Promise<DoctorReport>;
58
+ declare function runDoctorAuditReport(target: string, options?: {
59
+ force?: boolean;
60
+ mode?: AuditMode;
61
+ windowMs?: number;
62
+ }): Promise<DoctorAuditReport>;
3
63
 
4
64
  declare function createFabricServer(): McpServer;
5
65
  declare function startStdioServer(): Promise<void>;
@@ -12,4 +72,4 @@ declare function startHttpServer(options: {
12
72
  dev?: boolean;
13
73
  }): Promise<Server>;
14
74
 
15
- export { createFabricServer, startHttpServer, startStdioServer };
75
+ export { type DoctorAuditReport, type DoctorReport, createFabricServer, runDoctorAuditReport, runDoctorReport, startHttpServer, startStdioServer };