edsger 0.41.2 → 0.42.0

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.
Files changed (139) hide show
  1. package/.claude/settings.local.json +3 -23
  2. package/dist/commands/pr-resolve/index.d.ts +1 -0
  3. package/dist/commands/pr-resolve/index.js +1 -0
  4. package/dist/commands/workflow/executors/phase-executor.js +1 -3
  5. package/dist/commands/workflow/phase-orchestrator.js +1 -3
  6. package/dist/index.js +3 -2
  7. package/dist/phases/app-store-generation/index.js +1 -3
  8. package/dist/phases/branch-planning/index.js +1 -3
  9. package/dist/phases/bug-fixing/analyzer.js +1 -3
  10. package/dist/phases/code-implementation/index.js +1 -3
  11. package/dist/phases/code-refine/index.js +1 -3
  12. package/dist/phases/code-review/index.js +1 -3
  13. package/dist/phases/code-testing/analyzer.js +1 -3
  14. package/dist/phases/feature-analysis/index.js +1 -3
  15. package/dist/phases/functional-testing/analyzer.js +1 -3
  16. package/dist/phases/growth-analysis/index.js +1 -3
  17. package/dist/phases/pr-execution/index.js +59 -1
  18. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +1 -0
  19. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +157 -0
  20. package/dist/phases/pr-resolve/__tests__/types.test.d.ts +1 -0
  21. package/dist/phases/pr-resolve/__tests__/types.test.js +43 -0
  22. package/dist/phases/pr-resolve/checklist-learner.d.ts +28 -0
  23. package/dist/phases/pr-resolve/checklist-learner.js +128 -0
  24. package/dist/phases/pr-resolve/index.d.ts +4 -0
  25. package/dist/phases/pr-resolve/index.js +23 -5
  26. package/dist/phases/pr-resolve/prompts.js +2 -1
  27. package/dist/phases/pr-resolve/types.d.ts +18 -0
  28. package/dist/phases/pr-resolve/types.js +14 -0
  29. package/dist/phases/pr-resolve/workspace.d.ts +1 -1
  30. package/dist/phases/pr-resolve/workspace.js +1 -1
  31. package/dist/phases/pr-review/__tests__/review-comments.test.js +4 -2
  32. package/dist/phases/pr-review/index.js +1 -3
  33. package/dist/phases/pr-shared/agent-utils.js +0 -1
  34. package/dist/phases/pr-shared/context.d.ts +1 -1
  35. package/dist/phases/pr-splitting/context.js +20 -15
  36. package/dist/phases/pr-splitting/index.js +1 -3
  37. package/dist/phases/technical-design/index.js +1 -3
  38. package/dist/phases/test-cases-analysis/index.js +1 -3
  39. package/dist/phases/user-stories-analysis/index.js +1 -3
  40. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
  41. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
  42. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
  43. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
  44. package/dist/services/lifecycle-agent/index.d.ts +24 -0
  45. package/dist/services/lifecycle-agent/index.js +25 -0
  46. package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
  47. package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
  48. package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
  49. package/dist/services/lifecycle-agent/transition-rules.js +184 -0
  50. package/dist/services/lifecycle-agent/types.d.ts +190 -0
  51. package/dist/services/lifecycle-agent/types.js +12 -0
  52. package/package.json +1 -1
  53. package/.env.local +0 -12
  54. package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
  55. package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
  56. package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
  57. package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
  58. package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
  59. package/dist/commands/workflow/pipeline-runner.js +0 -393
  60. package/dist/commands/workflow/runner.d.ts +0 -26
  61. package/dist/commands/workflow/runner.js +0 -119
  62. package/dist/commands/workflow/workflow-runner.d.ts +0 -26
  63. package/dist/commands/workflow/workflow-runner.js +0 -119
  64. package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
  65. package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
  66. package/dist/phases/code-implementation/analyzer.d.ts +0 -32
  67. package/dist/phases/code-implementation/analyzer.js +0 -629
  68. package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
  69. package/dist/phases/code-implementation/context-fetcher.js +0 -86
  70. package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
  71. package/dist/phases/code-implementation/mcp-server.js +0 -93
  72. package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
  73. package/dist/phases/code-implementation/prompts-improvement.js +0 -108
  74. package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
  75. package/dist/phases/code-implementation-verification/verifier.js +0 -196
  76. package/dist/phases/code-refine/analyzer.d.ts +0 -41
  77. package/dist/phases/code-refine/analyzer.js +0 -561
  78. package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
  79. package/dist/phases/code-refine/context-fetcher.js +0 -423
  80. package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
  81. package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
  82. package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
  83. package/dist/phases/code-refine-verification/verifier.js +0 -597
  84. package/dist/phases/code-review/analyzer.d.ts +0 -29
  85. package/dist/phases/code-review/analyzer.js +0 -363
  86. package/dist/phases/code-review/context-fetcher.d.ts +0 -92
  87. package/dist/phases/code-review/context-fetcher.js +0 -296
  88. package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
  89. package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
  90. package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
  91. package/dist/phases/feature-analysis/analyzer.js +0 -208
  92. package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
  93. package/dist/phases/feature-analysis/context-fetcher.js +0 -134
  94. package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
  95. package/dist/phases/feature-analysis/http-fallback.js +0 -95
  96. package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
  97. package/dist/phases/feature-analysis/mcp-server.js +0 -144
  98. package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
  99. package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
  100. package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
  101. package/dist/phases/feature-analysis-verification/verifier.js +0 -147
  102. package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
  103. package/dist/phases/technical-design/analyzer-helpers.js +0 -39
  104. package/dist/phases/technical-design/analyzer.d.ts +0 -21
  105. package/dist/phases/technical-design/analyzer.js +0 -461
  106. package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
  107. package/dist/phases/technical-design/context-fetcher.js +0 -39
  108. package/dist/phases/technical-design/http-fallback.d.ts +0 -17
  109. package/dist/phases/technical-design/http-fallback.js +0 -151
  110. package/dist/phases/technical-design/mcp-server.d.ts +0 -1
  111. package/dist/phases/technical-design/mcp-server.js +0 -157
  112. package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
  113. package/dist/phases/technical-design/prompts-improvement.js +0 -93
  114. package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
  115. package/dist/phases/technical-design-verification/verifier.js +0 -170
  116. package/dist/services/feature-branches.d.ts +0 -77
  117. package/dist/services/feature-branches.js +0 -205
  118. package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
  119. package/dist/workflow-runner/config/phase-configs.js +0 -120
  120. package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
  121. package/dist/workflow-runner/core/feature-filter.js +0 -46
  122. package/dist/workflow-runner/core/index.d.ts +0 -8
  123. package/dist/workflow-runner/core/index.js +0 -12
  124. package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
  125. package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
  126. package/dist/workflow-runner/core/state-manager.d.ts +0 -24
  127. package/dist/workflow-runner/core/state-manager.js +0 -42
  128. package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
  129. package/dist/workflow-runner/core/workflow-logger.js +0 -65
  130. package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
  131. package/dist/workflow-runner/executors/phase-executor.js +0 -248
  132. package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
  133. package/dist/workflow-runner/feature-workflow-runner.js +0 -119
  134. package/dist/workflow-runner/index.d.ts +0 -2
  135. package/dist/workflow-runner/index.js +0 -2
  136. package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
  137. package/dist/workflow-runner/pipeline-runner.js +0 -393
  138. package/dist/workflow-runner/workflow-processor.d.ts +0 -54
  139. package/dist/workflow-runner/workflow-processor.js +0 -170
@@ -1,28 +1,8 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Read(//Users/steven/development/edsger/**)",
5
- "Bash(npm run build)",
6
- "Bash(node:*)",
7
- "Bash(git add:*)",
8
- "Bash(git commit:*)",
9
- "Bash(ls:*)",
10
- "Bash(cat:*)",
11
- "Bash(npm run typecheck:*)",
12
- "Bash(git diff:*)",
13
- "WebSearch",
14
- "WebFetch(domain:supabase.com)",
15
- "Bash(npm install:*)",
16
- "Bash(grep:*)",
17
- "Bash(npx supabase gen types typescript --help:*)",
18
- "Bash(git -C /Users/steven/development/edsger status)",
19
- "Bash(git -C /Users/steven/development/edsger diff)",
20
- "Bash(git -C /Users/steven/development/edsger log --oneline -5)",
21
- "Bash(git -C /Users/steven/development/edsger add supabase/migrations/20251231000000_drop_unused_views.sql)",
22
- "Bash(git -C /Users/steven/development/edsger commit -m \"$\\(cat <<''EOF''\nchore: drop unused database views\n\nRemove test_report_summary and user_stories_with_context views that are defined but never used in the application.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
23
- "Bash(git -C /Users/steven/development/edsger commit -m \"$\\(cat <<''EOF''\nchore: drop unused database views\n\nRemove test_report_summary and user_stories_with_context views\nthat are defined but never used in the application.\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
24
- ],
25
- "deny": [],
26
- "ask": []
4
+ "Bash(npx tsc:*)",
5
+ "Bash(npm run:*)"
6
+ ]
27
7
  }
28
8
  }
@@ -6,5 +6,6 @@ export interface PRResolveCliOptions {
6
6
  prUrl: string;
7
7
  prId?: string;
8
8
  verbose?: boolean;
9
+ learn?: boolean;
9
10
  }
10
11
  export declare function runPRResolve(productId: string, options: PRResolveCliOptions): Promise<void>;
@@ -26,6 +26,7 @@ export async function runPRResolve(productId, options) {
26
26
  repo: githubConfig.repo,
27
27
  prId,
28
28
  verbose,
29
+ learn: options.learn,
29
30
  });
30
31
  if (result.status === 'success') {
31
32
  logSuccess(`PR resolve completed: ${result.message}`);
@@ -130,9 +130,7 @@ async function validateAndLogChecklists(options, name, checklistContext, feature
130
130
  }
131
131
  }
132
132
  // Higher-order function for phase execution
133
- export const createPhaseRunner = (phaseConfig) => async (options, config
134
- // eslint-disable-next-line complexity -- orchestration function with sequential phase setup, execution, and result processing
135
- ) => {
133
+ export const createPhaseRunner = (phaseConfig) => async (options, config) => {
136
134
  const { featureId, verbose } = options;
137
135
  const { name, execute } = phaseConfig;
138
136
  // Track phase duration for logging
@@ -36,9 +36,7 @@ const logAndMarkPhaseCompleted = async (result, verbose) => {
36
36
  * Orchestrate phase execution based on execution mode
37
37
  * Routes to appropriate phase sequence based on mode (only_*, from_*, full_pipeline)
38
38
  */
39
- export const runPipelineByMode = async (options, config, executionMode
40
- // eslint-disable-next-line complexity -- orchestration function routing many execution modes to phase sequences
41
- ) => {
39
+ export const runPipelineByMode = async (options, config, executionMode) => {
42
40
  const { featureId, verbose } = options;
43
41
  if (verbose) {
44
42
  logInfo(`🚀 Starting pipeline with mode: ${executionMode} for feature: ${featureId}`);
package/dist/index.js CHANGED
@@ -16,10 +16,10 @@ import { runConfigGet, runConfigList, runConfigSet, runConfigUnset, } from './co
16
16
  import { runGrowthAnalysis } from './commands/growth-analysis/index.js';
17
17
  import { runInit } from './commands/init/index.js';
18
18
  import { runIntelligence } from './commands/intelligence/index.js';
19
+ import { runPRResolve } from './commands/pr-resolve/index.js';
20
+ import { runPRReview } from './commands/pr-review/index.js';
19
21
  import { runRefactor } from './commands/refactor/refactor.js';
20
22
  import { runTaskWorker } from './commands/task-worker/index.js';
21
- import { runPRReview } from './commands/pr-review/index.js';
22
- import { runPRResolve } from './commands/pr-resolve/index.js';
23
23
  import { runWorkflow } from './commands/workflow/index.js';
24
24
  import { logError, logInfo } from './utils/logger.js';
25
25
  // Get package.json version dynamically
@@ -299,6 +299,7 @@ program
299
299
  .description('AI-resolve change requests on a GitHub PR')
300
300
  .requiredOption('--pr-url <url>', 'GitHub PR URL')
301
301
  .option('--pr-id <id>', 'Pull request record ID in database')
302
+ .option('--no-learn', 'Skip checklist learning after resolve')
302
303
  .option('-v, --verbose', 'Verbose output')
303
304
  .action(async (productId, opts) => {
304
305
  try {
@@ -13,9 +13,7 @@ function truncateKeywords(keywords) {
13
13
  }
14
14
  return keywords.slice(0, 100).replace(/,[^,]*$/, '');
15
15
  }
16
- export const generateAppStoreAssets = async (options, config
17
- // eslint-disable-next-line complexity -- orchestration function with sequential asset generation steps
18
- ) => {
16
+ export const generateAppStoreAssets = async (options, config) => {
19
17
  const { productId, targetStore, screenshotsOnly, listingsOnly, verbose } = options;
20
18
  if (verbose) {
21
19
  logInfo(`Starting app store generation for product: ${productId}`);
@@ -116,9 +116,7 @@ async function persistBranches(sortedBranches, featureId, verbose) {
116
116
  }
117
117
  }
118
118
  }
119
- export const planFeatureBranches = async (options, config
120
- // eslint-disable-next-line complexity -- orchestration function with context fetching, agent execution, and result processing
121
- ) => {
119
+ export const planFeatureBranches = async (options, config) => {
122
120
  const { featureId, verbose, replaceExisting } = options;
123
121
  if (verbose) {
124
122
  logInfo(`Starting branch planning for feature ID: ${featureId}`);
@@ -32,9 +32,7 @@ async function* prompt(bugFixPrompt) {
32
32
  setTimeout(res, 10000);
33
33
  });
34
34
  }
35
- export const fixTestFailures = async (options, config
36
- // eslint-disable-next-line complexity -- orchestration function with agent setup, message processing, and result extraction
37
- ) => {
35
+ export const fixTestFailures = async (options, config) => {
38
36
  const { featureId, testErrors, attemptNumber = 1, verbose } = options;
39
37
  if (verbose) {
40
38
  logInfo(`Starting bug fixing for feature ID: ${featureId} (Attempt ${attemptNumber})`);
@@ -170,9 +170,7 @@ message, lastAssistantResponse, featureId, verbose
170
170
  }
171
171
  return null;
172
172
  }
173
- export const implementFeatureCode = async (options, config, checklistContext
174
- // eslint-disable-next-line complexity -- orchestration function coordinating full code implementation lifecycle
175
- ) => {
173
+ export const implementFeatureCode = async (options, config, checklistContext) => {
176
174
  const { featureId, verbose, baseBranch = 'main' } = options;
177
175
  if (verbose) {
178
176
  logInfo(`Starting code implementation for feature ID: ${featureId}`);
@@ -41,9 +41,7 @@ export const MAX_REFINE_ITERATIONS = 10;
41
41
  * Similar to technical-design, this includes an iterative improvement cycle:
42
42
  * refine → verification → improve → re-refine (if needed)
43
43
  */
44
- export const refineCodeFromPRFeedback = async (options, config, checklistContext
45
- // eslint-disable-next-line complexity -- orchestration function coordinating PR feedback refinement lifecycle
46
- ) => {
44
+ export const refineCodeFromPRFeedback = async (options, config, checklistContext) => {
47
45
  const { featureId, githubToken, verbose } = options;
48
46
  if (verbose) {
49
47
  logInfo(`Starting code refine for feature ID: ${featureId}`);
@@ -67,9 +67,7 @@ baseBranchInfo, baseBranchForRebase, originalBaseBranchForRebase, verbose) {
67
67
  /**
68
68
  * Main code review function
69
69
  */
70
- export const reviewPullRequest = async (options, config, checklistContext
71
- // eslint-disable-next-line complexity -- orchestration function coordinating full code review lifecycle
72
- ) => {
70
+ export const reviewPullRequest = async (options, config, checklistContext) => {
73
71
  const { featureId, githubToken, verbose } = options;
74
72
  if (verbose) {
75
73
  logInfo(`Starting code review for feature ID: ${featureId}`);
@@ -31,9 +31,7 @@ async function* prompt(testingPrompt) {
31
31
  setTimeout(res, 10000);
32
32
  });
33
33
  }
34
- export const writeCodeTests = async (options, config
35
- // eslint-disable-next-line complexity -- orchestration function with context fetching, agent execution, and test result processing
36
- ) => {
34
+ export const writeCodeTests = async (options, config) => {
37
35
  const { featureId, verbose } = options;
38
36
  if (verbose) {
39
37
  logInfo(`Starting code testing phase for feature ID: ${featureId}`);
@@ -6,9 +6,7 @@ import { executeAnalysisQuery, parseAnalysisResult } from './agent.js';
6
6
  import { prepareAnalysisContext } from './context.js';
7
7
  import { buildAnalysisResult, deleteArtifacts, deleteSpecificArtifacts, getAllDraftArtifactIds, resetReadyArtifactsToDraft, saveAnalysisArtifactsAsDraft, updateArtifactsToReady, } from './outcome.js';
8
8
  import { createFeatureAnalysisSystemPrompt } from './prompts.js';
9
- export const analyseFeature = async (options, config, checklistContext
10
- // eslint-disable-next-line complexity -- orchestration function with context assembly, agent execution, and result processing
11
- ) => {
9
+ export const analyseFeature = async (options, config, checklistContext) => {
12
10
  const { featureId, verbose } = options;
13
11
  if (verbose) {
14
12
  logInfo(`Starting feature analysis for feature ID: ${featureId}`);
@@ -326,9 +326,7 @@ async function saveTestResults(featureId, testStatus, structuredTestResult, last
326
326
  }
327
327
  return testReportResult;
328
328
  }
329
- export const runFunctionalTesting = async (options, config, checklistContext
330
- // eslint-disable-next-line complexity -- orchestration function coordinating functional test execution and result processing
331
- ) => {
329
+ export const runFunctionalTesting = async (options, config, checklistContext) => {
332
330
  const { featureId, verbose } = options;
333
331
  if (verbose) {
334
332
  logInfo(`Starting functional testing for feature ID: ${featureId}`);
@@ -49,9 +49,7 @@ contentSuggestions) {
49
49
  }
50
50
  return plans;
51
51
  }
52
- export const analyseGrowth = async (options, config
53
- // eslint-disable-next-line complexity -- orchestration function with context assembly, agent execution, and result processing
54
- ) => {
52
+ export const analyseGrowth = async (options, config) => {
55
53
  const { productId, verbose, guidance, analysisId } = options;
56
54
  if (verbose) {
57
55
  logInfo(`Starting growth analysis for product ID: ${productId}`);
@@ -2,7 +2,7 @@ import { query } from '@anthropic-ai/claude-agent-sdk';
2
2
  import { execSync } from 'child_process';
3
3
  import { DEFAULT_MODEL } from '../../constants.js';
4
4
  import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
5
- import { getPullRequests } from '../../services/pull-requests.js';
5
+ import { getPullRequests, } from '../../services/pull-requests.js';
6
6
  import { getCurrentBranch, returnToMainBranch, } from '../../utils/git-branch-manager.js';
7
7
  import { logDebug, logError, logInfo } from '../../utils/logger.js';
8
8
  import { fetchPRExecutionContext } from './context.js';
@@ -98,6 +98,18 @@ export const executeFeaturePRs = async (options, config) => {
98
98
  }
99
99
  }
100
100
  // ======================================
101
+ // Filter to only the next eligible PR
102
+ // ======================================
103
+ // Only process the first pending PR whose predecessor is merged/closed.
104
+ // Already-executed PRs (branch_created, pr_opened, etc.) are included for sync.
105
+ activePullRequests = filterToEligiblePRs(activePullRequests, verbose);
106
+ if (activePullRequests.length === 0) {
107
+ if (verbose) {
108
+ logInfo('No eligible PRs to process — waiting for current PR to be merged or closed.');
109
+ }
110
+ return buildNoChangeResult(featureId, context.pullRequests.length);
111
+ }
112
+ // ======================================
101
113
  // Agent: Create/Update branches
102
114
  // ======================================
103
115
  if (verbose) {
@@ -285,6 +297,52 @@ export const executeFeaturePRs = async (options, config) => {
285
297
  return buildExecutionErrorResult(featureId, errorMessage);
286
298
  }
287
299
  };
300
+ /**
301
+ * Filter PRs to only include the next eligible pending PR.
302
+ *
303
+ * - Already-executed PRs (branch_created, pr_opened, in_review, approved) are
304
+ * kept for incremental sync.
305
+ * - Merged/closed PRs are excluded (no branch work needed).
306
+ * - Only the first pending PR is included, and only if the PR immediately
307
+ * before it (by sequence) is merged or closed (or it's the first PR).
308
+ * - All other pending PRs are blocked until the current one completes.
309
+ */
310
+ function filterToEligiblePRs(pullRequests, verbose) {
311
+ const sorted = [...pullRequests].sort((a, b) => a.sequence - b.sequence);
312
+ const eligible = [];
313
+ let foundPendingToProcess = false;
314
+ let blockedCount = 0;
315
+ for (let i = 0; i < sorted.length; i++) {
316
+ const pr = sorted[i];
317
+ if (pr.status === 'merged' || pr.status === 'closed') {
318
+ // Done — no branch work needed
319
+ continue;
320
+ }
321
+ if (pr.status !== 'pending') {
322
+ // Already executed but not done — include for incremental sync
323
+ eligible.push(pr);
324
+ continue;
325
+ }
326
+ // PR is pending
327
+ if (foundPendingToProcess) {
328
+ blockedCount++;
329
+ continue;
330
+ }
331
+ // Check if the previous PR (by sequence) is merged or closed
332
+ const prevPR = i > 0 ? sorted[i - 1] : null;
333
+ if (!prevPR || prevPR.status === 'merged' || prevPR.status === 'closed') {
334
+ eligible.push(pr);
335
+ }
336
+ else {
337
+ blockedCount++;
338
+ }
339
+ foundPendingToProcess = true;
340
+ }
341
+ if (blockedCount > 0 && verbose) {
342
+ logInfo(`⏳ ${blockedCount} pending PR(s) waiting for previous PR to be merged or closed`);
343
+ }
344
+ return eligible;
345
+ }
288
346
  /**
289
347
  * Execute an agent query for branch creation/update
290
348
  */
@@ -0,0 +1,157 @@
1
+ import assert from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+ import { buildLearnerPrompt } from '../checklist-learner.js';
4
+ // ── helpers ──────────────────────────────────────────────────
5
+ function makeThread(id, overrides) {
6
+ return {
7
+ id,
8
+ isResolved: false,
9
+ isOutdated: false,
10
+ comments: {
11
+ totalCount: 1,
12
+ nodes: [
13
+ {
14
+ id: `${id}-c1`,
15
+ author: { login: overrides?.author || 'reviewer' },
16
+ body: overrides?.body || 'Please fix this',
17
+ path: overrides?.path || 'src/index.ts',
18
+ line: overrides && 'line' in overrides ? (overrides.line ?? null) : 10,
19
+ url: `https://github.com/o/r/pull/1#${id}`,
20
+ },
21
+ ],
22
+ },
23
+ };
24
+ }
25
+ function makeMap(entries) {
26
+ return new Map(entries);
27
+ }
28
+ // ── buildLearnerPrompt ──────────────────────────────────────
29
+ describe('buildLearnerPrompt', () => {
30
+ it('includes addressed comment count in header', () => {
31
+ const comments = [
32
+ { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
33
+ { comment_id: 'comment_2', action: 'changed', reply: 'Done' },
34
+ ];
35
+ const threads = [makeThread('t1'), makeThread('t2')];
36
+ const map = makeMap([
37
+ ['comment_1', 't1'],
38
+ ['comment_2', 't2'],
39
+ ]);
40
+ const prompt = buildLearnerPrompt(comments, threads, map);
41
+ assert.ok(prompt.includes('2 review comment(s)'));
42
+ });
43
+ it('includes reviewer, file path, line, and comment body', () => {
44
+ const comments = [
45
+ { comment_id: 'comment_1', action: 'changed', reply: 'Added null check' },
46
+ ];
47
+ const threads = [
48
+ makeThread('t1', {
49
+ path: 'src/auth.ts',
50
+ line: 42,
51
+ body: 'Missing null check here',
52
+ author: 'alice',
53
+ }),
54
+ ];
55
+ const map = makeMap([['comment_1', 't1']]);
56
+ const prompt = buildLearnerPrompt(comments, threads, map);
57
+ assert.ok(prompt.includes('src/auth.ts'));
58
+ assert.ok(prompt.includes('42'));
59
+ assert.ok(prompt.includes('@alice'));
60
+ assert.ok(prompt.includes('Missing null check here'));
61
+ assert.ok(prompt.includes('Added null check'));
62
+ });
63
+ it('includes resolution text for each comment', () => {
64
+ const comments = [
65
+ {
66
+ comment_id: 'comment_1',
67
+ action: 'changed',
68
+ reply: 'Refactored to use try/catch',
69
+ },
70
+ ];
71
+ const threads = [makeThread('t1')];
72
+ const map = makeMap([['comment_1', 't1']]);
73
+ const prompt = buildLearnerPrompt(comments, threads, map);
74
+ assert.ok(prompt.includes('**Resolution**: Refactored to use try/catch'));
75
+ });
76
+ it('includes summary when provided', () => {
77
+ const comments = [
78
+ { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
79
+ ];
80
+ const threads = [makeThread('t1')];
81
+ const map = makeMap([['comment_1', 't1']]);
82
+ const prompt = buildLearnerPrompt(comments, threads, map, 'Improved error handling across 3 files');
83
+ assert.ok(prompt.includes('## Overall Summary'));
84
+ assert.ok(prompt.includes('Improved error handling across 3 files'));
85
+ });
86
+ it('omits summary section when not provided', () => {
87
+ const comments = [
88
+ { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
89
+ ];
90
+ const threads = [makeThread('t1')];
91
+ const map = makeMap([['comment_1', 't1']]);
92
+ const prompt = buildLearnerPrompt(comments, threads, map);
93
+ assert.ok(!prompt.includes('## Overall Summary'));
94
+ });
95
+ it('handles comment with no matching thread gracefully', () => {
96
+ const comments = [
97
+ { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
98
+ ];
99
+ // Empty threads — no match for comment_1
100
+ const map = makeMap([['comment_1', 'nonexistent']]);
101
+ const prompt = buildLearnerPrompt(comments, [], map);
102
+ // Should still include the comment section with resolution
103
+ assert.ok(prompt.includes('## comment_1'));
104
+ assert.ok(prompt.includes('**Resolution**: Fixed'));
105
+ // Should NOT include reviewer info since thread was not found
106
+ assert.ok(!prompt.includes('**Reviewer**'));
107
+ });
108
+ it('handles comment_id not in map gracefully', () => {
109
+ const comments = [
110
+ { comment_id: 'comment_99', action: 'changed', reply: 'Fixed' },
111
+ ];
112
+ const prompt = buildLearnerPrompt(comments, [], new Map());
113
+ assert.ok(prompt.includes('## comment_99'));
114
+ assert.ok(prompt.includes('**Resolution**: Fixed'));
115
+ });
116
+ it('omits line when null', () => {
117
+ const comments = [
118
+ { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
119
+ ];
120
+ const threads = [makeThread('t1', { line: null })];
121
+ const map = makeMap([['comment_1', 't1']]);
122
+ const prompt = buildLearnerPrompt(comments, threads, map);
123
+ assert.ok(prompt.includes('**File**: src/index.ts'));
124
+ assert.ok(!prompt.includes('**Line**'));
125
+ });
126
+ it('uses threadById map correctly for multiple comments', () => {
127
+ const comments = [
128
+ { comment_id: 'comment_1', action: 'changed', reply: 'Fix A' },
129
+ { comment_id: 'comment_3', action: 'changed', reply: 'Fix C' },
130
+ ];
131
+ const threads = [
132
+ makeThread('t1', { body: 'Issue A', path: 'a.ts' }),
133
+ makeThread('t2', { body: 'Issue B', path: 'b.ts' }),
134
+ makeThread('t3', { body: 'Issue C', path: 'c.ts' }),
135
+ ];
136
+ const map = makeMap([
137
+ ['comment_1', 't1'],
138
+ ['comment_2', 't2'],
139
+ ['comment_3', 't3'],
140
+ ]);
141
+ const prompt = buildLearnerPrompt(comments, threads, map);
142
+ assert.ok(prompt.includes('Issue A'));
143
+ assert.ok(prompt.includes('a.ts'));
144
+ assert.ok(prompt.includes('Issue C'));
145
+ assert.ok(prompt.includes('c.ts'));
146
+ // comment_2 was not in addressedComments, should not appear
147
+ assert.ok(!prompt.includes('Issue B'));
148
+ });
149
+ it('includes instructions section', () => {
150
+ const comments = [
151
+ { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
152
+ ];
153
+ const prompt = buildLearnerPrompt(comments, [makeThread('t1')], makeMap([['comment_1', 't1']]));
154
+ assert.ok(prompt.includes('## Instructions'));
155
+ assert.ok(prompt.includes('code_review checklists'));
156
+ });
157
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ import assert from 'node:assert';
2
+ import { describe, it } from 'node:test';
3
+ import { isResolveResult } from '../types.js';
4
+ describe('isResolveResult', () => {
5
+ it('returns true for valid ResolveResult', () => {
6
+ assert.ok(isResolveResult({
7
+ comments: [
8
+ { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
9
+ ],
10
+ }));
11
+ });
12
+ it('returns true when optional fields are present', () => {
13
+ assert.ok(isResolveResult({
14
+ comments: [],
15
+ files_modified: ['a.ts'],
16
+ summary: 'Done',
17
+ }));
18
+ });
19
+ it('returns true for empty comments array', () => {
20
+ assert.ok(isResolveResult({ comments: [] }));
21
+ });
22
+ it('returns false for null', () => {
23
+ assert.ok(!isResolveResult(null));
24
+ });
25
+ it('returns false for undefined', () => {
26
+ assert.ok(!isResolveResult(undefined));
27
+ });
28
+ it('returns false for string', () => {
29
+ assert.ok(!isResolveResult('not an object'));
30
+ });
31
+ it('returns false for number', () => {
32
+ assert.ok(!isResolveResult(42));
33
+ });
34
+ it('returns false when comments is missing', () => {
35
+ assert.ok(!isResolveResult({ files_modified: ['a.ts'] }));
36
+ });
37
+ it('returns false when comments is not an array', () => {
38
+ assert.ok(!isResolveResult({ comments: 'not-array' }));
39
+ });
40
+ it('returns false for empty object', () => {
41
+ assert.ok(!isResolveResult({}));
42
+ });
43
+ });
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Checklist Learner — runs after PR resolve to analyse addressed review
3
+ * comments and create / update code-review checklists so the same issues
4
+ * don't recur.
5
+ *
6
+ * Uses the Claude Agent SDK with the existing checklist MCP tools.
7
+ * Strictly non-blocking: failures only log a warning.
8
+ */
9
+ import { type ReviewThread } from '../code-refine-verification/types.js';
10
+ import { type ResolveComment, type ResolveResult } from './types.js';
11
+ export interface ChecklistLearnerInput {
12
+ productId: string;
13
+ unresolvedThreads: ReviewThread[];
14
+ resolveResult: ResolveResult;
15
+ /** Maps comment_id (e.g. "comment_1") → GraphQL thread ID */
16
+ commentIdToThreadId: Map<string, string>;
17
+ verbose?: boolean;
18
+ }
19
+ /**
20
+ * Build a user prompt from the addressed review comments.
21
+ * @param addressedComments - pre-filtered list of comments with action === 'changed'
22
+ */
23
+ export declare function buildLearnerPrompt(addressedComments: ResolveComment[], unresolvedThreads: ReviewThread[], commentIdToThreadId: Map<string, string>, summary?: string): string;
24
+ /**
25
+ * Analyse addressed review comments and update code-review checklists.
26
+ * Non-blocking — catches all errors and logs a warning.
27
+ */
28
+ export declare function learnFromReviewFeedback(input: ChecklistLearnerInput): Promise<void>;