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

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
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { FabExtractKnowledgeInput, FabExtractKnowledgeOutput, FabReviewInput, FabReviewOutput, FabPendingInput, FabPendingOutput } from '@fenglimg/fabric-shared/schemas/api-contracts';
3
- import { EventLedgerEventInput, EventLedgerEvent, RuleDescriptionIndexItem, RecallScore, RecallScoreBreakdown, LedgerEntry, AgentsMeta } from '@fenglimg/fabric-shared';
3
+ import { RuleDescription, EventLedgerEventInput, EventLedgerEvent, RuleDescriptionIndexItem, RecallScore, RecallScoreBreakdown, LedgerEntry, AgentsMeta } from '@fenglimg/fabric-shared';
4
4
  import { PayloadGuardOptions } from '@fenglimg/fabric-shared/node/mcp-payload-guard';
5
5
 
6
6
  interface InFlightTracker {
@@ -33,6 +33,8 @@ type DoctorHealth = {
33
33
  };
34
34
  };
35
35
 
36
+ declare function extractRuleDescription(source: string): RuleDescription | undefined;
37
+
36
38
  interface AlwaysActiveBody {
37
39
  /** store-qualified id (`<alias>:<stableId>`). */
38
40
  stable_id: string;
@@ -78,6 +80,16 @@ interface KnowledgeCensus {
78
80
  */
79
81
  declare function buildKnowledgeCensus(projectRoot: string): Promise<KnowledgeCensus>;
80
82
  declare function buildAlwaysActiveBodies(projectRoot: string): Promise<AlwaysActiveBody[]>;
83
+ interface StoreCanonicalEntry {
84
+ stableId: string;
85
+ qualifiedId: string;
86
+ file: string;
87
+ type: string;
88
+ layer: "team" | "personal";
89
+ body: string;
90
+ description: NonNullable<ReturnType<typeof extractRuleDescription>>;
91
+ }
92
+ declare function collectStoreCanonicalEntries(projectRoot: string): Promise<StoreCanonicalEntry[]>;
81
93
 
82
94
  interface UnboundProjectViolation {
83
95
  /** The store already bound as the active write target. */
@@ -196,6 +208,11 @@ type CiteCoverageReport = {
196
208
  uncorrelatable_edits?: number;
197
209
  recall_backed_edits?: number;
198
210
  recall_coverage_rate?: number | null;
211
+ recall_diagnostics?: {
212
+ recalls_in_window: number;
213
+ recall_sessions: number;
214
+ recall_sessions_correlated: number;
215
+ };
199
216
  exposed_and_mutated?: {
200
217
  count: number;
201
218
  ids?: string[];
@@ -541,6 +558,20 @@ interface Embedder {
541
558
  embed(texts: string[]): Promise<number[][]>;
542
559
  }
543
560
  declare const OPTIONAL_EMBED_PACKAGE = "fastembed";
561
+ /**
562
+ * Is the optional embedder package resolvable FROM THE SERVER'S module location —
563
+ * i.e. from exactly where `loadEmbedder`'s dynamic `import()` will look for it?
564
+ *
565
+ * This is the honest "is fastembed installed?" probe. The naive alternative — a
566
+ * `createRequire(import.meta.url)` check run inside the CLI package — answers from
567
+ * the WRONG base: in a pnpm / non-hoisted layout (or a dev-linked global install)
568
+ * fastembed lives under the SERVER's `node_modules` (it is the server's
569
+ * optionalDependency), so the CLI cannot resolve it even though the server — the
570
+ * only code that actually imports it — can. That mismatch made `fabric info
571
+ * --recall` report "not installed" on a perfectly working setup. Callers that
572
+ * surface embedder availability MUST use this server-anchored probe.
573
+ */
574
+ declare function isEmbedderResolvable(): boolean;
544
575
  declare function defaultEmbedCacheDir(): string;
545
576
  /**
546
577
  * Lazy-load the optional fastembed embedder, pinned to CPU + cache-only. Returns
@@ -680,6 +711,148 @@ declare const COLD_EVAL_RUBRIC: string;
680
711
  */
681
712
  declare function buildColdEvalBatch(candidates: ColdEvalCandidate[]): ColdEvalBatch;
682
713
 
714
+ interface RelatedBrokenLink {
715
+ /** The entry (store-qualified id) that declared the `related` edge. */
716
+ source: string;
717
+ /** The unresolvable related value. */
718
+ target: string;
719
+ }
720
+ interface RelatedHubEntry {
721
+ stableId: string;
722
+ inDegree: number;
723
+ }
724
+ interface RelatedGraphInspection {
725
+ brokenLinks: RelatedBrokenLink[];
726
+ hubEntries: RelatedHubEntry[];
727
+ /** Total canonical entries scanned. */
728
+ totalEntries: number;
729
+ }
730
+ /** A single node fed into the pure graph builder. */
731
+ interface RelatedGraphNode {
732
+ /** Store-qualified id (`<alias>:<stableId>`). */
733
+ qualifiedId: string;
734
+ /** The entry's declared `related` edges (frontmatter), if any. */
735
+ related?: string[];
736
+ }
737
+ /**
738
+ * Build the related-edge graph from a list of nodes and report broken links +
739
+ * hub ranking. Pure — no I/O — so the broken-link / in-degree logic can be
740
+ * tested with hand-built fixtures.
741
+ *
742
+ * A "broken link" is a `related` value that does not appear as any node's
743
+ * store-qualified id NOR bare stable_id in the corpus. Store-qualified
744
+ * references (`alias:id`) and bare references (`id`) both resolve, because the
745
+ * id index carries both forms for every node.
746
+ */
747
+ declare function buildRelatedGraph(nodes: RelatedGraphNode[]): RelatedGraphInspection;
748
+ /**
749
+ * Walk the store canonical corpus and build the related graph. Never throws —
750
+ * collectStoreCanonicalEntries degrades to [] when no store is in the read-set.
751
+ *
752
+ * Note: the previous (dead, never-wired) draft of this module re-parsed each
753
+ * entry's frontmatter via extractRuleDescription(entry.file) — passing a FILE
754
+ * PATH where a raw-markdown SOURCE was expected, which silently produced no
755
+ * edges. StoreCanonicalEntry already carries the parsed `description`, so we
756
+ * read `description.related` directly (the single source the recall path uses).
757
+ */
758
+ declare function inspectRelatedGraph(projectRoot: string): Promise<RelatedGraphInspection>;
759
+
760
+ interface StoreReachability {
761
+ uuid: string;
762
+ alias: string;
763
+ reachable: boolean;
764
+ reason?: string;
765
+ }
766
+ interface PrecheckResult {
767
+ stores: StoreReachability[];
768
+ allReachable: boolean;
769
+ }
770
+ declare function clearPrecheckCache(): void;
771
+ /**
772
+ * Evaluate a single store directory's reachability. A store is reachable when
773
+ * BOTH hold:
774
+ * 1. The store directory exists on disk.
775
+ * 2. The directory contains either a `store.json` (valid JSON, local-only
776
+ * store) OR a `.git/` subdirectory (git-cloned store).
777
+ *
778
+ * Pure (filesystem-only, no config resolution) so the rule is testable with a
779
+ * temp directory regardless of the machine's ~/.fabric layout.
780
+ */
781
+ declare function evaluateStoreDir(storeDir: string, identity: {
782
+ uuid: string;
783
+ alias: string;
784
+ }): StoreReachability;
785
+ /**
786
+ * Check that every store in the project's read-set is reachable on disk.
787
+ * Results are cached for 60s (CACHE_TTL_MS). Call `clearPrecheckCache()` when a
788
+ * store mount / unmount operation invalidates the cache.
789
+ *
790
+ * `globalRoot` is injectable for hermetic tests; production callers use the
791
+ * resolved ~/.fabric root.
792
+ */
793
+ declare function precheckStoreReachability(projectRoot: string, globalRoot?: string, now?: number): Promise<PrecheckResult>;
794
+
795
+ interface ConsumptionLintConfig {
796
+ /** Rolling window in days (default 30). */
797
+ windowDays: number;
798
+ /** How many top-consumed entries to report (default 10). */
799
+ topN: number;
800
+ /** Minimum CORPUS size before the lint runs at all (fresh-install guard). */
801
+ minTotalEntries: number;
802
+ /**
803
+ * Data-maturity gate axis 1: minimum number of metrics windows (rows) that
804
+ * recorded ≥1 consumption counter before zero-consumed is trustworthy.
805
+ */
806
+ minConsumedWindows: number;
807
+ /**
808
+ * Data-maturity gate axis 2: minimum total consumption events in the window
809
+ * before zero-consumed is trustworthy.
810
+ */
811
+ minConsumedEvents: number;
812
+ }
813
+ interface ConsumptionEntry {
814
+ /** Store-qualified id (`<alias>:<stableId>`). */
815
+ stableId: string;
816
+ count: number;
817
+ }
818
+ interface ConsumptionInspection {
819
+ topConsumed: ConsumptionEntry[];
820
+ /**
821
+ * Corpus entries never consumed in the window. ALWAYS [] when the data is not
822
+ * mature (dataMature === false) — the gate suppresses the noisy signal.
823
+ */
824
+ zeroConsumed: string[];
825
+ /** Total corpus entries considered for the zero-consumed denominator. */
826
+ totalEntries: number;
827
+ /** Distinct entries with ≥1 consumption in the window. */
828
+ consumedEntries: number;
829
+ /** Number of metrics windows (rows) that carried ≥1 consumption counter. */
830
+ consumedWindows: number;
831
+ /** Sum of all consumption counts in the window. */
832
+ totalConsumedEvents: number;
833
+ /** True when both maturity axes clear their thresholds. */
834
+ dataMature: boolean;
835
+ windowDays: number;
836
+ }
837
+ /**
838
+ * Aggregate consumption from metrics rows against the canonical corpus id list.
839
+ * Pure — no I/O — so the windowing, prefix parsing, top-N ranking, zero-consumed
840
+ * computation, and the maturity gate are all testable with fixtures.
841
+ *
842
+ * @param rows metrics.jsonl rows (any window/order)
843
+ * @param allQualifiedIds store-qualified ids of the canonical corpus
844
+ * @param now current epoch ms (injected for deterministic windowing)
845
+ */
846
+ declare function aggregateConsumption(rows: MetricsRow[], allQualifiedIds: string[], now: number, config?: Partial<ConsumptionLintConfig>): ConsumptionInspection;
847
+ /**
848
+ * Read metrics.jsonl + the canonical corpus and aggregate consumption. Never
849
+ * throws — readMetrics returns [] on a missing ledger and collectStoreCanonical
850
+ * Entries returns [] when no store is in the read-set.
851
+ *
852
+ * `now` is injectable for deterministic tests.
853
+ */
854
+ declare function inspectConsumption(projectRoot: string, config?: Partial<ConsumptionLintConfig>, now?: number): Promise<ConsumptionInspection>;
855
+
683
856
  type StoredEventLedgerEvent = EventLedgerEvent;
684
857
  type ReadEventLedgerOptions = {
685
858
  event_type?: EventLedgerEvent["event_type"];
@@ -974,4 +1147,4 @@ interface ShutdownHandlerDeps {
974
1147
  */
975
1148
  declare function createShutdownHandler(deps: ShutdownHandlerDeps): () => void;
976
1149
 
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 };
1150
+ 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, type ConsumptionEntry, type ConsumptionInspection, type ConsumptionLintConfig, 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, type PrecheckResult, RETIRED_TOKENS, type RecallInput, type RecallResult, type RelatedBrokenLink, type RelatedGraphInspection, type RelatedGraphNode, type RelatedHubEntry, type RequirementProfile, type RetiredReferenceHit, type RetiredReferenceInspection, type RetiredToken, type SelectionTokenState, type ShutdownHandlerDeps, type StoreCanonicalEntry, type StoreReachability, type SurfaceVerdict, type UnboundProjectViolation, type WhyNotSurfacedResult, aggregateConsumption, appendEventLedgerEvent, buildAlwaysActiveBodies, buildColdEvalBatch, buildKnowledgeCensus, buildRelatedGraph, bumpCounter, clearPrecheckCache, collectStoreCanonicalEntries, contextCache, createFabricServer, createInFlightTracker, createShutdownHandler, defaultEmbedCacheDir, detectUnboundProject, drainCounters, enrichDescriptions, evaluateStoreDir, explainWhyNotSurfaced, extractKnowledge, findConflictCandidates, flushAndSyncEventLedger, flushMetrics, formatPreexistingRootMessage, getEventLedgerPath, getLedgerPath, getLegacyLedgerPath, getMetricsLedgerPath, inspectConsumption, inspectRelatedGraph, inspectRetiredReferences, isEmbedderResolvable, lintConflicts, loadConflictEntries, loadEmbedder, pairSimilarity, planContext, precheckStoreReachability, 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
- import { existsSync as existsSync9 } from "fs";
3
- import { readFile as readFile20 } from "fs/promises";
4
- import { join as join25, resolve as resolve4 } from "path";
2
+ import { existsSync as existsSync10 } from "fs";
3
+ import { readFile as readFile21 } from "fs/promises";
4
+ import { join as join26, 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";
@@ -893,7 +893,7 @@ async function awaitFirstReconcileGate(timeoutMs = 5e3) {
893
893
 
894
894
  // src/services/extract-knowledge.ts
895
895
  import { existsSync as existsSync3 } from "fs";
896
- import { readFile as readFile4 } from "fs/promises";
896
+ import { readFile as readFile5 } from "fs/promises";
897
897
  import { join as join7 } from "path";
898
898
  import {
899
899
  PROPOSED_REASON_DESCRIPTIONS_BY_LOCALE
@@ -901,6 +901,8 @@ import {
901
901
  import { hasSecrets, isPersonalScope, redactPii, resolveGlobalLocale } from "@fenglimg/fabric-shared";
902
902
 
903
903
  // src/services/cross-store-write.ts
904
+ import { createHash as createHash2 } from "crypto";
905
+ import { readFile as readFile3 } from "fs/promises";
904
906
  import { join as join5 } from "path";
905
907
  import {
906
908
  STORE_LAYOUT,
@@ -916,6 +918,7 @@ import {
916
918
  PersonalScopeLeakError,
917
919
  StoreWriteTargetUnresolvedError
918
920
  } from "@fenglimg/fabric-shared/errors";
921
+ import { withFileLock as withFileLock2 } from "@fenglimg/fabric-shared/node/atomic-write";
919
922
  function writeTargetUnresolved(scope, layer) {
920
923
  const actionHint = layer === "personal" ? "run `fabric install --global` to mint your personal store, then retry" : `mount + bind a shared store, then set an explicit route: \`fabric store switch-write <alias> --scope ${scope}\``;
921
924
  return new StoreWriteTargetUnresolvedError(
@@ -978,7 +981,7 @@ function resolveWriteScopeMeta(layer, projectRoot, semanticScope) {
978
981
  }
979
982
 
980
983
  // src/services/cross-store-recall.ts
981
- import { readFile as readFile3, stat } from "fs/promises";
984
+ import { readFile as readFile4, stat } from "fs/promises";
982
985
  import { join as join6 } from "path";
983
986
  import {
984
987
  buildStoreResolveInput as buildStoreResolveInput2,
@@ -1350,7 +1353,7 @@ async function walkReadSetStoresUncached(snapshot) {
1350
1353
  const entries = await Promise.all(snapshot.refs.map(async (ref) => {
1351
1354
  let source;
1352
1355
  try {
1353
- source = await readFile3(ref.file, "utf8");
1356
+ source = await readFile4(ref.file, "utf8");
1354
1357
  } catch {
1355
1358
  return null;
1356
1359
  }
@@ -1622,6 +1625,144 @@ function serializeBm25Model(model) {
1622
1625
  function rehydrateBm25Model(serialized) {
1623
1626
  return modelFromStats(serialized);
1624
1627
  }
1628
+ var SYNONYM_PAIRS = {
1629
+ // Code / engineering actions
1630
+ refactor: ["restructure", "rewrite", "redesign", "reorganize", "clean", "rework"],
1631
+ optimize: ["improve", "speed-up", "tune", "accelerate", "perf", "performance"],
1632
+ debug: ["fix", "troubleshoot", "diagnose", "investigate", "resolve", "correct"],
1633
+ migrate: ["port", "move", "transfer", "upgrade", "transition", "convert"],
1634
+ "set up": ["init", "initialize", "configure", "bootstrap", "install", "onboard"],
1635
+ implement: ["add", "build", "create", "develop", "write", "introduce", "introduce"],
1636
+ // Architecture / design
1637
+ architecture: ["design", "structure", "layout", "organization", "pattern", "system"],
1638
+ "data model": ["schema", "entity", "type", "structure", "contract", "shape"],
1639
+ // Quality / correctness
1640
+ test: ["verify", "validate", "assert", "check", "spec", "coverage"],
1641
+ lint: ["check", "validate", "audit", "inspect", "analyze"],
1642
+ // Communication / impact
1643
+ documentation: ["docs", "readme", "guide", "spec", "explanation", "reference"],
1644
+ decision: ["adr", "rationale", "why", "motivation", "reason", "trade-off"],
1645
+ // Change management
1646
+ release: ["deploy", "ship", "publish", "cut", "version", "tag"],
1647
+ rollback: ["revert", "undo", "back-out", "backout", "restore"],
1648
+ // Containers / infra
1649
+ container: ["docker", "image", "oci", "cri-o"],
1650
+ deploy: ["release", "rollout", "ship", "publish", "promote"],
1651
+ // Project / process
1652
+ on_boarding: ["getting-started", "quickstart", "newcomer", "new-hire", "first-time"],
1653
+ best_practice: ["convention", "guideline", "standard", "rule", "recommendation"],
1654
+ // Tech stacks
1655
+ api: ["endpoint", "route", "service", "interface", "rpc", "rest"],
1656
+ typescript: ["ts", "types", "type-safe"],
1657
+ react: ["jsx", "tsx", "component", "ui"],
1658
+ node: ["nodejs", "runtime", "backend", "server"],
1659
+ database: ["db", "sql", "nosql", "storage", "persistence"]
1660
+ };
1661
+ var STEMMING_PATTERNS = [
1662
+ { suffix: "e", alternatives: ["es", "ed", "ing", "ation"] },
1663
+ { suffix: "y", alternatives: ["ies", "ied", "ying"] }
1664
+ // Catch-all for non-verb / already-stemmed query terms: no-op by default.
1665
+ ];
1666
+ var DEFAULT_IDF_WEIGHT = 1;
1667
+ var COMMON_TERMS = /* @__PURE__ */ new Set([
1668
+ "a",
1669
+ "an",
1670
+ "the",
1671
+ "and",
1672
+ "or",
1673
+ "but",
1674
+ "in",
1675
+ "on",
1676
+ "at",
1677
+ "to",
1678
+ "for",
1679
+ "of",
1680
+ "with",
1681
+ "by",
1682
+ "from",
1683
+ "as",
1684
+ "is",
1685
+ "it",
1686
+ "at",
1687
+ "be",
1688
+ "do",
1689
+ "has",
1690
+ "have",
1691
+ "was",
1692
+ "are",
1693
+ "been",
1694
+ "this",
1695
+ "that",
1696
+ "these",
1697
+ "those",
1698
+ "will",
1699
+ "can",
1700
+ "may",
1701
+ "would",
1702
+ "could",
1703
+ "should",
1704
+ "does",
1705
+ "not",
1706
+ "no",
1707
+ "if",
1708
+ "so",
1709
+ "up",
1710
+ "out",
1711
+ "all",
1712
+ "each",
1713
+ "every",
1714
+ "both",
1715
+ "some",
1716
+ "any",
1717
+ "such",
1718
+ "only",
1719
+ "own",
1720
+ "same",
1721
+ "too",
1722
+ "very",
1723
+ "just",
1724
+ "about",
1725
+ "over",
1726
+ "than",
1727
+ "then",
1728
+ "also"
1729
+ ]);
1730
+ function idfWeight(term) {
1731
+ return COMMON_TERMS.has(term) ? 0.3 : DEFAULT_IDF_WEIGHT;
1732
+ }
1733
+ function expandQueryTerms(text) {
1734
+ const baseTerms = tokenize(text);
1735
+ const weighted = /* @__PURE__ */ new Map();
1736
+ for (const term of baseTerms) {
1737
+ const lower = term.toLowerCase();
1738
+ const aliases = /* @__PURE__ */ new Set();
1739
+ const syns = SYNONYM_PAIRS[lower];
1740
+ if (syns !== void 0) {
1741
+ for (const s of syns) aliases.add(s);
1742
+ }
1743
+ for (const [key, values] of Object.entries(SYNONYM_PAIRS)) {
1744
+ if (values.includes(lower)) {
1745
+ aliases.add(key);
1746
+ }
1747
+ }
1748
+ for (const { suffix, alternatives } of STEMMING_PATTERNS) {
1749
+ if (lower.endsWith(suffix) && lower.length > suffix.length) {
1750
+ const stem = lower.slice(0, -suffix.length);
1751
+ for (const alt of alternatives) {
1752
+ aliases.add(stem + alt);
1753
+ }
1754
+ }
1755
+ }
1756
+ const originalWeight = idfWeight(lower);
1757
+ weighted.set(term, originalWeight);
1758
+ for (const alias of aliases) {
1759
+ if (!weighted.has(alias)) {
1760
+ weighted.set(alias, originalWeight * 0.5);
1761
+ }
1762
+ }
1763
+ }
1764
+ return weighted;
1765
+ }
1625
1766
  function buildQueryTerms(text) {
1626
1767
  return tokenize(text);
1627
1768
  }
@@ -2021,7 +2162,7 @@ async function extractKnowledge(projectRoot, input) {
2021
2162
  const writeScopeMeta = resolveWriteScopeMeta(layer, projectRoot, semanticScope);
2022
2163
  await ensureParentDirectory(absolutePath);
2023
2164
  if (existsSync3(absolutePath)) {
2024
- const existing = await readFile4(absolutePath, "utf8");
2165
+ const existing = await readFile5(absolutePath, "utf8");
2025
2166
  const existingKey = readFrontmatterKey(existing, "x-fabric-idempotency-key");
2026
2167
  if (existingKey === effectiveIdempotencyKey) {
2027
2168
  const fresh2 = renderFreshEntry({
@@ -2154,7 +2295,7 @@ async function resolveDisambiguatedSlugPath(args) {
2154
2295
  idempotencyKey: candidateKey
2155
2296
  };
2156
2297
  }
2157
- const existing = await readFile4(candidatePath, "utf8");
2298
+ const existing = await readFile5(candidatePath, "utf8");
2158
2299
  const existingKey = readFrontmatterKey(existing, "x-fabric-idempotency-key");
2159
2300
  if (existingKey === candidateKey) {
2160
2301
  return {
@@ -2752,15 +2893,24 @@ var LEDGER_DUAL_WRITE_METRIC_NAMES = {
2752
2893
  };
2753
2894
 
2754
2895
  // src/services/plan-context.ts
2755
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
2896
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
2756
2897
  import { join as join10 } from "path";
2757
2898
 
2758
2899
  // src/services/vector-retrieval.ts
2759
2900
  import { mkdirSync } from "fs";
2901
+ import { createRequire } from "module";
2760
2902
  import { join as join9 } from "path";
2761
2903
  import { resolveGlobalRoot as resolveGlobalRoot3 } from "@fenglimg/fabric-shared";
2762
2904
  var embedderLoad;
2763
2905
  var OPTIONAL_EMBED_PACKAGE = "fastembed";
2906
+ function isEmbedderResolvable() {
2907
+ try {
2908
+ createRequire(import.meta.url).resolve(OPTIONAL_EMBED_PACKAGE);
2909
+ return true;
2910
+ } catch {
2911
+ return false;
2912
+ }
2913
+ }
2764
2914
  var embedderModuleLoader = (name) => import(name);
2765
2915
  var MISSING_EMBEDDER_HINT = "[fabric] vector semantic recall is enabled but the optional 'fastembed' package is unavailable \u2014 falling back to text-only ranking. Install it where the server resolves modules (e.g. `npm i -g fastembed`) to enable embeddings, or set embed_enabled:false to silence this.\n";
2766
2916
  var defaultMissingEmbedderHint = () => {
@@ -3170,7 +3320,7 @@ function bm25CachePath(projectRoot, revision) {
3170
3320
  }
3171
3321
  async function loadBm25ModelFromDisk(projectRoot, revision) {
3172
3322
  try {
3173
- const raw = await readFile5(bm25CachePath(projectRoot, revision), "utf8");
3323
+ const raw = await readFile6(bm25CachePath(projectRoot, revision), "utf8");
3174
3324
  const parsed = JSON.parse(raw);
3175
3325
  if (parsed.version !== 1) return null;
3176
3326
  return rehydrateBm25Model(parsed);
@@ -3283,6 +3433,10 @@ async function buildScoringContext(projectRoot, revision, rawItems, opts) {
3283
3433
  const configuredFusion = readFusion(projectRoot);
3284
3434
  const vectorActive = scoringContext.vectorScores !== void 0 && scoringContext.vectorScores.size > 0;
3285
3435
  scoringContext.fusion = configuredFusion === "auto" ? vectorActive ? "rrf" : "additive" : configuredFusion;
3436
+ let queryTermWeights;
3437
+ if (scoringContext.queryTerms.length > 0) {
3438
+ queryTermWeights = expandQueryTerms(opts.queryText);
3439
+ }
3286
3440
  if (scoringContext.fusion === "rrf" && scoringContext.queryTerms.length > 0 && rawItems.length > 0) {
3287
3441
  const rankIds = rawItems.map((item) => item.stable_id).sort((a, b) => compareStableIds(a, b));
3288
3442
  if (scoringContext.bm25 !== void 0) {
@@ -3294,6 +3448,49 @@ async function buildScoringContext(projectRoot, revision, rawItems, opts) {
3294
3448
  }
3295
3449
  return scoringContext;
3296
3450
  }
3451
+ var PROXIMITY_WINDOW = 6;
3452
+ var PROXIMITY_BOOST_CAP = 0.15;
3453
+ function proximityBoost(item, context, contentScore2) {
3454
+ if (contentScore2 <= 0) return 0;
3455
+ const text = context.docTexts?.get(item.stable_id);
3456
+ if (text === void 0 || text.length === 0) return 0;
3457
+ const tokens = text.toLowerCase().split(/[^a-z0-9_$#]+/u).filter(Boolean);
3458
+ const queryTerms = context.queryTerms;
3459
+ if (queryTerms.length < 2) return 0;
3460
+ const positions = /* @__PURE__ */ new Map();
3461
+ for (const qt of queryTerms) {
3462
+ const qtLower = qt.toLowerCase();
3463
+ const pos = [];
3464
+ for (let i = 0; i < tokens.length; i++) {
3465
+ if (tokens[i] === qtLower) {
3466
+ pos.push(i);
3467
+ }
3468
+ }
3469
+ if (pos.length > 0) {
3470
+ positions.set(qtLower, pos);
3471
+ }
3472
+ }
3473
+ if (positions.size < 2) return 0;
3474
+ const termList = [...positions.entries()];
3475
+ let minDist = Infinity;
3476
+ for (let i = 0; i < termList.length; i++) {
3477
+ const [, posI] = termList[i];
3478
+ for (let j = i + 1; j < termList.length; j++) {
3479
+ const [, posJ] = termList[j];
3480
+ for (const pi of posI) {
3481
+ for (const pj of posJ) {
3482
+ const dist = Math.abs(pi - pj);
3483
+ if (dist < minDist) minDist = dist;
3484
+ }
3485
+ }
3486
+ }
3487
+ }
3488
+ if (!Number.isFinite(minDist)) return 0;
3489
+ if (minDist >= PROXIMITY_WINDOW) return 0;
3490
+ const ratio = 1 - minDist / PROXIMITY_WINDOW;
3491
+ const boost = contentScore2 * PROXIMITY_BOOST_CAP * ratio;
3492
+ return boost;
3493
+ }
3297
3494
  function sortDescriptionItems(rawItems, scoringContext) {
3298
3495
  if (scoringContext === void 0) {
3299
3496
  return [...rawItems].sort((left, right) => compareStableIds(left.stable_id, right.stable_id)).map((item) => ({ item, score: 0 }));
@@ -3436,7 +3633,8 @@ function structuralScaleFor(context) {
3436
3633
  function scoreDescriptionItem(item, context) {
3437
3634
  const content = contentScore(item, context);
3438
3635
  const structural = salienceScore(item) + recencyBoost(item, context) + localityBoost(item, context);
3439
- return content + structuralScaleFor(context) * structural;
3636
+ const proximity = proximityBoost(item, context, content);
3637
+ return content + structuralScaleFor(context) * structural + proximity;
3440
3638
  }
3441
3639
  function scoreBreakdownForItem(item, context) {
3442
3640
  const hasQuery = context.queryTerms.length > 0;
@@ -3918,7 +4116,7 @@ import { enforcePayloadLimit as enforcePayloadLimit4 } from "@fenglimg/fabric-sh
3918
4116
  // src/services/review.ts
3919
4117
  import { execFileSync } from "child_process";
3920
4118
  import { existsSync as existsSync5 } from "fs";
3921
- import { readFile as readFile7, readdir as readdir2, stat as stat2, unlink as unlink2 } from "fs/promises";
4119
+ import { readFile as readFile8, readdir as readdir2, stat as stat2, unlink as unlink2 } from "fs/promises";
3922
4120
  import { homedir } from "os";
3923
4121
  import { basename, isAbsolute, join as join12, relative, resolve as resolve2, sep as sep2 } from "path";
3924
4122
 
@@ -3960,7 +4158,7 @@ import { allocateStoreKnowledgeId, isPersonalScope as isPersonalScope2 } from "@
3960
4158
 
3961
4159
  // src/services/pending-dedupe.ts
3962
4160
  import { existsSync as existsSync4 } from "fs";
3963
- import { readdir, readFile as readFile6, unlink } from "fs/promises";
4161
+ import { readdir, readFile as readFile7, unlink } from "fs/promises";
3964
4162
  import { join as join11 } from "path";
3965
4163
  var PENDING_TYPES = ["decisions", "pitfalls", "guidelines", "models", "processes"];
3966
4164
  var DISAMBIGUATION_SUFFIX = /^(.+)-([2-9])\.md$/u;
@@ -4058,7 +4256,7 @@ async function mergePendingTwins(projectRoot) {
4058
4256
  const abs = join11(dir, name);
4059
4257
  let content;
4060
4258
  try {
4061
- content = await readFile6(abs, "utf8");
4259
+ content = await readFile7(abs, "utf8");
4062
4260
  } catch {
4063
4261
  continue;
4064
4262
  }
@@ -4253,7 +4451,7 @@ async function listPending(projectRoot, filters) {
4253
4451
  const absolutePath = join12(dir, name);
4254
4452
  let content;
4255
4453
  try {
4256
- content = await readFile7(absolutePath, "utf8");
4454
+ content = await readFile8(absolutePath, "utf8");
4257
4455
  } catch {
4258
4456
  continue;
4259
4457
  }
@@ -4363,7 +4561,7 @@ async function approveOne(projectRoot, pendingPath) {
4363
4561
  let targetAbs;
4364
4562
  let writtenTarget = false;
4365
4563
  try {
4366
- const content = await readFile7(sourceAbs, "utf8");
4564
+ const content = await readFile8(sourceAbs, "utf8");
4367
4565
  const fm = parseFrontmatter(content);
4368
4566
  const pluralType = fm.type;
4369
4567
  if (pluralType === void 0 || !PLURAL_TYPES.includes(pluralType)) {
@@ -4435,7 +4633,7 @@ async function rejectAll(projectRoot, pendingPaths, reason) {
4435
4633
  try {
4436
4634
  const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
4437
4635
  if (existsSync5(sandboxed.abs)) {
4438
- const content = await readFile7(sandboxed.abs, "utf8");
4636
+ const content = await readFile8(sandboxed.abs, "utf8");
4439
4637
  const merged = rewriteFrontmatterMerge(content, { status: "rejected" });
4440
4638
  const rejectedAbs = sandboxed.abs.includes(`${sep2}pending${sep2}`) ? sandboxed.abs.replace(`${sep2}pending${sep2}`, `${sep2}rejected${sep2}`) : null;
4441
4639
  if (rejectedAbs !== null) {
@@ -4462,7 +4660,7 @@ async function modifyEntry(projectRoot, pendingPath, changes) {
4462
4660
  if (target === null) {
4463
4661
  throw new Error(`modify target not found: ${pendingPath}`);
4464
4662
  }
4465
- const content = await readFile7(target.absPath, "utf8");
4663
+ const content = await readFile8(target.absPath, "utf8");
4466
4664
  const fm = parseFrontmatter(content);
4467
4665
  const currentLayer = fm.layer ?? "team";
4468
4666
  if (fm.maturity === "verified" && changes.maturity === "proven" && fm.id !== void 0) {
@@ -4694,7 +4892,7 @@ async function listIndexedSearchEntries(source, type) {
4694
4892
  }
4695
4893
  let content;
4696
4894
  try {
4697
- content = await readFile7(absolutePath, "utf8");
4895
+ content = await readFile8(absolutePath, "utf8");
4698
4896
  searchEntryIndexContentReads += 1;
4699
4897
  } catch {
4700
4898
  directoryCache.files.delete(absolutePath);
@@ -4863,7 +5061,7 @@ async function deferAll(projectRoot, pendingPaths, until, reason) {
4863
5061
  try {
4864
5062
  const sandboxed = resolveSandboxedPath(projectRoot, pendingPath, { allowPersonal: true });
4865
5063
  if (existsSync5(sandboxed.abs)) {
4866
- const content = await readFile7(sandboxed.abs, "utf8");
5064
+ const content = await readFile8(sandboxed.abs, "utf8");
4867
5065
  stableId = parseFrontmatter(content).id;
4868
5066
  const patch = {
4869
5067
  status: "deferred",
@@ -5204,7 +5402,7 @@ function registerPending(server, tracker) {
5204
5402
  }
5205
5403
 
5206
5404
  // src/services/doctor.ts
5207
- import { access as access4, readFile as readFile17, readdir as readdirAsync, stat as statAsync, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
5405
+ import { access as access4, readFile as readFile18, readdir as readdirAsync, stat as statAsync, unlink as unlink4, writeFile as writeFile4 } from "fs/promises";
5208
5406
  import { constants as constants2 } from "fs";
5209
5407
  import { isAbsolute as isAbsolute2, join as join23, posix as posix5, resolve as resolve3 } from "path";
5210
5408
  import {
@@ -5420,7 +5618,7 @@ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
5420
5618
  }
5421
5619
 
5422
5620
  // src/services/doctor-stable-id-collision.ts
5423
- import { readFile as readFile8 } from "fs/promises";
5621
+ import { readFile as readFile9 } from "fs/promises";
5424
5622
  import { basename as basename2, join as join13 } from "path";
5425
5623
  import {
5426
5624
  buildStoreResolveInput as buildStoreResolveInput4,
@@ -5467,7 +5665,7 @@ async function inspectStoreStableIdIntegrity(projectRoot) {
5467
5665
  for (const ref of await readKnowledgeAcrossStores2(resolved.dirs)) {
5468
5666
  let source;
5469
5667
  try {
5470
- source = await readFile8(ref.file, "utf8");
5668
+ source = await readFile9(ref.file, "utf8");
5471
5669
  } catch {
5472
5670
  continue;
5473
5671
  }
@@ -5788,7 +5986,7 @@ function createNarrowNoPathsCheck(t, inspection) {
5788
5986
  }
5789
5987
 
5790
5988
  // src/services/doctor-broad-index.ts
5791
- import { readFile as readFile9 } from "fs/promises";
5989
+ import { readFile as readFile10 } from "fs/promises";
5792
5990
  import { join as join15 } from "path";
5793
5991
  var DEFAULT_BROAD_INDEX_BACKSTOP = 50;
5794
5992
  var BROAD_INDEX_BACKSTOP_MIN = 20;
@@ -5797,7 +5995,7 @@ var BROAD_INDEX_DRIFT_RATIO = 0.8;
5797
5995
  async function readBroadIndexBackstop(projectRoot) {
5798
5996
  const configPath = join15(projectRoot, ".fabric", "fabric-config.json");
5799
5997
  try {
5800
- const raw = await readFile9(configPath, "utf8");
5998
+ const raw = await readFile10(configPath, "utf8");
5801
5999
  const parsed = JSON.parse(raw);
5802
6000
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
5803
6001
  const v = parsed.broad_index_backstop;
@@ -6114,7 +6312,7 @@ function createBroadReviewRecheckCheck(t, inspection) {
6114
6312
  }
6115
6313
 
6116
6314
  // src/services/doctor-scope-lint.ts
6117
- import { readFile as readFile10 } from "fs/promises";
6315
+ import { readFile as readFile11 } from "fs/promises";
6118
6316
  import { join as join16 } from "path";
6119
6317
  import {
6120
6318
  buildStoreResolveInput as buildStoreResolveInput5,
@@ -6182,7 +6380,7 @@ async function lintStoreScopes(projectRoot) {
6182
6380
  }
6183
6381
  let source;
6184
6382
  try {
6185
- source = await readFile10(ref.file, "utf8");
6383
+ source = await readFile11(ref.file, "utf8");
6186
6384
  } catch {
6187
6385
  continue;
6188
6386
  }
@@ -6626,7 +6824,7 @@ async function inspectEventsJsonlGates(projectRoot, options = {}) {
6626
6824
  }
6627
6825
 
6628
6826
  // src/services/doctor-skill-lints.ts
6629
- import { readdir as readdir3, readFile as readFile11 } from "fs/promises";
6827
+ import { readdir as readdir3, readFile as readFile12 } from "fs/promises";
6630
6828
  import { join as join19, posix as posix2 } from "path";
6631
6829
  var FABRIC_SKILL_SLUGS = ["fabric-archive", "fabric-review"];
6632
6830
  var SKILL_MD_FRONTMATTER_ROOTS = [".claude/skills", ".codex/skills"];
@@ -6675,8 +6873,8 @@ async function inspectSkillRefMirror(projectRoot) {
6675
6873
  let codexBody;
6676
6874
  try {
6677
6875
  [claudeBody, codexBody] = await Promise.all([
6678
- readFile11(join19(claudeRef, fname), "utf8"),
6679
- readFile11(join19(codexRef, fname), "utf8")
6876
+ readFile12(join19(claudeRef, fname), "utf8"),
6877
+ readFile12(join19(codexRef, fname), "utf8")
6680
6878
  ]);
6681
6879
  } catch {
6682
6880
  continue;
@@ -6698,7 +6896,7 @@ async function inspectSkillTokenBudget(projectRoot) {
6698
6896
  const skillMdPath = join19(projectRoot, ".claude", "skills", slug, "SKILL.md");
6699
6897
  let body;
6700
6898
  try {
6701
- body = await readFile11(skillMdPath, "utf8");
6899
+ body = await readFile12(skillMdPath, "utf8");
6702
6900
  } catch {
6703
6901
  continue;
6704
6902
  }
@@ -6722,7 +6920,7 @@ async function inspectSkillDescription(projectRoot) {
6722
6920
  const skillMdPath = join19(projectRoot, ".claude", "skills", slug, "SKILL.md");
6723
6921
  let body;
6724
6922
  try {
6725
- body = await readFile11(skillMdPath, "utf8");
6923
+ body = await readFile12(skillMdPath, "utf8");
6726
6924
  } catch {
6727
6925
  continue;
6728
6926
  }
@@ -6765,7 +6963,7 @@ async function inspectSkillMdYamlInvalid(projectRoot) {
6765
6963
  const skillFile = join19(rootAbs, dirEntry.name, "SKILL.md");
6766
6964
  let raw;
6767
6965
  try {
6768
- raw = await readFile11(skillFile, "utf8");
6966
+ raw = await readFile12(skillFile, "utf8");
6769
6967
  } catch {
6770
6968
  continue;
6771
6969
  }
@@ -6888,7 +7086,7 @@ function createSkillMdYamlInvalidCheck(t, inspection) {
6888
7086
  }
6889
7087
 
6890
7088
  // src/services/doctor-retired-references-lint.ts
6891
- import { readdir as readdir4, readFile as readFile12, stat as stat3 } from "fs/promises";
7089
+ import { readdir as readdir4, readFile as readFile13, stat as stat3 } from "fs/promises";
6892
7090
  import { join as join20, posix as posix3, relative as relative2 } from "path";
6893
7091
  var RETIRED_TOKENS = [
6894
7092
  { token: "fab_plan_context", replacement: "fab_recall", reason: "retrieval collapsed to one lean fab_recall (KT-DEC-0026)" },
@@ -6956,7 +7154,7 @@ async function inspectRetiredReferences(projectRoot) {
6956
7154
  try {
6957
7155
  if ((await stat3(abs)).isFile()) {
6958
7156
  scannedFiles += 1;
6959
- scanText(toRel(abs), await readFile12(abs, "utf8"), false, hits);
7157
+ scanText(toRel(abs), await readFile13(abs, "utf8"), false, hits);
6960
7158
  }
6961
7159
  } catch {
6962
7160
  }
@@ -6965,7 +7163,7 @@ async function inspectRetiredReferences(projectRoot) {
6965
7163
  for (const file of await walkFiles(join20(projectRoot, dir), [".md"])) {
6966
7164
  scannedFiles += 1;
6967
7165
  try {
6968
- scanText(toRel(file), await readFile12(file, "utf8"), false, hits);
7166
+ scanText(toRel(file), await readFile13(file, "utf8"), false, hits);
6969
7167
  } catch {
6970
7168
  }
6971
7169
  }
@@ -6974,7 +7172,7 @@ async function inspectRetiredReferences(projectRoot) {
6974
7172
  for (const file of await walkFiles(join20(projectRoot, dir), [".cjs"])) {
6975
7173
  scannedFiles += 1;
6976
7174
  try {
6977
- scanText(toRel(file), await readFile12(file, "utf8"), true, hits);
7175
+ scanText(toRel(file), await readFile13(file, "utf8"), true, hits);
6978
7176
  } catch {
6979
7177
  }
6980
7178
  }
@@ -7009,7 +7207,7 @@ function createRetiredReferenceCheck(t, inspection) {
7009
7207
 
7010
7208
  // src/services/doctor-hooks-lints.ts
7011
7209
  import { constants } from "fs";
7012
- import { access, readdir as readdir5, readFile as readFile13, stat as stat4 } from "fs/promises";
7210
+ import { access, readdir as readdir5, readFile as readFile14, stat as stat4 } from "fs/promises";
7013
7211
  import { join as join21, posix as posix4 } from "path";
7014
7212
  import { Script } from "vm";
7015
7213
  var HOOKS_RUNTIME_CLIENT_DIRS = [
@@ -7079,7 +7277,7 @@ async function inspectHooksWired(projectRoot) {
7079
7277
  const settingsPath = join21(projectRoot, ".claude", "settings.json");
7080
7278
  let parsed;
7081
7279
  try {
7082
- parsed = JSON.parse(await readFile13(settingsPath, "utf8"));
7280
+ parsed = JSON.parse(await readFile14(settingsPath, "utf8"));
7083
7281
  } catch {
7084
7282
  return { status: "missing-settings", missingHooks: [] };
7085
7283
  }
@@ -7172,7 +7370,7 @@ async function inspectHooksContentDrift(projectRoot) {
7172
7370
  const hashes = [];
7173
7371
  for (const { client, abs } of copies) {
7174
7372
  try {
7175
- const body = await readFile13(abs, "utf8");
7373
+ const body = await readFile14(abs, "utf8");
7176
7374
  hashes.push({ client, sha: sha256(body) });
7177
7375
  } catch {
7178
7376
  }
@@ -7205,7 +7403,7 @@ async function inspectHooksRuntime(projectRoot) {
7205
7403
  scanned += 1;
7206
7404
  let body;
7207
7405
  try {
7208
- body = await readFile13(abs, "utf8");
7406
+ body = await readFile14(abs, "utf8");
7209
7407
  } catch (err) {
7210
7408
  issues.push({
7211
7409
  path: displayPath,
@@ -7342,7 +7540,7 @@ function createHookCacheWritabilityCheck(t, inspection) {
7342
7540
  }
7343
7541
 
7344
7542
  // src/services/doctor-bootstrap-lints.ts
7345
- import { access as access2, readFile as readFile14 } from "fs/promises";
7543
+ import { access as access2, readFile as readFile15 } from "fs/promises";
7346
7544
  import { join as join22 } from "path";
7347
7545
  import {
7348
7546
  BOOTSTRAP_MARKER_BEGIN,
@@ -7385,7 +7583,7 @@ async function inspectL1BootstrapSnapshotDrift(target) {
7385
7583
  const canonical = resolveBootstrapCanonical();
7386
7584
  let onDisk;
7387
7585
  try {
7388
- onDisk = await readFile14(abs, "utf8");
7586
+ onDisk = await readFile15(abs, "utf8");
7389
7587
  } catch {
7390
7588
  return { status: "missing", canonical, onDisk: null };
7391
7589
  }
@@ -7414,14 +7612,14 @@ async function inspectL2ManagedBlockDrift(target) {
7414
7612
  const snapshotPath = join22(target, ".fabric", "AGENTS.md");
7415
7613
  let snapshot;
7416
7614
  try {
7417
- snapshot = await readFile14(snapshotPath, "utf8");
7615
+ snapshot = await readFile15(snapshotPath, "utf8");
7418
7616
  } catch {
7419
7617
  return { status: "ok", drifted: [] };
7420
7618
  }
7421
7619
  const projectRulesPath = join22(target, ".fabric", "project-rules.md");
7422
7620
  let expectedBody = snapshot;
7423
7621
  try {
7424
- const projectRules = await readFile14(projectRulesPath, "utf8");
7622
+ const projectRules = await readFile15(projectRulesPath, "utf8");
7425
7623
  expectedBody = `${snapshot}
7426
7624
  ---
7427
7625
  ${projectRules}`;
@@ -7435,7 +7633,7 @@ ${projectRules}`;
7435
7633
  for (const abs of blockTargets) {
7436
7634
  let content;
7437
7635
  try {
7438
- content = await readFile14(abs, "utf8");
7636
+ content = await readFile15(abs, "utf8");
7439
7637
  } catch {
7440
7638
  continue;
7441
7639
  }
@@ -7460,7 +7658,7 @@ ${projectRules}`;
7460
7658
  }
7461
7659
  const claudeMdPath = join22(target, "CLAUDE.md");
7462
7660
  try {
7463
- const claudeContent = await readFile14(claudeMdPath, "utf8");
7661
+ const claudeContent = await readFile15(claudeMdPath, "utf8");
7464
7662
  anyManagedBlockFound = true;
7465
7663
  const lines = claudeContent.split(/\r?\n/u);
7466
7664
  const hasAtImport = lines.some((line) => line.trim() === "@.fabric/AGENTS.md");
@@ -7528,7 +7726,7 @@ import { appendFile as appendFile3 } from "fs/promises";
7528
7726
  import { minimatch as minimatch2 } from "minimatch";
7529
7727
 
7530
7728
  // src/services/cite-rollup.ts
7531
- import { readFile as readFile15 } from "fs/promises";
7729
+ import { readFile as readFile16 } from "fs/promises";
7532
7730
  import { createLedgerWriteQueue as createLedgerWriteQueue3 } from "@fenglimg/fabric-shared/node/atomic-write";
7533
7731
  var citeRollupQueue = createLedgerWriteQueue3();
7534
7732
  async function appendCiteRollupRow(projectRoot, row) {
@@ -7540,7 +7738,7 @@ async function readCiteRollup(projectRoot) {
7540
7738
  const path = getCiteRollupPath(projectRoot);
7541
7739
  let raw;
7542
7740
  try {
7543
- raw = await readFile15(path, "utf8");
7741
+ raw = await readFile16(path, "utf8");
7544
7742
  } catch (error) {
7545
7743
  if (isNodeError(error) && error.code === "ENOENT") return [];
7546
7744
  throw error;
@@ -8131,6 +8329,18 @@ async function runDoctorCiteCoverage(projectRoot, options) {
8131
8329
  }
8132
8330
  }
8133
8331
  }
8332
+ const editSessionIds = /* @__PURE__ */ new Set();
8333
+ for (const edit of editEvents) {
8334
+ if (typeof edit.session_id === "string" && edit.session_id.length > 0) {
8335
+ editSessionIds.add(edit.session_id);
8336
+ }
8337
+ }
8338
+ let recallsInWindow = 0;
8339
+ for (const list of plannedBySession.values()) recallsInWindow += list.length;
8340
+ let recallSessionsCorrelated = 0;
8341
+ for (const sid of plannedBySession.keys()) {
8342
+ if (editSessionIds.has(sid)) recallSessionsCorrelated += 1;
8343
+ }
8134
8344
  const noneTotal = Object.values(noneHistogram).reduce((a, b) => a + b, 0);
8135
8345
  const compliantCites = qualifyingCites + noneTotal;
8136
8346
  const noncompliantCites = expectedButMissed;
@@ -8198,6 +8408,11 @@ async function runDoctorCiteCoverage(projectRoot, options) {
8198
8408
  uncorrelatable_edits: uncorrelatableEdits,
8199
8409
  recall_backed_edits: recallBackedEdits,
8200
8410
  recall_coverage_rate: recallCoverageRate,
8411
+ recall_diagnostics: {
8412
+ recalls_in_window: recallsInWindow,
8413
+ recall_sessions: plannedBySession.size,
8414
+ recall_sessions_correlated: recallSessionsCorrelated
8415
+ },
8201
8416
  exposed_and_mutated: {
8202
8417
  count: exposedAndMutated.count,
8203
8418
  ...exposedAndMutated.ids.length > 0 ? { ids: exposedAndMutated.ids } : {}
@@ -9091,7 +9306,7 @@ function truncateErrorMessage(error) {
9091
9306
  async function inspectForensic(projectRoot) {
9092
9307
  const path = join23(projectRoot, ".fabric", "forensic.json");
9093
9308
  try {
9094
- const parsed = forensicReportSchema.parse(JSON.parse(await readFile17(path, "utf8")));
9309
+ const parsed = forensicReportSchema.parse(JSON.parse(await readFile18(path, "utf8")));
9095
9310
  return { present: true, valid: true, report: parsed };
9096
9311
  } catch (error) {
9097
9312
  if (isMissingFileError(error)) {
@@ -9121,7 +9336,7 @@ async function inspectEventLedger(projectRoot) {
9121
9336
  try {
9122
9337
  await access4(path, constants2.W_OK);
9123
9338
  const { warnings } = await readEventLedger(projectRoot);
9124
- const raw = await readFile17(path, "utf8");
9339
+ const raw = await readFile18(path, "utf8");
9125
9340
  const invalidLine = raw.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean).find((line) => !isValidJsonLine(line));
9126
9341
  const partialWarning = warnings.find((w) => w.kind === "partial_write_at_tail");
9127
9342
  const schemaVersionSamples = [];
@@ -9797,7 +10012,7 @@ function inspectStaleServeLock(projectRoot, now) {
9797
10012
  async function readUnderseedThresholdFromConfig(projectRoot) {
9798
10013
  const configPath = join23(projectRoot, ".fabric", "fabric-config.json");
9799
10014
  try {
9800
- const raw = await readFile17(configPath, "utf8");
10015
+ const raw = await readFile18(configPath, "utf8");
9801
10016
  const parsed = JSON.parse(raw);
9802
10017
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
9803
10018
  const v = parsed.underseed_node_threshold;
@@ -9916,7 +10131,7 @@ async function readOnboardOptedOut(projectRoot) {
9916
10131
  const path = join23(projectRoot, ".fabric", "fabric-config.json");
9917
10132
  let raw;
9918
10133
  try {
9919
- raw = await readFile17(path, "utf8");
10134
+ raw = await readFile18(path, "utf8");
9920
10135
  } catch {
9921
10136
  return [];
9922
10137
  }
@@ -10024,7 +10239,7 @@ async function rewriteThreeEndManagedBlocks(projectRoot) {
10024
10239
  }
10025
10240
  let snapshot;
10026
10241
  try {
10027
- snapshot = await readFile17(snapshotPath, "utf8");
10242
+ snapshot = await readFile18(snapshotPath, "utf8");
10028
10243
  } catch {
10029
10244
  return;
10030
10245
  }
@@ -10033,7 +10248,7 @@ async function rewriteThreeEndManagedBlocks(projectRoot) {
10033
10248
  let expectedBody = snapshot;
10034
10249
  if (hasProjectRules) {
10035
10250
  try {
10036
- const projectRules = await readFile17(projectRulesPath, "utf8");
10251
+ const projectRules = await readFile18(projectRulesPath, "utf8");
10037
10252
  expectedBody = `${snapshot}
10038
10253
  ---
10039
10254
  ${projectRules}`;
@@ -10052,7 +10267,7 @@ ${BOOTSTRAP_MARKER_END2}`;
10052
10267
  }
10053
10268
  let existing;
10054
10269
  try {
10055
- existing = await readFile17(abs, "utf8");
10270
+ existing = await readFile18(abs, "utf8");
10056
10271
  } catch {
10057
10272
  continue;
10058
10273
  }
@@ -10081,7 +10296,7 @@ ${managedBlock}
10081
10296
  if (await pathExists(claudeMdPath)) {
10082
10297
  let claudeContent;
10083
10298
  try {
10084
- claudeContent = await readFile17(claudeMdPath, "utf8");
10299
+ claudeContent = await readFile18(claudeMdPath, "utf8");
10085
10300
  } catch {
10086
10301
  return;
10087
10302
  }
@@ -10223,7 +10438,7 @@ async function enrichDescriptions(projectRoot, opts = {}) {
10223
10438
  scanned += 1;
10224
10439
  let source;
10225
10440
  try {
10226
- source = await readFile17(absPath, "utf8");
10441
+ source = await readFile18(absPath, "utf8");
10227
10442
  } catch {
10228
10443
  continue;
10229
10444
  }
@@ -10361,7 +10576,7 @@ async function runDoctorConflictLint(projectRoot, opts = {}) {
10361
10576
  }
10362
10577
 
10363
10578
  // src/services/why-not-surfaced.ts
10364
- import { readFile as readFile18 } from "fs/promises";
10579
+ import { readFile as readFile19 } from "fs/promises";
10365
10580
  import { basename as basename3, join as join24 } from "path";
10366
10581
  import {
10367
10582
  buildStoreResolveInput as buildStoreResolveInput7,
@@ -10411,7 +10626,7 @@ async function explainWhyNotSurfaced(projectRoot, query) {
10411
10626
  }
10412
10627
  let source;
10413
10628
  try {
10414
- source = await readFile18(candidate.file, "utf8");
10629
+ source = await readFile19(candidate.file, "utf8");
10415
10630
  } catch {
10416
10631
  return base;
10417
10632
  }
@@ -10464,6 +10679,191 @@ function buildColdEvalBatch(candidates) {
10464
10679
  return { rubric: COLD_EVAL_RUBRIC, candidates: judgeable };
10465
10680
  }
10466
10681
 
10682
+ // src/services/doctor-related-graph.ts
10683
+ function buildRelatedGraph(nodes) {
10684
+ const brokenLinks = [];
10685
+ const inDegree = /* @__PURE__ */ new Map();
10686
+ const allIds = /* @__PURE__ */ new Set();
10687
+ const edgeSources = [];
10688
+ for (const node of nodes) {
10689
+ const bareId = extractBareStableId(node.qualifiedId);
10690
+ allIds.add(node.qualifiedId);
10691
+ if (bareId !== null) {
10692
+ allIds.add(bareId);
10693
+ }
10694
+ const related = node.related;
10695
+ if (related !== void 0 && related.length > 0) {
10696
+ edgeSources.push({ source: node.qualifiedId, related });
10697
+ for (const rel of related) {
10698
+ inDegree.set(rel, (inDegree.get(rel) ?? 0) + 1);
10699
+ }
10700
+ }
10701
+ }
10702
+ for (const { source, related } of edgeSources) {
10703
+ for (const rel of related) {
10704
+ if (!allIds.has(rel)) {
10705
+ brokenLinks.push({ source, target: rel });
10706
+ }
10707
+ }
10708
+ }
10709
+ const hubEntries = [...inDegree.entries()].map(([stableId, degree]) => ({ stableId, inDegree: degree })).sort((a, b) => b.inDegree - a.inDegree || a.stableId.localeCompare(b.stableId));
10710
+ return {
10711
+ brokenLinks,
10712
+ hubEntries,
10713
+ totalEntries: nodes.length
10714
+ };
10715
+ }
10716
+ function extractBareStableId(qualifiedId) {
10717
+ const colonIdx = qualifiedId.indexOf(":");
10718
+ if (colonIdx > 0 && colonIdx < qualifiedId.length - 1) {
10719
+ return qualifiedId.slice(colonIdx + 1);
10720
+ }
10721
+ return null;
10722
+ }
10723
+ async function inspectRelatedGraph(projectRoot) {
10724
+ const entries = await collectStoreCanonicalEntries(projectRoot);
10725
+ return buildRelatedGraph(
10726
+ entries.map((entry) => ({
10727
+ qualifiedId: entry.qualifiedId,
10728
+ related: entry.description.related
10729
+ }))
10730
+ );
10731
+ }
10732
+
10733
+ // src/services/store-precheck.ts
10734
+ import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
10735
+ import { join as join25 } from "path";
10736
+ import {
10737
+ buildStoreResolveInput as buildStoreResolveInput8,
10738
+ createStoreResolver as createStoreResolver8,
10739
+ resolveGlobalRoot as resolveGlobalRoot9,
10740
+ storeRelativePathForMount as storeRelativePathForMount7
10741
+ } from "@fenglimg/fabric-shared";
10742
+ var CACHE_TTL_MS = 6e4;
10743
+ var cache = /* @__PURE__ */ new Map();
10744
+ function clearPrecheckCache() {
10745
+ cache.clear();
10746
+ }
10747
+ function evaluateStoreDir(storeDir, identity) {
10748
+ if (!existsSync9(storeDir)) {
10749
+ return {
10750
+ uuid: identity.uuid,
10751
+ alias: identity.alias,
10752
+ reachable: false,
10753
+ reason: `directory not found at ${storeDir}`
10754
+ };
10755
+ }
10756
+ const storeJsonPath = join25(storeDir, "store.json");
10757
+ const gitDirPath = join25(storeDir, ".git");
10758
+ const hasStoreJson = existsSync9(storeJsonPath) && isValidStoreJson(storeJsonPath);
10759
+ const hasGitDir = existsSync9(gitDirPath);
10760
+ if (!hasStoreJson && !hasGitDir) {
10761
+ return {
10762
+ uuid: identity.uuid,
10763
+ alias: identity.alias,
10764
+ reachable: false,
10765
+ reason: `no store.json or .git found in ${storeDir}`
10766
+ };
10767
+ }
10768
+ return { uuid: identity.uuid, alias: identity.alias, reachable: true };
10769
+ }
10770
+ function isValidStoreJson(path) {
10771
+ try {
10772
+ JSON.parse(readFileSync4(path, "utf8"));
10773
+ return true;
10774
+ } catch {
10775
+ return false;
10776
+ }
10777
+ }
10778
+ async function precheckStoreReachability(projectRoot, globalRoot = resolveGlobalRoot9(), now = Date.now()) {
10779
+ const cached = cache.get(projectRoot);
10780
+ if (cached !== void 0 && cached.expiresAt > now) {
10781
+ return cached.result;
10782
+ }
10783
+ const input = buildStoreResolveInput8(projectRoot, globalRoot);
10784
+ if (input === null) {
10785
+ const result2 = { stores: [], allReachable: true };
10786
+ cache.set(projectRoot, { result: result2, expiresAt: now + CACHE_TTL_MS });
10787
+ return result2;
10788
+ }
10789
+ const readSet = createStoreResolver8().resolveReadSet(input);
10790
+ const stores = readSet.stores.map((entry) => {
10791
+ const mounted = input.mountedStores.find((s) => s.store_uuid === entry.store_uuid);
10792
+ const storeDir = join25(
10793
+ globalRoot,
10794
+ storeRelativePathForMount7(mounted ?? { store_uuid: entry.store_uuid })
10795
+ );
10796
+ return evaluateStoreDir(storeDir, { uuid: entry.store_uuid, alias: entry.alias });
10797
+ });
10798
+ const result = {
10799
+ stores,
10800
+ allReachable: stores.every((s) => s.reachable)
10801
+ };
10802
+ cache.set(projectRoot, { result, expiresAt: now + CACHE_TTL_MS });
10803
+ return result;
10804
+ }
10805
+
10806
+ // src/services/doctor-consumption-lint.ts
10807
+ var DEFAULT_WINDOW_DAYS = 30;
10808
+ var DEFAULT_TOP_N = 10;
10809
+ var DEFAULT_MIN_TOTAL_ENTRIES = 20;
10810
+ var DEFAULT_MIN_CONSUMED_WINDOWS = 30;
10811
+ var DEFAULT_MIN_CONSUMED_EVENTS = 50;
10812
+ var CONSUMED_PREFIX = "knowledge_consumed:";
10813
+ var MS_PER_DAY5 = 24 * 60 * 60 * 1e3;
10814
+ function resolveConfig(config) {
10815
+ return {
10816
+ windowDays: config?.windowDays ?? DEFAULT_WINDOW_DAYS,
10817
+ topN: config?.topN ?? DEFAULT_TOP_N,
10818
+ minTotalEntries: config?.minTotalEntries ?? DEFAULT_MIN_TOTAL_ENTRIES,
10819
+ minConsumedWindows: config?.minConsumedWindows ?? DEFAULT_MIN_CONSUMED_WINDOWS,
10820
+ minConsumedEvents: config?.minConsumedEvents ?? DEFAULT_MIN_CONSUMED_EVENTS
10821
+ };
10822
+ }
10823
+ function aggregateConsumption(rows, allQualifiedIds, now, config) {
10824
+ const cfg = resolveConfig(config);
10825
+ const cutoffMs = now - cfg.windowDays * MS_PER_DAY5;
10826
+ const consumed = /* @__PURE__ */ new Map();
10827
+ let consumedWindows = 0;
10828
+ let totalConsumedEvents = 0;
10829
+ for (const row of rows) {
10830
+ const rowTs = Date.parse(row.timestamp);
10831
+ if (!Number.isFinite(rowTs) || rowTs < cutoffMs) continue;
10832
+ if (row.counters === null || typeof row.counters !== "object") continue;
10833
+ let windowHadConsumption = false;
10834
+ for (const [counterName, rawCount] of Object.entries(row.counters)) {
10835
+ if (!counterName.startsWith(CONSUMED_PREFIX)) continue;
10836
+ const count = typeof rawCount === "number" ? rawCount : 0;
10837
+ if (count <= 0) continue;
10838
+ const id = counterName.slice(CONSUMED_PREFIX.length);
10839
+ if (id.length === 0) continue;
10840
+ consumed.set(id, (consumed.get(id) ?? 0) + count);
10841
+ totalConsumedEvents += count;
10842
+ windowHadConsumption = true;
10843
+ }
10844
+ if (windowHadConsumption) consumedWindows += 1;
10845
+ }
10846
+ const topConsumed = [...consumed.entries()].map(([stableId, count]) => ({ stableId, count })).sort((a, b) => b.count - a.count || a.stableId.localeCompare(b.stableId)).slice(0, cfg.topN);
10847
+ const dataMature = consumedWindows >= cfg.minConsumedWindows && totalConsumedEvents >= cfg.minConsumedEvents && allQualifiedIds.length >= cfg.minTotalEntries;
10848
+ const zeroConsumed = dataMature ? allQualifiedIds.filter((id) => !consumed.has(id)).sort((a, b) => a.localeCompare(b)) : [];
10849
+ return {
10850
+ topConsumed,
10851
+ zeroConsumed,
10852
+ totalEntries: allQualifiedIds.length,
10853
+ consumedEntries: consumed.size,
10854
+ consumedWindows,
10855
+ totalConsumedEvents,
10856
+ dataMature,
10857
+ windowDays: cfg.windowDays
10858
+ };
10859
+ }
10860
+ async function inspectConsumption(projectRoot, config, now = Date.now()) {
10861
+ const rows = await readMetrics(projectRoot);
10862
+ const entries = await collectStoreCanonicalEntries(projectRoot);
10863
+ const allQualifiedIds = entries.map((entry) => entry.qualifiedId);
10864
+ return aggregateConsumption(rows, allQualifiedIds, now, config);
10865
+ }
10866
+
10467
10867
  // src/services/rotation-tick.ts
10468
10868
  var DEFAULT_TICK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
10469
10869
  var tickTimers = /* @__PURE__ */ new Map();
@@ -10499,7 +10899,7 @@ import { IOFabricError as IOFabricError2, RuleError } from "@fenglimg/fabric-sha
10499
10899
 
10500
10900
  // src/services/read-ledger.ts
10501
10901
  import { randomUUID as randomUUID7 } from "crypto";
10502
- import { access as access5, copyFile, readFile as readFile19, rm } from "fs/promises";
10902
+ import { access as access5, copyFile, readFile as readFile20, rm } from "fs/promises";
10503
10903
  import { ledgerEntrySchema } from "@fenglimg/fabric-shared";
10504
10904
  async function resolveLedgerPaths(projectRoot) {
10505
10905
  const primaryPath = getLedgerPath(projectRoot);
@@ -10527,7 +10927,7 @@ async function readLegacyLedger(projectRoot) {
10527
10927
  const { readPath } = await resolveLedgerPaths(projectRoot);
10528
10928
  let raw;
10529
10929
  try {
10530
- raw = await readFile19(readPath, "utf8");
10930
+ raw = await readFile20(readPath, "utf8");
10531
10931
  } catch (error) {
10532
10932
  if (isNodeError(error) && error.code === "ENOENT") {
10533
10933
  return [];
@@ -10782,8 +11182,8 @@ function formatError(error) {
10782
11182
  }
10783
11183
  function formatPreexistingRootMessage(projectRoot) {
10784
11184
  const preexisting = [];
10785
- if (existsSync9(join25(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
10786
- if (existsSync9(join25(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
11185
+ if (existsSync10(join26(projectRoot, "CLAUDE.md"))) preexisting.push("CLAUDE.md");
11186
+ if (existsSync10(join26(projectRoot, "AGENTS.md"))) preexisting.push("AGENTS.md");
10787
11187
  if (preexisting.length === 0) return null;
10788
11188
  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.`;
10789
11189
  }
@@ -10810,7 +11210,7 @@ function createFabricServer(tracker) {
10810
11210
  const server = new McpServer(
10811
11211
  {
10812
11212
  name: "fabric-knowledge-server",
10813
- version: "2.3.0-rc.3"
11213
+ version: "2.3.0-rc.4"
10814
11214
  },
10815
11215
  {
10816
11216
  instructions: FABRIC_SERVER_INSTRUCTIONS
@@ -10830,10 +11230,10 @@ function createFabricServer(tracker) {
10830
11230
  },
10831
11231
  async (_uri) => {
10832
11232
  const projectRoot = process.env.FABRIC_PROJECT_ROOT ?? process.cwd();
10833
- const path = join25(projectRoot, ".fabric", "bootstrap", "README.md");
11233
+ const path = join26(projectRoot, ".fabric", "bootstrap", "README.md");
10834
11234
  let text = "";
10835
- if (existsSync9(path)) {
10836
- text = await readFile20(path, "utf8");
11235
+ if (existsSync10(path)) {
11236
+ text = await readFile21(path, "utf8");
10837
11237
  }
10838
11238
  return {
10839
11239
  contents: [
@@ -10933,11 +11333,15 @@ export {
10933
11333
  METRIC_COUNTER_NAMES,
10934
11334
  OPTIONAL_EMBED_PACKAGE,
10935
11335
  RETIRED_TOKENS,
11336
+ aggregateConsumption,
10936
11337
  appendEventLedgerEvent,
10937
11338
  buildAlwaysActiveBodies,
10938
11339
  buildColdEvalBatch,
10939
11340
  buildKnowledgeCensus,
11341
+ buildRelatedGraph,
10940
11342
  bumpCounter,
11343
+ clearPrecheckCache,
11344
+ collectStoreCanonicalEntries,
10941
11345
  contextCache,
10942
11346
  createFabricServer,
10943
11347
  createInFlightTracker,
@@ -10946,6 +11350,7 @@ export {
10946
11350
  detectUnboundProject,
10947
11351
  drainCounters,
10948
11352
  enrichDescriptions,
11353
+ evaluateStoreDir,
10949
11354
  explainWhyNotSurfaced,
10950
11355
  extractKnowledge,
10951
11356
  findConflictCandidates,
@@ -10956,12 +11361,16 @@ export {
10956
11361
  getLedgerPath,
10957
11362
  getLegacyLedgerPath,
10958
11363
  getMetricsLedgerPath,
11364
+ inspectConsumption,
11365
+ inspectRelatedGraph,
10959
11366
  inspectRetiredReferences,
11367
+ isEmbedderResolvable,
10960
11368
  lintConflicts,
10961
11369
  loadConflictEntries,
10962
11370
  loadEmbedder,
10963
11371
  pairSimilarity,
10964
11372
  planContext,
11373
+ precheckStoreReachability,
10965
11374
  readEmbedConfig,
10966
11375
  readEventLedger,
10967
11376
  readFusion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-server",
3
- "version": "2.3.0-rc.3",
3
+ "version": "2.3.0-rc.4",
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.3"
40
+ "@fenglimg/fabric-shared": "2.3.0-rc.4"
41
41
  },
42
42
  "optionalDependencies": {
43
43
  "fastembed": "^2.0.0"