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.
Files changed (67) hide show
  1. package/README.md +162 -48
  2. package/dist/cli.js +14 -0
  3. package/dist/commands/doctor.d.ts +14 -2
  4. package/dist/commands/doctor.js +105 -41
  5. package/dist/commands/implement.d.ts +6 -0
  6. package/dist/commands/implement.js +82 -0
  7. package/dist/commands/init.d.ts +1 -0
  8. package/dist/commands/init.js +74 -7
  9. package/dist/commands/reset.d.ts +7 -0
  10. package/dist/commands/reset.js +81 -0
  11. package/dist/commands/status.js +86 -10
  12. package/dist/commands/upgrade.js +8 -5
  13. package/dist/installer.d.ts +15 -4
  14. package/dist/installer.js +190 -101
  15. package/dist/platform/aider.d.ts +2 -0
  16. package/dist/platform/aider.js +71 -0
  17. package/dist/platform/claude-code.d.ts +2 -0
  18. package/dist/platform/claude-code.js +87 -0
  19. package/dist/platform/codex.d.ts +2 -0
  20. package/dist/platform/codex.js +67 -0
  21. package/dist/platform/copilot.d.ts +2 -0
  22. package/dist/platform/copilot.js +71 -0
  23. package/dist/platform/cursor.d.ts +2 -0
  24. package/dist/platform/cursor.js +71 -0
  25. package/dist/platform/detect.d.ts +7 -0
  26. package/dist/platform/detect.js +23 -0
  27. package/dist/platform/index.d.ts +4 -0
  28. package/dist/platform/index.js +3 -0
  29. package/dist/platform/registry.d.ts +4 -0
  30. package/dist/platform/registry.js +27 -0
  31. package/dist/platform/resolve.d.ts +8 -0
  32. package/dist/platform/resolve.js +24 -0
  33. package/dist/platform/types.d.ts +41 -0
  34. package/dist/platform/types.js +7 -0
  35. package/dist/platform/windsurf.d.ts +2 -0
  36. package/dist/platform/windsurf.js +71 -0
  37. package/dist/reset.d.ts +18 -0
  38. package/dist/reset.js +181 -0
  39. package/dist/transition/artifact-scan.d.ts +27 -0
  40. package/dist/transition/artifact-scan.js +91 -0
  41. package/dist/transition/artifacts.d.ts +1 -0
  42. package/dist/transition/artifacts.js +2 -1
  43. package/dist/transition/context.js +34 -0
  44. package/dist/transition/fix-plan.d.ts +8 -2
  45. package/dist/transition/fix-plan.js +33 -7
  46. package/dist/transition/orchestration.d.ts +2 -2
  47. package/dist/transition/orchestration.js +120 -41
  48. package/dist/transition/preflight.d.ts +6 -0
  49. package/dist/transition/preflight.js +154 -0
  50. package/dist/transition/specs-changelog.js +4 -1
  51. package/dist/transition/specs-index.d.ts +1 -1
  52. package/dist/transition/specs-index.js +24 -1
  53. package/dist/transition/types.d.ts +23 -1
  54. package/dist/utils/config.d.ts +2 -0
  55. package/dist/utils/dryrun.d.ts +1 -1
  56. package/dist/utils/dryrun.js +22 -0
  57. package/dist/utils/validate.js +18 -2
  58. package/package.json +1 -1
  59. package/ralph/drivers/claude-code.sh +118 -0
  60. package/ralph/drivers/codex.sh +81 -0
  61. package/ralph/ralph_import.sh +11 -0
  62. package/ralph/ralph_loop.sh +52 -64
  63. package/ralph/templates/ralphrc.template +7 -0
  64. package/slash-commands/bmalph-doctor.md +16 -0
  65. package/slash-commands/bmalph-implement.md +18 -141
  66. package/slash-commands/bmalph-status.md +15 -0
  67. 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,2 @@
1
+ import type { Platform } from "./types.js";
2
+ export declare const cursorPlatform: Platform;
@@ -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,7 @@
1
+ import type { PlatformId } from "./types.js";
2
+ interface DetectionResult {
3
+ detected: PlatformId | null;
4
+ candidates: PlatformId[];
5
+ }
6
+ export declare function detectPlatform(projectDir: string): Promise<DetectionResult>;
7
+ export {};
@@ -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,3 @@
1
+ export { getPlatform, getAllPlatforms, isPlatformId } from "./registry.js";
2
+ export { resolveProjectPlatform } from "./resolve.js";
3
+ export { detectPlatform } from "./detect.js";
@@ -0,0 +1,4 @@
1
+ import type { Platform, PlatformId } from "./types.js";
2
+ export declare function getPlatform(id: PlatformId): Platform;
3
+ export declare function getAllPlatforms(): Platform[];
4
+ export declare function isPlatformId(value: string): value is PlatformId;
@@ -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,7 @@
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
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { Platform } from "./types.js";
2
+ export declare const windsurfPlatform: Platform;
@@ -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
+ };
@@ -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>;