@beastmode-develeap/beastmode 0.1.263 → 0.1.265
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 +279 -19
- package/dist/index.js.map +1 -1
- package/dist/web/board.html +52 -17
- package/dist/web/build-commit.txt +1 -1
- package/dist/web/build-stamp.txt +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4560,6 +4560,7 @@ var init_chat_handler = __esm({
|
|
|
4560
4560
|
import { readFileSync as readFileSync12, writeFileSync as writeFileSync12, existsSync as existsSync15, readdirSync as readdirSync6, unlinkSync as unlinkSync3, mkdirSync as mkdirSync11, statSync as statSync5, rmSync as rmSync3 } from "fs";
|
|
4561
4561
|
import { join as join13, basename as basename3, resolve as resolve4, dirname as dirname5 } from "path";
|
|
4562
4562
|
import { homedir } from "os";
|
|
4563
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4563
4564
|
import { execSync as execSync3, spawnSync } from "child_process";
|
|
4564
4565
|
import http2 from "http";
|
|
4565
4566
|
function assertSafeName(name) {
|
|
@@ -4806,6 +4807,30 @@ function scanStrandedRuns(runsDir, newThreshold) {
|
|
|
4806
4807
|
}
|
|
4807
4808
|
return stranded;
|
|
4808
4809
|
}
|
|
4810
|
+
function _readAnalyzeMeta(projectsDir, name) {
|
|
4811
|
+
const metaPath = join13(projectsDir, name, "codebase-guide.meta.json");
|
|
4812
|
+
if (!existsSync15(metaPath)) return null;
|
|
4813
|
+
try {
|
|
4814
|
+
return JSON.parse(readFileSync12(metaPath, "utf-8"));
|
|
4815
|
+
} catch {
|
|
4816
|
+
return null;
|
|
4817
|
+
}
|
|
4818
|
+
}
|
|
4819
|
+
function _gitHeadSha(repoPath) {
|
|
4820
|
+
try {
|
|
4821
|
+
return execSync3("git rev-parse HEAD", { cwd: repoPath, encoding: "utf-8" }).trim();
|
|
4822
|
+
} catch {
|
|
4823
|
+
return "no-git";
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
function _hasClaudeCli() {
|
|
4827
|
+
try {
|
|
4828
|
+
execSync3("which claude", { encoding: "utf-8" });
|
|
4829
|
+
return true;
|
|
4830
|
+
} catch {
|
|
4831
|
+
return false;
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4809
4834
|
function getBoardRoutes(factoryDir) {
|
|
4810
4835
|
return [
|
|
4811
4836
|
// ── Status ──
|
|
@@ -5607,36 +5632,270 @@ function getBoardRoutes(factoryDir) {
|
|
|
5607
5632
|
{
|
|
5608
5633
|
method: "POST",
|
|
5609
5634
|
pattern: "/api/projects/:name/analyze",
|
|
5610
|
-
handler: async (
|
|
5635
|
+
handler: async (body, params, query) => {
|
|
5611
5636
|
const { name } = params;
|
|
5637
|
+
assertSafeName(name);
|
|
5612
5638
|
const projectsDir = join13(factoryDir, ".beastmode", "projects");
|
|
5613
5639
|
const subDirConfigPath = join13(projectsDir, name, "project.json");
|
|
5614
5640
|
const flatConfigPath = join13(projectsDir, `${name}.json`);
|
|
5615
5641
|
const projPath = existsSync15(subDirConfigPath) ? subDirConfigPath : existsSync15(flatConfigPath) ? flatConfigPath : null;
|
|
5616
5642
|
if (!projPath) throw new Error(`Project not found: ${name}`);
|
|
5617
5643
|
const projConfig = JSON.parse(readFileSync12(projPath, "utf-8"));
|
|
5644
|
+
const projectPath = projConfig.path;
|
|
5618
5645
|
const subDir = join13(projectsDir, name);
|
|
5619
5646
|
if (!existsSync15(subDir)) mkdirSync11(subDir, { recursive: true });
|
|
5620
|
-
const
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
const
|
|
5626
|
-
|
|
5627
|
-
|
|
5647
|
+
const force = query?.force === "true" || query?.force === "1";
|
|
5648
|
+
const tier = body?.tier;
|
|
5649
|
+
if (tier === "quick") {
|
|
5650
|
+
const brownfieldPath = join13(subDir, "brownfield.md");
|
|
5651
|
+
if (!existsSync15(brownfieldPath)) {
|
|
5652
|
+
const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
5653
|
+
let stackInfo = "Unknown stack";
|
|
5654
|
+
try {
|
|
5655
|
+
const stack = detectStackFn(projectPath);
|
|
5656
|
+
const cmds = stack.suggested_commands;
|
|
5657
|
+
stackInfo = `Framework: ${stack.framework || "unknown"}
|
|
5628
5658
|
Build: ${cmds?.build || "unknown"}
|
|
5629
5659
|
Dev: ${cmds?.dev || "unknown"}`;
|
|
5630
|
-
|
|
5660
|
+
} catch {
|
|
5661
|
+
}
|
|
5662
|
+
writeFileSync12(brownfieldPath, `# Brownfield Analysis: ${name}
|
|
5663
|
+
|
|
5664
|
+
${stackInfo}
|
|
5665
|
+
|
|
5666
|
+
Path: ${projectPath}
|
|
5667
|
+
`);
|
|
5631
5668
|
}
|
|
5632
|
-
|
|
5669
|
+
return { analyzed: true, project: name, tier: "quick" };
|
|
5670
|
+
}
|
|
5671
|
+
if (!force) {
|
|
5672
|
+
const meta = _readAnalyzeMeta(projectsDir, name);
|
|
5673
|
+
const currentSha = _gitHeadSha(projectPath);
|
|
5674
|
+
if (meta && meta.status === "complete" && meta.target_repo_head_sha === currentSha) {
|
|
5675
|
+
return { analyzed: true, project: name, cached: true, analyzed_at: meta.analyzed_at };
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
const existingJob = _analyzeJobs.get(name);
|
|
5679
|
+
if (existingJob && existingJob.status === "running") {
|
|
5680
|
+
return { analyzing: true, project: name, job_id: existingJob.job_id, status: "running" };
|
|
5681
|
+
}
|
|
5682
|
+
const writeMeta = (m) => {
|
|
5683
|
+
try {
|
|
5684
|
+
writeFileSync12(
|
|
5685
|
+
join13(subDir, "codebase-guide.meta.json"),
|
|
5686
|
+
JSON.stringify(m, null, 2) + "\n",
|
|
5687
|
+
"utf-8"
|
|
5688
|
+
);
|
|
5689
|
+
} catch (err) {
|
|
5690
|
+
console.error(`[analyze] Failed to write meta for ${name}:`, err);
|
|
5691
|
+
}
|
|
5692
|
+
};
|
|
5693
|
+
if (!_hasClaudeCli()) {
|
|
5694
|
+
const brownfieldPath = join13(subDir, "brownfield.md");
|
|
5695
|
+
if (!existsSync15(brownfieldPath)) {
|
|
5696
|
+
const { detectStack: detectStackFn } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
5697
|
+
let stackInfo = "Unknown stack";
|
|
5698
|
+
try {
|
|
5699
|
+
const stack = detectStackFn(projectPath);
|
|
5700
|
+
const cmds = stack.suggested_commands;
|
|
5701
|
+
stackInfo = `Framework: ${stack.framework || "unknown"}
|
|
5702
|
+
Build: ${cmds?.build || "unknown"}
|
|
5703
|
+
Dev: ${cmds?.dev || "unknown"}`;
|
|
5704
|
+
} catch {
|
|
5705
|
+
}
|
|
5706
|
+
writeFileSync12(brownfieldPath, `# Brownfield Analysis: ${name}
|
|
5633
5707
|
|
|
5634
5708
|
${stackInfo}
|
|
5635
5709
|
|
|
5636
|
-
Path: ${
|
|
5710
|
+
Path: ${projectPath}
|
|
5637
5711
|
`);
|
|
5712
|
+
}
|
|
5713
|
+
writeMeta({
|
|
5714
|
+
analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5715
|
+
target_repo_head_sha: _gitHeadSha(projectPath),
|
|
5716
|
+
status: "failed",
|
|
5717
|
+
error: "Claude CLI not available",
|
|
5718
|
+
duration_seconds: 0
|
|
5719
|
+
});
|
|
5720
|
+
console.warn(`[analyze] Claude CLI unavailable for project ${name}; wrote detectStack() fallback.`);
|
|
5721
|
+
return { analyzed: true, project: name, fallback: true };
|
|
5722
|
+
}
|
|
5723
|
+
const jobId = randomUUID2();
|
|
5724
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5725
|
+
const startMs = Date.now();
|
|
5726
|
+
const job = {
|
|
5727
|
+
job_id: jobId,
|
|
5728
|
+
project: name,
|
|
5729
|
+
status: "running",
|
|
5730
|
+
started_at: startedAt
|
|
5731
|
+
};
|
|
5732
|
+
_analyzeJobs.set(name, job);
|
|
5733
|
+
(async () => {
|
|
5734
|
+
try {
|
|
5735
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
5736
|
+
const prompt = [
|
|
5737
|
+
"You are the Brownfield Analyst. Analyze the codebase at the current working directory.",
|
|
5738
|
+
`Write your output in pyramid format (L0/L1/L2) to the directory: ${subDir}`,
|
|
5739
|
+
"Produce exactly these four files:",
|
|
5740
|
+
` - ${join13(subDir, "codebase-guide.md")} (full L2 analysis: architecture, conventions, safe modification points, fragile areas)`,
|
|
5741
|
+
` - ${join13(subDir, "codebase-guide.l1.md")} (paragraph summary only)`,
|
|
5742
|
+
` - ${join13(subDir, "codebase-guide.l0.txt")} (one-sentence summary only)`,
|
|
5743
|
+
"Do not write anything else outside the BEASTMODE_OUTPUT_DIR."
|
|
5744
|
+
].join("\n");
|
|
5745
|
+
const isRoot = process.getuid?.() === 0;
|
|
5746
|
+
const claudeArgs = [
|
|
5747
|
+
"-p",
|
|
5748
|
+
prompt,
|
|
5749
|
+
"--agent",
|
|
5750
|
+
"brownfield-analyst",
|
|
5751
|
+
"--output-format",
|
|
5752
|
+
"stream-json",
|
|
5753
|
+
"--verbose",
|
|
5754
|
+
"--dangerously-skip-permissions"
|
|
5755
|
+
];
|
|
5756
|
+
const spawnCmd = isRoot ? "runuser" : "claude";
|
|
5757
|
+
const spawnArgs = isRoot ? ["-u", "node", "--", "claude", ...claudeArgs] : claudeArgs;
|
|
5758
|
+
const child = spawn4(spawnCmd, spawnArgs, {
|
|
5759
|
+
cwd: projectPath,
|
|
5760
|
+
env: {
|
|
5761
|
+
...process.env,
|
|
5762
|
+
BEASTMODE_PROJECT: name,
|
|
5763
|
+
BEASTMODE_OUTPUT_DIR: subDir,
|
|
5764
|
+
...isRoot ? { HOME: "/home/node" } : {}
|
|
5765
|
+
},
|
|
5766
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5767
|
+
});
|
|
5768
|
+
child.stdout?.on("data", () => {
|
|
5769
|
+
});
|
|
5770
|
+
child.stderr?.on("data", () => {
|
|
5771
|
+
});
|
|
5772
|
+
child.on("close", (code) => {
|
|
5773
|
+
const durationSeconds = (Date.now() - startMs) / 1e3;
|
|
5774
|
+
const sha = _gitHeadSha(projectPath);
|
|
5775
|
+
const guidePath = join13(subDir, "codebase-guide.md");
|
|
5776
|
+
const ok = code === 0 && existsSync15(guidePath);
|
|
5777
|
+
if (ok) {
|
|
5778
|
+
writeMeta({
|
|
5779
|
+
analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5780
|
+
analyst_version: "brownfield-analyst-v1",
|
|
5781
|
+
target_repo_head_sha: sha,
|
|
5782
|
+
project_name: name,
|
|
5783
|
+
duration_seconds: durationSeconds,
|
|
5784
|
+
status: "complete"
|
|
5785
|
+
});
|
|
5786
|
+
const legacy = join13(subDir, "brownfield.md");
|
|
5787
|
+
if (existsSync15(legacy)) {
|
|
5788
|
+
try {
|
|
5789
|
+
unlinkSync3(legacy);
|
|
5790
|
+
} catch {
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5793
|
+
_analyzeJobs.set(name, {
|
|
5794
|
+
...job,
|
|
5795
|
+
status: "complete",
|
|
5796
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5797
|
+
duration_seconds: durationSeconds
|
|
5798
|
+
});
|
|
5799
|
+
} else {
|
|
5800
|
+
const errMsg = `claude exited with code ${code}`;
|
|
5801
|
+
writeMeta({
|
|
5802
|
+
analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5803
|
+
target_repo_head_sha: sha,
|
|
5804
|
+
status: "failed",
|
|
5805
|
+
error: errMsg,
|
|
5806
|
+
duration_seconds: durationSeconds
|
|
5807
|
+
});
|
|
5808
|
+
_analyzeJobs.set(name, {
|
|
5809
|
+
...job,
|
|
5810
|
+
status: "failed",
|
|
5811
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5812
|
+
duration_seconds: durationSeconds,
|
|
5813
|
+
error: errMsg
|
|
5814
|
+
});
|
|
5815
|
+
}
|
|
5816
|
+
});
|
|
5817
|
+
child.on("error", (err) => {
|
|
5818
|
+
const durationSeconds = (Date.now() - startMs) / 1e3;
|
|
5819
|
+
const sha = _gitHeadSha(projectPath);
|
|
5820
|
+
writeMeta({
|
|
5821
|
+
analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5822
|
+
target_repo_head_sha: sha,
|
|
5823
|
+
status: "failed",
|
|
5824
|
+
error: String(err),
|
|
5825
|
+
duration_seconds: durationSeconds
|
|
5826
|
+
});
|
|
5827
|
+
_analyzeJobs.set(name, {
|
|
5828
|
+
...job,
|
|
5829
|
+
status: "failed",
|
|
5830
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5831
|
+
duration_seconds: durationSeconds,
|
|
5832
|
+
error: String(err)
|
|
5833
|
+
});
|
|
5834
|
+
});
|
|
5835
|
+
} catch (err) {
|
|
5836
|
+
const durationSeconds = (Date.now() - startMs) / 1e3;
|
|
5837
|
+
const sha = _gitHeadSha(projectPath);
|
|
5838
|
+
writeMeta({
|
|
5839
|
+
analyzed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5840
|
+
target_repo_head_sha: sha,
|
|
5841
|
+
status: "failed",
|
|
5842
|
+
error: String(err),
|
|
5843
|
+
duration_seconds: durationSeconds
|
|
5844
|
+
});
|
|
5845
|
+
_analyzeJobs.set(name, {
|
|
5846
|
+
...job,
|
|
5847
|
+
status: "failed",
|
|
5848
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5849
|
+
duration_seconds: durationSeconds,
|
|
5850
|
+
error: String(err)
|
|
5851
|
+
});
|
|
5852
|
+
}
|
|
5853
|
+
})();
|
|
5854
|
+
return { analyzing: true, project: name, job_id: jobId, status: "running" };
|
|
5855
|
+
}
|
|
5856
|
+
},
|
|
5857
|
+
{
|
|
5858
|
+
method: "GET",
|
|
5859
|
+
pattern: "/api/projects/:name/analyze/status",
|
|
5860
|
+
handler: (_body, params) => {
|
|
5861
|
+
const { name } = params;
|
|
5862
|
+
assertSafeName(name);
|
|
5863
|
+
const projectsDir = join13(factoryDir, ".beastmode", "projects");
|
|
5864
|
+
const job = _analyzeJobs.get(name);
|
|
5865
|
+
const meta = _readAnalyzeMeta(projectsDir, name);
|
|
5866
|
+
if (job && job.status === "running") {
|
|
5867
|
+
return {
|
|
5868
|
+
status: "running",
|
|
5869
|
+
job_id: job.job_id,
|
|
5870
|
+
started_at: job.started_at
|
|
5871
|
+
};
|
|
5638
5872
|
}
|
|
5639
|
-
|
|
5873
|
+
if (job && job.status === "failed") {
|
|
5874
|
+
return {
|
|
5875
|
+
status: "failed",
|
|
5876
|
+
job_id: job.job_id,
|
|
5877
|
+
error: job.error,
|
|
5878
|
+
started_at: job.started_at,
|
|
5879
|
+
completed_at: job.completed_at,
|
|
5880
|
+
duration_seconds: job.duration_seconds
|
|
5881
|
+
};
|
|
5882
|
+
}
|
|
5883
|
+
if (meta && meta.status === "complete") {
|
|
5884
|
+
return {
|
|
5885
|
+
status: job?.status === "cached" ? "cached" : "complete",
|
|
5886
|
+
analyzed_at: meta.analyzed_at,
|
|
5887
|
+
duration_seconds: meta.duration_seconds,
|
|
5888
|
+
target_repo_head_sha: meta.target_repo_head_sha
|
|
5889
|
+
};
|
|
5890
|
+
}
|
|
5891
|
+
if (meta && meta.status === "failed") {
|
|
5892
|
+
return {
|
|
5893
|
+
status: "failed",
|
|
5894
|
+
analyzed_at: meta.analyzed_at,
|
|
5895
|
+
error: meta.error
|
|
5896
|
+
};
|
|
5897
|
+
}
|
|
5898
|
+
return { status: "idle" };
|
|
5640
5899
|
}
|
|
5641
5900
|
},
|
|
5642
5901
|
// ── Runs ──
|
|
@@ -6348,7 +6607,7 @@ function matchBoardRoute(routes, method, url) {
|
|
|
6348
6607
|
}
|
|
6349
6608
|
return null;
|
|
6350
6609
|
}
|
|
6351
|
-
var BinaryResponse, HttpError, _TERMINAL_STAGES;
|
|
6610
|
+
var BinaryResponse, HttpError, _TERMINAL_STAGES, _analyzeJobs;
|
|
6352
6611
|
var init_board_api_routes = __esm({
|
|
6353
6612
|
"src/cli/ui/board-api-routes.ts"() {
|
|
6354
6613
|
"use strict";
|
|
@@ -6378,6 +6637,7 @@ var init_board_api_routes = __esm({
|
|
|
6378
6637
|
"stuck_infra_gap",
|
|
6379
6638
|
"ready_for_review"
|
|
6380
6639
|
]);
|
|
6640
|
+
_analyzeJobs = /* @__PURE__ */ new Map();
|
|
6381
6641
|
}
|
|
6382
6642
|
});
|
|
6383
6643
|
|
|
@@ -7733,7 +7993,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
7733
7993
|
import { z as z2 } from "zod";
|
|
7734
7994
|
import { readFileSync as readFileSync28, writeFileSync as writeFileSync24, existsSync as existsSync31, readdirSync as readdirSync11, mkdirSync as mkdirSync19 } from "fs";
|
|
7735
7995
|
import { join as join29, resolve as resolve18, basename as basename5 } from "path";
|
|
7736
|
-
import { randomUUID as
|
|
7996
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
7737
7997
|
function readJsonFile2(filePath) {
|
|
7738
7998
|
if (!existsSync31(filePath)) return null;
|
|
7739
7999
|
try {
|
|
@@ -7901,7 +8161,7 @@ function createMcpServer() {
|
|
|
7901
8161
|
const factoryDir = getFactoryPath();
|
|
7902
8162
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7903
8163
|
const item = {
|
|
7904
|
-
id:
|
|
8164
|
+
id: randomUUID4(),
|
|
7905
8165
|
title,
|
|
7906
8166
|
description: "",
|
|
7907
8167
|
status: status || "ready",
|
|
@@ -8099,7 +8359,7 @@ function createMcpServer() {
|
|
|
8099
8359
|
const factoryDir = getFactoryPath();
|
|
8100
8360
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8101
8361
|
const item = {
|
|
8102
|
-
id:
|
|
8362
|
+
id: randomUUID4(),
|
|
8103
8363
|
title,
|
|
8104
8364
|
description: description || "",
|
|
8105
8365
|
status: status || "new",
|
|
@@ -11533,7 +11793,7 @@ init_schemas();
|
|
|
11533
11793
|
import { Command as Command15 } from "commander";
|
|
11534
11794
|
import { join as join27 } from "path";
|
|
11535
11795
|
import { existsSync as existsSync29, readFileSync as readFileSync26, writeFileSync as writeFileSync22, mkdirSync as mkdirSync17 } from "fs";
|
|
11536
|
-
import { randomUUID as
|
|
11796
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
11537
11797
|
var runCommand = new Command15("run").description("Run a single pipeline task").argument("[project]", "Project name (defaults to first project)").option("--task <description>", "Task description").action(async (project, opts) => {
|
|
11538
11798
|
try {
|
|
11539
11799
|
await runPipeline(project, opts);
|
|
@@ -11596,7 +11856,7 @@ async function runPipeline(projectName, opts) {
|
|
|
11596
11856
|
}
|
|
11597
11857
|
}
|
|
11598
11858
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11599
|
-
const taskId =
|
|
11859
|
+
const taskId = randomUUID3();
|
|
11600
11860
|
const task = {
|
|
11601
11861
|
id: taskId,
|
|
11602
11862
|
title: opts.task,
|