@fenglimg/fabric-cli 0.1.4 → 1.1.0

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.
Files changed (31) hide show
  1. package/dist/{bootstrap-HUDJ2E3Q.js → bootstrap-PMIA4W6G.js} +16 -12
  2. package/dist/chunk-6ICJICVU.js +10 -0
  3. package/dist/{chunk-T3WQUWW4.js → chunk-6UUPKSDE.js} +78 -36
  4. package/dist/chunk-AEOYCVBG.js +0 -0
  5. package/dist/{chunk-U376IPKT.js → chunk-F2BXHPM5.js} +11 -7
  6. package/dist/chunk-JWUO6TIS.js +220 -0
  7. package/dist/{chunk-CZ7U6ULM.js → chunk-L43IGJ6X.js} +17 -7
  8. package/dist/{chunk-N7TTCGJA.js → chunk-VMYPJPKV.js} +1 -0
  9. package/dist/chunk-WWNXR34K.js +49 -0
  10. package/dist/{config-YKDWIRCT.js → config-PXEEXWLM.js} +14 -11
  11. package/dist/doctor-QTSG2RWF.js +125 -0
  12. package/dist/{hooks-VXXO4VZP.js → hooks-5S5IRVQE.js} +15 -12
  13. package/dist/human-lint-YSFOZHZ7.js +13 -0
  14. package/dist/index.js +16 -11
  15. package/dist/init-R73E5YTG.js +1164 -0
  16. package/dist/{ledger-append-EGIKSMU5.js → ledger-append-XZ5SX4O5.js} +2 -1
  17. package/dist/{pre-commit-CXPH7BZH.js → pre-commit-BLSUMT3P.js} +14 -9
  18. package/dist/{scan-UASZQLQP.js → scan-JBGFRB7P.js} +3 -2
  19. package/dist/serve-4J2CQY25.js +112 -0
  20. package/dist/{sync-meta-YTG5V3Y6.js → sync-meta-THZSEM7Y.js} +6 -2
  21. package/package.json +12 -8
  22. package/templates/agents-md/AGENTS.md.template +20 -29
  23. package/templates/agents-md/variants/cocos.md +20 -0
  24. package/templates/agents-md/variants/next.md +20 -0
  25. package/templates/agents-md/variants/vite.md +20 -0
  26. package/templates/claude-hooks/agents-md-init-reminder.cjs +18 -0
  27. package/templates/claude-skills/agents-md-init/SKILL.md +86 -0
  28. package/dist/chunk-BWZHNZG6.js +0 -236
  29. package/dist/chunk-P4KVFB2T.js +0 -22
  30. package/dist/human-lint-II6TBGP4.js +0 -9
  31. package/dist/init-IBS7KO7A.js +0 -149
@@ -1,10 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  resolveClients
4
- } from "./chunk-N7TTCGJA.js";
4
+ } from "./chunk-VMYPJPKV.js";
5
5
  import {
6
6
  readFabricConfig
7
7
  } from "./chunk-AEOYCVBG.js";
8
+ import {
9
+ t
10
+ } from "./chunk-6ICJICVU.js";
8
11
 
9
12
  // src/commands/bootstrap.ts
10
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -50,18 +53,18 @@ var CLIENT_TARGET_MAP = {
50
53
  var bootstrapCommand = defineCommand({
51
54
  meta: {
52
55
  name: "bootstrap",
53
- description: "\u4E3A\u652F\u6301\u7684 AI \u5BA2\u6237\u7AEF\u5B89\u88C5 Fabric \u5F15\u5BFC\u63D0\u793A\u6A21\u677F\u3002"
56
+ description: t("cli.bootstrap.description")
54
57
  },
55
58
  subCommands: {
56
59
  install: defineCommand({
57
60
  meta: {
58
61
  name: "install",
59
- description: "\u5C06 Fabric \u5F15\u5BFC\u6A21\u677F\u590D\u5236\u5230\u5404\u5BA2\u6237\u7AEF\u7684\u539F\u751F\u4F4D\u7F6E\u3002"
62
+ description: t("cli.bootstrap.install.description")
60
63
  },
61
64
  args: {
62
65
  clients: {
63
66
  type: "string",
64
- description: "\u53EF\u9009\u7684\u9017\u53F7\u5206\u9694\u5BA2\u6237\u7AEF\u8FC7\u6EE4\u5668\uFF0C\u4F8B\u5982 claude,cursor,codex\u3002"
67
+ description: t("cli.bootstrap.install.args.clients.description")
65
68
  }
66
69
  },
67
70
  async run({ args }) {
@@ -72,7 +75,8 @@ var bootstrapCommand = defineCommand({
72
75
  const clients = selectedClients ?? detectedClients;
73
76
  if (clients.size === 0) {
74
77
  process.stderr.write(
75
- "No bootstrap targets detected. Pass --clients claude,cursor,windsurf,roo,gemini,codex to install explicitly.\n"
78
+ `${t("cli.bootstrap.install.no-targets")}
79
+ `
76
80
  );
77
81
  return;
78
82
  }
@@ -93,7 +97,7 @@ function parseClientFilter(value) {
93
97
  const alias = rawClient.trim().toLowerCase();
94
98
  const client = CLIENT_ALIASES[alias];
95
99
  if (client === void 0) {
96
- throw new Error(`Unknown client "${rawClient}". Use a comma-separated list such as claude,cursor,codex.`);
100
+ throw new Error(t("cli.bootstrap.errors.unknown-client", { client: rawClient }));
97
101
  }
98
102
  clients.add(client);
99
103
  }
@@ -138,26 +142,26 @@ function installBootstrap(client, workspaceRoot) {
138
142
  return;
139
143
  }
140
144
  writeFileSync(targetPath, ensureTrailingNewline(template), "utf8");
141
- process.stderr.write(`Installed ${targetPath}
145
+ process.stderr.write(`${t("cli.bootstrap.install.installed", { path: targetPath })}
142
146
  `);
143
147
  }
144
148
  function writeCodexBootstrap(targetPath, template) {
145
149
  const nextContent = ensureTrailingNewline(template);
146
150
  if (!existsSync(targetPath)) {
147
151
  writeFileSync(targetPath, nextContent, "utf8");
148
- process.stderr.write(`Installed ${targetPath}
152
+ process.stderr.write(`${t("cli.bootstrap.install.installed", { path: targetPath })}
149
153
  `);
150
154
  return;
151
155
  }
152
156
  const existing = readFileSync(targetPath, "utf8");
153
157
  if (existing.includes("# Fabric Bootstrap")) {
154
- process.stderr.write(`Skipped ${targetPath}: Fabric Bootstrap header already present.
158
+ process.stderr.write(`${t("cli.bootstrap.install.skipped-header", { path: targetPath })}
155
159
  `);
156
160
  return;
157
161
  }
158
162
  const separator = existing.startsWith("\n") || existing.length === 0 ? "" : "\n";
159
163
  writeFileSync(targetPath, `${nextContent}${separator}${existing}`, "utf8");
160
- process.stderr.write(`Prepended ${targetPath}
164
+ process.stderr.write(`${t("cli.bootstrap.install.prepended", { path: targetPath })}
161
165
  `);
162
166
  }
163
167
  function ensureTrailingNewline(content) {
@@ -175,7 +179,7 @@ function findTemplatePath(relativePath) {
175
179
  return candidate;
176
180
  }
177
181
  }
178
- throw new Error(`Template not found: ${relativePath}`);
182
+ throw new Error(t("cli.shared.template-not-found", { path: relativePath }));
179
183
  }
180
184
  function templateCandidatesFrom(start, relativePath) {
181
185
  const candidates = [];
@@ -188,7 +192,7 @@ function templateCandidatesFrom(start, relativePath) {
188
192
  }
189
193
  current = parent;
190
194
  }
191
- return candidates;
195
+ return candidates.reverse();
192
196
  }
193
197
  export {
194
198
  bootstrapCommand,
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/i18n.ts
4
+ import { createTranslator, detectNodeLocale } from "@fenglimg/fabric-shared";
5
+ var locale = detectNodeLocale();
6
+ var t = createTranslator(locale);
7
+
8
+ export {
9
+ t
10
+ };
@@ -1,27 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- resolveIgnores
4
- } from "./chunk-P4KVFB2T.js";
3
+ t
4
+ } from "./chunk-6ICJICVU.js";
5
5
 
6
6
  // src/commands/sync-meta.ts
7
7
  import { createHash } from "crypto";
8
8
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
9
9
  import { isAbsolute, join, relative, resolve, sep } from "path";
10
+ import {
11
+ agentsMetaSchema,
12
+ deriveAgentsMetaLayer,
13
+ deriveAgentsMetaTopologyType
14
+ } from "@fenglimg/fabric-shared";
10
15
  import { defineCommand } from "citty";
11
16
  var syncMetaCommand = defineCommand({
12
17
  meta: {
13
18
  name: "sync-meta",
14
- description: "\u4ECE AGENTS.md \u6587\u4EF6\u540C\u6B65 Fabric \u5143\u6570\u636E\u3002"
19
+ description: t("cli.sync-meta.description")
15
20
  },
16
21
  args: {
17
22
  target: {
18
23
  type: "string",
19
- description: "\u76EE\u6807\u9879\u76EE\u8DEF\u5F84\uFF0C\u9ED8\u8BA4\u4E3A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u3002",
24
+ description: t("cli.sync-meta.args.target.description"),
20
25
  default: process.cwd()
21
26
  },
22
27
  "check-only": {
23
28
  type: "boolean",
24
- description: "\u5982\u679C .fabric/agents.meta.json \u5DF2\u8FC7\u671F\u5219\u4EE5\u4EE3\u7801 1 \u9000\u51FA\u3002",
29
+ description: t("cli.sync-meta.args.check-only.description"),
25
30
  default: false
26
31
  }
27
32
  },
@@ -32,7 +37,7 @@ var syncMetaCommand = defineCommand({
32
37
  const existingMeta = readExistingMeta(metaPath);
33
38
  if (args["check-only"]) {
34
39
  if (!existingMeta || stableStringify(existingMeta) !== stableStringify(computedMeta)) {
35
- writeStderr("Fabric metadata drift detected. Run fab sync-meta to update.");
40
+ writeStderr(t("cli.sync-meta.drift-detected"));
36
41
  process.exitCode = 1;
37
42
  }
38
43
  return;
@@ -43,7 +48,7 @@ var syncMetaCommand = defineCommand({
43
48
  mkdirSync(join(target, ".fabric"), { recursive: true });
44
49
  writeFileSync(metaPath, `${JSON.stringify(computedMeta, null, 2)}
45
50
  `, "utf8");
46
- writeStderr(`Updated ${metaPath}`);
51
+ writeStderr(t("cli.sync-meta.updated", { label: t("cli.shared.updated"), path: metaPath }));
47
52
  }
48
53
  });
49
54
  var sync_meta_default = syncMetaCommand;
@@ -52,11 +57,15 @@ function computeAgentsMeta(target) {
52
57
  const metaPath = join(target, ".fabric", "agents.meta.json");
53
58
  const existingMeta = readExistingMeta(metaPath);
54
59
  const existingByFile = indexExistingNodesByFile(existingMeta);
55
- const agentsFiles = findAgentsFiles(target);
60
+ const agentsFiles = findFabricAgentsFiles(target);
56
61
  const nodes = {};
62
+ const bootstrapNode = createBootstrapNode(target, existingByFile.get("AGENTS.md")?.node);
63
+ if (bootstrapNode !== void 0) {
64
+ nodes.L0 = bootstrapNode;
65
+ }
57
66
  for (const file of agentsFiles) {
58
67
  const existing = existingByFile.get(file);
59
- const id = existing?.id ?? deriveNodeId(file);
68
+ const id = deriveNodeId(file);
60
69
  const hash = sha256(readFileSync(join(target, file), "utf8"));
61
70
  const defaults = createDefaultNodeMeta(file);
62
71
  nodes[id] = {
@@ -77,7 +86,7 @@ function normalizeTarget(targetInput) {
77
86
  }
78
87
  function assertExistingDirectory(target) {
79
88
  if (!existsSync(target) || !statSync(target).isDirectory()) {
80
- throw new Error(`Target must be an existing directory: ${target}`);
89
+ throw new Error(t("cli.shared.target-invalid", { target }));
81
90
  }
82
91
  }
83
92
  function readExistingMeta(metaPath) {
@@ -85,15 +94,18 @@ function readExistingMeta(metaPath) {
85
94
  return void 0;
86
95
  }
87
96
  try {
88
- return JSON.parse(readFileSync(metaPath, "utf8"));
97
+ return agentsMetaSchema.parse(JSON.parse(readFileSync(metaPath, "utf8")));
89
98
  } catch {
90
99
  return void 0;
91
100
  }
92
101
  }
93
- function findAgentsFiles(target) {
94
- const ignorePatterns = resolveIgnores();
102
+ function findFabricAgentsFiles(target) {
103
+ const agentsRoot = join(target, ".fabric", "agents");
104
+ if (!existsSync(agentsRoot) || !statSync(agentsRoot).isDirectory()) {
105
+ return [];
106
+ }
95
107
  const files = [];
96
- const stack = [target];
108
+ const stack = [agentsRoot];
97
109
  while (stack.length > 0) {
98
110
  const current = stack.pop();
99
111
  if (current === void 0) {
@@ -102,31 +114,20 @@ function findAgentsFiles(target) {
102
114
  for (const entry of readdirSync(current, { withFileTypes: true })) {
103
115
  const absolutePath = join(current, entry.name);
104
116
  const relativePath = toPosixPath(relative(target, absolutePath));
105
- if (shouldIgnore(relativePath, entry.isDirectory(), ignorePatterns)) {
106
- continue;
107
- }
108
117
  if (entry.isDirectory()) {
109
118
  stack.push(absolutePath);
110
- } else if (entry.isFile() && entry.name === "AGENTS.md") {
119
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
111
120
  files.push(relativePath);
112
121
  }
113
122
  }
114
123
  }
115
124
  return files.sort();
116
125
  }
117
- function shouldIgnore(relativePath, isDirectory, ignorePatterns) {
118
- return ignorePatterns.some((pattern) => matchesIgnorePattern(relativePath, isDirectory, pattern));
126
+ function deriveLayer(relativePath) {
127
+ return deriveAgentsMetaLayer(relativePath);
119
128
  }
120
- function matchesIgnorePattern(relativePath, isDirectory, pattern) {
121
- const normalizedPattern = toPosixPath(pattern);
122
- if (normalizedPattern === "**/*.meta") {
123
- return relativePath.endsWith(".meta");
124
- }
125
- if (normalizedPattern.endsWith("/**")) {
126
- const directoryPrefix = normalizedPattern.slice(0, -3);
127
- return relativePath === directoryPrefix || relativePath.startsWith(`${directoryPrefix}/`) || isDirectory && `${relativePath}/` === directoryPrefix;
128
- }
129
- return relativePath === normalizedPattern;
129
+ function deriveTopologyType(relativePath) {
130
+ return deriveAgentsMetaTopologyType(relativePath);
130
131
  }
131
132
  function indexExistingNodesByFile(existingMeta) {
132
133
  const byFile = /* @__PURE__ */ new Map();
@@ -139,18 +140,57 @@ function deriveNodeId(file) {
139
140
  if (file === "AGENTS.md") {
140
141
  return "L0";
141
142
  }
142
- return file.replace(/\/AGENTS\.md$/, "");
143
+ const layer = deriveLayer(file);
144
+ const relativeStem = getMirrorRelativeStem(file);
145
+ return `${layer}/${relativeStem}`;
143
146
  }
144
147
  function createDefaultNodeMeta(file) {
145
- const scope = file === "AGENTS.md" ? "**" : `${file.replace(/\/AGENTS\.md$/, "")}/**`;
148
+ const layer = deriveLayer(file);
149
+ const topologyType = deriveTopologyType(file);
146
150
  return {
147
151
  file,
148
- scope_glob: scope,
149
- deps: file === "AGENTS.md" ? [] : ["L0"],
150
- priority: file === "AGENTS.md" ? "high" : "medium",
152
+ scope_glob: deriveScopeGlob(file),
153
+ deps: layer === "L0" ? [] : ["L0"],
154
+ priority: layer === "L0" ? "high" : "medium",
155
+ layer,
156
+ topology_type: topologyType,
151
157
  hash: ""
152
158
  };
153
159
  }
160
+ function createBootstrapNode(target, existing) {
161
+ const bootstrapPath = join(target, "AGENTS.md");
162
+ if (!existsSync(bootstrapPath)) {
163
+ return void 0;
164
+ }
165
+ const hash = sha256(readFileSync(bootstrapPath, "utf8"));
166
+ return {
167
+ ...createDefaultNodeMeta("AGENTS.md"),
168
+ ...existing,
169
+ file: "AGENTS.md",
170
+ hash
171
+ };
172
+ }
173
+ function deriveScopeGlob(file) {
174
+ if (file === "AGENTS.md") {
175
+ return "**";
176
+ }
177
+ const stem = getMirrorRelativeStem(file);
178
+ const segments = stem.split("/").filter(Boolean);
179
+ if (segments.length === 0 || stem === "root") {
180
+ return "**";
181
+ }
182
+ if (segments[0] === "_cross") {
183
+ return "**";
184
+ }
185
+ if (segments.at(-1) === "rules") {
186
+ segments.pop();
187
+ }
188
+ const scopePath = segments.join("/");
189
+ return scopePath === "" ? "**" : `${scopePath}/**`;
190
+ }
191
+ function getMirrorRelativeStem(file) {
192
+ return file.replace(/^\.fabric\/agents\//, "").replace(/\.md$/, "");
193
+ }
154
194
  function sortNodes(nodes) {
155
195
  return Object.fromEntries(Object.entries(nodes).sort(([left], [right]) => left.localeCompare(right)));
156
196
  }
@@ -184,5 +224,7 @@ function sha256(content) {
184
224
  export {
185
225
  syncMetaCommand,
186
226
  sync_meta_default,
187
- computeAgentsMeta
227
+ computeAgentsMeta,
228
+ deriveLayer,
229
+ deriveTopologyType
188
230
  };
File without changes
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ t
4
+ } from "./chunk-6ICJICVU.js";
2
5
 
3
6
  // src/commands/ledger-append.ts
4
7
  import { execSync } from "child_process";
@@ -10,17 +13,17 @@ var INITIAL_PARENT_SHA = "root";
10
13
  var ledgerAppendCommand = defineCommand({
11
14
  meta: {
12
15
  name: "ledger-append",
13
- description: "\u5411 Fabric \u610F\u56FE\u65E5\u5FD7\u6DFB\u52A0\u4E00\u6761\u8BB0\u5F55\u3002"
16
+ description: t("cli.ledger-append.description")
14
17
  },
15
18
  args: {
16
19
  target: {
17
20
  type: "string",
18
- description: "\u76EE\u6807\u9879\u76EE\u8DEF\u5F84\uFF0C\u9ED8\u8BA4\u4E3A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u3002",
21
+ description: t("cli.ledger-append.args.target.description"),
19
22
  default: process.cwd()
20
23
  },
21
24
  staged: {
22
25
  type: "boolean",
23
- description: "\u4ECE\u6682\u5B58\u53D8\u66F4\u4E2D\u63A8\u5BFC\u8BB0\u5F55\uFF08\u7528\u4E8E pre-commit \u9636\u6BB5\uFF09\u3002",
26
+ description: t("cli.ledger-append.args.staged.description"),
24
27
  default: false
25
28
  }
26
29
  },
@@ -28,7 +31,7 @@ var ledgerAppendCommand = defineCommand({
28
31
  const target = normalizeTarget(args.target);
29
32
  assertExistingDirectory(target);
30
33
  if (!args.staged) {
31
- writeStderr("requires --staged in pre-commit context");
34
+ writeStderr(t("cli.ledger-append.requires-staged"));
32
35
  process.exitCode = 1;
33
36
  return;
34
37
  }
@@ -40,6 +43,7 @@ var ledgerAppendCommand = defineCommand({
40
43
  const diffStat = readDiffStat(target).trim();
41
44
  const entry = {
42
45
  ts: Date.now(),
46
+ source: "human",
43
47
  parent_sha: readParentSha(target),
44
48
  intent,
45
49
  affected_paths: stagedFiles,
@@ -59,7 +63,7 @@ function normalizeTarget(targetInput) {
59
63
  }
60
64
  function assertExistingDirectory(target) {
61
65
  if (!existsSync(target) || !statSync(target).isDirectory()) {
62
- throw new Error(`Target must be an existing directory: ${target}`);
66
+ throw new Error(t("cli.shared.target-invalid", { target }));
63
67
  }
64
68
  }
65
69
  function getStagedFiles(target) {
@@ -83,8 +87,8 @@ function deriveIntent(stagedFiles) {
83
87
  }
84
88
  const uniqueNames = Array.from(new Set(stagedFiles.map((file) => basename(file))));
85
89
  const head = uniqueNames.slice(0, 2).join(", ");
86
- const suffix = uniqueNames.length > 2 ? ` +${uniqueNames.length - 2} more` : "";
87
- return `auto: ${head}${suffix}`;
90
+ const suffix = uniqueNames.length > 2 ? t("cli.ledger-append.intent.auto-more", { count: String(uniqueNames.length - 2) }) : "";
91
+ return t("cli.ledger-append.intent.auto", { head, suffix });
88
92
  }
89
93
  function hasMatchingTailEntry(target, entry) {
90
94
  const ledgerPath = join(target, LEDGER_FILE);
@@ -0,0 +1,220 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createDebugLogger,
4
+ readFabricConfig,
5
+ resolveDevMode
6
+ } from "./chunk-AEOYCVBG.js";
7
+ import {
8
+ displayWidth,
9
+ padEnd,
10
+ paint,
11
+ symbol
12
+ } from "./chunk-WWNXR34K.js";
13
+ import {
14
+ t
15
+ } from "./chunk-6ICJICVU.js";
16
+
17
+ // src/commands/scan.ts
18
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
19
+ import { isAbsolute, join, relative, resolve, sep } from "path";
20
+ import { defineCommand } from "citty";
21
+
22
+ // src/scanner/detector.ts
23
+ import { detectFramework } from "@fenglimg/fabric-shared/node";
24
+
25
+ // src/scanner/ignores.ts
26
+ var DEFAULT_IGNORES = [
27
+ "**/*.meta",
28
+ "library/**",
29
+ "temp/**",
30
+ "build/**",
31
+ "settings/**",
32
+ "profiles/**",
33
+ "node_modules/**",
34
+ "dist/**",
35
+ ".git/**",
36
+ ".fabric/**"
37
+ ];
38
+ function resolveIgnores(fabricConfig) {
39
+ return [...DEFAULT_IGNORES, ...fabricConfig?.scanIgnores ?? []];
40
+ }
41
+
42
+ // src/commands/scan.ts
43
+ function createScanReport(targetInput = process.cwd(), fabricConfig) {
44
+ const target = normalizeTarget(targetInput);
45
+ const framework = detectFramework(target);
46
+ const readmeQuality = getReadmeQuality(target);
47
+ const hasContributing = existsSync(join(target, "CONTRIBUTING.md"));
48
+ const hasExistingFabric = existsSync(join(target, "AGENTS.md")) || existsSync(join(target, ".fabric"));
49
+ const walkResult = walkFiles(target, resolveIgnores(fabricConfig));
50
+ return {
51
+ target,
52
+ framework,
53
+ readmeQuality,
54
+ hasContributing,
55
+ fileCount: walkResult.fileCount,
56
+ ignoredCount: walkResult.ignoredCount,
57
+ hasExistingFabric,
58
+ recommendations: buildRecommendations({
59
+ framework,
60
+ readmeQuality,
61
+ hasContributing,
62
+ hasExistingFabric
63
+ })
64
+ };
65
+ }
66
+ var scanCommand = defineCommand({
67
+ meta: {
68
+ name: "scan",
69
+ description: t("cli.scan.description")
70
+ },
71
+ args: {
72
+ target: {
73
+ type: "string",
74
+ description: t("cli.scan.args.target.description")
75
+ },
76
+ debug: {
77
+ type: "boolean",
78
+ description: t("cli.scan.args.debug.description"),
79
+ default: false
80
+ },
81
+ json: {
82
+ type: "boolean",
83
+ description: t("cli.scan.args.json.description"),
84
+ default: false
85
+ }
86
+ },
87
+ async run({ args }) {
88
+ const workspaceRoot = process.cwd();
89
+ const logger = createDebugLogger(args.debug);
90
+ const resolution = resolveDevMode(args.target, workspaceRoot);
91
+ const fabricConfig = readFabricConfig(workspaceRoot);
92
+ logger(`scan target source: ${resolution.source}`);
93
+ for (const step of resolution.chain) {
94
+ logger(step);
95
+ }
96
+ const report = createScanReport(resolution.target, fabricConfig);
97
+ if (args.json) {
98
+ console.log(JSON.stringify(report, null, 2));
99
+ return;
100
+ }
101
+ printPrettyReport(report, Boolean(args.debug));
102
+ }
103
+ });
104
+ var scan_default = scanCommand;
105
+ function normalizeTarget(targetInput) {
106
+ return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
107
+ }
108
+ function getReadmeQuality(target) {
109
+ const readmePath = join(target, "README.md");
110
+ if (!existsSync(readmePath)) {
111
+ return "stub";
112
+ }
113
+ const wordCount = readFileSync(readmePath, "utf8").trim().split(/\s+/).filter(Boolean).length;
114
+ return wordCount >= 200 ? "ok" : "stub";
115
+ }
116
+ function walkFiles(root, ignorePatterns) {
117
+ if (!existsSync(root) || !statSync(root).isDirectory()) {
118
+ throw new Error(t("cli.shared.target-invalid", { target: root }));
119
+ }
120
+ let fileCount = 0;
121
+ let ignoredCount = 0;
122
+ const stack = [root];
123
+ while (stack.length > 0) {
124
+ const current = stack.pop();
125
+ if (current === void 0) {
126
+ continue;
127
+ }
128
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
129
+ const absolutePath = join(current, entry.name);
130
+ const relativePath = toPosixPath(relative(root, absolutePath));
131
+ if (shouldIgnore(relativePath, entry.isDirectory(), ignorePatterns)) {
132
+ ignoredCount += 1;
133
+ continue;
134
+ }
135
+ if (entry.isDirectory()) {
136
+ stack.push(absolutePath);
137
+ } else if (entry.isFile()) {
138
+ fileCount += 1;
139
+ }
140
+ }
141
+ }
142
+ return { fileCount, ignoredCount };
143
+ }
144
+ function shouldIgnore(relativePath, isDirectory, ignorePatterns) {
145
+ return ignorePatterns.some((pattern) => matchesIgnorePattern(relativePath, isDirectory, pattern));
146
+ }
147
+ function matchesIgnorePattern(relativePath, isDirectory, pattern) {
148
+ const normalizedPattern = toPosixPath(pattern);
149
+ if (normalizedPattern === "**/*.meta") {
150
+ return relativePath.endsWith(".meta");
151
+ }
152
+ if (normalizedPattern.endsWith("/**")) {
153
+ const directoryPrefix = normalizedPattern.slice(0, -3);
154
+ return relativePath === directoryPrefix || relativePath.startsWith(`${directoryPrefix}/`) || isDirectory && `${relativePath}/` === directoryPrefix;
155
+ }
156
+ return relativePath === normalizedPattern;
157
+ }
158
+ function toPosixPath(path) {
159
+ return path.split(sep).join("/");
160
+ }
161
+ function buildRecommendations(input) {
162
+ const recommendations = [];
163
+ if (!input.hasExistingFabric) {
164
+ recommendations.push(t("cli.scan.recommendation.init"));
165
+ }
166
+ if (input.readmeQuality === "stub") {
167
+ recommendations.push(t("cli.scan.recommendation.readme"));
168
+ }
169
+ if (!input.hasContributing) {
170
+ recommendations.push(t("cli.scan.recommendation.contributing"));
171
+ }
172
+ if (input.framework.kind === "unknown") {
173
+ recommendations.push(t("cli.scan.recommendation.unknown-framework"));
174
+ } else {
175
+ recommendations.push(t("cli.scan.recommendation.framework-dirs", { framework: input.framework.kind }));
176
+ }
177
+ return recommendations;
178
+ }
179
+ function printPrettyReport(report, debug) {
180
+ console.log(paint.ai(t("cli.scan.report.title")));
181
+ const rows = [
182
+ [t("cli.scan.report.target"), paint.human(report.target)],
183
+ [t("cli.scan.report.framework"), paint.ai(report.framework.kind)],
184
+ [
185
+ t("cli.scan.report.readme-quality"),
186
+ report.readmeQuality === "ok" ? paint.success(t("cli.scan.readme-quality.ok")) : paint.warn(t("cli.scan.readme-quality.stub"))
187
+ ],
188
+ [
189
+ t("cli.scan.report.contributing"),
190
+ report.hasContributing ? paint.success(t("cli.shared.present")) : paint.warn(t("cli.shared.absent"))
191
+ ],
192
+ [t("cli.scan.report.files-counted"), String(report.fileCount)],
193
+ [t("cli.scan.report.ignored-entries"), report.ignoredCount > 0 ? paint.muted(String(report.ignoredCount)) : "0"],
194
+ [
195
+ t("cli.scan.report.existing-fabric"),
196
+ report.hasExistingFabric ? paint.warn(t("cli.shared.yes")) : paint.success(t("cli.shared.no"))
197
+ ]
198
+ ];
199
+ if (debug) {
200
+ rows.splice(2, 0, [
201
+ t("cli.scan.report.evidence"),
202
+ report.framework.evidence.length > 0 ? paint.muted(report.framework.evidence.join(", ")) : paint.muted(t("cli.shared.none"))
203
+ ]);
204
+ }
205
+ const labelWidth = Math.max(...rows.map(([key]) => displayWidth(key)));
206
+ for (const [key, value] of rows) {
207
+ console.log(`${paint.muted(padEnd(key, labelWidth))} ${value}`);
208
+ }
209
+ console.log(paint.muted(t("cli.scan.report.recommendations")));
210
+ for (const recommendation of report.recommendations) {
211
+ console.log(`${symbol.warn} ${paint.drift(recommendation)}`);
212
+ }
213
+ }
214
+
215
+ export {
216
+ detectFramework,
217
+ createScanReport,
218
+ scanCommand,
219
+ scan_default
220
+ };
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ padEnd
4
+ } from "./chunk-WWNXR34K.js";
5
+ import {
6
+ t
7
+ } from "./chunk-6ICJICVU.js";
2
8
 
3
9
  // src/commands/human-lint.ts
4
10
  import { createHash } from "crypto";
@@ -6,15 +12,16 @@ import { existsSync } from "fs";
6
12
  import { readFile } from "fs/promises";
7
13
  import { isAbsolute, join, resolve } from "path";
8
14
  import { defineCommand } from "citty";
15
+ import { humanLockEntrySchema } from "@fenglimg/fabric-shared";
9
16
  var humanLintCommand = defineCommand({
10
17
  meta: {
11
18
  name: "human-lint",
12
- description: "\u9A8C\u8BC1\u9501\u5B9A\u7684\u4EBA\u5DE5\u7F16\u8F91\u533A\u5757\u3002"
19
+ description: t("cli.human-lint.description")
13
20
  },
14
21
  args: {
15
22
  target: {
16
23
  type: "string",
17
- description: "\u76EE\u6807\u9879\u76EE\u8DEF\u5F84\uFF0C\u9ED8\u8BA4\u4E3A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u3002",
24
+ description: t("cli.human-lint.args.target.description"),
18
25
  default: process.cwd()
19
26
  }
20
27
  },
@@ -60,11 +67,13 @@ var humanLintCommand = defineCommand({
60
67
  if (violations.length === 0) {
61
68
  return;
62
69
  }
63
- writeStderr("Human-locked content drift detected. Revert the edit or update approved hashes before committing.");
64
- writeStderr("Location Expected Got");
70
+ writeStderr(t("cli.human-lint.drift-detected"));
71
+ writeStderr(
72
+ `${padEnd(t("cli.human-lint.table.location"), 32)} ${padEnd(t("cli.human-lint.table.expected"), 18)} ${t("cli.human-lint.table.got")}`
73
+ );
65
74
  for (const violation of violations) {
66
75
  writeStderr(
67
- `${violation.location.padEnd(32)} ${violation.expected.padEnd(18)} ${violation.actual}`
76
+ `${padEnd(violation.location, 32)} ${padEnd(violation.expected, 18)} ${violation.actual}`
68
77
  );
69
78
  }
70
79
  process.exitCode = 1;
@@ -81,7 +90,7 @@ function hashLockedContent(content, entry) {
81
90
  }
82
91
  function shortenHash(value) {
83
92
  if (value === "missing") {
84
- return value;
93
+ return t("cli.shared.missing");
85
94
  }
86
95
  return value.slice(0, 15);
87
96
  }
@@ -92,5 +101,6 @@ function writeStderr(message) {
92
101
 
93
102
  export {
94
103
  humanLintCommand,
95
- human_lint_default
104
+ human_lint_default,
105
+ humanLockEntrySchema
96
106
  };
@@ -240,6 +240,7 @@ var CodexTOMLConfigWriter = class {
240
240
  };
241
241
 
242
242
  // src/config/resolver.ts
243
+ import { clientPathsSchema, fabricConfigSchema } from "@fenglimg/fabric-shared";
243
244
  function hasExplicitPath(clientPaths, key) {
244
245
  return typeof clientPaths?.[key] === "string" && clientPaths[key].trim().length > 0;
245
246
  }