@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.
- package/dist/{bootstrap-HUDJ2E3Q.js → bootstrap-PMIA4W6G.js} +16 -12
- package/dist/chunk-6ICJICVU.js +10 -0
- package/dist/{chunk-T3WQUWW4.js → chunk-6UUPKSDE.js} +78 -36
- package/dist/chunk-AEOYCVBG.js +0 -0
- package/dist/{chunk-U376IPKT.js → chunk-F2BXHPM5.js} +11 -7
- package/dist/chunk-JWUO6TIS.js +220 -0
- package/dist/{chunk-CZ7U6ULM.js → chunk-L43IGJ6X.js} +17 -7
- package/dist/{chunk-N7TTCGJA.js → chunk-VMYPJPKV.js} +1 -0
- package/dist/chunk-WWNXR34K.js +49 -0
- package/dist/{config-YKDWIRCT.js → config-PXEEXWLM.js} +14 -11
- package/dist/doctor-QTSG2RWF.js +125 -0
- package/dist/{hooks-VXXO4VZP.js → hooks-5S5IRVQE.js} +15 -12
- package/dist/human-lint-YSFOZHZ7.js +13 -0
- package/dist/index.js +16 -11
- package/dist/init-R73E5YTG.js +1164 -0
- package/dist/{ledger-append-EGIKSMU5.js → ledger-append-XZ5SX4O5.js} +2 -1
- package/dist/{pre-commit-CXPH7BZH.js → pre-commit-BLSUMT3P.js} +14 -9
- package/dist/{scan-UASZQLQP.js → scan-JBGFRB7P.js} +3 -2
- package/dist/serve-4J2CQY25.js +112 -0
- package/dist/{sync-meta-YTG5V3Y6.js → sync-meta-THZSEM7Y.js} +6 -2
- package/package.json +12 -8
- package/templates/agents-md/AGENTS.md.template +20 -29
- package/templates/agents-md/variants/cocos.md +20 -0
- package/templates/agents-md/variants/next.md +20 -0
- package/templates/agents-md/variants/vite.md +20 -0
- package/templates/claude-hooks/agents-md-init-reminder.cjs +18 -0
- package/templates/claude-skills/agents-md-init/SKILL.md +86 -0
- package/dist/chunk-BWZHNZG6.js +0 -236
- package/dist/chunk-P4KVFB2T.js +0 -22
- package/dist/human-lint-II6TBGP4.js +0 -9
- 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-
|
|
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: "
|
|
56
|
+
description: t("cli.bootstrap.description")
|
|
54
57
|
},
|
|
55
58
|
subCommands: {
|
|
56
59
|
install: defineCommand({
|
|
57
60
|
meta: {
|
|
58
61
|
name: "install",
|
|
59
|
-
description: "
|
|
62
|
+
description: t("cli.bootstrap.install.description")
|
|
60
63
|
},
|
|
61
64
|
args: {
|
|
62
65
|
clients: {
|
|
63
66
|
type: "string",
|
|
64
|
-
description: "
|
|
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
|
-
"
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
@@ -1,27 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
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: "
|
|
19
|
+
description: t("cli.sync-meta.description")
|
|
15
20
|
},
|
|
16
21
|
args: {
|
|
17
22
|
target: {
|
|
18
23
|
type: "string",
|
|
19
|
-
description: "
|
|
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: "
|
|
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("
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
94
|
-
const
|
|
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 = [
|
|
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
|
|
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
|
|
118
|
-
return
|
|
126
|
+
function deriveLayer(relativePath) {
|
|
127
|
+
return deriveAgentsMetaLayer(relativePath);
|
|
119
128
|
}
|
|
120
|
-
function
|
|
121
|
-
|
|
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
|
-
|
|
143
|
+
const layer = deriveLayer(file);
|
|
144
|
+
const relativeStem = getMirrorRelativeStem(file);
|
|
145
|
+
return `${layer}/${relativeStem}`;
|
|
143
146
|
}
|
|
144
147
|
function createDefaultNodeMeta(file) {
|
|
145
|
-
const
|
|
148
|
+
const layer = deriveLayer(file);
|
|
149
|
+
const topologyType = deriveTopologyType(file);
|
|
146
150
|
return {
|
|
147
151
|
file,
|
|
148
|
-
scope_glob:
|
|
149
|
-
deps:
|
|
150
|
-
priority:
|
|
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
|
};
|
package/dist/chunk-AEOYCVBG.js
CHANGED
|
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: "
|
|
16
|
+
description: t("cli.ledger-append.description")
|
|
14
17
|
},
|
|
15
18
|
args: {
|
|
16
19
|
target: {
|
|
17
20
|
type: "string",
|
|
18
|
-
description: "
|
|
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: "
|
|
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
|
|
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(
|
|
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 ?
|
|
87
|
-
return
|
|
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: "
|
|
19
|
+
description: t("cli.human-lint.description")
|
|
13
20
|
},
|
|
14
21
|
args: {
|
|
15
22
|
target: {
|
|
16
23
|
type: "string",
|
|
17
|
-
description: "
|
|
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("
|
|
64
|
-
writeStderr(
|
|
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
|
|
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
|
|
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
|
}
|