@fenglimg/fabric-server 2.0.0-rc.34 → 2.0.0-rc.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -88,7 +88,7 @@ import { 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}`, {
91
- actionHint: opts?.actionHint ?? "Run `fab install` to scaffold the .fabric/agents.meta.json file"
91
+ actionHint: opts?.actionHint ?? "Run `fabric install` to scaffold the .fabric/agents.meta.json file"
92
92
  });
93
93
  this.metaPath = metaPath;
94
94
  }
@@ -241,7 +241,7 @@ async function appendEventLedgerEvent(projectRoot, event) {
241
241
  if (size > EVENT_LEDGER_SIZE_WARN_BYTES) {
242
242
  warnedOversize = true;
243
243
  process.stderr.write(
244
- 'fabric: events.jsonl > 50MB, run "fab doctor --fix" to rotate\n'
244
+ 'fabric: events.jsonl > 50MB, run "fabric doctor --fix" to rotate\n'
245
245
  );
246
246
  }
247
247
  } catch {
@@ -1280,7 +1280,7 @@ function validateFrontmatter(source, filePath, throwOnInvalid) {
1280
1280
  const msg = `Unterminated YAML frontmatter in ${filePath}`;
1281
1281
  if (throwOnInvalid) {
1282
1282
  throw new RuleValidationError(msg, {
1283
- actionHint: "Run `fab doctor --fix` to repair frontmatter",
1283
+ actionHint: "Run `fabric doctor --fix` to repair frontmatter",
1284
1284
  fixable: true,
1285
1285
  details: { file: filePath }
1286
1286
  });
@@ -1288,7 +1288,7 @@ function validateFrontmatter(source, filePath, throwOnInvalid) {
1288
1288
  return {
1289
1289
  code: "rule_frontmatter_invalid",
1290
1290
  file: filePath,
1291
- action_hint: "Run `fab doctor --fix` to repair frontmatter"
1291
+ action_hint: "Run `fabric doctor --fix` to repair frontmatter"
1292
1292
  };
1293
1293
  }
1294
1294
  const frontmatter = source.slice(3, endIdx).trim();
@@ -1301,7 +1301,7 @@ function validateFrontmatter(source, filePath, throwOnInvalid) {
1301
1301
  const msg = `Invalid YAML frontmatter line "${trimmed}" in ${filePath}`;
1302
1302
  if (throwOnInvalid) {
1303
1303
  throw new RuleValidationError(msg, {
1304
- actionHint: "Run `fab doctor --fix` to repair frontmatter",
1304
+ actionHint: "Run `fabric doctor --fix` to repair frontmatter",
1305
1305
  fixable: true,
1306
1306
  details: { file: filePath, line: trimmed }
1307
1307
  });
@@ -1309,7 +1309,7 @@ function validateFrontmatter(source, filePath, throwOnInvalid) {
1309
1309
  return {
1310
1310
  code: "rule_frontmatter_invalid",
1311
1311
  file: filePath,
1312
- action_hint: "Run `fab doctor --fix` to repair frontmatter"
1312
+ action_hint: "Run `fabric doctor --fix` to repair frontmatter"
1313
1313
  };
1314
1314
  }
1315
1315
  }
@@ -1634,13 +1634,14 @@ function checkLockOrThrow(projectRoot, opts) {
1634
1634
  }
1635
1635
 
1636
1636
  // src/services/doctor.ts
1637
- import { execFileSync } from "child_process";
1637
+ import { execFileSync, spawnSync } from "child_process";
1638
1638
  import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync3, statSync as statSync4 } from "fs";
1639
1639
  import { access, mkdir as mkdir4, readFile as readFile5, rename, unlink, writeFile as writeFile2 } from "fs/promises";
1640
1640
  import { constants } from "fs";
1641
1641
  import { homedir as homedir3 } from "os";
1642
1642
  import { isAbsolute as isAbsolute2, join as join7, posix, relative as nodeRelative, resolve as resolve3, sep as sep3 } from "path";
1643
1643
  import { minimatch } from "minimatch";
1644
+ import { ZodError } from "zod";
1644
1645
  import {
1645
1646
  agentsMetaSchema as agentsMetaSchema4,
1646
1647
  AgentsMetaCountersSchema,
@@ -1740,6 +1741,7 @@ var HINT_SILENCE_COUNTER_FILE_REL = posix.join(
1740
1741
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
1741
1742
  var MATURITY_LINE_PATTERN = /^maturity:\s*("?)(stable|endorsed|draft)\1\s*$/mu;
1742
1743
  var CREATED_AT_LINE_PATTERN = /^created_at:\s*("?)([^"\n]+)\1\s*$/mu;
1744
+ var TAGS_LINE_PATTERN = /^tags:\s*\[(.*)\]\s*$/mu;
1743
1745
  var RELEVANCE_SCOPE_LINE_PATTERN = /^relevance_scope:\s*("?)(narrow|broad)\1\s*$/mu;
1744
1746
  var RELEVANCE_PATHS_LINE_PATTERN = /^relevance_paths:\s*\[([^\]]*)\]\s*$/mu;
1745
1747
  var RELEVANCE_PATHS_DRIFT_WINDOW_DAYS = 90;
@@ -1829,6 +1831,8 @@ async function runDoctorReport(target) {
1829
1831
  const skillDescription = inspectSkillDescription(projectRoot);
1830
1832
  const citeGoodhart = await inspectCiteGoodhart(projectRoot);
1831
1833
  const draftBacklog = inspectDraftBacklog(projectRoot);
1834
+ const knowledgeTagsEmpty = inspectKnowledgeTagsEmpty(projectRoot);
1835
+ const driftUnconsumed = await inspectDriftUnconsumed(projectRoot);
1832
1836
  const metaManuallyDiverged = await inspectMetaManuallyDiverged(projectRoot);
1833
1837
  const knowledgeDirUnindexed = inspectKnowledgeDirUnindexed(projectRoot, meta);
1834
1838
  const knowledgeDirMissing = inspectKnowledgeDirMissing(projectRoot);
@@ -1857,6 +1861,7 @@ async function runDoctorReport(target) {
1857
1861
  const onboardCoverage = inspectOnboardCoverage(projectRoot);
1858
1862
  const hooksWired = inspectHooksWired(projectRoot);
1859
1863
  const promoteLedgerInvariant = eventLedger.exists && eventLedger.writable && eventLedger.parseable ? await inspectPromoteLedgerInvariant(projectRoot) : null;
1864
+ const globalCliVersion = process.env.VITEST === "true" ? { status: "ok", version: "test-skipped" } : inspectGlobalCliVersion();
1860
1865
  const checks = [
1861
1866
  createBootstrapAnchorCheck(t, bootstrapAnchor),
1862
1867
  // v2.0.0-rc.19 TASK-004: bootstrap marker migration check sits adjacent to
@@ -1880,7 +1885,7 @@ async function runDoctorReport(target) {
1880
1885
  // The file's absence is a legitimate post-init state when the skill has
1881
1886
  // not yet run, so flagging it as a doctor manual_error misrepresents
1882
1887
  // ownership.
1883
- createMetaCheck(t, meta),
1888
+ createMetaCheck(t, meta, globalCliVersion),
1884
1889
  createRuleContentRefCheck(t, meta),
1885
1890
  // v2.0 / rc.2: `createRuleSectionsCheck` removed — it parsed v1.x
1886
1891
  // [MANDATORY_INJECTION] sections out of legacy rule files, a structural
@@ -1896,12 +1901,14 @@ async function runDoctorReport(target) {
1896
1901
  // v2.0.0-rc.28 TASK-04 (audit §3.1 follow-up): SKILL ref/ mirror parity.
1897
1902
  // Detects hand-edits or partial install that breaks the byte-identical
1898
1903
  // contract between .claude/skills/<slug>/ref/ and .codex/skills/<slug>/
1899
- // ref/. warning severity — fab install restores parity.
1904
+ // ref/. warning severity — fabric install restores parity.
1900
1905
  createSkillRefMirrorCheck(t, skillRefMirror),
1901
1906
  createSkillTokenBudgetCheck(t, skillTokenBudget),
1902
1907
  createSkillDescriptionCheck(t, skillDescription),
1903
1908
  createCiteGoodhartCheck(t, citeGoodhart),
1904
1909
  createDraftBacklogCheck(t, draftBacklog),
1910
+ createKnowledgeTagsEmptyCheck(t, knowledgeTagsEmpty),
1911
+ createDriftUnconsumedCheck(t, driftUnconsumed),
1905
1912
  createMcpConfigInWrongFileCheck(t, mcpConfigInWrongFile),
1906
1913
  createMetaManuallyDivergedCheck(t, metaManuallyDiverged),
1907
1914
  createKnowledgeDirUnindexedCheck(t, knowledgeDirUnindexed),
@@ -1958,6 +1965,15 @@ async function runDoctorReport(target) {
1958
1965
  // rc.31 BUG-M3/NEW-4: hooks_wired observability. Adjacent to onboard /
1959
1966
  // promote-ledger checks — all three are install/runtime-state advisories.
1960
1967
  createHooksWiredCheck(t, hooksWired),
1968
+ // rc.35 TASK-04 (P0-9.b): global CLI version probe — surfaces rc.30 PATH
1969
+ // installs against rc.31+ project schemas (the silent-hooks fault mode).
1970
+ // Sits next to hooks_wired since both lints diagnose runtime install state.
1971
+ createGlobalCliVersionCheck(t, globalCliVersion),
1972
+ // rc.35 TASK-05 (P0-10.a): opaque-summary ratio — surfaces the
1973
+ // werewolf-eval failure mode where description.summary == stable_id so
1974
+ // hint output is "KT-PIT-0001 · KT-PIT-0001" (AI skips fetch). Built
1975
+ // from the same MetaInspection so no extra disk reads.
1976
+ createKnowledgeSummaryOpaqueCheck(t, inspectKnowledgeSummaryOpaque(meta)),
1961
1977
  // rc.31 BUG-G2/G5: promote-ledger invariant. Sits adjacent to onboard
1962
1978
  // coverage — both are observability advisories built off events.jsonl.
1963
1979
  ...promoteLedgerInvariant === null ? [] : [createPromoteLedgerInvariantCheck(t, promoteLedgerInvariant)],
@@ -2615,6 +2631,17 @@ async function inspectMeta(projectRoot) {
2615
2631
  changed: built?.changed ?? true
2616
2632
  };
2617
2633
  }
2634
+ let readErrorKind = "other";
2635
+ let readErrorZodIssues;
2636
+ if (error instanceof ZodError) {
2637
+ readErrorKind = "zod";
2638
+ readErrorZodIssues = error.issues.slice(0, 3).map((issue) => ({
2639
+ path: issue.path.length > 0 ? issue.path.join(".") : "<root>",
2640
+ message: issue.message
2641
+ }));
2642
+ } else if (error instanceof SyntaxError) {
2643
+ readErrorKind = "json";
2644
+ }
2618
2645
  return {
2619
2646
  present: true,
2620
2647
  valid: false,
@@ -2622,6 +2649,8 @@ async function inspectMeta(projectRoot) {
2622
2649
  revision: null,
2623
2650
  computedRevision: built?.meta.revision ?? null,
2624
2651
  ruleCount: 0,
2652
+ readErrorKind,
2653
+ readErrorZodIssues,
2625
2654
  missingContentRefs: [],
2626
2655
  invalidContentRefs: [],
2627
2656
  stale: true,
@@ -2956,6 +2985,35 @@ function inspectDraftBacklog(projectRoot) {
2956
2985
  ratio
2957
2986
  };
2958
2987
  }
2988
+ function inspectKnowledgeTagsEmpty(projectRoot) {
2989
+ const EMPTY_TAGS_RATIO_THRESHOLD = 0.5;
2990
+ const MIN_TOTAL_FOR_RATIO = 10;
2991
+ let emptyCount = 0;
2992
+ let totalCount = 0;
2993
+ for (const entry of iterateCanonicalEntries(projectRoot, /* @__PURE__ */ new Map())) {
2994
+ let source;
2995
+ try {
2996
+ source = readFileSync3(entry.absPath, "utf8");
2997
+ } catch {
2998
+ continue;
2999
+ }
3000
+ const isEmpty = isKnowledgeFrontmatterTagsEmpty(source);
3001
+ if (isEmpty === null || isEmpty === true) {
3002
+ emptyCount += 1;
3003
+ }
3004
+ totalCount += 1;
3005
+ }
3006
+ if (totalCount < MIN_TOTAL_FOR_RATIO) {
3007
+ return { status: "ok", emptyCount, totalCount, ratio: 0 };
3008
+ }
3009
+ const ratio = emptyCount / totalCount;
3010
+ return {
3011
+ status: ratio > EMPTY_TAGS_RATIO_THRESHOLD ? "warn" : "ok",
3012
+ emptyCount,
3013
+ totalCount,
3014
+ ratio
3015
+ };
3016
+ }
2959
3017
  async function inspectKnowledgeTestIndex(projectRoot) {
2960
3018
  const path2 = join7(projectRoot, ".fabric", ".cache", "knowledge-test.index.json");
2961
3019
  const built = await tryBuildRuleMeta(projectRoot);
@@ -3325,7 +3383,7 @@ function createForensicCheck(t, forensic, frameworkKind, entryPointCount) {
3325
3383
  t("doctor.check.forensic.ok", { frameworkKind: forensic.report?.framework.kind ?? "unknown" })
3326
3384
  );
3327
3385
  }
3328
- function createMetaCheck(t, meta) {
3386
+ function createMetaCheck(t, meta, globalCli) {
3329
3387
  if (!meta.present) {
3330
3388
  return issueCheck(
3331
3389
  t("doctor.check.agents_meta.name"),
@@ -3337,6 +3395,30 @@ function createMetaCheck(t, meta) {
3337
3395
  );
3338
3396
  }
3339
3397
  if (!meta.valid) {
3398
+ if (globalCli && globalCli.status === "outdated") {
3399
+ return issueCheck(
3400
+ t("doctor.check.agents_meta.name"),
3401
+ "error",
3402
+ "manual_error",
3403
+ "agents_meta_invalid_global_cli_outdated",
3404
+ t("doctor.check.agents_meta.message.invalid-from-old-cli", {
3405
+ version: globalCli.version,
3406
+ minVersion: globalCli.minVersion
3407
+ }),
3408
+ t("doctor.check.global_cli_outdated.remediation")
3409
+ );
3410
+ }
3411
+ if (meta.readErrorKind === "zod" && meta.readErrorZodIssues && meta.readErrorZodIssues.length > 0) {
3412
+ const formatted = meta.readErrorZodIssues.map((issue) => `${issue.path}: ${issue.message}`).join("; ");
3413
+ return issueCheck(
3414
+ t("doctor.check.agents_meta.name"),
3415
+ "error",
3416
+ "manual_error",
3417
+ "agents_meta_invalid",
3418
+ t("doctor.check.agents_meta.message.invalid-zod", { issues: formatted }),
3419
+ t("doctor.check.agents_meta.remediation.invalid")
3420
+ );
3421
+ }
3340
3422
  return issueCheck(
3341
3423
  t("doctor.check.agents_meta.name"),
3342
3424
  "error",
@@ -3347,15 +3429,15 @@ function createMetaCheck(t, meta) {
3347
3429
  );
3348
3430
  }
3349
3431
  if (meta.stale) {
3432
+ const revision = meta.revision;
3433
+ const computedRevision = meta.computedRevision ?? "<unknown>";
3434
+ const messageKey = revision !== null && revision === meta.computedRevision ? "doctor.check.agents_meta.message.stale_hash_equal" : "doctor.check.agents_meta.message.stale";
3350
3435
  return issueCheck(
3351
3436
  t("doctor.check.agents_meta.name"),
3352
3437
  "warn",
3353
3438
  "warning",
3354
3439
  "agents_meta_stale",
3355
- t("doctor.check.agents_meta.message.stale", {
3356
- revision: meta.revision,
3357
- computedRevision: meta.computedRevision ?? "<unknown>"
3358
- }),
3440
+ t(messageKey, { revision, computedRevision }),
3359
3441
  t("doctor.check.agents_meta.remediation.stale")
3360
3442
  );
3361
3443
  }
@@ -3569,7 +3651,11 @@ function createSkillTokenBudgetCheck(t, inspection) {
3569
3651
  count: String(count),
3570
3652
  list
3571
3653
  }),
3572
- t("doctor.check.skill_token_budget.remediation")
3654
+ t("doctor.check.skill_token_budget.remediation"),
3655
+ // rc.35 TASK-12 (P0-11): maintainer audience. Remediation points at
3656
+ // `packages/cli/templates/skills/*` source — only Fabric contributors
3657
+ // can act. CLI renderer folds by default; --verbose unfolds.
3658
+ "maintainer"
3573
3659
  );
3574
3660
  }
3575
3661
  function createDraftBacklogCheck(t, inspection) {
@@ -3593,6 +3679,71 @@ function createDraftBacklogCheck(t, inspection) {
3593
3679
  t("doctor.check.draft_backlog.remediation")
3594
3680
  );
3595
3681
  }
3682
+ async function inspectDriftUnconsumed(projectRoot) {
3683
+ const WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
3684
+ const MIN_DRIFT_FOR_WARN = 5;
3685
+ const cutoffMs = Date.now() - WINDOW_MS;
3686
+ let events = [];
3687
+ try {
3688
+ const result = await readEventLedger(projectRoot);
3689
+ events = result.events;
3690
+ } catch {
3691
+ return { status: "ok", driftCount: 0, demoteCount: 0 };
3692
+ }
3693
+ let driftCount = 0;
3694
+ let demoteCount = 0;
3695
+ for (const e of events) {
3696
+ if (e.ts < cutoffMs) continue;
3697
+ if (e.event_type === "knowledge_drift_detected") driftCount += 1;
3698
+ else if (e.event_type === "knowledge_demoted") demoteCount += 1;
3699
+ }
3700
+ const unconsumed = driftCount - demoteCount;
3701
+ return {
3702
+ status: unconsumed >= MIN_DRIFT_FOR_WARN ? "warn" : "ok",
3703
+ driftCount,
3704
+ demoteCount
3705
+ };
3706
+ }
3707
+ function createDriftUnconsumedCheck(t, inspection) {
3708
+ if (inspection.status === "ok") {
3709
+ return okCheck(
3710
+ t("doctor.check.drift_unconsumed.name"),
3711
+ t("doctor.check.drift_unconsumed.ok")
3712
+ );
3713
+ }
3714
+ return issueCheck(
3715
+ t("doctor.check.drift_unconsumed.name"),
3716
+ "warn",
3717
+ "warning",
3718
+ "knowledge_drift_unconsumed",
3719
+ t("doctor.check.drift_unconsumed.message", {
3720
+ driftCount: String(inspection.driftCount),
3721
+ demoteCount: String(inspection.demoteCount)
3722
+ }),
3723
+ t("doctor.check.drift_unconsumed.remediation")
3724
+ );
3725
+ }
3726
+ function createKnowledgeTagsEmptyCheck(t, inspection) {
3727
+ if (inspection.status === "ok") {
3728
+ return okCheck(
3729
+ t("doctor.check.knowledge_tags_empty.name"),
3730
+ t("doctor.check.knowledge_tags_empty.ok")
3731
+ );
3732
+ }
3733
+ const pct = Math.round(inspection.ratio * 100);
3734
+ return issueCheck(
3735
+ t("doctor.check.knowledge_tags_empty.name"),
3736
+ "warn",
3737
+ "warning",
3738
+ "knowledge_tags_empty_ratio",
3739
+ t("doctor.check.knowledge_tags_empty.message", {
3740
+ emptyCount: String(inspection.emptyCount),
3741
+ totalCount: String(inspection.totalCount),
3742
+ pct: String(pct)
3743
+ }),
3744
+ t("doctor.check.knowledge_tags_empty.remediation")
3745
+ );
3746
+ }
3596
3747
  function createCiteGoodhartCheck(t, inspection) {
3597
3748
  if (inspection.status === "ok") {
3598
3749
  return okCheck(
@@ -3611,7 +3762,11 @@ function createCiteGoodhartCheck(t, inspection) {
3611
3762
  count: String(count),
3612
3763
  list
3613
3764
  }),
3614
- t("doctor.check.cite_goodhart.remediation")
3765
+ t("doctor.check.cite_goodhart.remediation"),
3766
+ // rc.35 TASK-12 (P0-11): maintainer audience. G1/G2/G3/G5 are internal
3767
+ // pattern codes from the cite-policy design memo — npm end users have
3768
+ // no actionable lever for these. Fold by default; --verbose unfolds.
3769
+ "maintainer"
3615
3770
  );
3616
3771
  }
3617
3772
  function createSkillDescriptionCheck(t, inspection) {
@@ -3632,7 +3787,10 @@ function createSkillDescriptionCheck(t, inspection) {
3632
3787
  count: String(count),
3633
3788
  list
3634
3789
  }),
3635
- t("doctor.check.skill_description.remediation")
3790
+ t("doctor.check.skill_description.remediation"),
3791
+ // rc.35 TASK-12 (P0-11): maintainer audience. Remediation points at
3792
+ // `packages/cli/templates/skills/<slug>/SKILL.md` frontmatter.
3793
+ "maintainer"
3636
3794
  );
3637
3795
  }
3638
3796
  function createEventLedgerPartialWriteCheck(t, ledger) {
@@ -3794,7 +3952,157 @@ function createPromoteLedgerInvariantCheck(t, inspection) {
3794
3952
  t("doctor.check.promote_ledger_invariant.remediation")
3795
3953
  );
3796
3954
  }
3797
- function issueCheck(name, status, kind, code, message, actionHint) {
3955
+ var MIN_SUPPORTED_GLOBAL_CLI_VERSION = "2.0.0-rc.31";
3956
+ var defaultGlobalCliSpawn = () => {
3957
+ const res = spawnSync("fabric", ["-v"], {
3958
+ encoding: "utf8",
3959
+ timeout: 5e3,
3960
+ stdio: ["ignore", "pipe", "pipe"]
3961
+ });
3962
+ return { error: res.error ?? null, status: res.status, stdout: res.stdout };
3963
+ };
3964
+ function inspectGlobalCliVersion(spawn = defaultGlobalCliSpawn) {
3965
+ let res;
3966
+ try {
3967
+ res = spawn();
3968
+ } catch (e) {
3969
+ return { status: "unparseable", detail: e instanceof Error ? e.message : String(e) };
3970
+ }
3971
+ if (res.error) {
3972
+ if (res.error.code === "ENOENT") {
3973
+ return { status: "not-found" };
3974
+ }
3975
+ return { status: "unparseable", detail: res.error.message };
3976
+ }
3977
+ if (res.status !== 0) {
3978
+ return { status: "unparseable", detail: `exit ${res.status ?? "?"}` };
3979
+ }
3980
+ const raw = (res.stdout ?? "").trim();
3981
+ const m = /(\d+)\.(\d+)\.(\d+)-rc\.(\d+)/.exec(raw);
3982
+ if (!m) {
3983
+ return { status: "unparseable", detail: raw.slice(0, 80) };
3984
+ }
3985
+ const version = `${m[1]}.${m[2]}.${m[3]}-rc.${m[4]}`;
3986
+ const observedRc = Number(m[4]);
3987
+ const minMatch = /-rc\.(\d+)/.exec(MIN_SUPPORTED_GLOBAL_CLI_VERSION);
3988
+ const minRc = minMatch ? Number(minMatch[1]) : 0;
3989
+ if (observedRc < minRc) {
3990
+ return { status: "outdated", version, minVersion: MIN_SUPPORTED_GLOBAL_CLI_VERSION };
3991
+ }
3992
+ return { status: "ok", version };
3993
+ }
3994
+ function createGlobalCliVersionCheck(t, inspection) {
3995
+ if (inspection.status === "ok") {
3996
+ return okCheck(
3997
+ t("doctor.check.global_cli_outdated.name"),
3998
+ t("doctor.check.global_cli_outdated.ok", { version: inspection.version })
3999
+ );
4000
+ }
4001
+ if (inspection.status === "outdated") {
4002
+ return issueCheck(
4003
+ t("doctor.check.global_cli_outdated.name"),
4004
+ "error",
4005
+ "manual_error",
4006
+ "global_cli_outdated",
4007
+ t("doctor.check.global_cli_outdated.message.outdated", {
4008
+ version: inspection.version,
4009
+ minVersion: inspection.minVersion
4010
+ }),
4011
+ t("doctor.check.global_cli_outdated.remediation")
4012
+ );
4013
+ }
4014
+ if (inspection.status === "not-found") {
4015
+ return issueCheck(
4016
+ t("doctor.check.global_cli_outdated.name"),
4017
+ "warn",
4018
+ "warning",
4019
+ "global_cli_not_found",
4020
+ t("doctor.check.global_cli_outdated.message.not_found"),
4021
+ t("doctor.check.global_cli_outdated.remediation")
4022
+ );
4023
+ }
4024
+ return issueCheck(
4025
+ t("doctor.check.global_cli_outdated.name"),
4026
+ "warn",
4027
+ "warning",
4028
+ "global_cli_unparseable",
4029
+ t("doctor.check.global_cli_outdated.message.unparseable", { detail: inspection.detail }),
4030
+ t("doctor.check.global_cli_outdated.remediation")
4031
+ );
4032
+ }
4033
+ var KNOWLEDGE_SUMMARY_OPAQUE_THRESHOLD = 0.3;
4034
+ function inspectKnowledgeSummaryOpaque(meta) {
4035
+ const baseline = {
4036
+ totalWithDescription: 0,
4037
+ opaqueCount: 0,
4038
+ ratio: 0,
4039
+ threshold: KNOWLEDGE_SUMMARY_OPAQUE_THRESHOLD,
4040
+ opaqueSample: []
4041
+ };
4042
+ if (!meta.valid || meta.meta === null) {
4043
+ return { status: "skipped", ...baseline };
4044
+ }
4045
+ let total = 0;
4046
+ const opaqueIds = [];
4047
+ for (const node of Object.values(meta.meta.nodes)) {
4048
+ const description = node.description;
4049
+ const stableId = node.stable_id;
4050
+ if (!description || typeof stableId !== "string" || stableId.length === 0) {
4051
+ continue;
4052
+ }
4053
+ total += 1;
4054
+ const summary = (description.summary ?? "").trim();
4055
+ if (summary === stableId.trim()) {
4056
+ opaqueIds.push(stableId);
4057
+ }
4058
+ }
4059
+ if (total === 0) {
4060
+ return { status: "ok", ...baseline };
4061
+ }
4062
+ const ratio = opaqueIds.length / total;
4063
+ const status = ratio > KNOWLEDGE_SUMMARY_OPAQUE_THRESHOLD ? "warn" : "ok";
4064
+ return {
4065
+ status,
4066
+ totalWithDescription: total,
4067
+ opaqueCount: opaqueIds.length,
4068
+ ratio,
4069
+ threshold: KNOWLEDGE_SUMMARY_OPAQUE_THRESHOLD,
4070
+ opaqueSample: opaqueIds.slice(0, 5)
4071
+ };
4072
+ }
4073
+ function createKnowledgeSummaryOpaqueCheck(t, inspection) {
4074
+ if (inspection.status === "skipped") {
4075
+ return okCheck(
4076
+ t("doctor.check.knowledge_summary_opaque.name"),
4077
+ t("doctor.check.knowledge_summary_opaque.ok.skipped")
4078
+ );
4079
+ }
4080
+ if (inspection.status === "ok") {
4081
+ return okCheck(
4082
+ t("doctor.check.knowledge_summary_opaque.name"),
4083
+ t("doctor.check.knowledge_summary_opaque.ok", {
4084
+ opaque: String(inspection.opaqueCount),
4085
+ total: String(inspection.totalWithDescription)
4086
+ })
4087
+ );
4088
+ }
4089
+ const pct = Math.round(inspection.ratio * 1e3) / 10;
4090
+ return issueCheck(
4091
+ t("doctor.check.knowledge_summary_opaque.name"),
4092
+ "warn",
4093
+ "warning",
4094
+ "knowledge_summary_opaque",
4095
+ t("doctor.check.knowledge_summary_opaque.message.warn", {
4096
+ opaque: String(inspection.opaqueCount),
4097
+ total: String(inspection.totalWithDescription),
4098
+ pct: String(pct),
4099
+ threshold: String(Math.round(inspection.threshold * 100)),
4100
+ sample: inspection.opaqueSample.join(", ")
4101
+ }),
4102
+ t("doctor.check.knowledge_summary_opaque.remediation")
4103
+ );
4104
+ }
4105
+ function issueCheck(name, status, kind, code, message, actionHint, audience) {
3798
4106
  return {
3799
4107
  name,
3800
4108
  status,
@@ -3802,7 +4110,8 @@ function issueCheck(name, status, kind, code, message, actionHint) {
3802
4110
  code,
3803
4111
  fixable: kind === "fixable_error",
3804
4112
  message,
3805
- actionHint
4113
+ actionHint,
4114
+ audience
3806
4115
  };
3807
4116
  }
3808
4117
  function collectIssues(checks, kind) {
@@ -3810,7 +4119,8 @@ function collectIssues(checks, kind) {
3810
4119
  code: check.code ?? check.name,
3811
4120
  name: check.name,
3812
4121
  message: check.message,
3813
- actionHint: check.actionHint
4122
+ actionHint: check.actionHint,
4123
+ audience: check.audience
3814
4124
  }));
3815
4125
  }
3816
4126
  function findIssue(issues, code) {
@@ -4354,6 +4664,19 @@ function extractKnowledgeFrontmatterMaturity(source) {
4354
4664
  const match = MATURITY_LINE_PATTERN.exec(fm[1]);
4355
4665
  return match === null ? null : match[2];
4356
4666
  }
4667
+ function isKnowledgeFrontmatterTagsEmpty(source) {
4668
+ const FM_PATTERN = /^(?:)?---\r?\n([\s\S]*?)\r?\n---/u;
4669
+ const fm = FM_PATTERN.exec(source);
4670
+ if (fm === null) {
4671
+ return null;
4672
+ }
4673
+ const match = TAGS_LINE_PATTERN.exec(fm[1]);
4674
+ if (match === null) {
4675
+ return null;
4676
+ }
4677
+ const inner = match[1].replace(/[\s,]/g, "");
4678
+ return inner === "";
4679
+ }
4357
4680
  function extractKnowledgeFrontmatterCreatedAt(source) {
4358
4681
  const FM_PATTERN = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---/u;
4359
4682
  const fm = FM_PATTERN.exec(source);
@@ -14,7 +14,7 @@ import {
14
14
  readEventLedger,
15
15
  runDoctorReport,
16
16
  sha256
17
- } from "./chunk-Z23PAA5L.js";
17
+ } from "./chunk-5XXH2VZZ.js";
18
18
 
19
19
  // src/http.ts
20
20
  import { randomUUID as randomUUID2 } from "crypto";
@@ -944,7 +944,7 @@ function toPosixPath(path) {
944
944
  function buildRecommendations(input) {
945
945
  const recommendations = [];
946
946
  if (!input.hasExistingFabric) {
947
- recommendations.push("L0: Run `fab install` to scaffold the .fabric/ knowledge layout (decisions, pitfalls, guidelines, models, processes).");
947
+ recommendations.push("L0: Run `fabric install` to scaffold the .fabric/ knowledge layout (decisions, pitfalls, guidelines, models, processes).");
948
948
  }
949
949
  if (input.readmeQuality === "stub") {
950
950
  recommendations.push("L0: Expand README.md before promoting project facts into Fabric knowledge entries.");
@@ -980,7 +980,7 @@ function createLoopbackDenyMiddleware() {
980
980
  res,
981
981
  401,
982
982
  "UNAUTHORIZED",
983
- "FABRIC_AUTH_TOKEN is not set. Either export FABRIC_AUTH_TOKEN=<secret> before running `fab serve`, or pass `--allow-loopback-no-auth` to explicitly opt in to unauthenticated loopback access (security risk)."
983
+ "FABRIC_AUTH_TOKEN is not set. Either export FABRIC_AUTH_TOKEN=<secret> before running `fabric serve`, or pass `--allow-loopback-no-auth` to explicitly opt in to unauthenticated loopback access (security risk)."
984
984
  );
985
985
  };
986
986
  }
package/dist/index.d.ts CHANGED
@@ -32,6 +32,7 @@ type DoctorCheck = {
32
32
  code?: string;
33
33
  fixable?: boolean;
34
34
  actionHint?: string;
35
+ audience?: "user" | "maintainer";
35
36
  };
36
37
  type DoctorIssue = {
37
38
  code: string;
@@ -39,6 +40,7 @@ type DoctorIssue = {
39
40
  message: string;
40
41
  path?: string;
41
42
  actionHint?: string;
43
+ audience?: "user" | "maintainer";
42
44
  };
43
45
  type DoctorPayloadLimits = {
44
46
  warn_bytes: number;
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  sha256,
40
40
  stableStringify,
41
41
  writeKnowledgeMeta
42
- } from "./chunk-Z23PAA5L.js";
42
+ } from "./chunk-5XXH2VZZ.js";
43
43
 
44
44
  // src/index.ts
45
45
  import { existsSync as existsSync3 } from "fs";
@@ -59,13 +59,13 @@ function gateWarning(result) {
59
59
  return {
60
60
  code: "meta_stale",
61
61
  file: "<response>",
62
- action_hint: "Initial reconcile still pending; results may use cached meta. Retry shortly or run `fab doctor --fix`."
62
+ action_hint: "Initial reconcile still pending; results may use cached meta. Retry shortly or run `fabric doctor --fix`."
63
63
  };
64
64
  }
65
65
  return {
66
66
  code: "reconcile_failed",
67
67
  file: "<response>",
68
- action_hint: "Reconcile failed at startup; run `fab doctor --fix` and restart the MCP server."
68
+ action_hint: "Reconcile failed at startup; run `fabric doctor --fix` and restart the MCP server."
69
69
  };
70
70
  }
71
71
  var state = {
@@ -297,7 +297,7 @@ async function extractKnowledge(projectRoot, input) {
297
297
  // v2.0.0-rc.23 TASK-014 (F8c): optional S5 onboard-slot tag. Same emit
298
298
  // discipline as the four a-C1 fields — bare YAML line iff caller-supplied,
299
299
  // never in the idempotency_key hash. fabric-archive's first-run phase is
300
- // the only producer; downstream `fab onboard-coverage` walks frontmatter
300
+ // the only producer; downstream `fabric onboard-coverage` walks frontmatter
301
301
  // looking for this exact key.
302
302
  onboardSlot: input.onboard_slot
303
303
  });
@@ -2091,7 +2091,7 @@ function formatPreexistingRootMessage(projectRoot) {
2091
2091
  function createFabricServer(tracker) {
2092
2092
  const server = new McpServer({
2093
2093
  name: "fabric-knowledge-server",
2094
- version: "2.0.0-rc.34"
2094
+ version: "2.0.0-rc.36"
2095
2095
  });
2096
2096
  registerPlanContext(server, tracker);
2097
2097
  registerKnowledgeSections(server, tracker);
@@ -2199,7 +2199,7 @@ function createShutdownHandler(deps) {
2199
2199
  };
2200
2200
  }
2201
2201
  async function startHttpServer(options) {
2202
- const { createFabricHttpApp } = await import("./http-2L2SQN7A.js");
2202
+ const { createFabricHttpApp } = await import("./http-ZRVOXE6C.js");
2203
2203
  const { port, projectRoot, host = "127.0.0.1", authToken, allowLoopbackNoAuth } = options;
2204
2204
  const app = createFabricHttpApp({ projectRoot, host, authToken, allowLoopbackNoAuth });
2205
2205
  return await new Promise((resolveServer, rejectServer) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-server",
3
- "version": "2.0.0-rc.34",
3
+ "version": "2.0.0-rc.36",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -13,7 +13,7 @@
13
13
  "express": "^5.2.1",
14
14
  "minimatch": "^10.0.1",
15
15
  "zod": "^3.25.0",
16
- "@fenglimg/fabric-shared": "2.0.0-rc.34"
16
+ "@fenglimg/fabric-shared": "2.0.0-rc.36"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/express": "^5.0.6",