@fenglimg/fabric-cli 1.4.0 → 1.5.1

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
@@ -1,26 +1,30 @@
1
1
  # @fenglimg/fabric-cli
2
2
 
3
- `fabric` is the primary CLI binary for Fabric. `fab` is a permanent alias, so you can use either binary.
3
+ `fabric` Fabric 的主命令,`fab` 是永久别名,两者等价。
4
4
 
5
- ## Quick Start
6
-
7
- 1. Install dependencies from the monorepo root with `pnpm install`.
8
- 2. Build the CLI with `pnpm --filter @fenglimg/fabric-cli build`.
9
- 3. Run `fabric init` in the target project for the one-shot setup flow.
10
- 4. Start `fabric serve` and verify `fab_get_rules` in your client.
11
-
12
- `fabric init` auto-runs `bootstrap install`, `config install`, and `hooks install`. Use them standalone only for targeted re-runs.
5
+ ## 快速开始
13
6
 
14
- `fabric bootstrap install` refreshes the internal bootstrap guide at `.fabric/bootstrap/README.md`. It does not generate root `AGENTS.md`, `CLAUDE.md`, or `GEMINI.md`.
15
-
16
- ## Common Commands
17
-
18
- - `fabric init`
19
- - `fabric serve`
20
- - `fabric doctor --audit`
21
-
22
- ## Advanced Commands
7
+ 1. monorepo 根目录运行 `pnpm install`。
8
+ 2. 用 `pnpm --filter @fenglimg/fabric-cli build` 构建 CLI。
9
+ 3. 在目标项目运行 `fabric init`,完成一站式初始化。
10
+ 4. 启动 `fabric serve`,再去客户端里验证 `fab_get_rules`。
11
+
12
+ `fabric init` 会自动执行 `bootstrap install`、`config install` 和 `hooks install`。只有在需要单独重跑某个阶段时,才需要单独调用它们。
13
+
14
+ `fabric bootstrap install` 只会刷新 `.fabric/bootstrap/README.md` 里的内部初始化说明,不会再生成根级 `AGENTS.md`、`CLAUDE.md` 或 `GEMINI.md`。
23
15
 
24
- - `fabric bootstrap install`
25
- - `fabric config install`
26
- - `fabric hooks install`
16
+ ## 常用命令
17
+
18
+ - `fabric init`
19
+ - `fabric serve`
20
+ - `fabric doctor --audit`
21
+ - `fabric approve --interactive`
22
+ - `fabric approve --all`
23
+
24
+ ## 进阶命令
25
+
26
+ - `fabric bootstrap install`
27
+ - `fabric config install`
28
+ - `fabric hooks install`
29
+
30
+ `fabric approve` 会在审查完成后更新 `.fabric/human-lock.json` 中已经发生漂移的条目。需要逐项确认时使用 `--interactive`,只有在别处已经完成审查时才使用 `--all`。
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ padEnd
4
+ } from "./chunk-WWNXR34K.js";
5
+ import {
6
+ t
7
+ } from "./chunk-6ICJICVU.js";
8
+
9
+ // src/commands/approve.ts
10
+ import { createInterface } from "readline/promises";
11
+ import { stdin as input, stdout as output } from "process";
12
+ import { isAbsolute, resolve } from "path";
13
+ import { approveHumanLock, readHumanLock } from "@fenglimg/fabric-server";
14
+ import { defineCommand, renderUsage } from "citty";
15
+ var approveCommand = defineCommand({
16
+ meta: {
17
+ name: "approve",
18
+ description: t("cli.approve.description")
19
+ },
20
+ args: {
21
+ all: {
22
+ type: "boolean",
23
+ description: t("cli.approve.args.all.description"),
24
+ default: false
25
+ },
26
+ interactive: {
27
+ type: "boolean",
28
+ description: t("cli.approve.args.interactive.description"),
29
+ default: false
30
+ },
31
+ target: {
32
+ type: "string",
33
+ description: t("cli.approve.args.target.description"),
34
+ default: process.cwd()
35
+ }
36
+ },
37
+ async run({ args }) {
38
+ const target = normalizeTarget(args.target);
39
+ if (args.all === args.interactive) {
40
+ writeStdout(await renderUsage(approveCommand));
41
+ process.exitCode = 1;
42
+ return;
43
+ }
44
+ if (args.all) {
45
+ await runApproveAll(target);
46
+ return;
47
+ }
48
+ await runApproveInteractive(target);
49
+ }
50
+ });
51
+ var approve_default = approveCommand;
52
+ async function runApproveAll(projectRoot) {
53
+ const driftEntries = await readDriftEntries(projectRoot);
54
+ if (driftEntries.length === 0) {
55
+ writeStdout(t("cli.approve.no-drift"));
56
+ return;
57
+ }
58
+ let approvedCount = 0;
59
+ for (const entry of driftEntries) {
60
+ await approveEntry(projectRoot, entry);
61
+ approvedCount += 1;
62
+ }
63
+ writeStdout(t("cli.approve.summary", { approved: String(approvedCount), skipped: "0", total: String(driftEntries.length) }));
64
+ }
65
+ async function runApproveInteractive(projectRoot) {
66
+ const driftEntries = await readDriftEntries(projectRoot);
67
+ if (driftEntries.length === 0) {
68
+ writeStdout(t("cli.approve.no-drift"));
69
+ return;
70
+ }
71
+ const rl = createInterface({ input, output });
72
+ let approvedCount = 0;
73
+ let skippedCount = 0;
74
+ try {
75
+ for (const entry of driftEntries) {
76
+ writeStdout(formatEntry(entry));
77
+ const answer = (await rl.question(t("cli.approve.prompt"))).trim().toLowerCase();
78
+ if (answer === "y" || answer === "yes") {
79
+ await approveEntry(projectRoot, entry);
80
+ approvedCount += 1;
81
+ writeStdout(t("cli.approve.approved-one", { location: formatLocation(entry) }));
82
+ continue;
83
+ }
84
+ skippedCount += 1;
85
+ writeStdout(t("cli.approve.skipped-one", { location: formatLocation(entry) }));
86
+ }
87
+ } finally {
88
+ rl.close();
89
+ }
90
+ writeStdout(
91
+ t("cli.approve.summary", {
92
+ approved: String(approvedCount),
93
+ skipped: String(skippedCount),
94
+ total: String(driftEntries.length)
95
+ })
96
+ );
97
+ }
98
+ async function readDriftEntries(projectRoot) {
99
+ const entries = await readHumanLock(projectRoot);
100
+ return entries.filter((entry) => entry.drift);
101
+ }
102
+ async function approveEntry(projectRoot, entry) {
103
+ await approveHumanLock(projectRoot, {
104
+ file: entry.file,
105
+ start_line: entry.start_line,
106
+ end_line: entry.end_line,
107
+ new_hash: entry.current_hash
108
+ });
109
+ }
110
+ function normalizeTarget(targetInput) {
111
+ return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
112
+ }
113
+ function formatEntry(entry) {
114
+ return [
115
+ formatLocation(entry),
116
+ `${padEnd(t("cli.approve.table.expected"), 10)} ${shortenHash(entry.hash)}`,
117
+ `${padEnd(t("cli.approve.table.current"), 10)} ${shortenHash(entry.current_hash)}`
118
+ ].join("\n");
119
+ }
120
+ function formatLocation(entry) {
121
+ return `${entry.file}:${entry.start_line}-${entry.end_line}`;
122
+ }
123
+ function shortenHash(value) {
124
+ if (value === "missing") {
125
+ return t("cli.shared.missing");
126
+ }
127
+ return value.slice(0, 15);
128
+ }
129
+ function writeStdout(message) {
130
+ process.stdout.write(`${message}
131
+ `);
132
+ }
133
+ export {
134
+ approveCommand,
135
+ approve_default as default,
136
+ runApproveAll,
137
+ runApproveInteractive
138
+ };
@@ -3,11 +3,11 @@ import {
3
3
  bootstrapCommand,
4
4
  bootstrap_default,
5
5
  installBootstrap
6
- } from "./chunk-XQYY2U2C.js";
7
- import "./chunk-AZRKMFRY.js";
8
- import "./chunk-BEKSXO5N.js";
6
+ } from "./chunk-T2WJF5I3.js";
7
+ import "./chunk-QSAEGVKE.js";
9
8
  import "./chunk-AEOYCVBG.js";
10
9
  import "./chunk-WWNXR34K.js";
10
+ import "./chunk-BEKSXO5N.js";
11
11
  import "./chunk-6ICJICVU.js";
12
12
  export {
13
13
  bootstrapCommand,
@@ -40,7 +40,7 @@ function resolveIgnores(fabricConfig) {
40
40
  }
41
41
 
42
42
  // src/commands/scan.ts
43
- function createScanReport(targetInput = process.cwd(), fabricConfig) {
43
+ async function createScanReport(targetInput = process.cwd(), fabricConfig) {
44
44
  const target = normalizeTarget(targetInput);
45
45
  const framework = detectFramework(target);
46
46
  const readmeQuality = getReadmeQuality(target);
@@ -93,7 +93,7 @@ var scanCommand = defineCommand({
93
93
  for (const step of resolution.chain) {
94
94
  logger(step);
95
95
  }
96
- const report = createScanReport(resolution.target, fabricConfig);
96
+ const report = await createScanReport(resolution.target, fabricConfig);
97
97
  if (args.json) {
98
98
  console.log(JSON.stringify(report, null, 2));
99
99
  return;
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createScanReport
4
- } from "./chunk-AZRKMFRY.js";
5
- import {
6
- resolveClients
7
- } from "./chunk-BEKSXO5N.js";
4
+ } from "./chunk-QSAEGVKE.js";
8
5
  import {
9
6
  readFabricConfig
10
7
  } from "./chunk-AEOYCVBG.js";
8
+ import {
9
+ resolveClients
10
+ } from "./chunk-BEKSXO5N.js";
11
11
  import {
12
12
  t
13
13
  } from "./chunk-6ICJICVU.js";
@@ -26,22 +26,22 @@ var AGENTS_TEMPLATE_BY_FRAMEWORK = {
26
26
  next: "templates/agents-md/variants/next.md"
27
27
  };
28
28
  var FABRIC_GUIDE_PATH = ".fabric/bootstrap/README.md";
29
- function buildFabricBootstrapGuide(target) {
29
+ async function buildFabricBootstrapGuide(target) {
30
30
  const workspaceRoot = normalizeTarget(target);
31
- const scanReport = createScanReport(workspaceRoot);
31
+ const scanReport = await createScanReport(workspaceRoot);
32
32
  const template = readFileSync(findBootstrapTemplatePath(scanReport.framework.kind), "utf8");
33
33
  const packageName = readPackageName(workspaceRoot) ?? parse(workspaceRoot).base;
34
34
  return ensureTrailingNewline(
35
35
  template.replaceAll("{ projectName }", packageName).replaceAll("{ frameworkKind }", scanReport.framework.kind)
36
36
  );
37
37
  }
38
- function ensureFabricBootstrapGuide(workspaceRoot, force) {
38
+ async function ensureFabricBootstrapGuide(workspaceRoot, force) {
39
39
  const guidePath = resolve(workspaceRoot, FABRIC_GUIDE_PATH);
40
40
  if (existsSync(guidePath) && !force) {
41
41
  return;
42
42
  }
43
43
  mkdirSync(dirname(guidePath), { recursive: true });
44
- writeFileSync(guidePath, buildFabricBootstrapGuide(workspaceRoot), "utf8");
44
+ writeFileSync(guidePath, await buildFabricBootstrapGuide(workspaceRoot), "utf8");
45
45
  }
46
46
  function findBootstrapTemplatePath(frameworkKind) {
47
47
  const relativePath = AGENTS_TEMPLATE_BY_FRAMEWORK[frameworkKind] ?? "templates/agents-md/AGENTS.md.template";
@@ -170,7 +170,7 @@ async function installBootstrap(target, options = {}) {
170
170
  const installed = [];
171
171
  const skipped = [];
172
172
  const details = [];
173
- ensureFabricBootstrapGuide(workspaceRoot, options.force);
173
+ await ensureFabricBootstrapGuide(workspaceRoot, options.force);
174
174
  for (const bootstrapTarget of targets) {
175
175
  details.push({
176
176
  client: bootstrapTarget.client,
package/dist/index.js CHANGED
@@ -8,16 +8,17 @@ import { defineCommand, runMain } from "citty";
8
8
 
9
9
  // src/commands/index.ts
10
10
  var allCommands = {
11
- init: () => import("./init-QC2MLFHR.js").then((module) => module.default),
12
- update: () => import("./update-FY2WKWPB.js").then((module) => module.default),
13
- scan: () => import("./scan-43R3IBLR.js").then((module) => module.default),
11
+ approve: () => import("./approve-YT4DEABS.js").then((module) => module.default),
12
+ init: () => import("./init-QPKI2QOW.js").then((module) => module.default),
13
+ update: () => import("./update-M5M5PYKE.js").then((module) => module.default),
14
+ scan: () => import("./scan-QH76LC7Z.js").then((module) => module.default),
14
15
  serve: () => import("./serve-4J2CQY25.js").then((module) => module.default),
15
16
  doctor: () => import("./doctor-QTSG2RWF.js").then((module) => module.default),
16
17
  "sync-meta": () => import("./sync-meta-LKVSO6TS.js").then((module) => module.default),
17
18
  "human-lint": () => import("./human-lint-YSFOZHZ7.js").then((module) => module.default),
18
19
  "ledger-append": () => import("./ledger-append-DULKJ6Q2.js").then((module) => module.default),
19
20
  "pre-commit": () => import("./pre-commit-IK6SJOPT.js").then((module) => module.default),
20
- bootstrap: () => import("./bootstrap-B6RCVJZJ.js").then((module) => module.default),
21
+ bootstrap: () => import("./bootstrap-VGL3AR26.js").then((module) => module.default),
21
22
  config: () => import("./config-EC5L2QNI.js").then((module) => module.configCmd),
22
23
  hooks: () => import("./hooks-ZSWVH2JD.js").then((module) => ({
23
24
  ...module.default,
@@ -32,7 +33,7 @@ var allCommands = {
32
33
  var main = defineCommand({
33
34
  meta: {
34
35
  name: "fabric",
35
- version: "1.4.0",
36
+ version: "1.5.1",
36
37
  description: 'Initialize and manage Fabric projects. Use "fabric init" for one-shot setup.'
37
38
  },
38
39
  subCommands: allCommands
@@ -2,19 +2,10 @@
2
2
  import {
3
3
  buildFabricBootstrapGuide,
4
4
  installBootstrap
5
- } from "./chunk-XQYY2U2C.js";
5
+ } from "./chunk-T2WJF5I3.js";
6
6
  import {
7
7
  detectFramework
8
- } from "./chunk-AZRKMFRY.js";
9
- import {
10
- installMcpClients
11
- } from "./chunk-BVTMVW5M.js";
12
- import {
13
- detectClientSupports
14
- } from "./chunk-BEKSXO5N.js";
15
- import {
16
- installHooks
17
- } from "./chunk-YDZJRLHL.js";
8
+ } from "./chunk-QSAEGVKE.js";
18
9
  import {
19
10
  createDebugLogger,
20
11
  resolveDevMode
@@ -24,6 +15,15 @@ import {
24
15
  padEnd,
25
16
  paint
26
17
  } from "./chunk-WWNXR34K.js";
18
+ import {
19
+ installMcpClients
20
+ } from "./chunk-BVTMVW5M.js";
21
+ import {
22
+ detectClientSupports
23
+ } from "./chunk-BEKSXO5N.js";
24
+ import {
25
+ installHooks
26
+ } from "./chunk-YDZJRLHL.js";
27
27
  import {
28
28
  t
29
29
  } from "./chunk-6ICJICVU.js";
@@ -38,11 +38,14 @@ import { cancel, confirm, group, intro, isCancel, log, note, outro, select } fro
38
38
  import { defineCommand } from "citty";
39
39
 
40
40
  // src/scanner/forensic.ts
41
+ import { execFileSync } from "child_process";
41
42
  import { existsSync, readdirSync, readFileSync, statSync } from "fs";
43
+ import { createRequire } from "module";
42
44
  import { basename, extname, isAbsolute, join, posix, relative, resolve, sep } from "path";
43
45
  import {
44
46
  forensicReportSchema
45
47
  } from "@fenglimg/fabric-shared";
48
+ var require2 = createRequire(import.meta.url);
46
49
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
47
50
  ".fabric",
48
51
  ".git",
@@ -68,9 +71,48 @@ var SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx"]);
68
71
  var DOMAIN_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".ts", ".tsx", ".json", ".md"]);
69
72
  var EXPECTED_CONFIG_FILES_BY_FRAMEWORK = {
70
73
  "cocos-creator": ["package.json", "project.config.json", "tsconfig.json"],
74
+ react: ["package.json", "tsconfig.json"],
71
75
  next: ["package.json", "tsconfig.json"],
72
76
  vite: ["package.json", "tsconfig.json"]
73
77
  };
78
+ var FRAMEWORK_IMPORT_PROFILES = {
79
+ "cocos-creator": {
80
+ pattern: "cocos-component-class",
81
+ family: "component",
82
+ statement: "Sampled entry files use Cocos Creator component classes.",
83
+ proposedRule: "Treat assets/scripts/*.ts and adjacent .meta files as framework-owned structure unless the user says otherwise.",
84
+ alternatives: ["Generic TypeScript utility module"],
85
+ rationale: "Cocos framework imports and component markers co-occur in sampled entry files.",
86
+ packages: ["cc"]
87
+ },
88
+ react: {
89
+ pattern: "react-root",
90
+ family: "entry",
91
+ statement: "Sampled entry files import React framework packages.",
92
+ proposedRule: "Keep root rendering and component composition aligned with React entry conventions.",
93
+ alternatives: ["Server-rendered route module"],
94
+ rationale: "AST import declarations reference React packages rather than comments or strings.",
95
+ packages: ["react", "react-dom", "react/jsx-runtime", "react-dom/client"]
96
+ },
97
+ vite: {
98
+ pattern: "vite-main-entry",
99
+ family: "entry",
100
+ statement: "Sampled entry files use the conventional Vite main entrypoint.",
101
+ proposedRule: "Keep primary bootstrapping logic inside src/main.*.",
102
+ alternatives: ["Alternative bundler entrypoint"],
103
+ rationale: "Entry path and framework imports align with a Vite bootstrap surface.",
104
+ packages: ["@vitejs/plugin-react", "@vitejs/plugin-vue", "vite", "react", "vue"]
105
+ },
106
+ next: {
107
+ pattern: "next-route-component",
108
+ family: "entry",
109
+ statement: "Sampled entry files align with Next.js route modules.",
110
+ proposedRule: "Preserve route-segment boundaries when editing app/ or pages/ files.",
111
+ alternatives: ["Generic source module"],
112
+ rationale: "Route placement and Next/React imports anchor these files to the request surface.",
113
+ packages: ["next", "next/link", "next/navigation", "react"]
114
+ }
115
+ };
74
116
  var SAMPLE_LIMIT = 5;
75
117
  var SAMPLE_LINE_LIMIT = 30;
76
118
  var ENTRY_FAMILY_LIMIT = 1;
@@ -80,12 +122,17 @@ var DEFAULT_SAMPLING_BUDGET = {
80
122
  max_files: 15,
81
123
  max_lines_per_file: 100
82
124
  };
83
- function buildForensicReport(targetInput) {
125
+ var treeSitterModulePromise = null;
126
+ var parserInitPromise = null;
127
+ var languagePromiseByKind = {};
128
+ var parserBundlePromiseByKind = {};
129
+ async function buildForensicReport(targetInput) {
84
130
  const target = normalizeTarget(targetInput);
85
131
  const framework = detectFramework(target);
86
132
  const topology = buildTopology(target);
87
- const entryPoints = collectEntryPoints(topology.files);
88
- const codeSamples = buildCodeSamples(target, entryPoints);
133
+ const entryPoints = collectEntryPoints(target, topology.files);
134
+ const packageDependencies = readPackageDependencies(target);
135
+ const codeSamples = await buildCodeSamples(target, entryPoints, framework.kind, topology, packageDependencies);
89
136
  const assertions = buildAssertions(framework.kind, topology, codeSamples);
90
137
  const candidateFiles = buildCandidateFiles(topology, codeSamples, entryPoints);
91
138
  const readme = readReadmeInfo(target);
@@ -180,7 +227,7 @@ function isKeyDirectory(relativePath) {
180
227
  const name = basename(relativePath);
181
228
  return KEY_DIRECTORY_NAMES.has(name);
182
229
  }
183
- function collectEntryPoints(files) {
230
+ function collectEntryPoints(target, files) {
184
231
  const entryPoints = [];
185
232
  for (const file of files) {
186
233
  const reason = getEntryPointReason(file.relativePath);
@@ -193,7 +240,9 @@ function collectEntryPoints(files) {
193
240
  size_bytes: file.sizeBytes
194
241
  });
195
242
  }
196
- return entryPoints;
243
+ return entryPoints.sort(
244
+ (left, right) => compareCandidateScore(readGitChurnWeight(target, right.path), readGitChurnWeight(target, left.path))
245
+ );
197
246
  }
198
247
  function getEntryPointReason(relativePath) {
199
248
  if (!SCRIPT_EXTENSIONS.has(extname(relativePath))) {
@@ -216,20 +265,26 @@ function getEntryPointReason(relativePath) {
216
265
  }
217
266
  return null;
218
267
  }
219
- function buildCodeSamples(target, entryPoints) {
220
- return entryPoints.slice(0, SAMPLE_LIMIT).map((entryPoint) => {
268
+ async function buildCodeSamples(target, entryPoints, frameworkKind, topology, packageDependencies) {
269
+ const samples = [];
270
+ for (const entryPoint of entryPoints.slice(0, SAMPLE_LIMIT)) {
221
271
  const absolutePath = join(target, ...entryPoint.path.split("/"));
222
272
  const sample = readFirstLines(absolutePath, SAMPLE_LINE_LIMIT);
223
- const patternAnalysis = inferPatternHint(entryPoint.path, sample.snippet);
224
- return {
273
+ const patternAnalysis = await inferPatternHint(entryPoint.path, sample.snippet, {
274
+ frameworkKind,
275
+ topology,
276
+ packageDependencies
277
+ });
278
+ samples.push({
225
279
  path: entryPoint.path,
226
280
  lines: `1-${sample.lineCount}`,
227
281
  snippet: sample.snippet,
228
282
  pattern_hint: patternAnalysis.pattern,
229
283
  pattern_analysis: patternAnalysis,
230
284
  evidence: buildEvidenceAnchors(entryPoint.path, sample.snippet, patternAnalysis.evidence_lines)
231
- };
232
- });
285
+ });
286
+ }
287
+ return samples;
233
288
  }
234
289
  function readFirstLines(path, lineLimit) {
235
290
  try {
@@ -249,7 +304,100 @@ function readFirstLines(path, lineLimit) {
249
304
  };
250
305
  }
251
306
  }
252
- function inferPatternHint(relativePath, snippet) {
307
+ function readPackageDependencies(target) {
308
+ const packageJsonPath = join(target, "package.json");
309
+ if (!existsSync(packageJsonPath)) {
310
+ return /* @__PURE__ */ new Map();
311
+ }
312
+ try {
313
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
314
+ return new Map([
315
+ ...Object.entries(packageJson.dependencies ?? {}),
316
+ ...Object.entries(packageJson.devDependencies ?? {}),
317
+ ...Object.entries(packageJson.peerDependencies ?? {}),
318
+ ...Object.entries(packageJson.optionalDependencies ?? {})
319
+ ]);
320
+ } catch {
321
+ return /* @__PURE__ */ new Map();
322
+ }
323
+ }
324
+ function readGitChurnWeight(target, relativePath) {
325
+ try {
326
+ const output = execFileSync("git", ["log", "--follow", "--oneline", "-20", "--", relativePath], {
327
+ cwd: target,
328
+ encoding: "utf8",
329
+ stdio: ["ignore", "pipe", "ignore"],
330
+ timeout: 1e3
331
+ });
332
+ return output.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
333
+ } catch {
334
+ return 0;
335
+ }
336
+ }
337
+ async function inferPatternHint(relativePath, snippet, options = {}) {
338
+ const input = {
339
+ relativePath,
340
+ snippet,
341
+ frameworkKind: options.frameworkKind ?? "unknown",
342
+ topology: options.topology ?? createEmptyTopology(),
343
+ packageDependencies: options.packageDependencies ?? /* @__PURE__ */ new Map()
344
+ };
345
+ const importAnalysis = await analyzeImports(input.relativePath, input.snippet);
346
+ if (importAnalysis.astLevel) {
347
+ const astResult = buildAstPatternHint(input, importAnalysis.imports);
348
+ if (astResult !== null) {
349
+ return astResult;
350
+ }
351
+ }
352
+ return inferTextPatternHint(input.relativePath, input.snippet);
353
+ }
354
+ function createEmptyTopology() {
355
+ return {
356
+ total_files: 0,
357
+ by_ext: {},
358
+ key_dirs: [],
359
+ max_depth: 0,
360
+ files: []
361
+ };
362
+ }
363
+ function buildAstPatternHint(input, imports) {
364
+ const profile = resolveFrameworkImportProfile(input.frameworkKind, input.relativePath, imports);
365
+ if (profile === null) {
366
+ return null;
367
+ }
368
+ const matchingImports = imports.filter((source) => matchesAnyFrameworkPackage(source, profile.packages));
369
+ const configFiles = getExpectedConfigFiles(input.frameworkKind).filter((file) => hasFile(input.topology.files, file));
370
+ const packageMatches = profile.packages.filter((packageName) => input.packageDependencies.has(packageName));
371
+ const coOccurring = compactPatternNames([
372
+ ...matchingImports.map((source) => `import:${source}`),
373
+ ...configFiles.map(normalizeConfigPattern),
374
+ ...packageMatches.map((packageName) => `package:${packageName}`),
375
+ input.relativePath.startsWith("app/") ? "app-router" : null,
376
+ input.relativePath.startsWith("pages/") ? "pages-router" : null,
377
+ input.relativePath === "src/main.ts" || input.relativePath === "src/main.js" ? "main-entry" : null,
378
+ input.snippet.includes("@ccclass(") ? "ccclass-decorator" : null,
379
+ input.snippet.includes("extends Component") ? "component-base" : null
380
+ ]);
381
+ return {
382
+ pattern: profile.pattern,
383
+ type: "pattern",
384
+ confidence: scoreFrameworkConfidence({
385
+ importCount: matchingImports.length,
386
+ configCount: configFiles.length,
387
+ packageCount: packageMatches.length,
388
+ astLevel: true
389
+ }),
390
+ evidence_lines: matchingImports.length > 0 ? matchingImports : imports.slice(0, 3),
391
+ co_occurring: coOccurring,
392
+ family: profile.family,
393
+ ast_level: true,
394
+ statement: profile.statement,
395
+ proposed_rule: profile.proposedRule,
396
+ alternatives: profile.alternatives,
397
+ rationale: profile.rationale
398
+ };
399
+ }
400
+ function inferTextPatternHint(relativePath, snippet) {
253
401
  const cocosCoOccurring = compactPatternNames([
254
402
  snippet.includes('from "cc"') || snippet.includes("from 'cc'") ? "cc-import" : null,
255
403
  snippet.includes("@ccclass(") || snippet.includes("ccclass(") ? "ccclass-decorator" : null,
@@ -257,11 +405,16 @@ function inferPatternHint(relativePath, snippet) {
257
405
  snippet.includes("const { ccclass } = _decorator") ? "decorator-destructure" : null
258
406
  ]);
259
407
  if (cocosCoOccurring.length > 0) {
260
- const astLevel = snippet.includes("@ccclass(");
261
408
  return {
262
409
  pattern: "cocos-component-class",
263
410
  type: "pattern",
264
- confidence: determineConfidence(1, cocosCoOccurring, astLevel),
411
+ confidence: scoreFrameworkConfidence({
412
+ importCount: 0,
413
+ configCount: 0,
414
+ packageCount: 0,
415
+ astLevel: false,
416
+ keywordCount: cocosCoOccurring.length
417
+ }),
265
418
  evidence_lines: compactPatternNames([
266
419
  snippet.includes("_decorator") ? "_decorator" : null,
267
420
  snippet.includes("@ccclass(") ? "@ccclass(" : null,
@@ -269,7 +422,7 @@ function inferPatternHint(relativePath, snippet) {
269
422
  ]),
270
423
  co_occurring: cocosCoOccurring,
271
424
  family: "component",
272
- ast_level: astLevel,
425
+ ast_level: false,
273
426
  statement: "Sampled entry files use Cocos Creator component classes.",
274
427
  proposed_rule: "Treat assets/scripts/*.ts and adjacent .meta files as framework-owned structure unless the user says otherwise.",
275
428
  alternatives: ["Generic TypeScript utility module"],
@@ -285,7 +438,13 @@ function inferPatternHint(relativePath, snippet) {
285
438
  return {
286
439
  pattern: "react-root",
287
440
  type: "pattern",
288
- confidence: determineConfidence(1, reactCoOccurring, false),
441
+ confidence: scoreFrameworkConfidence({
442
+ importCount: 0,
443
+ configCount: 0,
444
+ packageCount: 0,
445
+ astLevel: false,
446
+ keywordCount: reactCoOccurring.length
447
+ }),
289
448
  evidence_lines: compactPatternNames([
290
449
  snippet.includes("createRoot(") ? "createRoot(" : null,
291
450
  snippet.includes("ReactDOM.render(") ? "ReactDOM.render(" : null
@@ -308,7 +467,13 @@ function inferPatternHint(relativePath, snippet) {
308
467
  return {
309
468
  pattern: "next-route-component",
310
469
  type: "pattern",
311
- confidence: determineConfidence(1, coOccurring, false),
470
+ confidence: scoreFrameworkConfidence({
471
+ importCount: 0,
472
+ configCount: 0,
473
+ packageCount: 0,
474
+ astLevel: false,
475
+ keywordCount: coOccurring.length
476
+ }),
312
477
  evidence_lines: compactPatternNames([
313
478
  relativePath.startsWith("app/") ? "app/" : null,
314
479
  relativePath.startsWith("pages/") ? "pages/" : null
@@ -331,7 +496,13 @@ function inferPatternHint(relativePath, snippet) {
331
496
  return {
332
497
  pattern: "vite-main-entry",
333
498
  type: "pattern",
334
- confidence: determineConfidence(1, coOccurring, false),
499
+ confidence: scoreFrameworkConfidence({
500
+ importCount: 0,
501
+ configCount: 0,
502
+ packageCount: 0,
503
+ astLevel: false,
504
+ keywordCount: coOccurring.length
505
+ }),
335
506
  evidence_lines: ["src/main"],
336
507
  co_occurring: coOccurring,
337
508
  family: "entry",
@@ -355,6 +526,125 @@ function inferPatternHint(relativePath, snippet) {
355
526
  rationale: "No strong framework markers were detected in the sampled snippet."
356
527
  };
357
528
  }
529
+ async function analyzeImports(relativePath, snippet) {
530
+ if (snippet.trim().length === 0) {
531
+ return { imports: [], astLevel: false };
532
+ }
533
+ try {
534
+ const imports = await extractImports(snippet, getLanguageKindForPath(relativePath));
535
+ return { imports, astLevel: true };
536
+ } catch {
537
+ return { imports: [], astLevel: false };
538
+ }
539
+ }
540
+ async function extractImports(source, languageKind) {
541
+ const { parser } = await loadTreeSitter(languageKind);
542
+ let tree = null;
543
+ try {
544
+ tree = parser.parse(source);
545
+ if (tree === null || tree.rootNode.hasError) {
546
+ throw new Error("tree-sitter parse failed");
547
+ }
548
+ const imports = [];
549
+ collectImportSources(tree.rootNode, imports);
550
+ return compactPatternNames(imports);
551
+ } finally {
552
+ tree?.delete();
553
+ }
554
+ }
555
+ async function loadTreeSitter(languageKind) {
556
+ parserBundlePromiseByKind[languageKind] ??= createTreeSitterParserBundle(languageKind);
557
+ return parserBundlePromiseByKind[languageKind];
558
+ }
559
+ async function createTreeSitterParserBundle(languageKind) {
560
+ const treeSitter = await loadTreeSitterModule();
561
+ await initTreeSitterParser(treeSitter);
562
+ const language = await loadTreeSitterLanguage(treeSitter, languageKind);
563
+ const parser = new treeSitter.Parser();
564
+ parser.setLanguage(language);
565
+ return { parser, language };
566
+ }
567
+ function loadTreeSitterModule() {
568
+ treeSitterModulePromise ??= import("web-tree-sitter");
569
+ return treeSitterModulePromise;
570
+ }
571
+ function initTreeSitterParser(treeSitter) {
572
+ parserInitPromise ??= treeSitter.Parser.init({
573
+ locateFile: (scriptName) => scriptName.endsWith(".wasm") ? require2.resolve("web-tree-sitter/web-tree-sitter.wasm") : scriptName
574
+ });
575
+ return parserInitPromise;
576
+ }
577
+ function loadTreeSitterLanguage(treeSitter, languageKind) {
578
+ languagePromiseByKind[languageKind] ??= treeSitter.Language.load(resolveTreeSitterGrammarPath(languageKind));
579
+ return languagePromiseByKind[languageKind];
580
+ }
581
+ function resolveTreeSitterGrammarPath(languageKind) {
582
+ switch (languageKind) {
583
+ case "typescript":
584
+ return require2.resolve("tree-sitter-typescript/tree-sitter-typescript.wasm");
585
+ case "tsx":
586
+ return require2.resolve("tree-sitter-typescript/tree-sitter-tsx.wasm");
587
+ case "javascript":
588
+ return require2.resolve("tree-sitter-javascript/tree-sitter-javascript.wasm");
589
+ }
590
+ }
591
+ function getLanguageKindForPath(relativePath) {
592
+ const extension = extname(relativePath);
593
+ if (extension === ".tsx") {
594
+ return "tsx";
595
+ }
596
+ if (extension === ".ts") {
597
+ return "typescript";
598
+ }
599
+ return "javascript";
600
+ }
601
+ function collectImportSources(node, imports) {
602
+ if (node.type === "import_statement" || node.type === "import_declaration") {
603
+ const sourceNode = node.childForFieldName("source");
604
+ if (sourceNode !== null) {
605
+ const source = stripStringLiteral(sourceNode.text);
606
+ if (source.length > 0) {
607
+ imports.push(source);
608
+ }
609
+ }
610
+ }
611
+ for (let index = 0; index < node.namedChildCount; index += 1) {
612
+ const child = node.namedChild(index);
613
+ if (child !== null) {
614
+ collectImportSources(child, imports);
615
+ }
616
+ }
617
+ }
618
+ function stripStringLiteral(value) {
619
+ return value.replace(/^['"]|['"]$/g, "");
620
+ }
621
+ function resolveFrameworkImportProfile(frameworkKind, relativePath, imports) {
622
+ const primaryProfile = FRAMEWORK_IMPORT_PROFILES[frameworkKind];
623
+ if (primaryProfile !== void 0 && imports.some((source) => matchesAnyFrameworkPackage(source, primaryProfile.packages))) {
624
+ return primaryProfile;
625
+ }
626
+ if ((relativePath.startsWith("app/") || relativePath.startsWith("pages/")) && FRAMEWORK_IMPORT_PROFILES.next !== void 0) {
627
+ return FRAMEWORK_IMPORT_PROFILES.next;
628
+ }
629
+ return Object.values(FRAMEWORK_IMPORT_PROFILES).find(
630
+ (profile) => imports.some((source) => matchesAnyFrameworkPackage(source, profile.packages))
631
+ ) ?? null;
632
+ }
633
+ function matchesAnyFrameworkPackage(source, packageNames) {
634
+ return packageNames.some((packageName) => source === packageName || source.startsWith(`${packageName}/`));
635
+ }
636
+ function scoreFrameworkConfidence(input) {
637
+ if (!input.astLevel) {
638
+ return (input.keywordCount ?? 0) > 0 ? "MEDIUM" : "LOW";
639
+ }
640
+ if (input.importCount > 3) {
641
+ return "HIGH";
642
+ }
643
+ if (input.importCount >= 1 && input.importCount <= 3) {
644
+ return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "MEDIUM";
645
+ }
646
+ return input.configCount > 0 || input.packageCount > 0 ? "MEDIUM" : "LOW";
647
+ }
358
648
  function readReadmeInfo(target) {
359
649
  const readmePath = join(target, "README.md");
360
650
  const hasContributing = existsSync(join(target, "CONTRIBUTING.md"));
@@ -857,7 +1147,7 @@ function readProjectName(target) {
857
1147
  return basename(target);
858
1148
  }
859
1149
  function getCliVersion() {
860
- return true ? "1.4.0" : "unknown";
1150
+ return true ? "1.5.1" : "unknown";
861
1151
  }
862
1152
  function sortRecord(record) {
863
1153
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
@@ -962,7 +1252,7 @@ async function runInitCommand(args) {
962
1252
  writeStderr(t("cli.init.compat.legacy-stage-flags"));
963
1253
  }
964
1254
  const supports = detectClientSupports(intent.target);
965
- const basePlan = buildInitExecutionPlan({
1255
+ const basePlan = await buildInitExecutionPlan({
966
1256
  target: intent.target,
967
1257
  options: intent.options,
968
1258
  mcpInstallMode: intent.mcpInstallMode,
@@ -998,9 +1288,9 @@ function resolveInitCliIntent(args, targetInput) {
998
1288
  wizardEnabled: shouldUseInitWizard(args, terminalInteractive) && !planOnly
999
1289
  };
1000
1290
  }
1001
- function buildInitExecutionPlan(input) {
1291
+ async function buildInitExecutionPlan(input) {
1002
1292
  const options = input.options ?? {};
1003
- const scaffold = buildInitFabricPlan(input.target, options);
1293
+ const scaffold = await buildInitFabricPlan(input.target, options);
1004
1294
  const supports = input.supports ?? detectClientSupports(input.target);
1005
1295
  const mcpInstallMode = input.mcpInstallMode ?? "global";
1006
1296
  const stages = [
@@ -1080,7 +1370,7 @@ async function executeInitExecutionPlan(plan) {
1080
1370
  finalSupports
1081
1371
  };
1082
1372
  }
1083
- function buildInitFabricPlan(target, options) {
1373
+ async function buildInitFabricPlan(target, options) {
1084
1374
  assertExistingDirectory2(target);
1085
1375
  const fabricDir = join2(target, ".fabric");
1086
1376
  const bootstrapPath = join2(fabricDir, "bootstrap", "README.md");
@@ -1099,9 +1389,9 @@ function buildInitFabricPlan(target, options) {
1099
1389
  const metaAction = planFreshPath(metaPath, options);
1100
1390
  const humanLockAction = planFreshPath(humanLockPath, options);
1101
1391
  const forensicAction = planFreshPath(forensicPath, options);
1102
- const forensicReport = buildForensicReport(target);
1392
+ const forensicReport = await buildForensicReport(target);
1103
1393
  const humanLockTemplate = readFileSync2(findTemplatePath("templates/fabric/human-lock.json"), "utf8");
1104
- const bootstrapContent = buildFabricBootstrapGuide(target);
1394
+ const bootstrapContent = await buildFabricBootstrapGuide(target);
1105
1395
  const bootstrapHash = sha256(bootstrapContent);
1106
1396
  const meta = createInitialMeta(bootstrapHash);
1107
1397
  return {
@@ -1194,8 +1484,8 @@ function executeInitFabricPlan(plan) {
1194
1484
  claudeSettingsAction: plan.claudeSettings.action
1195
1485
  };
1196
1486
  }
1197
- function initFabric(target, options) {
1198
- return executeInitFabricPlan(buildInitFabricPlan(target, options));
1487
+ async function initFabric(target, options) {
1488
+ return executeInitFabricPlan(await buildInitFabricPlan(target, options));
1199
1489
  }
1200
1490
  function shouldUseInitWizard(args, terminalInteractive = isInteractiveInit()) {
1201
1491
  return terminalInteractive && args.interactive !== false && args.yes !== true;
@@ -3,7 +3,7 @@ import {
3
3
  createScanReport,
4
4
  scanCommand,
5
5
  scan_default
6
- } from "./chunk-AZRKMFRY.js";
6
+ } from "./chunk-QSAEGVKE.js";
7
7
  import "./chunk-AEOYCVBG.js";
8
8
  import "./chunk-WWNXR34K.js";
9
9
  import "./chunk-6ICJICVU.js";
@@ -0,0 +1,24 @@
1
+ type TreeSitterProbeResult = {
2
+ ok: boolean;
3
+ node_version: string;
4
+ package_engines: "not-declared";
5
+ root_node_type: string;
6
+ has_error: boolean;
7
+ elapsed_ms: number;
8
+ wasm: {
9
+ runtime_path: string;
10
+ runtime_bytes: number;
11
+ javascript_grammar_path: string;
12
+ javascript_grammar_bytes: number;
13
+ };
14
+ decision: {
15
+ status: "feasible";
16
+ loading_strategy: "lazy";
17
+ bundle_size_impact: string;
18
+ grammar_strategy: string;
19
+ integration_note: string;
20
+ };
21
+ };
22
+ declare function runTreeSitterProbe(source?: string): Promise<TreeSitterProbeResult>;
23
+
24
+ export { type TreeSitterProbeResult, runTreeSitterProbe };
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/scanner/tree-sitter-probe.ts
4
+ import { realpathSync, statSync } from "fs";
5
+ import { createRequire } from "module";
6
+ import { resolve } from "path";
7
+ import { performance } from "perf_hooks";
8
+ import { fileURLToPath } from "url";
9
+ var require2 = createRequire(import.meta.url);
10
+ var PROBE_SOURCE = `import { strict as assert } from "node:assert";
11
+
12
+ const users = [{ id: "1", name: "Ada" }];
13
+
14
+ export function findUser(id) {
15
+ return users.find((user) => user.id === id);
16
+ }
17
+
18
+ assert.equal(findUser("1")?.name, "Ada");
19
+ console.log("parsed");`;
20
+ var treeSitterModulePromise = null;
21
+ var parserInitPromise = null;
22
+ var javascriptLanguagePromise = null;
23
+ async function runTreeSitterProbe(source = PROBE_SOURCE) {
24
+ const startedAt = performance.now();
25
+ const assets = resolveTreeSitterAssets();
26
+ const treeSitter = await loadTreeSitterModule();
27
+ await initParser(treeSitter, assets.runtimeWasmPath);
28
+ const language = await loadJavaScriptLanguage(treeSitter, assets.javascriptGrammarPath);
29
+ const parser = new treeSitter.Parser();
30
+ const runtimeBytes = statSync(assets.runtimeWasmPath).size;
31
+ const javascriptGrammarBytes = statSync(assets.javascriptGrammarPath).size;
32
+ let tree = null;
33
+ try {
34
+ parser.setLanguage(language);
35
+ tree = parser.parse(source);
36
+ if (tree === null) {
37
+ throw new Error("web-tree-sitter probe failed: parser returned null syntax tree");
38
+ }
39
+ const rootNode = tree.rootNode;
40
+ return {
41
+ ok: !rootNode.hasError,
42
+ node_version: process.version,
43
+ package_engines: "not-declared",
44
+ root_node_type: rootNode.type,
45
+ has_error: rootNode.hasError,
46
+ elapsed_ms: Math.round(performance.now() - startedAt),
47
+ wasm: {
48
+ runtime_path: assets.runtimeWasmPath,
49
+ runtime_bytes: runtimeBytes,
50
+ javascript_grammar_path: assets.javascriptGrammarPath,
51
+ javascript_grammar_bytes: javascriptGrammarBytes
52
+ },
53
+ decision: {
54
+ status: "feasible",
55
+ loading_strategy: "lazy",
56
+ bundle_size_impact: formatBundleImpact(runtimeBytes, javascriptGrammarBytes),
57
+ grammar_strategy: "Use tree-sitter-javascript WASM for JavaScript and TS-compatible syntax; evaluate tree-sitter-typescript before parsing TypeScript-only syntax.",
58
+ integration_note: "Keep web-tree-sitter behind a dynamic import at the forensic inferPatternHint() call site to avoid CLI startup cost."
59
+ }
60
+ };
61
+ } finally {
62
+ tree?.delete();
63
+ parser.delete();
64
+ }
65
+ }
66
+ function resolveTreeSitterAssets() {
67
+ return {
68
+ runtimeWasmPath: require2.resolve("web-tree-sitter/web-tree-sitter.wasm"),
69
+ javascriptGrammarPath: require2.resolve("tree-sitter-javascript/tree-sitter-javascript.wasm")
70
+ };
71
+ }
72
+ function loadTreeSitterModule() {
73
+ treeSitterModulePromise ??= import("web-tree-sitter");
74
+ return treeSitterModulePromise;
75
+ }
76
+ function initParser(treeSitter, runtimeWasmPath) {
77
+ parserInitPromise ??= treeSitter.Parser.init({
78
+ locateFile: (scriptName) => scriptName.endsWith(".wasm") ? runtimeWasmPath : scriptName
79
+ });
80
+ return parserInitPromise;
81
+ }
82
+ function loadJavaScriptLanguage(treeSitter, javascriptGrammarPath) {
83
+ javascriptLanguagePromise ??= treeSitter.Language.load(javascriptGrammarPath);
84
+ return javascriptLanguagePromise;
85
+ }
86
+ function formatBundleImpact(runtimeBytes, javascriptGrammarBytes) {
87
+ const combinedBytes = runtimeBytes + javascriptGrammarBytes;
88
+ return `${formatBytes(combinedBytes)} combined WASM payload (${formatBytes(runtimeBytes)} runtime + ${formatBytes(javascriptGrammarBytes)} JavaScript grammar); package unpacked sizes are larger and acceptable only with lazy loading.`;
89
+ }
90
+ function formatBytes(bytes) {
91
+ return `${(bytes / 1024).toFixed(1)}KB`;
92
+ }
93
+ var entrypoint = process.argv[1];
94
+ var currentFilePath = fileURLToPath(import.meta.url);
95
+ var isMainModule = entrypoint !== void 0 && realpathSync(resolve(entrypoint)) === currentFilePath;
96
+ if (isMainModule) {
97
+ runTreeSitterProbe().then((result) => {
98
+ console.log(JSON.stringify(result, null, 2));
99
+ }).catch((error) => {
100
+ const message = error instanceof Error ? error.message : String(error);
101
+ console.error(`web-tree-sitter probe failed: ${message}`);
102
+ process.exitCode = 1;
103
+ });
104
+ }
105
+ export {
106
+ runTreeSitterProbe
107
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-cli",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "fab": "dist/index.js",
@@ -19,8 +19,11 @@
19
19
  "minimatch": "^10.0.1",
20
20
  "picocolors": "^1.1.1",
21
21
  "string-width": "^7.2.0",
22
- "@fenglimg/fabric-server": "1.4.0",
23
- "@fenglimg/fabric-shared": "1.4.0"
22
+ "tree-sitter-javascript": "^0.25.0",
23
+ "tree-sitter-typescript": "^0.23.2",
24
+ "web-tree-sitter": "^0.26.8",
25
+ "@fenglimg/fabric-server": "1.5.1",
26
+ "@fenglimg/fabric-shared": "1.5.1"
24
27
  },
25
28
  "devDependencies": {
26
29
  "@types/iarna__toml": "^2.0.5",
@@ -13,7 +13,7 @@ process.stdout.write(
13
13
  JSON.stringify({
14
14
  hookSpecificOutput: {
15
15
  additionalContext:
16
- "Fabric initialization is still pending in this repository. Read .fabric/forensic.json, .fabric/bootstrap/README.md, and use the repo skill at .agents/skills/fabric-init/SKILL.md before proceeding. If Codex hooks are not firing, ensure features.codex_hooks = true is enabled in your Codex config.",
16
+ "这个仓库的 Fabric 初始化还没完成。继续操作前,请先查看 .fabric/forensic.json .fabric/bootstrap/README.md,并使用仓库内的 .agents/skills/fabric-init/SKILL.md。若 Codex hooks 没有触发,请确认配置里已启用 features.codex_hooks = true",
17
17
  },
18
18
  }),
19
19
  );
@@ -13,6 +13,6 @@ process.stdout.write(
13
13
  JSON.stringify({
14
14
  decision: "block",
15
15
  reason:
16
- "fab init has collected Fabric evidence, but initialization follow-up is still pending. Ensure features.codex_hooks = true is enabled, then use the repo skill at .agents/skills/fabric-init/SKILL.md and review .fabric/forensic.json plus .fabric/bootstrap/README.md before continuing.",
16
+ "fab init 已经收集完当前仓库的初始化依据,但后续初始化还没完成。请先确认 Codex 已启用 features.codex_hooks = true,然后查看 .fabric/forensic.json .fabric/bootstrap/README.md,并使用仓库内的 .agents/skills/fabric-init/SKILL.md 继续初始化。",
17
17
  }),
18
18
  );
@@ -1,27 +1,27 @@
1
1
  ---
2
2
  name: fabric-init
3
- description: Use this skill when `.fabric/forensic.json` exists and Fabric initialization follow-up still needs to be completed for this repository.
3
+ description: Use this skill when `.fabric/forensic.json` exists and this repository still needs the remaining Fabric initialization steps.
4
4
  ---
5
5
 
6
- ## Hard Rules (DO NOT TRANSLATE)
6
+ ## Hard Rules (不要翻译受保护 token)
7
7
 
8
- MUST: Read `.fabric/forensic.json` before taking any other action.
9
- MUST: Treat `.fabric/bootstrap/README.md` as the current bootstrap contract for this repository.
10
- MUST: If `.fabric/init-context.json` already exists, stop and report that initialization follow-up appears complete.
11
- MUST: Use evidence from `.fabric/forensic.json` and the repository layout to guide the initialization follow-up.
8
+ MUST: 先读取 `.fabric/forensic.json`,再做其他动作。
9
+ MUST: `.fabric/bootstrap/README.md` 视为当前仓库的初始化说明。
10
+ MUST: 如果 `.fabric/init-context.json` 已存在,立即停止并报告当前仓库看起来已经完成后续初始化。
11
+ MUST: 使用 `.fabric/forensic.json` 和仓库结构中的依据,判断接下来该做什么。
12
12
  MUST: Preserve protected tokens exactly: `AGENTS.md`, `FABRIC.md`, `.fabric/agents.meta.json`, `.fabric/human-lock.json`, `.fabric/init-context.json`, `.fabric/forensic.json`, `MUST`, `NEVER`.
13
- NEVER: Claim initialization is complete without checking `.fabric/init-context.json`.
14
- NEVER: Rewrite or translate protected tokens.
15
- NEVER: ignore `.fabric/bootstrap/README.md` while deciding next initialization steps.
13
+ NEVER: 在没有检查 `.fabric/init-context.json` 的情况下声称初始化已经完成。
14
+ NEVER: 改写或翻译受保护 token。
15
+ NEVER: 在判断下一步初始化动作时忽略 `.fabric/bootstrap/README.md`。
16
16
 
17
17
  ## Purpose
18
18
 
19
- Use this skill after `fab init` when Codex is working inside the repository and Fabric's evidence pack already exists. The goal is to continue the repository-specific initialization workflow using the generated forensic evidence and bootstrap contract.
19
+ 当你在 Codex 中处理这个仓库,并且 `fab init` 已经生成 `.fabric/forensic.json` 时,使用这个 skill 继续完成仓库专属的 Fabric 初始化。目标是基于当前仓库的初始化依据和内部说明,明确下一步该做什么,而不是重新解释一遍通用流程。
20
20
 
21
21
  ## Workflow
22
22
 
23
- 1. Read `.fabric/forensic.json`.
24
- 2. Read `.fabric/bootstrap/README.md`.
25
- 3. Check whether `.fabric/init-context.json` exists.
26
- 4. If initialization is still pending, summarize the next repository-specific initialization actions Codex should take.
27
- 5. Keep the guidance tightly scoped to Fabric initialization follow-up for this repository.
23
+ 1. 读取 `.fabric/forensic.json`。
24
+ 2. 读取 `.fabric/bootstrap/README.md`。
25
+ 3. 检查 `.fabric/init-context.json` 是否已经存在。
26
+ 4. 如果初始化仍未完成,明确总结当前仓库接下来要做的初始化动作。
27
+ 5. 只讨论这个仓库的后续初始化,不扩展到无关建议。
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ resolveDevModeTarget
4
+ } from "./chunk-AEOYCVBG.js";
5
+ import {
6
+ paint
7
+ } from "./chunk-WWNXR34K.js";
2
8
  import {
3
9
  installMcpClients
4
10
  } from "./chunk-BVTMVW5M.js";
@@ -6,12 +12,6 @@ import "./chunk-BEKSXO5N.js";
6
12
  import {
7
13
  installHooks
8
14
  } from "./chunk-YDZJRLHL.js";
9
- import {
10
- resolveDevModeTarget
11
- } from "./chunk-AEOYCVBG.js";
12
- import {
13
- paint
14
- } from "./chunk-WWNXR34K.js";
15
15
  import {
16
16
  t
17
17
  } from "./chunk-6ICJICVU.js";