pi-bmad-flow 0.1.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 (43) hide show
  1. package/README.md +111 -0
  2. package/docs/install-model.md +64 -0
  3. package/docs/workflow-wireframe.md +122 -0
  4. package/extensions/bmad-orchestrator/commands.ts +238 -0
  5. package/extensions/bmad-orchestrator/detector.ts +168 -0
  6. package/extensions/bmad-orchestrator/files.ts +49 -0
  7. package/extensions/bmad-orchestrator/gates.ts +70 -0
  8. package/extensions/bmad-orchestrator/index.ts +131 -0
  9. package/extensions/bmad-orchestrator/packets.ts +172 -0
  10. package/extensions/bmad-orchestrator/router.ts +79 -0
  11. package/extensions/bmad-orchestrator/sprint.ts +82 -0
  12. package/extensions/bmad-orchestrator/tool.ts +78 -0
  13. package/extensions/bmad-orchestrator/types.ts +96 -0
  14. package/extensions/bmad-orchestrator/ui.ts +19 -0
  15. package/fixtures/sample-bmad-project/_bmad/_config/manifest.yaml +5 -0
  16. package/fixtures/sample-bmad-project/_bmad/bmm/README.md +1 -0
  17. package/fixtures/sample-bmad-project/_bmad/core/README.md +1 -0
  18. package/fixtures/sample-bmad-project/_bmad/tea/README.md +1 -0
  19. package/fixtures/sample-bmad-project/_bmad/wds/README.md +1 -0
  20. package/fixtures/sample-bmad-project/_bmad-output/implementation-artifacts/sprint-status.yaml +12 -0
  21. package/fixtures/sample-bmad-project/_bmad-output/implementation-artifacts/stories/1-2-user-authentication-dashboard.md +26 -0
  22. package/fixtures/sample-bmad-project/_bmad-output/planning-artifacts/architecture.md +7 -0
  23. package/fixtures/sample-bmad-project/_bmad-output/planning-artifacts/epic-1-auth-dashboard.md +7 -0
  24. package/fixtures/sample-bmad-project/_bmad-output/planning-artifacts/prd.md +6 -0
  25. package/fixtures/sample-bmad-project/_bmad-output/test-artifacts/nfr-assessment.md +7 -0
  26. package/fixtures/sample-bmad-project/_bmad-output/test-artifacts/test-review.md +6 -0
  27. package/fixtures/sample-bmad-project/_bmad-output/test-artifacts/traceability-matrix.md +5 -0
  28. package/fixtures/sample-bmad-project/design-artifacts/C-UX-Scenarios/01-user-onboarding/00-scenario-overview.md +5 -0
  29. package/fixtures/sample-bmad-project/design-artifacts/C-UX-Scenarios/01-user-onboarding/1.2-authentication-dashboard/Frontend/specifications.md +12 -0
  30. package/fixtures/sample-bmad-project/design-artifacts/D-Design-System/components/auth-form.md +3 -0
  31. package/fixtures/sample-bmad-project/design-artifacts/D-Design-System/components/dashboard-primary-button.md +3 -0
  32. package/fixtures/sample-bmad-project/design-artifacts/D-Design-System/design-tokens.md +5 -0
  33. package/fixtures/sample-bmad-project/design-artifacts/E-PRD/Design-Deliveries/DD-001-auth-dashboard.yaml +17 -0
  34. package/fixtures/sample-bmad-project/design-artifacts/_progress/00-design-log.md +5 -0
  35. package/fixtures/sample-bmad-project/docs/project-context.md +5 -0
  36. package/package.json +36 -0
  37. package/scripts/audit-project.mjs +266 -0
  38. package/scripts/bootstrap.mjs +108 -0
  39. package/scripts/check.mjs +64 -0
  40. package/scripts/fixture-smoke.mjs +55 -0
  41. package/skills/bmad-phase-handoff/SKILL.md +13 -0
  42. package/skills/bmad-story-full/SKILL.md +20 -0
  43. package/skills/bmad-story-lean/SKILL.md +18 -0
package/README.md ADDED
@@ -0,0 +1,111 @@
1
+ # pi-bmad-flow
2
+
3
+ Pi-native orchestration overlay for BMAD.
4
+
5
+ This package is designed to run on top of:
6
+
7
+ 1. upstream `pi`
8
+ 2. official BMAD install targeting `pi`
9
+
10
+ It adds a thin control plane for phase routing and story-loop automation.
11
+
12
+ ## Install in a BMAD project
13
+
14
+ Install Pi and BMAD first, then install this package into the project:
15
+
16
+ ```bash
17
+ npm install -g @mariozechner/pi-coding-agent
18
+ cd /path/to/project
19
+ npx bmad-method install
20
+ pi install -l npm:pi-bmad-flow```
21
+
22
+ For early access builds before npm publish, install from git or a local path:
23
+
24
+ ```bash
25
+ pi install -l git:github.com/<your-org>/pi-bmad-flow
26
+ pi install -l /absolute/path/to/pi-bmad-flow
27
+ ```
28
+
29
+ After install, open `pi` in the project and run:
30
+
31
+ ```text
32
+ /bmad-status
33
+ ```
34
+
35
+ Recommended BMAD modules for this overlay:
36
+
37
+ - core
38
+ - bmm
39
+ - cis
40
+ - wds
41
+ - tea
42
+ - bmb
43
+
44
+ Package docs:
45
+
46
+ - [`docs/install-model.md`](./docs/install-model.md)
47
+ - [`docs/workflow-wireframe.md`](./docs/workflow-wireframe.md)
48
+
49
+ ## Publish
50
+
51
+ From `pi-bmad-flow/`:
52
+
53
+ ```bash
54
+ npm run check
55
+ npm run smoke:fixture
56
+ npm publish --access public
57
+ ```
58
+
59
+ `prepublishOnly` also runs package validation automatically before publish.
60
+
61
+ If `pi-bmad-flow` is already taken on npm, choose another unscoped name in `package.json`, run `npm view <name>` to confirm availability, and publish that name instead.
62
+
63
+ ## Validate against a project
64
+
65
+ ```bash
66
+ npm run audit:project -- --project-dir /absolute/path/to/project
67
+ ```
68
+
69
+ ## Expected project layout
70
+
71
+ The extension expects standard BMAD folders:
72
+
73
+ - `_bmad/`
74
+ - `_bmad-output/planning-artifacts/`
75
+ - `_bmad-output/implementation-artifacts/`
76
+
77
+ Optional:
78
+
79
+ - `design-artifacts/`
80
+ - `docs/`
81
+
82
+ ## Commands
83
+
84
+ - `/bmad-next`
85
+ - `/bmad-start`
86
+ - `/bmad-review`
87
+ - `/bmad-gate`
88
+ - `/bmad-status`
89
+ - `/bmad-phase`
90
+
91
+ ## Current scope
92
+
93
+ This overlay keeps BMAD adaptation inside Pi-native runtime features instead of prompt wrappers.
94
+
95
+ Current behavior:
96
+
97
+ - BMAD detection
98
+ - module detection
99
+ - artifact-aware phase detection
100
+ - sprint status parsing
101
+ - next-step routing
102
+ - plain-English BMAD intent routing through Pi input interception
103
+ - dedicated story sessions created by `/bmad-start`
104
+ - lean versus full packet selection
105
+ - WDS-aware packet assembly for UI stories
106
+ - TEA-aware review and gate routing
107
+ - BMAD-specific compaction summaries
108
+ - lightweight BMAD status widget and footer state
109
+ - optional LLM access to deterministic BMAD state through the `bmad_orchestrator` tool
110
+
111
+ This package does not replace official BMAD workflows. It reduces routing overhead around them.
@@ -0,0 +1,64 @@
1
+ # Install Model
2
+
3
+ `pi-bmad-flow` is a thin overlay. It should not replace either `pi` or BMAD.
4
+
5
+ ## Recommended install order
6
+
7
+ 1. Install upstream `pi`
8
+ 2. Install official BMAD into the project
9
+ 3. Install `pi-bmad-flow` project-locally
10
+
11
+ ```bash
12
+ npm install -g @mariozechner/pi-coding-agent
13
+ cd /path/to/project
14
+ npx bmad-method install
15
+ pi install -l /absolute/path/to/pi-bmad-flow
16
+ ```
17
+
18
+ ## BMAD installer choices
19
+
20
+ Use the official BMAD installer and keep your existing module selection:
21
+
22
+ - `BMad Core Module`
23
+ - `BMad Method Agile-AI Driven-Development`
24
+ - `BMad Builder`
25
+ - `BMad Creative Intelligence Suite`
26
+ - `Test Architect`
27
+ - `Whiteport Design Studio`
28
+
29
+ That gives you the official artifact model under `_bmad/` and `_bmad-output/`. `pi-bmad-flow` reads that structure and adds orchestration on top.
30
+
31
+ ## Why this install shape is correct
32
+
33
+ - Upstream `pi` stays updateable without maintaining a fork.
34
+ - Official BMAD stays updateable without maintaining a fork.
35
+ - Your custom behavior lives in one overlay package that can evolve independently.
36
+ - If BMAD changes, the overlay can adapt at the routing layer instead of forcing a reinstall strategy rewrite.
37
+
38
+ ## Day-to-day usage
39
+
40
+ Inside a BMAD project:
41
+
42
+ ```bash
43
+ pi
44
+ ```
45
+
46
+ Then use:
47
+
48
+ - `/bmad-status`
49
+ - `/bmad-next`
50
+ - `/bmad-start`
51
+ - `/bmad-review`
52
+ - `/bmad-gate`
53
+
54
+ `/bmad-start` should be the normal entry into implementation. It creates a dedicated Pi story session, attaches lean or full packet guidance, and keeps the planning session clean.
55
+
56
+ ## Distribution model for your version
57
+
58
+ Your install story should become:
59
+
60
+ 1. User installs upstream `pi`
61
+ 2. User runs official BMAD installer with the module set you standardize
62
+ 3. User installs your overlay package
63
+
64
+ Later, you can wrap this with a bootstrap script, but the bootstrap should still execute the same three layers under the hood. Do not hard-fork BMAD unless upstream compatibility stops mattering.
@@ -0,0 +1,122 @@
1
+ # Workflow Wireframe
2
+
3
+ This is the Pi-native BMAD operating loop optimized for low token usage, high reliability, and minimal manual routing.
4
+
5
+ ## Operating model
6
+
7
+ ```text
8
+ Install Project
9
+ -> official BMAD install
10
+ -> install pi-bmad-flow overlay
11
+ -> start pi
12
+
13
+ Project Start
14
+ -> /bmad-status
15
+ -> /bmad-next
16
+ -> overlay identifies missing phase artifact
17
+
18
+ Planning Loop
19
+ -> create PRD
20
+ -> create UX/WDS artifacts
21
+ -> create architecture
22
+ -> create epics and stories
23
+ -> run implementation-readiness gate
24
+
25
+ Delivery Loop
26
+ -> /bmad-start
27
+ -> overlay selects next ready story
28
+ -> overlay opens a dedicated Pi story session
29
+ -> overlay chooses lean or full packet
30
+ -> BMAD dev workflow executes story inside that story session
31
+ -> /bmad-review
32
+ -> /bmad-gate
33
+ -> story moves forward in sprint-status.yaml
34
+
35
+ Epic Completion
36
+ -> retrospective
37
+ -> next epic
38
+ ```
39
+
40
+ ## Human role
41
+
42
+ You should make fewer routing decisions and more approval decisions.
43
+
44
+ Your job becomes:
45
+
46
+ - approve planning outputs
47
+ - approve story boundaries
48
+ - intervene on ambiguous tradeoffs
49
+ - inspect review findings when risk is high
50
+
51
+ Pi should take over:
52
+
53
+ - phase detection
54
+ - next-step routing
55
+ - story pickup
56
+ - lean versus full context selection
57
+ - story-session setup
58
+ - gate selection
59
+
60
+ ## Recommended lane split
61
+
62
+ Use two planning lanes, but keep one delivery loop.
63
+
64
+ ### Lane A: BMAD core
65
+
66
+ - `bmad-create-prd`
67
+ - `bmad-create-architecture`
68
+ - `bmad-create-epics-and-stories`
69
+ - `bmad-check-implementation-readiness`
70
+
71
+ ### Lane B: WDS depth
72
+
73
+ - UX and scenario work
74
+ - page specs
75
+ - design delivery
76
+ - design system artifacts when reusable UI matters
77
+
78
+ The overlay should treat WDS artifacts as implementation inputs, not parallel decoration. That means UI stories should pull WDS page specs into the packet automatically when present.
79
+
80
+ ## Token minimization strategy
81
+
82
+ Default to lean packets.
83
+
84
+ Use full packets only when:
85
+
86
+ - story is security, auth, billing, migration, infra, or public API related
87
+ - project-context is missing or weak
88
+ - architecture is still unstable
89
+ - story is UI-heavy and depends on multiple WDS artifacts
90
+
91
+ Lean packet should usually include:
92
+
93
+ - current story file
94
+ - project-context if present
95
+ - relevant architecture section
96
+ - relevant WDS or UX file only if the story touches UI
97
+
98
+ Full packet adds:
99
+
100
+ - PRD context
101
+ - prior review or retrospective notes
102
+ - additional architecture and WDS support files
103
+
104
+ ## Reliability rules
105
+
106
+ - Never begin dev without a story file.
107
+ - Never start a new story while another is `in-progress`.
108
+ - Never skip architecture for medium or large work.
109
+ - Never treat WDS outputs as optional for UI-heavy stories.
110
+ - Never let operational session metadata become the source of truth over BMAD artifacts.
111
+
112
+ ## Best install experience for your eventual distribution
113
+
114
+ The cleanest user flow is:
115
+
116
+ 1. `npx bmad-method install`
117
+ 2. choose your standard BMAD module set
118
+ 3. `pi install -l your-overlay-package`
119
+ 4. open `pi`
120
+ 5. run `/bmad-status`
121
+
122
+ That keeps installation simple and preserves upstream compatibility.
@@ -0,0 +1,238 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import { detectBmadInstall, detectBmadProjectState } from "./detector.js";
3
+ import { decideGate } from "./gates.js";
4
+ import { buildStoryPacket, packetHint, selectPacketMode } from "./packets.js";
5
+ import { decideNextAction } from "./router.js";
6
+ import { updateStoryStatus } from "./sprint.js";
7
+ import type { GatePlan, StoryPacket } from "./types.js";
8
+ import { refreshBmadUi } from "./ui.js";
9
+
10
+ function now(): string {
11
+ return new Date().toISOString();
12
+ }
13
+
14
+ function appendBmadState(
15
+ pi: ExtensionAPI,
16
+ payload: {
17
+ event: "status" | "next" | "story-start" | "review" | "gate";
18
+ storyKey?: string;
19
+ packetMode?: "lean" | "full";
20
+ nextCommand?: string;
21
+ gateLevel?: "lightweight" | "strong";
22
+ files?: string[];
23
+ },
24
+ ): void {
25
+ pi.appendEntry("bmad-state", {
26
+ ...payload,
27
+ generatedAt: now(),
28
+ });
29
+ }
30
+
31
+ function formatPacketLaunchMessage(story: string, packet: StoryPacket): string {
32
+ const lines = [
33
+ `You are now in the dedicated story session for ${story}.`,
34
+ "",
35
+ `Start implementation with packet mode: ${packet.mode}`,
36
+ ...packet.notes.map((note) => `- ${note}`),
37
+ ];
38
+
39
+ if (packet.files.length > 0) {
40
+ lines.push("", "Load these files first:");
41
+ lines.push(...packet.files.map((file) => `- ${file}`));
42
+ }
43
+
44
+ lines.push("", `Then run the BMAD delivery workflow for ${story} and keep this session focused on that story only.`);
45
+ return lines.join("\n");
46
+ }
47
+
48
+ function formatGateMessage(plan: GatePlan): string {
49
+ const lines = [
50
+ `Gate level: ${plan.level}`,
51
+ `Reason: ${plan.reason}`,
52
+ "",
53
+ "Run these workflows in order:",
54
+ ...plan.commands.map((command, index) => `${index + 1}. ${command}`),
55
+ ];
56
+
57
+ if (plan.evidenceFiles.length > 0) {
58
+ lines.push("", "Available TEA evidence:");
59
+ lines.push(...plan.evidenceFiles.map((file) => `- ${file}`));
60
+ }
61
+
62
+ return lines.join("\n");
63
+ }
64
+
65
+ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
66
+ pi.registerCommand("bmad-status", {
67
+ description: "Show BMAD install, phase, and story queue status",
68
+ handler: async (_args, ctx) => {
69
+ const install = detectBmadInstall(cwd);
70
+ const state = detectBmadProjectState(cwd);
71
+ const next = decideNextAction(state);
72
+ const moduleText = install.modules.length > 0 ? install.modules.join(", ") : "none";
73
+ const planningSummary = `PRD ${state.inventory.prdFiles.length}, UX ${state.inventory.uxFiles.length}, Architecture ${state.inventory.architectureFiles.length}, Epics ${state.inventory.epicFiles.length}`;
74
+ const deliverySummary = `Stories ${state.inventory.storyFiles.length}, Reviews ${state.inventory.reviewFiles.length}, Sprint ${state.hasSprintStatus ? "yes" : "no"}`;
75
+ const designSummary = `WDS scenarios ${state.inventory.wdsScenarioFiles.length}, page specs ${state.inventory.wdsPageSpecFiles.length}, deliveries ${state.inventory.wdsDeliveryFiles.length}`;
76
+ const teaSummary = `TEA reviews ${state.inventory.teaReviewFiles.length}, trace ${state.inventory.teaTraceFiles.length}, NFR ${state.inventory.teaNfrFiles.length}`;
77
+
78
+ ctx.ui.notify(`BMAD installed: ${install.installed ? "yes" : "no"}`, "info");
79
+ ctx.ui.notify(`Modules: ${moduleText}`, "info");
80
+ ctx.ui.notify(`Phase: ${state.phase}`, "info");
81
+ ctx.ui.notify(`Planning: ${planningSummary}`, "info");
82
+ ctx.ui.notify(`Delivery: ${deliverySummary}`, "info");
83
+ ctx.ui.notify(`Design: ${designSummary}`, "info");
84
+ ctx.ui.notify(`TEA: ${teaSummary}`, "info");
85
+ ctx.ui.notify(`Next: ${next.command} - ${next.summary}`, "info");
86
+
87
+ refreshBmadUi(ctx, state, next);
88
+ appendBmadState(pi, { event: "status", nextCommand: next.command });
89
+ },
90
+ });
91
+
92
+ pi.registerCommand("bmad-phase", {
93
+ description: "Show current workflow phase and recommended route",
94
+ handler: async (_args, ctx) => {
95
+ const state = detectBmadProjectState(cwd);
96
+ const next = decideNextAction(state);
97
+ ctx.ui.notify(`Current phase: ${state.phase}`, "info");
98
+ ctx.ui.notify(
99
+ `Artifacts: PRD ${state.inventory.prdFiles.length}, UX ${state.inventory.uxFiles.length}, Architecture ${state.inventory.architectureFiles.length}, Stories ${state.inventory.storyFiles.length}`,
100
+ "info",
101
+ );
102
+ ctx.ui.notify(`Recommended: ${next.command} (${next.reason})`, "info");
103
+ refreshBmadUi(ctx, state, next);
104
+ appendBmadState(pi, { event: "status", nextCommand: next.command });
105
+ },
106
+ });
107
+
108
+ pi.registerCommand("bmad-next", {
109
+ description: "Determine next action from BMAD artifacts",
110
+ handler: async (_args, ctx) => {
111
+ const install = detectBmadInstall(cwd);
112
+ if (!install.installed) {
113
+ ctx.ui.notify("No BMAD installation detected at _bmad/_config/manifest.yaml", "warning");
114
+ return;
115
+ }
116
+
117
+ const state = detectBmadProjectState(cwd);
118
+ const next = decideNextAction(state);
119
+ ctx.ui.notify(next.summary, "info");
120
+ ctx.ui.notify(`Recommended command: ${next.command}`, "info");
121
+ refreshBmadUi(ctx, state, next);
122
+ appendBmadState(pi, { event: "next", nextCommand: next.command });
123
+
124
+ if (!ctx.hasUI) return;
125
+ const shouldLaunch = await ctx.ui.confirm("Launch recommended workflow?", next.command);
126
+ if (shouldLaunch) {
127
+ pi.sendUserMessage(next.command, { source: "extension" });
128
+ }
129
+ },
130
+ });
131
+
132
+ pi.registerCommand("bmad-start", {
133
+ description: "Start the next ready story in a dedicated story session with lean/full packet selection",
134
+ handler: async (_args, ctx) => {
135
+ const install = detectBmadInstall(cwd);
136
+ const state = detectBmadProjectState(cwd);
137
+
138
+ if (!install.installed || !state.hasSprintStatus) {
139
+ ctx.ui.notify("BMAD sprint status not found. Run bmad-sprint-planning first.", "warning");
140
+ return;
141
+ }
142
+
143
+ const story = state.nextReadyStory;
144
+ if (!story) {
145
+ ctx.ui.notify("No ready-for-dev story found. Run bmad-create-story first.", "warning");
146
+ return;
147
+ }
148
+
149
+ const mode = selectPacketMode(state);
150
+ const packet = buildStoryPacket(state, story, mode);
151
+ const statusUpdated = updateStoryStatus(install.paths.sprintStatusPath, story, "in-progress");
152
+ if (!statusUpdated) {
153
+ ctx.ui.notify(`Could not update sprint status for ${story}.`, "warning");
154
+ }
155
+
156
+ const originLeafId = ctx.sessionManager.getLeafId();
157
+ if (originLeafId) {
158
+ pi.setLabel(originLeafId, `handoff:${story}`);
159
+ }
160
+
161
+ await ctx.waitForIdle();
162
+ const sessionResult = await ctx.newSession({
163
+ parentSession: ctx.sessionManager.getSessionFile(),
164
+ });
165
+ if (sessionResult.cancelled) {
166
+ ctx.ui.notify("Story session creation was cancelled.", "warning");
167
+ return;
168
+ }
169
+
170
+ pi.setSessionName(`Story ${story}`);
171
+ appendBmadState(pi, {
172
+ event: "story-start",
173
+ storyKey: story,
174
+ packetMode: mode,
175
+ files: packet.files,
176
+ });
177
+
178
+ ctx.ui.notify(`Opened story session for ${story}`, "info");
179
+ ctx.ui.notify(packetHint(mode), "info");
180
+ if (packet.uiRelevant) {
181
+ ctx.ui.notify("WDS artifacts were added because this appears to be a UI-facing story.", "info");
182
+ }
183
+
184
+ const refreshedState = detectBmadProjectState(cwd);
185
+ const refreshedNext = decideNextAction(refreshedState);
186
+ refreshBmadUi(ctx, refreshedState, refreshedNext);
187
+ pi.sendUserMessage(formatPacketLaunchMessage(story, packet), { source: "extension" });
188
+ },
189
+ });
190
+
191
+ pi.registerCommand("bmad-review", {
192
+ description: "Run review workflow for active story",
193
+ handler: async (_args, ctx) => {
194
+ const state = detectBmadProjectState(cwd);
195
+ const story = state.activeStory ?? state.nextReadyStory;
196
+ if (!story) {
197
+ ctx.ui.notify("No active or ready story found to review.", "warning");
198
+ return;
199
+ }
200
+
201
+ ctx.ui.notify(`Running review for ${story}`, "info");
202
+ appendBmadState(pi, { event: "review", storyKey: story });
203
+ refreshBmadUi(ctx, state, decideNextAction(state));
204
+ pi.sendUserMessage(`bmad-code-review ${story}`, { source: "extension" });
205
+ },
206
+ });
207
+
208
+ pi.registerCommand("bmad-gate", {
209
+ description: "Run lightweight or TEA quality gate based on risk",
210
+ handler: async (_args, ctx) => {
211
+ const install = detectBmadInstall(cwd);
212
+ const state = detectBmadProjectState(cwd);
213
+ const story = state.activeStory ?? state.nextReadyStory;
214
+ if (!story) {
215
+ ctx.ui.notify("No active or ready story found to gate.", "warning");
216
+ return;
217
+ }
218
+ const decision = decideGate(state, install.modules.includes("tea"));
219
+ const plan: GatePlan = {
220
+ storyKey: story,
221
+ level: decision.level,
222
+ reason: decision.reason,
223
+ commands: decision.commands,
224
+ evidenceFiles: decision.evidenceFiles,
225
+ generatedAt: now(),
226
+ };
227
+ ctx.ui.notify(`Gate: ${decision.level}`, "info");
228
+ ctx.ui.notify(decision.reason, "info");
229
+ ctx.ui.notify(`Commands: ${decision.commands.length}`, "info");
230
+ if (decision.evidenceFiles.length > 0) {
231
+ ctx.ui.notify(`Evidence files: ${decision.evidenceFiles.length}`, "info");
232
+ }
233
+ appendBmadState(pi, { event: "gate", storyKey: story, gateLevel: decision.level });
234
+ refreshBmadUi(ctx, state, decideNextAction(state));
235
+ pi.sendUserMessage(formatGateMessage(plan), { source: "extension" });
236
+ },
237
+ });
238
+ }
@@ -0,0 +1,168 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { collectMarkdownFiles, collectYamlFiles } from "./files.js";
4
+ import { loadSprintStatus, resolveStoryLocation } from "./sprint.js";
5
+ import type { ArtifactInventory, BmadInstallState, BmadPaths, BmadPhase, BmadProjectState } from "./types.js";
6
+
7
+ function matches(files: string[], pattern: RegExp): string[] {
8
+ return files.filter((file) => pattern.test(file.toLowerCase()));
9
+ }
10
+
11
+ function unique(files: string[]): string[] {
12
+ return Array.from(new Set(files)).sort();
13
+ }
14
+
15
+ function collectStructuredFiles(dirPath: string): string[] {
16
+ return unique([...collectMarkdownFiles(dirPath), ...collectYamlFiles(dirPath)]);
17
+ }
18
+
19
+ function detectInventory(paths: BmadPaths): ArtifactInventory {
20
+ const sprint = loadSprintStatus(paths.sprintStatusPath);
21
+ const storyLocationDir = resolveStoryLocation(paths.sprintStatusPath, sprint);
22
+ const planningFiles = collectMarkdownFiles(paths.planningArtifactsDir);
23
+ const implementationFiles = collectMarkdownFiles(paths.implementationArtifactsDir);
24
+ const storyLocationFiles =
25
+ storyLocationDir !== paths.implementationArtifactsDir ? collectMarkdownFiles(storyLocationDir) : implementationFiles;
26
+ const designFiles = collectStructuredFiles(paths.designArtifactsDir);
27
+ const docsFiles = collectStructuredFiles(paths.docsDir);
28
+ const teaFiles = unique([
29
+ ...collectStructuredFiles(paths.teaArtifactsDir),
30
+ ...collectStructuredFiles(join(paths.root, "test-artifacts")),
31
+ ]);
32
+ const allContextFiles = unique([
33
+ ...planningFiles,
34
+ ...implementationFiles,
35
+ ...storyLocationFiles,
36
+ ...designFiles,
37
+ ...docsFiles,
38
+ ...teaFiles,
39
+ ]);
40
+
41
+ const wdsScenarioFiles = matches(allContextFiles, /\/c-ux-scenarios\//);
42
+ const wdsPageSpecFiles = matches(
43
+ allContextFiles,
44
+ /\/c-ux-scenarios\/.*(specifications\.md|page-spec|page-specification|\/frontend\/.*\.md)/,
45
+ );
46
+ const wdsStoryboardFiles = matches(allContextFiles, /storyboard/);
47
+ const wdsDeliveryFiles = matches(allContextFiles, /(design-deliver(y|ies)|\/deliveries\/|dd-\d+)/);
48
+ const wdsDesignSystemFiles = matches(allContextFiles, /\/d-design-system\//);
49
+ const wdsProgressFiles = matches(allContextFiles, /\/_progress\/00-design-log\.md$|scenario-tracking\.ya?ml|validation-report\.md/);
50
+ const teaTraceFiles = matches(allContextFiles, /traceability|gate-decision/);
51
+ const teaReviewFiles = matches(allContextFiles, /test-review/);
52
+ const teaNfrFiles = matches(allContextFiles, /nfr-assessment|nfr-report/);
53
+ const teaAutomationFiles = matches(allContextFiles, /automation-summary|atdd-checklist/);
54
+
55
+ return {
56
+ planningFiles,
57
+ implementationFiles,
58
+ designFiles,
59
+ docsFiles,
60
+ teaFiles,
61
+ prdFiles: matches(allContextFiles, /(^|\/)(prd|.*prd.*)\.md$/),
62
+ uxFiles: matches(
63
+ allContextFiles,
64
+ /(ux|wds-|wireframe|storyboard|page-spec|page-specification|design-delivery|scenario)/,
65
+ ),
66
+ architectureFiles: matches(allContextFiles, /architecture|adr/),
67
+ epicFiles: matches(allContextFiles, /(^|\/)epics?\/|epic-/),
68
+ storyFiles: matches(allContextFiles, /(^|\/)story-.*\.md$/),
69
+ reviewFiles: matches(allContextFiles, /review|retrospective|readiness-report|validation-report/),
70
+ projectContextFiles: matches(allContextFiles, /project-context/),
71
+ wdsScenarioFiles,
72
+ wdsPageSpecFiles,
73
+ wdsStoryboardFiles,
74
+ wdsDeliveryFiles,
75
+ wdsDesignSystemFiles,
76
+ wdsProgressFiles,
77
+ teaTraceFiles,
78
+ teaReviewFiles,
79
+ teaNfrFiles,
80
+ teaAutomationFiles,
81
+ };
82
+ }
83
+
84
+ function detectPhase(inventory: ArtifactInventory, hasSprintStatus: boolean): BmadPhase {
85
+ if (inventory.prdFiles.length === 0 && inventory.uxFiles.length === 0 && inventory.architectureFiles.length === 0) {
86
+ return "discovery";
87
+ }
88
+
89
+ if ((inventory.prdFiles.length > 0 || inventory.uxFiles.length > 0) && inventory.architectureFiles.length === 0) {
90
+ return "planning";
91
+ }
92
+
93
+ if ((inventory.architectureFiles.length > 0 || inventory.epicFiles.length > 0) && !hasSprintStatus) {
94
+ return "solutioning";
95
+ }
96
+
97
+ if (hasSprintStatus && inventory.storyFiles.length === 0) {
98
+ return "implementation-setup";
99
+ }
100
+
101
+ return "delivery";
102
+ }
103
+
104
+ export function getBmadPaths(cwd: string): BmadPaths {
105
+ const root = cwd;
106
+ const bmadDir = join(root, "_bmad");
107
+ const planningArtifactsDir = join(root, "_bmad-output", "planning-artifacts");
108
+ const implementationArtifactsDir = join(root, "_bmad-output", "implementation-artifacts");
109
+
110
+ return {
111
+ root,
112
+ bmadDir,
113
+ manifestPath: join(bmadDir, "_config", "manifest.yaml"),
114
+ planningArtifactsDir,
115
+ implementationArtifactsDir,
116
+ teaArtifactsDir: join(root, "_bmad-output", "test-artifacts"),
117
+ designArtifactsDir: join(root, "design-artifacts"),
118
+ docsDir: join(root, "docs"),
119
+ sprintStatusPath: join(implementationArtifactsDir, "sprint-status.yaml"),
120
+ extensionStateDir: join(root, ".pi", "bmad"),
121
+ extensionStatePath: join(root, ".pi", "bmad", "state.json"),
122
+ };
123
+ }
124
+
125
+ export function detectBmadInstall(cwd: string): BmadInstallState {
126
+ const paths = getBmadPaths(cwd);
127
+ const installed = existsSync(paths.manifestPath);
128
+ const knownModules = ["core", "bmm", "cis", "wds", "tea", "bmb"];
129
+ const modules = knownModules.filter((moduleId) => existsSync(join(paths.bmadDir, moduleId)));
130
+ return { installed, modules, paths };
131
+ }
132
+
133
+ export function detectBmadProjectState(cwd: string): BmadProjectState {
134
+ const install = detectBmadInstall(cwd);
135
+ const inventory = detectInventory(install.paths);
136
+ const hasPlanningArtifacts = inventory.planningFiles.length > 0;
137
+ const hasImplementationArtifacts = inventory.implementationFiles.length > 0;
138
+ const hasSprintStatus = existsSync(install.paths.sprintStatusPath);
139
+ const phase = detectPhase(inventory, hasSprintStatus);
140
+ const hasWdsArtifacts =
141
+ inventory.wdsScenarioFiles.length > 0 ||
142
+ inventory.wdsPageSpecFiles.length > 0 ||
143
+ inventory.wdsDeliveryFiles.length > 0 ||
144
+ inventory.wdsDesignSystemFiles.length > 0;
145
+ const hasTeaArtifacts =
146
+ inventory.teaFiles.length > 0 ||
147
+ inventory.teaTraceFiles.length > 0 ||
148
+ inventory.teaReviewFiles.length > 0 ||
149
+ inventory.teaNfrFiles.length > 0;
150
+
151
+ const sprint = hasSprintStatus ? loadSprintStatus(install.paths.sprintStatusPath) : undefined;
152
+ const nextBacklogStory = sprint?.storyOrder.find((story) => sprint.developmentStatus[story] === "backlog");
153
+ const nextReadyStory = sprint?.storyOrder.find((story) => sprint.developmentStatus[story] === "ready-for-dev");
154
+ const activeStory = sprint?.storyOrder.find((story) => sprint.developmentStatus[story] === "in-progress");
155
+
156
+ return {
157
+ phase,
158
+ hasPlanningArtifacts,
159
+ hasImplementationArtifacts,
160
+ hasSprintStatus,
161
+ hasWdsArtifacts,
162
+ hasTeaArtifacts,
163
+ inventory,
164
+ nextBacklogStory,
165
+ nextReadyStory,
166
+ activeStory,
167
+ };
168
+ }