edsger 0.45.0 → 0.45.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.
Files changed (95) hide show
  1. package/package.json +3 -3
  2. package/tsconfig.build.json +4 -0
  3. package/tsconfig.json +3 -9
  4. package/dist/api/__tests__/app-store.test.d.ts +0 -7
  5. package/dist/api/__tests__/app-store.test.js +0 -60
  6. package/dist/api/__tests__/intelligence.test.d.ts +0 -11
  7. package/dist/api/__tests__/intelligence.test.js +0 -315
  8. package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
  9. package/dist/api/features/__tests__/feature-utils.test.js +0 -370
  10. package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
  11. package/dist/api/features/__tests__/status-updater.test.js +0 -88
  12. package/dist/commands/build/__tests__/build.test.d.ts +0 -5
  13. package/dist/commands/build/__tests__/build.test.js +0 -206
  14. package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
  15. package/dist/commands/build/__tests__/detect-project.test.js +0 -160
  16. package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
  17. package/dist/commands/build/__tests__/run-build.test.js +0 -433
  18. package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
  19. package/dist/commands/intelligence/__tests__/command.test.js +0 -48
  20. package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
  21. package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
  22. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
  23. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
  24. package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
  25. package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
  26. package/dist/config/__tests__/config.test.d.ts +0 -4
  27. package/dist/config/__tests__/config.test.js +0 -286
  28. package/dist/config/__tests__/feature-status.test.d.ts +0 -4
  29. package/dist/config/__tests__/feature-status.test.js +0 -111
  30. package/dist/errors/__tests__/index.test.d.ts +0 -4
  31. package/dist/errors/__tests__/index.test.js +0 -349
  32. package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
  33. package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
  34. package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
  35. package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
  36. package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
  37. package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
  38. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
  39. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
  40. package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
  41. package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
  42. package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
  43. package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
  44. package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
  45. package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
  46. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
  47. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
  48. package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
  49. package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
  50. package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
  51. package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
  52. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
  53. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
  54. package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
  55. package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
  56. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
  57. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
  58. package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
  59. package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
  60. package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
  61. package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
  62. package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
  63. package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
  64. package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
  65. package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
  66. package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
  67. package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
  68. package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
  69. package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
  70. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
  71. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
  72. package/dist/phases/release-sync/__tests__/github.test.d.ts +0 -9
  73. package/dist/phases/release-sync/__tests__/github.test.js +0 -123
  74. package/dist/phases/release-sync/__tests__/snapshot.test.d.ts +0 -8
  75. package/dist/phases/release-sync/__tests__/snapshot.test.js +0 -93
  76. package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
  77. package/dist/phases/smoke-test/__tests__/agent.test.js +0 -85
  78. package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
  79. package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
  80. package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
  81. package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
  82. package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
  83. package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
  84. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
  85. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
  86. package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
  87. package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
  88. package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
  89. package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
  90. package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
  91. package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
  92. package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
  93. package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
  94. package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
  95. package/dist/workspace/__tests__/workspace-manager.test.js +0 -52
@@ -1,138 +0,0 @@
1
- /**
2
- * Integration-level tests for the resolve comment→thread mapping logic.
3
- * Tests the flow: agent JSON result → comment_id to thread_id mapping → reply decisions.
4
- */
5
- import assert from 'node:assert';
6
- import { describe, it } from 'node:test';
7
- import { createResolveUserPrompt } from '../prompts.js';
8
- // Simulate the mapping logic from resolveStandalonePR
9
- function processResolveResult(comments, commentIdToThreadId) {
10
- const addressed = [];
11
- const skipped = [];
12
- const errors = [];
13
- for (const comment of comments) {
14
- const threadId = commentIdToThreadId.get(comment.comment_id);
15
- if (!threadId) {
16
- errors.push(`Unknown comment_id "${comment.comment_id}"`);
17
- continue;
18
- }
19
- if (comment.action === 'changed') {
20
- addressed.push({ threadId, reply: comment.reply });
21
- }
22
- else {
23
- skipped.push({ threadId, reply: comment.reply });
24
- }
25
- }
26
- return { addressed, skipped, errors };
27
- }
28
- function makeThread(id, body) {
29
- return {
30
- id,
31
- isResolved: false,
32
- isOutdated: false,
33
- comments: {
34
- totalCount: 1,
35
- nodes: [
36
- {
37
- id: `${id}-c1`,
38
- author: { login: 'reviewer' },
39
- body,
40
- path: 'src/index.ts',
41
- line: 10,
42
- url: '',
43
- },
44
- ],
45
- },
46
- };
47
- }
48
- void describe('resolve comment→thread mapping (integration)', () => {
49
- void it('correctly maps comment_ids to thread IDs', () => {
50
- const threads = [
51
- makeThread('PRRT_aaa', 'Use const'),
52
- makeThread('PRRT_bbb', 'Add error handling'),
53
- makeThread('PRRT_ccc', 'Remove unused import'),
54
- ];
55
- const { commentIdToThreadId } = createResolveUserPrompt(threads);
56
- const agentResult = [
57
- {
58
- comment_id: 'comment_1',
59
- action: 'changed',
60
- reply: 'Done, switched to const',
61
- },
62
- { comment_id: 'comment_2', action: 'changed', reply: 'Added try/catch' },
63
- {
64
- comment_id: 'comment_3',
65
- action: 'skipped',
66
- reply: 'Import is used in tests',
67
- },
68
- ];
69
- const result = processResolveResult(agentResult, commentIdToThreadId);
70
- assert.strictEqual(result.addressed.length, 2);
71
- assert.strictEqual(result.skipped.length, 1);
72
- assert.strictEqual(result.errors.length, 0);
73
- // Verify thread ID mapping
74
- assert.strictEqual(result.addressed[0].threadId, 'PRRT_aaa');
75
- assert.strictEqual(result.addressed[1].threadId, 'PRRT_bbb');
76
- assert.strictEqual(result.skipped[0].threadId, 'PRRT_ccc');
77
- });
78
- void it('reports errors for unknown comment_ids', () => {
79
- const threads = [makeThread('PRRT_aaa', 'Fix bug')];
80
- const { commentIdToThreadId } = createResolveUserPrompt(threads);
81
- const agentResult = [
82
- { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
83
- { comment_id: 'comment_99', action: 'changed', reply: 'Unknown' },
84
- ];
85
- const result = processResolveResult(agentResult, commentIdToThreadId);
86
- assert.strictEqual(result.addressed.length, 1);
87
- assert.strictEqual(result.errors.length, 1);
88
- assert.ok(result.errors[0].includes('comment_99'));
89
- });
90
- void it('handles agent returning partial results', () => {
91
- const threads = [
92
- makeThread('PRRT_aaa', 'Fix A'),
93
- makeThread('PRRT_bbb', 'Fix B'),
94
- makeThread('PRRT_ccc', 'Fix C'),
95
- ];
96
- const { commentIdToThreadId } = createResolveUserPrompt(threads);
97
- // Agent only addressed 2 of 3
98
- const agentResult = [
99
- { comment_id: 'comment_1', action: 'changed', reply: 'Done' },
100
- { comment_id: 'comment_3', action: 'skipped', reply: 'No change needed' },
101
- ];
102
- const result = processResolveResult(agentResult, commentIdToThreadId);
103
- assert.strictEqual(result.addressed.length, 1);
104
- assert.strictEqual(result.skipped.length, 1);
105
- assert.strictEqual(result.errors.length, 0);
106
- // comment_2 was not mentioned - no error, just not processed
107
- });
108
- void it('handles empty agent result', () => {
109
- const threads = [makeThread('PRRT_aaa', 'Fix')];
110
- const { commentIdToThreadId } = createResolveUserPrompt(threads);
111
- const result = processResolveResult([], commentIdToThreadId);
112
- assert.strictEqual(result.addressed.length, 0);
113
- assert.strictEqual(result.skipped.length, 0);
114
- assert.strictEqual(result.errors.length, 0);
115
- });
116
- void it('preserves reply text in all cases', () => {
117
- const threads = [
118
- makeThread('PRRT_aaa', 'Fix this'),
119
- makeThread('PRRT_bbb', 'Change that'),
120
- ];
121
- const { commentIdToThreadId } = createResolveUserPrompt(threads);
122
- const agentResult = [
123
- {
124
- comment_id: 'comment_1',
125
- action: 'changed',
126
- reply: 'Switched to const as suggested. Good catch.',
127
- },
128
- {
129
- comment_id: 'comment_2',
130
- action: 'skipped',
131
- reply: 'The current approach is better because it allows lazy initialization which is needed for the singleton pattern.',
132
- },
133
- ];
134
- const result = processResolveResult(agentResult, commentIdToThreadId);
135
- assert.ok(result.addressed[0].reply.includes('Good catch'));
136
- assert.ok(result.skipped[0].reply.includes('singleton pattern'));
137
- });
138
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,43 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { isResolveResult } from '../types.js';
4
- void describe('isResolveResult', () => {
5
- void 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
- void 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
- void it('returns true for empty comments array', () => {
20
- assert.ok(isResolveResult({ comments: [] }));
21
- });
22
- void it('returns false for null', () => {
23
- assert.ok(!isResolveResult(null));
24
- });
25
- void it('returns false for undefined', () => {
26
- assert.ok(!isResolveResult(undefined));
27
- });
28
- void it('returns false for string', () => {
29
- assert.ok(!isResolveResult('not an object'));
30
- });
31
- void it('returns false for number', () => {
32
- assert.ok(!isResolveResult(42));
33
- });
34
- void it('returns false when comments is missing', () => {
35
- assert.ok(!isResolveResult({ files_modified: ['a.ts'] }));
36
- });
37
- void it('returns false when comments is not an array', () => {
38
- assert.ok(!isResolveResult({ comments: 'not-array' }));
39
- });
40
- void it('returns false for empty object', () => {
41
- assert.ok(!isResolveResult({}));
42
- });
43
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,111 +0,0 @@
1
- import assert from 'node:assert';
2
- import { execSync } from 'node:child_process';
3
- import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { afterEach, beforeEach, describe, it } from 'node:test';
7
- import { buildCredentialArgs, hasNewCommits, hasUncommittedChanges, } from '../workspace.js';
8
- // Helper: create a temp git repo with an initial commit
9
- function createTempRepo() {
10
- const dir = mkdtempSync(join(tmpdir(), 'edsger-test-'));
11
- execSync('git init', { cwd: dir, stdio: 'pipe' });
12
- execSync('git config user.email "test@test.com"', { cwd: dir, stdio: 'pipe' });
13
- execSync('git config user.name "Test"', { cwd: dir, stdio: 'pipe' });
14
- writeFileSync(join(dir, 'README.md'), '# Test');
15
- execSync('git add .', { cwd: dir, stdio: 'pipe' });
16
- execSync('git commit -m "init"', { cwd: dir, stdio: 'pipe' });
17
- return dir;
18
- }
19
- void describe('buildCredentialArgs', () => {
20
- void it('returns 4 args with credential helper config', () => {
21
- const args = buildCredentialArgs('my-token');
22
- assert.strictEqual(args.length, 4);
23
- assert.strictEqual(args[0], '-c');
24
- assert.strictEqual(args[1], 'credential.helper=');
25
- assert.strictEqual(args[2], '-c');
26
- assert.ok(args[3].includes('my-token'));
27
- assert.ok(args[3].includes('x-access-token'));
28
- });
29
- void it('escapes token in credential helper', () => {
30
- const args = buildCredentialArgs('token-with-special-chars!@#');
31
- assert.ok(args[3].includes('token-with-special-chars!@#'));
32
- });
33
- });
34
- void describe('hasUncommittedChanges', () => {
35
- let repoPath;
36
- beforeEach(() => {
37
- repoPath = createTempRepo();
38
- });
39
- afterEach(() => {
40
- rmSync(repoPath, { recursive: true, force: true });
41
- });
42
- void it('returns false for clean repo', () => {
43
- assert.strictEqual(hasUncommittedChanges(repoPath), false);
44
- });
45
- void it('returns true after modifying a file', () => {
46
- writeFileSync(join(repoPath, 'README.md'), '# Modified');
47
- assert.strictEqual(hasUncommittedChanges(repoPath), true);
48
- });
49
- void it('returns true for new untracked file', () => {
50
- writeFileSync(join(repoPath, 'new-file.txt'), 'content');
51
- assert.strictEqual(hasUncommittedChanges(repoPath), true);
52
- });
53
- void it('returns false after staging and committing', () => {
54
- writeFileSync(join(repoPath, 'new.txt'), 'x');
55
- execSync('git add . && git commit -m "add"', {
56
- cwd: repoPath,
57
- stdio: 'pipe',
58
- });
59
- assert.strictEqual(hasUncommittedChanges(repoPath), false);
60
- });
61
- void it('returns false for non-existent path', () => {
62
- assert.strictEqual(hasUncommittedChanges('/tmp/nonexistent-repo-xyz'), false);
63
- });
64
- });
65
- void describe('hasNewCommits', () => {
66
- let repoPath;
67
- let bareRemote;
68
- beforeEach(() => {
69
- // Create a bare remote and clone it
70
- bareRemote = mkdtempSync(join(tmpdir(), 'edsger-remote-'));
71
- execSync('git init --bare', { cwd: bareRemote, stdio: 'pipe' });
72
- repoPath = mkdtempSync(join(tmpdir(), 'edsger-clone-'));
73
- rmSync(repoPath, { recursive: true });
74
- execSync(`git clone "${bareRemote}" "${repoPath}"`, { stdio: 'pipe' });
75
- execSync('git config user.email "test@test.com"', {
76
- cwd: repoPath,
77
- stdio: 'pipe',
78
- });
79
- execSync('git config user.name "Test"', { cwd: repoPath, stdio: 'pipe' });
80
- // Create initial commit and push
81
- writeFileSync(join(repoPath, 'README.md'), '# Test');
82
- execSync('git add . && git commit -m "init"', {
83
- cwd: repoPath,
84
- stdio: 'pipe',
85
- });
86
- execSync('git push origin main', { cwd: repoPath, stdio: 'pipe' });
87
- });
88
- afterEach(() => {
89
- rmSync(repoPath, { recursive: true, force: true });
90
- rmSync(bareRemote, { recursive: true, force: true });
91
- });
92
- void it('returns false when HEAD matches remote', () => {
93
- assert.strictEqual(hasNewCommits(repoPath, 'main'), false);
94
- });
95
- void it('returns true after a local commit not pushed', () => {
96
- writeFileSync(join(repoPath, 'new.txt'), 'hello');
97
- execSync('git add . && git commit -m "local"', {
98
- cwd: repoPath,
99
- stdio: 'pipe',
100
- });
101
- assert.strictEqual(hasNewCommits(repoPath, 'main'), true);
102
- });
103
- void it('returns false after pushing local commit', () => {
104
- writeFileSync(join(repoPath, 'new.txt'), 'hello');
105
- execSync('git add . && git commit -m "local" && git push origin main', {
106
- cwd: repoPath,
107
- stdio: 'pipe',
108
- });
109
- assert.strictEqual(hasNewCommits(repoPath, 'main'), false);
110
- });
111
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,49 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { createStandaloneReviewSystemPrompt, createStandaloneReviewUserPrompt, } from '../prompts.js';
4
- void describe('createStandaloneReviewSystemPrompt', () => {
5
- void it('includes review focus areas', () => {
6
- const prompt = createStandaloneReviewSystemPrompt();
7
- assert.ok(prompt.includes('Code Quality'));
8
- assert.ok(prompt.includes('Security'));
9
- assert.ok(prompt.includes('Performance'));
10
- });
11
- void it('specifies JSON result format', () => {
12
- const prompt = createStandaloneReviewSystemPrompt();
13
- assert.ok(prompt.includes('review_result'));
14
- assert.ok(prompt.includes('"file"'));
15
- assert.ok(prompt.includes('"line"'));
16
- assert.ok(prompt.includes('"comment"'));
17
- });
18
- void it('includes assessment options', () => {
19
- const prompt = createStandaloneReviewSystemPrompt();
20
- assert.ok(prompt.includes('APPROVE'));
21
- assert.ok(prompt.includes('REQUEST_CHANGES'));
22
- assert.ok(prompt.includes('COMMENT'));
23
- });
24
- void it('does not include feature/checklist instructions', () => {
25
- const prompt = createStandaloneReviewSystemPrompt();
26
- assert.ok(!prompt.includes('checklist'));
27
- assert.ok(!prompt.includes('feature_id'));
28
- assert.ok(!prompt.includes('user stories'));
29
- });
30
- });
31
- void describe('createStandaloneReviewUserPrompt', () => {
32
- void it('includes context info in the prompt', () => {
33
- const contextInfo = '# Pull Request\n**Title**: Fix auth bug\n';
34
- const prompt = createStandaloneReviewUserPrompt(contextInfo);
35
- assert.ok(prompt.includes('Fix auth bug'));
36
- });
37
- void it('includes review instructions', () => {
38
- const prompt = createStandaloneReviewUserPrompt('some context');
39
- assert.ok(prompt.includes('Analyze Each File'));
40
- assert.ok(prompt.includes('Identify Issues'));
41
- assert.ok(prompt.includes('Actionable Feedback'));
42
- });
43
- void it('mentions severity categories', () => {
44
- const prompt = createStandaloneReviewUserPrompt('context');
45
- assert.ok(prompt.includes('Critical'));
46
- assert.ok(prompt.includes('Major'));
47
- assert.ok(prompt.includes('Minor'));
48
- });
49
- });
@@ -1,110 +0,0 @@
1
- /**
2
- * Integration-level tests for the review comment mapping logic.
3
- * Tests the flow: agent JSON result → diff position mapping → GitHub comment payload.
4
- */
5
- import assert from 'node:assert';
6
- import { describe, it } from 'node:test';
7
- import { buildLineToPositionMap, findClosestPosition, } from '../../code-review/diff-utils.js';
8
- // Simulate the comment mapping logic from reviewStandalonePR
9
- function mapCommentsToReviewPayload(agentComments, files) {
10
- // Build line-to-position maps
11
- const fileLineToPosition = new Map();
12
- for (const file of files) {
13
- if (file.patch) {
14
- fileLineToPosition.set(file.filename, buildLineToPositionMap(file.patch));
15
- }
16
- }
17
- const result = [];
18
- for (const comment of agentComments) {
19
- const lineToPosition = fileLineToPosition.get(comment.file);
20
- if (!lineToPosition) {
21
- continue;
22
- }
23
- const positionResult = findClosestPosition(comment.line, lineToPosition);
24
- if (!positionResult) {
25
- continue;
26
- }
27
- let body = comment.comment;
28
- if (positionResult.actualLine !== comment.line) {
29
- body = `**Note**: Comment originally for line ${comment.line}, adjusted to line ${positionResult.actualLine} (nearest line in diff).\n\n${body}`;
30
- }
31
- result.push({
32
- path: comment.file,
33
- position: positionResult.position,
34
- body,
35
- });
36
- }
37
- return result;
38
- }
39
- void describe('review comment mapping (integration)', () => {
40
- const samplePatch = `@@ -1,5 +1,7 @@
41
- import { useState } from 'react'
42
-
43
- +const MAX_RETRIES = 3
44
- +
45
- export function App() {
46
- - const [count, setCount] = useState(0)
47
- + const [count, setCount] = useState<number>(0)
48
- return <div>{count}</div>`;
49
- const files = [{ filename: 'src/App.tsx', patch: samplePatch }];
50
- void it('maps exact line comments to correct diff positions', () => {
51
- const comments = [
52
- {
53
- file: 'src/App.tsx',
54
- line: 3,
55
- comment: 'Consider making this configurable',
56
- },
57
- ];
58
- const result = mapCommentsToReviewPayload(comments, files);
59
- assert.strictEqual(result.length, 1);
60
- assert.strictEqual(result[0].path, 'src/App.tsx');
61
- assert.ok(result[0].position > 0);
62
- assert.ok(result[0].body.includes('Consider making this configurable'));
63
- // No "Note" prefix since line was exact
64
- assert.ok(!result[0].body.includes('**Note**'));
65
- });
66
- void it('adjusts line numbers when exact line not in diff', () => {
67
- // Line 2 is blank (in diff) but line 100 is way outside
68
- const comments = [{ file: 'src/App.tsx', line: 8, comment: 'Fix this' }];
69
- const result = mapCommentsToReviewPayload(comments, files);
70
- // Line 8 should find closest in range
71
- if (result.length > 0) {
72
- assert.ok(result[0].body.includes('**Note**'));
73
- }
74
- // If no match within range, it's filtered out - also valid
75
- });
76
- void it('filters out comments for files not in diff', () => {
77
- const comments = [
78
- {
79
- file: 'src/other.ts',
80
- line: 5,
81
- comment: 'This file is not in the diff',
82
- },
83
- ];
84
- const result = mapCommentsToReviewPayload(comments, files);
85
- assert.strictEqual(result.length, 0);
86
- });
87
- void it('handles multiple comments on same file', () => {
88
- const comments = [
89
- { file: 'src/App.tsx', line: 3, comment: 'Comment A' },
90
- { file: 'src/App.tsx', line: 6, comment: 'Comment B' },
91
- ];
92
- const result = mapCommentsToReviewPayload(comments, files);
93
- assert.strictEqual(result.length, 2);
94
- assert.ok(result[0].body.includes('Comment A'));
95
- assert.ok(result[1].body.includes('Comment B'));
96
- });
97
- void it('handles files with no patch (binary files)', () => {
98
- const binaryFiles = [
99
- { filename: 'image.png' }, // no patch
100
- { filename: 'src/App.tsx', patch: samplePatch },
101
- ];
102
- const comments = [
103
- { file: 'image.png', line: 1, comment: 'Binary comment' },
104
- { file: 'src/App.tsx', line: 3, comment: 'Code comment' },
105
- ];
106
- const result = mapCommentsToReviewPayload(comments, binaryFiles);
107
- assert.strictEqual(result.length, 1);
108
- assert.strictEqual(result[0].path, 'src/App.tsx');
109
- });
110
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,91 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { extractTextFromContent, tryExtractResult, tryParseJsonFromResponse, userMessage, } from '../agent-utils.js';
4
- void describe('userMessage', () => {
5
- void it('creates a user message object', () => {
6
- const msg = userMessage('hello');
7
- assert.deepStrictEqual(msg, {
8
- type: 'user',
9
- message: { role: 'user', content: 'hello' },
10
- });
11
- });
12
- });
13
- void describe('extractTextFromContent', () => {
14
- void it('extracts text items from content array', () => {
15
- const content = [
16
- { type: 'text', text: 'hello' },
17
- { type: 'tool_use', id: '123', name: 'read', input: {} },
18
- { type: 'text', text: 'world' },
19
- ];
20
- const result = extractTextFromContent(content);
21
- assert.strictEqual(result, 'hello\nworld\n');
22
- });
23
- void it('returns empty string for no text items', () => {
24
- const content = [{ type: 'tool_use', id: '123', name: 'read', input: {} }];
25
- const result = extractTextFromContent(content);
26
- assert.strictEqual(result, '');
27
- });
28
- void it('returns empty string for empty array', () => {
29
- assert.strictEqual(extractTextFromContent([]), '');
30
- });
31
- });
32
- void describe('tryParseJsonFromResponse', () => {
33
- void it('parses JSON from code block', () => {
34
- const text = 'Here is the result:\n```json\n{"key": "value"}\n```\nDone.';
35
- const result = tryParseJsonFromResponse(text);
36
- assert.deepStrictEqual(result, { key: 'value' });
37
- });
38
- void it('parses raw JSON', () => {
39
- const text = '{"key": "value"}';
40
- const result = tryParseJsonFromResponse(text);
41
- assert.deepStrictEqual(result, { key: 'value' });
42
- });
43
- void it('returns null for invalid JSON', () => {
44
- const result = tryParseJsonFromResponse('not json at all');
45
- assert.strictEqual(result, null);
46
- });
47
- void it('returns null for empty string', () => {
48
- const result = tryParseJsonFromResponse('');
49
- assert.strictEqual(result, null);
50
- });
51
- void it('parses JSON block with surrounding text', () => {
52
- const text = `I analyzed the code and here are my findings:
53
-
54
- \`\`\`json
55
- {
56
- "review_result": {
57
- "summary": "looks good",
58
- "comments": []
59
- }
60
- }
61
- \`\`\`
62
-
63
- That's my review.`;
64
- const result = tryParseJsonFromResponse(text);
65
- assert.ok(result);
66
- assert.ok(result.review_result);
67
- });
68
- });
69
- void describe('tryExtractResult', () => {
70
- void it('extracts keyed result from JSON code block', () => {
71
- const text = '```json\n{"review_result": {"summary": "good"}}\n```';
72
- const result = tryExtractResult(text, 'review_result');
73
- assert.deepStrictEqual(result, { summary: 'good' });
74
- });
75
- void it('returns whole object if key not found but JSON valid', () => {
76
- const text = '{"summary": "good", "comments": []}';
77
- const result = tryExtractResult(text, 'review_result');
78
- assert.deepStrictEqual(result, { summary: 'good', comments: [] });
79
- });
80
- void it('returns null for unparseable text', () => {
81
- const result = tryExtractResult('no json here', 'review_result');
82
- assert.strictEqual(result, null);
83
- });
84
- void it('extracts resolve_result key', () => {
85
- const text = '```json\n{"resolve_result": {"comments": [{"comment_id": "comment_1", "action": "changed", "reply": "fixed"}]}}\n```';
86
- const result = tryExtractResult(text, 'resolve_result');
87
- assert.ok(result);
88
- assert.ok(Array.isArray(result.comments));
89
- assert.strictEqual(result.comments.length, 1);
90
- });
91
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,94 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { formatStandalonePRContextForPrompt, parsePullRequestUrl, } from '../context.js';
4
- void describe('parsePullRequestUrl (re-exported)', () => {
5
- void it('parses a standard GitHub PR URL', () => {
6
- const result = parsePullRequestUrl('https://github.com/owner/repo/pull/123');
7
- assert.deepStrictEqual(result, {
8
- owner: 'owner',
9
- repo: 'repo',
10
- prNumber: 123,
11
- });
12
- });
13
- void it('parses URL with trailing path segments', () => {
14
- const result = parsePullRequestUrl('https://github.com/my-org/my-repo/pull/456/files');
15
- assert.deepStrictEqual(result, {
16
- owner: 'my-org',
17
- repo: 'my-repo',
18
- prNumber: 456,
19
- });
20
- });
21
- void it('returns null for non-PR URL', () => {
22
- assert.strictEqual(parsePullRequestUrl('https://github.com/owner/repo/issues/1'), null);
23
- });
24
- void it('returns null for non-GitHub URL', () => {
25
- assert.strictEqual(parsePullRequestUrl('https://example.com/pull/1'), null);
26
- });
27
- });
28
- void describe('formatStandalonePRContextForPrompt', () => {
29
- const context = {
30
- pullRequestUrl: 'https://github.com/owner/repo/pull/42',
31
- pullRequestNumber: 42,
32
- owner: 'owner',
33
- repo: 'repo',
34
- prData: {
35
- number: 42,
36
- title: 'Fix bug in auth',
37
- body: 'This fixes the login issue',
38
- state: 'open',
39
- head: { ref: 'fix/auth', sha: 'abc123' },
40
- base: { ref: 'main', sha: 'def456' },
41
- user: { login: 'testuser' },
42
- },
43
- files: [
44
- {
45
- filename: 'src/auth.ts',
46
- status: 'modified',
47
- additions: 5,
48
- deletions: 2,
49
- changes: 7,
50
- patch: '@@ -1,3 +1,6 @@\n line1\n+added\n line3',
51
- blob_url: '',
52
- },
53
- ],
54
- commits: [
55
- {
56
- sha: 'abc1234567890',
57
- commit: {
58
- message: 'fix: auth bug',
59
- author: { name: 'Test', date: '2026-01-01' },
60
- },
61
- },
62
- ],
63
- };
64
- void it('includes PR URL and number', () => {
65
- const output = formatStandalonePRContextForPrompt(context);
66
- assert.ok(output.includes('#42'));
67
- assert.ok(output.includes('github.com/owner/repo/pull/42'));
68
- });
69
- void it('includes PR title and author', () => {
70
- const output = formatStandalonePRContextForPrompt(context);
71
- assert.ok(output.includes('Fix bug in auth'));
72
- assert.ok(output.includes('@testuser'));
73
- });
74
- void it('includes branch info', () => {
75
- const output = formatStandalonePRContextForPrompt(context);
76
- assert.ok(output.includes('fix/auth'));
77
- assert.ok(output.includes('main'));
78
- });
79
- void it('includes file diff', () => {
80
- const output = formatStandalonePRContextForPrompt(context);
81
- assert.ok(output.includes('src/auth.ts'));
82
- assert.ok(output.includes('+added'));
83
- assert.ok(output.includes('+5 -2'));
84
- });
85
- void it('includes commit info', () => {
86
- const output = formatStandalonePRContextForPrompt(context);
87
- assert.ok(output.includes('abc1234'));
88
- assert.ok(output.includes('fix: auth bug'));
89
- });
90
- void it('includes PR body/description', () => {
91
- const output = formatStandalonePRContextForPrompt(context);
92
- assert.ok(output.includes('This fixes the login issue'));
93
- });
94
- });