@fenglimg/fabric-cli 0.1.4 → 1.0.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 (30) hide show
  1. package/dist/{bootstrap-HUDJ2E3Q.js → bootstrap-PMIA4W6G.js} +16 -12
  2. package/dist/{chunk-T3WQUWW4.js → chunk-5BSTO745.js} +9 -6
  3. package/dist/chunk-6ICJICVU.js +10 -0
  4. package/dist/chunk-AEOYCVBG.js +0 -0
  5. package/dist/chunk-DKQ3HOTK.js +206 -0
  6. package/dist/{chunk-U376IPKT.js → chunk-F2BXHPM5.js} +11 -7
  7. package/dist/{chunk-CZ7U6ULM.js → chunk-L43IGJ6X.js} +17 -7
  8. package/dist/chunk-P4KVFB2T.js +0 -0
  9. package/dist/{chunk-N7TTCGJA.js → chunk-VMYPJPKV.js} +1 -0
  10. package/dist/chunk-WWNXR34K.js +49 -0
  11. package/dist/{config-YKDWIRCT.js → config-PXEEXWLM.js} +14 -11
  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 +15 -11
  15. package/dist/init-G6Q3OOMC.js +601 -0
  16. package/dist/{ledger-append-EGIKSMU5.js → ledger-append-XZ5SX4O5.js} +2 -1
  17. package/dist/{pre-commit-CXPH7BZH.js → pre-commit-IEIXHKOD.js} +13 -7
  18. package/dist/{scan-UASZQLQP.js → scan-6CURGC3D.js} +3 -1
  19. package/dist/serve-4J2CQY25.js +112 -0
  20. package/dist/{sync-meta-YTG5V3Y6.js → sync-meta-L6M4AEUT.js} +2 -1
  21. package/package.json +12 -8
  22. package/templates/agents-md/AGENTS.md.template +17 -11
  23. package/templates/agents-md/variants/cocos.md +37 -0
  24. package/templates/agents-md/variants/next.md +37 -0
  25. package/templates/agents-md/variants/vite.md +37 -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/human-lint-II6TBGP4.js +0 -9
  30. package/dist/init-IBS7KO7A.js +0 -149
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createDebugLogger,
4
+ resolveDevMode
5
+ } from "./chunk-AEOYCVBG.js";
6
+ import {
7
+ paint,
8
+ symbol
9
+ } from "./chunk-WWNXR34K.js";
10
+ import {
11
+ t
12
+ } from "./chunk-6ICJICVU.js";
13
+
14
+ // src/commands/serve.ts
15
+ import { defineCommand } from "citty";
16
+ import { startHttpServer } from "@fenglimg/fabric-server";
17
+ var DEFAULT_PORT = 7373;
18
+ var serveCommand = defineCommand({
19
+ meta: {
20
+ name: "serve",
21
+ description: t("cli.serve.description")
22
+ },
23
+ args: {
24
+ port: {
25
+ type: "string",
26
+ description: t("cli.serve.args.port.description"),
27
+ default: String(DEFAULT_PORT)
28
+ },
29
+ host: {
30
+ type: "string",
31
+ description: t("cli.serve.args.host.description"),
32
+ default: "127.0.0.1"
33
+ },
34
+ target: {
35
+ type: "string",
36
+ description: t("cli.serve.args.target.description")
37
+ },
38
+ debug: {
39
+ type: "boolean",
40
+ description: t("cli.serve.args.debug.description"),
41
+ default: false
42
+ }
43
+ },
44
+ async run({ args }) {
45
+ const workspaceRoot = process.cwd();
46
+ const logger = createDebugLogger(args.debug);
47
+ const resolution = resolveDevMode(args.target, workspaceRoot);
48
+ const port = parsePort(args.port);
49
+ const requestedHost = parseHost(args.host);
50
+ const authToken = readAuthTokenFromEnv();
51
+ const host = validateHost(requestedHost, authToken);
52
+ logger(`serve target source: ${resolution.source}`);
53
+ for (const step of resolution.chain) {
54
+ logger(step);
55
+ }
56
+ try {
57
+ await startHttpServer({
58
+ port,
59
+ projectRoot: resolution.target,
60
+ host,
61
+ authToken
62
+ });
63
+ } catch (error) {
64
+ if (isNodeError(error) && error.code === "EADDRINUSE") {
65
+ throw new Error(t("cli.serve.error.port-in-use", { port: String(port), nextPort: String(port + 1) }));
66
+ }
67
+ throw error;
68
+ }
69
+ console.log(`${symbol.ok} ${paint.ai(t("cli.serve.ready.title"))} ${paint.human(`http://${host}:${port}`)}`);
70
+ }
71
+ });
72
+ var serve_default = serveCommand;
73
+ function parsePort(value) {
74
+ const port = Number.parseInt(value ?? String(DEFAULT_PORT), 10);
75
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
76
+ throw new Error(t("cli.shared.invalid-port", { value: value ?? "<unset>" }));
77
+ }
78
+ return port;
79
+ }
80
+ function parseHost(value) {
81
+ const host = value?.trim() ?? "127.0.0.1";
82
+ if (host.length === 0) {
83
+ throw new Error(t("cli.shared.invalid-host-empty"));
84
+ }
85
+ return host;
86
+ }
87
+ function readAuthTokenFromEnv() {
88
+ const token = process.env.FABRIC_AUTH_TOKEN;
89
+ return token === void 0 || token.length === 0 ? void 0 : token;
90
+ }
91
+ function validateHost(host, authToken) {
92
+ if (authToken !== void 0) {
93
+ return host;
94
+ }
95
+ if (!isLoopbackHost(host)) {
96
+ console.error(
97
+ `${symbol.warn} ${paint.warn(t("cli.serve.warning.host-fallback", { host }))}`
98
+ );
99
+ return "127.0.0.1";
100
+ }
101
+ return host;
102
+ }
103
+ function isLoopbackHost(host) {
104
+ return host === "127.0.0.1" || host === "localhost" || host === "::1";
105
+ }
106
+ function isNodeError(error) {
107
+ return error instanceof Error;
108
+ }
109
+ export {
110
+ serve_default as default,
111
+ serveCommand
112
+ };
@@ -3,8 +3,9 @@ import {
3
3
  computeAgentsMeta,
4
4
  syncMetaCommand,
5
5
  sync_meta_default
6
- } from "./chunk-T3WQUWW4.js";
6
+ } from "./chunk-5BSTO745.js";
7
7
  import "./chunk-P4KVFB2T.js";
8
+ import "./chunk-6ICJICVU.js";
8
9
  export {
9
10
  computeAgentsMeta,
10
11
  sync_meta_default as default,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fenglimg/fabric-cli",
3
- "version": "0.1.4",
3
+ "version": "1.0.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "fab": "dist/index.js"
@@ -11,19 +11,23 @@
11
11
  "dist",
12
12
  "templates"
13
13
  ],
14
- "scripts": {
15
- "build": "tsup",
16
- "dev": "tsup --watch"
17
- },
18
14
  "dependencies": {
19
- "@fenglimg/fabric-server": "^0.1.0",
20
15
  "@iarna/toml": "^2.2.5",
21
- "citty": "^0.2.2"
16
+ "citty": "^0.2.2",
17
+ "picocolors": "^1.1.1",
18
+ "string-width": "^7.2.0",
19
+ "@fenglimg/fabric-shared": "1.0.0",
20
+ "@fenglimg/fabric-server": "1.0.0"
22
21
  },
23
22
  "devDependencies": {
24
23
  "@types/iarna__toml": "^2.0.5",
25
24
  "@types/node": "^22.15.0",
26
25
  "tsup": "^8.5.0",
27
26
  "typescript": "^5.8.3"
27
+ },
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "dev": "tsup --watch",
31
+ "test": "vitest run"
28
32
  }
29
- }
33
+ }
@@ -1,29 +1,35 @@
1
1
  # { projectName } — L0 AGENTS.md
2
2
 
3
- // TODO: describe project purpose.
3
+ <!-- This is the fallback template. If you have Claude Code, run the agents-md-init skill for a semantically richer AGENTS.md. -->
4
4
 
5
- <!-- TODO: tech stack: detected framework is { frameworkKind }. Verify this manually before replacing TODOs. -->
5
+ This file is the non-AI fallback scaffold for repositories that have not gone through semantic AGENTS initialization yet.
6
6
 
7
7
  <!-- fab:index -->
8
8
  <!-- /fab:index -->
9
9
 
10
10
  ## Human Documentation References
11
11
 
12
- // TODO: reference README.md sections that are source-of-truth for humans.
13
- // TODO: reference CONTRIBUTING.md if this project has one.
12
+ - `README.md` is the source of truth for setup, scripts, runtime expectations, and product behavior.
13
+ - `CONTRIBUTING.md`, if present, overrides local assumptions for branching, review, and release workflow.
14
+ - When documentation and code disagree, pause and ask a human before rewriting either source.
14
15
 
15
16
  ## L0 AI Constraints
16
17
 
17
- // TODO: list hard constraints, e.g., ban any, require TypeScript strict.
18
- // TODO: list commands AI may run for validation.
19
- // TODO: list files or directories AI must not modify without permission.
18
+ - Keep changes small, reversible, and validated with the narrowest relevant command before broader builds.
19
+ - Match existing repository patterns before introducing new abstractions, tooling, or file structure.
20
+ - Avoid modifying secrets, deployment configuration, lockfiles, or generated artifacts unless the task explicitly requires it.
21
+
22
+ ## Framework Baseline
23
+
24
+ - Fabric did not match this repository to a first-class scaffold. Detected framework kind: `{ frameworkKind }`.
25
+ - Treat the current source tree and existing docs as the primary evidence before writing new rules.
26
+ - Add L1 AGENTS.md files only for directories that have clearly different runtime, build, or safety constraints.
20
27
 
21
28
  ## @HUMAN
22
29
 
23
- // TODO: human-owned decisions live here. AI must not rewrite locked sentences.
24
- // TODO: placeholder locked sentence 1.
25
- // TODO: placeholder locked sentence 2.
30
+ - Human-owned decisions belong in this section or `.fabric/human-lock.json`; AI must pause before changing them.
31
+ - Record release gates, product invariants, or directories that need explicit approval here.
26
32
 
27
33
  ## L1 Candidate Notes
28
34
 
29
- // TODO: identify domain folders that may need scoped AGENTS.md files.
35
+ - Promote a directory to L1 only when it has its own commands, deployment surface, or irreversible side effects.
@@ -0,0 +1,37 @@
1
+ # { projectName } — L0 AGENTS.md
2
+
3
+ <!-- This is the fallback template. If you have Claude Code, run the agents-md-init skill for a semantically richer AGENTS.md. -->
4
+
5
+ This file is the non-AI fallback scaffold for a Cocos Creator codebase. Prefer conservative edits and preserve editor-generated assets.
6
+
7
+ <!-- fab:index -->
8
+ <!-- /fab:index -->
9
+
10
+ ## Human Documentation References
11
+
12
+ - `README.md` is the source of truth for gameplay intent, scene flow, setup, and packaging steps.
13
+ - Cocos Editor settings, prefab wiring, and scene structure are human-reviewed surfaces; ask before broad refactors.
14
+ - When code and editor data disagree, inspect both before changing either side.
15
+
16
+ ## L0 AI Constraints
17
+
18
+ - Component scripts must follow the existing Cocos pattern: import from `cc`, destructure decorators from `_decorator`, apply `@ccclass`, and extend `Component`.
19
+ - Respect Cocos lifecycle order. Put initialization in `onLoad` or `start`, cleanup in `onDestroy`, and keep `update` lightweight and frame-safe.
20
+ - Do not mark `update()` as `async` and do not introduce blocking or network-heavy work into frame callbacks.
21
+ - Preserve `.meta` files, prefab references, scene bindings, and asset UUID relationships unless the task explicitly requires coordinated editor changes.
22
+ - Prefer targeted script edits over renaming assets, moving prefab trees, or restructuring `assets/` folders.
23
+
24
+ ## Cocos Baseline
25
+
26
+ - Keep gameplay logic inside component boundaries or small helpers that are already imported by components.
27
+ - When adding new nodes or serialized fields, match the repository's existing decorator and property style.
28
+ - Validate with the smallest relevant TypeScript or project command first; only ask for editor-side verification when asset wiring changes.
29
+
30
+ ## @HUMAN
31
+
32
+ - Human-owned decisions belong in this section or `.fabric/human-lock.json`; AI must pause before changing them.
33
+ - Record protected scenes, prefabs, release assets, or multiplayer protocol invariants here.
34
+
35
+ ## L1 Candidate Notes
36
+
37
+ - Add scoped AGENTS.md files only for large subtrees such as `assets/scripts`, `assets/prefabs`, or `assets/scenes` when they gain distinct local rules.
@@ -0,0 +1,37 @@
1
+ # { projectName } — L0 AGENTS.md
2
+
3
+ <!-- This is the fallback template. If you have Claude Code, run the agents-md-init skill for a semantically richer AGENTS.md. -->
4
+
5
+ This file is the non-AI fallback scaffold for a Next.js application using the App Router by default.
6
+
7
+ <!-- fab:index -->
8
+ <!-- /fab:index -->
9
+
10
+ ## Human Documentation References
11
+
12
+ - `README.md` is the source of truth for product flows, local setup, and deployment expectations.
13
+ - Operational behavior for routes, caching, auth, or background jobs should be verified against code and docs together before refactoring.
14
+ - If `CONTRIBUTING.md` exists, follow its review and release gates over local assumptions.
15
+
16
+ ## L0 AI Constraints
17
+
18
+ - Treat `app/` as App Router unless the repository clearly uses another convention.
19
+ - Keep React Server Components on the server side by default. Add `"use client"` only for components that need browser APIs, local state, effects, or event handlers.
20
+ - Do not import server-only modules into client components, and do not access browser-only globals from server components or route handlers.
21
+ - Keep data fetching, cache behavior, and mutations aligned with existing route, server action, and API handler patterns.
22
+ - Prefer small route-local changes over broad restructuring of layouts, middleware, or caching policy.
23
+
24
+ ## Next Baseline
25
+
26
+ - Preserve the server/client boundary at file level so the bundle stays predictable and hydration issues stay local.
27
+ - Reuse existing patterns for route handlers, metadata, and loading or error states before creating new abstractions.
28
+ - Validate the narrowest route or package command first, then run the broader build once local behavior is stable.
29
+
30
+ ## @HUMAN
31
+
32
+ - Human-owned decisions belong in this section or `.fabric/human-lock.json`; AI must pause before changing them.
33
+ - Record protected routes, auth assumptions, cache invariants, or SEO requirements here.
34
+
35
+ ## L1 Candidate Notes
36
+
37
+ - Add scoped AGENTS.md files only for areas such as `app`, `components`, or `lib` when they gain strong local conventions.
@@ -0,0 +1,37 @@
1
+ # { projectName } — L0 AGENTS.md
2
+
3
+ <!-- This is the fallback template. If you have Claude Code, run the agents-md-init skill for a semantically richer AGENTS.md. -->
4
+
5
+ This file is the non-AI fallback scaffold for a Vite application. Treat the repository as a client-rendered app unless the code clearly establishes another pattern.
6
+
7
+ <!-- fab:index -->
8
+ <!-- /fab:index -->
9
+
10
+ ## Human Documentation References
11
+
12
+ - `README.md` is the source of truth for setup, scripts, environment variables, and deployment assumptions.
13
+ - Product behavior and UX expectations should be taken from shipped screens and existing docs before introducing new architecture.
14
+ - If `CONTRIBUTING.md` exists, follow its branch, review, and validation rules over local defaults.
15
+
16
+ ## L0 AI Constraints
17
+
18
+ - Keep browser code on the client side. Assume SSR is off unless the repository already contains an explicit server rendering path.
19
+ - Preserve clear module boundaries: UI components stay presentation-focused, shared utilities stay framework-agnostic, and side effects remain close to the feature that needs them.
20
+ - Match the repo's TypeScript strictness and import style; do not weaken types or introduce `any` to move faster.
21
+ - Use `import.meta.env` for Vite environment access and avoid Node-only APIs in browser bundles.
22
+ - Prefer incremental feature-local edits over sweeping alias, build, or routing changes.
23
+
24
+ ## Vite Baseline
25
+
26
+ - Keep module scope cheap; avoid eager side effects that run on import when they can live in explicit startup code.
27
+ - Reuse existing state and styling patterns before adding new stores, plugin layers, or CSS systems.
28
+ - Validate with the narrowest app command first, then run the broader build only after local checks pass.
29
+
30
+ ## @HUMAN
31
+
32
+ - Human-owned decisions belong in this section or `.fabric/human-lock.json`; AI must pause before changing them.
33
+ - Record protected environment contracts, analytics events, or release-specific UI constraints here.
34
+
35
+ ## L1 Candidate Notes
36
+
37
+ - Add scoped AGENTS.md files only when areas such as `src/features`, `src/components`, or `src/lib` accumulate their own rules.
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ const { existsSync } = require("node:fs");
3
+ const { join } = require("node:path");
4
+
5
+ const forensicPath = join(process.cwd(), ".fabric", "forensic.json");
6
+ const initContextPath = join(process.cwd(), ".fabric", "init-context.json");
7
+
8
+ if (!existsSync(forensicPath) || existsSync(initContextPath)) {
9
+ process.exit(0);
10
+ }
11
+
12
+ process.stdout.write(
13
+ JSON.stringify({
14
+ decision: "block",
15
+ reason:
16
+ "fab init 已完成证据收集,但项目 AGENTS.md 初始化尚未完成。调用 agents-md-init skill (通过 Skill 工具) 完成 3 阶段初始化访谈。参考: .claude/skills/agents-md-init/SKILL.md + .fabric/forensic.json",
17
+ }),
18
+ );
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: agents-md-init
3
+ description: Use this skill when fab init just completed, when forensic.json generated, or when the user is asking to initialize AGENTS.md. This skill runs a 3-phase initialization interview, writes .fabric/init-context.json, generates layered AGENTS.md, and updates .fabric/agents.meta.json.
4
+ allowed-tools: Read, Write, Glob, Grep, Bash
5
+ ---
6
+
7
+ ## Precondition
8
+
9
+ 必须先 Read `.fabric/forensic.json`。若该文件不存在,终止 skill 并告知用户:`请先运行 fab init 生成证据包`。
10
+
11
+ 把以下状态视为 initialization pending:
12
+
13
+ - `.fabric/forensic.json` 存在
14
+ - `.fabric/init-context.json` 不存在
15
+
16
+ ## 执行流程 (3 Phase / 3 Round)
17
+
18
+ ### Phase 1 — 框架确认(1 轮,高效)
19
+
20
+ 展示 `.fabric/forensic.json` 的 `framework`、`topology.by_ext`、`entry_points` 摘要,向用户提 1-2 个框架架构澄清问题。
21
+
22
+ 示例(Cocos Creator 3.x):
23
+
24
+ > 我检测到 Cocos Creator 3.8 项目,主要脚本在 `assets/scripts`,采用 `@ccclass + extends Component` 模式。请确认:(1) 这是 TypeScript 项目(非 JavaScript)对吗?(2) 节点引用主要通过 `@property(Node)` 注入,还是 `find/getChildByName`?
25
+
26
+ 将用户确认结果暂存为已验证 framework assumptions。
27
+
28
+ ### Phase 2 — 不变式提取(1 轮,关键)
29
+
30
+ 基于 `.fabric/forensic.json` 的 `recommendations_for_skill` 列表,向用户提 3-5 个 invariants 问题,覆盖三类:
31
+
32
+ - `ban`:禁止 any、禁止 update() 中 async、禁止 find-by-name 等
33
+ - `require`:必须 strict TypeScript、必须 `@ccclass` decorator、必须 import from `cc` only 等
34
+ - `protect`:哪些目录或文件 AI 不能修改,一般是 `assets/prefabs/**`、`assets/scenes/**`、`**/*.meta`
35
+
36
+ 原则:
37
+
38
+ - 只问 invariants,不问 preferences
39
+ - 每个问题只接受 yes/no/具体规则,不接受模糊回答
40
+ - 不要自动推测用户未确认的硬约束
41
+
42
+ ### Phase 3 — 构造与落地(1 轮,自动)
43
+
44
+ 1. 写入 `.fabric/init-context.json`,包含:
45
+
46
+ - `framework`
47
+ - `architecture_patterns`
48
+ - `invariants`
49
+ - `domain_groups`
50
+ - `interview_trail`
51
+ - `forensic_ref`
52
+
53
+ 写入规则:
54
+
55
+ - `invariants[].type` 必须是 `ban`、`require`、`protect`
56
+ - `domain_groups` 由 `entry_points` 和访谈结果推断
57
+ - `interview_trail[]` 必须记录 Phase 1 和 Phase 2 的原始问答
58
+ - `forensic_ref` 必须为 `.fabric/forensic.json`
59
+
60
+ 2. 生成分层 `AGENTS.md`:
61
+
62
+ - 根 `AGENTS.md` 必须在 300 行以内,结构包含:
63
+ - `# {projectName} — L0 AGENTS.md`
64
+ - `<!-- fab:index -->`:填充 `domain_groups` 索引
65
+ - `## L0 AI Constraints`:从 invariants 派生,按 `ban`、`require`、`protect` 分段
66
+ - `## @HUMAN`:protect 路径和用户声明的人类保护规则
67
+ - `## L1 Candidate Notes`:domain_groups 对应的候选子模块说明
68
+
69
+ 如果 `domain_groups.length >= 2`,为每个 group 生成 `{group_path}/AGENTS.md`。最多到 L3,总嵌套不超过 4 层。
70
+
71
+ 3. 更新 `.fabric/agents.meta.json` 的 nodes 树,保持 revision hash 链一致:
72
+
73
+ - nodes 结构与生成后的 AGENTS 层级一致
74
+ - 更新所有变更 AGENTS 文件的 hash
75
+ - 保持 revision hash 链内部一致
76
+
77
+ 4. 最终输出:向用户列出生成文件清单,并建议后续维护时运行 `fab sync-meta`。
78
+
79
+ ## Hard Rules
80
+
81
+ - Zero TODO: 不生成任何 `TODO`、`TBD`、placeholder、stub
82
+ - No YAML frontmatter in outputs: 除本 skill 自身外,生成的 `AGENTS.md` 不得包含 YAML frontmatter
83
+ - Root AGENTS.md <= 300 lines
84
+ - AGENTS 嵌套总层级 <= 4
85
+ - 不得自动推测用户未确认的 invariants
86
+ - 有不确定内容时删除,不要留占位符
@@ -1,236 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- createDebugLogger,
4
- readFabricConfig,
5
- resolveDevMode
6
- } from "./chunk-AEOYCVBG.js";
7
- import {
8
- resolveIgnores
9
- } from "./chunk-P4KVFB2T.js";
10
-
11
- // src/commands/scan.ts
12
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
13
- import { isAbsolute, join as join2, relative, resolve, sep } from "path";
14
- import { defineCommand } from "citty";
15
-
16
- // src/scanner/detector.ts
17
- import { existsSync, readFileSync } from "fs";
18
- import { join } from "path";
19
- function detectFramework(root) {
20
- const evidence = [];
21
- if (existsSync(join(root, "project.config.json"))) {
22
- return {
23
- kind: "cocos-creator",
24
- evidence: ["project.config.json"]
25
- };
26
- }
27
- const packageJsonPath = join(root, "package.json");
28
- if (existsSync(packageJsonPath)) {
29
- const packageJson = readPackageJson(packageJsonPath);
30
- const deps = collectDependencyNames(packageJson);
31
- for (const [dependencyName, kind] of [
32
- ["next", "next"],
33
- ["vite", "vite"],
34
- ["react", "react"],
35
- ["vue", "vue"]
36
- ]) {
37
- if (deps.has(dependencyName)) {
38
- evidence.push(`package.json dependency: ${dependencyName}`);
39
- return { kind, evidence };
40
- }
41
- }
42
- evidence.push("package.json");
43
- }
44
- if (existsSync(join(root, "Cargo.toml"))) {
45
- return {
46
- kind: "rust",
47
- evidence: ["Cargo.toml"]
48
- };
49
- }
50
- if (existsSync(join(root, "pyproject.toml"))) {
51
- return {
52
- kind: "python",
53
- evidence: ["pyproject.toml"]
54
- };
55
- }
56
- return {
57
- kind: "unknown",
58
- evidence
59
- };
60
- }
61
- function readPackageJson(packageJsonPath) {
62
- try {
63
- return JSON.parse(readFileSync(packageJsonPath, "utf8"));
64
- } catch {
65
- return {};
66
- }
67
- }
68
- function collectDependencyNames(packageJson) {
69
- return /* @__PURE__ */ new Set([
70
- ...Object.keys(packageJson.dependencies ?? {}),
71
- ...Object.keys(packageJson.devDependencies ?? {}),
72
- ...Object.keys(packageJson.peerDependencies ?? {}),
73
- ...Object.keys(packageJson.optionalDependencies ?? {})
74
- ]);
75
- }
76
-
77
- // src/commands/scan.ts
78
- function createScanReport(targetInput = process.cwd(), fabricConfig) {
79
- const target = normalizeTarget(targetInput);
80
- const framework = detectFramework(target);
81
- const readmeQuality = getReadmeQuality(target);
82
- const hasContributing = existsSync2(join2(target, "CONTRIBUTING.md"));
83
- const hasExistingFabric = existsSync2(join2(target, "AGENTS.md")) || existsSync2(join2(target, ".fabric"));
84
- const walkResult = walkFiles(target, resolveIgnores(fabricConfig));
85
- return {
86
- target,
87
- framework,
88
- readmeQuality,
89
- hasContributing,
90
- fileCount: walkResult.fileCount,
91
- ignoredCount: walkResult.ignoredCount,
92
- hasExistingFabric,
93
- recommendations: buildRecommendations({
94
- framework,
95
- readmeQuality,
96
- hasContributing,
97
- hasExistingFabric
98
- })
99
- };
100
- }
101
- var scanCommand = defineCommand({
102
- meta: {
103
- name: "scan",
104
- description: "\u626B\u63CF\u9879\u76EE\u4EE5\u68C0\u6D4B Fabric \u5F15\u5BFC\u5019\u9009\u6A21\u5757\u3002"
105
- },
106
- args: {
107
- target: {
108
- type: "string",
109
- description: "\u626B\u63CF\u7684\u76EE\u6807\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u9ED8\u8BA4\u4F9D\u6B21\u4F7F\u7528 CLI \u53C2\u6570\u3001EXTERNAL_FIXTURE_PATH\u3001fabric.config.json \u6216\u5F53\u524D\u76EE\u5F55\u3002"
110
- },
111
- debug: {
112
- type: "boolean",
113
- description: "\u4EE5\u683C\u5F0F\u5316\u8F93\u51FA\u6253\u5370\u68C0\u6D4B\u8BC1\u636E\u3002",
114
- default: false
115
- },
116
- json: {
117
- type: "boolean",
118
- description: "\u4EE5 JSON \u683C\u5F0F\u8F93\u51FA\u8BCA\u65AD\u62A5\u544A\u3002",
119
- default: false
120
- }
121
- },
122
- async run({ args }) {
123
- const workspaceRoot = process.cwd();
124
- const logger = createDebugLogger(args.debug);
125
- const resolution = resolveDevMode(args.target, workspaceRoot);
126
- const fabricConfig = readFabricConfig(workspaceRoot);
127
- logger(`scan target source: ${resolution.source}`);
128
- for (const step of resolution.chain) {
129
- logger(step);
130
- }
131
- const report = createScanReport(resolution.target, fabricConfig);
132
- if (args.json) {
133
- console.log(JSON.stringify(report, null, 2));
134
- return;
135
- }
136
- printPrettyReport(report, Boolean(args.debug));
137
- }
138
- });
139
- var scan_default = scanCommand;
140
- function normalizeTarget(targetInput) {
141
- return isAbsolute(targetInput) ? targetInput : resolve(process.cwd(), targetInput);
142
- }
143
- function getReadmeQuality(target) {
144
- const readmePath = join2(target, "README.md");
145
- if (!existsSync2(readmePath)) {
146
- return "stub";
147
- }
148
- const wordCount = readFileSync2(readmePath, "utf8").trim().split(/\s+/).filter(Boolean).length;
149
- return wordCount >= 200 ? "ok" : "stub";
150
- }
151
- function walkFiles(root, ignorePatterns) {
152
- if (!existsSync2(root) || !statSync(root).isDirectory()) {
153
- throw new Error(`Target must be an existing directory: ${root}`);
154
- }
155
- let fileCount = 0;
156
- let ignoredCount = 0;
157
- const stack = [root];
158
- while (stack.length > 0) {
159
- const current = stack.pop();
160
- if (current === void 0) {
161
- continue;
162
- }
163
- for (const entry of readdirSync(current, { withFileTypes: true })) {
164
- const absolutePath = join2(current, entry.name);
165
- const relativePath = toPosixPath(relative(root, absolutePath));
166
- if (shouldIgnore(relativePath, entry.isDirectory(), ignorePatterns)) {
167
- ignoredCount += 1;
168
- continue;
169
- }
170
- if (entry.isDirectory()) {
171
- stack.push(absolutePath);
172
- } else if (entry.isFile()) {
173
- fileCount += 1;
174
- }
175
- }
176
- }
177
- return { fileCount, ignoredCount };
178
- }
179
- function shouldIgnore(relativePath, isDirectory, ignorePatterns) {
180
- return ignorePatterns.some((pattern) => matchesIgnorePattern(relativePath, isDirectory, pattern));
181
- }
182
- function matchesIgnorePattern(relativePath, isDirectory, pattern) {
183
- const normalizedPattern = toPosixPath(pattern);
184
- if (normalizedPattern === "**/*.meta") {
185
- return relativePath.endsWith(".meta");
186
- }
187
- if (normalizedPattern.endsWith("/**")) {
188
- const directoryPrefix = normalizedPattern.slice(0, -3);
189
- return relativePath === directoryPrefix || relativePath.startsWith(`${directoryPrefix}/`) || isDirectory && `${relativePath}/` === directoryPrefix;
190
- }
191
- return relativePath === normalizedPattern;
192
- }
193
- function toPosixPath(path) {
194
- return path.split(sep).join("/");
195
- }
196
- function buildRecommendations(input) {
197
- const recommendations = [];
198
- if (!input.hasExistingFabric) {
199
- recommendations.push("L0: Run fab init to scaffold AGENTS.md with TODO markers.");
200
- }
201
- if (input.readmeQuality === "stub") {
202
- recommendations.push("L0: Expand README.md before promoting project facts into AGENTS.md references.");
203
- }
204
- if (!input.hasContributing) {
205
- recommendations.push("L0: Add CONTRIBUTING.md or leave an AGENTS.md TODO reference for contribution flow.");
206
- }
207
- if (input.framework.kind === "unknown") {
208
- recommendations.push("L1: Add tech-stack TODOs manually because no framework marker was detected.");
209
- } else {
210
- recommendations.push(`L1: Review ${input.framework.kind} directories for future scoped AGENTS.md files.`);
211
- }
212
- return recommendations;
213
- }
214
- function printPrettyReport(report, debug) {
215
- console.log("Fabric scan report");
216
- console.log(`Target: ${report.target}`);
217
- console.log(`Framework: ${report.framework.kind}`);
218
- if (debug) {
219
- console.log(`Evidence: ${report.framework.evidence.length > 0 ? report.framework.evidence.join(", ") : "none"}`);
220
- }
221
- console.log(`README quality: ${report.readmeQuality}`);
222
- console.log(`CONTRIBUTING.md: ${report.hasContributing ? "present" : "missing"}`);
223
- console.log(`Files counted: ${report.fileCount}`);
224
- console.log(`Ignored entries: ${report.ignoredCount}`);
225
- console.log(`Existing Fabric files: ${report.hasExistingFabric ? "yes" : "no"}`);
226
- console.log("Recommendations:");
227
- for (const recommendation of report.recommendations) {
228
- console.log(`- ${recommendation}`);
229
- }
230
- }
231
-
232
- export {
233
- createScanReport,
234
- scanCommand,
235
- scan_default
236
- };
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- humanLintCommand,
4
- human_lint_default
5
- } from "./chunk-CZ7U6ULM.js";
6
- export {
7
- human_lint_default as default,
8
- humanLintCommand
9
- };