@fenglimg/fabric-cli 2.0.0-rc.10 → 2.0.0-rc.13

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.
package/README.md CHANGED
@@ -6,14 +6,14 @@
6
6
 
7
7
  1. 在 monorepo 根目录运行 `pnpm install`。
8
8
  2. 用 `pnpm --filter @fenglimg/fabric-cli build` 构建 CLI。
9
- 3. 在目标项目运行 `fabric init`,完成一站式初始化。
9
+ 3. 在目标项目运行 `fabric install`,完成一站式安装。
10
10
  4. 启动 `fabric serve`,再去客户端里验证 `fab_plan_context` 和 `fab_get_knowledge_sections`。
11
11
 
12
- `fabric init` 会自动准备 bootstrap、MCP 配置和 git hooks。公共命令面只保留 `init`、`scan`、`doctor`、`serve`。
12
+ `fabric install` 会自动准备 bootstrap、MCP 配置和 git hooks。公共命令面只保留 `install`、`scan`、`doctor`、`serve`。
13
13
 
14
14
  ## 常用命令
15
15
 
16
- - `fabric init`
16
+ - `fabric install`
17
17
  - `fabric scan`
18
18
  - `fabric doctor`
19
19
  - `fabric doctor --json`
@@ -88,8 +88,8 @@ async function runInitScan(targetInput, options = {}) {
88
88
  const nowIso = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
89
89
  const tags = deriveTagsFromForensic(forensic);
90
90
  const fabricConfig = readFabricConfig(target);
91
- const knowledgeLanguage = fabricConfig.knowledge_language ?? "match-existing";
92
- const resolvedLanguage = resolveKnowledgeLanguage(knowledgeLanguage, target);
91
+ const fabricLanguage = fabricConfig.fabric_language ?? "match-existing";
92
+ const resolvedLanguage = resolveFabricLanguage(fabricLanguage, target);
93
93
  const candidates = [
94
94
  buildTechStackEntry(forensic, nowIso, tags, resolvedLanguage),
95
95
  buildModuleStructureEntry(forensic, nowIso, tags, resolvedLanguage),
@@ -186,7 +186,7 @@ var scanCommand = defineCommand({
186
186
  }
187
187
  });
188
188
  var scan_default = scanCommand;
189
- var BASELINE_TEMPLATES = {
189
+ var STRICT_BASELINE_TEMPLATES = {
190
190
  en: {
191
191
  "tech-stack": {
192
192
  title: ({ frameworkSummary }) => `Tech stack: ${frameworkSummary}`,
@@ -336,11 +336,16 @@ var BASELINE_TEMPLATES = {
336
336
  }
337
337
  }
338
338
  };
339
+ var BASELINE_TEMPLATES = {
340
+ en: STRICT_BASELINE_TEMPLATES.en,
341
+ "zh-CN": STRICT_BASELINE_TEMPLATES["zh-CN"],
342
+ "zh-CN-hybrid": STRICT_BASELINE_TEMPLATES["zh-CN"]
343
+ };
339
344
  function resolveTemplateTitle(template, inputs) {
340
345
  return typeof template.title === "function" ? template.title(inputs) : template.title;
341
346
  }
342
- function resolveKnowledgeLanguage(configured, target) {
343
- if (configured === "en" || configured === "zh-CN") {
347
+ function resolveFabricLanguage(configured, target) {
348
+ if (configured === "en" || configured === "zh-CN" || configured === "zh-CN-hybrid") {
344
349
  return configured;
345
350
  }
346
351
  return detectExistingLanguage(target);
@@ -392,7 +397,7 @@ function detectExistingLanguage(target) {
392
397
  return "en";
393
398
  }
394
399
  const ratio = cjkCount / denominator;
395
- return ratio > ZH_CN_RATIO_THRESHOLD ? "zh-CN" : "en";
400
+ return ratio > ZH_CN_RATIO_THRESHOLD ? "zh-CN-hybrid" : "en";
396
401
  }
397
402
  function buildTechStackEntry(forensic, nowIso, tags, language = "en") {
398
403
  const framework = forensic.framework;
@@ -982,9 +987,9 @@ var __testing__ = {
982
987
  isBuildConfigPath,
983
988
  extractFirstParagraph,
984
989
  extractExplicitDescription,
985
- // TASK-008: bilingual template registry + language detection
990
+ // TASK-008 / rc.12: bilingual template registry + language detection
986
991
  detectExistingLanguage,
987
- resolveKnowledgeLanguage,
992
+ resolveFabricLanguage,
988
993
  BASELINE_TEMPLATES
989
994
  };
990
995
 
@@ -995,6 +1000,7 @@ export {
995
1000
  runInitScan,
996
1001
  scanCommand,
997
1002
  scan_default,
1003
+ detectExistingLanguage,
998
1004
  deriveTagsFromForensic,
999
1005
  __testing__
1000
1006
  };
@@ -6,7 +6,7 @@ import {
6
6
  normalizeConfigPath,
7
7
  removeJsonClientConfigEntry,
8
8
  writeJsonClientConfig
9
- } from "./chunk-AW3G7ZH5.js";
9
+ } from "./chunk-X7QPY5KH.js";
10
10
 
11
11
  // src/config/resolver.ts
12
12
  import { existsSync as existsSync3 } from "fs";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- addArchiveSkillPointer,
3
+ addFabricKnowledgeBaseSection,
4
4
  installArchiveHintHook,
5
5
  installFabricArchiveSkill,
6
6
  installFabricImportSkill,
@@ -9,8 +9,9 @@ import {
9
9
  installKnowledgeHintNarrowHook,
10
10
  mergeClaudeCodeHookConfig,
11
11
  mergeCodexHookConfig,
12
- mergeCursorHookConfig
13
- } from "./chunk-AW3G7ZH5.js";
12
+ mergeCursorHookConfig,
13
+ readFabricLanguagePreference
14
+ } from "./chunk-X7QPY5KH.js";
14
15
  import {
15
16
  t
16
17
  } from "./chunk-6ICJICVU.js";
@@ -66,7 +67,8 @@ async function installHooks(target, _options = {}) {
66
67
  results.push(await runSingleStep("claude-hook-config", () => mergeClaudeCodeHookConfig(normalizedTarget)));
67
68
  results.push(await runSingleStep("codex-hook-config", () => mergeCodexHookConfig(normalizedTarget)));
68
69
  results.push(await runSingleStep("cursor-hook-config", () => mergeCursorHookConfig(normalizedTarget)));
69
- results.push(...await runStep(() => addArchiveSkillPointer(normalizedTarget)));
70
+ const fabricLanguage = readFabricLanguagePreference(normalizedTarget);
71
+ results.push(...await runStep(() => addFabricKnowledgeBaseSection(normalizedTarget, fabricLanguage)));
70
72
  results.push(...validateHookPaths(normalizedTarget));
71
73
  return summarizeResults(results);
72
74
  }
@@ -307,10 +307,41 @@ var FABRIC_HOOK_COMMAND_PATHS = {
307
307
  knowledgeHintNarrow: ".cursor/hooks/knowledge-hint-narrow.cjs"
308
308
  }
309
309
  };
310
- var POINTER_LINE = "> Use the fabric-archive Skill when archiving knowledge entries (see .claude/skills/fabric-archive/SKILL.md).";
311
- var REVIEW_POINTER_LINE = "> Use the fabric-review Skill to review pending knowledge entries (see .claude/skills/fabric-review/SKILL.md).";
312
- var IMPORT_POINTER_LINE = "> Use the fabric-import Skill for cold-start enrichment from git history and docs (see .claude/skills/fabric-import/SKILL.md).";
313
- var POINTER_TARGETS = ["CLAUDE.md", "AGENTS.md", join2(".cursor", "rules")];
310
+ var SECTION_TARGETS = ["CLAUDE.md", "AGENTS.md", join2(".cursor", "rules")];
311
+ var FABRIC_SECTION_BEGIN_MARKER = "<!-- fabric:knowledge-base:begin -->";
312
+ var FABRIC_SECTION_END_MARKER = "<!-- fabric:knowledge-base:end -->";
313
+ var FABRIC_SECTION_REGEX = /(?:\r?\n){0,2}<!-- fabric:knowledge-base:begin -->[\s\S]*?<!-- fabric:knowledge-base:end -->/;
314
+ function readFabricLanguagePreference(projectRoot) {
315
+ const configPath = join2(projectRoot, ".fabric", "fabric-config.json");
316
+ if (!existsSync2(configPath)) {
317
+ return "match-existing";
318
+ }
319
+ try {
320
+ const raw = readFileSync(configPath, "utf8");
321
+ const parsed = JSON.parse(raw);
322
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
323
+ return "match-existing";
324
+ }
325
+ const value = parsed["fabric_language"];
326
+ return typeof value === "string" && value.length > 0 ? value : "match-existing";
327
+ } catch {
328
+ return "match-existing";
329
+ }
330
+ }
331
+ function buildFabricKnowledgeBaseSection(fabricLanguage) {
332
+ return `${FABRIC_SECTION_BEGIN_MARKER}
333
+
334
+ ## Fabric Knowledge Base
335
+
336
+ This project uses Fabric for persistent project knowledge under \`.fabric/knowledge/\`.
337
+
338
+ - **Discovery**: SessionStart lists available entries (broad menu); editing files may surface narrow hints
339
+ - **Usage**: call \`fab_get_knowledge_sections\` to fetch full content of any entry by id
340
+ - **Write flows**: see fabric-archive (record), fabric-review (validate), fabric-import (backfill) Skills
341
+ - **Language**: rendered per \`fabric_language\` in \`.fabric/fabric-config.json\` (current: \`${fabricLanguage}\`)
342
+
343
+ ${FABRIC_SECTION_END_MARKER}`;
344
+ }
314
345
  async function installFabricArchiveSkill(projectRoot, _options = {}) {
315
346
  const source = await readTemplate(SKILL_TEMPLATE_REL);
316
347
  const targets = SKILL_DESTINATIONS.fabricArchive.map((rel) => join2(projectRoot, rel));
@@ -416,12 +447,13 @@ async function mergeCursorHookConfig(projectRoot, _options = {}) {
416
447
  [...HOOK_CONFIG_ARRAY_PATHS.cursor]
417
448
  );
418
449
  }
419
- async function addArchiveSkillPointer(projectRoot, _options = {}) {
450
+ async function addFabricKnowledgeBaseSection(projectRoot, fabricLanguage, _options = {}) {
451
+ const sectionBody = buildFabricKnowledgeBaseSection(fabricLanguage);
420
452
  const results = [];
421
- for (const rel of POINTER_TARGETS) {
453
+ for (const rel of SECTION_TARGETS) {
422
454
  const target = join2(projectRoot, rel);
423
455
  if (!existsSync2(target)) {
424
- results.push({ step: "pointer", path: target, status: "skipped", message: "absent" });
456
+ results.push({ step: "section", path: target, status: "skipped", message: "absent" });
425
457
  continue;
426
458
  }
427
459
  let existing;
@@ -429,47 +461,43 @@ async function addArchiveSkillPointer(projectRoot, _options = {}) {
429
461
  existing = await readFile2(target, "utf8");
430
462
  } catch (error) {
431
463
  results.push({
432
- step: "pointer",
464
+ step: "section",
433
465
  path: target,
434
466
  status: "error",
435
467
  message: error instanceof Error ? error.message : String(error)
436
468
  });
437
469
  continue;
438
470
  }
439
- let next = existing;
440
- let wrote = false;
441
- if (next.includes(POINTER_LINE)) {
442
- results.push({ step: "pointer", path: target, status: "skipped", message: "already-present" });
443
- } else {
444
- const trailingNewline = next.length === 0 || next.endsWith("\n") ? "" : "\n";
445
- next = `${next}${trailingNewline}
446
- ${POINTER_LINE}
471
+ let next;
472
+ const match = existing.match(FABRIC_SECTION_REGEX);
473
+ if (match !== null) {
474
+ const before = existing.slice(0, match.index ?? 0);
475
+ const after = existing.slice((match.index ?? 0) + match[0].length);
476
+ const stripped = `${before}${after.replace(/^\r?\n/, "")}`;
477
+ const trailingNewline = stripped.length === 0 || stripped.endsWith("\n") ? "" : "\n";
478
+ next = `${stripped}${trailingNewline}
479
+ ${sectionBody}
447
480
  `;
448
- wrote = true;
449
- results.push({ step: "pointer", path: target, status: "written" });
450
- }
451
- if (next.includes(REVIEW_POINTER_LINE)) {
452
- results.push({ step: "pointer-review", path: target, status: "skipped", message: "already-present" });
453
481
  } else {
454
- const trailingNewline = next.length === 0 || next.endsWith("\n") ? "" : "\n";
455
- next = `${next}${trailingNewline}
456
- ${REVIEW_POINTER_LINE}
482
+ const trailingNewline = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
483
+ next = `${existing}${trailingNewline}
484
+ ${sectionBody}
457
485
  `;
458
- wrote = true;
459
- results.push({ step: "pointer-review", path: target, status: "written" });
460
486
  }
461
- if (next.includes(IMPORT_POINTER_LINE)) {
462
- results.push({ step: "pointer-import", path: target, status: "skipped", message: "already-present" });
463
- } else {
464
- const trailingNewline = next.length === 0 || next.endsWith("\n") ? "" : "\n";
465
- next = `${next}${trailingNewline}
466
- ${IMPORT_POINTER_LINE}
467
- `;
468
- wrote = true;
469
- results.push({ step: "pointer-import", path: target, status: "written" });
487
+ if (next === existing) {
488
+ results.push({ step: "section", path: target, status: "skipped", message: "up-to-date" });
489
+ continue;
470
490
  }
471
- if (wrote) {
491
+ try {
472
492
  await atomicWriteText(target, next);
493
+ results.push({ step: "section", path: target, status: "written" });
494
+ } catch (error) {
495
+ results.push({
496
+ step: "section",
497
+ path: target,
498
+ status: "error",
499
+ message: error instanceof Error ? error.message : String(error)
500
+ });
473
501
  }
474
502
  }
475
503
  return results;
@@ -559,10 +587,9 @@ export {
559
587
  HOOK_CONFIG_TARGETS,
560
588
  HOOK_CONFIG_ARRAY_PATHS,
561
589
  FABRIC_HOOK_COMMAND_PATHS,
562
- POINTER_LINE,
563
- REVIEW_POINTER_LINE,
564
- IMPORT_POINTER_LINE,
565
- POINTER_TARGETS,
590
+ SECTION_TARGETS,
591
+ FABRIC_SECTION_REGEX,
592
+ readFabricLanguagePreference,
566
593
  installFabricArchiveSkill,
567
594
  installFabricReviewSkill,
568
595
  installFabricImportSkill,
@@ -572,5 +599,5 @@ export {
572
599
  mergeClaudeCodeHookConfig,
573
600
  mergeCodexHookConfig,
574
601
  mergeCursorHookConfig,
575
- addArchiveSkillPointer
602
+ addFabricKnowledgeBaseSection
576
603
  };
@@ -3,8 +3,8 @@ import {
3
3
  hooksCommand,
4
4
  hooks_default,
5
5
  installHooks
6
- } from "./chunk-WPTA74BY.js";
7
- import "./chunk-AW3G7ZH5.js";
6
+ } from "./chunk-Q72D24BG.js";
7
+ import "./chunk-X7QPY5KH.js";
8
8
  import "./chunk-6ICJICVU.js";
9
9
  export {
10
10
  hooks_default as default,
package/dist/index.js CHANGED
@@ -8,12 +8,12 @@ import { defineCommand, runMain } from "citty";
8
8
 
9
9
  // src/commands/index.ts
10
10
  var allCommands = {
11
- init: () => import("./init-SAVH4SKE.js").then((module) => module.default),
12
- scan: () => import("./scan-ELSNCSKS.js").then((module) => module.default),
11
+ install: () => import("./install-SLS5W27W.js").then((module) => module.default),
12
+ scan: () => import("./scan-VHKZPT2W.js").then((module) => module.default),
13
13
  serve: () => import("./serve-NGLXHDYC.js").then((module) => module.default),
14
- uninstall: () => import("./uninstall-DBAR2JBS.js").then((module) => module.default),
14
+ uninstall: () => import("./uninstall-JHUSFENL.js").then((module) => module.default),
15
15
  doctor: () => import("./doctor-RILCO5OG.js").then((module) => module.default),
16
- hooks: () => import("./hooks-NX32PPEN.js").then((module) => module.default),
16
+ hooks: () => import("./hooks-HIWYI3VG.js").then((module) => module.default),
17
17
  "plan-context-hint": () => import("./plan-context-hint-QMUPAXIB.js").then((module) => module.default)
18
18
  };
19
19
 
@@ -21,8 +21,8 @@ var allCommands = {
21
21
  var main = defineCommand({
22
22
  meta: {
23
23
  name: "fabric",
24
- version: "2.0.0-rc.10",
25
- description: 'Initialize and manage Fabric projects. Use "fabric init" for one-shot setup.'
24
+ version: "2.0.0-rc.13",
25
+ description: 'Initialize and manage Fabric projects. Use "fabric install" for one-shot setup.'
26
26
  },
27
27
  subCommands: allCommands
28
28
  });