bmalph 2.2.1 → 2.4.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 +162 -48
- package/dist/cli.js +14 -0
- package/dist/commands/doctor.d.ts +14 -2
- package/dist/commands/doctor.js +105 -41
- package/dist/commands/implement.d.ts +6 -0
- package/dist/commands/implement.js +82 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +74 -7
- package/dist/commands/reset.d.ts +7 -0
- package/dist/commands/reset.js +81 -0
- package/dist/commands/status.js +86 -10
- 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 +87 -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/reset.d.ts +18 -0
- package/dist/reset.js +181 -0
- package/dist/transition/artifact-scan.d.ts +27 -0
- package/dist/transition/artifact-scan.js +91 -0
- package/dist/transition/artifacts.d.ts +1 -0
- package/dist/transition/artifacts.js +2 -1
- package/dist/transition/context.js +34 -0
- package/dist/transition/fix-plan.d.ts +8 -2
- package/dist/transition/fix-plan.js +33 -7
- package/dist/transition/orchestration.d.ts +2 -2
- package/dist/transition/orchestration.js +120 -41
- package/dist/transition/preflight.d.ts +6 -0
- package/dist/transition/preflight.js +154 -0
- package/dist/transition/specs-changelog.js +4 -1
- package/dist/transition/specs-index.d.ts +1 -1
- package/dist/transition/specs-index.js +24 -1
- package/dist/transition/types.d.ts +23 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/dryrun.d.ts +1 -1
- package/dist/utils/dryrun.js +22 -0
- package/dist/utils/validate.js +18 -2
- 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 +52 -64
- package/ralph/templates/ralphrc.template +7 -0
- package/slash-commands/bmalph-doctor.md +16 -0
- package/slash-commands/bmalph-implement.md +18 -141
- package/slash-commands/bmalph-status.md +15 -0
- package/slash-commands/bmalph-upgrade.md +15 -0
|
@@ -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
|
+
};
|
package/dist/reset.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Platform } from "./platform/types.js";
|
|
2
|
+
import type { DryRunAction } from "./utils/dryrun.js";
|
|
3
|
+
export interface ResetPlan {
|
|
4
|
+
directories: string[];
|
|
5
|
+
commandFiles: string[];
|
|
6
|
+
instructionsCleanup: {
|
|
7
|
+
path: string;
|
|
8
|
+
sectionsToRemove: string[];
|
|
9
|
+
} | null;
|
|
10
|
+
gitignoreLines: string[];
|
|
11
|
+
warnings: Array<{
|
|
12
|
+
path: string;
|
|
13
|
+
message: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
export declare function buildResetPlan(projectDir: string, platform: Platform): Promise<ResetPlan>;
|
|
17
|
+
export declare function executeResetPlan(projectDir: string, plan: ResetPlan): Promise<void>;
|
|
18
|
+
export declare function planToDryRunActions(plan: ResetPlan): DryRunAction[];
|
package/dist/reset.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { readdir, readFile, rm } from "fs/promises";
|
|
2
|
+
import { join, posix } from "path";
|
|
3
|
+
import { getSlashCommandsDir } from "./installer.js";
|
|
4
|
+
import { exists, atomicWriteFile } from "./utils/file-system.js";
|
|
5
|
+
import { isEnoent } from "./utils/errors.js";
|
|
6
|
+
import { BMAD_DIR, RALPH_DIR, BMALPH_DIR, BMAD_OUTPUT_DIR } from "./utils/constants.js";
|
|
7
|
+
export async function buildResetPlan(projectDir, platform) {
|
|
8
|
+
const plan = {
|
|
9
|
+
directories: [],
|
|
10
|
+
commandFiles: [],
|
|
11
|
+
instructionsCleanup: null,
|
|
12
|
+
gitignoreLines: [],
|
|
13
|
+
warnings: [],
|
|
14
|
+
};
|
|
15
|
+
// Check which managed directories exist
|
|
16
|
+
for (const dir of [BMAD_DIR, RALPH_DIR, BMALPH_DIR]) {
|
|
17
|
+
if (await exists(join(projectDir, dir))) {
|
|
18
|
+
plan.directories.push(dir);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Check for slash commands to remove (directory delivery only)
|
|
22
|
+
if (platform.commandDelivery.kind === "directory") {
|
|
23
|
+
const commandsDir = join(projectDir, platform.commandDelivery.dir);
|
|
24
|
+
if (await exists(commandsDir)) {
|
|
25
|
+
const bundledNames = await getBundledCommandNames();
|
|
26
|
+
try {
|
|
27
|
+
const existingFiles = await readdir(commandsDir);
|
|
28
|
+
for (const file of existingFiles) {
|
|
29
|
+
if (file.endsWith(".md") && bundledNames.has(file)) {
|
|
30
|
+
plan.commandFiles.push(posix.join(platform.commandDelivery.dir, file));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
if (!isEnoent(err))
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Check instructions file for BMAD sections
|
|
41
|
+
try {
|
|
42
|
+
const content = await readFile(join(projectDir, platform.instructionsFile), "utf-8");
|
|
43
|
+
const sectionsToRemove = [];
|
|
44
|
+
if (content.includes(platform.instructionsSectionMarker)) {
|
|
45
|
+
sectionsToRemove.push(platform.instructionsSectionMarker);
|
|
46
|
+
}
|
|
47
|
+
// Codex (inline) also has a BMAD Commands section
|
|
48
|
+
if (platform.commandDelivery.kind === "inline" && content.includes("## BMAD Commands")) {
|
|
49
|
+
sectionsToRemove.push("## BMAD Commands");
|
|
50
|
+
}
|
|
51
|
+
if (sectionsToRemove.length > 0) {
|
|
52
|
+
plan.instructionsCleanup = {
|
|
53
|
+
path: platform.instructionsFile,
|
|
54
|
+
sectionsToRemove,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (!isEnoent(err))
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
// Check .gitignore for bmalph entries
|
|
63
|
+
try {
|
|
64
|
+
const content = await readFile(join(projectDir, ".gitignore"), "utf-8");
|
|
65
|
+
const existingLines = new Set(content
|
|
66
|
+
.split(/\r?\n/)
|
|
67
|
+
.map((line) => line.trim())
|
|
68
|
+
.filter(Boolean));
|
|
69
|
+
const bmalpEntries = [".ralph/logs/", "_bmad-output/"];
|
|
70
|
+
for (const entry of bmalpEntries) {
|
|
71
|
+
if (existingLines.has(entry)) {
|
|
72
|
+
plan.gitignoreLines.push(entry);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (!isEnoent(err))
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
// Warn about _bmad-output/
|
|
81
|
+
if (await exists(join(projectDir, BMAD_OUTPUT_DIR))) {
|
|
82
|
+
plan.warnings.push({
|
|
83
|
+
path: `${BMAD_OUTPUT_DIR}/`,
|
|
84
|
+
message: "Contains user planning artifacts — not removed by reset",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return plan;
|
|
88
|
+
}
|
|
89
|
+
async function getBundledCommandNames() {
|
|
90
|
+
const slashCommandsDir = getSlashCommandsDir();
|
|
91
|
+
try {
|
|
92
|
+
const files = await readdir(slashCommandsDir);
|
|
93
|
+
return new Set(files.filter((f) => f.endsWith(".md")));
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
if (!isEnoent(err))
|
|
97
|
+
throw err;
|
|
98
|
+
return new Set();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export async function executeResetPlan(projectDir, plan) {
|
|
102
|
+
// Delete managed directories
|
|
103
|
+
for (const dir of plan.directories) {
|
|
104
|
+
await rm(join(projectDir, dir), { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
// Delete slash command files
|
|
107
|
+
for (const file of plan.commandFiles) {
|
|
108
|
+
await rm(join(projectDir, file), { force: true });
|
|
109
|
+
}
|
|
110
|
+
// Clean instructions file
|
|
111
|
+
if (plan.instructionsCleanup) {
|
|
112
|
+
const filePath = join(projectDir, plan.instructionsCleanup.path);
|
|
113
|
+
try {
|
|
114
|
+
let content = await readFile(filePath, "utf-8");
|
|
115
|
+
for (const marker of plan.instructionsCleanup.sectionsToRemove) {
|
|
116
|
+
content = removeSection(content, marker);
|
|
117
|
+
}
|
|
118
|
+
content = content.trim();
|
|
119
|
+
if (content.length === 0) {
|
|
120
|
+
await rm(filePath, { force: true });
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
await atomicWriteFile(filePath, content + "\n");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
if (!isEnoent(err))
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Clean .gitignore
|
|
132
|
+
if (plan.gitignoreLines.length > 0) {
|
|
133
|
+
const filePath = join(projectDir, ".gitignore");
|
|
134
|
+
try {
|
|
135
|
+
const content = await readFile(filePath, "utf-8");
|
|
136
|
+
const cleaned = removeGitignoreLines(content, plan.gitignoreLines);
|
|
137
|
+
await atomicWriteFile(filePath, cleaned);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (!isEnoent(err))
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function removeSection(content, marker) {
|
|
146
|
+
if (!content.includes(marker))
|
|
147
|
+
return content;
|
|
148
|
+
const sectionStart = content.indexOf(marker);
|
|
149
|
+
const before = content.slice(0, sectionStart);
|
|
150
|
+
const afterSection = content.slice(sectionStart);
|
|
151
|
+
// Find next level-2 heading that doesn't match this section's heading
|
|
152
|
+
const markerEscaped = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
153
|
+
const nextHeadingMatch = afterSection.match(new RegExp(`\\n## (?!${markerEscaped.slice(3)})`));
|
|
154
|
+
const after = nextHeadingMatch ? afterSection.slice(nextHeadingMatch.index) : "";
|
|
155
|
+
return before.trimEnd() + after;
|
|
156
|
+
}
|
|
157
|
+
function removeGitignoreLines(content, linesToRemove) {
|
|
158
|
+
const removeSet = new Set(linesToRemove);
|
|
159
|
+
const lines = content.split(/\r?\n/);
|
|
160
|
+
const filtered = lines.filter((line) => !removeSet.has(line.trim()));
|
|
161
|
+
return filtered.join("\n");
|
|
162
|
+
}
|
|
163
|
+
export function planToDryRunActions(plan) {
|
|
164
|
+
const actions = [];
|
|
165
|
+
for (const dir of plan.directories) {
|
|
166
|
+
actions.push({ type: "delete", path: `${dir}/` });
|
|
167
|
+
}
|
|
168
|
+
for (const file of plan.commandFiles) {
|
|
169
|
+
actions.push({ type: "delete", path: file });
|
|
170
|
+
}
|
|
171
|
+
if (plan.instructionsCleanup) {
|
|
172
|
+
actions.push({ type: "modify", path: plan.instructionsCleanup.path });
|
|
173
|
+
}
|
|
174
|
+
if (plan.gitignoreLines.length > 0) {
|
|
175
|
+
actions.push({ type: "modify", path: ".gitignore" });
|
|
176
|
+
}
|
|
177
|
+
for (const warning of plan.warnings) {
|
|
178
|
+
actions.push({ type: "warn", path: warning.path, reason: warning.message });
|
|
179
|
+
}
|
|
180
|
+
return actions;
|
|
181
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ArtifactClassification {
|
|
2
|
+
phase: number;
|
|
3
|
+
name: string;
|
|
4
|
+
required: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface ScannedArtifact extends ArtifactClassification {
|
|
7
|
+
filename: string;
|
|
8
|
+
}
|
|
9
|
+
export interface PhaseArtifacts {
|
|
10
|
+
1: ScannedArtifact[];
|
|
11
|
+
2: ScannedArtifact[];
|
|
12
|
+
3: ScannedArtifact[];
|
|
13
|
+
}
|
|
14
|
+
export interface ProjectArtifactScan {
|
|
15
|
+
directory: string;
|
|
16
|
+
found: string[];
|
|
17
|
+
detectedPhase: number;
|
|
18
|
+
missing: string[];
|
|
19
|
+
phases: PhaseArtifacts;
|
|
20
|
+
nextAction: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function classifyArtifact(filename: string): ArtifactClassification | null;
|
|
23
|
+
export declare function scanArtifacts(files: string[]): PhaseArtifacts;
|
|
24
|
+
export declare function detectPhase(phases: PhaseArtifacts): number;
|
|
25
|
+
export declare function getMissing(phases: PhaseArtifacts): string[];
|
|
26
|
+
export declare function suggestNext(phases: PhaseArtifacts, detectedPhase: number): string;
|
|
27
|
+
export declare function scanProjectArtifacts(projectDir: string): Promise<ProjectArtifactScan | null>;
|