@fenglimg/fabric-server 2.0.0-rc.28 → 2.0.0-rc.30

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}`, {
@@ -851,8 +851,9 @@ function createDefaultNodeMeta(contentRef) {
851
851
  scope_glob: deriveScopeGlob(contentRef),
852
852
  deps: layer === "L0" ? [] : ["L0"],
853
853
  priority: layer === "L0" ? "high" : "medium",
854
+ // v2.0.0-rc.30 TASK-004: dropped duplicate `layer:` write — was always
855
+ // identical to `level:`; AgentsMetaNode no longer carries the field.
854
856
  level: layer,
855
- layer,
856
857
  topology_type: topologyType,
857
858
  hash: ""
858
859
  };
@@ -1078,9 +1079,17 @@ function extractKnowledgeFieldsFromFrontmatter(frontmatter) {
1078
1079
  `);
1079
1080
  }
1080
1081
  }
1082
+ const SINGULAR_TO_PLURAL = {
1083
+ model: "models",
1084
+ decision: "decisions",
1085
+ guideline: "guidelines",
1086
+ pitfall: "pitfalls",
1087
+ process: "processes"
1088
+ };
1081
1089
  let knowledge_type;
1082
1090
  if (rawType !== void 0) {
1083
- const parsed = KnowledgeTypeSchema.safeParse(rawType);
1091
+ const normalized = SINGULAR_TO_PLURAL[rawType] ?? rawType;
1092
+ const parsed = KnowledgeTypeSchema.safeParse(normalized);
1084
1093
  if (parsed.success) {
1085
1094
  knowledge_type = parsed.data;
1086
1095
  } else {
@@ -1429,6 +1438,12 @@ async function ensureKnowledgeFresh(projectRoot, opts) {
1429
1438
  contextCache.invalidate("file_watch", projectRoot);
1430
1439
  }
1431
1440
  freshSyncCooldown.delete(projectRoot);
1441
+ if (opts?.autoHealOnDrift === true && events.length > 0) {
1442
+ try {
1443
+ await reconcileKnowledge(projectRoot, { trigger: "auto-heal-after-drift" });
1444
+ } catch {
1445
+ }
1446
+ }
1432
1447
  const status = warnings.length > 0 ? "errors" : "reconciled";
1433
1448
  return {
1434
1449
  status,
@@ -1620,11 +1635,11 @@ function checkLockOrThrow(projectRoot, opts) {
1620
1635
 
1621
1636
  // src/services/doctor.ts
1622
1637
  import { execFileSync } from "child_process";
1623
- import { existsSync as existsSync4, readdirSync, readFileSync as readFileSync2, statSync as statSync4 } from "fs";
1638
+ import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync as statSync4 } from "fs";
1624
1639
  import { access, mkdir as mkdir4, readFile as readFile5, rename, unlink, writeFile as writeFile2 } from "fs/promises";
1625
1640
  import { constants } from "fs";
1626
1641
  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";
1642
+ import { isAbsolute as isAbsolute2, join as join7, posix, relative as nodeRelative, resolve as resolve3, sep as sep3 } from "path";
1628
1643
  import { minimatch } from "minimatch";
1629
1644
  import {
1630
1645
  agentsMetaSchema as agentsMetaSchema4,
@@ -1643,6 +1658,41 @@ import {
1643
1658
  resolveFabricLocale as resolveFabricLocale2
1644
1659
  } from "@fenglimg/fabric-shared";
1645
1660
  import { detectFramework } from "@fenglimg/fabric-shared/node";
1661
+ import {
1662
+ PAYLOAD_LIMIT_DEFAULT_HARD_BYTES,
1663
+ PAYLOAD_LIMIT_DEFAULT_WARN_BYTES
1664
+ } from "@fenglimg/fabric-shared/node/mcp-payload-guard";
1665
+
1666
+ // src/config-loader.ts
1667
+ import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1668
+ import { join as join6 } from "path";
1669
+ import { selectionTokenTtlMsSchema } from "@fenglimg/fabric-shared";
1670
+ function readFabricConfig(projectRoot) {
1671
+ const configPath = join6(projectRoot, "fabric.config.json");
1672
+ if (!existsSync4(configPath)) {
1673
+ return {};
1674
+ }
1675
+ const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
1676
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1677
+ throw new Error(`Expected object in ${configPath}`);
1678
+ }
1679
+ return parsed;
1680
+ }
1681
+ function readPayloadLimits(projectRoot) {
1682
+ return readFabricConfig(projectRoot).mcpPayloadLimits;
1683
+ }
1684
+ function readSelectionTokenTtlMs(projectRoot) {
1685
+ try {
1686
+ const raw = readFabricConfig(projectRoot).selection_token_ttl_ms;
1687
+ if (raw === void 0) return void 0;
1688
+ const parsed = selectionTokenTtlMsSchema.safeParse(raw);
1689
+ return parsed.success ? parsed.data : void 0;
1690
+ } catch {
1691
+ return void 0;
1692
+ }
1693
+ }
1694
+
1695
+ // src/services/doctor.ts
1646
1696
  import { atomicWriteJson as atomicWriteJson2, atomicWriteText as atomicWriteText4 } from "@fenglimg/fabric-shared/node/atomic-write";
1647
1697
  var ORPHAN_DEMOTE_THRESHOLD_DAYS = {
1648
1698
  stable: 90,
@@ -1914,11 +1964,27 @@ async function runDoctorReport(target) {
1914
1964
  warningCount: warnings.length,
1915
1965
  infoCount: infos.length,
1916
1966
  targetFiles: Object.fromEntries(
1917
- TARGET_FILE_PATHS.map((path2) => [path2, existsSync4(join6(projectRoot, path2))])
1918
- )
1967
+ TARGET_FILE_PATHS.map((path2) => [path2, existsSync5(join7(projectRoot, path2))])
1968
+ ),
1969
+ // v2.0.0-rc.29 TASK-008 (BUG-F2): resolve and surface payload thresholds.
1970
+ // Best-effort: a corrupt fabric.config.json should not fail doctor; on
1971
+ // any read/parse error fall back to library defaults with source="default".
1972
+ payload_limits: resolvePayloadLimits(projectRoot)
1919
1973
  }
1920
1974
  };
1921
1975
  }
1976
+ function resolvePayloadLimits(projectRoot) {
1977
+ let override;
1978
+ try {
1979
+ override = readPayloadLimits(projectRoot);
1980
+ } catch {
1981
+ override = void 0;
1982
+ }
1983
+ const warn = override?.warnBytes ?? PAYLOAD_LIMIT_DEFAULT_WARN_BYTES;
1984
+ const hard = override?.hardBytes ?? PAYLOAD_LIMIT_DEFAULT_HARD_BYTES;
1985
+ const source = override?.warnBytes !== void 0 || override?.hardBytes !== void 0 ? "config" : "default";
1986
+ return { warn_bytes: warn, hard_bytes: hard, source };
1987
+ }
1922
1988
  async function runDoctorFix(target) {
1923
1989
  const projectRoot = normalizeTarget(target);
1924
1990
  const before = await runDoctorReport(projectRoot);
@@ -1941,7 +2007,7 @@ async function runDoctorFix(target) {
1941
2007
  }
1942
2008
  }
1943
2009
  if (before.fixable_errors.some((issue) => issue.code === "bootstrap_snapshot_drift")) {
1944
- const snapshotPath = join6(projectRoot, ".fabric", "AGENTS.md");
2010
+ const snapshotPath = join7(projectRoot, ".fabric", "AGENTS.md");
1945
2011
  await ensureParentDirectory(snapshotPath);
1946
2012
  await atomicWriteText4(snapshotPath, BOOTSTRAP_CANONICAL);
1947
2013
  fixed.push(findIssue(before.fixable_errors, "bootstrap_snapshot_drift"));
@@ -2015,7 +2081,7 @@ async function runDoctorFix(target) {
2015
2081
  if (before.infos.some((issue) => issue.code === "stale_serve_lock")) {
2016
2082
  const lockInspection = inspectStaleServeLock(projectRoot, Date.now());
2017
2083
  if (lockInspection.present && !lockInspection.pidAlive) {
2018
- const lockFilePath = join6(projectRoot, ".fabric", ".serve.lock");
2084
+ const lockFilePath = join7(projectRoot, ".fabric", ".serve.lock");
2019
2085
  try {
2020
2086
  await unlink(lockFilePath);
2021
2087
  } catch (err) {
@@ -2172,7 +2238,7 @@ async function applyOrphanDemote(projectRoot, candidate, now) {
2172
2238
  };
2173
2239
  }
2174
2240
  const detail = `${candidate.maturity} -> ${next}`;
2175
- const absPath = join6(projectRoot, candidate.path);
2241
+ const absPath = join7(projectRoot, candidate.path);
2176
2242
  try {
2177
2243
  const source = await readFile5(absPath, "utf8");
2178
2244
  const rewritten = rewriteFrontmatterMaturity(source, next);
@@ -2239,11 +2305,11 @@ async function applyOrphanDemote(projectRoot, candidate, now) {
2239
2305
  }
2240
2306
  }
2241
2307
  async function applyStaleArchive(projectRoot, candidate, now) {
2242
- const sourceAbs = join6(projectRoot, candidate.path);
2243
- const destAbs = join6(projectRoot, candidate.archive_path);
2308
+ const sourceAbs = join7(projectRoot, candidate.path);
2309
+ const destAbs = join7(projectRoot, candidate.archive_path);
2244
2310
  const detail = `${candidate.path} -> ${candidate.archive_path}`;
2245
2311
  try {
2246
- await mkdir4(join6(destAbs, ".."), { recursive: true });
2312
+ await mkdir4(join7(destAbs, ".."), { recursive: true });
2247
2313
  try {
2248
2314
  await rename(sourceAbs, destAbs);
2249
2315
  } catch (renameError) {
@@ -2302,7 +2368,7 @@ async function applyStaleArchive(projectRoot, candidate, now) {
2302
2368
  async function applyPendingAutoArchive(projectRoot, candidate, now) {
2303
2369
  const detail = `${candidate.pending_path} -> ${candidate.archived_to}`;
2304
2370
  try {
2305
- await mkdir4(join6(candidate.archived_to_abs, ".."), { recursive: true });
2371
+ await mkdir4(join7(candidate.archived_to_abs, ".."), { recursive: true });
2306
2372
  let moved = false;
2307
2373
  if (candidate.layer === "team") {
2308
2374
  try {
@@ -2379,7 +2445,7 @@ function relativePosix(projectRoot, absolutePath) {
2379
2445
  }
2380
2446
  async function applySessionHintsStaleCleanup(projectRoot, candidate) {
2381
2447
  const detail = `deleted (${candidate.age_days}d old)`;
2382
- const absPath = join6(projectRoot, candidate.path);
2448
+ const absPath = join7(projectRoot, candidate.path);
2383
2449
  try {
2384
2450
  const { unlink: unlink2 } = await import("fs/promises");
2385
2451
  await unlink2(absPath);
@@ -2400,7 +2466,7 @@ async function applySessionHintsStaleCleanup(projectRoot, candidate) {
2400
2466
  }
2401
2467
  }
2402
2468
  async function applyIndexDriftFix(projectRoot, inspection) {
2403
- const metaPath = join6(projectRoot, ".fabric", "agents.meta.json");
2469
+ const metaPath = join7(projectRoot, ".fabric", "agents.meta.json");
2404
2470
  const detailParts = [];
2405
2471
  try {
2406
2472
  const meta = agentsMetaSchema4.parse(JSON.parse(await readFile5(metaPath, "utf8")));
@@ -2436,7 +2502,7 @@ function truncateErrorMessage(error) {
2436
2502
  return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw;
2437
2503
  }
2438
2504
  async function inspectForensic(projectRoot) {
2439
- const path2 = join6(projectRoot, ".fabric", "forensic.json");
2505
+ const path2 = join7(projectRoot, ".fabric", "forensic.json");
2440
2506
  try {
2441
2507
  const parsed = forensicReportSchema.parse(JSON.parse(await readFile5(path2, "utf8")));
2442
2508
  return { present: true, valid: true, report: parsed };
@@ -2448,12 +2514,12 @@ async function inspectForensic(projectRoot) {
2448
2514
  }
2449
2515
  }
2450
2516
  function inspectMcpConfigInWrongFile(projectRoot) {
2451
- const settingsPath = join6(projectRoot, ".claude", "settings.json");
2452
- if (!existsSync4(settingsPath)) {
2517
+ const settingsPath = join7(projectRoot, ".claude", "settings.json");
2518
+ if (!existsSync5(settingsPath)) {
2453
2519
  return { hasWrongEntry: false, settingsPath };
2454
2520
  }
2455
2521
  try {
2456
- const parsed = JSON.parse(readFileSync2(settingsPath, "utf8"));
2522
+ const parsed = JSON.parse(readFileSync3(settingsPath, "utf8"));
2457
2523
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
2458
2524
  return { hasWrongEntry: false, settingsPath };
2459
2525
  }
@@ -2469,7 +2535,7 @@ function inspectMcpConfigInWrongFile(projectRoot) {
2469
2535
  }
2470
2536
  }
2471
2537
  async function inspectMeta(projectRoot) {
2472
- const metaPath = join6(projectRoot, ".fabric", "agents.meta.json");
2538
+ const metaPath = join7(projectRoot, ".fabric", "agents.meta.json");
2473
2539
  const built = await tryBuildRuleMeta(projectRoot);
2474
2540
  try {
2475
2541
  const raw = await readFile5(metaPath, "utf8");
@@ -2542,7 +2608,7 @@ function inspectContentRefs(projectRoot, meta) {
2542
2608
  if (isPersonalKnowledge) {
2543
2609
  continue;
2544
2610
  }
2545
- if (!existsSync4(join6(projectRoot, contentRef))) {
2611
+ if (!existsSync5(join7(projectRoot, contentRef))) {
2546
2612
  missing.push(contentRef);
2547
2613
  }
2548
2614
  }
@@ -2550,7 +2616,7 @@ function inspectContentRefs(projectRoot, meta) {
2550
2616
  }
2551
2617
  async function inspectEventLedger(projectRoot) {
2552
2618
  const path2 = getEventLedgerPath(projectRoot);
2553
- const exists = existsSync4(path2);
2619
+ const exists = existsSync5(path2);
2554
2620
  if (!exists) {
2555
2621
  return {
2556
2622
  exists: false,
@@ -2626,8 +2692,8 @@ function inspectSkillRefMirror(projectRoot) {
2626
2692
  const skillSlugs = ["fabric-archive", "fabric-review", "fabric-import"];
2627
2693
  const driftedPaths = [];
2628
2694
  for (const slug of skillSlugs) {
2629
- const claudeRef = join6(projectRoot, ".claude", "skills", slug, "ref");
2630
- const codexRef = join6(projectRoot, ".codex", "skills", slug, "ref");
2695
+ const claudeRef = join7(projectRoot, ".claude", "skills", slug, "ref");
2696
+ const codexRef = join7(projectRoot, ".codex", "skills", slug, "ref");
2631
2697
  let claudeFiles = null;
2632
2698
  let codexFiles = null;
2633
2699
  try {
@@ -2652,12 +2718,12 @@ function inspectSkillRefMirror(projectRoot) {
2652
2718
  let claudeBody;
2653
2719
  let codexBody;
2654
2720
  try {
2655
- claudeBody = readFileSync2(join6(claudeRef, fname), "utf8");
2721
+ claudeBody = readFileSync3(join7(claudeRef, fname), "utf8");
2656
2722
  } catch {
2657
2723
  continue;
2658
2724
  }
2659
2725
  try {
2660
- codexBody = readFileSync2(join6(codexRef, fname), "utf8");
2726
+ codexBody = readFileSync3(join7(codexRef, fname), "utf8");
2661
2727
  } catch {
2662
2728
  continue;
2663
2729
  }
@@ -2670,7 +2736,7 @@ function inspectSkillRefMirror(projectRoot) {
2670
2736
  return { status: "drift", driftedPaths };
2671
2737
  }
2672
2738
  async function inspectKnowledgeTestIndex(projectRoot) {
2673
- const path2 = join6(projectRoot, ".fabric", ".cache", "knowledge-test.index.json");
2739
+ const path2 = join7(projectRoot, ".fabric", ".cache", "knowledge-test.index.json");
2674
2740
  const built = await tryBuildRuleMeta(projectRoot);
2675
2741
  try {
2676
2742
  const index = knowledgeTestIndexSchema2.parse(JSON.parse(await readFile5(path2, "utf8")));
@@ -2694,8 +2760,8 @@ async function inspectKnowledgeTestIndex(projectRoot) {
2694
2760
  }
2695
2761
  function inspectBootstrapAnchor(projectRoot) {
2696
2762
  return {
2697
- hasAgentsMd: existsSync4(join6(projectRoot, "AGENTS.md")),
2698
- hasClaudeMd: existsSync4(join6(projectRoot, "CLAUDE.md"))
2763
+ hasAgentsMd: existsSync5(join7(projectRoot, "AGENTS.md")),
2764
+ hasClaudeMd: existsSync5(join7(projectRoot, "CLAUDE.md"))
2699
2765
  };
2700
2766
  }
2701
2767
  var BOOTSTRAP_MARKER_MIGRATION_TARGETS = [
@@ -2707,8 +2773,8 @@ var BOOTSTRAP_MARKER_MIGRATION_TARGETS = [
2707
2773
  async function inspectBootstrapMarkerMigration(target) {
2708
2774
  const filesNeedingMigration = [];
2709
2775
  for (const rel of BOOTSTRAP_MARKER_MIGRATION_TARGETS) {
2710
- const abs = join6(target, rel);
2711
- if (!existsSync4(abs)) {
2776
+ const abs = join7(target, rel);
2777
+ if (!existsSync5(abs)) {
2712
2778
  continue;
2713
2779
  }
2714
2780
  let content;
@@ -2745,8 +2811,8 @@ function createBootstrapMarkerMigrationCheck(t, inspection) {
2745
2811
  );
2746
2812
  }
2747
2813
  async function inspectL1BootstrapSnapshotDrift(target) {
2748
- const abs = join6(target, ".fabric", "AGENTS.md");
2749
- if (!existsSync4(abs)) {
2814
+ const abs = join7(target, ".fabric", "AGENTS.md");
2815
+ if (!existsSync5(abs)) {
2750
2816
  return { status: "missing", canonical: BOOTSTRAP_CANONICAL, onDisk: null };
2751
2817
  }
2752
2818
  let onDisk;
@@ -2777,8 +2843,8 @@ function createL1BootstrapSnapshotDriftCheck(t, inspection) {
2777
2843
  );
2778
2844
  }
2779
2845
  async function inspectL2ManagedBlockDrift(target) {
2780
- const snapshotPath = join6(target, ".fabric", "AGENTS.md");
2781
- if (!existsSync4(snapshotPath)) {
2846
+ const snapshotPath = join7(target, ".fabric", "AGENTS.md");
2847
+ if (!existsSync5(snapshotPath)) {
2782
2848
  return { status: "ok", drifted: [] };
2783
2849
  }
2784
2850
  let snapshot;
@@ -2787,9 +2853,9 @@ async function inspectL2ManagedBlockDrift(target) {
2787
2853
  } catch {
2788
2854
  return { status: "ok", drifted: [] };
2789
2855
  }
2790
- const projectRulesPath = join6(target, ".fabric", "project-rules.md");
2856
+ const projectRulesPath = join7(target, ".fabric", "project-rules.md");
2791
2857
  let expectedBody = snapshot;
2792
- if (existsSync4(projectRulesPath)) {
2858
+ if (existsSync5(projectRulesPath)) {
2793
2859
  try {
2794
2860
  const projectRules = await readFile5(projectRulesPath, "utf8");
2795
2861
  expectedBody = `${snapshot}
@@ -2801,11 +2867,11 @@ ${projectRules}`;
2801
2867
  const drifted = [];
2802
2868
  let anyManagedBlockFound = false;
2803
2869
  const blockTargets = [
2804
- join6(target, "AGENTS.md"),
2805
- join6(target, ".cursor", "rules", "fabric-bootstrap.mdc")
2870
+ join7(target, "AGENTS.md"),
2871
+ join7(target, ".cursor", "rules", "fabric-bootstrap.mdc")
2806
2872
  ];
2807
2873
  for (const abs of blockTargets) {
2808
- if (!existsSync4(abs)) {
2874
+ if (!existsSync5(abs)) {
2809
2875
  continue;
2810
2876
  }
2811
2877
  let content;
@@ -2836,8 +2902,8 @@ ${projectRules}`;
2836
2902
  drifted.push({ path: abs, expected: expectedBody, actual: body });
2837
2903
  }
2838
2904
  }
2839
- const claudeMdPath = join6(target, "CLAUDE.md");
2840
- if (existsSync4(claudeMdPath)) {
2905
+ const claudeMdPath = join7(target, "CLAUDE.md");
2906
+ if (existsSync5(claudeMdPath)) {
2841
2907
  let claudeContent;
2842
2908
  try {
2843
2909
  claudeContent = await readFile5(claudeMdPath, "utf8");
@@ -2907,11 +2973,11 @@ function createBootstrapAnchorCheck(t, inspection) {
2907
2973
  );
2908
2974
  }
2909
2975
  function inspectKnowledgeDirMissing(projectRoot) {
2910
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
2976
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
2911
2977
  const missingSubdirs = [];
2912
2978
  for (const sub of KNOWLEDGE_SUBDIRS3) {
2913
- const path2 = join6(knowledgeRoot, sub);
2914
- if (!existsSync4(path2)) {
2979
+ const path2 = join7(knowledgeRoot, sub);
2980
+ if (!existsSync5(path2)) {
2915
2981
  missingSubdirs.push(`.fabric/knowledge/${sub}`);
2916
2982
  }
2917
2983
  }
@@ -2919,13 +2985,13 @@ function inspectKnowledgeDirMissing(projectRoot) {
2919
2985
  }
2920
2986
  function inspectBaselineFilenameFormat(projectRoot) {
2921
2987
  const offenders = [];
2922
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
2923
- if (!existsSync4(knowledgeRoot)) {
2988
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
2989
+ if (!existsSync5(knowledgeRoot)) {
2924
2990
  return { offenders };
2925
2991
  }
2926
2992
  for (const sub of KNOWLEDGE_CANONICAL_TYPE_DIRS) {
2927
- const dir = join6(knowledgeRoot, sub);
2928
- if (!existsSync4(dir)) {
2993
+ const dir = join7(knowledgeRoot, sub);
2994
+ if (!existsSync5(dir)) {
2929
2995
  continue;
2930
2996
  }
2931
2997
  let entries;
@@ -2942,10 +3008,10 @@ function inspectBaselineFilenameFormat(projectRoot) {
2942
3008
  if (BASELINE_ID_PREFIXED_FILENAME_PATTERN.test(entryName)) {
2943
3009
  continue;
2944
3010
  }
2945
- const abs = join6(dir, entryName);
3011
+ const abs = join7(dir, entryName);
2946
3012
  let source;
2947
3013
  try {
2948
- source = readFileSync2(abs, "utf8");
3014
+ source = readFileSync3(abs, "utf8");
2949
3015
  } catch {
2950
3016
  continue;
2951
3017
  }
@@ -3319,8 +3385,8 @@ function findIssue(issues, code) {
3319
3385
  };
3320
3386
  }
3321
3387
  async function inspectMetaManuallyDiverged(projectRoot) {
3322
- const metaPath = join6(projectRoot, ".fabric", "agents.meta.json");
3323
- if (!existsSync4(metaPath)) {
3388
+ const metaPath = join7(projectRoot, ".fabric", "agents.meta.json");
3389
+ if (!existsSync5(metaPath)) {
3324
3390
  return { extraMetaEntries: [], hashMismatchEntries: [], readable: false };
3325
3391
  }
3326
3392
  let meta;
@@ -3339,13 +3405,13 @@ async function inspectMetaManuallyDiverged(projectRoot) {
3339
3405
  const hashMismatchEntries = [];
3340
3406
  for (const node of Object.values(meta.nodes)) {
3341
3407
  const contentRef = node.content_ref ?? node.file;
3342
- const absPath = join6(projectRoot, contentRef);
3343
- if (!existsSync4(absPath)) {
3408
+ const absPath = join7(projectRoot, contentRef);
3409
+ if (!existsSync5(absPath)) {
3344
3410
  extraMetaEntries.push(contentRef);
3345
3411
  continue;
3346
3412
  }
3347
3413
  try {
3348
- const content = readFileSync2(absPath, "utf8");
3414
+ const content = readFileSync3(absPath, "utf8");
3349
3415
  const diskHash = sha256(content);
3350
3416
  if (node.hash !== "" && node.hash !== diskHash) {
3351
3417
  hashMismatchEntries.push(contentRef);
@@ -3358,7 +3424,7 @@ async function inspectMetaManuallyDiverged(projectRoot) {
3358
3424
  }
3359
3425
  function inspectKnowledgeDirUnindexed(projectRoot, meta) {
3360
3426
  const physicalMdFiles = /* @__PURE__ */ new Set();
3361
- collectMdFilesUnder(physicalMdFiles, projectRoot, join6(projectRoot, ".fabric", "knowledge"), ".fabric/knowledge");
3427
+ collectMdFilesUnder(physicalMdFiles, projectRoot, join7(projectRoot, ".fabric", "knowledge"), ".fabric/knowledge");
3362
3428
  if (physicalMdFiles.size === 0) {
3363
3429
  return { unindexedFiles: [] };
3364
3430
  }
@@ -3373,7 +3439,7 @@ function inspectKnowledgeDirUnindexed(projectRoot, meta) {
3373
3439
  return { unindexedFiles };
3374
3440
  }
3375
3441
  function collectMdFilesUnder(out, projectRoot, rootDir, relPrefix) {
3376
- if (!existsSync4(rootDir)) {
3442
+ if (!existsSync5(rootDir)) {
3377
3443
  return;
3378
3444
  }
3379
3445
  const stack = [rootDir];
@@ -3383,7 +3449,7 @@ function collectMdFilesUnder(out, projectRoot, rootDir, relPrefix) {
3383
3449
  continue;
3384
3450
  }
3385
3451
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
3386
- const abs = join6(dir, entry.name);
3452
+ const abs = join7(dir, entry.name);
3387
3453
  if (entry.isDirectory()) {
3388
3454
  if (entry.name !== "pending" && entry.name !== "archive") {
3389
3455
  stack.push(abs);
@@ -3416,8 +3482,8 @@ function createKnowledgeDirUnindexedCheck(t, inspection) {
3416
3482
  }
3417
3483
  async function inspectStableIdCollisions(projectRoot) {
3418
3484
  const found = [];
3419
- const knowledgeDir = join6(projectRoot, ".fabric", "knowledge");
3420
- if (existsSync4(knowledgeDir)) {
3485
+ const knowledgeDir = join7(projectRoot, ".fabric", "knowledge");
3486
+ if (existsSync5(knowledgeDir)) {
3421
3487
  const stack = [knowledgeDir];
3422
3488
  while (stack.length > 0) {
3423
3489
  const dir = stack.pop();
@@ -3425,7 +3491,7 @@ async function inspectStableIdCollisions(projectRoot) {
3425
3491
  continue;
3426
3492
  }
3427
3493
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
3428
- const abs = join6(dir, entry.name);
3494
+ const abs = join7(dir, entry.name);
3429
3495
  if (entry.isDirectory()) {
3430
3496
  stack.push(abs);
3431
3497
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -3490,11 +3556,11 @@ function inspectCounterDesync(meta) {
3490
3556
  }
3491
3557
  const layer = parsed.layer === "personal" ? "KP" : "KT";
3492
3558
  const typeCode = [
3493
- ["model", "MOD"],
3494
- ["decision", "DEC"],
3495
- ["guideline", "GLD"],
3496
- ["pitfall", "PIT"],
3497
- ["process", "PRO"]
3559
+ ["models", "MOD"],
3560
+ ["decisions", "DEC"],
3561
+ ["guidelines", "GLD"],
3562
+ ["pitfalls", "PIT"],
3563
+ ["processes", "PRO"]
3498
3564
  ].find(([t]) => t === parsed.type)?.[1];
3499
3565
  if (typeCode === void 0) {
3500
3566
  continue;
@@ -3604,18 +3670,18 @@ function createMetaManuallyDivergedCheck(t, inspection) {
3604
3670
  }
3605
3671
  function inspectPreexistingRootFiles(projectRoot) {
3606
3672
  const candidates = ["CLAUDE.md", "AGENTS.md"];
3607
- const detected = candidates.filter((name) => existsSync4(join6(projectRoot, name)));
3673
+ const detected = candidates.filter((name) => existsSync5(join7(projectRoot, name)));
3608
3674
  return { detected };
3609
3675
  }
3610
3676
  async function inspectFilesystemEditFallback(projectRoot) {
3611
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
3612
- if (!existsSync4(knowledgeRoot)) {
3677
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
3678
+ if (!existsSync5(knowledgeRoot)) {
3613
3679
  return { synthesized: 0, synthesizedStableIds: [] };
3614
3680
  }
3615
3681
  const canonicalIds = /* @__PURE__ */ new Set();
3616
3682
  for (const typeDir of KNOWLEDGE_CANONICAL_TYPE_DIRS) {
3617
- const dir = join6(knowledgeRoot, typeDir);
3618
- if (!existsSync4(dir)) {
3683
+ const dir = join7(knowledgeRoot, typeDir);
3684
+ if (!existsSync5(dir)) {
3619
3685
  continue;
3620
3686
  }
3621
3687
  let entries;
@@ -3655,17 +3721,40 @@ async function inspectFilesystemEditFallback(projectRoot) {
3655
3721
  }
3656
3722
  orphanIds.sort();
3657
3723
  for (const stable_id of orphanIds) {
3658
- await appendEventLedgerEvent(projectRoot, {
3659
- event_type: "knowledge_promoted",
3660
- stable_id,
3661
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3662
- reason: SYNTHESIZED_PROMOTED_REASON,
3663
- correlation_id: "doctor-synthesized",
3664
- session_id: "doctor-synthesized"
3665
- });
3724
+ await emitSynthesizedPromotionTriplet(projectRoot, stable_id);
3666
3725
  }
3667
3726
  return { synthesized: orphanIds.length, synthesizedStableIds: orphanIds };
3668
3727
  }
3728
+ async function emitSynthesizedPromotionTriplet(projectRoot, stable_id) {
3729
+ const baseTs = Date.now();
3730
+ await appendEventLedgerEvent(projectRoot, {
3731
+ event_type: "knowledge_proposed",
3732
+ stable_id,
3733
+ timestamp: new Date(baseTs).toISOString(),
3734
+ reason: SYNTHESIZED_PROMOTED_REASON,
3735
+ correlation_id: "doctor-synthesized",
3736
+ session_id: "doctor-synthesized",
3737
+ ts: baseTs
3738
+ });
3739
+ await appendEventLedgerEvent(projectRoot, {
3740
+ event_type: "knowledge_promote_started",
3741
+ stable_id,
3742
+ timestamp: new Date(baseTs + 1).toISOString(),
3743
+ reason: SYNTHESIZED_PROMOTED_REASON,
3744
+ correlation_id: "doctor-synthesized",
3745
+ session_id: "doctor-synthesized",
3746
+ ts: baseTs + 1
3747
+ });
3748
+ await appendEventLedgerEvent(projectRoot, {
3749
+ event_type: "knowledge_promoted",
3750
+ stable_id,
3751
+ timestamp: new Date(baseTs + 2).toISOString(),
3752
+ reason: SYNTHESIZED_PROMOTED_REASON,
3753
+ correlation_id: "doctor-synthesized",
3754
+ session_id: "doctor-synthesized",
3755
+ ts: baseTs + 2
3756
+ });
3757
+ }
3669
3758
  function createFilesystemEditFallbackCheck(t, inspection) {
3670
3759
  if (inspection.synthesized === 0) {
3671
3760
  return okCheck(
@@ -3824,13 +3913,13 @@ function extractKnowledgeFrontmatterCreatedAt(source) {
3824
3913
  return Number.isFinite(parsed) ? parsed : null;
3825
3914
  }
3826
3915
  function* iterateCanonicalEntries(projectRoot, lastActiveIndex) {
3827
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
3828
- if (!existsSync4(knowledgeRoot)) {
3916
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
3917
+ if (!existsSync5(knowledgeRoot)) {
3829
3918
  return;
3830
3919
  }
3831
3920
  for (const typeDir of KNOWLEDGE_CANONICAL_TYPE_DIRS) {
3832
- const dir = join6(knowledgeRoot, typeDir);
3833
- if (!existsSync4(dir)) {
3921
+ const dir = join7(knowledgeRoot, typeDir);
3922
+ if (!existsSync5(dir)) {
3834
3923
  continue;
3835
3924
  }
3836
3925
  let entries;
@@ -3848,10 +3937,10 @@ function* iterateCanonicalEntries(projectRoot, lastActiveIndex) {
3848
3937
  continue;
3849
3938
  }
3850
3939
  const stableId = match[1];
3851
- const absPath = join6(dir, entry.name);
3940
+ const absPath = join7(dir, entry.name);
3852
3941
  let source;
3853
3942
  try {
3854
- source = readFileSync2(absPath, "utf8");
3943
+ source = readFileSync3(absPath, "utf8");
3855
3944
  } catch {
3856
3945
  continue;
3857
3946
  }
@@ -3924,13 +4013,13 @@ async function inspectStaleArchive(projectRoot, now) {
3924
4013
  return { candidates };
3925
4014
  }
3926
4015
  function* iteratePendingFiles(projectRoot, now) {
3927
- const teamRoot = join6(projectRoot, ".fabric", "knowledge", "pending");
3928
- const personalRoot = join6(resolvePersonalRootForPending(), ".fabric", "knowledge", "pending");
4016
+ const teamRoot = join7(projectRoot, ".fabric", "knowledge", "pending");
4017
+ const personalRoot = join7(resolvePersonalRootForPending(), ".fabric", "knowledge", "pending");
3929
4018
  for (const [layer, root, displayPrefix] of [
3930
4019
  ["team", teamRoot, ".fabric/knowledge/pending"],
3931
4020
  ["personal", personalRoot, "~/.fabric/knowledge/pending"]
3932
4021
  ]) {
3933
- if (!existsSync4(root)) {
4022
+ if (!existsSync5(root)) {
3934
4023
  continue;
3935
4024
  }
3936
4025
  let typeDirs = [];
@@ -3940,7 +4029,7 @@ function* iteratePendingFiles(projectRoot, now) {
3940
4029
  continue;
3941
4030
  }
3942
4031
  for (const typeDir of typeDirs) {
3943
- const dir = join6(root, typeDir);
4032
+ const dir = join7(root, typeDir);
3944
4033
  let entries;
3945
4034
  try {
3946
4035
  entries = readdirSync(dir, { withFileTypes: true });
@@ -3951,10 +4040,10 @@ function* iteratePendingFiles(projectRoot, now) {
3951
4040
  if (!entry.isFile() || !entry.name.endsWith(".md")) {
3952
4041
  continue;
3953
4042
  }
3954
- const absPath = join6(dir, entry.name);
4043
+ const absPath = join7(dir, entry.name);
3955
4044
  let source = "";
3956
4045
  try {
3957
- source = readFileSync2(absPath, "utf8");
4046
+ source = readFileSync3(absPath, "utf8");
3958
4047
  } catch {
3959
4048
  continue;
3960
4049
  }
@@ -4026,7 +4115,7 @@ function inspectPendingAutoArchive(projectRoot, now) {
4026
4115
  pending_path: visit.pending_path,
4027
4116
  pending_path_abs: visit.pending_path_abs,
4028
4117
  archived_to: archivedToRel,
4029
- archived_to_abs: join6(projectRoot, archivedToRel),
4118
+ archived_to_abs: join7(projectRoot, archivedToRel),
4030
4119
  age_days: visit.age_days
4031
4120
  });
4032
4121
  } else {
@@ -4035,7 +4124,7 @@ function inspectPendingAutoArchive(projectRoot, now) {
4035
4124
  visit.type,
4036
4125
  visit.filename
4037
4126
  );
4038
- const archivedToAbs = join6(
4127
+ const archivedToAbs = join7(
4039
4128
  resolvePersonalRootForPending(),
4040
4129
  ".fabric",
4041
4130
  ".archive",
@@ -4059,12 +4148,12 @@ function inspectPendingAutoArchive(projectRoot, now) {
4059
4148
  }
4060
4149
  function inspectUnderseeded(projectRoot) {
4061
4150
  const threshold = readUnderseedThresholdFromConfig(projectRoot);
4062
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
4151
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
4063
4152
  let nodeCount = 0;
4064
- if (existsSync4(knowledgeRoot)) {
4153
+ if (existsSync5(knowledgeRoot)) {
4065
4154
  for (const typeDir of KNOWLEDGE_CANONICAL_TYPE_DIRS) {
4066
- const dir = join6(knowledgeRoot, typeDir);
4067
- if (!existsSync4(dir)) continue;
4155
+ const dir = join7(knowledgeRoot, typeDir);
4156
+ if (!existsSync5(dir)) continue;
4068
4157
  let entries;
4069
4158
  try {
4070
4159
  entries = readdirSync(dir, { withFileTypes: true });
@@ -4085,8 +4174,8 @@ function inspectUnderseeded(projectRoot) {
4085
4174
  };
4086
4175
  }
4087
4176
  function inspectSessionHintsStale(projectRoot, now) {
4088
- const cacheDir = join6(projectRoot, ".fabric", ".cache");
4089
- if (!existsSync4(cacheDir)) {
4177
+ const cacheDir = join7(projectRoot, ".fabric", ".cache");
4178
+ if (!existsSync5(cacheDir)) {
4090
4179
  return { candidates: [] };
4091
4180
  }
4092
4181
  let entries;
@@ -4100,7 +4189,7 @@ function inspectSessionHintsStale(projectRoot, now) {
4100
4189
  if (!entry.isFile()) continue;
4101
4190
  if (!entry.name.startsWith(SESSION_HINTS_FILE_PREFIX)) continue;
4102
4191
  if (!entry.name.endsWith(SESSION_HINTS_FILE_SUFFIX)) continue;
4103
- const absPath = join6(cacheDir, entry.name);
4192
+ const absPath = join7(cacheDir, entry.name);
4104
4193
  let mtimeMs = 0;
4105
4194
  try {
4106
4195
  mtimeMs = statSync4(absPath).mtimeMs;
@@ -4144,11 +4233,11 @@ function inspectNarrowTooFew(projectRoot, now) {
4144
4233
  const structuralFlagged = total >= NARROW_MIN_TOTAL && narrowRatio < NARROW_RATIO_THRESHOLD;
4145
4234
  const windowStartMs = now - SILENCE_WINDOW_DAYS * MS_PER_DAY;
4146
4235
  const editFires = readCounterTimestamps(
4147
- join6(projectRoot, EDIT_COUNTER_FILE_REL),
4236
+ join7(projectRoot, EDIT_COUNTER_FILE_REL),
4148
4237
  windowStartMs
4149
4238
  );
4150
4239
  const silenceFires = readCounterTimestamps(
4151
- join6(projectRoot, HINT_SILENCE_COUNTER_FILE_REL),
4240
+ join7(projectRoot, HINT_SILENCE_COUNTER_FILE_REL),
4152
4241
  windowStartMs
4153
4242
  );
4154
4243
  const telemetrySkipped = editFires === 0;
@@ -4167,10 +4256,10 @@ function inspectNarrowTooFew(projectRoot, now) {
4167
4256
  };
4168
4257
  }
4169
4258
  function readCounterTimestamps(absPath, windowStartMs) {
4170
- if (!existsSync4(absPath)) return 0;
4259
+ if (!existsSync5(absPath)) return 0;
4171
4260
  let raw;
4172
4261
  try {
4173
- raw = readFileSync2(absPath, "utf8");
4262
+ raw = readFileSync3(absPath, "utf8");
4174
4263
  } catch {
4175
4264
  return 0;
4176
4265
  }
@@ -4186,10 +4275,10 @@ function readCounterTimestamps(absPath, windowStartMs) {
4186
4275
  return count;
4187
4276
  }
4188
4277
  function readUnderseedThresholdFromConfig(projectRoot) {
4189
- const configPath = join6(projectRoot, ".fabric", "fabric-config.json");
4190
- if (!existsSync4(configPath)) return DEFAULT_UNDERSEED_NODE_THRESHOLD;
4278
+ const configPath = join7(projectRoot, ".fabric", "fabric-config.json");
4279
+ if (!existsSync5(configPath)) return DEFAULT_UNDERSEED_NODE_THRESHOLD;
4191
4280
  try {
4192
- const raw = readFileSync2(configPath, "utf8");
4281
+ const raw = readFileSync3(configPath, "utf8");
4193
4282
  const parsed = JSON.parse(raw);
4194
4283
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4195
4284
  const v = parsed.underseed_node_threshold;
@@ -4383,11 +4472,11 @@ function extractKnowledgeFrontmatterRelevancePaths(source) {
4383
4472
  }
4384
4473
  function* iterateRelevanceFrontmatter(projectRoot) {
4385
4474
  for (const visit of iterateCanonicalFilenames(projectRoot)) {
4386
- const layerRoot = visit.layer === "team" ? join6(projectRoot, ".fabric", "knowledge") : resolvePersonalKnowledgeRoot();
4387
- const absPath = join6(layerRoot, visit.type, visit.filename);
4475
+ const layerRoot = visit.layer === "team" ? join7(projectRoot, ".fabric", "knowledge") : resolvePersonalKnowledgeRoot();
4476
+ const absPath = join7(layerRoot, visit.type, visit.filename);
4388
4477
  let source;
4389
4478
  try {
4390
- source = readFileSync2(absPath, "utf8");
4479
+ source = readFileSync3(absPath, "utf8");
4391
4480
  } catch {
4392
4481
  continue;
4393
4482
  }
@@ -4449,7 +4538,7 @@ function inspectRelevancePathsDangling(projectRoot) {
4449
4538
  return { entries };
4450
4539
  }
4451
4540
  function collectWorkspacePathsForGlobMatch(projectRoot) {
4452
- if (!existsSync4(projectRoot)) {
4541
+ if (!existsSync5(projectRoot)) {
4453
4542
  return [];
4454
4543
  }
4455
4544
  let rootStat;
@@ -4473,7 +4562,7 @@ function collectWorkspacePathsForGlobMatch(projectRoot) {
4473
4562
  continue;
4474
4563
  }
4475
4564
  for (const entry of entries) {
4476
- const abs = join6(current, entry.name);
4565
+ const abs = join7(current, entry.name);
4477
4566
  const rel = normalizePath(abs.slice(projectRoot.length + 1));
4478
4567
  if (rel.length === 0) continue;
4479
4568
  if (entry.isDirectory()) {
@@ -4630,8 +4719,8 @@ function inspectRelevanceFieldsMissing(projectRoot) {
4630
4719
  const candidates = [];
4631
4720
  let scannedCount = 0;
4632
4721
  const FM_PATTERN = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---/u;
4633
- const teamRoot = join6(projectRoot, ".fabric", "knowledge", "pending");
4634
- const personalRoot = join6(
4722
+ const teamRoot = join7(projectRoot, ".fabric", "knowledge", "pending");
4723
+ const personalRoot = join7(
4635
4724
  resolvePersonalRootForPending(),
4636
4725
  ".fabric",
4637
4726
  "knowledge",
@@ -4641,7 +4730,7 @@ function inspectRelevanceFieldsMissing(projectRoot) {
4641
4730
  [teamRoot, ".fabric/knowledge/pending"],
4642
4731
  [personalRoot, "~/.fabric/knowledge/pending"]
4643
4732
  ]) {
4644
- if (!existsSync4(root)) {
4733
+ if (!existsSync5(root)) {
4645
4734
  continue;
4646
4735
  }
4647
4736
  let typeDirs = [];
@@ -4651,7 +4740,7 @@ function inspectRelevanceFieldsMissing(projectRoot) {
4651
4740
  continue;
4652
4741
  }
4653
4742
  for (const typeDir of typeDirs) {
4654
- const dir = join6(root, typeDir);
4743
+ const dir = join7(root, typeDir);
4655
4744
  let entries;
4656
4745
  try {
4657
4746
  entries = readdirSync(dir, { withFileTypes: true });
@@ -4662,10 +4751,10 @@ function inspectRelevanceFieldsMissing(projectRoot) {
4662
4751
  if (!entry.isFile() || !entry.name.endsWith(".md")) {
4663
4752
  continue;
4664
4753
  }
4665
- const absPath = join6(dir, entry.name);
4754
+ const absPath = join7(dir, entry.name);
4666
4755
  let source;
4667
4756
  try {
4668
- source = readFileSync2(absPath, "utf8");
4757
+ source = readFileSync3(absPath, "utf8");
4669
4758
  } catch {
4670
4759
  continue;
4671
4760
  }
@@ -4797,8 +4886,8 @@ var SKILL_QUOTED_VALUE_LEADS = /* @__PURE__ */ new Set(['"', "'", "[", "{", ">",
4797
4886
  function inspectSkillMdYamlInvalid(projectRoot) {
4798
4887
  const candidates = [];
4799
4888
  for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
4800
- const rootAbs = join6(projectRoot, rootRel);
4801
- if (!existsSync4(rootAbs)) continue;
4889
+ const rootAbs = join7(projectRoot, rootRel);
4890
+ if (!existsSync5(rootAbs)) continue;
4802
4891
  let dirEntries;
4803
4892
  try {
4804
4893
  dirEntries = readdirSync(rootAbs, { withFileTypes: true });
@@ -4807,11 +4896,11 @@ function inspectSkillMdYamlInvalid(projectRoot) {
4807
4896
  }
4808
4897
  for (const dirEntry of dirEntries) {
4809
4898
  if (!dirEntry.isDirectory()) continue;
4810
- const skillFile = join6(rootAbs, dirEntry.name, "SKILL.md");
4811
- if (!existsSync4(skillFile)) continue;
4899
+ const skillFile = join7(rootAbs, dirEntry.name, "SKILL.md");
4900
+ if (!existsSync5(skillFile)) continue;
4812
4901
  let raw;
4813
4902
  try {
4814
- raw = readFileSync2(skillFile, "utf8");
4903
+ raw = readFileSync3(skillFile, "utf8");
4815
4904
  } catch {
4816
4905
  continue;
4817
4906
  }
@@ -4893,11 +4982,11 @@ function inspectOnboardCoverage(projectRoot) {
4893
4982
  for (const slot of ONBOARD_SLOT_NAMES) {
4894
4983
  filled[slot] = [];
4895
4984
  }
4896
- const knowledgeRoot = join6(projectRoot, ".fabric", "knowledge");
4897
- if (existsSync4(knowledgeRoot)) {
4985
+ const knowledgeRoot = join7(projectRoot, ".fabric", "knowledge");
4986
+ if (existsSync5(knowledgeRoot)) {
4898
4987
  for (const typeDir of KNOWLEDGE_CANONICAL_TYPE_DIRS_FOR_ONBOARD) {
4899
- const dir = join6(knowledgeRoot, typeDir);
4900
- if (!existsSync4(dir)) continue;
4988
+ const dir = join7(knowledgeRoot, typeDir);
4989
+ if (!existsSync5(dir)) continue;
4901
4990
  let entries;
4902
4991
  try {
4903
4992
  entries = readdirSync(dir, { withFileTypes: true });
@@ -4907,10 +4996,10 @@ function inspectOnboardCoverage(projectRoot) {
4907
4996
  for (const entry of entries) {
4908
4997
  if (!entry.isFile()) continue;
4909
4998
  if (!entry.name.endsWith(".md")) continue;
4910
- const filePath = join6(dir, entry.name);
4999
+ const filePath = join7(dir, entry.name);
4911
5000
  let content;
4912
5001
  try {
4913
- content = readFileSync2(filePath, "utf8");
5002
+ content = readFileSync3(filePath, "utf8");
4914
5003
  } catch {
4915
5004
  continue;
4916
5005
  }
@@ -4934,11 +5023,11 @@ function inspectOnboardCoverage(projectRoot) {
4934
5023
  return { filled, missing, opted_out: optedOut };
4935
5024
  }
4936
5025
  function readOnboardOptedOut(projectRoot) {
4937
- const path2 = join6(projectRoot, ".fabric", "fabric-config.json");
4938
- if (!existsSync4(path2)) return [];
5026
+ const path2 = join7(projectRoot, ".fabric", "fabric-config.json");
5027
+ if (!existsSync5(path2)) return [];
4939
5028
  let raw;
4940
5029
  try {
4941
- raw = readFileSync2(path2, "utf8");
5030
+ raw = readFileSync3(path2, "utf8");
4942
5031
  } catch {
4943
5032
  return [];
4944
5033
  }
@@ -5054,7 +5143,7 @@ function createNarrowTooFewCheck(t, inspection) {
5054
5143
  }
5055
5144
  function resolvePersonalKnowledgeRoot() {
5056
5145
  const home = process.env.FABRIC_HOME ?? homedir3();
5057
- return join6(home, ".fabric", "knowledge");
5146
+ return join7(home, ".fabric", "knowledge");
5058
5147
  }
5059
5148
  function parseStableIdFromCanonicalFilename(filename) {
5060
5149
  const match = CANONICAL_KNOWLEDGE_FILENAME_PATTERN.exec(filename);
@@ -5074,18 +5163,18 @@ function parseStableIdFromCanonicalFilename(filename) {
5074
5163
  };
5075
5164
  }
5076
5165
  function* iterateCanonicalFilenames(projectRoot) {
5077
- const teamRoot = join6(projectRoot, ".fabric", "knowledge");
5166
+ const teamRoot = join7(projectRoot, ".fabric", "knowledge");
5078
5167
  const personalRoot = resolvePersonalKnowledgeRoot();
5079
5168
  for (const [layer, root, displayPrefix] of [
5080
5169
  ["team", teamRoot, ".fabric/knowledge"],
5081
5170
  ["personal", personalRoot, "~/.fabric/knowledge"]
5082
5171
  ]) {
5083
- if (!existsSync4(root)) {
5172
+ if (!existsSync5(root)) {
5084
5173
  continue;
5085
5174
  }
5086
5175
  for (const typeDir of KNOWLEDGE_CANONICAL_TYPE_DIRS) {
5087
- const dir = join6(root, typeDir);
5088
- if (!existsSync4(dir)) {
5176
+ const dir = join7(root, typeDir);
5177
+ if (!existsSync5(dir)) {
5089
5178
  continue;
5090
5179
  }
5091
5180
  let entries;
@@ -5256,8 +5345,8 @@ async function migrateBootstrapMarkers(projectRoot) {
5256
5345
  const paths = [];
5257
5346
  const countPerPath = {};
5258
5347
  for (const rel of BOOTSTRAP_MARKER_MIGRATION_TARGETS) {
5259
- const abs = join6(projectRoot, rel);
5260
- if (!existsSync4(abs)) {
5348
+ const abs = join7(projectRoot, rel);
5349
+ if (!existsSync5(abs)) {
5261
5350
  continue;
5262
5351
  }
5263
5352
  let original;
@@ -5283,8 +5372,8 @@ async function migrateBootstrapMarkers(projectRoot) {
5283
5372
  return { paths, countPerPath };
5284
5373
  }
5285
5374
  async function rewriteThreeEndManagedBlocks(projectRoot) {
5286
- const snapshotPath = join6(projectRoot, ".fabric", "AGENTS.md");
5287
- if (!existsSync4(snapshotPath)) {
5375
+ const snapshotPath = join7(projectRoot, ".fabric", "AGENTS.md");
5376
+ if (!existsSync5(snapshotPath)) {
5288
5377
  return;
5289
5378
  }
5290
5379
  let snapshot;
@@ -5293,8 +5382,8 @@ async function rewriteThreeEndManagedBlocks(projectRoot) {
5293
5382
  } catch {
5294
5383
  return;
5295
5384
  }
5296
- const projectRulesPath = join6(projectRoot, ".fabric", "project-rules.md");
5297
- const hasProjectRules = existsSync4(projectRulesPath);
5385
+ const projectRulesPath = join7(projectRoot, ".fabric", "project-rules.md");
5386
+ const hasProjectRules = existsSync5(projectRulesPath);
5298
5387
  let expectedBody = snapshot;
5299
5388
  if (hasProjectRules) {
5300
5389
  try {
@@ -5309,11 +5398,11 @@ ${projectRules}`;
5309
5398
  ${expectedBody}
5310
5399
  ${BOOTSTRAP_MARKER_END}`;
5311
5400
  const blockTargets = [
5312
- join6(projectRoot, "AGENTS.md"),
5313
- join6(projectRoot, ".cursor", "rules", "fabric-bootstrap.mdc")
5401
+ join7(projectRoot, "AGENTS.md"),
5402
+ join7(projectRoot, ".cursor", "rules", "fabric-bootstrap.mdc")
5314
5403
  ];
5315
5404
  for (const abs of blockTargets) {
5316
- if (!existsSync4(abs)) {
5405
+ if (!existsSync5(abs)) {
5317
5406
  continue;
5318
5407
  }
5319
5408
  let existing;
@@ -5343,8 +5432,8 @@ ${managedBlock}
5343
5432
  }
5344
5433
  await atomicWriteText4(abs, next);
5345
5434
  }
5346
- const claudeMdPath = join6(projectRoot, "CLAUDE.md");
5347
- if (existsSync4(claudeMdPath)) {
5435
+ const claudeMdPath = join7(projectRoot, "CLAUDE.md");
5436
+ if (existsSync5(claudeMdPath)) {
5348
5437
  let claudeContent;
5349
5438
  try {
5350
5439
  claudeContent = await readFile5(claudeMdPath, "utf8");
@@ -5372,13 +5461,13 @@ ${managedBlock}
5372
5461
  }
5373
5462
  }
5374
5463
  async function fixMcpConfigInWrongFile(projectRoot) {
5375
- const settingsPath = join6(projectRoot, ".claude", "settings.json");
5376
- if (!existsSync4(settingsPath)) {
5464
+ const settingsPath = join7(projectRoot, ".claude", "settings.json");
5465
+ if (!existsSync5(settingsPath)) {
5377
5466
  return;
5378
5467
  }
5379
5468
  let settings;
5380
5469
  try {
5381
- const parsed = JSON.parse(readFileSync2(settingsPath, "utf8"));
5470
+ const parsed = JSON.parse(readFileSync3(settingsPath, "utf8"));
5382
5471
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
5383
5472
  return;
5384
5473
  }
@@ -5406,12 +5495,12 @@ async function fixMcpConfigInWrongFile(projectRoot) {
5406
5495
  }
5407
5496
  async function ensureKnowledgeSubdirs(projectRoot) {
5408
5497
  for (const sub of KNOWLEDGE_SUBDIRS3) {
5409
- await mkdir4(join6(projectRoot, ".fabric", "knowledge", sub), { recursive: true });
5498
+ await mkdir4(join7(projectRoot, ".fabric", "knowledge", sub), { recursive: true });
5410
5499
  }
5411
5500
  }
5412
5501
  async function fixCounterDesync(projectRoot) {
5413
- const metaPath = join6(projectRoot, ".fabric", "agents.meta.json");
5414
- if (!existsSync4(metaPath)) {
5502
+ const metaPath = join7(projectRoot, ".fabric", "agents.meta.json");
5503
+ if (!existsSync5(metaPath)) {
5415
5504
  return;
5416
5505
  }
5417
5506
  let meta;
@@ -5829,8 +5918,8 @@ async function runDoctorCiteCoverage(projectRoot, options) {
5829
5918
  continue;
5830
5919
  }
5831
5920
  bumpLayerType(citeId, kbType);
5832
- if (kbType === "decision" || kbType === "pitfall") {
5833
- if (kbType === "decision") decisionsCited += 1;
5921
+ if (kbType === "decisions" || kbType === "pitfalls") {
5922
+ if (kbType === "decisions") decisionsCited += 1;
5834
5923
  else pitfallsCited += 1;
5835
5924
  const commitment = commitments[i];
5836
5925
  const operators = commitment?.operators ?? [];
@@ -5994,7 +6083,7 @@ function normalizePath(path2) {
5994
6083
  return posix.normalize(path2.split("\\").join("/"));
5995
6084
  }
5996
6085
  function collectEntryPoints(root) {
5997
- if (!existsSync4(root) || !statSync4(root).isDirectory()) {
6086
+ if (!existsSync5(root) || !statSync4(root).isDirectory()) {
5998
6087
  return [];
5999
6088
  }
6000
6089
  const entries = [];
@@ -6005,7 +6094,7 @@ function collectEntryPoints(root) {
6005
6094
  continue;
6006
6095
  }
6007
6096
  for (const entry of readdirSync(current, { withFileTypes: true })) {
6008
- const absolutePath = join6(current, entry.name);
6097
+ const absolutePath = join7(current, entry.name);
6009
6098
  const relativePath = normalizePath(absolutePath.slice(root.length + 1));
6010
6099
  if (relativePath.length === 0) {
6011
6100
  continue;
@@ -6071,14 +6160,14 @@ var ENRICH_DESC_FIELD_PATTERNS = {
6071
6160
  async function enrichDescriptions(projectRoot, opts = {}) {
6072
6161
  const auto = opts.auto === true;
6073
6162
  const dryRun = opts.dryRun === true;
6074
- const mode = auto ? "auto" : "interactive";
6163
+ const mode = auto ? dryRun ? "preview" : "auto" : "readonly";
6075
6164
  const candidates = [];
6076
6165
  let scanned = 0;
6077
6166
  let modified = 0;
6078
6167
  let skipped = 0;
6079
6168
  for (const visit of iterateCanonicalFilenames(projectRoot)) {
6080
- const layerRoot = visit.layer === "team" ? join6(projectRoot, ".fabric", "knowledge") : resolvePersonalKnowledgeRoot();
6081
- const absPath = join6(layerRoot, visit.type, visit.filename);
6169
+ const layerRoot = visit.layer === "team" ? join7(projectRoot, ".fabric", "knowledge") : resolvePersonalKnowledgeRoot();
6170
+ const absPath = join7(layerRoot, visit.type, visit.filename);
6082
6171
  scanned += 1;
6083
6172
  let source;
6084
6173
  try {
@@ -6281,13 +6370,9 @@ async function emitAutoHealEventBestEffort(projectRoot, payload) {
6281
6370
 
6282
6371
  // src/services/get-knowledge.ts
6283
6372
  import { readFile as readFile6 } from "fs/promises";
6284
- import { join as join7 } from "path";
6373
+ import { join as join8 } from "path";
6374
+ import { deriveAgentsMetaLayer as deriveAgentsMetaLayer2 } from "@fenglimg/fabric-shared";
6285
6375
  import { minimatch as minimatch2 } from "minimatch";
6286
- var PRIORITY_ORDER = {
6287
- high: 0,
6288
- medium: 1,
6289
- low: 2
6290
- };
6291
6376
  async function getKnowledge(projectRoot, input) {
6292
6377
  const metaResult = await loadActiveMeta(projectRoot, { caller: "getKnowledge" });
6293
6378
  if (metaResult.auto_healed) {
@@ -6325,7 +6410,7 @@ async function loadGetKnowledgeContext(projectRoot) {
6325
6410
  return cached;
6326
6411
  }
6327
6412
  const meta = await readAgentsMeta(projectRoot);
6328
- const l0Content = await readFile6(join7(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
6413
+ const l0Content = await readFile6(join8(projectRoot, ".fabric", "bootstrap", "README.md"), "utf8");
6329
6414
  const context = {
6330
6415
  meta,
6331
6416
  l0Content,
@@ -6344,12 +6429,7 @@ function normalizeKnowledgePath(value) {
6344
6429
  }
6345
6430
  function matchRuleNodes(meta, path2) {
6346
6431
  const requestedPath = normalizeKnowledgePath(path2);
6347
- return Object.entries(meta.nodes).filter(([, node]) => shouldLoadNodeForPath(requestedPath, node)).sort((left, right) => {
6348
- const [leftId, leftNode] = left;
6349
- const [rightId, rightNode] = right;
6350
- const priorityDelta = PRIORITY_ORDER[leftNode.priority ?? "medium"] - PRIORITY_ORDER[rightNode.priority ?? "medium"];
6351
- return priorityDelta !== 0 ? priorityDelta : leftId.localeCompare(rightId);
6352
- }).map(([nodeId, node]) => ({
6432
+ return Object.entries(meta.nodes).filter(([, node]) => shouldLoadNodeForPath(requestedPath, node)).sort((left, right) => left[0].localeCompare(right[0])).map(([nodeId, node]) => ({
6353
6433
  node_id: nodeId,
6354
6434
  level: classifyNode(nodeId, node),
6355
6435
  stable_id: node.stable_id ?? nodeId,
@@ -6403,7 +6483,8 @@ function classifyNode(nodeId, node) {
6403
6483
  if (nodeId.startsWith("L2/")) {
6404
6484
  return "L2";
6405
6485
  }
6406
- return node.layer === "L0" ? null : node.layer ?? null;
6486
+ const layer = node.level ?? deriveAgentsMetaLayer2(node.file);
6487
+ return layer === "L0" ? null : layer;
6407
6488
  }
6408
6489
  function partitionRulesByLevel(loadedRules, dedupeByPath) {
6409
6490
  const l1 = [];
@@ -6464,7 +6545,7 @@ async function readRuleContent(projectRoot, file, fileContentCache) {
6464
6545
  if (cached !== void 0) {
6465
6546
  return await cached;
6466
6547
  }
6467
- const pending = readFile6(join7(projectRoot, file), "utf8");
6548
+ const pending = readFile6(join8(projectRoot, file), "utf8");
6468
6549
  fileContentCache.set(file, pending);
6469
6550
  return await pending;
6470
6551
  }
@@ -6499,6 +6580,8 @@ export {
6499
6580
  invalidateKnowledgeSyncCooldown,
6500
6581
  ensureKnowledgeFresh,
6501
6582
  reconcileKnowledge,
6583
+ readPayloadLimits,
6584
+ readSelectionTokenTtlMs,
6502
6585
  loadActiveMeta,
6503
6586
  loadActiveMetaOrStale,
6504
6587
  getKnowledge,