pi-bmad-flow 0.1.3 → 0.1.4
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
CHANGED
|
@@ -107,5 +107,71 @@ Current behavior:
|
|
|
107
107
|
- BMAD-specific compaction summaries
|
|
108
108
|
- lightweight BMAD status widget and footer state
|
|
109
109
|
- optional LLM access to deterministic BMAD state through the `bmad_orchestrator` tool
|
|
110
|
+
- workflow-aware model and thinking presets for routing, story prep, story start, review, and gates
|
|
110
111
|
|
|
111
112
|
This package does not replace official BMAD workflows. It reduces routing overhead around them.
|
|
113
|
+
|
|
114
|
+
## Optional model policy override
|
|
115
|
+
|
|
116
|
+
By default, the overlay uses a GPT-only policy aimed at:
|
|
117
|
+
|
|
118
|
+
- `openai-codex/gpt-5.4-mini` for routing and status
|
|
119
|
+
- `openai-codex/gpt-5.4` for story prep, story start, review, and gates
|
|
120
|
+
- thinking level as the main optimization knob
|
|
121
|
+
|
|
122
|
+
The policy is applied both for overlay commands and for common BMAD workflow inputs such as:
|
|
123
|
+
|
|
124
|
+
- `bmad-create-story`
|
|
125
|
+
- `bmad-dev-story`
|
|
126
|
+
- `bmad-create-prd`
|
|
127
|
+
- `bmad-create-architecture`
|
|
128
|
+
- `bmad-create-epics-and-stories`
|
|
129
|
+
- `bmad-code-review`
|
|
130
|
+
- `bmad-testarch-test-review`
|
|
131
|
+
- `bmad-testarch-trace`
|
|
132
|
+
- `bmad-testarch-nfr`
|
|
133
|
+
|
|
134
|
+
You can override the default preset behavior with:
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
.pi/bmad/model-policy.json
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Shape:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"presets": {
|
|
145
|
+
"routing": {
|
|
146
|
+
"model": "openai-codex/gpt-5.4-mini",
|
|
147
|
+
"thinking": "minimal"
|
|
148
|
+
},
|
|
149
|
+
"story-prep": {
|
|
150
|
+
"model": "openai-codex/gpt-5.4",
|
|
151
|
+
"thinking": "low"
|
|
152
|
+
},
|
|
153
|
+
"story-start-lean": {
|
|
154
|
+
"model": "openai-codex/gpt-5.4",
|
|
155
|
+
"thinking": "low"
|
|
156
|
+
},
|
|
157
|
+
"story-start-full": {
|
|
158
|
+
"model": "openai-codex/gpt-5.4",
|
|
159
|
+
"thinking": "medium"
|
|
160
|
+
},
|
|
161
|
+
"review": {
|
|
162
|
+
"model": "openai-codex/gpt-5.4",
|
|
163
|
+
"thinking": "low"
|
|
164
|
+
},
|
|
165
|
+
"gate-light": {
|
|
166
|
+
"model": "openai-codex/gpt-5.4",
|
|
167
|
+
"thinking": "low"
|
|
168
|
+
},
|
|
169
|
+
"gate-strong": {
|
|
170
|
+
"model": "openai-codex/gpt-5.4",
|
|
171
|
+
"thinking": "medium"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
If you omit `model`, the overlay falls back to tier-based matching.
|
|
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
|
2
2
|
import { detectBmadInstall, detectBmadProjectState } from "./detector.js";
|
|
3
3
|
import { decideGate } from "./gates.js";
|
|
4
4
|
import { buildStoryPacket, packetHint, selectPacketMode } from "./packets.js";
|
|
5
|
+
import { applyPreset, presetForNextCommand } from "./policy.js";
|
|
5
6
|
import { decideNextAction } from "./router.js";
|
|
6
7
|
import { updateStoryStatus } from "./sprint.js";
|
|
7
8
|
import type { GatePlan, StoryPacket } from "./types.js";
|
|
@@ -45,6 +46,11 @@ function formatPacketLaunchMessage(story: string, packet: StoryPacket): string {
|
|
|
45
46
|
return lines.join("\n");
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function currentModelLabel(ctx: { model?: { provider: string; id: string } }): string | undefined {
|
|
50
|
+
if (!ctx.model) return undefined;
|
|
51
|
+
return `${ctx.model.provider}/${ctx.model.id}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
48
54
|
function formatGateMessage(plan: GatePlan): string {
|
|
49
55
|
const lines = [
|
|
50
56
|
`Gate level: ${plan.level}`,
|
|
@@ -66,6 +72,7 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
66
72
|
pi.registerCommand("bmad-status", {
|
|
67
73
|
description: "Show BMAD install, phase, and story queue status",
|
|
68
74
|
handler: async (_args, ctx) => {
|
|
75
|
+
await applyPreset(pi, ctx, cwd, "routing");
|
|
69
76
|
const install = detectBmadInstall(cwd);
|
|
70
77
|
const state = detectBmadProjectState(cwd);
|
|
71
78
|
const next = decideNextAction(state);
|
|
@@ -93,7 +100,11 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
93
100
|
}
|
|
94
101
|
ctx.ui.notify(`Next: ${next.command} - ${next.summary}`, "info");
|
|
95
102
|
|
|
96
|
-
refreshBmadUi(ctx, state, next
|
|
103
|
+
refreshBmadUi(ctx, state, next, {
|
|
104
|
+
preset: "routing",
|
|
105
|
+
model: currentModelLabel(ctx),
|
|
106
|
+
thinking: pi.getThinkingLevel(),
|
|
107
|
+
});
|
|
97
108
|
appendBmadState(pi, { event: "status", nextCommand: next.command });
|
|
98
109
|
},
|
|
99
110
|
});
|
|
@@ -101,6 +112,7 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
101
112
|
pi.registerCommand("bmad-phase", {
|
|
102
113
|
description: "Show current workflow phase and recommended route",
|
|
103
114
|
handler: async (_args, ctx) => {
|
|
115
|
+
await applyPreset(pi, ctx, cwd, "routing");
|
|
104
116
|
const state = detectBmadProjectState(cwd);
|
|
105
117
|
const next = decideNextAction(state);
|
|
106
118
|
ctx.ui.notify(`Current phase: ${state.phase}`, "info");
|
|
@@ -115,7 +127,11 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
115
127
|
ctx.ui.notify(`Story location: ${state.activeStoryLocation}`, "info");
|
|
116
128
|
}
|
|
117
129
|
ctx.ui.notify(`Recommended: ${next.command} (${next.reason})`, "info");
|
|
118
|
-
refreshBmadUi(ctx, state, next
|
|
130
|
+
refreshBmadUi(ctx, state, next, {
|
|
131
|
+
preset: "routing",
|
|
132
|
+
model: currentModelLabel(ctx),
|
|
133
|
+
thinking: pi.getThinkingLevel(),
|
|
134
|
+
});
|
|
119
135
|
appendBmadState(pi, { event: "status", nextCommand: next.command });
|
|
120
136
|
},
|
|
121
137
|
});
|
|
@@ -123,6 +139,7 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
123
139
|
pi.registerCommand("bmad-next", {
|
|
124
140
|
description: "Determine next action from BMAD artifacts",
|
|
125
141
|
handler: async (_args, ctx) => {
|
|
142
|
+
await applyPreset(pi, ctx, cwd, "routing");
|
|
126
143
|
const install = detectBmadInstall(cwd);
|
|
127
144
|
if (!install.installed) {
|
|
128
145
|
ctx.ui.notify("No BMAD installation detected at _bmad/_config/manifest.yaml", "warning");
|
|
@@ -133,12 +150,17 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
133
150
|
const next = decideNextAction(state);
|
|
134
151
|
ctx.ui.notify(next.summary, "info");
|
|
135
152
|
ctx.ui.notify(`Recommended command: ${next.command}`, "info");
|
|
136
|
-
refreshBmadUi(ctx, state, next
|
|
153
|
+
refreshBmadUi(ctx, state, next, {
|
|
154
|
+
preset: "routing",
|
|
155
|
+
model: currentModelLabel(ctx),
|
|
156
|
+
thinking: pi.getThinkingLevel(),
|
|
157
|
+
});
|
|
137
158
|
appendBmadState(pi, { event: "next", nextCommand: next.command });
|
|
138
159
|
|
|
139
160
|
if (!ctx.hasUI) return;
|
|
140
161
|
const shouldLaunch = await ctx.ui.confirm("Launch recommended workflow?", next.command);
|
|
141
162
|
if (shouldLaunch) {
|
|
163
|
+
await applyPreset(pi, ctx, cwd, presetForNextCommand(next.command));
|
|
142
164
|
pi.sendUserMessage(next.command, { source: "extension" });
|
|
143
165
|
}
|
|
144
166
|
},
|
|
@@ -162,6 +184,7 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
162
184
|
}
|
|
163
185
|
|
|
164
186
|
const mode = selectPacketMode(state);
|
|
187
|
+
await applyPreset(pi, ctx, cwd, mode === "full" ? "story-start-full" : "story-start-lean");
|
|
165
188
|
const packet = buildStoryPacket(state, story, mode);
|
|
166
189
|
const statusUpdated = updateStoryStatus(install.paths.sprintStatusPath, story, "in-progress");
|
|
167
190
|
if (!statusUpdated) {
|
|
@@ -198,7 +221,11 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
198
221
|
|
|
199
222
|
const refreshedState = detectBmadProjectState(cwd);
|
|
200
223
|
const refreshedNext = decideNextAction(refreshedState);
|
|
201
|
-
refreshBmadUi(ctx, refreshedState, refreshedNext
|
|
224
|
+
refreshBmadUi(ctx, refreshedState, refreshedNext, {
|
|
225
|
+
preset: mode === "full" ? "story-start-full" : "story-start-lean",
|
|
226
|
+
model: currentModelLabel(ctx),
|
|
227
|
+
thinking: pi.getThinkingLevel(),
|
|
228
|
+
});
|
|
202
229
|
pi.sendUserMessage(formatPacketLaunchMessage(story, packet), { source: "extension" });
|
|
203
230
|
},
|
|
204
231
|
});
|
|
@@ -206,6 +233,7 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
206
233
|
pi.registerCommand("bmad-review", {
|
|
207
234
|
description: "Run review workflow for active story",
|
|
208
235
|
handler: async (_args, ctx) => {
|
|
236
|
+
await applyPreset(pi, ctx, cwd, "review");
|
|
209
237
|
const state = detectBmadProjectState(cwd);
|
|
210
238
|
const story = state.activeStory ?? state.nextReadyStory;
|
|
211
239
|
if (!story) {
|
|
@@ -215,7 +243,11 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
215
243
|
|
|
216
244
|
ctx.ui.notify(`Running review for ${story}`, "info");
|
|
217
245
|
appendBmadState(pi, { event: "review", storyKey: story });
|
|
218
|
-
refreshBmadUi(ctx, state, decideNextAction(state)
|
|
246
|
+
refreshBmadUi(ctx, state, decideNextAction(state), {
|
|
247
|
+
preset: "review",
|
|
248
|
+
model: currentModelLabel(ctx),
|
|
249
|
+
thinking: pi.getThinkingLevel(),
|
|
250
|
+
});
|
|
219
251
|
pi.sendUserMessage(`bmad-code-review ${story}`, { source: "extension" });
|
|
220
252
|
},
|
|
221
253
|
});
|
|
@@ -231,6 +263,7 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
231
263
|
return;
|
|
232
264
|
}
|
|
233
265
|
const decision = decideGate(state, install.modules.includes("tea"));
|
|
266
|
+
await applyPreset(pi, ctx, cwd, decision.level === "strong" ? "gate-strong" : "gate-light");
|
|
234
267
|
const plan: GatePlan = {
|
|
235
268
|
storyKey: story,
|
|
236
269
|
level: decision.level,
|
|
@@ -246,7 +279,11 @@ export function registerBmadCommands(pi: ExtensionAPI, cwd: string): void {
|
|
|
246
279
|
ctx.ui.notify(`Evidence files: ${decision.evidenceFiles.length}`, "info");
|
|
247
280
|
}
|
|
248
281
|
appendBmadState(pi, { event: "gate", storyKey: story, gateLevel: decision.level });
|
|
249
|
-
refreshBmadUi(ctx, state, decideNextAction(state)
|
|
282
|
+
refreshBmadUi(ctx, state, decideNextAction(state), {
|
|
283
|
+
preset: decision.level === "strong" ? "gate-strong" : "gate-light",
|
|
284
|
+
model: currentModelLabel(ctx),
|
|
285
|
+
thinking: pi.getThinkingLevel(),
|
|
286
|
+
});
|
|
250
287
|
pi.sendUserMessage(formatGateMessage(plan), { source: "extension" });
|
|
251
288
|
},
|
|
252
289
|
});
|
|
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
|
2
2
|
import { detectBmadInstall, detectBmadProjectState } from "./detector.js";
|
|
3
3
|
import { registerBmadCommands } from "./commands.js";
|
|
4
4
|
import { decideNextAction } from "./router.js";
|
|
5
|
+
import { applyPreset, presetForWorkflowInput } from "./policy.js";
|
|
5
6
|
import { registerBmadTool } from "./tool.js";
|
|
6
7
|
import { clearBmadUi, refreshBmadUi } from "./ui.js";
|
|
7
8
|
|
|
@@ -55,12 +56,25 @@ export default function bmadOrchestrator(pi: ExtensionAPI): void {
|
|
|
55
56
|
registerBmadCommands(pi, cwd);
|
|
56
57
|
registerBmadTool(pi, cwd);
|
|
57
58
|
|
|
58
|
-
pi.on("input", async (event) => {
|
|
59
|
+
pi.on("input", async (event, ctx) => {
|
|
59
60
|
if (event.source === "extension") return { action: "continue" };
|
|
61
|
+
|
|
60
62
|
const command = routeIntent(event.text);
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
if (command) {
|
|
64
|
+
const preset = presetForWorkflowInput(command);
|
|
65
|
+
if (preset) {
|
|
66
|
+
await applyPreset(pi, ctx, cwd, preset);
|
|
67
|
+
}
|
|
68
|
+
pi.sendUserMessage(command, { source: "extension" });
|
|
69
|
+
return { action: "handled" };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const workflowPreset = presetForWorkflowInput(event.text);
|
|
73
|
+
if (workflowPreset) {
|
|
74
|
+
await applyPreset(pi, ctx, cwd, workflowPreset);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { action: "continue" };
|
|
64
78
|
});
|
|
65
79
|
|
|
66
80
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -74,7 +88,10 @@ export default function bmadOrchestrator(pi: ExtensionAPI): void {
|
|
|
74
88
|
const modules = install.modules.length > 0 ? install.modules.join(", ") : "none";
|
|
75
89
|
const state = detectBmadProjectState(cwd);
|
|
76
90
|
const next = decideNextAction(state);
|
|
77
|
-
refreshBmadUi(ctx, state, next
|
|
91
|
+
refreshBmadUi(ctx, state, next, {
|
|
92
|
+
model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined,
|
|
93
|
+
thinking: pi.getThinkingLevel(),
|
|
94
|
+
});
|
|
78
95
|
ctx.ui.notify(`pi-bmad-flow ready. BMAD modules: ${modules}`, "info");
|
|
79
96
|
});
|
|
80
97
|
|
|
@@ -83,7 +100,10 @@ export default function bmadOrchestrator(pi: ExtensionAPI): void {
|
|
|
83
100
|
if (!install.installed) return;
|
|
84
101
|
const state = detectBmadProjectState(cwd);
|
|
85
102
|
const next = decideNextAction(state);
|
|
86
|
-
refreshBmadUi(ctx, state, next
|
|
103
|
+
refreshBmadUi(ctx, state, next, {
|
|
104
|
+
model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined,
|
|
105
|
+
thinking: pi.getThinkingLevel(),
|
|
106
|
+
});
|
|
87
107
|
});
|
|
88
108
|
|
|
89
109
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { ExtensionAPI, ExtensionContext, ThinkingLevel } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
|
|
5
|
+
export type BmadPresetName =
|
|
6
|
+
| "routing"
|
|
7
|
+
| "story-prep"
|
|
8
|
+
| "story-start-lean"
|
|
9
|
+
| "story-start-full"
|
|
10
|
+
| "review"
|
|
11
|
+
| "gate-light"
|
|
12
|
+
| "gate-strong";
|
|
13
|
+
|
|
14
|
+
type ModelTier = "fast" | "balanced" | "strong";
|
|
15
|
+
|
|
16
|
+
interface AvailableModel {
|
|
17
|
+
provider: string;
|
|
18
|
+
id: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface PresetConfig {
|
|
23
|
+
model?: string;
|
|
24
|
+
tier?: ModelTier;
|
|
25
|
+
thinking: ThinkingLevel;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ModelPolicyConfig {
|
|
29
|
+
tiers?: Partial<Record<ModelTier, string[]>>;
|
|
30
|
+
presets?: Partial<Record<BmadPresetName, Partial<PresetConfig>>>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const DEFAULT_TIERS: Record<ModelTier, string[]> = {
|
|
34
|
+
fast: ["openai-codex/gpt-5.4-mini", "openai-codex/*mini*"],
|
|
35
|
+
balanced: ["openai-codex/gpt-5.4", "openai-codex/*gpt-5.4*", "openai-codex/*codex*"],
|
|
36
|
+
strong: ["openai-codex/gpt-5.4", "openai-codex/*gpt-5.4*", "openai-codex/*codex*"],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const DEFAULT_PRESETS: Record<BmadPresetName, PresetConfig> = {
|
|
40
|
+
routing: { model: "openai-codex/gpt-5.4-mini", thinking: "minimal" },
|
|
41
|
+
"story-prep": { model: "openai-codex/gpt-5.4", thinking: "low" },
|
|
42
|
+
"story-start-lean": { model: "openai-codex/gpt-5.4", thinking: "low" },
|
|
43
|
+
"story-start-full": { model: "openai-codex/gpt-5.4", thinking: "medium" },
|
|
44
|
+
review: { model: "openai-codex/gpt-5.4", thinking: "low" },
|
|
45
|
+
"gate-light": { model: "openai-codex/gpt-5.4", thinking: "low" },
|
|
46
|
+
"gate-strong": { model: "openai-codex/gpt-5.4", thinking: "medium" },
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function loadPolicyConfig(cwd: string): ModelPolicyConfig | undefined {
|
|
50
|
+
const configPath = join(cwd, ".pi", "bmad", "model-policy.json");
|
|
51
|
+
if (!existsSync(configPath)) return undefined;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(readFileSync(configPath, "utf8")) as ModelPolicyConfig;
|
|
55
|
+
} catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function escapeRegex(value: string): string {
|
|
61
|
+
return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function matchesPattern(value: string, pattern: string): boolean {
|
|
65
|
+
const regex = new RegExp(`^${escapeRegex(pattern.toLowerCase()).replace(/\*/g, ".*")}$`);
|
|
66
|
+
return regex.test(value.toLowerCase());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function modelKey(model: AvailableModel): string {
|
|
70
|
+
return `${model.provider}/${model.id}`.toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolvePresetConfig(cwd: string, preset: BmadPresetName): { config: PresetConfig; tierPatterns?: string[] } {
|
|
74
|
+
const loaded = loadPolicyConfig(cwd);
|
|
75
|
+
const base = DEFAULT_PRESETS[preset];
|
|
76
|
+
const override = loaded?.presets?.[preset];
|
|
77
|
+
const config: PresetConfig = {
|
|
78
|
+
model: override?.model ?? base.model,
|
|
79
|
+
tier: override?.tier ?? base.tier,
|
|
80
|
+
thinking: override?.thinking ?? base.thinking,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const tierPatterns = config.tier ? loaded?.tiers?.[config.tier] ?? DEFAULT_TIERS[config.tier] : undefined;
|
|
84
|
+
return { config, tierPatterns };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function selectModel(
|
|
88
|
+
ctx: ExtensionContext,
|
|
89
|
+
explicitModel: string | undefined,
|
|
90
|
+
tierPatterns: string[] | undefined,
|
|
91
|
+
): Promise<AvailableModel | undefined> {
|
|
92
|
+
const available = (await ctx.modelRegistry.getAvailable()) as AvailableModel[];
|
|
93
|
+
const current = ctx.model as AvailableModel | undefined;
|
|
94
|
+
|
|
95
|
+
if (explicitModel) {
|
|
96
|
+
const found = available.find((candidate) => modelKey(candidate) === explicitModel.toLowerCase());
|
|
97
|
+
if (found) return found;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (tierPatterns) {
|
|
101
|
+
if (current) {
|
|
102
|
+
const currentKey = modelKey(current);
|
|
103
|
+
if (tierPatterns.some((pattern) => matchesPattern(currentKey, pattern))) return current;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const pattern of tierPatterns) {
|
|
107
|
+
const found = available.find((candidate) => matchesPattern(modelKey(candidate), pattern));
|
|
108
|
+
if (found) return found;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return current ?? available[0];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function applyPreset(pi: ExtensionAPI, ctx: ExtensionContext, cwd: string, preset: BmadPresetName): Promise<void> {
|
|
116
|
+
const { config, tierPatterns } = resolvePresetConfig(cwd, preset);
|
|
117
|
+
const selectedModel = await selectModel(ctx, config.model, tierPatterns);
|
|
118
|
+
|
|
119
|
+
if (selectedModel) {
|
|
120
|
+
const current = ctx.model as AvailableModel | undefined;
|
|
121
|
+
const currentKey = current ? modelKey(current) : undefined;
|
|
122
|
+
const selectedKey = modelKey(selectedModel);
|
|
123
|
+
if (currentKey !== selectedKey) {
|
|
124
|
+
const changed = await pi.setModel(selectedModel);
|
|
125
|
+
if (changed) {
|
|
126
|
+
ctx.ui.notify(`BMAD preset ${preset}: model ${selectedModel.provider}/${selectedModel.id}`, "info");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (pi.getThinkingLevel() !== config.thinking) {
|
|
132
|
+
pi.setThinkingLevel(config.thinking);
|
|
133
|
+
ctx.ui.notify(`BMAD preset ${preset}: thinking ${config.thinking}`, "info");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function presetForNextCommand(command: string): BmadPresetName {
|
|
138
|
+
if (command === "/bmad-start") return "story-start-lean";
|
|
139
|
+
if (command === "bmad-create-story") return "story-prep";
|
|
140
|
+
if (command === "bmad-create-prd" || command === "bmad-create-architecture") return "story-start-full";
|
|
141
|
+
return "routing";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function presetForWorkflowInput(text: string): BmadPresetName | undefined {
|
|
145
|
+
const normalized = text.trim().toLowerCase();
|
|
146
|
+
if (!normalized) return undefined;
|
|
147
|
+
|
|
148
|
+
if (normalized === "/bmad-status" || normalized === "/bmad-next" || normalized === "/bmad-phase") return "routing";
|
|
149
|
+
if (normalized === "/bmad-review") return "review";
|
|
150
|
+
if (normalized === "/bmad-gate") return "gate-light";
|
|
151
|
+
if (normalized === "/bmad-start") return "story-start-lean";
|
|
152
|
+
if (/^bmad-create-story\b/.test(normalized)) return "story-prep";
|
|
153
|
+
if (/^bmad-dev-story\b/.test(normalized)) return "story-start-lean";
|
|
154
|
+
if (/^bmad-create-prd\b/.test(normalized)) return "story-start-full";
|
|
155
|
+
if (/^bmad-create-architecture\b/.test(normalized)) return "story-start-full";
|
|
156
|
+
if (/^bmad-create-epics-and-stories\b/.test(normalized)) return "story-prep";
|
|
157
|
+
if (/^bmad-create-ux-design\b/.test(normalized)) return "story-prep";
|
|
158
|
+
if (/^bmad-code-review\b/.test(normalized)) return "review";
|
|
159
|
+
if (/^bmad-testarch-(test-review|trace)\b/.test(normalized)) return "gate-light";
|
|
160
|
+
if (/^bmad-testarch-nfr\b/.test(normalized)) return "gate-strong";
|
|
161
|
+
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
@@ -1,15 +1,26 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
1
|
+
import type { ExtensionContext, ThinkingLevel } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import type { BmadProjectState } from "./types.js";
|
|
3
3
|
import type { NextAction } from "./router.js";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
interface BmadUiMeta {
|
|
6
|
+
preset?: string;
|
|
7
|
+
model?: string;
|
|
8
|
+
thinking?: ThinkingLevel;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function refreshBmadUi(ctx: ExtensionContext, state: BmadProjectState, next: NextAction, meta?: BmadUiMeta): void {
|
|
6
12
|
const activeStory = state.activeStory ?? state.nextReadyStory ?? "none";
|
|
7
|
-
|
|
13
|
+
const modelLine = meta?.model ? ` | ${meta.model}` : "";
|
|
14
|
+
const presetLine = meta?.preset ? ` | ${meta.preset}` : "";
|
|
15
|
+
const thinkingLine = meta?.thinking ? ` | ${meta.thinking}` : "";
|
|
16
|
+
|
|
17
|
+
ctx.ui.setStatus("bmad", `BMAD ${state.phase} | story ${activeStory}${presetLine}${thinkingLine}`);
|
|
8
18
|
ctx.ui.setWidget("bmad", [
|
|
9
19
|
`Phase: ${state.phase}`,
|
|
10
20
|
`Active: ${state.activeStory ?? "none"}`,
|
|
11
21
|
`Ready: ${state.nextReadyStory ?? "none"}`,
|
|
12
22
|
`Next: ${next.command} — ${next.summary}`,
|
|
23
|
+
`Preset: ${meta?.preset ?? "default"}${thinkingLine}${modelLine}`,
|
|
13
24
|
]);
|
|
14
25
|
}
|
|
15
26
|
|
package/package.json
CHANGED
package/scripts/check.mjs
CHANGED
|
@@ -34,6 +34,7 @@ function main() {
|
|
|
34
34
|
"extensions/bmad-orchestrator/commands.ts",
|
|
35
35
|
"extensions/bmad-orchestrator/detector.ts",
|
|
36
36
|
"extensions/bmad-orchestrator/packets.ts",
|
|
37
|
+
"extensions/bmad-orchestrator/policy.ts",
|
|
37
38
|
"extensions/bmad-orchestrator/tool.ts",
|
|
38
39
|
"extensions/bmad-orchestrator/ui.ts",
|
|
39
40
|
"scripts/bootstrap.mjs",
|