@fenglimg/fabric-server 2.3.0-rc.2 → 2.3.0-rc.3

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
@@ -530,6 +530,31 @@ declare function runDoctorConflictLint(projectRoot: string, opts?: {
530
530
  judge?: ConflictJudge;
531
531
  }): Promise<ConflictLintReport>;
532
532
 
533
+ declare function readEmbedConfig(projectRoot: string): {
534
+ enabled: boolean;
535
+ weight: number;
536
+ model: string;
537
+ };
538
+ declare function readFusion(projectRoot: string): "additive" | "rrf" | "auto";
539
+
540
+ interface Embedder {
541
+ embed(texts: string[]): Promise<number[][]>;
542
+ }
543
+ declare const OPTIONAL_EMBED_PACKAGE = "fastembed";
544
+ declare function defaultEmbedCacheDir(): string;
545
+ /**
546
+ * Lazy-load the optional fastembed embedder, pinned to CPU + cache-only. Returns
547
+ * null (cached) when the package is not installed or initialization throws, so
548
+ * every caller degrades to the text-only path. Never throws.
549
+ *
550
+ * v2.1 ③: `modelName` (a fastembed EmbeddingModel enum value) selects the
551
+ * embedding model — the caller threads `embed_model` config through so the
552
+ * Chinese-heavy KB no longer embeds against fastembed's English default. The
553
+ * load is cached per-process; the FIRST model wins (a config change needs a
554
+ * server restart, already the norm for MCP config changes).
555
+ */
556
+ declare function loadEmbedder(modelName?: string): Promise<Embedder | null>;
557
+
533
558
  interface RetiredToken {
534
559
  /** The exact dead token as it appears in agent-facing text. */
535
560
  token: string;
@@ -949,4 +974,4 @@ interface ShutdownHandlerDeps {
949
974
  */
950
975
  declare function createShutdownHandler(deps: ShutdownHandlerDeps): () => void;
951
976
 
952
- export { AGENTS_MD_RESOURCE_URI, type AlwaysActiveBody, type ArchiveHistoryEntry, type ArchiveHistoryReport, COLD_EVAL_RUBRIC, type CiteCoverageReport, type ColdEvalBatch, type ColdEvalCandidate, type ColdEvalVerdict, type ConflictEntry, type ConflictJudge, type ConflictLintReport, type ConflictPair, type ConflictVerdict, DEFAULT_CONFLICT_SIMILARITY_THRESHOLD, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type EnrichDescriptionsCandidate, type EnrichDescriptionsMode, type EnrichDescriptionsReport, FABRIC_SERVER_INSTRUCTIONS, type HistoryAllReport, type HistoryDayRow, type InFlightTracker, type KnowledgeCensus, LEDGER_PATH, LEGACY_LEDGER_PATH, METRICS_LEDGER_PATH, METRIC_COUNTER_NAMES, type MetricCounterName, type MetricsRow, type PlanContextInput, type PlanContextResult, RETIRED_TOKENS, type RecallInput, type RecallResult, type RequirementProfile, type RetiredReferenceHit, type RetiredReferenceInspection, type RetiredToken, type SelectionTokenState, type ShutdownHandlerDeps, type SurfaceVerdict, type UnboundProjectViolation, type WhyNotSurfacedResult, appendEventLedgerEvent, buildAlwaysActiveBodies, buildColdEvalBatch, buildKnowledgeCensus, bumpCounter, contextCache, createFabricServer, createInFlightTracker, createShutdownHandler, detectUnboundProject, drainCounters, enrichDescriptions, explainWhyNotSurfaced, extractKnowledge, findConflictCandidates, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, inspectRetiredReferences, lintConflicts, loadConflictEntries, pairSimilarity, planContext, readEventLedger, readLedger, readMetrics, readSelectionToken, recall, rehydrateAgentsMetaAt, resolveLedgerPaths, reviewKnowledge, reviewPending, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorConflictLint, runDoctorFix, runDoctorHistoryAll, runDoctorReport, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick };
977
+ export { AGENTS_MD_RESOURCE_URI, type AlwaysActiveBody, type ArchiveHistoryEntry, type ArchiveHistoryReport, COLD_EVAL_RUBRIC, type CiteCoverageReport, type ColdEvalBatch, type ColdEvalCandidate, type ColdEvalVerdict, type ConflictEntry, type ConflictJudge, type ConflictLintReport, type ConflictPair, type ConflictVerdict, DEFAULT_CONFLICT_SIMILARITY_THRESHOLD, type DoctorApplyLintMutation, type DoctorApplyLintMutationKind, type DoctorApplyLintReport, type DoctorFixReport, type DoctorIssue, type DoctorReport, EVENT_LEDGER_PATH, type EnrichDescriptionsCandidate, type EnrichDescriptionsMode, type EnrichDescriptionsReport, FABRIC_SERVER_INSTRUCTIONS, type HistoryAllReport, type HistoryDayRow, type InFlightTracker, type KnowledgeCensus, LEDGER_PATH, LEGACY_LEDGER_PATH, METRICS_LEDGER_PATH, METRIC_COUNTER_NAMES, type MetricCounterName, type MetricsRow, OPTIONAL_EMBED_PACKAGE, type PlanContextInput, type PlanContextResult, RETIRED_TOKENS, type RecallInput, type RecallResult, type RequirementProfile, type RetiredReferenceHit, type RetiredReferenceInspection, type RetiredToken, type SelectionTokenState, type ShutdownHandlerDeps, type SurfaceVerdict, type UnboundProjectViolation, type WhyNotSurfacedResult, appendEventLedgerEvent, buildAlwaysActiveBodies, buildColdEvalBatch, buildKnowledgeCensus, bumpCounter, contextCache, createFabricServer, createInFlightTracker, createShutdownHandler, defaultEmbedCacheDir, detectUnboundProject, drainCounters, enrichDescriptions, explainWhyNotSurfaced, extractKnowledge, findConflictCandidates, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, inspectRetiredReferences, lintConflicts, loadConflictEntries, loadEmbedder, pairSimilarity, planContext, readEmbedConfig, readEventLedger, readFusion, readLedger, readMetrics, readSelectionToken, recall, rehydrateAgentsMetaAt, resolveLedgerPaths, reviewKnowledge, reviewPending, runDoctorApplyLint, runDoctorArchiveHistory, runDoctorCiteCoverage, runDoctorConflictLint, runDoctorFix, runDoctorHistoryAll, runDoctorReport, startMetricsFlush, startRotationTick, startStdioServer, stopMetricsFlush, stopRotationTick };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/index.ts
2
2
  import { existsSync as existsSync9 } from "fs";
3
3
  import { readFile as readFile20 } from "fs/promises";
4
- import { join as join24, resolve as resolve4 } from "path";
4
+ import { join as join25, 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";
@@ -820,9 +820,10 @@ function readOrphanDemoteThresholdDays(projectRoot) {
820
820
  function readFusion(projectRoot) {
821
821
  try {
822
822
  const raw = readFabricConfig(projectRoot).fusion;
823
- return raw === "rrf" ? "rrf" : "additive";
823
+ if (raw === "rrf" || raw === "additive") return raw;
824
+ return "auto";
824
825
  } catch {
825
- return "additive";
826
+ return "auto";
826
827
  }
827
828
  }
828
829
  function readConflictLintThreshold(projectRoot) {
@@ -2752,9 +2753,12 @@ var LEDGER_DUAL_WRITE_METRIC_NAMES = {
2752
2753
 
2753
2754
  // src/services/plan-context.ts
2754
2755
  import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
2755
- import { join as join9 } from "path";
2756
+ import { join as join10 } from "path";
2756
2757
 
2757
2758
  // src/services/vector-retrieval.ts
2759
+ import { mkdirSync } from "fs";
2760
+ import { join as join9 } from "path";
2761
+ import { resolveGlobalRoot as resolveGlobalRoot3 } from "@fenglimg/fabric-shared";
2758
2762
  var embedderLoad;
2759
2763
  var OPTIONAL_EMBED_PACKAGE = "fastembed";
2760
2764
  var embedderModuleLoader = (name) => import(name);
@@ -2771,10 +2775,13 @@ function hintMissingEmbedderOnce() {
2771
2775
  missingEmbedderHinted = true;
2772
2776
  emitMissingEmbedderHint();
2773
2777
  }
2778
+ function defaultEmbedCacheDir() {
2779
+ return join9(resolveGlobalRoot3(), "cache", "embed");
2780
+ }
2774
2781
  function buildEmbedInitOptions(modelName) {
2775
2782
  return {
2776
2783
  maxLength: 512,
2777
- cacheDir: process.env.FABRIC_EMBED_CACHE_DIR,
2784
+ cacheDir: process.env.FABRIC_EMBED_CACHE_DIR ?? defaultEmbedCacheDir(),
2778
2785
  ...typeof modelName === "string" && modelName.length > 0 ? { model: modelName } : {}
2779
2786
  };
2780
2787
  }
@@ -2788,7 +2795,9 @@ async function loadEmbedder(modelName) {
2788
2795
  hintMissingEmbedderOnce();
2789
2796
  return null;
2790
2797
  }
2791
- const model = await mod.FlagEmbedding.init(buildEmbedInitOptions(modelName));
2798
+ const initOpts = buildEmbedInitOptions(modelName);
2799
+ mkdirSync(initOpts.cacheDir, { recursive: true });
2800
+ const model = await mod.FlagEmbedding.init(initOpts);
2792
2801
  return {
2793
2802
  async embed(texts) {
2794
2803
  const out = [];
@@ -3157,7 +3166,7 @@ var bm25BuildCount = 0;
3157
3166
  var BM25_CACHE_DIR = ".fabric/cache/bm25";
3158
3167
  function bm25CachePath(projectRoot, revision) {
3159
3168
  const safe = revision.replace(/[^A-Za-z0-9_-]/g, "_");
3160
- return join9(projectRoot, BM25_CACHE_DIR, `${safe}.json`);
3169
+ return join10(projectRoot, BM25_CACHE_DIR, `${safe}.json`);
3161
3170
  }
3162
3171
  async function loadBm25ModelFromDisk(projectRoot, revision) {
3163
3172
  try {
@@ -3172,7 +3181,7 @@ async function loadBm25ModelFromDisk(projectRoot, revision) {
3172
3181
  async function saveBm25ModelToDisk(projectRoot, revision, model) {
3173
3182
  try {
3174
3183
  const path = bm25CachePath(projectRoot, revision);
3175
- await mkdir4(join9(projectRoot, BM25_CACHE_DIR), { recursive: true });
3184
+ await mkdir4(join10(projectRoot, BM25_CACHE_DIR), { recursive: true });
3176
3185
  await writeFile2(path, JSON.stringify(serializeBm25Model(model)), "utf8");
3177
3186
  } catch {
3178
3187
  }
@@ -3271,7 +3280,9 @@ async function buildScoringContext(projectRoot, revision, rawItems, opts) {
3271
3280
  scoringContext.vectorWeight = embedConfig.weight;
3272
3281
  }
3273
3282
  }
3274
- scoringContext.fusion = readFusion(projectRoot);
3283
+ const configuredFusion = readFusion(projectRoot);
3284
+ const vectorActive = scoringContext.vectorScores !== void 0 && scoringContext.vectorScores.size > 0;
3285
+ scoringContext.fusion = configuredFusion === "auto" ? vectorActive ? "rrf" : "additive" : configuredFusion;
3275
3286
  if (scoringContext.fusion === "rrf" && scoringContext.queryTerms.length > 0 && rawItems.length > 0) {
3276
3287
  const rankIds = rawItems.map((item) => item.stable_id).sort((a, b) => compareStableIds(a, b));
3277
3288
  if (scoringContext.bm25 !== void 0) {
@@ -3909,7 +3920,7 @@ import { execFileSync } from "child_process";
3909
3920
  import { existsSync as existsSync5 } from "fs";
3910
3921
  import { readFile as readFile7, readdir as readdir2, stat as stat2, unlink as unlink2 } from "fs/promises";
3911
3922
  import { homedir } from "os";
3912
- import { basename, isAbsolute, join as join11, relative, resolve as resolve2, sep as sep2 } from "path";
3923
+ import { basename, isAbsolute, join as join12, relative, resolve as resolve2, sep as sep2 } from "path";
3913
3924
 
3914
3925
  // src/services/promotion-gate.ts
3915
3926
  function toLocalId(id) {
@@ -3950,7 +3961,7 @@ import { allocateStoreKnowledgeId, isPersonalScope as isPersonalScope2 } from "@
3950
3961
  // src/services/pending-dedupe.ts
3951
3962
  import { existsSync as existsSync4 } from "fs";
3952
3963
  import { readdir, readFile as readFile6, unlink } from "fs/promises";
3953
- import { join as join10 } from "path";
3964
+ import { join as join11 } from "path";
3954
3965
  var PENDING_TYPES = ["decisions", "pitfalls", "guidelines", "models", "processes"];
3955
3966
  var DISAMBIGUATION_SUFFIX = /^(.+)-([2-9])\.md$/u;
3956
3967
  var SOURCE_SESSIONS_LINE = /^source_sessions:\s*\[(.*)\]\s*$/mu;
@@ -4023,7 +4034,7 @@ async function mergePendingTwins(projectRoot) {
4023
4034
  continue;
4024
4035
  }
4025
4036
  for (const type of PENDING_TYPES) {
4026
- const dir = join10(pendingBase2, type);
4037
+ const dir = join11(pendingBase2, type);
4027
4038
  if (!existsSync4(dir)) continue;
4028
4039
  let names;
4029
4040
  try {
@@ -4044,7 +4055,7 @@ async function mergePendingTwins(projectRoot) {
4044
4055
  if (groupNames.length < 2) continue;
4045
4056
  const parsed = [];
4046
4057
  for (const name of groupNames) {
4047
- const abs = join10(dir, name);
4058
+ const abs = join11(dir, name);
4048
4059
  let content;
4049
4060
  try {
4050
4061
  content = await readFile6(abs, "utf8");
@@ -4227,7 +4238,7 @@ async function listPending(projectRoot, filters) {
4227
4238
  }
4228
4239
  for (const source of sources) {
4229
4240
  for (const type of typesToScan) {
4230
- const dir = join11(source.root, type);
4241
+ const dir = join12(source.root, type);
4231
4242
  if (!existsSync5(dir)) {
4232
4243
  continue;
4233
4244
  }
@@ -4239,7 +4250,7 @@ async function listPending(projectRoot, filters) {
4239
4250
  }
4240
4251
  for (const name of entries) {
4241
4252
  if (!name.endsWith(".md")) continue;
4242
- const absolutePath = join11(dir, name);
4253
+ const absolutePath = join12(dir, name);
4243
4254
  let content;
4244
4255
  try {
4245
4256
  content = await readFile7(absolutePath, "utf8");
@@ -4366,7 +4377,7 @@ async function approveOne(projectRoot, pendingPath) {
4366
4377
  );
4367
4378
  allocatedId = stableId;
4368
4379
  const newFilename = `${stableId}--${slug}.md`;
4369
- targetAbs = join11(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
4380
+ targetAbs = join12(resolveStoreCanonicalBase(layer, projectRoot), pluralType, newFilename);
4370
4381
  await ensureParentDirectory(targetAbs);
4371
4382
  const rewritten = rewriteFrontmatterMerge(
4372
4383
  rewriteFrontmatterForPromote(content, stableId),
@@ -4553,7 +4564,7 @@ async function modifyLayerFlip(projectRoot, target, content, fm, changes) {
4553
4564
  pluralType,
4554
4565
  resolveWriteTargetStoreDir(toLayer, projectRoot)
4555
4566
  );
4556
- const toAbs = join11(
4567
+ const toAbs = join12(
4557
4568
  resolveStoreCanonicalBase(toLayer, projectRoot),
4558
4569
  pluralType,
4559
4570
  `${newStableId}--${slug}.md`
@@ -4653,7 +4664,7 @@ function getSearchDirectoryCache(cacheKey) {
4653
4664
  return created;
4654
4665
  }
4655
4666
  async function listIndexedSearchEntries(source, type) {
4656
- const dir = join11(source.root, type);
4667
+ const dir = join12(source.root, type);
4657
4668
  let entries;
4658
4669
  try {
4659
4670
  entries = await readdir2(dir);
@@ -4666,7 +4677,7 @@ async function listIndexedSearchEntries(source, type) {
4666
4677
  const indexed = [];
4667
4678
  for (const name of entries) {
4668
4679
  if (!name.endsWith(".md")) continue;
4669
- const absolutePath = join11(dir, name);
4680
+ const absolutePath = join12(dir, name);
4670
4681
  let fingerprint;
4671
4682
  try {
4672
4683
  const st = await stat2(absolutePath);
@@ -5195,7 +5206,7 @@ function registerPending(server, tracker) {
5195
5206
  // src/services/doctor.ts
5196
5207
  import { access as access4, readFile as readFile17, readdir as readdirAsync, stat as statAsync, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
5197
5208
  import { constants as constants2 } from "fs";
5198
- import { isAbsolute as isAbsolute2, join as join22, posix as posix5, resolve as resolve3 } from "path";
5209
+ import { isAbsolute as isAbsolute2, join as join23, posix as posix5, resolve as resolve3 } from "path";
5199
5210
  import {
5200
5211
  createTranslator,
5201
5212
  forensicReportSchema,
@@ -5410,12 +5421,12 @@ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
5410
5421
 
5411
5422
  // src/services/doctor-stable-id-collision.ts
5412
5423
  import { readFile as readFile8 } from "fs/promises";
5413
- import { basename as basename2, join as join12 } from "path";
5424
+ import { basename as basename2, join as join13 } from "path";
5414
5425
  import {
5415
5426
  buildStoreResolveInput as buildStoreResolveInput4,
5416
5427
  createStoreResolver as createStoreResolver4,
5417
5428
  readKnowledgeAcrossStores as readKnowledgeAcrossStores2,
5418
- resolveGlobalRoot as resolveGlobalRoot3,
5429
+ resolveGlobalRoot as resolveGlobalRoot4,
5419
5430
  storeRelativePathForMount as storeRelativePathForMount3
5420
5431
  } from "@fenglimg/fabric-shared";
5421
5432
  var ID_LINE = /^id:\s*"?([^"\n]+?)"?\s*$/mu;
@@ -5431,13 +5442,13 @@ function resolveIntegrityStores(projectRoot) {
5431
5442
  const personalUuids = new Set(
5432
5443
  input.mountedStores.filter((s) => s.personal).map((s) => s.store_uuid)
5433
5444
  );
5434
- const globalRoot = resolveGlobalRoot3();
5445
+ const globalRoot = resolveGlobalRoot4();
5435
5446
  const dirs = readSet.stores.map((entry) => {
5436
5447
  const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
5437
5448
  return {
5438
5449
  store_uuid: entry.store_uuid,
5439
5450
  alias: entry.alias,
5440
- dir: join12(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
5451
+ dir: join13(globalRoot, storeRelativePathForMount3(mounted ?? { store_uuid: entry.store_uuid }))
5441
5452
  };
5442
5453
  });
5443
5454
  return { dirs, personalUuids };
@@ -5541,7 +5552,7 @@ function createLayerMismatchCheck(t, inspection) {
5541
5552
  // src/services/doctor-relevance-paths.ts
5542
5553
  import { execFileSync as execFileSync2 } from "child_process";
5543
5554
  import { existsSync as existsSync6, readdirSync, statSync as statSync3 } from "fs";
5544
- import { join as join13, sep as sep3 } from "path";
5555
+ import { join as join14, sep as sep3 } from "path";
5545
5556
  import { minimatch } from "minimatch";
5546
5557
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
5547
5558
  var RELEVANCE_PATHS_DRIFT_WINDOW_DAYS = 90;
@@ -5584,7 +5595,7 @@ function collectWorkspacePaths(projectRoot) {
5584
5595
  continue;
5585
5596
  }
5586
5597
  for (const entry of entries) {
5587
- const abs = join13(current, entry.name);
5598
+ const abs = join14(current, entry.name);
5588
5599
  const rel = toPosix(abs.slice(projectRoot.length + 1));
5589
5600
  if (rel.length === 0) continue;
5590
5601
  if (entry.isDirectory()) {
@@ -5778,13 +5789,13 @@ function createNarrowNoPathsCheck(t, inspection) {
5778
5789
 
5779
5790
  // src/services/doctor-broad-index.ts
5780
5791
  import { readFile as readFile9 } from "fs/promises";
5781
- import { join as join14 } from "path";
5792
+ import { join as join15 } from "path";
5782
5793
  var DEFAULT_BROAD_INDEX_BACKSTOP = 50;
5783
5794
  var BROAD_INDEX_BACKSTOP_MIN = 20;
5784
5795
  var BROAD_INDEX_BACKSTOP_MAX = 500;
5785
5796
  var BROAD_INDEX_DRIFT_RATIO = 0.8;
5786
5797
  async function readBroadIndexBackstop(projectRoot) {
5787
- const configPath = join14(projectRoot, ".fabric", "fabric-config.json");
5798
+ const configPath = join15(projectRoot, ".fabric", "fabric-config.json");
5788
5799
  try {
5789
5800
  const raw = await readFile9(configPath, "utf8");
5790
5801
  const parsed = JSON.parse(raw);
@@ -6104,14 +6115,14 @@ function createBroadReviewRecheckCheck(t, inspection) {
6104
6115
 
6105
6116
  // src/services/doctor-scope-lint.ts
6106
6117
  import { readFile as readFile10 } from "fs/promises";
6107
- import { join as join15 } from "path";
6118
+ import { join as join16 } from "path";
6108
6119
  import {
6109
6120
  buildStoreResolveInput as buildStoreResolveInput5,
6110
6121
  createStoreResolver as createStoreResolver5,
6111
6122
  isPersonalScope as isPersonalScope3,
6112
6123
  readKnowledgeAcrossStores as readKnowledgeAcrossStores3,
6113
6124
  readStoreProjects,
6114
- resolveGlobalRoot as resolveGlobalRoot4,
6125
+ resolveGlobalRoot as resolveGlobalRoot5,
6115
6126
  scopeRoot as scopeRoot2,
6116
6127
  storeRelativePathForMount as storeRelativePathForMount4
6117
6128
  } from "@fenglimg/fabric-shared";
@@ -6136,10 +6147,10 @@ async function resolveLintStores(projectRoot) {
6136
6147
  const personalUuids = new Set(
6137
6148
  input.mountedStores.filter((s) => s.personal).map((s) => s.store_uuid)
6138
6149
  );
6139
- const globalRoot = resolveGlobalRoot4();
6150
+ const globalRoot = resolveGlobalRoot5();
6140
6151
  return Promise.all(readSet.stores.map(async (entry) => {
6141
6152
  const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
6142
- const dir = join15(
6153
+ const dir = join16(
6143
6154
  globalRoot,
6144
6155
  storeRelativePathForMount4(mounted ?? { store_uuid: entry.store_uuid })
6145
6156
  );
@@ -6294,7 +6305,7 @@ function createUnboundProjectCheck(t, violation) {
6294
6305
 
6295
6306
  // src/services/doctor-store-counters.ts
6296
6307
  import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
6297
- import { join as join16 } from "path";
6308
+ import { join as join17 } from "path";
6298
6309
  import {
6299
6310
  buildStoreResolveInput as buildStoreResolveInput6,
6300
6311
  createStoreResolver as createStoreResolver6,
@@ -6303,7 +6314,7 @@ import {
6303
6314
  parseKnowledgeId as parseKnowledgeId2,
6304
6315
  readStoreCounters,
6305
6316
  reconcileStoreCounters,
6306
- resolveGlobalRoot as resolveGlobalRoot5,
6317
+ resolveGlobalRoot as resolveGlobalRoot6,
6307
6318
  STORE_KNOWLEDGE_TYPE_DIRS,
6308
6319
  STORE_LAYOUT as STORE_LAYOUT2,
6309
6320
  storeRelativePathForMount as storeRelativePathForMount5
@@ -6317,11 +6328,11 @@ function resolveCounterStores(projectRoot) {
6317
6328
  if (readSet.stores.length === 0) {
6318
6329
  return [];
6319
6330
  }
6320
- const globalRoot = resolveGlobalRoot5();
6331
+ const globalRoot = resolveGlobalRoot6();
6321
6332
  return readSet.stores.map((entry) => ({
6322
6333
  uuid: entry.store_uuid,
6323
6334
  alias: entry.alias,
6324
- dir: join16(
6335
+ dir: join17(
6325
6336
  globalRoot,
6326
6337
  storeRelativePathForMount5(
6327
6338
  input.mountedStores.find((s) => s.store_uuid === entry.store_uuid) ?? {
@@ -6349,7 +6360,7 @@ function readEntryId(file) {
6349
6360
  function computeStoreDiskMax(storeDir) {
6350
6361
  const max = defaultAgentsMetaCounters();
6351
6362
  for (const type of STORE_KNOWLEDGE_TYPE_DIRS) {
6352
- const dir = join16(storeDir, STORE_LAYOUT2.knowledgeDir, type);
6363
+ const dir = join17(storeDir, STORE_LAYOUT2.knowledgeDir, type);
6353
6364
  if (!existsSync7(dir)) {
6354
6365
  continue;
6355
6366
  }
@@ -6363,7 +6374,7 @@ function computeStoreDiskMax(storeDir) {
6363
6374
  if (!name.endsWith(".md")) {
6364
6375
  continue;
6365
6376
  }
6366
- const parsed = parseKnowledgeId2(readEntryId(join16(dir, name)) ?? "");
6377
+ const parsed = parseKnowledgeId2(readEntryId(join17(dir, name)) ?? "");
6367
6378
  if (parsed === null) {
6368
6379
  continue;
6369
6380
  }
@@ -6446,14 +6457,14 @@ function createStoreCounterCheck(t, drifts) {
6446
6457
 
6447
6458
  // src/services/doctor-store-orphan.ts
6448
6459
  import { readdirSync as readdirSync3 } from "fs";
6449
- import { join as join17 } from "path";
6460
+ import { join as join18 } from "path";
6450
6461
  import {
6451
6462
  STORES_ROOT_DIR,
6452
6463
  addMountedStore,
6453
6464
  disambiguateAlias,
6454
6465
  loadGlobalConfig,
6455
6466
  readStoreIdentity,
6456
- resolveGlobalRoot as resolveGlobalRoot6,
6467
+ resolveGlobalRoot as resolveGlobalRoot7,
6457
6468
  saveGlobalConfig
6458
6469
  } from "@fenglimg/fabric-shared";
6459
6470
  var STORE_BY_ALIAS_DIR = "by-alias";
@@ -6464,21 +6475,21 @@ function listDir(dir) {
6464
6475
  return [];
6465
6476
  }
6466
6477
  }
6467
- function inspectStoreOrphans(globalRoot = resolveGlobalRoot6()) {
6478
+ function inspectStoreOrphans(globalRoot = resolveGlobalRoot7()) {
6468
6479
  const config = loadGlobalConfig(globalRoot);
6469
6480
  if (config === null) {
6470
6481
  return [];
6471
6482
  }
6472
6483
  const registered = new Set(config.stores.map((s) => s.store_uuid));
6473
- const storesRoot = join17(globalRoot, STORES_ROOT_DIR);
6484
+ const storesRoot = join18(globalRoot, STORES_ROOT_DIR);
6474
6485
  const orphans = [];
6475
6486
  for (const group of listDir(storesRoot)) {
6476
6487
  if (group === STORE_BY_ALIAS_DIR) {
6477
6488
  continue;
6478
6489
  }
6479
- const groupDir = join17(storesRoot, group);
6490
+ const groupDir = join18(storesRoot, group);
6480
6491
  for (const mount of listDir(groupDir)) {
6481
- const dir = join17(groupDir, mount);
6492
+ const dir = join18(groupDir, mount);
6482
6493
  const identity = readStoreIdentity(dir);
6483
6494
  if (identity === null || registered.has(identity.store_uuid)) {
6484
6495
  continue;
@@ -6488,7 +6499,7 @@ function inspectStoreOrphans(globalRoot = resolveGlobalRoot6()) {
6488
6499
  }
6489
6500
  return orphans;
6490
6501
  }
6491
- function fixStoreOrphans(globalRoot = resolveGlobalRoot6()) {
6502
+ function fixStoreOrphans(globalRoot = resolveGlobalRoot7()) {
6492
6503
  let config = loadGlobalConfig(globalRoot);
6493
6504
  if (config === null) {
6494
6505
  return [];
@@ -6616,7 +6627,7 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
6616
6627
 
6617
6628
  // src/services/doctor-skill-lints.ts
6618
6629
  import { readdir as readdir3, readFile as readFile11 } from "fs/promises";
6619
- import { join as join18, posix as posix2 } from "path";
6630
+ import { join as join19, posix as posix2 } from "path";
6620
6631
  var FABRIC_SKILL_SLUGS = ["fabric-archive", "fabric-review"];
6621
6632
  var SKILL_MD_FRONTMATTER_ROOTS = [".claude/skills", ".codex/skills"];
6622
6633
  var SKILL_FRONTMATTER_KEY_PATTERN = /^([A-Za-z_][A-Za-z0-9_-]*):[ \t]+(.+?)[ \t]*$/u;
@@ -6646,8 +6657,8 @@ async function listMarkdownFiles(dir) {
6646
6657
  async function inspectSkillRefMirror(projectRoot) {
6647
6658
  const driftedPaths = [];
6648
6659
  for (const slug of FABRIC_SKILL_SLUGS) {
6649
- const claudeRef = join18(projectRoot, ".claude", "skills", slug, "ref");
6650
- const codexRef = join18(projectRoot, ".codex", "skills", slug, "ref");
6660
+ const claudeRef = join19(projectRoot, ".claude", "skills", slug, "ref");
6661
+ const codexRef = join19(projectRoot, ".codex", "skills", slug, "ref");
6651
6662
  const [claudeFiles, codexFiles] = await Promise.all([listMarkdownFiles(claudeRef), listMarkdownFiles(codexRef)]);
6652
6663
  if (claudeFiles === null || codexFiles === null) continue;
6653
6664
  const claudeSet = new Set(claudeFiles);
@@ -6664,8 +6675,8 @@ async function inspectSkillRefMirror(projectRoot) {
6664
6675
  let codexBody;
6665
6676
  try {
6666
6677
  [claudeBody, codexBody] = await Promise.all([
6667
- readFile11(join18(claudeRef, fname), "utf8"),
6668
- readFile11(join18(codexRef, fname), "utf8")
6678
+ readFile11(join19(claudeRef, fname), "utf8"),
6679
+ readFile11(join19(codexRef, fname), "utf8")
6669
6680
  ]);
6670
6681
  } catch {
6671
6682
  continue;
@@ -6684,7 +6695,7 @@ async function inspectSkillTokenBudget(projectRoot) {
6684
6695
  const overSize = [];
6685
6696
  let highestSeverity = "ok";
6686
6697
  for (const slug of FABRIC_SKILL_SLUGS) {
6687
- const skillMdPath = join18(projectRoot, ".claude", "skills", slug, "SKILL.md");
6698
+ const skillMdPath = join19(projectRoot, ".claude", "skills", slug, "SKILL.md");
6688
6699
  let body;
6689
6700
  try {
6690
6701
  body = await readFile11(skillMdPath, "utf8");
@@ -6708,7 +6719,7 @@ async function inspectSkillDescription(projectRoot) {
6708
6719
  const CJK_PATTERN = /[\u3400-\u4dbf\u4e00-\u9fff]/u;
6709
6720
  const ASCII_PATTERN = /[a-zA-Z]{2,}/u;
6710
6721
  for (const slug of FABRIC_SKILL_SLUGS) {
6711
- const skillMdPath = join18(projectRoot, ".claude", "skills", slug, "SKILL.md");
6722
+ const skillMdPath = join19(projectRoot, ".claude", "skills", slug, "SKILL.md");
6712
6723
  let body;
6713
6724
  try {
6714
6725
  body = await readFile11(skillMdPath, "utf8");
@@ -6742,7 +6753,7 @@ async function inspectSkillDescription(projectRoot) {
6742
6753
  async function inspectSkillMdYamlInvalid(projectRoot) {
6743
6754
  const candidates = [];
6744
6755
  for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
6745
- const rootAbs = join18(projectRoot, rootRel);
6756
+ const rootAbs = join19(projectRoot, rootRel);
6746
6757
  let dirEntries;
6747
6758
  try {
6748
6759
  dirEntries = await readdir3(rootAbs, { withFileTypes: true });
@@ -6751,7 +6762,7 @@ async function inspectSkillMdYamlInvalid(projectRoot) {
6751
6762
  }
6752
6763
  for (const dirEntry of dirEntries) {
6753
6764
  if (!dirEntry.isDirectory()) continue;
6754
- const skillFile = join18(rootAbs, dirEntry.name, "SKILL.md");
6765
+ const skillFile = join19(rootAbs, dirEntry.name, "SKILL.md");
6755
6766
  let raw;
6756
6767
  try {
6757
6768
  raw = await readFile11(skillFile, "utf8");
@@ -6878,7 +6889,7 @@ function createSkillMdYamlInvalidCheck(t, inspection) {
6878
6889
 
6879
6890
  // src/services/doctor-retired-references-lint.ts
6880
6891
  import { readdir as readdir4, readFile as readFile12, stat as stat3 } from "fs/promises";
6881
- import { join as join19, posix as posix3, relative as relative2 } from "path";
6892
+ import { join as join20, posix as posix3, relative as relative2 } from "path";
6882
6893
  var RETIRED_TOKENS = [
6883
6894
  { token: "fab_plan_context", replacement: "fab_recall", reason: "retrieval collapsed to one lean fab_recall (KT-DEC-0026)" },
6884
6895
  { token: "fab_get_knowledge_sections", replacement: "fab_recall", reason: "two-step fetch retired (KT-DEC-0026)" },
@@ -6901,7 +6912,7 @@ var RETIRED_TOKENS = [
6901
6912
  ];
6902
6913
  var HOOK_DIRS = [".claude/hooks", ".codex/hooks"];
6903
6914
  var SKILL_DIRS = [".claude/skills", ".codex/skills"];
6904
- var BOOTSTRAP_FILES = ["AGENTS.md", "CLAUDE.md", join19(".fabric", "AGENTS.md")];
6915
+ var BOOTSTRAP_FILES = ["AGENTS.md", "CLAUDE.md", join20(".fabric", "AGENTS.md")];
6905
6916
  function isCommentLine(line) {
6906
6917
  const t = line.trim();
6907
6918
  return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*");
@@ -6915,7 +6926,7 @@ async function walkFiles(dir, exts) {
6915
6926
  }
6916
6927
  const out = [];
6917
6928
  for (const e of entries) {
6918
- const full = join19(dir, e.name);
6929
+ const full = join20(dir, e.name);
6919
6930
  if (e.isDirectory()) {
6920
6931
  out.push(...await walkFiles(full, exts));
6921
6932
  } else if (exts.some((ext) => e.name.endsWith(ext))) {
@@ -6941,7 +6952,7 @@ async function inspectRetiredReferences(projectRoot) {
6941
6952
  let scannedFiles = 0;
6942
6953
  const toRel = (p) => posix3.normalize(relative2(projectRoot, p).split("\\").join("/"));
6943
6954
  for (const rel of BOOTSTRAP_FILES) {
6944
- const abs = join19(projectRoot, rel);
6955
+ const abs = join20(projectRoot, rel);
6945
6956
  try {
6946
6957
  if ((await stat3(abs)).isFile()) {
6947
6958
  scannedFiles += 1;
@@ -6951,7 +6962,7 @@ async function inspectRetiredReferences(projectRoot) {
6951
6962
  }
6952
6963
  }
6953
6964
  for (const dir of SKILL_DIRS) {
6954
- for (const file of await walkFiles(join19(projectRoot, dir), [".md"])) {
6965
+ for (const file of await walkFiles(join20(projectRoot, dir), [".md"])) {
6955
6966
  scannedFiles += 1;
6956
6967
  try {
6957
6968
  scanText(toRel(file), await readFile12(file, "utf8"), false, hits);
@@ -6960,7 +6971,7 @@ async function inspectRetiredReferences(projectRoot) {
6960
6971
  }
6961
6972
  }
6962
6973
  for (const dir of HOOK_DIRS) {
6963
- for (const file of await walkFiles(join19(projectRoot, dir), [".cjs"])) {
6974
+ for (const file of await walkFiles(join20(projectRoot, dir), [".cjs"])) {
6964
6975
  scannedFiles += 1;
6965
6976
  try {
6966
6977
  scanText(toRel(file), await readFile12(file, "utf8"), true, hits);
@@ -6999,7 +7010,7 @@ function createRetiredReferenceCheck(t, inspection) {
6999
7010
  // src/services/doctor-hooks-lints.ts
7000
7011
  import { constants } from "fs";
7001
7012
  import { access, readdir as readdir5, readFile as readFile13, stat as stat4 } from "fs/promises";
7002
- import { join as join20, posix as posix4 } from "path";
7013
+ import { join as join21, posix as posix4 } from "path";
7003
7014
  import { Script } from "vm";
7004
7015
  var HOOKS_RUNTIME_CLIENT_DIRS = [
7005
7016
  { client: "claude", dir: ".claude/hooks" },
@@ -7061,11 +7072,11 @@ async function isFile(absPath) {
7061
7072
  }
7062
7073
  }
7063
7074
  async function inspectHooksWired(projectRoot) {
7064
- const claudeEntries = await readDirectoryFileNames(join20(projectRoot, ".claude"));
7075
+ const claudeEntries = await readDirectoryFileNames(join21(projectRoot, ".claude"));
7065
7076
  if (claudeEntries === null) {
7066
7077
  return { status: "skipped", missingHooks: [] };
7067
7078
  }
7068
- const settingsPath = join20(projectRoot, ".claude", "settings.json");
7079
+ const settingsPath = join21(projectRoot, ".claude", "settings.json");
7069
7080
  let parsed;
7070
7081
  try {
7071
7082
  parsed = JSON.parse(await readFile13(settingsPath, "utf8"));
@@ -7091,8 +7102,8 @@ async function inspectHooksWired(projectRoot) {
7091
7102
  }
7092
7103
  async function inspectHookCacheWritability(projectRoot) {
7093
7104
  const relPath = posix4.join(".fabric", ".cache");
7094
- const fabricDir = join20(projectRoot, ".fabric");
7095
- const cacheDir = join20(projectRoot, ".fabric", ".cache");
7105
+ const fabricDir = join21(projectRoot, ".fabric");
7106
+ const cacheDir = join21(projectRoot, ".fabric", ".cache");
7096
7107
  try {
7097
7108
  try {
7098
7109
  const cacheStats = await stat4(cacheDir);
@@ -7141,12 +7152,12 @@ async function inspectHookCacheWritability(projectRoot) {
7141
7152
  async function inspectHooksContentDrift(projectRoot) {
7142
7153
  const hookFilesByBasename = /* @__PURE__ */ new Map();
7143
7154
  for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
7144
- const absDir = join20(projectRoot, dir);
7155
+ const absDir = join21(projectRoot, dir);
7145
7156
  const entries = await readDirectoryFileNames(absDir);
7146
7157
  if (entries === null) continue;
7147
7158
  for (const name of entries) {
7148
7159
  if (!name.endsWith(".cjs")) continue;
7149
- const abs = join20(absDir, name);
7160
+ const abs = join21(absDir, name);
7150
7161
  if (!await isFile(abs)) continue;
7151
7162
  const arr = hookFilesByBasename.get(name) ?? [];
7152
7163
  arr.push({ client, abs });
@@ -7183,12 +7194,12 @@ async function inspectHooksRuntime(projectRoot) {
7183
7194
  const issues = [];
7184
7195
  let scanned = 0;
7185
7196
  for (const { client, dir } of HOOKS_RUNTIME_CLIENT_DIRS) {
7186
- const absDir = join20(projectRoot, dir);
7197
+ const absDir = join21(projectRoot, dir);
7187
7198
  const entries = await readDirectoryFileNames(absDir);
7188
7199
  if (entries === null) continue;
7189
7200
  for (const name of entries) {
7190
7201
  if (!name.endsWith(".cjs")) continue;
7191
- const abs = join20(absDir, name);
7202
+ const abs = join21(absDir, name);
7192
7203
  const displayPath = `${dir}/${name}`;
7193
7204
  if (!await isFile(abs)) continue;
7194
7205
  scanned += 1;
@@ -7332,7 +7343,7 @@ function createHookCacheWritabilityCheck(t, inspection) {
7332
7343
 
7333
7344
  // src/services/doctor-bootstrap-lints.ts
7334
7345
  import { access as access2, readFile as readFile14 } from "fs/promises";
7335
- import { join as join21 } from "path";
7346
+ import { join as join22 } from "path";
7336
7347
  import {
7337
7348
  BOOTSTRAP_MARKER_BEGIN,
7338
7349
  BOOTSTRAP_MARKER_END,
@@ -7364,13 +7375,13 @@ async function fileExists(path) {
7364
7375
  }
7365
7376
  async function inspectBootstrapAnchor(projectRoot) {
7366
7377
  const [hasAgentsMd, hasClaudeMd] = await Promise.all([
7367
- fileExists(join21(projectRoot, "AGENTS.md")),
7368
- fileExists(join21(projectRoot, "CLAUDE.md"))
7378
+ fileExists(join22(projectRoot, "AGENTS.md")),
7379
+ fileExists(join22(projectRoot, "CLAUDE.md"))
7369
7380
  ]);
7370
7381
  return { hasAgentsMd, hasClaudeMd };
7371
7382
  }
7372
7383
  async function inspectL1BootstrapSnapshotDrift(target) {
7373
- const abs = join21(target, ".fabric", "AGENTS.md");
7384
+ const abs = join22(target, ".fabric", "AGENTS.md");
7374
7385
  const canonical = resolveBootstrapCanonical();
7375
7386
  let onDisk;
7376
7387
  try {
@@ -7400,14 +7411,14 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
7400
7411
  );
7401
7412
  }
7402
7413
  async function inspectL2ManagedBlockDrift(target) {
7403
- const snapshotPath = join21(target, ".fabric", "AGENTS.md");
7414
+ const snapshotPath = join22(target, ".fabric", "AGENTS.md");
7404
7415
  let snapshot;
7405
7416
  try {
7406
7417
  snapshot = await readFile14(snapshotPath, "utf8");
7407
7418
  } catch {
7408
7419
  return { status: "ok", drifted: [] };
7409
7420
  }
7410
- const projectRulesPath = join21(target, ".fabric", "project-rules.md");
7421
+ const projectRulesPath = join22(target, ".fabric", "project-rules.md");
7411
7422
  let expectedBody = snapshot;
7412
7423
  try {
7413
7424
  const projectRules = await readFile14(projectRulesPath, "utf8");
@@ -7419,7 +7430,7 @@ ${projectRules}`;
7419
7430
  const drifted = [];
7420
7431
  let anyManagedBlockFound = false;
7421
7432
  const blockTargets = [
7422
- join21(target, "AGENTS.md")
7433
+ join22(target, "AGENTS.md")
7423
7434
  ];
7424
7435
  for (const abs of blockTargets) {
7425
7436
  let content;
@@ -7447,7 +7458,7 @@ ${projectRules}`;
7447
7458
  drifted.push({ path: abs, expected: expectedBody, actual: body });
7448
7459
  }
7449
7460
  }
7450
- const claudeMdPath = join21(target, "CLAUDE.md");
7461
+ const claudeMdPath = join22(target, "CLAUDE.md");
7451
7462
  try {
7452
7463
  const claudeContent = await readFile14(claudeMdPath, "utf8");
7453
7464
  anyManagedBlockFound = true;
@@ -8659,7 +8670,7 @@ async function runDoctorReport(target) {
8659
8670
  const globalCliVersion = process.env.VITEST === "true" ? { status: "ok", version: "test-skipped" } : inspectGlobalCliVersion();
8660
8671
  const targetFiles = Object.fromEntries(
8661
8672
  await Promise.all(
8662
- TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(join22(projectRoot, path))])
8673
+ TARGET_FILE_PATHS.map(async (path) => [path, await pathExists(join23(projectRoot, path))])
8663
8674
  )
8664
8675
  );
8665
8676
  const checks = [
@@ -8863,7 +8874,7 @@ async function runDoctorFix(target) {
8863
8874
  const fixed = [];
8864
8875
  const ledgerWarnings = [];
8865
8876
  if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
8866
- const snapshotPath = join22(projectRoot, ".fabric", "AGENTS.md");
8877
+ const snapshotPath = join23(projectRoot, ".fabric", "AGENTS.md");
8867
8878
  await ensureParentDirectory(snapshotPath);
8868
8879
  await atomicWriteText4(snapshotPath, resolveBootstrapCanonical2());
8869
8880
  fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
@@ -8936,7 +8947,7 @@ async function runDoctorFix(target) {
8936
8947
  if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
8937
8948
  const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
8938
8949
  if (lockInspection.present && !lockInspection.pidAlive) {
8939
- const lockFilePath = join22(projectRoot, ".fabric", ".serve.lock");
8950
+ const lockFilePath = join23(projectRoot, ".fabric", ".serve.lock");
8940
8951
  try {
8941
8952
  await unlink4(lockFilePath);
8942
8953
  } catch (err) {
@@ -9053,7 +9064,7 @@ function createApplyLintMessage(succeeded, failed, manualErrorCount) {
9053
9064
  }
9054
9065
  async function applySessionHintsStaleCleanup(projectRoot, candidate) {
9055
9066
  const detail = `deleted (${candidate.age_days}d old)`;
9056
- const absPath = join22(projectRoot, candidate.path);
9067
+ const absPath = join23(projectRoot, candidate.path);
9057
9068
  try {
9058
9069
  const { unlink: unlink5 } = await import("fs/promises");
9059
9070
  await unlink5(absPath);
@@ -9078,7 +9089,7 @@ function truncateErrorMessage(error) {
9078
9089
  return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
9079
9090
  }
9080
9091
  async function inspectForensic(projectRoot) {
9081
- const path = join22(projectRoot, ".fabric", "forensic.json");
9092
+ const path = join23(projectRoot, ".fabric", "forensic.json");
9082
9093
  try {
9083
9094
  const parsed = forensicReportSchema.parse(JSON.parse(await readFile17(path, "utf8")));
9084
9095
  return { present: true, valid: true, report: parsed };
@@ -9655,7 +9666,7 @@ async function inspectPreexistingRootFiles(projectRoot) {
9655
9666
  const candidates = ["CLAUDE.md", "AGENTS.md"];
9656
9667
  const detected = [];
9657
9668
  for (const name of candidates) {
9658
- if (await pathExists(join22(projectRoot, name))) {
9669
+ if (await pathExists(join23(projectRoot, name))) {
9659
9670
  detected.push(name);
9660
9671
  }
9661
9672
  }
@@ -9740,7 +9751,7 @@ async function buildLastActiveIndex(projectRoot) {
9740
9751
  return map;
9741
9752
  }
9742
9753
  async function inspectSessionHintsStale(projectRoot, now) {
9743
- const cacheDir = join22(projectRoot, ".fabric", ".cache");
9754
+ const cacheDir = join23(projectRoot, ".fabric", ".cache");
9744
9755
  let entries;
9745
9756
  try {
9746
9757
  entries = await readdirAsync(cacheDir, { withFileTypes: true });
@@ -9752,7 +9763,7 @@ async function inspectSessionHintsStale(projectRoot, now) {
9752
9763
  if (!entry.isFile()) continue;
9753
9764
  if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
9754
9765
  if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
9755
- const absPath = join22(cacheDir, entry.name);
9766
+ const absPath = join23(cacheDir, entry.name);
9756
9767
  let mtimeMs = 0;
9757
9768
  try {
9758
9769
  mtimeMs = (await statAsync(absPath)).mtimeMs;
@@ -9784,7 +9795,7 @@ function inspectStaleServeLock(projectRoot, now) {
9784
9795
  };
9785
9796
  }
9786
9797
  async function readUnderseedThresholdFromConfig(projectRoot) {
9787
- const configPath = join22(projectRoot, ".fabric", "fabric-config.json");
9798
+ const configPath = join23(projectRoot, ".fabric", "fabric-config.json");
9788
9799
  try {
9789
9800
  const raw = await readFile17(configPath, "utf8");
9790
9801
  const parsed = JSON.parse(raw);
@@ -9902,7 +9913,7 @@ async function inspectOnboardCoverage(projectRoot) {
9902
9913
  return { filled, missing, opted_out: optedOut };
9903
9914
  }
9904
9915
  async function readOnboardOptedOut(projectRoot) {
9905
- const path = join22(projectRoot, ".fabric", "fabric-config.json");
9916
+ const path = join23(projectRoot, ".fabric", "fabric-config.json");
9906
9917
  let raw;
9907
9918
  try {
9908
9919
  raw = await readFile17(path, "utf8");
@@ -10007,7 +10018,7 @@ async function* iterateCanonicalFilenames(projectRoot) {
10007
10018
  }
10008
10019
  }
10009
10020
  async function rewriteThreeEndManagedBlocks(projectRoot) {
10010
- const snapshotPath = join22(projectRoot, ".fabric", "AGENTS.md");
10021
+ const snapshotPath = join23(projectRoot, ".fabric", "AGENTS.md");
10011
10022
  if (!await pathExists(snapshotPath)) {
10012
10023
  return;
10013
10024
  }
@@ -10017,7 +10028,7 @@ async function rewriteThreeEndManagedBlocks(projectRoot) {
10017
10028
  } catch {
10018
10029
  return;
10019
10030
  }
10020
- const projectRulesPath = join22(projectRoot, ".fabric", "project-rules.md");
10031
+ const projectRulesPath = join23(projectRoot, ".fabric", "project-rules.md");
10021
10032
  const hasProjectRules = await pathExists(projectRulesPath);
10022
10033
  let expectedBody = snapshot;
10023
10034
  if (hasProjectRules) {
@@ -10033,7 +10044,7 @@ ${projectRules}`;
10033
10044
  ${expectedBody}
10034
10045
  ${BOOTSTRAP_MARKER_END2}`;
10035
10046
  const blockTargets = [
10036
- join22(projectRoot, "AGENTS.md")
10047
+ join23(projectRoot, "AGENTS.md")
10037
10048
  ];
10038
10049
  for (const abs of blockTargets) {
10039
10050
  if (!await pathExists(abs)) {
@@ -10066,7 +10077,7 @@ ${managedBlock}
10066
10077
  }
10067
10078
  await atomicWriteText4(abs, next);
10068
10079
  }
10069
- const claudeMdPath = join22(projectRoot, "CLAUDE.md");
10080
+ const claudeMdPath = join23(projectRoot, "CLAUDE.md");
10070
10081
  if (await pathExists(claudeMdPath)) {
10071
10082
  let claudeContent;
10072
10083
  try {
@@ -10136,7 +10147,7 @@ async function collectEntryPoints(root) {
10136
10147
  continue;
10137
10148
  }
10138
10149
  for (const entry of await readdirAsync(current, { withFileTypes: true })) {
10139
- const absolutePath = join22(current, entry.name);
10150
+ const absolutePath = join23(current, entry.name);
10140
10151
  const relativePath = normalizePath2(absolutePath.slice(root.length + 1));
10141
10152
  if (relativePath.length === 0) {
10142
10153
  continue;
@@ -10351,13 +10362,13 @@ async function runDoctorConflictLint(projectRoot, opts = {}) {
10351
10362
 
10352
10363
  // src/services/why-not-surfaced.ts
10353
10364
  import { readFile as readFile18 } from "fs/promises";
10354
- import { basename as basename3, join as join23 } from "path";
10365
+ import { basename as basename3, join as join24 } from "path";
10355
10366
  import {
10356
10367
  buildStoreResolveInput as buildStoreResolveInput7,
10357
10368
  createStoreResolver as createStoreResolver7,
10358
10369
  loadProjectConfig as loadProjectConfig4,
10359
10370
  readKnowledgeAcrossStores as readKnowledgeAcrossStores4,
10360
- resolveGlobalRoot as resolveGlobalRoot7,
10371
+ resolveGlobalRoot as resolveGlobalRoot8,
10361
10372
  scopeRoot as scopeRoot3,
10362
10373
  storeRelativePathForMount as storeRelativePathForMount6
10363
10374
  } from "@fenglimg/fabric-shared";
@@ -10387,11 +10398,11 @@ async function explainWhyNotSurfaced(projectRoot, query) {
10387
10398
  if (input === null) {
10388
10399
  return base;
10389
10400
  }
10390
- const globalRoot = resolveGlobalRoot7();
10401
+ const globalRoot = resolveGlobalRoot8();
10391
10402
  const allStores = input.mountedStores.map((s) => ({
10392
10403
  store_uuid: s.store_uuid,
10393
10404
  alias: s.alias,
10394
- dir: join23(globalRoot, storeRelativePathForMount6(s))
10405
+ dir: join24(globalRoot, storeRelativePathForMount6(s))
10395
10406
  }));
10396
10407
  const refs = await readKnowledgeAcrossStores4(allStores);
10397
10408
  const candidate = refs.find((ref) => fileMatchesId(ref.file, localId));
@@ -10771,8 +10782,8 @@ function formatError(error) {
10771
10782
  }
10772
10783
  function formatPreexistingRootMessage(projectRoot) {
10773
10784
  const preexisting = [];
10774
- if (existsSync9(join24(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
10775
- if (existsSync9(join24(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
10785
+ if (existsSync9(join25(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
10786
+ if (existsSync9(join25(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
10776
10787
  if (preexisting.length === 0) return null;
10777
10788
  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.`;
10778
10789
  }
@@ -10799,7 +10810,7 @@ function createFabricServer(tracker) {
10799
10810
  const server = new McpServer(
10800
10811
  {
10801
10812
  name: "fabric-knowledge-server",
10802
- version: "2.3.0-rc.2"
10813
+ version: "2.3.0-rc.3"
10803
10814
  },
10804
10815
  {
10805
10816
  instructions: FABRIC_SERVER_INSTRUCTIONS
@@ -10819,7 +10830,7 @@ function createFabricServer(tracker) {
10819
10830
  },
10820
10831
  async (_uri) => {
10821
10832
  const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
10822
- const path = join24(projectRoot, ".fabric", "bootstrap", "README.md");
10833
+ const path = join25(projectRoot, ".fabric", "bootstrap", "README.md");
10823
10834
  let text = "";
10824
10835
  if (existsSync9(path)) {
10825
10836
  text = await readFile20(path, "utf8");
@@ -10920,6 +10931,7 @@ export {
10920
10931
  LEGACY_LEDGER_PATH,
10921
10932
  METRICS_LEDGER_PATH,
10922
10933
  METRIC_COUNTER_NAMES,
10934
+ OPTIONAL_EMBED_PACKAGE,
10923
10935
  RETIRED_TOKENS,
10924
10936
  appendEventLedgerEvent,
10925
10937
  buildAlwaysActiveBodies,
@@ -10930,6 +10942,7 @@ export {
10930
10942
  createFabricServer,
10931
10943
  createInFlightTracker,
10932
10944
  createShutdownHandler,
10945
+ defaultEmbedCacheDir,
10933
10946
  detectUnboundProject,
10934
10947
  drainCounters,
10935
10948
  enrichDescriptions,
@@ -10946,9 +10959,12 @@ export {
10946
10959
  inspectRetiredReferences,
10947
10960
  lintConflicts,
10948
10961
  loadConflictEntries,
10962
+ loadEmbedder,
10949
10963
  pairSimilarity,
10950
10964
  planContext,
10965
+ readEmbedConfig,
10951
10966
  readEventLedger,
10967
+ readFusion,
10952
10968
  readLedger,
10953
10969
  readMetrics,
10954
10970
  readSelectionToken,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-server",
3
- "version": "2.3.0-rc.2",
3
+ "version": "2.3.0-rc.3",
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.3.0-rc.2"
40
+ "@fenglimg/fabric-shared": "2.3.0-rc.3"
41
41
  },
42
42
  "optionalDependencies": {
43
43
  "fastembed": "^2.0.0"