vibeman 0.0.1 → 0.0.3

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 (102) hide show
  1. package/dist/index.js +5 -7
  2. package/dist/runtime/api/.tsbuildinfo +1 -1
  3. package/dist/runtime/api/agent/agent-service.d.ts +18 -19
  4. package/dist/runtime/api/agent/agent-service.js +61 -58
  5. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.d.ts +2 -2
  6. package/dist/runtime/api/agent/ai-providers/claude-code-adapter.js +25 -36
  7. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.d.ts +2 -0
  8. package/dist/runtime/api/agent/ai-providers/codex-cli-provider.js +109 -43
  9. package/dist/runtime/api/agent/ai-providers/types.d.ts +2 -0
  10. package/dist/runtime/api/agent/codex-cli-provider.test.js +83 -1
  11. package/dist/runtime/api/agent/parsers.d.ts +1 -0
  12. package/dist/runtime/api/agent/parsers.js +75 -8
  13. package/dist/runtime/api/agent/prompt-service.d.ts +14 -1
  14. package/dist/runtime/api/agent/prompt-service.js +123 -14
  15. package/dist/runtime/api/agent/prompt-service.test.js +230 -0
  16. package/dist/runtime/api/agent/routing-policy.d.ts +25 -42
  17. package/dist/runtime/api/agent/routing-policy.js +82 -132
  18. package/dist/runtime/api/agent/routing-policy.test.js +63 -0
  19. package/dist/runtime/api/api/routers/ai.d.ts +19 -7
  20. package/dist/runtime/api/api/routers/ai.js +9 -23
  21. package/dist/runtime/api/api/routers/executions.d.ts +4 -4
  22. package/dist/runtime/api/api/routers/executions.js +12 -21
  23. package/dist/runtime/api/api/routers/provider-config.d.ts +165 -0
  24. package/dist/runtime/api/api/routers/provider-config.js +252 -0
  25. package/dist/runtime/api/api/routers/tasks.d.ts +9 -9
  26. package/dist/runtime/api/api/routers/workflows.d.ts +23 -16
  27. package/dist/runtime/api/api/routers/workflows.js +30 -27
  28. package/dist/runtime/api/api/routers/worktrees.d.ts +4 -5
  29. package/dist/runtime/api/api/routers/worktrees.js +11 -11
  30. package/dist/runtime/api/api/trpc.d.ts +16 -16
  31. package/dist/runtime/api/index.js +2 -10
  32. package/dist/runtime/api/lib/local-config.d.ts +245 -0
  33. package/dist/runtime/api/lib/local-config.js +288 -0
  34. package/dist/runtime/api/lib/provider-detection.d.ts +59 -0
  35. package/dist/runtime/api/lib/provider-detection.js +244 -0
  36. package/dist/runtime/api/lib/server/bootstrap.d.ts +38 -0
  37. package/dist/runtime/api/lib/server/bootstrap.js +197 -0
  38. package/dist/runtime/api/lib/server/project-root.js +24 -1
  39. package/dist/runtime/api/lib/trpc/server.d.ts +143 -30
  40. package/dist/runtime/api/lib/trpc/server.js +8 -8
  41. package/dist/runtime/api/lib/trpc/ws-server.js +2 -2
  42. package/dist/runtime/api/router.d.ts +144 -31
  43. package/dist/runtime/api/router.js +9 -31
  44. package/dist/runtime/api/settings-service.js +51 -1
  45. package/dist/runtime/api/types/index.d.ts +8 -1
  46. package/dist/runtime/api/types/settings.d.ts +15 -2
  47. package/dist/runtime/api/workflows/vibing-orchestrator.d.ts +8 -3
  48. package/dist/runtime/api/workflows/vibing-orchestrator.js +214 -184
  49. package/dist/runtime/web/.next/BUILD_ID +1 -1
  50. package/dist/runtime/web/.next/app-build-manifest.json +19 -12
  51. package/dist/runtime/web/.next/app-path-routes-manifest.json +2 -1
  52. package/dist/runtime/web/.next/build-manifest.json +2 -2
  53. package/dist/runtime/web/.next/prerender-manifest.json +10 -10
  54. package/dist/runtime/web/.next/routes-manifest.json +8 -0
  55. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js +1 -0
  56. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route.js.nft.json +1 -0
  57. package/dist/runtime/web/.next/server/app/.vibeman/assets/images/[...path]/route_client-reference-manifest.js +1 -0
  58. package/dist/runtime/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  59. package/dist/runtime/web/.next/server/app/_not-found.html +2 -2
  60. package/dist/runtime/web/.next/server/app/_not-found.rsc +5 -5
  61. package/dist/runtime/web/.next/server/app/api/health/route.js +1 -1
  62. package/dist/runtime/web/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  63. package/dist/runtime/web/.next/server/app/api/images/[...path]/route.js +1 -1
  64. package/dist/runtime/web/.next/server/app/api/images/[...path]/route_client-reference-manifest.js +1 -1
  65. package/dist/runtime/web/.next/server/app/api/upload/route.js +1 -1
  66. package/dist/runtime/web/.next/server/app/api/upload/route_client-reference-manifest.js +1 -1
  67. package/dist/runtime/web/.next/server/app/index.html +2 -2
  68. package/dist/runtime/web/.next/server/app/index.rsc +6 -6
  69. package/dist/runtime/web/.next/server/app/page.js +21 -21
  70. package/dist/runtime/web/.next/server/app/page_client-reference-manifest.js +1 -1
  71. package/dist/runtime/web/.next/server/app-paths-manifest.json +2 -1
  72. package/dist/runtime/web/.next/server/chunks/458.js +1 -1
  73. package/dist/runtime/web/.next/server/pages/404.html +2 -2
  74. package/dist/runtime/web/.next/server/pages/500.html +1 -1
  75. package/dist/runtime/web/.next/server/pages-manifest.json +1 -1
  76. package/dist/runtime/web/.next/server/server-reference-manifest.json +1 -1
  77. package/dist/runtime/web/.next/static/5_15u1WQCxN1_eHZpldCv/_buildManifest.js +1 -0
  78. package/dist/runtime/web/.next/static/chunks/{277-0142a939f08738c3.js → 823-6f371a6e829adbba.js} +1 -1
  79. package/dist/runtime/web/.next/static/chunks/app/.vibeman/assets/images/[...path]/route-751c9265a65409e5.js +1 -0
  80. package/dist/runtime/web/.next/static/chunks/app/api/health/route-751c9265a65409e5.js +1 -0
  81. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-751c9265a65409e5.js +1 -0
  82. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-751c9265a65409e5.js +1 -0
  83. package/dist/runtime/web/.next/static/chunks/app/{layout-dc0cfd29075b2160.js → layout-8435322f09fd0975.js} +1 -1
  84. package/dist/runtime/web/.next/static/chunks/app/page-9fe7d75095b4ccec.js +1 -0
  85. package/dist/tsconfig.tsbuildinfo +1 -1
  86. package/package.json +5 -1
  87. package/dist/runtime/api/lib/image-paste-drop-extension.d.ts +0 -26
  88. package/dist/runtime/api/lib/image-paste-drop-extension.js +0 -125
  89. package/dist/runtime/api/lib/markdown-utils.d.ts +0 -8
  90. package/dist/runtime/api/lib/markdown-utils.js +0 -282
  91. package/dist/runtime/api/lib/markdown-utils.test.js +0 -348
  92. package/dist/runtime/api/lib/tiptap-utils.clamp-selection.test.js +0 -27
  93. package/dist/runtime/api/lib/tiptap-utils.d.ts +0 -130
  94. package/dist/runtime/api/lib/tiptap-utils.js +0 -327
  95. package/dist/runtime/web/.next/static/1HR8N0rJkCvFRtbTPJMyH/_buildManifest.js +0 -1
  96. package/dist/runtime/web/.next/static/chunks/app/api/health/route-105a61ae865ba536.js +0 -1
  97. package/dist/runtime/web/.next/static/chunks/app/api/images/[...path]/route-105a61ae865ba536.js +0 -1
  98. package/dist/runtime/web/.next/static/chunks/app/api/upload/route-105a61ae865ba536.js +0 -1
  99. package/dist/runtime/web/.next/static/chunks/app/page-f34a8b196b18850b.js +0 -1
  100. /package/dist/runtime/api/{lib/markdown-utils.test.d.ts → agent/prompt-service.test.d.ts} +0 -0
  101. /package/dist/runtime/api/{lib/tiptap-utils.clamp-selection.test.d.ts → agent/routing-policy.test.d.ts} +0 -0
  102. /package/dist/runtime/web/.next/static/{1HR8N0rJkCvFRtbTPJMyH → 5_15u1WQCxN1_eHZpldCv}/_ssgManifest.js +0 -0
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { spawn } from 'child_process';
6
6
  import { getSettingsService } from '../../settings-service.js';
7
+ import { getProviderDetectionService } from '../../lib/provider-detection.js';
7
8
  import { logger } from '../../lib/logger.js';
8
9
  export class CodexCliProvider {
9
10
  constructor(config = {}) {
@@ -11,10 +12,16 @@ export class CodexCliProvider {
11
12
  this.name = 'codex';
12
13
  this.displayName = 'Codex CLI';
13
14
  }
14
- resolveExecutable() {
15
+ async resolveExecutable() {
15
16
  if (this.config.codexBinPath)
16
17
  return this.config.codexBinPath;
17
- // Check settings first
18
+ // Use enhanced detection service
19
+ const detectionService = getProviderDetectionService();
20
+ const result = await detectionService.detectProvider('codex');
21
+ if (result.found && result.path) {
22
+ return result.path;
23
+ }
24
+ // Fallback: try old settings-based approach for backwards compatibility
18
25
  const settingsBinPath = (() => {
19
26
  try {
20
27
  const svc = getSettingsService();
@@ -30,32 +37,67 @@ export class CodexCliProvider {
30
37
  // Default to binary name (resolve via PATH)
31
38
  return 'codex';
32
39
  }
40
+ normalizeReasoningEffort(value) {
41
+ if (!value)
42
+ return undefined;
43
+ const normalized = value.toLowerCase();
44
+ if (normalized === 'minimal')
45
+ return 'low';
46
+ const allowed = ['low', 'medium', 'high'];
47
+ return allowed.includes(normalized)
48
+ ? normalized
49
+ : undefined;
50
+ }
51
+ resolveModelSpec(model, fallbackEffort) {
52
+ const fallback = this.normalizeReasoningEffort(fallbackEffort);
53
+ if (!model) {
54
+ return { reasoning: fallback };
55
+ }
56
+ const trimmed = model.trim();
57
+ const normalized = trimmed.toLowerCase();
58
+ const match = normalized.match(/^(gpt-5(?:-codex)?)(?:-(minimal|low|medium|high))?$/);
59
+ if (!match) {
60
+ return {
61
+ cliModel: trimmed,
62
+ requestedModel: trimmed,
63
+ reasoning: fallback,
64
+ };
65
+ }
66
+ const baseModel = match[1];
67
+ const reasoning = this.normalizeReasoningEffort(match[2]);
68
+ return {
69
+ cliModel: baseModel,
70
+ requestedModel: trimmed,
71
+ reasoning: reasoning ?? fallback,
72
+ };
73
+ }
33
74
  async *execute(prompt, options) {
34
75
  // Effective options
35
76
  const cwd = options?.workingDirectory || this.config.defaultWorkingDirectory || process.cwd();
36
- const mapModel = (m) => {
37
- if (!m)
38
- return m;
39
- // Normalize reasoning-effort variants to the canonical model id
40
- // Users reported only `gpt-5` is selectable; variants are effort levels in the TUI.
41
- const lower = m.toLowerCase();
42
- if (lower === 'gpt-5-minimal' ||
43
- lower === 'gpt-5-low' ||
44
- lower === 'gpt-5-medium' ||
45
- lower === 'gpt-5-high') {
46
- return 'gpt-5';
47
- }
48
- return m;
49
- };
50
- const model = mapModel(options?.model || this.config.defaultModel);
77
+ const resolvedModel = this.resolveModelSpec(options?.model || this.config.defaultModel, options?.effort);
78
+ const model = resolvedModel.cliModel;
51
79
  const images = (options?.images || []).filter(Boolean);
52
- const effort = options?.effort;
80
+ const reasoningEffort = resolvedModel.reasoning;
53
81
  const timeoutMs = options?.timeout ?? this.config.defaultTimeoutMs ?? 10 * 60 * 1000; // 10m
82
+ const systemPrompt = options?.systemPrompt?.trim();
83
+ const appendSystemPrompt = options?.appendSystemPrompt?.trim();
84
+ const promptSegments = [];
85
+ if (systemPrompt) {
86
+ promptSegments.push(systemPrompt);
87
+ }
88
+ if (appendSystemPrompt) {
89
+ promptSegments.push(appendSystemPrompt);
90
+ }
91
+ promptSegments.push(prompt);
92
+ const effectivePrompt = promptSegments.join('\n\n');
54
93
  // Build argv for `codex exec` (non-interactive automation mode)
55
- const argv = ['exec', prompt];
94
+ const argv = ['exec', effectivePrompt];
56
95
  if (model) {
57
96
  argv.push('--model', model);
58
97
  }
98
+ if (reasoningEffort) {
99
+ argv.push('-c', `model_reasoning_effort=${reasoningEffort}`);
100
+ }
59
101
  // Prefer spawning with cwd, but also set --cd to make Codex aware of root
60
102
  if (cwd) {
61
103
  argv.push('--cd', cwd);
@@ -63,7 +105,10 @@ export class CodexCliProvider {
63
105
  if (images.length) {
64
106
  argv.push('--image', images.join(','));
65
107
  }
66
- const cmd = this.resolveExecutable();
108
+ if (options?.dangerouslyBypassApprovalsAndSandbox) {
109
+ argv.push('--dangerously-bypass-approvals-and-sandbox');
110
+ }
111
+ const cmd = await this.resolveExecutable();
67
112
  const child = spawn(cmd, argv, {
68
113
  cwd,
69
114
  env: { ...process.env },
@@ -74,15 +119,24 @@ export class CodexCliProvider {
74
119
  type: 'init',
75
120
  timestamp: new Date().toISOString(),
76
121
  sessionId: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
77
- model: model || 'default',
122
+ model: resolvedModel.requestedModel || model || 'default',
78
123
  provider: this.name,
79
124
  };
80
125
  yield init;
81
126
  yield {
82
127
  type: 'system',
83
128
  timestamp: new Date().toISOString(),
84
- content: `Running: ${cmd} ${argv.map((a) => (a.includes(' ') ? '"' + a + '"' : a)).join(' ')} (cwd=${cwd})`,
85
- metadata: { provider: this.name, images, effort },
129
+ content: `Running: ${cmd} ${argv
130
+ .map((a) => (a.includes(' ') ? '"' + a + '"' : a))
131
+ .join(' ')} (cwd=${cwd})`,
132
+ metadata: {
133
+ provider: this.name,
134
+ images,
135
+ effort: reasoningEffort || options?.effort,
136
+ systemPrompt,
137
+ appendSystemPrompt,
138
+ dangerouslyBypassApprovalsAndSandbox: !!options?.dangerouslyBypassApprovalsAndSandbox,
139
+ },
86
140
  };
87
141
  let stdoutBuf = '';
88
142
  // let stderrBuf = '';
@@ -212,6 +266,7 @@ export class CodexCliProvider {
212
266
  // Convenience: collect from execute()
213
267
  let content = '';
214
268
  const start = Date.now();
269
+ const resolvedModel = this.resolveModelSpec(options?.model || this.config.defaultModel, options?.effort);
215
270
  for await (const msg of this.execute(prompt, options)) {
216
271
  if (msg.type === 'assistant' && typeof msg.content === 'string') {
217
272
  content += (content ? '\n' : '') + msg.content;
@@ -220,36 +275,47 @@ export class CodexCliProvider {
220
275
  return {
221
276
  id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
222
277
  provider: this.name,
223
- model: options?.model || this.config.defaultModel || 'default',
278
+ model: resolvedModel.requestedModel || resolvedModel.cliModel || 'default',
224
279
  content,
225
280
  usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
226
281
  duration: Date.now() - start,
227
282
  };
228
283
  }
229
284
  async detectAvailableModels() {
230
- // Current public CLI exposes `--model gpt-5`; effort levels are chosen in-CLI.
285
+ const capabilities = ['code', 'analysis', 'tools', 'vision', 'image', 'reasoning'];
286
+ const makeModel = (id, displayName) => ({
287
+ id,
288
+ name: id,
289
+ displayName,
290
+ provider: this.name,
291
+ contextWindow: 256000,
292
+ maxOutputTokens: 16384,
293
+ capabilities,
294
+ });
231
295
  return [
232
- {
233
- id: 'gpt-5',
234
- name: 'gpt-5',
235
- displayName: 'gpt‑5',
236
- provider: this.name,
237
- contextWindow: 256000,
238
- maxOutputTokens: 16384,
239
- // Capabilities per Codex docs: supports images via --image and advanced reasoning
240
- capabilities: ['code', 'analysis', 'tools', 'vision', 'image', 'reasoning'],
241
- },
296
+ makeModel('gpt-5', 'gpt‑5'),
297
+ makeModel('gpt-5-low', 'gpt‑5 · low reasoning'),
298
+ makeModel('gpt-5-medium', 'gpt‑5 · medium reasoning'),
299
+ makeModel('gpt-5-high', 'gpt‑5 · high reasoning'),
300
+ makeModel('gpt-5-codex', 'gpt‑5 Codex'),
301
+ makeModel('gpt-5-codex-low', 'gpt‑5 Codex · low reasoning'),
302
+ makeModel('gpt-5-codex-medium', 'gpt‑5 Codex · medium reasoning'),
303
+ makeModel('gpt-5-codex-high', 'gpt‑5 Codex · high reasoning'),
242
304
  ];
243
305
  }
244
306
  async validateSetup() {
245
307
  try {
246
- const bin = this.resolveExecutable();
247
- // Try to spawn `codex --help` quickly to validate presence
248
- const child = spawn(bin, ['--help'], { stdio: 'ignore' });
249
- await new Promise((resolve, reject) => {
250
- child.once('error', reject);
251
- child.once('close', () => resolve());
252
- });
308
+ const detectionService = getProviderDetectionService();
309
+ const result = await detectionService.detectProvider('codex');
310
+ if (!result.found) {
311
+ return {
312
+ available: false,
313
+ error: result.error ||
314
+ 'Codex CLI not found. Install from https://github.com/openai/codex (see Getting Started: CLI usage).',
315
+ models: [],
316
+ capabilities: this.getCapabilities(),
317
+ };
318
+ }
253
319
  return {
254
320
  available: true,
255
321
  models: await this.detectAvailableModels(),
@@ -260,7 +326,7 @@ export class CodexCliProvider {
260
326
  logger.error(error);
261
327
  return {
262
328
  available: false,
263
- error: 'Codex CLI not found. Install from https://github.com/openai/codex (see Getting Started: CLI usage).',
329
+ error: error instanceof Error ? error.message : String(error),
264
330
  models: [],
265
331
  capabilities: this.getCapabilities(),
266
332
  };
@@ -45,6 +45,8 @@ export interface ExecutionOptions {
45
45
  images?: string[];
46
46
  /** Optional effort hint for reasoning presets (e.g., 'minimal'|'low'|'medium'|'high'); informational only */
47
47
  effort?: string;
48
+ /** Enable Codex CLI sandbox bypass flag when running automation that must edit files */
49
+ dangerouslyBypassApprovalsAndSandbox?: boolean;
48
50
  }
49
51
  /**
50
52
  * Message types for streaming execution
@@ -24,10 +24,12 @@ describe('CodexCliProvider (mocked)', () => {
24
24
  setTimeout(() => proc.emit('exit', 0, null), 20);
25
25
  return proc;
26
26
  },
27
+ exec: vi.fn(),
28
+ execFile: vi.fn(),
27
29
  };
28
30
  });
29
31
  const { CodexCliProvider } = await import('./ai-providers/codex-cli-provider.js');
30
- const provider = new CodexCliProvider({ defaultTimeoutMs: 1000 });
32
+ const provider = new CodexCliProvider({ defaultTimeoutMs: 1000, codexBinPath: 'codex' });
31
33
  const messages = [];
32
34
  const iter = provider.execute('What is this project about?', {
33
35
  workingDirectory: process.cwd(),
@@ -54,6 +56,86 @@ describe('CodexCliProvider (mocked)', () => {
54
56
  const modelIdx = captured[1].indexOf('--model');
55
57
  expect(modelIdx).toBeGreaterThan(-1);
56
58
  expect(captured[1][modelIdx + 1]).toBe('gpt-5');
59
+ const configIdx = captured[1].indexOf('-c');
60
+ expect(configIdx).toBeGreaterThan(-1);
61
+ expect(captured[1][configIdx + 1]).toBe('model_reasoning_effort=low');
62
+ });
63
+ it('maps reasoning level encoded in model name to CLI flags', async () => {
64
+ vi.resetModules();
65
+ vi.doMock('child_process', () => {
66
+ let captured = [];
67
+ return {
68
+ __captured: () => captured,
69
+ spawn: (cmd, args, opts) => {
70
+ captured = [cmd, args, opts];
71
+ const { EventEmitter } = require('events');
72
+ const stdout = new EventEmitter();
73
+ const proc = new EventEmitter();
74
+ proc.stdout = stdout;
75
+ setTimeout(() => stdout.emit('data', Buffer.from('Reasoned\n')), 10);
76
+ setTimeout(() => proc.emit('exit', 0, null), 20);
77
+ return proc;
78
+ },
79
+ exec: vi.fn(),
80
+ execFile: vi.fn(),
81
+ };
82
+ });
83
+ const { CodexCliProvider } = await import('./ai-providers/codex-cli-provider.js');
84
+ const provider = new CodexCliProvider({ codexBinPath: 'codex' });
85
+ const messages = [];
86
+ const iter = provider.execute('Solve with depth', {
87
+ workingDirectory: process.cwd(),
88
+ model: 'gpt-5-codex-high',
89
+ });
90
+ for await (const m of iter)
91
+ messages.push(m);
92
+ const mockSpawn = await import('child_process');
93
+ const captured = mockSpawn.__captured();
94
+ const modelIdx = captured[1].indexOf('--model');
95
+ expect(modelIdx).toBeGreaterThan(-1);
96
+ expect(captured[1][modelIdx + 1]).toBe('gpt-5-codex');
97
+ const configIdx = captured[1].indexOf('-c');
98
+ expect(configIdx).toBeGreaterThan(-1);
99
+ expect(captured[1][configIdx + 1]).toBe('model_reasoning_effort=high');
100
+ });
101
+ it('prepends system prompts and toggles sandbox bypass flag', async () => {
102
+ vi.resetModules();
103
+ vi.doMock('child_process', () => {
104
+ let captured = [];
105
+ return {
106
+ __captured: () => captured,
107
+ spawn: (cmd, args, opts) => {
108
+ captured = [cmd, args, opts];
109
+ const { EventEmitter } = require('events');
110
+ const stdout = new EventEmitter();
111
+ const proc = new EventEmitter();
112
+ proc.stdout = stdout;
113
+ setTimeout(() => stdout.emit('data', Buffer.from('Done\n')), 5);
114
+ setTimeout(() => proc.emit('exit', 0, null), 10);
115
+ return proc;
116
+ },
117
+ exec: vi.fn(),
118
+ execFile: vi.fn(),
119
+ };
120
+ });
121
+ const { CodexCliProvider } = await import('./ai-providers/codex-cli-provider.js');
122
+ const provider = new CodexCliProvider({ codexBinPath: 'codex' });
123
+ const iter = provider.execute('Implement feature', {
124
+ workingDirectory: '/tmp/project',
125
+ systemPrompt: 'Base system prompt',
126
+ appendSystemPrompt: 'Additional guidance',
127
+ dangerouslyBypassApprovalsAndSandbox: true,
128
+ });
129
+ for await (const _ of iter) {
130
+ // drain iterator
131
+ }
132
+ const mockSpawn = await import('child_process');
133
+ const captured = mockSpawn.__captured();
134
+ expect(captured[1][1]).toContain('Base system prompt');
135
+ expect(captured[1][1]).toContain('Additional guidance');
136
+ const bypassIdx = captured[1].indexOf('--dangerously-bypass-approvals-and-sandbox');
137
+ expect(bypassIdx).toBeGreaterThan(-1);
138
+ expect(captured[1][bypassIdx + 1]).toBeUndefined();
57
139
  });
58
140
  });
59
141
  // Real integration (opt-in)
@@ -7,6 +7,7 @@ export declare function parseImprovementResult(result: string, originalData: {
7
7
  type: string;
8
8
  priority: string;
9
9
  content: string;
10
+ title?: string;
10
11
  };
11
12
  export declare function parseReviewResult(result: string): {
12
13
  reviewSummary: string;
@@ -60,9 +60,10 @@ function extractLastValidJson(text) {
60
60
  */
61
61
  function parseJsonManually(jsonStr) {
62
62
  try {
63
- // Extract type and priority with simple regex (these are usually well-formed)
63
+ // Extract type, priority and title with simple regex (these are usually well-formed)
64
64
  const typeMatch = jsonStr.match(/"type":\s*"([^"]+)"/);
65
65
  const priorityMatch = jsonStr.match(/"priority":\s*"([^"]+)"/);
66
+ const titleMatch = jsonStr.match(/"title":\s*"([^"]+)"/);
66
67
  // For content, find the start and extract everything until the closing brace
67
68
  const contentStart = jsonStr.indexOf('"content":');
68
69
  if (contentStart === -1)
@@ -103,11 +104,16 @@ function parseJsonManually(jsonStr) {
103
104
  .replace(/\\t/g, '\t')
104
105
  .replace(/\\"/g, '"')
105
106
  .replace(/\\\\/g, '\\');
106
- return {
107
+ const result = {
107
108
  type: typeMatch[1],
108
109
  priority: priorityMatch[1],
109
110
  content: content,
110
111
  };
112
+ // Add title if found
113
+ if (titleMatch) {
114
+ result.title = titleMatch[1];
115
+ }
116
+ return result;
111
117
  }
112
118
  return null;
113
119
  }
@@ -117,6 +123,24 @@ function parseJsonManually(jsonStr) {
117
123
  return null;
118
124
  }
119
125
  }
126
+ /**
127
+ * Extract title from markdown content and remove it from content
128
+ * Returns { title: string | null, cleanedContent: string }
129
+ */
130
+ function extractTitleFromContent(content) {
131
+ if (!content || typeof content !== 'string') {
132
+ return { title: null, cleanedContent: content };
133
+ }
134
+ // Look for H1 heading at the start of content (with optional whitespace)
135
+ const h1Match = content.match(/^\s*#\s+([^\n\r]+)/);
136
+ if (h1Match) {
137
+ const title = h1Match[1].trim();
138
+ // Remove the H1 heading from content
139
+ const cleanedContent = content.replace(/^\s*#\s+[^\n\r]+\s*\n?/, '').trim();
140
+ return { title, cleanedContent };
141
+ }
142
+ return { title: null, cleanedContent: content };
143
+ }
120
144
  export function parseImprovementResult(result, originalData) {
121
145
  try {
122
146
  const jsonBlock = extractLastValidJson(result);
@@ -130,13 +154,31 @@ export function parseImprovementResult(result, originalData) {
130
154
  const validPriorities = ['high', 'medium', 'low'];
131
155
  const normalizedType = String(parsed.type).toLowerCase();
132
156
  const normalizedPriority = String(parsed.priority).toLowerCase();
133
- return {
157
+ // Extract title from JSON if present, otherwise from content
158
+ let finalTitle;
159
+ let finalContent = String(parsed.content).trim();
160
+ if (parsed.title && typeof parsed.title === 'string' && parsed.title.trim()) {
161
+ finalTitle = parsed.title.trim();
162
+ }
163
+ else {
164
+ // Try to extract title from content
165
+ const { title, cleanedContent } = extractTitleFromContent(finalContent);
166
+ if (title) {
167
+ finalTitle = title;
168
+ finalContent = cleanedContent;
169
+ }
170
+ }
171
+ const result = {
134
172
  type: validTypes.includes(normalizedType) ? normalizedType : originalData.type,
135
173
  priority: validPriorities.includes(normalizedPriority)
136
174
  ? normalizedPriority
137
175
  : originalData.priority,
138
- content: String(parsed.content).trim(),
176
+ content: finalContent,
139
177
  };
178
+ if (finalTitle) {
179
+ result.title = finalTitle;
180
+ }
181
+ return result;
140
182
  }
141
183
  }
142
184
  catch (parseError) {
@@ -151,13 +193,31 @@ export function parseImprovementResult(result, originalData) {
151
193
  const validPriorities = ['high', 'medium', 'low'];
152
194
  const normalizedType = String(parsed.type).toLowerCase();
153
195
  const normalizedPriority = String(parsed.priority).toLowerCase();
154
- return {
196
+ // Extract title from parsed JSON if present, otherwise from content
197
+ let finalTitle;
198
+ let finalContent = String(parsed.content).trim();
199
+ if (parsed.title && typeof parsed.title === 'string' && parsed.title.trim()) {
200
+ finalTitle = parsed.title.trim();
201
+ }
202
+ else {
203
+ // Try to extract title from content
204
+ const { title, cleanedContent } = extractTitleFromContent(finalContent);
205
+ if (title) {
206
+ finalTitle = title;
207
+ finalContent = cleanedContent;
208
+ }
209
+ }
210
+ const result = {
155
211
  type: validTypes.includes(normalizedType) ? normalizedType : originalData.type,
156
212
  priority: validPriorities.includes(normalizedPriority)
157
213
  ? normalizedPriority
158
214
  : originalData.priority,
159
- content: String(parsed.content).trim(),
215
+ content: finalContent,
160
216
  };
217
+ if (finalTitle) {
218
+ result.title = finalTitle;
219
+ }
220
+ return result;
161
221
  }
162
222
  }
163
223
  catch (secondError) {
@@ -171,11 +231,18 @@ export function parseImprovementResult(result, originalData) {
171
231
  const errMsg = error instanceof Error ? error.message : String(error);
172
232
  log.error('Failed to parse improvement result', { error: errMsg, result }, 'result-parsers');
173
233
  }
174
- return {
234
+ // Fallback: try to extract title from the raw result content
235
+ const fallbackContent = result.trim() || originalData.content;
236
+ const { title: extractedTitle, cleanedContent } = extractTitleFromContent(fallbackContent);
237
+ const fallbackResult = {
175
238
  type: originalData.type,
176
239
  priority: originalData.priority,
177
- content: result.trim() || originalData.content,
240
+ content: cleanedContent,
178
241
  };
242
+ if (extractedTitle) {
243
+ fallbackResult.title = extractedTitle;
244
+ }
245
+ return fallbackResult;
179
246
  }
180
247
  export function parseReviewResult(result) {
181
248
  try {
@@ -4,13 +4,26 @@ export declare class PromptService {
4
4
  private projectRoot;
5
5
  private taskService;
6
6
  constructor(projectRoot: string, taskService: TaskService);
7
+ /**
8
+ * Generate absolute path to task file
9
+ */
10
+ private generateTaskFilePath;
11
+ /**
12
+ * Validate that task file exists at the given path
13
+ */
14
+ private validateTaskFile;
7
15
  generateImprovementPrompt(task: Task, taskData: {
8
16
  title: string;
9
17
  type: string;
10
18
  priority: string;
11
19
  content: string;
12
20
  }): Promise<string>;
13
- generateTaskPrompt(task: Task): Promise<string>;
21
+ generateTaskPrompt(task: Task, workflowConfig?: import('../types/index.js').VibingConfig): Promise<string>;
22
+ /**
23
+ * Build a prominent, clearly formatted instruction section derived from workflow options.
24
+ * If no options are provided or none apply, returns a minimal guidance line.
25
+ */
26
+ private buildWorkflowInstructions;
14
27
  generateAIMergePrompt(task: Task, worktree: WorktreeInfo, baseBranch?: string): Promise<string>;
15
28
  generateReviewPrompt(task: Task, worktree: WorktreeInfo, reviewContext?: string): Promise<string>;
16
29
  }