@fenglimg/fabric-server 2.0.0-rc.10 → 2.0.0-rc.13

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.
@@ -88,7 +88,7 @@ import { agentsMetaNodeSchema, agentsMetaSchema as agentsMetaSchema2 } from "@fe
88
88
  var AgentsMetaFileMissingError = class extends IOFabricError {
89
89
  constructor(metaPath, opts) {
90
90
  super(`Fabric agents metadata file is missing: ${metaPath}`, {
91
- actionHint: opts?.actionHint ?? "Run `fab init` to scaffold the .fabric/agents.meta.json file"
91
+ actionHint: opts?.actionHint ?? "Run `fab install` to scaffold the .fabric/agents.meta.json file"
92
92
  });
93
93
  this.metaPath = metaPath;
94
94
  }
@@ -1381,12 +1381,14 @@ async function runDoctorReport(target) {
1381
1381
  const relevancePathsDrift = inspectRelevancePathsDrift(projectRoot);
1382
1382
  const narrowTooFew = inspectNarrowTooFew(projectRoot, lintNow);
1383
1383
  const sessionHintsStale = inspectSessionHintsStale(projectRoot, lintNow);
1384
+ const relevanceFieldsMissing = inspectRelevanceFieldsMissing(projectRoot);
1385
+ const skillMdYamlInvalid = inspectSkillMdYamlInvalid(projectRoot);
1384
1386
  const checks = [
1385
1387
  createBootstrapAnchorCheck(bootstrapAnchor),
1386
1388
  createKnowledgeDirMissingCheck(knowledgeDirMissing),
1387
1389
  createForensicCheck(forensic, framework.kind, entryPoints.length),
1388
1390
  // v2.0: removed `createInitContextCheck` — `.fabric/init-context.json`
1389
- // is owned by the AI-side client init skill, not by `fabric init` CLI.
1391
+ // is owned by the AI-side client init skill, not by `fabric install` CLI.
1390
1392
  // The file's absence is a legitimate post-init state when the skill has
1391
1393
  // not yet run, so flagging it as a doctor manual_error misrepresents
1392
1394
  // ownership.
@@ -1436,6 +1438,13 @@ async function runDoctorReport(target) {
1436
1438
  createNarrowTooFewCheck(narrowTooFew),
1437
1439
  // rc.6 TASK-021 (E3): session-hints cache hygiene (lint #27). Info kind.
1438
1440
  createSessionHintsStaleCheck(sessionHintsStale),
1441
+ // v2.0.0-rc.9 TASK-003 (A3): relevance fields back-fill (lint #28).
1442
+ // Info kind — applies to pending entries only; canonical entries get
1443
+ // the fields written verbatim by fab_review.approve/modify.
1444
+ createRelevanceFieldsMissingCheck(relevanceFieldsMissing),
1445
+ // rc.12 lint #29: skill_md_yaml_invalid. Warning kind — surfaces
1446
+ // SKILL.md frontmatter that Codex CLI silently drops at load.
1447
+ createSkillMdYamlInvalidCheck(skillMdYamlInvalid),
1439
1448
  createPreexistingRootFilesCheck(preexistingRootFiles)
1440
1449
  // v2.0 / rc.2: `createLegacyClientPathCheck` removed. The schema now
1441
1450
  // rejects retired clientPaths keys (windsurf/rooCode/geminiCLI) at Zod
@@ -1592,6 +1601,24 @@ async function runDoctorApplyLint(target) {
1592
1601
  for (const candidate of sessionHintsStale.candidates) {
1593
1602
  mutations.push(await applySessionHintsStaleCleanup(projectRoot, candidate));
1594
1603
  }
1604
+ const relevanceFieldsMissing = inspectRelevanceFieldsMissing(projectRoot);
1605
+ let relevanceTouchedCount = 0;
1606
+ for (const candidate of relevanceFieldsMissing.candidates) {
1607
+ const mutation = await applyRelevanceFieldsMissing(candidate);
1608
+ mutations.push(mutation);
1609
+ if (mutation.applied) {
1610
+ relevanceTouchedCount += 1;
1611
+ }
1612
+ }
1613
+ try {
1614
+ await appendEventLedgerEvent(projectRoot, {
1615
+ event_type: "relevance_migration_run",
1616
+ timestamp: new Date(now).toISOString(),
1617
+ scanned_count: relevanceFieldsMissing.scanned_count,
1618
+ touched_count: relevanceTouchedCount
1619
+ });
1620
+ } catch {
1621
+ }
1595
1622
  const meta = await inspectMeta(projectRoot);
1596
1623
  const indexDrift = inspectIndexDrift(projectRoot, meta);
1597
1624
  if (indexDrift.drifts.length > 0) {
@@ -2105,7 +2132,7 @@ function createBootstrapAnchorCheck(inspection) {
2105
2132
  "fixable_error",
2106
2133
  "bootstrap_anchor_missing",
2107
2134
  "Neither AGENTS.md nor CLAUDE.md exists at the repo root. Fabric requires a bootstrap anchor file at the project root.",
2108
- "Run `fabric init` to generate the AGENTS.md / CLAUDE.md bootstrap anchor at the repo root."
2135
+ "Run `fabric install` to generate the AGENTS.md / CLAUDE.md bootstrap anchor at the repo root."
2109
2136
  );
2110
2137
  }
2111
2138
  const present = [
@@ -2150,11 +2177,11 @@ function createForensicCheck(forensic, frameworkKind, entryPointCount) {
2150
2177
  "manual_error",
2151
2178
  "forensic_missing",
2152
2179
  `${forensic.error ?? ".fabric/forensic.json is missing."} Live scan detects ${frameworkKind} with ${entryPointCount} entry point${entryPointCount === 1 ? "" : "s"}.`,
2153
- "Run `fab init` to regenerate .fabric/forensic.json."
2180
+ "Run `fab install` to regenerate .fabric/forensic.json."
2154
2181
  );
2155
2182
  }
2156
2183
  if (!forensic.valid) {
2157
- return issueCheck("Scan evidence", "error", "manual_error", "forensic_invalid", forensic.error ?? ".fabric/forensic.json is invalid.", "Run `fab init` to regenerate .fabric/forensic.json.");
2184
+ return issueCheck("Scan evidence", "error", "manual_error", "forensic_invalid", forensic.error ?? ".fabric/forensic.json is invalid.", "Run `fab install` to regenerate .fabric/forensic.json.");
2158
2185
  }
2159
2186
  return okCheck("Scan evidence", `.fabric/forensic.json is valid for ${forensic.report?.framework.kind ?? "unknown"}.`);
2160
2187
  }
@@ -2234,8 +2261,8 @@ function createMcpConfigInWrongFileCheck(inspection) {
2234
2261
  "error",
2235
2262
  "fixable_error",
2236
2263
  "mcp_config_in_wrong_file",
2237
- `.claude/settings.json contains mcpServers.fabric \u2014 this file is for hooks/permissions only. Run --fix to remove it, then re-run fab init to write .mcp.json.`,
2238
- "Run `fab doctor --fix` to remove mcpServers.fabric from .claude/settings.json, then run `fab init` to write .mcp.json."
2264
+ `.claude/settings.json contains mcpServers.fabric \u2014 this file is for hooks/permissions only. Run --fix to remove it, then re-run fab install to write .mcp.json.`,
2265
+ "Run `fab doctor --fix` to remove mcpServers.fabric from .claude/settings.json, then run `fab install` to write .mcp.json."
2239
2266
  );
2240
2267
  }
2241
2268
  return okCheck("Claude MCP config location", "mcpServers.fabric is not in .claude/settings.json.");
@@ -3458,6 +3485,254 @@ function createRelevancePathsDriftCheck(inspection) {
3458
3485
  "Review whether the entry is still relevant \u2014 use `fab_review.modify` to refresh the anchors or `fab_review.reject` to archive."
3459
3486
  );
3460
3487
  }
3488
+ function inspectRelevanceFieldsMissing(projectRoot) {
3489
+ const candidates = [];
3490
+ let scannedCount = 0;
3491
+ const FM_PATTERN = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---/u;
3492
+ const teamRoot = join5(projectRoot, ".fabric", "knowledge", "pending");
3493
+ const personalRoot = join5(
3494
+ resolvePersonalRootForPending(),
3495
+ ".fabric",
3496
+ "knowledge",
3497
+ "pending"
3498
+ );
3499
+ for (const [root, displayPrefix] of [
3500
+ [teamRoot, ".fabric/knowledge/pending"],
3501
+ [personalRoot, "~/.fabric/knowledge/pending"]
3502
+ ]) {
3503
+ if (!existsSync4(root)) {
3504
+ continue;
3505
+ }
3506
+ let typeDirs = [];
3507
+ try {
3508
+ typeDirs = readdirSync(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
3509
+ } catch {
3510
+ continue;
3511
+ }
3512
+ for (const typeDir of typeDirs) {
3513
+ const dir = join5(root, typeDir);
3514
+ let entries;
3515
+ try {
3516
+ entries = readdirSync(dir, { withFileTypes: true });
3517
+ } catch {
3518
+ continue;
3519
+ }
3520
+ for (const entry of entries) {
3521
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
3522
+ continue;
3523
+ }
3524
+ const absPath = join5(dir, entry.name);
3525
+ let source;
3526
+ try {
3527
+ source = readFileSync(absPath, "utf8");
3528
+ } catch {
3529
+ continue;
3530
+ }
3531
+ const fm = FM_PATTERN.exec(source);
3532
+ if (fm === null) {
3533
+ continue;
3534
+ }
3535
+ scannedCount += 1;
3536
+ const block = fm[1];
3537
+ const missingScope = !RELEVANCE_SCOPE_LINE_PATTERN.test(block);
3538
+ const missingPaths = !RELEVANCE_PATHS_LINE_PATTERN.test(block);
3539
+ if (!missingScope && !missingPaths) {
3540
+ continue;
3541
+ }
3542
+ candidates.push({
3543
+ pending_path: posix.join(displayPrefix, typeDir, entry.name),
3544
+ pending_path_abs: absPath,
3545
+ missing_scope: missingScope,
3546
+ missing_paths: missingPaths
3547
+ });
3548
+ }
3549
+ }
3550
+ }
3551
+ candidates.sort((a, b) => a.pending_path.localeCompare(b.pending_path));
3552
+ return { candidates, scanned_count: scannedCount };
3553
+ }
3554
+ function appendRelevanceFieldsToFrontmatter(source, needsScope, needsPaths) {
3555
+ const FM_PATTERN = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---/u;
3556
+ const fm = FM_PATTERN.exec(source);
3557
+ if (fm === null) {
3558
+ return null;
3559
+ }
3560
+ const block = fm[1];
3561
+ const actuallyNeedsScope = needsScope && !RELEVANCE_SCOPE_LINE_PATTERN.test(block);
3562
+ const actuallyNeedsPaths = needsPaths && !RELEVANCE_PATHS_LINE_PATTERN.test(block);
3563
+ if (!actuallyNeedsScope && !actuallyNeedsPaths) {
3564
+ return source;
3565
+ }
3566
+ const additions = [];
3567
+ if (actuallyNeedsScope) {
3568
+ additions.push("relevance_scope: broad");
3569
+ }
3570
+ if (actuallyNeedsPaths) {
3571
+ additions.push("relevance_paths: []");
3572
+ }
3573
+ const trailing = block.endsWith("\n") ? "" : "\n";
3574
+ const replacedBlock = `${block}${trailing}${additions.join("\n")}`;
3575
+ const blockStart = source.indexOf(block);
3576
+ if (blockStart < 0) {
3577
+ return null;
3578
+ }
3579
+ return source.slice(0, blockStart) + replacedBlock + source.slice(blockStart + block.length);
3580
+ }
3581
+ async function applyRelevanceFieldsMissing(candidate) {
3582
+ const parts = [];
3583
+ if (candidate.missing_scope) parts.push("relevance_scope: broad");
3584
+ if (candidate.missing_paths) parts.push("relevance_paths: []");
3585
+ const detail = `back-filled: ${parts.join(", ")}`;
3586
+ try {
3587
+ const source = await readFile5(candidate.pending_path_abs, "utf8");
3588
+ const rewritten = appendRelevanceFieldsToFrontmatter(
3589
+ source,
3590
+ candidate.missing_scope,
3591
+ candidate.missing_paths
3592
+ );
3593
+ if (rewritten === null) {
3594
+ return {
3595
+ kind: "knowledge_relevance_fields_missing",
3596
+ path: candidate.pending_path,
3597
+ detail,
3598
+ applied: false,
3599
+ error: "frontmatter not parseable; cannot back-fill"
3600
+ };
3601
+ }
3602
+ if (rewritten === source) {
3603
+ return {
3604
+ kind: "knowledge_relevance_fields_missing",
3605
+ path: candidate.pending_path,
3606
+ detail,
3607
+ applied: false,
3608
+ error: "fields already present at write time (no diff)"
3609
+ };
3610
+ }
3611
+ await atomicWriteText3(candidate.pending_path_abs, rewritten);
3612
+ return {
3613
+ kind: "knowledge_relevance_fields_missing",
3614
+ path: candidate.pending_path,
3615
+ detail,
3616
+ applied: true
3617
+ };
3618
+ } catch (error) {
3619
+ return {
3620
+ kind: "knowledge_relevance_fields_missing",
3621
+ path: candidate.pending_path,
3622
+ detail,
3623
+ applied: false,
3624
+ error: truncateErrorMessage(error)
3625
+ };
3626
+ }
3627
+ }
3628
+ function createRelevanceFieldsMissingCheck(inspection) {
3629
+ if (inspection.candidates.length === 0) {
3630
+ return okCheck(
3631
+ "Knowledge relevance fields missing",
3632
+ "All pending entries declare both relevance_scope and relevance_paths."
3633
+ );
3634
+ }
3635
+ const first = inspection.candidates[0];
3636
+ const missingParts = [];
3637
+ if (first.missing_scope) missingParts.push("relevance_scope");
3638
+ if (first.missing_paths) missingParts.push("relevance_paths");
3639
+ const detail = `${first.pending_path} (missing: ${missingParts.join(", ")})`;
3640
+ return issueCheck(
3641
+ "Knowledge relevance fields missing",
3642
+ "ok",
3643
+ "info",
3644
+ "knowledge_relevance_fields_missing",
3645
+ `${inspection.candidates.length} pending entr${inspection.candidates.length === 1 ? "y is" : "ies are"} missing relevance_scope and/or relevance_paths in frontmatter. First: ${detail}.`,
3646
+ "Run `fab doctor --apply-lint` to back-fill the schema defaults (relevance_scope: broad, relevance_paths: [])."
3647
+ );
3648
+ }
3649
+ var SKILL_MD_FRONTMATTER_ROOTS = [".claude/skills", ".codex/skills"];
3650
+ var SKILL_FRONTMATTER_KEY_PATTERN = /^([A-Za-z_][A-Za-z0-9_-]*):[ \t]+(.+?)[ \t]*$/u;
3651
+ var SKILL_QUOTED_VALUE_LEADS = /* @__PURE__ */ new Set(['"', "'", "[", "{", ">", "|"]);
3652
+ function inspectSkillMdYamlInvalid(projectRoot) {
3653
+ const candidates = [];
3654
+ for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
3655
+ const rootAbs = join5(projectRoot, rootRel);
3656
+ if (!existsSync4(rootAbs)) continue;
3657
+ let dirEntries;
3658
+ try {
3659
+ dirEntries = readdirSync(rootAbs, { withFileTypes: true });
3660
+ } catch {
3661
+ continue;
3662
+ }
3663
+ for (const dirEntry of dirEntries) {
3664
+ if (!dirEntry.isDirectory()) continue;
3665
+ const skillFile = join5(rootAbs, dirEntry.name, "SKILL.md");
3666
+ if (!existsSync4(skillFile)) continue;
3667
+ let raw;
3668
+ try {
3669
+ raw = readFileSync(skillFile, "utf8");
3670
+ } catch {
3671
+ continue;
3672
+ }
3673
+ const frontmatter = extractSkillFrontmatterLines(raw);
3674
+ if (frontmatter === null) continue;
3675
+ for (const { line, lineNumber } of frontmatter) {
3676
+ const match = SKILL_FRONTMATTER_KEY_PATTERN.exec(line);
3677
+ if (!match) continue;
3678
+ const [, key, value] = match;
3679
+ if (value.length === 0) continue;
3680
+ if (SKILL_QUOTED_VALUE_LEADS.has(value[0])) continue;
3681
+ const colonSpaceIdx = value.indexOf(": ");
3682
+ const trailingColon = value.endsWith(":");
3683
+ if (colonSpaceIdx < 0 && !trailingColon) continue;
3684
+ const anchor = colonSpaceIdx >= 0 ? colonSpaceIdx : value.length - 1;
3685
+ const previewStart = Math.max(0, anchor - 25);
3686
+ const previewEnd = Math.min(value.length, anchor + 30);
3687
+ const preview = `${previewStart > 0 ? "\u2026" : ""}${value.slice(previewStart, previewEnd)}${previewEnd < value.length ? "\u2026" : ""}`;
3688
+ candidates.push({
3689
+ path: posix.join(rootRel, dirEntry.name, "SKILL.md"),
3690
+ line: lineNumber,
3691
+ key,
3692
+ preview
3693
+ });
3694
+ }
3695
+ }
3696
+ }
3697
+ candidates.sort((a, b) => {
3698
+ const byPath = a.path.localeCompare(b.path);
3699
+ return byPath !== 0 ? byPath : a.line - b.line;
3700
+ });
3701
+ return { candidates };
3702
+ }
3703
+ function extractSkillFrontmatterLines(raw) {
3704
+ const rawLines = raw.split(/\r?\n/u);
3705
+ if (rawLines.length < 2) return null;
3706
+ if (rawLines[0]?.trim() !== "---") return null;
3707
+ const out = [];
3708
+ for (let i = 1; i < rawLines.length; i++) {
3709
+ const line = rawLines[i];
3710
+ if (line.trim() === "---") {
3711
+ return out;
3712
+ }
3713
+ out.push({ line, lineNumber: i + 1 });
3714
+ }
3715
+ return null;
3716
+ }
3717
+ function createSkillMdYamlInvalidCheck(inspection) {
3718
+ if (inspection.candidates.length === 0) {
3719
+ return okCheck(
3720
+ "Skill markdown YAML",
3721
+ "All .claude/.codex SKILL.md frontmatter values parse as strict YAML."
3722
+ );
3723
+ }
3724
+ const first = inspection.candidates[0];
3725
+ const detail = `${first.path}:${first.line} (key \`${first.key}\` value contains an unquoted ': ' \u2014 preview: \`${first.preview}\`)`;
3726
+ const plural = inspection.candidates.length === 1;
3727
+ return issueCheck(
3728
+ "Skill markdown YAML",
3729
+ "warn",
3730
+ "warning",
3731
+ "skill_md_yaml_invalid",
3732
+ `${inspection.candidates.length} SKILL.md frontmatter ${plural ? "value contains" : "values contain"} an unquoted ': ' that strict YAML parsers reject (Claude Code tolerates it; Codex CLI drops the skill at load). First: ${detail}.`,
3733
+ 'Quote the value with double quotes (`description: "\u2026"`) or rewrite the inner `key: value` token to `key=value`.'
3734
+ );
3735
+ }
3461
3736
  function createNarrowTooFewCheck(inspection) {
3462
3737
  const { structural_flagged, telemetry_flagged } = inspection;
3463
3738
  if (!structural_flagged && !telemetry_flagged) {
@@ -14,7 +14,7 @@ import {
14
14
  readEventLedger,
15
15
  runDoctorReport,
16
16
  sha256
17
- } from "./chunk-AR2HV5JT.js";
17
+ } from "./chunk-3ZBYYPXQ.js";
18
18
 
19
19
  // src/http.ts
20
20
  import { randomUUID as randomUUID2 } from "crypto";
@@ -942,7 +942,7 @@ function toPosixPath(path) {
942
942
  function buildRecommendations(input) {
943
943
  const recommendations = [];
944
944
  if (!input.hasExistingFabric) {
945
- recommendations.push("L0: Run `fab init` to scaffold the .fabric/ knowledge layout (decisions, pitfalls, guidelines, models, processes).");
945
+ recommendations.push("L0: Run `fab install` to scaffold the .fabric/ knowledge layout (decisions, pitfalls, guidelines, models, processes).");
946
946
  }
947
947
  if (input.readmeQuality === "stub") {
948
948
  recommendations.push("L0: Expand README.md before promoting project facts into Fabric knowledge entries.");
package/dist/index.d.ts CHANGED
@@ -77,7 +77,7 @@ type DoctorFixReport = {
77
77
  message: string;
78
78
  report: DoctorReport;
79
79
  };
80
- type DoctorApplyLintMutationKind = "knowledge_orphan_demote_required" | "knowledge_stale_archive_required" | "knowledge_index_drift" | "knowledge_pending_auto_archive" | "knowledge_session_hints_stale_cleanup";
80
+ type DoctorApplyLintMutationKind = "knowledge_orphan_demote_required" | "knowledge_stale_archive_required" | "knowledge_index_drift" | "knowledge_pending_auto_archive" | "knowledge_session_hints_stale_cleanup" | "knowledge_relevance_fields_missing";
81
81
  type DoctorApplyLintMutation = {
82
82
  kind: DoctorApplyLintMutationKind;
83
83
  path: string;
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  sha256,
27
27
  stableStringify,
28
28
  writeKnowledgeMeta
29
- } from "./chunk-AR2HV5JT.js";
29
+ } from "./chunk-3ZBYYPXQ.js";
30
30
 
31
31
  // src/index.ts
32
32
  import { existsSync as existsSync4 } from "fs";
@@ -149,6 +149,21 @@ async function extractKnowledge(projectRoot, input) {
149
149
  };
150
150
  }
151
151
  const layer = input.layer ?? "team";
152
+ let relevanceScope = input.relevance_scope;
153
+ let relevancePaths = input.relevance_paths;
154
+ const shouldAutoDegrade = layer === "personal" && relevanceScope === "narrow";
155
+ if (shouldAutoDegrade) {
156
+ relevanceScope = "broad";
157
+ relevancePaths = [];
158
+ await emitEventBestEffort(projectRoot, {
159
+ event_type: "knowledge_scope_degraded",
160
+ stable_id: `pending:${idempotencyKey}`,
161
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
162
+ from_scope: "narrow",
163
+ to_scope: "broad",
164
+ reason: "personal-implies-broad"
165
+ });
166
+ }
152
167
  const baseDir = pendingBase(layer, projectRoot);
153
168
  const absolutePath = join2(baseDir, input.type, `${sanitizedSlug}.md`);
154
169
  const reportedPath = layer === "personal" ? `~/${relative(resolvePersonalRoot(), absolutePath)}` : relative(projectRoot, absolutePath);
@@ -190,7 +205,9 @@ async function extractKnowledge(projectRoot, input) {
190
205
  recentPaths: input.recent_paths,
191
206
  layer,
192
207
  proposedReason: input.proposed_reason,
193
- sessionContext: input.session_context
208
+ sessionContext: input.session_context,
209
+ relevanceScope,
210
+ relevancePaths
194
211
  });
195
212
  await atomicWriteText(absolutePath, fresh);
196
213
  await emitEventBestEffort(projectRoot, {
@@ -216,7 +233,7 @@ function sanitizeSlug(raw) {
216
233
  }
217
234
  function renderFreshEntry(args) {
218
235
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
219
- const frontmatter = [
236
+ const frontmatterLines = [
220
237
  "---",
221
238
  `type: ${args.type}`,
222
239
  "maturity: draft",
@@ -224,10 +241,20 @@ function renderFreshEntry(args) {
224
241
  `created_at: ${createdAt}`,
225
242
  `source_sessions: [${args.sourceSessions.map((s) => JSON.stringify(s)).join(", ")}]`,
226
243
  `proposed_reason: ${args.proposedReason}`,
227
- "tags: []",
244
+ "tags: []"
245
+ ];
246
+ if (args.relevanceScope !== void 0) {
247
+ frontmatterLines.push(`relevance_scope: ${args.relevanceScope}`);
248
+ }
249
+ if (args.relevancePaths !== void 0) {
250
+ const pathsBody = args.relevancePaths.map((p) => quoteRelevancePath(p)).join(", ");
251
+ frontmatterLines.push(`relevance_paths: [${pathsBody}]`);
252
+ }
253
+ frontmatterLines.push(
228
254
  `x-fabric-idempotency-key: ${args.idempotencyKey}`,
229
255
  "---"
230
- ].join("\n");
256
+ );
257
+ const frontmatter = frontmatterLines.join("\n");
231
258
  const reasonExplanation = PROPOSED_REASON_DESCRIPTIONS[args.proposedReason];
232
259
  const body = [
233
260
  "",
@@ -251,6 +278,9 @@ function renderFreshEntry(args) {
251
278
  return `${frontmatter}
252
279
  ${body}`;
253
280
  }
281
+ function quoteRelevancePath(value) {
282
+ return `"${value.replace(/"/g, '\\"')}"`;
283
+ }
254
284
  function renderEvidenceBlock(summary, recentPaths) {
255
285
  const pathLines = recentPaths.length === 0 ? "_(no recent paths reported)_" : recentPaths.map((p) => `- ${p}`).join("\n");
256
286
  return [
@@ -1860,7 +1890,7 @@ function formatPreexistingRootMessage(projectRoot) {
1860
1890
  function createFabricServer(tracker) {
1861
1891
  const server = new McpServer({
1862
1892
  name: "fabric-knowledge-server",
1863
- version: "2.0.0-rc.10"
1893
+ version: "2.0.0-rc.13"
1864
1894
  });
1865
1895
  registerPlanContext(server, tracker);
1866
1896
  registerKnowledgeSections(server, tracker);
@@ -1960,7 +1990,7 @@ function createShutdownHandler(deps) {
1960
1990
  };
1961
1991
  }
1962
1992
  async function startHttpServer(options) {
1963
- const { createFabricHttpApp } = await import("./http-7HJQGQSN.js");
1993
+ const { createFabricHttpApp } = await import("./http-B3UKRP5Y.js");
1964
1994
  const { port, projectRoot, host = "127.0.0.1", authToken } = options;
1965
1995
  const app = createFabricHttpApp({ projectRoot, host, authToken });
1966
1996
  return await new Promise((resolveServer, rejectServer) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-server",
3
- "version": "2.0.0-rc.10",
3
+ "version": "2.0.0-rc.13",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -13,7 +13,7 @@
13
13
  "express": "^5.2.1",
14
14
  "minimatch": "^10.0.1",
15
15
  "zod": "^3.25.0",
16
- "@fenglimg/fabric-shared": "2.0.0-rc.10"
16
+ "@fenglimg/fabric-shared": "2.0.0-rc.13"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/express": "^5.0.6",