@masslessai/push-todo 3.8.1 → 3.8.2

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 (2) hide show
  1. package/lib/daemon.js +67 -8
  2. package/package.json +1 -1
package/lib/daemon.js CHANGED
@@ -901,7 +901,7 @@ async function markTaskAsCompleted(displayNumber, taskId, comment) {
901
901
  * Checks for existing branch commits and PRs to avoid redundant re-execution.
902
902
  * Returns true if the task was healed (status updated, no re-execution needed).
903
903
  */
904
- async function autoHealExistingWork(displayNumber, summary, projectPath) {
904
+ async function autoHealExistingWork(displayNumber, summary, projectPath, taskId) {
905
905
  const suffix = getWorktreeSuffix();
906
906
  const branch = `push-${displayNumber}-${suffix}`;
907
907
  const gitCwd = projectPath || process.cwd();
@@ -931,7 +931,7 @@ async function autoHealExistingWork(displayNumber, summary, projectPath) {
931
931
  let prState = null;
932
932
  try {
933
933
  const prResult = execSync(
934
- `gh pr list --head ${branch} --json url,state --jq '.[0]' 2>/dev/null`,
934
+ `gh pr list --head ${branch} --state all --json url,state --jq '.[0]' 2>/dev/null`,
935
935
  { cwd: gitCwd, timeout: 15000, stdio: ['ignore', 'pipe', 'pipe'] }
936
936
  ).toString().trim();
937
937
  if (prResult) {
@@ -950,25 +950,59 @@ async function autoHealExistingWork(displayNumber, summary, projectPath) {
950
950
  await updateTaskStatus(displayNumber, 'session_finished', {
951
951
  summary: executionSummary
952
952
  });
953
+
954
+ // Auto-complete since PR is already merged
955
+ let status = 'session_finished';
956
+ if (getAutoCompleteEnabled() && taskId) {
957
+ const comment = `Auto-healed: PR already merged. ${prUrl}`;
958
+ const completed = await markTaskAsCompleted(displayNumber, taskId, comment);
959
+ if (completed) {
960
+ log(`Task #${displayNumber}: auto-completed (PR was already merged)`);
961
+ status = 'completed';
962
+ }
963
+ }
964
+
953
965
  completedToday.push({
954
966
  displayNumber, summary,
955
967
  completedAt: new Date().toISOString(),
956
- duration: 0, status: 'session_finished', prUrl
968
+ duration: 0, status, prUrl
957
969
  });
958
970
  return true;
959
971
  }
960
972
 
961
973
  if (prUrl && prState === 'OPEN') {
962
- // PR is open — work is done, just needs review
963
- log(`Task #${displayNumber}: PR already open (${prUrl}), updating status`);
964
- const executionSummary = `Auto-healed: previous execution completed. PR pending review: ${prUrl}`;
974
+ log(`Task #${displayNumber}: PR already open (${prUrl}), attempting merge`);
975
+
976
+ // Try to merge the existing PR
977
+ let merged = false;
978
+ if (getAutoMergeEnabled()) {
979
+ // Clean up worktree first so gh pr merge --delete-branch can delete the local branch
980
+ cleanupWorktree(displayNumber, projectPath);
981
+ merged = mergePRForTask(displayNumber, prUrl, projectPath);
982
+ }
983
+
984
+ const executionSummary = merged
985
+ ? `Auto-healed: previous PR merged. ${prUrl}`
986
+ : `Auto-healed: previous execution completed. PR pending review: ${prUrl}`;
965
987
  await updateTaskStatus(displayNumber, 'session_finished', {
966
988
  summary: executionSummary
967
989
  });
990
+
991
+ // Auto-complete if merge succeeded
992
+ let status = 'session_finished';
993
+ if (getAutoCompleteEnabled() && merged && taskId) {
994
+ const comment = `Auto-healed and merged. ${prUrl}`;
995
+ const completed = await markTaskAsCompleted(displayNumber, taskId, comment);
996
+ if (completed) {
997
+ log(`Task #${displayNumber}: auto-completed after auto-heal merge`);
998
+ status = 'completed';
999
+ }
1000
+ }
1001
+
968
1002
  completedToday.push({
969
1003
  displayNumber, summary,
970
1004
  completedAt: new Date().toISOString(),
971
- duration: 0, status: 'session_finished', prUrl
1005
+ duration: 0, status, prUrl
972
1006
  });
973
1007
  return true;
974
1008
  }
@@ -1213,7 +1247,8 @@ async function executeTask(task) {
1213
1247
  }
1214
1248
 
1215
1249
  // Auto-heal: check if previous execution already completed work for this task
1216
- const healed = await autoHealExistingWork(displayNumber, summary, projectPath);
1250
+ const taskId = task.id || task.todo_id || '';
1251
+ const healed = await autoHealExistingWork(displayNumber, summary, projectPath, taskId);
1217
1252
  if (healed) {
1218
1253
  log(`Task #${displayNumber}: auto-healed from previous execution, skipping re-execution`);
1219
1254
  return null;
@@ -1423,6 +1458,30 @@ async function handleTaskCompletion(displayNumber, exitCode) {
1423
1458
  merged = mergePRForTask(displayNumber, prUrl, projectPath);
1424
1459
  }
1425
1460
 
1461
+ // Safety net: if no new PR was created (Claude found work already done),
1462
+ // check if a previous PR for this branch was already merged.
1463
+ // See: docs/20260211_auto_complete_failure_investigation.md (Fix D)
1464
+ if (!prUrl && !merged) {
1465
+ const suffix = getWorktreeSuffix();
1466
+ const branch = `push-${displayNumber}-${suffix}`;
1467
+ try {
1468
+ const prCheck = execFileSync('gh', [
1469
+ 'pr', 'list', '--head', branch, '--state', 'merged',
1470
+ '--json', 'url', '--jq', '.[0].url'
1471
+ ], {
1472
+ cwd: projectPath || process.cwd(),
1473
+ timeout: 15000,
1474
+ stdio: ['ignore', 'pipe', 'pipe']
1475
+ }).toString().trim();
1476
+ if (prCheck) {
1477
+ log(`Task #${displayNumber}: found previously merged PR: ${prCheck}`);
1478
+ merged = true;
1479
+ }
1480
+ } catch {
1481
+ // gh not available or no merged PR found
1482
+ }
1483
+ }
1484
+
1426
1485
  // Auto-complete task after successful merge (configurable, default ON)
1427
1486
  const taskId = info.taskId;
1428
1487
  if (getAutoCompleteEnabled() && merged && taskId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masslessai/push-todo",
3
- "version": "3.8.1",
3
+ "version": "3.8.2",
4
4
  "description": "Voice tasks from Push iOS app for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {