agent-state-machine 2.5.0 → 2.6.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 (31) hide show
  1. package/lib/llm.js +14 -3
  2. package/lib/runtime/prompt.js +1 -1
  3. package/lib/runtime/runtime.js +14 -2
  4. package/lib/runtime/track-changes.js +84 -0
  5. package/package.json +1 -1
  6. package/templates/project-builder/agents/{code-writer.md → code-write.md} +18 -12
  7. package/templates/project-builder/agents/{assumptions-clarifier.md → intake-assumptions.md} +1 -0
  8. package/templates/project-builder/agents/{requirements-clarifier.md → intake-requirements.md} +1 -0
  9. package/templates/project-builder/agents/{scope-clarifier.md → intake-scope.md} +1 -0
  10. package/templates/project-builder/agents/{security-clarifier.md → intake-security.md} +1 -0
  11. package/templates/project-builder/agents/{roadmap-generator.md → plan-roadmap.md} +1 -0
  12. package/templates/project-builder/agents/{task-planner.md → plan-tasks.md} +1 -0
  13. package/templates/project-builder/agents/post-code-fix.md +59 -0
  14. package/templates/project-builder/agents/{code-reviewer.md → post-code-review.md} +10 -0
  15. package/templates/project-builder/agents/post-code-security.md +55 -0
  16. package/templates/project-builder/agents/{security-reviewer.md → pre-code-security.md} +8 -11
  17. package/templates/project-builder/agents/{test-planner.md → pre-code-tests.md} +1 -0
  18. package/templates/project-builder/agents/response-interpreter.md +1 -0
  19. package/templates/project-builder/agents/verify-commit-msg.md +64 -0
  20. package/templates/project-builder/agents/{sanity-checker.md → verify-sanity.md} +1 -12
  21. package/templates/project-builder/config.js +15 -4
  22. package/templates/project-builder/scripts/safeguard-recovery.js +40 -0
  23. package/templates/project-builder/scripts/validate-changes.js +61 -0
  24. package/templates/project-builder/scripts/workflow-helpers.js +87 -35
  25. package/templates/project-builder/workflow.js +231 -93
  26. package/vercel-server/public/remote/assets/{index-BSL55rdk.js → index-BnuR91vD.js} +1 -1
  27. package/vercel-server/public/remote/index.html +1 -1
  28. package/vercel-server/ui/src/components/ContentCard.jsx +7 -7
  29. package/vercel-server/ui/src/components/SettingsModal.jsx +19 -4
  30. package/templates/project-builder/agents/code-fixer.md +0 -50
  31. /package/templates/project-builder/{agents → scripts}/sanity-runner.js +0 -0
@@ -13,7 +13,6 @@ import path from 'path';
13
13
  import { fileURLToPath } from 'url';
14
14
  import {
15
15
  writeMarkdownFile,
16
- writeImplementationFiles,
17
16
  isApproval,
18
17
  renderRoadmapMarkdown,
19
18
  renderTasksMarkdown,
@@ -26,13 +25,19 @@ import {
26
25
  getQuickFixAttempts,
27
26
  incrementQuickFixAttempts,
28
27
  resetQuickFixAttempts,
29
- detectTestFramework
28
+ detectTestFramework,
29
+ getProjectFiles,
30
+ createGitCommit,
31
+ ensureGitRepo
30
32
  } from './scripts/workflow-helpers.js';
31
33
  import {
32
34
  createInteraction,
33
35
  parseResponse,
34
36
  formatInteractionPrompt as formatPrompt
35
37
  } from './scripts/interaction-helpers.js';
38
+ import { validateProjectChanges } from './scripts/validate-changes.js';
39
+ import { autoFixValidationIssues, rollbackFile } from './scripts/safeguard-recovery.js';
40
+ import runSanityChecks from './scripts/sanity-runner.js';
36
41
 
37
42
  // Derive workflow directory dynamically
38
43
  const __filename = fileURLToPath(import.meta.url);
@@ -49,48 +54,6 @@ const C = {
49
54
  yellow: '\x1b[33m'
50
55
  };
51
56
 
52
- function applyFixesToImplementation(originalImplementation, fixes) {
53
- if (!originalImplementation || !Array.isArray(fixes) || fixes.length === 0) {
54
- return originalImplementation;
55
- }
56
-
57
- const updated = { ...originalImplementation };
58
- const container = updated.implementation ? { ...updated.implementation } : updated;
59
- const files = Array.isArray(container.files) ? [...container.files] : [];
60
-
61
- for (const fix of fixes) {
62
- if (!fix?.path || !fix?.code) {
63
- console.warn(` [Fix] Skipping invalid fix entry: ${JSON.stringify(fix)}`);
64
- continue;
65
- }
66
- if (fix.operation && fix.operation !== 'replace') {
67
- console.warn(` [Fix] Unsupported operation "${fix.operation}" for ${fix.path}`);
68
- continue;
69
- }
70
-
71
- const existingIndex = files.findIndex((file) => file.path === fix.path);
72
- const nextFile = {
73
- ...(existingIndex >= 0 ? files[existingIndex] : {}),
74
- path: fix.path,
75
- code: fix.code,
76
- purpose: fix.purpose || (existingIndex >= 0 ? files[existingIndex].purpose : 'Updated by code-fixer')
77
- };
78
-
79
- if (existingIndex >= 0) {
80
- files[existingIndex] = nextFile;
81
- } else {
82
- files.push(nextFile);
83
- }
84
- }
85
-
86
- if (updated.implementation) {
87
- updated.implementation = { ...container, files };
88
- return updated;
89
- }
90
-
91
- return { ...updated, files };
92
- }
93
-
94
57
  // ============================================
95
58
  // MAIN WORKFLOW
96
59
  // ============================================
@@ -98,6 +61,13 @@ function applyFixesToImplementation(originalImplementation, fixes) {
98
61
  export default async function () {
99
62
  console.log('Starting Project Builder Workflow...\n');
100
63
 
64
+ // Ensure git repo exists for commit tracking
65
+ const runtime = getCurrentRuntime();
66
+ const gitResult = ensureGitRepo(runtime.workflowConfig.projectRoot);
67
+ if (gitResult.initialized) {
68
+ console.log(' [Git] Initialized new repository\n');
69
+ }
70
+
101
71
  // ============================================
102
72
  // PHASE 1: PROJECT INTAKE
103
73
  // ============================================
@@ -128,7 +98,7 @@ export default async function () {
128
98
  // 1. Scope Clarification
129
99
  if (!memory.scopeClarified) {
130
100
  console.log('--- Scope Clarification ---');
131
- const scopeResult = await agent('scope-clarifier', {
101
+ const scopeResult = await agent('intake-scope', {
132
102
  projectDescription: memory.projectDescription
133
103
  });
134
104
  memory.scope = scopeResult;
@@ -138,7 +108,7 @@ export default async function () {
138
108
  // 2. Requirements Clarification
139
109
  if (!memory.requirementsClarified) {
140
110
  console.log('--- Requirements Clarification ---');
141
- const reqResult = await agent('requirements-clarifier', {
111
+ const reqResult = await agent('intake-requirements', {
142
112
  projectDescription: memory.projectDescription,
143
113
  scope: memory.scope
144
114
  });
@@ -149,7 +119,7 @@ export default async function () {
149
119
  // 3. Assumptions Clarification
150
120
  if (!memory.assumptionsClarified) {
151
121
  console.log('--- Assumptions Clarification ---');
152
- const assumeResult = await agent('assumptions-clarifier', {
122
+ const assumeResult = await agent('intake-assumptions', {
153
123
  projectDescription: memory.projectDescription,
154
124
  scope: memory.scope,
155
125
  requirements: memory.requirements
@@ -161,7 +131,7 @@ export default async function () {
161
131
  // 4. Security Clarification
162
132
  if (!memory.securityClarified) {
163
133
  console.log('--- Security Clarification ---');
164
- const secResult = await agent('security-clarifier', {
134
+ const secResult = await agent('intake-security', {
165
135
  projectDescription: memory.projectDescription,
166
136
  scope: memory.scope,
167
137
  requirements: memory.requirements,
@@ -181,7 +151,7 @@ export default async function () {
181
151
  if (!memory.roadmapApproved) {
182
152
  // Generate roadmap as JSON
183
153
  if (!memory.roadmap) {
184
- const roadmapResult = await agent('roadmap-generator', {
154
+ const roadmapResult = await agent('plan-roadmap', {
185
155
  projectDescription: memory.projectDescription,
186
156
  scope: memory.scope,
187
157
  requirements: memory.requirements,
@@ -218,7 +188,7 @@ export default async function () {
218
188
  } else {
219
189
  const feedback = reviewResponse.customText || reviewResponse.text || reviewResponse.raw || reviewRaw;
220
190
  // Regenerate roadmap with feedback
221
- const updatedRoadmap = await agent('roadmap-generator', {
191
+ const updatedRoadmap = await agent('plan-roadmap', {
222
192
  projectDescription: memory.projectDescription,
223
193
  scope: memory.scope,
224
194
  requirements: memory.requirements,
@@ -256,7 +226,7 @@ export default async function () {
256
226
  // Generate task list for this phase (as JSON)
257
227
  if (!memory[tasksApprovedKey]) {
258
228
  if (!memory[tasksKey]) {
259
- const taskResult = await agent('task-planner', {
229
+ const taskResult = await agent('plan-tasks', {
260
230
  projectDescription: memory.projectDescription,
261
231
  scope: memory.scope,
262
232
  requirements: memory.requirements,
@@ -292,7 +262,7 @@ export default async function () {
292
262
  console.log(`Phase ${i + 1} task list approved!\n`);
293
263
  } else {
294
264
  const feedback = taskReview.customText || taskReview.text || taskReview.raw || taskReviewRaw;
295
- const updatedTasks = await agent('task-planner', {
265
+ const updatedTasks = await agent('plan-tasks', {
296
266
  projectDescription: memory.projectDescription,
297
267
  scope: memory.scope,
298
268
  requirements: memory.requirements,
@@ -347,11 +317,10 @@ export default async function () {
347
317
  if (stage === TASK_STAGES.PENDING || stage === TASK_STAGES.SECURITY_PRE) {
348
318
  if (!getTaskData(i, taskId, 'security_pre')) {
349
319
  console.log(' > Security pre-review...');
350
- const securityPreReview = await agent('security-reviewer', {
320
+ const securityPreReview = await agent('pre-code-security', {
351
321
  task: task,
352
322
  phase: phase,
353
323
  scope: memory.scope,
354
- stage: 'pre-implementation',
355
324
  feedback: feedback
356
325
  });
357
326
  setTaskData(i, taskId, 'security_pre', securityPreReview);
@@ -364,7 +333,7 @@ export default async function () {
364
333
  if (stage === TASK_STAGES.TEST_PLANNING) {
365
334
  if (!getTaskData(i, taskId, 'tests')) {
366
335
  console.log(' > Test planning...');
367
- const testPlan = await agent('test-planner', {
336
+ const testPlan = await agent('pre-code-tests', {
368
337
  task: task,
369
338
  phase: phase,
370
339
  requirements: memory.requirements,
@@ -381,22 +350,51 @@ export default async function () {
381
350
  if (stage === TASK_STAGES.IMPLEMENTING) {
382
351
  if (!getTaskData(i, taskId, 'code')) {
383
352
  console.log(' > Code implementation...');
384
- const implementation = await agent('code-writer', {
353
+ const projectRoot = getCurrentRuntime().workflowConfig.projectRoot;
354
+ const implementation = await agent('code-write', {
385
355
  task: task,
386
356
  phase: phase,
387
357
  requirements: memory.requirements,
388
358
  testPlan: getTaskData(i, taskId, 'tests'),
389
359
  securityConsiderations: getTaskData(i, taskId, 'security_pre'),
360
+ projectFiles: getProjectFiles(projectRoot),
390
361
  feedback: feedback
391
362
  });
392
363
  setTaskData(i, taskId, 'code', implementation);
364
+
365
+ // Validate project changes after code-write
366
+ const filesWritten = implementation?.implementation?.filesWritten || implementation?.filesWritten || [];
367
+ const modifiedPaths = filesWritten.map(f => f.path);
368
+ const validation = validateProjectChanges(projectRoot, { modified: modifiedPaths });
369
+
370
+ if (!validation.valid) {
371
+ console.log(` > Validation issues: ${validation.violations.join(', ')}`);
372
+
373
+ // Try auto-fix
374
+ const fixed = autoFixValidationIssues(projectRoot, validation.violations);
375
+ if (fixed.length > 0) {
376
+ console.log(` > Auto-fixed: ${fixed.join(', ')}`);
377
+ }
378
+
379
+ // Re-validate
380
+ const recheck = validateProjectChanges(projectRoot, { modified: modifiedPaths });
381
+ if (!recheck.valid) {
382
+ console.log(' > Auto-fix failed, rolling back package.json...');
383
+ const rollbackResult = rollbackFile(projectRoot, 'package.json');
384
+ if (rollbackResult.success) {
385
+ console.log(' > Rolled back package.json');
386
+ } else {
387
+ console.log(` > Rollback failed: ${rollbackResult.error}`);
388
+ }
389
+ }
390
+ }
393
391
  }
394
392
 
395
- // Write implementation files to disk
393
+ // Files are written directly by the agent using native file tools
396
394
  const implementation = getTaskData(i, taskId, 'code');
397
395
  if (implementation) {
398
- console.log(' > Writing files to disk...');
399
- writeImplementationFiles(implementation);
396
+ const filesWrittenCount = (implementation?.implementation?.filesWritten || implementation?.filesWritten || []).length;
397
+ console.log(` > Agent wrote ${filesWrittenCount} file(s) to disk`);
400
398
  }
401
399
 
402
400
  setTaskStage(i, taskId, TASK_STAGES.CODE_REVIEW);
@@ -407,14 +405,83 @@ export default async function () {
407
405
  if (stage === TASK_STAGES.CODE_REVIEW) {
408
406
  if (!getTaskData(i, taskId, 'review')) {
409
407
  console.log(' > Code review...');
410
- const codeReview = await agent('code-reviewer', {
411
- task: task,
412
- implementation: getTaskData(i, taskId, 'code'),
413
- testPlan: getTaskData(i, taskId, 'tests'),
408
+ const impl = getTaskData(i, taskId, 'code');
409
+ const filesWritten = impl?.implementation?.filesWritten || impl?.filesWritten || [];
410
+ const codeReview = await agent('post-code-review', {
411
+ task: { title: task.title, description: task.description },
412
+ filesToReview: filesWritten.map(f => f.path),
413
+ implementationSummary: impl?.implementation?.summary || impl?.summary || 'See files on disk',
414
414
  feedback: feedback
415
415
  });
416
416
  setTaskData(i, taskId, 'review', codeReview);
417
417
  }
418
+
419
+ // Check for code review issues and let user decide
420
+ const review = getTaskData(i, taskId, 'review');
421
+ const hasIssues = review?.issues?.length > 0 || review?.requiredChanges?.length > 0;
422
+
423
+ if (hasIssues && !review.approved && !getTaskData(i, taskId, 'review_decision')) {
424
+ const issuesList = [
425
+ ...(review.requiredChanges || []).map(c => `[REQUIRED] ${c}`),
426
+ ...(review.issues || [])
427
+ .filter(iss => iss.severity === 'critical' || iss.severity === 'major')
428
+ .map(iss => `[${iss.severity?.toUpperCase() || 'ISSUE'}] ${iss.location || 'general'}: ${iss.description}`)
429
+ ];
430
+
431
+ if (issuesList.length > 0) {
432
+ const reviewChoice = createInteraction('choice', `phase-${i + 1}-task-${taskId}-review-issues`, {
433
+ prompt: `Code review issues for "${task.title}":\n\n${issuesList.join('\n')}\n\nHow would you like to proceed?`,
434
+ options: [
435
+ { key: 'fix', label: 'Fix issues', description: 'Run code-fixer to address these issues' },
436
+ { key: 'ignore', label: 'Ignore and continue', description: 'Proceed to security review despite issues' },
437
+ { key: 'reimplement', label: 'Reimplement', description: 'Start implementation from scratch' }
438
+ ],
439
+ allowCustom: true
440
+ });
441
+
442
+ const reviewRaw = await askHuman(formatPrompt(reviewChoice), {
443
+ slug: reviewChoice.slug,
444
+ interaction: reviewChoice
445
+ });
446
+ const reviewDecision = await parseResponse(reviewChoice, reviewRaw);
447
+ setTaskData(i, taskId, 'review_decision', reviewDecision.selectedKey);
448
+
449
+ if (reviewDecision.selectedKey === 'fix') {
450
+ console.log(' > Fixing code review issues...');
451
+ const implForFix = getTaskData(i, taskId, 'code');
452
+ const filesForFix = implForFix?.implementation?.filesWritten || implForFix?.filesWritten || [];
453
+ const fixerResult = await agent('post-code-fix', {
454
+ task: { title: task.title },
455
+ reviewIssues: review.issues,
456
+ requiredChanges: review.requiredChanges,
457
+ filePaths: filesForFix.map(f => f.path)
458
+ });
459
+
460
+ // Agent writes files directly, just update metadata if fixes were applied
461
+ if (fixerResult?.fixesApplied?.length > 0) {
462
+ console.log(` > Fixed ${fixerResult.fixesApplied.length} file(s)`);
463
+ }
464
+
465
+ // Clear review to re-run it
466
+ setTaskData(i, taskId, 'review', null);
467
+ setTaskData(i, taskId, 'review_decision', null);
468
+ t--;
469
+ continue;
470
+ }
471
+
472
+ if (reviewDecision.selectedKey === 'reimplement') {
473
+ clearPartialTaskData(i, taskId, ['security_pre', 'tests']);
474
+ setTaskData(i, taskId, 'review', null);
475
+ setTaskData(i, taskId, 'review_decision', null);
476
+ setTaskStage(i, taskId, TASK_STAGES.IMPLEMENTING);
477
+ t--;
478
+ continue;
479
+ }
480
+
481
+ // 'ignore' falls through to next stage
482
+ }
483
+ }
484
+
418
485
  setTaskStage(i, taskId, TASK_STAGES.SECURITY_POST);
419
486
  stage = TASK_STAGES.SECURITY_POST;
420
487
  }
@@ -423,11 +490,11 @@ export default async function () {
423
490
  if (stage === TASK_STAGES.SECURITY_POST) {
424
491
  if (!getTaskData(i, taskId, 'security_post')) {
425
492
  console.log(' > Final security check...');
426
- const securityPostReview = await agent('security-reviewer', {
427
- task: task,
428
- phase: phase,
429
- implementation: getTaskData(i, taskId, 'code'),
430
- stage: 'post-implementation',
493
+ const implForSecurity = getTaskData(i, taskId, 'code');
494
+ const filesForSecurity = implForSecurity?.implementation?.filesWritten || implForSecurity?.filesWritten || [];
495
+ const securityPostReview = await agent('post-code-security', {
496
+ task: { title: task.title, description: task.description },
497
+ filePaths: filesForSecurity.map(f => f.path),
431
498
  feedback: feedback
432
499
  });
433
500
  setTaskData(i, taskId, 'security_post', securityPostReview);
@@ -439,10 +506,11 @@ export default async function () {
439
506
  // 6. Sanity check generation & execution
440
507
  if (stage === TASK_STAGES.SANITY_CHECK) {
441
508
  const testFramework = detectTestFramework();
442
- const executableChecks = await agent('sanity-checker', {
443
- task: task,
444
- implementation: getTaskData(i, taskId, 'code'),
445
- testPlan: getTaskData(i, taskId, 'tests'),
509
+ const implForSanity = getTaskData(i, taskId, 'code');
510
+ const filesForSanity = implForSanity?.implementation?.filesWritten || implForSanity?.filesWritten || [];
511
+ const executableChecks = await agent('verify-sanity', {
512
+ task: { title: task.title, sanityCheck: task.sanityCheck },
513
+ filePaths: filesForSanity.map(f => f.path),
446
514
  testFramework
447
515
  });
448
516
  setTaskData(i, taskId, 'sanity_checks', executableChecks);
@@ -478,10 +546,11 @@ export default async function () {
478
546
  const action = sanityResponse.selectedKey;
479
547
 
480
548
  if (action === 'auto') {
481
- const results = await agent('sanity-runner', {
549
+ const results = await runSanityChecks({
482
550
  checks: executableChecks.checks,
483
551
  setup: executableChecks.setup,
484
- teardown: executableChecks.teardown
552
+ teardown: executableChecks.teardown,
553
+ _config: { projectRoot: getCurrentRuntime().workflowConfig.projectRoot }
485
554
  });
486
555
  setTaskData(i, taskId, 'sanity_results', results);
487
556
 
@@ -533,30 +602,21 @@ export default async function () {
533
602
 
534
603
  if (failResponse.selectedKey === 'quickfix') {
535
604
  console.log(' > Running quick fix...');
536
- const fixerResult = await agent('code-fixer', {
537
- task: task,
538
- originalImplementation: getTaskData(i, taskId, 'code'),
539
- sanityCheckResults: {
540
- summary: results.summary,
541
- results: results.results,
542
- checks: executableChecks.checks
543
- },
544
- testPlan: getTaskData(i, taskId, 'tests'),
605
+ const implForQuickFix = getTaskData(i, taskId, 'code');
606
+ const filesForQuickFix = implForQuickFix?.implementation?.filesWritten || implForQuickFix?.filesWritten || [];
607
+ const failedChecksList = results.results.filter(r => r.status === 'failed');
608
+ const fixerResult = await agent('post-code-fix', {
609
+ task: { title: task.title },
610
+ failedChecks: failedChecksList.map(c => ({ id: c.id, error: c.error, output: c.output })),
611
+ filePaths: filesForQuickFix.map(f => f.path),
545
612
  previousAttempts: quickFixAttempts
546
613
  });
547
614
 
548
- const fixes = fixerResult?.fixes || [];
549
- const fixFiles = fixes
550
- .filter((fix) => fix?.path && fix?.code && (!fix.operation || fix.operation === 'replace'))
551
- .map((fix) => ({ path: fix.path, code: fix.code }));
552
-
553
- if (fixFiles.length > 0) {
554
- console.log(' > Applying fixes to disk...');
555
- writeImplementationFiles({ files: fixFiles });
615
+ // Agent writes files directly
616
+ if (fixerResult?.fixesApplied?.length > 0) {
617
+ console.log(` > Fixed ${fixerResult.fixesApplied.length} file(s)`);
556
618
  }
557
619
 
558
- const updatedImplementation = applyFixesToImplementation(getTaskData(i, taskId, 'code'), fixes);
559
- setTaskData(i, taskId, 'code', updatedImplementation);
560
620
  incrementQuickFixAttempts(i, taskId);
561
621
  setTaskData(i, taskId, 'sanity_checks', null);
562
622
  setTaskData(i, taskId, 'sanity_results', null);
@@ -590,6 +650,32 @@ export default async function () {
590
650
  task.stage = 'completed';
591
651
  memory[tasksKey] = tasks;
592
652
  writeMarkdownFile(STATE_DIR, `phase-${i + 1}-tasks.md`, renderTasksMarkdown(i + 1, phase.title, tasks));
653
+
654
+ // Git commit for completed task
655
+ const implForCommit = getTaskData(i, taskId, 'code');
656
+ const filesForCommit = implForCommit?.implementation?.filesWritten || implForCommit?.filesWritten || [];
657
+ if (filesForCommit.length > 0) {
658
+ console.log(' > Generating commit message...');
659
+ const commitMsg = await agent('verify-commit-msg', {
660
+ task: { title: task.title, description: task.description },
661
+ filesWritten: filesForCommit
662
+ });
663
+ const runtime = getCurrentRuntime();
664
+ const fullMessage = commitMsg.scope
665
+ ? `${commitMsg.type}(${commitMsg.scope}): ${commitMsg.message}\n\n${commitMsg.body || ''}`
666
+ : `${commitMsg.type}: ${commitMsg.message}\n\n${commitMsg.body || ''}`;
667
+ const commitResult = createGitCommit(
668
+ runtime.workflowConfig.projectRoot,
669
+ fullMessage.trim(),
670
+ filesForCommit.map(f => f.path)
671
+ );
672
+ if (commitResult.success) {
673
+ console.log(` > Committed: ${commitResult.hash}`);
674
+ } else {
675
+ console.log(` > Commit skipped: ${commitResult.error}`);
676
+ }
677
+ }
678
+
593
679
  console.log(` Task ${t + 1} confirmed complete!\n`);
594
680
  } else if (action === 'skip') {
595
681
  resetQuickFixAttempts(i, taskId);
@@ -598,6 +684,32 @@ export default async function () {
598
684
  task.stage = 'completed';
599
685
  memory[tasksKey] = tasks;
600
686
  writeMarkdownFile(STATE_DIR, `phase-${i + 1}-tasks.md`, renderTasksMarkdown(i + 1, phase.title, tasks));
687
+
688
+ // Git commit for completed task (skipped verification)
689
+ const implForCommitSkip = getTaskData(i, taskId, 'code');
690
+ const filesForCommitSkip = implForCommitSkip?.implementation?.filesWritten || implForCommitSkip?.filesWritten || [];
691
+ if (filesForCommitSkip.length > 0) {
692
+ console.log(' > Generating commit message...');
693
+ const commitMsgSkip = await agent('verify-commit-msg', {
694
+ task: { title: task.title, description: task.description },
695
+ filesWritten: filesForCommitSkip
696
+ });
697
+ const runtimeSkip = getCurrentRuntime();
698
+ const fullMessageSkip = commitMsgSkip.scope
699
+ ? `${commitMsgSkip.type}(${commitMsgSkip.scope}): ${commitMsgSkip.message}\n\n${commitMsgSkip.body || ''}`
700
+ : `${commitMsgSkip.type}: ${commitMsgSkip.message}\n\n${commitMsgSkip.body || ''}`;
701
+ const commitResultSkip = createGitCommit(
702
+ runtimeSkip.workflowConfig.projectRoot,
703
+ fullMessageSkip.trim(),
704
+ filesForCommitSkip.map(f => f.path)
705
+ );
706
+ if (commitResultSkip.success) {
707
+ console.log(` > Committed: ${commitResultSkip.hash}`);
708
+ } else {
709
+ console.log(` > Commit skipped: ${commitResultSkip.error}`);
710
+ }
711
+ }
712
+
601
713
  console.log(` Task ${t + 1} confirmed complete!\n`);
602
714
  } else {
603
715
  setTaskStage(i, taskId, TASK_STAGES.AWAITING_APPROVAL);
@@ -628,6 +740,32 @@ export default async function () {
628
740
  task.stage = 'completed';
629
741
  memory[tasksKey] = tasks;
630
742
  writeMarkdownFile(STATE_DIR, `phase-${i + 1}-tasks.md`, renderTasksMarkdown(i + 1, phase.title, tasks));
743
+
744
+ // Git commit for manually approved task
745
+ const implForCommitManual = getTaskData(i, taskId, 'code');
746
+ const filesForCommitManual = implForCommitManual?.implementation?.filesWritten || implForCommitManual?.filesWritten || [];
747
+ if (filesForCommitManual.length > 0) {
748
+ console.log(' > Generating commit message...');
749
+ const commitMsgManual = await agent('verify-commit-msg', {
750
+ task: { title: task.title, description: task.description },
751
+ filesWritten: filesForCommitManual
752
+ });
753
+ const runtimeManual = getCurrentRuntime();
754
+ const fullMessageManual = commitMsgManual.scope
755
+ ? `${commitMsgManual.type}(${commitMsgManual.scope}): ${commitMsgManual.message}\n\n${commitMsgManual.body || ''}`
756
+ : `${commitMsgManual.type}: ${commitMsgManual.message}\n\n${commitMsgManual.body || ''}`;
757
+ const commitResultManual = createGitCommit(
758
+ runtimeManual.workflowConfig.projectRoot,
759
+ fullMessageManual.trim(),
760
+ filesForCommitManual.map(f => f.path)
761
+ );
762
+ if (commitResultManual.success) {
763
+ console.log(` > Committed: ${commitResultManual.hash}`);
764
+ } else {
765
+ console.log(` > Commit skipped: ${commitResultManual.error}`);
766
+ }
767
+ }
768
+
631
769
  console.log(` Task ${t + 1} confirmed complete!\n`);
632
770
  } else {
633
771
  console.log(' > Issue flagged, reprocessing task with feedback...');