edsger 0.51.0 → 0.53.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 (188) hide show
  1. package/.claude/settings.local.json +23 -3
  2. package/.env.local +12 -0
  3. package/dist/commands/find-smells/index.d.ts +21 -0
  4. package/dist/commands/find-smells/index.js +65 -0
  5. package/dist/index.js +29 -0
  6. package/dist/phases/find-bugs/index.js +7 -92
  7. package/dist/phases/find-bugs/state.d.ts +10 -35
  8. package/dist/phases/find-bugs/state.js +12 -120
  9. package/dist/phases/find-features/index.js +16 -83
  10. package/dist/phases/find-features/prompts.d.ts +7 -1
  11. package/dist/phases/find-features/prompts.js +31 -11
  12. package/dist/phases/find-features/state.d.ts +15 -19
  13. package/dist/phases/find-features/state.js +17 -89
  14. package/dist/phases/find-features/types.d.ts +1 -1
  15. package/dist/phases/find-shared/git.d.ts +24 -0
  16. package/dist/phases/find-shared/git.js +60 -0
  17. package/dist/phases/find-shared/mcp.d.ts +33 -0
  18. package/dist/phases/find-shared/mcp.js +69 -0
  19. package/dist/phases/find-shared/scan-state.d.ts +33 -0
  20. package/dist/phases/find-shared/scan-state.js +112 -0
  21. package/dist/phases/find-smells/index.d.ts +47 -0
  22. package/dist/phases/find-smells/index.js +278 -0
  23. package/dist/phases/find-smells/prompts.d.ts +30 -0
  24. package/dist/phases/find-smells/prompts.js +129 -0
  25. package/dist/phases/find-smells/state.d.ts +21 -0
  26. package/dist/phases/find-smells/state.js +17 -0
  27. package/dist/phases/find-smells/types.d.ts +51 -0
  28. package/dist/phases/find-smells/types.js +64 -0
  29. package/dist/phases/pr-execution/context.js +40 -32
  30. package/dist/phases/pr-splitting/context.js +18 -13
  31. package/dist/utils/github-repo-info.d.ts +13 -1
  32. package/dist/utils/github-repo-info.js +32 -6
  33. package/package.json +1 -1
  34. package/vitest.config.ts +2 -0
  35. package/dist/api/__tests__/app-store.test.d.ts +0 -7
  36. package/dist/api/__tests__/app-store.test.js +0 -60
  37. package/dist/api/__tests__/intelligence.test.d.ts +0 -11
  38. package/dist/api/__tests__/intelligence.test.js +0 -315
  39. package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
  40. package/dist/api/features/__tests__/feature-utils.test.js +0 -370
  41. package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
  42. package/dist/api/features/__tests__/status-updater.test.js +0 -88
  43. package/dist/api/features/approval-checker.d.ts +0 -20
  44. package/dist/api/features/approval-checker.js +0 -152
  45. package/dist/api/features/batch-operations.d.ts +0 -17
  46. package/dist/api/features/batch-operations.js +0 -100
  47. package/dist/api/features/feature-utils.d.ts +0 -23
  48. package/dist/api/features/feature-utils.js +0 -80
  49. package/dist/api/features/get-feature.d.ts +0 -5
  50. package/dist/api/features/get-feature.js +0 -21
  51. package/dist/api/features/index.d.ts +0 -8
  52. package/dist/api/features/index.js +0 -10
  53. package/dist/api/features/status-updater.d.ts +0 -41
  54. package/dist/api/features/status-updater.js +0 -122
  55. package/dist/api/features/test-cases.d.ts +0 -29
  56. package/dist/api/features/test-cases.js +0 -110
  57. package/dist/api/features/update-feature.d.ts +0 -20
  58. package/dist/api/features/update-feature.js +0 -83
  59. package/dist/api/features/user-stories.d.ts +0 -21
  60. package/dist/api/features/user-stories.js +0 -88
  61. package/dist/commands/agent-workflow/feature-worker.d.ts +0 -14
  62. package/dist/commands/agent-workflow/feature-worker.js +0 -65
  63. package/dist/commands/build/__tests__/build.test.d.ts +0 -5
  64. package/dist/commands/build/__tests__/build.test.js +0 -206
  65. package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
  66. package/dist/commands/build/__tests__/detect-project.test.js +0 -160
  67. package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
  68. package/dist/commands/build/__tests__/run-build.test.js +0 -433
  69. package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
  70. package/dist/commands/intelligence/__tests__/command.test.js +0 -48
  71. package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
  72. package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
  73. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
  74. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
  75. package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
  76. package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
  77. package/dist/commands/workflow/core/feature-filter.d.ts +0 -16
  78. package/dist/commands/workflow/core/feature-filter.js +0 -47
  79. package/dist/commands/workflow/feature-coordinator.d.ts +0 -18
  80. package/dist/commands/workflow/feature-coordinator.js +0 -161
  81. package/dist/config/__tests__/config.test.d.ts +0 -4
  82. package/dist/config/__tests__/config.test.js +0 -286
  83. package/dist/config/__tests__/feature-status.test.d.ts +0 -4
  84. package/dist/config/__tests__/feature-status.test.js +0 -111
  85. package/dist/config/feature-status.d.ts +0 -56
  86. package/dist/config/feature-status.js +0 -130
  87. package/dist/errors/__tests__/index.test.d.ts +0 -4
  88. package/dist/errors/__tests__/index.test.js +0 -349
  89. package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
  90. package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
  91. package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
  92. package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
  93. package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
  94. package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
  95. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
  96. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
  97. package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
  98. package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
  99. package/dist/phases/feature-analysis/agent.d.ts +0 -13
  100. package/dist/phases/feature-analysis/agent.js +0 -112
  101. package/dist/phases/feature-analysis/context.d.ts +0 -24
  102. package/dist/phases/feature-analysis/context.js +0 -138
  103. package/dist/phases/feature-analysis/index.d.ts +0 -8
  104. package/dist/phases/feature-analysis/index.js +0 -199
  105. package/dist/phases/feature-analysis/outcome.d.ts +0 -40
  106. package/dist/phases/feature-analysis/outcome.js +0 -280
  107. package/dist/phases/feature-analysis/prompts.d.ts +0 -10
  108. package/dist/phases/feature-analysis/prompts.js +0 -212
  109. package/dist/phases/feature-analysis-verification/agent.d.ts +0 -33
  110. package/dist/phases/feature-analysis-verification/agent.js +0 -124
  111. package/dist/phases/feature-analysis-verification/index.d.ts +0 -25
  112. package/dist/phases/feature-analysis-verification/index.js +0 -92
  113. package/dist/phases/feature-analysis-verification/prompts.d.ts +0 -10
  114. package/dist/phases/feature-analysis-verification/prompts.js +0 -100
  115. package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
  116. package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
  117. package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
  118. package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
  119. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
  120. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
  121. package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
  122. package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
  123. package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
  124. package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
  125. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
  126. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
  127. package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
  128. package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
  129. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
  130. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
  131. package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
  132. package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
  133. package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
  134. package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
  135. package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
  136. package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
  137. package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
  138. package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
  139. package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
  140. package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
  141. package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
  142. package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
  143. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
  144. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
  145. package/dist/phases/run-sheet/render.d.ts +0 -60
  146. package/dist/phases/run-sheet/render.js +0 -297
  147. package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
  148. package/dist/phases/smoke-test/__tests__/agent.test.js +0 -84
  149. package/dist/phases/smoke-test/__tests__/github.test.d.ts +0 -9
  150. package/dist/phases/smoke-test/__tests__/github.test.js +0 -120
  151. package/dist/phases/smoke-test/__tests__/snapshot.test.d.ts +0 -8
  152. package/dist/phases/smoke-test/__tests__/snapshot.test.js +0 -93
  153. package/dist/phases/smoke-test/github.d.ts +0 -54
  154. package/dist/phases/smoke-test/github.js +0 -101
  155. package/dist/phases/smoke-test/snapshot.d.ts +0 -27
  156. package/dist/phases/smoke-test/snapshot.js +0 -157
  157. package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
  158. package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
  159. package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
  160. package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
  161. package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
  162. package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
  163. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
  164. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
  165. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
  166. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
  167. package/dist/services/lifecycle-agent/index.d.ts +0 -24
  168. package/dist/services/lifecycle-agent/index.js +0 -25
  169. package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
  170. package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
  171. package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
  172. package/dist/services/lifecycle-agent/transition-rules.js +0 -184
  173. package/dist/services/lifecycle-agent/types.d.ts +0 -190
  174. package/dist/services/lifecycle-agent/types.js +0 -12
  175. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
  176. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
  177. package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
  178. package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
  179. package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
  180. package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
  181. package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
  182. package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
  183. package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
  184. package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
  185. package/dist/types/features.d.ts +0 -35
  186. package/dist/types/features.js +0 -1
  187. package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
  188. package/dist/workspace/__tests__/workspace-manager.test.js +0 -52
@@ -1,10 +1,10 @@
1
- import { execFileSync, execSync } from 'child_process';
1
+ import { execFileSync } from 'child_process';
2
2
  import { getGitHubConfig } from '../../api/github.js';
3
3
  import { getIssue } from '../../api/issues/index.js';
4
4
  import { getPullRequests, } from '../../services/pull-requests.js';
5
5
  import { branchExists, remoteBranchExists, } from '../../utils/git-branch-manager.js';
6
6
  import { buildCredentialArgs } from '../../utils/git-push.js';
7
- import { getRepoForkInfo, } from '../../utils/github-repo-info.js';
7
+ import { getRepoForkInfo, resolveDefaultBranch, } from '../../utils/github-repo-info.js';
8
8
  import { logError, logInfo } from '../../utils/logger.js';
9
9
  /**
10
10
  * Get the dev branch name for an issue
@@ -16,14 +16,16 @@ function getDevBranchName(issueId) {
16
16
  * Get the HEAD SHA of a branch
17
17
  */
18
18
  function getBranchHeadSha(branchName) {
19
- return execSync(`git rev-parse ${branchName}`, { encoding: 'utf-8' }).trim();
19
+ return execFileSync('git', ['rev-parse', branchName], {
20
+ encoding: 'utf-8',
21
+ }).trim();
20
22
  }
21
23
  /**
22
24
  * Get diff stat between two refs
23
25
  */
24
26
  function getDiffStat(baseRef, headRef) {
25
27
  try {
26
- return execSync(`git diff --stat ${baseRef}...${headRef}`, {
28
+ return execFileSync('git', ['diff', '--stat', `${baseRef}...${headRef}`], {
27
29
  encoding: 'utf-8',
28
30
  }).trim();
29
31
  }
@@ -51,9 +53,7 @@ function parseGitStatus(status) {
51
53
  */
52
54
  function getChangedFiles(baseRef, headRef) {
53
55
  try {
54
- const output = execSync(`git diff --name-status ${baseRef}...${headRef}`, {
55
- encoding: 'utf-8',
56
- }).trim();
56
+ const output = execFileSync('git', ['diff', '--name-status', `${baseRef}...${headRef}`], { encoding: 'utf-8' }).trim();
57
57
  if (!output) {
58
58
  return [];
59
59
  }
@@ -77,7 +77,7 @@ function getChangedFiles(baseRef, headRef) {
77
77
  */
78
78
  function isAncestor(commitSha, ref) {
79
79
  try {
80
- execSync(`git merge-base --is-ancestor ${commitSha} ${ref}`, {
80
+ execFileSync('git', ['merge-base', '--is-ancestor', commitSha, ref], {
81
81
  encoding: 'utf-8',
82
82
  stdio: 'pipe',
83
83
  });
@@ -121,25 +121,50 @@ export async function fetchPRExecutionContext(issueId, verbose) {
121
121
  getPullRequests({ issueId, verbose }),
122
122
  getGitHubConfig(issueId, verbose),
123
123
  ]);
124
- // Fetch latest remote refs and fast-forward local main
124
+ // Detect fork status + default branch (used by the fast-forward step below)
125
+ let forkInfo = { isFork: false };
126
+ if (githubConfig.token && githubConfig.owner && githubConfig.repo) {
127
+ try {
128
+ forkInfo = await getRepoForkInfo(githubConfig.token, githubConfig.owner, githubConfig.repo);
129
+ if (verbose) {
130
+ logInfo(forkInfo.isFork
131
+ ? `📌 Repository is a fork of ${forkInfo.upstream?.owner}/${forkInfo.upstream?.repo}`
132
+ : `📌 Repository is not a fork`);
133
+ }
134
+ }
135
+ catch (error) {
136
+ if (verbose) {
137
+ logError(`Failed to detect fork status: ${error instanceof Error ? error.message : String(error)}`);
138
+ }
139
+ }
140
+ }
141
+ // Resolve the repo's default branch (e.g. main, master, develop). Falls back
142
+ // through GitHub API → local symbolic-ref → literal 'main'.
143
+ const defaultBranch = resolveDefaultBranch(forkInfo.defaultBranch);
144
+ // Fetch latest remote refs and fast-forward the local default branch.
145
+ // Use execFileSync (not execSync) so defaultBranch — which can flow in from
146
+ // the GitHub API's default_branch field — is never passed through a shell.
125
147
  try {
126
148
  const credArgs = buildCredentialArgs(githubConfig.token);
127
149
  execFileSync('git', [...credArgs, 'fetch', 'origin'], {
128
150
  encoding: 'utf-8',
129
151
  stdio: 'pipe',
130
152
  });
131
- execSync('git checkout main', { encoding: 'utf-8', stdio: 'pipe' });
132
- execSync('git merge --ff-only origin/main', {
153
+ execFileSync('git', ['checkout', defaultBranch], {
154
+ encoding: 'utf-8',
155
+ stdio: 'pipe',
156
+ });
157
+ execFileSync('git', ['merge', '--ff-only', `origin/${defaultBranch}`], {
133
158
  encoding: 'utf-8',
134
159
  stdio: 'pipe',
135
160
  });
136
161
  if (verbose) {
137
- logInfo('✅ Local main synced with origin/main');
162
+ logInfo(`✅ Local ${defaultBranch} synced with origin/${defaultBranch}`);
138
163
  }
139
164
  }
140
165
  catch (error) {
141
166
  if (verbose) {
142
- logInfo(`⚠️ Could not sync main with origin: ${error instanceof Error ? error.message : String(error)}`);
167
+ logInfo(`⚠️ Could not sync ${defaultBranch} with origin: ${error instanceof Error ? error.message : String(error)}`);
143
168
  }
144
169
  }
145
170
  // Verify dev branch exists
@@ -168,23 +193,6 @@ export async function fetchPRExecutionContext(issueId, verbose) {
168
193
  if (!githubConfig.configured) {
169
194
  throw new Error(`GitHub is not configured. ${githubConfig.message || 'Please configure GitHub integration.'}`);
170
195
  }
171
- // Detect fork status
172
- let forkInfo = { isFork: false };
173
- if (githubConfig.token && githubConfig.owner && githubConfig.repo) {
174
- try {
175
- forkInfo = await getRepoForkInfo(githubConfig.token, githubConfig.owner, githubConfig.repo);
176
- if (verbose) {
177
- logInfo(forkInfo.isFork
178
- ? `📌 Repository is a fork of ${forkInfo.upstream?.owner}/${forkInfo.upstream?.repo}`
179
- : `📌 Repository is not a fork`);
180
- }
181
- }
182
- catch (error) {
183
- if (verbose) {
184
- logError(`Failed to detect fork status: ${error instanceof Error ? error.message : String(error)}`);
185
- }
186
- }
187
- }
188
196
  // Determine sync mode
189
197
  const devRef = localExists ? devBranchName : `origin/${devBranchName}`;
190
198
  const devBranchHeadSha = getBranchHeadSha(devRef);
@@ -202,8 +210,8 @@ export async function fetchPRExecutionContext(issueId, verbose) {
202
210
  lastSyncedCommit = null;
203
211
  isIncrementalSync = false;
204
212
  }
205
- // Get diff info: for incremental, diff from last sync; for first run, diff from main
206
- const diffBase = isIncrementalSync && lastSyncedCommit ? lastSyncedCommit : 'main';
213
+ // Get diff info: for incremental, diff from last sync; for first run, diff from default branch
214
+ const diffBase = isIncrementalSync && lastSyncedCommit ? lastSyncedCommit : defaultBranch;
207
215
  const diffStat = getDiffStat(diffBase, devRef);
208
216
  const changedFiles = getChangedFiles(diffBase, devRef);
209
217
  if (verbose) {
@@ -1,4 +1,4 @@
1
- import { execFileSync, execSync } from 'child_process';
1
+ import { execFileSync } from 'child_process';
2
2
  import { getGitHubConfig } from '../../api/github.js';
3
3
  import { getIssue } from '../../api/issues/index.js';
4
4
  import { getProduct } from '../../api/products.js';
@@ -6,7 +6,7 @@ import { getBranches } from '../../services/branches.js';
6
6
  import { getPullRequests, } from '../../services/pull-requests.js';
7
7
  import { branchExists, remoteBranchExists, } from '../../utils/git-branch-manager.js';
8
8
  import { buildCredentialArgs } from '../../utils/git-push.js';
9
- import { getRepoForkInfo, } from '../../utils/github-repo-info.js';
9
+ import { getRepoForkInfo, resolveDefaultBranch, } from '../../utils/github-repo-info.js';
10
10
  import { logError, logInfo } from '../../utils/logger.js';
11
11
  /**
12
12
  * Get the dev branch name for an issue
@@ -18,14 +18,16 @@ function getDevBranchName(issueId) {
18
18
  * Get the HEAD SHA of a branch
19
19
  */
20
20
  function getBranchHeadSha(branchName) {
21
- return execSync(`git rev-parse ${branchName}`, { encoding: 'utf-8' }).trim();
21
+ return execFileSync('git', ['rev-parse', branchName], {
22
+ encoding: 'utf-8',
23
+ }).trim();
22
24
  }
23
25
  /**
24
26
  * Get diff stat between two refs
25
27
  */
26
28
  function getDiffStat(baseRef, headRef) {
27
29
  try {
28
- return execSync(`git diff --stat ${baseRef}...${headRef}`, {
30
+ return execFileSync('git', ['diff', '--stat', `${baseRef}...${headRef}`], {
29
31
  encoding: 'utf-8',
30
32
  }).trim();
31
33
  }
@@ -38,9 +40,7 @@ function getDiffStat(baseRef, headRef) {
38
40
  */
39
41
  function getChangedFiles(baseRef, headRef) {
40
42
  try {
41
- const output = execSync(`git diff --name-only ${baseRef}...${headRef}`, {
42
- encoding: 'utf-8',
43
- }).trim();
43
+ const output = execFileSync('git', ['diff', '--name-only', `${baseRef}...${headRef}`], { encoding: 'utf-8' }).trim();
44
44
  if (!output) {
45
45
  return [];
46
46
  }
@@ -53,18 +53,18 @@ function getChangedFiles(baseRef, headRef) {
53
53
  /**
54
54
  * Determine the diff base ref for incremental re-runs
55
55
  * If existing PRs have last_synced_commit, use the earliest one
56
- * Otherwise use origin/main (remote-tracking ref, always up-to-date after fetch)
56
+ * Otherwise use origin/<defaultBranch> (remote-tracking ref, always up-to-date after fetch)
57
57
  */
58
- function determineDiffBaseRef(existingPRs, replaceExisting) {
58
+ function determineDiffBaseRef(existingPRs, defaultBranchRef, replaceExisting) {
59
59
  if (replaceExisting || existingPRs.length === 0) {
60
- return 'origin/main';
60
+ return defaultBranchRef;
61
61
  }
62
62
  // Find the minimum last_synced_commit (earliest sync point)
63
63
  const syncedCommits = existingPRs
64
64
  .map((pr) => pr.last_synced_commit)
65
65
  .filter((c) => c !== null);
66
66
  if (syncedCommits.length === 0) {
67
- return 'origin/main';
67
+ return defaultBranchRef;
68
68
  }
69
69
  // All PRs should have been synced to the same commit
70
70
  // Use the first one (they should all be equal after a successful sync)
@@ -131,12 +131,17 @@ export async function fetchPRSplittingContext(issueId, verbose, replaceExisting)
131
131
  }
132
132
  }
133
133
  }
134
+ // Resolve the repo's default branch (handles non-main defaults and forks).
135
+ // Prefer the GitHub API value (already fetched in forkInfo above); fall back
136
+ // to the local symbolic-ref, then to literal 'main'.
137
+ const defaultBranch = resolveDefaultBranch(forkInfo.defaultBranch);
138
+ const defaultBranchRef = `origin/${defaultBranch}`;
134
139
  // Determine diff range
135
140
  const devRef = localExists ? devBranchName : `origin/${devBranchName}`;
136
- const baseRef = determineDiffBaseRef(existingPullRequests, replaceExisting);
141
+ const baseRef = determineDiffBaseRef(existingPullRequests, defaultBranchRef, replaceExisting);
137
142
  const devBranchHeadSha = getBranchHeadSha(devRef);
138
143
  // Check if there are new changes since last sync
139
- if (baseRef !== 'origin/main' && baseRef === devBranchHeadSha) {
144
+ if (baseRef !== defaultBranchRef && baseRef === devBranchHeadSha) {
140
145
  if (verbose) {
141
146
  logInfo(`No new changes since last sync (HEAD: ${devBranchHeadSha})`);
142
147
  }
@@ -7,8 +7,20 @@ export interface RepoForkInfo {
7
7
  owner: string;
8
8
  repo: string;
9
9
  };
10
+ defaultBranch?: string;
10
11
  }
11
12
  /**
12
- * Detect if a repository is a fork and get upstream info
13
+ * Detect if a repository is a fork and get upstream + default branch info
13
14
  */
14
15
  export declare function getRepoForkInfo(token: string, owner: string, repo: string): Promise<RepoForkInfo>;
16
+ /**
17
+ * Resolve the repo's default branch with a multi-tier fallback:
18
+ * 1. `apiValue` from GitHub API (most authoritative)
19
+ * 2. local `git symbolic-ref refs/remotes/origin/HEAD`
20
+ * 3. literal `'main'`
21
+ *
22
+ * Use this anywhere we need to diff/merge against the upstream default branch.
23
+ * The hardcoded `origin/main` assumption breaks for repos whose default is
24
+ * `master`, `develop`, etc., or for forks that inherited a non-`main` default.
25
+ */
26
+ export declare function resolveDefaultBranch(apiValue?: string): string;
@@ -2,18 +2,44 @@
2
2
  * GitHub repository information utilities
3
3
  */
4
4
  import { Octokit } from '@octokit/rest';
5
+ import { execFileSync } from 'child_process';
5
6
  /**
6
- * Detect if a repository is a fork and get upstream info
7
+ * Detect if a repository is a fork and get upstream + default branch info
7
8
  */
8
9
  export async function getRepoForkInfo(token, owner, repo) {
9
10
  const octokit = new Octokit({ auth: token });
10
11
  const { data } = await octokit.repos.get({ owner, repo });
12
+ const result = {
13
+ isFork: false,
14
+ defaultBranch: data.default_branch,
15
+ };
11
16
  if (data.fork && data.parent) {
12
17
  const [upstreamOwner, upstreamRepo] = data.parent.full_name.split('/');
13
- return {
14
- isFork: true,
15
- upstream: { owner: upstreamOwner, repo: upstreamRepo },
16
- };
18
+ result.isFork = true;
19
+ result.upstream = { owner: upstreamOwner, repo: upstreamRepo };
20
+ }
21
+ return result;
22
+ }
23
+ /**
24
+ * Resolve the repo's default branch with a multi-tier fallback:
25
+ * 1. `apiValue` from GitHub API (most authoritative)
26
+ * 2. local `git symbolic-ref refs/remotes/origin/HEAD`
27
+ * 3. literal `'main'`
28
+ *
29
+ * Use this anywhere we need to diff/merge against the upstream default branch.
30
+ * The hardcoded `origin/main` assumption breaks for repos whose default is
31
+ * `master`, `develop`, etc., or for forks that inherited a non-`main` default.
32
+ */
33
+ export function resolveDefaultBranch(apiValue) {
34
+ if (apiValue) {
35
+ return apiValue;
36
+ }
37
+ try {
38
+ const ref = execFileSync('git', ['symbolic-ref', '--short', 'refs/remotes/origin/HEAD'], { encoding: 'utf-8', stdio: 'pipe' }).trim();
39
+ const stripped = ref.replace(/^origin\//, '');
40
+ return stripped || 'main';
41
+ }
42
+ catch {
43
+ return 'main';
17
44
  }
18
- return { isFork: false };
19
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"
package/vitest.config.ts CHANGED
@@ -11,6 +11,8 @@ export default defineConfig({
11
11
  'src/phases/code-refine-verification/__tests__/**/*.test.ts',
12
12
  'src/phases/find-bugs/__tests__/**/*.test.ts',
13
13
  'src/phases/find-features/__tests__/**/*.test.ts',
14
+ 'src/phases/find-smells/__tests__/**/*.test.ts',
15
+ 'src/commands/find-smells/__tests__/**/*.test.ts',
14
16
  ],
15
17
  exclude: ['dist/**', 'node_modules/**'],
16
18
  environment: 'node',
@@ -1,7 +0,0 @@
1
- /**
2
- * Unit tests for app-store API helpers.
3
- *
4
- * Tests the parsing/extraction logic that getAppStorePrimaryLocale uses
5
- * to parse MCP response data.
6
- */
7
- export {};
@@ -1,60 +0,0 @@
1
- /**
2
- * Unit tests for app-store API helpers.
3
- *
4
- * Tests the parsing/extraction logic that getAppStorePrimaryLocale uses
5
- * to parse MCP response data.
6
- */
7
- import assert from 'node:assert';
8
- import { describe, it } from 'node:test';
9
- /**
10
- * Extracts locale from an MCP response — mirrors the parsing logic
11
- * inside getAppStorePrimaryLocale without requiring network mocks.
12
- */
13
- function parseLocaleFromMcpResponse(result) {
14
- const text = result?.content?.[0]?.text || '{}';
15
- const parsed = JSON.parse(text);
16
- return parsed.locale || null;
17
- }
18
- void describe('parseLocaleFromMcpResponse', () => {
19
- void it('should return locale string when present', () => {
20
- const result = { content: [{ text: '{"locale":"en-US"}' }] };
21
- assert.strictEqual(parseLocaleFromMcpResponse(result), 'en-US');
22
- });
23
- void it('should return locale for non-English locales', () => {
24
- assert.strictEqual(parseLocaleFromMcpResponse({ content: [{ text: '{"locale":"ja"}' }] }), 'ja');
25
- assert.strictEqual(parseLocaleFromMcpResponse({
26
- content: [{ text: '{"locale":"zh-Hans"}' }],
27
- }), 'zh-Hans');
28
- assert.strictEqual(parseLocaleFromMcpResponse({ content: [{ text: '{"locale":"fr-FR"}' }] }), 'fr-FR');
29
- });
30
- void it('should return null when locale is null', () => {
31
- const result = { content: [{ text: '{"locale":null}' }] };
32
- assert.strictEqual(parseLocaleFromMcpResponse(result), null);
33
- });
34
- void it('should return null when locale is empty string', () => {
35
- const result = { content: [{ text: '{"locale":""}' }] };
36
- assert.strictEqual(parseLocaleFromMcpResponse(result), null);
37
- });
38
- void it('should return null when content array is empty', () => {
39
- const result = { content: [] };
40
- assert.strictEqual(parseLocaleFromMcpResponse(result), null);
41
- });
42
- void it('should return null when content field is missing', () => {
43
- assert.strictEqual(parseLocaleFromMcpResponse({}), null);
44
- });
45
- void it('should return null when result is null', () => {
46
- assert.strictEqual(parseLocaleFromMcpResponse(null), null);
47
- });
48
- void it('should return null when result is undefined', () => {
49
- assert.strictEqual(parseLocaleFromMcpResponse(undefined), null);
50
- });
51
- void it('should throw when text is not valid JSON', () => {
52
- assert.throws(() => {
53
- parseLocaleFromMcpResponse({ content: [{ text: 'not json' }] });
54
- });
55
- });
56
- void it('should return null when text field is missing', () => {
57
- const result = { content: [{}] };
58
- assert.strictEqual(parseLocaleFromMcpResponse(result), null);
59
- });
60
- });
@@ -1,11 +0,0 @@
1
- /**
2
- * Tests for the MCP handler logic used in intelligence.ts.
3
- *
4
- * Since the actual MCP handlers run in Deno (supabase edge functions),
5
- * we test the equivalent logic at the API wrapper level by mocking
6
- * callMcpEndpoint responses. This verifies:
7
- * - Response parsing for all entity types
8
- * - Error handling for failed/empty responses
9
- * - Correct parameter formatting
10
- */
11
- export {};