ticket-to-pr 1.4.0 → 1.4.1

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/index.js CHANGED
@@ -52,6 +52,7 @@ function log(color, label, msg) {
52
52
  const reviewPrompt = readFileSync(join(PACKAGE_ROOT, 'prompts', 'review.md'), 'utf-8');
53
53
  const executePrompt = readFileSync(join(PACKAGE_ROOT, 'prompts', 'execute.md'), 'utf-8');
54
54
  const diffReviewPrompt = readFileSync(join(PACKAGE_ROOT, 'prompts', 'diff-review.md'), 'utf-8');
55
+ const retroPrompt = readFileSync(join(PACKAGE_ROOT, 'prompts', 'retro.md'), 'utf-8');
55
56
  // -- Agent Runner --
56
57
  async function runReviewAgent(ticket) {
57
58
  const projectDir = getProjectDir(ticket.project);
@@ -275,6 +276,108 @@ async function runDiffReviewAgent(worktreeDir, baseBranch, spec, description, af
275
276
  cost,
276
277
  };
277
278
  }
279
+ async function runRetroAgent(projectDir, worktreeDir, baseBranch, ticket, outcome) {
280
+ try {
281
+ // Get the diff (may be empty if agent failed before committing)
282
+ let diff = '';
283
+ try {
284
+ diff = execSync(`git diff origin/${shellEscape(baseBranch)}...HEAD`, {
285
+ cwd: worktreeDir, stdio: 'pipe', timeout: 15_000,
286
+ }).toString();
287
+ }
288
+ catch {
289
+ // No diff available
290
+ }
291
+ // Read existing learnings to avoid repeats
292
+ const existingLearnings = readLearnings(projectDir);
293
+ // Build the retro prompt
294
+ const parts = [
295
+ retroPrompt,
296
+ '',
297
+ `## Ticket: ${ticket.title}`,
298
+ '',
299
+ '**Description**:',
300
+ ticket.description,
301
+ '',
302
+ '**Spec**:',
303
+ ticket.spec ?? '(none)',
304
+ '',
305
+ `## Outcome: ${outcome.success ? 'SUCCESS' : 'FAILED'}`,
306
+ ];
307
+ if (!outcome.success && outcome.error) {
308
+ parts.push('', '**Error**:', outcome.error.slice(0, 1000));
309
+ }
310
+ if (outcome.diffReviewIssues && outcome.diffReviewIssues.length > 0) {
311
+ parts.push('', '**Diff Review Issues**:', ...outcome.diffReviewIssues.map(i => `- ${i}`));
312
+ }
313
+ if (outcome.buildFailed) {
314
+ parts.push('', '**Build**: FAILED');
315
+ }
316
+ if (diff) {
317
+ const truncatedDiff = diff.length > 50_000 ? diff.slice(0, 50_000) + '\n...(truncated)' : diff;
318
+ parts.push('', '## Diff', '```diff', truncatedDiff, '```');
319
+ }
320
+ else {
321
+ parts.push('', '## Diff', 'No diff available (agent may have failed before committing).');
322
+ }
323
+ if (existingLearnings) {
324
+ parts.push('', '## Existing Learnings (do not repeat these)', existingLearnings);
325
+ }
326
+ const prompt = parts.join('\n');
327
+ const messages = query({
328
+ prompt,
329
+ options: {
330
+ model: CONFIG.DIFF_REVIEW_MODEL, // Reuse Haiku config
331
+ cwd: projectDir,
332
+ tools: ['Read', 'Glob', 'Grep'],
333
+ allowedTools: ['Read', 'Glob', 'Grep'],
334
+ maxTurns: 5,
335
+ maxBudgetUsd: 0.25,
336
+ permissionMode: 'bypassPermissions',
337
+ allowDangerouslySkipPermissions: true,
338
+ settingSources: ['project'],
339
+ systemPrompt: { type: 'preset', preset: 'claude_code' },
340
+ stderr: () => { },
341
+ },
342
+ });
343
+ let output = '';
344
+ for await (const message of messages) {
345
+ if (message.type === 'assistant') {
346
+ const content = message.message?.content;
347
+ if (Array.isArray(content)) {
348
+ for (const block of content) {
349
+ if (block.type === 'text')
350
+ output = block.text;
351
+ }
352
+ }
353
+ else if (typeof content === 'string') {
354
+ output = content;
355
+ }
356
+ }
357
+ if (message.type === 'result') {
358
+ if ('result' in message && message.result) {
359
+ output = message.result;
360
+ }
361
+ }
362
+ }
363
+ // Only save if the retro produced something meaningful
364
+ const trimmed = output.trim();
365
+ if (trimmed && !trimmed.toLowerCase().includes('no new learnings')) {
366
+ const header = outcome.success
367
+ ? `**Retro (success): ${ticket.title}**`
368
+ : `**Retro (failed): ${ticket.title}**`;
369
+ appendLearning(projectDir, `${header}\n${trimmed}`);
370
+ log(DIM, 'RETRO', `Saved ${trimmed.split('\n').filter(l => l.startsWith('-') || l.startsWith('*')).length} learnings`);
371
+ }
372
+ else {
373
+ log(DIM, 'RETRO', 'No new learnings');
374
+ }
375
+ }
376
+ catch (e) {
377
+ // Retro is best-effort — never block the pipeline
378
+ log(DIM, 'RETRO', `Skipped: ${e instanceof Error ? e.message : e}`);
379
+ }
380
+ }
278
381
  async function runExecuteAgent(ticket) {
279
382
  const projectDir = getProjectDir(ticket.project);
280
383
  if (!projectDir) {
@@ -303,6 +406,7 @@ async function runExecuteAgent(ticket) {
303
406
  createWorktree(projectDir, branchName, worktreeDir, baseBranch);
304
407
  let cost = 0;
305
408
  let commitCount = 0;
409
+ let retroOutcome = { success: false };
306
410
  try {
307
411
  const promptParts = [
308
412
  executePrompt,
@@ -402,6 +506,7 @@ async function runExecuteAgent(ticket) {
402
506
  const diffReview = await runDiffReviewAgent(worktreeDir, baseBranch, ticket.spec ?? '', ticket.description, []);
403
507
  cost += diffReview.cost;
404
508
  if (!diffReview.result.approved) {
509
+ retroOutcome = { success: false, error: 'Diff review rejected the changes', diffReviewIssues: diffReview.result.issues };
405
510
  throw new Error(`Diff review failed:\n${diffReview.result.issues.map(i => ` - ${i}`).join('\n')}`);
406
511
  }
407
512
  log(GREEN, 'REVIEW', `Diff review passed: ${diffReview.result.summary}`);
@@ -484,11 +589,7 @@ async function runExecuteAgent(ticket) {
484
589
  `Cost: $${cost.toFixed(2)} | Duration: ${duration}s`,
485
590
  ].join('\n');
486
591
  await addComment(ticket.id, comment);
487
- appendLearning(projectDir, [
488
- `**Execute: ${ticket.title}**`,
489
- `Branch: ${branchName}, Commits: ${commitCount}`,
490
- `Cost: $${cost.toFixed(2)}`,
491
- ].join('\n'));
592
+ retroOutcome = { success: true };
492
593
  log(GREEN, 'EXECUTE', `Done: branch=${branchName} cost=$${cost.toFixed(2)}${prUrl ? ` pr=${prUrl}` : ''}`);
493
594
  }
494
595
  catch (error) {
@@ -502,13 +603,17 @@ async function runExecuteAgent(ticket) {
502
603
  `Cost: $${cost.toFixed(2)} | Duration: ${duration}s`,
503
604
  ].join('\n');
504
605
  await addComment(ticket.id, comment);
505
- appendLearning(projectDir, [
506
- `**Failed execute: ${ticket.title}**`,
507
- `Error: ${errMsg.slice(0, 200)}`,
508
- ].join('\n'));
606
+ retroOutcome = {
607
+ success: false,
608
+ error: errMsg,
609
+ buildFailed: errMsg.includes('Build validation failed'),
610
+ };
509
611
  throw error;
510
612
  }
511
613
  finally {
614
+ // Run retro before cleaning up worktree (so it can read the diff)
615
+ log(DIM, 'RETRO', 'Running post-execution retrospective...');
616
+ await runRetroAgent(projectDir, worktreeDir, baseBranch, ticket, retroOutcome);
512
617
  // Always clean up the worktree
513
618
  removeWorktree(projectDir, worktreeDir);
514
619
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticket-to-pr",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Drag a Notion ticket, get a pull request. AI-powered dev automation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,27 @@
1
+ You are conducting a retrospective on an AI agent's work on a codebase.
2
+
3
+ ## Your Task
4
+ Analyze what happened during this ticket execution and extract lessons that will help future agent runs on this same project. You are writing notes for a future AI agent, not a human.
5
+
6
+ ## What to Look For
7
+
8
+ ### On Success
9
+ - **Conventions discovered**: file naming, import patterns, export style, component structure, API response shapes, error handling patterns
10
+ - **What worked**: approaches or patterns that led to clean implementation
11
+ - **Codebase quirks**: path aliases, custom configs, non-obvious setup requirements, framework-specific patterns
12
+
13
+ ### On Failure
14
+ - **Root cause**: what specifically went wrong and why (not just the error message)
15
+ - **What to do differently**: concrete, actionable advice for next time
16
+ - **Codebase constraints**: things the agent didn't know about that caused the failure
17
+
18
+ ### Always
19
+ - **Capability assessment**: what types of changes are easy/hard in this project
20
+ - **Suggestions**: improvements to the project's CLAUDE.md or configuration that would help future runs
21
+
22
+ ## Output Rules
23
+ - Write 2-5 bullet points. Each must be a specific, actionable lesson.
24
+ - Start each bullet with a category tag: `[convention]`, `[mistake]`, `[capability]`, or `[suggestion]`
25
+ - Be specific to THIS project. "Use TypeScript" is useless. "This project uses strict TypeScript with no implicit any — always add explicit return types on exported functions" is useful.
26
+ - Don't repeat lessons that already exist in the project learnings.
27
+ - If nothing useful was learned (e.g., trivial change, obvious outcome), just write: `No new learnings.`