@beastmode-develeap/beastmode 0.1.265 → 0.1.266

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
@@ -800,8 +800,8 @@ var init_export_adapter = __esm({
800
800
  });
801
801
 
802
802
  // src/engine/stack-detector.ts
803
- import { existsSync, readFileSync } from "fs";
804
- import { join } from "path";
803
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
804
+ import { join, relative, basename, extname } from "path";
805
805
  function readFileSafe(path) {
806
806
  try {
807
807
  return readFileSync(path, "utf-8");
@@ -875,11 +875,362 @@ function extractGitRemote(projectDir) {
875
875
  if (httpsMatch) return httpsMatch[1].replace(/\.git$/, "");
876
876
  return null;
877
877
  }
878
+ function isDirectory(path) {
879
+ try {
880
+ return statSync(path).isDirectory();
881
+ } catch {
882
+ return false;
883
+ }
884
+ }
885
+ function listDirs(parent) {
886
+ try {
887
+ return readdirSync(parent, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
888
+ } catch {
889
+ return [];
890
+ }
891
+ }
892
+ function hasAnyManifest(dir) {
893
+ return PACKAGE_MANIFESTS.some((m) => existsSync(join(dir, m)));
894
+ }
895
+ function expandGlob(rootDir, pattern) {
896
+ let pat = pattern.replace(/^\.\//, "");
897
+ pat = pat.replace(/\/+$/, "");
898
+ if (!pat) return [];
899
+ const parts = pat.split("/");
900
+ let candidates = [""];
901
+ for (const part of parts) {
902
+ const next = [];
903
+ for (const cand of candidates) {
904
+ const abs = cand ? join(rootDir, cand) : rootDir;
905
+ if (part === "*" || part === "**") {
906
+ const children = listDirs(abs).filter((name) => !IGNORE_DIRS.has(name));
907
+ for (const child of children) {
908
+ next.push(cand ? join(cand, child) : child);
909
+ }
910
+ } else if (part.includes("*")) {
911
+ const regex = new RegExp(
912
+ "^" + part.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$"
913
+ );
914
+ const children = listDirs(abs).filter((name) => regex.test(name));
915
+ for (const child of children) {
916
+ next.push(cand ? join(cand, child) : child);
917
+ }
918
+ } else {
919
+ const candidate = cand ? join(cand, part) : part;
920
+ if (isDirectory(join(rootDir, candidate))) {
921
+ next.push(candidate);
922
+ }
923
+ }
924
+ }
925
+ candidates = next;
926
+ }
927
+ return candidates.filter((c) => hasAnyManifest(join(rootDir, c)));
928
+ }
929
+ function parsePnpmWorkspaceYaml(content) {
930
+ const lines = content.split(/\r?\n/);
931
+ const globs = [];
932
+ let inPackages = false;
933
+ for (const raw of lines) {
934
+ const line = raw.replace(/\s+$/, "");
935
+ if (/^packages\s*:/.test(line)) {
936
+ inPackages = true;
937
+ continue;
938
+ }
939
+ if (inPackages) {
940
+ if (line && !/^\s/.test(line) && !line.startsWith("-")) {
941
+ inPackages = false;
942
+ continue;
943
+ }
944
+ const m = line.match(/^\s*-\s*['"]?([^'"#]+?)['"]?\s*$/);
945
+ if (m) {
946
+ const val = m[1].trim();
947
+ if (val) globs.push(val);
948
+ }
949
+ }
950
+ }
951
+ return globs;
952
+ }
953
+ function parseGoWorkUseBlock(content) {
954
+ const dirs = [];
955
+ const singleLineMatches = content.matchAll(/^\s*use\s+(\S+)\s*$/gm);
956
+ for (const m of singleLineMatches) {
957
+ dirs.push(m[1].replace(/^\.\//, ""));
958
+ }
959
+ const blockMatch = content.match(/use\s*\(([\s\S]*?)\)/);
960
+ if (blockMatch) {
961
+ for (const raw of blockMatch[1].split(/\r?\n/)) {
962
+ const line = raw.trim();
963
+ if (!line || line.startsWith("//")) continue;
964
+ const cleaned = line.replace(/\/\/.*$/, "").trim();
965
+ if (cleaned) dirs.push(cleaned.replace(/^\.\//, ""));
966
+ }
967
+ }
968
+ return dirs;
969
+ }
970
+ function parseCargoWorkspaceMembers(content) {
971
+ const wsIdx = content.search(/^\s*\[workspace\]/m);
972
+ if (wsIdx < 0) return [];
973
+ const rest = content.slice(wsIdx);
974
+ const m = rest.match(/members\s*=\s*\[([\s\S]*?)\]/);
975
+ if (!m) return [];
976
+ const inner = m[1];
977
+ const members = [];
978
+ for (const raw of inner.split(",")) {
979
+ const cleaned = raw.replace(/#.*$/, "").trim().replace(/^['"]|['"]$/g, "");
980
+ if (cleaned) members.push(cleaned);
981
+ }
982
+ return members;
983
+ }
984
+ function parseGradleInclude(content) {
985
+ const modules = [];
986
+ const re = /include\s*(?:\(\s*)?([^)\n]+?)(?:\s*\))?\s*$/gm;
987
+ let match;
988
+ while ((match = re.exec(content)) !== null) {
989
+ const args = match[1];
990
+ const stringMatches = args.matchAll(/['"]([^'"]+)['"]/g);
991
+ for (const sm of stringMatches) {
992
+ const raw = sm[1].trim();
993
+ const path = raw.replace(/^:+/, "").replace(/:/g, "/");
994
+ if (path) modules.push(path);
995
+ }
996
+ }
997
+ return modules;
998
+ }
999
+ function parseMavenModules(content) {
1000
+ const modules = [];
1001
+ const re = /<module>\s*([^<\s]+)\s*<\/module>/g;
1002
+ let match;
1003
+ while ((match = re.exec(content)) !== null) {
1004
+ modules.push(match[1].trim());
1005
+ }
1006
+ return modules;
1007
+ }
1008
+ function detectWorkspaces(projectDir) {
1009
+ const pnpmYaml = readFileSafe(join(projectDir, "pnpm-workspace.yaml"));
1010
+ if (pnpmYaml !== null) {
1011
+ const globs = parsePnpmWorkspaceYaml(pnpmYaml);
1012
+ const packages = [];
1013
+ for (const g of globs) {
1014
+ packages.push(...expandGlob(projectDir, g));
1015
+ }
1016
+ return { type: "pnpm", packages: dedupe(packages) };
1017
+ }
1018
+ if (existsSync(join(projectDir, "nx.json"))) {
1019
+ const packages = [];
1020
+ for (const parent of ["packages", "apps", "libs"]) {
1021
+ const parentDir = join(projectDir, parent);
1022
+ if (!isDirectory(parentDir)) continue;
1023
+ for (const child of listDirs(parentDir)) {
1024
+ const childDir = join(parentDir, child);
1025
+ if (existsSync(join(childDir, "project.json")) || hasAnyManifest(childDir)) {
1026
+ packages.push(join(parent, child));
1027
+ }
1028
+ }
1029
+ }
1030
+ return { type: "nx", packages: dedupe(packages) };
1031
+ }
1032
+ const hasTurbo = existsSync(join(projectDir, "turbo.json"));
1033
+ const pkgContent = readFileSafe(join(projectDir, "package.json"));
1034
+ if (pkgContent) {
1035
+ const pkg = parseJsonSafe(pkgContent);
1036
+ if (pkg) {
1037
+ let workspaces;
1038
+ const ws = pkg.workspaces;
1039
+ if (Array.isArray(ws)) {
1040
+ workspaces = ws.filter((v) => typeof v === "string");
1041
+ } else if (ws && typeof ws === "object" && Array.isArray(ws.packages)) {
1042
+ workspaces = ws.packages.filter(
1043
+ (v) => typeof v === "string"
1044
+ );
1045
+ }
1046
+ if (workspaces && workspaces.length > 0) {
1047
+ const packages = [];
1048
+ for (const g of workspaces) {
1049
+ packages.push(...expandGlob(projectDir, g));
1050
+ }
1051
+ return {
1052
+ type: hasTurbo ? "turbo" : "npm",
1053
+ packages: dedupe(packages)
1054
+ };
1055
+ }
1056
+ }
1057
+ }
1058
+ const goWork = readFileSafe(join(projectDir, "go.work"));
1059
+ if (goWork !== null) {
1060
+ const dirs = parseGoWorkUseBlock(goWork);
1061
+ const packages = dirs.filter(
1062
+ (d) => existsSync(join(projectDir, d, "go.mod"))
1063
+ );
1064
+ return { type: "go", packages: dedupe(packages) };
1065
+ }
1066
+ const cargoToml = readFileSafe(join(projectDir, "Cargo.toml"));
1067
+ if (cargoToml !== null && /^\s*\[workspace\]/m.test(cargoToml)) {
1068
+ const members = parseCargoWorkspaceMembers(cargoToml);
1069
+ const packages = [];
1070
+ for (const m of members) {
1071
+ if (m.includes("*")) {
1072
+ packages.push(...expandGlob(projectDir, m));
1073
+ } else if (existsSync(join(projectDir, m, "Cargo.toml"))) {
1074
+ packages.push(m);
1075
+ }
1076
+ }
1077
+ return { type: "cargo", packages: dedupe(packages) };
1078
+ }
1079
+ const gradleSettings = readFileSafe(join(projectDir, "settings.gradle")) ?? readFileSafe(join(projectDir, "settings.gradle.kts"));
1080
+ if (gradleSettings !== null) {
1081
+ const modules = parseGradleInclude(gradleSettings);
1082
+ const packages = modules.filter((m) => isDirectory(join(projectDir, m)));
1083
+ if (packages.length > 0) {
1084
+ return { type: "gradle", packages: dedupe(packages) };
1085
+ }
1086
+ }
1087
+ const pom = readFileSafe(join(projectDir, "pom.xml"));
1088
+ if (pom !== null) {
1089
+ const modules = parseMavenModules(pom);
1090
+ const packages = modules.filter(
1091
+ (m) => existsSync(join(projectDir, m, "pom.xml"))
1092
+ );
1093
+ if (packages.length > 0) {
1094
+ return { type: "maven", packages: dedupe(packages) };
1095
+ }
1096
+ }
1097
+ return null;
1098
+ }
1099
+ function dedupe(items) {
1100
+ return Array.from(new Set(items));
1101
+ }
1102
+ function extractPackageName(packageDir) {
1103
+ const pkgContent = readFileSafe(join(packageDir, "package.json"));
1104
+ if (pkgContent) {
1105
+ const pkg = parseJsonSafe(pkgContent);
1106
+ if (pkg && typeof pkg.name === "string" && pkg.name) return pkg.name;
1107
+ }
1108
+ const cargoToml = readFileSafe(join(packageDir, "Cargo.toml"));
1109
+ if (cargoToml) {
1110
+ const m = cargoToml.match(/\[package\][\s\S]*?name\s*=\s*['"]([^'"]+)['"]/);
1111
+ if (m) return m[1];
1112
+ }
1113
+ const pom = readFileSafe(join(packageDir, "pom.xml"));
1114
+ if (pom) {
1115
+ const m = pom.match(/<artifactId>\s*([^<\s]+)\s*<\/artifactId>/);
1116
+ if (m) return m[1];
1117
+ }
1118
+ const goMod = readFileSafe(join(packageDir, "go.mod"));
1119
+ if (goMod) {
1120
+ const m = goMod.match(/^module\s+(\S+)/m);
1121
+ if (m) {
1122
+ const parts = m[1].split("/");
1123
+ return parts[parts.length - 1];
1124
+ }
1125
+ }
1126
+ return basename(packageDir);
1127
+ }
1128
+ function detectEntryPoints(packageDir, framework) {
1129
+ const entries = [];
1130
+ const pkgContent = readFileSafe(join(packageDir, "package.json"));
1131
+ if (pkgContent) {
1132
+ const pkg = parseJsonSafe(pkgContent);
1133
+ if (pkg) {
1134
+ if (typeof pkg.main === "string" && pkg.main) entries.push(pkg.main);
1135
+ if (typeof pkg.bin === "string" && pkg.bin) {
1136
+ entries.push(pkg.bin);
1137
+ } else if (pkg.bin && typeof pkg.bin === "object") {
1138
+ for (const v of Object.values(pkg.bin)) {
1139
+ if (typeof v === "string") entries.push(v);
1140
+ }
1141
+ }
1142
+ }
1143
+ }
1144
+ if (framework === "go") {
1145
+ const cmdDir = join(packageDir, "cmd");
1146
+ if (isDirectory(cmdDir)) {
1147
+ for (const sub of listDirs(cmdDir)) {
1148
+ entries.push(join("cmd", sub));
1149
+ }
1150
+ }
1151
+ }
1152
+ if (framework === "rust") {
1153
+ if (existsSync(join(packageDir, "src", "main.rs"))) {
1154
+ entries.push("src/main.rs");
1155
+ }
1156
+ }
1157
+ if (framework === "java-maven" || framework === "java-gradle") {
1158
+ if (isDirectory(join(packageDir, "src", "main", "java"))) {
1159
+ entries.push("src/main/java");
1160
+ }
1161
+ }
1162
+ return dedupe(entries);
1163
+ }
1164
+ function detectPackageStack(packageDir, rootDir) {
1165
+ const framework = detectFramework(packageDir);
1166
+ const preset = STACK_PRESETS[framework] || STACK_PRESETS.unknown;
1167
+ const pm = ["nextjs", "vite", "react", "node"].includes(framework) ? detectPackageManager(packageDir) : preset.language === "python" ? "pip" : "";
1168
+ const resolved = resolveCommands(preset, pm);
1169
+ const name = extractPackageName(packageDir);
1170
+ const entryPoints = detectEntryPoints(packageDir, framework);
1171
+ const relPath = relative(rootDir, packageDir) || ".";
1172
+ return {
1173
+ relative_path: relPath,
1174
+ name,
1175
+ language: resolved.language,
1176
+ framework,
1177
+ package_manager: pm,
1178
+ build_command: resolved.build,
1179
+ dev_command: resolved.dev,
1180
+ test_command: resolved.test,
1181
+ install_command: resolved.install,
1182
+ dev_port: resolved.port !== 0 ? resolved.port : void 0,
1183
+ entry_points: entryPoints
1184
+ };
1185
+ }
1186
+ function scanPrimaryLanguages(projectDir) {
1187
+ const counts = {};
1188
+ let total = 0;
1189
+ const MAX_DEPTH = 8;
1190
+ function walk(dir, depth) {
1191
+ if (depth > MAX_DEPTH) return;
1192
+ let entries;
1193
+ try {
1194
+ entries = readdirSync(dir, { withFileTypes: true });
1195
+ } catch {
1196
+ return;
1197
+ }
1198
+ for (const entry of entries) {
1199
+ if (entry.name.startsWith(".") && entry.name !== ".") {
1200
+ if (IGNORE_DIRS.has(entry.name)) continue;
1201
+ }
1202
+ if (IGNORE_DIRS.has(entry.name)) continue;
1203
+ const full = join(dir, entry.name);
1204
+ if (entry.isDirectory()) {
1205
+ walk(full, depth + 1);
1206
+ } else if (entry.isFile()) {
1207
+ const ext = extname(entry.name).toLowerCase();
1208
+ const lang = EXT_LANG_MAP[ext];
1209
+ if (lang) {
1210
+ counts[lang] = (counts[lang] || 0) + 1;
1211
+ total += 1;
1212
+ }
1213
+ }
1214
+ }
1215
+ }
1216
+ walk(projectDir, 0);
1217
+ if (total === 0) return [];
1218
+ 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);
1219
+ return ranked;
1220
+ }
878
1221
  function detectStack(projectDir) {
879
1222
  const framework = detectFramework(projectDir);
880
1223
  const preset = STACK_PRESETS[framework] || STACK_PRESETS.unknown;
881
1224
  const pm = ["nextjs", "vite", "react", "node"].includes(framework) ? detectPackageManager(projectDir) : preset.language === "python" ? "pip" : "";
882
1225
  const resolved = resolveCommands(preset, pm);
1226
+ const workspace = detectWorkspaces(projectDir);
1227
+ const primaryLanguages = scanPrimaryLanguages(projectDir);
1228
+ let packages;
1229
+ if (workspace && workspace.packages.length > 0) {
1230
+ packages = workspace.packages.map(
1231
+ (pkgRelPath) => detectPackageStack(join(projectDir, pkgRelPath), projectDir)
1232
+ );
1233
+ }
883
1234
  return {
884
1235
  language: resolved.language,
885
1236
  framework,
@@ -896,13 +1247,54 @@ function detectStack(projectDir) {
896
1247
  dev_port: resolved.port,
897
1248
  suggested_plugins: resolved.plugins,
898
1249
  suggested_preset: resolved.preset,
899
- suggested_deploy: resolved.deploy
1250
+ suggested_deploy: resolved.deploy,
1251
+ is_monorepo: workspace !== null,
1252
+ total_packages: packages ? packages.length : 0,
1253
+ primary_languages: primaryLanguages,
1254
+ packages
900
1255
  };
901
1256
  }
902
- var STACK_PRESETS;
1257
+ var IGNORE_DIRS, EXT_LANG_MAP, PACKAGE_MANIFESTS, STACK_PRESETS;
903
1258
  var init_stack_detector = __esm({
904
1259
  "src/engine/stack-detector.ts"() {
905
1260
  "use strict";
1261
+ IGNORE_DIRS = /* @__PURE__ */ new Set([
1262
+ "node_modules",
1263
+ ".git",
1264
+ "vendor",
1265
+ "target",
1266
+ "dist",
1267
+ "build",
1268
+ "__pycache__",
1269
+ ".venv",
1270
+ ".next",
1271
+ ".turbo",
1272
+ "coverage"
1273
+ ]);
1274
+ EXT_LANG_MAP = {
1275
+ ".ts": "typescript",
1276
+ ".tsx": "typescript",
1277
+ ".js": "javascript",
1278
+ ".jsx": "javascript",
1279
+ ".py": "python",
1280
+ ".go": "go",
1281
+ ".rs": "rust",
1282
+ ".java": "java",
1283
+ ".kt": "kotlin",
1284
+ ".swift": "swift",
1285
+ ".rb": "ruby",
1286
+ ".cs": "csharp"
1287
+ };
1288
+ PACKAGE_MANIFESTS = [
1289
+ "package.json",
1290
+ "go.mod",
1291
+ "Cargo.toml",
1292
+ "pom.xml",
1293
+ "build.gradle",
1294
+ "build.gradle.kts",
1295
+ "pyproject.toml",
1296
+ "requirements.txt"
1297
+ ];
906
1298
  STACK_PRESETS = {
907
1299
  nextjs: {
908
1300
  language: "typescript",
@@ -2251,15 +2643,15 @@ var init_plugin_installer = __esm({
2251
2643
  // src/engine/skill-manager.ts
2252
2644
  import {
2253
2645
  existsSync as existsSync7,
2254
- readdirSync,
2646
+ readdirSync as readdirSync2,
2255
2647
  cpSync as cpSync2,
2256
2648
  rmSync as rmSync2,
2257
2649
  readFileSync as readFileSync7,
2258
2650
  writeFileSync as writeFileSync5,
2259
2651
  mkdirSync as mkdirSync5,
2260
- statSync
2652
+ statSync as statSync2
2261
2653
  } from "fs";
2262
- import { join as join7, basename } from "path";
2654
+ import { join as join7, basename as basename2 } from "path";
2263
2655
  function parseSkillFrontmatter(content) {
2264
2656
  const match = content.match(/^---\n([\s\S]*?)\n---/);
2265
2657
  if (!match) return {};
@@ -2278,7 +2670,7 @@ function addSkill(factoryDir, sourcePath) {
2278
2670
  if (!existsSync7(sourcePath)) {
2279
2671
  throw new Error(`Skill source path does not exist: ${sourcePath}`);
2280
2672
  }
2281
- const stat = statSync(sourcePath);
2673
+ const stat = statSync2(sourcePath);
2282
2674
  if (!stat.isDirectory()) {
2283
2675
  throw new Error(`Skill source must be a directory: ${sourcePath}`);
2284
2676
  }
@@ -2288,7 +2680,7 @@ function addSkill(factoryDir, sourcePath) {
2288
2680
  `SKILL.md not found in ${sourcePath}. Every skill must have a SKILL.md with frontmatter.`
2289
2681
  );
2290
2682
  }
2291
- const skillName = basename(sourcePath);
2683
+ const skillName = basename2(sourcePath);
2292
2684
  const destPath = join7(factoryDir, ".beastmode", "skills", skillName);
2293
2685
  if (existsSync7(destPath)) {
2294
2686
  throw new Error(
@@ -2335,7 +2727,7 @@ function listSkills(factoryDir) {
2335
2727
  const bmDir = join7(factoryDir, ".beastmode");
2336
2728
  const customSkillsDir = join7(bmDir, "skills");
2337
2729
  if (existsSync7(customSkillsDir)) {
2338
- for (const entry of readdirSync(customSkillsDir, { withFileTypes: true })) {
2730
+ for (const entry of readdirSync2(customSkillsDir, { withFileTypes: true })) {
2339
2731
  if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
2340
2732
  const skillMdPath = join7(customSkillsDir, entry.name, "SKILL.md");
2341
2733
  if (!existsSync7(skillMdPath)) continue;
@@ -2352,7 +2744,7 @@ function listSkills(factoryDir) {
2352
2744
  }
2353
2745
  const pluginsDir = join7(bmDir, "plugins");
2354
2746
  if (existsSync7(pluginsDir)) {
2355
- for (const pluginEntry of readdirSync(pluginsDir, {
2747
+ for (const pluginEntry of readdirSync2(pluginsDir, {
2356
2748
  withFileTypes: true
2357
2749
  })) {
2358
2750
  if (!pluginEntry.isDirectory() || pluginEntry.name.startsWith("."))
@@ -2363,7 +2755,7 @@ function listSkills(factoryDir) {
2363
2755
  "skills"
2364
2756
  );
2365
2757
  if (!existsSync7(pluginSkillsDir)) continue;
2366
- for (const skillEntry of readdirSync(pluginSkillsDir, {
2758
+ for (const skillEntry of readdirSync2(pluginSkillsDir, {
2367
2759
  withFileTypes: true
2368
2760
  })) {
2369
2761
  if (!skillEntry.isDirectory() || skillEntry.name.startsWith("."))
@@ -2808,6 +3200,159 @@ var init_bridge = __esm({
2808
3200
  }
2809
3201
  });
2810
3202
 
3203
+ // src/engine/project-record.ts
3204
+ import { existsSync as existsSync8, writeFileSync as writeFileSync6, renameSync, readFileSync as readFileSync8, readdirSync as readdirSync3, mkdirSync as mkdirSync6 } from "fs";
3205
+ import { join as join8 } from "path";
3206
+ function detectShape(parsed) {
3207
+ if (parsed.github && typeof parsed.github === "object" && parsed.slots && typeof parsed.slots === "object") {
3208
+ return "canonical";
3209
+ }
3210
+ if (parsed.github && typeof parsed.github === "object") {
3211
+ return "cli-legacy";
3212
+ }
3213
+ return "http-legacy";
3214
+ }
3215
+ function normalizeToCanonical(parsed, name) {
3216
+ const shape = detectShape(parsed);
3217
+ if (shape === "canonical") {
3218
+ return parsed;
3219
+ }
3220
+ if (shape === "cli-legacy") {
3221
+ const github = parsed.github;
3222
+ return {
3223
+ name: parsed.name || name,
3224
+ path: parsed.path || "",
3225
+ github: {
3226
+ repo: github.repo || "",
3227
+ default_branch: github.default_branch || "main"
3228
+ },
3229
+ board: parsed.board || {
3230
+ id: null,
3231
+ url: "http://127.0.0.1:8080",
3232
+ auto_created: true
3233
+ },
3234
+ deploy: parsed.deploy || { verify_port: 3001 },
3235
+ pipeline: parsed.pipeline || {},
3236
+ models: parsed.models || {},
3237
+ slots: { max: null },
3238
+ registered_at: parsed.registered_at || (/* @__PURE__ */ new Date()).toISOString()
3239
+ };
3240
+ }
3241
+ return {
3242
+ name: parsed.name || name,
3243
+ path: parsed.path || "",
3244
+ github: {
3245
+ repo: parsed.repo || "",
3246
+ default_branch: "main"
3247
+ },
3248
+ board: { id: null, url: "http://127.0.0.1:8080", auto_created: true },
3249
+ deploy: { verify_port: 3001 },
3250
+ pipeline: parsed.pipeline || {},
3251
+ models: parsed.models || {},
3252
+ slots: { max: null },
3253
+ registered_at: (/* @__PURE__ */ new Date()).toISOString()
3254
+ };
3255
+ }
3256
+ function createProjectRecord(input) {
3257
+ return {
3258
+ name: input.name,
3259
+ path: input.resolvedPath,
3260
+ github: {
3261
+ repo: input.gitRemote || "",
3262
+ default_branch: "main"
3263
+ },
3264
+ board: {
3265
+ id: input.boardId ?? null,
3266
+ url: process.env.BEASTMODE_BOARD_URL || "http://127.0.0.1:8080",
3267
+ auto_created: !input.boardId
3268
+ },
3269
+ deploy: { verify_port: input.verifyPort ?? 3001 },
3270
+ pipeline: {},
3271
+ models: {},
3272
+ slots: { max: null },
3273
+ registered_at: (/* @__PURE__ */ new Date()).toISOString()
3274
+ };
3275
+ }
3276
+ function writeProjectRecord(projectsDir, name, record) {
3277
+ const dir = join8(projectsDir, name);
3278
+ mkdirSync6(dir, { recursive: true });
3279
+ const filePath = join8(dir, "project.json");
3280
+ const tmpPath = `${filePath}.tmp`;
3281
+ writeFileSync6(tmpPath, JSON.stringify(record, null, 2) + "\n");
3282
+ renameSync(tmpPath, filePath);
3283
+ const extPath = join8(dir, "extensions.json");
3284
+ if (!existsSync8(extPath)) {
3285
+ writeFileSync6(
3286
+ extPath,
3287
+ JSON.stringify(
3288
+ {
3289
+ plugins: { add: [], remove: [] },
3290
+ mcps: { add: {}, remove: [] },
3291
+ skills: { add: [], remove: [] }
3292
+ },
3293
+ null,
3294
+ 2
3295
+ ) + "\n"
3296
+ );
3297
+ }
3298
+ }
3299
+ function readProjectRecord(projectsDir, name) {
3300
+ const subdirPath = join8(projectsDir, name, "project.json");
3301
+ const flatPath = join8(projectsDir, `${name}.json`);
3302
+ let filePath;
3303
+ let isFlat = false;
3304
+ if (existsSync8(subdirPath)) {
3305
+ filePath = subdirPath;
3306
+ } else if (existsSync8(flatPath)) {
3307
+ filePath = flatPath;
3308
+ isFlat = true;
3309
+ } else {
3310
+ return null;
3311
+ }
3312
+ let parsed;
3313
+ try {
3314
+ parsed = JSON.parse(readFileSync8(filePath, "utf-8"));
3315
+ } catch {
3316
+ return null;
3317
+ }
3318
+ const shape = detectShape(parsed);
3319
+ const record = normalizeToCanonical(parsed, name);
3320
+ if (shape !== "canonical" || isFlat) {
3321
+ writeProjectRecord(projectsDir, name, record);
3322
+ }
3323
+ return record;
3324
+ }
3325
+ function listProjectRecords(projectsDir) {
3326
+ if (!existsSync8(projectsDir)) return [];
3327
+ const seen = /* @__PURE__ */ new Set();
3328
+ const records = [];
3329
+ for (const entry of readdirSync3(projectsDir)) {
3330
+ if (entry.startsWith(".")) continue;
3331
+ if (existsSync8(join8(projectsDir, entry, "project.json"))) {
3332
+ if (!seen.has(entry)) {
3333
+ seen.add(entry);
3334
+ const record = readProjectRecord(projectsDir, entry);
3335
+ if (record) records.push(record);
3336
+ }
3337
+ continue;
3338
+ }
3339
+ if (entry.endsWith(".json")) {
3340
+ const name = entry.slice(0, -5);
3341
+ if (!seen.has(name)) {
3342
+ seen.add(name);
3343
+ const record = readProjectRecord(projectsDir, name);
3344
+ if (record) records.push(record);
3345
+ }
3346
+ }
3347
+ }
3348
+ return records;
3349
+ }
3350
+ var init_project_record = __esm({
3351
+ "src/engine/project-record.ts"() {
3352
+ "use strict";
3353
+ }
3354
+ });
3355
+
2811
3356
  // src/engine/index.ts
2812
3357
  var engine_exports = {};
2813
3358
  __export(engine_exports, {
@@ -2851,6 +3396,7 @@ __export(engine_exports, {
2851
3396
  configGet: () => configGet,
2852
3397
  configReset: () => configReset,
2853
3398
  configSet: () => configSet,
3399
+ createProjectRecord: () => createProjectRecord,
2854
3400
  createSkill: () => createSkill,
2855
3401
  detectStack: () => detectStack,
2856
3402
  exportMarkdown: () => exportMarkdown,
@@ -2870,6 +3416,7 @@ __export(engine_exports, {
2870
3416
  listHooks: () => listHooks,
2871
3417
  listMcps: () => listMcps,
2872
3418
  listPresets: () => listPresets,
3419
+ listProjectRecords: () => listProjectRecords,
2873
3420
  listProviders: () => listProviders,
2874
3421
  listSkills: () => listSkills,
2875
3422
  mapDaemonToFactory: () => mapDaemonToFactory,
@@ -2879,6 +3426,7 @@ __export(engine_exports, {
2879
3426
  parseMethodologyManifest: () => parseMethodologyManifest,
2880
3427
  parseTemplate: () => parseTemplate,
2881
3428
  performUpgrade: () => performUpgrade,
3429
+ readProjectRecord: () => readProjectRecord,
2882
3430
  removeHook: () => removeHook,
2883
3431
  removeMcp: () => removeMcp,
2884
3432
  removePlugin: () => removePlugin,
@@ -2895,7 +3443,8 @@ __export(engine_exports, {
2895
3443
  scaffoldFactory: () => scaffoldFactory,
2896
3444
  validateFactory: () => validateFactory,
2897
3445
  validateProvider: () => validateProvider,
2898
- validateSecrets: () => validateSecrets
3446
+ validateSecrets: () => validateSecrets,
3447
+ writeProjectRecord: () => writeProjectRecord
2899
3448
  });
2900
3449
  var init_engine = __esm({
2901
3450
  "src/engine/index.ts"() {
@@ -2925,25 +3474,26 @@ var init_engine = __esm({
2925
3474
  init_schemas();
2926
3475
  init_migrator();
2927
3476
  init_bridge();
3477
+ init_project_record();
2928
3478
  }
2929
3479
  });
2930
3480
 
2931
3481
  // src/cli/utils/file-writer.ts
2932
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync6, appendFileSync, existsSync as existsSync8 } from "fs";
3482
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, appendFileSync, existsSync as existsSync9 } from "fs";
2933
3483
  import { dirname as dirname3 } from "path";
2934
3484
  function executeFileActions(actions) {
2935
3485
  for (const action of actions) {
2936
3486
  const dir = dirname3(action.path);
2937
- mkdirSync6(dir, { recursive: true });
3487
+ mkdirSync7(dir, { recursive: true });
2938
3488
  switch (action.action) {
2939
3489
  case "create":
2940
- if (existsSync8(action.path)) {
3490
+ if (existsSync9(action.path)) {
2941
3491
  throw new Error(`File already exists: ${action.path}`);
2942
3492
  }
2943
- writeFileSync6(action.path, action.content, "utf-8");
3493
+ writeFileSync7(action.path, action.content, "utf-8");
2944
3494
  break;
2945
3495
  case "overwrite":
2946
- writeFileSync6(action.path, action.content, "utf-8");
3496
+ writeFileSync7(action.path, action.content, "utf-8");
2947
3497
  break;
2948
3498
  case "append":
2949
3499
  appendFileSync(action.path, action.content, "utf-8");
@@ -2988,7 +3538,7 @@ var init_display = __esm({
2988
3538
 
2989
3539
  // src/cli/ui/api-routes.ts
2990
3540
  import { resolve as resolve3 } from "path";
2991
- import { existsSync as existsSync10, writeFileSync as writeFileSync7 } from "fs";
3541
+ import { existsSync as existsSync11, writeFileSync as writeFileSync8 } from "fs";
2992
3542
  function getRoutes() {
2993
3543
  return [
2994
3544
  {
@@ -3003,7 +3553,7 @@ function getRoutes() {
3003
3553
  const { path: projectPath } = body;
3004
3554
  if (!projectPath) throw new Error("Missing required field: path");
3005
3555
  const resolved = resolve3(projectPath);
3006
- if (!existsSync10(resolved)) throw new Error(`Directory not found: ${resolved}`);
3556
+ if (!existsSync11(resolved)) throw new Error(`Directory not found: ${resolved}`);
3007
3557
  return detectStack(resolved);
3008
3558
  }
3009
3559
  },
@@ -3061,7 +3611,7 @@ function getRoutes() {
3061
3611
  if (!name) throw new Error("Missing required field: name");
3062
3612
  if (!config) throw new Error("Missing required field: config");
3063
3613
  if (!project) throw new Error("Missing required field: project");
3064
- if (existsSync10(name) && existsSync10(resolve3(name, ".beastmode"))) {
3614
+ if (existsSync11(name) && existsSync11(resolve3(name, ".beastmode"))) {
3065
3615
  throw new Error(`Factory already exists at ./${name}. Use 'beastmode config' to modify.`);
3066
3616
  }
3067
3617
  const resolvedConfig = resolveDefaults(config, project.stack);
@@ -3078,7 +3628,7 @@ function getRoutes() {
3078
3628
  if (secretLines.length > 0) {
3079
3629
  const secretsPath = resolve3(name, ".beastmode", "secrets.env.local");
3080
3630
  const secretsContent = "# BeastMode secrets \u2014 DO NOT COMMIT\n" + secretLines.join("\n") + "\n";
3081
- writeFileSync7(secretsPath, secretsContent, "utf-8");
3631
+ writeFileSync8(secretsPath, secretsContent, "utf-8");
3082
3632
  }
3083
3633
  }
3084
3634
  const fileList = actions.filter((a) => !a.path.endsWith(".gitkeep")).map((a) => a.path.replace(`${name}/`, ""));
@@ -3118,52 +3668,52 @@ var init_api_routes = __esm({
3118
3668
  });
3119
3669
 
3120
3670
  // 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";
3671
+ 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";
3672
+ import { join as join10 } from "path";
3123
3673
  function archiveOldRuns(runsDir, archiveAfterDays) {
3124
- if (archiveAfterDays <= 0 || !existsSync11(runsDir)) return { archived: 0 };
3125
- const archiveDir = join9(runsDir, ".archive");
3674
+ if (archiveAfterDays <= 0 || !existsSync12(runsDir)) return { archived: 0 };
3675
+ const archiveDir = join10(runsDir, ".archive");
3126
3676
  const cutoff = Date.now() - archiveAfterDays * 24 * 60 * 60 * 1e3;
3127
3677
  let archived = 0;
3128
- for (const entry of readdirSync2(runsDir)) {
3678
+ for (const entry of readdirSync4(runsDir)) {
3129
3679
  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;
3680
+ const runDir = join10(runsDir, entry);
3681
+ if (existsSync12(join10(runDir, "pinned"))) continue;
3682
+ const cpPath = join10(runDir, "checkpoint.json");
3683
+ if (!existsSync12(cpPath)) continue;
3134
3684
  try {
3135
- const cp = JSON.parse(readFileSync8(cpPath, "utf-8"));
3685
+ const cp = JSON.parse(readFileSync9(cpPath, "utf-8"));
3136
3686
  const stage = (cp.current_stage || "").toLowerCase();
3137
3687
  if (stage !== "done" && stage !== "ship") continue;
3138
3688
  } catch {
3139
3689
  continue;
3140
3690
  }
3141
3691
  try {
3142
- const mtime = statSync2(cpPath).mtimeMs;
3692
+ const mtime = statSync3(cpPath).mtimeMs;
3143
3693
  if (mtime > cutoff) continue;
3144
3694
  } catch {
3145
3695
  continue;
3146
3696
  }
3147
- mkdirSync7(archiveDir, { recursive: true });
3148
- renameSync(runDir, join9(archiveDir, entry));
3697
+ mkdirSync8(archiveDir, { recursive: true });
3698
+ renameSync2(runDir, join10(archiveDir, entry));
3149
3699
  archived++;
3150
3700
  }
3151
3701
  return { archived };
3152
3702
  }
3153
3703
  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());
3704
+ const runDir = join10(runsDir, runId);
3705
+ if (!existsSync12(runDir)) return false;
3706
+ writeFileSync9(join10(runDir, "pinned"), (/* @__PURE__ */ new Date()).toISOString());
3157
3707
  return true;
3158
3708
  }
3159
3709
  function unpinRun(runsDir, runId) {
3160
- const pinFile = join9(runsDir, runId, "pinned");
3161
- if (!existsSync11(pinFile)) return false;
3710
+ const pinFile = join10(runsDir, runId, "pinned");
3711
+ if (!existsSync12(pinFile)) return false;
3162
3712
  unlinkSync(pinFile);
3163
3713
  return true;
3164
3714
  }
3165
3715
  function isRunPinned(runsDir, runId) {
3166
- return existsSync11(join9(runsDir, runId, "pinned"));
3716
+ return existsSync12(join10(runsDir, runId, "pinned"));
3167
3717
  }
3168
3718
  var init_archival = __esm({
3169
3719
  "src/cli/ui/archival.ts"() {
@@ -3188,37 +3738,37 @@ __export(inception_exports, {
3188
3738
  saveArtifact: () => saveArtifact,
3189
3739
  saveInceptionState: () => saveInceptionState
3190
3740
  });
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";
3741
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync10, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
3742
+ import { join as join11, dirname as dirname4 } from "path";
3193
3743
  import { fileURLToPath } from "url";
3194
3744
  function getMethodologiesDir() {
3195
3745
  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")
3746
+ join11(dirname4(fileURLToPath(import.meta.url)), "methodologies"),
3747
+ join11(dirname4(fileURLToPath(import.meta.url)), "..", "methodologies"),
3748
+ join11(process.cwd(), "src", "cli", "ui", "methodologies"),
3749
+ join11(process.cwd(), "dist", "methodologies"),
3750
+ join11(process.cwd(), "cli", "src", "cli", "ui", "methodologies"),
3751
+ join11(process.cwd(), "cli", "dist", "methodologies")
3202
3752
  ];
3203
3753
  for (const dir of candidates) {
3204
- if (existsSync12(dir)) return dir;
3754
+ if (existsSync13(dir)) return dir;
3205
3755
  }
3206
3756
  return candidates[0];
3207
3757
  }
3208
3758
  function getMethodology(id) {
3209
3759
  const dir = getMethodologiesDir();
3210
- const filePath = join10(dir, `${id.replace(/_/g, "-")}.json`);
3211
- if (!existsSync12(filePath)) {
3760
+ const filePath = join11(dir, `${id.replace(/_/g, "-")}.json`);
3761
+ if (!existsSync13(filePath)) {
3212
3762
  throw new Error(`Methodology not found: ${id}`);
3213
3763
  }
3214
- return JSON.parse(readFileSync9(filePath, "utf-8"));
3764
+ return JSON.parse(readFileSync10(filePath, "utf-8"));
3215
3765
  }
3216
3766
  function listMethodologies() {
3217
3767
  const dir = getMethodologiesDir();
3218
- if (!existsSync12(dir)) return [];
3219
- return readdirSync3(dir).filter((f) => f.endsWith(".json")).map((f) => {
3768
+ if (!existsSync13(dir)) return [];
3769
+ return readdirSync5(dir).filter((f) => f.endsWith(".json")).map((f) => {
3220
3770
  try {
3221
- const m = JSON.parse(readFileSync9(join10(dir, f), "utf-8"));
3771
+ const m = JSON.parse(readFileSync10(join11(dir, f), "utf-8"));
3222
3772
  return { id: m.id, name: m.name, description: m.description };
3223
3773
  } catch {
3224
3774
  return null;
@@ -3226,12 +3776,12 @@ function listMethodologies() {
3226
3776
  }).filter(Boolean);
3227
3777
  }
3228
3778
  function getProductDir(factoryDir, productName) {
3229
- return join10(factoryDir, ".beastmode", "products", productName);
3779
+ return join11(factoryDir, ".beastmode", "products", productName);
3230
3780
  }
3231
3781
  function createInceptionState(factoryDir, opts) {
3232
3782
  const methodology = getMethodology(opts.methodology);
3233
3783
  const productDir = getProductDir(factoryDir, opts.productName);
3234
- mkdirSync8(productDir, { recursive: true });
3784
+ mkdirSync9(productDir, { recursive: true });
3235
3785
  const state = {
3236
3786
  productName: opts.productName,
3237
3787
  idea: opts.idea,
@@ -3248,21 +3798,21 @@ function createInceptionState(factoryDir, opts) {
3248
3798
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3249
3799
  projectName: null
3250
3800
  };
3251
- writeFileSync9(join10(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
3801
+ writeFileSync10(join11(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
3252
3802
  return state;
3253
3803
  }
3254
3804
  function loadInception(factoryDir, productName) {
3255
- const filePath = join10(getProductDir(factoryDir, productName), "inception.json");
3256
- if (!existsSync12(filePath)) return null;
3805
+ const filePath = join11(getProductDir(factoryDir, productName), "inception.json");
3806
+ if (!existsSync13(filePath)) return null;
3257
3807
  try {
3258
- return JSON.parse(readFileSync9(filePath, "utf-8"));
3808
+ return JSON.parse(readFileSync10(filePath, "utf-8"));
3259
3809
  } catch {
3260
3810
  return null;
3261
3811
  }
3262
3812
  }
3263
3813
  function saveInceptionState(factoryDir, state) {
3264
3814
  const productDir = getProductDir(factoryDir, state.productName);
3265
- writeFileSync9(join10(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
3815
+ writeFileSync10(join11(productDir, "inception.json"), JSON.stringify(state, null, 2) + "\n");
3266
3816
  }
3267
3817
  function advancePhase(factoryDir, productName) {
3268
3818
  const state = loadInception(factoryDir, productName);
@@ -3282,28 +3832,28 @@ function advancePhase(factoryDir, productName) {
3282
3832
  }
3283
3833
  function saveArtifact(factoryDir, productName, filename, content) {
3284
3834
  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);
3835
+ mkdirSync9(productDir, { recursive: true });
3836
+ const filePath = join11(productDir, filename);
3837
+ mkdirSync9(dirname4(filePath), { recursive: true });
3838
+ writeFileSync10(filePath, content);
3289
3839
  }
3290
3840
  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");
3841
+ const filePath = join11(getProductDir(factoryDir, productName), filename);
3842
+ if (!existsSync13(filePath)) return null;
3843
+ return readFileSync10(filePath, "utf-8");
3294
3844
  }
3295
3845
  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-"))) {
3846
+ const productDir = join11(factoryDir, ".beastmode", "products", productName);
3847
+ const oldInception = join11(productDir, "inception.json");
3848
+ if (!existsSync13(oldInception)) return false;
3849
+ const sessionsDir = join11(productDir, "sessions");
3850
+ if (existsSync13(sessionsDir) && readdirSync5(sessionsDir).some((d) => d.startsWith("session-"))) {
3301
3851
  return false;
3302
3852
  }
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);
3853
+ const sessionDir = join11(sessionsDir, "session-001");
3854
+ mkdirSync9(sessionDir, { recursive: true });
3855
+ const content = readFileSync10(oldInception, "utf-8");
3856
+ writeFileSync10(join11(sessionDir, "inception.json"), content);
3307
3857
  const artifactFiles = [
3308
3858
  "prd.md",
3309
3859
  "architecture.md",
@@ -3316,9 +3866,9 @@ function migrateInceptionToSession(factoryDir, productName) {
3316
3866
  "project-plan.md"
3317
3867
  ];
3318
3868
  for (const file of artifactFiles) {
3319
- const src = join10(productDir, file);
3320
- if (existsSync12(src)) {
3321
- writeFileSync9(join10(sessionDir, file), readFileSync9(src, "utf-8"));
3869
+ const src = join11(productDir, file);
3870
+ if (existsSync13(src)) {
3871
+ writeFileSync10(join11(sessionDir, file), readFileSync10(src, "utf-8"));
3322
3872
  unlinkSync2(src);
3323
3873
  }
3324
3874
  }
@@ -3326,11 +3876,11 @@ function migrateInceptionToSession(factoryDir, productName) {
3326
3876
  return true;
3327
3877
  }
3328
3878
  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) => {
3879
+ const productsDir = join11(factoryDir, ".beastmode", "products");
3880
+ if (!existsSync13(productsDir)) return [];
3881
+ return readdirSync5(productsDir).filter((d) => existsSync13(join11(productsDir, d, "inception.json"))).map((d) => {
3332
3882
  try {
3333
- const state = JSON.parse(readFileSync9(join10(productsDir, d, "inception.json"), "utf-8"));
3883
+ const state = JSON.parse(readFileSync10(join11(productsDir, d, "inception.json"), "utf-8"));
3334
3884
  return {
3335
3885
  name: state.productName || d,
3336
3886
  methodology: state.methodology || "unknown",
@@ -3453,18 +4003,18 @@ __export(strategy_exports, {
3453
4003
  saveSessionArtifact: () => saveSessionArtifact,
3454
4004
  saveStrategySession: () => saveStrategySession
3455
4005
  });
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";
4006
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, writeFileSync as writeFileSync11, readFileSync as readFileSync11, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
4007
+ import { join as join12 } from "path";
3458
4008
  function getProductDir2(factoryDir, projectName) {
3459
- return join11(factoryDir, ".beastmode", "products", projectName);
4009
+ return join12(factoryDir, ".beastmode", "products", projectName);
3460
4010
  }
3461
4011
  function getSessionsDir(factoryDir, projectName) {
3462
- return join11(getProductDir2(factoryDir, projectName), "sessions");
4012
+ return join12(getProductDir2(factoryDir, projectName), "sessions");
3463
4013
  }
3464
4014
  function nextSessionId(factoryDir, projectName) {
3465
4015
  const sessionsDir = getSessionsDir(factoryDir, projectName);
3466
- if (!existsSync13(sessionsDir)) return "session-001";
3467
- const existing = readdirSync4(sessionsDir).filter((d) => d.startsWith("session-")).sort();
4016
+ if (!existsSync14(sessionsDir)) return "session-001";
4017
+ const existing = readdirSync6(sessionsDir).filter((d) => d.startsWith("session-")).sort();
3468
4018
  if (existing.length === 0) return "session-001";
3469
4019
  const lastNum = parseInt(existing[existing.length - 1].replace("session-", ""), 10);
3470
4020
  return `session-${String(lastNum + 1).padStart(3, "0")}`;
@@ -3472,8 +4022,8 @@ function nextSessionId(factoryDir, projectName) {
3472
4022
  function createStrategySession(factoryDir, projectName, opts) {
3473
4023
  const methodology = getMethodology(opts.methodology);
3474
4024
  const sessionId = nextSessionId(factoryDir, projectName);
3475
- const sessionDir = join11(getSessionsDir(factoryDir, projectName), sessionId);
3476
- mkdirSync9(sessionDir, { recursive: true });
4025
+ const sessionDir = join12(getSessionsDir(factoryDir, projectName), sessionId);
4026
+ mkdirSync10(sessionDir, { recursive: true });
3477
4027
  const session = {
3478
4028
  sessionId,
3479
4029
  name: opts.name,
@@ -3494,100 +4044,100 @@ function createStrategySession(factoryDir, projectName, opts) {
3494
4044
  sessionType: opts.sessionType || "free-form",
3495
4045
  approach: opts.approach || "auto"
3496
4046
  };
3497
- writeFileSync10(join11(sessionDir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
4047
+ writeFileSync11(join12(sessionDir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
3498
4048
  return session;
3499
4049
  }
3500
4050
  function listStrategySessions(factoryDir, projectName) {
3501
4051
  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;
4052
+ if (!existsSync14(sessionsDir)) return [];
4053
+ return readdirSync6(sessionsDir).filter((d) => d.startsWith("session-")).sort().map((d) => {
4054
+ const file = join12(sessionsDir, d, "inception.json");
4055
+ if (!existsSync14(file)) return null;
3506
4056
  try {
3507
- return JSON.parse(readFileSync10(file, "utf-8"));
4057
+ return JSON.parse(readFileSync11(file, "utf-8"));
3508
4058
  } catch {
3509
4059
  return null;
3510
4060
  }
3511
4061
  }).filter(Boolean);
3512
4062
  }
3513
4063
  function loadStrategySession(factoryDir, projectName, sessionId) {
3514
- const file = join11(getSessionsDir(factoryDir, projectName), sessionId, "inception.json");
3515
- if (!existsSync13(file)) return null;
4064
+ const file = join12(getSessionsDir(factoryDir, projectName), sessionId, "inception.json");
4065
+ if (!existsSync14(file)) return null;
3516
4066
  try {
3517
- return JSON.parse(readFileSync10(file, "utf-8"));
4067
+ return JSON.parse(readFileSync11(file, "utf-8"));
3518
4068
  } catch {
3519
4069
  return null;
3520
4070
  }
3521
4071
  }
3522
4072
  function saveStrategySession(factoryDir, projectName, sessionId, session) {
3523
- const dir = join11(getSessionsDir(factoryDir, projectName), sessionId);
3524
- mkdirSync9(dir, { recursive: true });
4073
+ const dir = join12(getSessionsDir(factoryDir, projectName), sessionId);
4074
+ mkdirSync10(dir, { recursive: true });
3525
4075
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3526
- writeFileSync10(join11(dir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
4076
+ writeFileSync11(join12(dir, "inception.json"), JSON.stringify(session, null, 2) + "\n");
3527
4077
  }
3528
4078
  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);
4079
+ const dir = join12(getSessionsDir(factoryDir, projectName), sessionId);
4080
+ mkdirSync10(dir, { recursive: true });
4081
+ writeFileSync11(join12(dir, filename), content);
3532
4082
  }
3533
4083
  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");
4084
+ const file = join12(getSessionsDir(factoryDir, projectName), sessionId, filename);
4085
+ if (!existsSync14(file)) return null;
4086
+ return readFileSync11(file, "utf-8");
3537
4087
  }
3538
4088
  function buildArtifactIndex(factoryDir, projectName) {
3539
4089
  const artifacts = [];
3540
- const brownfieldPath = join11(factoryDir, ".beastmode", "projects", projectName, "brownfield.md");
3541
- if (existsSync13(brownfieldPath)) {
3542
- const content = readFileSync10(brownfieldPath, "utf-8");
4090
+ const brownfieldPath = join12(factoryDir, ".beastmode", "projects", projectName, "brownfield.md");
4091
+ if (existsSync14(brownfieldPath)) {
4092
+ const content = readFileSync11(brownfieldPath, "utf-8");
3543
4093
  const firstLine = content.split("\n").find((l) => l.trim() && !l.startsWith("#")) || "";
3544
4094
  artifacts.push({
3545
4095
  type: "brownfield",
3546
4096
  path: brownfieldPath,
3547
4097
  summary: firstLine.slice(0, 200),
3548
- date: statSync3(brownfieldPath).mtime.toISOString()
4098
+ date: statSync4(brownfieldPath).mtime.toISOString()
3549
4099
  });
3550
4100
  }
3551
4101
  const sessions = listStrategySessions(factoryDir, projectName);
3552
4102
  for (const session of sessions) {
3553
4103
  artifacts.push({
3554
4104
  type: "strategy_session",
3555
- path: join11(getSessionsDir(factoryDir, projectName), session.sessionId),
4105
+ path: join12(getSessionsDir(factoryDir, projectName), session.sessionId),
3556
4106
  summary: `${session.name} (${session.methodology}) \u2014 ${session.status}`,
3557
4107
  date: session.createdAt,
3558
4108
  sessionId: session.sessionId
3559
4109
  });
3560
4110
  }
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);
4111
+ const runsDir = join12(factoryDir, "runs", projectName);
4112
+ if (existsSync14(runsDir)) {
4113
+ const runDirs = readdirSync6(runsDir).filter((d) => d.startsWith("run-")).sort().reverse().slice(0, 10);
3564
4114
  for (const runId of runDirs) {
3565
- const nlspecPath = join11(runsDir, runId, "nlspec.md");
3566
- if (existsSync13(nlspecPath)) {
3567
- const content = readFileSync10(nlspecPath, "utf-8");
4115
+ const nlspecPath = join12(runsDir, runId, "nlspec.md");
4116
+ if (existsSync14(nlspecPath)) {
4117
+ const content = readFileSync11(nlspecPath, "utf-8");
3568
4118
  const title = content.split("\n").find((l) => l.startsWith("# ")) || runId;
3569
4119
  artifacts.push({
3570
4120
  type: "nlspec",
3571
4121
  path: nlspecPath,
3572
4122
  summary: title.replace("# ", "").slice(0, 200),
3573
- date: statSync3(nlspecPath).mtime.toISOString(),
4123
+ date: statSync4(nlspecPath).mtime.toISOString(),
3574
4124
  runId
3575
4125
  });
3576
4126
  }
3577
4127
  }
3578
4128
  }
3579
- const learningsDir = join11(getProductDir2(factoryDir, projectName), "learnings");
3580
- if (existsSync13(learningsDir)) {
3581
- const learningFiles = readdirSync4(learningsDir).filter((f) => f.endsWith(".md") && !f.startsWith("."));
4129
+ const learningsDir = join12(getProductDir2(factoryDir, projectName), "learnings");
4130
+ if (existsSync14(learningsDir)) {
4131
+ const learningFiles = readdirSync6(learningsDir).filter((f) => f.endsWith(".md") && !f.startsWith("."));
3582
4132
  for (const file of learningFiles) {
3583
- const filePath = join11(learningsDir, file);
3584
- const content = readFileSync10(filePath, "utf-8");
4133
+ const filePath = join12(learningsDir, file);
4134
+ const content = readFileSync11(filePath, "utf-8");
3585
4135
  const firstHeading = content.split("\n").find((l) => l.startsWith("## ")) || file;
3586
4136
  artifacts.push({
3587
4137
  type: "learning",
3588
4138
  path: filePath,
3589
4139
  summary: firstHeading.replace("## ", "").slice(0, 200),
3590
- date: statSync3(filePath).mtime.toISOString()
4140
+ date: statSync4(filePath).mtime.toISOString()
3591
4141
  });
3592
4142
  }
3593
4143
  }
@@ -3612,8 +4162,8 @@ var init_strategy = __esm({
3612
4162
  // src/cli/ui/chat-handler.ts
3613
4163
  import { randomUUID } from "crypto";
3614
4164
  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";
4165
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync12, existsSync as existsSync15, mkdirSync as mkdirSync11, readdirSync as readdirSync7, statSync as statSync5 } from "fs";
4166
+ import { join as join13 } from "path";
3617
4167
  import http from "http";
3618
4168
  import { WebSocketServer, WebSocket as WsWebSocket } from "ws";
3619
4169
  function getRecentTokenUsage() {
@@ -3723,19 +4273,19 @@ function getToolDefinitions() {
3723
4273
  ];
3724
4274
  }
3725
4275
  function readJsonSafe(filePath) {
3726
- if (!existsSync14(filePath)) return null;
4276
+ if (!existsSync15(filePath)) return null;
3727
4277
  try {
3728
- return JSON.parse(readFileSync11(filePath, "utf-8"));
4278
+ return JSON.parse(readFileSync12(filePath, "utf-8"));
3729
4279
  } catch {
3730
4280
  return null;
3731
4281
  }
3732
4282
  }
3733
4283
  function getBoardUrl(factoryPath) {
3734
4284
  if (process.env.BEASTMODE_BOARD_URL) return process.env.BEASTMODE_BOARD_URL;
3735
- const configPath = join12(factoryPath, ".beastmode", "config.json");
3736
- if (existsSync14(configPath)) {
4285
+ const configPath = join13(factoryPath, ".beastmode", "config.json");
4286
+ if (existsSync15(configPath)) {
3737
4287
  try {
3738
- const config = JSON.parse(readFileSync11(configPath, "utf-8"));
4288
+ const config = JSON.parse(readFileSync12(configPath, "utf-8"));
3739
4289
  if (config.task_backend?.config?.url) return config.task_backend.config.url;
3740
4290
  } catch {
3741
4291
  }
@@ -3746,19 +4296,19 @@ async function executeTool(toolName, toolInput, factoryPath) {
3746
4296
  try {
3747
4297
  switch (toolName) {
3748
4298
  case "factory_status": {
3749
- const bmDir = join12(factoryPath, ".beastmode");
3750
- const factory = readJsonSafe(join12(bmDir, "factory.json"));
3751
- const projectsDir = join12(bmDir, "projects");
4299
+ const bmDir = join13(factoryPath, ".beastmode");
4300
+ const factory = readJsonSafe(join13(bmDir, "factory.json"));
4301
+ const projectsDir = join13(bmDir, "projects");
3752
4302
  let projects = [];
3753
- if (existsSync14(projectsDir)) {
3754
- projects = readdirSync5(projectsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
4303
+ if (existsSync15(projectsDir)) {
4304
+ projects = readdirSync7(projectsDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", ""));
3755
4305
  }
3756
- const runsDir = join12(factoryPath, "runs");
4306
+ const runsDir = join13(factoryPath, "runs");
3757
4307
  let runs = [];
3758
- if (existsSync14(runsDir)) {
3759
- runs = readdirSync5(runsDir).filter((d) => {
4308
+ if (existsSync15(runsDir)) {
4309
+ runs = readdirSync7(runsDir).filter((d) => {
3760
4310
  try {
3761
- return statSync4(join12(runsDir, d)).isDirectory();
4311
+ return statSync5(join13(runsDir, d)).isDirectory();
3762
4312
  } catch {
3763
4313
  return false;
3764
4314
  }
@@ -3766,10 +4316,10 @@ async function executeTool(toolName, toolInput, factoryPath) {
3766
4316
  }
3767
4317
  let daemonStatus = "stopped";
3768
4318
  let daemonPid = null;
3769
- const pidFile = join12(bmDir, "daemon.pid");
3770
- if (existsSync14(pidFile)) {
4319
+ const pidFile = join13(bmDir, "daemon.pid");
4320
+ if (existsSync15(pidFile)) {
3771
4321
  try {
3772
- daemonPid = parseInt(readFileSync11(pidFile, "utf-8").trim(), 10);
4322
+ daemonPid = parseInt(readFileSync12(pidFile, "utf-8").trim(), 10);
3773
4323
  process.kill(daemonPid, 0);
3774
4324
  daemonStatus = "running";
3775
4325
  } catch {
@@ -3817,7 +4367,7 @@ async function executeTool(toolName, toolInput, factoryPath) {
3817
4367
  }, null, 2);
3818
4368
  }
3819
4369
  case "factory_config": {
3820
- const configPath = join12(factoryPath, ".beastmode", "config.json");
4370
+ const configPath = join13(factoryPath, ".beastmode", "config.json");
3821
4371
  const config = readJsonSafe(configPath);
3822
4372
  return config ? JSON.stringify(config, null, 2) : "No config.json found.";
3823
4373
  }
@@ -3872,12 +4422,12 @@ async function executeTool(toolName, toolInput, factoryPath) {
3872
4422
  }
3873
4423
  }
3874
4424
  case "list_runs": {
3875
- const runsDir = join12(factoryPath, "runs");
3876
- if (!existsSync14(runsDir)) return "No runs directory.";
3877
- const runDirs = readdirSync5(runsDir).sort().reverse();
4425
+ const runsDir = join13(factoryPath, "runs");
4426
+ if (!existsSync15(runsDir)) return "No runs directory.";
4427
+ const runDirs = readdirSync7(runsDir).sort().reverse();
3878
4428
  const results = [];
3879
4429
  for (const id of runDirs.slice(0, 15)) {
3880
- const cp = readJsonSafe(join12(runsDir, id, "checkpoint.json"));
4430
+ const cp = readJsonSafe(join13(runsDir, id, "checkpoint.json"));
3881
4431
  if (cp) {
3882
4432
  const hist = Array.isArray(cp.satisfaction_history) ? cp.satisfaction_history : [];
3883
4433
  results.push({
@@ -3893,36 +4443,36 @@ async function executeTool(toolName, toolInput, factoryPath) {
3893
4443
  case "run_detail": {
3894
4444
  const runId = toolInput.run_id;
3895
4445
  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");
4446
+ const runDir = join13(factoryPath, "runs", runId);
4447
+ if (!existsSync15(runDir)) return `Run not found: ${runId}`;
4448
+ const manifest = readJsonSafe(join13(runDir, "manifest.json"));
4449
+ const checkpoint = readJsonSafe(join13(runDir, "checkpoint.json"));
4450
+ const iterDir = join13(runDir, "iterations");
3901
4451
  const iterations = [];
3902
- if (existsSync14(iterDir)) {
3903
- for (const d of readdirSync5(iterDir).sort()) {
3904
- const sat = readJsonSafe(join12(iterDir, d, "satisfaction.json"));
4452
+ if (existsSync15(iterDir)) {
4453
+ for (const d of readdirSync7(iterDir).sort()) {
4454
+ const sat = readJsonSafe(join13(iterDir, d, "satisfaction.json"));
3905
4455
  iterations.push({ number: parseInt(d, 10), satisfaction: sat });
3906
4456
  }
3907
4457
  }
3908
- const files = readdirSync5(runDir);
4458
+ const files = readdirSync7(runDir);
3909
4459
  return JSON.stringify({ id: runId, files, manifest, checkpoint, iterations }, null, 2);
3910
4460
  }
3911
4461
  case "read_run_file": {
3912
4462
  const runId = toolInput.run_id;
3913
4463
  const relPath = toolInput.file_path;
3914
4464
  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");
4465
+ const fullPath = join13(factoryPath, "runs", runId, relPath);
4466
+ if (!existsSync15(fullPath)) return `File not found: runs/${runId}/${relPath}`;
4467
+ const content = readFileSync12(fullPath, "utf-8");
3918
4468
  return content.length > 1e4 ? content.slice(0, 1e4) + "\n\n... (truncated at 10KB)" : content;
3919
4469
  }
3920
4470
  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) => {
4471
+ const projDir = join13(factoryPath, ".beastmode", "projects");
4472
+ if (!existsSync15(projDir)) return "No projects registered.";
4473
+ const projects = readdirSync7(projDir).filter((f) => f.endsWith(".json")).map((f) => {
3924
4474
  try {
3925
- return JSON.parse(readFileSync11(join12(projDir, f), "utf-8"));
4475
+ return JSON.parse(readFileSync12(join13(projDir, f), "utf-8"));
3926
4476
  } catch {
3927
4477
  return null;
3928
4478
  }
@@ -3932,25 +4482,25 @@ async function executeTool(toolName, toolInput, factoryPath) {
3932
4482
  case "read_file": {
3933
4483
  const relPath = toolInput.path;
3934
4484
  if (relPath.includes("..")) return "Invalid path.";
3935
- const fullPath = join12(factoryPath, relPath);
4485
+ const fullPath = join13(factoryPath, relPath);
3936
4486
  if (!fullPath.startsWith(factoryPath)) return "Path traversal not allowed.";
3937
- if (!existsSync14(fullPath)) return `File not found: ${relPath}`;
4487
+ if (!existsSync15(fullPath)) return `File not found: ${relPath}`;
3938
4488
  try {
3939
- const entries = readdirSync5(fullPath);
4489
+ const entries = readdirSync7(fullPath);
3940
4490
  return `"${relPath}" is a directory with ${entries.length} entries: ${entries.slice(0, 30).join(", ")}`;
3941
4491
  } catch {
3942
4492
  }
3943
- const content = readFileSync11(fullPath, "utf-8");
4493
+ const content = readFileSync12(fullPath, "utf-8");
3944
4494
  return content.length > 1e4 ? content.slice(0, 1e4) + "\n\n... (truncated at 10KB)" : content;
3945
4495
  }
3946
4496
  case "list_directory": {
3947
4497
  const relPath = toolInput.path || "";
3948
4498
  if (relPath.includes("..")) return "Invalid path.";
3949
- const fullPath = join12(factoryPath, relPath);
4499
+ const fullPath = join13(factoryPath, relPath);
3950
4500
  if (!fullPath.startsWith(factoryPath)) return "Path traversal not allowed.";
3951
- if (!existsSync14(fullPath)) return `Directory not found: ${relPath || "/"}`;
4501
+ if (!existsSync15(fullPath)) return `Directory not found: ${relPath || "/"}`;
3952
4502
  try {
3953
- return readdirSync5(fullPath).join("\n");
4503
+ return readdirSync7(fullPath).join("\n");
3954
4504
  } catch {
3955
4505
  return `Not a directory: ${relPath}`;
3956
4506
  }
@@ -3963,8 +4513,8 @@ async function executeTool(toolName, toolInput, factoryPath) {
3963
4513
  }
3964
4514
  }
3965
4515
  function getChatHistoryDir(factoryPath) {
3966
- const dir = join12(factoryPath, ".beastmode", "chat-history");
3967
- if (!existsSync14(dir)) mkdirSync10(dir, { recursive: true });
4516
+ const dir = join13(factoryPath, ".beastmode", "chat-history");
4517
+ if (!existsSync15(dir)) mkdirSync11(dir, { recursive: true });
3968
4518
  return dir;
3969
4519
  }
3970
4520
  function saveConversation(factoryPath, sessionId, messages, scope) {
@@ -3975,13 +4525,13 @@ function saveConversation(factoryPath, sessionId, messages, scope) {
3975
4525
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
3976
4526
  messages
3977
4527
  };
3978
- writeFileSync11(join12(dir, `${sessionId}.json`), JSON.stringify(data, null, 2));
4528
+ writeFileSync12(join13(dir, `${sessionId}.json`), JSON.stringify(data, null, 2));
3979
4529
  }
3980
4530
  function listConversations(factoryPath) {
3981
4531
  const dir = getChatHistoryDir(factoryPath);
3982
- return readdirSync5(dir).filter((f) => f.endsWith(".json")).map((f) => {
4532
+ return readdirSync7(dir).filter((f) => f.endsWith(".json")).map((f) => {
3983
4533
  try {
3984
- const data = JSON.parse(readFileSync11(join12(dir, f), "utf-8"));
4534
+ const data = JSON.parse(readFileSync12(join13(dir, f), "utf-8"));
3985
4535
  const msgs = data.messages || [];
3986
4536
  const firstUser = msgs.find((m) => m.role === "user");
3987
4537
  const preview = typeof firstUser?.content === "string" ? firstUser.content.slice(0, 80) : "";
@@ -3999,10 +4549,10 @@ function listConversations(factoryPath) {
3999
4549
  }
4000
4550
  function loadConversation(factoryPath, sessionId) {
4001
4551
  if (sessionId.includes("..")) return [];
4002
- const file = join12(getChatHistoryDir(factoryPath), `${sessionId}.json`);
4003
- if (!existsSync14(file)) return [];
4552
+ const file = join13(getChatHistoryDir(factoryPath), `${sessionId}.json`);
4553
+ if (!existsSync15(file)) return [];
4004
4554
  try {
4005
- const data = JSON.parse(readFileSync11(file, "utf-8"));
4555
+ const data = JSON.parse(readFileSync12(file, "utf-8"));
4006
4556
  return data.messages || [];
4007
4557
  } catch {
4008
4558
  return [];
@@ -4010,7 +4560,7 @@ function loadConversation(factoryPath, sessionId) {
4010
4560
  }
4011
4561
  function buildSystemPrompt(factoryPath) {
4012
4562
  let factoryName = "BeastMode Factory";
4013
- const factoryJson = readJsonSafe(join12(factoryPath, ".beastmode", "factory.json"));
4563
+ const factoryJson = readJsonSafe(join13(factoryPath, ".beastmode", "factory.json"));
4014
4564
  if (factoryJson?.factory_name) factoryName = factoryJson.factory_name;
4015
4565
  else if (factoryJson?.name) factoryName = factoryJson.name;
4016
4566
  return `You are BeastMode Assistant \u2014 a concise, helpful assistant for the "${factoryName}" factory at ${factoryPath}.
@@ -4557,8 +5107,8 @@ var init_chat_handler = __esm({
4557
5107
  });
4558
5108
 
4559
5109
  // 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";
5110
+ 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";
5111
+ import { join as join14, basename as basename4, resolve as resolve4, dirname as dirname5 } from "path";
4562
5112
  import { homedir } from "os";
4563
5113
  import { randomUUID as randomUUID2 } from "crypto";
4564
5114
  import { execSync as execSync3, spawnSync } from "child_process";
@@ -4570,10 +5120,10 @@ function assertSafeName(name) {
4570
5120
  }
4571
5121
  function getBoardUrl2(factoryDir) {
4572
5122
  if (process.env.BEASTMODE_BOARD_URL) return process.env.BEASTMODE_BOARD_URL;
4573
- const configPath = join13(factoryDir, ".beastmode", "config.json");
4574
- if (existsSync15(configPath)) {
5123
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
5124
+ if (existsSync16(configPath)) {
4575
5125
  try {
4576
- const config = JSON.parse(readFileSync12(configPath, "utf-8"));
5126
+ const config = JSON.parse(readFileSync13(configPath, "utf-8"));
4577
5127
  if (config.task_backend?.config?.url) {
4578
5128
  return config.task_backend.config.url;
4579
5129
  }
@@ -4712,9 +5262,9 @@ function scopedQuery(query) {
4712
5262
  return { board: b };
4713
5263
  }
4714
5264
  function readJsonFile(filePath) {
4715
- if (!existsSync15(filePath)) return null;
5265
+ if (!existsSync16(filePath)) return null;
4716
5266
  try {
4717
- return JSON.parse(readFileSync12(filePath, "utf-8"));
5267
+ return JSON.parse(readFileSync13(filePath, "utf-8"));
4718
5268
  } catch {
4719
5269
  return null;
4720
5270
  }
@@ -4734,25 +5284,25 @@ function deepMerge2(target, source) {
4734
5284
  return result;
4735
5285
  }
4736
5286
  function getRunsDir(factoryDir) {
4737
- const configPath = join13(factoryDir, ".beastmode", "config.json");
4738
- if (existsSync15(configPath)) {
5287
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
5288
+ if (existsSync16(configPath)) {
4739
5289
  try {
4740
- const config = JSON.parse(readFileSync12(configPath, "utf-8"));
5290
+ const config = JSON.parse(readFileSync13(configPath, "utf-8"));
4741
5291
  if (config.runs_path) return config.runs_path;
4742
5292
  } catch {
4743
5293
  }
4744
5294
  }
4745
- return join13(factoryDir, "runs");
5295
+ return join14(factoryDir, "runs");
4746
5296
  }
4747
5297
  function scanStrandedRuns(runsDir, newThreshold) {
4748
- if (!existsSync15(runsDir)) return [];
5298
+ if (!existsSync16(runsDir)) return [];
4749
5299
  const stranded = [];
4750
5300
  const walkRun = (runPath, projectId) => {
4751
- const ckptPath = join13(runPath, "checkpoint.json");
4752
- if (!existsSync15(ckptPath)) return;
5301
+ const ckptPath = join14(runPath, "checkpoint.json");
5302
+ if (!existsSync16(ckptPath)) return;
4753
5303
  let ckpt;
4754
5304
  try {
4755
- ckpt = JSON.parse(readFileSync12(ckptPath, "utf-8"));
5305
+ ckpt = JSON.parse(readFileSync13(ckptPath, "utf-8"));
4756
5306
  } catch {
4757
5307
  return;
4758
5308
  }
@@ -4770,12 +5320,12 @@ function scanStrandedRuns(runsDir, newThreshold) {
4770
5320
  });
4771
5321
  };
4772
5322
  try {
4773
- const entries = readdirSync6(runsDir);
5323
+ const entries = readdirSync8(runsDir);
4774
5324
  for (const entry of entries) {
4775
- const entryPath = join13(runsDir, entry);
5325
+ const entryPath = join14(runsDir, entry);
4776
5326
  let stat;
4777
5327
  try {
4778
- stat = statSync5(entryPath);
5328
+ stat = statSync6(entryPath);
4779
5329
  } catch {
4780
5330
  continue;
4781
5331
  }
@@ -4785,15 +5335,15 @@ function scanStrandedRuns(runsDir, newThreshold) {
4785
5335
  } else if (!entry.startsWith(".")) {
4786
5336
  let subEntries;
4787
5337
  try {
4788
- subEntries = readdirSync6(entryPath);
5338
+ subEntries = readdirSync8(entryPath);
4789
5339
  } catch {
4790
5340
  continue;
4791
5341
  }
4792
5342
  for (const sub of subEntries) {
4793
5343
  if (!sub.startsWith("run-")) continue;
4794
- const subPath = join13(entryPath, sub);
5344
+ const subPath = join14(entryPath, sub);
4795
5345
  try {
4796
- if (statSync5(subPath).isDirectory()) {
5346
+ if (statSync6(subPath).isDirectory()) {
4797
5347
  walkRun(subPath, entry);
4798
5348
  }
4799
5349
  } catch {
@@ -4808,10 +5358,10 @@ function scanStrandedRuns(runsDir, newThreshold) {
4808
5358
  return stranded;
4809
5359
  }
4810
5360
  function _readAnalyzeMeta(projectsDir, name) {
4811
- const metaPath = join13(projectsDir, name, "codebase-guide.meta.json");
4812
- if (!existsSync15(metaPath)) return null;
5361
+ const metaPath = join14(projectsDir, name, "codebase-guide.meta.json");
5362
+ if (!existsSync16(metaPath)) return null;
4813
5363
  try {
4814
- return JSON.parse(readFileSync12(metaPath, "utf-8"));
5364
+ return JSON.parse(readFileSync13(metaPath, "utf-8"));
4815
5365
  } catch {
4816
5366
  return null;
4817
5367
  }
@@ -4838,29 +5388,19 @@ function getBoardRoutes(factoryDir) {
4838
5388
  method: "GET",
4839
5389
  pattern: "/api/status",
4840
5390
  handler: async () => {
4841
- const bmDir = join13(factoryDir, ".beastmode");
4842
- const factoryJsonPath = join13(bmDir, "factory.json");
4843
- if (!existsSync15(factoryJsonPath)) {
5391
+ const bmDir = join14(factoryDir, ".beastmode");
5392
+ const factoryJsonPath = join14(bmDir, "factory.json");
5393
+ if (!existsSync16(factoryJsonPath)) {
4844
5394
  throw new Error("Not a valid factory: factory.json not found");
4845
5395
  }
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");
5396
+ const factoryIdentity = JSON.parse(readFileSync13(factoryJsonPath, "utf-8"));
5397
+ const projectsDir = join14(bmDir, "projects");
5398
+ const projectCount = listProjectRecords(projectsDir).length;
5399
+ const lockPath = join14(bmDir, "extensions.lock");
4860
5400
  let pluginNames = [];
4861
- if (existsSync15(lockPath)) {
5401
+ if (existsSync16(lockPath)) {
4862
5402
  try {
4863
- const lock = JSON.parse(readFileSync12(lockPath, "utf-8"));
5403
+ const lock = JSON.parse(readFileSync13(lockPath, "utf-8"));
4864
5404
  pluginNames = Object.keys(lock.plugins || {});
4865
5405
  } catch {
4866
5406
  }
@@ -4880,24 +5420,24 @@ function getBoardRoutes(factoryDir) {
4880
5420
  skillCount = listSkills(factoryDir).length;
4881
5421
  } catch {
4882
5422
  }
4883
- const runsDir = join13(factoryDir, "runs");
5423
+ const runsDir = join14(factoryDir, "runs");
4884
5424
  let runDirs = [];
4885
- if (existsSync15(runsDir)) {
4886
- runDirs = readdirSync6(runsDir).filter((d) => {
5425
+ if (existsSync16(runsDir)) {
5426
+ runDirs = readdirSync8(runsDir).filter((d) => {
4887
5427
  if (d.startsWith(".")) return false;
4888
5428
  try {
4889
- return readdirSync6(join13(runsDir, d)).length > 0;
5429
+ return readdirSync8(join14(runsDir, d)).length > 0;
4890
5430
  } catch {
4891
5431
  return false;
4892
5432
  }
4893
5433
  }).sort();
4894
5434
  }
4895
- const pidFile = join13(factoryDir, ".beastmode", "daemon.pid");
5435
+ const pidFile = join14(factoryDir, ".beastmode", "daemon.pid");
4896
5436
  let daemonPid = null;
4897
5437
  let pidAlive = false;
4898
- if (existsSync15(pidFile)) {
5438
+ if (existsSync16(pidFile)) {
4899
5439
  try {
4900
- daemonPid = parseInt(readFileSync12(pidFile, "utf-8").trim(), 10);
5440
+ daemonPid = parseInt(readFileSync13(pidFile, "utf-8").trim(), 10);
4901
5441
  process.kill(daemonPid, 0);
4902
5442
  pidAlive = true;
4903
5443
  } catch {
@@ -4954,10 +5494,10 @@ function getBoardRoutes(factoryDir) {
4954
5494
  handler: async () => {
4955
5495
  const boardUrl = getBoardUrl2(factoryDir);
4956
5496
  let ageSecs = null;
4957
- const heartbeatPath = join13(factoryDir, "daemon", "logs", ".heartbeat");
4958
- if (existsSync15(heartbeatPath)) {
5497
+ const heartbeatPath = join14(factoryDir, "daemon", "logs", ".heartbeat");
5498
+ if (existsSync16(heartbeatPath)) {
4959
5499
  try {
4960
- const ts = parseInt(readFileSync12(heartbeatPath, "utf-8").trim(), 10);
5500
+ const ts = parseInt(readFileSync13(heartbeatPath, "utf-8").trim(), 10);
4961
5501
  if (!isNaN(ts)) {
4962
5502
  ageSecs = Math.floor(Date.now() / 1e3) - ts;
4963
5503
  }
@@ -4999,11 +5539,11 @@ function getBoardRoutes(factoryDir) {
4999
5539
  let totalSlots = 3;
5000
5540
  let shortPhaseReserve;
5001
5541
  let slotsTimestamp;
5002
- const slotsPath = join13(factoryDir, "daemon", "logs", ".slots.json");
5542
+ const slotsPath = join14(factoryDir, "daemon", "logs", ".slots.json");
5003
5543
  let usedDaemonSlotsFile = false;
5004
- if (existsSync15(slotsPath)) {
5544
+ if (existsSync16(slotsPath)) {
5005
5545
  try {
5006
- const slots = JSON.parse(readFileSync12(slotsPath, "utf-8"));
5546
+ const slots = JSON.parse(readFileSync13(slotsPath, "utf-8"));
5007
5547
  if (typeof slots.busy === "number" && typeof slots.total === "number") {
5008
5548
  busySlots = slots.busy;
5009
5549
  totalSlots = slots.total;
@@ -5028,10 +5568,10 @@ function getBoardRoutes(factoryDir) {
5028
5568
  } catch {
5029
5569
  }
5030
5570
  for (const name of ["beastmode.docker.json", "beastmode.daemon.json"]) {
5031
- const configPath = join13(factoryDir, "config", name);
5032
- if (!existsSync15(configPath)) continue;
5571
+ const configPath = join14(factoryDir, "config", name);
5572
+ if (!existsSync16(configPath)) continue;
5033
5573
  try {
5034
- const config = JSON.parse(readFileSync12(configPath, "utf-8"));
5574
+ const config = JSON.parse(readFileSync13(configPath, "utf-8"));
5035
5575
  if (typeof config.max_slots === "number") {
5036
5576
  totalSlots = config.max_slots;
5037
5577
  }
@@ -5048,11 +5588,11 @@ function getBoardRoutes(factoryDir) {
5048
5588
  if (shortPhaseReserve === void 0) {
5049
5589
  shortPhaseReserve = Math.max(0, Math.min(1, totalSlots - 1));
5050
5590
  }
5051
- const alertsPath = join13(factoryDir, "daemon", "logs", ".alerts.json");
5591
+ const alertsPath = join14(factoryDir, "daemon", "logs", ".alerts.json");
5052
5592
  let alerts = [];
5053
- if (existsSync15(alertsPath)) {
5593
+ if (existsSync16(alertsPath)) {
5054
5594
  try {
5055
- const parsed = JSON.parse(readFileSync12(alertsPath, "utf-8"));
5595
+ const parsed = JSON.parse(readFileSync13(alertsPath, "utf-8"));
5056
5596
  if (Array.isArray(parsed)) alerts = parsed;
5057
5597
  } catch {
5058
5598
  }
@@ -5267,12 +5807,12 @@ function getBoardRoutes(factoryDir) {
5267
5807
  method: "GET",
5268
5808
  pattern: "/api/extensions/plugins",
5269
5809
  handler: () => {
5270
- const lockPath = join13(factoryDir, ".beastmode", "extensions.lock");
5271
- if (!existsSync15(lockPath)) {
5810
+ const lockPath = join14(factoryDir, ".beastmode", "extensions.lock");
5811
+ if (!existsSync16(lockPath)) {
5272
5812
  return { plugins: {} };
5273
5813
  }
5274
5814
  try {
5275
- const lock = JSON.parse(readFileSync12(lockPath, "utf-8"));
5815
+ const lock = JSON.parse(readFileSync13(lockPath, "utf-8"));
5276
5816
  return { plugins: lock.plugins || {} };
5277
5817
  } catch {
5278
5818
  return { plugins: {} };
@@ -5429,27 +5969,8 @@ function getBoardRoutes(factoryDir) {
5429
5969
  method: "GET",
5430
5970
  pattern: "/api/projects",
5431
5971
  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 };
5972
+ const projectsDir = join14(factoryDir, ".beastmode", "projects");
5973
+ return { projects: listProjectRecords(projectsDir) };
5453
5974
  }
5454
5975
  },
5455
5976
  {
@@ -5458,11 +5979,9 @@ function getBoardRoutes(factoryDir) {
5458
5979
  handler: (_body, params) => {
5459
5980
  const { name } = params;
5460
5981
  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"));
5982
+ const record = readProjectRecord(join14(factoryDir, ".beastmode", "projects"), name);
5983
+ if (!record) throw new Error(`Project not found: ${name}`);
5984
+ return record;
5466
5985
  }
5467
5986
  },
5468
5987
  {
@@ -5471,12 +5990,12 @@ function getBoardRoutes(factoryDir) {
5471
5990
  handler: (_body, params) => {
5472
5991
  const { name } = params;
5473
5992
  assertSafeName(name);
5474
- const extPath = join13(factoryDir, ".beastmode", "projects", name, "extensions.json");
5475
- if (!existsSync15(extPath)) {
5993
+ const extPath = join14(factoryDir, ".beastmode", "projects", name, "extensions.json");
5994
+ if (!existsSync16(extPath)) {
5476
5995
  return { plugins: { add: [], remove: [] }, mcps: { add: {}, remove: [] }, skills: { add: [], remove: [] } };
5477
5996
  }
5478
5997
  try {
5479
- return JSON.parse(readFileSync12(extPath, "utf-8"));
5998
+ return JSON.parse(readFileSync13(extPath, "utf-8"));
5480
5999
  } catch {
5481
6000
  return { plugins: { add: [], remove: [] }, mcps: { add: {}, remove: [] }, skills: { add: [], remove: [] } };
5482
6001
  }
@@ -5498,18 +6017,18 @@ function getBoardRoutes(factoryDir) {
5498
6017
  const { path: queryPath } = body || {};
5499
6018
  const startPath = queryPath || homedir();
5500
6019
  const target = resolve4(startPath);
5501
- if (!existsSync15(target)) throw new Error(`Path not found: ${target}`);
5502
- const st = statSync5(target);
6020
+ if (!existsSync16(target)) throw new Error(`Path not found: ${target}`);
6021
+ const st = statSync6(target);
5503
6022
  if (!st.isDirectory()) throw new Error(`Not a directory: ${target}`);
5504
6023
  let entries = [];
5505
6024
  try {
5506
- entries = readdirSync6(target, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
5507
- const full = join13(target, d.name);
6025
+ entries = readdirSync8(target, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => {
6026
+ const full = join14(target, d.name);
5508
6027
  return {
5509
6028
  name: d.name,
5510
6029
  path: full,
5511
6030
  is_dir: true,
5512
- has_git: existsSync15(join13(full, ".git"))
6031
+ has_git: existsSync16(join14(full, ".git"))
5513
6032
  };
5514
6033
  }).sort((a, b) => a.name.localeCompare(b.name));
5515
6034
  } catch {
@@ -5528,8 +6047,8 @@ function getBoardRoutes(factoryDir) {
5528
6047
  pattern: "/api/projects",
5529
6048
  handler: (body) => {
5530
6049
  const { path: projectPath, github_url, clone_target } = body;
5531
- const projectsDir = join13(factoryDir, ".beastmode", "projects");
5532
- if (!existsSync15(projectsDir)) mkdirSync11(projectsDir, { recursive: true });
6050
+ const projectsDir = join14(factoryDir, ".beastmode", "projects");
6051
+ if (!existsSync16(projectsDir)) mkdirSync12(projectsDir, { recursive: true });
5533
6052
  let resolvedPath;
5534
6053
  if (github_url) {
5535
6054
  const url = github_url.trim();
@@ -5538,10 +6057,10 @@ function getBoardRoutes(factoryDir) {
5538
6057
  }
5539
6058
  const repoName = url.replace(/\.git$/, "").split(/[/:]/).filter(Boolean).pop() || "";
5540
6059
  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)) {
6060
+ const baseDir = clone_target ? resolve4(clone_target) : process.env.BEASTMODE_CLONE_DIR ? resolve4(process.env.BEASTMODE_CLONE_DIR) : join14(homedir(), "repos");
6061
+ if (!existsSync16(baseDir)) mkdirSync12(baseDir, { recursive: true });
6062
+ resolvedPath = join14(baseDir, repoName);
6063
+ if (existsSync16(resolvedPath)) {
5545
6064
  throw new Error(`Target path already exists: ${resolvedPath} \u2014 pick a different clone_target or rename the existing folder`);
5546
6065
  }
5547
6066
  const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "";
@@ -5573,42 +6092,41 @@ function getBoardRoutes(factoryDir) {
5573
6092
  }
5574
6093
  } else if (projectPath) {
5575
6094
  resolvedPath = resolve4(projectPath);
5576
- if (!existsSync15(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
6095
+ if (!existsSync16(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
5577
6096
  } else {
5578
6097
  throw new Error("Missing required field: provide either `path` (local) or `github_url` (clone)");
5579
6098
  }
5580
- const projectName = basename3(resolvedPath);
6099
+ const projectName = basename4(resolvedPath);
5581
6100
  const stack = detectStack(resolvedPath);
5582
- const projectConfig = {
6101
+ let verifyPort = 3001;
6102
+ for (const existing of listProjectRecords(projectsDir)) {
6103
+ const port = existing.deploy?.verify_port;
6104
+ if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
6105
+ }
6106
+ const record = createProjectRecord({
5583
6107
  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
- }
6108
+ resolvedPath,
6109
+ gitRemote: stack.git_remote || void 0,
6110
+ verifyPort
6111
+ });
6112
+ record.stack = {
6113
+ detected: stack.framework,
6114
+ build_command: stack.suggested_commands.build,
6115
+ dev_command: stack.suggested_commands.dev,
6116
+ test_command: stack.suggested_commands.test,
6117
+ install_command: stack.suggested_commands.install,
6118
+ dev_port: stack.dev_port,
6119
+ is_monorepo: stack.is_monorepo,
6120
+ total_packages: stack.total_packages,
6121
+ primary_languages: stack.primary_languages,
6122
+ ...stack.packages ? { packages: stack.packages } : {}
5602
6123
  };
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;
6124
+ record.infra = { credentials: { mode: "factory" }, state_backend: "auto" };
6125
+ writeProjectRecord(projectsDir, projectName, record);
6126
+ const infraDir = join14(projectsDir, projectName, "infra");
6127
+ mkdirSync12(join14(infraDir, "terraform"), { recursive: true });
6128
+ mkdirSync12(join14(infraDir, "ci"), { recursive: true });
6129
+ return record;
5612
6130
  }
5613
6131
  },
5614
6132
  {
@@ -5617,14 +6135,14 @@ function getBoardRoutes(factoryDir) {
5617
6135
  handler: (_body, params) => {
5618
6136
  const { name } = params;
5619
6137
  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);
6138
+ const projectsDir = join14(factoryDir, ".beastmode", "projects");
6139
+ const subDir = join14(projectsDir, name);
6140
+ const flatPath = join14(projectsDir, `${name}.json`);
6141
+ const hasSubDir = existsSync16(join14(subDir, "project.json"));
6142
+ const hasFlat = existsSync16(flatPath);
5625
6143
  if (!hasSubDir && !hasFlat) throw new Error(`Project not found: ${name}`);
5626
6144
  if (hasSubDir) rmSync3(subDir, { recursive: true, force: true });
5627
- else if (existsSync15(subDir)) rmSync3(subDir, { recursive: true, force: true });
6145
+ else if (existsSync16(subDir)) rmSync3(subDir, { recursive: true, force: true });
5628
6146
  if (hasFlat) unlinkSync3(flatPath);
5629
6147
  return { success: true };
5630
6148
  }
@@ -5635,20 +6153,17 @@ function getBoardRoutes(factoryDir) {
5635
6153
  handler: async (body, params, query) => {
5636
6154
  const { name } = params;
5637
6155
  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"));
6156
+ const projectsDir = join14(factoryDir, ".beastmode", "projects");
6157
+ const projConfig = readProjectRecord(projectsDir, name);
6158
+ if (!projConfig) throw new Error(`Project not found: ${name}`);
5644
6159
  const projectPath = projConfig.path;
5645
- const subDir = join13(projectsDir, name);
5646
- if (!existsSync15(subDir)) mkdirSync11(subDir, { recursive: true });
6160
+ const subDir = join14(projectsDir, name);
6161
+ if (!existsSync16(subDir)) mkdirSync12(subDir, { recursive: true });
5647
6162
  const force = query?.force === "true" || query?.force === "1";
5648
6163
  const tier = body?.tier;
5649
6164
  if (tier === "quick") {
5650
- const brownfieldPath = join13(subDir, "brownfield.md");
5651
- if (!existsSync15(brownfieldPath)) {
6165
+ const brownfieldPath = join14(subDir, "brownfield.md");
6166
+ if (!existsSync16(brownfieldPath)) {
5652
6167
  const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
5653
6168
  let stackInfo = "Unknown stack";
5654
6169
  try {
@@ -5659,7 +6174,7 @@ Build: ${cmds?.build || "unknown"}
5659
6174
  Dev: ${cmds?.dev || "unknown"}`;
5660
6175
  } catch {
5661
6176
  }
5662
- writeFileSync12(brownfieldPath, `# Brownfield Analysis: ${name}
6177
+ writeFileSync13(brownfieldPath, `# Brownfield Analysis: ${name}
5663
6178
 
5664
6179
  ${stackInfo}
5665
6180
 
@@ -5681,8 +6196,8 @@ Path: ${projectPath}
5681
6196
  }
5682
6197
  const writeMeta = (m) => {
5683
6198
  try {
5684
- writeFileSync12(
5685
- join13(subDir, "codebase-guide.meta.json"),
6199
+ writeFileSync13(
6200
+ join14(subDir, "codebase-guide.meta.json"),
5686
6201
  JSON.stringify(m, null, 2) + "\n",
5687
6202
  "utf-8"
5688
6203
  );
@@ -5691,8 +6206,8 @@ Path: ${projectPath}
5691
6206
  }
5692
6207
  };
5693
6208
  if (!_hasClaudeCli()) {
5694
- const brownfieldPath = join13(subDir, "brownfield.md");
5695
- if (!existsSync15(brownfieldPath)) {
6209
+ const brownfieldPath = join14(subDir, "brownfield.md");
6210
+ if (!existsSync16(brownfieldPath)) {
5696
6211
  const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
5697
6212
  let stackInfo = "Unknown stack";
5698
6213
  try {
@@ -5703,7 +6218,7 @@ Build: ${cmds?.build || "unknown"}
5703
6218
  Dev: ${cmds?.dev || "unknown"}`;
5704
6219
  } catch {
5705
6220
  }
5706
- writeFileSync12(brownfieldPath, `# Brownfield Analysis: ${name}
6221
+ writeFileSync13(brownfieldPath, `# Brownfield Analysis: ${name}
5707
6222
 
5708
6223
  ${stackInfo}
5709
6224
 
@@ -5737,9 +6252,9 @@ Path: ${projectPath}
5737
6252
  "You are the Brownfield Analyst. Analyze the codebase at the current working directory.",
5738
6253
  `Write your output in pyramid format (L0/L1/L2) to the directory: ${subDir}`,
5739
6254
  "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)`,
6255
+ ` - ${join14(subDir, "codebase-guide.md")} (full L2 analysis: architecture, conventions, safe modification points, fragile areas)`,
6256
+ ` - ${join14(subDir, "codebase-guide.l1.md")} (paragraph summary only)`,
6257
+ ` - ${join14(subDir, "codebase-guide.l0.txt")} (one-sentence summary only)`,
5743
6258
  "Do not write anything else outside the BEASTMODE_OUTPUT_DIR."
5744
6259
  ].join("\n");
5745
6260
  const isRoot = process.getuid?.() === 0;
@@ -5772,8 +6287,8 @@ Path: ${projectPath}
5772
6287
  child.on("close", (code) => {
5773
6288
  const durationSeconds = (Date.now() - startMs) / 1e3;
5774
6289
  const sha = _gitHeadSha(projectPath);
5775
- const guidePath = join13(subDir, "codebase-guide.md");
5776
- const ok = code === 0 && existsSync15(guidePath);
6290
+ const guidePath = join14(subDir, "codebase-guide.md");
6291
+ const ok = code === 0 && existsSync16(guidePath);
5777
6292
  if (ok) {
5778
6293
  writeMeta({
5779
6294
  analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5783,8 +6298,8 @@ Path: ${projectPath}
5783
6298
  duration_seconds: durationSeconds,
5784
6299
  status: "complete"
5785
6300
  });
5786
- const legacy = join13(subDir, "brownfield.md");
5787
- if (existsSync15(legacy)) {
6301
+ const legacy = join14(subDir, "brownfield.md");
6302
+ if (existsSync16(legacy)) {
5788
6303
  try {
5789
6304
  unlinkSync3(legacy);
5790
6305
  } catch {
@@ -5860,7 +6375,7 @@ Path: ${projectPath}
5860
6375
  handler: (_body, params) => {
5861
6376
  const { name } = params;
5862
6377
  assertSafeName(name);
5863
- const projectsDir = join13(factoryDir, ".beastmode", "projects");
6378
+ const projectsDir = join14(factoryDir, ".beastmode", "projects");
5864
6379
  const job = _analyzeJobs.get(name);
5865
6380
  const meta = _readAnalyzeMeta(projectsDir, name);
5866
6381
  if (job && job.status === "running") {
@@ -5904,13 +6419,13 @@ Path: ${projectPath}
5904
6419
  pattern: "/api/runs",
5905
6420
  handler: () => {
5906
6421
  const runsDir = getRunsDir(factoryDir);
5907
- if (!existsSync15(runsDir)) return { runs: [] };
6422
+ if (!existsSync16(runsDir)) return { runs: [] };
5908
6423
  const runEntries = [];
5909
- for (const entry of readdirSync6(runsDir)) {
6424
+ for (const entry of readdirSync8(runsDir)) {
5910
6425
  if (entry.startsWith(".")) continue;
5911
- const entryPath = join13(runsDir, entry);
6426
+ const entryPath = join14(runsDir, entry);
5912
6427
  try {
5913
- const children = readdirSync6(entryPath);
6428
+ const children = readdirSync8(entryPath);
5914
6429
  if (entry.startsWith("run-")) {
5915
6430
  if (children.length > 0) {
5916
6431
  runEntries.push({ id: entry, dir: entryPath, projectId: "" });
@@ -5918,9 +6433,9 @@ Path: ${projectPath}
5918
6433
  } else {
5919
6434
  for (const child of children) {
5920
6435
  if (!child.startsWith("run-") || child.startsWith(".")) continue;
5921
- const childPath = join13(entryPath, child);
6436
+ const childPath = join14(entryPath, child);
5922
6437
  try {
5923
- const grandchildren = readdirSync6(childPath);
6438
+ const grandchildren = readdirSync8(childPath);
5924
6439
  if (grandchildren.length > 0) {
5925
6440
  runEntries.push({ id: child, dir: childPath, projectId: entry });
5926
6441
  }
@@ -5940,9 +6455,9 @@ Path: ${projectPath}
5940
6455
  }
5941
6456
  }
5942
6457
  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"));
6458
+ const manifest = readJsonFile(join14(dir, "manifest.json"));
6459
+ const checkpoint = readJsonFile(join14(dir, "checkpoint.json"));
6460
+ const prodVerif = readJsonFile(join14(dir, "prod-verification.json"));
5946
6461
  const manifestData = manifest;
5947
6462
  const cpData = checkpoint;
5948
6463
  const prodAcceptFloor = 0.65;
@@ -5970,27 +6485,27 @@ Path: ${projectPath}
5970
6485
  handler: (_body, params, query) => {
5971
6486
  const { id } = params;
5972
6487
  const runsDir = getRunsDir(factoryDir);
5973
- let runDir = join13(runsDir, id);
5974
- if (!existsSync15(runDir)) {
5975
- for (const proj of readdirSync6(runsDir)) {
6488
+ let runDir = join14(runsDir, id);
6489
+ if (!existsSync16(runDir)) {
6490
+ for (const proj of readdirSync8(runsDir)) {
5976
6491
  if (proj.startsWith(".") || proj.startsWith("run-")) continue;
5977
- const candidate = join13(runsDir, proj, id);
5978
- if (existsSync15(candidate)) {
6492
+ const candidate = join14(runsDir, proj, id);
6493
+ if (existsSync16(candidate)) {
5979
6494
  runDir = candidate;
5980
6495
  break;
5981
6496
  }
5982
6497
  }
5983
6498
  }
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");
6499
+ if (!existsSync16(runDir)) throw new Error(`Run not found: ${id}`);
6500
+ const manifest = readJsonFile(join14(runDir, "manifest.json"));
6501
+ const checkpoint = readJsonFile(join14(runDir, "checkpoint.json"));
6502
+ const iterationsDir = join14(runDir, "iterations");
5988
6503
  const iterations = [];
5989
- if (existsSync15(iterationsDir)) {
5990
- const iterDirs = readdirSync6(iterationsDir).sort();
6504
+ if (existsSync16(iterationsDir)) {
6505
+ const iterDirs = readdirSync8(iterationsDir).sort();
5991
6506
  for (const iterDir of iterDirs) {
5992
6507
  const satisfaction = readJsonFile(
5993
- join13(iterationsDir, iterDir, "satisfaction.json")
6508
+ join14(iterationsDir, iterDir, "satisfaction.json")
5994
6509
  );
5995
6510
  iterations.push({
5996
6511
  number: parseInt(iterDir, 10),
@@ -6018,11 +6533,11 @@ Path: ${projectPath}
6018
6533
  pattern: "/api/runs/archive",
6019
6534
  handler: () => {
6020
6535
  const runsDir = getRunsDir(factoryDir);
6021
- const configPath = join13(factoryDir, ".beastmode", "config.json");
6536
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
6022
6537
  let days = 7;
6023
- if (existsSync15(configPath)) {
6538
+ if (existsSync16(configPath)) {
6024
6539
  try {
6025
- days = JSON.parse(readFileSync12(configPath, "utf-8")).archive_after_days || 7;
6540
+ days = JSON.parse(readFileSync13(configPath, "utf-8")).archive_after_days || 7;
6026
6541
  } catch {
6027
6542
  }
6028
6543
  }
@@ -6048,8 +6563,8 @@ Path: ${projectPath}
6048
6563
  method: "GET",
6049
6564
  pattern: "/api/config",
6050
6565
  handler: () => {
6051
- const configPath = join13(factoryDir, ".beastmode", "config.json");
6052
- const raw = existsSync15(configPath) ? JSON.parse(readFileSync12(configPath, "utf-8")) : generateDefaults();
6566
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
6567
+ const raw = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : generateDefaults();
6053
6568
  return raw;
6054
6569
  }
6055
6570
  },
@@ -6067,8 +6582,8 @@ Path: ${projectPath}
6067
6582
  const updatesClean = { ...updates };
6068
6583
  delete updatesClean.force;
6069
6584
  delete updatesClean._force;
6070
- const configPath = join13(factoryDir, ".beastmode", "config.json");
6071
- const current = existsSync15(configPath) ? JSON.parse(readFileSync12(configPath, "utf-8")) : generateDefaults();
6585
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
6586
+ const current = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : generateDefaults();
6072
6587
  if (!force) {
6073
6588
  const oldPipeline = current.pipeline || {};
6074
6589
  const newPipeline = updatesClean.pipeline || {};
@@ -6099,11 +6614,11 @@ Path: ${projectPath}
6099
6614
  }
6100
6615
  }
6101
6616
  const merged = deepMerge2(current, updatesClean);
6102
- writeFileSync12(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6617
+ writeFileSync13(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6103
6618
  const daemonConfigPaths = [
6104
- join13(factoryDir, "config", "beastmode.daemon.json"),
6105
- join13(factoryDir, "config", "beastmode.docker.json")
6106
- ].filter(existsSync15);
6619
+ join14(factoryDir, "config", "beastmode.daemon.json"),
6620
+ join14(factoryDir, "config", "beastmode.docker.json")
6621
+ ].filter(existsSync16);
6107
6622
  const pipelineToDaemonMap = {
6108
6623
  satisfaction_threshold: ["verification", "satisfaction_threshold"],
6109
6624
  prod_accept_floor: ["verification", "prod_accept_floor"],
@@ -6119,7 +6634,7 @@ Path: ${projectPath}
6119
6634
  ];
6120
6635
  for (const daemonConfigPath of daemonConfigPaths) {
6121
6636
  try {
6122
- const daemonConfig = JSON.parse(readFileSync12(daemonConfigPath, "utf-8"));
6637
+ const daemonConfig = JSON.parse(readFileSync13(daemonConfigPath, "utf-8"));
6123
6638
  let changed = false;
6124
6639
  for (const field of daemonFields) {
6125
6640
  if (field in merged && merged[field] !== daemonConfig[field]) {
@@ -6159,7 +6674,7 @@ Path: ${projectPath}
6159
6674
  }
6160
6675
  }
6161
6676
  if (changed) {
6162
- writeFileSync12(daemonConfigPath, JSON.stringify(daemonConfig, null, 2) + "\n", "utf-8");
6677
+ writeFileSync13(daemonConfigPath, JSON.stringify(daemonConfig, null, 2) + "\n", "utf-8");
6163
6678
  }
6164
6679
  } catch {
6165
6680
  }
@@ -6172,10 +6687,10 @@ Path: ${projectPath}
6172
6687
  pattern: "/api/config/reset",
6173
6688
  handler: (body) => {
6174
6689
  const { keyPath } = body || {};
6175
- const configPath = join13(factoryDir, ".beastmode", "config.json");
6176
- const current = existsSync15(configPath) ? JSON.parse(readFileSync12(configPath, "utf-8")) : {};
6690
+ const configPath = join14(factoryDir, ".beastmode", "config.json");
6691
+ const current = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : {};
6177
6692
  const result = configReset(current, keyPath);
6178
- writeFileSync12(configPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
6693
+ writeFileSync13(configPath, JSON.stringify(result, null, 2) + "\n", "utf-8");
6179
6694
  return result;
6180
6695
  }
6181
6696
  },
@@ -6265,13 +6780,13 @@ Path: ${projectPath}
6265
6780
  handler: () => {
6266
6781
  const runsDir = getRunsDir(factoryDir);
6267
6782
  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)) {
6783
+ const rootRetroDir = join14(runsDir, ".retrospectives");
6784
+ if (existsSync16(rootRetroDir)) retroDirs.push(rootRetroDir);
6785
+ if (existsSync16(runsDir)) {
6786
+ for (const entry of readdirSync8(runsDir)) {
6272
6787
  if (entry === ".retrospectives" || entry.startsWith(".")) continue;
6273
- const projectRetroDir = join13(runsDir, entry, ".retrospectives");
6274
- if (existsSync15(projectRetroDir)) retroDirs.push(projectRetroDir);
6788
+ const projectRetroDir = join14(runsDir, entry, ".retrospectives");
6789
+ if (existsSync16(projectRetroDir)) retroDirs.push(projectRetroDir);
6275
6790
  }
6276
6791
  }
6277
6792
  if (retroDirs.length === 0) {
@@ -6280,9 +6795,9 @@ Path: ${projectPath}
6280
6795
  const retrospectives = [];
6281
6796
  const learnings = [];
6282
6797
  for (const retroDir of retroDirs) {
6283
- const retroFiles = readdirSync6(retroDir).filter((f) => f.startsWith("run-") && f.endsWith(".json")).sort().reverse();
6798
+ const retroFiles = readdirSync8(retroDir).filter((f) => f.startsWith("run-") && f.endsWith(".json")).sort().reverse();
6284
6799
  for (const file of retroFiles) {
6285
- const data = readJsonFile(join13(retroDir, file));
6800
+ const data = readJsonFile(join14(retroDir, file));
6286
6801
  if (!data) continue;
6287
6802
  const retro = data;
6288
6803
  retrospectives.push(retro);
@@ -6304,10 +6819,10 @@ Path: ${projectPath}
6304
6819
  let wisdom = "";
6305
6820
  let wisdomMeta = {};
6306
6821
  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")) || {};
6822
+ const wisdomPath = join14(retroDir, "wisdom.md");
6823
+ if (existsSync16(wisdomPath)) {
6824
+ wisdom = readFileSync13(wisdomPath, "utf-8");
6825
+ wisdomMeta = readJsonFile(join14(retroDir, "wisdom-meta.json")) || {};
6311
6826
  break;
6312
6827
  }
6313
6828
  }
@@ -6319,11 +6834,11 @@ Path: ${projectPath}
6319
6834
  pattern: "/api/learnings/wisdom/refresh",
6320
6835
  handler: () => {
6321
6836
  const runsDir = getRunsDir(factoryDir);
6322
- const retroDir = join13(runsDir, ".retrospectives");
6323
- if (!existsSync15(retroDir)) {
6324
- mkdirSync11(retroDir, { recursive: true });
6837
+ const retroDir = join14(runsDir, ".retrospectives");
6838
+ if (!existsSync16(retroDir)) {
6839
+ mkdirSync12(retroDir, { recursive: true });
6325
6840
  }
6326
- writeFileSync12(join13(retroDir, ".refresh-requested"), (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
6841
+ writeFileSync13(join14(retroDir, ".refresh-requested"), (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
6327
6842
  return { success: true };
6328
6843
  }
6329
6844
  },
@@ -6332,29 +6847,29 @@ Path: ${projectPath}
6332
6847
  pattern: "/api/learnings/:runId/:index/dismiss",
6333
6848
  handler: (_body, params) => {
6334
6849
  const runsDir = getRunsDir(factoryDir);
6335
- const candidateDirs = [join13(runsDir, ".retrospectives")];
6336
- if (existsSync15(runsDir)) {
6337
- for (const entry of readdirSync6(runsDir)) {
6850
+ const candidateDirs = [join14(runsDir, ".retrospectives")];
6851
+ if (existsSync16(runsDir)) {
6852
+ for (const entry of readdirSync8(runsDir)) {
6338
6853
  if (entry === ".retrospectives" || entry.startsWith(".")) continue;
6339
- candidateDirs.push(join13(runsDir, entry, ".retrospectives"));
6854
+ candidateDirs.push(join14(runsDir, entry, ".retrospectives"));
6340
6855
  }
6341
6856
  }
6342
6857
  let filePath = null;
6343
6858
  for (const dir of candidateDirs) {
6344
- const candidate = join13(dir, `${params.runId}.json`);
6345
- if (existsSync15(candidate)) {
6859
+ const candidate = join14(dir, `${params.runId}.json`);
6860
+ if (existsSync16(candidate)) {
6346
6861
  filePath = candidate;
6347
6862
  break;
6348
6863
  }
6349
6864
  }
6350
6865
  if (!filePath) throw new Error(`Retrospective not found: ${params.runId}`);
6351
- const data = JSON.parse(readFileSync12(filePath, "utf-8"));
6866
+ const data = JSON.parse(readFileSync13(filePath, "utf-8"));
6352
6867
  const idx = parseInt(params.index, 10);
6353
6868
  if (!Array.isArray(data.learnings) || idx < 0 || idx >= data.learnings.length) {
6354
6869
  throw new Error(`Learning index out of range: ${idx}`);
6355
6870
  }
6356
6871
  data.learnings[idx].dismissed = true;
6357
- writeFileSync12(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
6872
+ writeFileSync13(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
6358
6873
  return { success: true };
6359
6874
  }
6360
6875
  },
@@ -6363,29 +6878,29 @@ Path: ${projectPath}
6363
6878
  pattern: "/api/learnings/:runId/:index/restore",
6364
6879
  handler: (_body, params) => {
6365
6880
  const runsDir = getRunsDir(factoryDir);
6366
- const candidateDirs = [join13(runsDir, ".retrospectives")];
6367
- if (existsSync15(runsDir)) {
6368
- for (const entry of readdirSync6(runsDir)) {
6881
+ const candidateDirs = [join14(runsDir, ".retrospectives")];
6882
+ if (existsSync16(runsDir)) {
6883
+ for (const entry of readdirSync8(runsDir)) {
6369
6884
  if (entry === ".retrospectives" || entry.startsWith(".")) continue;
6370
- candidateDirs.push(join13(runsDir, entry, ".retrospectives"));
6885
+ candidateDirs.push(join14(runsDir, entry, ".retrospectives"));
6371
6886
  }
6372
6887
  }
6373
6888
  let filePath = null;
6374
6889
  for (const dir of candidateDirs) {
6375
- const candidate = join13(dir, `${params.runId}.json`);
6376
- if (existsSync15(candidate)) {
6890
+ const candidate = join14(dir, `${params.runId}.json`);
6891
+ if (existsSync16(candidate)) {
6377
6892
  filePath = candidate;
6378
6893
  break;
6379
6894
  }
6380
6895
  }
6381
6896
  if (!filePath) throw new Error(`Retrospective not found: ${params.runId}`);
6382
- const data = JSON.parse(readFileSync12(filePath, "utf-8"));
6897
+ const data = JSON.parse(readFileSync13(filePath, "utf-8"));
6383
6898
  const idx = parseInt(params.index, 10);
6384
6899
  if (!Array.isArray(data.learnings) || idx < 0 || idx >= data.learnings.length) {
6385
6900
  throw new Error(`Learning index out of range: ${idx}`);
6386
6901
  }
6387
6902
  data.learnings[idx].dismissed = false;
6388
- writeFileSync12(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
6903
+ writeFileSync13(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
6389
6904
  return { success: true };
6390
6905
  }
6391
6906
  },
@@ -6528,22 +7043,22 @@ Path: ${projectPath}
6528
7043
  } catch {
6529
7044
  }
6530
7045
  try {
6531
- const runsDir = join13(factoryDir, "runs", project);
6532
- if (existsSync15(runsDir)) {
6533
- const runDirs = readdirSync6(runsDir).filter((d) => d.startsWith("run-"));
7046
+ const runsDir = join14(factoryDir, "runs", project);
7047
+ if (existsSync16(runsDir)) {
7048
+ const runDirs = readdirSync8(runsDir).filter((d) => d.startsWith("run-"));
6534
7049
  for (const runId of runDirs) {
6535
- const ckptPath = join13(runsDir, runId, "checkpoint.json");
7050
+ const ckptPath = join14(runsDir, runId, "checkpoint.json");
6536
7051
  let subtitle = "";
6537
7052
  let timestamp = "";
6538
7053
  try {
6539
- const st = statSync5(join13(runsDir, runId));
7054
+ const st = statSync6(join14(runsDir, runId));
6540
7055
  timestamp = st.mtime.toISOString();
6541
7056
  } catch {
6542
7057
  timestamp = (/* @__PURE__ */ new Date()).toISOString();
6543
7058
  }
6544
- if (existsSync15(ckptPath)) {
7059
+ if (existsSync16(ckptPath)) {
6545
7060
  try {
6546
- const ckpt = JSON.parse(readFileSync12(ckptPath, "utf-8"));
7061
+ const ckpt = JSON.parse(readFileSync13(ckptPath, "utf-8"));
6547
7062
  const history = ckpt.satisfaction_history;
6548
7063
  if (history && history.length > 0) {
6549
7064
  const latest = history[history.length - 1];
@@ -6651,8 +7166,8 @@ __export(server_exports, {
6651
7166
  });
6652
7167
  import { createServer } from "http";
6653
7168
  import { createHmac } from "crypto";
6654
- import { readFileSync as readFileSync13, existsSync as existsSync16 } from "fs";
6655
- import { join as join14, dirname as dirname6 } from "path";
7169
+ import { readFileSync as readFileSync14, existsSync as existsSync17 } from "fs";
7170
+ import { join as join15, dirname as dirname6 } from "path";
6656
7171
  import { fileURLToPath as fileURLToPath2 } from "url";
6657
7172
  import { WebSocketServer as WebSocketServer2, WebSocket as WsClient } from "ws";
6658
7173
  function proxyBoardWebSocket(req, socket, head, boardWsUrl) {
@@ -6694,16 +7209,16 @@ function proxyBoardWebSocket(req, socket, head, boardWsUrl) {
6694
7209
  });
6695
7210
  }
6696
7211
  function resolveStaticDir() {
6697
- const devPath = join14(__dirname, "static");
6698
- if (existsSync16(join14(devPath, "index.html"))) {
7212
+ const devPath = join15(__dirname, "static");
7213
+ if (existsSync17(join15(devPath, "index.html"))) {
6699
7214
  return devPath;
6700
7215
  }
6701
- const distWebPath = join14(__dirname, "web");
6702
- if (existsSync16(join14(distWebPath, "index.html"))) {
7216
+ const distWebPath = join15(__dirname, "web");
7217
+ if (existsSync17(join15(distWebPath, "index.html"))) {
6703
7218
  return distWebPath;
6704
7219
  }
6705
- const siblingPath = join14(__dirname, "..", "web");
6706
- if (existsSync16(join14(siblingPath, "index.html"))) {
7220
+ const siblingPath = join15(__dirname, "..", "web");
7221
+ if (existsSync17(join15(siblingPath, "index.html"))) {
6707
7222
  return siblingPath;
6708
7223
  }
6709
7224
  throw new Error(
@@ -6812,9 +7327,9 @@ async function startServer(options = {}) {
6812
7327
  const fallback = "<html><body><h1>BeastMode Init Wizard</h1><p>Static files not found.</p></body></html>";
6813
7328
  if (!staticDir) return fallback;
6814
7329
  try {
6815
- const indexPath = join14(staticDir, "index.html");
6816
- if (!existsSync16(indexPath)) return fallback;
6817
- return readFileSync13(indexPath, "utf-8");
7330
+ const indexPath = join15(staticDir, "index.html");
7331
+ if (!existsSync17(indexPath)) return fallback;
7332
+ return readFileSync14(indexPath, "utf-8");
6818
7333
  } catch {
6819
7334
  return fallback;
6820
7335
  }
@@ -6822,9 +7337,9 @@ async function startServer(options = {}) {
6822
7337
  function loadBoardHtml() {
6823
7338
  try {
6824
7339
  const dir = staticDir || resolveStaticDir();
6825
- const boardPath = join14(dir, "board.html");
6826
- if (!existsSync16(boardPath)) return null;
6827
- return readFileSync13(boardPath, "utf-8");
7340
+ const boardPath = join15(dir, "board.html");
7341
+ if (!existsSync17(boardPath)) return null;
7342
+ return readFileSync14(boardPath, "utf-8");
6828
7343
  } catch {
6829
7344
  return null;
6830
7345
  }
@@ -6910,13 +7425,13 @@ async function startServer(options = {}) {
6910
7425
  let commit_sha = null;
6911
7426
  try {
6912
7427
  const dir = staticDir || resolveStaticDir();
6913
- const stampPath = join14(dir, "build-stamp.txt");
6914
- if (existsSync16(stampPath)) {
6915
- stamp = readFileSync13(stampPath, "utf-8").trim();
7428
+ const stampPath = join15(dir, "build-stamp.txt");
7429
+ if (existsSync17(stampPath)) {
7430
+ stamp = readFileSync14(stampPath, "utf-8").trim();
6916
7431
  }
6917
- const commitPath = join14(dir, "build-commit.txt");
6918
- if (existsSync16(commitPath)) {
6919
- commit_sha = readFileSync13(commitPath, "utf-8").trim() || null;
7432
+ const commitPath = join15(dir, "build-commit.txt");
7433
+ if (existsSync17(commitPath)) {
7434
+ commit_sha = readFileSync14(commitPath, "utf-8").trim() || null;
6920
7435
  }
6921
7436
  } catch {
6922
7437
  }
@@ -6994,8 +7509,8 @@ async function startServer(options = {}) {
6994
7509
  }
6995
7510
  if (method === "GET" && url.startsWith("/static/")) {
6996
7511
  const filename = url.slice("/static/".length);
6997
- const filePath = staticDir ? join14(staticDir, filename) : "";
6998
- if (filePath && existsSync16(filePath)) {
7512
+ const filePath = staticDir ? join15(staticDir, filename) : "";
7513
+ if (filePath && existsSync17(filePath)) {
6999
7514
  const ext = filename.split(".").pop()?.toLowerCase();
7000
7515
  const mimeTypes = {
7001
7516
  png: "image/png",
@@ -7006,7 +7521,7 @@ async function startServer(options = {}) {
7006
7521
  ico: "image/x-icon"
7007
7522
  };
7008
7523
  const contentType = mimeTypes[ext || ""] || "application/octet-stream";
7009
- const content = readFileSync13(filePath);
7524
+ const content = readFileSync14(filePath);
7010
7525
  res.writeHead(200, {
7011
7526
  "Content-Type": contentType,
7012
7527
  "Content-Length": content.length.toString(),
@@ -7255,30 +7770,29 @@ var init_server = __esm({
7255
7770
 
7256
7771
  // src/cli/commands/board.ts
7257
7772
  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";
7773
+ import { resolve as resolve5, join as join16 } from "path";
7774
+ import { existsSync as existsSync18, readFileSync as readFileSync15, mkdirSync as mkdirSync13, writeFileSync as writeFileSync14 } from "fs";
7260
7775
  import { execSync as execSync4 } from "child_process";
7261
7776
  function findFactoryDir(startDir) {
7262
7777
  let dir = startDir || process.cwd();
7263
7778
  const root = resolve5("/");
7264
7779
  while (dir !== root) {
7265
- if (existsSync17(join15(dir, ".beastmode", "factory.json"))) {
7780
+ if (existsSync18(join16(dir, ".beastmode", "factory.json"))) {
7266
7781
  return dir;
7267
7782
  }
7268
7783
  const parent = resolve5(dir, "..");
7269
7784
  if (parent === dir) break;
7270
7785
  dir = parent;
7271
7786
  }
7272
- if (existsSync17(join15(dir, ".beastmode", "factory.json"))) {
7787
+ if (existsSync18(join16(dir, ".beastmode", "factory.json"))) {
7273
7788
  return dir;
7274
7789
  }
7275
7790
  return null;
7276
7791
  }
7277
7792
  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$/, "");
7793
+ const projectsDir = join16(factoryDir, ".beastmode", "projects");
7794
+ const records = listProjectRecords(projectsDir);
7795
+ if (records.length === 1) return records[0].name;
7282
7796
  return null;
7283
7797
  }
7284
7798
  function tryExecSync(cmd, timeoutMs = 15e3) {
@@ -7355,13 +7869,13 @@ async function runBoard(opts) {
7355
7869
  let factoryDir = findFactoryDir();
7356
7870
  if (!factoryDir) {
7357
7871
  factoryDir = process.cwd();
7358
- const bmDir = join15(factoryDir, ".beastmode");
7359
- if (!existsSync17(bmDir)) {
7360
- mkdirSync12(bmDir, { recursive: true });
7872
+ const bmDir = join16(factoryDir, ".beastmode");
7873
+ if (!existsSync18(bmDir)) {
7874
+ mkdirSync13(bmDir, { recursive: true });
7361
7875
  }
7362
- const factoryJsonPath2 = join15(bmDir, "factory.json");
7363
- if (!existsSync17(factoryJsonPath2)) {
7364
- writeFileSync13(
7876
+ const factoryJsonPath2 = join16(bmDir, "factory.json");
7877
+ if (!existsSync18(factoryJsonPath2)) {
7878
+ writeFileSync14(
7365
7879
  factoryJsonPath2,
7366
7880
  JSON.stringify({ factory_name: "BeastMode", created_at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2),
7367
7881
  "utf-8"
@@ -7369,8 +7883,8 @@ async function runBoard(opts) {
7369
7883
  info("No factory found \u2014 created minimal stub at .beastmode/factory.json");
7370
7884
  }
7371
7885
  }
7372
- const factoryJsonPath = join15(factoryDir, ".beastmode", "factory.json");
7373
- const factoryJson = JSON.parse(readFileSync14(factoryJsonPath, "utf-8"));
7886
+ const factoryJsonPath = join16(factoryDir, ".beastmode", "factory.json");
7887
+ const factoryJson = JSON.parse(readFileSync15(factoryJsonPath, "utf-8"));
7374
7888
  const factoryName = factoryJson.factory_name || "BeastMode Factory";
7375
7889
  header(`BeastMode Board \u2014 ${factoryName}`);
7376
7890
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
@@ -7409,6 +7923,7 @@ var init_board = __esm({
7409
7923
  "src/cli/commands/board.ts"() {
7410
7924
  "use strict";
7411
7925
  init_display();
7926
+ init_engine();
7412
7927
  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
7928
  try {
7414
7929
  await runBoard(opts);
@@ -7442,26 +7957,26 @@ __export(sync_claude_creds_exports, {
7442
7957
  });
7443
7958
  import { Command as Command2 } from "commander";
7444
7959
  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";
7960
+ import { writeFileSync as writeFileSync15, readFileSync as readFileSync16, chmodSync, mkdirSync as mkdirSync14, existsSync as existsSync19, unlinkSync as unlinkSync4 } from "fs";
7961
+ import { join as join17 } from "path";
7447
7962
  import { homedir as homedir2, platform } from "os";
7448
7963
  function systemdUserDir() {
7449
- return join16(homedir2(), ".config", "systemd", "user");
7964
+ return join17(homedir2(), ".config", "systemd", "user");
7450
7965
  }
7451
7966
  function systemdServicePath() {
7452
- return join16(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.service`);
7967
+ return join17(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.service`);
7453
7968
  }
7454
7969
  function systemdTimerPath() {
7455
- return join16(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.timer`);
7970
+ return join17(systemdUserDir(), `${SYSTEMD_UNIT_NAME}.timer`);
7456
7971
  }
7457
7972
  function linuxCredsPath() {
7458
- return join16(homedir2(), ".claude", ".credentials.json");
7973
+ return join17(homedir2(), ".claude", ".credentials.json");
7459
7974
  }
7460
7975
  function plistPath() {
7461
- return join16(homedir2(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
7976
+ return join17(homedir2(), "Library", "LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
7462
7977
  }
7463
7978
  function agentLogPath() {
7464
- return join16(homedir2(), ".beastmode", "logs", "sync-claude-creds.log");
7979
+ return join17(homedir2(), ".beastmode", "logs", "sync-claude-creds.log");
7465
7980
  }
7466
7981
  function readKeychainTokenSafe() {
7467
7982
  try {
@@ -7493,10 +8008,10 @@ function writeCredentialsFile(rawJson) {
7493
8008
  if (!oauth?.accessToken) {
7494
8009
  throw new Error("Keychain entry missing claudeAiOauth.accessToken. Fix: run `claude login` again to reset the credential.");
7495
8010
  }
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");
8011
+ const claudeDir = join17(homedir2(), ".claude");
8012
+ if (!existsSync19(claudeDir)) mkdirSync14(claudeDir, { recursive: true });
8013
+ const credsPath = join17(claudeDir, ".credentials.json");
8014
+ writeFileSync15(credsPath, rawJson + "\n", "utf-8");
7500
8015
  chmodSync(credsPath, 384);
7501
8016
  if (oauth.expiresAt) {
7502
8017
  const hoursLeft = Math.round((oauth.expiresAt - Date.now()) / 36e5);
@@ -7509,7 +8024,7 @@ function writeCredentialsFile(rawJson) {
7509
8024
  return credsPath;
7510
8025
  }
7511
8026
  function urgencyMarkerHostPath(factoryDir) {
7512
- return join16(factoryDir, "daemon", "logs", ".cred-refresh-needed");
8027
+ return join17(factoryDir, "daemon", "logs", ".cred-refresh-needed");
7513
8028
  }
7514
8029
  function removeUrgencyMarker(factoryDir) {
7515
8030
  if (!factoryDir) return;
@@ -7523,7 +8038,7 @@ function buildPlist(intervalSeconds, factoryDir) {
7523
8038
  const nodePath = process.execPath;
7524
8039
  const cliEntry = process.argv[1];
7525
8040
  const logPath = agentLogPath();
7526
- const keychainPath = join16(homedir2(), "Library", "Keychains", "login.keychain-db");
8041
+ const keychainPath = join17(homedir2(), "Library", "Keychains", "login.keychain-db");
7527
8042
  const watchPaths = [keychainPath];
7528
8043
  if (factoryDir) {
7529
8044
  watchPaths.push(urgencyMarkerHostPath(factoryDir));
@@ -7563,15 +8078,15 @@ function installAgent(intervalSeconds, {
7563
8078
  } = {}) {
7564
8079
  const plist = plistPath();
7565
8080
  const logPath = agentLogPath();
7566
- mkdirSync13(join16(homedir2(), "Library", "LaunchAgents"), { recursive: true });
7567
- mkdirSync13(join16(homedir2(), ".beastmode", "logs"), { recursive: true });
8081
+ mkdirSync14(join17(homedir2(), "Library", "LaunchAgents"), { recursive: true });
8082
+ mkdirSync14(join17(homedir2(), ".beastmode", "logs"), { recursive: true });
7568
8083
  const uid = process.getuid?.();
7569
- if (existsSync18(plist)) {
8084
+ if (existsSync19(plist)) {
7570
8085
  spawnSync2("launchctl", ["bootout", `gui/${uid}`, plist], { stdio: "pipe" });
7571
8086
  }
7572
8087
  const resolvedFactory = factoryDir ?? findFactoryDir() ?? void 0;
7573
- writeFileSync14(plist, buildPlist(intervalSeconds, resolvedFactory), "utf-8");
7574
- writeFileSync14(logPath, "", { flag: "a" });
8088
+ writeFileSync15(plist, buildPlist(intervalSeconds, resolvedFactory), "utf-8");
8089
+ writeFileSync15(logPath, "", { flag: "a" });
7575
8090
  const result = spawnSync2("launchctl", ["bootstrap", `gui/${uid}`, plist], {
7576
8091
  stdio: "pipe",
7577
8092
  encoding: "utf-8"
@@ -7609,7 +8124,7 @@ function syncClaudeCredsOnce() {
7609
8124
  function uninstallAgent() {
7610
8125
  const plist = plistPath();
7611
8126
  const uid = process.getuid?.();
7612
- if (!existsSync18(plist)) {
8127
+ if (!existsSync19(plist)) {
7613
8128
  info("No LaunchAgent installed \u2014 nothing to remove.");
7614
8129
  return;
7615
8130
  }
@@ -7629,7 +8144,7 @@ function uninstallAgent() {
7629
8144
  function showStatus() {
7630
8145
  const plist = plistPath();
7631
8146
  const uid = process.getuid?.();
7632
- if (!existsSync18(plist)) {
8147
+ if (!existsSync19(plist)) {
7633
8148
  info("LaunchAgent not installed.");
7634
8149
  info("Install with: beastmode sync-claude-creds --install");
7635
8150
  return;
@@ -7653,14 +8168,14 @@ function showStatus() {
7653
8168
  }
7654
8169
  function syncClaudeCredsLinux() {
7655
8170
  const credsPath = linuxCredsPath();
7656
- if (!existsSync18(credsPath)) {
8171
+ if (!existsSync19(credsPath)) {
7657
8172
  return {
7658
8173
  error: `${credsPath} not found \u2014 run \`claude login\` first`
7659
8174
  };
7660
8175
  }
7661
8176
  let parsed;
7662
8177
  try {
7663
- const raw = readFileSync15(credsPath, "utf-8");
8178
+ const raw = readFileSync16(credsPath, "utf-8");
7664
8179
  parsed = JSON.parse(raw);
7665
8180
  } catch {
7666
8181
  return {
@@ -7689,7 +8204,7 @@ function syncClaudeCredsLinux() {
7689
8204
  );
7690
8205
  }
7691
8206
  try {
7692
- const raw2 = readFileSync15(credsPath, "utf-8");
8207
+ const raw2 = readFileSync16(credsPath, "utf-8");
7693
8208
  const parsed2 = JSON.parse(raw2);
7694
8209
  if (parsed2.claudeAiOauth?.expiresAt) {
7695
8210
  const newMinutes = (parsed2.claudeAiOauth.expiresAt - Date.now()) / 6e4;
@@ -7743,18 +8258,18 @@ function installAgentLinux(intervalSeconds, { throwOnError = false } = {}) {
7743
8258
  process.exit(1);
7744
8259
  }
7745
8260
  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");
8261
+ const logDir = join17(homedir2(), ".beastmode", "logs");
8262
+ mkdirSync14(unitDir, { recursive: true });
8263
+ mkdirSync14(logDir, { recursive: true });
8264
+ const logPath = join17(logDir, "sync-claude-creds.log");
7750
8265
  const nodePath = process.execPath;
7751
8266
  const cliEntry = process.argv[1];
7752
- writeFileSync14(
8267
+ writeFileSync15(
7753
8268
  systemdServicePath(),
7754
8269
  buildServiceUnit(nodePath, cliEntry, logPath),
7755
8270
  "utf-8"
7756
8271
  );
7757
- writeFileSync14(
8272
+ writeFileSync15(
7758
8273
  systemdTimerPath(),
7759
8274
  buildTimerUnit(intervalSeconds),
7760
8275
  "utf-8"
@@ -7805,14 +8320,14 @@ function uninstallAgentLinux() {
7805
8320
  const servicePath = systemdServicePath();
7806
8321
  const timerPath = systemdTimerPath();
7807
8322
  let removed = false;
7808
- if (existsSync18(servicePath)) {
8323
+ if (existsSync19(servicePath)) {
7809
8324
  try {
7810
8325
  unlinkSync4(servicePath);
7811
8326
  removed = true;
7812
8327
  } catch {
7813
8328
  }
7814
8329
  }
7815
- if (existsSync18(timerPath)) {
8330
+ if (existsSync19(timerPath)) {
7816
8331
  try {
7817
8332
  unlinkSync4(timerPath);
7818
8333
  removed = true;
@@ -7832,7 +8347,7 @@ function statusAgentLinux() {
7832
8347
  error(unavail);
7833
8348
  return;
7834
8349
  }
7835
- if (!existsSync18(systemdTimerPath())) {
8350
+ if (!existsSync19(systemdTimerPath())) {
7836
8351
  info("Systemd timer not installed.");
7837
8352
  info("Install with: beastmode sync-claude-creds --install");
7838
8353
  return;
@@ -7991,26 +8506,26 @@ var init_sync_claude_creds = __esm({
7991
8506
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7992
8507
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7993
8508
  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";
8509
+ import { readFileSync as readFileSync29, writeFileSync as writeFileSync25, existsSync as existsSync32, readdirSync as readdirSync12 } from "fs";
8510
+ import { join as join30, resolve as resolve18, basename as basename6 } from "path";
7996
8511
  import { randomUUID as randomUUID4 } from "crypto";
7997
8512
  function readJsonFile2(filePath) {
7998
- if (!existsSync31(filePath)) return null;
8513
+ if (!existsSync32(filePath)) return null;
7999
8514
  try {
8000
- return JSON.parse(readFileSync28(filePath, "utf-8"));
8515
+ return JSON.parse(readFileSync29(filePath, "utf-8"));
8001
8516
  } catch {
8002
8517
  return null;
8003
8518
  }
8004
8519
  }
8005
8520
  function getFactoryPath() {
8006
8521
  const envPath = process.env.BEASTMODE_FACTORY_PATH;
8007
- if (envPath && existsSync31(join29(envPath, ".beastmode", "factory.json"))) {
8522
+ if (envPath && existsSync32(join30(envPath, ".beastmode", "factory.json"))) {
8008
8523
  return envPath;
8009
8524
  }
8010
8525
  let dir = process.cwd();
8011
8526
  const root = resolve18("/");
8012
8527
  while (dir !== root) {
8013
- if (existsSync31(join29(dir, ".beastmode", "factory.json"))) {
8528
+ if (existsSync32(join30(dir, ".beastmode", "factory.json"))) {
8014
8529
  return dir;
8015
8530
  }
8016
8531
  const parent = resolve18(dir, "..");
@@ -8022,17 +8537,17 @@ function getFactoryPath() {
8022
8537
  );
8023
8538
  }
8024
8539
  function readFactoryStatus(factoryDir) {
8025
- const bmDir = join29(factoryDir, ".beastmode");
8540
+ const bmDir = join30(factoryDir, ".beastmode");
8026
8541
  const factoryIdentity = FactoryIdentitySchema.parse(
8027
- JSON.parse(readFileSync28(join29(bmDir, "factory.json"), "utf-8"))
8542
+ JSON.parse(readFileSync29(join30(bmDir, "factory.json"), "utf-8"))
8028
8543
  );
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");
8544
+ const projectsDir = join30(bmDir, "projects");
8545
+ const projectCount = listProjectRecords(projectsDir).length;
8546
+ const lockPath = join30(bmDir, "extensions.lock");
8032
8547
  let pluginNames = [];
8033
- if (existsSync31(lockPath)) {
8548
+ if (existsSync32(lockPath)) {
8034
8549
  try {
8035
- const lock = JSON.parse(readFileSync28(lockPath, "utf-8"));
8550
+ const lock = JSON.parse(readFileSync29(lockPath, "utf-8"));
8036
8551
  pluginNames = Object.keys(lock.plugins || {});
8037
8552
  } catch {
8038
8553
  }
@@ -8052,23 +8567,23 @@ function readFactoryStatus(factoryDir) {
8052
8567
  skillCount = listSkills(factoryDir).length;
8053
8568
  } catch {
8054
8569
  }
8055
- const runsDir = join29(factoryDir, "runs");
8570
+ const runsDir = join30(factoryDir, "runs");
8056
8571
  let runDirs = [];
8057
- if (existsSync31(runsDir)) {
8058
- runDirs = readdirSync11(runsDir).filter((d) => {
8572
+ if (existsSync32(runsDir)) {
8573
+ runDirs = readdirSync12(runsDir).filter((d) => {
8059
8574
  try {
8060
- return readdirSync11(join29(runsDir, d)).length > 0;
8575
+ return readdirSync12(join30(runsDir, d)).length > 0;
8061
8576
  } catch {
8062
8577
  return false;
8063
8578
  }
8064
8579
  }).sort();
8065
8580
  }
8066
- const pidFile = join29(bmDir, "daemon.pid");
8581
+ const pidFile = join30(bmDir, "daemon.pid");
8067
8582
  let daemonPid = null;
8068
8583
  let pidAlive = false;
8069
- if (existsSync31(pidFile)) {
8584
+ if (existsSync32(pidFile)) {
8070
8585
  try {
8071
- daemonPid = parseInt(readFileSync28(pidFile, "utf-8").trim(), 10);
8586
+ daemonPid = parseInt(readFileSync29(pidFile, "utf-8").trim(), 10);
8072
8587
  process.kill(daemonPid, 0);
8073
8588
  pidAlive = true;
8074
8589
  } catch {
@@ -8089,18 +8604,18 @@ function readFactoryStatus(factoryDir) {
8089
8604
  return collectStatus(input);
8090
8605
  }
8091
8606
  function readBoardItems(factoryDir) {
8092
- const filePath = join29(factoryDir, ".beastmode", "board.json");
8093
- if (!existsSync31(filePath)) return [];
8607
+ const filePath = join30(factoryDir, ".beastmode", "board.json");
8608
+ if (!existsSync32(filePath)) return [];
8094
8609
  try {
8095
- const raw = JSON.parse(readFileSync28(filePath, "utf-8"));
8610
+ const raw = JSON.parse(readFileSync29(filePath, "utf-8"));
8096
8611
  return Array.isArray(raw.items) ? raw.items : [];
8097
8612
  } catch {
8098
8613
  return [];
8099
8614
  }
8100
8615
  }
8101
8616
  function writeBoardItems(factoryDir, items) {
8102
- const filePath = join29(factoryDir, ".beastmode", "board.json");
8103
- writeFileSync24(filePath, JSON.stringify({ items }, null, 2) + "\n", "utf-8");
8617
+ const filePath = join30(factoryDir, ".beastmode", "board.json");
8618
+ writeFileSync25(filePath, JSON.stringify({ items }, null, 2) + "\n", "utf-8");
8104
8619
  }
8105
8620
  function createMcpServer() {
8106
8621
  const server = new McpServer(
@@ -8123,8 +8638,8 @@ function createMcpServer() {
8123
8638
  { key_path: z2.string().describe("Dot-notation key path") },
8124
8639
  async ({ key_path }) => {
8125
8640
  const factoryDir = getFactoryPath();
8126
- const configPath = join29(factoryDir, ".beastmode", "config.json");
8127
- const config = existsSync31(configPath) ? JSON.parse(readFileSync28(configPath, "utf-8")) : generateDefaults();
8641
+ const configPath = join30(factoryDir, ".beastmode", "config.json");
8642
+ const config = existsSync32(configPath) ? JSON.parse(readFileSync29(configPath, "utf-8")) : generateDefaults();
8128
8643
  try {
8129
8644
  const value = configGet(config, key_path);
8130
8645
  return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
@@ -8142,11 +8657,11 @@ function createMcpServer() {
8142
8657
  },
8143
8658
  async ({ key_path, value }) => {
8144
8659
  const factoryDir = getFactoryPath();
8145
- const configPath = join29(factoryDir, ".beastmode", "config.json");
8146
- const config = existsSync31(configPath) ? JSON.parse(readFileSync28(configPath, "utf-8")) : generateDefaults();
8660
+ const configPath = join30(factoryDir, ".beastmode", "config.json");
8661
+ const config = existsSync32(configPath) ? JSON.parse(readFileSync29(configPath, "utf-8")) : generateDefaults();
8147
8662
  const coerced = coerceValue(value);
8148
8663
  const updated = configSet(config, key_path, coerced);
8149
- writeFileSync24(configPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
8664
+ writeFileSync25(configPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
8150
8665
  return { content: [{ type: "text", text: `Set ${key_path} = ${JSON.stringify(coerced)}` }] };
8151
8666
  }
8152
8667
  );
@@ -8180,14 +8695,14 @@ function createMcpServer() {
8180
8695
  {},
8181
8696
  async () => {
8182
8697
  const factoryDir = getFactoryPath();
8183
- const runsDir = join29(factoryDir, "runs");
8184
- if (!existsSync31(runsDir)) {
8698
+ const runsDir = join30(factoryDir, "runs");
8699
+ if (!existsSync32(runsDir)) {
8185
8700
  return { content: [{ type: "text", text: "No runs directory found." }] };
8186
8701
  }
8187
- const runDirs = readdirSync11(runsDir).sort().reverse();
8702
+ const runDirs = readdirSync12(runsDir).sort().reverse();
8188
8703
  const activeRuns = [];
8189
8704
  for (const id of runDirs.slice(0, 10)) {
8190
- const cp = readJsonFile2(join29(runsDir, id, "checkpoint.json"));
8705
+ const cp = readJsonFile2(join30(runsDir, id, "checkpoint.json"));
8191
8706
  if (cp) {
8192
8707
  activeRuns.push({ id, checkpoint: cp });
8193
8708
  }
@@ -8201,17 +8716,17 @@ function createMcpServer() {
8201
8716
  { run_id: z2.string().describe("Run ID (directory name)") },
8202
8717
  async ({ run_id }) => {
8203
8718
  const factoryDir = getFactoryPath();
8204
- const runDir = join29(factoryDir, "runs", run_id);
8205
- if (!existsSync31(runDir)) {
8719
+ const runDir = join30(factoryDir, "runs", run_id);
8720
+ if (!existsSync32(runDir)) {
8206
8721
  return { content: [{ type: "text", text: `Run not found: ${run_id}` }], isError: true };
8207
8722
  }
8208
- const manifest = readJsonFile2(join29(runDir, "manifest.json"));
8209
- const checkpoint = readJsonFile2(join29(runDir, "checkpoint.json"));
8210
- const iterationsDir = join29(runDir, "iterations");
8723
+ const manifest = readJsonFile2(join30(runDir, "manifest.json"));
8724
+ const checkpoint = readJsonFile2(join30(runDir, "checkpoint.json"));
8725
+ const iterationsDir = join30(runDir, "iterations");
8211
8726
  const iterations = [];
8212
- if (existsSync31(iterationsDir)) {
8213
- for (const dir of readdirSync11(iterationsDir).sort()) {
8214
- const satisfaction = readJsonFile2(join29(iterationsDir, dir, "satisfaction.json"));
8727
+ if (existsSync32(iterationsDir)) {
8728
+ for (const dir of readdirSync12(iterationsDir).sort()) {
8729
+ const satisfaction = readJsonFile2(join30(iterationsDir, dir, "satisfaction.json"));
8215
8730
  iterations.push({ number: parseInt(dir, 10), satisfaction });
8216
8731
  }
8217
8732
  }
@@ -8257,12 +8772,12 @@ function createMcpServer() {
8257
8772
  {},
8258
8773
  async () => {
8259
8774
  const factoryDir = getFactoryPath();
8260
- const bmDir = join29(factoryDir, ".beastmode");
8775
+ const bmDir = join30(factoryDir, ".beastmode");
8261
8776
  let plugins = {};
8262
- const lockPath = join29(bmDir, "extensions.lock");
8263
- if (existsSync31(lockPath)) {
8777
+ const lockPath = join30(bmDir, "extensions.lock");
8778
+ if (existsSync32(lockPath)) {
8264
8779
  try {
8265
- const lock = JSON.parse(readFileSync28(lockPath, "utf-8"));
8780
+ const lock = JSON.parse(readFileSync29(lockPath, "utf-8"));
8266
8781
  plugins = lock.plugins || {};
8267
8782
  } catch {
8268
8783
  }
@@ -8296,17 +8811,8 @@ function createMcpServer() {
8296
8811
  {},
8297
8812
  async () => {
8298
8813
  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);
8814
+ const projectsDir = join30(factoryDir, ".beastmode", "projects");
8815
+ const projects = listProjectRecords(projectsDir);
8310
8816
  return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }] };
8311
8817
  }
8312
8818
  );
@@ -8317,34 +8823,25 @@ function createMcpServer() {
8317
8823
  async ({ path: projectPath }) => {
8318
8824
  const factoryDir = getFactoryPath();
8319
8825
  const resolvedPath = resolve18(projectPath);
8320
- if (!existsSync31(resolvedPath)) {
8826
+ if (!existsSync32(resolvedPath)) {
8321
8827
  return { content: [{ type: "text", text: `Directory not found: ${resolvedPath}` }], isError: true };
8322
8828
  }
8323
- const projectName = basename5(resolvedPath);
8829
+ const projectName = basename6(resolvedPath);
8324
8830
  const stack = detectStack(resolvedPath);
8325
- const projectConfig = {
8831
+ const projectsDir = join30(factoryDir, ".beastmode", "projects");
8832
+ let verifyPort = 3001;
8833
+ for (const existing of listProjectRecords(projectsDir)) {
8834
+ const port = existing.deploy?.verify_port;
8835
+ if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
8836
+ }
8837
+ const record = createProjectRecord({
8326
8838
  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) }] };
8839
+ resolvedPath,
8840
+ gitRemote: stack.git_remote || void 0,
8841
+ verifyPort
8842
+ });
8843
+ writeProjectRecord(projectsDir, projectName, record);
8844
+ return { content: [{ type: "text", text: JSON.stringify(record, null, 2) }] };
8348
8845
  }
8349
8846
  );
8350
8847
  server.tool(
@@ -8447,17 +8944,17 @@ init_engine();
8447
8944
  init_file_writer();
8448
8945
  import { Command as Command3 } from "commander";
8449
8946
  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";
8947
+ import { resolve as resolve6, basename as basename5, join as join18 } from "path";
8948
+ import { existsSync as existsSync20, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync17 } from "fs";
8452
8949
 
8453
8950
  // src/cli/utils/docker.ts
8454
- import { existsSync as existsSync9 } from "fs";
8455
- import { join as join8 } from "path";
8951
+ import { existsSync as existsSync10 } from "fs";
8952
+ import { join as join9 } from "path";
8456
8953
  import { execSync } from "child_process";
8457
8954
  var GHCR_IMAGE_PREFIX = "ghcr.io/develeap/beastmode";
8458
8955
  function findComposeFile(dir) {
8459
- const path = join8(dir, "docker-compose.yml");
8460
- return existsSync9(path) ? path : null;
8956
+ const path = join9(dir, "docker-compose.yml");
8957
+ return existsSync10(path) ? path : null;
8461
8958
  }
8462
8959
  function requireComposeFile(dir) {
8463
8960
  const path = findComposeFile(dir);
@@ -8520,7 +9017,7 @@ function loginToGhcr(token) {
8520
9017
  }
8521
9018
  }
8522
9019
  function seedConfigFromImage(targetDir, tag) {
8523
- const configDir = join8(targetDir, "config");
9020
+ const configDir = join9(targetDir, "config");
8524
9021
  const containerName = "bm-config-seed";
8525
9022
  try {
8526
9023
  try {
@@ -8700,15 +9197,15 @@ function collect(val, acc) {
8700
9197
  return acc;
8701
9198
  }
8702
9199
  function readSecretFile(filePath) {
8703
- return readFileSync16(resolve6(filePath), "utf-8").trim();
9200
+ return readFileSync17(resolve6(filePath), "utf-8").trim();
8704
9201
  }
8705
9202
  function isSourceRepo(dir) {
8706
- return existsSync19(resolve6(dir, "daemon")) && existsSync19(resolve6(dir, "board")) && existsSync19(resolve6(dir, "cli"));
9203
+ return existsSync20(resolve6(dir, "daemon")) && existsSync20(resolve6(dir, "board")) && existsSync20(resolve6(dir, "cli"));
8707
9204
  }
8708
9205
  async function runInit(name, opts) {
8709
9206
  if (opts.ui) {
8710
9207
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
8711
- const factoryName2 = name || basename4(resolve6("."));
9208
+ const factoryName2 = name || basename5(resolve6("."));
8712
9209
  const projectPath2 = opts.project ? resolve6(opts.project) : void 0;
8713
9210
  info("Starting init wizard...");
8714
9211
  const uiServer = await startServer2({
@@ -8741,13 +9238,13 @@ async function runInit(name, opts) {
8741
9238
  const totalSteps = 5;
8742
9239
  header("BeastMode \u2014 Dark Factory Init");
8743
9240
  info("Creating your Custom Dark Factory\n");
8744
- const factoryName = name || basename4(resolve6("."));
8745
- if (existsSync19(factoryName) && existsSync19(resolve6(factoryName, ".beastmode"))) {
9241
+ const factoryName = name || basename5(resolve6("."));
9242
+ if (existsSync20(factoryName) && existsSync20(resolve6(factoryName, ".beastmode"))) {
8746
9243
  throw new Error(`Factory already exists at ./${factoryName}. Use 'beastmode config' to modify.`);
8747
9244
  }
8748
9245
  if (opts.from) {
8749
- const { readFileSync: readFileSync36 } = await import("fs");
8750
- const templateContent = readFileSync36(resolve6(opts.from), "utf-8");
9246
+ const { readFileSync: readFileSync37 } = await import("fs");
9247
+ const templateContent = readFileSync37(resolve6(opts.from), "utf-8");
8751
9248
  const { parseTemplate: parseTemplate2 } = await Promise.resolve().then(() => (init_template_importer(), template_importer_exports));
8752
9249
  const template = parseTemplate2(templateContent);
8753
9250
  info(`Importing from template: ${opts.from}`);
@@ -8757,7 +9254,7 @@ async function runInit(name, opts) {
8757
9254
  }
8758
9255
  const templateProjectPath = resolve6(opts.project);
8759
9256
  const templateStack = detectStack(templateProjectPath);
8760
- const templateProjectName = basename4(templateProjectPath);
9257
+ const templateProjectName = basename5(templateProjectPath);
8761
9258
  const actions2 = scaffoldFactory(factoryName, template.config, {
8762
9259
  name: templateProjectName,
8763
9260
  repo: templateStack.git_remote || void 0,
@@ -8792,7 +9289,7 @@ async function runInit(name, opts) {
8792
9289
  name: "project",
8793
9290
  message: "Path to your project:",
8794
9291
  default: ".",
8795
- validate: (input) => existsSync19(resolve6(input)) || `Directory not found: ${input}`
9292
+ validate: (input) => existsSync20(resolve6(input)) || `Directory not found: ${input}`
8796
9293
  }
8797
9294
  ]);
8798
9295
  projectPath = resolve6(answer.project);
@@ -8952,7 +9449,7 @@ async function runInit(name, opts) {
8952
9449
  success("Board UI password set");
8953
9450
  }
8954
9451
  step(5, totalSteps, "Boot");
8955
- const projectName = basename4(projectPath);
9452
+ const projectName = basename5(projectPath);
8956
9453
  const actions = scaffoldFactory(factoryName, config, {
8957
9454
  name: projectName,
8958
9455
  repo: stack.git_remote || void 0,
@@ -9098,14 +9595,14 @@ async function runImageModeInit(name, opts) {
9098
9595
  let projectPath;
9099
9596
  if (opts.project) {
9100
9597
  projectPath = resolve6(opts.project);
9101
- if (!existsSync19(projectPath)) {
9598
+ if (!existsSync20(projectPath)) {
9102
9599
  error(`--project path does not exist: ${projectPath}`);
9103
9600
  process.exit(1);
9104
9601
  }
9105
9602
  success(`Project path: ${projectPath}`);
9106
9603
  } else if (opts.yes) {
9107
9604
  const cwd = resolve6(".");
9108
- if (!existsSync19(join17(cwd, ".git"))) {
9605
+ if (!existsSync20(join18(cwd, ".git"))) {
9109
9606
  error(
9110
9607
  "--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
9608
  );
@@ -9122,8 +9619,8 @@ async function runImageModeInit(name, opts) {
9122
9619
  default: ".",
9123
9620
  validate: (input) => {
9124
9621
  const p = resolve6(input);
9125
- if (!existsSync19(p)) return `Directory not found: ${p}`;
9126
- if (!existsSync19(join17(p, ".git"))) {
9622
+ if (!existsSync20(p)) return `Directory not found: ${p}`;
9623
+ if (!existsSync20(join18(p, ".git"))) {
9127
9624
  return `Not a git repo: ${p} (run 'git init' first, or pick a different path)`;
9128
9625
  }
9129
9626
  return true;
@@ -9162,9 +9659,9 @@ async function runImageModeInit(name, opts) {
9162
9659
  success("Authenticated to ghcr.io");
9163
9660
  }
9164
9661
  step(3, totalSteps, "Generate");
9165
- mkdirSync14(targetDir, { recursive: true });
9662
+ mkdirSync15(targetDir, { recursive: true });
9166
9663
  const composeContent = generateComposeYaml("latest");
9167
- writeFileSync15(join17(targetDir, "docker-compose.yml"), composeContent, "utf-8");
9664
+ writeFileSync16(join18(targetDir, "docker-compose.yml"), composeContent, "utf-8");
9168
9665
  success("docker-compose.yml");
9169
9666
  const envLines = [
9170
9667
  "# BeastMode environment \u2014 DO NOT COMMIT",
@@ -9204,10 +9701,10 @@ async function runImageModeInit(name, opts) {
9204
9701
  "# PROJECT_REPO=owner/repo",
9205
9702
  ""
9206
9703
  ];
9207
- writeFileSync15(join17(targetDir, ".env"), envLines.join("\n"), "utf-8");
9704
+ writeFileSync16(join18(targetDir, ".env"), envLines.join("\n"), "utf-8");
9208
9705
  success(`.env (PROJECT_DIR=${projectPath}, two-PAT model)`);
9209
9706
  for (const dir of ["data", "runs", "daemon/logs", ".beastmode", "config"]) {
9210
- mkdirSync14(join17(targetDir, dir), { recursive: true });
9707
+ mkdirSync15(join18(targetDir, dir), { recursive: true });
9211
9708
  }
9212
9709
  step(4, totalSteps, "Pull Images");
9213
9710
  try {
@@ -9296,20 +9793,20 @@ async function runImageModeInit(name, opts) {
9296
9793
  init_export_adapter();
9297
9794
  init_display();
9298
9795
  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";
9796
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync17, existsSync as existsSync21, readdirSync as readdirSync9 } from "fs";
9797
+ import { resolve as resolve7, join as join19 } from "path";
9301
9798
  function exportFactory(factoryDir, outputPath) {
9302
- const bmDir = join18(factoryDir, ".beastmode");
9303
- const configPath = join18(bmDir, "config.json");
9304
- if (!existsSync20(configPath)) {
9799
+ const bmDir = join19(factoryDir, ".beastmode");
9800
+ const configPath = join19(bmDir, "config.json");
9801
+ if (!existsSync21(configPath)) {
9305
9802
  throw new Error(`No factory found at ${factoryDir}`);
9306
9803
  }
9307
- const config = JSON.parse(readFileSync17(configPath, "utf-8"));
9308
- const identity = JSON.parse(readFileSync17(join18(bmDir, "factory.json"), "utf-8"));
9804
+ const config = JSON.parse(readFileSync18(configPath, "utf-8"));
9805
+ const identity = JSON.parse(readFileSync18(join19(bmDir, "factory.json"), "utf-8"));
9309
9806
  let plugins = [];
9310
- const lockPath = join18(bmDir, "extensions.lock");
9311
- if (existsSync20(lockPath)) {
9312
- const lock = JSON.parse(readFileSync17(lockPath, "utf-8"));
9807
+ const lockPath = join19(bmDir, "extensions.lock");
9808
+ if (existsSync21(lockPath)) {
9809
+ const lock = JSON.parse(readFileSync18(lockPath, "utf-8"));
9313
9810
  plugins = Object.keys(lock.plugins || {});
9314
9811
  }
9315
9812
  const template = {
@@ -9318,7 +9815,7 @@ function exportFactory(factoryDir, outputPath) {
9318
9815
  config,
9319
9816
  plugins
9320
9817
  };
9321
- writeFileSync16(outputPath, JSON.stringify(template, null, 2) + "\n", "utf-8");
9818
+ writeFileSync17(outputPath, JSON.stringify(template, null, 2) + "\n", "utf-8");
9322
9819
  }
9323
9820
  function findRunDir(runId) {
9324
9821
  const candidates = [
@@ -9326,7 +9823,7 @@ function findRunDir(runId) {
9326
9823
  resolve7(".", runId)
9327
9824
  ];
9328
9825
  for (const candidate of candidates) {
9329
- if (existsSync20(candidate)) {
9826
+ if (existsSync21(candidate)) {
9330
9827
  return candidate;
9331
9828
  }
9332
9829
  }
@@ -9340,23 +9837,23 @@ function createArtifactExportCommand(artifact) {
9340
9837
  const runDir = findRunDir(opts.run);
9341
9838
  let sourceContent;
9342
9839
  if (artifact === "scenarios") {
9343
- const scenariosDir = join18(runDir, "scenarios");
9344
- if (!existsSync20(scenariosDir)) {
9840
+ const scenariosDir = join19(runDir, "scenarios");
9841
+ if (!existsSync21(scenariosDir)) {
9345
9842
  throw new Error(`No scenarios directory found in run ${opts.run}`);
9346
9843
  }
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");
9844
+ const files = readdirSync9(scenariosDir).filter((f) => f.endsWith(".md")).sort();
9845
+ sourceContent = files.map((f) => readFileSync18(join19(scenariosDir, f), "utf-8")).join("\n\n---\n\n");
9349
9846
  } else {
9350
9847
  const artifactFile = artifact === "nlspec" ? "nlspec.md" : "plan.md";
9351
- const artifactPath = join18(runDir, artifactFile);
9352
- if (!existsSync20(artifactPath)) {
9848
+ const artifactPath = join19(runDir, artifactFile);
9849
+ if (!existsSync21(artifactPath)) {
9353
9850
  throw new Error(`${artifactFile} not found in run ${opts.run}`);
9354
9851
  }
9355
- sourceContent = readFileSync17(artifactPath, "utf-8");
9852
+ sourceContent = readFileSync18(artifactPath, "utf-8");
9356
9853
  }
9357
9854
  const result = runExportAdapter(opts.adapter, sourceContent);
9358
9855
  if (opts.output) {
9359
- writeFileSync16(resolve7(opts.output), result, "utf-8");
9856
+ writeFileSync17(resolve7(opts.output), result, "utf-8");
9360
9857
  header(`Exported ${artifact}`);
9361
9858
  success(`Written to: ${opts.output}`);
9362
9859
  } else {
@@ -9580,15 +10077,15 @@ init_presets();
9580
10077
  init_display();
9581
10078
  import { Command as Command8 } from "commander";
9582
10079
  import { resolve as resolve11 } from "path";
9583
- import { readFileSync as readFileSync18, existsSync as existsSync21 } from "fs";
9584
- import { join as join19 } from "path";
10080
+ import { readFileSync as readFileSync19, existsSync as existsSync22 } from "fs";
10081
+ import { join as join20 } from "path";
9585
10082
  function listPluginsAction(factoryDir) {
9586
- const lockPath = join19(factoryDir, ".beastmode", "extensions.lock");
9587
- if (!existsSync21(lockPath)) {
10083
+ const lockPath = join20(factoryDir, ".beastmode", "extensions.lock");
10084
+ if (!existsSync22(lockPath)) {
9588
10085
  console.log(" No plugins installed (extensions.lock not found).");
9589
10086
  return;
9590
10087
  }
9591
- const lock = JSON.parse(readFileSync18(lockPath, "utf-8"));
10088
+ const lock = JSON.parse(readFileSync19(lockPath, "utf-8"));
9592
10089
  const plugins = lock.plugins || {};
9593
10090
  const names = Object.keys(plugins);
9594
10091
  if (names.length === 0) {
@@ -9687,31 +10184,31 @@ var listCommand = new Command8("list").description("List installed extensions an
9687
10184
  init_import_adapter();
9688
10185
  init_display();
9689
10186
  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";
10187
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync18, mkdirSync as mkdirSync16, existsSync as existsSync23 } from "fs";
10188
+ import { resolve as resolve12, join as join21 } from "path";
9692
10189
  var VALID_ARTIFACTS = ["nlspec", "plan", "scenarios"];
9693
10190
  function createArtifactCommand(artifact) {
9694
10191
  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
10192
  try {
9696
10193
  const sourcePath = resolve12(opts.from);
9697
- if (!existsSync22(sourcePath)) {
10194
+ if (!existsSync23(sourcePath)) {
9698
10195
  throw new Error(`Source file not found: ${sourcePath}`);
9699
10196
  }
9700
- const sourceContent = readFileSync19(sourcePath, "utf-8");
10197
+ const sourceContent = readFileSync20(sourcePath, "utf-8");
9701
10198
  const result = runImportAdapter(opts.adapter, sourceContent);
9702
10199
  if (opts.output) {
9703
10200
  const outputPath = resolve12(opts.output);
9704
10201
  if (artifact === "scenarios" && opts.adapter.endsWith(":test-cases")) {
9705
10202
  const scenarios = JSON.parse(result);
9706
- mkdirSync15(outputPath, { recursive: true });
10203
+ mkdirSync16(outputPath, { recursive: true });
9707
10204
  for (const scenario of scenarios) {
9708
- const filePath = join20(outputPath, `${scenario.name}.md`);
9709
- writeFileSync17(filePath, scenario.content, "utf-8");
10205
+ const filePath = join21(outputPath, `${scenario.name}.md`);
10206
+ writeFileSync18(filePath, scenario.content, "utf-8");
9710
10207
  success(` ${scenario.name}.md`);
9711
10208
  }
9712
10209
  header(`Imported ${scenarios.length} scenarios to ${opts.output}`);
9713
10210
  } else {
9714
- writeFileSync17(outputPath, result, "utf-8");
10211
+ writeFileSync18(outputPath, result, "utf-8");
9715
10212
  header(`Imported ${artifact}`);
9716
10213
  success(`Written to: ${opts.output}`);
9717
10214
  }
@@ -9731,55 +10228,56 @@ for (const artifact of VALID_ARTIFACTS) {
9731
10228
 
9732
10229
  // src/cli/commands/status.ts
9733
10230
  init_status_checker();
10231
+ init_engine();
9734
10232
  init_schemas();
9735
10233
  init_display();
9736
10234
  import { Command as Command10 } from "commander";
9737
- import { existsSync as existsSync23, readFileSync as readFileSync20, readdirSync as readdirSync9 } from "fs";
10235
+ import { existsSync as existsSync24, readFileSync as readFileSync21, readdirSync as readdirSync10 } from "fs";
9738
10236
  import { execSync as execSync6 } from "child_process";
9739
- import { join as join21, resolve as resolve13 } from "path";
10237
+ import { join as join22, resolve as resolve13 } from "path";
9740
10238
  function statusAction(factoryDir, opts) {
9741
- const bmDir = join21(factoryDir, ".beastmode");
9742
- if (!existsSync23(bmDir)) {
10239
+ const bmDir = join22(factoryDir, ".beastmode");
10240
+ if (!existsSync24(bmDir)) {
9743
10241
  throw new Error(`No factory found at ${factoryDir}`);
9744
10242
  }
9745
- const factoryJsonPath = join21(bmDir, "factory.json");
9746
- const rawIdentity = JSON.parse(readFileSync20(factoryJsonPath, "utf-8"));
10243
+ const factoryJsonPath = join22(bmDir, "factory.json");
10244
+ const rawIdentity = JSON.parse(readFileSync21(factoryJsonPath, "utf-8"));
9747
10245
  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");
10246
+ const projectsDir = join22(bmDir, "projects");
10247
+ const projectCount = listProjectRecords(projectsDir).length;
10248
+ const pluginsDir = join22(bmDir, "plugins");
10249
+ const pluginNames = existsSync24(pluginsDir) ? readdirSync10(pluginsDir) : [];
10250
+ const mcpPath = join22(bmDir, "mcp-servers.json");
9753
10251
  let mcpServers = {};
9754
- if (existsSync23(mcpPath)) {
10252
+ if (existsSync24(mcpPath)) {
9755
10253
  try {
9756
- const raw = JSON.parse(readFileSync20(mcpPath, "utf-8"));
10254
+ const raw = JSON.parse(readFileSync21(mcpPath, "utf-8"));
9757
10255
  mcpServers = raw.servers || {};
9758
10256
  } catch {
9759
10257
  }
9760
10258
  }
9761
- const hooksPath = join21(bmDir, "hooks.json");
10259
+ const hooksPath = join22(bmDir, "hooks.json");
9762
10260
  let hooks = {};
9763
- if (existsSync23(hooksPath)) {
10261
+ if (existsSync24(hooksPath)) {
9764
10262
  try {
9765
- const raw = JSON.parse(readFileSync20(hooksPath, "utf-8"));
10263
+ const raw = JSON.parse(readFileSync21(hooksPath, "utf-8"));
9766
10264
  hooks = raw.hooks || {};
9767
10265
  } catch {
9768
10266
  }
9769
10267
  }
9770
- const skillsDir = join21(bmDir, "skills");
9771
- const skillCount = existsSync23(skillsDir) ? readdirSync9(skillsDir).length : 0;
9772
- const runsDir = join21(factoryDir, "runs");
10268
+ const skillsDir = join22(bmDir, "skills");
10269
+ const skillCount = existsSync24(skillsDir) ? readdirSync10(skillsDir).length : 0;
10270
+ const runsDir = join22(factoryDir, "runs");
9773
10271
  let runDirs = [];
9774
- if (existsSync23(runsDir)) {
9775
- runDirs = readdirSync9(runsDir).filter((d) => d.startsWith("run-")).sort();
10272
+ if (existsSync24(runsDir)) {
10273
+ runDirs = readdirSync10(runsDir).filter((d) => d.startsWith("run-")).sort();
9776
10274
  }
9777
- const pidPath = join21(bmDir, "daemon.pid");
10275
+ const pidPath = join22(bmDir, "daemon.pid");
9778
10276
  let daemonPid = null;
9779
10277
  let pidAlive = false;
9780
- if (existsSync23(pidPath)) {
10278
+ if (existsSync24(pidPath)) {
9781
10279
  try {
9782
- daemonPid = parseInt(readFileSync20(pidPath, "utf-8").trim(), 10);
10280
+ daemonPid = parseInt(readFileSync21(pidPath, "utf-8").trim(), 10);
9783
10281
  process.kill(daemonPid, 0);
9784
10282
  pidAlive = true;
9785
10283
  } catch {
@@ -9853,19 +10351,19 @@ var statusCommand = new Command10("status").description("Show factory status ove
9853
10351
  init_config_manager();
9854
10352
  init_display();
9855
10353
  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";
10354
+ import { existsSync as existsSync25, readFileSync as readFileSync22, writeFileSync as writeFileSync19 } from "fs";
10355
+ import { join as join23, resolve as resolve14 } from "path";
9858
10356
  import { execSync as execSync7 } from "child_process";
9859
10357
  function readConfig2(factoryDir) {
9860
- const configPath = join22(factoryDir, ".beastmode", "config.json");
9861
- if (!existsSync24(configPath)) {
10358
+ const configPath = join23(factoryDir, ".beastmode", "config.json");
10359
+ if (!existsSync25(configPath)) {
9862
10360
  throw new Error("No config.json found. Run beastmode init first.");
9863
10361
  }
9864
- return JSON.parse(readFileSync21(configPath, "utf-8"));
10362
+ return JSON.parse(readFileSync22(configPath, "utf-8"));
9865
10363
  }
9866
10364
  function writeConfig2(factoryDir, config) {
9867
- const configPath = join22(factoryDir, ".beastmode", "config.json");
9868
- writeFileSync18(configPath, JSON.stringify(config, null, 2) + "\n");
10365
+ const configPath = join23(factoryDir, ".beastmode", "config.json");
10366
+ writeFileSync19(configPath, JSON.stringify(config, null, 2) + "\n");
9869
10367
  }
9870
10368
  function configGetAction(factoryDir, key) {
9871
10369
  const config = readConfig2(factoryDir);
@@ -9908,8 +10406,8 @@ var configSetCmd = new Command11("set").description("Set a config value by dot-n
9908
10406
  });
9909
10407
  var configEditCmd = new Command11("edit").description("Open config.json in $EDITOR").action(() => {
9910
10408
  const factoryDir = resolve14(".");
9911
- const configPath = join22(factoryDir, ".beastmode", "config.json");
9912
- if (!existsSync24(configPath)) {
10409
+ const configPath = join23(factoryDir, ".beastmode", "config.json");
10410
+ if (!existsSync25(configPath)) {
9913
10411
  error("No config.json found. Run beastmode init first.");
9914
10412
  process.exit(1);
9915
10413
  }
@@ -9939,13 +10437,14 @@ var configCommand = new Command11("config").description("Manage factory configur
9939
10437
 
9940
10438
  // src/cli/commands/doctor.ts
9941
10439
  init_doctor();
10440
+ init_engine();
9942
10441
  init_schemas();
9943
10442
  init_version();
9944
10443
  init_plugin_resolver();
9945
10444
  init_display();
9946
10445
  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";
10446
+ import { existsSync as existsSync26, readFileSync as readFileSync23, readdirSync as readdirSync11 } from "fs";
10447
+ import { join as join24, resolve as resolve15 } from "path";
9949
10448
  import { execSync as execSync8, spawnSync as spawnSync3 } from "child_process";
9950
10449
  import { homedir as homedir3, platform as platform2 } from "os";
9951
10450
  import * as http3 from "http";
@@ -9986,8 +10485,8 @@ async function checkClaudeAuth(key) {
9986
10485
  });
9987
10486
  }
9988
10487
  if (claudeInstalled && platform2() === "darwin" && !key) {
9989
- const credsPath = join23(homedir3(), ".claude", ".credentials.json");
9990
- if (!existsSync25(credsPath)) {
10488
+ const credsPath = join24(homedir3(), ".claude", ".credentials.json");
10489
+ if (!existsSync26(credsPath)) {
9991
10490
  results.push({
9992
10491
  label: "Claude creds (Docker)",
9993
10492
  status: "warn",
@@ -10089,16 +10588,16 @@ async function checkProjectGithubToken(env, factoryDir) {
10089
10588
  }
10090
10589
  let ownerRepo = null;
10091
10590
  if (factoryDir) {
10092
- const envPath = join23(factoryDir, ".env");
10093
- if (existsSync25(envPath)) {
10591
+ const envPath = join24(factoryDir, ".env");
10592
+ if (existsSync26(envPath)) {
10094
10593
  try {
10095
- const content = readFileSync22(envPath, "utf-8");
10594
+ const content = readFileSync23(envPath, "utf-8");
10096
10595
  const dirLine = content.split("\n").find(
10097
10596
  (l) => l.trim().startsWith("PROJECT_DIR=") && !l.trim().startsWith("#")
10098
10597
  );
10099
10598
  if (dirLine) {
10100
10599
  const projectDir = dirLine.split("=").slice(1).join("=").trim();
10101
- if (projectDir && existsSync25(join23(projectDir, ".git"))) {
10600
+ if (projectDir && existsSync26(join24(projectDir, ".git"))) {
10102
10601
  const remote = tryExec(
10103
10602
  `git -C "${projectDir}" remote get-url origin 2>/dev/null`
10104
10603
  );
@@ -10314,8 +10813,8 @@ function checkProjectDirEnv(factoryDir) {
10314
10813
  detail: "no factory in scope"
10315
10814
  };
10316
10815
  }
10317
- const envPath = join23(factoryDir, ".env");
10318
- if (!existsSync25(envPath)) {
10816
+ const envPath = join24(factoryDir, ".env");
10817
+ if (!existsSync26(envPath)) {
10319
10818
  return {
10320
10819
  label: "PROJECT_DIR (.env)",
10321
10820
  status: "fail",
@@ -10325,7 +10824,7 @@ function checkProjectDirEnv(factoryDir) {
10325
10824
  }
10326
10825
  let content;
10327
10826
  try {
10328
- content = readFileSync22(envPath, "utf-8");
10827
+ content = readFileSync23(envPath, "utf-8");
10329
10828
  } catch {
10330
10829
  return {
10331
10830
  label: "PROJECT_DIR (.env)",
@@ -10354,7 +10853,7 @@ function checkProjectDirEnv(factoryDir) {
10354
10853
  fix: "Re-run `beastmode init --project <path>` to populate it"
10355
10854
  };
10356
10855
  }
10357
- if (!existsSync25(value)) {
10856
+ if (!existsSync26(value)) {
10358
10857
  return {
10359
10858
  label: "PROJECT_DIR (.env)",
10360
10859
  status: "fail",
@@ -10362,7 +10861,7 @@ function checkProjectDirEnv(factoryDir) {
10362
10861
  fix: `Clone your project to ${value} or re-run 'beastmode init --project <path>'`
10363
10862
  };
10364
10863
  }
10365
- if (!existsSync25(join23(value, ".git"))) {
10864
+ if (!existsSync26(join24(value, ".git"))) {
10366
10865
  return {
10367
10866
  label: "PROJECT_DIR (.env)",
10368
10867
  status: "fail",
@@ -10393,8 +10892,8 @@ async function checkFactoryContainers(factoryDir) {
10393
10892
  detail: "no factory in scope"
10394
10893
  };
10395
10894
  }
10396
- const composePath = join23(factoryDir, "docker-compose.yml");
10397
- if (!existsSync25(composePath)) {
10895
+ const composePath = join24(factoryDir, "docker-compose.yml");
10896
+ if (!existsSync26(composePath)) {
10398
10897
  return {
10399
10898
  label: "Factory containers",
10400
10899
  status: "warn",
@@ -10676,9 +11175,9 @@ function checkProjectDirectory(factoryDir) {
10676
11175
  fix: "Run beastmode init to create a factory"
10677
11176
  };
10678
11177
  }
10679
- const bmDir = join23(factoryDir, ".beastmode");
10680
- const projectsDir = join23(bmDir, "projects");
10681
- if (!existsSync25(projectsDir)) {
11178
+ const bmDir = join24(factoryDir, ".beastmode");
11179
+ const projectsDir = join24(bmDir, "projects");
11180
+ if (!existsSync26(projectsDir)) {
10682
11181
  return {
10683
11182
  label: "Project directory",
10684
11183
  status: "warn",
@@ -10686,8 +11185,8 @@ function checkProjectDirectory(factoryDir) {
10686
11185
  fix: "Run: beastmode add project <path>"
10687
11186
  };
10688
11187
  }
10689
- const projectFiles = existsSync25(projectsDir) ? readdirSync10(projectsDir).filter((f) => f.endsWith(".json")) : [];
10690
- if (projectFiles.length === 0) {
11188
+ const projectRecords = listProjectRecords(projectsDir);
11189
+ if (projectRecords.length === 0) {
10691
11190
  return {
10692
11191
  label: "Project directory",
10693
11192
  status: "warn",
@@ -10697,37 +11196,31 @@ function checkProjectDirectory(factoryDir) {
10697
11196
  }
10698
11197
  const results = [];
10699
11198
  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`);
11199
+ for (const proj of projectRecords) {
11200
+ const projPath = proj.path || "";
11201
+ const projName = proj.name;
11202
+ if (!projPath || !existsSync26(projPath)) {
11203
+ results.push(`${projName}: path not found`);
10729
11204
  anyFail = true;
11205
+ continue;
10730
11206
  }
11207
+ const isGit = existsSync26(join24(projPath, ".git"));
11208
+ const manifests = [
11209
+ "package.json",
11210
+ "Cargo.toml",
11211
+ "go.mod",
11212
+ "pyproject.toml",
11213
+ "requirements.txt",
11214
+ "pom.xml",
11215
+ "build.gradle",
11216
+ "build.gradle.kts"
11217
+ ];
11218
+ const manifest = manifests.find((m) => existsSync26(join24(projPath, m)));
11219
+ const framework = proj.stack?.detected || manifest?.replace(".json", "") || "unknown";
11220
+ results.push(
11221
+ `${projName}: ${projPath} (${framework})${isGit ? "" : " [no .git]"}`
11222
+ );
11223
+ if (!isGit) anyFail = true;
10731
11224
  }
10732
11225
  if (anyFail) {
10733
11226
  return {
@@ -10772,25 +11265,22 @@ function checkStack(factoryDir) {
10772
11265
  detail: "no factory \u2014 run beastmode init"
10773
11266
  };
10774
11267
  }
10775
- const projectsDir = join23(factoryDir, ".beastmode", "projects");
10776
- if (!existsSync25(projectsDir)) {
11268
+ const projectsDir = join24(factoryDir, ".beastmode", "projects");
11269
+ if (!existsSync26(projectsDir)) {
10777
11270
  return { label: "Stack", status: "warn", detail: "no projects configured" };
10778
11271
  }
10779
- const projectFiles = readdirSync10(projectsDir).filter((f) => f.endsWith(".json"));
10780
- if (projectFiles.length === 0) {
11272
+ const projectRecords = listProjectRecords(projectsDir);
11273
+ if (projectRecords.length === 0) {
10781
11274
  return { label: "Stack", status: "warn", detail: "no projects configured" };
10782
11275
  }
10783
11276
  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
- }
11277
+ for (const proj of projectRecords) {
11278
+ const name = proj.name;
11279
+ const stackInfo = proj.stack;
11280
+ const detected = stackInfo?.detected || "unknown";
11281
+ const build = stackInfo?.build_command || "";
11282
+ const port = stackInfo?.dev_port || 3e3;
11283
+ stacks.push(`${name}: ${detected} \u2014 ${build}, port ${port}`);
10794
11284
  }
10795
11285
  if (stacks.length === 0) {
10796
11286
  return { label: "Stack", status: "warn", detail: "no readable project configs" };
@@ -10827,12 +11317,12 @@ function checkBoardPassword(env, factoryDir) {
10827
11317
  return { label: "Board password", status: "pass", detail: "set" };
10828
11318
  }
10829
11319
  if (factoryDir) {
10830
- const dotEnv = join23(factoryDir, ".env");
10831
- const secretsEnv = join23(factoryDir, ".beastmode", "secrets.env.local");
11320
+ const dotEnv = join24(factoryDir, ".env");
11321
+ const secretsEnv = join24(factoryDir, ".beastmode", "secrets.env.local");
10832
11322
  for (const filePath of [dotEnv, secretsEnv]) {
10833
- if (existsSync25(filePath)) {
11323
+ if (existsSync26(filePath)) {
10834
11324
  try {
10835
- const content = readFileSync22(filePath, "utf-8");
11325
+ const content = readFileSync23(filePath, "utf-8");
10836
11326
  const lines = content.split("\n");
10837
11327
  const line = lines.find((l) => l.startsWith("BEASTMODE_UI_PASSWORD="));
10838
11328
  if (line) {
@@ -10854,12 +11344,12 @@ function checkBoardPassword(env, factoryDir) {
10854
11344
  };
10855
11345
  }
10856
11346
  function doctorAction(factoryDir, env) {
10857
- const bmDir = join23(factoryDir, ".beastmode");
10858
- const factoryDirExists = existsSync25(bmDir);
11347
+ const bmDir = join24(factoryDir, ".beastmode");
11348
+ const factoryDirExists = existsSync26(bmDir);
10859
11349
  let factoryIdentity = null;
10860
11350
  if (factoryDirExists) {
10861
11351
  try {
10862
- const raw = JSON.parse(readFileSync22(join23(bmDir, "factory.json"), "utf-8"));
11352
+ const raw = JSON.parse(readFileSync23(join24(bmDir, "factory.json"), "utf-8"));
10863
11353
  factoryIdentity = FactoryIdentitySchema.parse(raw);
10864
11354
  } catch {
10865
11355
  }
@@ -10867,10 +11357,10 @@ function doctorAction(factoryDir, env) {
10867
11357
  let config = null;
10868
11358
  let configParseError = null;
10869
11359
  if (factoryDirExists) {
10870
- const configPath = join23(bmDir, "config.json");
10871
- if (existsSync25(configPath)) {
11360
+ const configPath = join24(bmDir, "config.json");
11361
+ if (existsSync26(configPath)) {
10872
11362
  try {
10873
- const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
11363
+ const raw = JSON.parse(readFileSync23(configPath, "utf-8"));
10874
11364
  const result = FactoryConfigSchema.safeParse(raw);
10875
11365
  if (result.success) {
10876
11366
  config = raw;
@@ -10886,34 +11376,26 @@ function doctorAction(factoryDir, env) {
10886
11376
  }
10887
11377
  const projectPaths = [];
10888
11378
  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
- }
11379
+ const projectsDir = join24(bmDir, "projects");
11380
+ for (const proj of listProjectRecords(projectsDir)) {
11381
+ if (proj.path) {
11382
+ projectPaths.push({
11383
+ name: proj.name,
11384
+ path: proj.path,
11385
+ exists: existsSync26(proj.path)
11386
+ });
10905
11387
  }
10906
11388
  }
10907
11389
  }
10908
11390
  const installedPlugins = [];
10909
11391
  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)) {
11392
+ const pluginsDir = join24(bmDir, "plugins");
11393
+ if (existsSync26(pluginsDir)) {
11394
+ for (const pluginName of readdirSync11(pluginsDir)) {
11395
+ const manifestPath = join24(pluginsDir, pluginName, "manifest.json");
11396
+ if (existsSync26(manifestPath)) {
10915
11397
  try {
10916
- const manifest = JSON.parse(readFileSync22(manifestPath, "utf-8"));
11398
+ const manifest = JSON.parse(readFileSync23(manifestPath, "utf-8"));
10917
11399
  installedPlugins.push({
10918
11400
  name: pluginName,
10919
11401
  engine_version: manifest.engine_version || "*"
@@ -10941,8 +11423,8 @@ var SYSTEMD_CREDS_TIMER = "beastmode-claude-creds.timer";
10941
11423
  var LAUNCH_AGENT_LABEL2 = "com.develeap.beastmode.claude-creds";
10942
11424
  function checkCredentialSyncAgent() {
10943
11425
  const results = [];
10944
- const credsPath = join23(homedir3(), ".claude", ".credentials.json");
10945
- if (!existsSync25(credsPath)) {
11426
+ const credsPath = join24(homedir3(), ".claude", ".credentials.json");
11427
+ if (!existsSync26(credsPath)) {
10946
11428
  results.push({
10947
11429
  label: "Claude credentials file",
10948
11430
  status: "fail",
@@ -10957,7 +11439,7 @@ function checkCredentialSyncAgent() {
10957
11439
  detail: credsPath
10958
11440
  });
10959
11441
  try {
10960
- const creds = JSON.parse(readFileSync22(credsPath, "utf-8"));
11442
+ const creds = JSON.parse(readFileSync23(credsPath, "utf-8"));
10961
11443
  const expiresAt = creds?.claudeAiOauth?.expiresAt;
10962
11444
  if (typeof expiresAt === "number") {
10963
11445
  const hoursLeft = Math.round((expiresAt - Date.now()) / 36e5);
@@ -11273,7 +11755,7 @@ var doctorCommand = new Command12("doctor").description("Health check \u2014 val
11273
11755
  console.log();
11274
11756
  const env = process.env;
11275
11757
  const factoryDir = findFactoryDir() ?? resolve15(".");
11276
- const hasFactory = existsSync25(join23(factoryDir, ".beastmode"));
11758
+ const hasFactory = existsSync26(join24(factoryDir, ".beastmode"));
11277
11759
  const checks = [];
11278
11760
  checks.push(...await checkClaudeAuth(env.ANTHROPIC_API_KEY));
11279
11761
  checks.push(...checkCredentialSyncAgent());
@@ -11349,12 +11831,12 @@ init_schemas();
11349
11831
  init_version();
11350
11832
  init_display();
11351
11833
  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";
11834
+ import { existsSync as existsSync28, readFileSync as readFileSync25, writeFileSync as writeFileSync21 } from "fs";
11835
+ import { join as join26, resolve as resolve16 } from "path";
11354
11836
 
11355
11837
  // 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";
11838
+ import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync20, copyFileSync } from "fs";
11839
+ import { join as join25 } from "path";
11358
11840
  var RECOGNIZED_KEYS = /* @__PURE__ */ new Set([
11359
11841
  "PROJECT_DIR",
11360
11842
  "PROJECT_GITHUB_TOKEN",
@@ -11468,19 +11950,19 @@ function buildNewEnv(values) {
11468
11950
  return lines.join("\n");
11469
11951
  }
11470
11952
  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)) {
11953
+ const envPath = join25(factoryDir, ".env");
11954
+ const composePath = join25(factoryDir, "docker-compose.yml");
11955
+ if (!existsSync27(envPath)) {
11474
11956
  throw new Error(
11475
11957
  `.env not found at ${envPath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
11476
11958
  );
11477
11959
  }
11478
- if (!existsSync26(composePath)) {
11960
+ if (!existsSync27(composePath)) {
11479
11961
  throw new Error(
11480
11962
  `docker-compose.yml not found at ${composePath}. This does not look like a beastmode factory \u2014 run 'beastmode init' first.`
11481
11963
  );
11482
11964
  }
11483
- const existingEnv = readFileSync23(envPath, "utf-8");
11965
+ const existingEnv = readFileSync24(envPath, "utf-8");
11484
11966
  const values = parseExistingEnv(existingEnv);
11485
11967
  const missing = [];
11486
11968
  if (!values.projectDir) missing.push("PROJECT_DIR");
@@ -11498,7 +11980,7 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
11498
11980
  }
11499
11981
  const newEnv = buildNewEnv(values);
11500
11982
  const newCompose = generateComposeYaml("latest");
11501
- const existingCompose = readFileSync23(composePath, "utf-8");
11983
+ const existingCompose = readFileSync24(composePath, "utf-8");
11502
11984
  const envChanged = newEnv !== existingEnv;
11503
11985
  const composeChanged = newCompose !== existingCompose;
11504
11986
  if (!envChanged && !composeChanged) {
@@ -11516,12 +11998,12 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
11516
11998
  if (envChanged) {
11517
11999
  envBackupPath = `${envPath}.backup.${timestamp}`;
11518
12000
  copyFileSync(envPath, envBackupPath);
11519
- writeFileSync19(envPath, newEnv, "utf-8");
12001
+ writeFileSync20(envPath, newEnv, "utf-8");
11520
12002
  }
11521
12003
  if (composeChanged) {
11522
12004
  composeBackupPath = `${composePath}.backup.${timestamp}`;
11523
12005
  copyFileSync(composePath, composeBackupPath);
11524
- writeFileSync19(composePath, newCompose, "utf-8");
12006
+ writeFileSync20(composePath, newCompose, "utf-8");
11525
12007
  }
11526
12008
  return {
11527
12009
  envChanged,
@@ -11534,18 +12016,18 @@ function regenerateFactoryFiles(factoryDir, now = /* @__PURE__ */ new Date()) {
11534
12016
 
11535
12017
  // src/cli/commands/upgrade.ts
11536
12018
  function readIdentity(factoryDir) {
11537
- const path = join25(factoryDir, ".beastmode", "factory.json");
11538
- if (!existsSync27(path)) {
12019
+ const path = join26(factoryDir, ".beastmode", "factory.json");
12020
+ if (!existsSync28(path)) {
11539
12021
  throw new Error("No factory.json found. Run beastmode init first.");
11540
12022
  }
11541
- return FactoryIdentitySchema.parse(JSON.parse(readFileSync24(path, "utf-8")));
12023
+ return FactoryIdentitySchema.parse(JSON.parse(readFileSync25(path, "utf-8")));
11542
12024
  }
11543
12025
  function readConfig3(factoryDir) {
11544
- const path = join25(factoryDir, ".beastmode", "config.json");
11545
- if (!existsSync27(path)) {
12026
+ const path = join26(factoryDir, ".beastmode", "config.json");
12027
+ if (!existsSync28(path)) {
11546
12028
  return {};
11547
12029
  }
11548
- return JSON.parse(readFileSync24(path, "utf-8"));
12030
+ return JSON.parse(readFileSync25(path, "utf-8"));
11549
12031
  }
11550
12032
  function upgradeCheckAction(factoryDir) {
11551
12033
  const identity = readIdentity(factoryDir);
@@ -11557,12 +12039,12 @@ function upgradeAction(factoryDir, migrateOnly = false) {
11557
12039
  const targetVersion = migrateOnly ? identity.engine_version : ENGINE_VERSION;
11558
12040
  const result = performUpgrade(identity, config, targetVersion, SCHEMA_VERSION);
11559
12041
  if (result.changes.length > 0) {
11560
- writeFileSync20(
11561
- join25(factoryDir, ".beastmode", "factory.json"),
12042
+ writeFileSync21(
12043
+ join26(factoryDir, ".beastmode", "factory.json"),
11562
12044
  JSON.stringify(result.updatedIdentity, null, 2) + "\n"
11563
12045
  );
11564
- writeFileSync20(
11565
- join25(factoryDir, ".beastmode", "config.json"),
12046
+ writeFileSync21(
12047
+ join26(factoryDir, ".beastmode", "config.json"),
11566
12048
  JSON.stringify(result.updatedConfig, null, 2) + "\n"
11567
12049
  );
11568
12050
  }
@@ -11652,8 +12134,8 @@ init_board();
11652
12134
  init_display();
11653
12135
  init_migrator();
11654
12136
  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";
12137
+ import { resolve as resolve17, join as join27 } from "path";
12138
+ import { existsSync as existsSync29, readFileSync as readFileSync26, mkdirSync as mkdirSync17, writeFileSync as writeFileSync22 } from "fs";
11657
12139
  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
12140
  try {
11659
12141
  await runMigrate(opts);
@@ -11665,7 +12147,7 @@ var migrateCommand = new Command14("migrate").description("Migrate a daemon conf
11665
12147
  async function runMigrate(opts) {
11666
12148
  const cwd = process.cwd();
11667
12149
  const configPath = opts.config ? resolve17(opts.config) : resolve17(cwd, "config", "beastmode.daemon.json");
11668
- if (!existsSync28(configPath)) {
12150
+ if (!existsSync29(configPath)) {
11669
12151
  throw new Error(
11670
12152
  `Daemon config not found at ${configPath}
11671
12153
  Use --config <path> to specify a different location.`
@@ -11673,25 +12155,25 @@ async function runMigrate(opts) {
11673
12155
  }
11674
12156
  header("BeastMode Migrate");
11675
12157
  info(`Reading daemon config from: ${configPath}`);
11676
- const configContent = readFileSync25(configPath, "utf-8");
12158
+ const configContent = readFileSync26(configPath, "utf-8");
11677
12159
  const daemonConfig = parseDaemonConfig(configContent);
11678
- const runsDir = join26(cwd, "runs");
12160
+ const runsDir = join27(cwd, "runs");
11679
12161
  let runDirs = [];
11680
12162
  const checkpoints = /* @__PURE__ */ new Map();
11681
- if (existsSync28(runsDir)) {
12163
+ if (existsSync29(runsDir)) {
11682
12164
  const { readdirSync: readdirSync14 } = await import("fs");
11683
12165
  runDirs = readdirSync14(runsDir).filter((d) => {
11684
12166
  try {
11685
- return readdirSync14(join26(runsDir, d)).length > 0;
12167
+ return readdirSync14(join27(runsDir, d)).length > 0;
11686
12168
  } catch {
11687
12169
  return false;
11688
12170
  }
11689
12171
  });
11690
12172
  for (const dir of runDirs) {
11691
- const cpPath = join26(runsDir, dir, "checkpoint.json");
11692
- if (existsSync28(cpPath)) {
12173
+ const cpPath = join27(runsDir, dir, "checkpoint.json");
12174
+ if (existsSync29(cpPath)) {
11693
12175
  try {
11694
- const cp = JSON.parse(readFileSync25(cpPath, "utf-8"));
12176
+ const cp = JSON.parse(readFileSync26(cpPath, "utf-8"));
11695
12177
  checkpoints.set(dir, cp);
11696
12178
  } catch {
11697
12179
  }
@@ -11748,28 +12230,28 @@ async function runMigrate(opts) {
11748
12230
  warn("Dry run \u2014 no files written.");
11749
12231
  return;
11750
12232
  }
11751
- const bmDir = join26(cwd, ".beastmode");
11752
- if (existsSync28(bmDir)) {
12233
+ const bmDir = join27(cwd, ".beastmode");
12234
+ if (existsSync29(bmDir)) {
11753
12235
  throw new Error(
11754
12236
  "A .beastmode/ directory already exists. Remove it first to re-migrate."
11755
12237
  );
11756
12238
  }
11757
12239
  for (const file of files) {
11758
- const fullPath = join26(cwd, file.path);
12240
+ const fullPath = join27(cwd, file.path);
11759
12241
  const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
11760
- mkdirSync16(dir, { recursive: true });
11761
- writeFileSync21(fullPath, file.content, "utf-8");
12242
+ mkdirSync17(dir, { recursive: true });
12243
+ writeFileSync22(fullPath, file.content, "utf-8");
11762
12244
  }
11763
- const runsSymlinkTarget = join26(cwd, "runs");
11764
- const bmRunsPath = join26(cwd, "runs");
11765
- if (existsSync28(runsSymlinkTarget)) {
12245
+ const runsSymlinkTarget = join27(cwd, "runs");
12246
+ const bmRunsPath = join27(cwd, "runs");
12247
+ if (existsSync29(runsSymlinkTarget)) {
11766
12248
  info("Existing runs/ directory preserved in-place.");
11767
12249
  }
11768
- const boardPath = join26(bmDir, "board.json");
11769
- if (!existsSync28(boardPath)) {
11770
- writeFileSync21(boardPath, JSON.stringify({ items: [] }, null, 2), "utf-8");
12250
+ const boardPath = join27(bmDir, "board.json");
12251
+ if (!existsSync29(boardPath)) {
12252
+ writeFileSync22(boardPath, JSON.stringify({ items: [] }, null, 2), "utf-8");
11771
12253
  }
11772
- mkdirSync16(join26(bmDir, ".cache"), { recursive: true });
12254
+ mkdirSync17(join27(bmDir, ".cache"), { recursive: true });
11773
12255
  console.log();
11774
12256
  success("Migration complete!");
11775
12257
  info(`Factory created at: ${bmDir}`);
@@ -11791,8 +12273,8 @@ init_board();
11791
12273
  init_bridge();
11792
12274
  init_schemas();
11793
12275
  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";
12276
+ import { join as join28 } from "path";
12277
+ import { existsSync as existsSync30, readFileSync as readFileSync27, writeFileSync as writeFileSync23, mkdirSync as mkdirSync18 } from "fs";
11796
12278
  import { randomUUID as randomUUID3 } from "crypto";
11797
12279
  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
12280
  try {
@@ -11812,18 +12294,18 @@ async function runPipeline(projectName, opts) {
11812
12294
  "No BeastMode factory found. Run 'beastmode init' first."
11813
12295
  );
11814
12296
  }
11815
- const bmDir = join27(factoryDir, ".beastmode");
12297
+ const bmDir = join28(factoryDir, ".beastmode");
11816
12298
  header("BeastMode Run");
11817
- const configPath = join27(bmDir, "config.json");
11818
- if (!existsSync29(configPath)) {
12299
+ const configPath = join28(bmDir, "config.json");
12300
+ if (!existsSync30(configPath)) {
11819
12301
  throw new Error("Factory config not found. Run 'beastmode init' first.");
11820
12302
  }
11821
12303
  const factoryConfig = FactoryConfigSchema.parse(
11822
- JSON.parse(readFileSync26(configPath, "utf-8"))
12304
+ JSON.parse(readFileSync27(configPath, "utf-8"))
11823
12305
  );
11824
12306
  let projectConfig = null;
11825
- const projectsDir = join27(bmDir, "projects");
11826
- if (existsSync29(projectsDir)) {
12307
+ const projectsDir = join28(bmDir, "projects");
12308
+ if (existsSync30(projectsDir)) {
11827
12309
  const { readdirSync: readdirSync14 } = await import("fs");
11828
12310
  const projectFiles = readdirSync14(projectsDir).filter(
11829
12311
  (f) => f.endsWith(".json")
@@ -11836,20 +12318,20 @@ async function runPipeline(projectName, opts) {
11836
12318
  throw new Error(`Project not found: ${projectName}`);
11837
12319
  }
11838
12320
  projectConfig = ProjectConfigSchema.parse(
11839
- JSON.parse(readFileSync26(join27(projectsDir, file), "utf-8"))
12321
+ JSON.parse(readFileSync27(join28(projectsDir, file), "utf-8"))
11840
12322
  );
11841
12323
  } else if (projectFiles.length > 0) {
11842
12324
  projectConfig = ProjectConfigSchema.parse(
11843
- JSON.parse(readFileSync26(join27(projectsDir, projectFiles[0]), "utf-8"))
12325
+ JSON.parse(readFileSync27(join28(projectsDir, projectFiles[0]), "utf-8"))
11844
12326
  );
11845
12327
  info(`Using project: ${projectConfig.name}`);
11846
12328
  }
11847
12329
  }
11848
- const boardPath = join27(bmDir, "board.json");
12330
+ const boardPath = join28(bmDir, "board.json");
11849
12331
  let boardItems = [];
11850
- if (existsSync29(boardPath)) {
12332
+ if (existsSync30(boardPath)) {
11851
12333
  try {
11852
- const raw = JSON.parse(readFileSync26(boardPath, "utf-8"));
12334
+ const raw = JSON.parse(readFileSync27(boardPath, "utf-8"));
11853
12335
  boardItems = Array.isArray(raw.items) ? raw.items : [];
11854
12336
  } catch {
11855
12337
  boardItems = [];
@@ -11866,13 +12348,13 @@ async function runPipeline(projectName, opts) {
11866
12348
  updated_at: now
11867
12349
  };
11868
12350
  boardItems.push(task);
11869
- writeFileSync22(boardPath, JSON.stringify({ items: boardItems }, null, 2), "utf-8");
12351
+ writeFileSync23(boardPath, JSON.stringify({ items: boardItems }, null, 2), "utf-8");
11870
12352
  info(`Created task: ${task.title} (${taskId})`);
11871
- const cacheDir = join27(bmDir, ".cache");
11872
- mkdirSync17(cacheDir, { recursive: true });
11873
- const daemonConfigPath = join27(cacheDir, "daemon.json");
12353
+ const cacheDir = join28(bmDir, ".cache");
12354
+ mkdirSync18(cacheDir, { recursive: true });
12355
+ const daemonConfigPath = join28(cacheDir, "daemon.json");
11874
12356
  const daemonConfig = generateDaemonConfig(factoryConfig, projectConfig, factoryDir);
11875
- writeFileSync22(daemonConfigPath, JSON.stringify(daemonConfig, null, 2), "utf-8");
12357
+ writeFileSync23(daemonConfigPath, JSON.stringify(daemonConfig, null, 2), "utf-8");
11876
12358
  info(`Generated daemon config at: ${daemonConfigPath}`);
11877
12359
  const { execSync: execSync14 } = await import("child_process");
11878
12360
  let pythonAvailable = false;
@@ -11896,7 +12378,7 @@ async function runPipeline(projectName, opts) {
11896
12378
  const daemonPaths = findPythonDaemonPaths(envPath, factoryDir);
11897
12379
  let daemonFound = false;
11898
12380
  for (const p of daemonPaths) {
11899
- if (existsSync29(p)) {
12381
+ if (existsSync30(p)) {
11900
12382
  daemonFound = true;
11901
12383
  break;
11902
12384
  }
@@ -11923,7 +12405,7 @@ async function runPipeline(projectName, opts) {
11923
12405
  const startTime = Date.now();
11924
12406
  const pollInterval = setInterval(() => {
11925
12407
  try {
11926
- const board = JSON.parse(readFileSync26(boardPath, "utf-8"));
12408
+ const board = JSON.parse(readFileSync27(boardPath, "utf-8"));
11927
12409
  const items = Array.isArray(board.items) ? board.items : [];
11928
12410
  const taskItem = items.find((i) => i.id === taskId);
11929
12411
  if (taskItem) {
@@ -11969,8 +12451,8 @@ init_board();
11969
12451
  init_bridge();
11970
12452
  init_schemas();
11971
12453
  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";
12454
+ import { join as join29 } from "path";
12455
+ import { existsSync as existsSync31, readFileSync as readFileSync28, writeFileSync as writeFileSync24, mkdirSync as mkdirSync19 } from "fs";
11974
12456
  var daemonCommand = new Command16("daemon").description("Start the BeastMode daemon via bridge").option("--dry-run", "Generate config but don't start daemon").option(
11975
12457
  "--log-level <level>",
11976
12458
  "Log level (DEBUG, INFO, WARNING, ERROR)",
@@ -11990,38 +12472,38 @@ async function runDaemon(opts) {
11990
12472
  "No BeastMode factory found. Run 'beastmode init' first."
11991
12473
  );
11992
12474
  }
11993
- const bmDir = join28(factoryDir, ".beastmode");
12475
+ const bmDir = join29(factoryDir, ".beastmode");
11994
12476
  header("BeastMode Daemon");
11995
- const configPath = join28(bmDir, "config.json");
11996
- if (!existsSync30(configPath)) {
12477
+ const configPath = join29(bmDir, "config.json");
12478
+ if (!existsSync31(configPath)) {
11997
12479
  throw new Error("Factory config not found. Run 'beastmode init' first.");
11998
12480
  }
11999
12481
  const factoryConfig = FactoryConfigSchema.parse(
12000
- JSON.parse(readFileSync27(configPath, "utf-8"))
12482
+ JSON.parse(readFileSync28(configPath, "utf-8"))
12001
12483
  );
12002
12484
  let projectConfig = null;
12003
- const projectsDir = join28(bmDir, "projects");
12004
- if (existsSync30(projectsDir)) {
12485
+ const projectsDir = join29(bmDir, "projects");
12486
+ if (existsSync31(projectsDir)) {
12005
12487
  const { readdirSync: readdirSync14 } = await import("fs");
12006
12488
  const projectFiles = readdirSync14(projectsDir).filter(
12007
12489
  (f) => f.endsWith(".json")
12008
12490
  );
12009
12491
  if (projectFiles.length > 0) {
12010
12492
  projectConfig = ProjectConfigSchema.parse(
12011
- JSON.parse(readFileSync27(join28(projectsDir, projectFiles[0]), "utf-8"))
12493
+ JSON.parse(readFileSync28(join29(projectsDir, projectFiles[0]), "utf-8"))
12012
12494
  );
12013
12495
  info(`Using project: ${projectConfig.name}`);
12014
12496
  }
12015
12497
  }
12016
- const cacheDir = join28(bmDir, ".cache");
12017
- mkdirSync18(cacheDir, { recursive: true });
12018
- const daemonConfigPath = join28(cacheDir, "daemon.json");
12498
+ const cacheDir = join29(bmDir, ".cache");
12499
+ mkdirSync19(cacheDir, { recursive: true });
12500
+ const daemonConfigPath = join29(cacheDir, "daemon.json");
12019
12501
  const daemonConfig = generateDaemonConfig(
12020
12502
  factoryConfig,
12021
12503
  projectConfig,
12022
12504
  factoryDir
12023
12505
  );
12024
- writeFileSync23(
12506
+ writeFileSync24(
12025
12507
  daemonConfigPath,
12026
12508
  JSON.stringify(daemonConfig, null, 2),
12027
12509
  "utf-8"
@@ -12060,7 +12542,7 @@ async function runDaemon(opts) {
12060
12542
  });
12061
12543
  info(`Starting daemon: ${pythonCmd} ${cmd.args.join(" ")}`);
12062
12544
  console.log();
12063
- const pidFile = join28(bmDir, "daemon.pid");
12545
+ const pidFile = join29(bmDir, "daemon.pid");
12064
12546
  const { spawn: spawn4 } = await import("child_process");
12065
12547
  const child = spawn4(pythonCmd, cmd.args, {
12066
12548
  stdio: "inherit",
@@ -12071,7 +12553,7 @@ async function runDaemon(opts) {
12071
12553
  }
12072
12554
  });
12073
12555
  if (child.pid) {
12074
- writeFileSync23(pidFile, String(child.pid), "utf-8");
12556
+ writeFileSync24(pidFile, String(child.pid), "utf-8");
12075
12557
  }
12076
12558
  const signalHandler = (signal) => {
12077
12559
  info(`Forwarding ${signal} to daemon...`);
@@ -12110,8 +12592,8 @@ var mcpCommand = new Command17("mcp").description("Start the BeastMode MCP serve
12110
12592
  // src/cli/commands/deploy.ts
12111
12593
  init_display();
12112
12594
  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";
12595
+ import { resolve as resolve19, join as join31 } from "path";
12596
+ import { existsSync as existsSync33, writeFileSync as writeFileSync26, readFileSync as readFileSync30 } from "fs";
12115
12597
  import { execSync as execSync9 } from "child_process";
12116
12598
  import { randomBytes } from "crypto";
12117
12599
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -12166,8 +12648,8 @@ async function runDeploy(opts) {
12166
12648
  process.exit(1);
12167
12649
  }
12168
12650
  const factoryDir = resolve19(".");
12169
- const bmDir = join30(factoryDir, ".beastmode");
12170
- if (!existsSync32(bmDir)) {
12651
+ const bmDir = join31(factoryDir, ".beastmode");
12652
+ if (!existsSync33(bmDir)) {
12171
12653
  error(
12172
12654
  "No .beastmode directory found. Run 'beastmode init' or 'beastmode migrate' first."
12173
12655
  );
@@ -12191,33 +12673,33 @@ async function runDeploy(opts) {
12191
12673
  "../../index.js"
12192
12674
  );
12193
12675
  }
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";
12676
+ const boardVenvPython = join31(factoryDir, "board", ".venv", "bin", "python");
12677
+ const daemonVenvPython = join31(factoryDir, "daemon", ".venv", "bin", "python");
12678
+ const boardPython = existsSync33(boardVenvPython) ? boardVenvPython : "python3";
12679
+ const daemonPython = existsSync33(daemonVenvPython) ? daemonVenvPython : "python3";
12198
12680
  const user = execSync9("whoami", { encoding: "utf-8" }).trim();
12199
12681
  const home = process.env.HOME || `/home/${user}`;
12200
12682
  const port = opts.port;
12201
12683
  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") : "";
12684
+ const dotEnv = join31(factoryDir, ".env");
12685
+ const secretsEnv = join31(bmDir, "secrets.env.local");
12686
+ const envContent = existsSync33(dotEnv) ? readFileSync30(dotEnv, "utf-8") : "";
12687
+ const secretsContent = existsSync33(secretsEnv) ? readFileSync30(secretsEnv, "utf-8") : "";
12206
12688
  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
12689
  if (!hasPassword && opts.host === "0.0.0.0") {
12208
12690
  const generated = randomBytes(18).toString("base64url");
12209
- const target = existsSync32(secretsEnv) ? secretsEnv : dotEnv;
12691
+ const target = existsSync33(secretsEnv) ? secretsEnv : dotEnv;
12210
12692
  const append = `
12211
12693
  # Auto-generated board UI password (deploy)
12212
12694
  BEASTMODE_UI_PASSWORD=${generated}
12213
12695
  `;
12214
- writeFileSync25(target, (existsSync32(target) ? readFileSync29(target, "utf-8") : "") + append, "utf-8");
12696
+ writeFileSync26(target, (existsSync33(target) ? readFileSync30(target, "utf-8") : "") + append, "utf-8");
12215
12697
  info(`Board UI password auto-generated and saved to ${target}`);
12216
12698
  success(`Password: ${generated}`);
12217
12699
  info("Save this password \u2014 you'll need it to access the board UI.");
12218
12700
  }
12219
12701
  const envFileLines = [];
12220
- if (existsSync32(secretsEnv)) {
12702
+ if (existsSync33(secretsEnv)) {
12221
12703
  envFileLines.push(`EnvironmentFile=${secretsEnv}`);
12222
12704
  } else {
12223
12705
  envFileLines.push(`# No secrets.env.local found at time of deploy`);
@@ -12300,7 +12782,7 @@ BEASTMODE_UI_PASSWORD=${generated}
12300
12782
  info(`Writing service file to ${svc.path}...`);
12301
12783
  try {
12302
12784
  const tmpPath = `/tmp/${svc.name}.service`;
12303
- writeFileSync25(tmpPath, svc.content, "utf-8");
12785
+ writeFileSync26(tmpPath, svc.content, "utf-8");
12304
12786
  execSync9(`sudo cp ${tmpPath} ${svc.path}`, { stdio: "inherit" });
12305
12787
  success(`${svc.name} service file installed`);
12306
12788
  } catch {
@@ -12429,9 +12911,9 @@ async function deployToAWS(opts) {
12429
12911
  }
12430
12912
  const __filename2 = fileURLToPath3(import.meta.url);
12431
12913
  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;
12914
+ const templatePath = join31(__dirname2, "..", "..", "infra", "cloudformation", "beastmode.yaml");
12915
+ const cwdTemplate = join31(process.cwd(), "infra", "cloudformation", "beastmode.yaml");
12916
+ const template = existsSync33(templatePath) ? templatePath : existsSync33(cwdTemplate) ? cwdTemplate : null;
12435
12917
  if (!template) {
12436
12918
  error("CloudFormation template not found. Expected at infra/cloudformation/beastmode.yaml");
12437
12919
  process.exit(1);
@@ -12685,19 +13167,19 @@ var logsCommand = new Command21("logs").description("Stream BeastMode service lo
12685
13167
 
12686
13168
  // src/cli/commands/update.ts
12687
13169
  import { Command as Command22 } from "commander";
12688
- import { readFileSync as readFileSync30, writeFileSync as writeFileSync26 } from "fs";
13170
+ import { readFileSync as readFileSync31, writeFileSync as writeFileSync27 } from "fs";
12689
13171
  init_display();
12690
13172
  async function runUpdate(opts) {
12691
13173
  const cwd = opts.cwd ?? process.cwd();
12692
13174
  const composePath = requireComposeFile(cwd);
12693
13175
  if (opts.tag) {
12694
- let content = readFileSync30(composePath, "utf-8");
13176
+ let content = readFileSync31(composePath, "utf-8");
12695
13177
  const tagPattern = new RegExp(
12696
13178
  `(${GHCR_IMAGE_PREFIX.replace(/[/]/g, "\\/")}\\/(?:board|daemon|ui)):([\\w.\\-]+)`,
12697
13179
  "g"
12698
13180
  );
12699
13181
  content = content.replace(tagPattern, `$1:${opts.tag}`);
12700
- writeFileSync26(composePath, content, "utf-8");
13182
+ writeFileSync27(composePath, content, "utf-8");
12701
13183
  }
12702
13184
  runCompose(["pull"], { cwd, inherit: true });
12703
13185
  runCompose(["up", "-d"], { cwd, inherit: true });
@@ -12722,23 +13204,23 @@ var updateCommand = new Command22("update").description("Pull latest BeastMode i
12722
13204
  init_display();
12723
13205
  import { Command as Command23 } from "commander";
12724
13206
  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";
13207
+ import { existsSync as existsSync38, readdirSync as readdirSync13, readFileSync as readFileSync35 } from "fs";
13208
+ import { basename as basename7, join as join37, resolve as resolve20 } from "path";
12727
13209
 
12728
13210
  // src/cli/runner-image-builder.ts
12729
13211
  import { execSync as execSync10 } from "child_process";
12730
13212
  import { createHash } from "crypto";
12731
13213
  import {
12732
- existsSync as existsSync34,
12733
- mkdirSync as mkdirSync20,
12734
- readFileSync as readFileSync32,
12735
- writeFileSync as writeFileSync27
13214
+ existsSync as existsSync35,
13215
+ mkdirSync as mkdirSync21,
13216
+ readFileSync as readFileSync33,
13217
+ writeFileSync as writeFileSync28
12736
13218
  } from "fs";
12737
- import { join as join32 } from "path";
13219
+ import { join as join33 } from "path";
12738
13220
 
12739
13221
  // src/cli/stack-detect.ts
12740
- import { existsSync as existsSync33, readFileSync as readFileSync31 } from "fs";
12741
- import { join as join31 } from "path";
13222
+ import { existsSync as existsSync34, readFileSync as readFileSync32 } from "fs";
13223
+ import { join as join32 } from "path";
12742
13224
  var NODE_LOCKFILES = [
12743
13225
  "package-lock.json",
12744
13226
  "pnpm-lock.yaml",
@@ -12776,7 +13258,7 @@ var STACK_LOCKFILES = {
12776
13258
  };
12777
13259
  function readFileSafe2(path) {
12778
13260
  try {
12779
- return readFileSync31(path, "utf-8");
13261
+ return readFileSync32(path, "utf-8");
12780
13262
  } catch {
12781
13263
  return null;
12782
13264
  }
@@ -12789,7 +13271,7 @@ function parseJsonSafe2(content) {
12789
13271
  }
12790
13272
  }
12791
13273
  function detectRunnerStack(projectDir) {
12792
- const pkgContent = readFileSafe2(join31(projectDir, "package.json"));
13274
+ const pkgContent = readFileSafe2(join32(projectDir, "package.json"));
12793
13275
  if (pkgContent) {
12794
13276
  const pkg = parseJsonSafe2(pkgContent);
12795
13277
  if (pkg) {
@@ -12803,36 +13285,36 @@ function detectRunnerStack(projectDir) {
12803
13285
  }
12804
13286
  return { name: "node", language: "node" };
12805
13287
  }
12806
- if (existsSync33(join31(projectDir, "manage.py"))) {
13288
+ if (existsSync34(join32(projectDir, "manage.py"))) {
12807
13289
  return { name: "django", language: "python" };
12808
13290
  }
12809
- const pyproject = readFileSafe2(join31(projectDir, "pyproject.toml"));
13291
+ const pyproject = readFileSafe2(join32(projectDir, "pyproject.toml"));
12810
13292
  if (pyproject) {
12811
13293
  if (pyproject.toLowerCase().includes("fastapi")) {
12812
13294
  return { name: "fastapi", language: "python" };
12813
13295
  }
12814
13296
  return { name: "python", language: "python" };
12815
13297
  }
12816
- if (existsSync33(join31(projectDir, "requirements.txt"))) {
13298
+ if (existsSync34(join32(projectDir, "requirements.txt"))) {
12817
13299
  return { name: "python", language: "python" };
12818
13300
  }
12819
- if (existsSync33(join31(projectDir, "go.mod"))) {
13301
+ if (existsSync34(join32(projectDir, "go.mod"))) {
12820
13302
  return { name: "go", language: "go" };
12821
13303
  }
12822
- if (existsSync33(join31(projectDir, "Cargo.toml"))) {
13304
+ if (existsSync34(join32(projectDir, "Cargo.toml"))) {
12823
13305
  return { name: "rust", language: "rust" };
12824
13306
  }
12825
- if (existsSync33(join31(projectDir, "pom.xml"))) {
13307
+ if (existsSync34(join32(projectDir, "pom.xml"))) {
12826
13308
  return { name: "java-maven", language: "java" };
12827
13309
  }
12828
- if (existsSync33(join31(projectDir, "build.gradle")) || existsSync33(join31(projectDir, "build.gradle.kts"))) {
13310
+ if (existsSync34(join32(projectDir, "build.gradle")) || existsSync34(join32(projectDir, "build.gradle.kts"))) {
12829
13311
  return { name: "java-gradle", language: "java" };
12830
13312
  }
12831
13313
  return { name: "node", language: "node" };
12832
13314
  }
12833
13315
  function findLockfiles(projectDir, stackName) {
12834
13316
  const candidates = STACK_LOCKFILES[stackName] ?? [];
12835
- return candidates.filter((f) => existsSync33(join31(projectDir, f)));
13317
+ return candidates.filter((f) => existsSync34(join32(projectDir, f)));
12836
13318
  }
12837
13319
 
12838
13320
  // src/cli/runner-image-builder.ts
@@ -12934,12 +13416,12 @@ function generateDockerfile(stack, lockfiles) {
12934
13416
  function computeLockfileHash(projectDir, lockfiles) {
12935
13417
  const hash = createHash("sha256");
12936
13418
  for (const lf of lockfiles) {
12937
- const path = join32(projectDir, lf);
13419
+ const path = join33(projectDir, lf);
12938
13420
  hash.update(lf);
12939
13421
  hash.update("\0");
12940
- if (existsSync34(path)) {
13422
+ if (existsSync35(path)) {
12941
13423
  try {
12942
- hash.update(readFileSync32(path));
13424
+ hash.update(readFileSync33(path));
12943
13425
  } catch {
12944
13426
  hash.update("<unreadable>");
12945
13427
  }
@@ -12951,10 +13433,10 @@ function computeLockfileHash(projectDir, lockfiles) {
12951
13433
  return hash.digest("hex").slice(0, 16);
12952
13434
  }
12953
13435
  function readLastBuild(projectDir) {
12954
- const path = join32(projectDir, RUNNER_STATE_DIR, LAST_BUILD_FILE);
12955
- if (!existsSync34(path)) return null;
13436
+ const path = join33(projectDir, RUNNER_STATE_DIR, LAST_BUILD_FILE);
13437
+ if (!existsSync35(path)) return null;
12956
13438
  try {
12957
- const data = JSON.parse(readFileSync32(path, "utf-8"));
13439
+ const data = JSON.parse(readFileSync33(path, "utf-8"));
12958
13440
  if (data && typeof data === "object" && typeof data.lockfileHash === "string" && typeof data.imageTag === "string" && typeof data.builtAt === "string") {
12959
13441
  return data;
12960
13442
  }
@@ -12973,17 +13455,17 @@ function imageTagFor(projectName, hash) {
12973
13455
  return `beastmode-runner-${safeName}:${hash}`;
12974
13456
  }
12975
13457
  function writeStateFile(projectDir, fileName, payload) {
12976
- const dir = join32(projectDir, RUNNER_STATE_DIR);
12977
- mkdirSync20(dir, { recursive: true });
12978
- writeFileSync27(
12979
- join32(dir, fileName),
13458
+ const dir = join33(projectDir, RUNNER_STATE_DIR);
13459
+ mkdirSync21(dir, { recursive: true });
13460
+ writeFileSync28(
13461
+ join33(dir, fileName),
12980
13462
  JSON.stringify(payload, null, 2) + "\n",
12981
13463
  "utf-8"
12982
13464
  );
12983
13465
  }
12984
13466
  function buildRunnerImage(opts) {
12985
13467
  const { projectDir, projectName, stack, lockfiles, force, dryRun } = opts;
12986
- if (!existsSync34(projectDir)) {
13468
+ if (!existsSync35(projectDir)) {
12987
13469
  throw new Error(`Project directory not found: ${projectDir}`);
12988
13470
  }
12989
13471
  const lockfileHash = computeLockfileHash(projectDir, lockfiles);
@@ -13102,11 +13584,11 @@ function resolveGitHubConfig() {
13102
13584
  // src/cli/runner-helpers.ts
13103
13585
  import { execSync as execSync11, spawn, spawnSync as spawnSync5 } from "child_process";
13104
13586
  import { promises as fs } from "fs";
13105
- import { join as join33 } from "path";
13587
+ import { join as join34 } from "path";
13106
13588
  init_display();
13107
13589
  async function writeRunnerMeta(dir, meta) {
13108
13590
  await fs.mkdir(dir, { recursive: true });
13109
- const path = join33(dir, "runner-meta.json");
13591
+ const path = join34(dir, "runner-meta.json");
13110
13592
  await fs.writeFile(path, JSON.stringify(meta, null, 2) + "\n", "utf-8");
13111
13593
  }
13112
13594
  function resolveRepoSlug() {
@@ -13307,13 +13789,13 @@ import {
13307
13789
  } from "child_process";
13308
13790
  import {
13309
13791
  createWriteStream,
13310
- existsSync as existsSync35,
13311
- mkdirSync as mkdirSync21,
13792
+ existsSync as existsSync36,
13793
+ mkdirSync as mkdirSync22,
13312
13794
  unlinkSync as unlinkSync5,
13313
- writeFileSync as writeFileSync28
13795
+ writeFileSync as writeFileSync29
13314
13796
  } from "fs";
13315
13797
  import { homedir as homedir4 } from "os";
13316
- import { join as join34, dirname as dirname8 } from "path";
13798
+ import { join as join35, dirname as dirname8 } from "path";
13317
13799
  import { Readable } from "stream";
13318
13800
  import { pipeline } from "stream/promises";
13319
13801
  init_display();
@@ -13343,8 +13825,8 @@ function runnerDownloadUrl(os, arch) {
13343
13825
  return `https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-${ghOs}-${arch}-${RUNNER_VERSION}.tar.gz`;
13344
13826
  }
13345
13827
  async function downloadAndExtractRunner(installDir, os, arch) {
13346
- const runShPath = join34(installDir, "run.sh");
13347
- if (existsSync35(runShPath)) {
13828
+ const runShPath = join35(installDir, "run.sh");
13829
+ if (existsSync36(runShPath)) {
13348
13830
  info(
13349
13831
  `Runner binary already present at ${runShPath} \u2014 skipping download.`
13350
13832
  );
@@ -13354,7 +13836,7 @@ async function downloadAndExtractRunner(installDir, os, arch) {
13354
13836
  return;
13355
13837
  }
13356
13838
  const url = runnerDownloadUrl(os, arch);
13357
- const tarball = join34(installDir, "runner.tar.gz");
13839
+ const tarball = join35(installDir, "runner.tar.gz");
13358
13840
  setupStep(
13359
13841
  `Downloading GitHub Actions runner v${RUNNER_VERSION} (${os}/${arch})...`
13360
13842
  );
@@ -13405,7 +13887,7 @@ async function configureRunner(installDir, repoUrl, token, name, labels) {
13405
13887
  "--unattended",
13406
13888
  "--replace"
13407
13889
  ];
13408
- const result = spawnSync6(join34(installDir, "config.sh"), args, {
13890
+ const result = spawnSync6(join35(installDir, "config.sh"), args, {
13409
13891
  cwd: installDir,
13410
13892
  stdio: ["ignore", "inherit", "pipe"],
13411
13893
  encoding: "utf-8"
@@ -13433,7 +13915,7 @@ function startRunnerForeground(installDir) {
13433
13915
  "Use --service to install as a launchd agent for persistent operation."
13434
13916
  );
13435
13917
  }
13436
- spawn2(join34(installDir, "run.sh"), [], {
13918
+ spawn2(join35(installDir, "run.sh"), [], {
13437
13919
  cwd: installDir,
13438
13920
  stdio: "inherit"
13439
13921
  });
@@ -13458,10 +13940,10 @@ WantedBy=default.target
13458
13940
  }
13459
13941
  async function installSystemdService(installDir, name) {
13460
13942
  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),
13943
+ const unitDir = join35(homedir4(), ".config", "systemd", "user");
13944
+ mkdirSync22(unitDir, { recursive: true });
13945
+ writeFileSync29(
13946
+ join35(unitDir, unitName),
13465
13947
  systemdUnitContent(installDir, name),
13466
13948
  "utf-8"
13467
13949
  );
@@ -13515,15 +13997,15 @@ function launchdPlistContent(installDir, name) {
13515
13997
  }
13516
13998
  async function installLaunchdService(installDir, name) {
13517
13999
  const label = `com.beastmode.runner.${name}`;
13518
- const plistPath2 = join34(
14000
+ const plistPath2 = join35(
13519
14001
  homedir4(),
13520
14002
  "Library",
13521
14003
  "LaunchAgents",
13522
14004
  `${label}.plist`
13523
14005
  );
13524
- mkdirSync21(dirname8(plistPath2), { recursive: true });
13525
- mkdirSync21(join34(installDir, "logs"), { recursive: true });
13526
- writeFileSync28(plistPath2, launchdPlistContent(installDir, name), "utf-8");
14006
+ mkdirSync22(dirname8(plistPath2), { recursive: true });
14007
+ mkdirSync22(join35(installDir, "logs"), { recursive: true });
14008
+ writeFileSync29(plistPath2, launchdPlistContent(installDir, name), "utf-8");
13527
14009
  try {
13528
14010
  execSync12(`launchctl load ${plistPath2}`, { stdio: "inherit" });
13529
14011
  } catch (err) {
@@ -13564,7 +14046,7 @@ async function nativeRunnerSetup(opts) {
13564
14046
  }
13565
14047
  const ghConfig = resolveGitHubConfig();
13566
14048
  const repoSlug = opts.repo ?? resolveRepoSlug();
13567
- const installDir = join34(homedir4(), ".beastmode", "runners", opts.name);
14049
+ const installDir = join35(homedir4(), ".beastmode", "runners", opts.name);
13568
14050
  if (opts.dryRun) {
13569
14051
  info(
13570
14052
  `[dry-run] Would install native runner '${opts.name}' for repo ${repoSlug}`
@@ -13574,7 +14056,7 @@ async function nativeRunnerSetup(opts) {
13574
14056
  info(`[dry-run] Mode: ${opts.service ? "service" : "foreground"}`);
13575
14057
  return;
13576
14058
  }
13577
- mkdirSync21(installDir, { recursive: true });
14059
+ mkdirSync22(installDir, { recursive: true });
13578
14060
  setupStep("Generating registration token via GitHub API...");
13579
14061
  const { token: regToken } = await createRegistrationToken(ghConfig);
13580
14062
  await downloadAndExtractRunner(installDir, platformOs, arch);
@@ -13612,13 +14094,13 @@ async function nativeRunnerSetup(opts) {
13612
14094
 
13613
14095
  // src/cli/workflow-switcher.ts
13614
14096
  import {
13615
- existsSync as existsSync36,
13616
- mkdirSync as mkdirSync22,
13617
- readFileSync as readFileSync33,
14097
+ existsSync as existsSync37,
14098
+ mkdirSync as mkdirSync23,
14099
+ readFileSync as readFileSync34,
13618
14100
  unlinkSync as unlinkSync6,
13619
- writeFileSync as writeFileSync29
14101
+ writeFileSync as writeFileSync30
13620
14102
  } from "fs";
13621
- import { dirname as dirname9, join as join35 } from "path";
14103
+ import { dirname as dirname9, join as join36 } from "path";
13622
14104
  var TARGET_LABEL = "[self-hosted, beastmode]";
13623
14105
  var TARGET_WORKFLOWS = [
13624
14106
  ".github/workflows/test.yml",
@@ -13657,8 +14139,8 @@ function restoreRunsOn(content, originals) {
13657
14139
  return newLines.join("\n");
13658
14140
  }
13659
14141
  async function switchWorkflows(projectDir) {
13660
- const statePath = join35(projectDir, STATE_FILE);
13661
- if (existsSync36(statePath)) {
14142
+ const statePath = join36(projectDir, STATE_FILE);
14143
+ if (existsSync37(statePath)) {
13662
14144
  return { alreadySwitched: true, files: [] };
13663
14145
  }
13664
14146
  const state = {
@@ -13668,40 +14150,40 @@ async function switchWorkflows(projectDir) {
13668
14150
  };
13669
14151
  const resultFiles = [];
13670
14152
  for (const relPath of TARGET_WORKFLOWS) {
13671
- const absPath = join35(projectDir, relPath);
13672
- if (!existsSync36(absPath)) {
14153
+ const absPath = join36(projectDir, relPath);
14154
+ if (!existsSync37(absPath)) {
13673
14155
  throw new Error(`Workflow file not found: ${relPath}`);
13674
14156
  }
13675
- const content = readFileSync33(absPath, "utf-8");
14157
+ const content = readFileSync34(absPath, "utf-8");
13676
14158
  const { newContent, originals } = replaceRunsOn(content, TARGET_LABEL);
13677
14159
  if (originals.length === 0) {
13678
14160
  throw new Error(`No runs-on found in ${relPath}`);
13679
14161
  }
13680
- writeFileSync29(absPath, newContent, "utf-8");
14162
+ writeFileSync30(absPath, newContent, "utf-8");
13681
14163
  state.files.push({ relativePath: relPath, originals });
13682
14164
  resultFiles.push({ relativePath: relPath, jobCount: originals.length });
13683
14165
  }
13684
- mkdirSync22(dirname9(statePath), { recursive: true });
13685
- writeFileSync29(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
14166
+ mkdirSync23(dirname9(statePath), { recursive: true });
14167
+ writeFileSync30(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
13686
14168
  return { alreadySwitched: false, files: resultFiles };
13687
14169
  }
13688
14170
  async function restoreWorkflows(projectDir) {
13689
- const statePath = join35(projectDir, STATE_FILE);
13690
- if (!existsSync36(statePath)) {
14171
+ const statePath = join36(projectDir, STATE_FILE);
14172
+ if (!existsSync37(statePath)) {
13691
14173
  return { nothingToRestore: true, files: [] };
13692
14174
  }
13693
14175
  const state = JSON.parse(
13694
- readFileSync33(statePath, "utf-8")
14176
+ readFileSync34(statePath, "utf-8")
13695
14177
  );
13696
14178
  const resultFiles = [];
13697
14179
  for (const fileState of state.files) {
13698
- const absPath = join35(projectDir, fileState.relativePath);
13699
- if (!existsSync36(absPath)) {
14180
+ const absPath = join36(projectDir, fileState.relativePath);
14181
+ if (!existsSync37(absPath)) {
13700
14182
  throw new Error(`Workflow file not found: ${fileState.relativePath}`);
13701
14183
  }
13702
- const content = readFileSync33(absPath, "utf-8");
14184
+ const content = readFileSync34(absPath, "utf-8");
13703
14185
  const newContent = restoreRunsOn(content, fileState.originals);
13704
- writeFileSync29(absPath, newContent, "utf-8");
14186
+ writeFileSync30(absPath, newContent, "utf-8");
13705
14187
  resultFiles.push({
13706
14188
  relativePath: fileState.relativePath,
13707
14189
  jobCount: fileState.originals.length
@@ -13714,14 +14196,14 @@ async function restoreWorkflows(projectDir) {
13714
14196
  // src/cli/commands/runner-cmd.ts
13715
14197
  var runnerCommand = new Command23("runner").description("Manage self-hosted GitHub Actions runners");
13716
14198
  function resolveProjectName(projectDir) {
13717
- const projectsDir = join36(projectDir, ".beastmode", "projects");
13718
- if (existsSync37(projectsDir)) {
14199
+ const projectsDir = join37(projectDir, ".beastmode", "projects");
14200
+ if (existsSync38(projectsDir)) {
13719
14201
  try {
13720
- for (const entry of readdirSync12(projectsDir)) {
14202
+ for (const entry of readdirSync13(projectsDir)) {
13721
14203
  if (!entry.endsWith(".json")) continue;
13722
- const file = join36(projectsDir, entry);
14204
+ const file = join37(projectsDir, entry);
13723
14205
  try {
13724
- const raw = readFileSync34(file, "utf-8");
14206
+ const raw = readFileSync35(file, "utf-8");
13725
14207
  const data = JSON.parse(raw);
13726
14208
  if (typeof data.path === "string" && resolve20(data.path) === resolve20(projectDir) && typeof data.name === "string" && data.name.length > 0) {
13727
14209
  return data.name;
@@ -13732,7 +14214,7 @@ function resolveProjectName(projectDir) {
13732
14214
  } catch {
13733
14215
  }
13734
14216
  }
13735
- return basename6(resolve20(projectDir));
14217
+ return basename7(resolve20(projectDir));
13736
14218
  }
13737
14219
  async function runnerSetupAction(opts) {
13738
14220
  if (opts.service !== void 0 && !opts.native) {
@@ -14057,7 +14539,7 @@ runnerCommand.command("restore-workflows").description("Restore workflows to ori
14057
14539
  });
14058
14540
  async function runnerBuildImageAction(opts) {
14059
14541
  const projectDir = resolve20(opts.projectDir);
14060
- if (!existsSync37(projectDir)) {
14542
+ if (!existsSync38(projectDir)) {
14061
14543
  throw new Error(`Project directory not found: ${projectDir}`);
14062
14544
  }
14063
14545
  const stack = detectRunnerStack(projectDir);
@@ -14107,27 +14589,16 @@ runnerCommand.command("build-image").description(
14107
14589
  // src/cli/commands/project-cmd.ts
14108
14590
  init_engine();
14109
14591
  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";
14592
+ import { existsSync as existsSync39, mkdirSync as mkdirSync24, readFileSync as readFileSync36, renameSync as renameSync3 } from "fs";
14593
+ import { join as join38, resolve as resolve21, basename as basename8 } from "path";
14112
14594
  import { execSync as execSync13 } from "child_process";
14113
14595
  var DEFAULT_MAX_PROJECTS = 5;
14114
14596
  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
14597
  function getMaxProjects(factoryDir) {
14127
- const configPath = join37(factoryDir, ".beastmode", "config.json");
14128
- if (existsSync38(configPath)) {
14598
+ const configPath = join38(factoryDir, ".beastmode", "config.json");
14599
+ if (existsSync39(configPath)) {
14129
14600
  try {
14130
- const config = JSON.parse(readFileSync35(configPath, "utf-8"));
14601
+ const config = JSON.parse(readFileSync36(configPath, "utf-8"));
14131
14602
  if (typeof config.max_projects === "number") return config.max_projects;
14132
14603
  } catch {
14133
14604
  }
@@ -14146,11 +14617,13 @@ function getFreeDiskGB() {
14146
14617
  }
14147
14618
  function projectAddAction(factoryDir, projectPath, opts) {
14148
14619
  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);
14620
+ if (!existsSync39(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
14621
+ const projectName = opts.name || basename8(resolvedPath);
14622
+ const projectsDir = join38(factoryDir, ".beastmode", "projects");
14623
+ if (existsSync39(join38(projectsDir, projectName, "project.json"))) {
14624
+ throw new Error(`Project already exists: ${projectName}`);
14625
+ }
14626
+ const currentCount = listProjectRecords(projectsDir).length;
14154
14627
  const maxProjects = getMaxProjects(factoryDir);
14155
14628
  if (currentCount >= maxProjects) {
14156
14629
  throw new Error(
@@ -14163,86 +14636,57 @@ function projectAddAction(factoryDir, projectPath, opts) {
14163
14636
  `Warning: only ${freeGB}GB free disk space. Each project with worktrees may use 1-2GB. Consider freeing space.`
14164
14637
  );
14165
14638
  }
14166
- mkdirSync23(projectsDir, { recursive: true });
14167
- let stack = {};
14639
+ let detectedStack = null;
14640
+ let gitRemote;
14168
14641
  try {
14169
- stack = detectStack(resolvedPath);
14642
+ detectedStack = detectStack(resolvedPath);
14643
+ if (detectedStack.git_remote) gitRemote = detectedStack.git_remote;
14170
14644
  } catch {
14171
14645
  }
14172
14646
  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
- }
14647
+ for (const existing of listProjectRecords(projectsDir)) {
14648
+ const port = existing.deploy?.verify_port;
14649
+ if (typeof port === "number" && port >= verifyPort) verifyPort = port + 1;
14650
+ }
14651
+ const boardId = opts.boardId ? parseInt(opts.boardId, 10) : null;
14652
+ const record = createProjectRecord({ name: projectName, resolvedPath, boardId, verifyPort, gitRemote });
14653
+ if (detectedStack) {
14654
+ record.stack = {
14655
+ detected: detectedStack.framework,
14656
+ build_command: detectedStack.suggested_commands.build,
14657
+ dev_command: detectedStack.suggested_commands.dev,
14658
+ test_command: detectedStack.suggested_commands.test,
14659
+ install_command: detectedStack.suggested_commands.install,
14660
+ dev_port: detectedStack.dev_port,
14661
+ is_monorepo: detectedStack.is_monorepo,
14662
+ total_packages: detectedStack.total_packages,
14663
+ primary_languages: detectedStack.primary_languages,
14664
+ ...detectedStack.packages ? { packages: detectedStack.packages } : {}
14665
+ };
14188
14666
  }
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) {
14667
+ writeProjectRecord(projectsDir, projectName, record);
14668
+ const runsDir = join38(factoryDir, "runs", projectName);
14669
+ if (!existsSync39(runsDir)) mkdirSync24(runsDir, { recursive: true });
14670
+ if (!boardId) {
14216
14671
  void (async () => {
14217
14672
  try {
14218
- const boardUrl = projectConfig.board.url;
14673
+ const boardUrl = record.board.url;
14219
14674
  const http4 = await import("http");
14220
14675
  const postData = JSON.stringify({ project_name: projectName });
14221
- await new Promise((resolve22, reject) => {
14676
+ await new Promise((res) => {
14222
14677
  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 {
14678
+ const req = http4.request(
14679
+ url,
14680
+ {
14681
+ method: "POST",
14682
+ headers: {
14683
+ "Content-Type": "application/json",
14684
+ "Content-Length": Buffer.byteLength(postData).toString()
14241
14685
  }
14242
- resolve22();
14243
- });
14244
- });
14245
- req.on("error", () => resolve22());
14686
+ },
14687
+ () => res()
14688
+ );
14689
+ req.on("error", () => res());
14246
14690
  req.end(postData);
14247
14691
  });
14248
14692
  } catch {
@@ -14251,28 +14695,21 @@ function projectAddAction(factoryDir, projectPath, opts) {
14251
14695
  }
14252
14696
  }
14253
14697
  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);
14698
+ const projectsDir = join38(factoryDir, ".beastmode", "projects");
14699
+ return listProjectRecords(projectsDir);
14263
14700
  }
14264
14701
  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);
14702
+ const projectDir = join38(factoryDir, ".beastmode", "projects", name);
14703
+ if (!existsSync39(projectDir)) throw new Error(`Project not found: ${name}`);
14704
+ const archivedBase = join38(factoryDir, ".beastmode", "projects", ".archived");
14705
+ mkdirSync24(archivedBase, { recursive: true });
14706
+ renameSync3(projectDir, join38(archivedBase, name));
14270
14707
  }
14271
14708
  var projectCommand = new Command24("project").description("Manage projects in this factory");
14272
14709
  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
14710
  const factoryDir = resolve21(".");
14274
14711
  projectAddAction(factoryDir, path, opts);
14275
- console.log(`Project registered: ${opts.name || basename7(resolve21(path))}`);
14712
+ console.log(`Project registered: ${opts.name || basename8(resolve21(path))}`);
14276
14713
  });
14277
14714
  projectCommand.command("list").description("List registered projects").action(() => {
14278
14715
  const projects = projectListAction(resolve21("."));
@@ -14281,7 +14718,7 @@ projectCommand.command("list").description("List registered projects").action(()
14281
14718
  return;
14282
14719
  }
14283
14720
  for (const p of projects) {
14284
- console.log(` ${p.name} \u2014 ${p.path}`);
14721
+ console.log(` ${p["name"]} \u2014 ${p["path"]}`);
14285
14722
  }
14286
14723
  });
14287
14724
  projectCommand.command("remove <name>").description("Archive a project").action((name) => {