@fenglimg/fabric-cli 1.1.0 → 1.3.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 +24 -0
- package/dist/bootstrap-IUL4SAAK.js +14 -0
- package/dist/{bootstrap-PMIA4W6G.js → chunk-RUQCZA2Q.js} +110 -33
- package/dist/{config-PXEEXWLM.js → chunk-TO5RUB4R.js} +53 -17
- package/dist/chunk-YDZJRLHL.js +155 -0
- package/dist/config-MKWKDE32.js +16 -0
- package/dist/hooks-ZSWVH2JD.js +12 -0
- package/dist/index.js +18 -14
- package/dist/{init-R73E5YTG.js → init-BZ73IUHH.js} +277 -58
- package/dist/pre-commit-AK55G73F.js +97 -0
- package/dist/{scan-JBGFRB7P.js → scan-WKDSKEBB.js} +2 -2
- package/dist/update-JZPUJ36D.js +116 -0
- package/package.json +6 -4
- package/dist/hooks-5S5IRVQE.js +0 -124
- package/dist/pre-commit-BLSUMT3P.js +0 -59
- package/dist/{chunk-JWUO6TIS.js → chunk-N4DCTOXW.js} +5 -5
- package/dist/{doctor-QTSG2RWF.js → doctor-5KJGOV2P.js} +3 -3
- package/dist/{serve-4J2CQY25.js → serve-MMN4GYLM.js} +4 -4
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
installBootstrap
|
|
4
|
+
} from "./chunk-RUQCZA2Q.js";
|
|
2
5
|
import {
|
|
3
6
|
createScanReport,
|
|
4
7
|
detectFramework
|
|
5
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-N4DCTOXW.js";
|
|
6
9
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
installMcpClients
|
|
11
|
+
} from "./chunk-TO5RUB4R.js";
|
|
12
|
+
import "./chunk-VMYPJPKV.js";
|
|
13
|
+
import {
|
|
14
|
+
installHooks
|
|
15
|
+
} from "./chunk-YDZJRLHL.js";
|
|
10
16
|
import {
|
|
11
17
|
paint
|
|
12
18
|
} from "./chunk-WWNXR34K.js";
|
|
19
|
+
import {
|
|
20
|
+
createDebugLogger,
|
|
21
|
+
resolveDevMode
|
|
22
|
+
} from "./chunk-AEOYCVBG.js";
|
|
13
23
|
import {
|
|
14
24
|
t
|
|
15
25
|
} from "./chunk-6ICJICVU.js";
|
|
16
26
|
|
|
17
27
|
// src/commands/init.ts
|
|
18
28
|
import { createHash } from "crypto";
|
|
19
|
-
import
|
|
29
|
+
import * as childProcess from "child_process";
|
|
30
|
+
import { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, rmSync, statSync as statSync2, writeFileSync } from "fs";
|
|
20
31
|
import { dirname, isAbsolute as isAbsolute2, join as join2, parse, resolve as resolve2 } from "path";
|
|
21
32
|
import { fileURLToPath } from "url";
|
|
22
33
|
import { defineCommand } from "citty";
|
|
@@ -841,7 +852,7 @@ function readProjectName(target) {
|
|
|
841
852
|
return basename(target);
|
|
842
853
|
}
|
|
843
854
|
function getCliVersion() {
|
|
844
|
-
return true ? "1.
|
|
855
|
+
return true ? "1.3.0" : "unknown";
|
|
845
856
|
}
|
|
846
857
|
function sortRecord(record) {
|
|
847
858
|
return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
|
|
@@ -859,6 +870,8 @@ var AGENTS_TEMPLATE_BY_FRAMEWORK = {
|
|
|
859
870
|
var CLAUDE_INIT_SKILL_TEMPLATE = "templates/claude-skills/agents-md-init/SKILL.md";
|
|
860
871
|
var CLAUDE_INIT_REMINDER_HOOK_TEMPLATE = "templates/claude-hooks/agents-md-init-reminder.cjs";
|
|
861
872
|
var CLAUDE_INIT_REMINDER_COMMAND = ".claude/hooks/agents-md-init-reminder.cjs";
|
|
873
|
+
var LOCAL_FABRIC_SERVER_PATH = join2("node_modules", "@fenglimg", "fabric-server", "dist", "index.js");
|
|
874
|
+
var FABRIC_SERVER_PACKAGE = "@fenglimg/fabric-server";
|
|
862
875
|
var initCommand = defineCommand({
|
|
863
876
|
meta: {
|
|
864
877
|
name: "init",
|
|
@@ -873,44 +886,148 @@ var initCommand = defineCommand({
|
|
|
873
886
|
type: "boolean",
|
|
874
887
|
description: t("cli.init.args.debug.description"),
|
|
875
888
|
default: false
|
|
889
|
+
},
|
|
890
|
+
force: {
|
|
891
|
+
type: "boolean",
|
|
892
|
+
description: t("cli.init.args.force.description"),
|
|
893
|
+
default: false
|
|
894
|
+
},
|
|
895
|
+
bootstrap: {
|
|
896
|
+
type: "boolean",
|
|
897
|
+
default: true,
|
|
898
|
+
negativeDescription: t("cli.init.args.no-bootstrap.description")
|
|
899
|
+
},
|
|
900
|
+
mcp: {
|
|
901
|
+
type: "boolean",
|
|
902
|
+
default: true,
|
|
903
|
+
negativeDescription: t("cli.init.args.no-mcp.description")
|
|
904
|
+
},
|
|
905
|
+
hooks: {
|
|
906
|
+
type: "boolean",
|
|
907
|
+
default: true,
|
|
908
|
+
negativeDescription: t("cli.init.args.no-hooks.description")
|
|
909
|
+
},
|
|
910
|
+
"mcp-install": {
|
|
911
|
+
type: "string",
|
|
912
|
+
default: "global",
|
|
913
|
+
description: t("cli.init.mcp.install.prompt")
|
|
876
914
|
}
|
|
877
915
|
},
|
|
878
916
|
async run({ args }) {
|
|
879
917
|
const logger = createDebugLogger(args.debug);
|
|
880
918
|
const resolution = resolveDevMode(args.target, process.cwd());
|
|
881
919
|
const target = normalizeTarget2(resolution.target);
|
|
920
|
+
const mcpInstallMode = resolveMcpInstallMode(args["mcp-install"]);
|
|
921
|
+
const options = {
|
|
922
|
+
force: args.force,
|
|
923
|
+
skipBootstrap: args.bootstrap === false ? true : args.skipBootstrap,
|
|
924
|
+
skipMcp: args.mcp === false ? true : args.skipMcp,
|
|
925
|
+
skipHooks: args.hooks === false ? true : args.skipHooks
|
|
926
|
+
};
|
|
882
927
|
logger(`init target source: ${resolution.source}`);
|
|
883
928
|
for (const step of resolution.chain) {
|
|
884
929
|
logger(step);
|
|
885
930
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
console.log(
|
|
931
|
+
if (options.force) {
|
|
932
|
+
writeStderr(t("cli.init.force.warning", { path: target }));
|
|
933
|
+
}
|
|
934
|
+
const created = initFabric(target, options);
|
|
935
|
+
console.log(formatInitPathAction(created.agentsPath, created.agentsAction));
|
|
936
|
+
console.log(formatInitPathAction(created.metaPath, created.metaAction));
|
|
937
|
+
console.log(formatInitPathAction(created.humanLockPath, created.humanLockAction));
|
|
938
|
+
console.log(formatInitPathAction(created.forensicPath, created.forensicAction));
|
|
891
939
|
writeStderr(
|
|
892
|
-
|
|
940
|
+
formatOptionalInitPathAction(created.claudeSkillPath, created.claudeSkillAction)
|
|
893
941
|
);
|
|
894
942
|
writeStderr(
|
|
895
|
-
|
|
943
|
+
formatOptionalInitPathAction(created.claudeHookPath, created.claudeHookAction)
|
|
896
944
|
);
|
|
897
945
|
writeStderr(formatClaudeSettingsAction(created.claudeSettingsPath, created.claudeSettingsAction));
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
946
|
+
const stageResults = [];
|
|
947
|
+
if (options.skipBootstrap) {
|
|
948
|
+
stageResults.push({ name: "bootstrap", disposition: "skipped" });
|
|
949
|
+
} else {
|
|
950
|
+
console.log(formatInitStageHeader(t("cli.init.stages.bootstrap")));
|
|
951
|
+
try {
|
|
952
|
+
const result = await installBootstrap(target, { force: options.force });
|
|
953
|
+
if (result.details.length === 0) {
|
|
954
|
+
console.log(formatInitStageResult("bootstrap", "skipped", 0, 0, t("cli.bootstrap.install.no-targets")));
|
|
955
|
+
stageResults.push({ name: "bootstrap", disposition: "skipped" });
|
|
956
|
+
} else {
|
|
957
|
+
console.log(
|
|
958
|
+
formatInitStageResult("bootstrap", "completed", result.installed.length, result.skipped.length)
|
|
959
|
+
);
|
|
960
|
+
stageResults.push({ name: "bootstrap", disposition: "ran" });
|
|
961
|
+
}
|
|
962
|
+
} catch (error) {
|
|
963
|
+
writeStderr(formatInitStageFailure("bootstrap", error));
|
|
964
|
+
stageResults.push({ name: "bootstrap", disposition: "failed" });
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
if (options.skipMcp) {
|
|
968
|
+
stageResults.push({ name: "mcp", disposition: "skipped" });
|
|
969
|
+
} else {
|
|
970
|
+
console.log(formatInitStageHeader(t("cli.init.stages.mcp")));
|
|
971
|
+
try {
|
|
972
|
+
let localServerPath;
|
|
973
|
+
if (mcpInstallMode === "local") {
|
|
974
|
+
const manager = detectPackageManager(target);
|
|
975
|
+
writeStderr(t("cli.init.mcp.install.local"));
|
|
976
|
+
writeStderr(t("cli.init.mcp.local.installing", { manager }));
|
|
977
|
+
installLocalFabricServer(target, manager);
|
|
978
|
+
writeStderr(t("cli.init.mcp.local.installed"));
|
|
979
|
+
localServerPath = LOCAL_FABRIC_SERVER_PATH;
|
|
980
|
+
} else {
|
|
981
|
+
writeStderr(t("cli.init.mcp.install.global"));
|
|
982
|
+
}
|
|
983
|
+
const result = await installMcpClients(target, {
|
|
984
|
+
force: options.force,
|
|
985
|
+
localServerPath
|
|
986
|
+
});
|
|
987
|
+
if (result.details.length === 0) {
|
|
988
|
+
console.log(formatInitStageResult("mcp", "skipped", 0, 0, t("cli.config.install.no-configs")));
|
|
989
|
+
stageResults.push({ name: "mcp", disposition: "skipped" });
|
|
990
|
+
} else {
|
|
991
|
+
console.log(formatInitStageResult("mcp", "completed", result.installed.length, result.skipped.length));
|
|
992
|
+
stageResults.push({ name: "mcp", disposition: "ran" });
|
|
993
|
+
}
|
|
994
|
+
} catch (error) {
|
|
995
|
+
writeStderr(formatInitStageFailure("mcp", error));
|
|
996
|
+
stageResults.push({ name: "mcp", disposition: "failed" });
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
if (options.skipHooks) {
|
|
1000
|
+
stageResults.push({ name: "hooks", disposition: "skipped" });
|
|
1001
|
+
} else {
|
|
1002
|
+
console.log(formatInitStageHeader(t("cli.init.stages.hooks")));
|
|
1003
|
+
try {
|
|
1004
|
+
const result = await installHooks(target, { force: options.force });
|
|
1005
|
+
console.log(formatInitStageResult("hooks", "completed", result.installed.length, result.skipped.length));
|
|
1006
|
+
stageResults.push({ name: "hooks", disposition: "ran" });
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
writeStderr(formatInitStageFailure("hooks", error));
|
|
1009
|
+
stageResults.push({ name: "hooks", disposition: "failed" });
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if (shouldPrintHooksNextStep(options, stageResults)) {
|
|
1013
|
+
console.log(
|
|
1014
|
+
t("cli.init.next-step", {
|
|
1015
|
+
label: nextLabel(),
|
|
1016
|
+
message: paint.muted(t("cli.init.next-step.message"))
|
|
1017
|
+
})
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
904
1020
|
console.log(
|
|
905
1021
|
t("cli.init.reason-message", {
|
|
906
1022
|
label: reasonLabel(),
|
|
907
1023
|
message: paint.muted(t("cli.init.reason-message.body"))
|
|
908
1024
|
})
|
|
909
1025
|
);
|
|
1026
|
+
printInitStageSummary(stageResults);
|
|
910
1027
|
}
|
|
911
1028
|
});
|
|
912
1029
|
var init_default = initCommand;
|
|
913
|
-
function initFabric(target) {
|
|
1030
|
+
function initFabric(target, options) {
|
|
914
1031
|
assertExistingDirectory2(target);
|
|
915
1032
|
const agentsPath = join2(target, "AGENTS.md");
|
|
916
1033
|
const fabricDir = join2(target, ".fabric");
|
|
@@ -918,15 +1035,10 @@ function initFabric(target) {
|
|
|
918
1035
|
const claudeSkillPath = join2(target, ".claude", "skills", "agents-md-init", "SKILL.md");
|
|
919
1036
|
const claudeHookPath = join2(target, ".claude", "hooks", "agents-md-init-reminder.cjs");
|
|
920
1037
|
const claudeSettingsPath = join2(target, ".claude", "settings.json");
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
throw new Error(`ABORT: ${agentsPath} already exists. fab init is non-destructive.`);
|
|
926
|
-
}
|
|
927
|
-
if (existsSync2(fabricDir)) {
|
|
928
|
-
throw new Error(`ABORT: ${fabricDir} already exists. fab init is non-destructive.`);
|
|
929
|
-
}
|
|
1038
|
+
const forensicGuardAction = prepareFreshPath(forensicPath, options);
|
|
1039
|
+
const agentsAction = prepareFreshPath(agentsPath, options);
|
|
1040
|
+
const fabricDirAction = prepareFreshPath(fabricDir, options);
|
|
1041
|
+
const forensicAction = forensicGuardAction === "overwritten" || fabricDirAction === "overwritten" ? "overwritten" : "created";
|
|
930
1042
|
const scanReport = createScanReport(target);
|
|
931
1043
|
const forensicReport = buildForensicReport(target);
|
|
932
1044
|
const template = readFileSync2(findAgentsTemplatePath(scanReport.framework.kind), "utf8");
|
|
@@ -938,24 +1050,29 @@ function initFabric(target) {
|
|
|
938
1050
|
const metaPath = join2(fabricDir, "agents.meta.json");
|
|
939
1051
|
const humanLockPath = join2(fabricDir, "human-lock.json");
|
|
940
1052
|
mkdirSync(fabricDir, { recursive: false });
|
|
941
|
-
writeNewFile(agentsPath, agentsContent);
|
|
1053
|
+
writeNewFile(agentsPath, agentsContent, options);
|
|
942
1054
|
writeNewFile(metaPath, `${JSON.stringify(meta, null, 2)}
|
|
943
|
-
|
|
1055
|
+
`, options);
|
|
944
1056
|
writeNewFile(humanLockPath, humanLockTemplate.endsWith("\n") ? humanLockTemplate : `${humanLockTemplate}
|
|
945
|
-
|
|
1057
|
+
`, options);
|
|
946
1058
|
writeNewFile(forensicPath, `${JSON.stringify(forensicReport, null, 2)}
|
|
947
|
-
|
|
948
|
-
const claudeSkillAction = copyTemplateIfMissing(findTemplatePath(CLAUDE_INIT_SKILL_TEMPLATE), claudeSkillPath);
|
|
1059
|
+
`, options);
|
|
1060
|
+
const claudeSkillAction = copyTemplateIfMissing(findTemplatePath(CLAUDE_INIT_SKILL_TEMPLATE), claudeSkillPath, options);
|
|
949
1061
|
const claudeHookAction = copyExecutableTemplateIfMissing(
|
|
950
1062
|
findTemplatePath(CLAUDE_INIT_REMINDER_HOOK_TEMPLATE),
|
|
951
|
-
claudeHookPath
|
|
1063
|
+
claudeHookPath,
|
|
1064
|
+
options
|
|
952
1065
|
);
|
|
953
|
-
const claudeSettingsAction = mergeClaudeStopHook(claudeSettingsPath);
|
|
1066
|
+
const claudeSettingsAction = mergeClaudeStopHook(claudeSettingsPath, options);
|
|
954
1067
|
return {
|
|
955
1068
|
agentsPath,
|
|
1069
|
+
agentsAction,
|
|
956
1070
|
metaPath,
|
|
1071
|
+
metaAction: fabricDirAction,
|
|
957
1072
|
humanLockPath,
|
|
1073
|
+
humanLockAction: fabricDirAction,
|
|
958
1074
|
forensicPath,
|
|
1075
|
+
forensicAction,
|
|
959
1076
|
claudeSkillPath,
|
|
960
1077
|
claudeSkillAction,
|
|
961
1078
|
claudeHookPath,
|
|
@@ -976,6 +1093,34 @@ function assertExistingDirectory2(target) {
|
|
|
976
1093
|
throw new Error(`Target must be an existing directory: ${target}`);
|
|
977
1094
|
}
|
|
978
1095
|
}
|
|
1096
|
+
function detectPackageManager(cwd) {
|
|
1097
|
+
const workspaceRoot = resolve2(cwd);
|
|
1098
|
+
if (existsSync2(join2(workspaceRoot, "pnpm-lock.yaml"))) {
|
|
1099
|
+
return "pnpm";
|
|
1100
|
+
}
|
|
1101
|
+
if (existsSync2(join2(workspaceRoot, "yarn.lock"))) {
|
|
1102
|
+
return "yarn";
|
|
1103
|
+
}
|
|
1104
|
+
if (existsSync2(join2(workspaceRoot, "package-lock.json"))) {
|
|
1105
|
+
return "npm";
|
|
1106
|
+
}
|
|
1107
|
+
return "npm";
|
|
1108
|
+
}
|
|
1109
|
+
function resolveMcpInstallMode(rawMode) {
|
|
1110
|
+
if (rawMode === void 0 || rawMode === "global" || rawMode === "local") {
|
|
1111
|
+
return rawMode ?? "global";
|
|
1112
|
+
}
|
|
1113
|
+
writeStderr(t("cli.init.mcp.install.invalid", { value: rawMode }));
|
|
1114
|
+
return "global";
|
|
1115
|
+
}
|
|
1116
|
+
function installLocalFabricServer(target, manager) {
|
|
1117
|
+
const installArgs = manager === "npm" ? ["install", "-D", FABRIC_SERVER_PACKAGE] : ["add", "-D", FABRIC_SERVER_PACKAGE];
|
|
1118
|
+
childProcess.execFileSync(manager, installArgs, {
|
|
1119
|
+
cwd: target,
|
|
1120
|
+
stdio: "inherit",
|
|
1121
|
+
shell: process.platform === "win32"
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
979
1124
|
function createInitialMeta(agentsHash) {
|
|
980
1125
|
return {
|
|
981
1126
|
revision: sha256(agentsHash),
|
|
@@ -1030,28 +1175,41 @@ function templateCandidatesFrom(start, relativePath) {
|
|
|
1030
1175
|
}
|
|
1031
1176
|
return candidates.reverse();
|
|
1032
1177
|
}
|
|
1033
|
-
function
|
|
1034
|
-
if (existsSync2(path)) {
|
|
1035
|
-
|
|
1178
|
+
function prepareFreshPath(path, options) {
|
|
1179
|
+
if (!existsSync2(path)) {
|
|
1180
|
+
return "created";
|
|
1181
|
+
}
|
|
1182
|
+
if (!options?.force) {
|
|
1183
|
+
throw new Error(t("cli.init.errors.abort-existing", { path }));
|
|
1184
|
+
}
|
|
1185
|
+
rmSync(path, { recursive: true, force: true });
|
|
1186
|
+
return "overwritten";
|
|
1187
|
+
}
|
|
1188
|
+
function writeNewFile(path, content, options) {
|
|
1189
|
+
const existed = existsSync2(path);
|
|
1190
|
+
if (existed && !options?.force) {
|
|
1191
|
+
throw new Error(t("cli.init.errors.abort-existing", { path }));
|
|
1036
1192
|
}
|
|
1037
1193
|
writeFileSync(path, content, "utf8");
|
|
1194
|
+
return existed ? "overwritten" : "created";
|
|
1038
1195
|
}
|
|
1039
|
-
function copyTemplateIfMissing(templatePath, targetPath) {
|
|
1196
|
+
function copyTemplateIfMissing(templatePath, targetPath, options) {
|
|
1040
1197
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
1041
|
-
|
|
1198
|
+
const existed = existsSync2(targetPath);
|
|
1199
|
+
if (existed && !options?.force) {
|
|
1042
1200
|
return "skipped";
|
|
1043
1201
|
}
|
|
1044
1202
|
copyFileSync(templatePath, targetPath);
|
|
1045
|
-
return "created";
|
|
1203
|
+
return existed ? "overwritten" : "created";
|
|
1046
1204
|
}
|
|
1047
|
-
function copyExecutableTemplateIfMissing(templatePath, targetPath) {
|
|
1048
|
-
const action = copyTemplateIfMissing(templatePath, targetPath);
|
|
1049
|
-
if (action
|
|
1205
|
+
function copyExecutableTemplateIfMissing(templatePath, targetPath, options) {
|
|
1206
|
+
const action = copyTemplateIfMissing(templatePath, targetPath, options);
|
|
1207
|
+
if (action !== "skipped") {
|
|
1050
1208
|
chmodSync(targetPath, 493);
|
|
1051
1209
|
}
|
|
1052
1210
|
return action;
|
|
1053
1211
|
}
|
|
1054
|
-
function mergeClaudeStopHook(settingsPath) {
|
|
1212
|
+
function mergeClaudeStopHook(settingsPath, options) {
|
|
1055
1213
|
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
1056
1214
|
let settings;
|
|
1057
1215
|
let action = "updated";
|
|
@@ -1083,10 +1241,12 @@ function mergeClaudeStopHook(settingsPath) {
|
|
|
1083
1241
|
return "skipped-invalid";
|
|
1084
1242
|
}
|
|
1085
1243
|
const stopHooks = Array.isArray(stopHooksValue) ? stopHooksValue : [];
|
|
1086
|
-
|
|
1244
|
+
const hasExistingFabricHook = hasClaudeInitReminderHook(stopHooks);
|
|
1245
|
+
if (hasExistingFabricHook && !options?.force) {
|
|
1087
1246
|
return "skipped";
|
|
1088
1247
|
}
|
|
1089
|
-
stopHooks
|
|
1248
|
+
const nextStopHooks = hasExistingFabricHook && options?.force ? removeClaudeInitReminderHook(stopHooks) : [...stopHooks];
|
|
1249
|
+
nextStopHooks.push({
|
|
1090
1250
|
matcher: "*",
|
|
1091
1251
|
hooks: [
|
|
1092
1252
|
{
|
|
@@ -1097,20 +1257,24 @@ function mergeClaudeStopHook(settingsPath) {
|
|
|
1097
1257
|
});
|
|
1098
1258
|
settings.hooks = {
|
|
1099
1259
|
...hooks,
|
|
1100
|
-
Stop:
|
|
1260
|
+
Stop: nextStopHooks
|
|
1101
1261
|
};
|
|
1102
1262
|
writeJsonAtomically(settingsPath, settings);
|
|
1103
|
-
return action;
|
|
1263
|
+
return hasExistingFabricHook && options?.force ? "overwritten" : action;
|
|
1104
1264
|
}
|
|
1105
1265
|
function hasClaudeInitReminderHook(stopHooks) {
|
|
1106
|
-
return stopHooks.some((entry) =>
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1266
|
+
return stopHooks.some((entry) => isClaudeInitReminderStopEntry(entry));
|
|
1267
|
+
}
|
|
1268
|
+
function removeClaudeInitReminderHook(stopHooks) {
|
|
1269
|
+
return stopHooks.filter((entry) => !isClaudeInitReminderStopEntry(entry));
|
|
1270
|
+
}
|
|
1271
|
+
function isClaudeInitReminderStopEntry(entry) {
|
|
1272
|
+
if (!isRecord(entry) || !Array.isArray(entry.hooks)) {
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
return entry.hooks.some(
|
|
1276
|
+
(hook) => isRecord(hook) && hook.type === "command" && typeof hook.command === "string" && hook.command.includes("agents-md-init-reminder.cjs")
|
|
1277
|
+
);
|
|
1114
1278
|
}
|
|
1115
1279
|
function writeJsonAtomically(path, value) {
|
|
1116
1280
|
const tempPath = `${path}.${process.pid}.tmp`;
|
|
@@ -1127,6 +1291,8 @@ function formatClaudeSettingsAction(settingsPath, action) {
|
|
|
1127
1291
|
return t("cli.init.claude-settings.created", { label: createdLabel(), path: settingsPath });
|
|
1128
1292
|
case "updated":
|
|
1129
1293
|
return t("cli.init.claude-settings.updated", { label: updatedLabel(), path: settingsPath });
|
|
1294
|
+
case "overwritten":
|
|
1295
|
+
return t("cli.init.claude-settings.updated", { label: overwrittenLabel(), path: settingsPath });
|
|
1130
1296
|
case "skipped":
|
|
1131
1297
|
return t("cli.init.claude-settings.skipped", { label: skippedLabel(), path: settingsPath });
|
|
1132
1298
|
case "skipped-invalid":
|
|
@@ -1135,6 +1301,46 @@ function formatClaudeSettingsAction(settingsPath, action) {
|
|
|
1135
1301
|
return t("cli.init.claude-settings.updated", { label: updatedLabel(), path: settingsPath });
|
|
1136
1302
|
}
|
|
1137
1303
|
}
|
|
1304
|
+
function formatInitStageHeader(message) {
|
|
1305
|
+
return `${nextLabel()} ${paint.muted(message)}`;
|
|
1306
|
+
}
|
|
1307
|
+
function formatInitStageResult(stage, status, installedCount, skippedCount, note) {
|
|
1308
|
+
const label = status === "completed" ? completedStageLabel() : skippedStageLabel();
|
|
1309
|
+
const counts = `installed=${installedCount} skipped=${skippedCount}`;
|
|
1310
|
+
const suffix = note ? ` ${paint.muted(`(${note})`)}` : "";
|
|
1311
|
+
return `${label} ${stage}: ${counts}${suffix}`;
|
|
1312
|
+
}
|
|
1313
|
+
function formatInitStageFailure(stage, error) {
|
|
1314
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1315
|
+
return `${failedStageLabel()} ${stage}: ${message}`;
|
|
1316
|
+
}
|
|
1317
|
+
function printInitStageSummary(stageResults) {
|
|
1318
|
+
console.log(formatInitStageSummaryLine("ran", collectInitStageNames(stageResults, "ran")));
|
|
1319
|
+
console.log(formatInitStageSummaryLine("skipped", collectInitStageNames(stageResults, "skipped")));
|
|
1320
|
+
console.log(formatInitStageSummaryLine("failed", collectInitStageNames(stageResults, "failed")));
|
|
1321
|
+
}
|
|
1322
|
+
function formatInitStageSummaryLine(disposition, stages) {
|
|
1323
|
+
const label = disposition === "ran" ? paint.success(t("cli.init.stages.summary.ran")) : disposition === "skipped" ? paint.muted(t("cli.init.stages.summary.skipped")) : paint.error(t("cli.init.stages.summary.failed"));
|
|
1324
|
+
return `${label}: ${stages.length > 0 ? stages.join(", ") : t("cli.shared.none")}`;
|
|
1325
|
+
}
|
|
1326
|
+
function collectInitStageNames(stageResults, disposition) {
|
|
1327
|
+
return stageResults.filter((stage) => stage.disposition === disposition).map((stage) => stage.name);
|
|
1328
|
+
}
|
|
1329
|
+
function shouldPrintHooksNextStep(options, stageResults) {
|
|
1330
|
+
return Boolean(options.skipHooks) || stageResults.some((stage) => stage.name === "hooks" && stage.disposition === "failed");
|
|
1331
|
+
}
|
|
1332
|
+
function formatInitPathAction(path, action) {
|
|
1333
|
+
return t("cli.init.created-path", { label: labelForInitWriteAction(action), path });
|
|
1334
|
+
}
|
|
1335
|
+
function formatOptionalInitPathAction(path, action) {
|
|
1336
|
+
if (action === "skipped") {
|
|
1337
|
+
return t("cli.init.skipped-existing-path", { label: skippedLabel(), path });
|
|
1338
|
+
}
|
|
1339
|
+
return formatInitPathAction(path, action);
|
|
1340
|
+
}
|
|
1341
|
+
function labelForInitWriteAction(action) {
|
|
1342
|
+
return action === "overwritten" ? overwrittenLabel() : createdLabel();
|
|
1343
|
+
}
|
|
1138
1344
|
function createdLabel() {
|
|
1139
1345
|
return paint.success(t("cli.shared.created"));
|
|
1140
1346
|
}
|
|
@@ -1150,6 +1356,18 @@ function reasonLabel() {
|
|
|
1150
1356
|
function updatedLabel() {
|
|
1151
1357
|
return paint.success(t("cli.shared.updated"));
|
|
1152
1358
|
}
|
|
1359
|
+
function overwrittenLabel() {
|
|
1360
|
+
return paint.warn(t("cli.init.force.overwritten"));
|
|
1361
|
+
}
|
|
1362
|
+
function completedStageLabel() {
|
|
1363
|
+
return paint.success(t("cli.init.stages.completed"));
|
|
1364
|
+
}
|
|
1365
|
+
function skippedStageLabel() {
|
|
1366
|
+
return paint.muted(t("cli.init.stages.skipped"));
|
|
1367
|
+
}
|
|
1368
|
+
function failedStageLabel() {
|
|
1369
|
+
return paint.error(t("cli.init.stages.failed"));
|
|
1370
|
+
}
|
|
1153
1371
|
function writeStderr(message) {
|
|
1154
1372
|
process.stderr.write(`${message}
|
|
1155
1373
|
`);
|
|
@@ -1159,6 +1377,7 @@ function sha256(content) {
|
|
|
1159
1377
|
}
|
|
1160
1378
|
export {
|
|
1161
1379
|
init_default as default,
|
|
1380
|
+
detectPackageManager,
|
|
1162
1381
|
initCommand,
|
|
1163
1382
|
initFabric
|
|
1164
1383
|
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
sync_meta_default
|
|
4
|
+
} from "./chunk-6UUPKSDE.js";
|
|
5
|
+
import {
|
|
6
|
+
human_lint_default
|
|
7
|
+
} from "./chunk-L43IGJ6X.js";
|
|
8
|
+
import {
|
|
9
|
+
ledger_append_default
|
|
10
|
+
} from "./chunk-F2BXHPM5.js";
|
|
11
|
+
import "./chunk-WWNXR34K.js";
|
|
12
|
+
import {
|
|
13
|
+
resolveDevModeTarget
|
|
14
|
+
} from "./chunk-AEOYCVBG.js";
|
|
15
|
+
import {
|
|
16
|
+
t
|
|
17
|
+
} from "./chunk-6ICJICVU.js";
|
|
18
|
+
|
|
19
|
+
// src/commands/pre-commit.ts
|
|
20
|
+
import { execSync } from "child_process";
|
|
21
|
+
import { readFileSync } from "fs";
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
import process from "process";
|
|
24
|
+
import { agentsMetaSchema } from "@fenglimg/fabric-shared";
|
|
25
|
+
import { defineCommand } from "citty";
|
|
26
|
+
import { minimatch } from "minimatch";
|
|
27
|
+
async function runOrFail(name, cmd, args) {
|
|
28
|
+
try {
|
|
29
|
+
await cmd.run?.({ args });
|
|
30
|
+
} catch (err) {
|
|
31
|
+
process.stderr.write(
|
|
32
|
+
`${t("cli.pre-commit.run-failed", { name, message: err.message })}
|
|
33
|
+
`
|
|
34
|
+
);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function getStagedFiles(target) {
|
|
39
|
+
try {
|
|
40
|
+
const output = execSync("git diff --cached --name-only --no-renames", {
|
|
41
|
+
cwd: target,
|
|
42
|
+
encoding: "utf8",
|
|
43
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
44
|
+
});
|
|
45
|
+
return output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
46
|
+
} catch {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function tryReadAgentsMeta(target) {
|
|
51
|
+
const metaPath = join(target, ".fabric", "agents.meta.json");
|
|
52
|
+
try {
|
|
53
|
+
const raw = readFileSync(metaPath, "utf8");
|
|
54
|
+
return agentsMetaSchema.parse(JSON.parse(raw));
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function matchesFabricScope(stagedFiles, meta) {
|
|
60
|
+
const scopeGlobs = Object.values(meta.nodes).filter((node) => node.file !== "AGENTS.md").map((node) => node.scope_glob);
|
|
61
|
+
return stagedFiles.some(
|
|
62
|
+
(file) => file === "AGENTS.md" || file === ".fabric/agents.meta.json" || file === ".fabric/human-lock.json" || file === ".intent-ledger.jsonl" || scopeGlobs.some((pattern) => minimatch(file, pattern, { dot: true }))
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
var pre_commit_default = defineCommand({
|
|
66
|
+
meta: {
|
|
67
|
+
name: "pre-commit",
|
|
68
|
+
description: t("cli.pre-commit.description")
|
|
69
|
+
},
|
|
70
|
+
args: {
|
|
71
|
+
target: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: t("cli.pre-commit.args.target.description")
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
async run({ args }) {
|
|
77
|
+
const target = resolveDevModeTarget(args.target);
|
|
78
|
+
const stagedFiles = getStagedFiles(target);
|
|
79
|
+
const meta = tryReadAgentsMeta(target);
|
|
80
|
+
if (stagedFiles.length > 0 && meta !== null && !matchesFabricScope(stagedFiles, meta)) {
|
|
81
|
+
process.stderr.write("No fabric-managed files staged, skipping checks\n");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
await runOrFail("sync-meta --check-only", sync_meta_default, {
|
|
85
|
+
target,
|
|
86
|
+
"check-only": true
|
|
87
|
+
});
|
|
88
|
+
await runOrFail("human-lint", human_lint_default, { target });
|
|
89
|
+
await runOrFail("ledger-append --staged", ledger_append_default, {
|
|
90
|
+
target,
|
|
91
|
+
staged: true
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
export {
|
|
96
|
+
pre_commit_default as default
|
|
97
|
+
};
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
createScanReport,
|
|
4
4
|
scanCommand,
|
|
5
5
|
scan_default
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-AEOYCVBG.js";
|
|
6
|
+
} from "./chunk-N4DCTOXW.js";
|
|
8
7
|
import "./chunk-WWNXR34K.js";
|
|
8
|
+
import "./chunk-AEOYCVBG.js";
|
|
9
9
|
import "./chunk-6ICJICVU.js";
|
|
10
10
|
export {
|
|
11
11
|
createScanReport,
|