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.
- package/.claude/settings.local.json +3 -23
- package/dist/commands/pr-resolve/index.d.ts +1 -0
- package/dist/commands/pr-resolve/index.js +1 -0
- package/dist/commands/workflow/executors/phase-executor.js +1 -3
- package/dist/commands/workflow/phase-orchestrator.js +1 -3
- package/dist/index.js +3 -2
- package/dist/phases/app-store-generation/index.js +1 -3
- package/dist/phases/branch-planning/index.js +1 -3
- package/dist/phases/bug-fixing/analyzer.js +1 -3
- package/dist/phases/code-implementation/index.js +1 -3
- package/dist/phases/code-refine/index.js +1 -3
- package/dist/phases/code-review/index.js +1 -3
- package/dist/phases/code-testing/analyzer.js +1 -3
- package/dist/phases/feature-analysis/index.js +1 -3
- package/dist/phases/functional-testing/analyzer.js +1 -3
- package/dist/phases/growth-analysis/index.js +1 -3
- package/dist/phases/pr-execution/index.js +59 -1
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +1 -0
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +157 -0
- package/dist/phases/pr-resolve/__tests__/types.test.d.ts +1 -0
- package/dist/phases/pr-resolve/__tests__/types.test.js +43 -0
- package/dist/phases/pr-resolve/checklist-learner.d.ts +28 -0
- package/dist/phases/pr-resolve/checklist-learner.js +128 -0
- package/dist/phases/pr-resolve/index.d.ts +4 -0
- package/dist/phases/pr-resolve/index.js +23 -5
- package/dist/phases/pr-resolve/prompts.js +2 -1
- package/dist/phases/pr-resolve/types.d.ts +18 -0
- package/dist/phases/pr-resolve/types.js +14 -0
- package/dist/phases/pr-resolve/workspace.d.ts +1 -1
- package/dist/phases/pr-resolve/workspace.js +1 -1
- package/dist/phases/pr-review/__tests__/review-comments.test.js +4 -2
- package/dist/phases/pr-review/index.js +1 -3
- package/dist/phases/pr-shared/agent-utils.js +0 -1
- package/dist/phases/pr-shared/context.d.ts +1 -1
- package/dist/phases/pr-splitting/context.js +20 -15
- package/dist/phases/pr-splitting/index.js +1 -3
- package/dist/phases/technical-design/index.js +1 -3
- package/dist/phases/test-cases-analysis/index.js +1 -3
- package/dist/phases/user-stories-analysis/index.js +1 -3
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
- package/dist/services/lifecycle-agent/index.d.ts +24 -0
- package/dist/services/lifecycle-agent/index.js +25 -0
- package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
- package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
- package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
- package/dist/services/lifecycle-agent/transition-rules.js +184 -0
- package/dist/services/lifecycle-agent/types.d.ts +190 -0
- package/dist/services/lifecycle-agent/types.js +12 -0
- package/package.json +1 -1
- package/.env.local +0 -12
- package/dist/api/features/__tests__/regression-prevention.test.d.ts +0 -5
- package/dist/api/features/__tests__/regression-prevention.test.js +0 -338
- package/dist/api/features/__tests__/status-updater.integration.test.d.ts +0 -5
- package/dist/api/features/__tests__/status-updater.integration.test.js +0 -497
- package/dist/commands/workflow/pipeline-runner.d.ts +0 -17
- package/dist/commands/workflow/pipeline-runner.js +0 -393
- package/dist/commands/workflow/runner.d.ts +0 -26
- package/dist/commands/workflow/runner.js +0 -119
- package/dist/commands/workflow/workflow-runner.d.ts +0 -26
- package/dist/commands/workflow/workflow-runner.js +0 -119
- package/dist/phases/code-implementation/analyzer-helpers.d.ts +0 -28
- package/dist/phases/code-implementation/analyzer-helpers.js +0 -177
- package/dist/phases/code-implementation/analyzer.d.ts +0 -32
- package/dist/phases/code-implementation/analyzer.js +0 -629
- package/dist/phases/code-implementation/context-fetcher.d.ts +0 -17
- package/dist/phases/code-implementation/context-fetcher.js +0 -86
- package/dist/phases/code-implementation/mcp-server.d.ts +0 -1
- package/dist/phases/code-implementation/mcp-server.js +0 -93
- package/dist/phases/code-implementation/prompts-improvement.d.ts +0 -5
- package/dist/phases/code-implementation/prompts-improvement.js +0 -108
- package/dist/phases/code-implementation-verification/verifier.d.ts +0 -31
- package/dist/phases/code-implementation-verification/verifier.js +0 -196
- package/dist/phases/code-refine/analyzer.d.ts +0 -41
- package/dist/phases/code-refine/analyzer.js +0 -561
- package/dist/phases/code-refine/context-fetcher.d.ts +0 -94
- package/dist/phases/code-refine/context-fetcher.js +0 -423
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +0 -22
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +0 -134
- package/dist/phases/code-refine-verification/verifier.d.ts +0 -47
- package/dist/phases/code-refine-verification/verifier.js +0 -597
- package/dist/phases/code-review/analyzer.d.ts +0 -29
- package/dist/phases/code-review/analyzer.js +0 -363
- package/dist/phases/code-review/context-fetcher.d.ts +0 -92
- package/dist/phases/code-review/context-fetcher.js +0 -296
- package/dist/phases/feature-analysis/analyzer-helpers.d.ts +0 -10
- package/dist/phases/feature-analysis/analyzer-helpers.js +0 -47
- package/dist/phases/feature-analysis/analyzer.d.ts +0 -11
- package/dist/phases/feature-analysis/analyzer.js +0 -208
- package/dist/phases/feature-analysis/context-fetcher.d.ts +0 -26
- package/dist/phases/feature-analysis/context-fetcher.js +0 -134
- package/dist/phases/feature-analysis/http-fallback.d.ts +0 -20
- package/dist/phases/feature-analysis/http-fallback.js +0 -95
- package/dist/phases/feature-analysis/mcp-server.d.ts +0 -1
- package/dist/phases/feature-analysis/mcp-server.js +0 -144
- package/dist/phases/feature-analysis/prompts-improvement.d.ts +0 -8
- package/dist/phases/feature-analysis/prompts-improvement.js +0 -109
- package/dist/phases/feature-analysis-verification/verifier.d.ts +0 -37
- package/dist/phases/feature-analysis-verification/verifier.js +0 -147
- package/dist/phases/technical-design/analyzer-helpers.d.ts +0 -25
- package/dist/phases/technical-design/analyzer-helpers.js +0 -39
- package/dist/phases/technical-design/analyzer.d.ts +0 -21
- package/dist/phases/technical-design/analyzer.js +0 -461
- package/dist/phases/technical-design/context-fetcher.d.ts +0 -12
- package/dist/phases/technical-design/context-fetcher.js +0 -39
- package/dist/phases/technical-design/http-fallback.d.ts +0 -17
- package/dist/phases/technical-design/http-fallback.js +0 -151
- package/dist/phases/technical-design/mcp-server.d.ts +0 -1
- package/dist/phases/technical-design/mcp-server.js +0 -157
- package/dist/phases/technical-design/prompts-improvement.d.ts +0 -5
- package/dist/phases/technical-design/prompts-improvement.js +0 -93
- package/dist/phases/technical-design-verification/verifier.d.ts +0 -53
- package/dist/phases/technical-design-verification/verifier.js +0 -170
- package/dist/services/feature-branches.d.ts +0 -77
- package/dist/services/feature-branches.js +0 -205
- package/dist/workflow-runner/config/phase-configs.d.ts +0 -5
- package/dist/workflow-runner/config/phase-configs.js +0 -120
- package/dist/workflow-runner/core/feature-filter.d.ts +0 -16
- package/dist/workflow-runner/core/feature-filter.js +0 -46
- package/dist/workflow-runner/core/index.d.ts +0 -8
- package/dist/workflow-runner/core/index.js +0 -12
- package/dist/workflow-runner/core/pipeline-evaluator.d.ts +0 -24
- package/dist/workflow-runner/core/pipeline-evaluator.js +0 -32
- package/dist/workflow-runner/core/state-manager.d.ts +0 -24
- package/dist/workflow-runner/core/state-manager.js +0 -42
- package/dist/workflow-runner/core/workflow-logger.d.ts +0 -20
- package/dist/workflow-runner/core/workflow-logger.js +0 -65
- package/dist/workflow-runner/executors/phase-executor.d.ts +0 -8
- package/dist/workflow-runner/executors/phase-executor.js +0 -248
- package/dist/workflow-runner/feature-workflow-runner.d.ts +0 -26
- package/dist/workflow-runner/feature-workflow-runner.js +0 -119
- package/dist/workflow-runner/index.d.ts +0 -2
- package/dist/workflow-runner/index.js +0 -2
- package/dist/workflow-runner/pipeline-runner.d.ts +0 -17
- package/dist/workflow-runner/pipeline-runner.js +0 -393
- package/dist/workflow-runner/workflow-processor.d.ts +0 -54
- package/dist/workflow-runner/workflow-processor.js +0 -170
|
@@ -1,28 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"
|
|
5
|
-
"Bash(npm run
|
|
6
|
-
|
|
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
|
}
|
|
@@ -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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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>;
|