@fenglimg/fabric-server 2.0.0-rc.27 → 2.0.0-rc.29

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.
@@ -84,7 +84,7 @@ import { readFile } from "fs/promises";
84
84
  import { join } from "path";
85
85
  import { agentsMetaSchema } from "@fenglimg/fabric-shared";
86
86
  import { IOFabricError } from "@fenglimg/fabric-shared/errors";
87
- import { agentsMetaNodeSchema, agentsMetaSchema as agentsMetaSchema2 } from "@fenglimg/fabric-shared";
87
+ import { agentsMetaSchema as agentsMetaSchema2 } from "@fenglimg/fabric-shared";
88
88
  var AgentsMetaFileMissingError = class extends IOFabricError {
89
89
  constructor(metaPath, opts) {
90
90
  super(`Fabric agents metadata file is missing: ${metaPath}`, {
@@ -1078,9 +1078,17 @@ function extractKnowledgeFieldsFromFrontmatter(frontmatter) {
1078
1078
  `);
1079
1079
  }
1080
1080
  }
1081
+ const SINGULAR_TO_PLURAL = {
1082
+ model: "models",
1083
+ decision: "decisions",
1084
+ guideline: "guidelines",
1085
+ pitfall: "pitfalls",
1086
+ process: "processes"
1087
+ };
1081
1088
  let knowledge_type;
1082
1089
  if (rawType !== void 0) {
1083
- const parsed = KnowledgeTypeSchema.safeParse(rawType);
1090
+ const normalized = SINGULAR_TO_PLURAL[rawType] ?? rawType;
1091
+ const parsed = KnowledgeTypeSchema.safeParse(normalized);
1084
1092
  if (parsed.success) {
1085
1093
  knowledge_type = parsed.data;
1086
1094
  } else {
@@ -1429,6 +1437,12 @@ async function ensureKnowledgeFresh(projectRoot, opts) {
1429
1437
  contextCache.invalidate("file_watch", projectRoot);
1430
1438
  }
1431
1439
  freshSyncCooldown.delete(projectRoot);
1440
+ if (opts?.autoHealOnDrift === true && events.length > 0) {
1441
+ try {
1442
+ await reconcileKnowledge(projectRoot, { trigger: "auto-heal-after-drift" });
1443
+ } catch {
1444
+ }
1445
+ }
1432
1446
  const status = warnings.length > 0 ? "errors" : "reconciled";
1433
1447
  return {
1434
1448
  status,
@@ -1620,11 +1634,11 @@ function checkLockOrThrow(projectRoot, opts) {
1620
1634
 
1621
1635
  // src/services/doctor.ts
1622
1636
  import { execFileSync } from "child_process";
1623
- import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync as statSync4 } from "fs";
1637
+ import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync as statSync4 } from "fs";
1624
1638
  import { access, mkdir as mkdir4, readFile as readFile5, rename, unlink, writeFile as writeFile2 } from "fs/promises";
1625
1639
  import { constants } from "fs";
1626
1640
  import { homedir as homedir3 } from "os";
1627
- import { isAbsolute as isAbsolute2, join as join6, posix, relative as nodeRelative, resolve as resolve3, sep as sep3 } from "path";
1641
+ import { isAbsolute as isAbsolute2, join as join7, posix, relative as nodeRelative, resolve as resolve3, sep as sep3 } from "path";
1628
1642
  import { minimatch } from "minimatch";
1629
1643
  import {
1630
1644
  agentsMetaSchema as agentsMetaSchema4,
@@ -1643,6 +1657,41 @@ import {
1643
1657
  resolveFabricLocale as resolveFabricLocale2
1644
1658
  } from "@fenglimg/fabric-shared";
1645
1659
  import { detectFramework } from "@fenglimg/fabric-shared/node";
1660
+ import {
1661
+ PAYLOAD_LIMIT_DEFAULT_HARD_BYTES,
1662
+ PAYLOAD_LIMIT_DEFAULT_WARN_BYTES
1663
+ } from "@fenglimg/fabric-shared/node/mcp-payload-guard";
1664
+
1665
+ // src/config-loader.ts
1666
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1667
+ import { join as join6 } from "path";
1668
+ import { selectionTokenTtlMsSchema } from "@fenglimg/fabric-shared";
1669
+ function readFabricConfig(projectRoot) {
1670
+ const configPath = join6(projectRoot, "fabric.config.json");
1671
+ if (!existsSync4(configPath)) {
1672
+ return {};
1673
+ }
1674
+ const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
1675
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1676
+ throw new Error(`Expected object in ${configPath}`);
1677
+ }
1678
+ return parsed;
1679
+ }
1680
+ function readPayloadLimits(projectRoot) {
1681
+ return readFabricConfig(projectRoot).mcpPayloadLimits;
1682
+ }
1683
+ function readSelectionTokenTtlMs(projectRoot) {
1684
+ try {
1685
+ const raw = readFabricConfig(projectRoot).selection_token_ttl_ms;
1686
+ if (raw === void 0) return void 0;
1687
+ const parsed = selectionTokenTtlMsSchema.safeParse(raw);
1688
+ return parsed.success ? parsed.data : void 0;
1689
+ } catch {
1690
+ return void 0;
1691
+ }
1692
+ }
1693
+
1694
+ // src/services/doctor.ts
1646
1695
  import { atomicWriteJson as atomicWriteJson2, atomicWriteText as atomicWriteText4 } from "@fenglimg/fabric-shared/node/atomic-write";
1647
1696
  var ORPHAN_DEMOTE_THRESHOLD_DAYS = {
1648
1697
  stable: 90,
@@ -1753,6 +1802,7 @@ async function runDoctorReport(target) {
1753
1802
  inspectL2ManagedBlockDrift(projectRoot)
1754
1803
  ]);
1755
1804
  const mcpConfigInWrongFile = inspectMcpConfigInWrongFile(projectRoot);
1805
+ const skillRefMirror = inspectSkillRefMirror(projectRoot);
1756
1806
  const metaManuallyDiverged = await inspectMetaManuallyDiverged(projectRoot);
1757
1807
  const knowledgeDirUnindexed = inspectKnowledgeDirUnindexed(projectRoot, meta);
1758
1808
  const knowledgeDirMissing = inspectKnowledgeDirMissing(projectRoot);
@@ -1815,6 +1865,11 @@ async function runDoctorReport(target) {
1815
1865
  // events.jsonl rows that fail Zod validation because of unknown
1816
1866
  // schema_version or event_type tokens. Previously silently dropped.
1817
1867
  createEventLedgerSchemaCompatCheck(t, eventLedger),
1868
+ // v2.0.0-rc.28 TASK-04 (audit §3.1 follow-up): SKILL ref/ mirror parity.
1869
+ // Detects hand-edits or partial install that breaks the byte-identical
1870
+ // contract between .claude/skills/<slug>/ref/ and .codex/skills/<slug>/
1871
+ // ref/. warning severity — fab install restores parity.
1872
+ createSkillRefMirrorCheck(t, skillRefMirror),
1818
1873
  createMcpConfigInWrongFileCheck(t, mcpConfigInWrongFile),
1819
1874
  createMetaManuallyDivergedCheck(t, metaManuallyDiverged),
1820
1875
  createKnowledgeDirUnindexedCheck(t, knowledgeDirUnindexed),
@@ -1908,11 +1963,27 @@ async function runDoctorReport(target) {
1908
1963
  warningCount: warnings.length,
1909
1964
  infoCount: infos.length,
1910
1965
  targetFiles: Object.fromEntries(
1911
- TARGET_FILE_PATHS.map((path2) => [path2, existsSync4(join6(projectRoot, path2))])
1912
- )
1966
+ TARGET_FILE_PATHS.map((path2) => [path2, existsSync5(join7(projectRoot, path2))])
1967
+ ),
1968
+ // v2.0.0-rc.29 TASK-008 (BUG-F2): resolve and surface payload thresholds.
1969
+ // Best-effort: a corrupt fabric.config.json should not fail doctor; on
1970
+ // any read/parse error fall back to library defaults with source="default".
1971
+ payload_limits: resolvePayloadLimits(projectRoot)
1913
1972
  }
1914
1973
  };
1915
1974
  }
1975
+ function resolvePayloadLimits(projectRoot) {
1976
+ let override;
1977
+ try {
1978
+ override = readPayloadLimits(projectRoot);
1979
+ } catch {
1980
+ override = void 0;
1981
+ }
1982
+ const warn = override?.warnBytes ?? PAYLOAD_LIMIT_DEFAULT_WARN_BYTES;
1983
+ const hard = override?.hardBytes ?? PAYLOAD_LIMIT_DEFAULT_HARD_BYTES;
1984
+ const source = override?.warnBytes !== void 0 || override?.hardBytes !== void 0 ? "config" : "default";
1985
+ return { warn_bytes: warn, hard_bytes: hard, source };
1986
+ }
1916
1987
  async function runDoctorFix(target) {
1917
1988
  const projectRoot = normalizeTarget(target);
1918
1989
  const before = await runDoctorReport(projectRoot);
@@ -1935,7 +2006,7 @@ async function runDoctorFix(target) {
1935
2006
  }
1936
2007
  }
1937
2008
  if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
1938
- const snapshotPath = join6(projectRoot, ".fabric", "AGENTS.md");
2009
+ const snapshotPath = join7(projectRoot, ".fabric", "AGENTS.md");
1939
2010
  await ensureParentDirectory(snapshotPath);
1940
2011
  await atomicWriteText4(snapshotPath, BOOTSTRAP_CANONICAL);
1941
2012
  fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
@@ -2009,7 +2080,7 @@ async function runDoctorFix(target) {
2009
2080
  if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
2010
2081
  const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
2011
2082
  if (lockInspection.present && !lockInspection.pidAlive) {
2012
- const lockFilePath = join6(projectRoot, ".fabric", ".serve.lock");
2083
+ const lockFilePath = join7(projectRoot, ".fabric", ".serve.lock");
2013
2084
  try {
2014
2085
  await unlink(lockFilePath);
2015
2086
  } catch (err) {
@@ -2166,7 +2237,7 @@ async function applyOrphanDemote(projectRoot, candidate, now) {
2166
2237
  };
2167
2238
  }
2168
2239
  const detail = `${candidate.maturity} -> ${next}`;
2169
- const absPath = join6(projectRoot, candidate.path);
2240
+ const absPath = join7(projectRoot, candidate.path);
2170
2241
  try {
2171
2242
  const source = await readFile5(absPath, "utf8");
2172
2243
  const rewritten = rewriteFrontmatterMaturity(source, next);
@@ -2233,11 +2304,11 @@ async function applyOrphanDemote(projectRoot, candidate, now) {
2233
2304
  }
2234
2305
  }
2235
2306
  async function applyStaleArchive(projectRoot, candidate, now) {
2236
- const sourceAbs = join6(projectRoot, candidate.path);
2237
- const destAbs = join6(projectRoot, candidate.archive_path);
2307
+ const sourceAbs = join7(projectRoot, candidate.path);
2308
+ const destAbs = join7(projectRoot, candidate.archive_path);
2238
2309
  const detail = `${candidate.path} -> ${candidate.archive_path}`;
2239
2310
  try {
2240
- await mkdir4(join6(destAbs, ".."), { recursive: true });
2311
+ await mkdir4(join7(destAbs, ".."), { recursive: true });
2241
2312
  try {
2242
2313
  await rename(sourceAbs, destAbs);
2243
2314
  } catch (renameError) {
@@ -2296,7 +2367,7 @@ async function applyStaleArchive(projectRoot, candidate, now) {
2296
2367
  async function applyPendingAutoArchive(projectRoot, candidate, now) {
2297
2368
  const detail = `${candidate.pending_path} -> ${candidate.archived_to}`;
2298
2369
  try {
2299
- await mkdir4(join6(candidate.archived_to_abs, ".."), { recursive: true });
2370
+ await mkdir4(join7(candidate.archived_to_abs, ".."), { recursive: true });
2300
2371
  let moved = false;
2301
2372
  if (candidate.layer === "team") {
2302
2373
  try {
@@ -2373,7 +2444,7 @@ function relativePosix(projectRoot, absolutePath) {
2373
2444
  }
2374
2445
  async function applySessionHintsStaleCleanup(projectRoot, candidate) {
2375
2446
  const detail = `deleted (${candidate.age_days}d old)`;
2376
- const absPath = join6(projectRoot, candidate.path);
2447
+ const absPath = join7(projectRoot, candidate.path);
2377
2448
  try {
2378
2449
  const { unlink: unlink2 } = await import("fs/promises");
2379
2450
  await unlink2(absPath);
@@ -2394,7 +2465,7 @@ async function applySessionHintsStaleCleanup(projectRoot, candidate) {
2394
2465
  }
2395
2466
  }
2396
2467
  async function applyIndexDriftFix(projectRoot, inspection) {
2397
- const metaPath = join6(projectRoot, ".fabric", "agents.meta.json");
2468
+ const metaPath = join7(projectRoot, ".fabric", "agents.meta.json");
2398
2469
  const detailParts = [];
2399
2470
  try {
2400
2471
  const meta = agentsMetaSchema4.parse(JSON.parse(await readFile5(metaPath, "utf8")));
@@ -2430,7 +2501,7 @@ function truncateErrorMessage(error) {
2430
2501
  return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
2431
2502
  }
2432
2503
  async function inspectForensic(projectRoot) {
2433
- const path2 = join6(projectRoot, ".fabric", "forensic.json");
2504
+ const path2 = join7(projectRoot, ".fabric", "forensic.json");
2434
2505
  try {
2435
2506
  const parsed = forensicReportSchema.parse(JSON.parse(await readFile5(path2, "utf8")));
2436
2507
  return { present: true, valid: true, report: parsed };
@@ -2442,12 +2513,12 @@ async function inspectForensic(projectRoot) {
2442
2513
  }
2443
2514
  }
2444
2515
  function inspectMcpConfigInWrongFile(projectRoot) {
2445
- const settingsPath = join6(projectRoot, ".claude", "settings.json");
2446
- if (!existsSync4(settingsPath)) {
2516
+ const settingsPath = join7(projectRoot, ".claude", "settings.json");
2517
+ if (!existsSync5(settingsPath)) {
2447
2518
  return { hasWrongEntry: false, settingsPath };
2448
2519
  }
2449
2520
  try {
2450
- const parsed = JSON.parse(readFileSync2(settingsPath, "utf8"));
2521
+ const parsed = JSON.parse(readFileSync3(settingsPath, "utf8"));
2451
2522
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2452
2523
  return { hasWrongEntry: false, settingsPath };
2453
2524
  }
@@ -2463,7 +2534,7 @@ function inspectMcpConfigInWrongFile(projectRoot) {
2463
2534
  }
2464
2535
  }
2465
2536
  async function inspectMeta(projectRoot) {
2466
- const metaPath = join6(projectRoot, ".fabric", "agents.meta.json");
2537
+ const metaPath = join7(projectRoot, ".fabric", "agents.meta.json");
2467
2538
  const built = await tryBuildRuleMeta(projectRoot);
2468
2539
  try {
2469
2540
  const raw = await readFile5(metaPath, "utf8");
@@ -2536,7 +2607,7 @@ function inspectContentRefs(projectRoot, meta) {
2536
2607
  if (isPersonalKnowledge) {
2537
2608
  continue;
2538
2609
  }
2539
- if (!existsSync4(join6(projectRoot, contentRef))) {
2610
+ if (!existsSync5(join7(projectRoot, contentRef))) {
2540
2611
  missing.push(contentRef);
2541
2612
  }
2542
2613
  }
@@ -2544,7 +2615,7 @@ function inspectContentRefs(projectRoot, meta) {
2544
2615
  }
2545
2616
  async function inspectEventLedger(projectRoot) {
2546
2617
  const path2 = getEventLedgerPath(projectRoot);
2547
- const exists = existsSync4(path2);
2618
+ const exists = existsSync5(path2);
2548
2619
  if (!exists) {
2549
2620
  return {
2550
2621
  exists: false,
@@ -2616,8 +2687,55 @@ async function inspectEventLedger(projectRoot) {
2616
2687
  };
2617
2688
  }
2618
2689
  }
2690
+ function inspectSkillRefMirror(projectRoot) {
2691
+ const skillSlugs = ["fabric-archive", "fabric-review", "fabric-import"];
2692
+ const driftedPaths = [];
2693
+ for (const slug of skillSlugs) {
2694
+ const claudeRef = join7(projectRoot, ".claude", "skills", slug, "ref");
2695
+ const codexRef = join7(projectRoot, ".codex", "skills", slug, "ref");
2696
+ let claudeFiles = null;
2697
+ let codexFiles = null;
2698
+ try {
2699
+ claudeFiles = readdirSync(claudeRef).filter((n) => n.endsWith(".md"));
2700
+ } catch {
2701
+ }
2702
+ try {
2703
+ codexFiles = readdirSync(codexRef).filter((n) => n.endsWith(".md"));
2704
+ } catch {
2705
+ }
2706
+ if (claudeFiles === null || codexFiles === null) continue;
2707
+ const claudeSet = new Set(claudeFiles);
2708
+ const codexSet = new Set(codexFiles);
2709
+ const union = /* @__PURE__ */ new Set([...claudeFiles, ...codexFiles]);
2710
+ for (const fname of union) {
2711
+ const inClaude = claudeSet.has(fname);
2712
+ const inCodex = codexSet.has(fname);
2713
+ if (!inClaude || !inCodex) {
2714
+ driftedPaths.push(`skills/${slug}/ref/${fname}`);
2715
+ continue;
2716
+ }
2717
+ let claudeBody;
2718
+ let codexBody;
2719
+ try {
2720
+ claudeBody = readFileSync3(join7(claudeRef, fname), "utf8");
2721
+ } catch {
2722
+ continue;
2723
+ }
2724
+ try {
2725
+ codexBody = readFileSync3(join7(codexRef, fname), "utf8");
2726
+ } catch {
2727
+ continue;
2728
+ }
2729
+ if (claudeBody !== codexBody) {
2730
+ driftedPaths.push(`skills/${slug}/ref/${fname}`);
2731
+ }
2732
+ }
2733
+ }
2734
+ if (driftedPaths.length === 0) return { status: "ok" };
2735
+ return { status: "drift", driftedPaths };
2736
+ }
2619
2737
  async function inspectKnowledgeTestIndex(projectRoot) {
2620
- const path2 = join6(projectRoot, ".fabric", ".cache", "knowledge-test.index.json");
2738
+ const path2 = join7(projectRoot, ".fabric", ".cache", "knowledge-test.index.json");
2621
2739
  const built = await tryBuildRuleMeta(projectRoot);
2622
2740
  try {
2623
2741
  const index = knowledgeTestIndexSchema2.parse(JSON.parse(await readFile5(path2, "utf8")));
@@ -2641,8 +2759,8 @@ async function inspectKnowledgeTestIndex(projectRoot) {
2641
2759
  }
2642
2760
  function inspectBootstrapAnchor(projectRoot) {
2643
2761
  return {
2644
- hasAgentsMd: existsSync4(join6(projectRoot, "AGENTS.md")),
2645
- hasClaudeMd: existsSync4(join6(projectRoot, "CLAUDE.md"))
2762
+ hasAgentsMd: existsSync5(join7(projectRoot, "AGENTS.md")),
2763
+ hasClaudeMd: existsSync5(join7(projectRoot, "CLAUDE.md"))
2646
2764
  };
2647
2765
  }
2648
2766
  var BOOTSTRAP_MARKER_MIGRATION_TARGETS = [
@@ -2654,8 +2772,8 @@ var BOOTSTRAP_MARKER_MIGRATION_TARGETS = [
2654
2772
  async function inspectBootstrapMarkerMigration(target) {
2655
2773
  const filesNeedingMigration = [];
2656
2774
  for (const rel of BOOTSTRAP_MARKER_MIGRATION_TARGETS) {
2657
- const abs = join6(target, rel);
2658
- if (!existsSync4(abs)) {
2775
+ const abs = join7(target, rel);
2776
+ if (!existsSync5(abs)) {
2659
2777
  continue;
2660
2778
  }
2661
2779
  let content;
@@ -2692,8 +2810,8 @@ function createBootstrapMarkerMigrationCheck(t, inspection) {
2692
2810
  );
2693
2811
  }
2694
2812
  async function inspectL1BootstrapSnapshotDrift(target) {
2695
- const abs = join6(target, ".fabric", "AGENTS.md");
2696
- if (!existsSync4(abs)) {
2813
+ const abs = join7(target, ".fabric", "AGENTS.md");
2814
+ if (!existsSync5(abs)) {
2697
2815
  return { status: "missing", canonical: BOOTSTRAP_CANONICAL, onDisk: null };
2698
2816
  }
2699
2817
  let onDisk;
@@ -2724,8 +2842,8 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
2724
2842
  );
2725
2843
  }
2726
2844
  async function inspectL2ManagedBlockDrift(target) {
2727
- const snapshotPath = join6(target, ".fabric", "AGENTS.md");
2728
- if (!existsSync4(snapshotPath)) {
2845
+ const snapshotPath = join7(target, ".fabric", "AGENTS.md");
2846
+ if (!existsSync5(snapshotPath)) {
2729
2847
  return { status: "ok", drifted: [] };
2730
2848
  }
2731
2849
  let snapshot;
@@ -2734,9 +2852,9 @@ async function inspectL2ManagedBlockDrift(target) {
2734
2852
  } catch {
2735
2853
  return { status: "ok", drifted: [] };
2736
2854
  }
2737
- const projectRulesPath = join6(target, ".fabric", "project-rules.md");
2855
+ const projectRulesPath = join7(target, ".fabric", "project-rules.md");
2738
2856
  let expectedBody = snapshot;
2739
- if (existsSync4(projectRulesPath)) {
2857
+ if (existsSync5(projectRulesPath)) {
2740
2858
  try {
2741
2859
  const projectRules = await readFile5(projectRulesPath, "utf8");
2742
2860
  expectedBody = `${snapshot}
@@ -2748,11 +2866,11 @@ ${projectRules}`;
2748
2866
  const drifted = [];
2749
2867
  let anyManagedBlockFound = false;
2750
2868
  const blockTargets = [
2751
- join6(target, "AGENTS.md"),
2752
- join6(target, ".cursor", "rules", "fabric-bootstrap.mdc")
2869
+ join7(target, "AGENTS.md"),
2870
+ join7(target, ".cursor", "rules", "fabric-bootstrap.mdc")
2753
2871
  ];
2754
2872
  for (const abs of blockTargets) {
2755
- if (!existsSync4(abs)) {
2873
+ if (!existsSync5(abs)) {
2756
2874
  continue;
2757
2875
  }
2758
2876
  let content;
@@ -2783,8 +2901,8 @@ ${projectRules}`;
2783
2901
  drifted.push({ path: abs, expected: expectedBody, actual: body });
2784
2902
  }
2785
2903
  }
2786
- const claudeMdPath = join6(target, "CLAUDE.md");
2787
- if (existsSync4(claudeMdPath)) {
2904
+ const claudeMdPath = join7(target, "CLAUDE.md");
2905
+ if (existsSync5(claudeMdPath)) {
2788
2906
  let claudeContent;
2789
2907
  try {
2790
2908
  claudeContent = await readFile5(claudeMdPath, "utf8");
@@ -2854,11 +2972,11 @@ function createBootstrapAnchorCheck(t, inspection) {
2854
2972
  );
2855
2973
  }
2856
2974
  function inspectKnowledgeDirMissing(projectRoot) {
2857
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
2975
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
2858
2976
  const missingSubdirs = [];
2859
2977
  for (const sub of KNOWLEDGE_SUBDIRS3) {
2860
- const path2 = join6(knowledgeRoot, sub);
2861
- if (!existsSync4(path2)) {
2978
+ const path2 = join7(knowledgeRoot, sub);
2979
+ if (!existsSync5(path2)) {
2862
2980
  missingSubdirs.push(`.fabric/knowledge/${sub}`);
2863
2981
  }
2864
2982
  }
@@ -2866,13 +2984,13 @@ function inspectKnowledgeDirMissing(projectRoot) {
2866
2984
  }
2867
2985
  function inspectBaselineFilenameFormat(projectRoot) {
2868
2986
  const offenders = [];
2869
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
2870
- if (!existsSync4(knowledgeRoot)) {
2987
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
2988
+ if (!existsSync5(knowledgeRoot)) {
2871
2989
  return { offenders };
2872
2990
  }
2873
2991
  for (const sub of KNOWLEDGE_CANONICAL_TYPE_DIRS) {
2874
- const dir = join6(knowledgeRoot, sub);
2875
- if (!existsSync4(dir)) {
2992
+ const dir = join7(knowledgeRoot, sub);
2993
+ if (!existsSync5(dir)) {
2876
2994
  continue;
2877
2995
  }
2878
2996
  let entries;
@@ -2889,10 +3007,10 @@ function inspectBaselineFilenameFormat(projectRoot) {
2889
3007
  if (BASELINE_ID_PREFIXED_FILENAME_PATTERN.test(entryName)) {
2890
3008
  continue;
2891
3009
  }
2892
- const abs = join6(dir, entryName);
3010
+ const abs = join7(dir, entryName);
2893
3011
  let source;
2894
3012
  try {
2895
- source = readFileSync2(abs, "utf8");
3013
+ source = readFileSync3(abs, "utf8");
2896
3014
  } catch {
2897
3015
  continue;
2898
3016
  }
@@ -3192,6 +3310,25 @@ function createEventLedgerSchemaCompatCheck(t, ledger) {
3192
3310
  t("doctor.check.event_ledger_schema_compat.remediation")
3193
3311
  );
3194
3312
  }
3313
+ function createSkillRefMirrorCheck(t, inspection) {
3314
+ if (inspection.status === "ok") {
3315
+ return okCheck(
3316
+ t("doctor.check.skill_ref_mirror.name"),
3317
+ t("doctor.check.skill_ref_mirror.ok")
3318
+ );
3319
+ }
3320
+ return issueCheck(
3321
+ t("doctor.check.skill_ref_mirror.name"),
3322
+ "warn",
3323
+ "warning",
3324
+ "skill_ref_mirror_drift",
3325
+ t("doctor.check.skill_ref_mirror.message", {
3326
+ count: String(inspection.driftedPaths.length),
3327
+ list: inspection.driftedPaths.join(", ")
3328
+ }),
3329
+ t("doctor.check.skill_ref_mirror.remediation")
3330
+ );
3331
+ }
3195
3332
  function createEventLedgerPartialWriteCheck(t, ledger) {
3196
3333
  if (!ledger.exists || !ledger.writable) {
3197
3334
  return okCheck(
@@ -3247,8 +3384,8 @@ function findIssue(issues, code) {
3247
3384
  };
3248
3385
  }
3249
3386
  async function inspectMetaManuallyDiverged(projectRoot) {
3250
- const metaPath = join6(projectRoot, ".fabric", "agents.meta.json");
3251
- if (!existsSync4(metaPath)) {
3387
+ const metaPath = join7(projectRoot, ".fabric", "agents.meta.json");
3388
+ if (!existsSync5(metaPath)) {
3252
3389
  return { extraMetaEntries: [], hashMismatchEntries: [], readable: false };
3253
3390
  }
3254
3391
  let meta;
@@ -3267,13 +3404,13 @@ async function inspectMetaManuallyDiverged(projectRoot) {
3267
3404
  const hashMismatchEntries = [];
3268
3405
  for (const node of Object.values(meta.nodes)) {
3269
3406
  const contentRef = node.content_ref ?? node.file;
3270
- const absPath = join6(projectRoot, contentRef);
3271
- if (!existsSync4(absPath)) {
3407
+ const absPath = join7(projectRoot, contentRef);
3408
+ if (!existsSync5(absPath)) {
3272
3409
  extraMetaEntries.push(contentRef);
3273
3410
  continue;
3274
3411
  }
3275
3412
  try {
3276
- const content = readFileSync2(absPath, "utf8");
3413
+ const content = readFileSync3(absPath, "utf8");
3277
3414
  const diskHash = sha256(content);
3278
3415
  if (node.hash !== "" && node.hash !== diskHash) {
3279
3416
  hashMismatchEntries.push(contentRef);
@@ -3286,7 +3423,7 @@ async function inspectMetaManuallyDiverged(projectRoot) {
3286
3423
  }
3287
3424
  function inspectKnowledgeDirUnindexed(projectRoot, meta) {
3288
3425
  const physicalMdFiles = /* @__PURE__ */ new Set();
3289
- collectMdFilesUnder(physicalMdFiles, projectRoot, join6(projectRoot, ".fabric", "knowledge"), ".fabric/knowledge");
3426
+ collectMdFilesUnder(physicalMdFiles, projectRoot, join7(projectRoot, ".fabric", "knowledge"), ".fabric/knowledge");
3290
3427
  if (physicalMdFiles.size === 0) {
3291
3428
  return { unindexedFiles: [] };
3292
3429
  }
@@ -3301,7 +3438,7 @@ function inspectKnowledgeDirUnindexed(projectRoot, meta) {
3301
3438
  return { unindexedFiles };
3302
3439
  }
3303
3440
  function collectMdFilesUnder(out, projectRoot, rootDir, relPrefix) {
3304
- if (!existsSync4(rootDir)) {
3441
+ if (!existsSync5(rootDir)) {
3305
3442
  return;
3306
3443
  }
3307
3444
  const stack = [rootDir];
@@ -3311,7 +3448,7 @@ function collectMdFilesUnder(out, projectRoot, rootDir, relPrefix) {
3311
3448
  continue;
3312
3449
  }
3313
3450
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
3314
- const abs = join6(dir, entry.name);
3451
+ const abs = join7(dir, entry.name);
3315
3452
  if (entry.isDirectory()) {
3316
3453
  if (entry.name !== "pending" && entry.name !== "archive") {
3317
3454
  stack.push(abs);
@@ -3344,8 +3481,8 @@ function createKnowledgeDirUnindexedCheck(t, inspection) {
3344
3481
  }
3345
3482
  async function inspectStableIdCollisions(projectRoot) {
3346
3483
  const found = [];
3347
- const knowledgeDir = join6(projectRoot, ".fabric", "knowledge");
3348
- if (existsSync4(knowledgeDir)) {
3484
+ const knowledgeDir = join7(projectRoot, ".fabric", "knowledge");
3485
+ if (existsSync5(knowledgeDir)) {
3349
3486
  const stack = [knowledgeDir];
3350
3487
  while (stack.length > 0) {
3351
3488
  const dir = stack.pop();
@@ -3353,7 +3490,7 @@ async function inspectStableIdCollisions(projectRoot) {
3353
3490
  continue;
3354
3491
  }
3355
3492
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
3356
- const abs = join6(dir, entry.name);
3493
+ const abs = join7(dir, entry.name);
3357
3494
  if (entry.isDirectory()) {
3358
3495
  stack.push(abs);
3359
3496
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -3418,11 +3555,11 @@ function inspectCounterDesync(meta) {
3418
3555
  }
3419
3556
  const layer = parsed.layer === "personal" ? "KP" : "KT";
3420
3557
  const typeCode = [
3421
- ["model", "MOD"],
3422
- ["decision", "DEC"],
3423
- ["guideline", "GLD"],
3424
- ["pitfall", "PIT"],
3425
- ["process", "PRO"]
3558
+ ["models", "MOD"],
3559
+ ["decisions", "DEC"],
3560
+ ["guidelines", "GLD"],
3561
+ ["pitfalls", "PIT"],
3562
+ ["processes", "PRO"]
3426
3563
  ].find(([t]) => t === parsed.type)?.[1];
3427
3564
  if (typeCode === void 0) {
3428
3565
  continue;
@@ -3532,18 +3669,18 @@ function createMetaManuallyDivergedCheck(t, inspection) {
3532
3669
  }
3533
3670
  function inspectPreexistingRootFiles(projectRoot) {
3534
3671
  const candidates = ["CLAUDE.md", "AGENTS.md"];
3535
- const detected = candidates.filter((name) => existsSync4(join6(projectRoot, name)));
3672
+ const detected = candidates.filter((name) => existsSync5(join7(projectRoot, name)));
3536
3673
  return { detected };
3537
3674
  }
3538
3675
  async function inspectFilesystemEditFallback(projectRoot) {
3539
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
3540
- if (!existsSync4(knowledgeRoot)) {
3676
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
3677
+ if (!existsSync5(knowledgeRoot)) {
3541
3678
  return { synthesized: 0, synthesizedStableIds: [] };
3542
3679
  }
3543
3680
  const canonicalIds = /* @__PURE__ */ new Set();
3544
3681
  for (const typeDir of KNOWLEDGE_CANONICAL_TYPE_DIRS) {
3545
- const dir = join6(knowledgeRoot, typeDir);
3546
- if (!existsSync4(dir)) {
3682
+ const dir = join7(knowledgeRoot, typeDir);
3683
+ if (!existsSync5(dir)) {
3547
3684
  continue;
3548
3685
  }
3549
3686
  let entries;
@@ -3583,17 +3720,40 @@ async function inspectFilesystemEditFallback(projectRoot) {
3583
3720
  }
3584
3721
  orphanIds.sort();
3585
3722
  for (const stable_id of orphanIds) {
3586
- await appendEventLedgerEvent(projectRoot, {
3587
- event_type: "knowledge_promoted",
3588
- stable_id,
3589
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3590
- reason: SYNTHESIZED_PROMOTED_REASON,
3591
- correlation_id: "doctor-synthesized",
3592
- session_id: "doctor-synthesized"
3593
- });
3723
+ await emitSynthesizedPromotionTriplet(projectRoot, stable_id);
3594
3724
  }
3595
3725
  return { synthesized: orphanIds.length, synthesizedStableIds: orphanIds };
3596
3726
  }
3727
+ async function emitSynthesizedPromotionTriplet(projectRoot, stable_id) {
3728
+ const baseTs = Date.now();
3729
+ await appendEventLedgerEvent(projectRoot, {
3730
+ event_type: "knowledge_proposed",
3731
+ stable_id,
3732
+ timestamp: new Date(baseTs).toISOString(),
3733
+ reason: SYNTHESIZED_PROMOTED_REASON,
3734
+ correlation_id: "doctor-synthesized",
3735
+ session_id: "doctor-synthesized",
3736
+ ts: baseTs
3737
+ });
3738
+ await appendEventLedgerEvent(projectRoot, {
3739
+ event_type: "knowledge_promote_started",
3740
+ stable_id,
3741
+ timestamp: new Date(baseTs + 1).toISOString(),
3742
+ reason: SYNTHESIZED_PROMOTED_REASON,
3743
+ correlation_id: "doctor-synthesized",
3744
+ session_id: "doctor-synthesized",
3745
+ ts: baseTs + 1
3746
+ });
3747
+ await appendEventLedgerEvent(projectRoot, {
3748
+ event_type: "knowledge_promoted",
3749
+ stable_id,
3750
+ timestamp: new Date(baseTs + 2).toISOString(),
3751
+ reason: SYNTHESIZED_PROMOTED_REASON,
3752
+ correlation_id: "doctor-synthesized",
3753
+ session_id: "doctor-synthesized",
3754
+ ts: baseTs + 2
3755
+ });
3756
+ }
3597
3757
  function createFilesystemEditFallbackCheck(t, inspection) {
3598
3758
  if (inspection.synthesized === 0) {
3599
3759
  return okCheck(
@@ -3752,13 +3912,13 @@ function extractKnowledgeFrontmatterCreatedAt(source) {
3752
3912
  return Number.isFinite(parsed) ? parsed : null;
3753
3913
  }
3754
3914
  function* iterateCanonicalEntries(projectRoot, lastActiveIndex) {
3755
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
3756
- if (!existsSync4(knowledgeRoot)) {
3915
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
3916
+ if (!existsSync5(knowledgeRoot)) {
3757
3917
  return;
3758
3918
  }
3759
3919
  for (const typeDir of KNOWLEDGE_CANONICAL_TYPE_DIRS) {
3760
- const dir = join6(knowledgeRoot, typeDir);
3761
- if (!existsSync4(dir)) {
3920
+ const dir = join7(knowledgeRoot, typeDir);
3921
+ if (!existsSync5(dir)) {
3762
3922
  continue;
3763
3923
  }
3764
3924
  let entries;
@@ -3776,10 +3936,10 @@ function* iterateCanonicalEntries(projectRoot, lastActiveIndex) {
3776
3936
  continue;
3777
3937
  }
3778
3938
  const stableId = match[1];
3779
- const absPath = join6(dir, entry.name);
3939
+ const absPath = join7(dir, entry.name);
3780
3940
  let source;
3781
3941
  try {
3782
- source = readFileSync2(absPath, "utf8");
3942
+ source = readFileSync3(absPath, "utf8");
3783
3943
  } catch {
3784
3944
  continue;
3785
3945
  }
@@ -3852,13 +4012,13 @@ async function inspectStaleArchive(projectRoot, now) {
3852
4012
  return { candidates };
3853
4013
  }
3854
4014
  function* iteratePendingFiles(projectRoot, now) {
3855
- const teamRoot = join6(projectRoot, ".fabric", "knowledge", "pending");
3856
- const personalRoot = join6(resolvePersonalRootForPending(), ".fabric", "knowledge", "pending");
4015
+ const teamRoot = join7(projectRoot, ".fabric", "knowledge", "pending");
4016
+ const personalRoot = join7(resolvePersonalRootForPending(), ".fabric", "knowledge", "pending");
3857
4017
  for (const [layer, root, displayPrefix] of [
3858
4018
  ["team", teamRoot, ".fabric/knowledge/pending"],
3859
4019
  ["personal", personalRoot, "~/.fabric/knowledge/pending"]
3860
4020
  ]) {
3861
- if (!existsSync4(root)) {
4021
+ if (!existsSync5(root)) {
3862
4022
  continue;
3863
4023
  }
3864
4024
  let typeDirs = [];
@@ -3868,7 +4028,7 @@ function* iteratePendingFiles(projectRoot, now) {
3868
4028
  continue;
3869
4029
  }
3870
4030
  for (const typeDir of typeDirs) {
3871
- const dir = join6(root, typeDir);
4031
+ const dir = join7(root, typeDir);
3872
4032
  let entries;
3873
4033
  try {
3874
4034
  entries = readdirSync(dir, { withFileTypes: true });
@@ -3879,10 +4039,10 @@ function* iteratePendingFiles(projectRoot, now) {
3879
4039
  if (!entry.isFile() || !entry.name.endsWith(".md")) {
3880
4040
  continue;
3881
4041
  }
3882
- const absPath = join6(dir, entry.name);
4042
+ const absPath = join7(dir, entry.name);
3883
4043
  let source = "";
3884
4044
  try {
3885
- source = readFileSync2(absPath, "utf8");
4045
+ source = readFileSync3(absPath, "utf8");
3886
4046
  } catch {
3887
4047
  continue;
3888
4048
  }
@@ -3954,7 +4114,7 @@ function inspectPendingAutoArchive(projectRoot, now) {
3954
4114
  pending_path: visit.pending_path,
3955
4115
  pending_path_abs: visit.pending_path_abs,
3956
4116
  archived_to: archivedToRel,
3957
- archived_to_abs: join6(projectRoot, archivedToRel),
4117
+ archived_to_abs: join7(projectRoot, archivedToRel),
3958
4118
  age_days: visit.age_days
3959
4119
  });
3960
4120
  } else {
@@ -3963,7 +4123,7 @@ function inspectPendingAutoArchive(projectRoot, now) {
3963
4123
  visit.type,
3964
4124
  visit.filename
3965
4125
  );
3966
- const archivedToAbs = join6(
4126
+ const archivedToAbs = join7(
3967
4127
  resolvePersonalRootForPending(),
3968
4128
  ".fabric",
3969
4129
  ".archive",
@@ -3987,12 +4147,12 @@ function inspectPendingAutoArchive(projectRoot, now) {
3987
4147
  }
3988
4148
  function inspectUnderseeded(projectRoot) {
3989
4149
  const threshold = readUnderseedThresholdFromConfig(projectRoot);
3990
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
4150
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
3991
4151
  let nodeCount = 0;
3992
- if (existsSync4(knowledgeRoot)) {
4152
+ if (existsSync5(knowledgeRoot)) {
3993
4153
  for (const typeDir of KNOWLEDGE_CANONICAL_TYPE_DIRS) {
3994
- const dir = join6(knowledgeRoot, typeDir);
3995
- if (!existsSync4(dir)) continue;
4154
+ const dir = join7(knowledgeRoot, typeDir);
4155
+ if (!existsSync5(dir)) continue;
3996
4156
  let entries;
3997
4157
  try {
3998
4158
  entries = readdirSync(dir, { withFileTypes: true });
@@ -4013,8 +4173,8 @@ function inspectUnderseeded(projectRoot) {
4013
4173
  };
4014
4174
  }
4015
4175
  function inspectSessionHintsStale(projectRoot, now) {
4016
- const cacheDir = join6(projectRoot, ".fabric", ".cache");
4017
- if (!existsSync4(cacheDir)) {
4176
+ const cacheDir = join7(projectRoot, ".fabric", ".cache");
4177
+ if (!existsSync5(cacheDir)) {
4018
4178
  return { candidates: [] };
4019
4179
  }
4020
4180
  let entries;
@@ -4028,7 +4188,7 @@ function inspectSessionHintsStale(projectRoot, now) {
4028
4188
  if (!entry.isFile()) continue;
4029
4189
  if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
4030
4190
  if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
4031
- const absPath = join6(cacheDir, entry.name);
4191
+ const absPath = join7(cacheDir, entry.name);
4032
4192
  let mtimeMs = 0;
4033
4193
  try {
4034
4194
  mtimeMs = statSync4(absPath).mtimeMs;
@@ -4072,11 +4232,11 @@ function inspectNarrowTooFew(projectRoot, now) {
4072
4232
  const structuralFlagged = total >= NARROW_MIN_TOTAL && narrowRatio < NARROW_RATIO_THRESHOLD;
4073
4233
  const windowStartMs = now - SILENCE_WINDOW_DAYS * MS_PER_DAY;
4074
4234
  const editFires = readCounterTimestamps(
4075
- join6(projectRoot, EDIT_COUNTER_FILE_REL),
4235
+ join7(projectRoot, EDIT_COUNTER_FILE_REL),
4076
4236
  windowStartMs
4077
4237
  );
4078
4238
  const silenceFires = readCounterTimestamps(
4079
- join6(projectRoot, HINT_SILENCE_COUNTER_FILE_REL),
4239
+ join7(projectRoot, HINT_SILENCE_COUNTER_FILE_REL),
4080
4240
  windowStartMs
4081
4241
  );
4082
4242
  const telemetrySkipped = editFires === 0;
@@ -4095,10 +4255,10 @@ function inspectNarrowTooFew(projectRoot, now) {
4095
4255
  };
4096
4256
  }
4097
4257
  function readCounterTimestamps(absPath, windowStartMs) {
4098
- if (!existsSync4(absPath)) return 0;
4258
+ if (!existsSync5(absPath)) return 0;
4099
4259
  let raw;
4100
4260
  try {
4101
- raw = readFileSync2(absPath, "utf8");
4261
+ raw = readFileSync3(absPath, "utf8");
4102
4262
  } catch {
4103
4263
  return 0;
4104
4264
  }
@@ -4114,10 +4274,10 @@ function readCounterTimestamps(absPath, windowStartMs) {
4114
4274
  return count;
4115
4275
  }
4116
4276
  function readUnderseedThresholdFromConfig(projectRoot) {
4117
- const configPath = join6(projectRoot, ".fabric", "fabric-config.json");
4118
- if (!existsSync4(configPath)) return DEFAULT_UNDERSEED_NODE_THRESHOLD;
4277
+ const configPath = join7(projectRoot, ".fabric", "fabric-config.json");
4278
+ if (!existsSync5(configPath)) return DEFAULT_UNDERSEED_NODE_THRESHOLD;
4119
4279
  try {
4120
- const raw = readFileSync2(configPath, "utf8");
4280
+ const raw = readFileSync3(configPath, "utf8");
4121
4281
  const parsed = JSON.parse(raw);
4122
4282
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4123
4283
  const v = parsed.underseed_node_threshold;
@@ -4311,11 +4471,11 @@ function extractKnowledgeFrontmatterRelevancePaths(source) {
4311
4471
  }
4312
4472
  function* iterateRelevanceFrontmatter(projectRoot) {
4313
4473
  for (const visit of iterateCanonicalFilenames(projectRoot)) {
4314
- const layerRoot = visit.layer === "team" ? join6(projectRoot, ".fabric", "knowledge") : resolvePersonalKnowledgeRoot();
4315
- const absPath = join6(layerRoot, visit.type, visit.filename);
4474
+ const layerRoot = visit.layer === "team" ? join7(projectRoot, ".fabric", "knowledge") : resolvePersonalKnowledgeRoot();
4475
+ const absPath = join7(layerRoot, visit.type, visit.filename);
4316
4476
  let source;
4317
4477
  try {
4318
- source = readFileSync2(absPath, "utf8");
4478
+ source = readFileSync3(absPath, "utf8");
4319
4479
  } catch {
4320
4480
  continue;
4321
4481
  }
@@ -4377,7 +4537,7 @@ function inspectRelevancePathsDangling(projectRoot) {
4377
4537
  return { entries };
4378
4538
  }
4379
4539
  function collectWorkspacePathsForGlobMatch(projectRoot) {
4380
- if (!existsSync4(projectRoot)) {
4540
+ if (!existsSync5(projectRoot)) {
4381
4541
  return [];
4382
4542
  }
4383
4543
  let rootStat;
@@ -4401,7 +4561,7 @@ function collectWorkspacePathsForGlobMatch(projectRoot) {
4401
4561
  continue;
4402
4562
  }
4403
4563
  for (const entry of entries) {
4404
- const abs = join6(current, entry.name);
4564
+ const abs = join7(current, entry.name);
4405
4565
  const rel = normalizePath(abs.slice(projectRoot.length + 1));
4406
4566
  if (rel.length === 0) continue;
4407
4567
  if (entry.isDirectory()) {
@@ -4558,8 +4718,8 @@ function inspectRelevanceFieldsMissing(projectRoot) {
4558
4718
  const candidates = [];
4559
4719
  let scannedCount = 0;
4560
4720
  const FM_PATTERN = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---/u;
4561
- const teamRoot = join6(projectRoot, ".fabric", "knowledge", "pending");
4562
- const personalRoot = join6(
4721
+ const teamRoot = join7(projectRoot, ".fabric", "knowledge", "pending");
4722
+ const personalRoot = join7(
4563
4723
  resolvePersonalRootForPending(),
4564
4724
  ".fabric",
4565
4725
  "knowledge",
@@ -4569,7 +4729,7 @@ function inspectRelevanceFieldsMissing(projectRoot) {
4569
4729
  [teamRoot, ".fabric/knowledge/pending"],
4570
4730
  [personalRoot, "~/.fabric/knowledge/pending"]
4571
4731
  ]) {
4572
- if (!existsSync4(root)) {
4732
+ if (!existsSync5(root)) {
4573
4733
  continue;
4574
4734
  }
4575
4735
  let typeDirs = [];
@@ -4579,7 +4739,7 @@ function inspectRelevanceFieldsMissing(projectRoot) {
4579
4739
  continue;
4580
4740
  }
4581
4741
  for (const typeDir of typeDirs) {
4582
- const dir = join6(root, typeDir);
4742
+ const dir = join7(root, typeDir);
4583
4743
  let entries;
4584
4744
  try {
4585
4745
  entries = readdirSync(dir, { withFileTypes: true });
@@ -4590,10 +4750,10 @@ function inspectRelevanceFieldsMissing(projectRoot) {
4590
4750
  if (!entry.isFile() || !entry.name.endsWith(".md")) {
4591
4751
  continue;
4592
4752
  }
4593
- const absPath = join6(dir, entry.name);
4753
+ const absPath = join7(dir, entry.name);
4594
4754
  let source;
4595
4755
  try {
4596
- source = readFileSync2(absPath, "utf8");
4756
+ source = readFileSync3(absPath, "utf8");
4597
4757
  } catch {
4598
4758
  continue;
4599
4759
  }
@@ -4725,8 +4885,8 @@ var SKILL_QUOTED_VALUE_LEADS = /* @__PURE__ */ new Set(['"', "'", "[", "{", ">",
4725
4885
  function inspectSkillMdYamlInvalid(projectRoot) {
4726
4886
  const candidates = [];
4727
4887
  for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
4728
- const rootAbs = join6(projectRoot, rootRel);
4729
- if (!existsSync4(rootAbs)) continue;
4888
+ const rootAbs = join7(projectRoot, rootRel);
4889
+ if (!existsSync5(rootAbs)) continue;
4730
4890
  let dirEntries;
4731
4891
  try {
4732
4892
  dirEntries = readdirSync(rootAbs, { withFileTypes: true });
@@ -4735,11 +4895,11 @@ function inspectSkillMdYamlInvalid(projectRoot) {
4735
4895
  }
4736
4896
  for (const dirEntry of dirEntries) {
4737
4897
  if (!dirEntry.isDirectory()) continue;
4738
- const skillFile = join6(rootAbs, dirEntry.name, "SKILL.md");
4739
- if (!existsSync4(skillFile)) continue;
4898
+ const skillFile = join7(rootAbs, dirEntry.name, "SKILL.md");
4899
+ if (!existsSync5(skillFile)) continue;
4740
4900
  let raw;
4741
4901
  try {
4742
- raw = readFileSync2(skillFile, "utf8");
4902
+ raw = readFileSync3(skillFile, "utf8");
4743
4903
  } catch {
4744
4904
  continue;
4745
4905
  }
@@ -4821,11 +4981,11 @@ function inspectOnboardCoverage(projectRoot) {
4821
4981
  for (const slot of ONBOARD_SLOT_NAMES) {
4822
4982
  filled[slot] = [];
4823
4983
  }
4824
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
4825
- if (existsSync4(knowledgeRoot)) {
4984
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
4985
+ if (existsSync5(knowledgeRoot)) {
4826
4986
  for (const typeDir of KNOWLEDGE_CANONICAL_TYPE_DIRS_FOR_ONBOARD) {
4827
- const dir = join6(knowledgeRoot, typeDir);
4828
- if (!existsSync4(dir)) continue;
4987
+ const dir = join7(knowledgeRoot, typeDir);
4988
+ if (!existsSync5(dir)) continue;
4829
4989
  let entries;
4830
4990
  try {
4831
4991
  entries = readdirSync(dir, { withFileTypes: true });
@@ -4835,10 +4995,10 @@ function inspectOnboardCoverage(projectRoot) {
4835
4995
  for (const entry of entries) {
4836
4996
  if (!entry.isFile()) continue;
4837
4997
  if (!entry.name.endsWith(".md")) continue;
4838
- const filePath = join6(dir, entry.name);
4998
+ const filePath = join7(dir, entry.name);
4839
4999
  let content;
4840
5000
  try {
4841
- content = readFileSync2(filePath, "utf8");
5001
+ content = readFileSync3(filePath, "utf8");
4842
5002
  } catch {
4843
5003
  continue;
4844
5004
  }
@@ -4862,11 +5022,11 @@ function inspectOnboardCoverage(projectRoot) {
4862
5022
  return { filled, missing, opted_out: optedOut };
4863
5023
  }
4864
5024
  function readOnboardOptedOut(projectRoot) {
4865
- const path2 = join6(projectRoot, ".fabric", "fabric-config.json");
4866
- if (!existsSync4(path2)) return [];
5025
+ const path2 = join7(projectRoot, ".fabric", "fabric-config.json");
5026
+ if (!existsSync5(path2)) return [];
4867
5027
  let raw;
4868
5028
  try {
4869
- raw = readFileSync2(path2, "utf8");
5029
+ raw = readFileSync3(path2, "utf8");
4870
5030
  } catch {
4871
5031
  return [];
4872
5032
  }
@@ -4982,7 +5142,7 @@ function createNarrowTooFewCheck(t, inspection) {
4982
5142
  }
4983
5143
  function resolvePersonalKnowledgeRoot() {
4984
5144
  const home = process.env.FABRIC_HOME ?? homedir3();
4985
- return join6(home, ".fabric", "knowledge");
5145
+ return join7(home, ".fabric", "knowledge");
4986
5146
  }
4987
5147
  function parseStableIdFromCanonicalFilename(filename) {
4988
5148
  const match = CANONICAL_KNOWLEDGE_FILENAME_PATTERN.exec(filename);
@@ -5002,18 +5162,18 @@ function parseStableIdFromCanonicalFilename(filename) {
5002
5162
  };
5003
5163
  }
5004
5164
  function* iterateCanonicalFilenames(projectRoot) {
5005
- const teamRoot = join6(projectRoot, ".fabric", "knowledge");
5165
+ const teamRoot = join7(projectRoot, ".fabric", "knowledge");
5006
5166
  const personalRoot = resolvePersonalKnowledgeRoot();
5007
5167
  for (const [layer, root, displayPrefix] of [
5008
5168
  ["team", teamRoot, ".fabric/knowledge"],
5009
5169
  ["personal", personalRoot, "~/.fabric/knowledge"]
5010
5170
  ]) {
5011
- if (!existsSync4(root)) {
5171
+ if (!existsSync5(root)) {
5012
5172
  continue;
5013
5173
  }
5014
5174
  for (const typeDir of KNOWLEDGE_CANONICAL_TYPE_DIRS) {
5015
- const dir = join6(root, typeDir);
5016
- if (!existsSync4(dir)) {
5175
+ const dir = join7(root, typeDir);
5176
+ if (!existsSync5(dir)) {
5017
5177
  continue;
5018
5178
  }
5019
5179
  let entries;
@@ -5184,8 +5344,8 @@ async function migrateBootstrapMarkers(projectRoot) {
5184
5344
  const paths = [];
5185
5345
  const countPerPath = {};
5186
5346
  for (const rel of BOOTSTRAP_MARKER_MIGRATION_TARGETS) {
5187
- const abs = join6(projectRoot, rel);
5188
- if (!existsSync4(abs)) {
5347
+ const abs = join7(projectRoot, rel);
5348
+ if (!existsSync5(abs)) {
5189
5349
  continue;
5190
5350
  }
5191
5351
  let original;
@@ -5211,8 +5371,8 @@ async function migrateBootstrapMarkers(projectRoot) {
5211
5371
  return { paths, countPerPath };
5212
5372
  }
5213
5373
  async function rewriteThreeEndManagedBlocks(projectRoot) {
5214
- const snapshotPath = join6(projectRoot, ".fabric", "AGENTS.md");
5215
- if (!existsSync4(snapshotPath)) {
5374
+ const snapshotPath = join7(projectRoot, ".fabric", "AGENTS.md");
5375
+ if (!existsSync5(snapshotPath)) {
5216
5376
  return;
5217
5377
  }
5218
5378
  let snapshot;
@@ -5221,8 +5381,8 @@ async function rewriteThreeEndManagedBlocks(projectRoot) {
5221
5381
  } catch {
5222
5382
  return;
5223
5383
  }
5224
- const projectRulesPath = join6(projectRoot, ".fabric", "project-rules.md");
5225
- const hasProjectRules = existsSync4(projectRulesPath);
5384
+ const projectRulesPath = join7(projectRoot, ".fabric", "project-rules.md");
5385
+ const hasProjectRules = existsSync5(projectRulesPath);
5226
5386
  let expectedBody = snapshot;
5227
5387
  if (hasProjectRules) {
5228
5388
  try {
@@ -5237,11 +5397,11 @@ ${projectRules}`;
5237
5397
  ${expectedBody}
5238
5398
  ${BOOTSTRAP_MARKER_END}`;
5239
5399
  const blockTargets = [
5240
- join6(projectRoot, "AGENTS.md"),
5241
- join6(projectRoot, ".cursor", "rules", "fabric-bootstrap.mdc")
5400
+ join7(projectRoot, "AGENTS.md"),
5401
+ join7(projectRoot, ".cursor", "rules", "fabric-bootstrap.mdc")
5242
5402
  ];
5243
5403
  for (const abs of blockTargets) {
5244
- if (!existsSync4(abs)) {
5404
+ if (!existsSync5(abs)) {
5245
5405
  continue;
5246
5406
  }
5247
5407
  let existing;
@@ -5271,8 +5431,8 @@ ${managedBlock}
5271
5431
  }
5272
5432
  await atomicWriteText4(abs, next);
5273
5433
  }
5274
- const claudeMdPath = join6(projectRoot, "CLAUDE.md");
5275
- if (existsSync4(claudeMdPath)) {
5434
+ const claudeMdPath = join7(projectRoot, "CLAUDE.md");
5435
+ if (existsSync5(claudeMdPath)) {
5276
5436
  let claudeContent;
5277
5437
  try {
5278
5438
  claudeContent = await readFile5(claudeMdPath, "utf8");
@@ -5300,13 +5460,13 @@ ${managedBlock}
5300
5460
  }
5301
5461
  }
5302
5462
  async function fixMcpConfigInWrongFile(projectRoot) {
5303
- const settingsPath = join6(projectRoot, ".claude", "settings.json");
5304
- if (!existsSync4(settingsPath)) {
5463
+ const settingsPath = join7(projectRoot, ".claude", "settings.json");
5464
+ if (!existsSync5(settingsPath)) {
5305
5465
  return;
5306
5466
  }
5307
5467
  let settings;
5308
5468
  try {
5309
- const parsed = JSON.parse(readFileSync2(settingsPath, "utf8"));
5469
+ const parsed = JSON.parse(readFileSync3(settingsPath, "utf8"));
5310
5470
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
5311
5471
  return;
5312
5472
  }
@@ -5334,12 +5494,12 @@ async function fixMcpConfigInWrongFile(projectRoot) {
5334
5494
  }
5335
5495
  async function ensureKnowledgeSubdirs(projectRoot) {
5336
5496
  for (const sub of KNOWLEDGE_SUBDIRS3) {
5337
- await mkdir4(join6(projectRoot, ".fabric", "knowledge", sub), { recursive: true });
5497
+ await mkdir4(join7(projectRoot, ".fabric", "knowledge", sub), { recursive: true });
5338
5498
  }
5339
5499
  }
5340
5500
  async function fixCounterDesync(projectRoot) {
5341
- const metaPath = join6(projectRoot, ".fabric", "agents.meta.json");
5342
- if (!existsSync4(metaPath)) {
5501
+ const metaPath = join7(projectRoot, ".fabric", "agents.meta.json");
5502
+ if (!existsSync5(metaPath)) {
5343
5503
  return;
5344
5504
  }
5345
5505
  let meta;
@@ -5757,8 +5917,8 @@ async function runDoctorCiteCoverage(projectRoot, options) {
5757
5917
  continue;
5758
5918
  }
5759
5919
  bumpLayerType(citeId, kbType);
5760
- if (kbType === "decision" || kbType === "pitfall") {
5761
- if (kbType === "decision") decisionsCited += 1;
5920
+ if (kbType === "decisions" || kbType === "pitfalls") {
5921
+ if (kbType === "decisions") decisionsCited += 1;
5762
5922
  else pitfallsCited += 1;
5763
5923
  const commitment = commitments[i];
5764
5924
  const operators = commitment?.operators ?? [];
@@ -5922,7 +6082,7 @@ function normalizePath(path2) {
5922
6082
  return posix.normalize(path2.split("\\").join("/"));
5923
6083
  }
5924
6084
  function collectEntryPoints(root) {
5925
- if (!existsSync4(root) || !statSync4(root).isDirectory()) {
6085
+ if (!existsSync5(root) || !statSync4(root).isDirectory()) {
5926
6086
  return [];
5927
6087
  }
5928
6088
  const entries = [];
@@ -5933,7 +6093,7 @@ function collectEntryPoints(root) {
5933
6093
  continue;
5934
6094
  }
5935
6095
  for (const entry of readdirSync(current, { withFileTypes: true })) {
5936
- const absolutePath = join6(current, entry.name);
6096
+ const absolutePath = join7(current, entry.name);
5937
6097
  const relativePath = normalizePath(absolutePath.slice(root.length + 1));
5938
6098
  if (relativePath.length === 0) {
5939
6099
  continue;
@@ -5999,14 +6159,14 @@ var ENRICH_DESC_FIELD_PATTERNS = {
5999
6159
  async function enrichDescriptions(projectRoot, opts = {}) {
6000
6160
  const auto = opts.auto === true;
6001
6161
  const dryRun = opts.dryRun === true;
6002
- const mode = auto ? "auto" : "interactive";
6162
+ const mode = auto ? dryRun ? "preview" : "auto" : "readonly";
6003
6163
  const candidates = [];
6004
6164
  let scanned = 0;
6005
6165
  let modified = 0;
6006
6166
  let skipped = 0;
6007
6167
  for (const visit of iterateCanonicalFilenames(projectRoot)) {
6008
- const layerRoot = visit.layer === "team" ? join6(projectRoot, ".fabric", "knowledge") : resolvePersonalKnowledgeRoot();
6009
- const absPath = join6(layerRoot, visit.type, visit.filename);
6168
+ const layerRoot = visit.layer === "team" ? join7(projectRoot, ".fabric", "knowledge") : resolvePersonalKnowledgeRoot();
6169
+ const absPath = join7(layerRoot, visit.type, visit.filename);
6010
6170
  scanned += 1;
6011
6171
  let source;
6012
6172
  try {
@@ -6209,7 +6369,7 @@ async function emitAutoHealEventBestEffort(projectRoot, payload) {
6209
6369
 
6210
6370
  // src/services/get-knowledge.ts
6211
6371
  import { readFile as readFile6 } from "fs/promises";
6212
- import { join as join7 } from "path";
6372
+ import { join as join8 } from "path";
6213
6373
  import { minimatch as minimatch2 } from "minimatch";
6214
6374
  var PRIORITY_ORDER = {
6215
6375
  high: 0,
@@ -6253,7 +6413,7 @@ async function loadGetKnowledgeContext(projectRoot) {
6253
6413
  return cached;
6254
6414
  }
6255
6415
  const meta = await readAgentsMeta(projectRoot);
6256
- const l0Content = await readFile6(join7(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
6416
+ const l0Content = await readFile6(join8(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
6257
6417
  const context = {
6258
6418
  meta,
6259
6419
  l0Content,
@@ -6392,7 +6552,7 @@ async function readRuleContent(projectRoot, file, fileContentCache) {
6392
6552
  if (cached !== void 0) {
6393
6553
  return await cached;
6394
6554
  }
6395
- const pending = readFile6(join7(projectRoot, file), "utf8");
6555
+ const pending = readFile6(join8(projectRoot, file), "utf8");
6396
6556
  fileContentCache.set(file, pending);
6397
6557
  return await pending;
6398
6558
  }
@@ -6427,6 +6587,8 @@ export {
6427
6587
  invalidateKnowledgeSyncCooldown,
6428
6588
  ensureKnowledgeFresh,
6429
6589
  reconcileKnowledge,
6590
+ readPayloadLimits,
6591
+ readSelectionTokenTtlMs,
6430
6592
  loadActiveMeta,
6431
6593
  loadActiveMetaOrStale,
6432
6594
  getKnowledge,