@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 `
|
|
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 "
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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 —
|
|
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(
|
|
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
|
-
|
|
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-
|
|
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 `
|
|
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 `
|
|
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-
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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.
|
|
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-
|
|
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.
|
|
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.
|
|
16
|
+
"@fenglimg/fabric-shared": "2.0.0-rc.36"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/express": "^5.0.6",
|