bmalph 2.2.1 → 2.3.0
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/README.md +111 -30
- package/dist/cli.js +1 -0
- package/dist/commands/doctor.d.ts +14 -2
- package/dist/commands/doctor.js +83 -35
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +74 -7
- package/dist/commands/upgrade.js +8 -5
- package/dist/installer.d.ts +15 -4
- package/dist/installer.js +190 -101
- package/dist/platform/aider.d.ts +2 -0
- package/dist/platform/aider.js +71 -0
- package/dist/platform/claude-code.d.ts +2 -0
- package/dist/platform/claude-code.js +88 -0
- package/dist/platform/codex.d.ts +2 -0
- package/dist/platform/codex.js +67 -0
- package/dist/platform/copilot.d.ts +2 -0
- package/dist/platform/copilot.js +71 -0
- package/dist/platform/cursor.d.ts +2 -0
- package/dist/platform/cursor.js +71 -0
- package/dist/platform/detect.d.ts +7 -0
- package/dist/platform/detect.js +23 -0
- package/dist/platform/index.d.ts +4 -0
- package/dist/platform/index.js +3 -0
- package/dist/platform/registry.d.ts +4 -0
- package/dist/platform/registry.js +27 -0
- package/dist/platform/resolve.d.ts +8 -0
- package/dist/platform/resolve.js +24 -0
- package/dist/platform/types.d.ts +41 -0
- package/dist/platform/types.js +7 -0
- package/dist/platform/windsurf.d.ts +2 -0
- package/dist/platform/windsurf.js +71 -0
- package/dist/transition/artifacts.js +1 -1
- package/dist/transition/specs-changelog.js +4 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/validate.js +16 -0
- package/package.json +1 -1
- package/ralph/drivers/claude-code.sh +118 -0
- package/ralph/drivers/codex.sh +81 -0
- package/ralph/ralph_import.sh +11 -0
- package/ralph/ralph_loop.sh +37 -64
- package/ralph/templates/ralphrc.template +7 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { isEnoent, formatError } from "../utils/errors.js";
|
|
4
|
+
export const codexPlatform = {
|
|
5
|
+
id: "codex",
|
|
6
|
+
displayName: "OpenAI Codex",
|
|
7
|
+
tier: "full",
|
|
8
|
+
instructionsFile: "AGENTS.md",
|
|
9
|
+
commandDelivery: { kind: "inline" },
|
|
10
|
+
instructionsSectionMarker: "## BMAD-METHOD Integration",
|
|
11
|
+
generateInstructionsSnippet: () => `
|
|
12
|
+
## BMAD-METHOD Integration
|
|
13
|
+
|
|
14
|
+
Run the BMAD master agent to navigate phases. Ask for help to discover all available agents and workflows.
|
|
15
|
+
|
|
16
|
+
### Phases
|
|
17
|
+
|
|
18
|
+
| Phase | Focus | Key Agents |
|
|
19
|
+
|-------|-------|-----------|
|
|
20
|
+
| 1. Analysis | Understand the problem | Analyst agent |
|
|
21
|
+
| 2. Planning | Define the solution | Product Manager agent |
|
|
22
|
+
| 3. Solutioning | Design the architecture | Architect agent |
|
|
23
|
+
| 4. Implementation | Build it | Developer agent, then Ralph autonomous loop |
|
|
24
|
+
|
|
25
|
+
### Workflow
|
|
26
|
+
|
|
27
|
+
1. Work through Phases 1-3 using BMAD agents and workflows
|
|
28
|
+
2. Use the bmalph-implement transition to prepare Ralph format, then start Ralph
|
|
29
|
+
|
|
30
|
+
### Available Agents
|
|
31
|
+
|
|
32
|
+
| Agent | Role |
|
|
33
|
+
|-------|------|
|
|
34
|
+
| Analyst | Research, briefs, discovery |
|
|
35
|
+
| Architect | Technical design, architecture |
|
|
36
|
+
| Product Manager | PRDs, epics, stories |
|
|
37
|
+
| Scrum Master | Sprint planning, status, coordination |
|
|
38
|
+
| Developer | Implementation, coding |
|
|
39
|
+
| UX Designer | User experience, wireframes |
|
|
40
|
+
| QA Engineer | Test automation, quality assurance |
|
|
41
|
+
`,
|
|
42
|
+
getDoctorChecks: () => [
|
|
43
|
+
{
|
|
44
|
+
id: "instructions-file",
|
|
45
|
+
label: "AGENTS.md contains BMAD snippet",
|
|
46
|
+
check: async (projectDir) => {
|
|
47
|
+
try {
|
|
48
|
+
const content = await readFile(join(projectDir, "AGENTS.md"), "utf-8");
|
|
49
|
+
if (content.includes("BMAD-METHOD Integration")) {
|
|
50
|
+
return { passed: true };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
passed: false,
|
|
54
|
+
detail: "missing BMAD-METHOD Integration section",
|
|
55
|
+
hint: "Run: bmalph init",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isEnoent(err)) {
|
|
60
|
+
return { passed: false, detail: "AGENTS.md not found", hint: "Run: bmalph init" };
|
|
61
|
+
}
|
|
62
|
+
return { passed: false, detail: formatError(err), hint: "Check file permissions" };
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { isEnoent, formatError } from "../utils/errors.js";
|
|
4
|
+
export const copilotPlatform = {
|
|
5
|
+
id: "copilot",
|
|
6
|
+
displayName: "GitHub Copilot",
|
|
7
|
+
tier: "instructions-only",
|
|
8
|
+
instructionsFile: ".github/copilot-instructions.md",
|
|
9
|
+
commandDelivery: { kind: "none" },
|
|
10
|
+
instructionsSectionMarker: "## BMAD-METHOD Integration",
|
|
11
|
+
generateInstructionsSnippet: () => `
|
|
12
|
+
## BMAD-METHOD Integration
|
|
13
|
+
|
|
14
|
+
Ask the BMAD master agent to navigate phases. Ask for help to discover all available agents and workflows.
|
|
15
|
+
|
|
16
|
+
### Phases
|
|
17
|
+
|
|
18
|
+
| Phase | Focus | Key Agents |
|
|
19
|
+
|-------|-------|-----------|
|
|
20
|
+
| 1. Analysis | Understand the problem | Analyst agent |
|
|
21
|
+
| 2. Planning | Define the solution | Product Manager agent |
|
|
22
|
+
| 3. Solutioning | Design the architecture | Architect agent |
|
|
23
|
+
|
|
24
|
+
### Workflow
|
|
25
|
+
|
|
26
|
+
Work through Phases 1-3 using BMAD agents and workflows interactively.
|
|
27
|
+
|
|
28
|
+
> **Note:** Ralph (Phase 4 — autonomous implementation) is not supported on this platform.
|
|
29
|
+
|
|
30
|
+
### Available Agents
|
|
31
|
+
|
|
32
|
+
| Agent | Role |
|
|
33
|
+
|-------|------|
|
|
34
|
+
| Analyst | Research, briefs, discovery |
|
|
35
|
+
| Architect | Technical design, architecture |
|
|
36
|
+
| Product Manager | PRDs, epics, stories |
|
|
37
|
+
| Scrum Master | Sprint planning, status, coordination |
|
|
38
|
+
| Developer | Implementation, coding |
|
|
39
|
+
| UX Designer | User experience, wireframes |
|
|
40
|
+
| QA Engineer | Test automation, quality assurance |
|
|
41
|
+
`,
|
|
42
|
+
getDoctorChecks: () => [
|
|
43
|
+
{
|
|
44
|
+
id: "instructions-file",
|
|
45
|
+
label: ".github/copilot-instructions.md contains BMAD snippet",
|
|
46
|
+
check: async (projectDir) => {
|
|
47
|
+
try {
|
|
48
|
+
const content = await readFile(join(projectDir, ".github/copilot-instructions.md"), "utf-8");
|
|
49
|
+
if (content.includes("BMAD-METHOD Integration")) {
|
|
50
|
+
return { passed: true };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
passed: false,
|
|
54
|
+
detail: "missing BMAD-METHOD Integration section",
|
|
55
|
+
hint: "Run: bmalph init",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isEnoent(err)) {
|
|
60
|
+
return {
|
|
61
|
+
passed: false,
|
|
62
|
+
detail: ".github/copilot-instructions.md not found",
|
|
63
|
+
hint: "Run: bmalph init",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return { passed: false, detail: formatError(err), hint: "Check file permissions" };
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { isEnoent, formatError } from "../utils/errors.js";
|
|
4
|
+
export const cursorPlatform = {
|
|
5
|
+
id: "cursor",
|
|
6
|
+
displayName: "Cursor",
|
|
7
|
+
tier: "instructions-only",
|
|
8
|
+
instructionsFile: ".cursor/rules/bmad.mdc",
|
|
9
|
+
commandDelivery: { kind: "none" },
|
|
10
|
+
instructionsSectionMarker: "## BMAD-METHOD Integration",
|
|
11
|
+
generateInstructionsSnippet: () => `
|
|
12
|
+
## BMAD-METHOD Integration
|
|
13
|
+
|
|
14
|
+
Ask the BMAD master agent to navigate phases. Ask for help to discover all available agents and workflows.
|
|
15
|
+
|
|
16
|
+
### Phases
|
|
17
|
+
|
|
18
|
+
| Phase | Focus | Key Agents |
|
|
19
|
+
|-------|-------|-----------|
|
|
20
|
+
| 1. Analysis | Understand the problem | Analyst agent |
|
|
21
|
+
| 2. Planning | Define the solution | Product Manager agent |
|
|
22
|
+
| 3. Solutioning | Design the architecture | Architect agent |
|
|
23
|
+
|
|
24
|
+
### Workflow
|
|
25
|
+
|
|
26
|
+
Work through Phases 1-3 using BMAD agents and workflows interactively.
|
|
27
|
+
|
|
28
|
+
> **Note:** Ralph (Phase 4 — autonomous implementation) is not supported on this platform.
|
|
29
|
+
|
|
30
|
+
### Available Agents
|
|
31
|
+
|
|
32
|
+
| Agent | Role |
|
|
33
|
+
|-------|------|
|
|
34
|
+
| Analyst | Research, briefs, discovery |
|
|
35
|
+
| Architect | Technical design, architecture |
|
|
36
|
+
| Product Manager | PRDs, epics, stories |
|
|
37
|
+
| Scrum Master | Sprint planning, status, coordination |
|
|
38
|
+
| Developer | Implementation, coding |
|
|
39
|
+
| UX Designer | User experience, wireframes |
|
|
40
|
+
| QA Engineer | Test automation, quality assurance |
|
|
41
|
+
`,
|
|
42
|
+
getDoctorChecks: () => [
|
|
43
|
+
{
|
|
44
|
+
id: "instructions-file",
|
|
45
|
+
label: ".cursor/rules/bmad.mdc contains BMAD snippet",
|
|
46
|
+
check: async (projectDir) => {
|
|
47
|
+
try {
|
|
48
|
+
const content = await readFile(join(projectDir, ".cursor/rules/bmad.mdc"), "utf-8");
|
|
49
|
+
if (content.includes("BMAD-METHOD Integration")) {
|
|
50
|
+
return { passed: true };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
passed: false,
|
|
54
|
+
detail: "missing BMAD-METHOD Integration section",
|
|
55
|
+
hint: "Run: bmalph init",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isEnoent(err)) {
|
|
60
|
+
return {
|
|
61
|
+
passed: false,
|
|
62
|
+
detail: ".cursor/rules/bmad.mdc not found",
|
|
63
|
+
hint: "Run: bmalph init",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return { passed: false, detail: formatError(err), hint: "Check file permissions" };
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { exists } from "../utils/file-system.js";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
const DETECTION_MARKERS = [
|
|
4
|
+
{ platform: "claude-code", markers: [".claude"] },
|
|
5
|
+
{ platform: "codex", markers: ["AGENTS.md"] },
|
|
6
|
+
{ platform: "cursor", markers: [".cursor"] },
|
|
7
|
+
{ platform: "windsurf", markers: [".windsurf"] },
|
|
8
|
+
{ platform: "copilot", markers: [".github/copilot-instructions.md"] },
|
|
9
|
+
{ platform: "aider", markers: [".aider.conf.yml"] },
|
|
10
|
+
];
|
|
11
|
+
export async function detectPlatform(projectDir) {
|
|
12
|
+
const candidates = [];
|
|
13
|
+
for (const { platform, markers } of DETECTION_MARKERS) {
|
|
14
|
+
for (const marker of markers) {
|
|
15
|
+
if (await exists(join(projectDir, marker))) {
|
|
16
|
+
candidates.push(platform);
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const detected = candidates.length === 1 ? (candidates[0] ?? null) : null;
|
|
22
|
+
return { detected, candidates };
|
|
23
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { Platform, PlatformId, PlatformTier, CommandDelivery, PlatformDoctorCheck, } from "./types.js";
|
|
2
|
+
export { getPlatform, getAllPlatforms, isPlatformId } from "./registry.js";
|
|
3
|
+
export { resolveProjectPlatform } from "./resolve.js";
|
|
4
|
+
export { detectPlatform } from "./detect.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { claudeCodePlatform } from "./claude-code.js";
|
|
2
|
+
import { codexPlatform } from "./codex.js";
|
|
3
|
+
import { cursorPlatform } from "./cursor.js";
|
|
4
|
+
import { windsurfPlatform } from "./windsurf.js";
|
|
5
|
+
import { copilotPlatform } from "./copilot.js";
|
|
6
|
+
import { aiderPlatform } from "./aider.js";
|
|
7
|
+
const PLATFORMS = new Map([
|
|
8
|
+
["claude-code", claudeCodePlatform],
|
|
9
|
+
["codex", codexPlatform],
|
|
10
|
+
["cursor", cursorPlatform],
|
|
11
|
+
["windsurf", windsurfPlatform],
|
|
12
|
+
["copilot", copilotPlatform],
|
|
13
|
+
["aider", aiderPlatform],
|
|
14
|
+
]);
|
|
15
|
+
export function getPlatform(id) {
|
|
16
|
+
const platform = PLATFORMS.get(id);
|
|
17
|
+
if (!platform) {
|
|
18
|
+
throw new Error(`Unknown platform: ${id}`);
|
|
19
|
+
}
|
|
20
|
+
return platform;
|
|
21
|
+
}
|
|
22
|
+
export function getAllPlatforms() {
|
|
23
|
+
return [...PLATFORMS.values()];
|
|
24
|
+
}
|
|
25
|
+
export function isPlatformId(value) {
|
|
26
|
+
return PLATFORMS.has(value);
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Platform } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the platform for a project from its config, defaulting to claude-code.
|
|
4
|
+
*
|
|
5
|
+
* Used by doctor and upgrade commands to determine which platform checks and
|
|
6
|
+
* assets to use. Falls back to claude-code when config is missing or unreadable.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveProjectPlatform(projectDir: string): Promise<Platform>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readConfig } from "../utils/config.js";
|
|
2
|
+
import { isEnoent, formatError } from "../utils/errors.js";
|
|
3
|
+
import { warn } from "../utils/logger.js";
|
|
4
|
+
import { getPlatform } from "./registry.js";
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the platform for a project from its config, defaulting to claude-code.
|
|
7
|
+
*
|
|
8
|
+
* Used by doctor and upgrade commands to determine which platform checks and
|
|
9
|
+
* assets to use. Falls back to claude-code when config is missing or unreadable.
|
|
10
|
+
*/
|
|
11
|
+
export async function resolveProjectPlatform(projectDir) {
|
|
12
|
+
try {
|
|
13
|
+
const config = await readConfig(projectDir);
|
|
14
|
+
if (config?.platform) {
|
|
15
|
+
return getPlatform(config.platform);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
if (!isEnoent(err)) {
|
|
20
|
+
warn(`Failed to read project config: ${formatError(err)}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return getPlatform("claude-code");
|
|
24
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform abstraction layer for bmalph.
|
|
3
|
+
*
|
|
4
|
+
* Controls how `bmalph init/upgrade/doctor` install instruction files,
|
|
5
|
+
* deliver slash commands, and run health checks per platform.
|
|
6
|
+
*/
|
|
7
|
+
/** Supported platform identifiers. */
|
|
8
|
+
export type PlatformId = "claude-code" | "codex" | "cursor" | "windsurf" | "copilot" | "aider";
|
|
9
|
+
/** Full platforms support Phases 1-4 (planning + Ralph implementation). */
|
|
10
|
+
/** Instructions-only platforms support Phases 1-3 (planning only). */
|
|
11
|
+
export type PlatformTier = "full" | "instructions-only";
|
|
12
|
+
/** How slash commands are delivered to the platform. */
|
|
13
|
+
export type CommandDelivery = {
|
|
14
|
+
kind: "directory";
|
|
15
|
+
dir: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: "inline";
|
|
18
|
+
} | {
|
|
19
|
+
kind: "none";
|
|
20
|
+
};
|
|
21
|
+
/** Result of a single platform-specific doctor check. */
|
|
22
|
+
export interface PlatformDoctorCheck {
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
check: (projectDir: string) => Promise<{
|
|
26
|
+
passed: boolean;
|
|
27
|
+
detail?: string;
|
|
28
|
+
hint?: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
/** Platform definition controlling install, upgrade, and doctor behavior. */
|
|
32
|
+
export interface Platform {
|
|
33
|
+
readonly id: PlatformId;
|
|
34
|
+
readonly displayName: string;
|
|
35
|
+
readonly tier: PlatformTier;
|
|
36
|
+
readonly instructionsFile: string;
|
|
37
|
+
readonly commandDelivery: CommandDelivery;
|
|
38
|
+
readonly instructionsSectionMarker: string;
|
|
39
|
+
readonly generateInstructionsSnippet: () => string;
|
|
40
|
+
readonly getDoctorChecks: () => PlatformDoctorCheck[];
|
|
41
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { isEnoent, formatError } from "../utils/errors.js";
|
|
4
|
+
export const windsurfPlatform = {
|
|
5
|
+
id: "windsurf",
|
|
6
|
+
displayName: "Windsurf",
|
|
7
|
+
tier: "instructions-only",
|
|
8
|
+
instructionsFile: ".windsurf/rules/bmad.md",
|
|
9
|
+
commandDelivery: { kind: "none" },
|
|
10
|
+
instructionsSectionMarker: "## BMAD-METHOD Integration",
|
|
11
|
+
generateInstructionsSnippet: () => `
|
|
12
|
+
## BMAD-METHOD Integration
|
|
13
|
+
|
|
14
|
+
Ask the BMAD master agent to navigate phases. Ask for help to discover all available agents and workflows.
|
|
15
|
+
|
|
16
|
+
### Phases
|
|
17
|
+
|
|
18
|
+
| Phase | Focus | Key Agents |
|
|
19
|
+
|-------|-------|-----------|
|
|
20
|
+
| 1. Analysis | Understand the problem | Analyst agent |
|
|
21
|
+
| 2. Planning | Define the solution | Product Manager agent |
|
|
22
|
+
| 3. Solutioning | Design the architecture | Architect agent |
|
|
23
|
+
|
|
24
|
+
### Workflow
|
|
25
|
+
|
|
26
|
+
Work through Phases 1-3 using BMAD agents and workflows interactively.
|
|
27
|
+
|
|
28
|
+
> **Note:** Ralph (Phase 4 — autonomous implementation) is not supported on this platform.
|
|
29
|
+
|
|
30
|
+
### Available Agents
|
|
31
|
+
|
|
32
|
+
| Agent | Role |
|
|
33
|
+
|-------|------|
|
|
34
|
+
| Analyst | Research, briefs, discovery |
|
|
35
|
+
| Architect | Technical design, architecture |
|
|
36
|
+
| Product Manager | PRDs, epics, stories |
|
|
37
|
+
| Scrum Master | Sprint planning, status, coordination |
|
|
38
|
+
| Developer | Implementation, coding |
|
|
39
|
+
| UX Designer | User experience, wireframes |
|
|
40
|
+
| QA Engineer | Test automation, quality assurance |
|
|
41
|
+
`,
|
|
42
|
+
getDoctorChecks: () => [
|
|
43
|
+
{
|
|
44
|
+
id: "instructions-file",
|
|
45
|
+
label: ".windsurf/rules/bmad.md contains BMAD snippet",
|
|
46
|
+
check: async (projectDir) => {
|
|
47
|
+
try {
|
|
48
|
+
const content = await readFile(join(projectDir, ".windsurf/rules/bmad.md"), "utf-8");
|
|
49
|
+
if (content.includes("BMAD-METHOD Integration")) {
|
|
50
|
+
return { passed: true };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
passed: false,
|
|
54
|
+
detail: "missing BMAD-METHOD Integration section",
|
|
55
|
+
hint: "Run: bmalph init",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isEnoent(err)) {
|
|
60
|
+
return {
|
|
61
|
+
passed: false,
|
|
62
|
+
detail: ".windsurf/rules/bmad.md not found",
|
|
63
|
+
hint: "Run: bmalph init",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return { passed: false, detail: formatError(err), hint: "Check file permissions" };
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
@@ -36,7 +36,10 @@ export async function generateSpecsChangelog(oldSpecsDir, newSourceDir) {
|
|
|
36
36
|
debug(`Could not read old spec file ${file}: ${formatError(err)}`);
|
|
37
37
|
return "";
|
|
38
38
|
});
|
|
39
|
-
const newContent = await readFile(join(newSourceDir, file), "utf-8")
|
|
39
|
+
const newContent = await readFile(join(newSourceDir, file), "utf-8").catch((err) => {
|
|
40
|
+
debug(`Could not read new spec file ${file}: ${formatError(err)}`);
|
|
41
|
+
return "";
|
|
42
|
+
});
|
|
40
43
|
if (oldContent !== newContent) {
|
|
41
44
|
changes.push({
|
|
42
45
|
file,
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PlatformId } from "../platform/types.js";
|
|
1
2
|
export interface UpstreamVersions {
|
|
2
3
|
bmadCommit: string;
|
|
3
4
|
}
|
|
@@ -5,6 +6,7 @@ export interface BmalphConfig {
|
|
|
5
6
|
name: string;
|
|
6
7
|
description: string;
|
|
7
8
|
createdAt: string;
|
|
9
|
+
platform?: PlatformId;
|
|
8
10
|
upstreamVersions?: UpstreamVersions;
|
|
9
11
|
}
|
|
10
12
|
export declare function readConfig(projectDir: string): Promise<BmalphConfig | null>;
|
package/dist/utils/validate.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { MAX_PROJECT_NAME_LENGTH } from "./constants.js";
|
|
2
|
+
const VALID_PLATFORM_IDS = [
|
|
3
|
+
"claude-code",
|
|
4
|
+
"codex",
|
|
5
|
+
"cursor",
|
|
6
|
+
"windsurf",
|
|
7
|
+
"copilot",
|
|
8
|
+
"aider",
|
|
9
|
+
];
|
|
2
10
|
const VALID_STATUSES = ["planning", "implementing", "completed"];
|
|
3
11
|
// Invalid filesystem characters (Windows + POSIX)
|
|
4
12
|
const INVALID_FS_CHARS = /[<>:"/\\|?*]/;
|
|
@@ -50,6 +58,13 @@ export function validateConfig(data) {
|
|
|
50
58
|
throw new Error("config.createdAt must be a string");
|
|
51
59
|
}
|
|
52
60
|
const description = typeof data.description === "string" ? data.description : "";
|
|
61
|
+
let platform;
|
|
62
|
+
if (data.platform !== undefined) {
|
|
63
|
+
if (typeof data.platform !== "string" || !VALID_PLATFORM_IDS.includes(data.platform)) {
|
|
64
|
+
throw new Error(`config.platform must be one of: ${VALID_PLATFORM_IDS.join(", ")}`);
|
|
65
|
+
}
|
|
66
|
+
platform = data.platform;
|
|
67
|
+
}
|
|
53
68
|
const upstreamVersions = data.upstreamVersions !== undefined
|
|
54
69
|
? validateUpstreamVersions(data.upstreamVersions)
|
|
55
70
|
: undefined;
|
|
@@ -57,6 +72,7 @@ export function validateConfig(data) {
|
|
|
57
72
|
name: data.name,
|
|
58
73
|
description,
|
|
59
74
|
createdAt: data.createdAt,
|
|
75
|
+
...(platform !== undefined && { platform }),
|
|
60
76
|
upstreamVersions,
|
|
61
77
|
};
|
|
62
78
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Claude Code driver for Ralph
|
|
3
|
+
# Provides platform-specific CLI invocation logic
|
|
4
|
+
|
|
5
|
+
# Driver identification
|
|
6
|
+
driver_name() {
|
|
7
|
+
echo "claude-code"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
driver_display_name() {
|
|
11
|
+
echo "Claude Code"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
driver_cli_binary() {
|
|
15
|
+
echo "claude"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
driver_min_version() {
|
|
19
|
+
echo "2.0.76"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Check if the CLI binary is available
|
|
23
|
+
driver_check_available() {
|
|
24
|
+
command -v "$(driver_cli_binary)" &>/dev/null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Valid tool patterns for --allowedTools validation
|
|
28
|
+
# Sets the global VALID_TOOL_PATTERNS array
|
|
29
|
+
driver_valid_tools() {
|
|
30
|
+
VALID_TOOL_PATTERNS=(
|
|
31
|
+
"Write"
|
|
32
|
+
"Read"
|
|
33
|
+
"Edit"
|
|
34
|
+
"MultiEdit"
|
|
35
|
+
"Glob"
|
|
36
|
+
"Grep"
|
|
37
|
+
"Task"
|
|
38
|
+
"TodoWrite"
|
|
39
|
+
"WebFetch"
|
|
40
|
+
"WebSearch"
|
|
41
|
+
"Bash"
|
|
42
|
+
"Bash(git *)"
|
|
43
|
+
"Bash(npm *)"
|
|
44
|
+
"Bash(bats *)"
|
|
45
|
+
"Bash(python *)"
|
|
46
|
+
"Bash(node *)"
|
|
47
|
+
"NotebookEdit"
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# Build the CLI command arguments
|
|
52
|
+
# Populates global CLAUDE_CMD_ARGS array
|
|
53
|
+
# Parameters:
|
|
54
|
+
# $1 - prompt_file: path to the prompt file
|
|
55
|
+
# $2 - loop_context: context string for session continuity
|
|
56
|
+
# $3 - session_id: session ID for resume (empty for new session)
|
|
57
|
+
driver_build_command() {
|
|
58
|
+
local prompt_file=$1
|
|
59
|
+
local loop_context=$2
|
|
60
|
+
local session_id=$3
|
|
61
|
+
|
|
62
|
+
# Note: We do NOT use --dangerously-skip-permissions here. Tool permissions
|
|
63
|
+
# are controlled via --allowedTools from CLAUDE_ALLOWED_TOOLS in .ralphrc.
|
|
64
|
+
# This preserves the permission denial circuit breaker (Issue #101).
|
|
65
|
+
CLAUDE_CMD_ARGS=("$(driver_cli_binary)")
|
|
66
|
+
|
|
67
|
+
if [[ ! -f "$prompt_file" ]]; then
|
|
68
|
+
echo "ERROR: Prompt file not found: $prompt_file" >&2
|
|
69
|
+
return 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Output format
|
|
73
|
+
if [[ "$CLAUDE_OUTPUT_FORMAT" == "json" ]]; then
|
|
74
|
+
CLAUDE_CMD_ARGS+=("--output-format" "json")
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Allowed tools
|
|
78
|
+
if [[ -n "$CLAUDE_ALLOWED_TOOLS" ]]; then
|
|
79
|
+
CLAUDE_CMD_ARGS+=("--allowedTools")
|
|
80
|
+
local IFS=','
|
|
81
|
+
read -ra tools_array <<< "$CLAUDE_ALLOWED_TOOLS"
|
|
82
|
+
for tool in "${tools_array[@]}"; do
|
|
83
|
+
tool=$(echo "$tool" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
84
|
+
if [[ -n "$tool" ]]; then
|
|
85
|
+
CLAUDE_CMD_ARGS+=("$tool")
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# Session resume
|
|
91
|
+
# IMPORTANT: Use --resume with explicit session ID instead of --continue.
|
|
92
|
+
# --continue resumes the "most recent session in current directory" which
|
|
93
|
+
# can hijack active Claude Code sessions. --resume with a specific session ID
|
|
94
|
+
# ensures we only resume Ralph's own sessions. (Issue #151)
|
|
95
|
+
if [[ "$CLAUDE_USE_CONTINUE" == "true" && -n "$session_id" ]]; then
|
|
96
|
+
CLAUDE_CMD_ARGS+=("--resume" "$session_id")
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Loop context as system prompt
|
|
100
|
+
if [[ -n "$loop_context" ]]; then
|
|
101
|
+
CLAUDE_CMD_ARGS+=("--append-system-prompt" "$loop_context")
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Prompt content
|
|
105
|
+
local prompt_content
|
|
106
|
+
prompt_content=$(cat "$prompt_file")
|
|
107
|
+
CLAUDE_CMD_ARGS+=("-p" "$prompt_content")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# Whether this driver supports session continuity
|
|
111
|
+
driver_supports_sessions() {
|
|
112
|
+
return 0 # true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Stream filter for live output (jq filter for JSON streaming)
|
|
116
|
+
driver_stream_filter() {
|
|
117
|
+
echo '.content // empty | select(type == "string")'
|
|
118
|
+
}
|