@beastmode-develeap/beastmode 0.1.266 → 0.1.268

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -95,17 +95,36 @@ var init_schemas = __esm({
95
95
  });
96
96
  DeployConfigSchema = z.object({
97
97
  target: z.string().default("pr-only"),
98
+ verify_port: z.number().int().default(3001),
98
99
  config: z.record(z.unknown()).default({})
99
100
  });
100
101
  ProjectConfigSchema = z.object({
101
102
  name: z.string(),
102
- repo: z.string().optional(),
103
103
  path: z.string(),
104
+ github: z.object({
105
+ repo: z.string().default(""),
106
+ default_branch: z.string().default("main")
107
+ }).default({}),
108
+ board: z.object({
109
+ id: z.number().nullable().default(null),
110
+ url: z.string().default("http://127.0.0.1:8080"),
111
+ auto_created: z.boolean().default(false)
112
+ }).default({}),
104
113
  stack: StackConfigSchema,
105
114
  deploy: DeployConfigSchema.default({}),
106
115
  pipeline: z.record(z.unknown()).default({}),
107
116
  models: z.record(z.string()).default({}),
108
- plugins: z.array(z.string()).default([])
117
+ plugins: z.array(z.string()).default([]),
118
+ slots: z.object({
119
+ max: z.number().nullable().default(null)
120
+ }).default({}),
121
+ infra: z.object({
122
+ credentials: z.object({
123
+ mode: z.string().default("factory")
124
+ }).default({}),
125
+ state_backend: z.string().default("auto")
126
+ }).default({}),
127
+ registered_at: z.string().default("")
109
128
  });
110
129
  FactoryIdentitySchema = z.object({
111
130
  factory_name: z.string(),
@@ -2854,7 +2873,10 @@ function mapDaemonToFactory(daemon) {
2854
2873
  const hasDeployConfig = Object.keys(deployConfig).length > 0;
2855
2874
  const projectRaw = {
2856
2875
  name: repoName,
2857
- repo: daemon.github?.project_repo || "",
2876
+ github: {
2877
+ repo: daemon.github?.project_repo || "",
2878
+ default_branch: daemon.github?.base_branch || "main"
2879
+ },
2858
2880
  path: daemon.repos?.project || "",
2859
2881
  stack: {
2860
2882
  detected: stack.name || "node",
@@ -3058,7 +3080,7 @@ function generateDaemonConfig(factoryConfig, projectConfig, factoryPath) {
3058
3080
  taskBackendValue = backendAdapter;
3059
3081
  }
3060
3082
  const boardUrl = process.env.BEASTMODE_BOARD_URL || taskBackend.config?.url || "http://127.0.0.1:8080";
3061
- const projectRepo = projectConfig?.repo || "";
3083
+ const projectRepo = projectConfig?.github?.repo || (projectConfig?.repo ?? "");
3062
3084
  const projectPath = projectConfig?.path || "";
3063
3085
  const projectOrg = projectRepo.split("/")[0] || "beastmode-agent";
3064
3086
  const stack = projectConfig?.stack;
@@ -3201,8 +3223,18 @@ var init_bridge = __esm({
3201
3223
  });
3202
3224
 
3203
3225
  // 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";
3226
+ import { existsSync as existsSync8, writeFileSync as writeFileSync6, renameSync, readFileSync as readFileSync8, readdirSync as readdirSync3, mkdirSync as mkdirSync6, unlinkSync } from "fs";
3227
+ import { join as join8, basename as basename3 } from "path";
3228
+ function computeVerifyPort(existingProjects) {
3229
+ let port = 3001;
3230
+ if (existingProjects) {
3231
+ for (const p of existingProjects) {
3232
+ const v = p.deploy?.verify_port;
3233
+ if (typeof v === "number" && v >= port) port = v + 1;
3234
+ }
3235
+ }
3236
+ return port;
3237
+ }
3206
3238
  function detectShape(parsed) {
3207
3239
  if (parsed.github && typeof parsed.github === "object" && parsed.slots && typeof parsed.slots === "object") {
3208
3240
  return "canonical";
@@ -3212,10 +3244,34 @@ function detectShape(parsed) {
3212
3244
  }
3213
3245
  return "http-legacy";
3214
3246
  }
3247
+ function hasMissingCanonicalFields(parsed) {
3248
+ if (!parsed.stack) return true;
3249
+ if (!parsed.plugins) return true;
3250
+ if (!parsed.infra) return true;
3251
+ const deploy = parsed.deploy;
3252
+ if (!deploy?.target) return true;
3253
+ return false;
3254
+ }
3255
+ function normalizeDeploy(existing) {
3256
+ return {
3257
+ ...existing,
3258
+ target: existing?.target || "pr-only",
3259
+ verify_port: existing?.verify_port || 3001,
3260
+ config: existing?.config || {}
3261
+ };
3262
+ }
3215
3263
  function normalizeToCanonical(parsed, name) {
3216
3264
  const shape = detectShape(parsed);
3265
+ const existingDeploy = parsed.deploy;
3217
3266
  if (shape === "canonical") {
3218
- return parsed;
3267
+ const record = parsed;
3268
+ return {
3269
+ ...record,
3270
+ stack: record.stack || DEFAULT_STACK,
3271
+ plugins: record.plugins || [],
3272
+ infra: record.infra || DEFAULT_INFRA,
3273
+ deploy: normalizeDeploy(existingDeploy)
3274
+ };
3219
3275
  }
3220
3276
  if (shape === "cli-legacy") {
3221
3277
  const github = parsed.github;
@@ -3231,10 +3287,13 @@ function normalizeToCanonical(parsed, name) {
3231
3287
  url: "http://127.0.0.1:8080",
3232
3288
  auto_created: true
3233
3289
  },
3234
- deploy: parsed.deploy || { verify_port: 3001 },
3290
+ stack: parsed.stack || DEFAULT_STACK,
3291
+ deploy: normalizeDeploy(existingDeploy),
3235
3292
  pipeline: parsed.pipeline || {},
3236
3293
  models: parsed.models || {},
3294
+ plugins: parsed.plugins || [],
3237
3295
  slots: { max: null },
3296
+ infra: parsed.infra || DEFAULT_INFRA,
3238
3297
  registered_at: parsed.registered_at || (/* @__PURE__ */ new Date()).toISOString()
3239
3298
  };
3240
3299
  }
@@ -3242,34 +3301,74 @@ function normalizeToCanonical(parsed, name) {
3242
3301
  name: parsed.name || name,
3243
3302
  path: parsed.path || "",
3244
3303
  github: {
3245
- repo: parsed.repo || "",
3304
+ repo: parsed.repo || parsed.github?.repo || "",
3246
3305
  default_branch: "main"
3247
3306
  },
3248
- board: { id: null, url: "http://127.0.0.1:8080", auto_created: true },
3249
- deploy: { verify_port: 3001 },
3307
+ board: parsed.board || {
3308
+ id: null,
3309
+ url: "http://127.0.0.1:8080",
3310
+ auto_created: true
3311
+ },
3312
+ stack: parsed.stack || DEFAULT_STACK,
3313
+ deploy: normalizeDeploy(existingDeploy),
3250
3314
  pipeline: parsed.pipeline || {},
3251
3315
  models: parsed.models || {},
3252
- slots: { max: null },
3253
- registered_at: (/* @__PURE__ */ new Date()).toISOString()
3316
+ plugins: parsed.plugins || [],
3317
+ slots: parsed.slots || { max: null },
3318
+ infra: parsed.infra || DEFAULT_INFRA,
3319
+ registered_at: parsed.registered_at || (/* @__PURE__ */ new Date()).toISOString()
3254
3320
  };
3255
3321
  }
3256
3322
  function createProjectRecord(input) {
3323
+ const name = input.name || basename3(input.resolvedPath);
3324
+ let boardId = null;
3325
+ if (input.boardId !== void 0 && input.boardId !== null) {
3326
+ const parsed = typeof input.boardId === "string" ? parseInt(input.boardId, 10) : input.boardId;
3327
+ boardId = isNaN(parsed) ? null : parsed;
3328
+ }
3329
+ let stack = { ...DEFAULT_STACK };
3330
+ let gitRemote = input.gitRemote;
3331
+ try {
3332
+ const detected = detectStack(input.resolvedPath);
3333
+ stack = {
3334
+ detected: detected.framework,
3335
+ build_command: detected.suggested_commands.build,
3336
+ dev_command: detected.suggested_commands.dev,
3337
+ test_command: detected.suggested_commands.test,
3338
+ install_command: detected.suggested_commands.install,
3339
+ dev_port: detected.dev_port,
3340
+ is_monorepo: detected.is_monorepo,
3341
+ total_packages: detected.total_packages,
3342
+ primary_languages: detected.primary_languages,
3343
+ ...detected.packages ? { packages: detected.packages } : {}
3344
+ };
3345
+ if (!gitRemote && detected.git_remote) gitRemote = detected.git_remote;
3346
+ } catch {
3347
+ }
3348
+ const verifyPort = input.verifyPort ?? computeVerifyPort(input.existingProjects);
3257
3349
  return {
3258
- name: input.name,
3350
+ name,
3259
3351
  path: input.resolvedPath,
3260
3352
  github: {
3261
- repo: input.gitRemote || "",
3353
+ repo: gitRemote || "",
3262
3354
  default_branch: "main"
3263
3355
  },
3264
3356
  board: {
3265
- id: input.boardId ?? null,
3357
+ id: boardId,
3266
3358
  url: process.env.BEASTMODE_BOARD_URL || "http://127.0.0.1:8080",
3267
- auto_created: !input.boardId
3359
+ auto_created: boardId === null
3360
+ },
3361
+ stack,
3362
+ deploy: {
3363
+ target: "pr-only",
3364
+ verify_port: verifyPort,
3365
+ config: {}
3268
3366
  },
3269
- deploy: { verify_port: input.verifyPort ?? 3001 },
3270
3367
  pipeline: {},
3271
3368
  models: {},
3369
+ plugins: [],
3272
3370
  slots: { max: null },
3371
+ infra: { credentials: { mode: "factory" }, state_backend: "auto" },
3273
3372
  registered_at: (/* @__PURE__ */ new Date()).toISOString()
3274
3373
  };
3275
3374
  }
@@ -3317,8 +3416,15 @@ function readProjectRecord(projectsDir, name) {
3317
3416
  }
3318
3417
  const shape = detectShape(parsed);
3319
3418
  const record = normalizeToCanonical(parsed, name);
3320
- if (shape !== "canonical" || isFlat) {
3419
+ const needsWrite = shape !== "canonical" || isFlat || hasMissingCanonicalFields(parsed);
3420
+ if (needsWrite) {
3321
3421
  writeProjectRecord(projectsDir, name, record);
3422
+ if (isFlat) {
3423
+ try {
3424
+ unlinkSync(flatPath);
3425
+ } catch {
3426
+ }
3427
+ }
3322
3428
  }
3323
3429
  return record;
3324
3430
  }
@@ -3347,9 +3453,23 @@ function listProjectRecords(projectsDir) {
3347
3453
  }
3348
3454
  return records;
3349
3455
  }
3456
+ var DEFAULT_STACK, DEFAULT_INFRA;
3350
3457
  var init_project_record = __esm({
3351
3458
  "src/engine/project-record.ts"() {
3352
3459
  "use strict";
3460
+ init_stack_detector();
3461
+ DEFAULT_STACK = {
3462
+ detected: "unknown",
3463
+ build_command: "",
3464
+ dev_command: "",
3465
+ test_command: "",
3466
+ install_command: "",
3467
+ dev_port: 3e3
3468
+ };
3469
+ DEFAULT_INFRA = {
3470
+ credentials: { mode: "factory" },
3471
+ state_backend: "auto"
3472
+ };
3353
3473
  }
3354
3474
  });
3355
3475
 
@@ -3668,7 +3788,7 @@ var init_api_routes = __esm({
3668
3788
  });
3669
3789
 
3670
3790
  // src/cli/ui/archival.ts
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";
3791
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync4, renameSync as renameSync2, readFileSync as readFileSync9, statSync as statSync3, writeFileSync as writeFileSync9, unlinkSync as unlinkSync2 } from "fs";
3672
3792
  import { join as join10 } from "path";
3673
3793
  function archiveOldRuns(runsDir, archiveAfterDays) {
3674
3794
  if (archiveAfterDays <= 0 || !existsSync12(runsDir)) return { archived: 0 };
@@ -3709,7 +3829,7 @@ function pinRun(runsDir, runId) {
3709
3829
  function unpinRun(runsDir, runId) {
3710
3830
  const pinFile = join10(runsDir, runId, "pinned");
3711
3831
  if (!existsSync12(pinFile)) return false;
3712
- unlinkSync(pinFile);
3832
+ unlinkSync2(pinFile);
3713
3833
  return true;
3714
3834
  }
3715
3835
  function isRunPinned(runsDir, runId) {
@@ -3738,7 +3858,7 @@ __export(inception_exports, {
3738
3858
  saveArtifact: () => saveArtifact,
3739
3859
  saveInceptionState: () => saveInceptionState
3740
3860
  });
3741
- import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync10, readdirSync as readdirSync5, unlinkSync as unlinkSync2 } from "fs";
3861
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10, readFileSync as readFileSync10, readdirSync as readdirSync5, unlinkSync as unlinkSync3 } from "fs";
3742
3862
  import { join as join11, dirname as dirname4 } from "path";
3743
3863
  import { fileURLToPath } from "url";
3744
3864
  function getMethodologiesDir() {
@@ -3869,10 +3989,10 @@ function migrateInceptionToSession(factoryDir, productName) {
3869
3989
  const src = join11(productDir, file);
3870
3990
  if (existsSync13(src)) {
3871
3991
  writeFileSync10(join11(sessionDir, file), readFileSync10(src, "utf-8"));
3872
- unlinkSync2(src);
3992
+ unlinkSync3(src);
3873
3993
  }
3874
3994
  }
3875
- unlinkSync2(oldInception);
3995
+ unlinkSync3(oldInception);
3876
3996
  return true;
3877
3997
  }
3878
3998
  function listProducts(factoryDir) {
@@ -5107,8 +5227,8 @@ var init_chat_handler = __esm({
5107
5227
  });
5108
5228
 
5109
5229
  // src/cli/ui/board-api-routes.ts
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";
5230
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync13, existsSync as existsSync16, readdirSync as readdirSync8, unlinkSync as unlinkSync4, mkdirSync as mkdirSync12, statSync as statSync6, rmSync as rmSync3 } from "fs";
5231
+ import { join as join14, basename as basename5, resolve as resolve4, dirname as dirname5 } from "path";
5112
5232
  import { homedir } from "os";
5113
5233
  import { randomUUID as randomUUID2 } from "crypto";
5114
5234
  import { execSync as execSync3, spawnSync } from "child_process";
@@ -5373,14 +5493,6 @@ function _gitHeadSha(repoPath) {
5373
5493
  return "no-git";
5374
5494
  }
5375
5495
  }
5376
- function _hasClaudeCli() {
5377
- try {
5378
- execSync3("which claude", { encoding: "utf-8" });
5379
- return true;
5380
- } catch {
5381
- return false;
5382
- }
5383
- }
5384
5496
  function getBoardRoutes(factoryDir) {
5385
5497
  return [
5386
5498
  // ── Status ──
@@ -6096,7 +6208,7 @@ function getBoardRoutes(factoryDir) {
6096
6208
  } else {
6097
6209
  throw new Error("Missing required field: provide either `path` (local) or `github_url` (clone)");
6098
6210
  }
6099
- const projectName = basename4(resolvedPath);
6211
+ const projectName = basename5(resolvedPath);
6100
6212
  const stack = detectStack(resolvedPath);
6101
6213
  let verifyPort = 3001;
6102
6214
  for (const existing of listProjectRecords(projectsDir)) {
@@ -6143,11 +6255,24 @@ function getBoardRoutes(factoryDir) {
6143
6255
  if (!hasSubDir && !hasFlat) throw new Error(`Project not found: ${name}`);
6144
6256
  if (hasSubDir) rmSync3(subDir, { recursive: true, force: true });
6145
6257
  else if (existsSync16(subDir)) rmSync3(subDir, { recursive: true, force: true });
6146
- if (hasFlat) unlinkSync3(flatPath);
6258
+ if (hasFlat) unlinkSync4(flatPath);
6147
6259
  return { success: true };
6148
6260
  }
6149
6261
  },
6150
6262
  {
6263
+ // Trigger brownfield analysis for a project.
6264
+ //
6265
+ // POST body: { tier?: "quick" | "standard" | "full" }
6266
+ // tier "quick" — writes a lightweight detectStack() placeholder, no Claude invocation.
6267
+ // tier "standard" / "full" (default) — delegates real analysis to the daemon via
6268
+ // a trigger file in the shared .beastmode volume. The daemon has access to the
6269
+ // project source files (/app/project) which the UI container does NOT have.
6270
+ // Query params: force=true — bypass cache and re-run even if HEAD hasn't changed.
6271
+ //
6272
+ // Returns immediately (analysis runs in daemon background):
6273
+ // { analyzing: true, project: string, job_id: string, status: "running" }
6274
+ // { analyzed: true, project: string, cached: true, analyzed_at: string } (cache hit)
6275
+ // { analyzed: true, project: string, tier: "quick" } (quick tier)
6151
6276
  method: "POST",
6152
6277
  pattern: "/api/projects/:name/analyze",
6153
6278
  handler: async (body, params, query) => {
@@ -6185,59 +6310,36 @@ Path: ${projectPath}
6185
6310
  }
6186
6311
  if (!force) {
6187
6312
  const meta = _readAnalyzeMeta(projectsDir, name);
6188
- const currentSha = _gitHeadSha(projectPath);
6189
- if (meta && meta.status === "complete" && meta.target_repo_head_sha === currentSha) {
6190
- return { analyzed: true, project: name, cached: true, analyzed_at: meta.analyzed_at };
6313
+ if (meta && meta.status === "complete") {
6314
+ const currentSha = _gitHeadSha(projectPath);
6315
+ if (currentSha === "no-git" || meta.target_repo_head_sha === currentSha) {
6316
+ return { analyzed: true, project: name, cached: true, analyzed_at: meta.analyzed_at };
6317
+ }
6191
6318
  }
6192
6319
  }
6193
6320
  const existingJob = _analyzeJobs.get(name);
6194
6321
  if (existingJob && existingJob.status === "running") {
6195
6322
  return { analyzing: true, project: name, job_id: existingJob.job_id, status: "running" };
6196
6323
  }
6197
- const writeMeta = (m) => {
6324
+ const triggerPath = join14(subDir, ".analyze-request.json");
6325
+ const inProgressPath = join14(subDir, ".analyze-in-progress.json");
6326
+ if (existsSync16(triggerPath) || existsSync16(inProgressPath)) {
6327
+ const existingTrigger = existsSync16(triggerPath) ? triggerPath : inProgressPath;
6198
6328
  try {
6199
- writeFileSync13(
6200
- join14(subDir, "codebase-guide.meta.json"),
6201
- JSON.stringify(m, null, 2) + "\n",
6202
- "utf-8"
6203
- );
6204
- } catch (err) {
6205
- console.error(`[analyze] Failed to write meta for ${name}:`, err);
6206
- }
6207
- };
6208
- if (!_hasClaudeCli()) {
6209
- const brownfieldPath = join14(subDir, "brownfield.md");
6210
- if (!existsSync16(brownfieldPath)) {
6211
- const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
6212
- let stackInfo = "Unknown stack";
6213
- try {
6214
- const stack = detectStackFn(projectPath);
6215
- const cmds = stack.suggested_commands;
6216
- stackInfo = `Framework: ${stack.framework || "unknown"}
6217
- Build: ${cmds?.build || "unknown"}
6218
- Dev: ${cmds?.dev || "unknown"}`;
6219
- } catch {
6220
- }
6221
- writeFileSync13(brownfieldPath, `# Brownfield Analysis: ${name}
6222
-
6223
- ${stackInfo}
6224
-
6225
- Path: ${projectPath}
6226
- `);
6329
+ const t = JSON.parse(readFileSync13(existingTrigger, "utf-8"));
6330
+ const job2 = {
6331
+ job_id: t.job_id,
6332
+ project: name,
6333
+ status: "running",
6334
+ started_at: t.requested_at
6335
+ };
6336
+ _analyzeJobs.set(name, job2);
6337
+ return { analyzing: true, project: name, job_id: t.job_id, status: "running" };
6338
+ } catch {
6227
6339
  }
6228
- writeMeta({
6229
- analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
6230
- target_repo_head_sha: _gitHeadSha(projectPath),
6231
- status: "failed",
6232
- error: "Claude CLI not available",
6233
- duration_seconds: 0
6234
- });
6235
- console.warn(`[analyze] Claude CLI unavailable for project ${name}; wrote detectStack() fallback.`);
6236
- return { analyzed: true, project: name, fallback: true };
6237
6340
  }
6238
6341
  const jobId = randomUUID2();
6239
6342
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
6240
- const startMs = Date.now();
6241
6343
  const job = {
6242
6344
  job_id: jobId,
6243
6345
  project: name,
@@ -6245,171 +6347,86 @@ Path: ${projectPath}
6245
6347
  started_at: startedAt
6246
6348
  };
6247
6349
  _analyzeJobs.set(name, job);
6248
- (async () => {
6249
- try {
6250
- const { spawn: spawn4 } = await import("child_process");
6251
- const prompt = [
6252
- "You are the Brownfield Analyst. Analyze the codebase at the current working directory.",
6253
- `Write your output in pyramid format (L0/L1/L2) to the directory: ${subDir}`,
6254
- "Produce exactly these four files:",
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)`,
6258
- "Do not write anything else outside the BEASTMODE_OUTPUT_DIR."
6259
- ].join("\n");
6260
- const isRoot = process.getuid?.() === 0;
6261
- const claudeArgs = [
6262
- "-p",
6263
- prompt,
6264
- "--agent",
6265
- "brownfield-analyst",
6266
- "--output-format",
6267
- "stream-json",
6268
- "--verbose",
6269
- "--dangerously-skip-permissions"
6270
- ];
6271
- const spawnCmd = isRoot ? "runuser" : "claude";
6272
- const spawnArgs = isRoot ? ["-u", "node", "--", "claude", ...claudeArgs] : claudeArgs;
6273
- const child = spawn4(spawnCmd, spawnArgs, {
6274
- cwd: projectPath,
6275
- env: {
6276
- ...process.env,
6277
- BEASTMODE_PROJECT: name,
6278
- BEASTMODE_OUTPUT_DIR: subDir,
6279
- ...isRoot ? { HOME: "/home/node" } : {}
6280
- },
6281
- stdio: ["ignore", "pipe", "pipe"]
6282
- });
6283
- child.stdout?.on("data", () => {
6284
- });
6285
- child.stderr?.on("data", () => {
6286
- });
6287
- child.on("close", (code) => {
6288
- const durationSeconds = (Date.now() - startMs) / 1e3;
6289
- const sha = _gitHeadSha(projectPath);
6290
- const guidePath = join14(subDir, "codebase-guide.md");
6291
- const ok = code === 0 && existsSync16(guidePath);
6292
- if (ok) {
6293
- writeMeta({
6294
- analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
6295
- analyst_version: "brownfield-analyst-v1",
6296
- target_repo_head_sha: sha,
6297
- project_name: name,
6298
- duration_seconds: durationSeconds,
6299
- status: "complete"
6300
- });
6301
- const legacy = join14(subDir, "brownfield.md");
6302
- if (existsSync16(legacy)) {
6303
- try {
6304
- unlinkSync3(legacy);
6305
- } catch {
6306
- }
6307
- }
6308
- _analyzeJobs.set(name, {
6309
- ...job,
6310
- status: "complete",
6311
- completed_at: (/* @__PURE__ */ new Date()).toISOString(),
6312
- duration_seconds: durationSeconds
6313
- });
6314
- } else {
6315
- const errMsg = `claude exited with code ${code}`;
6316
- writeMeta({
6317
- analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
6318
- target_repo_head_sha: sha,
6319
- status: "failed",
6320
- error: errMsg,
6321
- duration_seconds: durationSeconds
6322
- });
6323
- _analyzeJobs.set(name, {
6324
- ...job,
6325
- status: "failed",
6326
- completed_at: (/* @__PURE__ */ new Date()).toISOString(),
6327
- duration_seconds: durationSeconds,
6328
- error: errMsg
6329
- });
6330
- }
6331
- });
6332
- child.on("error", (err) => {
6333
- const durationSeconds = (Date.now() - startMs) / 1e3;
6334
- const sha = _gitHeadSha(projectPath);
6335
- writeMeta({
6336
- analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
6337
- target_repo_head_sha: sha,
6338
- status: "failed",
6339
- error: String(err),
6340
- duration_seconds: durationSeconds
6341
- });
6342
- _analyzeJobs.set(name, {
6343
- ...job,
6344
- status: "failed",
6345
- completed_at: (/* @__PURE__ */ new Date()).toISOString(),
6346
- duration_seconds: durationSeconds,
6347
- error: String(err)
6348
- });
6349
- });
6350
- } catch (err) {
6351
- const durationSeconds = (Date.now() - startMs) / 1e3;
6352
- const sha = _gitHeadSha(projectPath);
6353
- writeMeta({
6354
- analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
6355
- target_repo_head_sha: sha,
6356
- status: "failed",
6357
- error: String(err),
6358
- duration_seconds: durationSeconds
6359
- });
6360
- _analyzeJobs.set(name, {
6361
- ...job,
6362
- status: "failed",
6363
- completed_at: (/* @__PURE__ */ new Date()).toISOString(),
6364
- duration_seconds: durationSeconds,
6365
- error: String(err)
6366
- });
6367
- }
6368
- })();
6350
+ writeFileSync13(
6351
+ triggerPath,
6352
+ JSON.stringify({
6353
+ job_id: jobId,
6354
+ project_name: name,
6355
+ project_path: projectPath,
6356
+ requested_at: startedAt,
6357
+ force
6358
+ }, null, 2) + "\n",
6359
+ "utf-8"
6360
+ );
6369
6361
  return { analyzing: true, project: name, job_id: jobId, status: "running" };
6370
6362
  }
6371
6363
  },
6372
6364
  {
6365
+ // Poll the status of a background brownfield analysis job.
6366
+ //
6367
+ // Returns one of:
6368
+ // { status: "running", job_id: string, started_at: string }
6369
+ // { status: "complete", analyzed_at: string, duration_seconds: number, target_repo_head_sha: string }
6370
+ // { status: "cached", analyzed_at: string, duration_seconds: number, target_repo_head_sha: string }
6371
+ // { status: "failed", job_id?: string, error: string, started_at?: string, completed_at?: string }
6372
+ // { status: "idle" } — no analysis has been requested for this project
6373
6373
  method: "GET",
6374
6374
  pattern: "/api/projects/:name/analyze/status",
6375
6375
  handler: (_body, params) => {
6376
6376
  const { name } = params;
6377
6377
  assertSafeName(name);
6378
6378
  const projectsDir = join14(factoryDir, ".beastmode", "projects");
6379
- const job = _analyzeJobs.get(name);
6380
- const meta = _readAnalyzeMeta(projectsDir, name);
6381
- if (job && job.status === "running") {
6382
- return {
6383
- status: "running",
6384
- job_id: job.job_id,
6385
- started_at: job.started_at
6386
- };
6387
- }
6388
- if (job && job.status === "failed") {
6389
- return {
6390
- status: "failed",
6391
- job_id: job.job_id,
6392
- error: job.error,
6393
- started_at: job.started_at,
6394
- completed_at: job.completed_at,
6395
- duration_seconds: job.duration_seconds
6396
- };
6379
+ const subDir = join14(projectsDir, name);
6380
+ const triggerPath = join14(subDir, ".analyze-request.json");
6381
+ const inProgressPath = join14(subDir, ".analyze-in-progress.json");
6382
+ if (existsSync16(triggerPath) || existsSync16(inProgressPath)) {
6383
+ const job2 = _analyzeJobs.get(name);
6384
+ try {
6385
+ const p = existsSync16(triggerPath) ? triggerPath : inProgressPath;
6386
+ const t = JSON.parse(readFileSync13(p, "utf-8"));
6387
+ if (!job2 || job2.status !== "running") {
6388
+ _analyzeJobs.set(name, { job_id: t.job_id, project: name, status: "running", started_at: t.requested_at });
6389
+ }
6390
+ return { status: "running", job_id: t.job_id, started_at: t.requested_at };
6391
+ } catch {
6392
+ return { status: "running", job_id: job2?.job_id, started_at: job2?.started_at };
6393
+ }
6397
6394
  }
6395
+ const meta = _readAnalyzeMeta(projectsDir, name);
6398
6396
  if (meta && meta.status === "complete") {
6397
+ const job2 = _analyzeJobs.get(name);
6398
+ if (job2?.status === "running") {
6399
+ _analyzeJobs.set(name, { ...job2, status: "complete" });
6400
+ }
6399
6401
  return {
6400
- status: job?.status === "cached" ? "cached" : "complete",
6402
+ status: "complete",
6401
6403
  analyzed_at: meta.analyzed_at,
6402
6404
  duration_seconds: meta.duration_seconds,
6403
6405
  target_repo_head_sha: meta.target_repo_head_sha
6404
6406
  };
6405
6407
  }
6406
6408
  if (meta && meta.status === "failed") {
6409
+ const job2 = _analyzeJobs.get(name);
6410
+ if (job2?.status === "running") {
6411
+ _analyzeJobs.set(name, { ...job2, status: "failed", error: meta.error });
6412
+ }
6407
6413
  return {
6408
6414
  status: "failed",
6409
6415
  analyzed_at: meta.analyzed_at,
6410
6416
  error: meta.error
6411
6417
  };
6412
6418
  }
6419
+ const job = _analyzeJobs.get(name);
6420
+ if (job && job.status === "failed") {
6421
+ return {
6422
+ status: "failed",
6423
+ job_id: job.job_id,
6424
+ error: job.error,
6425
+ started_at: job.started_at,
6426
+ completed_at: job.completed_at,
6427
+ duration_seconds: job.duration_seconds
6428
+ };
6429
+ }
6413
6430
  return { status: "idle" };
6414
6431
  }
6415
6432
  },
@@ -7957,7 +7974,7 @@ __export(sync_claude_creds_exports, {
7957
7974
  });
7958
7975
  import { Command as Command2 } from "commander";
7959
7976
  import { execSync as execSync5, spawnSync as spawnSync2 } from "child_process";
7960
- import { writeFileSync as writeFileSync15, readFileSync as readFileSync16, chmodSync, mkdirSync as mkdirSync14, existsSync as existsSync19, unlinkSync as unlinkSync4 } from "fs";
7977
+ import { writeFileSync as writeFileSync15, readFileSync as readFileSync16, chmodSync, mkdirSync as mkdirSync14, existsSync as existsSync19, unlinkSync as unlinkSync5 } from "fs";
7961
7978
  import { join as join17 } from "path";
7962
7979
  import { homedir as homedir2, platform } from "os";
7963
7980
  function systemdUserDir() {
@@ -8030,7 +8047,7 @@ function removeUrgencyMarker(factoryDir) {
8030
8047
  if (!factoryDir) return;
8031
8048
  const markerPath = urgencyMarkerHostPath(factoryDir);
8032
8049
  try {
8033
- unlinkSync4(markerPath);
8050
+ unlinkSync5(markerPath);
8034
8051
  } catch {
8035
8052
  }
8036
8053
  }
@@ -8136,7 +8153,7 @@ function uninstallAgent() {
8136
8153
  warn(`launchctl bootout returned: ${result.stderr || result.stdout}`);
8137
8154
  }
8138
8155
  try {
8139
- unlinkSync4(plist);
8156
+ unlinkSync5(plist);
8140
8157
  } catch {
8141
8158
  }
8142
8159
  success(`LaunchAgent removed: ${LAUNCH_AGENT_LABEL}`);
@@ -8322,14 +8339,14 @@ function uninstallAgentLinux() {
8322
8339
  let removed = false;
8323
8340
  if (existsSync19(servicePath)) {
8324
8341
  try {
8325
- unlinkSync4(servicePath);
8342
+ unlinkSync5(servicePath);
8326
8343
  removed = true;
8327
8344
  } catch {
8328
8345
  }
8329
8346
  }
8330
8347
  if (existsSync19(timerPath)) {
8331
8348
  try {
8332
- unlinkSync4(timerPath);
8349
+ unlinkSync5(timerPath);
8333
8350
  removed = true;
8334
8351
  } catch {
8335
8352
  }
@@ -8507,7 +8524,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8507
8524
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8508
8525
  import { z as z2 } from "zod";
8509
8526
  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";
8527
+ import { join as join30, resolve as resolve18, basename as basename7 } from "path";
8511
8528
  import { randomUUID as randomUUID4 } from "crypto";
8512
8529
  function readJsonFile2(filePath) {
8513
8530
  if (!existsSync32(filePath)) return null;
@@ -8826,7 +8843,7 @@ function createMcpServer() {
8826
8843
  if (!existsSync32(resolvedPath)) {
8827
8844
  return { content: [{ type: "text", text: `Directory not found: ${resolvedPath}` }], isError: true };
8828
8845
  }
8829
- const projectName = basename6(resolvedPath);
8846
+ const projectName = basename7(resolvedPath);
8830
8847
  const stack = detectStack(resolvedPath);
8831
8848
  const projectsDir = join30(factoryDir, ".beastmode", "projects");
8832
8849
  let verifyPort = 3001;
@@ -8944,7 +8961,7 @@ init_engine();
8944
8961
  init_file_writer();
8945
8962
  import { Command as Command3 } from "commander";
8946
8963
  import inquirer from "inquirer";
8947
- import { resolve as resolve6, basename as basename5, join as join18 } from "path";
8964
+ import { resolve as resolve6, basename as basename6, join as join18 } from "path";
8948
8965
  import { existsSync as existsSync20, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync17 } from "fs";
8949
8966
 
8950
8967
  // src/cli/utils/docker.ts
@@ -9205,7 +9222,7 @@ function isSourceRepo(dir) {
9205
9222
  async function runInit(name, opts) {
9206
9223
  if (opts.ui) {
9207
9224
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
9208
- const factoryName2 = name || basename5(resolve6("."));
9225
+ const factoryName2 = name || basename6(resolve6("."));
9209
9226
  const projectPath2 = opts.project ? resolve6(opts.project) : void 0;
9210
9227
  info("Starting init wizard...");
9211
9228
  const uiServer = await startServer2({
@@ -9238,13 +9255,13 @@ async function runInit(name, opts) {
9238
9255
  const totalSteps = 5;
9239
9256
  header("BeastMode \u2014 Dark Factory Init");
9240
9257
  info("Creating your Custom Dark Factory\n");
9241
- const factoryName = name || basename5(resolve6("."));
9258
+ const factoryName = name || basename6(resolve6("."));
9242
9259
  if (existsSync20(factoryName) && existsSync20(resolve6(factoryName, ".beastmode"))) {
9243
9260
  throw new Error(`Factory already exists at ./${factoryName}. Use 'beastmode config' to modify.`);
9244
9261
  }
9245
9262
  if (opts.from) {
9246
- const { readFileSync: readFileSync37 } = await import("fs");
9247
- const templateContent = readFileSync37(resolve6(opts.from), "utf-8");
9263
+ const { readFileSync: readFileSync36 } = await import("fs");
9264
+ const templateContent = readFileSync36(resolve6(opts.from), "utf-8");
9248
9265
  const { parseTemplate: parseTemplate2 } = await Promise.resolve().then(() => (init_template_importer(), template_importer_exports));
9249
9266
  const template = parseTemplate2(templateContent);
9250
9267
  info(`Importing from template: ${opts.from}`);
@@ -9254,7 +9271,7 @@ async function runInit(name, opts) {
9254
9271
  }
9255
9272
  const templateProjectPath = resolve6(opts.project);
9256
9273
  const templateStack = detectStack(templateProjectPath);
9257
- const templateProjectName = basename5(templateProjectPath);
9274
+ const templateProjectName = basename6(templateProjectPath);
9258
9275
  const actions2 = scaffoldFactory(factoryName, template.config, {
9259
9276
  name: templateProjectName,
9260
9277
  repo: templateStack.git_remote || void 0,
@@ -9449,7 +9466,7 @@ async function runInit(name, opts) {
9449
9466
  success("Board UI password set");
9450
9467
  }
9451
9468
  step(5, totalSteps, "Boot");
9452
- const projectName = basename5(projectPath);
9469
+ const projectName = basename6(projectPath);
9453
9470
  const actions = scaffoldFactory(factoryName, config, {
9454
9471
  name: projectName,
9455
9472
  repo: stack.git_remote || void 0,
@@ -12161,10 +12178,10 @@ async function runMigrate(opts) {
12161
12178
  let runDirs = [];
12162
12179
  const checkpoints = /* @__PURE__ */ new Map();
12163
12180
  if (existsSync29(runsDir)) {
12164
- const { readdirSync: readdirSync14 } = await import("fs");
12165
- runDirs = readdirSync14(runsDir).filter((d) => {
12181
+ const { readdirSync: readdirSync13 } = await import("fs");
12182
+ runDirs = readdirSync13(runsDir).filter((d) => {
12166
12183
  try {
12167
- return readdirSync14(join27(runsDir, d)).length > 0;
12184
+ return readdirSync13(join27(runsDir, d)).length > 0;
12168
12185
  } catch {
12169
12186
  return false;
12170
12187
  }
@@ -12272,6 +12289,7 @@ init_display();
12272
12289
  init_board();
12273
12290
  init_bridge();
12274
12291
  init_schemas();
12292
+ init_engine();
12275
12293
  import { Command as Command15 } from "commander";
12276
12294
  import { join as join28 } from "path";
12277
12295
  import { existsSync as existsSync30, readFileSync as readFileSync27, writeFileSync as writeFileSync23, mkdirSync as mkdirSync18 } from "fs";
@@ -12305,25 +12323,16 @@ async function runPipeline(projectName, opts) {
12305
12323
  );
12306
12324
  let projectConfig = null;
12307
12325
  const projectsDir = join28(bmDir, "projects");
12308
- if (existsSync30(projectsDir)) {
12309
- const { readdirSync: readdirSync14 } = await import("fs");
12310
- const projectFiles = readdirSync14(projectsDir).filter(
12311
- (f) => f.endsWith(".json")
12312
- );
12313
- if (projectName) {
12314
- const file = projectFiles.find(
12315
- (f) => f === `${projectName}.json`
12316
- );
12317
- if (!file) {
12318
- throw new Error(`Project not found: ${projectName}`);
12319
- }
12320
- projectConfig = ProjectConfigSchema.parse(
12321
- JSON.parse(readFileSync27(join28(projectsDir, file), "utf-8"))
12322
- );
12323
- } else if (projectFiles.length > 0) {
12324
- projectConfig = ProjectConfigSchema.parse(
12325
- JSON.parse(readFileSync27(join28(projectsDir, projectFiles[0]), "utf-8"))
12326
- );
12326
+ if (projectName) {
12327
+ const record = readProjectRecord(projectsDir, projectName);
12328
+ if (!record) {
12329
+ throw new Error(`Project not found: ${projectName}`);
12330
+ }
12331
+ projectConfig = record;
12332
+ } else {
12333
+ const projects = listProjectRecords(projectsDir);
12334
+ if (projects.length > 0) {
12335
+ projectConfig = projects[0];
12327
12336
  info(`Using project: ${projectConfig.name}`);
12328
12337
  }
12329
12338
  }
@@ -12450,6 +12459,7 @@ init_display();
12450
12459
  init_board();
12451
12460
  init_bridge();
12452
12461
  init_schemas();
12462
+ init_engine();
12453
12463
  import { Command as Command16 } from "commander";
12454
12464
  import { join as join29 } from "path";
12455
12465
  import { existsSync as existsSync31, readFileSync as readFileSync28, writeFileSync as writeFileSync24, mkdirSync as mkdirSync19 } from "fs";
@@ -12483,17 +12493,10 @@ async function runDaemon(opts) {
12483
12493
  );
12484
12494
  let projectConfig = null;
12485
12495
  const projectsDir = join29(bmDir, "projects");
12486
- if (existsSync31(projectsDir)) {
12487
- const { readdirSync: readdirSync14 } = await import("fs");
12488
- const projectFiles = readdirSync14(projectsDir).filter(
12489
- (f) => f.endsWith(".json")
12490
- );
12491
- if (projectFiles.length > 0) {
12492
- projectConfig = ProjectConfigSchema.parse(
12493
- JSON.parse(readFileSync28(join29(projectsDir, projectFiles[0]), "utf-8"))
12494
- );
12495
- info(`Using project: ${projectConfig.name}`);
12496
- }
12496
+ const projects = listProjectRecords(projectsDir);
12497
+ if (projects.length > 0) {
12498
+ projectConfig = projects[0];
12499
+ info(`Using project: ${projectConfig.name}`);
12497
12500
  }
12498
12501
  const cacheDir = join29(bmDir, ".cache");
12499
12502
  mkdirSync19(cacheDir, { recursive: true });
@@ -12564,8 +12567,8 @@ async function runDaemon(opts) {
12564
12567
  const exitCode = await new Promise((resolvePromise) => {
12565
12568
  child.on("exit", (code) => {
12566
12569
  try {
12567
- const { unlinkSync: unlinkSync7 } = __require("fs");
12568
- unlinkSync7(pidFile);
12570
+ const { unlinkSync: unlinkSync8 } = __require("fs");
12571
+ unlinkSync8(pidFile);
12569
12572
  } catch {
12570
12573
  }
12571
12574
  resolvePromise(code ?? 1);
@@ -13201,11 +13204,12 @@ var updateCommand = new Command22("update").description("Pull latest BeastMode i
13201
13204
  });
13202
13205
 
13203
13206
  // src/cli/commands/runner-cmd.ts
13207
+ init_engine();
13204
13208
  init_display();
13205
13209
  import { Command as Command23 } from "commander";
13206
13210
  import { spawn as spawn3 } from "child_process";
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";
13211
+ import { existsSync as existsSync38 } from "fs";
13212
+ import { basename as basename8, join as join37, resolve as resolve20 } from "path";
13209
13213
 
13210
13214
  // src/cli/runner-image-builder.ts
13211
13215
  import { execSync as execSync10 } from "child_process";
@@ -13791,7 +13795,7 @@ import {
13791
13795
  createWriteStream,
13792
13796
  existsSync as existsSync36,
13793
13797
  mkdirSync as mkdirSync22,
13794
- unlinkSync as unlinkSync5,
13798
+ unlinkSync as unlinkSync6,
13795
13799
  writeFileSync as writeFileSync29
13796
13800
  } from "fs";
13797
13801
  import { homedir as homedir4 } from "os";
@@ -13859,7 +13863,7 @@ async function downloadAndExtractRunner(installDir, os, arch) {
13859
13863
  );
13860
13864
  }
13861
13865
  try {
13862
- unlinkSync5(tarball);
13866
+ unlinkSync6(tarball);
13863
13867
  } catch {
13864
13868
  }
13865
13869
  if (os === "darwin") {
@@ -14097,7 +14101,7 @@ import {
14097
14101
  existsSync as existsSync37,
14098
14102
  mkdirSync as mkdirSync23,
14099
14103
  readFileSync as readFileSync34,
14100
- unlinkSync as unlinkSync6,
14104
+ unlinkSync as unlinkSync7,
14101
14105
  writeFileSync as writeFileSync30
14102
14106
  } from "fs";
14103
14107
  import { dirname as dirname9, join as join36 } from "path";
@@ -14189,7 +14193,7 @@ async function restoreWorkflows(projectDir) {
14189
14193
  jobCount: fileState.originals.length
14190
14194
  });
14191
14195
  }
14192
- unlinkSync6(statePath);
14196
+ unlinkSync7(statePath);
14193
14197
  return { nothingToRestore: false, files: resultFiles };
14194
14198
  }
14195
14199
 
@@ -14199,22 +14203,16 @@ function resolveProjectName(projectDir) {
14199
14203
  const projectsDir = join37(projectDir, ".beastmode", "projects");
14200
14204
  if (existsSync38(projectsDir)) {
14201
14205
  try {
14202
- for (const entry of readdirSync13(projectsDir)) {
14203
- if (!entry.endsWith(".json")) continue;
14204
- const file = join37(projectsDir, entry);
14205
- try {
14206
- const raw = readFileSync35(file, "utf-8");
14207
- const data = JSON.parse(raw);
14208
- if (typeof data.path === "string" && resolve20(data.path) === resolve20(projectDir) && typeof data.name === "string" && data.name.length > 0) {
14209
- return data.name;
14210
- }
14211
- } catch {
14212
- }
14213
- }
14206
+ const records = listProjectRecords(projectsDir);
14207
+ const target = resolve20(projectDir);
14208
+ const match = records.find(
14209
+ (r) => typeof r.path === "string" && resolve20(r.path) === target
14210
+ );
14211
+ if (match && match.name) return match.name;
14214
14212
  } catch {
14215
14213
  }
14216
14214
  }
14217
- return basename7(resolve20(projectDir));
14215
+ return basename8(resolve20(projectDir));
14218
14216
  }
14219
14217
  async function runnerSetupAction(opts) {
14220
14218
  if (opts.service !== void 0 && !opts.native) {
@@ -14589,8 +14587,8 @@ runnerCommand.command("build-image").description(
14589
14587
  // src/cli/commands/project-cmd.ts
14590
14588
  init_engine();
14591
14589
  import { Command as Command24 } from "commander";
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";
14590
+ import { existsSync as existsSync39, mkdirSync as mkdirSync24, readFileSync as readFileSync35, renameSync as renameSync3 } from "fs";
14591
+ import { join as join38, resolve as resolve21, basename as basename9 } from "path";
14594
14592
  import { execSync as execSync13 } from "child_process";
14595
14593
  var DEFAULT_MAX_PROJECTS = 5;
14596
14594
  var MIN_DISK_WARNING_GB = 10;
@@ -14598,7 +14596,7 @@ function getMaxProjects(factoryDir) {
14598
14596
  const configPath = join38(factoryDir, ".beastmode", "config.json");
14599
14597
  if (existsSync39(configPath)) {
14600
14598
  try {
14601
- const config = JSON.parse(readFileSync36(configPath, "utf-8"));
14599
+ const config = JSON.parse(readFileSync35(configPath, "utf-8"));
14602
14600
  if (typeof config.max_projects === "number") return config.max_projects;
14603
14601
  } catch {
14604
14602
  }
@@ -14618,7 +14616,7 @@ function getFreeDiskGB() {
14618
14616
  function projectAddAction(factoryDir, projectPath, opts) {
14619
14617
  const resolvedPath = resolve21(projectPath);
14620
14618
  if (!existsSync39(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
14621
- const projectName = opts.name || basename8(resolvedPath);
14619
+ const projectName = opts.name || basename9(resolvedPath);
14622
14620
  const projectsDir = join38(factoryDir, ".beastmode", "projects");
14623
14621
  if (existsSync39(join38(projectsDir, projectName, "project.json"))) {
14624
14622
  throw new Error(`Project already exists: ${projectName}`);
@@ -14709,7 +14707,7 @@ var projectCommand = new Command24("project").description("Manage projects in th
14709
14707
  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) => {
14710
14708
  const factoryDir = resolve21(".");
14711
14709
  projectAddAction(factoryDir, path, opts);
14712
- console.log(`Project registered: ${opts.name || basename8(resolve21(path))}`);
14710
+ console.log(`Project registered: ${opts.name || basename9(resolve21(path))}`);
14713
14711
  });
14714
14712
  projectCommand.command("list").description("List registered projects").action(() => {
14715
14713
  const projects = projectListAction(resolve21("."));