claude-kanban 0.3.1 → 0.4.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.
- package/dist/bin/cli.js +433 -22
- package/dist/bin/cli.js.map +1 -1
- package/dist/server/index.js +360 -22
- package/dist/server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -134,6 +134,7 @@ function getTaskCounts(projectPath) {
|
|
|
134
134
|
draft: 0,
|
|
135
135
|
ready: 0,
|
|
136
136
|
in_progress: 0,
|
|
137
|
+
in_review: 0,
|
|
137
138
|
completed: 0,
|
|
138
139
|
failed: 0
|
|
139
140
|
};
|
|
@@ -210,6 +211,7 @@ var WORKTREES_DIR = "worktrees";
|
|
|
210
211
|
var TaskExecutor = class extends EventEmitter {
|
|
211
212
|
projectPath;
|
|
212
213
|
runningTasks = /* @__PURE__ */ new Map();
|
|
214
|
+
reviewingTasks = /* @__PURE__ */ new Map();
|
|
213
215
|
afkMode = false;
|
|
214
216
|
afkIteration = 0;
|
|
215
217
|
afkMaxIterations = 0;
|
|
@@ -513,7 +515,7 @@ ${worktreeSection}## INSTRUCTIONS
|
|
|
513
515
|
${verifySection}${verifySteps.length > 0 ? "4" : "3"}. When complete, update the task in ${prdPath}:
|
|
514
516
|
- Find the task with id "${task.id}"
|
|
515
517
|
- Set "passes": true
|
|
516
|
-
|
|
518
|
+
(Note: Do NOT change the status - the system will move it to review)
|
|
517
519
|
|
|
518
520
|
${verifySteps.length > 0 ? "5" : "4"}. Document your work in ${progressPath}:
|
|
519
521
|
- What you implemented and files changed
|
|
@@ -692,7 +694,7 @@ Focus only on this task. When successfully complete, output: <promise>COMPLETE</
|
|
|
692
694
|
}, timeoutMs);
|
|
693
695
|
}
|
|
694
696
|
/**
|
|
695
|
-
* Handle task completion
|
|
697
|
+
* Handle task completion - moves to in_review, keeps worktree alive
|
|
696
698
|
*/
|
|
697
699
|
handleTaskComplete(taskId, exitCode, startedAt) {
|
|
698
700
|
const runningTask = this.runningTasks.get(taskId);
|
|
@@ -704,31 +706,25 @@ Focus only on this task. When successfully complete, output: <promise>COMPLETE</
|
|
|
704
706
|
const task = getTaskById(this.projectPath, taskId);
|
|
705
707
|
if (isComplete || exitCode === 0) {
|
|
706
708
|
if (runningTask.worktreePath && runningTask.branchName) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
}
|
|
713
|
-
|
|
709
|
+
this.reviewingTasks.set(taskId, {
|
|
710
|
+
worktreePath: runningTask.worktreePath,
|
|
711
|
+
branchName: runningTask.branchName,
|
|
712
|
+
startedAt,
|
|
713
|
+
endedAt
|
|
714
|
+
});
|
|
715
|
+
console.log(`[executor] Task ${taskId} ready for review at ${runningTask.worktreePath}`);
|
|
714
716
|
}
|
|
715
717
|
updateTask(this.projectPath, taskId, {
|
|
716
|
-
status: "
|
|
718
|
+
status: "in_review",
|
|
717
719
|
passes: true
|
|
718
720
|
});
|
|
719
|
-
addExecutionEntry(this.projectPath, taskId, {
|
|
720
|
-
startedAt: startedAt.toISOString(),
|
|
721
|
-
endedAt: endedAt.toISOString(),
|
|
722
|
-
status: "completed",
|
|
723
|
-
duration
|
|
724
|
-
});
|
|
725
721
|
logTaskExecution(this.projectPath, {
|
|
726
722
|
taskId,
|
|
727
723
|
taskTitle: task?.title || "Unknown",
|
|
728
|
-
status: "
|
|
724
|
+
status: "in_review",
|
|
729
725
|
duration
|
|
730
726
|
});
|
|
731
|
-
this.emit("task:completed", { taskId, duration });
|
|
727
|
+
this.emit("task:completed", { taskId, duration, status: "in_review" });
|
|
732
728
|
this.afkTasksCompleted++;
|
|
733
729
|
} else {
|
|
734
730
|
if (runningTask.worktreePath) {
|
|
@@ -760,6 +756,151 @@ Focus only on this task. When successfully complete, output: <promise>COMPLETE</
|
|
|
760
756
|
this.continueAFKMode();
|
|
761
757
|
}
|
|
762
758
|
}
|
|
759
|
+
/**
|
|
760
|
+
* Get info about a task in review
|
|
761
|
+
*/
|
|
762
|
+
getReviewingTask(taskId) {
|
|
763
|
+
return this.reviewingTasks.get(taskId);
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Merge a reviewed task into the base branch
|
|
767
|
+
*/
|
|
768
|
+
mergeTask(taskId) {
|
|
769
|
+
const reviewInfo = this.reviewingTasks.get(taskId);
|
|
770
|
+
if (!reviewInfo) {
|
|
771
|
+
return { success: false, error: "Task not in review or no worktree found" };
|
|
772
|
+
}
|
|
773
|
+
const merged = this.mergeWorktreeBranch(taskId);
|
|
774
|
+
if (!merged) {
|
|
775
|
+
return { success: false, error: "Merge failed - may have conflicts" };
|
|
776
|
+
}
|
|
777
|
+
this.removeWorktree(taskId);
|
|
778
|
+
this.reviewingTasks.delete(taskId);
|
|
779
|
+
updateTask(this.projectPath, taskId, {
|
|
780
|
+
status: "completed"
|
|
781
|
+
});
|
|
782
|
+
addExecutionEntry(this.projectPath, taskId, {
|
|
783
|
+
startedAt: reviewInfo.startedAt.toISOString(),
|
|
784
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
785
|
+
status: "completed",
|
|
786
|
+
duration: (/* @__PURE__ */ new Date()).getTime() - reviewInfo.startedAt.getTime()
|
|
787
|
+
});
|
|
788
|
+
console.log(`[executor] Task ${taskId} merged successfully`);
|
|
789
|
+
return { success: true };
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Create a PR for a reviewed task
|
|
793
|
+
*/
|
|
794
|
+
createPR(taskId) {
|
|
795
|
+
const reviewInfo = this.reviewingTasks.get(taskId);
|
|
796
|
+
const task = getTaskById(this.projectPath, taskId);
|
|
797
|
+
if (!reviewInfo) {
|
|
798
|
+
return { success: false, error: "Task not in review or no worktree found" };
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
execSync(`git push -u origin ${reviewInfo.branchName}`, {
|
|
802
|
+
cwd: this.projectPath,
|
|
803
|
+
stdio: "pipe"
|
|
804
|
+
});
|
|
805
|
+
const prTitle = task?.title || `Task ${taskId}`;
|
|
806
|
+
const prBody = task?.description || "";
|
|
807
|
+
const result = execSync(
|
|
808
|
+
`gh pr create --title "${prTitle.replace(/"/g, '\\"')}" --body "${prBody.replace(/"/g, '\\"')}" --head ${reviewInfo.branchName}`,
|
|
809
|
+
{
|
|
810
|
+
cwd: this.projectPath,
|
|
811
|
+
encoding: "utf-8"
|
|
812
|
+
}
|
|
813
|
+
);
|
|
814
|
+
const prUrl = result.trim();
|
|
815
|
+
try {
|
|
816
|
+
execSync(`git worktree remove "${reviewInfo.worktreePath}" --force`, {
|
|
817
|
+
cwd: this.projectPath,
|
|
818
|
+
stdio: "pipe"
|
|
819
|
+
});
|
|
820
|
+
} catch {
|
|
821
|
+
}
|
|
822
|
+
this.reviewingTasks.delete(taskId);
|
|
823
|
+
updateTask(this.projectPath, taskId, {
|
|
824
|
+
status: "completed"
|
|
825
|
+
});
|
|
826
|
+
addExecutionEntry(this.projectPath, taskId, {
|
|
827
|
+
startedAt: reviewInfo.startedAt.toISOString(),
|
|
828
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
829
|
+
status: "completed",
|
|
830
|
+
duration: (/* @__PURE__ */ new Date()).getTime() - reviewInfo.startedAt.getTime()
|
|
831
|
+
});
|
|
832
|
+
console.log(`[executor] PR created for task ${taskId}: ${prUrl}`);
|
|
833
|
+
return { success: true, prUrl };
|
|
834
|
+
} catch (error) {
|
|
835
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
836
|
+
console.error(`[executor] Failed to create PR:`, errorMsg);
|
|
837
|
+
return { success: false, error: errorMsg };
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Discard a reviewed task - delete worktree and return to ready
|
|
842
|
+
*/
|
|
843
|
+
discardTask(taskId) {
|
|
844
|
+
const reviewInfo = this.reviewingTasks.get(taskId);
|
|
845
|
+
if (!reviewInfo) {
|
|
846
|
+
return { success: false, error: "Task not in review or no worktree found" };
|
|
847
|
+
}
|
|
848
|
+
this.removeWorktree(taskId);
|
|
849
|
+
this.reviewingTasks.delete(taskId);
|
|
850
|
+
updateTask(this.projectPath, taskId, {
|
|
851
|
+
status: "ready",
|
|
852
|
+
passes: false
|
|
853
|
+
});
|
|
854
|
+
addExecutionEntry(this.projectPath, taskId, {
|
|
855
|
+
startedAt: reviewInfo.startedAt.toISOString(),
|
|
856
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
857
|
+
status: "discarded",
|
|
858
|
+
duration: (/* @__PURE__ */ new Date()).getTime() - reviewInfo.startedAt.getTime()
|
|
859
|
+
});
|
|
860
|
+
console.log(`[executor] Task ${taskId} discarded, returned to ready`);
|
|
861
|
+
return { success: true };
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Get diff for a task in review
|
|
865
|
+
*/
|
|
866
|
+
getTaskDiff(taskId) {
|
|
867
|
+
const reviewInfo = this.reviewingTasks.get(taskId);
|
|
868
|
+
if (!reviewInfo) {
|
|
869
|
+
return { success: false, error: "Task not in review or no worktree found" };
|
|
870
|
+
}
|
|
871
|
+
try {
|
|
872
|
+
const diff = execSync(`git diff main...${reviewInfo.branchName}`, {
|
|
873
|
+
cwd: this.projectPath,
|
|
874
|
+
encoding: "utf-8",
|
|
875
|
+
maxBuffer: 10 * 1024 * 1024
|
|
876
|
+
// 10MB buffer for large diffs
|
|
877
|
+
});
|
|
878
|
+
return { success: true, diff };
|
|
879
|
+
} catch (error) {
|
|
880
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
881
|
+
return { success: false, error: errorMsg };
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Get list of changed files for a task in review
|
|
886
|
+
*/
|
|
887
|
+
getTaskChangedFiles(taskId) {
|
|
888
|
+
const reviewInfo = this.reviewingTasks.get(taskId);
|
|
889
|
+
if (!reviewInfo) {
|
|
890
|
+
return { success: false, error: "Task not in review or no worktree found" };
|
|
891
|
+
}
|
|
892
|
+
try {
|
|
893
|
+
const result = execSync(`git diff --name-only main...${reviewInfo.branchName}`, {
|
|
894
|
+
cwd: this.projectPath,
|
|
895
|
+
encoding: "utf-8"
|
|
896
|
+
});
|
|
897
|
+
const files = result.trim().split("\n").filter((f) => f);
|
|
898
|
+
return { success: true, files };
|
|
899
|
+
} catch (error) {
|
|
900
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
901
|
+
return { success: false, error: errorMsg };
|
|
902
|
+
}
|
|
903
|
+
}
|
|
763
904
|
/**
|
|
764
905
|
* Cancel a running task
|
|
765
906
|
*/
|
|
@@ -1426,6 +1567,90 @@ async function createServer(projectPath, port) {
|
|
|
1426
1567
|
res.status(500).json({ error: String(error) });
|
|
1427
1568
|
}
|
|
1428
1569
|
});
|
|
1570
|
+
app.post("/api/tasks/:id/merge", (req, res) => {
|
|
1571
|
+
try {
|
|
1572
|
+
const result = executor.mergeTask(req.params.id);
|
|
1573
|
+
if (!result.success) {
|
|
1574
|
+
res.status(400).json({ error: result.error });
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
const task = getTaskById(projectPath, req.params.id);
|
|
1578
|
+
if (task) {
|
|
1579
|
+
io.emit("task:updated", task);
|
|
1580
|
+
}
|
|
1581
|
+
res.json({ success: true });
|
|
1582
|
+
} catch (error) {
|
|
1583
|
+
res.status(500).json({ error: String(error) });
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
app.post("/api/tasks/:id/create-pr", (req, res) => {
|
|
1587
|
+
try {
|
|
1588
|
+
const result = executor.createPR(req.params.id);
|
|
1589
|
+
if (!result.success) {
|
|
1590
|
+
res.status(400).json({ error: result.error });
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
const task = getTaskById(projectPath, req.params.id);
|
|
1594
|
+
if (task) {
|
|
1595
|
+
io.emit("task:updated", task);
|
|
1596
|
+
}
|
|
1597
|
+
res.json({ success: true, prUrl: result.prUrl });
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
res.status(500).json({ error: String(error) });
|
|
1600
|
+
}
|
|
1601
|
+
});
|
|
1602
|
+
app.post("/api/tasks/:id/discard", (req, res) => {
|
|
1603
|
+
try {
|
|
1604
|
+
const result = executor.discardTask(req.params.id);
|
|
1605
|
+
if (!result.success) {
|
|
1606
|
+
res.status(400).json({ error: result.error });
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
const task = getTaskById(projectPath, req.params.id);
|
|
1610
|
+
if (task) {
|
|
1611
|
+
io.emit("task:updated", task);
|
|
1612
|
+
}
|
|
1613
|
+
res.json({ success: true });
|
|
1614
|
+
} catch (error) {
|
|
1615
|
+
res.status(500).json({ error: String(error) });
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
app.get("/api/tasks/:id/diff", (req, res) => {
|
|
1619
|
+
try {
|
|
1620
|
+
const result = executor.getTaskDiff(req.params.id);
|
|
1621
|
+
if (!result.success) {
|
|
1622
|
+
res.status(400).json({ error: result.error });
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
res.json({ diff: result.diff });
|
|
1626
|
+
} catch (error) {
|
|
1627
|
+
res.status(500).json({ error: String(error) });
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1630
|
+
app.get("/api/tasks/:id/changed-files", (req, res) => {
|
|
1631
|
+
try {
|
|
1632
|
+
const result = executor.getTaskChangedFiles(req.params.id);
|
|
1633
|
+
if (!result.success) {
|
|
1634
|
+
res.status(400).json({ error: result.error });
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
res.json({ files: result.files });
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
res.status(500).json({ error: String(error) });
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
app.get("/api/tasks/:id/review-info", (req, res) => {
|
|
1643
|
+
try {
|
|
1644
|
+
const info = executor.getReviewingTask(req.params.id);
|
|
1645
|
+
if (!info) {
|
|
1646
|
+
res.status(404).json({ error: "Task not in review" });
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
res.json(info);
|
|
1650
|
+
} catch (error) {
|
|
1651
|
+
res.status(500).json({ error: String(error) });
|
|
1652
|
+
}
|
|
1653
|
+
});
|
|
1429
1654
|
app.get("/api/tasks/:id/logs", (req, res) => {
|
|
1430
1655
|
try {
|
|
1431
1656
|
const logs = executor.getTaskLog(req.params.id);
|
|
@@ -1735,6 +1960,7 @@ function getClientHTML() {
|
|
|
1735
1960
|
.status-dot-draft { background: #a3a3a3; }
|
|
1736
1961
|
.status-dot-ready { background: #3b82f6; }
|
|
1737
1962
|
.status-dot-in_progress { background: #f97316; }
|
|
1963
|
+
.status-dot-in_review { background: #8b5cf6; }
|
|
1738
1964
|
.status-dot-completed { background: #22c55e; }
|
|
1739
1965
|
.status-dot-failed { background: #ef4444; }
|
|
1740
1966
|
|
|
@@ -1944,6 +2170,7 @@ function getClientHTML() {
|
|
|
1944
2170
|
.status-badge-draft { background: #f5f5f5; color: #737373; }
|
|
1945
2171
|
.status-badge-ready { background: #eff6ff; color: #3b82f6; }
|
|
1946
2172
|
.status-badge-in_progress { background: #fff7ed; color: #f97316; }
|
|
2173
|
+
.status-badge-in_review { background: #f5f3ff; color: #8b5cf6; }
|
|
1947
2174
|
.status-badge-completed { background: #f0fdf4; color: #22c55e; }
|
|
1948
2175
|
.status-badge-failed { background: #fef2f2; color: #ef4444; }
|
|
1949
2176
|
|
|
@@ -2269,11 +2496,16 @@ socket.on('task:output', ({ taskId, line }) => {
|
|
|
2269
2496
|
}
|
|
2270
2497
|
});
|
|
2271
2498
|
|
|
2272
|
-
socket.on('task:completed', ({ taskId }) => {
|
|
2499
|
+
socket.on('task:completed', ({ taskId, status }) => {
|
|
2273
2500
|
state.running = state.running.filter(id => id !== taskId);
|
|
2274
2501
|
const task = state.tasks.find(t => t.id === taskId);
|
|
2275
|
-
if (task) {
|
|
2276
|
-
|
|
2502
|
+
if (task) {
|
|
2503
|
+
// Use status from server (could be 'in_review' or 'completed')
|
|
2504
|
+
task.status = status || 'in_review';
|
|
2505
|
+
task.passes = true;
|
|
2506
|
+
}
|
|
2507
|
+
const statusMsg = (status === 'in_review') ? 'ready for review' : 'completed';
|
|
2508
|
+
showToast('Task ' + statusMsg + ': ' + (task?.title || taskId), 'success');
|
|
2277
2509
|
render();
|
|
2278
2510
|
});
|
|
2279
2511
|
|
|
@@ -2347,6 +2579,53 @@ async function retryTask(id) {
|
|
|
2347
2579
|
});
|
|
2348
2580
|
}
|
|
2349
2581
|
|
|
2582
|
+
async function mergeTask(id) {
|
|
2583
|
+
const res = await fetch('/api/tasks/' + id + '/merge', { method: 'POST' });
|
|
2584
|
+
const data = await res.json();
|
|
2585
|
+
if (!res.ok) {
|
|
2586
|
+
throw new Error(data.error || 'Merge failed');
|
|
2587
|
+
}
|
|
2588
|
+
return data;
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
async function createPR(id) {
|
|
2592
|
+
const res = await fetch('/api/tasks/' + id + '/create-pr', { method: 'POST' });
|
|
2593
|
+
const data = await res.json();
|
|
2594
|
+
if (!res.ok) {
|
|
2595
|
+
throw new Error(data.error || 'PR creation failed');
|
|
2596
|
+
}
|
|
2597
|
+
return data;
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
async function discardTask(id) {
|
|
2601
|
+
const res = await fetch('/api/tasks/' + id + '/discard', { method: 'POST' });
|
|
2602
|
+
const data = await res.json();
|
|
2603
|
+
if (!res.ok) {
|
|
2604
|
+
throw new Error(data.error || 'Discard failed');
|
|
2605
|
+
}
|
|
2606
|
+
return data;
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
async function getReviewInfo(id) {
|
|
2610
|
+
const res = await fetch('/api/tasks/' + id + '/review-info');
|
|
2611
|
+
const data = await res.json();
|
|
2612
|
+
if (!res.ok) {
|
|
2613
|
+
throw new Error(data.error || 'Failed to get review info');
|
|
2614
|
+
}
|
|
2615
|
+
return data;
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
async function openInVSCode(id) {
|
|
2619
|
+
try {
|
|
2620
|
+
const info = await getReviewInfo(id);
|
|
2621
|
+
// Open VS Code at the worktree path
|
|
2622
|
+
window.open('vscode://file/' + encodeURIComponent(info.worktreePath), '_blank');
|
|
2623
|
+
showToast('Opening in VS Code...', 'info');
|
|
2624
|
+
} catch (e) {
|
|
2625
|
+
showToast('Failed to open in VS Code: ' + e.message, 'error');
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2350
2629
|
async function generateTask(prompt) {
|
|
2351
2630
|
state.aiGenerating = true;
|
|
2352
2631
|
render();
|
|
@@ -2550,6 +2829,7 @@ function renderColumn(status, title, tasks) {
|
|
|
2550
2829
|
draft: 'To Do',
|
|
2551
2830
|
ready: 'Ready',
|
|
2552
2831
|
in_progress: 'In Progress',
|
|
2832
|
+
in_review: 'In Review',
|
|
2553
2833
|
completed: 'Done',
|
|
2554
2834
|
failed: 'Failed'
|
|
2555
2835
|
};
|
|
@@ -2915,7 +3195,7 @@ function renderSidePanel() {
|
|
|
2915
3195
|
</div>
|
|
2916
3196
|
|
|
2917
3197
|
<!-- Action Buttons -->
|
|
2918
|
-
<div class="px-5 py-4 border-b border-canvas-200 flex gap-2 flex-shrink-0">
|
|
3198
|
+
<div class="px-5 py-4 border-b border-canvas-200 flex flex-wrap gap-2 flex-shrink-0">
|
|
2919
3199
|
\${task.status === 'draft' ? \`
|
|
2920
3200
|
<button onclick="updateTask('\${task.id}', { status: 'ready' })" class="btn btn-primary px-4 py-2 text-sm">
|
|
2921
3201
|
\u2192 Move to Ready
|
|
@@ -2931,6 +3211,20 @@ function renderSidePanel() {
|
|
|
2931
3211
|
\u23F9 Stop Attempt
|
|
2932
3212
|
</button>
|
|
2933
3213
|
\` : ''}
|
|
3214
|
+
\${task.status === 'in_review' ? \`
|
|
3215
|
+
<button onclick="handleMerge('\${task.id}')" class="btn btn-primary px-4 py-2 text-sm">
|
|
3216
|
+
\u2713 Merge
|
|
3217
|
+
</button>
|
|
3218
|
+
<button onclick="handleCreatePR('\${task.id}')" class="btn btn-ghost px-4 py-2 text-sm">
|
|
3219
|
+
\u21E1 Create PR
|
|
3220
|
+
</button>
|
|
3221
|
+
<button onclick="openInVSCode('\${task.id}')" class="btn btn-ghost px-4 py-2 text-sm">
|
|
3222
|
+
\u{1F4C2} Open in VS Code
|
|
3223
|
+
</button>
|
|
3224
|
+
<button onclick="handleDiscard('\${task.id}')" class="btn btn-danger px-4 py-2 text-sm">
|
|
3225
|
+
\u2715 Discard
|
|
3226
|
+
</button>
|
|
3227
|
+
\` : ''}
|
|
2934
3228
|
\${task.status === 'failed' ? \`
|
|
2935
3229
|
<button onclick="retryTask('\${task.id}')" class="btn btn-primary px-4 py-2 text-sm">
|
|
2936
3230
|
\u21BB Retry
|
|
@@ -3131,6 +3425,41 @@ function handleStartAFK() {
|
|
|
3131
3425
|
render();
|
|
3132
3426
|
}
|
|
3133
3427
|
|
|
3428
|
+
async function handleMerge(taskId) {
|
|
3429
|
+
if (!confirm('Merge this task into the main branch?')) return;
|
|
3430
|
+
try {
|
|
3431
|
+
await mergeTask(taskId);
|
|
3432
|
+
showToast('Task merged successfully!', 'success');
|
|
3433
|
+
closeSidePanel();
|
|
3434
|
+
} catch (e) {
|
|
3435
|
+
showToast('Merge failed: ' + e.message, 'error');
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
async function handleCreatePR(taskId) {
|
|
3440
|
+
try {
|
|
3441
|
+
const result = await createPR(taskId);
|
|
3442
|
+
showToast('PR created successfully!', 'success');
|
|
3443
|
+
if (result.prUrl) {
|
|
3444
|
+
window.open(result.prUrl, '_blank');
|
|
3445
|
+
}
|
|
3446
|
+
closeSidePanel();
|
|
3447
|
+
} catch (e) {
|
|
3448
|
+
showToast('PR creation failed: ' + e.message, 'error');
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
async function handleDiscard(taskId) {
|
|
3453
|
+
if (!confirm('Discard this work? The task will return to Ready status.')) return;
|
|
3454
|
+
try {
|
|
3455
|
+
await discardTask(taskId);
|
|
3456
|
+
showToast('Task discarded, returned to Ready', 'warning');
|
|
3457
|
+
closeSidePanel();
|
|
3458
|
+
} catch (e) {
|
|
3459
|
+
showToast('Discard failed: ' + e.message, 'error');
|
|
3460
|
+
}
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3134
3463
|
// Filter tasks based on search query
|
|
3135
3464
|
function filterTasks(tasks) {
|
|
3136
3465
|
if (!state.searchQuery) return tasks;
|
|
@@ -3192,6 +3521,7 @@ function render() {
|
|
|
3192
3521
|
\${renderColumn('draft', 'To Do', filterTasks(state.tasks))}
|
|
3193
3522
|
\${renderColumn('ready', 'Ready', filterTasks(state.tasks))}
|
|
3194
3523
|
\${renderColumn('in_progress', 'In Progress', filterTasks(state.tasks))}
|
|
3524
|
+
\${renderColumn('in_review', 'In Review', filterTasks(state.tasks))}
|
|
3195
3525
|
\${renderColumn('completed', 'Done', filterTasks(state.tasks))}
|
|
3196
3526
|
\${renderColumn('failed', 'Failed', filterTasks(state.tasks))}
|
|
3197
3527
|
</div>
|
|
@@ -3256,6 +3586,14 @@ window.closeSidePanel = closeSidePanel;
|
|
|
3256
3586
|
window.showTaskMenu = showTaskMenu;
|
|
3257
3587
|
window.filterTasks = filterTasks;
|
|
3258
3588
|
window.scrollSidePanelLog = scrollSidePanelLog;
|
|
3589
|
+
window.mergeTask = mergeTask;
|
|
3590
|
+
window.createPR = createPR;
|
|
3591
|
+
window.discardTask = discardTask;
|
|
3592
|
+
window.handleMerge = handleMerge;
|
|
3593
|
+
window.handleCreatePR = handleCreatePR;
|
|
3594
|
+
window.handleDiscard = handleDiscard;
|
|
3595
|
+
window.openInVSCode = openInVSCode;
|
|
3596
|
+
window.getReviewInfo = getReviewInfo;
|
|
3259
3597
|
|
|
3260
3598
|
// Keyboard shortcuts
|
|
3261
3599
|
document.addEventListener('keydown', (e) => {
|