@fenglimg/fabric-server 2.0.0-rc.11 → 2.0.0-rc.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -88,7 +88,7 @@ import { agentsMetaNodeSchema, agentsMetaSchema as agentsMetaSchema2 } from "@fe
|
|
|
88
88
|
var AgentsMetaFileMissingError = class extends IOFabricError {
|
|
89
89
|
constructor(metaPath, opts) {
|
|
90
90
|
super(`Fabric agents metadata file is missing: ${metaPath}`, {
|
|
91
|
-
actionHint: opts?.actionHint ?? "Run `fab
|
|
91
|
+
actionHint: opts?.actionHint ?? "Run `fab install` to scaffold the .fabric/agents.meta.json file"
|
|
92
92
|
});
|
|
93
93
|
this.metaPath = metaPath;
|
|
94
94
|
}
|
|
@@ -1382,12 +1382,13 @@ async function runDoctorReport(target) {
|
|
|
1382
1382
|
const narrowTooFew = inspectNarrowTooFew(projectRoot, lintNow);
|
|
1383
1383
|
const sessionHintsStale = inspectSessionHintsStale(projectRoot, lintNow);
|
|
1384
1384
|
const relevanceFieldsMissing = inspectRelevanceFieldsMissing(projectRoot);
|
|
1385
|
+
const skillMdYamlInvalid = inspectSkillMdYamlInvalid(projectRoot);
|
|
1385
1386
|
const checks = [
|
|
1386
1387
|
createBootstrapAnchorCheck(bootstrapAnchor),
|
|
1387
1388
|
createKnowledgeDirMissingCheck(knowledgeDirMissing),
|
|
1388
1389
|
createForensicCheck(forensic, framework.kind, entryPoints.length),
|
|
1389
1390
|
// v2.0: removed `createInitContextCheck` — `.fabric/init-context.json`
|
|
1390
|
-
// is owned by the AI-side client init skill, not by `fabric
|
|
1391
|
+
// is owned by the AI-side client init skill, not by `fabric install` CLI.
|
|
1391
1392
|
// The file's absence is a legitimate post-init state when the skill has
|
|
1392
1393
|
// not yet run, so flagging it as a doctor manual_error misrepresents
|
|
1393
1394
|
// ownership.
|
|
@@ -1441,6 +1442,9 @@ async function runDoctorReport(target) {
|
|
|
1441
1442
|
// Info kind — applies to pending entries only; canonical entries get
|
|
1442
1443
|
// the fields written verbatim by fab_review.approve/modify.
|
|
1443
1444
|
createRelevanceFieldsMissingCheck(relevanceFieldsMissing),
|
|
1445
|
+
// rc.12 lint #29: skill_md_yaml_invalid. Warning kind — surfaces
|
|
1446
|
+
// SKILL.md frontmatter that Codex CLI silently drops at load.
|
|
1447
|
+
createSkillMdYamlInvalidCheck(skillMdYamlInvalid),
|
|
1444
1448
|
createPreexistingRootFilesCheck(preexistingRootFiles)
|
|
1445
1449
|
// v2.0 / rc.2: `createLegacyClientPathCheck` removed. The schema now
|
|
1446
1450
|
// rejects retired clientPaths keys (windsurf/rooCode/geminiCLI) at Zod
|
|
@@ -2128,7 +2132,7 @@ function createBootstrapAnchorCheck(inspection) {
|
|
|
2128
2132
|
"fixable_error",
|
|
2129
2133
|
"bootstrap_anchor_missing",
|
|
2130
2134
|
"Neither AGENTS.md nor CLAUDE.md exists at the repo root. Fabric requires a bootstrap anchor file at the project root.",
|
|
2131
|
-
"Run `fabric
|
|
2135
|
+
"Run `fabric install` to generate the AGENTS.md / CLAUDE.md bootstrap anchor at the repo root."
|
|
2132
2136
|
);
|
|
2133
2137
|
}
|
|
2134
2138
|
const present = [
|
|
@@ -2173,11 +2177,11 @@ function createForensicCheck(forensic, frameworkKind, entryPointCount) {
|
|
|
2173
2177
|
"manual_error",
|
|
2174
2178
|
"forensic_missing",
|
|
2175
2179
|
`${forensic.error ?? ".fabric/forensic.json is missing."} Live scan detects ${frameworkKind} with ${entryPointCount} entry point${entryPointCount === 1 ? "" : "s"}.`,
|
|
2176
|
-
"Run `fab
|
|
2180
|
+
"Run `fab install` to regenerate .fabric/forensic.json."
|
|
2177
2181
|
);
|
|
2178
2182
|
}
|
|
2179
2183
|
if (!forensic.valid) {
|
|
2180
|
-
return issueCheck("Scan evidence", "error", "manual_error", "forensic_invalid", forensic.error ?? ".fabric/forensic.json is invalid.", "Run `fab
|
|
2184
|
+
return issueCheck("Scan evidence", "error", "manual_error", "forensic_invalid", forensic.error ?? ".fabric/forensic.json is invalid.", "Run `fab install` to regenerate .fabric/forensic.json.");
|
|
2181
2185
|
}
|
|
2182
2186
|
return okCheck("Scan evidence", `.fabric/forensic.json is valid for ${forensic.report?.framework.kind ?? "unknown"}.`);
|
|
2183
2187
|
}
|
|
@@ -2257,8 +2261,8 @@ function createMcpConfigInWrongFileCheck(inspection) {
|
|
|
2257
2261
|
"error",
|
|
2258
2262
|
"fixable_error",
|
|
2259
2263
|
"mcp_config_in_wrong_file",
|
|
2260
|
-
`.claude/settings.json contains mcpServers.fabric \u2014 this file is for hooks/permissions only. Run --fix to remove it, then re-run fab
|
|
2261
|
-
"Run `fab doctor --fix` to remove mcpServers.fabric from .claude/settings.json, then run `fab
|
|
2264
|
+
`.claude/settings.json contains mcpServers.fabric \u2014 this file is for hooks/permissions only. Run --fix to remove it, then re-run fab install to write .mcp.json.`,
|
|
2265
|
+
"Run `fab doctor --fix` to remove mcpServers.fabric from .claude/settings.json, then run `fab install` to write .mcp.json."
|
|
2262
2266
|
);
|
|
2263
2267
|
}
|
|
2264
2268
|
return okCheck("Claude MCP config location", "mcpServers.fabric is not in .claude/settings.json.");
|
|
@@ -3642,6 +3646,93 @@ function createRelevanceFieldsMissingCheck(inspection) {
|
|
|
3642
3646
|
"Run `fab doctor --apply-lint` to back-fill the schema defaults (relevance_scope: broad, relevance_paths: [])."
|
|
3643
3647
|
);
|
|
3644
3648
|
}
|
|
3649
|
+
var SKILL_MD_FRONTMATTER_ROOTS = [".claude/skills", ".codex/skills"];
|
|
3650
|
+
var SKILL_FRONTMATTER_KEY_PATTERN = /^([A-Za-z_][A-Za-z0-9_-]*):[ \t]+(.+?)[ \t]*$/u;
|
|
3651
|
+
var SKILL_QUOTED_VALUE_LEADS = /* @__PURE__ */ new Set(['"', "'", "[", "{", ">", "|"]);
|
|
3652
|
+
function inspectSkillMdYamlInvalid(projectRoot) {
|
|
3653
|
+
const candidates = [];
|
|
3654
|
+
for (const rootRel of SKILL_MD_FRONTMATTER_ROOTS) {
|
|
3655
|
+
const rootAbs = join5(projectRoot, rootRel);
|
|
3656
|
+
if (!existsSync4(rootAbs)) continue;
|
|
3657
|
+
let dirEntries;
|
|
3658
|
+
try {
|
|
3659
|
+
dirEntries = readdirSync(rootAbs, { withFileTypes: true });
|
|
3660
|
+
} catch {
|
|
3661
|
+
continue;
|
|
3662
|
+
}
|
|
3663
|
+
for (const dirEntry of dirEntries) {
|
|
3664
|
+
if (!dirEntry.isDirectory()) continue;
|
|
3665
|
+
const skillFile = join5(rootAbs, dirEntry.name, "SKILL.md");
|
|
3666
|
+
if (!existsSync4(skillFile)) continue;
|
|
3667
|
+
let raw;
|
|
3668
|
+
try {
|
|
3669
|
+
raw = readFileSync(skillFile, "utf8");
|
|
3670
|
+
} catch {
|
|
3671
|
+
continue;
|
|
3672
|
+
}
|
|
3673
|
+
const frontmatter = extractSkillFrontmatterLines(raw);
|
|
3674
|
+
if (frontmatter === null) continue;
|
|
3675
|
+
for (const { line, lineNumber } of frontmatter) {
|
|
3676
|
+
const match = SKILL_FRONTMATTER_KEY_PATTERN.exec(line);
|
|
3677
|
+
if (!match) continue;
|
|
3678
|
+
const [, key, value] = match;
|
|
3679
|
+
if (value.length === 0) continue;
|
|
3680
|
+
if (SKILL_QUOTED_VALUE_LEADS.has(value[0])) continue;
|
|
3681
|
+
const colonSpaceIdx = value.indexOf(": ");
|
|
3682
|
+
const trailingColon = value.endsWith(":");
|
|
3683
|
+
if (colonSpaceIdx < 0 && !trailingColon) continue;
|
|
3684
|
+
const anchor = colonSpaceIdx >= 0 ? colonSpaceIdx : value.length - 1;
|
|
3685
|
+
const previewStart = Math.max(0, anchor - 25);
|
|
3686
|
+
const previewEnd = Math.min(value.length, anchor + 30);
|
|
3687
|
+
const preview = `${previewStart > 0 ? "\u2026" : ""}${value.slice(previewStart, previewEnd)}${previewEnd < value.length ? "\u2026" : ""}`;
|
|
3688
|
+
candidates.push({
|
|
3689
|
+
path: posix.join(rootRel, dirEntry.name, "SKILL.md"),
|
|
3690
|
+
line: lineNumber,
|
|
3691
|
+
key,
|
|
3692
|
+
preview
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
candidates.sort((a, b) => {
|
|
3698
|
+
const byPath = a.path.localeCompare(b.path);
|
|
3699
|
+
return byPath !== 0 ? byPath : a.line - b.line;
|
|
3700
|
+
});
|
|
3701
|
+
return { candidates };
|
|
3702
|
+
}
|
|
3703
|
+
function extractSkillFrontmatterLines(raw) {
|
|
3704
|
+
const rawLines = raw.split(/\r?\n/u);
|
|
3705
|
+
if (rawLines.length < 2) return null;
|
|
3706
|
+
if (rawLines[0]?.trim() !== "---") return null;
|
|
3707
|
+
const out = [];
|
|
3708
|
+
for (let i = 1; i < rawLines.length; i++) {
|
|
3709
|
+
const line = rawLines[i];
|
|
3710
|
+
if (line.trim() === "---") {
|
|
3711
|
+
return out;
|
|
3712
|
+
}
|
|
3713
|
+
out.push({ line, lineNumber: i + 1 });
|
|
3714
|
+
}
|
|
3715
|
+
return null;
|
|
3716
|
+
}
|
|
3717
|
+
function createSkillMdYamlInvalidCheck(inspection) {
|
|
3718
|
+
if (inspection.candidates.length === 0) {
|
|
3719
|
+
return okCheck(
|
|
3720
|
+
"Skill markdown YAML",
|
|
3721
|
+
"All .claude/.codex SKILL.md frontmatter values parse as strict YAML."
|
|
3722
|
+
);
|
|
3723
|
+
}
|
|
3724
|
+
const first = inspection.candidates[0];
|
|
3725
|
+
const detail = `${first.path}:${first.line} (key \`${first.key}\` value contains an unquoted ': ' \u2014 preview: \`${first.preview}\`)`;
|
|
3726
|
+
const plural = inspection.candidates.length === 1;
|
|
3727
|
+
return issueCheck(
|
|
3728
|
+
"Skill markdown YAML",
|
|
3729
|
+
"warn",
|
|
3730
|
+
"warning",
|
|
3731
|
+
"skill_md_yaml_invalid",
|
|
3732
|
+
`${inspection.candidates.length} SKILL.md frontmatter ${plural ? "value contains" : "values contain"} an unquoted ': ' that strict YAML parsers reject (Claude Code tolerates it; Codex CLI drops the skill at load). First: ${detail}.`,
|
|
3733
|
+
'Quote the value with double quotes (`description: "\u2026"`) or rewrite the inner `key: value` token to `key=value`.'
|
|
3734
|
+
);
|
|
3735
|
+
}
|
|
3645
3736
|
function createNarrowTooFewCheck(inspection) {
|
|
3646
3737
|
const { structural_flagged, telemetry_flagged } = inspection;
|
|
3647
3738
|
if (!structural_flagged && !telemetry_flagged) {
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
readEventLedger,
|
|
15
15
|
runDoctorReport,
|
|
16
16
|
sha256
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-3ZBYYPXQ.js";
|
|
18
18
|
|
|
19
19
|
// src/http.ts
|
|
20
20
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
@@ -942,7 +942,7 @@ function toPosixPath(path) {
|
|
|
942
942
|
function buildRecommendations(input) {
|
|
943
943
|
const recommendations = [];
|
|
944
944
|
if (!input.hasExistingFabric) {
|
|
945
|
-
recommendations.push("L0: Run `fab
|
|
945
|
+
recommendations.push("L0: Run `fab install` to scaffold the .fabric/ knowledge layout (decisions, pitfalls, guidelines, models, processes).");
|
|
946
946
|
}
|
|
947
947
|
if (input.readmeQuality === "stub") {
|
|
948
948
|
recommendations.push("L0: Expand README.md before promoting project facts into Fabric knowledge entries.");
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
sha256,
|
|
27
27
|
stableStringify,
|
|
28
28
|
writeKnowledgeMeta
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-3ZBYYPXQ.js";
|
|
30
30
|
|
|
31
31
|
// src/index.ts
|
|
32
32
|
import { existsSync as existsSync4 } from "fs";
|
|
@@ -318,9 +318,9 @@ ${renderEvidenceBlock(newSummary, newRecentPaths)}
|
|
|
318
318
|
const pathSection = /Recent paths:\s*\n([\s\S]*?)(?:\n\s*Notes:|$)/u.exec(block);
|
|
319
319
|
if (pathSection !== null) {
|
|
320
320
|
for (const rawLine of (pathSection[1] ?? "").split(/\r?\n/u)) {
|
|
321
|
-
const
|
|
322
|
-
if (
|
|
323
|
-
existingPaths.push(
|
|
321
|
+
const t2 = rawLine.trim();
|
|
322
|
+
if (t2.startsWith("- ")) {
|
|
323
|
+
existingPaths.push(t2.slice(2).trim());
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
}
|
|
@@ -329,16 +329,16 @@ ${renderEvidenceBlock(newSummary, newRecentPaths)}
|
|
|
329
329
|
const bulletLines = [];
|
|
330
330
|
let prose = [];
|
|
331
331
|
for (const rawLine of noteBody.split(/\r?\n/u)) {
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
334
|
-
if (
|
|
332
|
+
const t2 = rawLine.trim();
|
|
333
|
+
if (t2.length === 0) continue;
|
|
334
|
+
if (t2.startsWith("- ")) {
|
|
335
335
|
if (prose.length > 0) {
|
|
336
336
|
existingNotes.push(prose.join(" ").trim());
|
|
337
337
|
prose = [];
|
|
338
338
|
}
|
|
339
|
-
bulletLines.push(
|
|
339
|
+
bulletLines.push(t2.slice(2).trim());
|
|
340
340
|
} else {
|
|
341
|
-
prose.push(
|
|
341
|
+
prose.push(t2);
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
344
|
if (prose.length > 0) existingNotes.push(prose.join(" ").trim());
|
|
@@ -934,7 +934,7 @@ async function listPending(projectRoot, filters) {
|
|
|
934
934
|
}
|
|
935
935
|
if (filters?.tags !== void 0 && filters.tags.length > 0) {
|
|
936
936
|
const itemTags = fm.tags ?? [];
|
|
937
|
-
const hasAll = filters.tags.every((
|
|
937
|
+
const hasAll = filters.tags.every((t2) => itemTags.includes(t2));
|
|
938
938
|
if (!hasAll) continue;
|
|
939
939
|
}
|
|
940
940
|
if (filters?.created_after !== void 0) {
|
|
@@ -1234,7 +1234,7 @@ async function searchEntries(projectRoot, query, filters) {
|
|
|
1234
1234
|
}
|
|
1235
1235
|
if (filters?.tags !== void 0 && filters.tags.length > 0) {
|
|
1236
1236
|
const itemTags = fm.tags ?? [];
|
|
1237
|
-
const hasAll = filters.tags.every((
|
|
1237
|
+
const hasAll = filters.tags.every((t2) => itemTags.includes(t2));
|
|
1238
1238
|
if (!hasAll) continue;
|
|
1239
1239
|
}
|
|
1240
1240
|
if (filters?.created_after !== void 0) {
|
|
@@ -1781,8 +1781,10 @@ function registerKnowledgeSections(server, tracker) {
|
|
|
1781
1781
|
// src/services/serve-lock.ts
|
|
1782
1782
|
import fs from "fs";
|
|
1783
1783
|
import path from "path";
|
|
1784
|
+
import { createTranslator, detectNodeLocale } from "@fenglimg/fabric-shared";
|
|
1784
1785
|
import { IOFabricError } from "@fenglimg/fabric-shared/errors";
|
|
1785
1786
|
var LOCK_FILENAME = ".serve.lock";
|
|
1787
|
+
var t = createTranslator(detectNodeLocale());
|
|
1786
1788
|
var ServeLockHeldError = class extends IOFabricError {
|
|
1787
1789
|
code = "SERVE_LOCK_HELD";
|
|
1788
1790
|
httpStatus = 423;
|
|
@@ -1813,7 +1815,7 @@ function acquireLock(projectRoot, opts) {
|
|
|
1813
1815
|
throw new ServeLockHeldError(
|
|
1814
1816
|
`serve lock held by live PID ${state.pid}`,
|
|
1815
1817
|
{
|
|
1816
|
-
actionHint:
|
|
1818
|
+
actionHint: t("cli.serve.lock-held.action-hint", { pid: String(state.pid) }),
|
|
1817
1819
|
details: state
|
|
1818
1820
|
}
|
|
1819
1821
|
);
|
|
@@ -1863,7 +1865,7 @@ function checkLockOrThrow(projectRoot, opts) {
|
|
|
1863
1865
|
throw new ServeLockHeldError(
|
|
1864
1866
|
`serve lock held by live PID ${state.pid}`,
|
|
1865
1867
|
{
|
|
1866
|
-
actionHint:
|
|
1868
|
+
actionHint: t("cli.serve.lock-held.action-hint", { pid: String(state.pid) }),
|
|
1867
1869
|
details: state
|
|
1868
1870
|
}
|
|
1869
1871
|
);
|
|
@@ -1890,7 +1892,7 @@ function formatPreexistingRootMessage(projectRoot) {
|
|
|
1890
1892
|
function createFabricServer(tracker) {
|
|
1891
1893
|
const server = new McpServer({
|
|
1892
1894
|
name: "fabric-knowledge-server",
|
|
1893
|
-
version: "2.0.0-rc.
|
|
1895
|
+
version: "2.0.0-rc.15"
|
|
1894
1896
|
});
|
|
1895
1897
|
registerPlanContext(server, tracker);
|
|
1896
1898
|
registerKnowledgeSections(server, tracker);
|
|
@@ -1990,7 +1992,7 @@ function createShutdownHandler(deps) {
|
|
|
1990
1992
|
};
|
|
1991
1993
|
}
|
|
1992
1994
|
async function startHttpServer(options) {
|
|
1993
|
-
const { createFabricHttpApp } = await import("./http-
|
|
1995
|
+
const { createFabricHttpApp } = await import("./http-FCXAUGSO.js");
|
|
1994
1996
|
const { port, projectRoot, host = "127.0.0.1", authToken } = options;
|
|
1995
1997
|
const app = createFabricHttpApp({ projectRoot, host, authToken });
|
|
1996
1998
|
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.15",
|
|
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.15"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/express": "^5.0.6",
|