@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.
@@ -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-JWUO6TIS.js";
8
+ } from "./chunk-N4DCTOXW.js";
6
9
  import {
7
- createDebugLogger,
8
- resolveDevMode
9
- } from "./chunk-AEOYCVBG.js";
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 { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, renameSync, statSync as statSync2, writeFileSync } from "fs";
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.1.0" : "unknown";
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
- const created = initFabric(target);
887
- console.log(t("cli.init.created-path", { label: createdLabel(), path: created.agentsPath }));
888
- console.log(t("cli.init.created-path", { label: createdLabel(), path: created.metaPath }));
889
- console.log(t("cli.init.created-path", { label: createdLabel(), path: created.humanLockPath }));
890
- console.log(t("cli.init.created-path", { label: createdLabel(), path: created.forensicPath }));
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
- created.claudeSkillAction === "created" ? t("cli.init.created-path", { label: createdLabel(), path: created.claudeSkillPath }) : t("cli.init.skipped-existing-path", { label: skippedLabel(), path: created.claudeSkillPath })
940
+ formatOptionalInitPathAction(created.claudeSkillPath, created.claudeSkillAction)
893
941
  );
894
942
  writeStderr(
895
- created.claudeHookAction === "created" ? t("cli.init.created-path", { label: createdLabel(), path: created.claudeHookPath }) : t("cli.init.skipped-existing-path", { label: skippedLabel(), path: created.claudeHookPath })
943
+ formatOptionalInitPathAction(created.claudeHookPath, created.claudeHookAction)
896
944
  );
897
945
  writeStderr(formatClaudeSettingsAction(created.claudeSettingsPath, created.claudeSettingsAction));
898
- console.log(
899
- t("cli.init.next-step", {
900
- label: nextLabel(),
901
- message: paint.muted(t("cli.init.next-step.message"))
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
- if (existsSync2(forensicPath)) {
922
- throw new Error(`ABORT: ${forensicPath} already exists. fab init is non-destructive.`);
923
- }
924
- if (existsSync2(agentsPath)) {
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 writeNewFile(path, content) {
1034
- if (existsSync2(path)) {
1035
- throw new Error(`ABORT: ${path} already exists. fab init is non-destructive.`);
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
- if (existsSync2(targetPath)) {
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 === "created") {
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
- if (hasClaudeInitReminderHook(stopHooks)) {
1244
+ const hasExistingFabricHook = hasClaudeInitReminderHook(stopHooks);
1245
+ if (hasExistingFabricHook && !options?.force) {
1087
1246
  return "skipped";
1088
1247
  }
1089
- stopHooks.push({
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: stopHooks
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
- if (!isRecord(entry) || !Array.isArray(entry.hooks)) {
1108
- return false;
1109
- }
1110
- return entry.hooks.some(
1111
- (hook) => isRecord(hook) && hook.type === "command" && typeof hook.command === "string" && hook.command.includes("agents-md-init-reminder.cjs")
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-JWUO6TIS.js";
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,