@fenglimg/fabric-cli 1.0.0 → 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/{chunk-5BSTO745.js → chunk-6UUPKSDE.js} +70 -31
- package/dist/{chunk-DKQ3HOTK.js → chunk-JWUO6TIS.js} +18 -4
- package/dist/doctor-QTSG2RWF.js +125 -0
- package/dist/index.js +6 -5
- package/dist/init-R73E5YTG.js +1164 -0
- package/dist/{pre-commit-IEIXHKOD.js → pre-commit-BLSUMT3P.js} +4 -5
- package/dist/{scan-6CURGC3D.js → scan-JBGFRB7P.js} +1 -2
- package/dist/{sync-meta-L6M4AEUT.js → sync-meta-THZSEM7Y.js} +5 -2
- package/package.json +3 -3
- package/templates/agents-md/AGENTS.md.template +20 -35
- package/templates/agents-md/variants/cocos.md +20 -37
- package/templates/agents-md/variants/next.md +20 -37
- package/templates/agents-md/variants/vite.md +20 -37
- package/dist/chunk-P4KVFB2T.js +0 -22
- package/dist/init-G6Q3OOMC.js +0 -601
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
resolveIgnores
|
|
4
|
-
} from "./chunk-P4KVFB2T.js";
|
|
5
2
|
import {
|
|
6
3
|
t
|
|
7
4
|
} from "./chunk-6ICJICVU.js";
|
|
@@ -10,6 +7,11 @@ import {
|
|
|
10
7
|
import { createHash } from "crypto";
|
|
11
8
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
12
9
|
import { isAbsolute, join, relative, resolve, sep } from "path";
|
|
10
|
+
import {
|
|
11
|
+
agentsMetaSchema,
|
|
12
|
+
deriveAgentsMetaLayer,
|
|
13
|
+
deriveAgentsMetaTopologyType
|
|
14
|
+
} from "@fenglimg/fabric-shared";
|
|
13
15
|
import { defineCommand } from "citty";
|
|
14
16
|
var syncMetaCommand = defineCommand({
|
|
15
17
|
meta: {
|
|
@@ -55,11 +57,15 @@ function computeAgentsMeta(target) {
|
|
|
55
57
|
const metaPath = join(target, ".fabric", "agents.meta.json");
|
|
56
58
|
const existingMeta = readExistingMeta(metaPath);
|
|
57
59
|
const existingByFile = indexExistingNodesByFile(existingMeta);
|
|
58
|
-
const agentsFiles =
|
|
60
|
+
const agentsFiles = findFabricAgentsFiles(target);
|
|
59
61
|
const nodes = {};
|
|
62
|
+
const bootstrapNode = createBootstrapNode(target, existingByFile.get("AGENTS.md")?.node);
|
|
63
|
+
if (bootstrapNode !== void 0) {
|
|
64
|
+
nodes.L0 = bootstrapNode;
|
|
65
|
+
}
|
|
60
66
|
for (const file of agentsFiles) {
|
|
61
67
|
const existing = existingByFile.get(file);
|
|
62
|
-
const id =
|
|
68
|
+
const id = deriveNodeId(file);
|
|
63
69
|
const hash = sha256(readFileSync(join(target, file), "utf8"));
|
|
64
70
|
const defaults = createDefaultNodeMeta(file);
|
|
65
71
|
nodes[id] = {
|
|
@@ -88,15 +94,18 @@ function readExistingMeta(metaPath) {
|
|
|
88
94
|
return void 0;
|
|
89
95
|
}
|
|
90
96
|
try {
|
|
91
|
-
return JSON.parse(readFileSync(metaPath, "utf8"));
|
|
97
|
+
return agentsMetaSchema.parse(JSON.parse(readFileSync(metaPath, "utf8")));
|
|
92
98
|
} catch {
|
|
93
99
|
return void 0;
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
|
-
function
|
|
97
|
-
const
|
|
102
|
+
function findFabricAgentsFiles(target) {
|
|
103
|
+
const agentsRoot = join(target, ".fabric", "agents");
|
|
104
|
+
if (!existsSync(agentsRoot) || !statSync(agentsRoot).isDirectory()) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
98
107
|
const files = [];
|
|
99
|
-
const stack = [
|
|
108
|
+
const stack = [agentsRoot];
|
|
100
109
|
while (stack.length > 0) {
|
|
101
110
|
const current = stack.pop();
|
|
102
111
|
if (current === void 0) {
|
|
@@ -105,31 +114,20 @@ function findAgentsFiles(target) {
|
|
|
105
114
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
106
115
|
const absolutePath = join(current, entry.name);
|
|
107
116
|
const relativePath = toPosixPath(relative(target, absolutePath));
|
|
108
|
-
if (shouldIgnore(relativePath, entry.isDirectory(), ignorePatterns)) {
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
117
|
if (entry.isDirectory()) {
|
|
112
118
|
stack.push(absolutePath);
|
|
113
|
-
} else if (entry.isFile() && entry.name
|
|
119
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
114
120
|
files.push(relativePath);
|
|
115
121
|
}
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
return files.sort();
|
|
119
125
|
}
|
|
120
|
-
function
|
|
121
|
-
return
|
|
126
|
+
function deriveLayer(relativePath) {
|
|
127
|
+
return deriveAgentsMetaLayer(relativePath);
|
|
122
128
|
}
|
|
123
|
-
function
|
|
124
|
-
|
|
125
|
-
if (normalizedPattern === "**/*.meta") {
|
|
126
|
-
return relativePath.endsWith(".meta");
|
|
127
|
-
}
|
|
128
|
-
if (normalizedPattern.endsWith("/**")) {
|
|
129
|
-
const directoryPrefix = normalizedPattern.slice(0, -3);
|
|
130
|
-
return relativePath === directoryPrefix || relativePath.startsWith(`${directoryPrefix}/`) || isDirectory && `${relativePath}/` === directoryPrefix;
|
|
131
|
-
}
|
|
132
|
-
return relativePath === normalizedPattern;
|
|
129
|
+
function deriveTopologyType(relativePath) {
|
|
130
|
+
return deriveAgentsMetaTopologyType(relativePath);
|
|
133
131
|
}
|
|
134
132
|
function indexExistingNodesByFile(existingMeta) {
|
|
135
133
|
const byFile = /* @__PURE__ */ new Map();
|
|
@@ -142,18 +140,57 @@ function deriveNodeId(file) {
|
|
|
142
140
|
if (file === "AGENTS.md") {
|
|
143
141
|
return "L0";
|
|
144
142
|
}
|
|
145
|
-
|
|
143
|
+
const layer = deriveLayer(file);
|
|
144
|
+
const relativeStem = getMirrorRelativeStem(file);
|
|
145
|
+
return `${layer}/${relativeStem}`;
|
|
146
146
|
}
|
|
147
147
|
function createDefaultNodeMeta(file) {
|
|
148
|
-
const
|
|
148
|
+
const layer = deriveLayer(file);
|
|
149
|
+
const topologyType = deriveTopologyType(file);
|
|
149
150
|
return {
|
|
150
151
|
file,
|
|
151
|
-
scope_glob:
|
|
152
|
-
deps:
|
|
153
|
-
priority:
|
|
152
|
+
scope_glob: deriveScopeGlob(file),
|
|
153
|
+
deps: layer === "L0" ? [] : ["L0"],
|
|
154
|
+
priority: layer === "L0" ? "high" : "medium",
|
|
155
|
+
layer,
|
|
156
|
+
topology_type: topologyType,
|
|
154
157
|
hash: ""
|
|
155
158
|
};
|
|
156
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
|
+
}
|
|
157
194
|
function sortNodes(nodes) {
|
|
158
195
|
return Object.fromEntries(Object.entries(nodes).sort(([left], [right]) => left.localeCompare(right)));
|
|
159
196
|
}
|
|
@@ -187,5 +224,7 @@ function sha256(content) {
|
|
|
187
224
|
export {
|
|
188
225
|
syncMetaCommand,
|
|
189
226
|
sync_meta_default,
|
|
190
|
-
computeAgentsMeta
|
|
227
|
+
computeAgentsMeta,
|
|
228
|
+
deriveLayer,
|
|
229
|
+
deriveTopologyType
|
|
191
230
|
};
|
|
@@ -4,9 +4,6 @@ import {
|
|
|
4
4
|
readFabricConfig,
|
|
5
5
|
resolveDevMode
|
|
6
6
|
} from "./chunk-AEOYCVBG.js";
|
|
7
|
-
import {
|
|
8
|
-
resolveIgnores
|
|
9
|
-
} from "./chunk-P4KVFB2T.js";
|
|
10
7
|
import {
|
|
11
8
|
displayWidth,
|
|
12
9
|
padEnd,
|
|
@@ -23,7 +20,24 @@ import { isAbsolute, join, relative, resolve, sep } from "path";
|
|
|
23
20
|
import { defineCommand } from "citty";
|
|
24
21
|
|
|
25
22
|
// src/scanner/detector.ts
|
|
26
|
-
import { detectFramework } from "@fenglimg/fabric-shared";
|
|
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
|
+
}
|
|
27
41
|
|
|
28
42
|
// src/commands/scan.ts
|
|
29
43
|
function createScanReport(targetInput = process.cwd(), fabricConfig) {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolveDevMode
|
|
4
|
+
} from "./chunk-AEOYCVBG.js";
|
|
5
|
+
import {
|
|
6
|
+
padEnd,
|
|
7
|
+
paint,
|
|
8
|
+
symbol
|
|
9
|
+
} from "./chunk-WWNXR34K.js";
|
|
10
|
+
import {
|
|
11
|
+
t
|
|
12
|
+
} from "./chunk-6ICJICVU.js";
|
|
13
|
+
|
|
14
|
+
// src/commands/doctor.ts
|
|
15
|
+
import { defineCommand } from "citty";
|
|
16
|
+
import { runDoctorAuditReport, runDoctorReport } from "@fenglimg/fabric-server";
|
|
17
|
+
var DEFAULT_AUDIT_WINDOW_MINUTES = 5;
|
|
18
|
+
var doctorCommand = defineCommand({
|
|
19
|
+
meta: {
|
|
20
|
+
name: "doctor",
|
|
21
|
+
description: t("cli.doctor.description")
|
|
22
|
+
},
|
|
23
|
+
args: {
|
|
24
|
+
target: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: t("cli.doctor.args.target.description")
|
|
27
|
+
},
|
|
28
|
+
audit: {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
description: t("cli.doctor.args.audit.description"),
|
|
31
|
+
default: false
|
|
32
|
+
},
|
|
33
|
+
"window-minutes": {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: t("cli.doctor.args.window-minutes.description"),
|
|
36
|
+
default: String(DEFAULT_AUDIT_WINDOW_MINUTES)
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async run({ args }) {
|
|
40
|
+
const workspaceRoot = process.cwd();
|
|
41
|
+
const resolution = resolveDevMode(args.target, workspaceRoot);
|
|
42
|
+
const report = await runDoctorReport(resolution.target);
|
|
43
|
+
writeStdout(`${renderStatus(report.status)} ${paint.ai("fab doctor")} ${paint.human(resolution.target)}`);
|
|
44
|
+
for (const check of report.checks) {
|
|
45
|
+
writeStdout(`${renderStatus(check.status)} ${check.name}: ${check.message}`);
|
|
46
|
+
}
|
|
47
|
+
if (!args.audit) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const auditReport = await runDoctorAuditReport(resolution.target, {
|
|
51
|
+
force: true,
|
|
52
|
+
windowMs: parseWindowMinutes(args["window-minutes"])
|
|
53
|
+
});
|
|
54
|
+
if (auditReport.mode === "off") {
|
|
55
|
+
writeStderr(t("cli.doctor.audit.preview-only"));
|
|
56
|
+
}
|
|
57
|
+
if (auditReport.checkedPathCount === 0) {
|
|
58
|
+
writeStderr(t("cli.doctor.audit.none"));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (auditReport.violationCount === 0) {
|
|
62
|
+
writeStderr(
|
|
63
|
+
`${symbol.ok} ${t("cli.doctor.audit.clean", {
|
|
64
|
+
count: String(auditReport.checkedPathCount),
|
|
65
|
+
window: formatDuration(auditReport.windowMs)
|
|
66
|
+
})}`
|
|
67
|
+
);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const writer = auditReport.mode === "strict" ? console.error : console.warn;
|
|
71
|
+
writer(
|
|
72
|
+
t("cli.doctor.audit.violations", {
|
|
73
|
+
count: String(auditReport.violationCount),
|
|
74
|
+
window: formatDuration(auditReport.windowMs)
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
writeStderr(
|
|
78
|
+
`${padEnd(t("cli.doctor.audit.table.path"), 32)} ${padEnd(t("cli.doctor.audit.table.edit"), 22)} ${padEnd(t("cli.doctor.audit.table.rules"), 22)} ${t("cli.doctor.audit.table.intent")}`
|
|
79
|
+
);
|
|
80
|
+
for (const violation of auditReport.violations) {
|
|
81
|
+
writeStderr(
|
|
82
|
+
`${padEnd(violation.path, 32)} ${padEnd(new Date(violation.editTs).toISOString(), 22)} ${padEnd(formatRulesTs(violation.lastGetRulesTs), 22)} ${violation.intent}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (auditReport.mode === "strict") {
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
var doctor_default = doctorCommand;
|
|
91
|
+
function renderStatus(status) {
|
|
92
|
+
if (status === "ok") {
|
|
93
|
+
return symbol.ok;
|
|
94
|
+
}
|
|
95
|
+
if (status === "warn") {
|
|
96
|
+
return symbol.warn;
|
|
97
|
+
}
|
|
98
|
+
return symbol.error;
|
|
99
|
+
}
|
|
100
|
+
function parseWindowMinutes(value) {
|
|
101
|
+
const minutes = Number.parseInt(value ?? String(DEFAULT_AUDIT_WINDOW_MINUTES), 10);
|
|
102
|
+
if (!Number.isInteger(minutes) || minutes < 1) {
|
|
103
|
+
throw new Error(t("cli.doctor.errors.invalid-window", { value: value ?? "<unset>" }));
|
|
104
|
+
}
|
|
105
|
+
return minutes * 60 * 1e3;
|
|
106
|
+
}
|
|
107
|
+
function formatDuration(durationMs) {
|
|
108
|
+
const minutes = Math.max(Math.floor(durationMs / (60 * 1e3)), 1);
|
|
109
|
+
return `${minutes}m`;
|
|
110
|
+
}
|
|
111
|
+
function formatRulesTs(value) {
|
|
112
|
+
return value === null ? t("cli.shared.none") : new Date(value).toISOString();
|
|
113
|
+
}
|
|
114
|
+
function writeStdout(message) {
|
|
115
|
+
process.stdout.write(`${message}
|
|
116
|
+
`);
|
|
117
|
+
}
|
|
118
|
+
function writeStderr(message) {
|
|
119
|
+
process.stderr.write(`${message}
|
|
120
|
+
`);
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
doctor_default as default,
|
|
124
|
+
doctorCommand
|
|
125
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -12,22 +12,23 @@ import { defineCommand, runMain } from "citty";
|
|
|
12
12
|
// src/commands/index.ts
|
|
13
13
|
var allCommands = {
|
|
14
14
|
bootstrap: () => import("./bootstrap-PMIA4W6G.js").then((module) => module.default),
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
doctor: () => import("./doctor-QTSG2RWF.js").then((module) => module.default),
|
|
16
|
+
init: () => import("./init-R73E5YTG.js").then((module) => module.default),
|
|
17
|
+
scan: () => import("./scan-JBGFRB7P.js").then((module) => module.default),
|
|
17
18
|
serve: () => import("./serve-4J2CQY25.js").then((module) => module.default),
|
|
18
|
-
"sync-meta": () => import("./sync-meta-
|
|
19
|
+
"sync-meta": () => import("./sync-meta-THZSEM7Y.js").then((module) => module.default),
|
|
19
20
|
"human-lint": () => import("./human-lint-YSFOZHZ7.js").then((module) => module.default),
|
|
20
21
|
"ledger-append": () => import("./ledger-append-XZ5SX4O5.js").then((module) => module.default),
|
|
21
22
|
hooks: () => import("./hooks-5S5IRVQE.js").then((module) => module.default),
|
|
22
23
|
config: () => import("./config-PXEEXWLM.js").then((module) => module.configCmd),
|
|
23
|
-
"pre-commit": () => import("./pre-commit-
|
|
24
|
+
"pre-commit": () => import("./pre-commit-BLSUMT3P.js").then((module) => module.default)
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
// src/index.ts
|
|
27
28
|
var main = defineCommand({
|
|
28
29
|
meta: {
|
|
29
30
|
name: "fab",
|
|
30
|
-
version: "1.
|
|
31
|
+
version: "1.1.0",
|
|
31
32
|
description: t("cli.main.description")
|
|
32
33
|
},
|
|
33
34
|
subCommands: allCommands
|