@fenglimg/fabric-server 2.2.0-rc.10 → 2.2.0-rc.11

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.
Files changed (2) hide show
  1. package/dist/index.js +298 -150
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/index.ts
2
- import { existsSync as existsSync8 } from "fs";
3
- import { readFile as readFile16 } from "fs/promises";
4
- import { join as join20, resolve as resolve4 } from "path";
2
+ import { existsSync as existsSync9 } from "fs";
3
+ import { readFile as readFile17 } from "fs/promises";
4
+ import { join as join21, resolve as resolve4 } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -3415,11 +3415,155 @@ import { enforcePayloadLimit as enforcePayloadLimit4 } from "@fenglimg/fabric-sh
3415
3415
 
3416
3416
  // src/services/review.ts
3417
3417
  import { execFileSync } from "child_process";
3418
- import { existsSync as existsSync4 } from "fs";
3419
- import { readFile as readFile5, readdir, stat as stat2, unlink } from "fs/promises";
3418
+ import { existsSync as existsSync5 } from "fs";
3419
+ import { readFile as readFile6, readdir as readdir2, stat as stat2, unlink as unlink2 } from "fs/promises";
3420
3420
  import { homedir } from "os";
3421
- import { basename, isAbsolute, join as join9, relative, resolve as resolve2, sep as sep2 } from "path";
3421
+ import { basename, isAbsolute, join as join10, relative, resolve as resolve2, sep as sep2 } from "path";
3422
3422
  import { allocateStoreKnowledgeId, isPersonalScope as isPersonalScope2 } from "@fenglimg/fabric-shared";
3423
+
3424
+ // src/services/pending-dedupe.ts
3425
+ import { existsSync as existsSync4 } from "fs";
3426
+ import { readdir, readFile as readFile5, unlink } from "fs/promises";
3427
+ import { join as join9 } from "path";
3428
+ var PENDING_TYPES = ["decisions", "pitfalls", "guidelines", "models", "processes"];
3429
+ var DISAMBIGUATION_SUFFIX = /^(.+)-([2-9])\.md$/u;
3430
+ var SOURCE_SESSIONS_LINE = /^source_sessions:\s*\[(.*)\]\s*$/mu;
3431
+ var CREATED_AT_LINE = /^created_at:\s*(.+)$/mu;
3432
+ function parseSourceSessions(content) {
3433
+ const m = SOURCE_SESSIONS_LINE.exec(content);
3434
+ if (!m) return [];
3435
+ try {
3436
+ const arr = JSON.parse(`[${m[1]}]`);
3437
+ return Array.isArray(arr) ? arr.filter((s) => typeof s === "string") : [];
3438
+ } catch {
3439
+ return [];
3440
+ }
3441
+ }
3442
+ function parseCreatedAt(content) {
3443
+ const m = CREATED_AT_LINE.exec(content);
3444
+ return m ? m[1].trim() : "";
3445
+ }
3446
+ function resolveBaseSlug(name, present) {
3447
+ const m = DISAMBIGUATION_SUFFIX.exec(name);
3448
+ if (m && present.has(`${m[1]}.md`)) return m[1];
3449
+ return name.replace(/\.md$/u, "");
3450
+ }
3451
+ function unionSessions(survivor, twins) {
3452
+ const seen = /* @__PURE__ */ new Set();
3453
+ const out = [];
3454
+ for (const s of [survivor, ...twins]) {
3455
+ for (const sid of s.sourceSessions) {
3456
+ if (sid.length > 0 && !seen.has(sid)) {
3457
+ seen.add(sid);
3458
+ out.push(sid);
3459
+ }
3460
+ }
3461
+ }
3462
+ return out;
3463
+ }
3464
+ function buildMergedContent(survivor, twins) {
3465
+ const union = unionSessions(survivor, twins);
3466
+ let merged = survivor.content.replace(
3467
+ SOURCE_SESSIONS_LINE,
3468
+ `source_sessions: [${union.map((s) => JSON.stringify(s)).join(", ")}]`
3469
+ );
3470
+ if (!merged.endsWith("\n")) merged += "\n";
3471
+ for (const twin of twins) {
3472
+ const body = extractBody(twin.content).trim();
3473
+ if (body.length === 0) continue;
3474
+ merged += `
3475
+ ## Evidence (merged from session ${twin.primarySession || "unknown"})
3476
+
3477
+ ${body}
3478
+ `;
3479
+ }
3480
+ return merged;
3481
+ }
3482
+ function chooseSurvivor(baseSlug, group) {
3483
+ const exact = group.find((p) => p.name === `${baseSlug}.md`);
3484
+ if (exact) return exact;
3485
+ return [...group].sort((a, b) => {
3486
+ if (a.createdAt !== b.createdAt) return a.createdAt < b.createdAt ? -1 : 1;
3487
+ return a.name < b.name ? -1 : 1;
3488
+ })[0];
3489
+ }
3490
+ async function mergePendingTwins(projectRoot) {
3491
+ const merged = [];
3492
+ for (const layer of ["team", "personal"]) {
3493
+ let pendingBase2;
3494
+ try {
3495
+ pendingBase2 = resolveStorePendingBase(layer, projectRoot);
3496
+ } catch {
3497
+ continue;
3498
+ }
3499
+ for (const type of PENDING_TYPES) {
3500
+ const dir = join9(pendingBase2, type);
3501
+ if (!existsSync4(dir)) continue;
3502
+ let names;
3503
+ try {
3504
+ names = (await readdir(dir)).filter((n) => n.endsWith(".md"));
3505
+ } catch {
3506
+ continue;
3507
+ }
3508
+ if (names.length < 2) continue;
3509
+ const present = new Set(names);
3510
+ const groups = /* @__PURE__ */ new Map();
3511
+ for (const name of names) {
3512
+ const base = resolveBaseSlug(name, present);
3513
+ const arr = groups.get(base);
3514
+ if (arr) arr.push(name);
3515
+ else groups.set(base, [name]);
3516
+ }
3517
+ for (const [baseSlug, groupNames] of groups) {
3518
+ if (groupNames.length < 2) continue;
3519
+ const parsed = [];
3520
+ for (const name of groupNames) {
3521
+ const abs = join9(dir, name);
3522
+ let content;
3523
+ try {
3524
+ content = await readFile5(abs, "utf8");
3525
+ } catch {
3526
+ continue;
3527
+ }
3528
+ const sourceSessions = parseSourceSessions(content);
3529
+ parsed.push({
3530
+ name,
3531
+ abs,
3532
+ content,
3533
+ sourceSessions,
3534
+ primarySession: sourceSessions[0] ?? "",
3535
+ createdAt: parseCreatedAt(content)
3536
+ });
3537
+ }
3538
+ if (parsed.length < 2) continue;
3539
+ const distinctPrimaries = new Set(parsed.map((p) => p.primarySession).filter((s) => s.length > 0));
3540
+ if (distinctPrimaries.size < 2) continue;
3541
+ const survivor = chooseSurvivor(baseSlug, parsed);
3542
+ const twins = parsed.filter((p) => p.abs !== survivor.abs);
3543
+ const mergedContent = buildMergedContent(survivor, twins);
3544
+ try {
3545
+ await atomicWriteText(survivor.abs, mergedContent);
3546
+ for (const twin of twins) {
3547
+ await unlink(twin.abs);
3548
+ }
3549
+ } catch {
3550
+ continue;
3551
+ }
3552
+ merged.push({
3553
+ layer,
3554
+ type,
3555
+ base_slug: baseSlug,
3556
+ survivor: survivor.abs,
3557
+ removed: twins.map((t) => t.abs),
3558
+ source_sessions: unionSessions(survivor, twins)
3559
+ });
3560
+ }
3561
+ }
3562
+ }
3563
+ return { merged };
3564
+ }
3565
+
3566
+ // src/services/review.ts
3423
3567
  var PLURAL_TYPES = [
3424
3568
  "decisions",
3425
3569
  "pitfalls",
@@ -3526,6 +3670,10 @@ function isVisibleByLifecycle(fm, filters) {
3526
3670
  return true;
3527
3671
  }
3528
3672
  async function listPending(projectRoot, filters) {
3673
+ try {
3674
+ await mergePendingTwins(projectRoot);
3675
+ } catch {
3676
+ }
3529
3677
  const items = [];
3530
3678
  const typesToScan = filters?.type !== void 0 ? [filters.type] : PLURAL_TYPES;
3531
3679
  const sources = [];
@@ -3545,22 +3693,22 @@ async function listPending(projectRoot, filters) {
3545
3693
  }
3546
3694
  for (const source of sources) {
3547
3695
  for (const type of typesToScan) {
3548
- const dir = join9(source.root, type);
3549
- if (!existsSync4(dir)) {
3696
+ const dir = join10(source.root, type);
3697
+ if (!existsSync5(dir)) {
3550
3698
  continue;
3551
3699
  }
3552
3700
  let entries;
3553
3701
  try {
3554
- entries = await readdir(dir);
3702
+ entries = await readdir2(dir);
3555
3703
  } catch {
3556
3704
  continue;
3557
3705
  }
3558
3706
  for (const name of entries) {
3559
3707
  if (!name.endsWith(".md")) continue;
3560
- const absolutePath = join9(dir, name);
3708
+ const absolutePath = join10(dir, name);
3561
3709
  let content;
3562
3710
  try {
3563
- content = await readFile5(absolutePath, "utf8");
3711
+ content = await readFile6(absolutePath, "utf8");
3564
3712
  } catch {
3565
3713
  continue;
3566
3714
  }
@@ -3670,7 +3818,7 @@ async function approveOne(projectRoot, pendingPath) {
3670
3818
  let targetAbs;
3671
3819
  let writtenTarget = false;
3672
3820
  try {
3673
- const content = await readFile5(sourceAbs, "utf8");
3821
+ const content = await readFile6(sourceAbs, "utf8");
3674
3822
  const fm = parseFrontmatter(content);
3675
3823
  const pluralType = fm.type;
3676
3824
  if (pluralType === void 0 || !PLURAL_TYPES.includes(pluralType)) {
@@ -3684,14 +3832,14 @@ async function approveOne(projectRoot, pendingPath) {
3684
3832
  );
3685
3833
  allocatedId = stableId;
3686
3834
  const newFilename = `${stableId}--${slug}.md`;
3687
- targetAbs = join9(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
3835
+ targetAbs = join10(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
3688
3836
  await ensureParentDirectory(targetAbs);
3689
3837
  const rewritten = rewriteFrontmatterForPromote(content, stableId);
3690
3838
  await atomicWriteText(targetAbs, rewritten);
3691
3839
  writtenTarget = true;
3692
3840
  if (sourceIsStore) {
3693
- if (existsSync4(sourceAbs)) {
3694
- await unlink(sourceAbs);
3841
+ if (existsSync5(sourceAbs)) {
3842
+ await unlink2(sourceAbs);
3695
3843
  }
3696
3844
  } else if (sourceOrigin === "team") {
3697
3845
  try {
@@ -3700,13 +3848,13 @@ async function approveOne(projectRoot, pendingPath) {
3700
3848
  stdio: ["ignore", "pipe", "pipe"]
3701
3849
  });
3702
3850
  } catch {
3703
- if (existsSync4(sourceAbs)) {
3704
- await unlink(sourceAbs);
3851
+ if (existsSync5(sourceAbs)) {
3852
+ await unlink2(sourceAbs);
3705
3853
  }
3706
3854
  }
3707
3855
  } else {
3708
- if (existsSync4(sourceAbs)) {
3709
- await unlink(sourceAbs);
3856
+ if (existsSync5(sourceAbs)) {
3857
+ await unlink2(sourceAbs);
3710
3858
  }
3711
3859
  }
3712
3860
  await emitEventBestEffort2(projectRoot, {
@@ -3717,9 +3865,9 @@ async function approveOne(projectRoot, pendingPath) {
3717
3865
  });
3718
3866
  return { pending_path: pendingPath, stable_id: stableId };
3719
3867
  } catch (err) {
3720
- if (writtenTarget && targetAbs !== void 0 && existsSync4(targetAbs)) {
3868
+ if (writtenTarget && targetAbs !== void 0 && existsSync5(targetAbs)) {
3721
3869
  try {
3722
- await unlink(targetAbs);
3870
+ await unlink2(targetAbs);
3723
3871
  } catch {
3724
3872
  }
3725
3873
  }
@@ -3738,14 +3886,14 @@ async function rejectAll(projectRoot, pendingPaths, reason) {
3738
3886
  for (const pendingPath of pendingPaths) {
3739
3887
  try {
3740
3888
  const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
3741
- if (existsSync4(sandboxed.abs)) {
3742
- const content = await readFile5(sandboxed.abs, "utf8");
3889
+ if (existsSync5(sandboxed.abs)) {
3890
+ const content = await readFile6(sandboxed.abs, "utf8");
3743
3891
  const merged = rewriteFrontmatterMerge(content, { status: "rejected" });
3744
3892
  const rejectedAbs = sandboxed.abs.includes(`${sep2}pending${sep2}`) ? sandboxed.abs.replace(`${sep2}pending${sep2}`, `${sep2}rejected${sep2}`) : null;
3745
3893
  if (rejectedAbs !== null) {
3746
3894
  await ensureParentDirectory(rejectedAbs);
3747
3895
  await atomicWriteText(rejectedAbs, merged);
3748
- await unlink(sandboxed.abs);
3896
+ await unlink2(sandboxed.abs);
3749
3897
  } else if (merged !== content) {
3750
3898
  await atomicWriteText(sandboxed.abs, merged);
3751
3899
  }
@@ -3766,7 +3914,7 @@ async function modifyEntry(projectRoot, pendingPath, changes) {
3766
3914
  if (target === null) {
3767
3915
  throw new Error(`modify target not found: ${pendingPath}`);
3768
3916
  }
3769
- const content = await readFile5(target.absPath, "utf8");
3917
+ const content = await readFile6(target.absPath, "utf8");
3770
3918
  const fm = parseFrontmatter(content);
3771
3919
  const currentLayer = fm.layer ?? "team";
3772
3920
  if (changes.semantic_scope !== void 0 && isPersonalScope2(changes.semantic_scope)) {
@@ -3811,7 +3959,7 @@ function resolveModifyTarget(projectRoot, pendingPath) {
3811
3959
  } catch {
3812
3960
  return null;
3813
3961
  }
3814
- if (existsSync4(sandboxed.abs)) {
3962
+ if (existsSync5(sandboxed.abs)) {
3815
3963
  return {
3816
3964
  absPath: sandboxed.abs,
3817
3965
  isInProjectTree: sandboxed.isInProjectTree,
@@ -3858,7 +4006,7 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
3858
4006
  pluralType,
3859
4007
  resolveWriteTargetStoreDir(toLayer, projectRoot)
3860
4008
  );
3861
- const toAbs = join9(
4009
+ const toAbs = join10(
3862
4010
  resolveStoreCanonicalBase(toLayer, projectRoot),
3863
4011
  pluralType,
3864
4012
  `${newStableId}--${slug}.md`
@@ -3886,12 +4034,12 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
3886
4034
  stdio: ["ignore", "pipe", "pipe"]
3887
4035
  });
3888
4036
  } catch {
3889
- if (existsSync4(target.absPath)) {
3890
- await unlink(target.absPath);
4037
+ if (existsSync5(target.absPath)) {
4038
+ await unlink2(target.absPath);
3891
4039
  }
3892
4040
  }
3893
- } else if (existsSync4(target.absPath)) {
3894
- await unlink(target.absPath);
4041
+ } else if (existsSync5(target.absPath)) {
4042
+ await unlink2(target.absPath);
3895
4043
  }
3896
4044
  const flipReason = `layer_flip:${priorStableId ?? "<unassigned>"}->${newStableId}`;
3897
4045
  const flipTimestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -3954,10 +4102,10 @@ function getSearchDirectoryCache(cacheKey) {
3954
4102
  return created;
3955
4103
  }
3956
4104
  async function listIndexedSearchEntries(source, type) {
3957
- const dir = join9(source.root, type);
4105
+ const dir = join10(source.root, type);
3958
4106
  let entries;
3959
4107
  try {
3960
- entries = await readdir(dir);
4108
+ entries = await readdir2(dir);
3961
4109
  } catch {
3962
4110
  return [];
3963
4111
  }
@@ -3967,7 +4115,7 @@ async function listIndexedSearchEntries(source, type) {
3967
4115
  const indexed = [];
3968
4116
  for (const name of entries) {
3969
4117
  if (!name.endsWith(".md")) continue;
3970
- const absolutePath = join9(dir, name);
4118
+ const absolutePath = join10(dir, name);
3971
4119
  let fingerprint;
3972
4120
  try {
3973
4121
  const st = await stat2(absolutePath);
@@ -3984,7 +4132,7 @@ async function listIndexedSearchEntries(source, type) {
3984
4132
  }
3985
4133
  let content;
3986
4134
  try {
3987
- content = await readFile5(absolutePath, "utf8");
4135
+ content = await readFile6(absolutePath, "utf8");
3988
4136
  searchEntryIndexContentReads += 1;
3989
4137
  } catch {
3990
4138
  directoryCache.files.delete(absolutePath);
@@ -4104,8 +4252,8 @@ async function deferAll(projectRoot, pendingPaths, until, reason) {
4104
4252
  let stableId;
4105
4253
  try {
4106
4254
  const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
4107
- if (existsSync4(sandboxed.abs)) {
4108
- const content = await readFile5(sandboxed.abs, "utf8");
4255
+ if (existsSync5(sandboxed.abs)) {
4256
+ const content = await readFile6(sandboxed.abs, "utf8");
4109
4257
  stableId = parseFrontmatter(content).id;
4110
4258
  const patch = {
4111
4259
  status: "deferred",
@@ -4377,9 +4525,9 @@ function registerReview(server, tracker) {
4377
4525
  }
4378
4526
 
4379
4527
  // src/services/doctor.ts
4380
- import { access as access4, readFile as readFile14, readdir as readdirAsync, stat as statAsync, unlink as unlink3, writeFile as writeFile3 } from "fs/promises";
4528
+ import { access as access4, readFile as readFile15, readdir as readdirAsync, stat as statAsync, unlink as unlink4, writeFile as writeFile3 } from "fs/promises";
4381
4529
  import { constants as constants2 } from "fs";
4382
- import { isAbsolute as isAbsolute2, join as join19, posix as posix4, resolve as resolve3 } from "path";
4530
+ import { isAbsolute as isAbsolute2, join as join20, posix as posix4, resolve as resolve3 } from "path";
4383
4531
  import {
4384
4532
  createTranslator,
4385
4533
  forensicReportSchema,
@@ -4593,8 +4741,8 @@ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
4593
4741
  }
4594
4742
 
4595
4743
  // src/services/doctor-stable-id-collision.ts
4596
- import { readFile as readFile6 } from "fs/promises";
4597
- import { basename as basename2, join as join10 } from "path";
4744
+ import { readFile as readFile7 } from "fs/promises";
4745
+ import { basename as basename2, join as join11 } from "path";
4598
4746
  import {
4599
4747
  buildStoreResolveInput as buildStoreResolveInput4,
4600
4748
  createStoreResolver as createStoreResolver4,
@@ -4621,7 +4769,7 @@ function resolveIntegrityStores(projectRoot) {
4621
4769
  return {
4622
4770
  store_uuid: entry.store_uuid,
4623
4771
  alias: entry.alias,
4624
- dir: join10(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
4772
+ dir: join11(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
4625
4773
  };
4626
4774
  });
4627
4775
  return { dirs, personalUuids };
@@ -4640,7 +4788,7 @@ async function inspectStoreStableIdIntegrity(projectRoot) {
4640
4788
  for (const ref of await readKnowledgeAcrossStores2(resolved.dirs)) {
4641
4789
  let source;
4642
4790
  try {
4643
- source = await readFile6(ref.file, "utf8");
4791
+ source = await readFile7(ref.file, "utf8");
4644
4792
  } catch {
4645
4793
  continue;
4646
4794
  }
@@ -4724,8 +4872,8 @@ function createLayerMismatchCheck(t, inspection) {
4724
4872
 
4725
4873
  // src/services/doctor-relevance-paths.ts
4726
4874
  import { execFileSync as execFileSync2 } from "child_process";
4727
- import { existsSync as existsSync5, readdirSync, statSync as statSync3 } from "fs";
4728
- import { join as join11, sep as sep3 } from "path";
4875
+ import { existsSync as existsSync6, readdirSync, statSync as statSync3 } from "fs";
4876
+ import { join as join12, sep as sep3 } from "path";
4729
4877
  import { minimatch } from "minimatch";
4730
4878
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
4731
4879
  var RELEVANCE_PATHS_DRIFT_WINDOW_DAYS = 90;
@@ -4746,7 +4894,7 @@ function expandGlob(rawGlob) {
4746
4894
  return rawGlob.endsWith("/") ? `${rawGlob}**` : rawGlob;
4747
4895
  }
4748
4896
  function collectWorkspacePaths(projectRoot) {
4749
- if (!existsSync5(projectRoot)) {
4897
+ if (!existsSync6(projectRoot)) {
4750
4898
  return [];
4751
4899
  }
4752
4900
  try {
@@ -4768,7 +4916,7 @@ function collectWorkspacePaths(projectRoot) {
4768
4916
  continue;
4769
4917
  }
4770
4918
  for (const entry of entries) {
4771
- const abs = join11(current, entry.name);
4919
+ const abs = join12(current, entry.name);
4772
4920
  const rel = toPosix(abs.slice(projectRoot.length + 1));
4773
4921
  if (rel.length === 0) continue;
4774
4922
  if (entry.isDirectory()) {
@@ -4961,16 +5109,16 @@ function createNarrowNoPathsCheck(t, inspection) {
4961
5109
  }
4962
5110
 
4963
5111
  // src/services/doctor-broad-index.ts
4964
- import { readFile as readFile7 } from "fs/promises";
4965
- import { join as join12 } from "path";
5112
+ import { readFile as readFile8 } from "fs/promises";
5113
+ import { join as join13 } from "path";
4966
5114
  var DEFAULT_BROAD_INDEX_BACKSTOP = 50;
4967
5115
  var BROAD_INDEX_BACKSTOP_MIN = 20;
4968
5116
  var BROAD_INDEX_BACKSTOP_MAX = 500;
4969
5117
  var BROAD_INDEX_DRIFT_RATIO = 0.8;
4970
5118
  async function readBroadIndexBackstop(projectRoot) {
4971
- const configPath = join12(projectRoot, ".fabric", "fabric-config.json");
5119
+ const configPath = join13(projectRoot, ".fabric", "fabric-config.json");
4972
5120
  try {
4973
- const raw = await readFile7(configPath, "utf8");
5121
+ const raw = await readFile8(configPath, "utf8");
4974
5122
  const parsed = JSON.parse(raw);
4975
5123
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4976
5124
  const v = parsed.broad_index_backstop;
@@ -5151,8 +5299,8 @@ function createStaleArchiveCheck(t, inspection) {
5151
5299
  }
5152
5300
 
5153
5301
  // src/services/doctor-scope-lint.ts
5154
- import { readFile as readFile8 } from "fs/promises";
5155
- import { join as join13 } from "path";
5302
+ import { readFile as readFile9 } from "fs/promises";
5303
+ import { join as join14 } from "path";
5156
5304
  import {
5157
5305
  buildStoreResolveInput as buildStoreResolveInput5,
5158
5306
  createStoreResolver as createStoreResolver5,
@@ -5187,7 +5335,7 @@ async function resolveLintStores(projectRoot) {
5187
5335
  const globalRoot = resolveGlobalRoot4();
5188
5336
  return Promise.all(readSet.stores.map(async (entry) => {
5189
5337
  const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
5190
- const dir = join13(
5338
+ const dir = join14(
5191
5339
  globalRoot,
5192
5340
  storeRelativePathForMount4(mounted ?? { store_uuid: entry.store_uuid })
5193
5341
  );
@@ -5219,7 +5367,7 @@ async function lintStoreScopes(projectRoot) {
5219
5367
  }
5220
5368
  let source;
5221
5369
  try {
5222
- source = await readFile8(ref.file, "utf8");
5370
+ source = await readFile9(ref.file, "utf8");
5223
5371
  } catch {
5224
5372
  continue;
5225
5373
  }
@@ -5341,8 +5489,8 @@ function createUnboundProjectCheck(t, violation) {
5341
5489
  }
5342
5490
 
5343
5491
  // src/services/doctor-store-counters.ts
5344
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
5345
- import { join as join14 } from "path";
5492
+ import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
5493
+ import { join as join15 } from "path";
5346
5494
  import {
5347
5495
  buildStoreResolveInput as buildStoreResolveInput6,
5348
5496
  createStoreResolver as createStoreResolver6,
@@ -5369,7 +5517,7 @@ function resolveCounterStores(projectRoot) {
5369
5517
  return readSet.stores.map((entry) => ({
5370
5518
  uuid: entry.store_uuid,
5371
5519
  alias: entry.alias,
5372
- dir: join14(
5520
+ dir: join15(
5373
5521
  globalRoot,
5374
5522
  storeRelativePathForMount5(
5375
5523
  input.mountedStores.find((s) => s.store_uuid === entry.store_uuid) ?? {
@@ -5397,8 +5545,8 @@ function readEntryId(file) {
5397
5545
  function computeStoreDiskMax(storeDir) {
5398
5546
  const max = defaultAgentsMetaCounters();
5399
5547
  for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
5400
- const dir = join14(storeDir, STORE_LAYOUT2.knowledgeDir, type);
5401
- if (!existsSync6(dir)) {
5548
+ const dir = join15(storeDir, STORE_LAYOUT2.knowledgeDir, type);
5549
+ if (!existsSync7(dir)) {
5402
5550
  continue;
5403
5551
  }
5404
5552
  let names;
@@ -5411,7 +5559,7 @@ function computeStoreDiskMax(storeDir) {
5411
5559
  if (!name.endsWith(".md")) {
5412
5560
  continue;
5413
5561
  }
5414
- const parsed = parseKnowledgeId2(readEntryId(join14(dir, name)) ?? "");
5562
+ const parsed = parseKnowledgeId2(readEntryId(join15(dir, name)) ?? "");
5415
5563
  if (parsed === null) {
5416
5564
  continue;
5417
5565
  }
@@ -5494,7 +5642,7 @@ function createStoreCounterCheck(t, drifts) {
5494
5642
 
5495
5643
  // src/services/doctor-store-orphan.ts
5496
5644
  import { readdirSync as readdirSync3 } from "fs";
5497
- import { join as join15 } from "path";
5645
+ import { join as join16 } from "path";
5498
5646
  import {
5499
5647
  STORES_ROOT_DIR,
5500
5648
  addMountedStore,
@@ -5518,15 +5666,15 @@ function inspectStoreOrphans(globalRoot = resolveGlobalRoot6()) {
5518
5666
  return [];
5519
5667
  }
5520
5668
  const registered = new Set(config.stores.map((s) => s.store_uuid));
5521
- const storesRoot = join15(globalRoot, STORES_ROOT_DIR);
5669
+ const storesRoot = join16(globalRoot, STORES_ROOT_DIR);
5522
5670
  const orphans = [];
5523
5671
  for (const group of listDir(storesRoot)) {
5524
5672
  if (group === STORE_BY_ALIAS_DIR) {
5525
5673
  continue;
5526
5674
  }
5527
- const groupDir = join15(storesRoot, group);
5675
+ const groupDir = join16(storesRoot, group);
5528
5676
  for (const mount of listDir(groupDir)) {
5529
- const dir = join15(groupDir, mount);
5677
+ const dir = join16(groupDir, mount);
5530
5678
  const identity = readStoreIdentity(dir);
5531
5679
  if (identity === null || registered.has(identity.store_uuid)) {
5532
5680
  continue;
@@ -5599,7 +5747,7 @@ import {
5599
5747
 
5600
5748
  // src/services/events-jsonl-gates.ts
5601
5749
  import { promises as fs } from "fs";
5602
- import { existsSync as existsSync7 } from "fs";
5750
+ import { existsSync as existsSync8 } from "fs";
5603
5751
  var EVENTS_JSONL_SIZE_WARN_BYTES = 10 * 1024 * 1024;
5604
5752
  var METRICS_STALE_WARN_MS = 10 * 60 * 1e3;
5605
5753
  var ROTATION_OVERDUE_WARN_MS = 90 * 24 * 60 * 60 * 1e3;
@@ -5621,7 +5769,7 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
5621
5769
  if (!(isNodeError(error) && error.code === "ENOENT")) throw error;
5622
5770
  }
5623
5771
  let metricsStalenessMs = null;
5624
- if (existsSync7(metricsPath)) {
5772
+ if (existsSync8(metricsPath)) {
5625
5773
  try {
5626
5774
  const stat4 = await fs.stat(metricsPath);
5627
5775
  metricsStalenessMs = Math.max(0, now.getTime() - stat4.mtimeMs);
@@ -5663,8 +5811,8 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
5663
5811
  }
5664
5812
 
5665
5813
  // src/services/doctor-skill-lints.ts
5666
- import { readdir as readdir2, readFile as readFile9 } from "fs/promises";
5667
- import { join as join16, posix as posix2 } from "path";
5814
+ import { readdir as readdir3, readFile as readFile10 } from "fs/promises";
5815
+ import { join as join17, posix as posix2 } from "path";
5668
5816
  var FABRIC_SKILL_SLUGS = ["fabric-archive", "fabric-review", "fabric-import"];
5669
5817
  var ROUTER_VALID_LEAF_SLUGS = /* @__PURE__ */ new Set([
5670
5818
  "fabric-archive",
@@ -5695,7 +5843,7 @@ function issueCheck(name, status, kind, code, message, actionHint, audience) {
5695
5843
  }
5696
5844
  async function listMarkdownFiles(dir) {
5697
5845
  try {
5698
- return (await readdir2(dir)).filter((name) => name.endsWith(".md"));
5846
+ return (await readdir3(dir)).filter((name) => name.endsWith(".md"));
5699
5847
  } catch {
5700
5848
  return null;
5701
5849
  }
@@ -5703,8 +5851,8 @@ async function listMarkdownFiles(dir) {
5703
5851
  async function inspectSkillRefMirror(projectRoot) {
5704
5852
  const driftedPaths = [];
5705
5853
  for (const slug of FABRIC_SKILL_SLUGS) {
5706
- const claudeRef = join16(projectRoot, ".claude", "skills", slug, "ref");
5707
- const codexRef = join16(projectRoot, ".codex", "skills", slug, "ref");
5854
+ const claudeRef = join17(projectRoot, ".claude", "skills", slug, "ref");
5855
+ const codexRef = join17(projectRoot, ".codex", "skills", slug, "ref");
5708
5856
  const [claudeFiles, codexFiles] = await Promise.all([listMarkdownFiles(claudeRef), listMarkdownFiles(codexRef)]);
5709
5857
  if (claudeFiles === null || codexFiles === null) continue;
5710
5858
  const claudeSet = new Set(claudeFiles);
@@ -5721,8 +5869,8 @@ async function inspectSkillRefMirror(projectRoot) {
5721
5869
  let codexBody;
5722
5870
  try {
5723
5871
  [claudeBody, codexBody] = await Promise.all([
5724
- readFile9(join16(claudeRef, fname), "utf8"),
5725
- readFile9(join16(codexRef, fname), "utf8")
5872
+ readFile10(join17(claudeRef, fname), "utf8"),
5873
+ readFile10(join17(codexRef, fname), "utf8")
5726
5874
  ]);
5727
5875
  } catch {
5728
5876
  continue;
@@ -5741,10 +5889,10 @@ async function inspectSkillTokenBudget(projectRoot) {
5741
5889
  const overSize = [];
5742
5890
  let highestSeverity = "ok";
5743
5891
  for (const slug of FABRIC_SKILL_SLUGS) {
5744
- const skillMdPath = join16(projectRoot, ".claude", "skills", slug, "SKILL.md");
5892
+ const skillMdPath = join17(projectRoot, ".claude", "skills", slug, "SKILL.md");
5745
5893
  let body;
5746
5894
  try {
5747
- body = await readFile9(skillMdPath, "utf8");
5895
+ body = await readFile10(skillMdPath, "utf8");
5748
5896
  } catch {
5749
5897
  continue;
5750
5898
  }
@@ -5765,10 +5913,10 @@ async function inspectSkillDescription(projectRoot) {
5765
5913
  const CJK_PATTERN = /[\u3400-\u4dbf\u4e00-\u9fff]/u;
5766
5914
  const ASCII_PATTERN = /[a-zA-Z]{2,}/u;
5767
5915
  for (const slug of FABRIC_SKILL_SLUGS) {
5768
- const skillMdPath = join16(projectRoot, ".claude", "skills", slug, "SKILL.md");
5916
+ const skillMdPath = join17(projectRoot, ".claude", "skills", slug, "SKILL.md");
5769
5917
  let body;
5770
5918
  try {
5771
- body = await readFile9(skillMdPath, "utf8");
5919
+ body = await readFile10(skillMdPath, "utf8");
5772
5920
  } catch {
5773
5921
  continue;
5774
5922
  }
@@ -5799,19 +5947,19 @@ async function inspectSkillDescription(projectRoot) {
5799
5947
  async function inspectSkillMdYamlInvalid(projectRoot) {
5800
5948
  const candidates = [];
5801
5949
  for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
5802
- const rootAbs = join16(projectRoot, rootRel);
5950
+ const rootAbs = join17(projectRoot, rootRel);
5803
5951
  let dirEntries;
5804
5952
  try {
5805
- dirEntries = await readdir2(rootAbs, { withFileTypes: true });
5953
+ dirEntries = await readdir3(rootAbs, { withFileTypes: true });
5806
5954
  } catch {
5807
5955
  continue;
5808
5956
  }
5809
5957
  for (const dirEntry of dirEntries) {
5810
5958
  if (!dirEntry.isDirectory()) continue;
5811
- const skillFile = join16(rootAbs, dirEntry.name, "SKILL.md");
5959
+ const skillFile = join17(rootAbs, dirEntry.name, "SKILL.md");
5812
5960
  let raw;
5813
5961
  try {
5814
- raw = await readFile9(skillFile, "utf8");
5962
+ raw = await readFile10(skillFile, "utf8");
5815
5963
  } catch {
5816
5964
  continue;
5817
5965
  }
@@ -5880,13 +6028,13 @@ function extractMarkdownSectionBody(markdown, sectionName) {
5880
6028
  }
5881
6029
  async function inspectRouterChainRef(projectRoot) {
5882
6030
  const candidatePaths = [
5883
- join16(projectRoot, ".claude", "skills", "fabric", "SKILL.md"),
5884
- join16(projectRoot, ".codex", "skills", "fabric", "SKILL.md")
6031
+ join17(projectRoot, ".claude", "skills", "fabric", "SKILL.md"),
6032
+ join17(projectRoot, ".codex", "skills", "fabric", "SKILL.md")
5885
6033
  ];
5886
6034
  let body = null;
5887
6035
  for (const candidate of candidatePaths) {
5888
6036
  try {
5889
- body = await readFile9(candidate, "utf8");
6037
+ body = await readFile10(candidate, "utf8");
5890
6038
  break;
5891
6039
  } catch {
5892
6040
  }
@@ -5997,8 +6145,8 @@ function createSkillMdYamlInvalidCheck(t, inspection) {
5997
6145
 
5998
6146
  // src/services/doctor-hooks-lints.ts
5999
6147
  import { constants } from "fs";
6000
- import { access, readdir as readdir3, readFile as readFile10, stat as stat3 } from "fs/promises";
6001
- import { join as join17, posix as posix3 } from "path";
6148
+ import { access, readdir as readdir4, readFile as readFile11, stat as stat3 } from "fs/promises";
6149
+ import { join as join18, posix as posix3 } from "path";
6002
6150
  import { Script } from "vm";
6003
6151
  var HOOKS_RUNTIME_CLIENT_DIRS = [
6004
6152
  { client: "claude", dir: ".claude/hooks" },
@@ -6047,7 +6195,7 @@ function isHookWiredForEvent(hooks, event, hookFile) {
6047
6195
  }
6048
6196
  async function readDirectoryFileNames(dir) {
6049
6197
  try {
6050
- return await readdir3(dir);
6198
+ return await readdir4(dir);
6051
6199
  } catch {
6052
6200
  return null;
6053
6201
  }
@@ -6060,14 +6208,14 @@ async function isFile(absPath) {
6060
6208
  }
6061
6209
  }
6062
6210
  async function inspectHooksWired(projectRoot) {
6063
- const claudeEntries = await readDirectoryFileNames(join17(projectRoot, ".claude"));
6211
+ const claudeEntries = await readDirectoryFileNames(join18(projectRoot, ".claude"));
6064
6212
  if (claudeEntries === null) {
6065
6213
  return { status: "skipped", missingHooks: [] };
6066
6214
  }
6067
- const settingsPath = join17(projectRoot, ".claude", "settings.json");
6215
+ const settingsPath = join18(projectRoot, ".claude", "settings.json");
6068
6216
  let parsed;
6069
6217
  try {
6070
- parsed = JSON.parse(await readFile10(settingsPath, "utf8"));
6218
+ parsed = JSON.parse(await readFile11(settingsPath, "utf8"));
6071
6219
  } catch {
6072
6220
  return { status: "missing-settings", missingHooks: [] };
6073
6221
  }
@@ -6090,8 +6238,8 @@ async function inspectHooksWired(projectRoot) {
6090
6238
  }
6091
6239
  async function inspectHookCacheWritability(projectRoot) {
6092
6240
  const relPath = posix3.join(".fabric", ".cache");
6093
- const fabricDir = join17(projectRoot, ".fabric");
6094
- const cacheDir = join17(projectRoot, ".fabric", ".cache");
6241
+ const fabricDir = join18(projectRoot, ".fabric");
6242
+ const cacheDir = join18(projectRoot, ".fabric", ".cache");
6095
6243
  try {
6096
6244
  try {
6097
6245
  const cacheStats = await stat3(cacheDir);
@@ -6140,12 +6288,12 @@ async function inspectHookCacheWritability(projectRoot) {
6140
6288
  async function inspectHooksContentDrift(projectRoot) {
6141
6289
  const hookFilesByBasename = /* @__PURE__ */ new Map();
6142
6290
  for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
6143
- const absDir = join17(projectRoot, dir);
6291
+ const absDir = join18(projectRoot, dir);
6144
6292
  const entries = await readDirectoryFileNames(absDir);
6145
6293
  if (entries === null) continue;
6146
6294
  for (const name of entries) {
6147
6295
  if (!name.endsWith(".cjs")) continue;
6148
- const abs = join17(absDir, name);
6296
+ const abs = join18(absDir, name);
6149
6297
  if (!await isFile(abs)) continue;
6150
6298
  const arr = hookFilesByBasename.get(name) ?? [];
6151
6299
  arr.push({ client, abs });
@@ -6160,7 +6308,7 @@ async function inspectHooksContentDrift(projectRoot) {
6160
6308
  const hashes = [];
6161
6309
  for (const { client, abs } of copies) {
6162
6310
  try {
6163
- const body = await readFile10(abs, "utf8");
6311
+ const body = await readFile11(abs, "utf8");
6164
6312
  hashes.push({ client, sha: sha256(body) });
6165
6313
  } catch {
6166
6314
  }
@@ -6182,18 +6330,18 @@ async function inspectHooksRuntime(projectRoot) {
6182
6330
  const issues = [];
6183
6331
  let scanned = 0;
6184
6332
  for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
6185
- const absDir = join17(projectRoot, dir);
6333
+ const absDir = join18(projectRoot, dir);
6186
6334
  const entries = await readDirectoryFileNames(absDir);
6187
6335
  if (entries === null) continue;
6188
6336
  for (const name of entries) {
6189
6337
  if (!name.endsWith(".cjs")) continue;
6190
- const abs = join17(absDir, name);
6338
+ const abs = join18(absDir, name);
6191
6339
  const displayPath = `${dir}/${name}`;
6192
6340
  if (!await isFile(abs)) continue;
6193
6341
  scanned += 1;
6194
6342
  let body;
6195
6343
  try {
6196
- body = await readFile10(abs, "utf8");
6344
+ body = await readFile11(abs, "utf8");
6197
6345
  } catch (err) {
6198
6346
  issues.push({
6199
6347
  path: displayPath,
@@ -6330,8 +6478,8 @@ function createHookCacheWritabilityCheck(t, inspection) {
6330
6478
  }
6331
6479
 
6332
6480
  // src/services/doctor-bootstrap-lints.ts
6333
- import { access as access2, readFile as readFile11 } from "fs/promises";
6334
- import { join as join18 } from "path";
6481
+ import { access as access2, readFile as readFile12 } from "fs/promises";
6482
+ import { join as join19 } from "path";
6335
6483
  import {
6336
6484
  BOOTSTRAP_MARKER_BEGIN,
6337
6485
  BOOTSTRAP_MARKER_END,
@@ -6363,17 +6511,17 @@ async function fileExists(path) {
6363
6511
  }
6364
6512
  async function inspectBootstrapAnchor(projectRoot) {
6365
6513
  const [hasAgentsMd, hasClaudeMd] = await Promise.all([
6366
- fileExists(join18(projectRoot, "AGENTS.md")),
6367
- fileExists(join18(projectRoot, "CLAUDE.md"))
6514
+ fileExists(join19(projectRoot, "AGENTS.md")),
6515
+ fileExists(join19(projectRoot, "CLAUDE.md"))
6368
6516
  ]);
6369
6517
  return { hasAgentsMd, hasClaudeMd };
6370
6518
  }
6371
6519
  async function inspectL1BootstrapSnapshotDrift(target) {
6372
- const abs = join18(target, ".fabric", "AGENTS.md");
6520
+ const abs = join19(target, ".fabric", "AGENTS.md");
6373
6521
  const canonical = resolveBootstrapCanonical();
6374
6522
  let onDisk;
6375
6523
  try {
6376
- onDisk = await readFile11(abs, "utf8");
6524
+ onDisk = await readFile12(abs, "utf8");
6377
6525
  } catch {
6378
6526
  return { status: "missing", canonical, onDisk: null };
6379
6527
  }
@@ -6399,17 +6547,17 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
6399
6547
  );
6400
6548
  }
6401
6549
  async function inspectL2ManagedBlockDrift(target) {
6402
- const snapshotPath = join18(target, ".fabric", "AGENTS.md");
6550
+ const snapshotPath = join19(target, ".fabric", "AGENTS.md");
6403
6551
  let snapshot;
6404
6552
  try {
6405
- snapshot = await readFile11(snapshotPath, "utf8");
6553
+ snapshot = await readFile12(snapshotPath, "utf8");
6406
6554
  } catch {
6407
6555
  return { status: "ok", drifted: [] };
6408
6556
  }
6409
- const projectRulesPath = join18(target, ".fabric", "project-rules.md");
6557
+ const projectRulesPath = join19(target, ".fabric", "project-rules.md");
6410
6558
  let expectedBody = snapshot;
6411
6559
  try {
6412
- const projectRules = await readFile11(projectRulesPath, "utf8");
6560
+ const projectRules = await readFile12(projectRulesPath, "utf8");
6413
6561
  expectedBody = `${snapshot}
6414
6562
  ---
6415
6563
  ${projectRules}`;
@@ -6418,12 +6566,12 @@ ${projectRules}`;
6418
6566
  const drifted = [];
6419
6567
  let anyManagedBlockFound = false;
6420
6568
  const blockTargets = [
6421
- join18(target, "AGENTS.md")
6569
+ join19(target, "AGENTS.md")
6422
6570
  ];
6423
6571
  for (const abs of blockTargets) {
6424
6572
  let content;
6425
6573
  try {
6426
- content = await readFile11(abs, "utf8");
6574
+ content = await readFile12(abs, "utf8");
6427
6575
  } catch {
6428
6576
  continue;
6429
6577
  }
@@ -6446,9 +6594,9 @@ ${projectRules}`;
6446
6594
  drifted.push({ path: abs, expected: expectedBody, actual: body });
6447
6595
  }
6448
6596
  }
6449
- const claudeMdPath = join18(target, "CLAUDE.md");
6597
+ const claudeMdPath = join19(target, "CLAUDE.md");
6450
6598
  try {
6451
- const claudeContent = await readFile11(claudeMdPath, "utf8");
6599
+ const claudeContent = await readFile12(claudeMdPath, "utf8");
6452
6600
  anyManagedBlockFound = true;
6453
6601
  const lines = claudeContent.split(/\r?\n/u);
6454
6602
  const hasAtImport = lines.some((line) => line.trim() === "@.fabric/AGENTS.md");
@@ -6516,7 +6664,7 @@ import { appendFile as appendFile3 } from "fs/promises";
6516
6664
  import { minimatch as minimatch2 } from "minimatch";
6517
6665
 
6518
6666
  // src/services/cite-rollup.ts
6519
- import { readFile as readFile12 } from "fs/promises";
6667
+ import { readFile as readFile13 } from "fs/promises";
6520
6668
  import { createLedgerWriteQueue as createLedgerWriteQueue3 } from "@fenglimg/fabric-shared/node/atomic-write";
6521
6669
  var citeRollupQueue = createLedgerWriteQueue3();
6522
6670
  async function appendCiteRollupRow(projectRoot, row) {
@@ -6528,7 +6676,7 @@ async function readCiteRollup(projectRoot) {
6528
6676
  const path = getCiteRollupPath(projectRoot);
6529
6677
  let raw;
6530
6678
  try {
6531
- raw = await readFile12(path, "utf8");
6679
+ raw = await readFile13(path, "utf8");
6532
6680
  } catch (error) {
6533
6681
  if (isNodeError(error) && error.code === "ENOENT") return [];
6534
6682
  throw error;
@@ -7653,7 +7801,7 @@ async function runDoctorReport(target) {
7653
7801
  const globalCliVersion = process.env.VITEST === "true" ? { status: "ok", version: "test-skipped" } : inspectGlobalCliVersion();
7654
7802
  const targetFiles = Object.fromEntries(
7655
7803
  await Promise.all(
7656
- TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(join19(projectRoot, path))])
7804
+ TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(join20(projectRoot, path))])
7657
7805
  )
7658
7806
  );
7659
7807
  const checks = [
@@ -7853,7 +8001,7 @@ async function runDoctorFix(target) {
7853
8001
  const fixed = [];
7854
8002
  const ledgerWarnings = [];
7855
8003
  if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
7856
- const snapshotPath = join19(projectRoot, ".fabric", "AGENTS.md");
8004
+ const snapshotPath = join20(projectRoot, ".fabric", "AGENTS.md");
7857
8005
  await ensureParentDirectory(snapshotPath);
7858
8006
  await atomicWriteText4(snapshotPath, resolveBootstrapCanonical2());
7859
8007
  fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
@@ -7926,9 +8074,9 @@ async function runDoctorFix(target) {
7926
8074
  if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
7927
8075
  const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
7928
8076
  if (lockInspection.present && !lockInspection.pidAlive) {
7929
- const lockFilePath = join19(projectRoot, ".fabric", ".serve.lock");
8077
+ const lockFilePath = join20(projectRoot, ".fabric", ".serve.lock");
7930
8078
  try {
7931
- await unlink3(lockFilePath);
8079
+ await unlink4(lockFilePath);
7932
8080
  } catch (err) {
7933
8081
  const errno = err;
7934
8082
  if (errno.code !== "ENOENT") throw err;
@@ -8043,10 +8191,10 @@ function createApplyLintMessage(succeeded, failed, manualErrorCount) {
8043
8191
  }
8044
8192
  async function applySessionHintsStaleCleanup(projectRoot, candidate) {
8045
8193
  const detail = `deleted (${candidate.age_days}d old)`;
8046
- const absPath = join19(projectRoot, candidate.path);
8194
+ const absPath = join20(projectRoot, candidate.path);
8047
8195
  try {
8048
- const { unlink: unlink4 } = await import("fs/promises");
8049
- await unlink4(absPath);
8196
+ const { unlink: unlink5 } = await import("fs/promises");
8197
+ await unlink5(absPath);
8050
8198
  return {
8051
8199
  kind: "knowledge_session_hints_stale_cleanup",
8052
8200
  path: candidate.path,
@@ -8068,9 +8216,9 @@ function truncateErrorMessage(error) {
8068
8216
  return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
8069
8217
  }
8070
8218
  async function inspectForensic(projectRoot) {
8071
- const path = join19(projectRoot, ".fabric", "forensic.json");
8219
+ const path = join20(projectRoot, ".fabric", "forensic.json");
8072
8220
  try {
8073
- const parsed = forensicReportSchema.parse(JSON.parse(await readFile14(path, "utf8")));
8221
+ const parsed = forensicReportSchema.parse(JSON.parse(await readFile15(path, "utf8")));
8074
8222
  return { present: true, valid: true, report: parsed };
8075
8223
  } catch (error) {
8076
8224
  if (isMissingFileError(error)) {
@@ -8100,7 +8248,7 @@ async function inspectEventLedger(projectRoot) {
8100
8248
  try {
8101
8249
  await access4(path, constants2.W_OK);
8102
8250
  const { warnings } = await readEventLedger(projectRoot);
8103
- const raw = await readFile14(path, "utf8");
8251
+ const raw = await readFile15(path, "utf8");
8104
8252
  const invalidLine = raw.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).find((line) => !isValidJsonLine(line));
8105
8253
  const partialWarning = warnings.find((w) => w.kind === "partial_write_at_tail");
8106
8254
  const schemaVersionSamples = [];
@@ -8645,7 +8793,7 @@ async function inspectPreexistingRootFiles(projectRoot) {
8645
8793
  const candidates = ["CLAUDE.md", "AGENTS.md"];
8646
8794
  const detected = [];
8647
8795
  for (const name of candidates) {
8648
- if (await pathExists(join19(projectRoot, name))) {
8796
+ if (await pathExists(join20(projectRoot, name))) {
8649
8797
  detected.push(name);
8650
8798
  }
8651
8799
  }
@@ -8730,7 +8878,7 @@ async function buildLastActiveIndex(projectRoot) {
8730
8878
  return map;
8731
8879
  }
8732
8880
  async function inspectSessionHintsStale(projectRoot, now) {
8733
- const cacheDir = join19(projectRoot, ".fabric", ".cache");
8881
+ const cacheDir = join20(projectRoot, ".fabric", ".cache");
8734
8882
  let entries;
8735
8883
  try {
8736
8884
  entries = await readdirAsync(cacheDir, { withFileTypes: true });
@@ -8742,7 +8890,7 @@ async function inspectSessionHintsStale(projectRoot, now) {
8742
8890
  if (!entry.isFile()) continue;
8743
8891
  if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
8744
8892
  if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
8745
- const absPath = join19(cacheDir, entry.name);
8893
+ const absPath = join20(cacheDir, entry.name);
8746
8894
  let mtimeMs = 0;
8747
8895
  try {
8748
8896
  mtimeMs = (await statAsync(absPath)).mtimeMs;
@@ -8774,9 +8922,9 @@ function inspectStaleServeLock(projectRoot, now) {
8774
8922
  };
8775
8923
  }
8776
8924
  async function readUnderseedThresholdFromConfig(projectRoot) {
8777
- const configPath = join19(projectRoot, ".fabric", "fabric-config.json");
8925
+ const configPath = join20(projectRoot, ".fabric", "fabric-config.json");
8778
8926
  try {
8779
- const raw = await readFile14(configPath, "utf8");
8927
+ const raw = await readFile15(configPath, "utf8");
8780
8928
  const parsed = JSON.parse(raw);
8781
8929
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8782
8930
  const v = parsed.underseed_node_threshold;
@@ -8892,10 +9040,10 @@ async function inspectOnboardCoverage(projectRoot) {
8892
9040
  return { filled, missing, opted_out: optedOut };
8893
9041
  }
8894
9042
  async function readOnboardOptedOut(projectRoot) {
8895
- const path = join19(projectRoot, ".fabric", "fabric-config.json");
9043
+ const path = join20(projectRoot, ".fabric", "fabric-config.json");
8896
9044
  let raw;
8897
9045
  try {
8898
- raw = await readFile14(path, "utf8");
9046
+ raw = await readFile15(path, "utf8");
8899
9047
  } catch {
8900
9048
  return [];
8901
9049
  }
@@ -8997,22 +9145,22 @@ async function* iterateCanonicalFilenames(projectRoot) {
8997
9145
  }
8998
9146
  }
8999
9147
  async function rewriteThreeEndManagedBlocks(projectRoot) {
9000
- const snapshotPath = join19(projectRoot, ".fabric", "AGENTS.md");
9148
+ const snapshotPath = join20(projectRoot, ".fabric", "AGENTS.md");
9001
9149
  if (!await pathExists(snapshotPath)) {
9002
9150
  return;
9003
9151
  }
9004
9152
  let snapshot;
9005
9153
  try {
9006
- snapshot = await readFile14(snapshotPath, "utf8");
9154
+ snapshot = await readFile15(snapshotPath, "utf8");
9007
9155
  } catch {
9008
9156
  return;
9009
9157
  }
9010
- const projectRulesPath = join19(projectRoot, ".fabric", "project-rules.md");
9158
+ const projectRulesPath = join20(projectRoot, ".fabric", "project-rules.md");
9011
9159
  const hasProjectRules = await pathExists(projectRulesPath);
9012
9160
  let expectedBody = snapshot;
9013
9161
  if (hasProjectRules) {
9014
9162
  try {
9015
- const projectRules = await readFile14(projectRulesPath, "utf8");
9163
+ const projectRules = await readFile15(projectRulesPath, "utf8");
9016
9164
  expectedBody = `${snapshot}
9017
9165
  ---
9018
9166
  ${projectRules}`;
@@ -9023,7 +9171,7 @@ ${projectRules}`;
9023
9171
  ${expectedBody}
9024
9172
  ${BOOTSTRAP_MARKER_END2}`;
9025
9173
  const blockTargets = [
9026
- join19(projectRoot, "AGENTS.md")
9174
+ join20(projectRoot, "AGENTS.md")
9027
9175
  ];
9028
9176
  for (const abs of blockTargets) {
9029
9177
  if (!await pathExists(abs)) {
@@ -9031,7 +9179,7 @@ ${BOOTSTRAP_MARKER_END2}`;
9031
9179
  }
9032
9180
  let existing;
9033
9181
  try {
9034
- existing = await readFile14(abs, "utf8");
9182
+ existing = await readFile15(abs, "utf8");
9035
9183
  } catch {
9036
9184
  continue;
9037
9185
  }
@@ -9056,11 +9204,11 @@ ${managedBlock}
9056
9204
  }
9057
9205
  await atomicWriteText4(abs, next);
9058
9206
  }
9059
- const claudeMdPath = join19(projectRoot, "CLAUDE.md");
9207
+ const claudeMdPath = join20(projectRoot, "CLAUDE.md");
9060
9208
  if (await pathExists(claudeMdPath)) {
9061
9209
  let claudeContent;
9062
9210
  try {
9063
- claudeContent = await readFile14(claudeMdPath, "utf8");
9211
+ claudeContent = await readFile15(claudeMdPath, "utf8");
9064
9212
  } catch {
9065
9213
  return;
9066
9214
  }
@@ -9126,7 +9274,7 @@ async function collectEntryPoints(root) {
9126
9274
  continue;
9127
9275
  }
9128
9276
  for (const entry of await readdirAsync(current, { withFileTypes: true })) {
9129
- const absolutePath = join19(current, entry.name);
9277
+ const absolutePath = join20(current, entry.name);
9130
9278
  const relativePath = normalizePath2(absolutePath.slice(root.length + 1));
9131
9279
  if (relativePath.length === 0) {
9132
9280
  continue;
@@ -9202,7 +9350,7 @@ async function enrichDescriptions(projectRoot, opts = {}) {
9202
9350
  scanned += 1;
9203
9351
  let source;
9204
9352
  try {
9205
- source = await readFile14(absPath, "utf8");
9353
+ source = await readFile15(absPath, "utf8");
9206
9354
  } catch {
9207
9355
  continue;
9208
9356
  }
@@ -9475,7 +9623,7 @@ import { IOFabricError as IOFabricError2, RuleError } from "@fenglimg/fabric-sha
9475
9623
 
9476
9624
  // src/services/read-ledger.ts
9477
9625
  import { randomUUID as randomUUID6 } from "crypto";
9478
- import { access as access5, copyFile, readFile as readFile15, rm } from "fs/promises";
9626
+ import { access as access5, copyFile, readFile as readFile16, rm } from "fs/promises";
9479
9627
  import { ledgerEntrySchema } from "@fenglimg/fabric-shared";
9480
9628
  async function resolveLedgerPaths(projectRoot) {
9481
9629
  const primaryPath = getLedgerPath(projectRoot);
@@ -9503,7 +9651,7 @@ async function readLegacyLedger(projectRoot) {
9503
9651
  const { readPath } = await resolveLedgerPaths(projectRoot);
9504
9652
  let raw;
9505
9653
  try {
9506
- raw = await readFile15(readPath, "utf8");
9654
+ raw = await readFile16(readPath, "utf8");
9507
9655
  } catch (error) {
9508
9656
  if (isNodeError(error) && error.code === "ENOENT") {
9509
9657
  return [];
@@ -9758,8 +9906,8 @@ function formatError(error) {
9758
9906
  }
9759
9907
  function formatPreexistingRootMessage(projectRoot) {
9760
9908
  const preexisting = [];
9761
- if (existsSync8(join20(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
9762
- if (existsSync8(join20(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
9909
+ if (existsSync9(join21(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
9910
+ if (existsSync9(join21(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
9763
9911
  if (preexisting.length === 0) return null;
9764
9912
  return `[startup] info: detected ${preexisting.join(", ")} at project root. Note: Fabric serves knowledge from mounted stores via MCP \u2014 root markdown files are not auto-loaded into the AI context.`;
9765
9913
  }
@@ -9785,7 +9933,7 @@ function createFabricServer(tracker) {
9785
9933
  const server = new McpServer(
9786
9934
  {
9787
9935
  name: "fabric-knowledge-server",
9788
- version: "2.2.0-rc.10"
9936
+ version: "2.2.0-rc.11"
9789
9937
  },
9790
9938
  {
9791
9939
  instructions: FABRIC_SERVER_INSTRUCTIONS
@@ -9804,10 +9952,10 @@ function createFabricServer(tracker) {
9804
9952
  },
9805
9953
  async (_uri) => {
9806
9954
  const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
9807
- const path = join20(projectRoot, ".fabric", "bootstrap", "README.md");
9955
+ const path = join21(projectRoot, ".fabric", "bootstrap", "README.md");
9808
9956
  let text = "";
9809
- if (existsSync8(path)) {
9810
- text = await readFile16(path, "utf8");
9957
+ if (existsSync9(path)) {
9958
+ text = await readFile17(path, "utf8");
9811
9959
  }
9812
9960
  return {
9813
9961
  contents: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-server",
3
- "version": "2.2.0-rc.10",
3
+ "version": "2.2.0-rc.11",
4
4
  "description": "Fabric MCP knowledge server — stdio transport for Claude Code / Codex CLI, manages .fabric/ knowledge base + agents.meta.json + event ledger.",
5
5
  "license": "MIT",
6
6
  "author": "wangzhichao <fenglimg90@gmail.com>",
@@ -37,7 +37,7 @@
37
37
  "@modelcontextprotocol/sdk": "^1.29.0",
38
38
  "minimatch": "^10.0.1",
39
39
  "zod": "^3.25.0",
40
- "@fenglimg/fabric-shared": "2.2.0-rc.10"
40
+ "@fenglimg/fabric-shared": "2.2.0-rc.11"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^22.15.0",