@beastmode-develeap/beastmode 0.1.267 → 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
@@ -3223,8 +3223,18 @@ var init_bridge = __esm({
3223
3223
  });
3224
3224
 
3225
3225
  // src/engine/project-record.ts
3226
- import { existsSync as existsSync8, writeFileSync as writeFileSync6, renameSync, readFileSync as readFileSync8, readdirSync as readdirSync3, mkdirSync as mkdirSync6 } from "fs";
3227
- import { join as join8 } from "path";
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
+ }
3228
3238
  function detectShape(parsed) {
3229
3239
  if (parsed.github && typeof parsed.github === "object" && parsed.slots && typeof parsed.slots === "object") {
3230
3240
  return "canonical";
@@ -3234,10 +3244,34 @@ function detectShape(parsed) {
3234
3244
  }
3235
3245
  return "http-legacy";
3236
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
+ }
3237
3263
  function normalizeToCanonical(parsed, name) {
3238
3264
  const shape = detectShape(parsed);
3265
+ const existingDeploy = parsed.deploy;
3239
3266
  if (shape === "canonical") {
3240
- 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
+ };
3241
3275
  }
3242
3276
  if (shape === "cli-legacy") {
3243
3277
  const github = parsed.github;
@@ -3253,10 +3287,13 @@ function normalizeToCanonical(parsed, name) {
3253
3287
  url: "http://127.0.0.1:8080",
3254
3288
  auto_created: true
3255
3289
  },
3256
- deploy: parsed.deploy || { verify_port: 3001 },
3290
+ stack: parsed.stack || DEFAULT_STACK,
3291
+ deploy: normalizeDeploy(existingDeploy),
3257
3292
  pipeline: parsed.pipeline || {},
3258
3293
  models: parsed.models || {},
3294
+ plugins: parsed.plugins || [],
3259
3295
  slots: { max: null },
3296
+ infra: parsed.infra || DEFAULT_INFRA,
3260
3297
  registered_at: parsed.registered_at || (/* @__PURE__ */ new Date()).toISOString()
3261
3298
  };
3262
3299
  }
@@ -3264,34 +3301,74 @@ function normalizeToCanonical(parsed, name) {
3264
3301
  name: parsed.name || name,
3265
3302
  path: parsed.path || "",
3266
3303
  github: {
3267
- repo: parsed.repo || "",
3304
+ repo: parsed.repo || parsed.github?.repo || "",
3268
3305
  default_branch: "main"
3269
3306
  },
3270
- board: { id: null, url: "http://127.0.0.1:8080", auto_created: true },
3271
- 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),
3272
3314
  pipeline: parsed.pipeline || {},
3273
3315
  models: parsed.models || {},
3274
- slots: { max: null },
3275
- 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()
3276
3320
  };
3277
3321
  }
3278
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);
3279
3349
  return {
3280
- name: input.name,
3350
+ name,
3281
3351
  path: input.resolvedPath,
3282
3352
  github: {
3283
- repo: input.gitRemote || "",
3353
+ repo: gitRemote || "",
3284
3354
  default_branch: "main"
3285
3355
  },
3286
3356
  board: {
3287
- id: input.boardId ?? null,
3357
+ id: boardId,
3288
3358
  url: process.env.BEASTMODE_BOARD_URL || "http://127.0.0.1:8080",
3289
- auto_created: !input.boardId
3359
+ auto_created: boardId === null
3360
+ },
3361
+ stack,
3362
+ deploy: {
3363
+ target: "pr-only",
3364
+ verify_port: verifyPort,
3365
+ config: {}
3290
3366
  },
3291
- deploy: { verify_port: input.verifyPort ?? 3001 },
3292
3367
  pipeline: {},
3293
3368
  models: {},
3369
+ plugins: [],
3294
3370
  slots: { max: null },
3371
+ infra: { credentials: { mode: "factory" }, state_backend: "auto" },
3295
3372
  registered_at: (/* @__PURE__ */ new Date()).toISOString()
3296
3373
  };
3297
3374
  }
@@ -3339,8 +3416,15 @@ function readProjectRecord(projectsDir, name) {
3339
3416
  }
3340
3417
  const shape = detectShape(parsed);
3341
3418
  const record = normalizeToCanonical(parsed, name);
3342
- if (shape !== "canonical" || isFlat) {
3419
+ const needsWrite = shape !== "canonical" || isFlat || hasMissingCanonicalFields(parsed);
3420
+ if (needsWrite) {
3343
3421
  writeProjectRecord(projectsDir, name, record);
3422
+ if (isFlat) {
3423
+ try {
3424
+ unlinkSync(flatPath);
3425
+ } catch {
3426
+ }
3427
+ }
3344
3428
  }
3345
3429
  return record;
3346
3430
  }
@@ -3369,9 +3453,23 @@ function listProjectRecords(projectsDir) {
3369
3453
  }
3370
3454
  return records;
3371
3455
  }
3456
+ var DEFAULT_STACK, DEFAULT_INFRA;
3372
3457
  var init_project_record = __esm({
3373
3458
  "src/engine/project-record.ts"() {
3374
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
+ };
3375
3473
  }
3376
3474
  });
3377
3475
 
@@ -3690,7 +3788,7 @@ var init_api_routes = __esm({
3690
3788
  });
3691
3789
 
3692
3790
  // src/cli/ui/archival.ts
3693
- import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync4, renameSync as renameSync2, readFileSync as readFileSync9, statSync as statSync3, writeFileSync as writeFileSync9, unlinkSync } from "fs";
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";
3694
3792
  import { join as join10 } from "path";
3695
3793
  function archiveOldRuns(runsDir, archiveAfterDays) {
3696
3794
  if (archiveAfterDays <= 0 || !existsSync12(runsDir)) return { archived: 0 };
@@ -3731,7 +3829,7 @@ function pinRun(runsDir, runId) {
3731
3829
  function unpinRun(runsDir, runId) {
3732
3830
  const pinFile = join10(runsDir, runId, "pinned");
3733
3831
  if (!existsSync12(pinFile)) return false;
3734
- unlinkSync(pinFile);
3832
+ unlinkSync2(pinFile);
3735
3833
  return true;
3736
3834
  }
3737
3835
  function isRunPinned(runsDir, runId) {
@@ -3760,7 +3858,7 @@ __export(inception_exports, {
3760
3858
  saveArtifact: () => saveArtifact,
3761
3859
  saveInceptionState: () => saveInceptionState
3762
3860
  });
3763
- 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";
3764
3862
  import { join as join11, dirname as dirname4 } from "path";
3765
3863
  import { fileURLToPath } from "url";
3766
3864
  function getMethodologiesDir() {
@@ -3891,10 +3989,10 @@ function migrateInceptionToSession(factoryDir, productName) {
3891
3989
  const src = join11(productDir, file);
3892
3990
  if (existsSync13(src)) {
3893
3991
  writeFileSync10(join11(sessionDir, file), readFileSync10(src, "utf-8"));
3894
- unlinkSync2(src);
3992
+ unlinkSync3(src);
3895
3993
  }
3896
3994
  }
3897
- unlinkSync2(oldInception);
3995
+ unlinkSync3(oldInception);
3898
3996
  return true;
3899
3997
  }
3900
3998
  function listProducts(factoryDir) {
@@ -5129,8 +5227,8 @@ var init_chat_handler = __esm({
5129
5227
  });
5130
5228
 
5131
5229
  // src/cli/ui/board-api-routes.ts
5132
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync13, existsSync as existsSync16, readdirSync as readdirSync8, unlinkSync as unlinkSync3, mkdirSync as mkdirSync12, statSync as statSync6, rmSync as rmSync3 } from "fs";
5133
- import { join as join14, basename as basename4, resolve as resolve4, dirname as dirname5 } from "path";
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";
5134
5232
  import { homedir } from "os";
5135
5233
  import { randomUUID as randomUUID2 } from "crypto";
5136
5234
  import { execSync as execSync3, spawnSync } from "child_process";
@@ -5395,14 +5493,6 @@ function _gitHeadSha(repoPath) {
5395
5493
  return "no-git";
5396
5494
  }
5397
5495
  }
5398
- function _hasClaudeCli() {
5399
- try {
5400
- execSync3("which claude", { encoding: "utf-8" });
5401
- return true;
5402
- } catch {
5403
- return false;
5404
- }
5405
- }
5406
5496
  function getBoardRoutes(factoryDir) {
5407
5497
  return [
5408
5498
  // ── Status ──
@@ -6118,7 +6208,7 @@ function getBoardRoutes(factoryDir) {
6118
6208
  } else {
6119
6209
  throw new Error("Missing required field: provide either `path` (local) or `github_url` (clone)");
6120
6210
  }
6121
- const projectName = basename4(resolvedPath);
6211
+ const projectName = basename5(resolvedPath);
6122
6212
  const stack = detectStack(resolvedPath);
6123
6213
  let verifyPort = 3001;
6124
6214
  for (const existing of listProjectRecords(projectsDir)) {
@@ -6165,11 +6255,24 @@ function getBoardRoutes(factoryDir) {
6165
6255
  if (!hasSubDir && !hasFlat) throw new Error(`Project not found: ${name}`);
6166
6256
  if (hasSubDir) rmSync3(subDir, { recursive: true, force: true });
6167
6257
  else if (existsSync16(subDir)) rmSync3(subDir, { recursive: true, force: true });
6168
- if (hasFlat) unlinkSync3(flatPath);
6258
+ if (hasFlat) unlinkSync4(flatPath);
6169
6259
  return { success: true };
6170
6260
  }
6171
6261
  },
6172
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)
6173
6276
  method: "POST",
6174
6277
  pattern: "/api/projects/:name/analyze",
6175
6278
  handler: async (body, params, query) => {
@@ -6207,59 +6310,36 @@ Path: ${projectPath}
6207
6310
  }
6208
6311
  if (!force) {
6209
6312
  const meta = _readAnalyzeMeta(projectsDir, name);
6210
- const currentSha = _gitHeadSha(projectPath);
6211
- if (meta && meta.status === "complete" && meta.target_repo_head_sha === currentSha) {
6212
- 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
+ }
6213
6318
  }
6214
6319
  }
6215
6320
  const existingJob = _analyzeJobs.get(name);
6216
6321
  if (existingJob && existingJob.status === "running") {
6217
6322
  return { analyzing: true, project: name, job_id: existingJob.job_id, status: "running" };
6218
6323
  }
6219
- 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;
6220
6328
  try {
6221
- writeFileSync13(
6222
- join14(subDir, "codebase-guide.meta.json"),
6223
- JSON.stringify(m, null, 2) + "\n",
6224
- "utf-8"
6225
- );
6226
- } catch (err) {
6227
- console.error(`[analyze] Failed to write meta for ${name}:`, err);
6228
- }
6229
- };
6230
- if (!_hasClaudeCli()) {
6231
- const brownfieldPath = join14(subDir, "brownfield.md");
6232
- if (!existsSync16(brownfieldPath)) {
6233
- const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
6234
- let stackInfo = "Unknown stack";
6235
- try {
6236
- const stack = detectStackFn(projectPath);
6237
- const cmds = stack.suggested_commands;
6238
- stackInfo = `Framework: ${stack.framework || "unknown"}
6239
- Build: ${cmds?.build || "unknown"}
6240
- Dev: ${cmds?.dev || "unknown"}`;
6241
- } catch {
6242
- }
6243
- writeFileSync13(brownfieldPath, `# Brownfield Analysis: ${name}
6244
-
6245
- ${stackInfo}
6246
-
6247
- Path: ${projectPath}
6248
- `);
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 {
6249
6339
  }
6250
- writeMeta({
6251
- analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
6252
- target_repo_head_sha: _gitHeadSha(projectPath),
6253
- status: "failed",
6254
- error: "Claude CLI not available",
6255
- duration_seconds: 0
6256
- });
6257
- console.warn(`[analyze] Claude CLI unavailable for project ${name}; wrote detectStack() fallback.`);
6258
- return { analyzed: true, project: name, fallback: true };
6259
6340
  }
6260
6341
  const jobId = randomUUID2();
6261
6342
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
6262
- const startMs = Date.now();
6263
6343
  const job = {
6264
6344
  job_id: jobId,
6265
6345
  project: name,
@@ -6267,171 +6347,86 @@ Path: ${projectPath}
6267
6347
  started_at: startedAt
6268
6348
  };
6269
6349
  _analyzeJobs.set(name, job);
6270
- (async () => {
6271
- try {
6272
- const { spawn: spawn4 } = await import("child_process");
6273
- const prompt = [
6274
- "You are the Brownfield Analyst. Analyze the codebase at the current working directory.",
6275
- `Write your output in pyramid format (L0/L1/L2) to the directory: ${subDir}`,
6276
- "Produce exactly these four files:",
6277
- ` - ${join14(subDir, "codebase-guide.md")} (full L2 analysis: architecture, conventions, safe modification points, fragile areas)`,
6278
- ` - ${join14(subDir, "codebase-guide.l1.md")} (paragraph summary only)`,
6279
- ` - ${join14(subDir, "codebase-guide.l0.txt")} (one-sentence summary only)`,
6280
- "Do not write anything else outside the BEASTMODE_OUTPUT_DIR."
6281
- ].join("\n");
6282
- const isRoot = process.getuid?.() === 0;
6283
- const claudeArgs = [
6284
- "-p",
6285
- prompt,
6286
- "--agent",
6287
- "brownfield-analyst",
6288
- "--output-format",
6289
- "stream-json",
6290
- "--verbose",
6291
- "--dangerously-skip-permissions"
6292
- ];
6293
- const spawnCmd = isRoot ? "runuser" : "claude";
6294
- const spawnArgs = isRoot ? ["-u", "node", "--", "claude", ...claudeArgs] : claudeArgs;
6295
- const child = spawn4(spawnCmd, spawnArgs, {
6296
- cwd: projectPath,
6297
- env: {
6298
- ...process.env,
6299
- BEASTMODE_PROJECT: name,
6300
- BEASTMODE_OUTPUT_DIR: subDir,
6301
- ...isRoot ? { HOME: "/home/node" } : {}
6302
- },
6303
- stdio: ["ignore", "pipe", "pipe"]
6304
- });
6305
- child.stdout?.on("data", () => {
6306
- });
6307
- child.stderr?.on("data", () => {
6308
- });
6309
- child.on("close", (code) => {
6310
- const durationSeconds = (Date.now() - startMs) / 1e3;
6311
- const sha = _gitHeadSha(projectPath);
6312
- const guidePath = join14(subDir, "codebase-guide.md");
6313
- const ok = code === 0 && existsSync16(guidePath);
6314
- if (ok) {
6315
- writeMeta({
6316
- analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
6317
- analyst_version: "brownfield-analyst-v1",
6318
- target_repo_head_sha: sha,
6319
- project_name: name,
6320
- duration_seconds: durationSeconds,
6321
- status: "complete"
6322
- });
6323
- const legacy = join14(subDir, "brownfield.md");
6324
- if (existsSync16(legacy)) {
6325
- try {
6326
- unlinkSync3(legacy);
6327
- } catch {
6328
- }
6329
- }
6330
- _analyzeJobs.set(name, {
6331
- ...job,
6332
- status: "complete",
6333
- completed_at: (/* @__PURE__ */ new Date()).toISOString(),
6334
- duration_seconds: durationSeconds
6335
- });
6336
- } else {
6337
- const errMsg = `claude exited with code ${code}`;
6338
- writeMeta({
6339
- analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
6340
- target_repo_head_sha: sha,
6341
- status: "failed",
6342
- error: errMsg,
6343
- duration_seconds: durationSeconds
6344
- });
6345
- _analyzeJobs.set(name, {
6346
- ...job,
6347
- status: "failed",
6348
- completed_at: (/* @__PURE__ */ new Date()).toISOString(),
6349
- duration_seconds: durationSeconds,
6350
- error: errMsg
6351
- });
6352
- }
6353
- });
6354
- child.on("error", (err) => {
6355
- const durationSeconds = (Date.now() - startMs) / 1e3;
6356
- const sha = _gitHeadSha(projectPath);
6357
- writeMeta({
6358
- analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
6359
- target_repo_head_sha: sha,
6360
- status: "failed",
6361
- error: String(err),
6362
- duration_seconds: durationSeconds
6363
- });
6364
- _analyzeJobs.set(name, {
6365
- ...job,
6366
- status: "failed",
6367
- completed_at: (/* @__PURE__ */ new Date()).toISOString(),
6368
- duration_seconds: durationSeconds,
6369
- error: String(err)
6370
- });
6371
- });
6372
- } catch (err) {
6373
- const durationSeconds = (Date.now() - startMs) / 1e3;
6374
- const sha = _gitHeadSha(projectPath);
6375
- writeMeta({
6376
- analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
6377
- target_repo_head_sha: sha,
6378
- status: "failed",
6379
- error: String(err),
6380
- duration_seconds: durationSeconds
6381
- });
6382
- _analyzeJobs.set(name, {
6383
- ...job,
6384
- status: "failed",
6385
- completed_at: (/* @__PURE__ */ new Date()).toISOString(),
6386
- duration_seconds: durationSeconds,
6387
- error: String(err)
6388
- });
6389
- }
6390
- })();
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
+ );
6391
6361
  return { analyzing: true, project: name, job_id: jobId, status: "running" };
6392
6362
  }
6393
6363
  },
6394
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
6395
6373
  method: "GET",
6396
6374
  pattern: "/api/projects/:name/analyze/status",
6397
6375
  handler: (_body, params) => {
6398
6376
  const { name } = params;
6399
6377
  assertSafeName(name);
6400
6378
  const projectsDir = join14(factoryDir, ".beastmode", "projects");
6401
- const job = _analyzeJobs.get(name);
6402
- const meta = _readAnalyzeMeta(projectsDir, name);
6403
- if (job && job.status === "running") {
6404
- return {
6405
- status: "running",
6406
- job_id: job.job_id,
6407
- started_at: job.started_at
6408
- };
6409
- }
6410
- if (job && job.status === "failed") {
6411
- return {
6412
- status: "failed",
6413
- job_id: job.job_id,
6414
- error: job.error,
6415
- started_at: job.started_at,
6416
- completed_at: job.completed_at,
6417
- duration_seconds: job.duration_seconds
6418
- };
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
+ }
6419
6394
  }
6395
+ const meta = _readAnalyzeMeta(projectsDir, name);
6420
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
+ }
6421
6401
  return {
6422
- status: job?.status === "cached" ? "cached" : "complete",
6402
+ status: "complete",
6423
6403
  analyzed_at: meta.analyzed_at,
6424
6404
  duration_seconds: meta.duration_seconds,
6425
6405
  target_repo_head_sha: meta.target_repo_head_sha
6426
6406
  };
6427
6407
  }
6428
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
+ }
6429
6413
  return {
6430
6414
  status: "failed",
6431
6415
  analyzed_at: meta.analyzed_at,
6432
6416
  error: meta.error
6433
6417
  };
6434
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
+ }
6435
6430
  return { status: "idle" };
6436
6431
  }
6437
6432
  },
@@ -7979,7 +7974,7 @@ __export(sync_claude_creds_exports, {
7979
7974
  });
7980
7975
  import { Command as Command2 } from "commander";
7981
7976
  import { execSync as execSync5, spawnSync as spawnSync2 } from "child_process";
7982
- 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";
7983
7978
  import { join as join17 } from "path";
7984
7979
  import { homedir as homedir2, platform } from "os";
7985
7980
  function systemdUserDir() {
@@ -8052,7 +8047,7 @@ function removeUrgencyMarker(factoryDir) {
8052
8047
  if (!factoryDir) return;
8053
8048
  const markerPath = urgencyMarkerHostPath(factoryDir);
8054
8049
  try {
8055
- unlinkSync4(markerPath);
8050
+ unlinkSync5(markerPath);
8056
8051
  } catch {
8057
8052
  }
8058
8053
  }
@@ -8158,7 +8153,7 @@ function uninstallAgent() {
8158
8153
  warn(`launchctl bootout returned: ${result.stderr || result.stdout}`);
8159
8154
  }
8160
8155
  try {
8161
- unlinkSync4(plist);
8156
+ unlinkSync5(plist);
8162
8157
  } catch {
8163
8158
  }
8164
8159
  success(`LaunchAgent removed: ${LAUNCH_AGENT_LABEL}`);
@@ -8344,14 +8339,14 @@ function uninstallAgentLinux() {
8344
8339
  let removed = false;
8345
8340
  if (existsSync19(servicePath)) {
8346
8341
  try {
8347
- unlinkSync4(servicePath);
8342
+ unlinkSync5(servicePath);
8348
8343
  removed = true;
8349
8344
  } catch {
8350
8345
  }
8351
8346
  }
8352
8347
  if (existsSync19(timerPath)) {
8353
8348
  try {
8354
- unlinkSync4(timerPath);
8349
+ unlinkSync5(timerPath);
8355
8350
  removed = true;
8356
8351
  } catch {
8357
8352
  }
@@ -8529,7 +8524,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8529
8524
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8530
8525
  import { z as z2 } from "zod";
8531
8526
  import { readFileSync as readFileSync29, writeFileSync as writeFileSync25, existsSync as existsSync32, readdirSync as readdirSync12 } from "fs";
8532
- import { join as join30, resolve as resolve18, basename as basename6 } from "path";
8527
+ import { join as join30, resolve as resolve18, basename as basename7 } from "path";
8533
8528
  import { randomUUID as randomUUID4 } from "crypto";
8534
8529
  function readJsonFile2(filePath) {
8535
8530
  if (!existsSync32(filePath)) return null;
@@ -8848,7 +8843,7 @@ function createMcpServer() {
8848
8843
  if (!existsSync32(resolvedPath)) {
8849
8844
  return { content: [{ type: "text", text: `Directory not found: ${resolvedPath}` }], isError: true };
8850
8845
  }
8851
- const projectName = basename6(resolvedPath);
8846
+ const projectName = basename7(resolvedPath);
8852
8847
  const stack = detectStack(resolvedPath);
8853
8848
  const projectsDir = join30(factoryDir, ".beastmode", "projects");
8854
8849
  let verifyPort = 3001;
@@ -8966,7 +8961,7 @@ init_engine();
8966
8961
  init_file_writer();
8967
8962
  import { Command as Command3 } from "commander";
8968
8963
  import inquirer from "inquirer";
8969
- 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";
8970
8965
  import { existsSync as existsSync20, writeFileSync as writeFileSync16, mkdirSync as mkdirSync15, readFileSync as readFileSync17 } from "fs";
8971
8966
 
8972
8967
  // src/cli/utils/docker.ts
@@ -9227,7 +9222,7 @@ function isSourceRepo(dir) {
9227
9222
  async function runInit(name, opts) {
9228
9223
  if (opts.ui) {
9229
9224
  const { startServer: startServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
9230
- const factoryName2 = name || basename5(resolve6("."));
9225
+ const factoryName2 = name || basename6(resolve6("."));
9231
9226
  const projectPath2 = opts.project ? resolve6(opts.project) : void 0;
9232
9227
  info("Starting init wizard...");
9233
9228
  const uiServer = await startServer2({
@@ -9260,7 +9255,7 @@ async function runInit(name, opts) {
9260
9255
  const totalSteps = 5;
9261
9256
  header("BeastMode \u2014 Dark Factory Init");
9262
9257
  info("Creating your Custom Dark Factory\n");
9263
- const factoryName = name || basename5(resolve6("."));
9258
+ const factoryName = name || basename6(resolve6("."));
9264
9259
  if (existsSync20(factoryName) && existsSync20(resolve6(factoryName, ".beastmode"))) {
9265
9260
  throw new Error(`Factory already exists at ./${factoryName}. Use 'beastmode config' to modify.`);
9266
9261
  }
@@ -9276,7 +9271,7 @@ async function runInit(name, opts) {
9276
9271
  }
9277
9272
  const templateProjectPath = resolve6(opts.project);
9278
9273
  const templateStack = detectStack(templateProjectPath);
9279
- const templateProjectName = basename5(templateProjectPath);
9274
+ const templateProjectName = basename6(templateProjectPath);
9280
9275
  const actions2 = scaffoldFactory(factoryName, template.config, {
9281
9276
  name: templateProjectName,
9282
9277
  repo: templateStack.git_remote || void 0,
@@ -9471,7 +9466,7 @@ async function runInit(name, opts) {
9471
9466
  success("Board UI password set");
9472
9467
  }
9473
9468
  step(5, totalSteps, "Boot");
9474
- const projectName = basename5(projectPath);
9469
+ const projectName = basename6(projectPath);
9475
9470
  const actions = scaffoldFactory(factoryName, config, {
9476
9471
  name: projectName,
9477
9472
  repo: stack.git_remote || void 0,
@@ -10250,8 +10245,8 @@ for (const artifact of VALID_ARTIFACTS) {
10250
10245
 
10251
10246
  // src/cli/commands/status.ts
10252
10247
  init_status_checker();
10248
+ init_engine();
10253
10249
  init_schemas();
10254
- init_project_record();
10255
10250
  init_display();
10256
10251
  import { Command as Command10 } from "commander";
10257
10252
  import { existsSync as existsSync24, readFileSync as readFileSync21, readdirSync as readdirSync10 } from "fs";
@@ -12572,8 +12567,8 @@ async function runDaemon(opts) {
12572
12567
  const exitCode = await new Promise((resolvePromise) => {
12573
12568
  child.on("exit", (code) => {
12574
12569
  try {
12575
- const { unlinkSync: unlinkSync7 } = __require("fs");
12576
- unlinkSync7(pidFile);
12570
+ const { unlinkSync: unlinkSync8 } = __require("fs");
12571
+ unlinkSync8(pidFile);
12577
12572
  } catch {
12578
12573
  }
12579
12574
  resolvePromise(code ?? 1);
@@ -13214,7 +13209,7 @@ init_display();
13214
13209
  import { Command as Command23 } from "commander";
13215
13210
  import { spawn as spawn3 } from "child_process";
13216
13211
  import { existsSync as existsSync38 } from "fs";
13217
- import { basename as basename7, join as join37, resolve as resolve20 } from "path";
13212
+ import { basename as basename8, join as join37, resolve as resolve20 } from "path";
13218
13213
 
13219
13214
  // src/cli/runner-image-builder.ts
13220
13215
  import { execSync as execSync10 } from "child_process";
@@ -13800,7 +13795,7 @@ import {
13800
13795
  createWriteStream,
13801
13796
  existsSync as existsSync36,
13802
13797
  mkdirSync as mkdirSync22,
13803
- unlinkSync as unlinkSync5,
13798
+ unlinkSync as unlinkSync6,
13804
13799
  writeFileSync as writeFileSync29
13805
13800
  } from "fs";
13806
13801
  import { homedir as homedir4 } from "os";
@@ -13868,7 +13863,7 @@ async function downloadAndExtractRunner(installDir, os, arch) {
13868
13863
  );
13869
13864
  }
13870
13865
  try {
13871
- unlinkSync5(tarball);
13866
+ unlinkSync6(tarball);
13872
13867
  } catch {
13873
13868
  }
13874
13869
  if (os === "darwin") {
@@ -14106,7 +14101,7 @@ import {
14106
14101
  existsSync as existsSync37,
14107
14102
  mkdirSync as mkdirSync23,
14108
14103
  readFileSync as readFileSync34,
14109
- unlinkSync as unlinkSync6,
14104
+ unlinkSync as unlinkSync7,
14110
14105
  writeFileSync as writeFileSync30
14111
14106
  } from "fs";
14112
14107
  import { dirname as dirname9, join as join36 } from "path";
@@ -14198,7 +14193,7 @@ async function restoreWorkflows(projectDir) {
14198
14193
  jobCount: fileState.originals.length
14199
14194
  });
14200
14195
  }
14201
- unlinkSync6(statePath);
14196
+ unlinkSync7(statePath);
14202
14197
  return { nothingToRestore: false, files: resultFiles };
14203
14198
  }
14204
14199
 
@@ -14217,7 +14212,7 @@ function resolveProjectName(projectDir) {
14217
14212
  } catch {
14218
14213
  }
14219
14214
  }
14220
- return basename7(resolve20(projectDir));
14215
+ return basename8(resolve20(projectDir));
14221
14216
  }
14222
14217
  async function runnerSetupAction(opts) {
14223
14218
  if (opts.service !== void 0 && !opts.native) {
@@ -14593,7 +14588,7 @@ runnerCommand.command("build-image").description(
14593
14588
  init_engine();
14594
14589
  import { Command as Command24 } from "commander";
14595
14590
  import { existsSync as existsSync39, mkdirSync as mkdirSync24, readFileSync as readFileSync35, renameSync as renameSync3 } from "fs";
14596
- import { join as join38, resolve as resolve21, basename as basename8 } from "path";
14591
+ import { join as join38, resolve as resolve21, basename as basename9 } from "path";
14597
14592
  import { execSync as execSync13 } from "child_process";
14598
14593
  var DEFAULT_MAX_PROJECTS = 5;
14599
14594
  var MIN_DISK_WARNING_GB = 10;
@@ -14621,7 +14616,7 @@ function getFreeDiskGB() {
14621
14616
  function projectAddAction(factoryDir, projectPath, opts) {
14622
14617
  const resolvedPath = resolve21(projectPath);
14623
14618
  if (!existsSync39(resolvedPath)) throw new Error(`Directory not found: ${resolvedPath}`);
14624
- const projectName = opts.name || basename8(resolvedPath);
14619
+ const projectName = opts.name || basename9(resolvedPath);
14625
14620
  const projectsDir = join38(factoryDir, ".beastmode", "projects");
14626
14621
  if (existsSync39(join38(projectsDir, projectName, "project.json"))) {
14627
14622
  throw new Error(`Project already exists: ${projectName}`);
@@ -14712,7 +14707,7 @@ var projectCommand = new Command24("project").description("Manage projects in th
14712
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) => {
14713
14708
  const factoryDir = resolve21(".");
14714
14709
  projectAddAction(factoryDir, path, opts);
14715
- console.log(`Project registered: ${opts.name || basename8(resolve21(path))}`);
14710
+ console.log(`Project registered: ${opts.name || basename9(resolve21(path))}`);
14716
14711
  });
14717
14712
  projectCommand.command("list").description("List registered projects").action(() => {
14718
14713
  const projects = projectListAction(resolve21("."));