@hyperdrive.bot/bmad-workflow 1.0.9 → 1.0.10

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.
@@ -147,6 +147,7 @@ export default class Decompose extends Command {
147
147
  filePattern: flags['file-pattern'],
148
148
  goal: args.goal,
149
149
  maxParallel: flags['max-parallel'],
150
+ model: flags.model,
150
151
  perFile: flags['per-file'],
151
152
  planOnly: flags['plan-only'],
152
153
  storyFormat: flags['story-format'],
@@ -18,7 +18,7 @@ export const PROVIDER_CONFIGS = {
18
18
  supportsFileReferences: false,
19
19
  },
20
20
  opencode: {
21
- command: 'opencode',
21
+ command: process.env.HOME + '/.opencode/bin/opencode',
22
22
  flags: ['run', '--format', 'json'],
23
23
  modelFlag: '--model',
24
24
  supportsFileReferences: true,
@@ -155,11 +155,11 @@ export class OpenCodeAgentRunner {
155
155
  tempDir = await mkdtemp(join(tmpdir(), 'opencode-prompt-'));
156
156
  tempFile = join(tempDir, 'prompt.txt');
157
157
  await writeFile(tempFile, prompt, 'utf8');
158
- // Build command with --file flag (Open Code specific)
159
- // Format: opencode run --format json [--model provider/model] --file "${tempFile}"
158
+ // Build command - use stdin to pass the prompt (handles newlines better)
159
+ // Format: cat tempfile | opencode run --format json [--model provider/model] -
160
160
  const flags = this.config.flags.join(' ');
161
161
  const modelArg = options.model ? `${this.config.modelFlag} ${options.model}` : '';
162
- const command = `${this.config.command} ${flags} ${modelArg} --file "${tempFile}"`.replace(/\s+/g, ' ').trim();
162
+ const command = `cat "${tempFile}" | ${this.config.command} ${flags} ${modelArg} -`.replace(/\s+/g, ' ').trim();
163
163
  // Log the command being executed
164
164
  this.logger.info({
165
165
  command,
@@ -264,47 +264,65 @@ export class OpenCodeAgentRunner {
264
264
  if (!stdoutData || stdoutData.trim().length === 0) {
265
265
  return '';
266
266
  }
267
- try {
268
- const parsed = JSON.parse(stdoutData);
269
- // Try common JSON response structures
270
- // Open Code may output different structures depending on version
271
- if (typeof parsed === 'string') {
272
- return parsed;
273
- }
274
- if (parsed.content) {
275
- return String(parsed.content);
276
- }
277
- if (parsed.message) {
278
- return String(parsed.message);
279
- }
280
- if (parsed.response) {
281
- return String(parsed.response);
282
- }
283
- if (parsed.output) {
284
- return String(parsed.output);
285
- }
286
- if (parsed.text) {
287
- return String(parsed.text);
288
- }
289
- // If we have a result array, try to extract content
290
- if (Array.isArray(parsed)) {
291
- const messages = parsed
292
- .filter((item) => item.type === 'message' || item.content || item.text)
293
- .map((item) => item.content || item.text || item.message || '')
294
- .filter(Boolean);
295
- if (messages.length > 0) {
296
- return messages.join('\n');
267
+ // Open Code outputs NDJSON (newline-delimited JSON)
268
+ // Each line is a separate JSON object: step_start, text, step_finish
269
+ // We need to extract the "text" from entries with type="text"
270
+ const lines = stdoutData.trim().split('\n');
271
+ const textParts = [];
272
+ for (const line of lines) {
273
+ if (!line.trim())
274
+ continue;
275
+ try {
276
+ const parsed = JSON.parse(line);
277
+ // Handle JSON string (like "Just a string")
278
+ if (typeof parsed === 'string') {
279
+ textParts.push(parsed);
280
+ continue;
297
281
  }
282
+ // Handle JSON array
283
+ if (Array.isArray(parsed)) {
284
+ for (const item of parsed) {
285
+ if (item.content)
286
+ textParts.push(item.content);
287
+ else if (item.text)
288
+ textParts.push(item.text);
289
+ else if (item.message)
290
+ textParts.push(item.message);
291
+ }
292
+ continue;
293
+ }
294
+ // Extract text from "text" type entries (Open Code NDJSON format)
295
+ if (parsed.type === 'text' && parsed.part?.text) {
296
+ textParts.push(parsed.part.text);
297
+ }
298
+ else if (parsed.text) {
299
+ textParts.push(parsed.text);
300
+ }
301
+ else if (parsed.content) {
302
+ textParts.push(parsed.content);
303
+ }
304
+ else if (parsed.message) {
305
+ textParts.push(parsed.message);
306
+ }
307
+ else if (parsed.response) {
308
+ textParts.push(parsed.response);
309
+ }
310
+ else if (parsed.output) {
311
+ textParts.push(parsed.output);
312
+ }
313
+ }
314
+ catch {
315
+ // If line isn't valid JSON, it might be plain text output
316
+ this.logger.debug({ lineLength: line.length }, 'Line is not JSON, treating as plain text');
317
+ textParts.push(line);
298
318
  }
299
- // Fallback: stringify the parsed object
300
- this.logger.debug({ parsedKeys: Object.keys(parsed) }, 'Unknown JSON structure, returning stringified');
301
- return JSON.stringify(parsed, null, 2);
302
319
  }
303
- catch {
304
- // JSON parsing failed, return raw output
305
- this.logger.debug({ stdoutLength: stdoutData.length }, 'JSON parsing failed, returning raw output');
306
- return stdoutData;
320
+ if (textParts.length > 0) {
321
+ return textParts.join('\n');
307
322
  }
323
+ // Fallback: return raw output
324
+ this.logger.debug({ stdoutLength: stdoutData.length }, 'No text extracted, returning raw output');
325
+ return stdoutData;
308
326
  }
309
327
  /**
310
328
  * Build detailed error context for debugging process failures
@@ -41,6 +41,7 @@ export class TaskDecompositionService {
41
41
  // Execute Claude agent to generate task graph
42
42
  const result = await this.agentRunner.runAgent(prompt, {
43
43
  agentType: (options.agent ?? 'architect'),
44
+ model: options.model,
44
45
  references: options.contextFiles,
45
46
  timeout: options.taskTimeout ?? 600_000, // 10 min default for planning
46
47
  });
@@ -119,6 +119,8 @@ export interface DecomposeOptions {
119
119
  goal: string;
120
120
  /** Maximum number of tasks to run in parallel */
121
121
  maxParallel?: number;
122
+ /** Model to use for AI provider */
123
+ model?: string;
122
124
  /** Enable per-file task generation for file-heavy operations */
123
125
  perFile?: boolean;
124
126
  /** Plan only without executing */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hyperdrive.bot/bmad-workflow",
3
3
  "description": "AI-driven development workflow orchestration CLI for BMAD projects",
4
- "version": "1.0.9",
4
+ "version": "1.0.10",
5
5
  "author": {
6
6
  "name": "DevSquad",
7
7
  "email": "marcelo@devsquad.email",