@beastmode-develeap/beastmode 0.1.298 → 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 +219 -69
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +84 -35
- package/dist/web/build-commit.txt +1 -1
- package/dist/web/build-stamp.txt +1 -1
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -3246,6 +3246,8 @@ function detectShape(parsed) {
|
|
|
3246
3246
|
}
|
|
3247
3247
|
function hasMissingCanonicalFields(parsed) {
|
|
3248
3248
|
if (!parsed.stack) return true;
|
|
3249
|
+
const stack = parsed.stack;
|
|
3250
|
+
if (stack.detected === "unknown" && !stack.build_command) return true;
|
|
3249
3251
|
if (!parsed.plugins) return true;
|
|
3250
3252
|
if (!parsed.infra) return true;
|
|
3251
3253
|
const deploy = parsed.deploy;
|
|
@@ -3413,6 +3415,21 @@ function readProjectRecord(projectsDir, name) {
|
|
|
3413
3415
|
let isFlat = false;
|
|
3414
3416
|
if (existsSync8(subdirPath)) {
|
|
3415
3417
|
filePath = subdirPath;
|
|
3418
|
+
if (existsSync8(flatPath)) {
|
|
3419
|
+
try {
|
|
3420
|
+
const flatParsed = JSON.parse(readFileSync8(flatPath, "utf-8"));
|
|
3421
|
+
const flatStack = flatParsed.stack;
|
|
3422
|
+
const subdirParsed = JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
3423
|
+
const subdirStack = subdirParsed.stack;
|
|
3424
|
+
if (flatStack && flatStack.detected && flatStack.detected !== "unknown" && (!subdirStack || subdirStack.detected === "unknown")) {
|
|
3425
|
+
subdirParsed.stack = flatStack;
|
|
3426
|
+
writeFileSync6(filePath, JSON.stringify(subdirParsed, null, 2) + "\n");
|
|
3427
|
+
console.error(`[project-record] Recovered stack from legacy ${name}.json`);
|
|
3428
|
+
}
|
|
3429
|
+
unlinkSync(flatPath);
|
|
3430
|
+
} catch {
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3416
3433
|
} else if (existsSync8(flatPath)) {
|
|
3417
3434
|
filePath = flatPath;
|
|
3418
3435
|
isFlat = true;
|
|
@@ -5415,6 +5432,116 @@ function deepMerge2(target, source) {
|
|
|
5415
5432
|
}
|
|
5416
5433
|
return result;
|
|
5417
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
|
+
}
|
|
5418
5545
|
function getRunsDir(factoryDir) {
|
|
5419
5546
|
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
5420
5547
|
if (existsSync16(configPath)) {
|
|
@@ -6421,8 +6548,13 @@ Path: ${projectPath}
|
|
|
6421
6548
|
// { status: "running", job_id: string, started_at: string }
|
|
6422
6549
|
// { status: "complete", analyzed_at: string, duration_seconds: number, target_repo_head_sha: string }
|
|
6423
6550
|
// { status: "cached", analyzed_at: string, duration_seconds: number, target_repo_head_sha: string }
|
|
6424
|
-
// { 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 }
|
|
6425
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.
|
|
6426
6558
|
method: "GET",
|
|
6427
6559
|
pattern: "/api/projects/:name/analyze/status",
|
|
6428
6560
|
handler: (_body, params) => {
|
|
@@ -6436,7 +6568,34 @@ Path: ${projectPath}
|
|
|
6436
6568
|
const job2 = _analyzeJobs.get(name);
|
|
6437
6569
|
try {
|
|
6438
6570
|
const p = existsSync16(triggerPath) ? triggerPath : inProgressPath;
|
|
6439
|
-
const
|
|
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
|
+
}
|
|
6440
6599
|
if (!job2 || job2.status !== "running") {
|
|
6441
6600
|
_analyzeJobs.set(name, { job_id: t.job_id, project: name, status: "running", started_at: t.requested_at });
|
|
6442
6601
|
}
|
|
@@ -6666,6 +6825,8 @@ Path: ${projectPath}
|
|
|
6666
6825
|
handler: () => {
|
|
6667
6826
|
const configPath = join14(factoryDir, ".beastmode", "config.json");
|
|
6668
6827
|
const raw = existsSync16(configPath) ? JSON.parse(readFileSync13(configPath, "utf-8")) : generateDefaults();
|
|
6828
|
+
syncToDaemonConfigs(raw, factoryDir);
|
|
6829
|
+
delete raw.archive_s3_bucket;
|
|
6669
6830
|
return raw;
|
|
6670
6831
|
}
|
|
6671
6832
|
},
|
|
@@ -6714,72 +6875,15 @@ Path: ${projectPath}
|
|
|
6714
6875
|
}
|
|
6715
6876
|
}
|
|
6716
6877
|
}
|
|
6717
|
-
const
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
].filter(existsSync16);
|
|
6723
|
-
const pipelineToDaemonMap = {
|
|
6724
|
-
satisfaction_threshold: ["verification", "satisfaction_threshold"],
|
|
6725
|
-
prod_accept_floor: ["verification", "prod_accept_floor"],
|
|
6726
|
-
max_iterations: ["convergence", "max_iterations"],
|
|
6727
|
-
fail_fast_threshold: ["convergence", "fail_fast_threshold"]
|
|
6728
|
-
};
|
|
6729
|
-
const daemonFields = ["max_slots", "max_projects", "poll_interval_seconds", "stale_task_hours", "runs_path"];
|
|
6730
|
-
const flatDictSections = [
|
|
6731
|
-
"models",
|
|
6732
|
-
"migration_safety",
|
|
6733
|
-
"alerts",
|
|
6734
|
-
"human_gates"
|
|
6735
|
-
];
|
|
6736
|
-
for (const daemonConfigPath of daemonConfigPaths) {
|
|
6737
|
-
try {
|
|
6738
|
-
const daemonConfig = JSON.parse(readFileSync13(daemonConfigPath, "utf-8"));
|
|
6739
|
-
let changed = false;
|
|
6740
|
-
for (const field of daemonFields) {
|
|
6741
|
-
if (field in merged && merged[field] !== daemonConfig[field]) {
|
|
6742
|
-
daemonConfig[field] = merged[field];
|
|
6743
|
-
changed = true;
|
|
6744
|
-
}
|
|
6745
|
-
}
|
|
6746
|
-
if (merged.pipeline && typeof merged.pipeline === "object") {
|
|
6747
|
-
const mergedPipeline = merged.pipeline;
|
|
6748
|
-
for (const [srcKey, [section, destKey]] of Object.entries(pipelineToDaemonMap)) {
|
|
6749
|
-
if (!(srcKey in mergedPipeline)) continue;
|
|
6750
|
-
const val = mergedPipeline[srcKey];
|
|
6751
|
-
if (val === void 0) continue;
|
|
6752
|
-
if (!daemonConfig[section] || typeof daemonConfig[section] !== "object") {
|
|
6753
|
-
daemonConfig[section] = {};
|
|
6754
|
-
}
|
|
6755
|
-
const bucket = daemonConfig[section];
|
|
6756
|
-
if (bucket[destKey] !== val) {
|
|
6757
|
-
bucket[destKey] = val;
|
|
6758
|
-
changed = true;
|
|
6759
|
-
}
|
|
6760
|
-
}
|
|
6761
|
-
}
|
|
6762
|
-
for (const section of flatDictSections) {
|
|
6763
|
-
const src = merged[section];
|
|
6764
|
-
if (src && typeof src === "object") {
|
|
6765
|
-
if (!daemonConfig[section] || typeof daemonConfig[section] !== "object") {
|
|
6766
|
-
daemonConfig[section] = {};
|
|
6767
|
-
}
|
|
6768
|
-
const bucket = daemonConfig[section];
|
|
6769
|
-
for (const [k, v] of Object.entries(src)) {
|
|
6770
|
-
if (bucket[k] !== v) {
|
|
6771
|
-
bucket[k] = v;
|
|
6772
|
-
changed = true;
|
|
6773
|
-
}
|
|
6774
|
-
}
|
|
6775
|
-
}
|
|
6776
|
-
}
|
|
6777
|
-
if (changed) {
|
|
6778
|
-
writeFileSync13(daemonConfigPath, JSON.stringify(daemonConfig, null, 2) + "\n", "utf-8");
|
|
6779
|
-
}
|
|
6780
|
-
} catch {
|
|
6781
|
-
}
|
|
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);
|
|
6782
6883
|
}
|
|
6884
|
+
const merged = deepMerge2(baseline, updatesClean);
|
|
6885
|
+
writeFileSync13(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
6886
|
+
syncToDaemonConfigs(merged, factoryDir);
|
|
6783
6887
|
return merged;
|
|
6784
6888
|
}
|
|
6785
6889
|
},
|
|
@@ -9055,7 +9159,7 @@ var init_mcp = __esm({
|
|
|
9055
9159
|
});
|
|
9056
9160
|
|
|
9057
9161
|
// src/index.ts
|
|
9058
|
-
import { Command as
|
|
9162
|
+
import { Command as Command26 } from "commander";
|
|
9059
9163
|
|
|
9060
9164
|
// src/cli/commands/init.ts
|
|
9061
9165
|
init_engine();
|
|
@@ -14831,9 +14935,54 @@ projectCommand.command("remove <name>").description("Archive a project").action(
|
|
|
14831
14935
|
console.log(`Project archived: ${name}`);
|
|
14832
14936
|
});
|
|
14833
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
|
+
|
|
14834
14983
|
// src/index.ts
|
|
14835
14984
|
init_version();
|
|
14836
|
-
var program = new
|
|
14985
|
+
var program = new Command26();
|
|
14837
14986
|
program.name("beastmode").description("BeastMode Dark Factory \u2014 turn intent into verified software").version(ENGINE_VERSION);
|
|
14838
14987
|
program.addCommand(initCommand);
|
|
14839
14988
|
program.addCommand(boardCommand);
|
|
@@ -14859,5 +15008,6 @@ program.addCommand(logsCommand);
|
|
|
14859
15008
|
program.addCommand(updateCommand);
|
|
14860
15009
|
program.addCommand(runnerCommand);
|
|
14861
15010
|
program.addCommand(projectCommand);
|
|
15011
|
+
program.addCommand(redeployCommand);
|
|
14862
15012
|
program.parse();
|
|
14863
15013
|
//# sourceMappingURL=index.js.map
|