@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 init` to scaffold the .fabric/agents.meta.json file"
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 init` CLI.
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 init` to generate the AGENTS.md / CLAUDE.md bootstrap anchor at the repo root."
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 init` to regenerate .fabric/forensic.json."
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 init` to regenerate .fabric/forensic.json.");
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 init to write .mcp.json.`,
2261
- "Run `fab doctor --fix` to remove mcpServers.fabric from .claude/settings.json, then run `fab init` to write .mcp.json."
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-MMGHD42I.js";
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 init` to scaffold the .fabric/ knowledge layout (decisions, pitfalls, guidelines, models, processes).");
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-MMGHD42I.js";
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 t = rawLine.trim();
322
- if (t.startsWith("- ")) {
323
- existingPaths.push(t.slice(2).trim());
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 t = rawLine.trim();
333
- if (t.length === 0) continue;
334
- if (t.startsWith("- ")) {
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(t.slice(2).trim());
339
+ bulletLines.push(t2.slice(2).trim());
340
340
  } else {
341
- prose.push(t);
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((t) => itemTags.includes(t));
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((t) => itemTags.includes(t));
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: `Stop the other process (PID ${state.pid}) or run with --force to override`,
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: `Stop the other serve process (PID ${state.pid}) before running this command, or pass --force to override`,
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.11"
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-U5RL5Q6P.js");
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.11",
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.11"
16
+ "@fenglimg/fabric-shared": "2.0.0-rc.15"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/express": "^5.0.6",