@fenglimg/fabric-server 2.2.0-rc.9 → 2.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.
package/dist/index.d.ts CHANGED
@@ -62,6 +62,9 @@ interface KnowledgeCensus {
62
62
  personal: number;
63
63
  project: number;
64
64
  };
65
+ broad_by_type: Record<string, number>;
66
+ /** count of narrow-scope kept entries (file-specific; only合计, not per-type). */
67
+ narrow_total: number;
65
68
  /** entries专属 to OTHER projects that filterByActiveProject removed. */
66
69
  dropped_other_project: number;
67
70
  /** kept (post-filter) total. */
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";
@@ -2028,6 +2028,8 @@ async function buildKnowledgeCensus(projectRoot) {
2028
2028
  const census = {
2029
2029
  by_type: {},
2030
2030
  by_layer: { team: 0, personal: 0, project: 0 },
2031
+ broad_by_type: {},
2032
+ narrow_total: 0,
2031
2033
  dropped_other_project: 0,
2032
2034
  total: 0
2033
2035
  };
@@ -2037,10 +2039,16 @@ async function buildKnowledgeCensus(projectRoot) {
2037
2039
  const kept = filterByActiveProject(all, activeProject);
2038
2040
  census.dropped_other_project = all.length - kept.length;
2039
2041
  for (const entry of kept) {
2040
- const type = extractRuleDescription(entry.source)?.knowledge_type;
2042
+ const desc = extractRuleDescription(entry.source);
2043
+ const type = desc?.knowledge_type;
2044
+ const isNarrow = desc?.relevance_scope === "narrow";
2041
2045
  if (typeof type === "string") {
2042
2046
  census.by_type[type] = (census.by_type[type] ?? 0) + 1;
2047
+ if (!isNarrow) {
2048
+ census.broad_by_type[type] = (census.broad_by_type[type] ?? 0) + 1;
2049
+ }
2043
2050
  }
2051
+ if (isNarrow) census.narrow_total += 1;
2044
2052
  if (scopeRoot(entry.semanticScope) === "project") {
2045
2053
  census.by_layer.project += 1;
2046
2054
  } else {
@@ -2064,6 +2072,7 @@ async function buildAlwaysActiveBodies(projectRoot) {
2064
2072
  if (desc === void 0) continue;
2065
2073
  const type = desc.knowledge_type;
2066
2074
  if (typeof type !== "string" || !ALWAYS_ACTIVE_TYPES.has(type)) continue;
2075
+ if (desc.relevance_scope === "narrow") continue;
2067
2076
  out.push({
2068
2077
  stable_id: entry.qualifiedId,
2069
2078
  type,
@@ -3406,11 +3415,155 @@ import { enforcePayloadLimit as enforcePayloadLimit4 } from "@fenglimg/fabric-sh
3406
3415
 
3407
3416
  // src/services/review.ts
3408
3417
  import { execFileSync } from "child_process";
3409
- import { existsSync as existsSync4 } from "fs";
3410
- 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";
3411
3420
  import { homedir } from "os";
3412
- 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";
3413
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
3414
3567
  var PLURAL_TYPES = [
3415
3568
  "decisions",
3416
3569
  "pitfalls",
@@ -3517,6 +3670,10 @@ function isVisibleByLifecycle(fm, filters) {
3517
3670
  return true;
3518
3671
  }
3519
3672
  async function listPending(projectRoot, filters) {
3673
+ try {
3674
+ await mergePendingTwins(projectRoot);
3675
+ } catch {
3676
+ }
3520
3677
  const items = [];
3521
3678
  const typesToScan = filters?.type !== void 0 ? [filters.type] : PLURAL_TYPES;
3522
3679
  const sources = [];
@@ -3536,22 +3693,22 @@ async function listPending(projectRoot, filters) {
3536
3693
  }
3537
3694
  for (const source of sources) {
3538
3695
  for (const type of typesToScan) {
3539
- const dir = join9(source.root, type);
3540
- if (!existsSync4(dir)) {
3696
+ const dir = join10(source.root, type);
3697
+ if (!existsSync5(dir)) {
3541
3698
  continue;
3542
3699
  }
3543
3700
  let entries;
3544
3701
  try {
3545
- entries = await readdir(dir);
3702
+ entries = await readdir2(dir);
3546
3703
  } catch {
3547
3704
  continue;
3548
3705
  }
3549
3706
  for (const name of entries) {
3550
3707
  if (!name.endsWith(".md")) continue;
3551
- const absolutePath = join9(dir, name);
3708
+ const absolutePath = join10(dir, name);
3552
3709
  let content;
3553
3710
  try {
3554
- content = await readFile5(absolutePath, "utf8");
3711
+ content = await readFile6(absolutePath, "utf8");
3555
3712
  } catch {
3556
3713
  continue;
3557
3714
  }
@@ -3661,7 +3818,7 @@ async function approveOne(projectRoot, pendingPath) {
3661
3818
  let targetAbs;
3662
3819
  let writtenTarget = false;
3663
3820
  try {
3664
- const content = await readFile5(sourceAbs, "utf8");
3821
+ const content = await readFile6(sourceAbs, "utf8");
3665
3822
  const fm = parseFrontmatter(content);
3666
3823
  const pluralType = fm.type;
3667
3824
  if (pluralType === void 0 || !PLURAL_TYPES.includes(pluralType)) {
@@ -3675,14 +3832,14 @@ async function approveOne(projectRoot, pendingPath) {
3675
3832
  );
3676
3833
  allocatedId = stableId;
3677
3834
  const newFilename = `${stableId}--${slug}.md`;
3678
- targetAbs = join9(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
3835
+ targetAbs = join10(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
3679
3836
  await ensureParentDirectory(targetAbs);
3680
3837
  const rewritten = rewriteFrontmatterForPromote(content, stableId);
3681
3838
  await atomicWriteText(targetAbs, rewritten);
3682
3839
  writtenTarget = true;
3683
3840
  if (sourceIsStore) {
3684
- if (existsSync4(sourceAbs)) {
3685
- await unlink(sourceAbs);
3841
+ if (existsSync5(sourceAbs)) {
3842
+ await unlink2(sourceAbs);
3686
3843
  }
3687
3844
  } else if (sourceOrigin === "team") {
3688
3845
  try {
@@ -3691,13 +3848,13 @@ async function approveOne(projectRoot, pendingPath) {
3691
3848
  stdio: ["ignore", "pipe", "pipe"]
3692
3849
  });
3693
3850
  } catch {
3694
- if (existsSync4(sourceAbs)) {
3695
- await unlink(sourceAbs);
3851
+ if (existsSync5(sourceAbs)) {
3852
+ await unlink2(sourceAbs);
3696
3853
  }
3697
3854
  }
3698
3855
  } else {
3699
- if (existsSync4(sourceAbs)) {
3700
- await unlink(sourceAbs);
3856
+ if (existsSync5(sourceAbs)) {
3857
+ await unlink2(sourceAbs);
3701
3858
  }
3702
3859
  }
3703
3860
  await emitEventBestEffort2(projectRoot, {
@@ -3708,9 +3865,9 @@ async function approveOne(projectRoot, pendingPath) {
3708
3865
  });
3709
3866
  return { pending_path: pendingPath, stable_id: stableId };
3710
3867
  } catch (err) {
3711
- if (writtenTarget && targetAbs !== void 0 && existsSync4(targetAbs)) {
3868
+ if (writtenTarget && targetAbs !== void 0 && existsSync5(targetAbs)) {
3712
3869
  try {
3713
- await unlink(targetAbs);
3870
+ await unlink2(targetAbs);
3714
3871
  } catch {
3715
3872
  }
3716
3873
  }
@@ -3729,14 +3886,14 @@ async function rejectAll(projectRoot, pendingPaths, reason) {
3729
3886
  for (const pendingPath of pendingPaths) {
3730
3887
  try {
3731
3888
  const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
3732
- if (existsSync4(sandboxed.abs)) {
3733
- const content = await readFile5(sandboxed.abs, "utf8");
3889
+ if (existsSync5(sandboxed.abs)) {
3890
+ const content = await readFile6(sandboxed.abs, "utf8");
3734
3891
  const merged = rewriteFrontmatterMerge(content, { status: "rejected" });
3735
3892
  const rejectedAbs = sandboxed.abs.includes(`${sep2}pending${sep2}`) ? sandboxed.abs.replace(`${sep2}pending${sep2}`, `${sep2}rejected${sep2}`) : null;
3736
3893
  if (rejectedAbs !== null) {
3737
3894
  await ensureParentDirectory(rejectedAbs);
3738
3895
  await atomicWriteText(rejectedAbs, merged);
3739
- await unlink(sandboxed.abs);
3896
+ await unlink2(sandboxed.abs);
3740
3897
  } else if (merged !== content) {
3741
3898
  await atomicWriteText(sandboxed.abs, merged);
3742
3899
  }
@@ -3757,7 +3914,7 @@ async function modifyEntry(projectRoot, pendingPath, changes) {
3757
3914
  if (target === null) {
3758
3915
  throw new Error(`modify target not found: ${pendingPath}`);
3759
3916
  }
3760
- const content = await readFile5(target.absPath, "utf8");
3917
+ const content = await readFile6(target.absPath, "utf8");
3761
3918
  const fm = parseFrontmatter(content);
3762
3919
  const currentLayer = fm.layer ?? "team";
3763
3920
  if (changes.semantic_scope !== void 0 && isPersonalScope2(changes.semantic_scope)) {
@@ -3802,7 +3959,7 @@ function resolveModifyTarget(projectRoot, pendingPath) {
3802
3959
  } catch {
3803
3960
  return null;
3804
3961
  }
3805
- if (existsSync4(sandboxed.abs)) {
3962
+ if (existsSync5(sandboxed.abs)) {
3806
3963
  return {
3807
3964
  absPath: sandboxed.abs,
3808
3965
  isInProjectTree: sandboxed.isInProjectTree,
@@ -3849,7 +4006,7 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
3849
4006
  pluralType,
3850
4007
  resolveWriteTargetStoreDir(toLayer, projectRoot)
3851
4008
  );
3852
- const toAbs = join9(
4009
+ const toAbs = join10(
3853
4010
  resolveStoreCanonicalBase(toLayer, projectRoot),
3854
4011
  pluralType,
3855
4012
  `${newStableId}--${slug}.md`
@@ -3877,12 +4034,12 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
3877
4034
  stdio: ["ignore", "pipe", "pipe"]
3878
4035
  });
3879
4036
  } catch {
3880
- if (existsSync4(target.absPath)) {
3881
- await unlink(target.absPath);
4037
+ if (existsSync5(target.absPath)) {
4038
+ await unlink2(target.absPath);
3882
4039
  }
3883
4040
  }
3884
- } else if (existsSync4(target.absPath)) {
3885
- await unlink(target.absPath);
4041
+ } else if (existsSync5(target.absPath)) {
4042
+ await unlink2(target.absPath);
3886
4043
  }
3887
4044
  const flipReason = `layer_flip:${priorStableId ?? "<unassigned>"}->${newStableId}`;
3888
4045
  const flipTimestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -3945,10 +4102,10 @@ function getSearchDirectoryCache(cacheKey) {
3945
4102
  return created;
3946
4103
  }
3947
4104
  async function listIndexedSearchEntries(source, type) {
3948
- const dir = join9(source.root, type);
4105
+ const dir = join10(source.root, type);
3949
4106
  let entries;
3950
4107
  try {
3951
- entries = await readdir(dir);
4108
+ entries = await readdir2(dir);
3952
4109
  } catch {
3953
4110
  return [];
3954
4111
  }
@@ -3958,7 +4115,7 @@ async function listIndexedSearchEntries(source, type) {
3958
4115
  const indexed = [];
3959
4116
  for (const name of entries) {
3960
4117
  if (!name.endsWith(".md")) continue;
3961
- const absolutePath = join9(dir, name);
4118
+ const absolutePath = join10(dir, name);
3962
4119
  let fingerprint;
3963
4120
  try {
3964
4121
  const st = await stat2(absolutePath);
@@ -3975,7 +4132,7 @@ async function listIndexedSearchEntries(source, type) {
3975
4132
  }
3976
4133
  let content;
3977
4134
  try {
3978
- content = await readFile5(absolutePath, "utf8");
4135
+ content = await readFile6(absolutePath, "utf8");
3979
4136
  searchEntryIndexContentReads += 1;
3980
4137
  } catch {
3981
4138
  directoryCache.files.delete(absolutePath);
@@ -4095,8 +4252,8 @@ async function deferAll(projectRoot, pendingPaths, until, reason) {
4095
4252
  let stableId;
4096
4253
  try {
4097
4254
  const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
4098
- if (existsSync4(sandboxed.abs)) {
4099
- const content = await readFile5(sandboxed.abs, "utf8");
4255
+ if (existsSync5(sandboxed.abs)) {
4256
+ const content = await readFile6(sandboxed.abs, "utf8");
4100
4257
  stableId = parseFrontmatter(content).id;
4101
4258
  const patch = {
4102
4259
  status: "deferred",
@@ -4368,9 +4525,9 @@ function registerReview(server, tracker) {
4368
4525
  }
4369
4526
 
4370
4527
  // src/services/doctor.ts
4371
- 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";
4372
4529
  import { constants as constants2 } from "fs";
4373
- 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";
4374
4531
  import {
4375
4532
  createTranslator,
4376
4533
  forensicReportSchema,
@@ -4584,8 +4741,8 @@ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
4584
4741
  }
4585
4742
 
4586
4743
  // src/services/doctor-stable-id-collision.ts
4587
- import { readFile as readFile6 } from "fs/promises";
4588
- 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";
4589
4746
  import {
4590
4747
  buildStoreResolveInput as buildStoreResolveInput4,
4591
4748
  createStoreResolver as createStoreResolver4,
@@ -4612,7 +4769,7 @@ function resolveIntegrityStores(projectRoot) {
4612
4769
  return {
4613
4770
  store_uuid: entry.store_uuid,
4614
4771
  alias: entry.alias,
4615
- dir: join10(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
4772
+ dir: join11(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
4616
4773
  };
4617
4774
  });
4618
4775
  return { dirs, personalUuids };
@@ -4631,7 +4788,7 @@ async function inspectStoreStableIdIntegrity(projectRoot) {
4631
4788
  for (const ref of await readKnowledgeAcrossStores2(resolved.dirs)) {
4632
4789
  let source;
4633
4790
  try {
4634
- source = await readFile6(ref.file, "utf8");
4791
+ source = await readFile7(ref.file, "utf8");
4635
4792
  } catch {
4636
4793
  continue;
4637
4794
  }
@@ -4715,8 +4872,8 @@ function createLayerMismatchCheck(t, inspection) {
4715
4872
 
4716
4873
  // src/services/doctor-relevance-paths.ts
4717
4874
  import { execFileSync as execFileSync2 } from "child_process";
4718
- import { existsSync as existsSync5, readdirSync, statSync as statSync3 } from "fs";
4719
- 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";
4720
4877
  import { minimatch } from "minimatch";
4721
4878
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
4722
4879
  var RELEVANCE_PATHS_DRIFT_WINDOW_DAYS = 90;
@@ -4737,7 +4894,7 @@ function expandGlob(rawGlob) {
4737
4894
  return rawGlob.endsWith("/") ? `${rawGlob}**` : rawGlob;
4738
4895
  }
4739
4896
  function collectWorkspacePaths(projectRoot) {
4740
- if (!existsSync5(projectRoot)) {
4897
+ if (!existsSync6(projectRoot)) {
4741
4898
  return [];
4742
4899
  }
4743
4900
  try {
@@ -4759,7 +4916,7 @@ function collectWorkspacePaths(projectRoot) {
4759
4916
  continue;
4760
4917
  }
4761
4918
  for (const entry of entries) {
4762
- const abs = join11(current, entry.name);
4919
+ const abs = join12(current, entry.name);
4763
4920
  const rel = toPosix(abs.slice(projectRoot.length + 1));
4764
4921
  if (rel.length === 0) continue;
4765
4922
  if (entry.isDirectory()) {
@@ -4952,16 +5109,16 @@ function createNarrowNoPathsCheck(t, inspection) {
4952
5109
  }
4953
5110
 
4954
5111
  // src/services/doctor-broad-index.ts
4955
- import { readFile as readFile7 } from "fs/promises";
4956
- import { join as join12 } from "path";
5112
+ import { readFile as readFile8 } from "fs/promises";
5113
+ import { join as join13 } from "path";
4957
5114
  var DEFAULT_BROAD_INDEX_BACKSTOP = 50;
4958
5115
  var BROAD_INDEX_BACKSTOP_MIN = 20;
4959
5116
  var BROAD_INDEX_BACKSTOP_MAX = 500;
4960
5117
  var BROAD_INDEX_DRIFT_RATIO = 0.8;
4961
5118
  async function readBroadIndexBackstop(projectRoot) {
4962
- const configPath = join12(projectRoot, ".fabric", "fabric-config.json");
5119
+ const configPath = join13(projectRoot, ".fabric", "fabric-config.json");
4963
5120
  try {
4964
- const raw = await readFile7(configPath, "utf8");
5121
+ const raw = await readFile8(configPath, "utf8");
4965
5122
  const parsed = JSON.parse(raw);
4966
5123
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4967
5124
  const v = parsed.broad_index_backstop;
@@ -5142,8 +5299,8 @@ function createStaleArchiveCheck(t, inspection) {
5142
5299
  }
5143
5300
 
5144
5301
  // src/services/doctor-scope-lint.ts
5145
- import { readFile as readFile8 } from "fs/promises";
5146
- import { join as join13 } from "path";
5302
+ import { readFile as readFile9 } from "fs/promises";
5303
+ import { join as join14 } from "path";
5147
5304
  import {
5148
5305
  buildStoreResolveInput as buildStoreResolveInput5,
5149
5306
  createStoreResolver as createStoreResolver5,
@@ -5178,7 +5335,7 @@ async function resolveLintStores(projectRoot) {
5178
5335
  const globalRoot = resolveGlobalRoot4();
5179
5336
  return Promise.all(readSet.stores.map(async (entry) => {
5180
5337
  const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
5181
- const dir = join13(
5338
+ const dir = join14(
5182
5339
  globalRoot,
5183
5340
  storeRelativePathForMount4(mounted ?? { store_uuid: entry.store_uuid })
5184
5341
  );
@@ -5210,7 +5367,7 @@ async function lintStoreScopes(projectRoot) {
5210
5367
  }
5211
5368
  let source;
5212
5369
  try {
5213
- source = await readFile8(ref.file, "utf8");
5370
+ source = await readFile9(ref.file, "utf8");
5214
5371
  } catch {
5215
5372
  continue;
5216
5373
  }
@@ -5332,8 +5489,8 @@ function createUnboundProjectCheck(t, violation) {
5332
5489
  }
5333
5490
 
5334
5491
  // src/services/doctor-store-counters.ts
5335
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
5336
- 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";
5337
5494
  import {
5338
5495
  buildStoreResolveInput as buildStoreResolveInput6,
5339
5496
  createStoreResolver as createStoreResolver6,
@@ -5360,7 +5517,7 @@ function resolveCounterStores(projectRoot) {
5360
5517
  return readSet.stores.map((entry) => ({
5361
5518
  uuid: entry.store_uuid,
5362
5519
  alias: entry.alias,
5363
- dir: join14(
5520
+ dir: join15(
5364
5521
  globalRoot,
5365
5522
  storeRelativePathForMount5(
5366
5523
  input.mountedStores.find((s) => s.store_uuid === entry.store_uuid) ?? {
@@ -5388,8 +5545,8 @@ function readEntryId(file) {
5388
5545
  function computeStoreDiskMax(storeDir) {
5389
5546
  const max = defaultAgentsMetaCounters();
5390
5547
  for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
5391
- const dir = join14(storeDir, STORE_LAYOUT2.knowledgeDir, type);
5392
- if (!existsSync6(dir)) {
5548
+ const dir = join15(storeDir, STORE_LAYOUT2.knowledgeDir, type);
5549
+ if (!existsSync7(dir)) {
5393
5550
  continue;
5394
5551
  }
5395
5552
  let names;
@@ -5402,7 +5559,7 @@ function computeStoreDiskMax(storeDir) {
5402
5559
  if (!name.endsWith(".md")) {
5403
5560
  continue;
5404
5561
  }
5405
- const parsed = parseKnowledgeId2(readEntryId(join14(dir, name)) ?? "");
5562
+ const parsed = parseKnowledgeId2(readEntryId(join15(dir, name)) ?? "");
5406
5563
  if (parsed === null) {
5407
5564
  continue;
5408
5565
  }
@@ -5485,7 +5642,7 @@ function createStoreCounterCheck(t, drifts) {
5485
5642
 
5486
5643
  // src/services/doctor-store-orphan.ts
5487
5644
  import { readdirSync as readdirSync3 } from "fs";
5488
- import { join as join15 } from "path";
5645
+ import { join as join16 } from "path";
5489
5646
  import {
5490
5647
  STORES_ROOT_DIR,
5491
5648
  addMountedStore,
@@ -5509,15 +5666,15 @@ function inspectStoreOrphans(globalRoot = resolveGlobalRoot6()) {
5509
5666
  return [];
5510
5667
  }
5511
5668
  const registered = new Set(config.stores.map((s) => s.store_uuid));
5512
- const storesRoot = join15(globalRoot, STORES_ROOT_DIR);
5669
+ const storesRoot = join16(globalRoot, STORES_ROOT_DIR);
5513
5670
  const orphans = [];
5514
5671
  for (const group of listDir(storesRoot)) {
5515
5672
  if (group === STORE_BY_ALIAS_DIR) {
5516
5673
  continue;
5517
5674
  }
5518
- const groupDir = join15(storesRoot, group);
5675
+ const groupDir = join16(storesRoot, group);
5519
5676
  for (const mount of listDir(groupDir)) {
5520
- const dir = join15(groupDir, mount);
5677
+ const dir = join16(groupDir, mount);
5521
5678
  const identity = readStoreIdentity(dir);
5522
5679
  if (identity === null || registered.has(identity.store_uuid)) {
5523
5680
  continue;
@@ -5590,7 +5747,7 @@ import {
5590
5747
 
5591
5748
  // src/services/events-jsonl-gates.ts
5592
5749
  import { promises as fs } from "fs";
5593
- import { existsSync as existsSync7 } from "fs";
5750
+ import { existsSync as existsSync8 } from "fs";
5594
5751
  var EVENTS_JSONL_SIZE_WARN_BYTES = 10 * 1024 * 1024;
5595
5752
  var METRICS_STALE_WARN_MS = 10 * 60 * 1e3;
5596
5753
  var ROTATION_OVERDUE_WARN_MS = 90 * 24 * 60 * 60 * 1e3;
@@ -5612,7 +5769,7 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
5612
5769
  if (!(isNodeError(error) && error.code === "ENOENT")) throw error;
5613
5770
  }
5614
5771
  let metricsStalenessMs = null;
5615
- if (existsSync7(metricsPath)) {
5772
+ if (existsSync8(metricsPath)) {
5616
5773
  try {
5617
5774
  const stat4 = await fs.stat(metricsPath);
5618
5775
  metricsStalenessMs = Math.max(0, now.getTime() - stat4.mtimeMs);
@@ -5654,8 +5811,8 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
5654
5811
  }
5655
5812
 
5656
5813
  // src/services/doctor-skill-lints.ts
5657
- import { readdir as readdir2, readFile as readFile9 } from "fs/promises";
5658
- 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";
5659
5816
  var FABRIC_SKILL_SLUGS = ["fabric-archive", "fabric-review", "fabric-import"];
5660
5817
  var ROUTER_VALID_LEAF_SLUGS = /* @__PURE__ */ new Set([
5661
5818
  "fabric-archive",
@@ -5686,7 +5843,7 @@ function issueCheck(name, status, kind, code, message, actionHint, audience) {
5686
5843
  }
5687
5844
  async function listMarkdownFiles(dir) {
5688
5845
  try {
5689
- return (await readdir2(dir)).filter((name) => name.endsWith(".md"));
5846
+ return (await readdir3(dir)).filter((name) => name.endsWith(".md"));
5690
5847
  } catch {
5691
5848
  return null;
5692
5849
  }
@@ -5694,8 +5851,8 @@ async function listMarkdownFiles(dir) {
5694
5851
  async function inspectSkillRefMirror(projectRoot) {
5695
5852
  const driftedPaths = [];
5696
5853
  for (const slug of FABRIC_SKILL_SLUGS) {
5697
- const claudeRef = join16(projectRoot, ".claude", "skills", slug, "ref");
5698
- 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");
5699
5856
  const [claudeFiles, codexFiles] = await Promise.all([listMarkdownFiles(claudeRef), listMarkdownFiles(codexRef)]);
5700
5857
  if (claudeFiles === null || codexFiles === null) continue;
5701
5858
  const claudeSet = new Set(claudeFiles);
@@ -5712,8 +5869,8 @@ async function inspectSkillRefMirror(projectRoot) {
5712
5869
  let codexBody;
5713
5870
  try {
5714
5871
  [claudeBody, codexBody] = await Promise.all([
5715
- readFile9(join16(claudeRef, fname), "utf8"),
5716
- readFile9(join16(codexRef, fname), "utf8")
5872
+ readFile10(join17(claudeRef, fname), "utf8"),
5873
+ readFile10(join17(codexRef, fname), "utf8")
5717
5874
  ]);
5718
5875
  } catch {
5719
5876
  continue;
@@ -5732,10 +5889,10 @@ async function inspectSkillTokenBudget(projectRoot) {
5732
5889
  const overSize = [];
5733
5890
  let highestSeverity = "ok";
5734
5891
  for (const slug of FABRIC_SKILL_SLUGS) {
5735
- const skillMdPath = join16(projectRoot, ".claude", "skills", slug, "SKILL.md");
5892
+ const skillMdPath = join17(projectRoot, ".claude", "skills", slug, "SKILL.md");
5736
5893
  let body;
5737
5894
  try {
5738
- body = await readFile9(skillMdPath, "utf8");
5895
+ body = await readFile10(skillMdPath, "utf8");
5739
5896
  } catch {
5740
5897
  continue;
5741
5898
  }
@@ -5756,10 +5913,10 @@ async function inspectSkillDescription(projectRoot) {
5756
5913
  const CJK_PATTERN = /[\u3400-\u4dbf\u4e00-\u9fff]/u;
5757
5914
  const ASCII_PATTERN = /[a-zA-Z]{2,}/u;
5758
5915
  for (const slug of FABRIC_SKILL_SLUGS) {
5759
- const skillMdPath = join16(projectRoot, ".claude", "skills", slug, "SKILL.md");
5916
+ const skillMdPath = join17(projectRoot, ".claude", "skills", slug, "SKILL.md");
5760
5917
  let body;
5761
5918
  try {
5762
- body = await readFile9(skillMdPath, "utf8");
5919
+ body = await readFile10(skillMdPath, "utf8");
5763
5920
  } catch {
5764
5921
  continue;
5765
5922
  }
@@ -5790,19 +5947,19 @@ async function inspectSkillDescription(projectRoot) {
5790
5947
  async function inspectSkillMdYamlInvalid(projectRoot) {
5791
5948
  const candidates = [];
5792
5949
  for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
5793
- const rootAbs = join16(projectRoot, rootRel);
5950
+ const rootAbs = join17(projectRoot, rootRel);
5794
5951
  let dirEntries;
5795
5952
  try {
5796
- dirEntries = await readdir2(rootAbs, { withFileTypes: true });
5953
+ dirEntries = await readdir3(rootAbs, { withFileTypes: true });
5797
5954
  } catch {
5798
5955
  continue;
5799
5956
  }
5800
5957
  for (const dirEntry of dirEntries) {
5801
5958
  if (!dirEntry.isDirectory()) continue;
5802
- const skillFile = join16(rootAbs, dirEntry.name, "SKILL.md");
5959
+ const skillFile = join17(rootAbs, dirEntry.name, "SKILL.md");
5803
5960
  let raw;
5804
5961
  try {
5805
- raw = await readFile9(skillFile, "utf8");
5962
+ raw = await readFile10(skillFile, "utf8");
5806
5963
  } catch {
5807
5964
  continue;
5808
5965
  }
@@ -5871,13 +6028,13 @@ function extractMarkdownSectionBody(markdown, sectionName) {
5871
6028
  }
5872
6029
  async function inspectRouterChainRef(projectRoot) {
5873
6030
  const candidatePaths = [
5874
- join16(projectRoot, ".claude", "skills", "fabric", "SKILL.md"),
5875
- join16(projectRoot, ".codex", "skills", "fabric", "SKILL.md")
6031
+ join17(projectRoot, ".claude", "skills", "fabric", "SKILL.md"),
6032
+ join17(projectRoot, ".codex", "skills", "fabric", "SKILL.md")
5876
6033
  ];
5877
6034
  let body = null;
5878
6035
  for (const candidate of candidatePaths) {
5879
6036
  try {
5880
- body = await readFile9(candidate, "utf8");
6037
+ body = await readFile10(candidate, "utf8");
5881
6038
  break;
5882
6039
  } catch {
5883
6040
  }
@@ -5988,8 +6145,8 @@ function createSkillMdYamlInvalidCheck(t, inspection) {
5988
6145
 
5989
6146
  // src/services/doctor-hooks-lints.ts
5990
6147
  import { constants } from "fs";
5991
- import { access, readdir as readdir3, readFile as readFile10, stat as stat3 } from "fs/promises";
5992
- 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";
5993
6150
  import { Script } from "vm";
5994
6151
  var HOOKS_RUNTIME_CLIENT_DIRS = [
5995
6152
  { client: "claude", dir: ".claude/hooks" },
@@ -6038,7 +6195,7 @@ function isHookWiredForEvent(hooks, event, hookFile) {
6038
6195
  }
6039
6196
  async function readDirectoryFileNames(dir) {
6040
6197
  try {
6041
- return await readdir3(dir);
6198
+ return await readdir4(dir);
6042
6199
  } catch {
6043
6200
  return null;
6044
6201
  }
@@ -6051,14 +6208,14 @@ async function isFile(absPath) {
6051
6208
  }
6052
6209
  }
6053
6210
  async function inspectHooksWired(projectRoot) {
6054
- const claudeEntries = await readDirectoryFileNames(join17(projectRoot, ".claude"));
6211
+ const claudeEntries = await readDirectoryFileNames(join18(projectRoot, ".claude"));
6055
6212
  if (claudeEntries === null) {
6056
6213
  return { status: "skipped", missingHooks: [] };
6057
6214
  }
6058
- const settingsPath = join17(projectRoot, ".claude", "settings.json");
6215
+ const settingsPath = join18(projectRoot, ".claude", "settings.json");
6059
6216
  let parsed;
6060
6217
  try {
6061
- parsed = JSON.parse(await readFile10(settingsPath, "utf8"));
6218
+ parsed = JSON.parse(await readFile11(settingsPath, "utf8"));
6062
6219
  } catch {
6063
6220
  return { status: "missing-settings", missingHooks: [] };
6064
6221
  }
@@ -6081,8 +6238,8 @@ async function inspectHooksWired(projectRoot) {
6081
6238
  }
6082
6239
  async function inspectHookCacheWritability(projectRoot) {
6083
6240
  const relPath = posix3.join(".fabric", ".cache");
6084
- const fabricDir = join17(projectRoot, ".fabric");
6085
- const cacheDir = join17(projectRoot, ".fabric", ".cache");
6241
+ const fabricDir = join18(projectRoot, ".fabric");
6242
+ const cacheDir = join18(projectRoot, ".fabric", ".cache");
6086
6243
  try {
6087
6244
  try {
6088
6245
  const cacheStats = await stat3(cacheDir);
@@ -6131,12 +6288,12 @@ async function inspectHookCacheWritability(projectRoot) {
6131
6288
  async function inspectHooksContentDrift(projectRoot) {
6132
6289
  const hookFilesByBasename = /* @__PURE__ */ new Map();
6133
6290
  for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
6134
- const absDir = join17(projectRoot, dir);
6291
+ const absDir = join18(projectRoot, dir);
6135
6292
  const entries = await readDirectoryFileNames(absDir);
6136
6293
  if (entries === null) continue;
6137
6294
  for (const name of entries) {
6138
6295
  if (!name.endsWith(".cjs")) continue;
6139
- const abs = join17(absDir, name);
6296
+ const abs = join18(absDir, name);
6140
6297
  if (!await isFile(abs)) continue;
6141
6298
  const arr = hookFilesByBasename.get(name) ?? [];
6142
6299
  arr.push({ client, abs });
@@ -6151,7 +6308,7 @@ async function inspectHooksContentDrift(projectRoot) {
6151
6308
  const hashes = [];
6152
6309
  for (const { client, abs } of copies) {
6153
6310
  try {
6154
- const body = await readFile10(abs, "utf8");
6311
+ const body = await readFile11(abs, "utf8");
6155
6312
  hashes.push({ client, sha: sha256(body) });
6156
6313
  } catch {
6157
6314
  }
@@ -6173,18 +6330,18 @@ async function inspectHooksRuntime(projectRoot) {
6173
6330
  const issues = [];
6174
6331
  let scanned = 0;
6175
6332
  for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
6176
- const absDir = join17(projectRoot, dir);
6333
+ const absDir = join18(projectRoot, dir);
6177
6334
  const entries = await readDirectoryFileNames(absDir);
6178
6335
  if (entries === null) continue;
6179
6336
  for (const name of entries) {
6180
6337
  if (!name.endsWith(".cjs")) continue;
6181
- const abs = join17(absDir, name);
6338
+ const abs = join18(absDir, name);
6182
6339
  const displayPath = `${dir}/${name}`;
6183
6340
  if (!await isFile(abs)) continue;
6184
6341
  scanned += 1;
6185
6342
  let body;
6186
6343
  try {
6187
- body = await readFile10(abs, "utf8");
6344
+ body = await readFile11(abs, "utf8");
6188
6345
  } catch (err) {
6189
6346
  issues.push({
6190
6347
  path: displayPath,
@@ -6321,8 +6478,8 @@ function createHookCacheWritabilityCheck(t, inspection) {
6321
6478
  }
6322
6479
 
6323
6480
  // src/services/doctor-bootstrap-lints.ts
6324
- import { access as access2, readFile as readFile11 } from "fs/promises";
6325
- import { join as join18 } from "path";
6481
+ import { access as access2, readFile as readFile12 } from "fs/promises";
6482
+ import { join as join19 } from "path";
6326
6483
  import {
6327
6484
  BOOTSTRAP_MARKER_BEGIN,
6328
6485
  BOOTSTRAP_MARKER_END,
@@ -6354,17 +6511,17 @@ async function fileExists(path) {
6354
6511
  }
6355
6512
  async function inspectBootstrapAnchor(projectRoot) {
6356
6513
  const [hasAgentsMd, hasClaudeMd] = await Promise.all([
6357
- fileExists(join18(projectRoot, "AGENTS.md")),
6358
- fileExists(join18(projectRoot, "CLAUDE.md"))
6514
+ fileExists(join19(projectRoot, "AGENTS.md")),
6515
+ fileExists(join19(projectRoot, "CLAUDE.md"))
6359
6516
  ]);
6360
6517
  return { hasAgentsMd, hasClaudeMd };
6361
6518
  }
6362
6519
  async function inspectL1BootstrapSnapshotDrift(target) {
6363
- const abs = join18(target, ".fabric", "AGENTS.md");
6520
+ const abs = join19(target, ".fabric", "AGENTS.md");
6364
6521
  const canonical = resolveBootstrapCanonical();
6365
6522
  let onDisk;
6366
6523
  try {
6367
- onDisk = await readFile11(abs, "utf8");
6524
+ onDisk = await readFile12(abs, "utf8");
6368
6525
  } catch {
6369
6526
  return { status: "missing", canonical, onDisk: null };
6370
6527
  }
@@ -6390,17 +6547,17 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
6390
6547
  );
6391
6548
  }
6392
6549
  async function inspectL2ManagedBlockDrift(target) {
6393
- const snapshotPath = join18(target, ".fabric", "AGENTS.md");
6550
+ const snapshotPath = join19(target, ".fabric", "AGENTS.md");
6394
6551
  let snapshot;
6395
6552
  try {
6396
- snapshot = await readFile11(snapshotPath, "utf8");
6553
+ snapshot = await readFile12(snapshotPath, "utf8");
6397
6554
  } catch {
6398
6555
  return { status: "ok", drifted: [] };
6399
6556
  }
6400
- const projectRulesPath = join18(target, ".fabric", "project-rules.md");
6557
+ const projectRulesPath = join19(target, ".fabric", "project-rules.md");
6401
6558
  let expectedBody = snapshot;
6402
6559
  try {
6403
- const projectRules = await readFile11(projectRulesPath, "utf8");
6560
+ const projectRules = await readFile12(projectRulesPath, "utf8");
6404
6561
  expectedBody = `${snapshot}
6405
6562
  ---
6406
6563
  ${projectRules}`;
@@ -6409,12 +6566,12 @@ ${projectRules}`;
6409
6566
  const drifted = [];
6410
6567
  let anyManagedBlockFound = false;
6411
6568
  const blockTargets = [
6412
- join18(target, "AGENTS.md")
6569
+ join19(target, "AGENTS.md")
6413
6570
  ];
6414
6571
  for (const abs of blockTargets) {
6415
6572
  let content;
6416
6573
  try {
6417
- content = await readFile11(abs, "utf8");
6574
+ content = await readFile12(abs, "utf8");
6418
6575
  } catch {
6419
6576
  continue;
6420
6577
  }
@@ -6437,9 +6594,9 @@ ${projectRules}`;
6437
6594
  drifted.push({ path: abs, expected: expectedBody, actual: body });
6438
6595
  }
6439
6596
  }
6440
- const claudeMdPath = join18(target, "CLAUDE.md");
6597
+ const claudeMdPath = join19(target, "CLAUDE.md");
6441
6598
  try {
6442
- const claudeContent = await readFile11(claudeMdPath, "utf8");
6599
+ const claudeContent = await readFile12(claudeMdPath, "utf8");
6443
6600
  anyManagedBlockFound = true;
6444
6601
  const lines = claudeContent.split(/\r?\n/u);
6445
6602
  const hasAtImport = lines.some((line) => line.trim() === "@.fabric/AGENTS.md");
@@ -6507,7 +6664,7 @@ import { appendFile as appendFile3 } from "fs/promises";
6507
6664
  import { minimatch as minimatch2 } from "minimatch";
6508
6665
 
6509
6666
  // src/services/cite-rollup.ts
6510
- import { readFile as readFile12 } from "fs/promises";
6667
+ import { readFile as readFile13 } from "fs/promises";
6511
6668
  import { createLedgerWriteQueue as createLedgerWriteQueue3 } from "@fenglimg/fabric-shared/node/atomic-write";
6512
6669
  var citeRollupQueue = createLedgerWriteQueue3();
6513
6670
  async function appendCiteRollupRow(projectRoot, row) {
@@ -6519,7 +6676,7 @@ async function readCiteRollup(projectRoot) {
6519
6676
  const path = getCiteRollupPath(projectRoot);
6520
6677
  let raw;
6521
6678
  try {
6522
- raw = await readFile12(path, "utf8");
6679
+ raw = await readFile13(path, "utf8");
6523
6680
  } catch (error) {
6524
6681
  if (isNodeError(error) && error.code === "ENOENT") return [];
6525
6682
  throw error;
@@ -7644,7 +7801,7 @@ async function runDoctorReport(target) {
7644
7801
  const globalCliVersion = process.env.VITEST === "true" ? { status: "ok", version: "test-skipped" } : inspectGlobalCliVersion();
7645
7802
  const targetFiles = Object.fromEntries(
7646
7803
  await Promise.all(
7647
- 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))])
7648
7805
  )
7649
7806
  );
7650
7807
  const checks = [
@@ -7844,7 +8001,7 @@ async function runDoctorFix(target) {
7844
8001
  const fixed = [];
7845
8002
  const ledgerWarnings = [];
7846
8003
  if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
7847
- const snapshotPath = join19(projectRoot, ".fabric", "AGENTS.md");
8004
+ const snapshotPath = join20(projectRoot, ".fabric", "AGENTS.md");
7848
8005
  await ensureParentDirectory(snapshotPath);
7849
8006
  await atomicWriteText4(snapshotPath, resolveBootstrapCanonical2());
7850
8007
  fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
@@ -7917,9 +8074,9 @@ async function runDoctorFix(target) {
7917
8074
  if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
7918
8075
  const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
7919
8076
  if (lockInspection.present && !lockInspection.pidAlive) {
7920
- const lockFilePath = join19(projectRoot, ".fabric", ".serve.lock");
8077
+ const lockFilePath = join20(projectRoot, ".fabric", ".serve.lock");
7921
8078
  try {
7922
- await unlink3(lockFilePath);
8079
+ await unlink4(lockFilePath);
7923
8080
  } catch (err) {
7924
8081
  const errno = err;
7925
8082
  if (errno.code !== "ENOENT") throw err;
@@ -8034,10 +8191,10 @@ function createApplyLintMessage(succeeded, failed, manualErrorCount) {
8034
8191
  }
8035
8192
  async function applySessionHintsStaleCleanup(projectRoot, candidate) {
8036
8193
  const detail = `deleted (${candidate.age_days}d old)`;
8037
- const absPath = join19(projectRoot, candidate.path);
8194
+ const absPath = join20(projectRoot, candidate.path);
8038
8195
  try {
8039
- const { unlink: unlink4 } = await import("fs/promises");
8040
- await unlink4(absPath);
8196
+ const { unlink: unlink5 } = await import("fs/promises");
8197
+ await unlink5(absPath);
8041
8198
  return {
8042
8199
  kind: "knowledge_session_hints_stale_cleanup",
8043
8200
  path: candidate.path,
@@ -8059,9 +8216,9 @@ function truncateErrorMessage(error) {
8059
8216
  return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
8060
8217
  }
8061
8218
  async function inspectForensic(projectRoot) {
8062
- const path = join19(projectRoot, ".fabric", "forensic.json");
8219
+ const path = join20(projectRoot, ".fabric", "forensic.json");
8063
8220
  try {
8064
- const parsed = forensicReportSchema.parse(JSON.parse(await readFile14(path, "utf8")));
8221
+ const parsed = forensicReportSchema.parse(JSON.parse(await readFile15(path, "utf8")));
8065
8222
  return { present: true, valid: true, report: parsed };
8066
8223
  } catch (error) {
8067
8224
  if (isMissingFileError(error)) {
@@ -8091,7 +8248,7 @@ async function inspectEventLedger(projectRoot) {
8091
8248
  try {
8092
8249
  await access4(path, constants2.W_OK);
8093
8250
  const { warnings } = await readEventLedger(projectRoot);
8094
- const raw = await readFile14(path, "utf8");
8251
+ const raw = await readFile15(path, "utf8");
8095
8252
  const invalidLine = raw.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).find((line) => !isValidJsonLine(line));
8096
8253
  const partialWarning = warnings.find((w) => w.kind === "partial_write_at_tail");
8097
8254
  const schemaVersionSamples = [];
@@ -8636,7 +8793,7 @@ async function inspectPreexistingRootFiles(projectRoot) {
8636
8793
  const candidates = ["CLAUDE.md", "AGENTS.md"];
8637
8794
  const detected = [];
8638
8795
  for (const name of candidates) {
8639
- if (await pathExists(join19(projectRoot, name))) {
8796
+ if (await pathExists(join20(projectRoot, name))) {
8640
8797
  detected.push(name);
8641
8798
  }
8642
8799
  }
@@ -8721,7 +8878,7 @@ async function buildLastActiveIndex(projectRoot) {
8721
8878
  return map;
8722
8879
  }
8723
8880
  async function inspectSessionHintsStale(projectRoot, now) {
8724
- const cacheDir = join19(projectRoot, ".fabric", ".cache");
8881
+ const cacheDir = join20(projectRoot, ".fabric", ".cache");
8725
8882
  let entries;
8726
8883
  try {
8727
8884
  entries = await readdirAsync(cacheDir, { withFileTypes: true });
@@ -8733,7 +8890,7 @@ async function inspectSessionHintsStale(projectRoot, now) {
8733
8890
  if (!entry.isFile()) continue;
8734
8891
  if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
8735
8892
  if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
8736
- const absPath = join19(cacheDir, entry.name);
8893
+ const absPath = join20(cacheDir, entry.name);
8737
8894
  let mtimeMs = 0;
8738
8895
  try {
8739
8896
  mtimeMs = (await statAsync(absPath)).mtimeMs;
@@ -8765,9 +8922,9 @@ function inspectStaleServeLock(projectRoot, now) {
8765
8922
  };
8766
8923
  }
8767
8924
  async function readUnderseedThresholdFromConfig(projectRoot) {
8768
- const configPath = join19(projectRoot, ".fabric", "fabric-config.json");
8925
+ const configPath = join20(projectRoot, ".fabric", "fabric-config.json");
8769
8926
  try {
8770
- const raw = await readFile14(configPath, "utf8");
8927
+ const raw = await readFile15(configPath, "utf8");
8771
8928
  const parsed = JSON.parse(raw);
8772
8929
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8773
8930
  const v = parsed.underseed_node_threshold;
@@ -8883,10 +9040,10 @@ async function inspectOnboardCoverage(projectRoot) {
8883
9040
  return { filled, missing, opted_out: optedOut };
8884
9041
  }
8885
9042
  async function readOnboardOptedOut(projectRoot) {
8886
- const path = join19(projectRoot, ".fabric", "fabric-config.json");
9043
+ const path = join20(projectRoot, ".fabric", "fabric-config.json");
8887
9044
  let raw;
8888
9045
  try {
8889
- raw = await readFile14(path, "utf8");
9046
+ raw = await readFile15(path, "utf8");
8890
9047
  } catch {
8891
9048
  return [];
8892
9049
  }
@@ -8988,22 +9145,22 @@ async function* iterateCanonicalFilenames(projectRoot) {
8988
9145
  }
8989
9146
  }
8990
9147
  async function rewriteThreeEndManagedBlocks(projectRoot) {
8991
- const snapshotPath = join19(projectRoot, ".fabric", "AGENTS.md");
9148
+ const snapshotPath = join20(projectRoot, ".fabric", "AGENTS.md");
8992
9149
  if (!await pathExists(snapshotPath)) {
8993
9150
  return;
8994
9151
  }
8995
9152
  let snapshot;
8996
9153
  try {
8997
- snapshot = await readFile14(snapshotPath, "utf8");
9154
+ snapshot = await readFile15(snapshotPath, "utf8");
8998
9155
  } catch {
8999
9156
  return;
9000
9157
  }
9001
- const projectRulesPath = join19(projectRoot, ".fabric", "project-rules.md");
9158
+ const projectRulesPath = join20(projectRoot, ".fabric", "project-rules.md");
9002
9159
  const hasProjectRules = await pathExists(projectRulesPath);
9003
9160
  let expectedBody = snapshot;
9004
9161
  if (hasProjectRules) {
9005
9162
  try {
9006
- const projectRules = await readFile14(projectRulesPath, "utf8");
9163
+ const projectRules = await readFile15(projectRulesPath, "utf8");
9007
9164
  expectedBody = `${snapshot}
9008
9165
  ---
9009
9166
  ${projectRules}`;
@@ -9014,7 +9171,7 @@ ${projectRules}`;
9014
9171
  ${expectedBody}
9015
9172
  ${BOOTSTRAP_MARKER_END2}`;
9016
9173
  const blockTargets = [
9017
- join19(projectRoot, "AGENTS.md")
9174
+ join20(projectRoot, "AGENTS.md")
9018
9175
  ];
9019
9176
  for (const abs of blockTargets) {
9020
9177
  if (!await pathExists(abs)) {
@@ -9022,7 +9179,7 @@ ${BOOTSTRAP_MARKER_END2}`;
9022
9179
  }
9023
9180
  let existing;
9024
9181
  try {
9025
- existing = await readFile14(abs, "utf8");
9182
+ existing = await readFile15(abs, "utf8");
9026
9183
  } catch {
9027
9184
  continue;
9028
9185
  }
@@ -9047,11 +9204,11 @@ ${managedBlock}
9047
9204
  }
9048
9205
  await atomicWriteText4(abs, next);
9049
9206
  }
9050
- const claudeMdPath = join19(projectRoot, "CLAUDE.md");
9207
+ const claudeMdPath = join20(projectRoot, "CLAUDE.md");
9051
9208
  if (await pathExists(claudeMdPath)) {
9052
9209
  let claudeContent;
9053
9210
  try {
9054
- claudeContent = await readFile14(claudeMdPath, "utf8");
9211
+ claudeContent = await readFile15(claudeMdPath, "utf8");
9055
9212
  } catch {
9056
9213
  return;
9057
9214
  }
@@ -9117,7 +9274,7 @@ async function collectEntryPoints(root) {
9117
9274
  continue;
9118
9275
  }
9119
9276
  for (const entry of await readdirAsync(current, { withFileTypes: true })) {
9120
- const absolutePath = join19(current, entry.name);
9277
+ const absolutePath = join20(current, entry.name);
9121
9278
  const relativePath = normalizePath2(absolutePath.slice(root.length + 1));
9122
9279
  if (relativePath.length === 0) {
9123
9280
  continue;
@@ -9193,7 +9350,7 @@ async function enrichDescriptions(projectRoot, opts = {}) {
9193
9350
  scanned += 1;
9194
9351
  let source;
9195
9352
  try {
9196
- source = await readFile14(absPath, "utf8");
9353
+ source = await readFile15(absPath, "utf8");
9197
9354
  } catch {
9198
9355
  continue;
9199
9356
  }
@@ -9466,7 +9623,7 @@ import { IOFabricError as IOFabricError2, RuleError } from "@fenglimg/fabric-sha
9466
9623
 
9467
9624
  // src/services/read-ledger.ts
9468
9625
  import { randomUUID as randomUUID6 } from "crypto";
9469
- 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";
9470
9627
  import { ledgerEntrySchema } from "@fenglimg/fabric-shared";
9471
9628
  async function resolveLedgerPaths(projectRoot) {
9472
9629
  const primaryPath = getLedgerPath(projectRoot);
@@ -9494,7 +9651,7 @@ async function readLegacyLedger(projectRoot) {
9494
9651
  const { readPath } = await resolveLedgerPaths(projectRoot);
9495
9652
  let raw;
9496
9653
  try {
9497
- raw = await readFile15(readPath, "utf8");
9654
+ raw = await readFile16(readPath, "utf8");
9498
9655
  } catch (error) {
9499
9656
  if (isNodeError(error) && error.code === "ENOENT") {
9500
9657
  return [];
@@ -9749,8 +9906,8 @@ function formatError(error) {
9749
9906
  }
9750
9907
  function formatPreexistingRootMessage(projectRoot) {
9751
9908
  const preexisting = [];
9752
- if (existsSync8(join20(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
9753
- 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");
9754
9911
  if (preexisting.length === 0) return null;
9755
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.`;
9756
9913
  }
@@ -9776,7 +9933,7 @@ function createFabricServer(tracker) {
9776
9933
  const server = new McpServer(
9777
9934
  {
9778
9935
  name: "fabric-knowledge-server",
9779
- version: "2.2.0-rc.9"
9936
+ version: "2.2.0"
9780
9937
  },
9781
9938
  {
9782
9939
  instructions: FABRIC_SERVER_INSTRUCTIONS
@@ -9795,10 +9952,10 @@ function createFabricServer(tracker) {
9795
9952
  },
9796
9953
  async (_uri) => {
9797
9954
  const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
9798
- const path = join20(projectRoot, ".fabric", "bootstrap", "README.md");
9955
+ const path = join21(projectRoot, ".fabric", "bootstrap", "README.md");
9799
9956
  let text = "";
9800
- if (existsSync8(path)) {
9801
- text = await readFile16(path, "utf8");
9957
+ if (existsSync9(path)) {
9958
+ text = await readFile17(path, "utf8");
9802
9959
  }
9803
9960
  return {
9804
9961
  contents: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-server",
3
- "version": "2.2.0-rc.9",
3
+ "version": "2.2.0",
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.9"
40
+ "@fenglimg/fabric-shared": "2.2.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^22.15.0",