@fenglimg/fabric-server 2.3.0-rc.6 → 2.3.0-rc.8

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
@@ -988,10 +988,12 @@ type RecallInput = PlanContextInput & {
988
988
  */
989
989
  include_related?: boolean;
990
990
  };
991
+ type FullRuleDescription = PlanContextResult["candidates"][number]["description"];
992
+ type RecallEntryDescription = Pick<FullRuleDescription, "summary" | "must_read_if" | "intent_clues"> & Partial<Pick<FullRuleDescription, "knowledge_type">>;
991
993
  type RecallEntry = {
992
994
  stable_id: string;
993
995
  rank: number;
994
- description: PlanContextResult["candidates"][number]["description"];
996
+ description: RecallEntryDescription;
995
997
  read_path?: string;
996
998
  store?: {
997
999
  alias: string;
package/dist/index.js CHANGED
@@ -817,6 +817,64 @@ function readOrphanDemoteThresholdDays(projectRoot) {
817
817
  return {};
818
818
  }
819
819
  }
820
+ function readCredibilityHalfLives(projectRoot) {
821
+ const defaults = {
822
+ decisions: 180,
823
+ guidelines: 150,
824
+ models: 150,
825
+ pitfalls: 120,
826
+ processes: 120
827
+ };
828
+ try {
829
+ const cfg = readFabricConfig(projectRoot);
830
+ const validate = (v) => {
831
+ if (typeof v !== "number" || !Number.isFinite(v) || v < 1 || v > 3650 || !Number.isInteger(v)) {
832
+ return void 0;
833
+ }
834
+ return v;
835
+ };
836
+ const out = { ...defaults };
837
+ const dec = validate(cfg.credibility_half_life_decisions_days);
838
+ if (dec !== void 0) out.decisions = dec;
839
+ const gui = validate(cfg.credibility_half_life_guidelines_days);
840
+ if (gui !== void 0) out.guidelines = gui;
841
+ const mod = validate(cfg.credibility_half_life_models_days);
842
+ if (mod !== void 0) out.models = mod;
843
+ const pit = validate(cfg.credibility_half_life_pitfalls_days);
844
+ if (pit !== void 0) out.pitfalls = pit;
845
+ const pro = validate(cfg.credibility_half_life_processes_days);
846
+ if (pro !== void 0) out.processes = pro;
847
+ return out;
848
+ } catch {
849
+ return { ...defaults };
850
+ }
851
+ }
852
+ function readCredibilityFloors(projectRoot) {
853
+ const defaults = {
854
+ draft: 0.4,
855
+ verified: 0.55,
856
+ proven: 0.7
857
+ };
858
+ try {
859
+ const cfg = readFabricConfig(projectRoot);
860
+ const validate = (v) => {
861
+ if (typeof v !== "number" || !Number.isFinite(v) || v < 0 || v > 1) {
862
+ return void 0;
863
+ }
864
+ return v;
865
+ };
866
+ const out = { ...defaults };
867
+ const draft = validate(cfg.credibility_floor_draft);
868
+ if (draft !== void 0) out.draft = draft;
869
+ const verified = validate(cfg.credibility_floor_verified);
870
+ if (verified !== void 0) out.verified = verified;
871
+ const proven = validate(cfg.credibility_floor_proven);
872
+ if (proven !== void 0) out.proven = proven;
873
+ return out;
874
+ } catch {
875
+ return { ...defaults };
876
+ }
877
+ }
820
878
  function readFusion(projectRoot) {
821
879
  try {
822
880
  const raw = readFabricConfig(projectRoot).fusion;
@@ -1109,7 +1167,9 @@ function extractRuleDescription(source) {
1109
1167
  relevance_scope: knowledge?.relevance_scope ?? "broad",
1110
1168
  relevance_paths: knowledge?.relevance_paths ?? [],
1111
1169
  // v2.2 H2-related (W1-T7): graph edges, undefined when absent.
1112
- related: knowledge?.related
1170
+ related: knowledge?.related,
1171
+ // v2.2 glossary aliases FIELD (C-002): synonym terms, undefined when absent.
1172
+ aliases: knowledge?.aliases
1113
1173
  };
1114
1174
  }
1115
1175
  function extractDescriptionFromFrontmatter(frontmatter) {
@@ -1133,7 +1193,9 @@ function extractDescriptionFromFrontmatter(frontmatter) {
1133
1193
  relevance_scope: knowledge.relevance_scope,
1134
1194
  relevance_paths: knowledge.relevance_paths,
1135
1195
  // v2.2 H2-related (W1-T7): graph edges parsed from frontmatter.
1136
- related: knowledge.related
1196
+ related: knowledge.related,
1197
+ // v2.2 glossary aliases FIELD (C-002): synonym terms for the BM25 body.
1198
+ aliases: knowledge.aliases
1137
1199
  };
1138
1200
  }
1139
1201
  function isForbiddenCrossLayerEdge(sourceLayer, targetId) {
@@ -1222,6 +1284,7 @@ function extractKnowledgeFieldsFromFrontmatter(frontmatter) {
1222
1284
  }
1223
1285
  return true;
1224
1286
  });
1287
+ const aliases = extractInlineArray(frontmatter, "aliases");
1225
1288
  return {
1226
1289
  id,
1227
1290
  knowledge_type,
@@ -1230,7 +1293,8 @@ function extractKnowledgeFieldsFromFrontmatter(frontmatter) {
1230
1293
  tags: tags.length > 0 ? tags : void 0,
1231
1294
  relevance_scope,
1232
1295
  relevance_paths,
1233
- related: related.length > 0 ? related : void 0
1296
+ related: related.length > 0 ? related : void 0,
1297
+ aliases: aliases.length > 0 ? aliases : void 0
1234
1298
  };
1235
1299
  }
1236
1300
  function extractScalar(frontmatter, key) {
@@ -3491,7 +3555,11 @@ async function buildScoringContext(projectRoot, revision, rawItems, opts) {
3491
3555
  const scoringContext = {
3492
3556
  nowMs: Date.now(),
3493
3557
  targetPaths: opts.targetPaths,
3494
- queryTerms: buildQueryTerms(opts.queryText)
3558
+ queryTerms: buildQueryTerms(opts.queryText),
3559
+ // PLN-004 F1: resolve the credibility half-lives + floors ONCE here (never per
3560
+ // candidate) so credibilityFactor is a pure lookup on the ranking hot path.
3561
+ credibilityHalfLives: readCredibilityHalfLives(projectRoot),
3562
+ credibilityFloors: readCredibilityFloors(projectRoot)
3495
3563
  };
3496
3564
  const docTexts = /* @__PURE__ */ new Map();
3497
3565
  for (const item of rawItems) {
@@ -3620,14 +3688,21 @@ function documentTextForItem(description) {
3620
3688
  ...description.intent_clues,
3621
3689
  ...description.tech_stack,
3622
3690
  ...description.impact,
3623
- ...description.tags ?? []
3691
+ ...description.tags ?? [],
3692
+ // v2.2 glossary aliases (C-002): long-tail synonym terms feed the flat
3693
+ // vector-embedding document verbatim, same as the BM25F summary slot below.
3694
+ ...description.aliases ?? []
3624
3695
  ].join(" ");
3625
3696
  }
3626
3697
  function documentFieldsForItem(description) {
3627
3698
  return {
3628
3699
  title: tokenize2(description.summary),
3629
3700
  tags: tokenize2([...description.tags ?? [], ...description.tech_stack].join(" ")),
3630
- summary: tokenize2([description.must_read_if, ...description.intent_clues].join(" ")),
3701
+ summary: tokenize2(
3702
+ [description.must_read_if, ...description.intent_clues, ...description.aliases ?? []].join(
3703
+ " "
3704
+ )
3705
+ ),
3631
3706
  body: tokenize2(description.impact.join(" "))
3632
3707
  };
3633
3708
  }
@@ -3716,11 +3791,28 @@ function localityBoost(item, context) {
3716
3791
  function structuralScaleFor(context) {
3717
3792
  return context.fusion === "rrf" && context.queryTerms.length > 0 ? RRF_STRUCTURAL_SCALE : 1;
3718
3793
  }
3794
+ function credibilityFactor(item, context) {
3795
+ const halfLives = context.credibilityHalfLives;
3796
+ const floors = context.credibilityFloors;
3797
+ if (halfLives === void 0 || floors === void 0) return 1;
3798
+ const createdAtRaw = item.description?.created_at;
3799
+ if (typeof createdAtRaw !== "string" || createdAtRaw.length === 0) return 1;
3800
+ const createdMs = Date.parse(createdAtRaw);
3801
+ if (!Number.isFinite(createdMs)) return 1;
3802
+ const ageDays = (context.nowMs - createdMs) / (24 * 60 * 60 * 1e3);
3803
+ if (ageDays <= 0) return 1;
3804
+ const type = item.description?.knowledge_type;
3805
+ const halfLife = type !== void 0 ? halfLives[type] : halfLives.decisions;
3806
+ const factor = Math.pow(2, -ageDays / halfLife);
3807
+ const maturity = item.description?.maturity;
3808
+ const floor = maturity !== void 0 ? floors[maturity] : floors.draft;
3809
+ return Math.max(floor, Math.min(1, factor));
3810
+ }
3719
3811
  function scoreDescriptionItem(item, context) {
3720
3812
  const content = contentScore(item, context);
3721
3813
  const structural = salienceScore(item) + recencyBoost(item, context) + localityBoost(item, context);
3722
3814
  const proximity = proximityBoost(item, context, content);
3723
- return content + structuralScaleFor(context) * structural + proximity;
3815
+ return (content + structuralScaleFor(context) * structural + proximity) * credibilityFactor(item, context);
3724
3816
  }
3725
3817
  function scoreBreakdownForItem(item, context) {
3726
3818
  const hasQuery = context.queryTerms.length > 0;
@@ -3742,7 +3834,9 @@ function scoreBreakdownForItem(item, context) {
3742
3834
  const salience = salienceScore(item) * scale;
3743
3835
  const recency = recencyBoost(item, context) * scale;
3744
3836
  const locality = localityBoost(item, context) * scale;
3745
- const final = bm25 + vector + salience + recency + locality;
3837
+ const proximity = proximityBoost(item, context, bm25 + vector);
3838
+ const credibility = credibilityFactor(item, context);
3839
+ const final = (bm25 + vector + salience + recency + locality + proximity) * credibility;
3746
3840
  return {
3747
3841
  final,
3748
3842
  ...bm25 !== 0 ? { bm25 } : {},
@@ -3751,7 +3845,9 @@ function scoreBreakdownForItem(item, context) {
3751
3845
  ...vectorRank !== void 0 ? { vector_rank: vectorRank } : {},
3752
3846
  salience,
3753
3847
  recency,
3754
- locality
3848
+ locality,
3849
+ proximity,
3850
+ credibility
3755
3851
  };
3756
3852
  }
3757
3853
  function localityTier(relevancePath, targetPath) {
@@ -3848,7 +3944,7 @@ async function recall(projectRoot, input) {
3848
3944
  return {
3849
3945
  stable_id: c.stable_id,
3850
3946
  rank: index + 1,
3851
- description: c.description,
3947
+ description: slimDescription(c.description),
3852
3948
  ...readPath ? { read_path: readPath.path } : {},
3853
3949
  ...readPath?.store ? { store: readPath.store } : {},
3854
3950
  ...isAlwaysActive(c) ? { body_in_context: true } : {},
@@ -3868,6 +3964,14 @@ function isAlwaysActive(candidate) {
3868
3964
  const { relevance_scope, knowledge_type } = candidate.description;
3869
3965
  return (relevance_scope ?? "broad") !== "narrow" && ALWAYS_ACTIVE_TYPES2.has(knowledge_type ?? "");
3870
3966
  }
3967
+ function slimDescription(d) {
3968
+ return {
3969
+ summary: d.summary,
3970
+ must_read_if: d.must_read_if,
3971
+ intent_clues: d.intent_clues,
3972
+ ...d.knowledge_type !== void 0 ? { knowledge_type: d.knowledge_type } : {}
3973
+ };
3974
+ }
3871
3975
  function buildNextSteps(planResult, paths, candidateById, candidateLookup) {
3872
3976
  const nextSteps = [];
3873
3977
  const omitted = (planResult.dropped ?? []).filter((d) => d.reason === "retrieval_budget").length;
@@ -11513,7 +11617,7 @@ function createFabricServer(tracker) {
11513
11617
  const server = new McpServer(
11514
11618
  {
11515
11619
  name: "fabric-knowledge-server",
11516
- version: "2.3.0-rc.6"
11620
+ version: "2.3.0-rc.8"
11517
11621
  },
11518
11622
  {
11519
11623
  instructions: FABRIC_SERVER_INSTRUCTIONS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-server",
3
- "version": "2.3.0-rc.6",
3
+ "version": "2.3.0-rc.8",
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.6"
40
+ "@fenglimg/fabric-shared": "2.3.0-rc.8"
41
41
  },
42
42
  "optionalDependencies": {
43
43
  "fastembed": "^2.0.0"