@agile-vibe-coding/avc 0.3.4 → 0.3.5
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/cli/seed-processor.js
CHANGED
|
@@ -779,6 +779,37 @@ Return your response as JSON following the exact structure specified in your ins
|
|
|
779
779
|
this.debug(`[INFO] Updated story doc.md after task extraction (${storyDocContent.length} bytes)`);
|
|
780
780
|
}
|
|
781
781
|
|
|
782
|
+
// Compute ready status for tasks/subtasks with no pending dependencies
|
|
783
|
+
try {
|
|
784
|
+
const { checkDependenciesReady } = await import('./dependency-checker.js');
|
|
785
|
+
const lookup = {};
|
|
786
|
+
for (const task of hierarchy.tasks) {
|
|
787
|
+
lookup[task.id] = { id: task.id, name: task.name, type: 'task', status: 'planned', dependencies: task.dependencies || [] };
|
|
788
|
+
for (const sub of task.subtasks || []) {
|
|
789
|
+
lookup[sub.id] = { id: sub.id, name: sub.name, type: 'subtask', status: 'planned', dependencies: sub.dependencies || [] };
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
let readyCount = 0;
|
|
794
|
+
for (const id of Object.keys(lookup)) {
|
|
795
|
+
const result = checkDependenciesReady(id, lookup);
|
|
796
|
+
if (result.ready) {
|
|
797
|
+
const itemDir = path.join(this.projectPath, id);
|
|
798
|
+
const wjPath = path.join(itemDir, 'work.json');
|
|
799
|
+
if (fs.existsSync(wjPath)) {
|
|
800
|
+
const wj = JSON.parse(fs.readFileSync(wjPath, 'utf8'));
|
|
801
|
+
wj.status = 'ready';
|
|
802
|
+
fs.writeFileSync(wjPath, JSON.stringify(wj, null, 2), 'utf8');
|
|
803
|
+
lookup[id].status = 'ready';
|
|
804
|
+
readyCount++;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
this.debug(`[INFO] Set ${readyCount} task/subtask items to 'ready' status (dependency-free)`);
|
|
809
|
+
} catch (err) {
|
|
810
|
+
this.debug(`[WARN] Ready status computation failed (non-critical): ${err.message}`);
|
|
811
|
+
}
|
|
812
|
+
|
|
782
813
|
return { taskCount, subtaskCount, taskIds };
|
|
783
814
|
}
|
|
784
815
|
|
package/cli/worktree-runner.js
CHANGED
|
@@ -141,16 +141,21 @@ export class WorktreeRunner {
|
|
|
141
141
|
return { success: false, error: `Tests failed: ${testResult.summary}` };
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
// 5. Commit
|
|
145
|
-
progressCallback?.('Committing
|
|
144
|
+
// 5. Commit in worktree (do NOT merge — leave for human review)
|
|
145
|
+
progressCallback?.('Committing changes in worktree...');
|
|
146
146
|
if (cancelledCheck?.()) throw new Error('CANCELLED');
|
|
147
|
-
this.
|
|
147
|
+
this.commitInWorktree();
|
|
148
148
|
|
|
149
|
-
// 6.
|
|
150
|
-
progressCallback?.(
|
|
151
|
-
this.
|
|
149
|
+
// 6. Leave worktree in place for review — do NOT cleanup or merge
|
|
150
|
+
progressCallback?.(`Code ready for review in worktree: ${this.worktreePath}`);
|
|
151
|
+
progressCallback?.(`Branch: ${this.branchName}`);
|
|
152
152
|
|
|
153
|
-
return {
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
review: true,
|
|
156
|
+
worktreePath: this.worktreePath,
|
|
157
|
+
branchName: this.branchName,
|
|
158
|
+
};
|
|
154
159
|
} catch (err) {
|
|
155
160
|
// Always cleanup on failure
|
|
156
161
|
try { this.cleanup(); } catch {}
|
|
@@ -583,28 +588,35 @@ export class WorktreeRunner {
|
|
|
583
588
|
/**
|
|
584
589
|
* Commit changes in the worktree and merge to the main branch.
|
|
585
590
|
*/
|
|
586
|
-
|
|
587
|
-
|
|
591
|
+
/**
|
|
592
|
+
* Commit all changes in the worktree (does NOT merge to main).
|
|
593
|
+
* Called after tests pass — the worktree stays in place for human review.
|
|
594
|
+
*/
|
|
595
|
+
commitInWorktree() {
|
|
588
596
|
git(['add', '-A'], this.worktreePath);
|
|
589
597
|
|
|
590
|
-
// Check if there's anything to commit
|
|
591
598
|
const status = git(['status', '--porcelain'], this.worktreePath);
|
|
592
599
|
if (!status) {
|
|
593
600
|
this.debug('No changes to commit');
|
|
594
601
|
return;
|
|
595
602
|
}
|
|
596
603
|
|
|
597
|
-
// Commit
|
|
598
604
|
const commitMsg = `feat(${this.taskId}): implement task\n\nGenerated by AVC WorktreeRunner`;
|
|
599
605
|
git(['commit', '-m', commitMsg], this.worktreePath);
|
|
600
606
|
this.debug('Committed in worktree');
|
|
607
|
+
}
|
|
601
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Merge the worktree branch into main and clean up.
|
|
611
|
+
* Called AFTER human review approves the code (status: implemented → completed).
|
|
612
|
+
* Can be invoked via API or CLI.
|
|
613
|
+
*/
|
|
614
|
+
mergeAndCleanup() {
|
|
602
615
|
// Determine the main branch name
|
|
603
616
|
let mainBranch = 'main';
|
|
604
617
|
try {
|
|
605
618
|
mainBranch = git(['symbolic-ref', '--short', 'HEAD'], this.projectRoot);
|
|
606
619
|
} catch {
|
|
607
|
-
// If HEAD is detached, try common branch names
|
|
608
620
|
try { git(['rev-parse', '--verify', 'main'], this.projectRoot); mainBranch = 'main'; } catch {
|
|
609
621
|
try { git(['rev-parse', '--verify', 'master'], this.projectRoot); mainBranch = 'master'; } catch {}
|
|
610
622
|
}
|
|
@@ -615,10 +627,12 @@ export class WorktreeRunner {
|
|
|
615
627
|
git(['merge', '--no-ff', this.branchName, '-m', `Merge ${this.branchName}: implement ${this.taskId}`], this.projectRoot);
|
|
616
628
|
this.debug('Merged to main', { mainBranch });
|
|
617
629
|
} catch (err) {
|
|
618
|
-
// Merge conflict — abort and report
|
|
619
630
|
try { git(['merge', '--abort'], this.projectRoot); } catch {}
|
|
620
631
|
throw new Error(`Merge conflict: ${err.message}`);
|
|
621
632
|
}
|
|
633
|
+
|
|
634
|
+
// Cleanup worktree and branch
|
|
635
|
+
this.cleanup();
|
|
622
636
|
}
|
|
623
637
|
|
|
624
638
|
/**
|
|
@@ -289,8 +289,9 @@ export function createWorkItemsRouter(dataStore, refineService, ceremonyService)
|
|
|
289
289
|
const VALID_TRANSITIONS = {
|
|
290
290
|
'planned': ['ready', 'implementing'],
|
|
291
291
|
'ready': ['implementing'],
|
|
292
|
-
'implementing': ['completed', 'failed'],
|
|
293
|
-
'
|
|
292
|
+
'implementing': ['implemented', 'completed', 'failed'],
|
|
293
|
+
'implemented': ['completed', 'failed'], // review → approve (merge) or reject
|
|
294
|
+
'failed': ['implementing', 'ready'],
|
|
294
295
|
'completed': [],
|
|
295
296
|
};
|
|
296
297
|
|
|
@@ -328,6 +329,20 @@ export function createWorkItemsRouter(dataStore, refineService, ceremonyService)
|
|
|
328
329
|
fsSync.writeFileSync(workJsonPath, JSON.stringify(workJson, null, 2), 'utf8');
|
|
329
330
|
}
|
|
330
331
|
|
|
332
|
+
// When a task moves from 'implemented' to 'completed' (review approved),
|
|
333
|
+
// merge its worktree branch into main and clean up.
|
|
334
|
+
if (status === 'completed' && currentStatus === 'implemented' && (item._type === 'task' || item.type === 'task')) {
|
|
335
|
+
try {
|
|
336
|
+
const { WorktreeRunner } = await import('../../../cli/worktree-runner.js');
|
|
337
|
+
const runner = new WorktreeRunner(req.params.id, path.resolve(item._dirPath, '..', '..', '..', '..'));
|
|
338
|
+
runner.mergeAndCleanup();
|
|
339
|
+
console.log(`[review-merge] Merged worktree for ${req.params.id}`);
|
|
340
|
+
} catch (mergeErr) {
|
|
341
|
+
console.error(`[review-merge] Failed to merge ${req.params.id}: ${mergeErr.message}`);
|
|
342
|
+
// Non-fatal — the status is already set to completed
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
331
346
|
// Reload hierarchy so in-memory state reflects the change
|
|
332
347
|
await dataStore.reload();
|
|
333
348
|
|
|
@@ -376,6 +391,52 @@ export function createWorkItemsRouter(dataStore, refineService, ceremonyService)
|
|
|
376
391
|
}
|
|
377
392
|
}
|
|
378
393
|
|
|
394
|
+
// Upward cascade: propagate status to parent items
|
|
395
|
+
try {
|
|
396
|
+
const { items: freshItems } = dataStore.getHierarchy();
|
|
397
|
+
const updatedItem = freshItems.get(req.params.id);
|
|
398
|
+
let parentId = updatedItem?._parentId || null;
|
|
399
|
+
let needsReload = false;
|
|
400
|
+
|
|
401
|
+
while (parentId) {
|
|
402
|
+
const parent = freshItems.get(parentId);
|
|
403
|
+
if (!parent) break;
|
|
404
|
+
|
|
405
|
+
const children = [...freshItems.values()].filter(c => c._parentId === parentId);
|
|
406
|
+
if (children.length === 0) break;
|
|
407
|
+
|
|
408
|
+
let newParentStatus = null;
|
|
409
|
+
const allCompleted = children.every(c => c.status === 'completed');
|
|
410
|
+
const anyImplementing = children.some(c =>
|
|
411
|
+
c.status === 'implementing' || c.status === 'completed'
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
if (allCompleted && parent.status !== 'completed') {
|
|
415
|
+
newParentStatus = 'completed';
|
|
416
|
+
} else if (anyImplementing && parent.status !== 'implementing' && parent.status !== 'completed') {
|
|
417
|
+
newParentStatus = 'implementing';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (newParentStatus) {
|
|
421
|
+
const parentWjPath = path.join(parent._dirPath, 'work.json');
|
|
422
|
+
if (fsSync.existsSync(parentWjPath)) {
|
|
423
|
+
const wj = JSON.parse(fsSync.readFileSync(parentWjPath, 'utf8'));
|
|
424
|
+
wj.status = newParentStatus;
|
|
425
|
+
wj.updated = new Date().toISOString();
|
|
426
|
+
fsSync.writeFileSync(parentWjPath, JSON.stringify(wj, null, 2), 'utf8');
|
|
427
|
+
needsReload = true;
|
|
428
|
+
console.log(`[status-cascade-up] ${parentId}: ${parent.status} → ${newParentStatus}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
parentId = parent._parentId || null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (needsReload) await dataStore.reload();
|
|
436
|
+
} catch (upErr) {
|
|
437
|
+
console.error('Upward status cascade error (non-critical):', upErr.message);
|
|
438
|
+
}
|
|
439
|
+
|
|
379
440
|
res.json({ status: 'ok', item: cleanWorkItem(item) });
|
|
380
441
|
} catch (error) {
|
|
381
442
|
console.error(`Error updating status for ${req.params.id}:`, error);
|
|
@@ -85,11 +85,18 @@ async function run(taskId) {
|
|
|
85
85
|
updateStatus(taskId, 'planned'); // Reset on cancel
|
|
86
86
|
try { process.send({ type: 'cancelled' }); } catch {}
|
|
87
87
|
} else if (result.success) {
|
|
88
|
-
|
|
88
|
+
// Tests passed — move to review (not completed). Human must approve before merge.
|
|
89
|
+
updateStatus(taskId, 'implemented');
|
|
89
90
|
try {
|
|
90
91
|
process.send({
|
|
91
92
|
type: 'complete',
|
|
92
|
-
result: {
|
|
93
|
+
result: {
|
|
94
|
+
success: true,
|
|
95
|
+
taskId,
|
|
96
|
+
review: true,
|
|
97
|
+
worktreePath: result.worktreePath,
|
|
98
|
+
branchName: result.branchName,
|
|
99
|
+
},
|
|
93
100
|
});
|
|
94
101
|
} catch {}
|
|
95
102
|
} else {
|
package/package.json
CHANGED