@damper/cli 0.9.8 → 0.9.10
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/services/claude.js
CHANGED
|
@@ -281,35 +281,48 @@ export async function postTaskFlow(options) {
|
|
|
281
281
|
}
|
|
282
282
|
else if (mergeAction === 'merge') {
|
|
283
283
|
// Merge directly to main — first merge main into feature to resolve conflicts
|
|
284
|
+
const { execa } = await import('execa');
|
|
285
|
+
// Fetch latest main
|
|
284
286
|
try {
|
|
285
|
-
const { execa } = await import('execa');
|
|
286
|
-
// Fetch latest main
|
|
287
287
|
await execa('git', ['fetch', 'origin', 'main'], { cwd, stdio: 'pipe' });
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
console.log(pc.red('Failed to fetch origin/main. Check your network connection.\n'));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
// Step 1: Merge origin/main INTO the feature branch (in worktree)
|
|
294
|
+
console.log(pc.dim('Merging main into feature branch...'));
|
|
295
|
+
let mainMergedIntoFeature = false;
|
|
296
|
+
try {
|
|
297
|
+
await execa('git', ['merge', 'origin/main', '--no-edit'], { cwd, stdio: 'pipe' });
|
|
298
|
+
mainMergedIntoFeature = true;
|
|
299
|
+
console.log(pc.green('✓ Main merged into feature branch (no conflicts)'));
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// Merge conflicts — abort and launch Claude to resolve
|
|
303
|
+
console.log(pc.yellow('Merge conflicts detected. Launching Claude to resolve...'));
|
|
304
|
+
try {
|
|
305
|
+
await execa('git', ['merge', '--abort'], { cwd, stdio: 'pipe' });
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// merge --abort can fail if repo is in a weird state, reset instead
|
|
309
|
+
console.log(pc.dim('Cleaning up merge state...'));
|
|
310
|
+
await execa('git', ['reset', '--merge'], { cwd, stdio: 'pipe' }).catch(() => { });
|
|
311
|
+
}
|
|
312
|
+
await launchClaudeForMerge({ cwd, apiKey });
|
|
313
|
+
// Verify merge was completed (origin/main should be ancestor of HEAD)
|
|
291
314
|
try {
|
|
292
|
-
await execa('git', ['merge', 'origin/main', '
|
|
315
|
+
await execa('git', ['merge-base', '--is-ancestor', 'origin/main', 'HEAD'], { cwd, stdio: 'pipe' });
|
|
293
316
|
mainMergedIntoFeature = true;
|
|
294
|
-
console.log(pc.green('✓
|
|
317
|
+
console.log(pc.green('✓ Claude resolved merge conflicts'));
|
|
295
318
|
}
|
|
296
319
|
catch {
|
|
297
|
-
|
|
298
|
-
console.log(pc.yellow('Merge conflicts detected. Launching Claude to resolve...'));
|
|
299
|
-
await execa('git', ['merge', '--abort'], { cwd, stdio: 'pipe' });
|
|
300
|
-
await launchClaudeForMerge({ cwd, apiKey });
|
|
301
|
-
// Verify merge was completed (origin/main should be ancestor of HEAD)
|
|
302
|
-
try {
|
|
303
|
-
await execa('git', ['merge-base', '--is-ancestor', 'origin/main', 'HEAD'], { cwd, stdio: 'pipe' });
|
|
304
|
-
mainMergedIntoFeature = true;
|
|
305
|
-
console.log(pc.green('✓ Claude resolved merge conflicts'));
|
|
306
|
-
}
|
|
307
|
-
catch {
|
|
308
|
-
console.log(pc.red('Claude did not complete the merge. Skipping merge to main.\n'));
|
|
309
|
-
}
|
|
320
|
+
console.log(pc.red('Claude did not complete the merge. Skipping merge to main.\n'));
|
|
310
321
|
}
|
|
311
|
-
|
|
312
|
-
|
|
322
|
+
}
|
|
323
|
+
// Step 2: Merge feature → main (should be clean now)
|
|
324
|
+
if (mainMergedIntoFeature) {
|
|
325
|
+
try {
|
|
313
326
|
console.log(pc.dim('Merging feature into main...'));
|
|
314
327
|
await execa('git', ['checkout', 'main'], { cwd: projectRoot, stdio: 'pipe' });
|
|
315
328
|
await execa('git', ['merge', currentBranch, '--no-edit'], { cwd: projectRoot, stdio: 'inherit' });
|
|
@@ -326,9 +339,9 @@ export async function postTaskFlow(options) {
|
|
|
326
339
|
console.log(pc.dim('Skipped push. Run `git push origin main` when ready.\n'));
|
|
327
340
|
}
|
|
328
341
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
342
|
+
catch (err) {
|
|
343
|
+
console.log(pc.red('Failed to merge feature into main. You may need to resolve this manually.\n'));
|
|
344
|
+
}
|
|
332
345
|
}
|
|
333
346
|
}
|
|
334
347
|
else {
|
|
@@ -538,12 +551,14 @@ export async function launchClaudeForReview(options) {
|
|
|
538
551
|
}
|
|
539
552
|
/**
|
|
540
553
|
* Launch Claude to resolve merge conflicts
|
|
554
|
+
* Uses --allowedTools to restrict Claude to only git/file operations
|
|
555
|
+
* so it doesn't pick up task context and try to work on the task
|
|
541
556
|
*/
|
|
542
557
|
async function launchClaudeForMerge(options) {
|
|
543
558
|
const { cwd, apiKey } = options;
|
|
544
|
-
const prompt = '
|
|
559
|
+
const prompt = 'IMPORTANT: Your ONLY job is to resolve merge conflicts. Do NOT read TASK_CONTEXT.md or work on any task. Run: git merge origin/main --no-edit. If there are conflicts, resolve them, stage, and commit. Do not use any MCP tools.';
|
|
545
560
|
await new Promise((resolve) => {
|
|
546
|
-
const child = spawn('claude', [prompt], {
|
|
561
|
+
const child = spawn('claude', ['--allowedTools', 'Bash,Read,Write,Edit,Glob,Grep', prompt], {
|
|
547
562
|
cwd,
|
|
548
563
|
stdio: 'inherit',
|
|
549
564
|
env: { ...process.env, DAMPER_API_KEY: apiKey },
|
|
@@ -219,7 +219,7 @@ export declare class DamperApi {
|
|
|
219
219
|
title: string;
|
|
220
220
|
}>;
|
|
221
221
|
}>;
|
|
222
|
-
createTask(title: string, type?: 'bug' | 'feature' | 'improvement' | 'task', description?: string): Promise<Task>;
|
|
222
|
+
createTask(title: string, type?: 'bug' | 'feature' | 'improvement' | 'task', description?: string, isPublic?: boolean): Promise<Task>;
|
|
223
223
|
deleteTask(taskId: string): Promise<{
|
|
224
224
|
id: string;
|
|
225
225
|
deleted: boolean;
|
|
@@ -189,8 +189,8 @@ This project uses Damper MCP for task tracking. **You MUST follow this workflow.
|
|
|
189
189
|
return this.request('POST', `/api/agent/changelogs/${changelogId}/items`, { taskIds });
|
|
190
190
|
}
|
|
191
191
|
// Create task
|
|
192
|
-
async createTask(title, type = 'task', description) {
|
|
193
|
-
return this.request('POST', '/api/agent/tasks', { title, type, status: 'planned', description });
|
|
192
|
+
async createTask(title, type = 'task', description, isPublic) {
|
|
193
|
+
return this.request('POST', '/api/agent/tasks', { title, type, status: 'planned', description, isPublic });
|
|
194
194
|
}
|
|
195
195
|
// Delete task (only planned, no commits)
|
|
196
196
|
async deleteTask(taskId) {
|
|
@@ -27,7 +27,7 @@ ${planSection}
|
|
|
27
27
|
1. **Do NOT commit or complete tasks without explicit user confirmation** - Always ask the user before running \`git commit\` or calling \`complete_task\`
|
|
28
28
|
2. Use \`add_commit\` after each git commit
|
|
29
29
|
3. Use \`add_note\` ONLY for non-obvious approach decisions (e.g. "Decision: chose X because Y")
|
|
30
|
-
4. When user confirms: call \`complete_task\` with
|
|
30
|
+
4. When user confirms: call \`complete_task\` with summary and \`reviewInstructions\` (what to test/verify)
|
|
31
31
|
5. If stopping early: call \`abandon_task\` with what remains and blockers
|
|
32
32
|
|
|
33
33
|
The CLI just bootstrapped this environment - YOU handle the task lifecycle.
|
package/dist/ui/task-picker.js
CHANGED
|
@@ -174,11 +174,19 @@ export async function pickTask(options) {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
console.log(pc.bold(`\nProject: ${project}`));
|
|
177
|
+
// Track last search term for passing to create flow
|
|
178
|
+
let lastSearchTerm = '';
|
|
177
179
|
// Build choices with search filtering support
|
|
178
180
|
const buildChoices = (term) => {
|
|
179
181
|
const lowerTerm = term?.toLowerCase().trim() || '';
|
|
182
|
+
lastSearchTerm = term?.trim() || '';
|
|
180
183
|
const matches = (task) => !lowerTerm || task.title.toLowerCase().includes(lowerTerm) || task.id.includes(lowerTerm);
|
|
181
184
|
const filtered = [];
|
|
185
|
+
// Always show "Create new task" at the top
|
|
186
|
+
const createLabel = lastSearchTerm
|
|
187
|
+
? pc.green(`+ New task: "${lastSearchTerm}"`)
|
|
188
|
+
: pc.green('+ Create new task');
|
|
189
|
+
filtered.push({ name: createLabel, value: { type: 'create_new' } });
|
|
182
190
|
const filteredInProgress = inProgressChoices.filter(c => matches(c.task));
|
|
183
191
|
if (filteredInProgress.length > 0) {
|
|
184
192
|
filtered.push(new Separator(`\n${sectionHeader(`In Progress (${filteredInProgress.length})`)}`));
|
|
@@ -200,9 +208,6 @@ export async function pickTask(options) {
|
|
|
200
208
|
filtered.push({ name: formatTaskChoice(choice, titleWidth, layout), value: choice });
|
|
201
209
|
}
|
|
202
210
|
}
|
|
203
|
-
// Always show "Create new task"
|
|
204
|
-
filtered.push(new Separator(''));
|
|
205
|
-
filtered.push({ name: pc.green('+ Create new task'), value: { type: 'create_new' } });
|
|
206
211
|
return filtered;
|
|
207
212
|
};
|
|
208
213
|
const selected = await search({
|
|
@@ -211,7 +216,7 @@ export async function pickTask(options) {
|
|
|
211
216
|
pageSize: 20,
|
|
212
217
|
});
|
|
213
218
|
if (selected.type === 'create_new') {
|
|
214
|
-
return handleCreateNewTask(api);
|
|
219
|
+
return handleCreateNewTask(api, lastSearchTerm);
|
|
215
220
|
}
|
|
216
221
|
if (selected.type === 'in_progress') {
|
|
217
222
|
const action = await select({
|
|
@@ -260,41 +265,14 @@ export async function pickTask(options) {
|
|
|
260
265
|
action,
|
|
261
266
|
};
|
|
262
267
|
}
|
|
263
|
-
async function
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
'Rules:',
|
|
269
|
-
'- Max 60 characters, sentence case, no trailing period',
|
|
270
|
-
'- Verb prefix matching type: bug → "Fix …", feature → "Add …", improvement → "Improve …", task → "Set up …" / "Update …"',
|
|
271
|
-
'- Specific enough to understand without reading a description',
|
|
272
|
-
'- Examples: "Add dark mode support", "Fix login timeout on slow connections", "Improve search result ranking"',
|
|
273
|
-
'Return ONLY the title, nothing else.',
|
|
274
|
-
'',
|
|
275
|
-
`User input: ${instructions}`,
|
|
276
|
-
].join('\n');
|
|
277
|
-
const { stdout } = await execa('claude', ['--print', '--model', 'haiku', prompt], {
|
|
278
|
-
stdio: 'pipe',
|
|
279
|
-
timeout: 15000,
|
|
280
|
-
});
|
|
281
|
-
const title = stdout.trim();
|
|
282
|
-
if (title && title.length <= 80 && !title.includes('\n')) {
|
|
283
|
-
return title;
|
|
284
|
-
}
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
|
-
catch {
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
async function handleCreateNewTask(api) {
|
|
292
|
-
const instructions = await input({
|
|
293
|
-
message: 'What needs to be done?',
|
|
294
|
-
validate: (value) => value.trim().length > 0 || 'Instructions are required',
|
|
268
|
+
async function handleCreateNewTask(api, searchTerm) {
|
|
269
|
+
const title = await input({
|
|
270
|
+
message: 'Task title:',
|
|
271
|
+
default: searchTerm || undefined,
|
|
272
|
+
validate: (value) => value.trim().length > 0 || 'Title is required',
|
|
295
273
|
});
|
|
296
|
-
const
|
|
297
|
-
message: '
|
|
274
|
+
const description = await input({
|
|
275
|
+
message: 'Description (optional, press Enter to skip):',
|
|
298
276
|
});
|
|
299
277
|
const type = await select({
|
|
300
278
|
message: 'Task type:',
|
|
@@ -305,24 +283,17 @@ async function handleCreateNewTask(api) {
|
|
|
305
283
|
{ name: 'Task', value: 'task' },
|
|
306
284
|
],
|
|
307
285
|
});
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
else {
|
|
319
|
-
const maxTitleLen = 60;
|
|
320
|
-
title = trimmed.length <= maxTitleLen
|
|
321
|
-
? trimmed
|
|
322
|
-
: trimmed.slice(0, trimmed.lastIndexOf(' ', maxTitleLen) || maxTitleLen) + '…';
|
|
323
|
-
}
|
|
286
|
+
const isPublic = await select({
|
|
287
|
+
message: 'Visibility:',
|
|
288
|
+
choices: [
|
|
289
|
+
{ name: 'Public', value: true },
|
|
290
|
+
{ name: 'Private', value: false },
|
|
291
|
+
],
|
|
292
|
+
});
|
|
293
|
+
const trimmedTitle = title.trim();
|
|
294
|
+
const trimmedDescription = description.trim() || undefined;
|
|
324
295
|
console.log(pc.dim('Creating task in Damper...'));
|
|
325
|
-
const task = await api.createTask(
|
|
296
|
+
const task = await api.createTask(trimmedTitle, type, trimmedDescription, isPublic);
|
|
326
297
|
console.log(pc.green(`✓ Created task #${shortIdRaw(task.id)}: ${task.title}`));
|
|
327
298
|
return {
|
|
328
299
|
task,
|