@fenglimg/fabric-cli 2.0.0-rc.22 → 2.0.0-rc.23

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
@@ -9,7 +9,7 @@
9
9
  3. 在目标项目运行 `fabric install`,完成一站式安装。
10
10
  4. 启动 `fabric serve`,再去客户端里验证 `fab_plan_context` 和 `fab_get_knowledge_sections`。
11
11
 
12
- `fabric install` 会自动准备 bootstrap、MCP 配置和 git hooks。公共命令面只保留 `install`、`doctor`、`serve`、`uninstall`、`config`(rc.15 `fab scan` 已折叠到 `fab doctor --rescan`)。
12
+ `fabric install` 会自动准备 bootstrap、MCP 配置和 git hooks。公共命令面只保留 `install`、`doctor`、`serve`、`uninstall`、`config`(rc.23 起移除了 baseline scan 机制,知识库唯一合法来源是 Skill 路径:`fabric-archive` / `fabric-import` / `fabric-review`)。
13
13
 
14
14
  ## 常用命令
15
15
 
@@ -18,7 +18,6 @@
18
18
  - `fabric doctor --json`
19
19
  - `fabric doctor --strict`
20
20
  - `fabric doctor --fix`
21
- - `fabric doctor --rescan`(替代旧的 `fabric scan`)
22
21
  - `fabric serve`
23
22
  - `fabric uninstall`
24
23
  - `fabric config`(rc.16 起将提供配置面板;当前为占位提示)
@@ -109,14 +109,14 @@ var HOOK_CONFIG_ARRAY_PATHS = {
109
109
  };
110
110
  var FABRIC_HOOK_COMMAND_PATHS = {
111
111
  claudeCode: {
112
- fabricHint: ".claude/hooks/fabric-hint.cjs",
113
- knowledgeHintBroad: ".claude/hooks/knowledge-hint-broad.cjs",
114
- knowledgeHintNarrow: ".claude/hooks/knowledge-hint-narrow.cjs"
112
+ fabricHint: "${CLAUDE_PROJECT_DIR}/.claude/hooks/fabric-hint.cjs",
113
+ knowledgeHintBroad: "${CLAUDE_PROJECT_DIR}/.claude/hooks/knowledge-hint-broad.cjs",
114
+ knowledgeHintNarrow: "${CLAUDE_PROJECT_DIR}/.claude/hooks/knowledge-hint-narrow.cjs"
115
115
  },
116
116
  codex: {
117
- fabricHint: ".codex/hooks/fabric-hint.cjs",
118
- knowledgeHintBroad: ".codex/hooks/knowledge-hint-broad.cjs",
119
- knowledgeHintNarrow: ".codex/hooks/knowledge-hint-narrow.cjs"
117
+ fabricHint: '"$(git rev-parse --show-toplevel)/.codex/hooks/fabric-hint.cjs"',
118
+ knowledgeHintBroad: '"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-broad.cjs"',
119
+ knowledgeHintNarrow: '"$(git rev-parse --show-toplevel)/.codex/hooks/knowledge-hint-narrow.cjs"'
120
120
  },
121
121
  cursor: {
122
122
  fabricHint: ".cursor/hooks/fabric-hint.cjs",
@@ -3,17 +3,6 @@
3
3
  // src/dev-mode.ts
4
4
  import { existsSync, readFileSync } from "fs";
5
5
  import { isAbsolute, join, resolve } from "path";
6
- function readFabricConfig(workspaceRoot = process.cwd()) {
7
- const configPath = join(workspaceRoot, "fabric.config.json");
8
- if (!existsSync(configPath)) {
9
- return {};
10
- }
11
- const parsed = JSON.parse(readFileSync(configPath, "utf8"));
12
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
13
- throw new Error(`Expected object in ${configPath}`);
14
- }
15
- return parsed;
16
- }
17
6
  function resolveDevMode(cliTarget, workspaceRoot = process.cwd()) {
18
7
  const envTarget = normalizeTarget(process.env.EXTERNAL_FIXTURE_PATH, workspaceRoot);
19
8
  const directTarget = normalizeTarget(cliTarget, workspaceRoot);
@@ -51,7 +40,6 @@ function formatResolutionStep(source, value) {
51
40
  }
52
41
 
53
42
  export {
54
- readFabricConfig,
55
43
  resolveDevMode,
56
44
  createDebugLogger
57
45
  };
@@ -12,7 +12,10 @@ import { readFile } from "fs/promises";
12
12
  import { join, resolve } from "path";
13
13
  import { fileURLToPath } from "url";
14
14
  import { cancel, intro, isCancel, log, outro, select, text } from "@clack/prompts";
15
- import { getPanelFields } from "@fenglimg/fabric-shared";
15
+ import {
16
+ getPanelFields,
17
+ ONBOARD_SLOT_NAMES
18
+ } from "@fenglimg/fabric-shared";
16
19
  import { atomicWriteJson } from "@fenglimg/fabric-shared/node/atomic-write";
17
20
  import { defineCommand } from "citty";
18
21
  async function loadFabricConfig(workspaceRoot) {
@@ -33,6 +36,131 @@ function resolveServerPath(override) {
33
36
  }
34
37
  var PANEL_CONFIG_RELATIVE_PATH = [".fabric", "fabric-config.json"];
35
38
  var EXIT_CHOICE = "__exit__";
39
+ async function readOnboardSlotsList(configPath) {
40
+ const raw = await readFile(configPath, "utf8");
41
+ const parsed = JSON.parse(raw);
42
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
43
+ throw new Error(t("cli.config.errors.expected-object", { path: configPath }));
44
+ }
45
+ const obj = parsed;
46
+ const list = obj.onboard_slots_opted_out;
47
+ const optedOut = Array.isArray(list) ? list.filter((v) => typeof v === "string") : [];
48
+ return { config: obj, optedOut };
49
+ }
50
+ function ensureUninitGate(workspaceRoot) {
51
+ const configPath = join(workspaceRoot, ...PANEL_CONFIG_RELATIVE_PATH);
52
+ const fabricDir = join(workspaceRoot, ".fabric");
53
+ const fabricDirOk = existsSync(fabricDir) && statSync(fabricDir).isDirectory();
54
+ const configOk = fabricDirOk && existsSync(configPath);
55
+ if (!configOk) {
56
+ console.error(t("cli.config.errors.uninit-workspace.message"));
57
+ return null;
58
+ }
59
+ return configPath;
60
+ }
61
+ function validateSlotArg(slot) {
62
+ if (slot === void 0 || slot.length === 0) {
63
+ console.error(`Missing required <slot> argument. Valid slots: ${ONBOARD_SLOT_NAMES.join(", ")}.`);
64
+ return null;
65
+ }
66
+ if (!ONBOARD_SLOT_NAMES.includes(slot)) {
67
+ console.error(`Unknown slot "${slot}". Valid slots: ${ONBOARD_SLOT_NAMES.join(", ")}.`);
68
+ return null;
69
+ }
70
+ return slot;
71
+ }
72
+ var dismissSlotCmd = defineCommand({
73
+ meta: {
74
+ name: "dismiss-slot",
75
+ description: "Add an S5 onboard slot to the opted-out list (fabric-archive Skill onboard phase invokes this).",
76
+ hidden: true
77
+ },
78
+ args: {
79
+ slot: {
80
+ type: "positional",
81
+ description: "Slot name to dismiss (one of the locked S5 set).",
82
+ required: true
83
+ },
84
+ target: {
85
+ type: "string",
86
+ description: "Override the project root (defaults to cwd)."
87
+ }
88
+ },
89
+ async run({ args }) {
90
+ const slot = validateSlotArg(args.slot);
91
+ if (slot === null) {
92
+ process.exitCode = 1;
93
+ return;
94
+ }
95
+ const workspaceRoot = resolve(args.target ?? process.cwd());
96
+ const configPath = ensureUninitGate(workspaceRoot);
97
+ if (configPath === null) {
98
+ process.exitCode = 1;
99
+ return;
100
+ }
101
+ try {
102
+ const { config, optedOut } = await readOnboardSlotsList(configPath);
103
+ if (optedOut.includes(slot)) {
104
+ console.log(`Slot "${slot}" already opted out; no-op.`);
105
+ return;
106
+ }
107
+ const next = [...optedOut, slot];
108
+ const merged = { ...config, onboard_slots_opted_out: next };
109
+ await atomicWriteJson(configPath, merged);
110
+ console.log(`Dismissed onboard slot "${slot}". Run \`fab config onboard-reset ${slot}\` to re-open.`);
111
+ } catch (err) {
112
+ const message = err instanceof Error ? err.message : String(err);
113
+ console.error(`dismiss-slot failed: ${message}`);
114
+ process.exitCode = 1;
115
+ }
116
+ }
117
+ });
118
+ var onboardResetCmd = defineCommand({
119
+ meta: {
120
+ name: "onboard-reset",
121
+ description: "Remove an S5 onboard slot from the opted-out list \u2014 re-opens the slot for future fabric-archive onboard prompts.",
122
+ hidden: true
123
+ },
124
+ args: {
125
+ slot: {
126
+ type: "positional",
127
+ description: "Slot name to reset (one of the locked S5 set).",
128
+ required: true
129
+ },
130
+ target: {
131
+ type: "string",
132
+ description: "Override the project root (defaults to cwd)."
133
+ }
134
+ },
135
+ async run({ args }) {
136
+ const slot = validateSlotArg(args.slot);
137
+ if (slot === null) {
138
+ process.exitCode = 1;
139
+ return;
140
+ }
141
+ const workspaceRoot = resolve(args.target ?? process.cwd());
142
+ const configPath = ensureUninitGate(workspaceRoot);
143
+ if (configPath === null) {
144
+ process.exitCode = 1;
145
+ return;
146
+ }
147
+ try {
148
+ const { config, optedOut } = await readOnboardSlotsList(configPath);
149
+ if (!optedOut.includes(slot)) {
150
+ console.log(`Slot "${slot}" not opted out; no-op.`);
151
+ return;
152
+ }
153
+ const next = optedOut.filter((s) => s !== slot);
154
+ const merged = { ...config, onboard_slots_opted_out: next };
155
+ await atomicWriteJson(configPath, merged);
156
+ console.log(`Reset onboard slot "${slot}"; it will appear in \`fab onboard-coverage\` as missing again.`);
157
+ } catch (err) {
158
+ const message = err instanceof Error ? err.message : String(err);
159
+ console.error(`onboard-reset failed: ${message}`);
160
+ process.exitCode = 1;
161
+ }
162
+ }
163
+ });
36
164
  var configCmd = defineCommand({
37
165
  meta: {
38
166
  name: "config",
@@ -45,7 +173,15 @@ var configCmd = defineCommand({
45
173
  valueHint: "path"
46
174
  }
47
175
  },
176
+ subCommands: {
177
+ "dismiss-slot": dismissSlotCmd,
178
+ "onboard-reset": onboardResetCmd
179
+ },
48
180
  async run({ args }) {
181
+ const argvSub = process.argv[3];
182
+ if (argvSub === "dismiss-slot" || argvSub === "onboard-reset") {
183
+ return;
184
+ }
49
185
  const workspaceRoot = resolve(args.target ?? process.cwd());
50
186
  const configPath = join(workspaceRoot, ...PANEL_CONFIG_RELATIVE_PATH);
51
187
  const fabricDir = join(workspaceRoot, ".fabric");
@@ -3,7 +3,7 @@ import {
3
3
  configCmd,
4
4
  config_default,
5
5
  installMcpClients
6
- } from "./chunk-KZ2YITOS.js";
6
+ } from "./chunk-STLR2GHP.js";
7
7
  import "./chunk-MF3OTILQ.js";
8
8
  import "./chunk-6ICJICVU.js";
9
9
  export {
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- runInitScan
4
- } from "./chunk-PSVKSMRO.js";
5
2
  import {
6
3
  hasActionHint,
7
4
  paint,
@@ -13,7 +10,7 @@ import {
13
10
  } from "./chunk-6ICJICVU.js";
14
11
  import {
15
12
  resolveDevMode
16
- } from "./chunk-ZSESMG6L.js";
13
+ } from "./chunk-COI5VDFU.js";
17
14
 
18
15
  // src/commands/doctor.ts
19
16
  import { confirm, isCancel } from "@clack/prompts";
@@ -21,6 +18,7 @@ import { defineCommand } from "citty";
21
18
  import {
22
19
  appendEventLedgerEvent,
23
20
  checkLockOrThrow,
21
+ enrichDescriptions,
24
22
  runDoctorApplyLint as runDoctorFixKnowledge,
25
23
  runDoctorCiteCoverage,
26
24
  runDoctorFix,
@@ -59,11 +57,6 @@ var doctorCommand = defineCommand({
59
57
  description: t("cli.doctor.args.json.description"),
60
58
  default: false
61
59
  },
62
- rescan: {
63
- type: "boolean",
64
- description: t("cli.doctor.args.rescan.description"),
65
- default: false
66
- },
67
60
  strict: {
68
61
  type: "boolean",
69
62
  description: t("cli.doctor.args.strict.description"),
@@ -95,6 +88,24 @@ var doctorCommand = defineCommand({
95
88
  description: t("cli.doctor.args.client.description"),
96
89
  default: "all",
97
90
  valueHint: "cc|codex|cursor|all"
91
+ },
92
+ // rc.23 TASK-007 (a-C2): description-grade back-fill flag set. Read-side
93
+ // by default; `--auto` flips the writer arm on. Mutually exclusive with
94
+ // --fix / --fix-knowledge / --cite-coverage (different mutation surfaces).
95
+ "enrich-descriptions": {
96
+ type: "boolean",
97
+ description: t("cli.doctor.args.enrich-descriptions.description"),
98
+ default: false
99
+ },
100
+ auto: {
101
+ type: "boolean",
102
+ description: t("cli.doctor.args.auto.description"),
103
+ default: false
104
+ },
105
+ "dry-run": {
106
+ type: "boolean",
107
+ description: t("cli.doctor.args.dry-run.description"),
108
+ default: false
98
109
  }
99
110
  },
100
111
  async run({ args }) {
@@ -111,8 +122,27 @@ var doctorCommand = defineCommand({
111
122
  }
112
123
  const fixKnowledge = args["fix-knowledge"] === true;
113
124
  const fix = args.fix === true;
114
- const rescan = args.rescan === true;
115
125
  const citeCoverage = args["cite-coverage"] === true;
126
+ const enrichDesc = args["enrich-descriptions"] === true;
127
+ if (enrichDesc) {
128
+ if (fix || fixKnowledge || citeCoverage) {
129
+ writeStderr(t("cli.doctor.errors.enrich-descriptions-mutex"));
130
+ process.exitCode = 1;
131
+ return;
132
+ }
133
+ const autoFlag = args.auto === true;
134
+ const dryRun = args["dry-run"] === true;
135
+ const report2 = await enrichDescriptions(resolution.target, {
136
+ auto: autoFlag,
137
+ dryRun
138
+ });
139
+ if (args.json === true) {
140
+ writeStdout(JSON.stringify(report2, null, 2));
141
+ } else {
142
+ renderEnrichDescriptionsReport(report2);
143
+ }
144
+ return;
145
+ }
116
146
  if (citeCoverage) {
117
147
  if (fix || fixKnowledge) {
118
148
  writeStderr(t("cli.doctor.errors.cite-coverage-mutex"));
@@ -145,9 +175,6 @@ var doctorCommand = defineCommand({
145
175
  process.exitCode = 1;
146
176
  return;
147
177
  }
148
- if (rescan) {
149
- await runInitScan(resolution.target, { source: "doctor-rescan" });
150
- }
151
178
  let fixKnowledgeReport = null;
152
179
  let fixReport = null;
153
180
  let report;
@@ -392,8 +419,40 @@ function renderCiteCoverageReport(report, jsonMode) {
392
419
  lines.push(` ${label}: ${count}`);
393
420
  }
394
421
  }
422
+ if (report.none_reason_histogram !== void 0 && Object.keys(report.none_reason_histogram).length > 0) {
423
+ lines.push("");
424
+ lines.push(`### ${t("doctor.cite.section.noneReasons")}`);
425
+ for (const [reason, count] of Object.entries(report.none_reason_histogram)) {
426
+ const label = t(`doctor.cite.none.${reason}`);
427
+ lines.push(` ${label}: ${count}`);
428
+ }
429
+ }
395
430
  writeStdout(lines.join("\n"));
396
431
  }
432
+ function renderEnrichDescriptionsReport(report) {
433
+ const header = `${symbol.ok} ${paint.ai("fab doctor --enrich-descriptions")} mode=${report.mode}${report.dryRun ? " (dry-run)" : ""} scanned=${report.scanned} modified=${report.modified} skipped=${report.skipped}`;
434
+ writeStdout(header);
435
+ if (report.candidates.length === 0) {
436
+ writeStdout(t("doctor.enrich.allComplete"));
437
+ return;
438
+ }
439
+ writeStdout("");
440
+ for (const candidate of report.candidates) {
441
+ if (candidate.error !== void 0) {
442
+ writeStdout(`${symbol.error} ${candidate.path} \u2014 ${candidate.error}`);
443
+ continue;
444
+ }
445
+ const missing = candidate.missing.join(", ");
446
+ if (candidate.modified) {
447
+ const added = candidate.added_fields.join(", ");
448
+ writeStdout(
449
+ `${symbol.ok} ${candidate.path} \u2014 missing: ${missing} \u2192 added: ${added}`
450
+ );
451
+ } else {
452
+ writeStdout(`${symbol.warn} ${candidate.path} \u2014 missing: ${missing}`);
453
+ }
454
+ }
455
+ }
397
456
  function parseSinceDuration(input) {
398
457
  const trimmed = input.trim();
399
458
  if (trimmed.length === 0) {
package/dist/index.js CHANGED
@@ -11,19 +11,22 @@ import { defineCommand, runMain } from "citty";
11
11
 
12
12
  // src/commands/index.ts
13
13
  var allCommands = {
14
- install: () => import("./install-WJZQZM7D.js").then((module) => module.default),
15
- doctor: () => import("./doctor-HIX2FFEP.js").then((module) => module.default),
16
- serve: () => import("./serve-6PPQX7AW.js").then((module) => module.default),
17
- uninstall: () => import("./uninstall-L2HEEOU3.js").then((module) => module.default),
18
- config: () => import("./config-AYP5F72E.js").then((module) => module.default),
19
- "plan-context-hint": () => import("./plan-context-hint-RYVSMULL.js").then((module) => module.default)
14
+ install: () => import("./install-TDZYZV54.js").then((module) => module.default),
15
+ doctor: () => import("./doctor-R2E2XO6A.js").then((module) => module.default),
16
+ serve: () => import("./serve-NPCI342P.js").then((module) => module.default),
17
+ uninstall: () => import("./uninstall-MQM6NUFM.js").then((module) => module.default),
18
+ config: () => import("./config-XGUUAYX6.js").then((module) => module.default),
19
+ "plan-context-hint": () => import("./plan-context-hint-KPGOW3QC.js").then((module) => module.default),
20
+ // v2.0.0-rc.23 TASK-014 (F8c): S5 onboard-slot coverage. Used by the
21
+ // fabric-archive Skill's first-run phase to detect unclaimed slots.
22
+ "onboard-coverage": () => import("./onboard-coverage-JJ5NGU7I.js").then((module) => module.default)
20
23
  };
21
24
 
22
25
  // src/index.ts
23
26
  var main = defineCommand({
24
27
  meta: {
25
28
  name: "fabric",
26
- version: "2.0.0-rc.22",
29
+ version: "2.0.0-rc.23",
27
30
  description: t("cli.main.description")
28
31
  },
29
32
  subCommands: allCommands