@damper/cli 0.9.5 → 0.9.7

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.
@@ -23,10 +23,15 @@ export async function releaseCommand() {
23
23
  process.exit(1);
24
24
  }
25
25
  const api = createDamperApi(apiKey);
26
- // Get tasks that are in_progress (locked)
27
- const { tasks, project } = await api.listTasks({ status: 'in_progress' });
26
+ // Get tasks that are in_progress or in_review (locked)
27
+ const [ipResult, irResult] = await Promise.all([
28
+ api.listTasks({ status: 'in_progress' }),
29
+ api.listTasks({ status: 'in_review' }),
30
+ ]);
31
+ const tasks = [...ipResult.tasks, ...irResult.tasks];
32
+ const project = ipResult.project;
28
33
  if (tasks.length === 0) {
29
- console.log(pc.yellow('\nNo in-progress tasks to release.\n'));
34
+ console.log(pc.yellow('\nNo in-progress or in-review tasks to release.\n'));
30
35
  return;
31
36
  }
32
37
  console.log(pc.bold(`\nProject: ${project}`));
@@ -1,7 +1,7 @@
1
1
  export interface StartOptions {
2
2
  taskId?: string;
3
3
  type?: 'bug' | 'feature' | 'improvement' | 'task';
4
- status?: 'planned' | 'in_progress' | 'done' | 'all';
4
+ status?: 'planned' | 'in_progress' | 'in_review' | 'done' | 'all';
5
5
  yolo?: boolean;
6
6
  force?: boolean;
7
7
  }
@@ -243,7 +243,7 @@ export async function postTaskFlow(options) {
243
243
  }
244
244
  // Show status
245
245
  if (taskStatus) {
246
- const statusColor = taskStatus === 'done' ? pc.green : taskStatus === 'in_progress' ? pc.yellow : pc.dim;
246
+ const statusColor = taskStatus === 'done' ? pc.green : (taskStatus === 'in_progress' || taskStatus === 'in_review') ? pc.yellow : pc.dim;
247
247
  console.log(`Task status: ${statusColor(taskStatus)}`);
248
248
  }
249
249
  if (currentBranch) {
@@ -256,8 +256,8 @@ export async function postTaskFlow(options) {
256
256
  console.log(pc.yellow('⚠ Unpushed commits detected'));
257
257
  }
258
258
  console.log();
259
- // Offer to add to changelog if task is completed
260
- if (taskStatus === 'done') {
259
+ // Offer to add to changelog if task is completed (done or in_review)
260
+ if (taskStatus === 'done' || taskStatus === 'in_review') {
261
261
  await offerChangelogFlow({ taskId, taskTitle, apiKey, isPublic: isPublicTask, confirm, select });
262
262
  }
263
263
  // Offer actions based on state
@@ -352,9 +352,9 @@ export async function postTaskFlow(options) {
352
352
  }
353
353
  }
354
354
  }
355
- // Offer cleanup if task is done or abandoned
356
- if (taskStatus === 'done' || taskStatus === 'planned') {
357
- const statusText = taskStatus === 'done' ? 'completed' : 'not started/abandoned';
355
+ // Offer cleanup if task is done, in_review, or abandoned
356
+ if (taskStatus === 'done' || taskStatus === 'in_review' || taskStatus === 'planned') {
357
+ const statusText = taskStatus === 'done' ? 'completed' : taskStatus === 'in_review' ? 'in review' : 'not started/abandoned';
358
358
  const shouldCleanup = await confirm({
359
359
  message: `Task is ${statusText}. Remove worktree and branch?`,
360
360
  default: taskStatus === 'done',
@@ -435,7 +435,7 @@ export async function postTaskFlow(options) {
435
435
  console.log(pc.dim(`\n Task removed. No changes saved.\n`));
436
436
  }
437
437
  else {
438
- const statusColor = taskStatus === 'done' ? pc.green : taskStatus === 'in_progress' ? pc.yellow : pc.dim;
438
+ const statusColor = taskStatus === 'done' ? pc.green : (taskStatus === 'in_progress' || taskStatus === 'in_review') ? pc.yellow : pc.dim;
439
439
  const statusLabel = taskStatus || 'unknown';
440
440
  console.log(pc.bold(`\n #${shortIdRaw(taskId)} ${taskTitle || 'Unknown task'}`) + ` ${statusColor(statusLabel)}`);
441
441
  if (!worktreeRemoved) {
@@ -111,7 +111,7 @@ export declare class DamperApi {
111
111
  constructor(apiKey: string);
112
112
  private request;
113
113
  listTasks(filters?: {
114
- status?: 'planned' | 'in_progress' | 'done' | 'all';
114
+ status?: 'planned' | 'in_progress' | 'in_review' | 'done' | 'all';
115
115
  type?: 'bug' | 'feature' | 'improvement' | 'task';
116
116
  quarter?: string;
117
117
  sort?: 'importance' | 'newest' | 'votes';
@@ -123,7 +123,7 @@ export declare class DamperApi {
123
123
  total: number;
124
124
  }>;
125
125
  listAllTasks(filters?: {
126
- status?: 'planned' | 'in_progress' | 'done' | 'all';
126
+ status?: 'planned' | 'in_progress' | 'in_review' | 'done' | 'all';
127
127
  type?: 'bug' | 'feature' | 'improvement' | 'task';
128
128
  quarter?: string;
129
129
  sort?: 'importance' | 'newest' | 'votes';
@@ -4,7 +4,7 @@ interface TaskPickerOptions {
4
4
  api: DamperApi;
5
5
  worktrees: WorktreeState[];
6
6
  typeFilter?: 'bug' | 'feature' | 'improvement' | 'task';
7
- statusFilter?: 'planned' | 'in_progress' | 'done' | 'all';
7
+ statusFilter?: 'planned' | 'in_progress' | 'in_review' | 'done' | 'all';
8
8
  }
9
9
  interface TaskPickerResult {
10
10
  task: Task;
@@ -126,7 +126,7 @@ export async function pickTask(options) {
126
126
  });
127
127
  // Filter out completed tasks unless specifically requested
128
128
  const availableTasks = tasks.filter(t => statusFilter === 'done' || statusFilter === 'all' ||
129
- (t.status === 'planned' || t.status === 'in_progress'));
129
+ (t.status === 'planned' || t.status === 'in_progress' || t.status === 'in_review'));
130
130
  // Match worktrees with tasks
131
131
  const inProgressChoices = [];
132
132
  const worktreeTaskIds = new Set();
@@ -155,7 +155,7 @@ export async function pickTask(options) {
155
155
  lockedBy: taskAny.lockedBy,
156
156
  });
157
157
  }
158
- else if (task.status === 'planned' || task.status === 'in_progress') {
158
+ else if (task.status === 'planned' || task.status === 'in_progress' || task.status === 'in_review') {
159
159
  availableChoices.push({ type: 'available', task });
160
160
  }
161
161
  }
@@ -260,6 +260,34 @@ export async function pickTask(options) {
260
260
  action,
261
261
  };
262
262
  }
263
+ async function generateTaskTitle(instructions, type) {
264
+ try {
265
+ const { execa } = await import('execa');
266
+ const prompt = [
267
+ `Generate a task title for a ${type} task.`,
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
+ }
263
291
  async function handleCreateNewTask(api) {
264
292
  const instructions = await input({
265
293
  message: 'What needs to be done?',
@@ -274,16 +302,23 @@ async function handleCreateNewTask(api) {
274
302
  { name: 'Task', value: 'task' },
275
303
  ],
276
304
  });
277
- // Use instructions as description; derive a placeholder title
278
305
  const trimmed = instructions.trim();
279
- const maxTitleLen = 60;
280
- const placeholderTitle = trimmed.length <= maxTitleLen
281
- ? trimmed
282
- : trimmed.slice(0, trimmed.lastIndexOf(' ', maxTitleLen) || maxTitleLen) + '…';
283
- console.log(pc.dim('\nCreating task in Damper...'));
284
- const task = await api.createTask(placeholderTitle, type, trimmed);
306
+ // Generate title with Claude, fall back to truncation
307
+ console.log(pc.dim('\nGenerating title...'));
308
+ const generatedTitle = await generateTaskTitle(trimmed, type);
309
+ let title;
310
+ if (generatedTitle) {
311
+ title = generatedTitle;
312
+ }
313
+ else {
314
+ const maxTitleLen = 60;
315
+ title = trimmed.length <= maxTitleLen
316
+ ? trimmed
317
+ : trimmed.slice(0, trimmed.lastIndexOf(' ', maxTitleLen) || maxTitleLen) + '…';
318
+ }
319
+ console.log(pc.dim('Creating task in Damper...'));
320
+ const task = await api.createTask(title, type, trimmed);
285
321
  console.log(pc.green(`✓ Created task #${shortIdRaw(task.id)}: ${task.title}`));
286
- console.log(pc.dim(' The agent will refine the title after planning.'));
287
322
  return {
288
323
  task,
289
324
  isResume: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damper/cli",
3
- "version": "0.9.5",
3
+ "version": "0.9.7",
4
4
  "description": "CLI tool for orchestrating Damper task workflows with Claude Code",
5
5
  "author": "Damper <hello@usedamper.com>",
6
6
  "repository": {