@damper/cli 0.6.12 → 0.6.13
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/commands/start.js +3 -0
- package/dist/services/claude.d.ts +1 -0
- package/dist/services/claude.js +60 -6
- package/dist/services/damper-api.d.ts +5 -1
- package/dist/services/damper-api.js +6 -2
- package/dist/templates/CLAUDE_APPEND.md.js +5 -4
- package/dist/ui/format.d.ts +3 -1
- package/dist/ui/format.js +10 -5
- package/dist/ui/task-picker.d.ts +1 -0
- package/dist/ui/task-picker.js +74 -26
- package/package.json +1 -1
package/dist/commands/start.js
CHANGED
|
@@ -57,6 +57,7 @@ export async function startCommand(options) {
|
|
|
57
57
|
let taskId;
|
|
58
58
|
let taskTitle;
|
|
59
59
|
let isResume = false;
|
|
60
|
+
let isNewTask = false;
|
|
60
61
|
let worktreePath;
|
|
61
62
|
let forceTakeover = options.force || false;
|
|
62
63
|
if (options.taskId) {
|
|
@@ -89,6 +90,7 @@ export async function startCommand(options) {
|
|
|
89
90
|
taskId = result.task.id;
|
|
90
91
|
taskTitle = result.task.title;
|
|
91
92
|
isResume = result.isResume;
|
|
93
|
+
isNewTask = result.isNewTask || false;
|
|
92
94
|
forceTakeover = result.forceTakeover || options.force || false;
|
|
93
95
|
if (result.worktree) {
|
|
94
96
|
worktreePath = result.worktree.path;
|
|
@@ -167,5 +169,6 @@ export async function startCommand(options) {
|
|
|
167
169
|
taskId: result.taskId,
|
|
168
170
|
apiKey: result.apiKey,
|
|
169
171
|
projectRoot,
|
|
172
|
+
isNewTask,
|
|
170
173
|
});
|
|
171
174
|
}
|
package/dist/services/claude.js
CHANGED
|
@@ -139,16 +139,14 @@ export async function launchClaude(options) {
|
|
|
139
139
|
});
|
|
140
140
|
child.on('close', () => resolve());
|
|
141
141
|
});
|
|
142
|
-
|
|
143
|
-
console.log(pc.dim('\n─────────────────────────────────────────'));
|
|
144
|
-
console.log(pc.bold('\nClaude session ended.\n'));
|
|
142
|
+
console.log(pc.dim('\n─────────────────────────────────────────\n'));
|
|
145
143
|
return { cwd, taskId, apiKey };
|
|
146
144
|
}
|
|
147
145
|
/**
|
|
148
146
|
* Post-task actions after Claude exits
|
|
149
147
|
*/
|
|
150
148
|
export async function postTaskFlow(options) {
|
|
151
|
-
const { cwd, taskId, apiKey, projectRoot } = options;
|
|
149
|
+
const { cwd, taskId, apiKey, projectRoot, isNewTask } = options;
|
|
152
150
|
const { confirm, select } = await import('@inquirer/prompts');
|
|
153
151
|
const { createDamperApi } = await import('./damper-api.js');
|
|
154
152
|
const { removeWorktreeDir } = await import('./worktree.js');
|
|
@@ -156,15 +154,34 @@ export async function postTaskFlow(options) {
|
|
|
156
154
|
// Check task status from Damper
|
|
157
155
|
let taskStatus;
|
|
158
156
|
let taskTitle;
|
|
157
|
+
let hasCommits = false;
|
|
159
158
|
try {
|
|
160
159
|
const api = createDamperApi(apiKey);
|
|
161
160
|
const task = await api.getTask(taskId);
|
|
162
161
|
taskStatus = task.status;
|
|
163
162
|
taskTitle = task.title;
|
|
163
|
+
hasCommits = (task.commits?.length ?? 0) > 0;
|
|
164
164
|
}
|
|
165
165
|
catch {
|
|
166
166
|
console.log(pc.yellow('Could not fetch task status from Damper.'));
|
|
167
167
|
}
|
|
168
|
+
// Clean up CLI-generated modifications to tracked files before checking git status
|
|
169
|
+
// The CLI appends a "## Current Task:" section to CLAUDE.md which is never meant to be committed
|
|
170
|
+
try {
|
|
171
|
+
const claudeMdPath = path.join(cwd, 'CLAUDE.md');
|
|
172
|
+
if (fs.existsSync(claudeMdPath)) {
|
|
173
|
+
const content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
174
|
+
const marker = '## Current Task:';
|
|
175
|
+
const markerIndex = content.indexOf(marker);
|
|
176
|
+
if (markerIndex !== -1) {
|
|
177
|
+
const restored = content.slice(0, markerIndex).trimEnd() + '\n';
|
|
178
|
+
fs.writeFileSync(claudeMdPath, restored, 'utf-8');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Ignore cleanup errors
|
|
184
|
+
}
|
|
168
185
|
// Check git status
|
|
169
186
|
let hasUnpushedCommits = false;
|
|
170
187
|
let hasUncommittedChanges = false;
|
|
@@ -220,6 +237,7 @@ export async function postTaskFlow(options) {
|
|
|
220
237
|
console.log();
|
|
221
238
|
// Offer actions based on state
|
|
222
239
|
const hasChanges = hasUnpushedCommits || hasUncommittedChanges;
|
|
240
|
+
let worktreeRemoved = false;
|
|
223
241
|
if (hasChanges) {
|
|
224
242
|
if (hasUncommittedChanges) {
|
|
225
243
|
console.log(pc.yellow('⚠ There are uncommitted changes. You may want to commit them first.\n'));
|
|
@@ -286,6 +304,7 @@ export async function postTaskFlow(options) {
|
|
|
286
304
|
if (shouldCleanup) {
|
|
287
305
|
try {
|
|
288
306
|
await removeWorktreeDir(cwd, projectRoot);
|
|
307
|
+
worktreeRemoved = true;
|
|
289
308
|
console.log(pc.green('✓ Worktree and branch removed\n'));
|
|
290
309
|
}
|
|
291
310
|
catch (err) {
|
|
@@ -314,6 +333,7 @@ export async function postTaskFlow(options) {
|
|
|
314
333
|
if (shouldRemove) {
|
|
315
334
|
try {
|
|
316
335
|
await removeWorktreeDir(cwd, projectRoot);
|
|
336
|
+
worktreeRemoved = true;
|
|
317
337
|
console.log(pc.green('✓ Worktree and branch removed\n'));
|
|
318
338
|
}
|
|
319
339
|
catch (err) {
|
|
@@ -321,10 +341,44 @@ export async function postTaskFlow(options) {
|
|
|
321
341
|
console.log(pc.red(`Failed to remove worktree: ${error.message}\n`));
|
|
322
342
|
}
|
|
323
343
|
}
|
|
324
|
-
|
|
325
|
-
|
|
344
|
+
}
|
|
345
|
+
// Offer to delete CLI-created tasks with no work done
|
|
346
|
+
let taskDeleted = false;
|
|
347
|
+
if (isNewTask && !hasCommits && taskStatus !== 'done') {
|
|
348
|
+
const shouldDelete = await confirm({
|
|
349
|
+
message: 'No work was committed. Remove task from backlog?',
|
|
350
|
+
default: true,
|
|
351
|
+
});
|
|
352
|
+
if (shouldDelete) {
|
|
353
|
+
try {
|
|
354
|
+
const api = createDamperApi(apiKey);
|
|
355
|
+
await api.deleteTask(taskId);
|
|
356
|
+
taskDeleted = true;
|
|
357
|
+
console.log(pc.green('✓ Task removed from backlog\n'));
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
const error = err;
|
|
361
|
+
console.log(pc.yellow(`Could not delete task: ${error.message}\n`));
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Final summary
|
|
366
|
+
console.log(pc.dim('─────────────────────────────────────────'));
|
|
367
|
+
if (taskDeleted) {
|
|
368
|
+
console.log(pc.dim(`\n Task removed. No changes saved.\n`));
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
const statusColor = taskStatus === 'done' ? pc.green : taskStatus === 'in_progress' ? pc.yellow : pc.dim;
|
|
372
|
+
const statusLabel = taskStatus || 'unknown';
|
|
373
|
+
console.log(pc.bold(`\n #${shortIdRaw(taskId)} ${taskTitle || 'Unknown task'}`) + ` ${statusColor(statusLabel)}`);
|
|
374
|
+
if (!worktreeRemoved) {
|
|
375
|
+
console.log(pc.dim(` ${cwd}`));
|
|
376
|
+
console.log(`\n To continue, run:`);
|
|
377
|
+
console.log(pc.cyan(` cd ${cwd} && claude -c`));
|
|
378
|
+
console.log(pc.dim(` or: npx @damper/cli --task ${taskId}`));
|
|
326
379
|
}
|
|
327
380
|
}
|
|
381
|
+
console.log();
|
|
328
382
|
}
|
|
329
383
|
/**
|
|
330
384
|
* Check if Claude Code CLI is installed
|
|
@@ -170,7 +170,11 @@ export declare class DamperApi {
|
|
|
170
170
|
}>;
|
|
171
171
|
getModule(name: string): Promise<Module>;
|
|
172
172
|
getAgentInstructions(format?: 'markdown' | 'section'): Promise<AgentInstructions>;
|
|
173
|
-
createTask(title: string, type?: 'bug' | 'feature' | 'improvement' | 'task'): Promise<Task>;
|
|
173
|
+
createTask(title: string, type?: 'bug' | 'feature' | 'improvement' | 'task', description?: string): Promise<Task>;
|
|
174
|
+
deleteTask(taskId: string): Promise<{
|
|
175
|
+
id: string;
|
|
176
|
+
deleted: boolean;
|
|
177
|
+
}>;
|
|
174
178
|
getLinkedFeedback(taskId: string): Promise<Array<{
|
|
175
179
|
id: string;
|
|
176
180
|
title: string;
|
|
@@ -152,8 +152,12 @@ This project uses Damper MCP for task tracking. **You MUST follow this workflow.
|
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
154
|
// Create task
|
|
155
|
-
async createTask(title, type = 'task') {
|
|
156
|
-
return this.request('POST', '/api/agent/tasks', { title, type, status: 'planned' });
|
|
155
|
+
async createTask(title, type = 'task', description) {
|
|
156
|
+
return this.request('POST', '/api/agent/tasks', { title, type, status: 'planned', description });
|
|
157
|
+
}
|
|
158
|
+
// Delete task (only planned, no commits)
|
|
159
|
+
async deleteTask(taskId) {
|
|
160
|
+
return this.request('DELETE', `/api/agent/tasks/${taskId}`);
|
|
157
161
|
}
|
|
158
162
|
// Feedback
|
|
159
163
|
async getLinkedFeedback(taskId) {
|
|
@@ -24,10 +24,11 @@ ${planSection}
|
|
|
24
24
|
- \`.claude/settings.local.json\`
|
|
25
25
|
|
|
26
26
|
**Your responsibilities (via Damper MCP):**
|
|
27
|
-
1.
|
|
28
|
-
2. Use \`
|
|
29
|
-
3.
|
|
30
|
-
4.
|
|
27
|
+
1. After planning, use \`update_task\` to set a concise title if the current one looks like instructions/description
|
|
28
|
+
2. Use \`add_commit\` after each git commit
|
|
29
|
+
3. Use \`add_note\` ONLY for non-obvious approach decisions (e.g. "Decision: chose X because Y")
|
|
30
|
+
4. When done: call \`complete_task\` with a one-line summary
|
|
31
|
+
5. If stopping early: call \`abandon_task\` with what remains and blockers
|
|
31
32
|
|
|
32
33
|
The CLI just bootstrapped this environment - YOU handle the task lifecycle.
|
|
33
34
|
`.trim();
|
package/dist/ui/format.d.ts
CHANGED
|
@@ -22,7 +22,9 @@ export declare function formatSubtaskProgress(progress: {
|
|
|
22
22
|
export declare function relativeTime(date: string | Date | null | undefined): string;
|
|
23
23
|
/** Returns a picocolors function for the given status */
|
|
24
24
|
export declare function statusColor(status: string): (s: string) => string;
|
|
25
|
-
/**
|
|
25
|
+
/** Terminal width (min 80) */
|
|
26
|
+
export declare function getTerminalWidth(): number;
|
|
27
|
+
/** Section header spanning terminal width: `── Label ───────────` */
|
|
26
28
|
export declare function sectionHeader(label: string, width?: number): string;
|
|
27
29
|
/** Compact one-liner: `🔴 ✨ #a1b2c3d4 Title ▪ L` */
|
|
28
30
|
export declare function formatTaskLine(task: Task): string;
|
package/dist/ui/format.js
CHANGED
|
@@ -93,11 +93,16 @@ export function statusColor(status) {
|
|
|
93
93
|
default: return pc.dim;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
/**
|
|
97
|
-
export function
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
/** Terminal width (min 80) */
|
|
97
|
+
export function getTerminalWidth() {
|
|
98
|
+
return Math.max(80, process.stdout.columns || 80);
|
|
99
|
+
}
|
|
100
|
+
/** Section header spanning terminal width: `── Label ───────────` */
|
|
101
|
+
export function sectionHeader(label, width) {
|
|
102
|
+
const w = width ?? getTerminalWidth();
|
|
103
|
+
const prefix = `── ${label} `;
|
|
104
|
+
const remaining = Math.max(0, w - prefix.length);
|
|
105
|
+
return pc.dim(prefix + '─'.repeat(remaining));
|
|
101
106
|
}
|
|
102
107
|
/** Compact one-liner: `🔴 ✨ #a1b2c3d4 Title ▪ L` */
|
|
103
108
|
export function formatTaskLine(task) {
|
package/dist/ui/task-picker.d.ts
CHANGED
package/dist/ui/task-picker.js
CHANGED
|
@@ -1,38 +1,81 @@
|
|
|
1
1
|
import { select, confirm, input, Separator } from '@inquirer/prompts';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
-
import { shortId, shortIdRaw, getTypeIcon, getPriorityIcon, formatEffort,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
import { shortId, shortIdRaw, getTypeIcon, getPriorityIcon, formatEffort, formatSubtaskProgress, sectionHeader, relativeTime, getTerminalWidth, } from './format.js';
|
|
4
|
+
// Layout constants (terminal column widths)
|
|
5
|
+
const CURSOR_WIDTH = 2; // inquirer select prefix (❯ or )
|
|
6
|
+
const PRIORITY_WIDTH = 3; // emoji(2) + space(1), or 3 spaces
|
|
7
|
+
const TYPE_WIDTH = 2; // emoji(2)
|
|
8
|
+
const GAP1 = 1; // space after type
|
|
9
|
+
const ID_WIDTH = 9; // # + 8 chars
|
|
10
|
+
const GAP2 = 2; // spaces after ID
|
|
11
|
+
const GAP3 = 2; // spaces between title and meta
|
|
12
|
+
const META_RESERVE = 16; // max width for right-side meta
|
|
13
|
+
const MIN_TITLE = 20; // minimum title column width
|
|
14
|
+
const LEFT_FIXED = CURSOR_WIDTH + PRIORITY_WIDTH + TYPE_WIDTH + GAP1 + ID_WIDTH + GAP2; // 19
|
|
15
|
+
const INDENT_WIDTH = PRIORITY_WIDTH + TYPE_WIDTH + GAP1 + ID_WIDTH + GAP2; // 17 (without cursor)
|
|
16
|
+
function getTitleWidth() {
|
|
17
|
+
const termWidth = getTerminalWidth();
|
|
18
|
+
return Math.max(MIN_TITLE, termWidth - LEFT_FIXED - GAP3 - META_RESERVE);
|
|
19
|
+
}
|
|
20
|
+
function formatTableRow(task, titleWidth) {
|
|
21
|
+
// Priority: always 3 terminal cols
|
|
22
|
+
const hasPriority = task.priority === 'high' || task.priority === 'medium';
|
|
23
|
+
const priorityStr = hasPriority ? getPriorityIcon(task.priority) : ' ';
|
|
24
|
+
// Type icon: 2 terminal cols
|
|
25
|
+
const typeStr = getTypeIcon(task.type);
|
|
26
|
+
// ID: 9 visible chars
|
|
27
|
+
const idStr = shortId(task.id);
|
|
28
|
+
// Title: padded or truncated to fixed width
|
|
29
|
+
const titleStr = task.title.length > titleWidth
|
|
30
|
+
? task.title.slice(0, titleWidth - 1) + '\u2026'
|
|
31
|
+
: task.title + ' '.repeat(titleWidth - task.title.length);
|
|
32
|
+
// Right-side meta (compact)
|
|
33
|
+
const metaParts = [];
|
|
34
|
+
if (task.effort)
|
|
35
|
+
metaParts.push(formatEffort(task.effort));
|
|
36
|
+
if (task.feedbackCount)
|
|
37
|
+
metaParts.push(pc.dim(`\u{1F4AC}${task.feedbackCount}`));
|
|
38
|
+
if (task.subtaskProgress?.total)
|
|
39
|
+
metaParts.push(formatSubtaskProgress(task.subtaskProgress));
|
|
40
|
+
const meta = metaParts.join(' ');
|
|
41
|
+
return `${priorityStr}${typeStr} ${idStr} ${titleStr} ${meta}`;
|
|
42
|
+
}
|
|
43
|
+
function formatTaskChoice(choice, titleWidth) {
|
|
44
|
+
const firstLine = formatTableRow(choice.task, titleWidth);
|
|
45
|
+
const indent = ' '.repeat(INDENT_WIDTH);
|
|
11
46
|
if (choice.type === 'in_progress') {
|
|
12
47
|
const worktreeName = choice.worktree.path.split('/').pop() || choice.worktree.path;
|
|
13
48
|
const time = relativeTime(choice.worktree.createdAt);
|
|
14
49
|
const metaParts = [worktreeName];
|
|
15
50
|
if (time)
|
|
16
51
|
metaParts.push(time);
|
|
17
|
-
let description = `${firstLine}\n
|
|
52
|
+
let description = `${firstLine}\n${indent}${pc.dim(metaParts.join(' \u00B7 '))}`;
|
|
18
53
|
if (choice.lastNote) {
|
|
19
|
-
const
|
|
20
|
-
|
|
54
|
+
const maxNoteWidth = titleWidth;
|
|
55
|
+
const note = choice.lastNote.length > maxNoteWidth ? choice.lastNote.slice(0, maxNoteWidth - 3) + '...' : choice.lastNote;
|
|
56
|
+
description += `\n${indent}${pc.dim(`Last: "${note}"`)}`;
|
|
21
57
|
}
|
|
22
58
|
return description;
|
|
23
59
|
}
|
|
24
60
|
if (choice.type === 'locked') {
|
|
25
|
-
return `${firstLine}\n
|
|
61
|
+
return `${firstLine}\n${indent}${pc.yellow(`locked by ${choice.lockedBy}`)}`;
|
|
26
62
|
}
|
|
27
|
-
// Available task
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
|
|
63
|
+
// Available task - add secondary meta line if there's useful info
|
|
64
|
+
const secondaryParts = [];
|
|
65
|
+
if (choice.task.quarter)
|
|
66
|
+
secondaryParts.push(choice.task.quarter);
|
|
67
|
+
if (choice.task.hasImplementationPlan)
|
|
68
|
+
secondaryParts.push('has plan');
|
|
69
|
+
if (choice.task.labels?.length)
|
|
70
|
+
secondaryParts.push(choice.task.labels.join(' \u00B7 '));
|
|
71
|
+
if (secondaryParts.length > 0) {
|
|
72
|
+
return `${firstLine}\n${indent}${pc.dim(secondaryParts.join(` \u00B7 `))}`;
|
|
31
73
|
}
|
|
32
74
|
return firstLine;
|
|
33
75
|
}
|
|
34
76
|
export async function pickTask(options) {
|
|
35
77
|
const { api, worktrees, typeFilter, statusFilter } = options;
|
|
78
|
+
const titleWidth = getTitleWidth();
|
|
36
79
|
// Fetch tasks from Damper
|
|
37
80
|
const { tasks, project } = await api.listTasks({
|
|
38
81
|
status: statusFilter || 'all',
|
|
@@ -50,8 +93,6 @@ export async function pickTask(options) {
|
|
|
50
93
|
const task = tasks.find(t => t.id === worktree.taskId);
|
|
51
94
|
if (task) {
|
|
52
95
|
worktreeTaskIds.add(task.id);
|
|
53
|
-
// Get last note for display (we'd need to fetch task detail for this)
|
|
54
|
-
// For now, we'll leave it undefined - the API doesn't include notes in list
|
|
55
96
|
inProgressChoices.push({
|
|
56
97
|
type: 'in_progress',
|
|
57
98
|
task,
|
|
@@ -67,7 +108,6 @@ export async function pickTask(options) {
|
|
|
67
108
|
continue;
|
|
68
109
|
const taskAny = task;
|
|
69
110
|
if (task.status === 'in_progress' && taskAny.lockedBy) {
|
|
70
|
-
// Task is locked by someone else
|
|
71
111
|
lockedChoices.push({
|
|
72
112
|
type: 'locked',
|
|
73
113
|
task,
|
|
@@ -83,7 +123,7 @@ export async function pickTask(options) {
|
|
|
83
123
|
choices.push(new Separator(`\n${sectionHeader(`In Progress (${inProgressChoices.length})`)}`));
|
|
84
124
|
for (const choice of inProgressChoices) {
|
|
85
125
|
choices.push({
|
|
86
|
-
name: formatTaskChoice(choice),
|
|
126
|
+
name: formatTaskChoice(choice, titleWidth),
|
|
87
127
|
value: choice,
|
|
88
128
|
});
|
|
89
129
|
}
|
|
@@ -92,7 +132,7 @@ export async function pickTask(options) {
|
|
|
92
132
|
choices.push(new Separator(`\n${sectionHeader(`Available (${availableChoices.length})`)}`));
|
|
93
133
|
for (const choice of availableChoices) {
|
|
94
134
|
choices.push({
|
|
95
|
-
name: formatTaskChoice(choice),
|
|
135
|
+
name: formatTaskChoice(choice, titleWidth),
|
|
96
136
|
value: choice,
|
|
97
137
|
});
|
|
98
138
|
}
|
|
@@ -101,7 +141,7 @@ export async function pickTask(options) {
|
|
|
101
141
|
choices.push(new Separator(`\n${sectionHeader(`Locked (${lockedChoices.length})`)}`));
|
|
102
142
|
for (const choice of lockedChoices) {
|
|
103
143
|
choices.push({
|
|
104
|
-
name: formatTaskChoice(choice),
|
|
144
|
+
name: formatTaskChoice(choice, titleWidth),
|
|
105
145
|
value: choice,
|
|
106
146
|
});
|
|
107
147
|
}
|
|
@@ -158,9 +198,9 @@ export async function pickTask(options) {
|
|
|
158
198
|
};
|
|
159
199
|
}
|
|
160
200
|
async function handleCreateNewTask(api) {
|
|
161
|
-
const
|
|
162
|
-
message: '
|
|
163
|
-
validate: (value) => value.trim().length > 0 || '
|
|
201
|
+
const instructions = await input({
|
|
202
|
+
message: 'What needs to be done?',
|
|
203
|
+
validate: (value) => value.trim().length > 0 || 'Instructions are required',
|
|
164
204
|
});
|
|
165
205
|
const type = await select({
|
|
166
206
|
message: 'Task type:',
|
|
@@ -171,11 +211,19 @@ async function handleCreateNewTask(api) {
|
|
|
171
211
|
{ name: 'Task', value: 'task' },
|
|
172
212
|
],
|
|
173
213
|
});
|
|
214
|
+
// Use instructions as description; derive a placeholder title
|
|
215
|
+
const trimmed = instructions.trim();
|
|
216
|
+
const maxTitleLen = 60;
|
|
217
|
+
const placeholderTitle = trimmed.length <= maxTitleLen
|
|
218
|
+
? trimmed
|
|
219
|
+
: trimmed.slice(0, trimmed.lastIndexOf(' ', maxTitleLen) || maxTitleLen) + '…';
|
|
174
220
|
console.log(pc.dim('\nCreating task in Damper...'));
|
|
175
|
-
const task = await api.createTask(
|
|
221
|
+
const task = await api.createTask(placeholderTitle, type, trimmed);
|
|
176
222
|
console.log(pc.green(`✓ Created task #${shortIdRaw(task.id)}: ${task.title}`));
|
|
223
|
+
console.log(pc.dim(' The agent will refine the title after planning.'));
|
|
177
224
|
return {
|
|
178
225
|
task,
|
|
179
226
|
isResume: false,
|
|
227
|
+
isNewTask: true,
|
|
180
228
|
};
|
|
181
229
|
}
|