@fenglimg/fabric-cli 1.4.0 → 1.5.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.
- package/README.md +15 -11
- package/dist/approve-YT4DEABS.js +138 -0
- package/dist/{bootstrap-B6RCVJZJ.js → bootstrap-VGL3AR26.js} +3 -3
- package/dist/{chunk-AZRKMFRY.js → chunk-QSAEGVKE.js} +2 -2
- package/dist/{chunk-XQYY2U2C.js → chunk-T2WJF5I3.js} +9 -9
- package/dist/index.js +6 -5
- package/dist/{init-QC2MLFHR.js → init-YR7EVBYQ.js} +328 -38
- package/dist/{scan-43R3IBLR.js → scan-QH76LC7Z.js} +1 -1
- package/dist/scanner/tree-sitter-probe.d.ts +24 -0
- package/dist/scanner/tree-sitter-probe.js +107 -0
- package/package.json +6 -3
- package/dist/{update-FY2WKWPB.js → update-M5M5PYKE.js} +6 -6
package/README.md
CHANGED
|
@@ -13,14 +13,18 @@
|
|
|
13
13
|
|
|
14
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
15
|
|
|
16
|
-
## Common Commands
|
|
17
|
-
|
|
18
|
-
- `fabric init`
|
|
19
|
-
- `fabric serve`
|
|
20
|
-
- `fabric doctor --audit`
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- `fabric
|
|
16
|
+
## Common Commands
|
|
17
|
+
|
|
18
|
+
- `fabric init`
|
|
19
|
+
- `fabric serve`
|
|
20
|
+
- `fabric doctor --audit`
|
|
21
|
+
- `fabric approve --interactive`
|
|
22
|
+
- `fabric approve --all`
|
|
23
|
+
|
|
24
|
+
## Advanced Commands
|
|
25
|
+
|
|
26
|
+
- `fabric bootstrap install`
|
|
27
|
+
- `fabric config install`
|
|
28
|
+
- `fabric hooks install`
|
|
29
|
+
|
|
30
|
+
`fabric approve` updates drifted entries in `.fabric/human-lock.json` after review. Use `--interactive` for per-entry confirmation and `--all` only when drift has already been reviewed elsewhere.
|
|
@@ -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-
|
|
7
|
-
import "./chunk-
|
|
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-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
approve: () => import("./approve-YT4DEABS.js").then((module) => module.default),
|
|
12
|
+
init: () => import("./init-YR7EVBYQ.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-
|
|
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.
|
|
36
|
+
version: "1.5.0",
|
|
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-
|
|
5
|
+
} from "./chunk-T2WJF5I3.js";
|
|
6
6
|
import {
|
|
7
7
|
detectFramework
|
|
8
|
-
} from "./chunk-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
1150
|
+
return true ? "1.5.0" : "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;
|
|
@@ -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.
|
|
3
|
+
"version": "1.5.0",
|
|
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
|
-
"
|
|
23
|
-
"
|
|
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.0",
|
|
26
|
+
"@fenglimg/fabric-shared": "1.5.0"
|
|
24
27
|
},
|
|
25
28
|
"devDependencies": {
|
|
26
29
|
"@types/iarna__toml": "^2.0.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";
|