@beastmode-develeap/beastmode 0.1.265 → 0.1.267

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/index.js CHANGED
@@ -95,17 +95,36 @@ var init_schemas = __esm({
95
95
  });
96
96
  DeployConfigSchema = z.object({
97
97
  target: z.string().default("pr-only"),
98
+ verify_port: z.number().int().default(3001),
98
99
  config: z.record(z.unknown()).default({})
99
100
  });
100
101
  ProjectConfigSchema = z.object({
101
102
  name: z.string(),
102
- repo: z.string().optional(),
103
103
  path: z.string(),
104
+ github: z.object({
105
+ repo: z.string().default(""),
106
+ default_branch: z.string().default("main")
107
+ }).default({}),
108
+ board: z.object({
109
+ id: z.number().nullable().default(null),
110
+ url: z.string().default("http://127.0.0.1:8080"),
111
+ auto_created: z.boolean().default(false)
112
+ }).default({}),
104
113
  stack: StackConfigSchema,
105
114
  deploy: DeployConfigSchema.default({}),
106
115
  pipeline: z.record(z.unknown()).default({}),
107
116
  models: z.record(z.string()).default({}),
108
- plugins: z.array(z.string()).default([])
117
+ plugins: z.array(z.string()).default([]),
118
+ slots: z.object({
119
+ max: z.number().nullable().default(null)
120
+ }).default({}),
121
+ infra: z.object({
122
+ credentials: z.object({
123
+ mode: z.string().default("factory")
124
+ }).default({}),
125
+ state_backend: z.string().default("auto")
126
+ }).default({}),
127
+ registered_at: z.string().default("")
109
128
  });
110
129
  FactoryIdentitySchema = z.object({
111
130
  factory_name: z.string(),
@@ -800,8 +819,8 @@ var init_export_adapter = __esm({
800
819
  });
801
820
 
802
821
  // src/engine/stack-detector.ts
803
- import { existsSync, readFileSync } from "fs";
804
- import { join } from "path";
822
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
823
+ import { join, relative, basename, extname } from "path";
805
824
  function readFileSafe(path) {
806
825
  try {
807
826
  return readFileSync(path, "utf-8");
@@ -875,11 +894,362 @@ function extractGitRemote(projectDir) {
875
894
  if (httpsMatch) return httpsMatch[1].replace(/\.git$/, "");
876
895
  return null;
877
896
  }
897
+ function isDirectory(path) {
898
+ try {
899
+ return statSync(path).isDirectory();
900
+ } catch {
901
+ return false;
902
+ }
903
+ }
904
+ function listDirs(parent) {
905
+ try {
906
+ return readdirSync(parent, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
907
+ } catch {
908
+ return [];
909
+ }
910
+ }
911
+ function hasAnyManifest(dir) {
912
+ return PACKAGE_MANIFESTS.some((m) => existsSync(join(dir, m)));
913
+ }
914
+ function expandGlob(rootDir, pattern) {
915
+ let pat = pattern.replace(/^\.\//, "");
916
+ pat = pat.replace(/\/+$/, "");
917
+ if (!pat) return [];
918
+ const parts = pat.split("/");
919
+ let candidates = [""];
920
+ for (const part of parts) {
921
+ const next = [];
922
+ for (const cand of candidates) {
923
+ const abs = cand ? join(rootDir, cand) : rootDir;
924
+ if (part === "*" || part === "**") {
925
+ const children = listDirs(abs).filter((name) => !IGNORE_DIRS.has(name));
926
+ for (const child of children) {
927
+ next.push(cand ? join(cand, child) : child);
928
+ }
929
+ } else if (part.includes("*")) {
930
+ const regex = new RegExp(
931
+ "^" + part.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$"
932
+ );
933
+ const children = listDirs(abs).filter((name) => regex.test(name));
934
+ for (const child of children) {
935
+ next.push(cand ? join(cand, child) : child);
936
+ }
937
+ } else {
938
+ const candidate = cand ? join(cand, part) : part;
939
+ if (isDirectory(join(rootDir, candidate))) {
940
+ next.push(candidate);
941
+ }
942
+ }
943
+ }
944
+ candidates = next;
945
+ }
946
+ return candidates.filter((c) => hasAnyManifest(join(rootDir, c)));
947
+ }
948
+ function parsePnpmWorkspaceYaml(content) {
949
+ const lines = content.split(/\r?\n/);
950
+ const globs = [];
951
+ let inPackages = false;
952
+ for (const raw of lines) {
953
+ const line = raw.replace(/\s+$/, "");
954
+ if (/^packages\s*:/.test(line)) {
955
+ inPackages = true;
956
+ continue;
957
+ }
958
+ if (inPackages) {
959
+ if (line && !/^\s/.test(line) && !line.startsWith("-")) {
960
+ inPackages = false;
961
+ continue;
962
+ }
963
+ const m = line.match(/^\s*-\s*['"]?([^'"#]+?)['"]?\s*$/);
964
+ if (m) {
965
+ const val = m[1].trim();
966
+ if (val) globs.push(val);
967
+ }
968
+ }
969
+ }
970
+ return globs;
971
+ }
972
+ function parseGoWorkUseBlock(content) {
973
+ const dirs = [];
974
+ const singleLineMatches = content.matchAll(/^\s*use\s+(\S+)\s*$/gm);
975
+ for (const m of singleLineMatches) {
976
+ dirs.push(m[1].replace(/^\.\//, ""));
977
+ }
978
+ const blockMatch = content.match(/use\s*\(([\s\S]*?)\)/);
979
+ if (blockMatch) {
980
+ for (const raw of blockMatch[1].split(/\r?\n/)) {
981
+ const line = raw.trim();
982
+ if (!line || line.startsWith("//")) continue;
983
+ const cleaned = line.replace(/\/\/.*$/, "").trim();
984
+ if (cleaned) dirs.push(cleaned.replace(/^\.\//, ""));
985
+ }
986
+ }
987
+ return dirs;
988
+ }
989
+ function parseCargoWorkspaceMembers(content) {
990
+ const wsIdx = content.search(/^\s*\[workspace\]/m);
991
+ if (wsIdx < 0) return [];
992
+ const rest = content.slice(wsIdx);
993
+ const m = rest.match(/members\s*=\s*\[([\s\S]*?)\]/);
994
+ if (!m) return [];
995
+ const inner = m[1];
996
+ const members = [];
997
+ for (const raw of inner.split(",")) {
998
+ const cleaned = raw.replace(/#.*$/, "").trim().replace(/^['"]|['"]$/g, "");
999
+ if (cleaned) members.push(cleaned);
1000
+ }
1001
+ return members;
1002
+ }
1003
+ function parseGradleInclude(content) {
1004
+ const modules = [];
1005
+ const re = /include\s*(?:\(\s*)?([^)\n]+?)(?:\s*\))?\s*$/gm;
1006
+ let match;
1007
+ while ((match = re.exec(content)) !== null) {
1008
+ const args = match[1];
1009
+ const stringMatches = args.matchAll(/['"]([^'"]+)['"]/g);
1010
+ for (const sm of stringMatches) {
1011
+ const raw = sm[1].trim();
1012
+ const path = raw.replace(/^:+/, "").replace(/:/g, "/");
1013
+ if (path) modules.push(path);
1014
+ }
1015
+ }
1016
+ return modules;
1017
+ }
1018
+ function parseMavenModules(content) {
1019
+ const modules = [];
1020
+ const re = /<module>\s*([^<\s]+)\s*<\/module>/g;
1021
+ let match;
1022
+ while ((match = re.exec(content)) !== null) {
1023
+ modules.push(match[1].trim());
1024
+ }
1025
+ return modules;
1026
+ }
1027
+ function detectWorkspaces(projectDir) {
1028
+ const pnpmYaml = readFileSafe(join(projectDir, "pnpm-workspace.yaml"));
1029
+ if (pnpmYaml !== null) {
1030
+ const globs = parsePnpmWorkspaceYaml(pnpmYaml);
1031
+ const packages = [];
1032
+ for (const g of globs) {
1033
+ packages.push(...expandGlob(projectDir, g));
1034
+ }
1035
+ return { type: "pnpm", packages: dedupe(packages) };
1036
+ }
1037
+ if (existsSync(join(projectDir, "nx.json"))) {
1038
+ const packages = [];
1039
+ for (const parent of ["packages", "apps", "libs"]) {
1040
+ const parentDir = join(projectDir, parent);
1041
+ if (!isDirectory(parentDir)) continue;
1042
+ for (const child of listDirs(parentDir)) {
1043
+ const childDir = join(parentDir, child);
1044
+ if (existsSync(join(childDir, "project.json")) || hasAnyManifest(childDir)) {
1045
+ packages.push(join(parent, child));
1046
+ }
1047
+ }
1048
+ }
1049
+ return { type: "nx", packages: dedupe(packages) };
1050
+ }
1051
+ const hasTurbo = existsSync(join(projectDir, "turbo.json"));
1052
+ const pkgContent = readFileSafe(join(projectDir, "package.json"));
1053
+ if (pkgContent) {
1054
+ const pkg = parseJsonSafe(pkgContent);
1055
+ if (pkg) {
1056
+ let workspaces;
1057
+ const ws = pkg.workspaces;
1058
+ if (Array.isArray(ws)) {
1059
+ workspaces = ws.filter((v) => typeof v === "string");
1060
+ } else if (ws && typeof ws === "object" && Array.isArray(ws.packages)) {
1061
+ workspaces = ws.packages.filter(
1062
+ (v) => typeof v === "string"
1063
+ );
1064
+ }
1065
+ if (workspaces && workspaces.length > 0) {
1066
+ const packages = [];
1067
+ for (const g of workspaces) {
1068
+ packages.push(...expandGlob(projectDir, g));
1069
+ }
1070
+ return {
1071
+ type: hasTurbo ? "turbo" : "npm",
1072
+ packages: dedupe(packages)
1073
+ };
1074
+ }
1075
+ }
1076
+ }
1077
+ const goWork = readFileSafe(join(projectDir, "go.work"));
1078
+ if (goWork !== null) {
1079
+ const dirs = parseGoWorkUseBlock(goWork);
1080
+ const packages = dirs.filter(
1081
+ (d) => existsSync(join(projectDir, d, "go.mod"))
1082
+ );
1083
+ return { type: "go", packages: dedupe(packages) };
1084
+ }
1085
+ const cargoToml = readFileSafe(join(projectDir, "Cargo.toml"));
1086
+ if (cargoToml !== null && /^\s*\[workspace\]/m.test(cargoToml)) {
1087
+ const members = parseCargoWorkspaceMembers(cargoToml);
1088
+ const packages = [];
1089
+ for (const m of members) {
1090
+ if (m.includes("*")) {
1091
+ packages.push(...expandGlob(projectDir, m));
1092
+ } else if (existsSync(join(projectDir, m, "Cargo.toml"))) {
1093
+ packages.push(m);
1094
+ }
1095
+ }
1096
+ return { type: "cargo", packages: dedupe(packages) };
1097
+ }
1098
+ const gradleSettings = readFileSafe(join(projectDir, "settings.gradle")) ?? readFileSafe(join(projectDir, "settings.gradle.kts"));
1099
+ if (gradleSettings !== null) {
1100
+ const modules = parseGradleInclude(gradleSettings);
1101
+ const packages = modules.filter((m) => isDirectory(join(projectDir, m)));
1102
+ if (packages.length > 0) {
1103
+ return { type: "gradle", packages: dedupe(packages) };
1104
+ }
1105
+ }
1106
+ const pom = readFileSafe(join(projectDir, "pom.xml"));
1107
+ if (pom !== null) {
1108
+ const modules = parseMavenModules(pom);
1109
+ const packages = modules.filter(
1110
+ (m) => existsSync(join(projectDir, m, "pom.xml"))
1111
+ );
1112
+ if (packages.length > 0) {
1113
+ return { type: "maven", packages: dedupe(packages) };
1114
+ }
1115
+ }
1116
+ return null;
1117
+ }
1118
+ function dedupe(items) {
1119
+ return Array.from(new Set(items));
1120
+ }
1121
+ function extractPackageName(packageDir) {
1122
+ const pkgContent = readFileSafe(join(packageDir, "package.json"));
1123
+ if (pkgContent) {
1124
+ const pkg = parseJsonSafe(pkgContent);
1125
+ if (pkg && typeof pkg.name === "string" && pkg.name) return pkg.name;
1126
+ }
1127
+ const cargoToml = readFileSafe(join(packageDir, "Cargo.toml"));
1128
+ if (cargoToml) {
1129
+ const m = cargoToml.match(/\[package\][\s\S]*?name\s*=\s*['"]([^'"]+)['"]/);
1130
+ if (m) return m[1];
1131
+ }
1132
+ const pom = readFileSafe(join(packageDir, "pom.xml"));
1133
+ if (pom) {
1134
+ const m = pom.match(/<artifactId>\s*([^<\s]+)\s*<\/artifactId>/);
1135
+ if (m) return m[1];
1136
+ }
1137
+ const goMod = readFileSafe(join(packageDir, "go.mod"));
1138
+ if (goMod) {
1139
+ const m = goMod.match(/^module\s+(\S+)/m);
1140
+ if (m) {
1141
+ const parts = m[1].split("/");
1142
+ return parts[parts.length - 1];
1143
+ }
1144
+ }
1145
+ return basename(packageDir);
1146
+ }
1147
+ function detectEntryPoints(packageDir, framework) {
1148
+ const entries = [];
1149
+ const pkgContent = readFileSafe(join(packageDir, "package.json"));
1150
+ if (pkgContent) {
1151
+ const pkg = parseJsonSafe(pkgContent);
1152
+ if (pkg) {
1153
+ if (typeof pkg.main === "string" && pkg.main) entries.push(pkg.main);
1154
+ if (typeof pkg.bin === "string" && pkg.bin) {
1155
+ entries.push(pkg.bin);
1156
+ } else if (pkg.bin && typeof pkg.bin === "object") {
1157
+ for (const v of Object.values(pkg.bin)) {
1158
+ if (typeof v === "string") entries.push(v);
1159
+ }
1160
+ }
1161
+ }
1162
+ }
1163
+ if (framework === "go") {
1164
+ const cmdDir = join(packageDir, "cmd");
1165
+ if (isDirectory(cmdDir)) {
1166
+ for (const sub of listDirs(cmdDir)) {
1167
+ entries.push(join("cmd", sub));
1168
+ }
1169
+ }
1170
+ }
1171
+ if (framework === "rust") {
1172
+ if (existsSync(join(packageDir, "src", "main.rs"))) {
1173
+ entries.push("src/main.rs");
1174
+ }
1175
+ }
1176
+ if (framework === "java-maven" || framework === "java-gradle") {
1177
+ if (isDirectory(join(packageDir, "src", "main", "java"))) {
1178
+ entries.push("src/main/java");
1179
+ }
1180
+ }
1181
+ return dedupe(entries);
1182
+ }
1183
+ function detectPackageStack(packageDir, rootDir) {
1184
+ const framework = detectFramework(packageDir);
1185
+ const preset = STACK_PRESETS[framework] || STACK_PRESETS.unknown;
1186
+ const pm = ["nextjs", "vite", "react", "node"].includes(framework) ? detectPackageManager(packageDir) : preset.language === "python" ? "pip" : "";
1187
+ const resolved = resolveCommands(preset, pm);
1188
+ const name = extractPackageName(packageDir);
1189
+ const entryPoints = detectEntryPoints(packageDir, framework);
1190
+ const relPath = relative(rootDir, packageDir) || ".";
1191
+ return {
1192
+ relative_path: relPath,
1193
+ name,
1194
+ language: resolved.language,
1195
+ framework,
1196
+ package_manager: pm,
1197
+ build_command: resolved.build,
1198
+ dev_command: resolved.dev,
1199
+ test_command: resolved.test,
1200
+ install_command: resolved.install,
1201
+ dev_port: resolved.port !== 0 ? resolved.port : void 0,
1202
+ entry_points: entryPoints
1203
+ };
1204
+ }
1205
+ function scanPrimaryLanguages(projectDir) {
1206
+ const counts = {};
1207
+ let total = 0;
1208
+ const MAX_DEPTH = 8;
1209
+ function walk(dir, depth) {
1210
+ if (depth > MAX_DEPTH) return;
1211
+ let entries;
1212
+ try {
1213
+ entries = readdirSync(dir, { withFileTypes: true });
1214
+ } catch {
1215
+ return;
1216
+ }
1217
+ for (const entry of entries) {
1218
+ if (entry.name.startsWith(".") && entry.name !== ".") {
1219
+ if (IGNORE_DIRS.has(entry.name)) continue;
1220
+ }
1221
+ if (IGNORE_DIRS.has(entry.name)) continue;
1222
+ const full = join(dir, entry.name);
1223
+ if (entry.isDirectory()) {
1224
+ walk(full, depth + 1);
1225
+ } else if (entry.isFile()) {
1226
+ const ext = extname(entry.name).toLowerCase();
1227
+ const lang = EXT_LANG_MAP[ext];
1228
+ if (lang) {
1229
+ counts[lang] = (counts[lang] || 0) + 1;
1230
+ total += 1;
1231
+ }
1232
+ }
1233
+ }
1234
+ }
1235
+ walk(projectDir, 0);
1236
+ if (total === 0) return [];
1237
+ const ranked = Object.entries(counts).map(([language, count]) => ({ language, loc_share: count / total })).sort((a, b) => b.loc_share - a.loc_share).slice(0, 3);
1238
+ return ranked;
1239
+ }
878
1240
  function detectStack(projectDir) {
879
1241
  const framework = detectFramework(projectDir);
880
1242
  const preset = STACK_PRESETS[framework] || STACK_PRESETS.unknown;
881
1243
  const pm = ["nextjs", "vite", "react", "node"].includes(framework) ? detectPackageManager(projectDir) : preset.language === "python" ? "pip" : "";
882
1244
  const resolved = resolveCommands(preset, pm);
1245
+ const workspace = detectWorkspaces(projectDir);
1246
+ const primaryLanguages = scanPrimaryLanguages(projectDir);
1247
+ let packages;
1248
+ if (workspace && workspace.packages.length > 0) {
1249
+ packages = workspace.packages.map(
1250
+ (pkgRelPath) => detectPackageStack(join(projectDir, pkgRelPath), projectDir)
1251
+ );
1252
+ }
883
1253
  return {
884
1254
  language: resolved.language,
885
1255
  framework,
@@ -896,13 +1266,54 @@ function detectStack(projectDir) {
896
1266
  dev_port: resolved.port,
897
1267
  suggested_plugins: resolved.plugins,
898
1268
  suggested_preset: resolved.preset,
899
- suggested_deploy: resolved.deploy
1269
+ suggested_deploy: resolved.deploy,
1270
+ is_monorepo: workspace !== null,
1271
+ total_packages: packages ? packages.length : 0,
1272
+ primary_languages: primaryLanguages,
1273
+ packages
900
1274
  };
901
1275
  }
902
- var STACK_PRESETS;
1276
+ var IGNORE_DIRS, EXT_LANG_MAP, PACKAGE_MANIFESTS, STACK_PRESETS;
903
1277
  var init_stack_detector = __esm({
904
1278
  "src/engine/stack-detector.ts"() {
905
1279
  "use strict";
1280
+ IGNORE_DIRS = /* @__PURE__ */ new Set([
1281
+ "node_modules",
1282
+ ".git",
1283
+ "vendor",
1284
+ "target",
1285
+ "dist",
1286
+ "build",
1287
+ "__pycache__",
1288
+ ".venv",
1289
+ ".next",
1290
+ ".turbo",
1291
+ "coverage"
1292
+ ]);
1293
+ EXT_LANG_MAP = {
1294
+ ".ts": "typescript",
1295
+ ".tsx": "typescript",
1296
+ ".js": "javascript",
1297
+ ".jsx": "javascript",
1298
+ ".py": "python",
1299
+ ".go": "go",
1300
+ ".rs": "rust",
1301
+ ".java": "java",
1302
+ ".kt": "kotlin",
1303
+ ".swift": "swift",
1304
+ ".rb": "ruby",
1305
+ ".cs": "csharp"
1306
+ };
1307
+ PACKAGE_MANIFESTS = [
1308
+ "package.json",
1309
+ "go.mod",
1310
+ "Cargo.toml",
1311
+ "pom.xml",
1312
+ "build.gradle",
1313
+ "build.gradle.kts",
1314
+ "pyproject.toml",
1315
+ "requirements.txt"
1316
+ ];
906
1317
  STACK_PRESETS = {
907
1318
  nextjs: {
908
1319
  language: "typescript",
@@ -2251,15 +2662,15 @@ var init_plugin_installer = __esm({
2251
2662
  // src/engine/skill-manager.ts
2252
2663
  import {
2253
2664
  existsSync as existsSync7,
2254
- readdirSync,
2665
+ readdirSync as readdirSync2,
2255
2666
  cpSync as cpSync2,
2256
2667
  rmSync as rmSync2,
2257
2668
  readFileSync as readFileSync7,
2258
2669
  writeFileSync as writeFileSync5,
2259
2670
  mkdirSync as mkdirSync5,
2260
- statSync
2671
+ statSync as statSync2
2261
2672
  } from "fs";
2262
- import { join as join7, basename } from "path";
2673
+ import { join as join7, basename as basename2 } from "path";
2263
2674
  function parseSkillFrontmatter(content) {
2264
2675
  const match = content.match(/^---\n([\s\S]*?)\n---/);
2265
2676
  if (!match) return {};
@@ -2278,7 +2689,7 @@ function addSkill(factoryDir, sourcePath) {
2278
2689
  if (!existsSync7(sourcePath)) {
2279
2690
  throw new Error(`Skill source path does not exist: ${sourcePath}`);
2280
2691
  }
2281
- const stat = statSync(sourcePath);
2692
+ const stat = statSync2(sourcePath);
2282
2693
  if (!stat.isDirectory()) {
2283
2694
  throw new Error(`Skill source must be a directory: ${sourcePath}`);
2284
2695
  }
@@ -2288,7 +2699,7 @@ function addSkill(factoryDir, sourcePath) {
2288
2699
  `SKILL.md not found in ${sourcePath}. Every skill must have a SKILL.md with frontmatter.`
2289
2700
  );
2290
2701
  }
2291
- const skillName = basename(sourcePath);
2702
+ const skillName = basename2(sourcePath);
2292
2703
  const destPath = join7(factoryDir, ".beastmode", "skills", skillName);
2293
2704
  if (existsSync7(destPath)) {
2294
2705
  throw new Error(
@@ -2335,7 +2746,7 @@ function listSkills(factoryDir) {
2335
2746
  const bmDir = join7(factoryDir, ".beastmode");
2336
2747
  const customSkillsDir = join7(bmDir, "skills");
2337
2748
  if (existsSync7(customSkillsDir)) {
2338
- for (const entry of readdirSync(customSkillsDir, { withFileTypes: true })) {
2749
+ for (const entry of readdirSync2(customSkillsDir, { withFileTypes: true })) {
2339
2750
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
2340
2751
  const skillMdPath = join7(customSkillsDir, entry.name, "SKILL.md");
2341
2752
  if (!existsSync7(skillMdPath)) continue;
@@ -2352,7 +2763,7 @@ function listSkills(factoryDir) {
2352
2763
  }
2353
2764
  const pluginsDir = join7(bmDir, "plugins");
2354
2765
  if (existsSync7(pluginsDir)) {
2355
- for (const pluginEntry of readdirSync(pluginsDir, {
2766
+ for (const pluginEntry of readdirSync2(pluginsDir, {
2356
2767
  withFileTypes: true
2357
2768
  })) {
2358
2769
  if (!pluginEntry.isDirectory() || pluginEntry.name.startsWith("."))
@@ -2363,7 +2774,7 @@ function listSkills(factoryDir) {
2363
2774
  "skills"
2364
2775
  );
2365
2776
  if (!existsSync7(pluginSkillsDir)) continue;
2366
- for (const skillEntry of readdirSync(pluginSkillsDir, {
2777
+ for (const skillEntry of readdirSync2(pluginSkillsDir, {
2367
2778
  withFileTypes: true
2368
2779
  })) {
2369
2780
  if (!skillEntry.isDirectory() || skillEntry.name.startsWith("."))
@@ -2462,7 +2873,10 @@ function mapDaemonToFactory(daemon) {
2462
2873
  const hasDeployConfig = Object.keys(deployConfig).length > 0;
2463
2874
  const projectRaw = {
2464
2875
  name: repoName,
2465
- repo: daemon.github?.project_repo || "",
2876
+ github: {
2877
+ repo: daemon.github?.project_repo || "",
2878
+ default_branch: daemon.github?.base_branch || "main"
2879
+ },
2466
2880
  path: daemon.repos?.project || "",
2467
2881
  stack: {
2468
2882
  detected: stack.name || "node",
@@ -2666,7 +3080,7 @@ function generateDaemonConfig(factoryConfig, projectConfig, factoryPath) {
2666
3080
  taskBackendValue = backendAdapter;
2667
3081
  }
2668
3082
  const boardUrl = process.env.BEASTMODE_BOARD_URL || taskBackend.config?.url || "http://127.0.0.1:8080";
2669
- const projectRepo = projectConfig?.repo || "";
3083
+ const projectRepo = projectConfig?.github?.repo || (projectConfig?.repo ?? "");
2670
3084
  const projectPath = projectConfig?.path || "";
2671
3085
  const projectOrg = projectRepo.split("/")[0] || "beastmode-agent";
2672
3086
  const stack = projectConfig?.stack;
@@ -2808,6 +3222,159 @@ var init_bridge = __esm({
2808
3222
  }
2809
3223
  });
2810
3224
 
3225
+ // src/engine/project-record.ts
3226
+ import { existsSync as existsSync8, writeFileSync as writeFileSync6, renameSync, readFileSync as readFileSync8, readdirSync as readdirSync3, mkdirSync as mkdirSync6 } from "fs";
3227
+ import { join as join8 } from "path";
3228
+ function detectShape(parsed) {
3229
+ if (parsed.github && typeof parsed.github === "object" && parsed.slots && typeof parsed.slots === "object") {
3230
+ return "canonical";
3231
+ }
3232
+ if (parsed.github && typeof parsed.github === "object") {
3233
+ return "cli-legacy";
3234
+ }
3235
+ return "http-legacy";
3236
+ }
3237
+ function normalizeToCanonical(parsed, name) {
3238
+ const shape = detectShape(parsed);
3239
+ if (shape === "canonical") {
3240
+ return parsed;
3241
+ }
3242
+ if (shape === "cli-legacy") {
3243
+ const github = parsed.github;
3244
+ return {
3245
+ name: parsed.name || name,
3246
+ path: parsed.path || "",
3247
+ github: {
3248
+ repo: github.repo || "",
3249
+ default_branch: github.default_branch || "main"
3250
+ },
3251
+ board: parsed.board || {
3252
+ id: null,
3253
+ url: "http://127.0.0.1:8080",
3254
+ auto_created: true
3255
+ },
3256
+ deploy: parsed.deploy || { verify_port: 3001 },
3257
+ pipeline: parsed.pipeline || {},
3258
+ models: parsed.models || {},
3259
+ slots: { max: null },
3260
+ registered_at: parsed.registered_at || (/* @__PURE__ */ new Date()).toISOString()
3261
+ };
3262
+ }
3263
+ return {
3264
+ name: parsed.name || name,
3265
+ path: parsed.path || "",
3266
+ github: {
3267
+ repo: parsed.repo || "",
3268
+ default_branch: "main"
3269
+ },
3270
+ board: { id: null, url: "http://127.0.0.1:8080", auto_created: true },
3271
+ deploy: { verify_port: 3001 },
3272
+ pipeline: parsed.pipeline || {},
3273
+ models: parsed.models || {},
3274
+ slots: { max: null },
3275
+ registered_at: (/* @__PURE__ */ new Date()).toISOString()
3276
+ };
3277
+ }
3278
+ function createProjectRecord(input) {
3279
+ return {
3280
+ name: input.name,
3281
+ path: input.resolvedPath,
3282
+ github: {
3283
+ repo: input.gitRemote || "",
3284
+ default_branch: "main"
3285
+ },
3286
+ board: {
3287
+ id: input.boardId ?? null,
3288
+ url: process.env.BEASTMODE_BOARD_URL || "http://127.0.0.1:8080",
3289
+ auto_created: !input.boardId
3290
+ },
3291
+ deploy: { verify_port: input.verifyPort ?? 3001 },
3292
+ pipeline: {},
3293
+ models: {},
3294
+ slots: { max: null },
3295
+ registered_at: (/* @__PURE__ */ new Date()).toISOString()
3296
+ };
3297
+ }
3298
+ function writeProjectRecord(projectsDir, name, record) {
3299
+ const dir = join8(projectsDir, name);
3300
+ mkdirSync6(dir, { recursive: true });
3301
+ const filePath = join8(dir, "project.json");
3302
+ const tmpPath = `${filePath}.tmp`;
3303
+ writeFileSync6(tmpPath, JSON.stringify(record, null, 2) + "\n");
3304
+ renameSync(tmpPath, filePath);
3305
+ const extPath = join8(dir, "extensions.json");
3306
+ if (!existsSync8(extPath)) {
3307
+ writeFileSync6(
3308
+ extPath,
3309
+ JSON.stringify(
3310
+ {
3311
+ plugins: { add: [], remove: [] },
3312
+ mcps: { add: {}, remove: [] },
3313
+ skills: { add: [], remove: [] }
3314
+ },
3315
+ null,
3316
+ 2
3317
+ ) + "\n"
3318
+ );
3319
+ }
3320
+ }
3321
+ function readProjectRecord(projectsDir, name) {
3322
+ const subdirPath = join8(projectsDir, name, "project.json");
3323
+ const flatPath = join8(projectsDir, `${name}.json`);
3324
+ let filePath;
3325
+ let isFlat = false;
3326
+ if (existsSync8(subdirPath)) {
3327
+ filePath = subdirPath;
3328
+ } else if (existsSync8(flatPath)) {
3329
+ filePath = flatPath;
3330
+ isFlat = true;
3331
+ } else {
3332
+ return null;
3333
+ }
3334
+ let parsed;
3335
+ try {
3336
+ parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
3337
+ } catch {
3338
+ return null;
3339
+ }
3340
+ const shape = detectShape(parsed);
3341
+ const record = normalizeToCanonical(parsed, name);
3342
+ if (shape !== "canonical" || isFlat) {
3343
+ writeProjectRecord(projectsDir, name, record);
3344
+ }
3345
+ return record;
3346
+ }
3347
+ function listProjectRecords(projectsDir) {
3348
+ if (!existsSync8(projectsDir)) return [];
3349
+ const seen = /* @__PURE__ */ new Set();
3350
+ const records = [];
3351
+ for (const entry of readdirSync3(projectsDir)) {
3352
+ if (entry.startsWith(".")) continue;
3353
+ if (existsSync8(join8(projectsDir, entry, "project.json"))) {
3354
+ if (!seen.has(entry)) {
3355
+ seen.add(entry);
3356
+ const record = readProjectRecord(projectsDir, entry);
3357
+ if (record) records.push(record);
3358
+ }
3359
+ continue;
3360
+ }
3361
+ if (entry.endsWith(".json")) {
3362
+ const name = entry.slice(0, -5);
3363
+ if (!seen.has(name)) {
3364
+ seen.add(name);
3365
+ const record = readProjectRecord(projectsDir, name);
3366
+ if (record) records.push(record);
3367
+ }
3368
+ }
3369
+ }
3370
+ return records;
3371
+ }
3372
+ var init_project_record = __esm({
3373
+ "src/engine/project-record.ts"() {
3374
+ "use strict";
3375
+ }
3376
+ });
3377
+
2811
3378
  // src/engine/index.ts
2812
3379
  var engine_exports = {};
2813
3380
  __export(engine_exports, {
@@ -2851,6 +3418,7 @@ __export(engine_exports, {
2851
3418
  configGet: () => configGet,
2852
3419
  configReset: () => configReset,
2853
3420
  configSet: () => configSet,
3421
+ createProjectRecord: () => createProjectRecord,
2854
3422
  createSkill: () => createSkill,
2855
3423
  detectStack: () => detectStack,
2856
3424
  exportMarkdown: () => exportMarkdown,
@@ -2870,6 +3438,7 @@ __export(engine_exports, {
2870
3438
  listHooks: () => listHooks,
2871
3439
  listMcps: () => listMcps,
2872
3440
  listPresets: () => listPresets,
3441
+ listProjectRecords: () => listProjectRecords,
2873
3442
  listProviders: () => listProviders,
2874
3443
  listSkills: () => listSkills,
2875
3444
  mapDaemonToFactory: () => mapDaemonToFactory,
@@ -2879,6 +3448,7 @@ __export(engine_exports, {
2879
3448
  parseMethodologyManifest: () => parseMethodologyManifest,
2880
3449
  parseTemplate: () => parseTemplate,
2881
3450
  performUpgrade: () => performUpgrade,
3451
+ readProjectRecord: () => readProjectRecord,
2882
3452
  removeHook: () => removeHook,
2883
3453
  removeMcp: () => removeMcp,
2884
3454
  removePlugin: () => removePlugin,
@@ -2895,7 +3465,8 @@ __export(engine_exports, {
2895
3465
  scaffoldFactory: () => scaffoldFactory,
2896
3466
  validateFactory: () => validateFactory,
2897
3467
  validateProvider: () => validateProvider,
2898
- validateSecrets: () => validateSecrets
3468
+ validateSecrets: () => validateSecrets,
3469
+ writeProjectRecord: () => writeProjectRecord
2899
3470
  });
2900
3471
  var init_engine = __esm({
2901
3472
  "src/engine/index.ts"() {
@@ -2925,25 +3496,26 @@ var init_engine = __esm({
2925
3496
  init_schemas();
2926
3497
  init_migrator();
2927
3498
  init_bridge();
3499
+ init_project_record();
2928
3500
  }
2929
3501
  });
2930
3502
 
2931
3503
  // src/cli/utils/file-writer.ts
2932
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, appendFileSync, existsSync as existsSync8 } from "fs";
3504
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, appendFileSync, existsSync as existsSync9 } from "fs";
2933
3505
  import { dirname as dirname3 } from "path";
2934
3506
  function executeFileActions(actions) {
2935
3507
  for (const action of actions) {
2936
3508
  const dir = dirname3(action.path);
2937
- mkdirSync6(dir, { recursive: true });
3509
+ mkdirSync7(dir, { recursive: true });
2938
3510
  switch (action.action) {
2939
3511
  case "create":
2940
- if (existsSync8(action.path)) {
3512
+ if (existsSync9(action.path)) {
2941
3513
  throw new Error(`File already exists: ${action.path}`);
2942
3514
  }
2943
- writeFileSync6(action.path, action.content, "utf-8");
3515
+ writeFileSync7(action.path, action.content, "utf-8");
2944
3516
  break;
2945
3517
  case "overwrite":
2946
- writeFileSync6(action.path, action.content, "utf-8");
3518
+ writeFileSync7(action.path, action.content, "utf-8");
2947
3519
  break;
2948
3520
  case "append":
2949
3521
  appendFileSync(action.path, action.content, "utf-8");
@@ -2988,7 +3560,7 @@ var init_display = __esm({
2988
3560
 
2989
3561
  // src/cli/ui/api-routes.ts
2990
3562
  import { resolve as resolve3 } from "path";
2991
- import { existsSync as existsSync10, writeFileSync as writeFileSync7 } from "fs";
3563
+ import { existsSync as existsSync11, writeFileSync as writeFileSync8 } from "fs";
2992
3564
  function getRoutes() {
2993
3565
  return [
2994
3566
  {
@@ -3003,7 +3575,7 @@ function getRoutes() {
3003
3575
  const { path: projectPath } = body;
3004
3576
  if (!projectPath) throw new Error("Missing required field: path");
3005
3577
  const resolved = resolve3(projectPath);
3006
- if (!existsSync10(resolved)) throw new Error(`Directory not found: ${resolved}`);
3578
+ if (!existsSync11(resolved)) throw new Error(`Directory not found: ${resolved}`);
3007
3579
  return detectStack(resolved);
3008
3580
  }
3009
3581
  },
@@ -3061,7 +3633,7 @@ function getRoutes() {
3061
3633
  if (!name) throw new Error("Missing required field: name");
3062
3634
  if (!config) throw new Error("Missing required field: config");
3063
3635
  if (!project) throw new Error("Missing required field: project");
3064
- if (existsSync10(name) && existsSync10(resolve3(name, ".beastmode"))) {
3636
+ if (existsSync11(name) && existsSync11(resolve3(name, ".beastmode"))) {
3065
3637
  throw new Error(`Factory already exists at ./${name}. Use 'beastmode config' to modify.`);
3066
3638
  }
3067
3639
  const resolvedConfig = resolveDefaults(config, project.stack);
@@ -3078,7 +3650,7 @@ function getRoutes() {
3078
3650
  if (secretLines.length > 0) {
3079
3651
  const secretsPath = resolve3(name, ".beastmode", "secrets.env.local");
3080
3652
  const secretsContent = "# BeastMode secrets \u2014 DO NOT COMMIT\n" + secretLines.join("\n") + "\n";
3081
- writeFileSync7(secretsPath, secretsContent, "utf-8");
3653
+ writeFileSync8(secretsPath, secretsContent, "utf-8");
3082
3654
  }
3083
3655
  }
3084
3656
  const fileList = actions.filter((a) => !a.path.endsWith(".gitkeep")).map((a) => a.path.replace(`${name}/`, ""));
@@ -3118,52 +3690,52 @@ var init_api_routes = __esm({
3118
3690
  });
3119
3691
 
3120
3692
  // src/cli/ui/archival.ts
3121
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync2, renameSync, readFileSync as readFileSync8, statSync as statSync2, writeFileSync as writeFileSync8, unlinkSync } from "fs";
3122
- import { join as join9 } from "path";
3693
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync4, renameSync as renameSync2, readFileSync as readFileSync9, statSync as statSync3, writeFileSync as writeFileSync9, unlinkSync } from "fs";
3694
+ import { join as join10 } from "path";
3123
3695
  function archiveOldRuns(runsDir, archiveAfterDays) {
3124
- if (archiveAfterDays <= 0 || !existsSync11(runsDir)) return { archived: 0 };
3125
- const archiveDir = join9(runsDir, ".archive");
3696
+ if (archiveAfterDays <= 0 || !existsSync12(runsDir)) return { archived: 0 };
3697
+ const archiveDir = join10(runsDir, ".archive");
3126
3698
  const cutoff = Date.now() - archiveAfterDays * 24 * 60 * 60 * 1e3;
3127
3699
  let archived = 0;
3128
- for (const entry of readdirSync2(runsDir)) {
3700
+ for (const entry of readdirSync4(runsDir)) {
3129
3701
  if (entry.startsWith(".") || !entry.startsWith("run-")) continue;
3130
- const runDir = join9(runsDir, entry);
3131
- if (existsSync11(join9(runDir, "pinned"))) continue;
3132
- const cpPath = join9(runDir, "checkpoint.json");
3133
- if (!existsSync11(cpPath)) continue;
3702
+ const runDir = join10(runsDir, entry);
3703
+ if (existsSync12(join10(runDir, "pinned"))) continue;
3704
+ const cpPath = join10(runDir, "checkpoint.json");
3705
+ if (!existsSync12(cpPath)) continue;
3134
3706
  try {
3135
- const cp = JSON.parse(readFileSync8(cpPath, "utf-8"));
3707
+ const cp = JSON.parse(readFileSync9(cpPath, "utf-8"));
3136
3708
  const stage = (cp.current_stage || "").toLowerCase();
3137
3709
  if (stage !== "done" && stage !== "ship") continue;
3138
3710
  } catch {
3139
3711
  continue;
3140
3712
  }
3141
3713
  try {
3142
- const mtime = statSync2(cpPath).mtimeMs;
3714
+ const mtime = statSync3(cpPath).mtimeMs;
3143
3715
  if (mtime > cutoff) continue;
3144
3716
  } catch {
3145
3717
  continue;
3146
3718
  }
3147
- mkdirSync7(archiveDir, { recursive: true });
3148
- renameSync(runDir, join9(archiveDir, entry));
3719
+ mkdirSync8(archiveDir, { recursive: true });
3720
+ renameSync2(runDir, join10(archiveDir, entry));
3149
3721
  archived++;
3150
3722
  }
3151
3723
  return { archived };
3152
3724
  }
3153
3725
  function pinRun(runsDir, runId) {
3154
- const runDir = join9(runsDir, runId);
3155
- if (!existsSync11(runDir)) return false;
3156
- writeFileSync8(join9(runDir, "pinned"), (/* @__PURE__ */ new Date()).toISOString());
3726
+ const runDir = join10(runsDir, runId);
3727
+ if (!existsSync12(runDir)) return false;
3728
+ writeFileSync9(join10(runDir, "pinned"), (/* @__PURE__ */ new Date()).toISOString());
3157
3729
  return true;
3158
3730
  }
3159
3731
  function unpinRun(runsDir, runId) {
3160
- const pinFile = join9(runsDir, runId, "pinned");
3161
- if (!existsSync11(pinFile)) return false;
3732
+ const pinFile = join10(runsDir, runId, "pinned");
3733
+ if (!existsSync12(pinFile)) return false;
3162
3734
  unlinkSync(pinFile);
3163
3735
  return true;
3164
3736
  }
3165
3737
  function isRunPinned(runsDir, runId) {
3166
- return existsSync11(join9(runsDir, runId, "pinned"));
3738
+ return existsSync12(join10(runsDir, runId, "pinned"));
3167
3739
  }
3168
3740
  var init_archival = __esm({
3169
3741
  "src/cli/ui/archival.ts"() {
@@ -3188,37 +3760,37 @@ __export(inception_exports, {
3188
3760
  saveArtifact: () => saveArtifact,
3189
3761
  saveInceptionState: () => saveInceptionState
3190
3762
  });
3191
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9, readFileSync as readFileSync9, readdirSync as readdirSync3, unlinkSync as unlinkSync2 } from "fs";
3192
- import { join as join10, dirname as dirname4 } from "path";
3763
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync10, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
3764
+ import { join as join11, dirname as dirname4 } from "path";
3193
3765
  import { fileURLToPath } from "url";
3194
3766
  function getMethodologiesDir() {
3195
3767
  const candidates = [
3196
- join10(dirname4(fileURLToPath(import.meta.url)), "methodologies"),
3197
- join10(dirname4(fileURLToPath(import.meta.url)), "..", "methodologies"),
3198
- join10(process.cwd(), "src", "cli", "ui", "methodologies"),
3199
- join10(process.cwd(), "dist", "methodologies"),
3200
- join10(process.cwd(), "cli", "src", "cli", "ui", "methodologies"),
3201
- join10(process.cwd(), "cli", "dist", "methodologies")
3768
+ join11(dirname4(fileURLToPath(import.meta.url)), "methodologies"),
3769
+ join11(dirname4(fileURLToPath(import.meta.url)), "..", "methodologies"),
3770
+ join11(process.cwd(), "src", "cli", "ui", "methodologies"),
3771
+ join11(process.cwd(), "dist", "methodologies"),
3772
+ join11(process.cwd(), "cli", "src", "cli", "ui", "methodologies"),
3773
+ join11(process.cwd(), "cli", "dist", "methodologies")
3202
3774
  ];
3203
3775
  for (const dir of candidates) {
3204
- if (existsSync12(dir)) return dir;
3776
+ if (existsSync13(dir)) return dir;
3205
3777
  }
3206
3778
  return candidates[0];
3207
3779
  }
3208
3780
  function getMethodology(id) {
3209
3781
  const dir = getMethodologiesDir();
3210
- const filePath = join10(dir, `${id.replace(/_/g, "-")}.json`);
3211
- if (!existsSync12(filePath)) {
3782
+ const filePath = join11(dir, `${id.replace(/_/g, "-")}.json`);
3783
+ if (!existsSync13(filePath)) {
3212
3784
  throw new Error(`Methodology not found: ${id}`);
3213
3785
  }
3214
- return JSON.parse(readFileSync9(filePath, "utf-8"));
3786
+ return JSON.parse(readFileSync10(filePath, "utf-8"));
3215
3787
  }
3216
3788
  function listMethodologies() {
3217
3789
  const dir = getMethodologiesDir();
3218
- if (!existsSync12(dir)) return [];
3219
- return readdirSync3(dir).filter((f) => f.endsWith(".json")).map((f) => {
3790
+ if (!existsSync13(dir)) return [];
3791
+ return readdirSync5(dir).filter((f) => f.endsWith(".json")).map((f) => {
3220
3792
  try {
3221
- const m = JSON.parse(readFileSync9(join10(dir, f), "utf-8"));
3793
+ const m = JSON.parse(readFileSync10(join11(dir, f), "utf-8"));
3222
3794
  return { id: m.id, name: m.name, description: m.description };
3223
3795
  } catch {
3224
3796
  return null;
@@ -3226,12 +3798,12 @@ function listMethodologies() {
3226
3798
  }).filter(Boolean);
3227
3799
  }
3228
3800
  function getProductDir(factoryDir, productName) {
3229
- return join10(factoryDir, ".beastmode", "products", productName);
3801
+ return join11(factoryDir, ".beastmode", "products", productName);
3230
3802
  }
3231
3803
  function createInceptionState(factoryDir, opts) {
3232
3804
  const methodology = getMethodology(opts.methodology);
3233
3805
  const productDir = getProductDir(factoryDir, opts.productName);
3234
- mkdirSync8(productDir, { recursive: true });
3806
+ mkdirSync9(productDir, { recursive: true });
3235
3807
  const state = {
3236
3808
  productName: opts.productName,
3237
3809
  idea: opts.idea,
@@ -3248,21 +3820,21 @@ function createInceptionState(factoryDir, opts) {
3248
3820
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3249
3821
  projectName: null
3250
3822
  };
3251
- writeFileSync9(join10(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
3823
+ writeFileSync10(join11(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
3252
3824
  return state;
3253
3825
  }
3254
3826
  function loadInception(factoryDir, productName) {
3255
- const filePath = join10(getProductDir(factoryDir, productName), "inception.json");
3256
- if (!existsSync12(filePath)) return null;
3827
+ const filePath = join11(getProductDir(factoryDir, productName), "inception.json");
3828
+ if (!existsSync13(filePath)) return null;
3257
3829
  try {
3258
- return JSON.parse(readFileSync9(filePath, "utf-8"));
3830
+ return JSON.parse(readFileSync10(filePath, "utf-8"));
3259
3831
  } catch {
3260
3832
  return null;
3261
3833
  }
3262
3834
  }
3263
3835
  function saveInceptionState(factoryDir, state) {
3264
3836
  const productDir = getProductDir(factoryDir, state.productName);
3265
- writeFileSync9(join10(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
3837
+ writeFileSync10(join11(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
3266
3838
  }
3267
3839
  function advancePhase(factoryDir, productName) {
3268
3840
  const state = loadInception(factoryDir, productName);
@@ -3282,28 +3854,28 @@ function advancePhase(factoryDir, productName) {
3282
3854
  }
3283
3855
  function saveArtifact(factoryDir, productName, filename, content) {
3284
3856
  const productDir = getProductDir(factoryDir, productName);
3285
- mkdirSync8(productDir, { recursive: true });
3286
- const filePath = join10(productDir, filename);
3287
- mkdirSync8(dirname4(filePath), { recursive: true });
3288
- writeFileSync9(filePath, content);
3857
+ mkdirSync9(productDir, { recursive: true });
3858
+ const filePath = join11(productDir, filename);
3859
+ mkdirSync9(dirname4(filePath), { recursive: true });
3860
+ writeFileSync10(filePath, content);
3289
3861
  }
3290
3862
  function loadArtifact(factoryDir, productName, filename) {
3291
- const filePath = join10(getProductDir(factoryDir, productName), filename);
3292
- if (!existsSync12(filePath)) return null;
3293
- return readFileSync9(filePath, "utf-8");
3863
+ const filePath = join11(getProductDir(factoryDir, productName), filename);
3864
+ if (!existsSync13(filePath)) return null;
3865
+ return readFileSync10(filePath, "utf-8");
3294
3866
  }
3295
3867
  function migrateInceptionToSession(factoryDir, productName) {
3296
- const productDir = join10(factoryDir, ".beastmode", "products", productName);
3297
- const oldInception = join10(productDir, "inception.json");
3298
- if (!existsSync12(oldInception)) return false;
3299
- const sessionsDir = join10(productDir, "sessions");
3300
- if (existsSync12(sessionsDir) && readdirSync3(sessionsDir).some((d) => d.startsWith("session-"))) {
3868
+ const productDir = join11(factoryDir, ".beastmode", "products", productName);
3869
+ const oldInception = join11(productDir, "inception.json");
3870
+ if (!existsSync13(oldInception)) return false;
3871
+ const sessionsDir = join11(productDir, "sessions");
3872
+ if (existsSync13(sessionsDir) && readdirSync5(sessionsDir).some((d) => d.startsWith("session-"))) {
3301
3873
  return false;
3302
3874
  }
3303
- const sessionDir = join10(sessionsDir, "session-001");
3304
- mkdirSync8(sessionDir, { recursive: true });
3305
- const content = readFileSync9(oldInception, "utf-8");
3306
- writeFileSync9(join10(sessionDir, "inception.json"), content);
3875
+ const sessionDir = join11(sessionsDir, "session-001");
3876
+ mkdirSync9(sessionDir, { recursive: true });
3877
+ const content = readFileSync10(oldInception, "utf-8");
3878
+ writeFileSync10(join11(sessionDir, "inception.json"), content);
3307
3879
  const artifactFiles = [
3308
3880
  "prd.md",
3309
3881
  "architecture.md",
@@ -3316,9 +3888,9 @@ function migrateInceptionToSession(factoryDir, productName) {
3316
3888
  "project-plan.md"
3317
3889
  ];
3318
3890
  for (const file of artifactFiles) {
3319
- const src = join10(productDir, file);
3320
- if (existsSync12(src)) {
3321
- writeFileSync9(join10(sessionDir, file), readFileSync9(src, "utf-8"));
3891
+ const src = join11(productDir, file);
3892
+ if (existsSync13(src)) {
3893
+ writeFileSync10(join11(sessionDir, file), readFileSync10(src, "utf-8"));
3322
3894
  unlinkSync2(src);
3323
3895
  }
3324
3896
  }
@@ -3326,11 +3898,11 @@ function migrateInceptionToSession(factoryDir, productName) {
3326
3898
  return true;
3327
3899
  }
3328
3900
  function listProducts(factoryDir) {
3329
- const productsDir = join10(factoryDir, ".beastmode", "products");
3330
- if (!existsSync12(productsDir)) return [];
3331
- return readdirSync3(productsDir).filter((d) => existsSync12(join10(productsDir, d, "inception.json"))).map((d) => {
3901
+ const productsDir = join11(factoryDir, ".beastmode", "products");
3902
+ if (!existsSync13(productsDir)) return [];
3903
+ return readdirSync5(productsDir).filter((d) => existsSync13(join11(productsDir, d, "inception.json"))).map((d) => {
3332
3904
  try {
3333
- const state = JSON.parse(readFileSync9(join10(productsDir, d, "inception.json"), "utf-8"));
3905
+ const state = JSON.parse(readFileSync10(join11(productsDir, d, "inception.json"), "utf-8"));
3334
3906
  return {
3335
3907
  name: state.productName || d,
3336
3908
  methodology: state.methodology || "unknown",
@@ -3453,18 +4025,18 @@ __export(strategy_exports, {
3453
4025
  saveSessionArtifact: () => saveSessionArtifact,
3454
4026
  saveStrategySession: () => saveStrategySession
3455
4027
  });
3456
- import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync3 } from "fs";
3457
- import { join as join11 } from "path";
4028
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11, readFileSync as readFileSync11, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
4029
+ import { join as join12 } from "path";
3458
4030
  function getProductDir2(factoryDir, projectName) {
3459
- return join11(factoryDir, ".beastmode", "products", projectName);
4031
+ return join12(factoryDir, ".beastmode", "products", projectName);
3460
4032
  }
3461
4033
  function getSessionsDir(factoryDir, projectName) {
3462
- return join11(getProductDir2(factoryDir, projectName), "sessions");
4034
+ return join12(getProductDir2(factoryDir, projectName), "sessions");
3463
4035
  }
3464
4036
  function nextSessionId(factoryDir, projectName) {
3465
4037
  const sessionsDir = getSessionsDir(factoryDir, projectName);
3466
- if (!existsSync13(sessionsDir)) return "session-001";
3467
- const existing = readdirSync4(sessionsDir).filter((d) => d.startsWith("session-")).sort();
4038
+ if (!existsSync14(sessionsDir)) return "session-001";
4039
+ const existing = readdirSync6(sessionsDir).filter((d) => d.startsWith("session-")).sort();
3468
4040
  if (existing.length === 0) return "session-001";
3469
4041
  const lastNum = parseInt(existing[existing.length - 1].replace("session-", ""), 10);
3470
4042
  return `session-${String(lastNum + 1).padStart(3, "0")}`;
@@ -3472,8 +4044,8 @@ function nextSessionId(factoryDir, projectName) {
3472
4044
  function createStrategySession(factoryDir, projectName, opts) {
3473
4045
  const methodology = getMethodology(opts.methodology);
3474
4046
  const sessionId = nextSessionId(factoryDir, projectName);
3475
- const sessionDir = join11(getSessionsDir(factoryDir, projectName), sessionId);
3476
- mkdirSync9(sessionDir, { recursive: true });
4047
+ const sessionDir = join12(getSessionsDir(factoryDir, projectName), sessionId);
4048
+ mkdirSync10(sessionDir, { recursive: true });
3477
4049
  const session = {
3478
4050
  sessionId,
3479
4051
  name: opts.name,
@@ -3494,100 +4066,100 @@ function createStrategySession(factoryDir, projectName, opts) {
3494
4066
  sessionType: opts.sessionType || "free-form",
3495
4067
  approach: opts.approach || "auto"
3496
4068
  };
3497
- writeFileSync10(join11(sessionDir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
4069
+ writeFileSync11(join12(sessionDir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
3498
4070
  return session;
3499
4071
  }
3500
4072
  function listStrategySessions(factoryDir, projectName) {
3501
4073
  const sessionsDir = getSessionsDir(factoryDir, projectName);
3502
- if (!existsSync13(sessionsDir)) return [];
3503
- return readdirSync4(sessionsDir).filter((d) => d.startsWith("session-")).sort().map((d) => {
3504
- const file = join11(sessionsDir, d, "inception.json");
3505
- if (!existsSync13(file)) return null;
4074
+ if (!existsSync14(sessionsDir)) return [];
4075
+ return readdirSync6(sessionsDir).filter((d) => d.startsWith("session-")).sort().map((d) => {
4076
+ const file = join12(sessionsDir, d, "inception.json");
4077
+ if (!existsSync14(file)) return null;
3506
4078
  try {
3507
- return JSON.parse(readFileSync10(file, "utf-8"));
4079
+ return JSON.parse(readFileSync11(file, "utf-8"));
3508
4080
  } catch {
3509
4081
  return null;
3510
4082
  }
3511
4083
  }).filter(Boolean);
3512
4084
  }
3513
4085
  function loadStrategySession(factoryDir, projectName, sessionId) {
3514
- const file = join11(getSessionsDir(factoryDir, projectName), sessionId, "inception.json");
3515
- if (!existsSync13(file)) return null;
4086
+ const file = join12(getSessionsDir(factoryDir, projectName), sessionId, "inception.json");
4087
+ if (!existsSync14(file)) return null;
3516
4088
  try {
3517
- return JSON.parse(readFileSync10(file, "utf-8"));
4089
+ return JSON.parse(readFileSync11(file, "utf-8"));
3518
4090
  } catch {
3519
4091
  return null;
3520
4092
  }
3521
4093
  }
3522
4094
  function saveStrategySession(factoryDir, projectName, sessionId, session) {
3523
- const dir = join11(getSessionsDir(factoryDir, projectName), sessionId);
3524
- mkdirSync9(dir, { recursive: true });
4095
+ const dir = join12(getSessionsDir(factoryDir, projectName), sessionId);
4096
+ mkdirSync10(dir, { recursive: true });
3525
4097
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3526
- writeFileSync10(join11(dir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
4098
+ writeFileSync11(join12(dir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
3527
4099
  }
3528
4100
  function saveSessionArtifact(factoryDir, projectName, sessionId, filename, content) {
3529
- const dir = join11(getSessionsDir(factoryDir, projectName), sessionId);
3530
- mkdirSync9(dir, { recursive: true });
3531
- writeFileSync10(join11(dir, filename), content);
4101
+ const dir = join12(getSessionsDir(factoryDir, projectName), sessionId);
4102
+ mkdirSync10(dir, { recursive: true });
4103
+ writeFileSync11(join12(dir, filename), content);
3532
4104
  }
3533
4105
  function loadSessionArtifact(factoryDir, projectName, sessionId, filename) {
3534
- const file = join11(getSessionsDir(factoryDir, projectName), sessionId, filename);
3535
- if (!existsSync13(file)) return null;
3536
- return readFileSync10(file, "utf-8");
4106
+ const file = join12(getSessionsDir(factoryDir, projectName), sessionId, filename);
4107
+ if (!existsSync14(file)) return null;
4108
+ return readFileSync11(file, "utf-8");
3537
4109
  }
3538
4110
  function buildArtifactIndex(factoryDir, projectName) {
3539
4111
  const artifacts = [];
3540
- const brownfieldPath = join11(factoryDir, ".beastmode", "projects", projectName, "brownfield.md");
3541
- if (existsSync13(brownfieldPath)) {
3542
- const content = readFileSync10(brownfieldPath, "utf-8");
4112
+ const brownfieldPath = join12(factoryDir, ".beastmode", "projects", projectName, "brownfield.md");
4113
+ if (existsSync14(brownfieldPath)) {
4114
+ const content = readFileSync11(brownfieldPath, "utf-8");
3543
4115
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#")) || "";
3544
4116
  artifacts.push({
3545
4117
  type: "brownfield",
3546
4118
  path: brownfieldPath,
3547
4119
  summary: firstLine.slice(0, 200),
3548
- date: statSync3(brownfieldPath).mtime.toISOString()
4120
+ date: statSync4(brownfieldPath).mtime.toISOString()
3549
4121
  });
3550
4122
  }
3551
4123
  const sessions = listStrategySessions(factoryDir, projectName);
3552
4124
  for (const session of sessions) {
3553
4125
  artifacts.push({
3554
4126
  type: "strategy_session",
3555
- path: join11(getSessionsDir(factoryDir, projectName), session.sessionId),
4127
+ path: join12(getSessionsDir(factoryDir, projectName), session.sessionId),
3556
4128
  summary: `${session.name} (${session.methodology}) \u2014 ${session.status}`,
3557
4129
  date: session.createdAt,
3558
4130
  sessionId: session.sessionId
3559
4131
  });
3560
4132
  }
3561
- const runsDir = join11(factoryDir, "runs", projectName);
3562
- if (existsSync13(runsDir)) {
3563
- const runDirs = readdirSync4(runsDir).filter((d) => d.startsWith("run-")).sort().reverse().slice(0, 10);
4133
+ const runsDir = join12(factoryDir, "runs", projectName);
4134
+ if (existsSync14(runsDir)) {
4135
+ const runDirs = readdirSync6(runsDir).filter((d) => d.startsWith("run-")).sort().reverse().slice(0, 10);
3564
4136
  for (const runId of runDirs) {
3565
- const nlspecPath = join11(runsDir, runId, "nlspec.md");
3566
- if (existsSync13(nlspecPath)) {
3567
- const content = readFileSync10(nlspecPath, "utf-8");
4137
+ const nlspecPath = join12(runsDir, runId, "nlspec.md");
4138
+ if (existsSync14(nlspecPath)) {
4139
+ const content = readFileSync11(nlspecPath, "utf-8");
3568
4140
  const title = content.split("\n").find((l) => l.startsWith("# ")) || runId;
3569
4141
  artifacts.push({
3570
4142
  type: "nlspec",
3571
4143
  path: nlspecPath,
3572
4144
  summary: title.replace("# ", "").slice(0, 200),
3573
- date: statSync3(nlspecPath).mtime.toISOString(),
4145
+ date: statSync4(nlspecPath).mtime.toISOString(),
3574
4146
  runId
3575
4147
  });
3576
4148
  }
3577
4149
  }
3578
4150
  }
3579
- const learningsDir = join11(getProductDir2(factoryDir, projectName), "learnings");
3580
- if (existsSync13(learningsDir)) {
3581
- const learningFiles = readdirSync4(learningsDir).filter((f) => f.endsWith(".md") && !f.startsWith("."));
4151
+ const learningsDir = join12(getProductDir2(factoryDir, projectName), "learnings");
4152
+ if (existsSync14(learningsDir)) {
4153
+ const learningFiles = readdirSync6(learningsDir).filter((f) => f.endsWith(".md") && !f.startsWith("."));
3582
4154
  for (const file of learningFiles) {
3583
- const filePath = join11(learningsDir, file);
3584
- const content = readFileSync10(filePath, "utf-8");
4155
+ const filePath = join12(learningsDir, file);
4156
+ const content = readFileSync11(filePath, "utf-8");
3585
4157
  const firstHeading = content.split("\n").find((l) => l.startsWith("## ")) || file;
3586
4158
  artifacts.push({
3587
4159
  type: "learning",
3588
4160
  path: filePath,
3589
4161
  summary: firstHeading.replace("## ", "").slice(0, 200),
3590
- date: statSync3(filePath).mtime.toISOString()
4162
+ date: statSync4(filePath).mtime.toISOString()
3591
4163
  });
3592
4164
  }
3593
4165
  }
@@ -3612,8 +4184,8 @@ var init_strategy = __esm({
3612
4184
  // src/cli/ui/chat-handler.ts
3613
4185
  import { randomUUID } from "crypto";
3614
4186
  import { execSync as execSync2 } from "child_process";
3615
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync11, existsSync as existsSync14, mkdirSync as mkdirSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
3616
- import { join as join12 } from "path";
4187
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync12, existsSync as existsSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
4188
+ import { join as join13 } from "path";
3617
4189
  import http from "http";
3618
4190
  import { WebSocketServer, WebSocket as WsWebSocket } from "ws";
3619
4191
  function getRecentTokenUsage() {
@@ -3723,19 +4295,19 @@ function getToolDefinitions() {
3723
4295
  ];
3724
4296
  }
3725
4297
  function readJsonSafe(filePath) {
3726
- if (!existsSync14(filePath)) return null;
4298
+ if (!existsSync15(filePath)) return null;
3727
4299
  try {
3728
- return JSON.parse(readFileSync11(filePath, "utf-8"));
4300
+ return JSON.parse(readFileSync12(filePath, "utf-8"));
3729
4301
  } catch {
3730
4302
  return null;
3731
4303
  }
3732
4304
  }
3733
4305
  function getBoardUrl(factoryPath) {
3734
4306
  if (process.env.BEASTMODE_BOARD_URL) return process.env.BEASTMODE_BOARD_URL;
3735
- const configPath = join12(factoryPath, ".beastmode", "config.json");
3736
- if (existsSync14(configPath)) {
4307
+ const configPath = join13(factoryPath, ".beastmode", "config.json");
4308
+ if (existsSync15(configPath)) {
3737
4309
  try {
3738
- const config = JSON.parse(readFileSync11(configPath, "utf-8"));
4310
+ const config = JSON.parse(readFileSync12(configPath, "utf-8"));
3739
4311
  if (config.task_backend?.config?.url) return config.task_backend.config.url;
3740
4312
  } catch {
3741
4313
  }
@@ -3746,19 +4318,19 @@ async function executeTool(toolName, toolInput, factoryPath) {
3746
4318
  try {
3747
4319
  switch (toolName) {
3748
4320
  case "factory_status": {
3749
- const bmDir = join12(factoryPath, ".beastmode");
3750
- const factory = readJsonSafe(join12(bmDir, "factory.json"));
3751
- const projectsDir = join12(bmDir, "projects");
4321
+ const bmDir = join13(factoryPath, ".beastmode");
4322
+ const factory = readJsonSafe(join13(bmDir, "factory.json"));
4323
+ const projectsDir = join13(bmDir, "projects");
3752
4324
  let projects = [];
3753
- if (existsSync14(projectsDir)) {
3754
- projects = readdirSync5(projectsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
4325
+ if (existsSync15(projectsDir)) {
4326
+ projects = readdirSync7(projectsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
3755
4327
  }
3756
- const runsDir = join12(factoryPath, "runs");
4328
+ const runsDir = join13(factoryPath, "runs");
3757
4329
  let runs = [];
3758
- if (existsSync14(runsDir)) {
3759
- runs = readdirSync5(runsDir).filter((d) => {
4330
+ if (existsSync15(runsDir)) {
4331
+ runs = readdirSync7(runsDir).filter((d) => {
3760
4332
  try {
3761
- return statSync4(join12(runsDir, d)).isDirectory();
4333
+ return statSync5(join13(runsDir, d)).isDirectory();
3762
4334
  } catch {
3763
4335
  return false;
3764
4336
  }
@@ -3766,10 +4338,10 @@ async function executeTool(toolName, toolInput, factoryPath) {
3766
4338
  }
3767
4339
  let daemonStatus = "stopped";
3768
4340
  let daemonPid = null;
3769
- const pidFile = join12(bmDir, "daemon.pid");
3770
- if (existsSync14(pidFile)) {
4341
+ const pidFile = join13(bmDir, "daemon.pid");
4342
+ if (existsSync15(pidFile)) {
3771
4343
  try {
3772
- daemonPid = parseInt(readFileSync11(pidFile, "utf-8").trim(), 10);
4344
+ daemonPid = parseInt(readFileSync12(pidFile, "utf-8").trim(), 10);
3773
4345
  process.kill(daemonPid, 0);
3774
4346
  daemonStatus = "running";
3775
4347
  } catch {
@@ -3817,7 +4389,7 @@ async function executeTool(toolName, toolInput, factoryPath) {
3817
4389
  }, null, 2);
3818
4390
  }
3819
4391
  case "factory_config": {
3820
- const configPath = join12(factoryPath, ".beastmode", "config.json");
4392
+ const configPath = join13(factoryPath, ".beastmode", "config.json");
3821
4393
  const config = readJsonSafe(configPath);
3822
4394
  return config ? JSON.stringify(config, null, 2) : "No config.json found.";
3823
4395
  }
@@ -3872,12 +4444,12 @@ async function executeTool(toolName, toolInput, factoryPath) {
3872
4444
  }
3873
4445
  }
3874
4446
  case "list_runs": {
3875
- const runsDir = join12(factoryPath, "runs");
3876
- if (!existsSync14(runsDir)) return "No runs directory.";
3877
- const runDirs = readdirSync5(runsDir).sort().reverse();
4447
+ const runsDir = join13(factoryPath, "runs");
4448
+ if (!existsSync15(runsDir)) return "No runs directory.";
4449
+ const runDirs = readdirSync7(runsDir).sort().reverse();
3878
4450
  const results = [];
3879
4451
  for (const id of runDirs.slice(0, 15)) {
3880
- const cp = readJsonSafe(join12(runsDir, id, "checkpoint.json"));
4452
+ const cp = readJsonSafe(join13(runsDir, id, "checkpoint.json"));
3881
4453
  if (cp) {
3882
4454
  const hist = Array.isArray(cp.satisfaction_history) ? cp.satisfaction_history : [];
3883
4455
  results.push({
@@ -3893,36 +4465,36 @@ async function executeTool(toolName, toolInput, factoryPath) {
3893
4465
  case "run_detail": {
3894
4466
  const runId = toolInput.run_id;
3895
4467
  if (runId.includes("..")) return "Invalid run_id.";
3896
- const runDir = join12(factoryPath, "runs", runId);
3897
- if (!existsSync14(runDir)) return `Run not found: ${runId}`;
3898
- const manifest = readJsonSafe(join12(runDir, "manifest.json"));
3899
- const checkpoint = readJsonSafe(join12(runDir, "checkpoint.json"));
3900
- const iterDir = join12(runDir, "iterations");
4468
+ const runDir = join13(factoryPath, "runs", runId);
4469
+ if (!existsSync15(runDir)) return `Run not found: ${runId}`;
4470
+ const manifest = readJsonSafe(join13(runDir, "manifest.json"));
4471
+ const checkpoint = readJsonSafe(join13(runDir, "checkpoint.json"));
4472
+ const iterDir = join13(runDir, "iterations");
3901
4473
  const iterations = [];
3902
- if (existsSync14(iterDir)) {
3903
- for (const d of readdirSync5(iterDir).sort()) {
3904
- const sat = readJsonSafe(join12(iterDir, d, "satisfaction.json"));
4474
+ if (existsSync15(iterDir)) {
4475
+ for (const d of readdirSync7(iterDir).sort()) {
4476
+ const sat = readJsonSafe(join13(iterDir, d, "satisfaction.json"));
3905
4477
  iterations.push({ number: parseInt(d, 10), satisfaction: sat });
3906
4478
  }
3907
4479
  }
3908
- const files = readdirSync5(runDir);
4480
+ const files = readdirSync7(runDir);
3909
4481
  return JSON.stringify({ id: runId, files, manifest, checkpoint, iterations }, null, 2);
3910
4482
  }
3911
4483
  case "read_run_file": {
3912
4484
  const runId = toolInput.run_id;
3913
4485
  const relPath = toolInput.file_path;
3914
4486
  if (runId.includes("..") || relPath.includes("..")) return "Invalid path.";
3915
- const fullPath = join12(factoryPath, "runs", runId, relPath);
3916
- if (!existsSync14(fullPath)) return `File not found: runs/${runId}/${relPath}`;
3917
- const content = readFileSync11(fullPath, "utf-8");
4487
+ const fullPath = join13(factoryPath, "runs", runId, relPath);
4488
+ if (!existsSync15(fullPath)) return `File not found: runs/${runId}/${relPath}`;
4489
+ const content = readFileSync12(fullPath, "utf-8");
3918
4490
  return content.length > 1e4 ? content.slice(0, 1e4) + "\n\n... (truncated at 10KB)" : content;
3919
4491
  }
3920
4492
  case "list_projects": {
3921
- const projDir = join12(factoryPath, ".beastmode", "projects");
3922
- if (!existsSync14(projDir)) return "No projects registered.";
3923
- const projects = readdirSync5(projDir).filter((f) => f.endsWith(".json")).map((f) => {
4493
+ const projDir = join13(factoryPath, ".beastmode", "projects");
4494
+ if (!existsSync15(projDir)) return "No projects registered.";
4495
+ const projects = readdirSync7(projDir).filter((f) => f.endsWith(".json")).map((f) => {
3924
4496
  try {
3925
- return JSON.parse(readFileSync11(join12(projDir, f), "utf-8"));
4497
+ return JSON.parse(readFileSync12(join13(projDir, f), "utf-8"));
3926
4498
  } catch {
3927
4499
  return null;
3928
4500
  }
@@ -3932,25 +4504,25 @@ async function executeTool(toolName, toolInput, factoryPath) {
3932
4504
  case "read_file": {
3933
4505
  const relPath = toolInput.path;
3934
4506
  if (relPath.includes("..")) return "Invalid path.";
3935
- const fullPath = join12(factoryPath, relPath);
4507
+ const fullPath = join13(factoryPath, relPath);
3936
4508
  if (!fullPath.startsWith(factoryPath)) return "Path traversal not allowed.";
3937
- if (!existsSync14(fullPath)) return `File not found: ${relPath}`;
4509
+ if (!existsSync15(fullPath)) return `File not found: ${relPath}`;
3938
4510
  try {
3939
- const entries = readdirSync5(fullPath);
4511
+ const entries = readdirSync7(fullPath);
3940
4512
  return `"${relPath}" is a directory with ${entries.length} entries: ${entries.slice(0, 30).join(", ")}`;
3941
4513
  } catch {
3942
4514
  }
3943
- const content = readFileSync11(fullPath, "utf-8");
4515
+ const content = readFileSync12(fullPath, "utf-8");
3944
4516
  return content.length > 1e4 ? content.slice(0, 1e4) + "\n\n... (truncated at 10KB)" : content;
3945
4517
  }
3946
4518
  case "list_directory": {
3947
4519
  const relPath = toolInput.path || "";
3948
4520
  if (relPath.includes("..")) return "Invalid path.";
3949
- const fullPath = join12(factoryPath, relPath);
4521
+ const fullPath = join13(factoryPath, relPath);
3950
4522
  if (!fullPath.startsWith(factoryPath)) return "Path traversal not allowed.";
3951
- if (!existsSync14(fullPath)) return `Directory not found: ${relPath || "/"}`;
4523
+ if (!existsSync15(fullPath)) return `Directory not found: ${relPath || "/"}`;
3952
4524
  try {
3953
- return readdirSync5(fullPath).join("\n");
4525
+ return readdirSync7(fullPath).join("\n");
3954
4526
  } catch {
3955
4527
  return `Not a directory: ${relPath}`;
3956
4528
  }
@@ -3963,8 +4535,8 @@ async function executeTool(toolName, toolInput, factoryPath) {
3963
4535
  }
3964
4536
  }
3965
4537
  function getChatHistoryDir(factoryPath) {
3966
- const dir = join12(factoryPath, ".beastmode", "chat-history");
3967
- if (!existsSync14(dir)) mkdirSync10(dir, { recursive: true });
4538
+ const dir = join13(factoryPath, ".beastmode", "chat-history");
4539
+ if (!existsSync15(dir)) mkdirSync11(dir, { recursive: true });
3968
4540
  return dir;
3969
4541
  }
3970
4542
  function saveConversation(factoryPath, sessionId, messages, scope) {
@@ -3975,13 +4547,13 @@ function saveConversation(factoryPath, sessionId, messages, scope) {
3975
4547
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
3976
4548
  messages
3977
4549
  };
3978
- writeFileSync11(join12(dir, `${sessionId}.json`), JSON.stringify(data, null, 2));
4550
+ writeFileSync12(join13(dir, `${sessionId}.json`), JSON.stringify(data, null, 2));
3979
4551
  }
3980
4552
  function listConversations(factoryPath) {
3981
4553
  const dir = getChatHistoryDir(factoryPath);
3982
- return readdirSync5(dir).filter((f) => f.endsWith(".json")).map((f) => {
4554
+ return readdirSync7(dir).filter((f) => f.endsWith(".json")).map((f) => {
3983
4555
  try {
3984
- const data = JSON.parse(readFileSync11(join12(dir, f), "utf-8"));
4556
+ const data = JSON.parse(readFileSync12(join13(dir, f), "utf-8"));
3985
4557
  const msgs = data.messages || [];
3986
4558
  const firstUser = msgs.find((m) => m.role === "user");
3987
4559
  const preview = typeof firstUser?.content === "string" ? firstUser.content.slice(0, 80) : "";
@@ -3999,10 +4571,10 @@ function listConversations(factoryPath) {
3999
4571
  }
4000
4572
  function loadConversation(factoryPath, sessionId) {
4001
4573
  if (sessionId.includes("..")) return [];
4002
- const file = join12(getChatHistoryDir(factoryPath), `${sessionId}.json`);
4003
- if (!existsSync14(file)) return [];
4574
+ const file = join13(getChatHistoryDir(factoryPath), `${sessionId}.json`);
4575
+ if (!existsSync15(file)) return [];
4004
4576
  try {
4005
- const data = JSON.parse(readFileSync11(file, "utf-8"));
4577
+ const data = JSON.parse(readFileSync12(file, "utf-8"));
4006
4578
  return data.messages || [];
4007
4579
  } catch {
4008
4580
  return [];
@@ -4010,7 +4582,7 @@ function loadConversation(factoryPath, sessionId) {
4010
4582
  }
4011
4583
  function buildSystemPrompt(factoryPath) {
4012
4584
  let factoryName = "BeastMode Factory";
4013
- const factoryJson = readJsonSafe(join12(factoryPath, ".beastmode", "factory.json"));
4585
+ const factoryJson = readJsonSafe(join13(factoryPath, ".beastmode", "factory.json"));
4014
4586
  if (factoryJson?.factory_name) factoryName = factoryJson.factory_name;
4015
4587
  else if (factoryJson?.name) factoryName = factoryJson.name;
4016
4588
  return `You are BeastMode Assistant \u2014 a concise, helpful assistant for the "${factoryName}" factory at ${factoryPath}.
@@ -4557,8 +5129,8 @@ var init_chat_handler = __esm({
4557
5129
  });
4558
5130
 
4559
5131
  // src/cli/ui/board-api-routes.ts
4560
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync12, existsSync as existsSync15, readdirSync as readdirSync6, unlinkSync as unlinkSync3, mkdirSync as mkdirSync11, statSync as statSync5, rmSync as rmSync3 } from "fs";
4561
- import { join as join13, basename as basename3, resolve as resolve4, dirname as dirname5 } from "path";
5132
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync13, existsSync as existsSync16, readdirSync as readdirSync8, unlinkSync as unlinkSync3, mkdirSync as mkdirSync12, statSync as statSync6, rmSync as rmSync3 } from "fs";
5133
+ import { join as join14, basename as basename4, resolve as resolve4, dirname as dirname5 } from "path";
4562
5134
  import { homedir } from "os";
4563
5135
  import { randomUUID as randomUUID2 } from "crypto";
4564
5136
  import { execSync as execSync3, spawnSync } from "child_process";
@@ -4570,10 +5142,10 @@ function assertSafeName(name) {
4570
5142
  }
4571
5143
  function getBoardUrl2(factoryDir) {
4572
5144
  if (process.env.BEASTMODE_BOARD_URL) return process.env.BEASTMODE_BOARD_URL;
4573
- const configPath = join13(factoryDir, ".beastmode", "config.json");
4574
- if (existsSync15(configPath)) {
5145
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
5146
+ if (existsSync16(configPath)) {
4575
5147
  try {
4576
- const config = JSON.parse(readFileSync12(configPath, "utf-8"));
5148
+ const config = JSON.parse(readFileSync13(configPath, "utf-8"));
4577
5149
  if (config.task_backend?.config?.url) {
4578
5150
  return config.task_backend.config.url;
4579
5151
  }
@@ -4712,9 +5284,9 @@ function scopedQuery(query) {
4712
5284
  return { board: b };
4713
5285
  }
4714
5286
  function readJsonFile(filePath) {
4715
- if (!existsSync15(filePath)) return null;
5287
+ if (!existsSync16(filePath)) return null;
4716
5288
  try {
4717
- return JSON.parse(readFileSync12(filePath, "utf-8"));
5289
+ return JSON.parse(readFileSync13(filePath, "utf-8"));
4718
5290
  } catch {
4719
5291
  return null;
4720
5292
  }
@@ -4734,25 +5306,25 @@ function deepMerge2(target, source) {
4734
5306
  return result;
4735
5307
  }
4736
5308
  function getRunsDir(factoryDir) {
4737
- const configPath = join13(factoryDir, ".beastmode", "config.json");
4738
- if (existsSync15(configPath)) {
5309
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
5310
+ if (existsSync16(configPath)) {
4739
5311
  try {
4740
- const config = JSON.parse(readFileSync12(configPath, "utf-8"));
5312
+ const config = JSON.parse(readFileSync13(configPath, "utf-8"));
4741
5313
  if (config.runs_path) return config.runs_path;
4742
5314
  } catch {
4743
5315
  }
4744
5316
  }
4745
- return join13(factoryDir, "runs");
5317
+ return join14(factoryDir, "runs");
4746
5318
  }
4747
5319
  function scanStrandedRuns(runsDir, newThreshold) {
4748
- if (!existsSync15(runsDir)) return [];
5320
+ if (!existsSync16(runsDir)) return [];
4749
5321
  const stranded = [];
4750
5322
  const walkRun = (runPath, projectId) => {
4751
- const ckptPath = join13(runPath, "checkpoint.json");
4752
- if (!existsSync15(ckptPath)) return;
5323
+ const ckptPath = join14(runPath, "checkpoint.json");
5324
+ if (!existsSync16(ckptPath)) return;
4753
5325
  let ckpt;
4754
5326
  try {
4755
- ckpt = JSON.parse(readFileSync12(ckptPath, "utf-8"));
5327
+ ckpt = JSON.parse(readFileSync13(ckptPath, "utf-8"));
4756
5328
  } catch {
4757
5329
  return;
4758
5330
  }
@@ -4770,12 +5342,12 @@ function scanStrandedRuns(runsDir, newThreshold) {
4770
5342
  });
4771
5343
  };
4772
5344
  try {
4773
- const entries = readdirSync6(runsDir);
5345
+ const entries = readdirSync8(runsDir);
4774
5346
  for (const entry of entries) {
4775
- const entryPath = join13(runsDir, entry);
5347
+ const entryPath = join14(runsDir, entry);
4776
5348
  let stat;
4777
5349
  try {
4778
- stat = statSync5(entryPath);
5350
+ stat = statSync6(entryPath);
4779
5351
  } catch {
4780
5352
  continue;
4781
5353
  }
@@ -4785,15 +5357,15 @@ function scanStrandedRuns(runsDir, newThreshold) {
4785
5357
  } else if (!entry.startsWith(".")) {
4786
5358
  let subEntries;
4787
5359
  try {
4788
- subEntries = readdirSync6(entryPath);
5360
+ subEntries = readdirSync8(entryPath);
4789
5361
  } catch {
4790
5362
  continue;
4791
5363
  }
4792
5364
  for (const sub of subEntries) {
4793
5365
  if (!sub.startsWith("run-")) continue;
4794
- const subPath = join13(entryPath, sub);
5366
+ const subPath = join14(entryPath, sub);
4795
5367
  try {
4796
- if (statSync5(subPath).isDirectory()) {
5368
+ if (statSync6(subPath).isDirectory()) {
4797
5369
  walkRun(subPath, entry);
4798
5370
  }
4799
5371
  } catch {
@@ -4808,10 +5380,10 @@ function scanStrandedRuns(runsDir, newThreshold) {
4808
5380
  return stranded;
4809
5381
  }
4810
5382
  function _readAnalyzeMeta(projectsDir, name) {
4811
- const metaPath = join13(projectsDir, name, "codebase-guide.meta.json");
4812
- if (!existsSync15(metaPath)) return null;
5383
+ const metaPath = join14(projectsDir, name, "codebase-guide.meta.json");
5384
+ if (!existsSync16(metaPath)) return null;
4813
5385
  try {
4814
- return JSON.parse(readFileSync12(metaPath, "utf-8"));
5386
+ return JSON.parse(readFileSync13(metaPath, "utf-8"));
4815
5387
  } catch {
4816
5388
  return null;
4817
5389
  }
@@ -4838,29 +5410,19 @@ function getBoardRoutes(factoryDir) {
4838
5410
  method: "GET",
4839
5411
  pattern: "/api/status",
4840
5412
  handler: async () => {
4841
- const bmDir = join13(factoryDir, ".beastmode");
4842
- const factoryJsonPath = join13(bmDir, "factory.json");
4843
- if (!existsSync15(factoryJsonPath)) {
5413
+ const bmDir = join14(factoryDir, ".beastmode");
5414
+ const factoryJsonPath = join14(bmDir, "factory.json");
5415
+ if (!existsSync16(factoryJsonPath)) {
4844
5416
  throw new Error("Not a valid factory: factory.json not found");
4845
5417
  }
4846
- const factoryIdentity = JSON.parse(readFileSync12(factoryJsonPath, "utf-8"));
4847
- const projectsDir = join13(bmDir, "projects");
4848
- let projectCount = 0;
4849
- if (existsSync15(projectsDir)) {
4850
- for (const entry of readdirSync6(projectsDir)) {
4851
- if (entry.startsWith(".")) continue;
4852
- if (existsSync15(join13(projectsDir, entry, "project.json"))) {
4853
- projectCount++;
4854
- continue;
4855
- }
4856
- if (entry.endsWith(".json")) projectCount++;
4857
- }
4858
- }
4859
- const lockPath = join13(bmDir, "extensions.lock");
5418
+ const factoryIdentity = JSON.parse(readFileSync13(factoryJsonPath, "utf-8"));
5419
+ const projectsDir = join14(bmDir, "projects");
5420
+ const projectCount = listProjectRecords(projectsDir).length;
5421
+ const lockPath = join14(bmDir, "extensions.lock");
4860
5422
  let pluginNames = [];
4861
- if (existsSync15(lockPath)) {
5423
+ if (existsSync16(lockPath)) {
4862
5424
  try {
4863
- const lock = JSON.parse(readFileSync12(lockPath, "utf-8"));
5425
+ const lock = JSON.parse(readFileSync13(lockPath, "utf-8"));
4864
5426
  pluginNames = Object.keys(lock.plugins || {});
4865
5427
  } catch {
4866
5428
  }
@@ -4880,24 +5442,24 @@ function getBoardRoutes(factoryDir) {
4880
5442
  skillCount = listSkills(factoryDir).length;
4881
5443
  } catch {
4882
5444
  }
4883
- const runsDir = join13(factoryDir, "runs");
5445
+ const runsDir = join14(factoryDir, "runs");
4884
5446
  let runDirs = [];
4885
- if (existsSync15(runsDir)) {
4886
- runDirs = readdirSync6(runsDir).filter((d) => {
5447
+ if (existsSync16(runsDir)) {
5448
+ runDirs = readdirSync8(runsDir).filter((d) => {
4887
5449
  if (d.startsWith(".")) return false;
4888
5450
  try {
4889
- return readdirSync6(join13(runsDir, d)).length > 0;
5451
+ return readdirSync8(join14(runsDir, d)).length > 0;
4890
5452
  } catch {
4891
5453
  return false;
4892
5454
  }
4893
5455
  }).sort();
4894
5456
  }
4895
- const pidFile = join13(factoryDir, ".beastmode", "daemon.pid");
5457
+ const pidFile = join14(factoryDir, ".beastmode", "daemon.pid");
4896
5458
  let daemonPid = null;
4897
5459
  let pidAlive = false;
4898
- if (existsSync15(pidFile)) {
5460
+ if (existsSync16(pidFile)) {
4899
5461
  try {
4900
- daemonPid = parseInt(readFileSync12(pidFile, "utf-8").trim(), 10);
5462
+ daemonPid = parseInt(readFileSync13(pidFile, "utf-8").trim(), 10);
4901
5463
  process.kill(daemonPid, 0);
4902
5464
  pidAlive = true;
4903
5465
  } catch {
@@ -4954,10 +5516,10 @@ function getBoardRoutes(factoryDir) {
4954
5516
  handler: async () => {
4955
5517
  const boardUrl = getBoardUrl2(factoryDir);
4956
5518
  let ageSecs = null;
4957
- const heartbeatPath = join13(factoryDir, "daemon", "logs", ".heartbeat");
4958
- if (existsSync15(heartbeatPath)) {
5519
+ const heartbeatPath = join14(factoryDir, "daemon", "logs", ".heartbeat");
5520
+ if (existsSync16(heartbeatPath)) {
4959
5521
  try {
4960
- const ts = parseInt(readFileSync12(heartbeatPath, "utf-8").trim(), 10);
5522
+ const ts = parseInt(readFileSync13(heartbeatPath, "utf-8").trim(), 10);
4961
5523
  if (!isNaN(ts)) {
4962
5524
  ageSecs = Math.floor(Date.now() / 1e3) - ts;
4963
5525
  }
@@ -4999,11 +5561,11 @@ function getBoardRoutes(factoryDir) {
4999
5561
  let totalSlots = 3;
5000
5562
  let shortPhaseReserve;
5001
5563
  let slotsTimestamp;
5002
- const slotsPath = join13(factoryDir, "daemon", "logs", ".slots.json");
5564
+ const slotsPath = join14(factoryDir, "daemon", "logs", ".slots.json");
5003
5565
  let usedDaemonSlotsFile = false;
5004
- if (existsSync15(slotsPath)) {
5566
+ if (existsSync16(slotsPath)) {
5005
5567
  try {
5006
- const slots = JSON.parse(readFileSync12(slotsPath, "utf-8"));
5568
+ const slots = JSON.parse(readFileSync13(slotsPath, "utf-8"));
5007
5569
  if (typeof slots.busy === "number" && typeof slots.total === "number") {
5008
5570
  busySlots = slots.busy;
5009
5571
  totalSlots = slots.total;
@@ -5028,10 +5590,10 @@ function getBoardRoutes(factoryDir) {
5028
5590
  } catch {
5029
5591
  }
5030
5592
  for (const name of ["beastmode.docker.json", "beastmode.daemon.json"]) {
5031
- const configPath = join13(factoryDir, "config", name);
5032
- if (!existsSync15(configPath)) continue;
5593
+ const configPath = join14(factoryDir, "config", name);
5594
+ if (!existsSync16(configPath)) continue;
5033
5595
  try {
5034
- const config = JSON.parse(readFileSync12(configPath, "utf-8"));
5596
+ const config = JSON.parse(readFileSync13(configPath, "utf-8"));
5035
5597
  if (typeof config.max_slots === "number") {
5036
5598
  totalSlots = config.max_slots;
5037
5599
  }
@@ -5048,11 +5610,11 @@ function getBoardRoutes(factoryDir) {
5048
5610
  if (shortPhaseReserve === void 0) {
5049
5611
  shortPhaseReserve = Math.max(0, Math.min(1, totalSlots - 1));
5050
5612
  }
5051
- const alertsPath = join13(factoryDir, "daemon", "logs", ".alerts.json");
5613
+ const alertsPath = join14(factoryDir, "daemon", "logs", ".alerts.json");
5052
5614
  let alerts = [];
5053
- if (existsSync15(alertsPath)) {
5615
+ if (existsSync16(alertsPath)) {
5054
5616
  try {
5055
- const parsed = JSON.parse(readFileSync12(alertsPath, "utf-8"));
5617
+ const parsed = JSON.parse(readFileSync13(alertsPath, "utf-8"));
5056
5618
  if (Array.isArray(parsed)) alerts = parsed;
5057
5619
  } catch {
5058
5620
  }
@@ -5267,12 +5829,12 @@ function getBoardRoutes(factoryDir) {
5267
5829
  method: "GET",
5268
5830
  pattern: "/api/extensions/plugins",
5269
5831
  handler: () => {
5270
- const lockPath = join13(factoryDir, ".beastmode", "extensions.lock");
5271
- if (!existsSync15(lockPath)) {
5832
+ const lockPath = join14(factoryDir, ".beastmode", "extensions.lock");
5833
+ if (!existsSync16(lockPath)) {
5272
5834
  return { plugins: {} };
5273
5835
  }
5274
5836
  try {
5275
- const lock = JSON.parse(readFileSync12(lockPath, "utf-8"));
5837
+ const lock = JSON.parse(readFileSync13(lockPath, "utf-8"));
5276
5838
  return { plugins: lock.plugins || {} };
5277
5839
  } catch {
5278
5840
  return { plugins: {} };
@@ -5429,27 +5991,8 @@ function getBoardRoutes(factoryDir) {
5429
5991
  method: "GET",
5430
5992
  pattern: "/api/projects",
5431
5993
  handler: () => {
5432
- const projectsDir = join13(factoryDir, ".beastmode", "projects");
5433
- if (!existsSync15(projectsDir)) return { projects: [] };
5434
- const projects = [];
5435
- for (const entry of readdirSync6(projectsDir)) {
5436
- if (entry.startsWith(".")) continue;
5437
- const subDirPath = join13(projectsDir, entry, "project.json");
5438
- if (existsSync15(subDirPath)) {
5439
- try {
5440
- projects.push(JSON.parse(readFileSync12(subDirPath, "utf-8")));
5441
- } catch {
5442
- }
5443
- continue;
5444
- }
5445
- if (entry.endsWith(".json")) {
5446
- try {
5447
- projects.push(JSON.parse(readFileSync12(join13(projectsDir, entry), "utf-8")));
5448
- } catch {
5449
- }
5450
- }
5451
- }
5452
- return { projects };
5994
+ const projectsDir = join14(factoryDir, ".beastmode", "projects");
5995
+ return { projects: listProjectRecords(projectsDir) };
5453
5996
  }
5454
5997
  },
5455
5998
  {
@@ -5458,11 +6001,9 @@ function getBoardRoutes(factoryDir) {
5458
6001
  handler: (_body, params) => {
5459
6002
  const { name } = params;
5460
6003
  assertSafeName(name);
5461
- const subDirPath = join13(factoryDir, ".beastmode", "projects", name, "project.json");
5462
- const flatPath = join13(factoryDir, ".beastmode", "projects", `${name}.json`);
5463
- const filePath = existsSync15(subDirPath) ? subDirPath : flatPath;
5464
- if (!existsSync15(filePath)) throw new Error(`Project not found: ${name}`);
5465
- return JSON.parse(readFileSync12(filePath, "utf-8"));
6004
+ const record = readProjectRecord(join14(factoryDir, ".beastmode", "projects"), name);
6005
+ if (!record) throw new Error(`Project not found: ${name}`);
6006
+ return record;
5466
6007
  }
5467
6008
  },
5468
6009
  {
@@ -5471,12 +6012,12 @@ function getBoardRoutes(factoryDir) {
5471
6012
  handler: (_body, params) => {
5472
6013
  const { name } = params;
5473
6014
  assertSafeName(name);
5474
- const extPath = join13(factoryDir, ".beastmode", "projects", name, "extensions.json");
5475
- if (!existsSync15(extPath)) {
6015
+ const extPath = join14(factoryDir, ".beastmode", "projects", name, "extensions.json");
6016
+ if (!existsSync16(extPath)) {
5476
6017
  return { plugins: { add: [], remove: [] }, mcps: { add: {}, remove: [] }, skills: { add: [], remove: [] } };
5477
6018
  }
5478
6019
  try {
5479
- return JSON.parse(readFileSync12(extPath, "utf-8"));
6020
+ return JSON.parse(readFileSync13(extPath, "utf-8"));
5480
6021
  } catch {
5481
6022
  return { plugins: { add: [], remove: [] }, mcps: { add: {}, remove: [] }, skills: { add: [], remove: [] } };
5482
6023
  }
@@ -5498,18 +6039,18 @@ function getBoardRoutes(factoryDir) {
5498
6039
  const { path: queryPath } = body || {};
5499
6040
  const startPath = queryPath || homedir();
5500
6041
  const target = resolve4(startPath);
5501
- if (!existsSync15(target)) throw new Error(`Path not found: ${target}`);
5502
- const st = statSync5(target);
6042
+ if (!existsSync16(target)) throw new Error(`Path not found: ${target}`);
6043
+ const st = statSync6(target);
5503
6044
  if (!st.isDirectory()) throw new Error(`Not a directory: ${target}`);
5504
6045
  let entries = [];
5505
6046
  try {
5506
- entries = readdirSync6(target, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
5507
- const full = join13(target, d.name);
6047
+ entries = readdirSync8(target, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
6048
+ const full = join14(target, d.name);
5508
6049
  return {
5509
6050
  name: d.name,
5510
6051
  path: full,
5511
6052
  is_dir: true,
5512
- has_git: existsSync15(join13(full, ".git"))
6053
+ has_git: existsSync16(join14(full, ".git"))
5513
6054
  };
5514
6055
  }).sort((a, b) => a.name.localeCompare(b.name));
5515
6056
  } catch {
@@ -5528,8 +6069,8 @@ function getBoardRoutes(factoryDir) {
5528
6069
  pattern: "/api/projects",
5529
6070
  handler: (body) => {
5530
6071
  const { path: projectPath, github_url, clone_target } = body;
5531
- const projectsDir = join13(factoryDir, ".beastmode", "projects");
5532
- if (!existsSync15(projectsDir)) mkdirSync11(projectsDir, { recursive: true });
6072
+ const projectsDir = join14(factoryDir, ".beastmode", "projects");
6073
+ if (!existsSync16(projectsDir)) mkdirSync12(projectsDir, { recursive: true });
5533
6074
  let resolvedPath;
5534
6075
  if (github_url) {
5535
6076
  const url = github_url.trim();
@@ -5538,10 +6079,10 @@ function getBoardRoutes(factoryDir) {
5538
6079
  }
5539
6080
  const repoName = url.replace(/\.git$/, "").split(/[/:]/).filter(Boolean).pop() || "";
5540
6081
  if (!repoName) throw new Error("Could not derive repo name from URL");
5541
- const baseDir = clone_target ? resolve4(clone_target) : process.env.BEASTMODE_CLONE_DIR ? resolve4(process.env.BEASTMODE_CLONE_DIR) : join13(homedir(), "repos");
5542
- if (!existsSync15(baseDir)) mkdirSync11(baseDir, { recursive: true });
5543
- resolvedPath = join13(baseDir, repoName);
5544
- if (existsSync15(resolvedPath)) {
6082
+ const baseDir = clone_target ? resolve4(clone_target) : process.env.BEASTMODE_CLONE_DIR ? resolve4(process.env.BEASTMODE_CLONE_DIR) : join14(homedir(), "repos");
6083
+ if (!existsSync16(baseDir)) mkdirSync12(baseDir, { recursive: true });
6084
+ resolvedPath = join14(baseDir, repoName);
6085
+ if (existsSync16(resolvedPath)) {
5545
6086
  throw new Error(`Target path already exists: ${resolvedPath} \u2014 pick a different clone_target or rename the existing folder`);
5546
6087
  }
5547
6088
  const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "";
@@ -5573,42 +6114,41 @@ function getBoardRoutes(factoryDir) {
5573
6114
  }
5574
6115
  } else if (projectPath) {
5575
6116
  resolvedPath = resolve4(projectPath);
5576
- if (!existsSync15(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
6117
+ if (!existsSync16(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
5577
6118
  } else {
5578
6119
  throw new Error("Missing required field: provide either `path` (local) or `github_url` (clone)");
5579
6120
  }
5580
- const projectName = basename3(resolvedPath);
6121
+ const projectName = basename4(resolvedPath);
5581
6122
  const stack = detectStack(resolvedPath);
5582
- const projectConfig = {
6123
+ let verifyPort = 3001;
6124
+ for (const existing of listProjectRecords(projectsDir)) {
6125
+ const port = existing.deploy?.verify_port;
6126
+ if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
6127
+ }
6128
+ const record = createProjectRecord({
5583
6129
  name: projectName,
5584
- path: resolvedPath,
5585
- repo: stack.git_remote || void 0,
5586
- stack: {
5587
- detected: stack.framework,
5588
- build_command: stack.suggested_commands.build,
5589
- dev_command: stack.suggested_commands.dev,
5590
- test_command: stack.suggested_commands.test,
5591
- install_command: stack.suggested_commands.install,
5592
- dev_port: stack.dev_port
5593
- },
5594
- deploy: { target: stack.suggested_deploy },
5595
- plugins: stack.suggested_plugins,
5596
- infra: {
5597
- credentials: {
5598
- mode: "factory"
5599
- },
5600
- state_backend: "auto"
5601
- }
6130
+ resolvedPath,
6131
+ gitRemote: stack.git_remote || void 0,
6132
+ verifyPort
6133
+ });
6134
+ record.stack = {
6135
+ detected: stack.framework,
6136
+ build_command: stack.suggested_commands.build,
6137
+ dev_command: stack.suggested_commands.dev,
6138
+ test_command: stack.suggested_commands.test,
6139
+ install_command: stack.suggested_commands.install,
6140
+ dev_port: stack.dev_port,
6141
+ is_monorepo: stack.is_monorepo,
6142
+ total_packages: stack.total_packages,
6143
+ primary_languages: stack.primary_languages,
6144
+ ...stack.packages ? { packages: stack.packages } : {}
5602
6145
  };
5603
- const projName = projectName;
5604
- const projectFile = join13(projectsDir, `${projName}.json`);
5605
- writeFileSync12(projectFile, JSON.stringify(projectConfig, null, 2) + "\n", "utf-8");
5606
- const infraDir = join13(projectsDir, projName, "infra");
5607
- const terraformDir = join13(infraDir, "terraform");
5608
- const ciDir = join13(infraDir, "ci");
5609
- mkdirSync11(terraformDir, { recursive: true });
5610
- mkdirSync11(ciDir, { recursive: true });
5611
- return projectConfig;
6146
+ record.infra = { credentials: { mode: "factory" }, state_backend: "auto" };
6147
+ writeProjectRecord(projectsDir, projectName, record);
6148
+ const infraDir = join14(projectsDir, projectName, "infra");
6149
+ mkdirSync12(join14(infraDir, "terraform"), { recursive: true });
6150
+ mkdirSync12(join14(infraDir, "ci"), { recursive: true });
6151
+ return record;
5612
6152
  }
5613
6153
  },
5614
6154
  {
@@ -5617,14 +6157,14 @@ function getBoardRoutes(factoryDir) {
5617
6157
  handler: (_body, params) => {
5618
6158
  const { name } = params;
5619
6159
  assertSafeName(name);
5620
- const projectsDir = join13(factoryDir, ".beastmode", "projects");
5621
- const subDir = join13(projectsDir, name);
5622
- const flatPath = join13(projectsDir, `${name}.json`);
5623
- const hasSubDir = existsSync15(join13(subDir, "project.json"));
5624
- const hasFlat = existsSync15(flatPath);
6160
+ const projectsDir = join14(factoryDir, ".beastmode", "projects");
6161
+ const subDir = join14(projectsDir, name);
6162
+ const flatPath = join14(projectsDir, `${name}.json`);
6163
+ const hasSubDir = existsSync16(join14(subDir, "project.json"));
6164
+ const hasFlat = existsSync16(flatPath);
5625
6165
  if (!hasSubDir && !hasFlat) throw new Error(`Project not found: ${name}`);
5626
6166
  if (hasSubDir) rmSync3(subDir, { recursive: true, force: true });
5627
- else if (existsSync15(subDir)) rmSync3(subDir, { recursive: true, force: true });
6167
+ else if (existsSync16(subDir)) rmSync3(subDir, { recursive: true, force: true });
5628
6168
  if (hasFlat) unlinkSync3(flatPath);
5629
6169
  return { success: true };
5630
6170
  }
@@ -5635,20 +6175,17 @@ function getBoardRoutes(factoryDir) {
5635
6175
  handler: async (body, params, query) => {
5636
6176
  const { name } = params;
5637
6177
  assertSafeName(name);
5638
- const projectsDir = join13(factoryDir, ".beastmode", "projects");
5639
- const subDirConfigPath = join13(projectsDir, name, "project.json");
5640
- const flatConfigPath = join13(projectsDir, `${name}.json`);
5641
- const projPath = existsSync15(subDirConfigPath) ? subDirConfigPath : existsSync15(flatConfigPath) ? flatConfigPath : null;
5642
- if (!projPath) throw new Error(`Project not found: ${name}`);
5643
- const projConfig = JSON.parse(readFileSync12(projPath, "utf-8"));
6178
+ const projectsDir = join14(factoryDir, ".beastmode", "projects");
6179
+ const projConfig = readProjectRecord(projectsDir, name);
6180
+ if (!projConfig) throw new Error(`Project not found: ${name}`);
5644
6181
  const projectPath = projConfig.path;
5645
- const subDir = join13(projectsDir, name);
5646
- if (!existsSync15(subDir)) mkdirSync11(subDir, { recursive: true });
6182
+ const subDir = join14(projectsDir, name);
6183
+ if (!existsSync16(subDir)) mkdirSync12(subDir, { recursive: true });
5647
6184
  const force = query?.force === "true" || query?.force === "1";
5648
6185
  const tier = body?.tier;
5649
6186
  if (tier === "quick") {
5650
- const brownfieldPath = join13(subDir, "brownfield.md");
5651
- if (!existsSync15(brownfieldPath)) {
6187
+ const brownfieldPath = join14(subDir, "brownfield.md");
6188
+ if (!existsSync16(brownfieldPath)) {
5652
6189
  const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
5653
6190
  let stackInfo = "Unknown stack";
5654
6191
  try {
@@ -5659,7 +6196,7 @@ Build: ${cmds?.build || "unknown"}
5659
6196
  Dev: ${cmds?.dev || "unknown"}`;
5660
6197
  } catch {
5661
6198
  }
5662
- writeFileSync12(brownfieldPath, `# Brownfield Analysis: ${name}
6199
+ writeFileSync13(brownfieldPath, `# Brownfield Analysis: ${name}
5663
6200
 
5664
6201
  ${stackInfo}
5665
6202
 
@@ -5681,8 +6218,8 @@ Path: ${projectPath}
5681
6218
  }
5682
6219
  const writeMeta = (m) => {
5683
6220
  try {
5684
- writeFileSync12(
5685
- join13(subDir, "codebase-guide.meta.json"),
6221
+ writeFileSync13(
6222
+ join14(subDir, "codebase-guide.meta.json"),
5686
6223
  JSON.stringify(m, null, 2) + "\n",
5687
6224
  "utf-8"
5688
6225
  );
@@ -5691,8 +6228,8 @@ Path: ${projectPath}
5691
6228
  }
5692
6229
  };
5693
6230
  if (!_hasClaudeCli()) {
5694
- const brownfieldPath = join13(subDir, "brownfield.md");
5695
- if (!existsSync15(brownfieldPath)) {
6231
+ const brownfieldPath = join14(subDir, "brownfield.md");
6232
+ if (!existsSync16(brownfieldPath)) {
5696
6233
  const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
5697
6234
  let stackInfo = "Unknown stack";
5698
6235
  try {
@@ -5703,7 +6240,7 @@ Build: ${cmds?.build || "unknown"}
5703
6240
  Dev: ${cmds?.dev || "unknown"}`;
5704
6241
  } catch {
5705
6242
  }
5706
- writeFileSync12(brownfieldPath, `# Brownfield Analysis: ${name}
6243
+ writeFileSync13(brownfieldPath, `# Brownfield Analysis: ${name}
5707
6244
 
5708
6245
  ${stackInfo}
5709
6246
 
@@ -5737,9 +6274,9 @@ Path: ${projectPath}
5737
6274
  "You are the Brownfield Analyst. Analyze the codebase at the current working directory.",
5738
6275
  `Write your output in pyramid format (L0/L1/L2) to the directory: ${subDir}`,
5739
6276
  "Produce exactly these four files:",
5740
- ` - ${join13(subDir, "codebase-guide.md")} (full L2 analysis: architecture, conventions, safe modification points, fragile areas)`,
5741
- ` - ${join13(subDir, "codebase-guide.l1.md")} (paragraph summary only)`,
5742
- ` - ${join13(subDir, "codebase-guide.l0.txt")} (one-sentence summary only)`,
6277
+ ` - ${join14(subDir, "codebase-guide.md")} (full L2 analysis: architecture, conventions, safe modification points, fragile areas)`,
6278
+ ` - ${join14(subDir, "codebase-guide.l1.md")} (paragraph summary only)`,
6279
+ ` - ${join14(subDir, "codebase-guide.l0.txt")} (one-sentence summary only)`,
5743
6280
  "Do not write anything else outside the BEASTMODE_OUTPUT_DIR."
5744
6281
  ].join("\n");
5745
6282
  const isRoot = process.getuid?.() === 0;
@@ -5772,8 +6309,8 @@ Path: ${projectPath}
5772
6309
  child.on("close", (code) => {
5773
6310
  const durationSeconds = (Date.now() - startMs) / 1e3;
5774
6311
  const sha = _gitHeadSha(projectPath);
5775
- const guidePath = join13(subDir, "codebase-guide.md");
5776
- const ok = code === 0 && existsSync15(guidePath);
6312
+ const guidePath = join14(subDir, "codebase-guide.md");
6313
+ const ok = code === 0 && existsSync16(guidePath);
5777
6314
  if (ok) {
5778
6315
  writeMeta({
5779
6316
  analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5783,8 +6320,8 @@ Path: ${projectPath}
5783
6320
  duration_seconds: durationSeconds,
5784
6321
  status: "complete"
5785
6322
  });
5786
- const legacy = join13(subDir, "brownfield.md");
5787
- if (existsSync15(legacy)) {
6323
+ const legacy = join14(subDir, "brownfield.md");
6324
+ if (existsSync16(legacy)) {
5788
6325
  try {
5789
6326
  unlinkSync3(legacy);
5790
6327
  } catch {
@@ -5860,7 +6397,7 @@ Path: ${projectPath}
5860
6397
  handler: (_body, params) => {
5861
6398
  const { name } = params;
5862
6399
  assertSafeName(name);
5863
- const projectsDir = join13(factoryDir, ".beastmode", "projects");
6400
+ const projectsDir = join14(factoryDir, ".beastmode", "projects");
5864
6401
  const job = _analyzeJobs.get(name);
5865
6402
  const meta = _readAnalyzeMeta(projectsDir, name);
5866
6403
  if (job && job.status === "running") {
@@ -5904,13 +6441,13 @@ Path: ${projectPath}
5904
6441
  pattern: "/api/runs",
5905
6442
  handler: () => {
5906
6443
  const runsDir = getRunsDir(factoryDir);
5907
- if (!existsSync15(runsDir)) return { runs: [] };
6444
+ if (!existsSync16(runsDir)) return { runs: [] };
5908
6445
  const runEntries = [];
5909
- for (const entry of readdirSync6(runsDir)) {
6446
+ for (const entry of readdirSync8(runsDir)) {
5910
6447
  if (entry.startsWith(".")) continue;
5911
- const entryPath = join13(runsDir, entry);
6448
+ const entryPath = join14(runsDir, entry);
5912
6449
  try {
5913
- const children = readdirSync6(entryPath);
6450
+ const children = readdirSync8(entryPath);
5914
6451
  if (entry.startsWith("run-")) {
5915
6452
  if (children.length > 0) {
5916
6453
  runEntries.push({ id: entry, dir: entryPath, projectId: "" });
@@ -5918,9 +6455,9 @@ Path: ${projectPath}
5918
6455
  } else {
5919
6456
  for (const child of children) {
5920
6457
  if (!child.startsWith("run-") || child.startsWith(".")) continue;
5921
- const childPath = join13(entryPath, child);
6458
+ const childPath = join14(entryPath, child);
5922
6459
  try {
5923
- const grandchildren = readdirSync6(childPath);
6460
+ const grandchildren = readdirSync8(childPath);
5924
6461
  if (grandchildren.length > 0) {
5925
6462
  runEntries.push({ id: child, dir: childPath, projectId: entry });
5926
6463
  }
@@ -5940,9 +6477,9 @@ Path: ${projectPath}
5940
6477
  }
5941
6478
  }
5942
6479
  const runs = Array.from(deduped.values()).sort((a, b) => b.id.localeCompare(a.id)).map(({ id, dir, projectId }) => {
5943
- const manifest = readJsonFile(join13(dir, "manifest.json"));
5944
- const checkpoint = readJsonFile(join13(dir, "checkpoint.json"));
5945
- const prodVerif = readJsonFile(join13(dir, "prod-verification.json"));
6480
+ const manifest = readJsonFile(join14(dir, "manifest.json"));
6481
+ const checkpoint = readJsonFile(join14(dir, "checkpoint.json"));
6482
+ const prodVerif = readJsonFile(join14(dir, "prod-verification.json"));
5946
6483
  const manifestData = manifest;
5947
6484
  const cpData = checkpoint;
5948
6485
  const prodAcceptFloor = 0.65;
@@ -5970,27 +6507,27 @@ Path: ${projectPath}
5970
6507
  handler: (_body, params, query) => {
5971
6508
  const { id } = params;
5972
6509
  const runsDir = getRunsDir(factoryDir);
5973
- let runDir = join13(runsDir, id);
5974
- if (!existsSync15(runDir)) {
5975
- for (const proj of readdirSync6(runsDir)) {
6510
+ let runDir = join14(runsDir, id);
6511
+ if (!existsSync16(runDir)) {
6512
+ for (const proj of readdirSync8(runsDir)) {
5976
6513
  if (proj.startsWith(".") || proj.startsWith("run-")) continue;
5977
- const candidate = join13(runsDir, proj, id);
5978
- if (existsSync15(candidate)) {
6514
+ const candidate = join14(runsDir, proj, id);
6515
+ if (existsSync16(candidate)) {
5979
6516
  runDir = candidate;
5980
6517
  break;
5981
6518
  }
5982
6519
  }
5983
6520
  }
5984
- if (!existsSync15(runDir)) throw new Error(`Run not found: ${id}`);
5985
- const manifest = readJsonFile(join13(runDir, "manifest.json"));
5986
- const checkpoint = readJsonFile(join13(runDir, "checkpoint.json"));
5987
- const iterationsDir = join13(runDir, "iterations");
6521
+ if (!existsSync16(runDir)) throw new Error(`Run not found: ${id}`);
6522
+ const manifest = readJsonFile(join14(runDir, "manifest.json"));
6523
+ const checkpoint = readJsonFile(join14(runDir, "checkpoint.json"));
6524
+ const iterationsDir = join14(runDir, "iterations");
5988
6525
  const iterations = [];
5989
- if (existsSync15(iterationsDir)) {
5990
- const iterDirs = readdirSync6(iterationsDir).sort();
6526
+ if (existsSync16(iterationsDir)) {
6527
+ const iterDirs = readdirSync8(iterationsDir).sort();
5991
6528
  for (const iterDir of iterDirs) {
5992
6529
  const satisfaction = readJsonFile(
5993
- join13(iterationsDir, iterDir, "satisfaction.json")
6530
+ join14(iterationsDir, iterDir, "satisfaction.json")
5994
6531
  );
5995
6532
  iterations.push({
5996
6533
  number: parseInt(iterDir, 10),
@@ -6018,11 +6555,11 @@ Path: ${projectPath}
6018
6555
  pattern: "/api/runs/archive",
6019
6556
  handler: () => {
6020
6557
  const runsDir = getRunsDir(factoryDir);
6021
- const configPath = join13(factoryDir, ".beastmode", "config.json");
6558
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
6022
6559
  let days = 7;
6023
- if (existsSync15(configPath)) {
6560
+ if (existsSync16(configPath)) {
6024
6561
  try {
6025
- days = JSON.parse(readFileSync12(configPath, "utf-8")).archive_after_days || 7;
6562
+ days = JSON.parse(readFileSync13(configPath, "utf-8")).archive_after_days || 7;
6026
6563
  } catch {
6027
6564
  }
6028
6565
  }
@@ -6048,8 +6585,8 @@ Path: ${projectPath}
6048
6585
  method: "GET",
6049
6586
  pattern: "/api/config",
6050
6587
  handler: () => {
6051
- const configPath = join13(factoryDir, ".beastmode", "config.json");
6052
- const raw = existsSync15(configPath) ? JSON.parse(readFileSync12(configPath, "utf-8")) : generateDefaults();
6588
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
6589
+ const raw = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : generateDefaults();
6053
6590
  return raw;
6054
6591
  }
6055
6592
  },
@@ -6067,8 +6604,8 @@ Path: ${projectPath}
6067
6604
  const updatesClean = { ...updates };
6068
6605
  delete updatesClean.force;
6069
6606
  delete updatesClean._force;
6070
- const configPath = join13(factoryDir, ".beastmode", "config.json");
6071
- const current = existsSync15(configPath) ? JSON.parse(readFileSync12(configPath, "utf-8")) : generateDefaults();
6607
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
6608
+ const current = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : generateDefaults();
6072
6609
  if (!force) {
6073
6610
  const oldPipeline = current.pipeline || {};
6074
6611
  const newPipeline = updatesClean.pipeline || {};
@@ -6099,11 +6636,11 @@ Path: ${projectPath}
6099
6636
  }
6100
6637
  }
6101
6638
  const merged = deepMerge2(current, updatesClean);
6102
- writeFileSync12(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6639
+ writeFileSync13(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6103
6640
  const daemonConfigPaths = [
6104
- join13(factoryDir, "config", "beastmode.daemon.json"),
6105
- join13(factoryDir, "config", "beastmode.docker.json")
6106
- ].filter(existsSync15);
6641
+ join14(factoryDir, "config", "beastmode.daemon.json"),
6642
+ join14(factoryDir, "config", "beastmode.docker.json")
6643
+ ].filter(existsSync16);
6107
6644
  const pipelineToDaemonMap = {
6108
6645
  satisfaction_threshold: ["verification", "satisfaction_threshold"],
6109
6646
  prod_accept_floor: ["verification", "prod_accept_floor"],
@@ -6119,7 +6656,7 @@ Path: ${projectPath}
6119
6656
  ];
6120
6657
  for (const daemonConfigPath of daemonConfigPaths) {
6121
6658
  try {
6122
- const daemonConfig = JSON.parse(readFileSync12(daemonConfigPath, "utf-8"));
6659
+ const daemonConfig = JSON.parse(readFileSync13(daemonConfigPath, "utf-8"));
6123
6660
  let changed = false;
6124
6661
  for (const field of daemonFields) {
6125
6662
  if (field in merged && merged[field] !== daemonConfig[field]) {
@@ -6159,7 +6696,7 @@ Path: ${projectPath}
6159
6696
  }
6160
6697
  }
6161
6698
  if (changed) {
6162
- writeFileSync12(daemonConfigPath, JSON.stringify(daemonConfig, null, 2) + "\n", "utf-8");
6699
+ writeFileSync13(daemonConfigPath, JSON.stringify(daemonConfig, null, 2) + "\n", "utf-8");
6163
6700
  }
6164
6701
  } catch {
6165
6702
  }
@@ -6172,10 +6709,10 @@ Path: ${projectPath}
6172
6709
  pattern: "/api/config/reset",
6173
6710
  handler: (body) => {
6174
6711
  const { keyPath } = body || {};
6175
- const configPath = join13(factoryDir, ".beastmode", "config.json");
6176
- const current = existsSync15(configPath) ? JSON.parse(readFileSync12(configPath, "utf-8")) : {};
6712
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
6713
+ const current = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : {};
6177
6714
  const result = configReset(current, keyPath);
6178
- writeFileSync12(configPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
6715
+ writeFileSync13(configPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
6179
6716
  return result;
6180
6717
  }
6181
6718
  },
@@ -6265,13 +6802,13 @@ Path: ${projectPath}
6265
6802
  handler: () => {
6266
6803
  const runsDir = getRunsDir(factoryDir);
6267
6804
  const retroDirs = [];
6268
- const rootRetroDir = join13(runsDir, ".retrospectives");
6269
- if (existsSync15(rootRetroDir)) retroDirs.push(rootRetroDir);
6270
- if (existsSync15(runsDir)) {
6271
- for (const entry of readdirSync6(runsDir)) {
6805
+ const rootRetroDir = join14(runsDir, ".retrospectives");
6806
+ if (existsSync16(rootRetroDir)) retroDirs.push(rootRetroDir);
6807
+ if (existsSync16(runsDir)) {
6808
+ for (const entry of readdirSync8(runsDir)) {
6272
6809
  if (entry === ".retrospectives" || entry.startsWith(".")) continue;
6273
- const projectRetroDir = join13(runsDir, entry, ".retrospectives");
6274
- if (existsSync15(projectRetroDir)) retroDirs.push(projectRetroDir);
6810
+ const projectRetroDir = join14(runsDir, entry, ".retrospectives");
6811
+ if (existsSync16(projectRetroDir)) retroDirs.push(projectRetroDir);
6275
6812
  }
6276
6813
  }
6277
6814
  if (retroDirs.length === 0) {
@@ -6280,9 +6817,9 @@ Path: ${projectPath}
6280
6817
  const retrospectives = [];
6281
6818
  const learnings = [];
6282
6819
  for (const retroDir of retroDirs) {
6283
- const retroFiles = readdirSync6(retroDir).filter((f) => f.startsWith("run-") && f.endsWith(".json")).sort().reverse();
6820
+ const retroFiles = readdirSync8(retroDir).filter((f) => f.startsWith("run-") && f.endsWith(".json")).sort().reverse();
6284
6821
  for (const file of retroFiles) {
6285
- const data = readJsonFile(join13(retroDir, file));
6822
+ const data = readJsonFile(join14(retroDir, file));
6286
6823
  if (!data) continue;
6287
6824
  const retro = data;
6288
6825
  retrospectives.push(retro);
@@ -6304,10 +6841,10 @@ Path: ${projectPath}
6304
6841
  let wisdom = "";
6305
6842
  let wisdomMeta = {};
6306
6843
  for (const retroDir of retroDirs) {
6307
- const wisdomPath = join13(retroDir, "wisdom.md");
6308
- if (existsSync15(wisdomPath)) {
6309
- wisdom = readFileSync12(wisdomPath, "utf-8");
6310
- wisdomMeta = readJsonFile(join13(retroDir, "wisdom-meta.json")) || {};
6844
+ const wisdomPath = join14(retroDir, "wisdom.md");
6845
+ if (existsSync16(wisdomPath)) {
6846
+ wisdom = readFileSync13(wisdomPath, "utf-8");
6847
+ wisdomMeta = readJsonFile(join14(retroDir, "wisdom-meta.json")) || {};
6311
6848
  break;
6312
6849
  }
6313
6850
  }
@@ -6319,11 +6856,11 @@ Path: ${projectPath}
6319
6856
  pattern: "/api/learnings/wisdom/refresh",
6320
6857
  handler: () => {
6321
6858
  const runsDir = getRunsDir(factoryDir);
6322
- const retroDir = join13(runsDir, ".retrospectives");
6323
- if (!existsSync15(retroDir)) {
6324
- mkdirSync11(retroDir, { recursive: true });
6859
+ const retroDir = join14(runsDir, ".retrospectives");
6860
+ if (!existsSync16(retroDir)) {
6861
+ mkdirSync12(retroDir, { recursive: true });
6325
6862
  }
6326
- writeFileSync12(join13(retroDir, ".refresh-requested"), (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
6863
+ writeFileSync13(join14(retroDir, ".refresh-requested"), (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
6327
6864
  return { success: true };
6328
6865
  }
6329
6866
  },
@@ -6332,29 +6869,29 @@ Path: ${projectPath}
6332
6869
  pattern: "/api/learnings/:runId/:index/dismiss",
6333
6870
  handler: (_body, params) => {
6334
6871
  const runsDir = getRunsDir(factoryDir);
6335
- const candidateDirs = [join13(runsDir, ".retrospectives")];
6336
- if (existsSync15(runsDir)) {
6337
- for (const entry of readdirSync6(runsDir)) {
6872
+ const candidateDirs = [join14(runsDir, ".retrospectives")];
6873
+ if (existsSync16(runsDir)) {
6874
+ for (const entry of readdirSync8(runsDir)) {
6338
6875
  if (entry === ".retrospectives" || entry.startsWith(".")) continue;
6339
- candidateDirs.push(join13(runsDir, entry, ".retrospectives"));
6876
+ candidateDirs.push(join14(runsDir, entry, ".retrospectives"));
6340
6877
  }
6341
6878
  }
6342
6879
  let filePath = null;
6343
6880
  for (const dir of candidateDirs) {
6344
- const candidate = join13(dir, `${params.runId}.json`);
6345
- if (existsSync15(candidate)) {
6881
+ const candidate = join14(dir, `${params.runId}.json`);
6882
+ if (existsSync16(candidate)) {
6346
6883
  filePath = candidate;
6347
6884
  break;
6348
6885
  }
6349
6886
  }
6350
6887
  if (!filePath) throw new Error(`Retrospective not found: ${params.runId}`);
6351
- const data = JSON.parse(readFileSync12(filePath, "utf-8"));
6888
+ const data = JSON.parse(readFileSync13(filePath, "utf-8"));
6352
6889
  const idx = parseInt(params.index, 10);
6353
6890
  if (!Array.isArray(data.learnings) || idx < 0 || idx >= data.learnings.length) {
6354
6891
  throw new Error(`Learning index out of range: ${idx}`);
6355
6892
  }
6356
6893
  data.learnings[idx].dismissed = true;
6357
- writeFileSync12(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
6894
+ writeFileSync13(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
6358
6895
  return { success: true };
6359
6896
  }
6360
6897
  },
@@ -6363,29 +6900,29 @@ Path: ${projectPath}
6363
6900
  pattern: "/api/learnings/:runId/:index/restore",
6364
6901
  handler: (_body, params) => {
6365
6902
  const runsDir = getRunsDir(factoryDir);
6366
- const candidateDirs = [join13(runsDir, ".retrospectives")];
6367
- if (existsSync15(runsDir)) {
6368
- for (const entry of readdirSync6(runsDir)) {
6903
+ const candidateDirs = [join14(runsDir, ".retrospectives")];
6904
+ if (existsSync16(runsDir)) {
6905
+ for (const entry of readdirSync8(runsDir)) {
6369
6906
  if (entry === ".retrospectives" || entry.startsWith(".")) continue;
6370
- candidateDirs.push(join13(runsDir, entry, ".retrospectives"));
6907
+ candidateDirs.push(join14(runsDir, entry, ".retrospectives"));
6371
6908
  }
6372
6909
  }
6373
6910
  let filePath = null;
6374
6911
  for (const dir of candidateDirs) {
6375
- const candidate = join13(dir, `${params.runId}.json`);
6376
- if (existsSync15(candidate)) {
6912
+ const candidate = join14(dir, `${params.runId}.json`);
6913
+ if (existsSync16(candidate)) {
6377
6914
  filePath = candidate;
6378
6915
  break;
6379
6916
  }
6380
6917
  }
6381
6918
  if (!filePath) throw new Error(`Retrospective not found: ${params.runId}`);
6382
- const data = JSON.parse(readFileSync12(filePath, "utf-8"));
6919
+ const data = JSON.parse(readFileSync13(filePath, "utf-8"));
6383
6920
  const idx = parseInt(params.index, 10);
6384
6921
  if (!Array.isArray(data.learnings) || idx < 0 || idx >= data.learnings.length) {
6385
6922
  throw new Error(`Learning index out of range: ${idx}`);
6386
6923
  }
6387
6924
  data.learnings[idx].dismissed = false;
6388
- writeFileSync12(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
6925
+ writeFileSync13(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
6389
6926
  return { success: true };
6390
6927
  }
6391
6928
  },
@@ -6528,22 +7065,22 @@ Path: ${projectPath}
6528
7065
  } catch {
6529
7066
  }
6530
7067
  try {
6531
- const runsDir = join13(factoryDir, "runs", project);
6532
- if (existsSync15(runsDir)) {
6533
- const runDirs = readdirSync6(runsDir).filter((d) => d.startsWith("run-"));
7068
+ const runsDir = join14(factoryDir, "runs", project);
7069
+ if (existsSync16(runsDir)) {
7070
+ const runDirs = readdirSync8(runsDir).filter((d) => d.startsWith("run-"));
6534
7071
  for (const runId of runDirs) {
6535
- const ckptPath = join13(runsDir, runId, "checkpoint.json");
7072
+ const ckptPath = join14(runsDir, runId, "checkpoint.json");
6536
7073
  let subtitle = "";
6537
7074
  let timestamp = "";
6538
7075
  try {
6539
- const st = statSync5(join13(runsDir, runId));
7076
+ const st = statSync6(join14(runsDir, runId));
6540
7077
  timestamp = st.mtime.toISOString();
6541
7078
  } catch {
6542
7079
  timestamp = (/* @__PURE__ */ new Date()).toISOString();
6543
7080
  }
6544
- if (existsSync15(ckptPath)) {
7081
+ if (existsSync16(ckptPath)) {
6545
7082
  try {
6546
- const ckpt = JSON.parse(readFileSync12(ckptPath, "utf-8"));
7083
+ const ckpt = JSON.parse(readFileSync13(ckptPath, "utf-8"));
6547
7084
  const history = ckpt.satisfaction_history;
6548
7085
  if (history && history.length > 0) {
6549
7086
  const latest = history[history.length - 1];
@@ -6651,8 +7188,8 @@ __export(server_exports, {
6651
7188
  });
6652
7189
  import { createServer } from "http";
6653
7190
  import { createHmac } from "crypto";
6654
- import { readFileSync as readFileSync13, existsSync as existsSync16 } from "fs";
6655
- import { join as join14, dirname as dirname6 } from "path";
7191
+ import { readFileSync as readFileSync14, existsSync as existsSync17 } from "fs";
7192
+ import { join as join15, dirname as dirname6 } from "path";
6656
7193
  import { fileURLToPath as fileURLToPath2 } from "url";
6657
7194
  import { WebSocketServer as WebSocketServer2, WebSocket as WsClient } from "ws";
6658
7195
  function proxyBoardWebSocket(req, socket, head, boardWsUrl) {
@@ -6694,16 +7231,16 @@ function proxyBoardWebSocket(req, socket, head, boardWsUrl) {
6694
7231
  });
6695
7232
  }
6696
7233
  function resolveStaticDir() {
6697
- const devPath = join14(__dirname, "static");
6698
- if (existsSync16(join14(devPath, "index.html"))) {
7234
+ const devPath = join15(__dirname, "static");
7235
+ if (existsSync17(join15(devPath, "index.html"))) {
6699
7236
  return devPath;
6700
7237
  }
6701
- const distWebPath = join14(__dirname, "web");
6702
- if (existsSync16(join14(distWebPath, "index.html"))) {
7238
+ const distWebPath = join15(__dirname, "web");
7239
+ if (existsSync17(join15(distWebPath, "index.html"))) {
6703
7240
  return distWebPath;
6704
7241
  }
6705
- const siblingPath = join14(__dirname, "..", "web");
6706
- if (existsSync16(join14(siblingPath, "index.html"))) {
7242
+ const siblingPath = join15(__dirname, "..", "web");
7243
+ if (existsSync17(join15(siblingPath, "index.html"))) {
6707
7244
  return siblingPath;
6708
7245
  }
6709
7246
  throw new Error(
@@ -6812,9 +7349,9 @@ async function startServer(options = {}) {
6812
7349
  const fallback = "<html><body><h1>BeastMode Init Wizard</h1><p>Static files not found.</p></body></html>";
6813
7350
  if (!staticDir) return fallback;
6814
7351
  try {
6815
- const indexPath = join14(staticDir, "index.html");
6816
- if (!existsSync16(indexPath)) return fallback;
6817
- return readFileSync13(indexPath, "utf-8");
7352
+ const indexPath = join15(staticDir, "index.html");
7353
+ if (!existsSync17(indexPath)) return fallback;
7354
+ return readFileSync14(indexPath, "utf-8");
6818
7355
  } catch {
6819
7356
  return fallback;
6820
7357
  }
@@ -6822,9 +7359,9 @@ async function startServer(options = {}) {
6822
7359
  function loadBoardHtml() {
6823
7360
  try {
6824
7361
  const dir = staticDir || resolveStaticDir();
6825
- const boardPath = join14(dir, "board.html");
6826
- if (!existsSync16(boardPath)) return null;
6827
- return readFileSync13(boardPath, "utf-8");
7362
+ const boardPath = join15(dir, "board.html");
7363
+ if (!existsSync17(boardPath)) return null;
7364
+ return readFileSync14(boardPath, "utf-8");
6828
7365
  } catch {
6829
7366
  return null;
6830
7367
  }
@@ -6910,13 +7447,13 @@ async function startServer(options = {}) {
6910
7447
  let commit_sha = null;
6911
7448
  try {
6912
7449
  const dir = staticDir || resolveStaticDir();
6913
- const stampPath = join14(dir, "build-stamp.txt");
6914
- if (existsSync16(stampPath)) {
6915
- stamp = readFileSync13(stampPath, "utf-8").trim();
7450
+ const stampPath = join15(dir, "build-stamp.txt");
7451
+ if (existsSync17(stampPath)) {
7452
+ stamp = readFileSync14(stampPath, "utf-8").trim();
6916
7453
  }
6917
- const commitPath = join14(dir, "build-commit.txt");
6918
- if (existsSync16(commitPath)) {
6919
- commit_sha = readFileSync13(commitPath, "utf-8").trim() || null;
7454
+ const commitPath = join15(dir, "build-commit.txt");
7455
+ if (existsSync17(commitPath)) {
7456
+ commit_sha = readFileSync14(commitPath, "utf-8").trim() || null;
6920
7457
  }
6921
7458
  } catch {
6922
7459
  }
@@ -6994,8 +7531,8 @@ async function startServer(options = {}) {
6994
7531
  }
6995
7532
  if (method === "GET" && url.startsWith("/static/")) {
6996
7533
  const filename = url.slice("/static/".length);
6997
- const filePath = staticDir ? join14(staticDir, filename) : "";
6998
- if (filePath && existsSync16(filePath)) {
7534
+ const filePath = staticDir ? join15(staticDir, filename) : "";
7535
+ if (filePath && existsSync17(filePath)) {
6999
7536
  const ext = filename.split(".").pop()?.toLowerCase();
7000
7537
  const mimeTypes = {
7001
7538
  png: "image/png",
@@ -7006,7 +7543,7 @@ async function startServer(options = {}) {
7006
7543
  ico: "image/x-icon"
7007
7544
  };
7008
7545
  const contentType = mimeTypes[ext || ""] || "application/octet-stream";
7009
- const content = readFileSync13(filePath);
7546
+ const content = readFileSync14(filePath);
7010
7547
  res.writeHead(200, {
7011
7548
  "Content-Type": contentType,
7012
7549
  "Content-Length": content.length.toString(),
@@ -7255,30 +7792,29 @@ var init_server = __esm({
7255
7792
 
7256
7793
  // src/cli/commands/board.ts
7257
7794
  import { Command } from "commander";
7258
- import { resolve as resolve5, join as join15 } from "path";
7259
- import { existsSync as existsSync17, readFileSync as readFileSync14, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13, readdirSync as readdirSync7 } from "fs";
7795
+ import { resolve as resolve5, join as join16 } from "path";
7796
+ import { existsSync as existsSync18, readFileSync as readFileSync15, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14 } from "fs";
7260
7797
  import { execSync as execSync4 } from "child_process";
7261
7798
  function findFactoryDir(startDir) {
7262
7799
  let dir = startDir || process.cwd();
7263
7800
  const root = resolve5("/");
7264
7801
  while (dir !== root) {
7265
- if (existsSync17(join15(dir, ".beastmode", "factory.json"))) {
7802
+ if (existsSync18(join16(dir, ".beastmode", "factory.json"))) {
7266
7803
  return dir;
7267
7804
  }
7268
7805
  const parent = resolve5(dir, "..");
7269
7806
  if (parent === dir) break;
7270
7807
  dir = parent;
7271
7808
  }
7272
- if (existsSync17(join15(dir, ".beastmode", "factory.json"))) {
7809
+ if (existsSync18(join16(dir, ".beastmode", "factory.json"))) {
7273
7810
  return dir;
7274
7811
  }
7275
7812
  return null;
7276
7813
  }
7277
7814
  function inferProjectName(factoryDir) {
7278
- const projectsDir = join15(factoryDir, ".beastmode", "projects");
7279
- if (!existsSync17(projectsDir)) return null;
7280
- const files = readdirSync7(projectsDir).filter((f) => f.endsWith(".json"));
7281
- if (files.length === 1) return files[0].replace(/\.json$/, "");
7815
+ const projectsDir = join16(factoryDir, ".beastmode", "projects");
7816
+ const records = listProjectRecords(projectsDir);
7817
+ if (records.length === 1) return records[0].name;
7282
7818
  return null;
7283
7819
  }
7284
7820
  function tryExecSync(cmd, timeoutMs = 15e3) {
@@ -7355,13 +7891,13 @@ async function runBoard(opts) {
7355
7891
  let factoryDir = findFactoryDir();
7356
7892
  if (!factoryDir) {
7357
7893
  factoryDir = process.cwd();
7358
- const bmDir = join15(factoryDir, ".beastmode");
7359
- if (!existsSync17(bmDir)) {
7360
- mkdirSync12(bmDir, { recursive: true });
7894
+ const bmDir = join16(factoryDir, ".beastmode");
7895
+ if (!existsSync18(bmDir)) {
7896
+ mkdirSync13(bmDir, { recursive: true });
7361
7897
  }
7362
- const factoryJsonPath2 = join15(bmDir, "factory.json");
7363
- if (!existsSync17(factoryJsonPath2)) {
7364
- writeFileSync13(
7898
+ const factoryJsonPath2 = join16(bmDir, "factory.json");
7899
+ if (!existsSync18(factoryJsonPath2)) {
7900
+ writeFileSync14(
7365
7901
  factoryJsonPath2,
7366
7902
  JSON.stringify({ factory_name: "BeastMode", created_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
7367
7903
  "utf-8"
@@ -7369,8 +7905,8 @@ async function runBoard(opts) {
7369
7905
  info("No factory found \u2014 created minimal stub at .beastmode/factory.json");
7370
7906
  }
7371
7907
  }
7372
- const factoryJsonPath = join15(factoryDir, ".beastmode", "factory.json");
7373
- const factoryJson = JSON.parse(readFileSync14(factoryJsonPath, "utf-8"));
7908
+ const factoryJsonPath = join16(factoryDir, ".beastmode", "factory.json");
7909
+ const factoryJson = JSON.parse(readFileSync15(factoryJsonPath, "utf-8"));
7374
7910
  const factoryName = factoryJson.factory_name || "BeastMode Factory";
7375
7911
  header(`BeastMode Board \u2014 ${factoryName}`);
7376
7912
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
@@ -7409,6 +7945,7 @@ var init_board = __esm({
7409
7945
  "src/cli/commands/board.ts"() {
7410
7946
  "use strict";
7411
7947
  init_display();
7948
+ init_engine();
7412
7949
  boardCommand = new Command("board").description("Launch the BeastMode Board web UI").option("--port <number>", "Port to serve on", "7669").option("--host <host>", "Host to bind to (use 0.0.0.0 for external access)", "127.0.0.1").action(async (opts) => {
7413
7950
  try {
7414
7951
  await runBoard(opts);
@@ -7442,26 +7979,26 @@ __export(sync_claude_creds_exports, {
7442
7979
  });
7443
7980
  import { Command as Command2 } from "commander";
7444
7981
  import { execSync as execSync5, spawnSync as spawnSync2 } from "child_process";
7445
- import { writeFileSync as writeFileSync14, readFileSync as readFileSync15, chmodSync, mkdirSync as mkdirSync13, existsSync as existsSync18, unlinkSync as unlinkSync4 } from "fs";
7446
- import { join as join16 } from "path";
7982
+ import { writeFileSync as writeFileSync15, readFileSync as readFileSync16, chmodSync, mkdirSync as mkdirSync14, existsSync as existsSync19, unlinkSync as unlinkSync4 } from "fs";
7983
+ import { join as join17 } from "path";
7447
7984
  import { homedir as homedir2, platform } from "os";
7448
7985
  function systemdUserDir() {
7449
- return join16(homedir2(), ".config", "systemd", "user");
7986
+ return join17(homedir2(), ".config", "systemd", "user");
7450
7987
  }
7451
7988
  function systemdServicePath() {
7452
- return join16(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.service`);
7989
+ return join17(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.service`);
7453
7990
  }
7454
7991
  function systemdTimerPath() {
7455
- return join16(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.timer`);
7992
+ return join17(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.timer`);
7456
7993
  }
7457
7994
  function linuxCredsPath() {
7458
- return join16(homedir2(), ".claude", ".credentials.json");
7995
+ return join17(homedir2(), ".claude", ".credentials.json");
7459
7996
  }
7460
7997
  function plistPath() {
7461
- return join16(homedir2(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
7998
+ return join17(homedir2(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
7462
7999
  }
7463
8000
  function agentLogPath() {
7464
- return join16(homedir2(), ".beastmode", "logs", "sync-claude-creds.log");
8001
+ return join17(homedir2(), ".beastmode", "logs", "sync-claude-creds.log");
7465
8002
  }
7466
8003
  function readKeychainTokenSafe() {
7467
8004
  try {
@@ -7493,10 +8030,10 @@ function writeCredentialsFile(rawJson) {
7493
8030
  if (!oauth?.accessToken) {
7494
8031
  throw new Error("Keychain entry missing claudeAiOauth.accessToken. Fix: run `claude login` again to reset the credential.");
7495
8032
  }
7496
- const claudeDir = join16(homedir2(), ".claude");
7497
- if (!existsSync18(claudeDir)) mkdirSync13(claudeDir, { recursive: true });
7498
- const credsPath = join16(claudeDir, ".credentials.json");
7499
- writeFileSync14(credsPath, rawJson + "\n", "utf-8");
8033
+ const claudeDir = join17(homedir2(), ".claude");
8034
+ if (!existsSync19(claudeDir)) mkdirSync14(claudeDir, { recursive: true });
8035
+ const credsPath = join17(claudeDir, ".credentials.json");
8036
+ writeFileSync15(credsPath, rawJson + "\n", "utf-8");
7500
8037
  chmodSync(credsPath, 384);
7501
8038
  if (oauth.expiresAt) {
7502
8039
  const hoursLeft = Math.round((oauth.expiresAt - Date.now()) / 36e5);
@@ -7509,7 +8046,7 @@ function writeCredentialsFile(rawJson) {
7509
8046
  return credsPath;
7510
8047
  }
7511
8048
  function urgencyMarkerHostPath(factoryDir) {
7512
- return join16(factoryDir, "daemon", "logs", ".cred-refresh-needed");
8049
+ return join17(factoryDir, "daemon", "logs", ".cred-refresh-needed");
7513
8050
  }
7514
8051
  function removeUrgencyMarker(factoryDir) {
7515
8052
  if (!factoryDir) return;
@@ -7523,7 +8060,7 @@ function buildPlist(intervalSeconds, factoryDir) {
7523
8060
  const nodePath = process.execPath;
7524
8061
  const cliEntry = process.argv[1];
7525
8062
  const logPath = agentLogPath();
7526
- const keychainPath = join16(homedir2(), "Library", "Keychains", "login.keychain-db");
8063
+ const keychainPath = join17(homedir2(), "Library", "Keychains", "login.keychain-db");
7527
8064
  const watchPaths = [keychainPath];
7528
8065
  if (factoryDir) {
7529
8066
  watchPaths.push(urgencyMarkerHostPath(factoryDir));
@@ -7563,15 +8100,15 @@ function installAgent(intervalSeconds, {
7563
8100
  } = {}) {
7564
8101
  const plist = plistPath();
7565
8102
  const logPath = agentLogPath();
7566
- mkdirSync13(join16(homedir2(), "Library", "LaunchAgents"), { recursive: true });
7567
- mkdirSync13(join16(homedir2(), ".beastmode", "logs"), { recursive: true });
8103
+ mkdirSync14(join17(homedir2(), "Library", "LaunchAgents"), { recursive: true });
8104
+ mkdirSync14(join17(homedir2(), ".beastmode", "logs"), { recursive: true });
7568
8105
  const uid = process.getuid?.();
7569
- if (existsSync18(plist)) {
8106
+ if (existsSync19(plist)) {
7570
8107
  spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], { stdio: "pipe" });
7571
8108
  }
7572
8109
  const resolvedFactory = factoryDir ?? findFactoryDir() ?? void 0;
7573
- writeFileSync14(plist, buildPlist(intervalSeconds, resolvedFactory), "utf-8");
7574
- writeFileSync14(logPath, "", { flag: "a" });
8110
+ writeFileSync15(plist, buildPlist(intervalSeconds, resolvedFactory), "utf-8");
8111
+ writeFileSync15(logPath, "", { flag: "a" });
7575
8112
  const result = spawnSync2("launchctl", ["bootstrap", `gui/${uid}`, plist], {
7576
8113
  stdio: "pipe",
7577
8114
  encoding: "utf-8"
@@ -7609,7 +8146,7 @@ function syncClaudeCredsOnce() {
7609
8146
  function uninstallAgent() {
7610
8147
  const plist = plistPath();
7611
8148
  const uid = process.getuid?.();
7612
- if (!existsSync18(plist)) {
8149
+ if (!existsSync19(plist)) {
7613
8150
  info("No LaunchAgent installed \u2014 nothing to remove.");
7614
8151
  return;
7615
8152
  }
@@ -7629,7 +8166,7 @@ function uninstallAgent() {
7629
8166
  function showStatus() {
7630
8167
  const plist = plistPath();
7631
8168
  const uid = process.getuid?.();
7632
- if (!existsSync18(plist)) {
8169
+ if (!existsSync19(plist)) {
7633
8170
  info("LaunchAgent not installed.");
7634
8171
  info("Install with: beastmode sync-claude-creds --install");
7635
8172
  return;
@@ -7653,14 +8190,14 @@ function showStatus() {
7653
8190
  }
7654
8191
  function syncClaudeCredsLinux() {
7655
8192
  const credsPath = linuxCredsPath();
7656
- if (!existsSync18(credsPath)) {
8193
+ if (!existsSync19(credsPath)) {
7657
8194
  return {
7658
8195
  error: `${credsPath} not found \u2014 run \`claude login\` first`
7659
8196
  };
7660
8197
  }
7661
8198
  let parsed;
7662
8199
  try {
7663
- const raw = readFileSync15(credsPath, "utf-8");
8200
+ const raw = readFileSync16(credsPath, "utf-8");
7664
8201
  parsed = JSON.parse(raw);
7665
8202
  } catch {
7666
8203
  return {
@@ -7689,7 +8226,7 @@ function syncClaudeCredsLinux() {
7689
8226
  );
7690
8227
  }
7691
8228
  try {
7692
- const raw2 = readFileSync15(credsPath, "utf-8");
8229
+ const raw2 = readFileSync16(credsPath, "utf-8");
7693
8230
  const parsed2 = JSON.parse(raw2);
7694
8231
  if (parsed2.claudeAiOauth?.expiresAt) {
7695
8232
  const newMinutes = (parsed2.claudeAiOauth.expiresAt - Date.now()) / 6e4;
@@ -7743,18 +8280,18 @@ function installAgentLinux(intervalSeconds, { throwOnError = false } = {}) {
7743
8280
  process.exit(1);
7744
8281
  }
7745
8282
  const unitDir = systemdUserDir();
7746
- const logDir = join16(homedir2(), ".beastmode", "logs");
7747
- mkdirSync13(unitDir, { recursive: true });
7748
- mkdirSync13(logDir, { recursive: true });
7749
- const logPath = join16(logDir, "sync-claude-creds.log");
8283
+ const logDir = join17(homedir2(), ".beastmode", "logs");
8284
+ mkdirSync14(unitDir, { recursive: true });
8285
+ mkdirSync14(logDir, { recursive: true });
8286
+ const logPath = join17(logDir, "sync-claude-creds.log");
7750
8287
  const nodePath = process.execPath;
7751
8288
  const cliEntry = process.argv[1];
7752
- writeFileSync14(
8289
+ writeFileSync15(
7753
8290
  systemdServicePath(),
7754
8291
  buildServiceUnit(nodePath, cliEntry, logPath),
7755
8292
  "utf-8"
7756
8293
  );
7757
- writeFileSync14(
8294
+ writeFileSync15(
7758
8295
  systemdTimerPath(),
7759
8296
  buildTimerUnit(intervalSeconds),
7760
8297
  "utf-8"
@@ -7805,14 +8342,14 @@ function uninstallAgentLinux() {
7805
8342
  const servicePath = systemdServicePath();
7806
8343
  const timerPath = systemdTimerPath();
7807
8344
  let removed = false;
7808
- if (existsSync18(servicePath)) {
8345
+ if (existsSync19(servicePath)) {
7809
8346
  try {
7810
8347
  unlinkSync4(servicePath);
7811
8348
  removed = true;
7812
8349
  } catch {
7813
8350
  }
7814
8351
  }
7815
- if (existsSync18(timerPath)) {
8352
+ if (existsSync19(timerPath)) {
7816
8353
  try {
7817
8354
  unlinkSync4(timerPath);
7818
8355
  removed = true;
@@ -7832,7 +8369,7 @@ function statusAgentLinux() {
7832
8369
  error(unavail);
7833
8370
  return;
7834
8371
  }
7835
- if (!existsSync18(systemdTimerPath())) {
8372
+ if (!existsSync19(systemdTimerPath())) {
7836
8373
  info("Systemd timer not installed.");
7837
8374
  info("Install with: beastmode sync-claude-creds --install");
7838
8375
  return;
@@ -7991,26 +8528,26 @@ var init_sync_claude_creds = __esm({
7991
8528
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7992
8529
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7993
8530
  import { z as z2 } from "zod";
7994
- import { readFileSync as readFileSync28, writeFileSync as writeFileSync24, existsSync as existsSync31, readdirSync as readdirSync11, mkdirSync as mkdirSync19 } from "fs";
7995
- import { join as join29, resolve as resolve18, basename as basename5 } from "path";
8531
+ import { readFileSync as readFileSync29, writeFileSync as writeFileSync25, existsSync as existsSync32, readdirSync as readdirSync12 } from "fs";
8532
+ import { join as join30, resolve as resolve18, basename as basename6 } from "path";
7996
8533
  import { randomUUID as randomUUID4 } from "crypto";
7997
8534
  function readJsonFile2(filePath) {
7998
- if (!existsSync31(filePath)) return null;
8535
+ if (!existsSync32(filePath)) return null;
7999
8536
  try {
8000
- return JSON.parse(readFileSync28(filePath, "utf-8"));
8537
+ return JSON.parse(readFileSync29(filePath, "utf-8"));
8001
8538
  } catch {
8002
8539
  return null;
8003
8540
  }
8004
8541
  }
8005
8542
  function getFactoryPath() {
8006
8543
  const envPath = process.env.BEASTMODE_FACTORY_PATH;
8007
- if (envPath && existsSync31(join29(envPath, ".beastmode", "factory.json"))) {
8544
+ if (envPath && existsSync32(join30(envPath, ".beastmode", "factory.json"))) {
8008
8545
  return envPath;
8009
8546
  }
8010
8547
  let dir = process.cwd();
8011
8548
  const root = resolve18("/");
8012
8549
  while (dir !== root) {
8013
- if (existsSync31(join29(dir, ".beastmode", "factory.json"))) {
8550
+ if (existsSync32(join30(dir, ".beastmode", "factory.json"))) {
8014
8551
  return dir;
8015
8552
  }
8016
8553
  const parent = resolve18(dir, "..");
@@ -8022,17 +8559,17 @@ function getFactoryPath() {
8022
8559
  );
8023
8560
  }
8024
8561
  function readFactoryStatus(factoryDir) {
8025
- const bmDir = join29(factoryDir, ".beastmode");
8562
+ const bmDir = join30(factoryDir, ".beastmode");
8026
8563
  const factoryIdentity = FactoryIdentitySchema.parse(
8027
- JSON.parse(readFileSync28(join29(bmDir, "factory.json"), "utf-8"))
8564
+ JSON.parse(readFileSync29(join30(bmDir, "factory.json"), "utf-8"))
8028
8565
  );
8029
- const projectsDir = join29(bmDir, "projects");
8030
- const projectCount = existsSync31(projectsDir) ? readdirSync11(projectsDir).filter((f) => f.endsWith(".json")).length : 0;
8031
- const lockPath = join29(bmDir, "extensions.lock");
8566
+ const projectsDir = join30(bmDir, "projects");
8567
+ const projectCount = listProjectRecords(projectsDir).length;
8568
+ const lockPath = join30(bmDir, "extensions.lock");
8032
8569
  let pluginNames = [];
8033
- if (existsSync31(lockPath)) {
8570
+ if (existsSync32(lockPath)) {
8034
8571
  try {
8035
- const lock = JSON.parse(readFileSync28(lockPath, "utf-8"));
8572
+ const lock = JSON.parse(readFileSync29(lockPath, "utf-8"));
8036
8573
  pluginNames = Object.keys(lock.plugins || {});
8037
8574
  } catch {
8038
8575
  }
@@ -8052,23 +8589,23 @@ function readFactoryStatus(factoryDir) {
8052
8589
  skillCount = listSkills(factoryDir).length;
8053
8590
  } catch {
8054
8591
  }
8055
- const runsDir = join29(factoryDir, "runs");
8592
+ const runsDir = join30(factoryDir, "runs");
8056
8593
  let runDirs = [];
8057
- if (existsSync31(runsDir)) {
8058
- runDirs = readdirSync11(runsDir).filter((d) => {
8594
+ if (existsSync32(runsDir)) {
8595
+ runDirs = readdirSync12(runsDir).filter((d) => {
8059
8596
  try {
8060
- return readdirSync11(join29(runsDir, d)).length > 0;
8597
+ return readdirSync12(join30(runsDir, d)).length > 0;
8061
8598
  } catch {
8062
8599
  return false;
8063
8600
  }
8064
8601
  }).sort();
8065
8602
  }
8066
- const pidFile = join29(bmDir, "daemon.pid");
8603
+ const pidFile = join30(bmDir, "daemon.pid");
8067
8604
  let daemonPid = null;
8068
8605
  let pidAlive = false;
8069
- if (existsSync31(pidFile)) {
8606
+ if (existsSync32(pidFile)) {
8070
8607
  try {
8071
- daemonPid = parseInt(readFileSync28(pidFile, "utf-8").trim(), 10);
8608
+ daemonPid = parseInt(readFileSync29(pidFile, "utf-8").trim(), 10);
8072
8609
  process.kill(daemonPid, 0);
8073
8610
  pidAlive = true;
8074
8611
  } catch {
@@ -8089,18 +8626,18 @@ function readFactoryStatus(factoryDir) {
8089
8626
  return collectStatus(input);
8090
8627
  }
8091
8628
  function readBoardItems(factoryDir) {
8092
- const filePath = join29(factoryDir, ".beastmode", "board.json");
8093
- if (!existsSync31(filePath)) return [];
8629
+ const filePath = join30(factoryDir, ".beastmode", "board.json");
8630
+ if (!existsSync32(filePath)) return [];
8094
8631
  try {
8095
- const raw = JSON.parse(readFileSync28(filePath, "utf-8"));
8632
+ const raw = JSON.parse(readFileSync29(filePath, "utf-8"));
8096
8633
  return Array.isArray(raw.items) ? raw.items : [];
8097
8634
  } catch {
8098
8635
  return [];
8099
8636
  }
8100
8637
  }
8101
8638
  function writeBoardItems(factoryDir, items) {
8102
- const filePath = join29(factoryDir, ".beastmode", "board.json");
8103
- writeFileSync24(filePath, JSON.stringify({ items }, null, 2) + "\n", "utf-8");
8639
+ const filePath = join30(factoryDir, ".beastmode", "board.json");
8640
+ writeFileSync25(filePath, JSON.stringify({ items }, null, 2) + "\n", "utf-8");
8104
8641
  }
8105
8642
  function createMcpServer() {
8106
8643
  const server = new McpServer(
@@ -8123,8 +8660,8 @@ function createMcpServer() {
8123
8660
  { key_path: z2.string().describe("Dot-notation key path") },
8124
8661
  async ({ key_path }) => {
8125
8662
  const factoryDir = getFactoryPath();
8126
- const configPath = join29(factoryDir, ".beastmode", "config.json");
8127
- const config = existsSync31(configPath) ? JSON.parse(readFileSync28(configPath, "utf-8")) : generateDefaults();
8663
+ const configPath = join30(factoryDir, ".beastmode", "config.json");
8664
+ const config = existsSync32(configPath) ? JSON.parse(readFileSync29(configPath, "utf-8")) : generateDefaults();
8128
8665
  try {
8129
8666
  const value = configGet(config, key_path);
8130
8667
  return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
@@ -8142,11 +8679,11 @@ function createMcpServer() {
8142
8679
  },
8143
8680
  async ({ key_path, value }) => {
8144
8681
  const factoryDir = getFactoryPath();
8145
- const configPath = join29(factoryDir, ".beastmode", "config.json");
8146
- const config = existsSync31(configPath) ? JSON.parse(readFileSync28(configPath, "utf-8")) : generateDefaults();
8682
+ const configPath = join30(factoryDir, ".beastmode", "config.json");
8683
+ const config = existsSync32(configPath) ? JSON.parse(readFileSync29(configPath, "utf-8")) : generateDefaults();
8147
8684
  const coerced = coerceValue(value);
8148
8685
  const updated = configSet(config, key_path, coerced);
8149
- writeFileSync24(configPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
8686
+ writeFileSync25(configPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
8150
8687
  return { content: [{ type: "text", text: `Set ${key_path} = ${JSON.stringify(coerced)}` }] };
8151
8688
  }
8152
8689
  );
@@ -8180,14 +8717,14 @@ function createMcpServer() {
8180
8717
  {},
8181
8718
  async () => {
8182
8719
  const factoryDir = getFactoryPath();
8183
- const runsDir = join29(factoryDir, "runs");
8184
- if (!existsSync31(runsDir)) {
8720
+ const runsDir = join30(factoryDir, "runs");
8721
+ if (!existsSync32(runsDir)) {
8185
8722
  return { content: [{ type: "text", text: "No runs directory found." }] };
8186
8723
  }
8187
- const runDirs = readdirSync11(runsDir).sort().reverse();
8724
+ const runDirs = readdirSync12(runsDir).sort().reverse();
8188
8725
  const activeRuns = [];
8189
8726
  for (const id of runDirs.slice(0, 10)) {
8190
- const cp = readJsonFile2(join29(runsDir, id, "checkpoint.json"));
8727
+ const cp = readJsonFile2(join30(runsDir, id, "checkpoint.json"));
8191
8728
  if (cp) {
8192
8729
  activeRuns.push({ id, checkpoint: cp });
8193
8730
  }
@@ -8201,17 +8738,17 @@ function createMcpServer() {
8201
8738
  { run_id: z2.string().describe("Run ID (directory name)") },
8202
8739
  async ({ run_id }) => {
8203
8740
  const factoryDir = getFactoryPath();
8204
- const runDir = join29(factoryDir, "runs", run_id);
8205
- if (!existsSync31(runDir)) {
8741
+ const runDir = join30(factoryDir, "runs", run_id);
8742
+ if (!existsSync32(runDir)) {
8206
8743
  return { content: [{ type: "text", text: `Run not found: ${run_id}` }], isError: true };
8207
8744
  }
8208
- const manifest = readJsonFile2(join29(runDir, "manifest.json"));
8209
- const checkpoint = readJsonFile2(join29(runDir, "checkpoint.json"));
8210
- const iterationsDir = join29(runDir, "iterations");
8745
+ const manifest = readJsonFile2(join30(runDir, "manifest.json"));
8746
+ const checkpoint = readJsonFile2(join30(runDir, "checkpoint.json"));
8747
+ const iterationsDir = join30(runDir, "iterations");
8211
8748
  const iterations = [];
8212
- if (existsSync31(iterationsDir)) {
8213
- for (const dir of readdirSync11(iterationsDir).sort()) {
8214
- const satisfaction = readJsonFile2(join29(iterationsDir, dir, "satisfaction.json"));
8749
+ if (existsSync32(iterationsDir)) {
8750
+ for (const dir of readdirSync12(iterationsDir).sort()) {
8751
+ const satisfaction = readJsonFile2(join30(iterationsDir, dir, "satisfaction.json"));
8215
8752
  iterations.push({ number: parseInt(dir, 10), satisfaction });
8216
8753
  }
8217
8754
  }
@@ -8257,12 +8794,12 @@ function createMcpServer() {
8257
8794
  {},
8258
8795
  async () => {
8259
8796
  const factoryDir = getFactoryPath();
8260
- const bmDir = join29(factoryDir, ".beastmode");
8797
+ const bmDir = join30(factoryDir, ".beastmode");
8261
8798
  let plugins = {};
8262
- const lockPath = join29(bmDir, "extensions.lock");
8263
- if (existsSync31(lockPath)) {
8799
+ const lockPath = join30(bmDir, "extensions.lock");
8800
+ if (existsSync32(lockPath)) {
8264
8801
  try {
8265
- const lock = JSON.parse(readFileSync28(lockPath, "utf-8"));
8802
+ const lock = JSON.parse(readFileSync29(lockPath, "utf-8"));
8266
8803
  plugins = lock.plugins || {};
8267
8804
  } catch {
8268
8805
  }
@@ -8296,17 +8833,8 @@ function createMcpServer() {
8296
8833
  {},
8297
8834
  async () => {
8298
8835
  const factoryDir = getFactoryPath();
8299
- const projectsDir = join29(factoryDir, ".beastmode", "projects");
8300
- if (!existsSync31(projectsDir)) {
8301
- return { content: [{ type: "text", text: "[]" }] };
8302
- }
8303
- const projects = readdirSync11(projectsDir).filter((f) => f.endsWith(".json")).map((f) => {
8304
- try {
8305
- return JSON.parse(readFileSync28(join29(projectsDir, f), "utf-8"));
8306
- } catch {
8307
- return null;
8308
- }
8309
- }).filter(Boolean);
8836
+ const projectsDir = join30(factoryDir, ".beastmode", "projects");
8837
+ const projects = listProjectRecords(projectsDir);
8310
8838
  return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }] };
8311
8839
  }
8312
8840
  );
@@ -8317,34 +8845,25 @@ function createMcpServer() {
8317
8845
  async ({ path: projectPath }) => {
8318
8846
  const factoryDir = getFactoryPath();
8319
8847
  const resolvedPath = resolve18(projectPath);
8320
- if (!existsSync31(resolvedPath)) {
8848
+ if (!existsSync32(resolvedPath)) {
8321
8849
  return { content: [{ type: "text", text: `Directory not found: ${resolvedPath}` }], isError: true };
8322
8850
  }
8323
- const projectName = basename5(resolvedPath);
8851
+ const projectName = basename6(resolvedPath);
8324
8852
  const stack = detectStack(resolvedPath);
8325
- const projectConfig = {
8853
+ const projectsDir = join30(factoryDir, ".beastmode", "projects");
8854
+ let verifyPort = 3001;
8855
+ for (const existing of listProjectRecords(projectsDir)) {
8856
+ const port = existing.deploy?.verify_port;
8857
+ if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
8858
+ }
8859
+ const record = createProjectRecord({
8326
8860
  name: projectName,
8327
- path: resolvedPath,
8328
- repo: stack.git_remote || void 0,
8329
- stack: {
8330
- detected: stack.framework,
8331
- build_command: stack.suggested_commands.build,
8332
- dev_command: stack.suggested_commands.dev,
8333
- test_command: stack.suggested_commands.test,
8334
- install_command: stack.suggested_commands.install,
8335
- dev_port: stack.dev_port
8336
- },
8337
- deploy: { target: stack.suggested_deploy },
8338
- plugins: stack.suggested_plugins
8339
- };
8340
- const projectsDir = join29(factoryDir, ".beastmode", "projects");
8341
- mkdirSync19(projectsDir, { recursive: true });
8342
- writeFileSync24(
8343
- join29(projectsDir, `${projectName}.json`),
8344
- JSON.stringify(projectConfig, null, 2) + "\n",
8345
- "utf-8"
8346
- );
8347
- return { content: [{ type: "text", text: JSON.stringify(projectConfig, null, 2) }] };
8861
+ resolvedPath,
8862
+ gitRemote: stack.git_remote || void 0,
8863
+ verifyPort
8864
+ });
8865
+ writeProjectRecord(projectsDir, projectName, record);
8866
+ return { content: [{ type: "text", text: JSON.stringify(record, null, 2) }] };
8348
8867
  }
8349
8868
  );
8350
8869
  server.tool(
@@ -8447,17 +8966,17 @@ init_engine();
8447
8966
  init_file_writer();
8448
8967
  import { Command as Command3 } from "commander";
8449
8968
  import inquirer from "inquirer";
8450
- import { resolve as resolve6, basename as basename4, join as join17 } from "path";
8451
- import { existsSync as existsSync19, writeFileSync as writeFileSync15, mkdirSync as mkdirSync14, readFileSync as readFileSync16 } from "fs";
8969
+ import { resolve as resolve6, basename as basename5, join as join18 } from "path";
8970
+ import { existsSync as existsSync20, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync17 } from "fs";
8452
8971
 
8453
8972
  // src/cli/utils/docker.ts
8454
- import { existsSync as existsSync9 } from "fs";
8455
- import { join as join8 } from "path";
8973
+ import { existsSync as existsSync10 } from "fs";
8974
+ import { join as join9 } from "path";
8456
8975
  import { execSync } from "child_process";
8457
8976
  var GHCR_IMAGE_PREFIX = "ghcr.io/develeap/beastmode";
8458
8977
  function findComposeFile(dir) {
8459
- const path = join8(dir, "docker-compose.yml");
8460
- return existsSync9(path) ? path : null;
8978
+ const path = join9(dir, "docker-compose.yml");
8979
+ return existsSync10(path) ? path : null;
8461
8980
  }
8462
8981
  function requireComposeFile(dir) {
8463
8982
  const path = findComposeFile(dir);
@@ -8520,7 +9039,7 @@ function loginToGhcr(token) {
8520
9039
  }
8521
9040
  }
8522
9041
  function seedConfigFromImage(targetDir, tag) {
8523
- const configDir = join8(targetDir, "config");
9042
+ const configDir = join9(targetDir, "config");
8524
9043
  const containerName = "bm-config-seed";
8525
9044
  try {
8526
9045
  try {
@@ -8700,15 +9219,15 @@ function collect(val, acc) {
8700
9219
  return acc;
8701
9220
  }
8702
9221
  function readSecretFile(filePath) {
8703
- return readFileSync16(resolve6(filePath), "utf-8").trim();
9222
+ return readFileSync17(resolve6(filePath), "utf-8").trim();
8704
9223
  }
8705
9224
  function isSourceRepo(dir) {
8706
- return existsSync19(resolve6(dir, "daemon")) && existsSync19(resolve6(dir, "board")) && existsSync19(resolve6(dir, "cli"));
9225
+ return existsSync20(resolve6(dir, "daemon")) && existsSync20(resolve6(dir, "board")) && existsSync20(resolve6(dir, "cli"));
8707
9226
  }
8708
9227
  async function runInit(name, opts) {
8709
9228
  if (opts.ui) {
8710
9229
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
8711
- const factoryName2 = name || basename4(resolve6("."));
9230
+ const factoryName2 = name || basename5(resolve6("."));
8712
9231
  const projectPath2 = opts.project ? resolve6(opts.project) : void 0;
8713
9232
  info("Starting init wizard...");
8714
9233
  const uiServer = await startServer2({
@@ -8741,8 +9260,8 @@ async function runInit(name, opts) {
8741
9260
  const totalSteps = 5;
8742
9261
  header("BeastMode \u2014 Dark Factory Init");
8743
9262
  info("Creating your Custom Dark Factory\n");
8744
- const factoryName = name || basename4(resolve6("."));
8745
- if (existsSync19(factoryName) && existsSync19(resolve6(factoryName, ".beastmode"))) {
9263
+ const factoryName = name || basename5(resolve6("."));
9264
+ if (existsSync20(factoryName) && existsSync20(resolve6(factoryName, ".beastmode"))) {
8746
9265
  throw new Error(`Factory already exists at ./${factoryName}. Use 'beastmode config' to modify.`);
8747
9266
  }
8748
9267
  if (opts.from) {
@@ -8757,7 +9276,7 @@ async function runInit(name, opts) {
8757
9276
  }
8758
9277
  const templateProjectPath = resolve6(opts.project);
8759
9278
  const templateStack = detectStack(templateProjectPath);
8760
- const templateProjectName = basename4(templateProjectPath);
9279
+ const templateProjectName = basename5(templateProjectPath);
8761
9280
  const actions2 = scaffoldFactory(factoryName, template.config, {
8762
9281
  name: templateProjectName,
8763
9282
  repo: templateStack.git_remote || void 0,
@@ -8792,7 +9311,7 @@ async function runInit(name, opts) {
8792
9311
  name: "project",
8793
9312
  message: "Path to your project:",
8794
9313
  default: ".",
8795
- validate: (input) => existsSync19(resolve6(input)) || `Directory not found: ${input}`
9314
+ validate: (input) => existsSync20(resolve6(input)) || `Directory not found: ${input}`
8796
9315
  }
8797
9316
  ]);
8798
9317
  projectPath = resolve6(answer.project);
@@ -8952,7 +9471,7 @@ async function runInit(name, opts) {
8952
9471
  success("Board UI password set");
8953
9472
  }
8954
9473
  step(5, totalSteps, "Boot");
8955
- const projectName = basename4(projectPath);
9474
+ const projectName = basename5(projectPath);
8956
9475
  const actions = scaffoldFactory(factoryName, config, {
8957
9476
  name: projectName,
8958
9477
  repo: stack.git_remote || void 0,
@@ -9098,14 +9617,14 @@ async function runImageModeInit(name, opts) {
9098
9617
  let projectPath;
9099
9618
  if (opts.project) {
9100
9619
  projectPath = resolve6(opts.project);
9101
- if (!existsSync19(projectPath)) {
9620
+ if (!existsSync20(projectPath)) {
9102
9621
  error(`--project path does not exist: ${projectPath}`);
9103
9622
  process.exit(1);
9104
9623
  }
9105
9624
  success(`Project path: ${projectPath}`);
9106
9625
  } else if (opts.yes) {
9107
9626
  const cwd = resolve6(".");
9108
- if (!existsSync19(join17(cwd, ".git"))) {
9627
+ if (!existsSync20(join18(cwd, ".git"))) {
9109
9628
  error(
9110
9629
  "--project is required in non-interactive mode (--yes) unless you run from inside a git repository. Pass --project <path> or cd into your project clone first."
9111
9630
  );
@@ -9122,8 +9641,8 @@ async function runImageModeInit(name, opts) {
9122
9641
  default: ".",
9123
9642
  validate: (input) => {
9124
9643
  const p = resolve6(input);
9125
- if (!existsSync19(p)) return `Directory not found: ${p}`;
9126
- if (!existsSync19(join17(p, ".git"))) {
9644
+ if (!existsSync20(p)) return `Directory not found: ${p}`;
9645
+ if (!existsSync20(join18(p, ".git"))) {
9127
9646
  return `Not a git repo: ${p} (run 'git init' first, or pick a different path)`;
9128
9647
  }
9129
9648
  return true;
@@ -9162,9 +9681,9 @@ async function runImageModeInit(name, opts) {
9162
9681
  success("Authenticated to ghcr.io");
9163
9682
  }
9164
9683
  step(3, totalSteps, "Generate");
9165
- mkdirSync14(targetDir, { recursive: true });
9684
+ mkdirSync15(targetDir, { recursive: true });
9166
9685
  const composeContent = generateComposeYaml("latest");
9167
- writeFileSync15(join17(targetDir, "docker-compose.yml"), composeContent, "utf-8");
9686
+ writeFileSync16(join18(targetDir, "docker-compose.yml"), composeContent, "utf-8");
9168
9687
  success("docker-compose.yml");
9169
9688
  const envLines = [
9170
9689
  "# BeastMode environment \u2014 DO NOT COMMIT",
@@ -9204,10 +9723,10 @@ async function runImageModeInit(name, opts) {
9204
9723
  "# PROJECT_REPO=owner/repo",
9205
9724
  ""
9206
9725
  ];
9207
- writeFileSync15(join17(targetDir, ".env"), envLines.join("\n"), "utf-8");
9726
+ writeFileSync16(join18(targetDir, ".env"), envLines.join("\n"), "utf-8");
9208
9727
  success(`.env (PROJECT_DIR=${projectPath}, two-PAT model)`);
9209
9728
  for (const dir of ["data", "runs", "daemon/logs", ".beastmode", "config"]) {
9210
- mkdirSync14(join17(targetDir, dir), { recursive: true });
9729
+ mkdirSync15(join18(targetDir, dir), { recursive: true });
9211
9730
  }
9212
9731
  step(4, totalSteps, "Pull Images");
9213
9732
  try {
@@ -9296,20 +9815,20 @@ async function runImageModeInit(name, opts) {
9296
9815
  init_export_adapter();
9297
9816
  init_display();
9298
9817
  import { Command as Command4 } from "commander";
9299
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync16, existsSync as existsSync20, readdirSync as readdirSync8 } from "fs";
9300
- import { resolve as resolve7, join as join18 } from "path";
9818
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync17, existsSync as existsSync21, readdirSync as readdirSync9 } from "fs";
9819
+ import { resolve as resolve7, join as join19 } from "path";
9301
9820
  function exportFactory(factoryDir, outputPath) {
9302
- const bmDir = join18(factoryDir, ".beastmode");
9303
- const configPath = join18(bmDir, "config.json");
9304
- if (!existsSync20(configPath)) {
9821
+ const bmDir = join19(factoryDir, ".beastmode");
9822
+ const configPath = join19(bmDir, "config.json");
9823
+ if (!existsSync21(configPath)) {
9305
9824
  throw new Error(`No factory found at ${factoryDir}`);
9306
9825
  }
9307
- const config = JSON.parse(readFileSync17(configPath, "utf-8"));
9308
- const identity = JSON.parse(readFileSync17(join18(bmDir, "factory.json"), "utf-8"));
9826
+ const config = JSON.parse(readFileSync18(configPath, "utf-8"));
9827
+ const identity = JSON.parse(readFileSync18(join19(bmDir, "factory.json"), "utf-8"));
9309
9828
  let plugins = [];
9310
- const lockPath = join18(bmDir, "extensions.lock");
9311
- if (existsSync20(lockPath)) {
9312
- const lock = JSON.parse(readFileSync17(lockPath, "utf-8"));
9829
+ const lockPath = join19(bmDir, "extensions.lock");
9830
+ if (existsSync21(lockPath)) {
9831
+ const lock = JSON.parse(readFileSync18(lockPath, "utf-8"));
9313
9832
  plugins = Object.keys(lock.plugins || {});
9314
9833
  }
9315
9834
  const template = {
@@ -9318,7 +9837,7 @@ function exportFactory(factoryDir, outputPath) {
9318
9837
  config,
9319
9838
  plugins
9320
9839
  };
9321
- writeFileSync16(outputPath, JSON.stringify(template, null, 2) + "\n", "utf-8");
9840
+ writeFileSync17(outputPath, JSON.stringify(template, null, 2) + "\n", "utf-8");
9322
9841
  }
9323
9842
  function findRunDir(runId) {
9324
9843
  const candidates = [
@@ -9326,7 +9845,7 @@ function findRunDir(runId) {
9326
9845
  resolve7(".", runId)
9327
9846
  ];
9328
9847
  for (const candidate of candidates) {
9329
- if (existsSync20(candidate)) {
9848
+ if (existsSync21(candidate)) {
9330
9849
  return candidate;
9331
9850
  }
9332
9851
  }
@@ -9340,23 +9859,23 @@ function createArtifactExportCommand(artifact) {
9340
9859
  const runDir = findRunDir(opts.run);
9341
9860
  let sourceContent;
9342
9861
  if (artifact === "scenarios") {
9343
- const scenariosDir = join18(runDir, "scenarios");
9344
- if (!existsSync20(scenariosDir)) {
9862
+ const scenariosDir = join19(runDir, "scenarios");
9863
+ if (!existsSync21(scenariosDir)) {
9345
9864
  throw new Error(`No scenarios directory found in run ${opts.run}`);
9346
9865
  }
9347
- const files = readdirSync8(scenariosDir).filter((f) => f.endsWith(".md")).sort();
9348
- sourceContent = files.map((f) => readFileSync17(join18(scenariosDir, f), "utf-8")).join("\n\n---\n\n");
9866
+ const files = readdirSync9(scenariosDir).filter((f) => f.endsWith(".md")).sort();
9867
+ sourceContent = files.map((f) => readFileSync18(join19(scenariosDir, f), "utf-8")).join("\n\n---\n\n");
9349
9868
  } else {
9350
9869
  const artifactFile = artifact === "nlspec" ? "nlspec.md" : "plan.md";
9351
- const artifactPath = join18(runDir, artifactFile);
9352
- if (!existsSync20(artifactPath)) {
9870
+ const artifactPath = join19(runDir, artifactFile);
9871
+ if (!existsSync21(artifactPath)) {
9353
9872
  throw new Error(`${artifactFile} not found in run ${opts.run}`);
9354
9873
  }
9355
- sourceContent = readFileSync17(artifactPath, "utf-8");
9874
+ sourceContent = readFileSync18(artifactPath, "utf-8");
9356
9875
  }
9357
9876
  const result = runExportAdapter(opts.adapter, sourceContent);
9358
9877
  if (opts.output) {
9359
- writeFileSync16(resolve7(opts.output), result, "utf-8");
9878
+ writeFileSync17(resolve7(opts.output), result, "utf-8");
9360
9879
  header(`Exported ${artifact}`);
9361
9880
  success(`Written to: ${opts.output}`);
9362
9881
  } else {
@@ -9580,15 +10099,15 @@ init_presets();
9580
10099
  init_display();
9581
10100
  import { Command as Command8 } from "commander";
9582
10101
  import { resolve as resolve11 } from "path";
9583
- import { readFileSync as readFileSync18, existsSync as existsSync21 } from "fs";
9584
- import { join as join19 } from "path";
10102
+ import { readFileSync as readFileSync19, existsSync as existsSync22 } from "fs";
10103
+ import { join as join20 } from "path";
9585
10104
  function listPluginsAction(factoryDir) {
9586
- const lockPath = join19(factoryDir, ".beastmode", "extensions.lock");
9587
- if (!existsSync21(lockPath)) {
10105
+ const lockPath = join20(factoryDir, ".beastmode", "extensions.lock");
10106
+ if (!existsSync22(lockPath)) {
9588
10107
  console.log(" No plugins installed (extensions.lock not found).");
9589
10108
  return;
9590
10109
  }
9591
- const lock = JSON.parse(readFileSync18(lockPath, "utf-8"));
10110
+ const lock = JSON.parse(readFileSync19(lockPath, "utf-8"));
9592
10111
  const plugins = lock.plugins || {};
9593
10112
  const names = Object.keys(plugins);
9594
10113
  if (names.length === 0) {
@@ -9687,31 +10206,31 @@ var listCommand = new Command8("list").description("List installed extensions an
9687
10206
  init_import_adapter();
9688
10207
  init_display();
9689
10208
  import { Command as Command9 } from "commander";
9690
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync17, mkdirSync as mkdirSync15, existsSync as existsSync22 } from "fs";
9691
- import { resolve as resolve12, join as join20 } from "path";
10209
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync18, mkdirSync as mkdirSync16, existsSync as existsSync23 } from "fs";
10210
+ import { resolve as resolve12, join as join21 } from "path";
9692
10211
  var VALID_ARTIFACTS = ["nlspec", "plan", "scenarios"];
9693
10212
  function createArtifactCommand(artifact) {
9694
10213
  return new Command9(artifact).description(`Import external document as ${artifact}`).requiredOption("--from <path>", "Path to source file").requiredOption("--adapter <id>", "Adapter ID (e.g., generic:prd, bmad:brainstorm)").option("--output <path>", "Output path (default: stdout)").action((opts) => {
9695
10214
  try {
9696
10215
  const sourcePath = resolve12(opts.from);
9697
- if (!existsSync22(sourcePath)) {
10216
+ if (!existsSync23(sourcePath)) {
9698
10217
  throw new Error(`Source file not found: ${sourcePath}`);
9699
10218
  }
9700
- const sourceContent = readFileSync19(sourcePath, "utf-8");
10219
+ const sourceContent = readFileSync20(sourcePath, "utf-8");
9701
10220
  const result = runImportAdapter(opts.adapter, sourceContent);
9702
10221
  if (opts.output) {
9703
10222
  const outputPath = resolve12(opts.output);
9704
10223
  if (artifact === "scenarios" && opts.adapter.endsWith(":test-cases")) {
9705
10224
  const scenarios = JSON.parse(result);
9706
- mkdirSync15(outputPath, { recursive: true });
10225
+ mkdirSync16(outputPath, { recursive: true });
9707
10226
  for (const scenario of scenarios) {
9708
- const filePath = join20(outputPath, `${scenario.name}.md`);
9709
- writeFileSync17(filePath, scenario.content, "utf-8");
10227
+ const filePath = join21(outputPath, `${scenario.name}.md`);
10228
+ writeFileSync18(filePath, scenario.content, "utf-8");
9710
10229
  success(` ${scenario.name}.md`);
9711
10230
  }
9712
10231
  header(`Imported ${scenarios.length} scenarios to ${opts.output}`);
9713
10232
  } else {
9714
- writeFileSync17(outputPath, result, "utf-8");
10233
+ writeFileSync18(outputPath, result, "utf-8");
9715
10234
  header(`Imported ${artifact}`);
9716
10235
  success(`Written to: ${opts.output}`);
9717
10236
  }
@@ -9732,54 +10251,55 @@ for (const artifact of VALID_ARTIFACTS) {
9732
10251
  // src/cli/commands/status.ts
9733
10252
  init_status_checker();
9734
10253
  init_schemas();
10254
+ init_project_record();
9735
10255
  init_display();
9736
10256
  import { Command as Command10 } from "commander";
9737
- import { existsSync as existsSync23, readFileSync as readFileSync20, readdirSync as readdirSync9 } from "fs";
10257
+ import { existsSync as existsSync24, readFileSync as readFileSync21, readdirSync as readdirSync10 } from "fs";
9738
10258
  import { execSync as execSync6 } from "child_process";
9739
- import { join as join21, resolve as resolve13 } from "path";
10259
+ import { join as join22, resolve as resolve13 } from "path";
9740
10260
  function statusAction(factoryDir, opts) {
9741
- const bmDir = join21(factoryDir, ".beastmode");
9742
- if (!existsSync23(bmDir)) {
10261
+ const bmDir = join22(factoryDir, ".beastmode");
10262
+ if (!existsSync24(bmDir)) {
9743
10263
  throw new Error(`No factory found at ${factoryDir}`);
9744
10264
  }
9745
- const factoryJsonPath = join21(bmDir, "factory.json");
9746
- const rawIdentity = JSON.parse(readFileSync20(factoryJsonPath, "utf-8"));
10265
+ const factoryJsonPath = join22(bmDir, "factory.json");
10266
+ const rawIdentity = JSON.parse(readFileSync21(factoryJsonPath, "utf-8"));
9747
10267
  const factoryIdentity = FactoryIdentitySchema.parse(rawIdentity);
9748
- const projectsDir = join21(bmDir, "projects");
9749
- const projectCount = existsSync23(projectsDir) ? readdirSync9(projectsDir).filter((f) => f.endsWith(".json")).length : 0;
9750
- const pluginsDir = join21(bmDir, "plugins");
9751
- const pluginNames = existsSync23(pluginsDir) ? readdirSync9(pluginsDir) : [];
9752
- const mcpPath = join21(bmDir, "mcp-servers.json");
10268
+ const projectsDir = join22(bmDir, "projects");
10269
+ const projectCount = listProjectRecords(projectsDir).length;
10270
+ const pluginsDir = join22(bmDir, "plugins");
10271
+ const pluginNames = existsSync24(pluginsDir) ? readdirSync10(pluginsDir) : [];
10272
+ const mcpPath = join22(bmDir, "mcp-servers.json");
9753
10273
  let mcpServers = {};
9754
- if (existsSync23(mcpPath)) {
10274
+ if (existsSync24(mcpPath)) {
9755
10275
  try {
9756
- const raw = JSON.parse(readFileSync20(mcpPath, "utf-8"));
10276
+ const raw = JSON.parse(readFileSync21(mcpPath, "utf-8"));
9757
10277
  mcpServers = raw.servers || {};
9758
10278
  } catch {
9759
10279
  }
9760
10280
  }
9761
- const hooksPath = join21(bmDir, "hooks.json");
10281
+ const hooksPath = join22(bmDir, "hooks.json");
9762
10282
  let hooks = {};
9763
- if (existsSync23(hooksPath)) {
10283
+ if (existsSync24(hooksPath)) {
9764
10284
  try {
9765
- const raw = JSON.parse(readFileSync20(hooksPath, "utf-8"));
10285
+ const raw = JSON.parse(readFileSync21(hooksPath, "utf-8"));
9766
10286
  hooks = raw.hooks || {};
9767
10287
  } catch {
9768
10288
  }
9769
10289
  }
9770
- const skillsDir = join21(bmDir, "skills");
9771
- const skillCount = existsSync23(skillsDir) ? readdirSync9(skillsDir).length : 0;
9772
- const runsDir = join21(factoryDir, "runs");
10290
+ const skillsDir = join22(bmDir, "skills");
10291
+ const skillCount = existsSync24(skillsDir) ? readdirSync10(skillsDir).length : 0;
10292
+ const runsDir = join22(factoryDir, "runs");
9773
10293
  let runDirs = [];
9774
- if (existsSync23(runsDir)) {
9775
- runDirs = readdirSync9(runsDir).filter((d) => d.startsWith("run-")).sort();
10294
+ if (existsSync24(runsDir)) {
10295
+ runDirs = readdirSync10(runsDir).filter((d) => d.startsWith("run-")).sort();
9776
10296
  }
9777
- const pidPath = join21(bmDir, "daemon.pid");
10297
+ const pidPath = join22(bmDir, "daemon.pid");
9778
10298
  let daemonPid = null;
9779
10299
  let pidAlive = false;
9780
- if (existsSync23(pidPath)) {
10300
+ if (existsSync24(pidPath)) {
9781
10301
  try {
9782
- daemonPid = parseInt(readFileSync20(pidPath, "utf-8").trim(), 10);
10302
+ daemonPid = parseInt(readFileSync21(pidPath, "utf-8").trim(), 10);
9783
10303
  process.kill(daemonPid, 0);
9784
10304
  pidAlive = true;
9785
10305
  } catch {
@@ -9853,19 +10373,19 @@ var statusCommand = new Command10("status").description("Show factory status ove
9853
10373
  init_config_manager();
9854
10374
  init_display();
9855
10375
  import { Command as Command11 } from "commander";
9856
- import { existsSync as existsSync24, readFileSync as readFileSync21, writeFileSync as writeFileSync18 } from "fs";
9857
- import { join as join22, resolve as resolve14 } from "path";
10376
+ import { existsSync as existsSync25, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "fs";
10377
+ import { join as join23, resolve as resolve14 } from "path";
9858
10378
  import { execSync as execSync7 } from "child_process";
9859
10379
  function readConfig2(factoryDir) {
9860
- const configPath = join22(factoryDir, ".beastmode", "config.json");
9861
- if (!existsSync24(configPath)) {
10380
+ const configPath = join23(factoryDir, ".beastmode", "config.json");
10381
+ if (!existsSync25(configPath)) {
9862
10382
  throw new Error("No config.json found. Run beastmode init first.");
9863
10383
  }
9864
- return JSON.parse(readFileSync21(configPath, "utf-8"));
10384
+ return JSON.parse(readFileSync22(configPath, "utf-8"));
9865
10385
  }
9866
10386
  function writeConfig2(factoryDir, config) {
9867
- const configPath = join22(factoryDir, ".beastmode", "config.json");
9868
- writeFileSync18(configPath, JSON.stringify(config, null, 2) + "\n");
10387
+ const configPath = join23(factoryDir, ".beastmode", "config.json");
10388
+ writeFileSync19(configPath, JSON.stringify(config, null, 2) + "\n");
9869
10389
  }
9870
10390
  function configGetAction(factoryDir, key) {
9871
10391
  const config = readConfig2(factoryDir);
@@ -9908,8 +10428,8 @@ var configSetCmd = new Command11("set").description("Set a config value by dot-n
9908
10428
  });
9909
10429
  var configEditCmd = new Command11("edit").description("Open config.json in $EDITOR").action(() => {
9910
10430
  const factoryDir = resolve14(".");
9911
- const configPath = join22(factoryDir, ".beastmode", "config.json");
9912
- if (!existsSync24(configPath)) {
10431
+ const configPath = join23(factoryDir, ".beastmode", "config.json");
10432
+ if (!existsSync25(configPath)) {
9913
10433
  error("No config.json found. Run beastmode init first.");
9914
10434
  process.exit(1);
9915
10435
  }
@@ -9939,13 +10459,14 @@ var configCommand = new Command11("config").description("Manage factory configur
9939
10459
 
9940
10460
  // src/cli/commands/doctor.ts
9941
10461
  init_doctor();
10462
+ init_engine();
9942
10463
  init_schemas();
9943
10464
  init_version();
9944
10465
  init_plugin_resolver();
9945
10466
  init_display();
9946
10467
  import { Command as Command12 } from "commander";
9947
- import { existsSync as existsSync25, readFileSync as readFileSync22, readdirSync as readdirSync10 } from "fs";
9948
- import { join as join23, resolve as resolve15 } from "path";
10468
+ import { existsSync as existsSync26, readFileSync as readFileSync23, readdirSync as readdirSync11 } from "fs";
10469
+ import { join as join24, resolve as resolve15 } from "path";
9949
10470
  import { execSync as execSync8, spawnSync as spawnSync3 } from "child_process";
9950
10471
  import { homedir as homedir3, platform as platform2 } from "os";
9951
10472
  import * as http3 from "http";
@@ -9986,8 +10507,8 @@ async function checkClaudeAuth(key) {
9986
10507
  });
9987
10508
  }
9988
10509
  if (claudeInstalled && platform2() === "darwin" && !key) {
9989
- const credsPath = join23(homedir3(), ".claude", ".credentials.json");
9990
- if (!existsSync25(credsPath)) {
10510
+ const credsPath = join24(homedir3(), ".claude", ".credentials.json");
10511
+ if (!existsSync26(credsPath)) {
9991
10512
  results.push({
9992
10513
  label: "Claude creds (Docker)",
9993
10514
  status: "warn",
@@ -10089,16 +10610,16 @@ async function checkProjectGithubToken(env, factoryDir) {
10089
10610
  }
10090
10611
  let ownerRepo = null;
10091
10612
  if (factoryDir) {
10092
- const envPath = join23(factoryDir, ".env");
10093
- if (existsSync25(envPath)) {
10613
+ const envPath = join24(factoryDir, ".env");
10614
+ if (existsSync26(envPath)) {
10094
10615
  try {
10095
- const content = readFileSync22(envPath, "utf-8");
10616
+ const content = readFileSync23(envPath, "utf-8");
10096
10617
  const dirLine = content.split("\n").find(
10097
10618
  (l) => l.trim().startsWith("PROJECT_DIR=") && !l.trim().startsWith("#")
10098
10619
  );
10099
10620
  if (dirLine) {
10100
10621
  const projectDir = dirLine.split("=").slice(1).join("=").trim();
10101
- if (projectDir && existsSync25(join23(projectDir, ".git"))) {
10622
+ if (projectDir && existsSync26(join24(projectDir, ".git"))) {
10102
10623
  const remote = tryExec(
10103
10624
  `git -C "${projectDir}" remote get-url origin 2>/dev/null`
10104
10625
  );
@@ -10314,8 +10835,8 @@ function checkProjectDirEnv(factoryDir) {
10314
10835
  detail: "no factory in scope"
10315
10836
  };
10316
10837
  }
10317
- const envPath = join23(factoryDir, ".env");
10318
- if (!existsSync25(envPath)) {
10838
+ const envPath = join24(factoryDir, ".env");
10839
+ if (!existsSync26(envPath)) {
10319
10840
  return {
10320
10841
  label: "PROJECT_DIR (.env)",
10321
10842
  status: "fail",
@@ -10325,7 +10846,7 @@ function checkProjectDirEnv(factoryDir) {
10325
10846
  }
10326
10847
  let content;
10327
10848
  try {
10328
- content = readFileSync22(envPath, "utf-8");
10849
+ content = readFileSync23(envPath, "utf-8");
10329
10850
  } catch {
10330
10851
  return {
10331
10852
  label: "PROJECT_DIR (.env)",
@@ -10354,7 +10875,7 @@ function checkProjectDirEnv(factoryDir) {
10354
10875
  fix: "Re-run `beastmode init --project <path>` to populate it"
10355
10876
  };
10356
10877
  }
10357
- if (!existsSync25(value)) {
10878
+ if (!existsSync26(value)) {
10358
10879
  return {
10359
10880
  label: "PROJECT_DIR (.env)",
10360
10881
  status: "fail",
@@ -10362,7 +10883,7 @@ function checkProjectDirEnv(factoryDir) {
10362
10883
  fix: `Clone your project to ${value} or re-run 'beastmode init --project <path>'`
10363
10884
  };
10364
10885
  }
10365
- if (!existsSync25(join23(value, ".git"))) {
10886
+ if (!existsSync26(join24(value, ".git"))) {
10366
10887
  return {
10367
10888
  label: "PROJECT_DIR (.env)",
10368
10889
  status: "fail",
@@ -10393,8 +10914,8 @@ async function checkFactoryContainers(factoryDir) {
10393
10914
  detail: "no factory in scope"
10394
10915
  };
10395
10916
  }
10396
- const composePath = join23(factoryDir, "docker-compose.yml");
10397
- if (!existsSync25(composePath)) {
10917
+ const composePath = join24(factoryDir, "docker-compose.yml");
10918
+ if (!existsSync26(composePath)) {
10398
10919
  return {
10399
10920
  label: "Factory containers",
10400
10921
  status: "warn",
@@ -10676,9 +11197,9 @@ function checkProjectDirectory(factoryDir) {
10676
11197
  fix: "Run beastmode init to create a factory"
10677
11198
  };
10678
11199
  }
10679
- const bmDir = join23(factoryDir, ".beastmode");
10680
- const projectsDir = join23(bmDir, "projects");
10681
- if (!existsSync25(projectsDir)) {
11200
+ const bmDir = join24(factoryDir, ".beastmode");
11201
+ const projectsDir = join24(bmDir, "projects");
11202
+ if (!existsSync26(projectsDir)) {
10682
11203
  return {
10683
11204
  label: "Project directory",
10684
11205
  status: "warn",
@@ -10686,8 +11207,8 @@ function checkProjectDirectory(factoryDir) {
10686
11207
  fix: "Run: beastmode add project <path>"
10687
11208
  };
10688
11209
  }
10689
- const projectFiles = existsSync25(projectsDir) ? readdirSync10(projectsDir).filter((f) => f.endsWith(".json")) : [];
10690
- if (projectFiles.length === 0) {
11210
+ const projectRecords = listProjectRecords(projectsDir);
11211
+ if (projectRecords.length === 0) {
10691
11212
  return {
10692
11213
  label: "Project directory",
10693
11214
  status: "warn",
@@ -10697,37 +11218,31 @@ function checkProjectDirectory(factoryDir) {
10697
11218
  }
10698
11219
  const results = [];
10699
11220
  let anyFail = false;
10700
- for (const file of projectFiles) {
10701
- try {
10702
- const proj = JSON.parse(readFileSync22(join23(projectsDir, file), "utf-8"));
10703
- const projPath = proj.path || "";
10704
- const projName = proj.name || file.replace(".json", "");
10705
- if (!projPath || !existsSync25(projPath)) {
10706
- results.push(`${projName}: path not found`);
10707
- anyFail = true;
10708
- continue;
10709
- }
10710
- const isGit = existsSync25(join23(projPath, ".git"));
10711
- const manifests = [
10712
- "package.json",
10713
- "Cargo.toml",
10714
- "go.mod",
10715
- "pyproject.toml",
10716
- "requirements.txt",
10717
- "pom.xml",
10718
- "build.gradle",
10719
- "build.gradle.kts"
10720
- ];
10721
- const manifest = manifests.find((m) => existsSync25(join23(projPath, m)));
10722
- const framework = proj.stack?.detected || manifest?.replace(".json", "") || "unknown";
10723
- results.push(
10724
- `${projName}: ${projPath} (${framework})${isGit ? "" : " [no .git]"}`
10725
- );
10726
- if (!isGit) anyFail = true;
10727
- } catch {
10728
- results.push(`${file}: unreadable`);
11221
+ for (const proj of projectRecords) {
11222
+ const projPath = proj.path || "";
11223
+ const projName = proj.name;
11224
+ if (!projPath || !existsSync26(projPath)) {
11225
+ results.push(`${projName}: path not found`);
10729
11226
  anyFail = true;
11227
+ continue;
10730
11228
  }
11229
+ const isGit = existsSync26(join24(projPath, ".git"));
11230
+ const manifests = [
11231
+ "package.json",
11232
+ "Cargo.toml",
11233
+ "go.mod",
11234
+ "pyproject.toml",
11235
+ "requirements.txt",
11236
+ "pom.xml",
11237
+ "build.gradle",
11238
+ "build.gradle.kts"
11239
+ ];
11240
+ const manifest = manifests.find((m) => existsSync26(join24(projPath, m)));
11241
+ const framework = proj.stack?.detected || manifest?.replace(".json", "") || "unknown";
11242
+ results.push(
11243
+ `${projName}: ${projPath} (${framework})${isGit ? "" : " [no .git]"}`
11244
+ );
11245
+ if (!isGit) anyFail = true;
10731
11246
  }
10732
11247
  if (anyFail) {
10733
11248
  return {
@@ -10772,25 +11287,22 @@ function checkStack(factoryDir) {
10772
11287
  detail: "no factory \u2014 run beastmode init"
10773
11288
  };
10774
11289
  }
10775
- const projectsDir = join23(factoryDir, ".beastmode", "projects");
10776
- if (!existsSync25(projectsDir)) {
11290
+ const projectsDir = join24(factoryDir, ".beastmode", "projects");
11291
+ if (!existsSync26(projectsDir)) {
10777
11292
  return { label: "Stack", status: "warn", detail: "no projects configured" };
10778
11293
  }
10779
- const projectFiles = readdirSync10(projectsDir).filter((f) => f.endsWith(".json"));
10780
- if (projectFiles.length === 0) {
11294
+ const projectRecords = listProjectRecords(projectsDir);
11295
+ if (projectRecords.length === 0) {
10781
11296
  return { label: "Stack", status: "warn", detail: "no projects configured" };
10782
11297
  }
10783
11298
  const stacks = [];
10784
- for (const file of projectFiles) {
10785
- try {
10786
- const proj = JSON.parse(readFileSync22(join23(projectsDir, file), "utf-8"));
10787
- const name = proj.name || file.replace(".json", "");
10788
- const detected = proj.stack?.detected || "unknown";
10789
- const build = proj.stack?.build_command || "";
10790
- const port = proj.stack?.dev_port || 3e3;
10791
- stacks.push(`${name}: ${detected} \u2014 ${build}, port ${port}`);
10792
- } catch {
10793
- }
11299
+ for (const proj of projectRecords) {
11300
+ const name = proj.name;
11301
+ const stackInfo = proj.stack;
11302
+ const detected = stackInfo?.detected || "unknown";
11303
+ const build = stackInfo?.build_command || "";
11304
+ const port = stackInfo?.dev_port || 3e3;
11305
+ stacks.push(`${name}: ${detected} \u2014 ${build}, port ${port}`);
10794
11306
  }
10795
11307
  if (stacks.length === 0) {
10796
11308
  return { label: "Stack", status: "warn", detail: "no readable project configs" };
@@ -10827,12 +11339,12 @@ function checkBoardPassword(env, factoryDir) {
10827
11339
  return { label: "Board password", status: "pass", detail: "set" };
10828
11340
  }
10829
11341
  if (factoryDir) {
10830
- const dotEnv = join23(factoryDir, ".env");
10831
- const secretsEnv = join23(factoryDir, ".beastmode", "secrets.env.local");
11342
+ const dotEnv = join24(factoryDir, ".env");
11343
+ const secretsEnv = join24(factoryDir, ".beastmode", "secrets.env.local");
10832
11344
  for (const filePath of [dotEnv, secretsEnv]) {
10833
- if (existsSync25(filePath)) {
11345
+ if (existsSync26(filePath)) {
10834
11346
  try {
10835
- const content = readFileSync22(filePath, "utf-8");
11347
+ const content = readFileSync23(filePath, "utf-8");
10836
11348
  const lines = content.split("\n");
10837
11349
  const line = lines.find((l) => l.startsWith("BEASTMODE_UI_PASSWORD="));
10838
11350
  if (line) {
@@ -10854,12 +11366,12 @@ function checkBoardPassword(env, factoryDir) {
10854
11366
  };
10855
11367
  }
10856
11368
  function doctorAction(factoryDir, env) {
10857
- const bmDir = join23(factoryDir, ".beastmode");
10858
- const factoryDirExists = existsSync25(bmDir);
11369
+ const bmDir = join24(factoryDir, ".beastmode");
11370
+ const factoryDirExists = existsSync26(bmDir);
10859
11371
  let factoryIdentity = null;
10860
11372
  if (factoryDirExists) {
10861
11373
  try {
10862
- const raw = JSON.parse(readFileSync22(join23(bmDir, "factory.json"), "utf-8"));
11374
+ const raw = JSON.parse(readFileSync23(join24(bmDir, "factory.json"), "utf-8"));
10863
11375
  factoryIdentity = FactoryIdentitySchema.parse(raw);
10864
11376
  } catch {
10865
11377
  }
@@ -10867,10 +11379,10 @@ function doctorAction(factoryDir, env) {
10867
11379
  let config = null;
10868
11380
  let configParseError = null;
10869
11381
  if (factoryDirExists) {
10870
- const configPath = join23(bmDir, "config.json");
10871
- if (existsSync25(configPath)) {
11382
+ const configPath = join24(bmDir, "config.json");
11383
+ if (existsSync26(configPath)) {
10872
11384
  try {
10873
- const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
11385
+ const raw = JSON.parse(readFileSync23(configPath, "utf-8"));
10874
11386
  const result = FactoryConfigSchema.safeParse(raw);
10875
11387
  if (result.success) {
10876
11388
  config = raw;
@@ -10886,34 +11398,26 @@ function doctorAction(factoryDir, env) {
10886
11398
  }
10887
11399
  const projectPaths = [];
10888
11400
  if (factoryDirExists) {
10889
- const projectsDir = join23(bmDir, "projects");
10890
- if (existsSync25(projectsDir)) {
10891
- for (const file of readdirSync10(projectsDir)) {
10892
- if (file.endsWith(".json")) {
10893
- try {
10894
- const proj = JSON.parse(readFileSync22(join23(projectsDir, file), "utf-8"));
10895
- if (proj.path) {
10896
- projectPaths.push({
10897
- name: proj.name || file.replace(".json", ""),
10898
- path: proj.path,
10899
- exists: existsSync25(proj.path)
10900
- });
10901
- }
10902
- } catch {
10903
- }
10904
- }
11401
+ const projectsDir = join24(bmDir, "projects");
11402
+ for (const proj of listProjectRecords(projectsDir)) {
11403
+ if (proj.path) {
11404
+ projectPaths.push({
11405
+ name: proj.name,
11406
+ path: proj.path,
11407
+ exists: existsSync26(proj.path)
11408
+ });
10905
11409
  }
10906
11410
  }
10907
11411
  }
10908
11412
  const installedPlugins = [];
10909
11413
  if (factoryDirExists) {
10910
- const pluginsDir = join23(bmDir, "plugins");
10911
- if (existsSync25(pluginsDir)) {
10912
- for (const pluginName of readdirSync10(pluginsDir)) {
10913
- const manifestPath = join23(pluginsDir, pluginName, "manifest.json");
10914
- if (existsSync25(manifestPath)) {
11414
+ const pluginsDir = join24(bmDir, "plugins");
11415
+ if (existsSync26(pluginsDir)) {
11416
+ for (const pluginName of readdirSync11(pluginsDir)) {
11417
+ const manifestPath = join24(pluginsDir, pluginName, "manifest.json");
11418
+ if (existsSync26(manifestPath)) {
10915
11419
  try {
10916
- const manifest = JSON.parse(readFileSync22(manifestPath, "utf-8"));
11420
+ const manifest = JSON.parse(readFileSync23(manifestPath, "utf-8"));
10917
11421
  installedPlugins.push({
10918
11422
  name: pluginName,
10919
11423
  engine_version: manifest.engine_version || "*"
@@ -10941,8 +11445,8 @@ var SYSTEMD_CREDS_TIMER = "beastmode-claude-creds.timer";
10941
11445
  var LAUNCH_AGENT_LABEL2 = "com.develeap.beastmode.claude-creds";
10942
11446
  function checkCredentialSyncAgent() {
10943
11447
  const results = [];
10944
- const credsPath = join23(homedir3(), ".claude", ".credentials.json");
10945
- if (!existsSync25(credsPath)) {
11448
+ const credsPath = join24(homedir3(), ".claude", ".credentials.json");
11449
+ if (!existsSync26(credsPath)) {
10946
11450
  results.push({
10947
11451
  label: "Claude credentials file",
10948
11452
  status: "fail",
@@ -10957,7 +11461,7 @@ function checkCredentialSyncAgent() {
10957
11461
  detail: credsPath
10958
11462
  });
10959
11463
  try {
10960
- const creds = JSON.parse(readFileSync22(credsPath, "utf-8"));
11464
+ const creds = JSON.parse(readFileSync23(credsPath, "utf-8"));
10961
11465
  const expiresAt = creds?.claudeAiOauth?.expiresAt;
10962
11466
  if (typeof expiresAt === "number") {
10963
11467
  const hoursLeft = Math.round((expiresAt - Date.now()) / 36e5);
@@ -11273,7 +11777,7 @@ var doctorCommand = new Command12("doctor").description("Health check \u2014 val
11273
11777
  console.log();
11274
11778
  const env = process.env;
11275
11779
  const factoryDir = findFactoryDir() ?? resolve15(".");
11276
- const hasFactory = existsSync25(join23(factoryDir, ".beastmode"));
11780
+ const hasFactory = existsSync26(join24(factoryDir, ".beastmode"));
11277
11781
  const checks = [];
11278
11782
  checks.push(...await checkClaudeAuth(env.ANTHROPIC_API_KEY));
11279
11783
  checks.push(...checkCredentialSyncAgent());
@@ -11349,12 +11853,12 @@ init_schemas();
11349
11853
  init_version();
11350
11854
  init_display();
11351
11855
  import { Command as Command13 } from "commander";
11352
- import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync20 } from "fs";
11353
- import { join as join25, resolve as resolve16 } from "path";
11856
+ import { existsSync as existsSync28, readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "fs";
11857
+ import { join as join26, resolve as resolve16 } from "path";
11354
11858
 
11355
11859
  // src/cli/utils/regenerate.ts
11356
- import { existsSync as existsSync26, readFileSync as readFileSync23, writeFileSync as writeFileSync19, copyFileSync } from "fs";
11357
- import { join as join24 } from "path";
11860
+ import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync20, copyFileSync } from "fs";
11861
+ import { join as join25 } from "path";
11358
11862
  var RECOGNIZED_KEYS = /* @__PURE__ */ new Set([
11359
11863
  "PROJECT_DIR",
11360
11864
  "PROJECT_GITHUB_TOKEN",
@@ -11468,19 +11972,19 @@ function buildNewEnv(values) {
11468
11972
  return lines.join("\n");
11469
11973
  }
11470
11974
  function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
11471
- const envPath = join24(factoryDir, ".env");
11472
- const composePath = join24(factoryDir, "docker-compose.yml");
11473
- if (!existsSync26(envPath)) {
11975
+ const envPath = join25(factoryDir, ".env");
11976
+ const composePath = join25(factoryDir, "docker-compose.yml");
11977
+ if (!existsSync27(envPath)) {
11474
11978
  throw new Error(
11475
11979
  `.env not found at ${envPath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
11476
11980
  );
11477
11981
  }
11478
- if (!existsSync26(composePath)) {
11982
+ if (!existsSync27(composePath)) {
11479
11983
  throw new Error(
11480
11984
  `docker-compose.yml not found at ${composePath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
11481
11985
  );
11482
11986
  }
11483
- const existingEnv = readFileSync23(envPath, "utf-8");
11987
+ const existingEnv = readFileSync24(envPath, "utf-8");
11484
11988
  const values = parseExistingEnv(existingEnv);
11485
11989
  const missing = [];
11486
11990
  if (!values.projectDir) missing.push("PROJECT_DIR");
@@ -11498,7 +12002,7 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
11498
12002
  }
11499
12003
  const newEnv = buildNewEnv(values);
11500
12004
  const newCompose = generateComposeYaml("latest");
11501
- const existingCompose = readFileSync23(composePath, "utf-8");
12005
+ const existingCompose = readFileSync24(composePath, "utf-8");
11502
12006
  const envChanged = newEnv !== existingEnv;
11503
12007
  const composeChanged = newCompose !== existingCompose;
11504
12008
  if (!envChanged && !composeChanged) {
@@ -11516,12 +12020,12 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
11516
12020
  if (envChanged) {
11517
12021
  envBackupPath = `${envPath}.backup.${timestamp}`;
11518
12022
  copyFileSync(envPath, envBackupPath);
11519
- writeFileSync19(envPath, newEnv, "utf-8");
12023
+ writeFileSync20(envPath, newEnv, "utf-8");
11520
12024
  }
11521
12025
  if (composeChanged) {
11522
12026
  composeBackupPath = `${composePath}.backup.${timestamp}`;
11523
12027
  copyFileSync(composePath, composeBackupPath);
11524
- writeFileSync19(composePath, newCompose, "utf-8");
12028
+ writeFileSync20(composePath, newCompose, "utf-8");
11525
12029
  }
11526
12030
  return {
11527
12031
  envChanged,
@@ -11534,18 +12038,18 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
11534
12038
 
11535
12039
  // src/cli/commands/upgrade.ts
11536
12040
  function readIdentity(factoryDir) {
11537
- const path = join25(factoryDir, ".beastmode", "factory.json");
11538
- if (!existsSync27(path)) {
12041
+ const path = join26(factoryDir, ".beastmode", "factory.json");
12042
+ if (!existsSync28(path)) {
11539
12043
  throw new Error("No factory.json found. Run beastmode init first.");
11540
12044
  }
11541
- return FactoryIdentitySchema.parse(JSON.parse(readFileSync24(path, "utf-8")));
12045
+ return FactoryIdentitySchema.parse(JSON.parse(readFileSync25(path, "utf-8")));
11542
12046
  }
11543
12047
  function readConfig3(factoryDir) {
11544
- const path = join25(factoryDir, ".beastmode", "config.json");
11545
- if (!existsSync27(path)) {
12048
+ const path = join26(factoryDir, ".beastmode", "config.json");
12049
+ if (!existsSync28(path)) {
11546
12050
  return {};
11547
12051
  }
11548
- return JSON.parse(readFileSync24(path, "utf-8"));
12052
+ return JSON.parse(readFileSync25(path, "utf-8"));
11549
12053
  }
11550
12054
  function upgradeCheckAction(factoryDir) {
11551
12055
  const identity = readIdentity(factoryDir);
@@ -11557,12 +12061,12 @@ function upgradeAction(factoryDir, migrateOnly = false) {
11557
12061
  const targetVersion = migrateOnly ? identity.engine_version : ENGINE_VERSION;
11558
12062
  const result = performUpgrade(identity, config, targetVersion, SCHEMA_VERSION);
11559
12063
  if (result.changes.length > 0) {
11560
- writeFileSync20(
11561
- join25(factoryDir, ".beastmode", "factory.json"),
12064
+ writeFileSync21(
12065
+ join26(factoryDir, ".beastmode", "factory.json"),
11562
12066
  JSON.stringify(result.updatedIdentity, null, 2) + "\n"
11563
12067
  );
11564
- writeFileSync20(
11565
- join25(factoryDir, ".beastmode", "config.json"),
12068
+ writeFileSync21(
12069
+ join26(factoryDir, ".beastmode", "config.json"),
11566
12070
  JSON.stringify(result.updatedConfig, null, 2) + "\n"
11567
12071
  );
11568
12072
  }
@@ -11652,8 +12156,8 @@ init_board();
11652
12156
  init_display();
11653
12157
  init_migrator();
11654
12158
  import { Command as Command14 } from "commander";
11655
- import { resolve as resolve17, join as join26 } from "path";
11656
- import { existsSync as existsSync28, readFileSync as readFileSync25, mkdirSync as mkdirSync16, writeFileSync as writeFileSync21 } from "fs";
12159
+ import { resolve as resolve17, join as join27 } from "path";
12160
+ import { existsSync as existsSync29, readFileSync as readFileSync26, mkdirSync as mkdirSync17, writeFileSync as writeFileSync22 } from "fs";
11657
12161
  var migrateCommand = new Command14("migrate").description("Migrate a daemon config into a .beastmode/ factory").option("--config <path>", "Path to beastmode.daemon.json").option("--dry-run", "Show what would be created without writing files").action(async (opts) => {
11658
12162
  try {
11659
12163
  await runMigrate(opts);
@@ -11665,7 +12169,7 @@ var migrateCommand = new Command14("migrate").description("Migrate a daemon conf
11665
12169
  async function runMigrate(opts) {
11666
12170
  const cwd = process.cwd();
11667
12171
  const configPath = opts.config ? resolve17(opts.config) : resolve17(cwd, "config", "beastmode.daemon.json");
11668
- if (!existsSync28(configPath)) {
12172
+ if (!existsSync29(configPath)) {
11669
12173
  throw new Error(
11670
12174
  `Daemon config not found at ${configPath}
11671
12175
  Use --config <path> to specify a different location.`
@@ -11673,25 +12177,25 @@ async function runMigrate(opts) {
11673
12177
  }
11674
12178
  header("BeastMode Migrate");
11675
12179
  info(`Reading daemon config from: ${configPath}`);
11676
- const configContent = readFileSync25(configPath, "utf-8");
12180
+ const configContent = readFileSync26(configPath, "utf-8");
11677
12181
  const daemonConfig = parseDaemonConfig(configContent);
11678
- const runsDir = join26(cwd, "runs");
12182
+ const runsDir = join27(cwd, "runs");
11679
12183
  let runDirs = [];
11680
12184
  const checkpoints = /* @__PURE__ */ new Map();
11681
- if (existsSync28(runsDir)) {
11682
- const { readdirSync: readdirSync14 } = await import("fs");
11683
- runDirs = readdirSync14(runsDir).filter((d) => {
12185
+ if (existsSync29(runsDir)) {
12186
+ const { readdirSync: readdirSync13 } = await import("fs");
12187
+ runDirs = readdirSync13(runsDir).filter((d) => {
11684
12188
  try {
11685
- return readdirSync14(join26(runsDir, d)).length > 0;
12189
+ return readdirSync13(join27(runsDir, d)).length > 0;
11686
12190
  } catch {
11687
12191
  return false;
11688
12192
  }
11689
12193
  });
11690
12194
  for (const dir of runDirs) {
11691
- const cpPath = join26(runsDir, dir, "checkpoint.json");
11692
- if (existsSync28(cpPath)) {
12195
+ const cpPath = join27(runsDir, dir, "checkpoint.json");
12196
+ if (existsSync29(cpPath)) {
11693
12197
  try {
11694
- const cp = JSON.parse(readFileSync25(cpPath, "utf-8"));
12198
+ const cp = JSON.parse(readFileSync26(cpPath, "utf-8"));
11695
12199
  checkpoints.set(dir, cp);
11696
12200
  } catch {
11697
12201
  }
@@ -11748,28 +12252,28 @@ async function runMigrate(opts) {
11748
12252
  warn("Dry run \u2014 no files written.");
11749
12253
  return;
11750
12254
  }
11751
- const bmDir = join26(cwd, ".beastmode");
11752
- if (existsSync28(bmDir)) {
12255
+ const bmDir = join27(cwd, ".beastmode");
12256
+ if (existsSync29(bmDir)) {
11753
12257
  throw new Error(
11754
12258
  "A .beastmode/ directory already exists. Remove it first to re-migrate."
11755
12259
  );
11756
12260
  }
11757
12261
  for (const file of files) {
11758
- const fullPath = join26(cwd, file.path);
12262
+ const fullPath = join27(cwd, file.path);
11759
12263
  const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
11760
- mkdirSync16(dir, { recursive: true });
11761
- writeFileSync21(fullPath, file.content, "utf-8");
12264
+ mkdirSync17(dir, { recursive: true });
12265
+ writeFileSync22(fullPath, file.content, "utf-8");
11762
12266
  }
11763
- const runsSymlinkTarget = join26(cwd, "runs");
11764
- const bmRunsPath = join26(cwd, "runs");
11765
- if (existsSync28(runsSymlinkTarget)) {
12267
+ const runsSymlinkTarget = join27(cwd, "runs");
12268
+ const bmRunsPath = join27(cwd, "runs");
12269
+ if (existsSync29(runsSymlinkTarget)) {
11766
12270
  info("Existing runs/ directory preserved in-place.");
11767
12271
  }
11768
- const boardPath = join26(bmDir, "board.json");
11769
- if (!existsSync28(boardPath)) {
11770
- writeFileSync21(boardPath, JSON.stringify({ items: [] }, null, 2), "utf-8");
12272
+ const boardPath = join27(bmDir, "board.json");
12273
+ if (!existsSync29(boardPath)) {
12274
+ writeFileSync22(boardPath, JSON.stringify({ items: [] }, null, 2), "utf-8");
11771
12275
  }
11772
- mkdirSync16(join26(bmDir, ".cache"), { recursive: true });
12276
+ mkdirSync17(join27(bmDir, ".cache"), { recursive: true });
11773
12277
  console.log();
11774
12278
  success("Migration complete!");
11775
12279
  info(`Factory created at: ${bmDir}`);
@@ -11790,9 +12294,10 @@ init_display();
11790
12294
  init_board();
11791
12295
  init_bridge();
11792
12296
  init_schemas();
12297
+ init_engine();
11793
12298
  import { Command as Command15 } from "commander";
11794
- import { join as join27 } from "path";
11795
- import { existsSync as existsSync29, readFileSync as readFileSync26, writeFileSync as writeFileSync22, mkdirSync as mkdirSync17 } from "fs";
12299
+ import { join as join28 } from "path";
12300
+ import { existsSync as existsSync30, readFileSync as readFileSync27, writeFileSync as writeFileSync23, mkdirSync as mkdirSync18 } from "fs";
11796
12301
  import { randomUUID as randomUUID3 } from "crypto";
11797
12302
  var runCommand = new Command15("run").description("Run a single pipeline task").argument("[project]", "Project name (defaults to first project)").option("--task <description>", "Task description").action(async (project, opts) => {
11798
12303
  try {
@@ -11812,44 +12317,35 @@ async function runPipeline(projectName, opts) {
11812
12317
  "No BeastMode factory found. Run 'beastmode init' first."
11813
12318
  );
11814
12319
  }
11815
- const bmDir = join27(factoryDir, ".beastmode");
12320
+ const bmDir = join28(factoryDir, ".beastmode");
11816
12321
  header("BeastMode Run");
11817
- const configPath = join27(bmDir, "config.json");
11818
- if (!existsSync29(configPath)) {
12322
+ const configPath = join28(bmDir, "config.json");
12323
+ if (!existsSync30(configPath)) {
11819
12324
  throw new Error("Factory config not found. Run 'beastmode init' first.");
11820
12325
  }
11821
12326
  const factoryConfig = FactoryConfigSchema.parse(
11822
- JSON.parse(readFileSync26(configPath, "utf-8"))
12327
+ JSON.parse(readFileSync27(configPath, "utf-8"))
11823
12328
  );
11824
12329
  let projectConfig = null;
11825
- const projectsDir = join27(bmDir, "projects");
11826
- if (existsSync29(projectsDir)) {
11827
- const { readdirSync: readdirSync14 } = await import("fs");
11828
- const projectFiles = readdirSync14(projectsDir).filter(
11829
- (f) => f.endsWith(".json")
11830
- );
11831
- if (projectName) {
11832
- const file = projectFiles.find(
11833
- (f) => f === `${projectName}.json`
11834
- );
11835
- if (!file) {
11836
- throw new Error(`Project not found: ${projectName}`);
11837
- }
11838
- projectConfig = ProjectConfigSchema.parse(
11839
- JSON.parse(readFileSync26(join27(projectsDir, file), "utf-8"))
11840
- );
11841
- } else if (projectFiles.length > 0) {
11842
- projectConfig = ProjectConfigSchema.parse(
11843
- JSON.parse(readFileSync26(join27(projectsDir, projectFiles[0]), "utf-8"))
11844
- );
12330
+ const projectsDir = join28(bmDir, "projects");
12331
+ if (projectName) {
12332
+ const record = readProjectRecord(projectsDir, projectName);
12333
+ if (!record) {
12334
+ throw new Error(`Project not found: ${projectName}`);
12335
+ }
12336
+ projectConfig = record;
12337
+ } else {
12338
+ const projects = listProjectRecords(projectsDir);
12339
+ if (projects.length > 0) {
12340
+ projectConfig = projects[0];
11845
12341
  info(`Using project: ${projectConfig.name}`);
11846
12342
  }
11847
12343
  }
11848
- const boardPath = join27(bmDir, "board.json");
12344
+ const boardPath = join28(bmDir, "board.json");
11849
12345
  let boardItems = [];
11850
- if (existsSync29(boardPath)) {
12346
+ if (existsSync30(boardPath)) {
11851
12347
  try {
11852
- const raw = JSON.parse(readFileSync26(boardPath, "utf-8"));
12348
+ const raw = JSON.parse(readFileSync27(boardPath, "utf-8"));
11853
12349
  boardItems = Array.isArray(raw.items) ? raw.items : [];
11854
12350
  } catch {
11855
12351
  boardItems = [];
@@ -11866,13 +12362,13 @@ async function runPipeline(projectName, opts) {
11866
12362
  updated_at: now
11867
12363
  };
11868
12364
  boardItems.push(task);
11869
- writeFileSync22(boardPath, JSON.stringify({ items: boardItems }, null, 2), "utf-8");
12365
+ writeFileSync23(boardPath, JSON.stringify({ items: boardItems }, null, 2), "utf-8");
11870
12366
  info(`Created task: ${task.title} (${taskId})`);
11871
- const cacheDir = join27(bmDir, ".cache");
11872
- mkdirSync17(cacheDir, { recursive: true });
11873
- const daemonConfigPath = join27(cacheDir, "daemon.json");
12367
+ const cacheDir = join28(bmDir, ".cache");
12368
+ mkdirSync18(cacheDir, { recursive: true });
12369
+ const daemonConfigPath = join28(cacheDir, "daemon.json");
11874
12370
  const daemonConfig = generateDaemonConfig(factoryConfig, projectConfig, factoryDir);
11875
- writeFileSync22(daemonConfigPath, JSON.stringify(daemonConfig, null, 2), "utf-8");
12371
+ writeFileSync23(daemonConfigPath, JSON.stringify(daemonConfig, null, 2), "utf-8");
11876
12372
  info(`Generated daemon config at: ${daemonConfigPath}`);
11877
12373
  const { execSync: execSync14 } = await import("child_process");
11878
12374
  let pythonAvailable = false;
@@ -11896,7 +12392,7 @@ async function runPipeline(projectName, opts) {
11896
12392
  const daemonPaths = findPythonDaemonPaths(envPath, factoryDir);
11897
12393
  let daemonFound = false;
11898
12394
  for (const p of daemonPaths) {
11899
- if (existsSync29(p)) {
12395
+ if (existsSync30(p)) {
11900
12396
  daemonFound = true;
11901
12397
  break;
11902
12398
  }
@@ -11923,7 +12419,7 @@ async function runPipeline(projectName, opts) {
11923
12419
  const startTime = Date.now();
11924
12420
  const pollInterval = setInterval(() => {
11925
12421
  try {
11926
- const board = JSON.parse(readFileSync26(boardPath, "utf-8"));
12422
+ const board = JSON.parse(readFileSync27(boardPath, "utf-8"));
11927
12423
  const items = Array.isArray(board.items) ? board.items : [];
11928
12424
  const taskItem = items.find((i) => i.id === taskId);
11929
12425
  if (taskItem) {
@@ -11968,9 +12464,10 @@ init_display();
11968
12464
  init_board();
11969
12465
  init_bridge();
11970
12466
  init_schemas();
12467
+ init_engine();
11971
12468
  import { Command as Command16 } from "commander";
11972
- import { join as join28 } from "path";
11973
- import { existsSync as existsSync30, readFileSync as readFileSync27, writeFileSync as writeFileSync23, mkdirSync as mkdirSync18 } from "fs";
12469
+ import { join as join29 } from "path";
12470
+ import { existsSync as existsSync31, readFileSync as readFileSync28, writeFileSync as writeFileSync24, mkdirSync as mkdirSync19 } from "fs";
11974
12471
  var daemonCommand = new Command16("daemon").description("Start the BeastMode daemon via bridge").option("--dry-run", "Generate config but don't start daemon").option(
11975
12472
  "--log-level <level>",
11976
12473
  "Log level (DEBUG, INFO, WARNING, ERROR)",
@@ -11990,38 +12487,31 @@ async function runDaemon(opts) {
11990
12487
  "No BeastMode factory found. Run 'beastmode init' first."
11991
12488
  );
11992
12489
  }
11993
- const bmDir = join28(factoryDir, ".beastmode");
12490
+ const bmDir = join29(factoryDir, ".beastmode");
11994
12491
  header("BeastMode Daemon");
11995
- const configPath = join28(bmDir, "config.json");
11996
- if (!existsSync30(configPath)) {
12492
+ const configPath = join29(bmDir, "config.json");
12493
+ if (!existsSync31(configPath)) {
11997
12494
  throw new Error("Factory config not found. Run 'beastmode init' first.");
11998
12495
  }
11999
12496
  const factoryConfig = FactoryConfigSchema.parse(
12000
- JSON.parse(readFileSync27(configPath, "utf-8"))
12497
+ JSON.parse(readFileSync28(configPath, "utf-8"))
12001
12498
  );
12002
12499
  let projectConfig = null;
12003
- const projectsDir = join28(bmDir, "projects");
12004
- if (existsSync30(projectsDir)) {
12005
- const { readdirSync: readdirSync14 } = await import("fs");
12006
- const projectFiles = readdirSync14(projectsDir).filter(
12007
- (f) => f.endsWith(".json")
12008
- );
12009
- if (projectFiles.length > 0) {
12010
- projectConfig = ProjectConfigSchema.parse(
12011
- JSON.parse(readFileSync27(join28(projectsDir, projectFiles[0]), "utf-8"))
12012
- );
12013
- info(`Using project: ${projectConfig.name}`);
12014
- }
12015
- }
12016
- const cacheDir = join28(bmDir, ".cache");
12017
- mkdirSync18(cacheDir, { recursive: true });
12018
- const daemonConfigPath = join28(cacheDir, "daemon.json");
12500
+ const projectsDir = join29(bmDir, "projects");
12501
+ const projects = listProjectRecords(projectsDir);
12502
+ if (projects.length > 0) {
12503
+ projectConfig = projects[0];
12504
+ info(`Using project: ${projectConfig.name}`);
12505
+ }
12506
+ const cacheDir = join29(bmDir, ".cache");
12507
+ mkdirSync19(cacheDir, { recursive: true });
12508
+ const daemonConfigPath = join29(cacheDir, "daemon.json");
12019
12509
  const daemonConfig = generateDaemonConfig(
12020
12510
  factoryConfig,
12021
12511
  projectConfig,
12022
12512
  factoryDir
12023
12513
  );
12024
- writeFileSync23(
12514
+ writeFileSync24(
12025
12515
  daemonConfigPath,
12026
12516
  JSON.stringify(daemonConfig, null, 2),
12027
12517
  "utf-8"
@@ -12060,7 +12550,7 @@ async function runDaemon(opts) {
12060
12550
  });
12061
12551
  info(`Starting daemon: ${pythonCmd} ${cmd.args.join(" ")}`);
12062
12552
  console.log();
12063
- const pidFile = join28(bmDir, "daemon.pid");
12553
+ const pidFile = join29(bmDir, "daemon.pid");
12064
12554
  const { spawn: spawn4 } = await import("child_process");
12065
12555
  const child = spawn4(pythonCmd, cmd.args, {
12066
12556
  stdio: "inherit",
@@ -12071,7 +12561,7 @@ async function runDaemon(opts) {
12071
12561
  }
12072
12562
  });
12073
12563
  if (child.pid) {
12074
- writeFileSync23(pidFile, String(child.pid), "utf-8");
12564
+ writeFileSync24(pidFile, String(child.pid), "utf-8");
12075
12565
  }
12076
12566
  const signalHandler = (signal) => {
12077
12567
  info(`Forwarding ${signal} to daemon...`);
@@ -12110,8 +12600,8 @@ var mcpCommand = new Command17("mcp").description("Start the BeastMode MCP serve
12110
12600
  // src/cli/commands/deploy.ts
12111
12601
  init_display();
12112
12602
  import { Command as Command18 } from "commander";
12113
- import { resolve as resolve19, join as join30 } from "path";
12114
- import { existsSync as existsSync32, writeFileSync as writeFileSync25, readFileSync as readFileSync29 } from "fs";
12603
+ import { resolve as resolve19, join as join31 } from "path";
12604
+ import { existsSync as existsSync33, writeFileSync as writeFileSync26, readFileSync as readFileSync30 } from "fs";
12115
12605
  import { execSync as execSync9 } from "child_process";
12116
12606
  import { randomBytes } from "crypto";
12117
12607
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -12166,8 +12656,8 @@ async function runDeploy(opts) {
12166
12656
  process.exit(1);
12167
12657
  }
12168
12658
  const factoryDir = resolve19(".");
12169
- const bmDir = join30(factoryDir, ".beastmode");
12170
- if (!existsSync32(bmDir)) {
12659
+ const bmDir = join31(factoryDir, ".beastmode");
12660
+ if (!existsSync33(bmDir)) {
12171
12661
  error(
12172
12662
  "No .beastmode directory found. Run 'beastmode init' or 'beastmode migrate' first."
12173
12663
  );
@@ -12191,33 +12681,33 @@ async function runDeploy(opts) {
12191
12681
  "../../index.js"
12192
12682
  );
12193
12683
  }
12194
- const boardVenvPython = join30(factoryDir, "board", ".venv", "bin", "python");
12195
- const daemonVenvPython = join30(factoryDir, "daemon", ".venv", "bin", "python");
12196
- const boardPython = existsSync32(boardVenvPython) ? boardVenvPython : "python3";
12197
- const daemonPython = existsSync32(daemonVenvPython) ? daemonVenvPython : "python3";
12684
+ const boardVenvPython = join31(factoryDir, "board", ".venv", "bin", "python");
12685
+ const daemonVenvPython = join31(factoryDir, "daemon", ".venv", "bin", "python");
12686
+ const boardPython = existsSync33(boardVenvPython) ? boardVenvPython : "python3";
12687
+ const daemonPython = existsSync33(daemonVenvPython) ? daemonVenvPython : "python3";
12198
12688
  const user = execSync9("whoami", { encoding: "utf-8" }).trim();
12199
12689
  const home = process.env.HOME || `/home/${user}`;
12200
12690
  const port = opts.port;
12201
12691
  const host = opts.host;
12202
- const dotEnv = join30(factoryDir, ".env");
12203
- const secretsEnv = join30(bmDir, "secrets.env.local");
12204
- const envContent = existsSync32(dotEnv) ? readFileSync29(dotEnv, "utf-8") : "";
12205
- const secretsContent = existsSync32(secretsEnv) ? readFileSync29(secretsEnv, "utf-8") : "";
12692
+ const dotEnv = join31(factoryDir, ".env");
12693
+ const secretsEnv = join31(bmDir, "secrets.env.local");
12694
+ const envContent = existsSync33(dotEnv) ? readFileSync30(dotEnv, "utf-8") : "";
12695
+ const secretsContent = existsSync33(secretsEnv) ? readFileSync30(secretsEnv, "utf-8") : "";
12206
12696
  const hasPassword = envContent.includes("BEASTMODE_UI_PASSWORD=") && !envContent.includes("BEASTMODE_UI_PASSWORD=\n") || secretsContent.includes("BEASTMODE_UI_PASSWORD=") && !secretsContent.includes("BEASTMODE_UI_PASSWORD=\n") || !!process.env.BEASTMODE_UI_PASSWORD;
12207
12697
  if (!hasPassword && opts.host === "0.0.0.0") {
12208
12698
  const generated = randomBytes(18).toString("base64url");
12209
- const target = existsSync32(secretsEnv) ? secretsEnv : dotEnv;
12699
+ const target = existsSync33(secretsEnv) ? secretsEnv : dotEnv;
12210
12700
  const append = `
12211
12701
  # Auto-generated board UI password (deploy)
12212
12702
  BEASTMODE_UI_PASSWORD=${generated}
12213
12703
  `;
12214
- writeFileSync25(target, (existsSync32(target) ? readFileSync29(target, "utf-8") : "") + append, "utf-8");
12704
+ writeFileSync26(target, (existsSync33(target) ? readFileSync30(target, "utf-8") : "") + append, "utf-8");
12215
12705
  info(`Board UI password auto-generated and saved to ${target}`);
12216
12706
  success(`Password: ${generated}`);
12217
12707
  info("Save this password \u2014 you'll need it to access the board UI.");
12218
12708
  }
12219
12709
  const envFileLines = [];
12220
- if (existsSync32(secretsEnv)) {
12710
+ if (existsSync33(secretsEnv)) {
12221
12711
  envFileLines.push(`EnvironmentFile=${secretsEnv}`);
12222
12712
  } else {
12223
12713
  envFileLines.push(`# No secrets.env.local found at time of deploy`);
@@ -12300,7 +12790,7 @@ BEASTMODE_UI_PASSWORD=${generated}
12300
12790
  info(`Writing service file to ${svc.path}...`);
12301
12791
  try {
12302
12792
  const tmpPath = `/tmp/${svc.name}.service`;
12303
- writeFileSync25(tmpPath, svc.content, "utf-8");
12793
+ writeFileSync26(tmpPath, svc.content, "utf-8");
12304
12794
  execSync9(`sudo cp ${tmpPath} ${svc.path}`, { stdio: "inherit" });
12305
12795
  success(`${svc.name} service file installed`);
12306
12796
  } catch {
@@ -12429,9 +12919,9 @@ async function deployToAWS(opts) {
12429
12919
  }
12430
12920
  const __filename2 = fileURLToPath3(import.meta.url);
12431
12921
  const __dirname2 = dirname7(__filename2);
12432
- const templatePath = join30(__dirname2, "..", "..", "infra", "cloudformation", "beastmode.yaml");
12433
- const cwdTemplate = join30(process.cwd(), "infra", "cloudformation", "beastmode.yaml");
12434
- const template = existsSync32(templatePath) ? templatePath : existsSync32(cwdTemplate) ? cwdTemplate : null;
12922
+ const templatePath = join31(__dirname2, "..", "..", "infra", "cloudformation", "beastmode.yaml");
12923
+ const cwdTemplate = join31(process.cwd(), "infra", "cloudformation", "beastmode.yaml");
12924
+ const template = existsSync33(templatePath) ? templatePath : existsSync33(cwdTemplate) ? cwdTemplate : null;
12435
12925
  if (!template) {
12436
12926
  error("CloudFormation template not found. Expected at infra/cloudformation/beastmode.yaml");
12437
12927
  process.exit(1);
@@ -12685,19 +13175,19 @@ var logsCommand = new Command21("logs").description("Stream BeastMode service lo
12685
13175
 
12686
13176
  // src/cli/commands/update.ts
12687
13177
  import { Command as Command22 } from "commander";
12688
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync26 } from "fs";
13178
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync27 } from "fs";
12689
13179
  init_display();
12690
13180
  async function runUpdate(opts) {
12691
13181
  const cwd = opts.cwd ?? process.cwd();
12692
13182
  const composePath = requireComposeFile(cwd);
12693
13183
  if (opts.tag) {
12694
- let content = readFileSync30(composePath, "utf-8");
13184
+ let content = readFileSync31(composePath, "utf-8");
12695
13185
  const tagPattern = new RegExp(
12696
13186
  `(${GHCR_IMAGE_PREFIX.replace(/[/]/g, "\\/")}\\/(?:board|daemon|ui)):([\\w.\\-]+)`,
12697
13187
  "g"
12698
13188
  );
12699
13189
  content = content.replace(tagPattern, `$1:${opts.tag}`);
12700
- writeFileSync26(composePath, content, "utf-8");
13190
+ writeFileSync27(composePath, content, "utf-8");
12701
13191
  }
12702
13192
  runCompose(["pull"], { cwd, inherit: true });
12703
13193
  runCompose(["up", "-d"], { cwd, inherit: true });
@@ -12719,26 +13209,27 @@ var updateCommand = new Command22("update").description("Pull latest BeastMode i
12719
13209
  });
12720
13210
 
12721
13211
  // src/cli/commands/runner-cmd.ts
13212
+ init_engine();
12722
13213
  init_display();
12723
13214
  import { Command as Command23 } from "commander";
12724
13215
  import { spawn as spawn3 } from "child_process";
12725
- import { existsSync as existsSync37, readdirSync as readdirSync12, readFileSync as readFileSync34 } from "fs";
12726
- import { basename as basename6, join as join36, resolve as resolve20 } from "path";
13216
+ import { existsSync as existsSync38 } from "fs";
13217
+ import { basename as basename7, join as join37, resolve as resolve20 } from "path";
12727
13218
 
12728
13219
  // src/cli/runner-image-builder.ts
12729
13220
  import { execSync as execSync10 } from "child_process";
12730
13221
  import { createHash } from "crypto";
12731
13222
  import {
12732
- existsSync as existsSync34,
12733
- mkdirSync as mkdirSync20,
12734
- readFileSync as readFileSync32,
12735
- writeFileSync as writeFileSync27
13223
+ existsSync as existsSync35,
13224
+ mkdirSync as mkdirSync21,
13225
+ readFileSync as readFileSync33,
13226
+ writeFileSync as writeFileSync28
12736
13227
  } from "fs";
12737
- import { join as join32 } from "path";
13228
+ import { join as join33 } from "path";
12738
13229
 
12739
13230
  // src/cli/stack-detect.ts
12740
- import { existsSync as existsSync33, readFileSync as readFileSync31 } from "fs";
12741
- import { join as join31 } from "path";
13231
+ import { existsSync as existsSync34, readFileSync as readFileSync32 } from "fs";
13232
+ import { join as join32 } from "path";
12742
13233
  var NODE_LOCKFILES = [
12743
13234
  "package-lock.json",
12744
13235
  "pnpm-lock.yaml",
@@ -12776,7 +13267,7 @@ var STACK_LOCKFILES = {
12776
13267
  };
12777
13268
  function readFileSafe2(path) {
12778
13269
  try {
12779
- return readFileSync31(path, "utf-8");
13270
+ return readFileSync32(path, "utf-8");
12780
13271
  } catch {
12781
13272
  return null;
12782
13273
  }
@@ -12789,7 +13280,7 @@ function parseJsonSafe2(content) {
12789
13280
  }
12790
13281
  }
12791
13282
  function detectRunnerStack(projectDir) {
12792
- const pkgContent = readFileSafe2(join31(projectDir, "package.json"));
13283
+ const pkgContent = readFileSafe2(join32(projectDir, "package.json"));
12793
13284
  if (pkgContent) {
12794
13285
  const pkg = parseJsonSafe2(pkgContent);
12795
13286
  if (pkg) {
@@ -12803,36 +13294,36 @@ function detectRunnerStack(projectDir) {
12803
13294
  }
12804
13295
  return { name: "node", language: "node" };
12805
13296
  }
12806
- if (existsSync33(join31(projectDir, "manage.py"))) {
13297
+ if (existsSync34(join32(projectDir, "manage.py"))) {
12807
13298
  return { name: "django", language: "python" };
12808
13299
  }
12809
- const pyproject = readFileSafe2(join31(projectDir, "pyproject.toml"));
13300
+ const pyproject = readFileSafe2(join32(projectDir, "pyproject.toml"));
12810
13301
  if (pyproject) {
12811
13302
  if (pyproject.toLowerCase().includes("fastapi")) {
12812
13303
  return { name: "fastapi", language: "python" };
12813
13304
  }
12814
13305
  return { name: "python", language: "python" };
12815
13306
  }
12816
- if (existsSync33(join31(projectDir, "requirements.txt"))) {
13307
+ if (existsSync34(join32(projectDir, "requirements.txt"))) {
12817
13308
  return { name: "python", language: "python" };
12818
13309
  }
12819
- if (existsSync33(join31(projectDir, "go.mod"))) {
13310
+ if (existsSync34(join32(projectDir, "go.mod"))) {
12820
13311
  return { name: "go", language: "go" };
12821
13312
  }
12822
- if (existsSync33(join31(projectDir, "Cargo.toml"))) {
13313
+ if (existsSync34(join32(projectDir, "Cargo.toml"))) {
12823
13314
  return { name: "rust", language: "rust" };
12824
13315
  }
12825
- if (existsSync33(join31(projectDir, "pom.xml"))) {
13316
+ if (existsSync34(join32(projectDir, "pom.xml"))) {
12826
13317
  return { name: "java-maven", language: "java" };
12827
13318
  }
12828
- if (existsSync33(join31(projectDir, "build.gradle")) || existsSync33(join31(projectDir, "build.gradle.kts"))) {
13319
+ if (existsSync34(join32(projectDir, "build.gradle")) || existsSync34(join32(projectDir, "build.gradle.kts"))) {
12829
13320
  return { name: "java-gradle", language: "java" };
12830
13321
  }
12831
13322
  return { name: "node", language: "node" };
12832
13323
  }
12833
13324
  function findLockfiles(projectDir, stackName) {
12834
13325
  const candidates = STACK_LOCKFILES[stackName] ?? [];
12835
- return candidates.filter((f) => existsSync33(join31(projectDir, f)));
13326
+ return candidates.filter((f) => existsSync34(join32(projectDir, f)));
12836
13327
  }
12837
13328
 
12838
13329
  // src/cli/runner-image-builder.ts
@@ -12934,12 +13425,12 @@ function generateDockerfile(stack, lockfiles) {
12934
13425
  function computeLockfileHash(projectDir, lockfiles) {
12935
13426
  const hash = createHash("sha256");
12936
13427
  for (const lf of lockfiles) {
12937
- const path = join32(projectDir, lf);
13428
+ const path = join33(projectDir, lf);
12938
13429
  hash.update(lf);
12939
13430
  hash.update("\0");
12940
- if (existsSync34(path)) {
13431
+ if (existsSync35(path)) {
12941
13432
  try {
12942
- hash.update(readFileSync32(path));
13433
+ hash.update(readFileSync33(path));
12943
13434
  } catch {
12944
13435
  hash.update("<unreadable>");
12945
13436
  }
@@ -12951,10 +13442,10 @@ function computeLockfileHash(projectDir, lockfiles) {
12951
13442
  return hash.digest("hex").slice(0, 16);
12952
13443
  }
12953
13444
  function readLastBuild(projectDir) {
12954
- const path = join32(projectDir, RUNNER_STATE_DIR, LAST_BUILD_FILE);
12955
- if (!existsSync34(path)) return null;
13445
+ const path = join33(projectDir, RUNNER_STATE_DIR, LAST_BUILD_FILE);
13446
+ if (!existsSync35(path)) return null;
12956
13447
  try {
12957
- const data = JSON.parse(readFileSync32(path, "utf-8"));
13448
+ const data = JSON.parse(readFileSync33(path, "utf-8"));
12958
13449
  if (data && typeof data === "object" && typeof data.lockfileHash === "string" && typeof data.imageTag === "string" && typeof data.builtAt === "string") {
12959
13450
  return data;
12960
13451
  }
@@ -12973,17 +13464,17 @@ function imageTagFor(projectName, hash) {
12973
13464
  return `beastmode-runner-${safeName}:${hash}`;
12974
13465
  }
12975
13466
  function writeStateFile(projectDir, fileName, payload) {
12976
- const dir = join32(projectDir, RUNNER_STATE_DIR);
12977
- mkdirSync20(dir, { recursive: true });
12978
- writeFileSync27(
12979
- join32(dir, fileName),
13467
+ const dir = join33(projectDir, RUNNER_STATE_DIR);
13468
+ mkdirSync21(dir, { recursive: true });
13469
+ writeFileSync28(
13470
+ join33(dir, fileName),
12980
13471
  JSON.stringify(payload, null, 2) + "\n",
12981
13472
  "utf-8"
12982
13473
  );
12983
13474
  }
12984
13475
  function buildRunnerImage(opts) {
12985
13476
  const { projectDir, projectName, stack, lockfiles, force, dryRun } = opts;
12986
- if (!existsSync34(projectDir)) {
13477
+ if (!existsSync35(projectDir)) {
12987
13478
  throw new Error(`Project directory not found: ${projectDir}`);
12988
13479
  }
12989
13480
  const lockfileHash = computeLockfileHash(projectDir, lockfiles);
@@ -13102,11 +13593,11 @@ function resolveGitHubConfig() {
13102
13593
  // src/cli/runner-helpers.ts
13103
13594
  import { execSync as execSync11, spawn, spawnSync as spawnSync5 } from "child_process";
13104
13595
  import { promises as fs } from "fs";
13105
- import { join as join33 } from "path";
13596
+ import { join as join34 } from "path";
13106
13597
  init_display();
13107
13598
  async function writeRunnerMeta(dir, meta) {
13108
13599
  await fs.mkdir(dir, { recursive: true });
13109
- const path = join33(dir, "runner-meta.json");
13600
+ const path = join34(dir, "runner-meta.json");
13110
13601
  await fs.writeFile(path, JSON.stringify(meta, null, 2) + "\n", "utf-8");
13111
13602
  }
13112
13603
  function resolveRepoSlug() {
@@ -13307,13 +13798,13 @@ import {
13307
13798
  } from "child_process";
13308
13799
  import {
13309
13800
  createWriteStream,
13310
- existsSync as existsSync35,
13311
- mkdirSync as mkdirSync21,
13801
+ existsSync as existsSync36,
13802
+ mkdirSync as mkdirSync22,
13312
13803
  unlinkSync as unlinkSync5,
13313
- writeFileSync as writeFileSync28
13804
+ writeFileSync as writeFileSync29
13314
13805
  } from "fs";
13315
13806
  import { homedir as homedir4 } from "os";
13316
- import { join as join34, dirname as dirname8 } from "path";
13807
+ import { join as join35, dirname as dirname8 } from "path";
13317
13808
  import { Readable } from "stream";
13318
13809
  import { pipeline } from "stream/promises";
13319
13810
  init_display();
@@ -13343,8 +13834,8 @@ function runnerDownloadUrl(os, arch) {
13343
13834
  return `https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-${ghOs}-${arch}-${RUNNER_VERSION}.tar.gz`;
13344
13835
  }
13345
13836
  async function downloadAndExtractRunner(installDir, os, arch) {
13346
- const runShPath = join34(installDir, "run.sh");
13347
- if (existsSync35(runShPath)) {
13837
+ const runShPath = join35(installDir, "run.sh");
13838
+ if (existsSync36(runShPath)) {
13348
13839
  info(
13349
13840
  `Runner binary already present at ${runShPath} \u2014 skipping download.`
13350
13841
  );
@@ -13354,7 +13845,7 @@ async function downloadAndExtractRunner(installDir, os, arch) {
13354
13845
  return;
13355
13846
  }
13356
13847
  const url = runnerDownloadUrl(os, arch);
13357
- const tarball = join34(installDir, "runner.tar.gz");
13848
+ const tarball = join35(installDir, "runner.tar.gz");
13358
13849
  setupStep(
13359
13850
  `Downloading GitHub Actions runner v${RUNNER_VERSION} (${os}/${arch})...`
13360
13851
  );
@@ -13405,7 +13896,7 @@ async function configureRunner(installDir, repoUrl, token, name, labels) {
13405
13896
  "--unattended",
13406
13897
  "--replace"
13407
13898
  ];
13408
- const result = spawnSync6(join34(installDir, "config.sh"), args, {
13899
+ const result = spawnSync6(join35(installDir, "config.sh"), args, {
13409
13900
  cwd: installDir,
13410
13901
  stdio: ["ignore", "inherit", "pipe"],
13411
13902
  encoding: "utf-8"
@@ -13433,7 +13924,7 @@ function startRunnerForeground(installDir) {
13433
13924
  "Use --service to install as a launchd agent for persistent operation."
13434
13925
  );
13435
13926
  }
13436
- spawn2(join34(installDir, "run.sh"), [], {
13927
+ spawn2(join35(installDir, "run.sh"), [], {
13437
13928
  cwd: installDir,
13438
13929
  stdio: "inherit"
13439
13930
  });
@@ -13458,10 +13949,10 @@ WantedBy=default.target
13458
13949
  }
13459
13950
  async function installSystemdService(installDir, name) {
13460
13951
  const unitName = `beastmode-runner-${name}.service`;
13461
- const unitDir = join34(homedir4(), ".config", "systemd", "user");
13462
- mkdirSync21(unitDir, { recursive: true });
13463
- writeFileSync28(
13464
- join34(unitDir, unitName),
13952
+ const unitDir = join35(homedir4(), ".config", "systemd", "user");
13953
+ mkdirSync22(unitDir, { recursive: true });
13954
+ writeFileSync29(
13955
+ join35(unitDir, unitName),
13465
13956
  systemdUnitContent(installDir, name),
13466
13957
  "utf-8"
13467
13958
  );
@@ -13515,15 +14006,15 @@ function launchdPlistContent(installDir, name) {
13515
14006
  }
13516
14007
  async function installLaunchdService(installDir, name) {
13517
14008
  const label = `com.beastmode.runner.${name}`;
13518
- const plistPath2 = join34(
14009
+ const plistPath2 = join35(
13519
14010
  homedir4(),
13520
14011
  "Library",
13521
14012
  "LaunchAgents",
13522
14013
  `${label}.plist`
13523
14014
  );
13524
- mkdirSync21(dirname8(plistPath2), { recursive: true });
13525
- mkdirSync21(join34(installDir, "logs"), { recursive: true });
13526
- writeFileSync28(plistPath2, launchdPlistContent(installDir, name), "utf-8");
14015
+ mkdirSync22(dirname8(plistPath2), { recursive: true });
14016
+ mkdirSync22(join35(installDir, "logs"), { recursive: true });
14017
+ writeFileSync29(plistPath2, launchdPlistContent(installDir, name), "utf-8");
13527
14018
  try {
13528
14019
  execSync12(`launchctl load ${plistPath2}`, { stdio: "inherit" });
13529
14020
  } catch (err) {
@@ -13564,7 +14055,7 @@ async function nativeRunnerSetup(opts) {
13564
14055
  }
13565
14056
  const ghConfig = resolveGitHubConfig();
13566
14057
  const repoSlug = opts.repo ?? resolveRepoSlug();
13567
- const installDir = join34(homedir4(), ".beastmode", "runners", opts.name);
14058
+ const installDir = join35(homedir4(), ".beastmode", "runners", opts.name);
13568
14059
  if (opts.dryRun) {
13569
14060
  info(
13570
14061
  `[dry-run] Would install native runner '${opts.name}' for repo ${repoSlug}`
@@ -13574,7 +14065,7 @@ async function nativeRunnerSetup(opts) {
13574
14065
  info(`[dry-run] Mode: ${opts.service ? "service" : "foreground"}`);
13575
14066
  return;
13576
14067
  }
13577
- mkdirSync21(installDir, { recursive: true });
14068
+ mkdirSync22(installDir, { recursive: true });
13578
14069
  setupStep("Generating registration token via GitHub API...");
13579
14070
  const { token: regToken } = await createRegistrationToken(ghConfig);
13580
14071
  await downloadAndExtractRunner(installDir, platformOs, arch);
@@ -13612,13 +14103,13 @@ async function nativeRunnerSetup(opts) {
13612
14103
 
13613
14104
  // src/cli/workflow-switcher.ts
13614
14105
  import {
13615
- existsSync as existsSync36,
13616
- mkdirSync as mkdirSync22,
13617
- readFileSync as readFileSync33,
14106
+ existsSync as existsSync37,
14107
+ mkdirSync as mkdirSync23,
14108
+ readFileSync as readFileSync34,
13618
14109
  unlinkSync as unlinkSync6,
13619
- writeFileSync as writeFileSync29
14110
+ writeFileSync as writeFileSync30
13620
14111
  } from "fs";
13621
- import { dirname as dirname9, join as join35 } from "path";
14112
+ import { dirname as dirname9, join as join36 } from "path";
13622
14113
  var TARGET_LABEL = "[self-hosted, beastmode]";
13623
14114
  var TARGET_WORKFLOWS = [
13624
14115
  ".github/workflows/test.yml",
@@ -13657,8 +14148,8 @@ function restoreRunsOn(content, originals) {
13657
14148
  return newLines.join("\n");
13658
14149
  }
13659
14150
  async function switchWorkflows(projectDir) {
13660
- const statePath = join35(projectDir, STATE_FILE);
13661
- if (existsSync36(statePath)) {
14151
+ const statePath = join36(projectDir, STATE_FILE);
14152
+ if (existsSync37(statePath)) {
13662
14153
  return { alreadySwitched: true, files: [] };
13663
14154
  }
13664
14155
  const state = {
@@ -13668,40 +14159,40 @@ async function switchWorkflows(projectDir) {
13668
14159
  };
13669
14160
  const resultFiles = [];
13670
14161
  for (const relPath of TARGET_WORKFLOWS) {
13671
- const absPath = join35(projectDir, relPath);
13672
- if (!existsSync36(absPath)) {
14162
+ const absPath = join36(projectDir, relPath);
14163
+ if (!existsSync37(absPath)) {
13673
14164
  throw new Error(`Workflow file not found: ${relPath}`);
13674
14165
  }
13675
- const content = readFileSync33(absPath, "utf-8");
14166
+ const content = readFileSync34(absPath, "utf-8");
13676
14167
  const { newContent, originals } = replaceRunsOn(content, TARGET_LABEL);
13677
14168
  if (originals.length === 0) {
13678
14169
  throw new Error(`No runs-on found in ${relPath}`);
13679
14170
  }
13680
- writeFileSync29(absPath, newContent, "utf-8");
14171
+ writeFileSync30(absPath, newContent, "utf-8");
13681
14172
  state.files.push({ relativePath: relPath, originals });
13682
14173
  resultFiles.push({ relativePath: relPath, jobCount: originals.length });
13683
14174
  }
13684
- mkdirSync22(dirname9(statePath), { recursive: true });
13685
- writeFileSync29(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
14175
+ mkdirSync23(dirname9(statePath), { recursive: true });
14176
+ writeFileSync30(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
13686
14177
  return { alreadySwitched: false, files: resultFiles };
13687
14178
  }
13688
14179
  async function restoreWorkflows(projectDir) {
13689
- const statePath = join35(projectDir, STATE_FILE);
13690
- if (!existsSync36(statePath)) {
14180
+ const statePath = join36(projectDir, STATE_FILE);
14181
+ if (!existsSync37(statePath)) {
13691
14182
  return { nothingToRestore: true, files: [] };
13692
14183
  }
13693
14184
  const state = JSON.parse(
13694
- readFileSync33(statePath, "utf-8")
14185
+ readFileSync34(statePath, "utf-8")
13695
14186
  );
13696
14187
  const resultFiles = [];
13697
14188
  for (const fileState of state.files) {
13698
- const absPath = join35(projectDir, fileState.relativePath);
13699
- if (!existsSync36(absPath)) {
14189
+ const absPath = join36(projectDir, fileState.relativePath);
14190
+ if (!existsSync37(absPath)) {
13700
14191
  throw new Error(`Workflow file not found: ${fileState.relativePath}`);
13701
14192
  }
13702
- const content = readFileSync33(absPath, "utf-8");
14193
+ const content = readFileSync34(absPath, "utf-8");
13703
14194
  const newContent = restoreRunsOn(content, fileState.originals);
13704
- writeFileSync29(absPath, newContent, "utf-8");
14195
+ writeFileSync30(absPath, newContent, "utf-8");
13705
14196
  resultFiles.push({
13706
14197
  relativePath: fileState.relativePath,
13707
14198
  jobCount: fileState.originals.length
@@ -13714,25 +14205,19 @@ async function restoreWorkflows(projectDir) {
13714
14205
  // src/cli/commands/runner-cmd.ts
13715
14206
  var runnerCommand = new Command23("runner").description("Manage self-hosted GitHub Actions runners");
13716
14207
  function resolveProjectName(projectDir) {
13717
- const projectsDir = join36(projectDir, ".beastmode", "projects");
13718
- if (existsSync37(projectsDir)) {
14208
+ const projectsDir = join37(projectDir, ".beastmode", "projects");
14209
+ if (existsSync38(projectsDir)) {
13719
14210
  try {
13720
- for (const entry of readdirSync12(projectsDir)) {
13721
- if (!entry.endsWith(".json")) continue;
13722
- const file = join36(projectsDir, entry);
13723
- try {
13724
- const raw = readFileSync34(file, "utf-8");
13725
- const data = JSON.parse(raw);
13726
- if (typeof data.path === "string" && resolve20(data.path) === resolve20(projectDir) && typeof data.name === "string" && data.name.length > 0) {
13727
- return data.name;
13728
- }
13729
- } catch {
13730
- }
13731
- }
14211
+ const records = listProjectRecords(projectsDir);
14212
+ const target = resolve20(projectDir);
14213
+ const match = records.find(
14214
+ (r) => typeof r.path === "string" && resolve20(r.path) === target
14215
+ );
14216
+ if (match && match.name) return match.name;
13732
14217
  } catch {
13733
14218
  }
13734
14219
  }
13735
- return basename6(resolve20(projectDir));
14220
+ return basename7(resolve20(projectDir));
13736
14221
  }
13737
14222
  async function runnerSetupAction(opts) {
13738
14223
  if (opts.service !== void 0 && !opts.native) {
@@ -14057,7 +14542,7 @@ runnerCommand.command("restore-workflows").description("Restore workflows to ori
14057
14542
  });
14058
14543
  async function runnerBuildImageAction(opts) {
14059
14544
  const projectDir = resolve20(opts.projectDir);
14060
- if (!existsSync37(projectDir)) {
14545
+ if (!existsSync38(projectDir)) {
14061
14546
  throw new Error(`Project directory not found: ${projectDir}`);
14062
14547
  }
14063
14548
  const stack = detectRunnerStack(projectDir);
@@ -14107,25 +14592,14 @@ runnerCommand.command("build-image").description(
14107
14592
  // src/cli/commands/project-cmd.ts
14108
14593
  init_engine();
14109
14594
  import { Command as Command24 } from "commander";
14110
- import { existsSync as existsSync38, mkdirSync as mkdirSync23, writeFileSync as writeFileSync30, readFileSync as readFileSync35, readdirSync as readdirSync13, renameSync as renameSync2 } from "fs";
14111
- import { join as join37, resolve as resolve21, basename as basename7 } from "path";
14595
+ import { existsSync as existsSync39, mkdirSync as mkdirSync24, readFileSync as readFileSync35, renameSync as renameSync3 } from "fs";
14596
+ import { join as join38, resolve as resolve21, basename as basename8 } from "path";
14112
14597
  import { execSync as execSync13 } from "child_process";
14113
14598
  var DEFAULT_MAX_PROJECTS = 5;
14114
14599
  var MIN_DISK_WARNING_GB = 10;
14115
- function countExistingProjects(factoryDir) {
14116
- const projectsDir = join37(factoryDir, ".beastmode", "projects");
14117
- if (!existsSync38(projectsDir)) return 0;
14118
- let count = 0;
14119
- for (const entry of readdirSync13(projectsDir)) {
14120
- if (entry.startsWith(".")) continue;
14121
- if (existsSync38(join37(projectsDir, entry, "project.json"))) count++;
14122
- else if (entry.endsWith(".json")) count++;
14123
- }
14124
- return count;
14125
- }
14126
14600
  function getMaxProjects(factoryDir) {
14127
- const configPath = join37(factoryDir, ".beastmode", "config.json");
14128
- if (existsSync38(configPath)) {
14601
+ const configPath = join38(factoryDir, ".beastmode", "config.json");
14602
+ if (existsSync39(configPath)) {
14129
14603
  try {
14130
14604
  const config = JSON.parse(readFileSync35(configPath, "utf-8"));
14131
14605
  if (typeof config.max_projects === "number") return config.max_projects;
@@ -14146,11 +14620,13 @@ function getFreeDiskGB() {
14146
14620
  }
14147
14621
  function projectAddAction(factoryDir, projectPath, opts) {
14148
14622
  const resolvedPath = resolve21(projectPath);
14149
- if (!existsSync38(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
14150
- const projectName = opts.name || basename7(resolvedPath);
14151
- const projectsDir = join37(factoryDir, ".beastmode", "projects", projectName);
14152
- if (existsSync38(projectsDir)) throw new Error(`Project already exists: ${projectName}`);
14153
- const currentCount = countExistingProjects(factoryDir);
14623
+ if (!existsSync39(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
14624
+ const projectName = opts.name || basename8(resolvedPath);
14625
+ const projectsDir = join38(factoryDir, ".beastmode", "projects");
14626
+ if (existsSync39(join38(projectsDir, projectName, "project.json"))) {
14627
+ throw new Error(`Project already exists: ${projectName}`);
14628
+ }
14629
+ const currentCount = listProjectRecords(projectsDir).length;
14154
14630
  const maxProjects = getMaxProjects(factoryDir);
14155
14631
  if (currentCount >= maxProjects) {
14156
14632
  throw new Error(
@@ -14163,86 +14639,57 @@ function projectAddAction(factoryDir, projectPath, opts) {
14163
14639
  `Warning: only ${freeGB}GB free disk space. Each project with worktrees may use 1-2GB. Consider freeing space.`
14164
14640
  );
14165
14641
  }
14166
- mkdirSync23(projectsDir, { recursive: true });
14167
- let stack = {};
14642
+ let detectedStack = null;
14643
+ let gitRemote;
14168
14644
  try {
14169
- stack = detectStack(resolvedPath);
14645
+ detectedStack = detectStack(resolvedPath);
14646
+ if (detectedStack.git_remote) gitRemote = detectedStack.git_remote;
14170
14647
  } catch {
14171
14648
  }
14172
14649
  let verifyPort = 3001;
14173
- const allProjectsDir = join37(factoryDir, ".beastmode", "projects");
14174
- if (existsSync38(allProjectsDir)) {
14175
- for (const d of readdirSync13(allProjectsDir)) {
14176
- if (d.startsWith(".")) continue;
14177
- if (d === projectName) continue;
14178
- const pf = join37(allProjectsDir, d, "project.json");
14179
- if (existsSync38(pf)) {
14180
- try {
14181
- const pc = JSON.parse(readFileSync35(pf, "utf-8"));
14182
- const port = pc?.deploy?.verify_port;
14183
- if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
14184
- } catch {
14185
- }
14186
- }
14187
- }
14650
+ for (const existing of listProjectRecords(projectsDir)) {
14651
+ const port = existing.deploy?.verify_port;
14652
+ if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
14653
+ }
14654
+ const boardId = opts.boardId ? parseInt(opts.boardId, 10) : null;
14655
+ const record = createProjectRecord({ name: projectName, resolvedPath, boardId, verifyPort, gitRemote });
14656
+ if (detectedStack) {
14657
+ record.stack = {
14658
+ detected: detectedStack.framework,
14659
+ build_command: detectedStack.suggested_commands.build,
14660
+ dev_command: detectedStack.suggested_commands.dev,
14661
+ test_command: detectedStack.suggested_commands.test,
14662
+ install_command: detectedStack.suggested_commands.install,
14663
+ dev_port: detectedStack.dev_port,
14664
+ is_monorepo: detectedStack.is_monorepo,
14665
+ total_packages: detectedStack.total_packages,
14666
+ primary_languages: detectedStack.primary_languages,
14667
+ ...detectedStack.packages ? { packages: detectedStack.packages } : {}
14668
+ };
14188
14669
  }
14189
- const projectConfig = {
14190
- name: projectName,
14191
- path: resolvedPath,
14192
- github: {
14193
- repo: stack.git_remote || "",
14194
- default_branch: "main"
14195
- },
14196
- board: {
14197
- id: opts.boardId ? parseInt(opts.boardId, 10) : null,
14198
- url: "http://127.0.0.1:8080",
14199
- auto_created: !opts.boardId
14200
- },
14201
- deploy: { verify_port: verifyPort },
14202
- pipeline: {},
14203
- models: {},
14204
- slots: { max: null },
14205
- registered_at: (/* @__PURE__ */ new Date()).toISOString()
14206
- };
14207
- writeFileSync30(join37(projectsDir, "project.json"), JSON.stringify(projectConfig, null, 2) + "\n");
14208
- writeFileSync30(join37(projectsDir, "extensions.json"), JSON.stringify({
14209
- plugins: { add: [], remove: [] },
14210
- mcps: { add: {}, remove: [] },
14211
- skills: { add: [], remove: [] }
14212
- }, null, 2) + "\n");
14213
- const runsDir = join37(factoryDir, "runs", projectName);
14214
- if (!existsSync38(runsDir)) mkdirSync23(runsDir, { recursive: true });
14215
- if (!opts.boardId) {
14670
+ writeProjectRecord(projectsDir, projectName, record);
14671
+ const runsDir = join38(factoryDir, "runs", projectName);
14672
+ if (!existsSync39(runsDir)) mkdirSync24(runsDir, { recursive: true });
14673
+ if (!boardId) {
14216
14674
  void (async () => {
14217
14675
  try {
14218
- const boardUrl = projectConfig.board.url;
14676
+ const boardUrl = record.board.url;
14219
14677
  const http4 = await import("http");
14220
14678
  const postData = JSON.stringify({ project_name: projectName });
14221
- await new Promise((resolve22, reject) => {
14679
+ await new Promise((res) => {
14222
14680
  const url = new URL("/api/boards", boardUrl);
14223
- const req = http4.request(url, {
14224
- method: "POST",
14225
- headers: {
14226
- "Content-Type": "application/json",
14227
- "Content-Length": Buffer.byteLength(postData).toString()
14228
- }
14229
- }, (res) => {
14230
- let data = "";
14231
- res.on("data", (chunk) => {
14232
- data += chunk.toString();
14233
- });
14234
- res.on("end", () => {
14235
- try {
14236
- const result = JSON.parse(data);
14237
- if (result.created) {
14238
- projectConfig.board.auto_created = true;
14239
- }
14240
- } catch {
14681
+ const req = http4.request(
14682
+ url,
14683
+ {
14684
+ method: "POST",
14685
+ headers: {
14686
+ "Content-Type": "application/json",
14687
+ "Content-Length": Buffer.byteLength(postData).toString()
14241
14688
  }
14242
- resolve22();
14243
- });
14244
- });
14245
- req.on("error", () => resolve22());
14689
+ },
14690
+ () => res()
14691
+ );
14692
+ req.on("error", () => res());
14246
14693
  req.end(postData);
14247
14694
  });
14248
14695
  } catch {
@@ -14251,28 +14698,21 @@ function projectAddAction(factoryDir, projectPath, opts) {
14251
14698
  }
14252
14699
  }
14253
14700
  function projectListAction(factoryDir) {
14254
- const projectsDir = join37(factoryDir, ".beastmode", "projects");
14255
- if (!existsSync38(projectsDir)) return [];
14256
- return readdirSync13(projectsDir).filter((d) => !d.startsWith(".") && existsSync38(join37(projectsDir, d, "project.json"))).map((d) => {
14257
- try {
14258
- return JSON.parse(readFileSync35(join37(projectsDir, d, "project.json"), "utf-8"));
14259
- } catch {
14260
- return null;
14261
- }
14262
- }).filter(Boolean);
14701
+ const projectsDir = join38(factoryDir, ".beastmode", "projects");
14702
+ return listProjectRecords(projectsDir);
14263
14703
  }
14264
14704
  function projectRemoveAction(factoryDir, name) {
14265
- const projectDir = join37(factoryDir, ".beastmode", "projects", name);
14266
- if (!existsSync38(projectDir)) throw new Error(`Project not found: ${name}`);
14267
- const archiveDir = join37(factoryDir, ".beastmode", "projects", ".archived", name);
14268
- mkdirSync23(join37(factoryDir, ".beastmode", "projects", ".archived"), { recursive: true });
14269
- renameSync2(projectDir, archiveDir);
14705
+ const projectDir = join38(factoryDir, ".beastmode", "projects", name);
14706
+ if (!existsSync39(projectDir)) throw new Error(`Project not found: ${name}`);
14707
+ const archivedBase = join38(factoryDir, ".beastmode", "projects", ".archived");
14708
+ mkdirSync24(archivedBase, { recursive: true });
14709
+ renameSync3(projectDir, join38(archivedBase, name));
14270
14710
  }
14271
14711
  var projectCommand = new Command24("project").description("Manage projects in this factory");
14272
14712
  projectCommand.command("add <path>").description("Register a project").option("--name <name>", "Override project name").option("--board-id <id>", "Link to existing board ID").action((path, opts) => {
14273
14713
  const factoryDir = resolve21(".");
14274
14714
  projectAddAction(factoryDir, path, opts);
14275
- console.log(`Project registered: ${opts.name || basename7(resolve21(path))}`);
14715
+ console.log(`Project registered: ${opts.name || basename8(resolve21(path))}`);
14276
14716
  });
14277
14717
  projectCommand.command("list").description("List registered projects").action(() => {
14278
14718
  const projects = projectListAction(resolve21("."));
@@ -14281,7 +14721,7 @@ projectCommand.command("list").description("List registered projects").action(()
14281
14721
  return;
14282
14722
  }
14283
14723
  for (const p of projects) {
14284
- console.log(` ${p.name} \u2014 ${p.path}`);
14724
+ console.log(` ${p["name"]} \u2014 ${p["path"]}`);
14285
14725
  }
14286
14726
  });
14287
14727
  projectCommand.command("remove <name>").description("Archive a project").action((name) => {