@masslessai/push-todo 3.7.4 → 3.7.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/lib/config.js +88 -6
- package/lib/daemon.js +124 -1
- package/package.json +1 -1
package/lib/config.js
CHANGED
|
@@ -150,6 +150,48 @@ export function setAutoCommitEnabled(enabled) {
|
|
|
150
150
|
return setConfigValue('AUTO_COMMIT', enabled ? 'true' : 'false');
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Check if auto-merge into main is enabled after daemon task completion.
|
|
155
|
+
* Default: true (merge PR into main after session_finished)
|
|
156
|
+
*
|
|
157
|
+
* @returns {boolean}
|
|
158
|
+
*/
|
|
159
|
+
export function getAutoMergeEnabled() {
|
|
160
|
+
const value = getConfigValue('AUTO_MERGE', 'true');
|
|
161
|
+
return value.toLowerCase() === 'true' || value === '1' || value.toLowerCase() === 'yes';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Set auto-merge setting.
|
|
166
|
+
*
|
|
167
|
+
* @param {boolean} enabled
|
|
168
|
+
* @returns {boolean} True if successful
|
|
169
|
+
*/
|
|
170
|
+
export function setAutoMergeEnabled(enabled) {
|
|
171
|
+
return setConfigValue('AUTO_MERGE', enabled ? 'true' : 'false');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Check if auto-complete is enabled after daemon task merge.
|
|
176
|
+
* Default: true (mark task as completed after successful merge)
|
|
177
|
+
*
|
|
178
|
+
* @returns {boolean}
|
|
179
|
+
*/
|
|
180
|
+
export function getAutoCompleteEnabled() {
|
|
181
|
+
const value = getConfigValue('AUTO_COMPLETE', 'true');
|
|
182
|
+
return value.toLowerCase() === 'true' || value === '1' || value.toLowerCase() === 'yes';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Set auto-complete setting.
|
|
187
|
+
*
|
|
188
|
+
* @param {boolean} enabled
|
|
189
|
+
* @returns {boolean} True if successful
|
|
190
|
+
*/
|
|
191
|
+
export function setAutoCompleteEnabled(enabled) {
|
|
192
|
+
return setConfigValue('AUTO_COMPLETE', enabled ? 'true' : 'false');
|
|
193
|
+
}
|
|
194
|
+
|
|
153
195
|
/**
|
|
154
196
|
* Get the maximum batch size for queuing tasks.
|
|
155
197
|
* Default: 5
|
|
@@ -226,16 +268,24 @@ export function showSettings() {
|
|
|
226
268
|
console.log();
|
|
227
269
|
|
|
228
270
|
const autoCommit = getAutoCommitEnabled();
|
|
271
|
+
const autoMerge = getAutoMergeEnabled();
|
|
272
|
+
const autoComplete = getAutoCompleteEnabled();
|
|
229
273
|
const batchSize = getMaxBatchSize();
|
|
230
274
|
|
|
231
|
-
console.log(` auto-commit:
|
|
232
|
-
console.log('
|
|
275
|
+
console.log(` auto-commit: ${autoCommit ? 'ON' : 'OFF'}`);
|
|
276
|
+
console.log(' Auto-commit when task completes');
|
|
277
|
+
console.log();
|
|
278
|
+
console.log(` auto-merge: ${autoMerge ? 'ON' : 'OFF'}`);
|
|
279
|
+
console.log(' Merge PR into main after daemon task finishes');
|
|
233
280
|
console.log();
|
|
234
|
-
console.log(`
|
|
235
|
-
console.log('
|
|
281
|
+
console.log(` auto-complete: ${autoComplete ? 'ON' : 'OFF'}`);
|
|
282
|
+
console.log(' Mark task completed after successful merge');
|
|
283
|
+
console.log();
|
|
284
|
+
console.log(` batch-size: ${batchSize}`);
|
|
285
|
+
console.log(' Max tasks for batch queue (1-20)');
|
|
236
286
|
console.log();
|
|
237
287
|
console.log(' Toggle with: push-todo setting <name>');
|
|
238
|
-
console.log(' Example: push-todo setting auto-
|
|
288
|
+
console.log(' Example: push-todo setting auto-merge');
|
|
239
289
|
console.log();
|
|
240
290
|
}
|
|
241
291
|
|
|
@@ -264,6 +314,38 @@ export function toggleSetting(settingName) {
|
|
|
264
314
|
return false;
|
|
265
315
|
}
|
|
266
316
|
|
|
317
|
+
if (normalized === 'auto-merge') {
|
|
318
|
+
const current = getAutoMergeEnabled();
|
|
319
|
+
const newValue = !current;
|
|
320
|
+
if (setAutoMergeEnabled(newValue)) {
|
|
321
|
+
console.log(`Auto-merge is now ${newValue ? 'ON' : 'OFF'}`);
|
|
322
|
+
if (newValue) {
|
|
323
|
+
console.log('PRs will be merged into main after daemon task finishes.');
|
|
324
|
+
} else {
|
|
325
|
+
console.log('PRs will NOT be merged automatically.');
|
|
326
|
+
}
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
console.error('Failed to update setting');
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (normalized === 'auto-complete') {
|
|
334
|
+
const current = getAutoCompleteEnabled();
|
|
335
|
+
const newValue = !current;
|
|
336
|
+
if (setAutoCompleteEnabled(newValue)) {
|
|
337
|
+
console.log(`Auto-complete is now ${newValue ? 'ON' : 'OFF'}`);
|
|
338
|
+
if (newValue) {
|
|
339
|
+
console.log('Tasks will be marked completed after successful merge.');
|
|
340
|
+
} else {
|
|
341
|
+
console.log('Tasks will NOT be marked completed automatically.');
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
console.error('Failed to update setting');
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
267
349
|
if (normalized === 'batch-size') {
|
|
268
350
|
const batchSize = getMaxBatchSize();
|
|
269
351
|
console.log(`Current batch size: ${batchSize}`);
|
|
@@ -272,7 +354,7 @@ export function toggleSetting(settingName) {
|
|
|
272
354
|
}
|
|
273
355
|
|
|
274
356
|
console.error(`Unknown setting: ${settingName}`);
|
|
275
|
-
console.error('Available settings: auto-commit, batch-size');
|
|
357
|
+
console.error('Available settings: auto-commit, auto-merge, auto-complete, batch-size');
|
|
276
358
|
return false;
|
|
277
359
|
}
|
|
278
360
|
|
package/lib/daemon.js
CHANGED
|
@@ -199,6 +199,37 @@ function getVersion() {
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
function getConfigValueFromFile(key, defaultValue = '') {
|
|
203
|
+
const fullKey = `PUSH_${key}`;
|
|
204
|
+
if (!existsSync(CONFIG_FILE)) return defaultValue;
|
|
205
|
+
try {
|
|
206
|
+
const content = readFileSync(CONFIG_FILE, 'utf8');
|
|
207
|
+
for (const line of content.split('\n')) {
|
|
208
|
+
const trimmed = line.trim();
|
|
209
|
+
if (trimmed.startsWith(`export ${fullKey}=`)) {
|
|
210
|
+
let value = trimmed.split('=')[1] || '';
|
|
211
|
+
value = value.trim();
|
|
212
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
213
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
214
|
+
value = value.slice(1, -1);
|
|
215
|
+
}
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch {}
|
|
220
|
+
return defaultValue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function getAutoMergeEnabled() {
|
|
224
|
+
const v = getConfigValueFromFile('AUTO_MERGE', 'true');
|
|
225
|
+
return v.toLowerCase() === 'true' || v === '1' || v.toLowerCase() === 'yes';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getAutoCompleteEnabled() {
|
|
229
|
+
const v = getConfigValueFromFile('AUTO_COMPLETE', 'true');
|
|
230
|
+
return v.toLowerCase() === 'true' || v === '1' || v.toLowerCase() === 'yes';
|
|
231
|
+
}
|
|
232
|
+
|
|
202
233
|
// ==================== E2EE Decryption ====================
|
|
203
234
|
|
|
204
235
|
let decryptTodoField = null;
|
|
@@ -596,6 +627,83 @@ Automated PR from Push daemon for task #${displayNumber}.
|
|
|
596
627
|
}
|
|
597
628
|
}
|
|
598
629
|
|
|
630
|
+
/**
|
|
631
|
+
* Merge a PR into main and update local main branch.
|
|
632
|
+
* Uses `gh pr merge` for clean remote merge, then pulls locally.
|
|
633
|
+
*
|
|
634
|
+
* @returns {boolean} True if merge succeeded
|
|
635
|
+
*/
|
|
636
|
+
function mergePRForTask(displayNumber, prUrl, projectPath) {
|
|
637
|
+
const gitCwd = projectPath || process.cwd();
|
|
638
|
+
|
|
639
|
+
// Extract PR number from URL (e.g., https://github.com/user/repo/pull/42)
|
|
640
|
+
const prMatch = prUrl.match(/\/pull\/(\d+)/);
|
|
641
|
+
if (!prMatch) {
|
|
642
|
+
logError(`Could not extract PR number from: ${prUrl}`);
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
const prNumber = prMatch[1];
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
execFileSync('gh', ['pr', 'merge', prNumber, '--merge', '--delete-branch'], {
|
|
649
|
+
cwd: gitCwd,
|
|
650
|
+
timeout: 60000,
|
|
651
|
+
stdio: 'pipe'
|
|
652
|
+
});
|
|
653
|
+
log(`Merged PR #${prNumber} for task #${displayNumber}`);
|
|
654
|
+
|
|
655
|
+
// Pull main locally so next worktree is up to date
|
|
656
|
+
try {
|
|
657
|
+
execFileSync('git', ['pull', '--ff-only'], {
|
|
658
|
+
cwd: gitCwd,
|
|
659
|
+
timeout: 30000,
|
|
660
|
+
stdio: 'pipe'
|
|
661
|
+
});
|
|
662
|
+
log('Updated local main branch');
|
|
663
|
+
} catch {
|
|
664
|
+
log('Could not pull main (may not be checked out), skipping local update');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return true;
|
|
668
|
+
} catch (e) {
|
|
669
|
+
const stderr = e.stderr?.toString() || e.message || '';
|
|
670
|
+
if (stderr.includes('merge conflict') || stderr.includes('conflict')) {
|
|
671
|
+
logError(`PR #${prNumber} has merge conflicts, skipping auto-merge`);
|
|
672
|
+
} else if (stderr.includes('not found') || stderr.includes('ENOENT')) {
|
|
673
|
+
log('GitHub CLI (gh) not installed, skipping merge');
|
|
674
|
+
} else {
|
|
675
|
+
logError(`Failed to merge PR #${prNumber}: ${stderr.slice(0, 200)}`);
|
|
676
|
+
}
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Mark a task as completed via the todo-status endpoint.
|
|
683
|
+
*/
|
|
684
|
+
async function markTaskAsCompleted(displayNumber, taskId, comment) {
|
|
685
|
+
try {
|
|
686
|
+
const response = await apiRequest('todo-status', {
|
|
687
|
+
method: 'PATCH',
|
|
688
|
+
body: JSON.stringify({
|
|
689
|
+
todoId: taskId,
|
|
690
|
+
isCompleted: true,
|
|
691
|
+
completedAt: new Date().toISOString(),
|
|
692
|
+
completionComment: comment || `Completed by daemon on ${getMachineName()}`
|
|
693
|
+
})
|
|
694
|
+
});
|
|
695
|
+
if (response.ok) {
|
|
696
|
+
log(`Task #${displayNumber} marked as completed`);
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
logError(`Failed to mark #${displayNumber} complete: HTTP ${response.status}`);
|
|
700
|
+
return false;
|
|
701
|
+
} catch (error) {
|
|
702
|
+
logError(`Failed to mark #${displayNumber} complete: ${error.message}`);
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
599
707
|
// ==================== Stuck Detection ====================
|
|
600
708
|
|
|
601
709
|
function checkStuckPatterns(displayNumber, line) {
|
|
@@ -1000,12 +1108,27 @@ function handleTaskCompletion(displayNumber, exitCode) {
|
|
|
1000
1108
|
);
|
|
1001
1109
|
}
|
|
1002
1110
|
|
|
1111
|
+
// Auto-merge PR into main (configurable, default ON)
|
|
1112
|
+
let merged = false;
|
|
1113
|
+
if (getAutoMergeEnabled() && prUrl) {
|
|
1114
|
+
merged = mergePRForTask(displayNumber, prUrl, projectPath);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// Auto-complete task after successful merge (configurable, default ON)
|
|
1118
|
+
const taskId = info.taskId;
|
|
1119
|
+
if (getAutoCompleteEnabled() && merged && taskId) {
|
|
1120
|
+
const comment = semanticSummary
|
|
1121
|
+
? `${semanticSummary} (${durationStr} on ${machineName})`
|
|
1122
|
+
: `Completed in ${durationStr} on ${machineName}`;
|
|
1123
|
+
markTaskAsCompleted(displayNumber, taskId, comment);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1003
1126
|
completedToday.push({
|
|
1004
1127
|
displayNumber,
|
|
1005
1128
|
summary,
|
|
1006
1129
|
completedAt: new Date().toISOString(),
|
|
1007
1130
|
duration,
|
|
1008
|
-
status: 'session_finished',
|
|
1131
|
+
status: merged ? 'completed' : 'session_finished',
|
|
1009
1132
|
prUrl,
|
|
1010
1133
|
sessionId
|
|
1011
1134
|
});
|