@beastmode-develeap/beastmode 0.1.299 → 0.1.300

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
@@ -5432,6 +5432,116 @@ function deepMerge2(target, source) {
5432
5432
  }
5433
5433
  return result;
5434
5434
  }
5435
+ function applyPresetOverrides(current, patch, presetName) {
5436
+ const preset = PRESETS[presetName];
5437
+ if (!preset) return current;
5438
+ const next = { ...current };
5439
+ const pipelineNext = { ...next.pipeline ?? {} };
5440
+ const patchPipeline = patch.pipeline ?? {};
5441
+ const explicitlySet = (key) => key in patchPipeline;
5442
+ if (!explicitlySet("satisfaction_threshold")) {
5443
+ pipelineNext.satisfaction_threshold = preset.satisfaction_threshold;
5444
+ }
5445
+ if (!explicitlySet("max_iterations")) {
5446
+ pipelineNext.max_iterations = preset.max_iterations;
5447
+ }
5448
+ if (!explicitlySet("stages")) {
5449
+ pipelineNext.stages = preset.stages;
5450
+ }
5451
+ pipelineNext.preset = presetName;
5452
+ next.pipeline = pipelineNext;
5453
+ return next;
5454
+ }
5455
+ function syncToDaemonConfigs(merged, factoryDir) {
5456
+ const daemonConfigPaths = [
5457
+ join14(factoryDir, "config", "beastmode.daemon.json"),
5458
+ join14(factoryDir, "config", "beastmode.docker.json")
5459
+ ].filter(existsSync16);
5460
+ const pipelineToDaemonMap = {
5461
+ satisfaction_threshold: ["verification", "satisfaction_threshold"],
5462
+ prod_accept_floor: ["verification", "prod_accept_floor"],
5463
+ max_iterations: ["convergence", "max_iterations"],
5464
+ fail_fast_threshold: ["convergence", "fail_fast_threshold"],
5465
+ preset: ["pipeline", "preset"]
5466
+ // FR-1a: live preset
5467
+ };
5468
+ const resilienceToDaemonMap = {
5469
+ max_failures: ["retry", "max_failures"],
5470
+ healing_enabled: ["healing", "enabled"],
5471
+ healing_max_attempts: ["healing", "max_attempts"]
5472
+ };
5473
+ const daemonFields = ["max_slots", "max_projects", "poll_interval_seconds", "stale_task_hours", "runs_path"];
5474
+ const flatDictSections = [
5475
+ "models",
5476
+ "migration_safety",
5477
+ "alerts",
5478
+ "human_gates",
5479
+ "cost"
5480
+ ];
5481
+ for (const daemonConfigPath of daemonConfigPaths) {
5482
+ try {
5483
+ const daemonConfig = JSON.parse(readFileSync13(daemonConfigPath, "utf-8"));
5484
+ let changed = false;
5485
+ for (const field of daemonFields) {
5486
+ if (field in merged && merged[field] !== daemonConfig[field]) {
5487
+ daemonConfig[field] = merged[field];
5488
+ changed = true;
5489
+ }
5490
+ }
5491
+ if (merged.pipeline && typeof merged.pipeline === "object") {
5492
+ const mergedPipeline = merged.pipeline;
5493
+ for (const [srcKey, [section, destKey]] of Object.entries(pipelineToDaemonMap)) {
5494
+ if (!(srcKey in mergedPipeline)) continue;
5495
+ const val = mergedPipeline[srcKey];
5496
+ if (val === void 0) continue;
5497
+ if (!daemonConfig[section] || typeof daemonConfig[section] !== "object") {
5498
+ daemonConfig[section] = {};
5499
+ }
5500
+ const bucket = daemonConfig[section];
5501
+ if (bucket[destKey] !== val) {
5502
+ bucket[destKey] = val;
5503
+ changed = true;
5504
+ }
5505
+ }
5506
+ }
5507
+ if (merged.resilience && typeof merged.resilience === "object") {
5508
+ const mergedResilience = merged.resilience;
5509
+ for (const [srcKey, [section, destKey]] of Object.entries(resilienceToDaemonMap)) {
5510
+ if (!(srcKey in mergedResilience)) continue;
5511
+ const val = mergedResilience[srcKey];
5512
+ if (val === void 0) continue;
5513
+ if (!daemonConfig[section] || typeof daemonConfig[section] !== "object") {
5514
+ daemonConfig[section] = {};
5515
+ }
5516
+ const bucket = daemonConfig[section];
5517
+ if (bucket[destKey] !== val) {
5518
+ bucket[destKey] = val;
5519
+ changed = true;
5520
+ }
5521
+ }
5522
+ }
5523
+ for (const section of flatDictSections) {
5524
+ const src = merged[section];
5525
+ if (src && typeof src === "object") {
5526
+ if (!daemonConfig[section] || typeof daemonConfig[section] !== "object") {
5527
+ daemonConfig[section] = {};
5528
+ }
5529
+ const bucket = daemonConfig[section];
5530
+ for (const [k, v] of Object.entries(src)) {
5531
+ if (bucket[k] !== v) {
5532
+ bucket[k] = v;
5533
+ changed = true;
5534
+ }
5535
+ }
5536
+ }
5537
+ }
5538
+ if (changed) {
5539
+ writeFileSync13(daemonConfigPath, JSON.stringify(daemonConfig, null, 2) + "\n", "utf-8");
5540
+ }
5541
+ } catch {
5542
+ }
5543
+ }
5544
+ }
5435
5545
  function getRunsDir(factoryDir) {
5436
5546
  const configPath = join14(factoryDir, ".beastmode", "config.json");
5437
5547
  if (existsSync16(configPath)) {
@@ -6438,8 +6548,13 @@ Path: ${projectPath}
6438
6548
  // { status: "running", job_id: string, started_at: string }
6439
6549
  // { status: "complete", analyzed_at: string, duration_seconds: number, target_repo_head_sha: string }
6440
6550
  // { status: "cached", analyzed_at: string, duration_seconds: number, target_repo_head_sha: string }
6441
- // { status: "failed", job_id?: string, error: string, started_at?: string, completed_at?: string }
6551
+ // { status: "failed", job_id?: string, error: string, started_at?: string, completed_at?: string, failure_reason?: string }
6442
6552
  // { status: "idle" } — no analysis has been requested for this project
6553
+ //
6554
+ // Stuck-job recovery: when a `.analyze-in-progress.json` file is older than 15
6555
+ // minutes (hard timeout) or its `last_heartbeat` field is older than 90 seconds,
6556
+ // the file is deleted on read and the response flips to `status: "failed"` with
6557
+ // `failure_reason` set to `"timeout"` or `"heartbeat_timeout"` respectively.
6443
6558
  method: "GET",
6444
6559
  pattern: "/api/projects/:name/analyze/status",
6445
6560
  handler: (_body, params) => {
@@ -6453,7 +6568,34 @@ Path: ${projectPath}
6453
6568
  const job2 = _analyzeJobs.get(name);
6454
6569
  try {
6455
6570
  const p = existsSync16(triggerPath) ? triggerPath : inProgressPath;
6456
- const t = JSON.parse(readFileSync13(p, "utf-8"));
6571
+ const raw = readFileSync13(p, "utf-8");
6572
+ const t = JSON.parse(raw);
6573
+ const now = Date.now();
6574
+ const startedMs = new Date(t.requested_at).getTime();
6575
+ const HARD_TIMEOUT_MS = 15 * 60 * 1e3;
6576
+ const HEARTBEAT_STALE_MS = 90 * 1e3;
6577
+ const hardTimedOut = now - startedMs > HARD_TIMEOUT_MS;
6578
+ const heartbeatStale = t.last_heartbeat ? now - new Date(t.last_heartbeat).getTime() > HEARTBEAT_STALE_MS : false;
6579
+ if (hardTimedOut || heartbeatStale) {
6580
+ try {
6581
+ unlinkSync4(p);
6582
+ } catch {
6583
+ }
6584
+ const failReason = hardTimedOut ? "timeout" : "heartbeat_timeout";
6585
+ _analyzeJobs.set(name, {
6586
+ job_id: t.job_id,
6587
+ project: name,
6588
+ status: "failed",
6589
+ started_at: t.requested_at,
6590
+ error: failReason
6591
+ });
6592
+ return {
6593
+ status: "failed",
6594
+ failure_reason: failReason,
6595
+ job_id: t.job_id,
6596
+ started_at: t.requested_at
6597
+ };
6598
+ }
6457
6599
  if (!job2 || job2.status !== "running") {
6458
6600
  _analyzeJobs.set(name, { job_id: t.job_id, project: name, status: "running", started_at: t.requested_at });
6459
6601
  }
@@ -6683,6 +6825,8 @@ Path: ${projectPath}
6683
6825
  handler: () => {
6684
6826
  const configPath = join14(factoryDir, ".beastmode", "config.json");
6685
6827
  const raw = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : generateDefaults();
6828
+ syncToDaemonConfigs(raw, factoryDir);
6829
+ delete raw.archive_s3_bucket;
6686
6830
  return raw;
6687
6831
  }
6688
6832
  },
@@ -6731,72 +6875,15 @@ Path: ${projectPath}
6731
6875
  }
6732
6876
  }
6733
6877
  }
6734
- const merged = deepMerge2(current, updatesClean);
6735
- writeFileSync13(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6736
- const daemonConfigPaths = [
6737
- join14(factoryDir, "config", "beastmode.daemon.json"),
6738
- join14(factoryDir, "config", "beastmode.docker.json")
6739
- ].filter(existsSync16);
6740
- const pipelineToDaemonMap = {
6741
- satisfaction_threshold: ["verification", "satisfaction_threshold"],
6742
- prod_accept_floor: ["verification", "prod_accept_floor"],
6743
- max_iterations: ["convergence", "max_iterations"],
6744
- fail_fast_threshold: ["convergence", "fail_fast_threshold"]
6745
- };
6746
- const daemonFields = ["max_slots", "max_projects", "poll_interval_seconds", "stale_task_hours", "runs_path"];
6747
- const flatDictSections = [
6748
- "models",
6749
- "migration_safety",
6750
- "alerts",
6751
- "human_gates"
6752
- ];
6753
- for (const daemonConfigPath of daemonConfigPaths) {
6754
- try {
6755
- const daemonConfig = JSON.parse(readFileSync13(daemonConfigPath, "utf-8"));
6756
- let changed = false;
6757
- for (const field of daemonFields) {
6758
- if (field in merged && merged[field] !== daemonConfig[field]) {
6759
- daemonConfig[field] = merged[field];
6760
- changed = true;
6761
- }
6762
- }
6763
- if (merged.pipeline && typeof merged.pipeline === "object") {
6764
- const mergedPipeline = merged.pipeline;
6765
- for (const [srcKey, [section, destKey]] of Object.entries(pipelineToDaemonMap)) {
6766
- if (!(srcKey in mergedPipeline)) continue;
6767
- const val = mergedPipeline[srcKey];
6768
- if (val === void 0) continue;
6769
- if (!daemonConfig[section] || typeof daemonConfig[section] !== "object") {
6770
- daemonConfig[section] = {};
6771
- }
6772
- const bucket = daemonConfig[section];
6773
- if (bucket[destKey] !== val) {
6774
- bucket[destKey] = val;
6775
- changed = true;
6776
- }
6777
- }
6778
- }
6779
- for (const section of flatDictSections) {
6780
- const src = merged[section];
6781
- if (src && typeof src === "object") {
6782
- if (!daemonConfig[section] || typeof daemonConfig[section] !== "object") {
6783
- daemonConfig[section] = {};
6784
- }
6785
- const bucket = daemonConfig[section];
6786
- for (const [k, v] of Object.entries(src)) {
6787
- if (bucket[k] !== v) {
6788
- bucket[k] = v;
6789
- changed = true;
6790
- }
6791
- }
6792
- }
6793
- }
6794
- if (changed) {
6795
- writeFileSync13(daemonConfigPath, JSON.stringify(daemonConfig, null, 2) + "\n", "utf-8");
6796
- }
6797
- } catch {
6798
- }
6878
+ const incomingPreset = (updatesClean.pipeline ?? {}).preset;
6879
+ const currentPreset = (current.pipeline ?? {}).preset;
6880
+ let baseline = current;
6881
+ if (incomingPreset && incomingPreset !== currentPreset) {
6882
+ baseline = applyPresetOverrides(current, updatesClean, incomingPreset);
6799
6883
  }
6884
+ const merged = deepMerge2(baseline, updatesClean);
6885
+ writeFileSync13(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6886
+ syncToDaemonConfigs(merged, factoryDir);
6800
6887
  return merged;
6801
6888
  }
6802
6889
  },
@@ -9072,7 +9159,7 @@ var init_mcp = __esm({
9072
9159
  });
9073
9160
 
9074
9161
  // src/index.ts
9075
- import { Command as Command25 } from "commander";
9162
+ import { Command as Command26 } from "commander";
9076
9163
 
9077
9164
  // src/cli/commands/init.ts
9078
9165
  init_engine();
@@ -14848,9 +14935,54 @@ projectCommand.command("remove <name>").description("Archive a project").action(
14848
14935
  console.log(`Project archived: ${name}`);
14849
14936
  });
14850
14937
 
14938
+ // src/cli/commands/redeploy.ts
14939
+ init_display();
14940
+ init_board();
14941
+ import { Command as Command25 } from "commander";
14942
+ import { join as join39 } from "path";
14943
+ import { existsSync as existsSync40, writeFileSync as writeFileSync31, mkdirSync as mkdirSync25 } from "fs";
14944
+ var redeployCommand = new Command25("redeploy").description("Trigger a rebuild + restart of a BeastMode service").argument("<service>", "Service to redeploy (currently: daemon)").action(async (service) => {
14945
+ try {
14946
+ await runRedeploy(service);
14947
+ } catch (err) {
14948
+ error(err.message);
14949
+ process.exit(1);
14950
+ }
14951
+ });
14952
+ async function runRedeploy(service) {
14953
+ if (service !== "daemon") {
14954
+ throw new Error(
14955
+ `Unknown service "${service}". Currently supported: daemon`
14956
+ );
14957
+ }
14958
+ const factoryDir = findFactoryDir();
14959
+ if (!factoryDir) {
14960
+ throw new Error(
14961
+ "No BeastMode factory found. Run 'beastmode init' first."
14962
+ );
14963
+ }
14964
+ const markerDir = join39(factoryDir, "daemon", "logs");
14965
+ const markerPath = join39(markerDir, ".daemon-rebuild-requested");
14966
+ if (!existsSync40(markerDir)) {
14967
+ mkdirSync25(markerDir, { recursive: true });
14968
+ }
14969
+ const timestamp = Math.floor(Date.now() / 1e3);
14970
+ writeFileSync31(
14971
+ markerPath,
14972
+ `pr=manual-operator
14973
+ timestamp=${timestamp}
14974
+ `
14975
+ );
14976
+ header("Redeploy requested");
14977
+ success(`Wrote rebuild marker to ${markerPath}`);
14978
+ info(
14979
+ "The daemon-rebuilder sidecar will pick this up within ~10 seconds, wait for the daemon to idle, then rebuild + restart."
14980
+ );
14981
+ }
14982
+
14851
14983
  // src/index.ts
14852
14984
  init_version();
14853
- var program = new Command25();
14985
+ var program = new Command26();
14854
14986
  program.name("beastmode").description("BeastMode Dark Factory \u2014 turn intent into verified software").version(ENGINE_VERSION);
14855
14987
  program.addCommand(initCommand);
14856
14988
  program.addCommand(boardCommand);
@@ -14876,5 +15008,6 @@ program.addCommand(logsCommand);
14876
15008
  program.addCommand(updateCommand);
14877
15009
  program.addCommand(runnerCommand);
14878
15010
  program.addCommand(projectCommand);
15011
+ program.addCommand(redeployCommand);
14879
15012
  program.parse();
14880
15013
  //# sourceMappingURL=index.js.map