edsger 0.45.1 → 0.46.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.
@@ -130,7 +130,9 @@ 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) => {
133
+ export const createPhaseRunner = (phaseConfig) =>
134
+ // eslint-disable-next-line complexity
135
+ async (options, config) => {
134
136
  const { featureId, verbose } = options;
135
137
  const { name, execute } = phaseConfig;
136
138
  // Track phase duration for logging
@@ -36,9 +36,8 @@ 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
39
  // eslint-disable-next-line complexity
41
- ) => {
40
+ export const runPipelineByMode = async (options, config, executionMode) => {
42
41
  const { featureId, verbose } = options;
43
42
  if (verbose) {
44
43
  logInfo(`🚀 Starting pipeline with mode: ${executionMode} for feature: ${featureId}`);
@@ -13,9 +13,8 @@ function truncateKeywords(keywords) {
13
13
  }
14
14
  return keywords.slice(0, 100).replace(/,[^,]*$/, '');
15
15
  }
16
- export const generateAppStoreAssets = async (options, config
17
16
  // eslint-disable-next-line complexity
18
- ) => {
17
+ export const generateAppStoreAssets = async (options, config) => {
19
18
  const { productId, targetStore, screenshotsOnly, listingsOnly, verbose } = options;
20
19
  if (verbose) {
21
20
  logInfo(`Starting app store generation for product: ${productId}`);
@@ -116,9 +116,8 @@ async function persistBranches(sortedBranches, featureId, verbose) {
116
116
  }
117
117
  }
118
118
  }
119
- export const planFeatureBranches = async (options, config
120
119
  // eslint-disable-next-line complexity
121
- ) => {
120
+ export const planFeatureBranches = async (options, config) => {
122
121
  const { featureId, verbose, replaceExisting } = options;
123
122
  if (verbose) {
124
123
  logInfo(`Starting branch planning for feature ID: ${featureId}`);
@@ -32,9 +32,8 @@ async function* prompt(bugFixPrompt) {
32
32
  setTimeout(res, 10000);
33
33
  });
34
34
  }
35
- export const fixTestFailures = async (options, config
36
35
  // eslint-disable-next-line complexity
37
- ) => {
36
+ export const fixTestFailures = async (options, config) => {
38
37
  const { featureId, testErrors, attemptNumber = 1, verbose } = options;
39
38
  if (verbose) {
40
39
  logInfo(`Starting bug fixing for feature ID: ${featureId} (Attempt ${attemptNumber})`);
@@ -170,9 +170,8 @@ message, lastAssistantResponse, featureId, verbose
170
170
  }
171
171
  return null;
172
172
  }
173
- export const implementFeatureCode = async (options, config, checklistContext
174
173
  // eslint-disable-next-line complexity
175
- ) => {
174
+ export const implementFeatureCode = async (options, config, checklistContext) => {
176
175
  const { featureId, verbose, baseBranch = 'main' } = options;
177
176
  if (verbose) {
178
177
  logInfo(`Starting code implementation for feature ID: ${featureId}`);
@@ -41,9 +41,8 @@ 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
44
  // eslint-disable-next-line complexity
46
- ) => {
45
+ export const refineCodeFromPRFeedback = async (options, config, checklistContext) => {
47
46
  const { featureId, githubToken, verbose } = options;
48
47
  if (verbose) {
49
48
  logInfo(`Starting code refine for feature ID: ${featureId}`);
@@ -67,9 +67,8 @@ baseBranchInfo, baseBranchForRebase, originalBaseBranchForRebase, verbose) {
67
67
  /**
68
68
  * Main code review function
69
69
  */
70
- export const reviewPullRequest = async (options, config, checklistContext
71
70
  // eslint-disable-next-line complexity
72
- ) => {
71
+ export const reviewPullRequest = async (options, config, checklistContext) => {
73
72
  const { featureId, githubToken, verbose } = options;
74
73
  if (verbose) {
75
74
  logInfo(`Starting code review for feature ID: ${featureId}`);
@@ -31,9 +31,8 @@ async function* prompt(testingPrompt) {
31
31
  setTimeout(res, 10000);
32
32
  });
33
33
  }
34
- export const writeCodeTests = async (options, config
35
34
  // eslint-disable-next-line complexity
36
- ) => {
35
+ export const writeCodeTests = async (options, config) => {
37
36
  const { featureId, verbose } = options;
38
37
  if (verbose) {
39
38
  logInfo(`Starting code testing phase for feature ID: ${featureId}`);
@@ -6,9 +6,8 @@ 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
9
  // eslint-disable-next-line complexity
11
- ) => {
10
+ export const analyseFeature = async (options, config, checklistContext) => {
12
11
  const { featureId, verbose } = options;
13
12
  if (verbose) {
14
13
  logInfo(`Starting feature analysis for feature ID: ${featureId}`);
@@ -326,9 +326,8 @@ async function saveTestResults(featureId, testStatus, structuredTestResult, last
326
326
  }
327
327
  return testReportResult;
328
328
  }
329
- export const runFunctionalTesting = async (options, config, checklistContext
330
329
  // eslint-disable-next-line complexity
331
- ) => {
330
+ export const runFunctionalTesting = async (options, config, checklistContext) => {
332
331
  const { featureId, verbose } = options;
333
332
  if (verbose) {
334
333
  logInfo(`Starting functional testing for feature ID: ${featureId}`);
@@ -49,9 +49,8 @@ contentSuggestions) {
49
49
  }
50
50
  return plans;
51
51
  }
52
- export const analyseGrowth = async (options, config
53
52
  // eslint-disable-next-line complexity
54
- ) => {
53
+ export const analyseGrowth = async (options, config) => {
55
54
  const { productId, verbose, guidance, analysisId } = options;
56
55
  if (verbose) {
57
56
  logInfo(`Starting growth analysis for product ID: ${productId}`);
@@ -33,6 +33,7 @@ async function* prompt(executionPrompt) {
33
33
  *
34
34
  * GitHub PRs are created manually by the user via compare URLs in the UI.
35
35
  */
36
+ // eslint-disable-next-line complexity
36
37
  export const executeFeaturePRs = async (options, config) => {
37
38
  const { featureId, verbose } = options;
38
39
  if (verbose) {
@@ -27,9 +27,8 @@ async function* prompt(analysisPrompt) {
27
27
  * then uses AI to produce a PR split plan saved to the database.
28
28
  * Human review is expected before running the pr-execution phase.
29
29
  */
30
- export const splitFeatureIntoPRs = async (options, config
31
30
  // eslint-disable-next-line complexity
32
- ) => {
31
+ export const splitFeatureIntoPRs = async (options, config) => {
33
32
  const { featureId, verbose, replaceExisting } = options;
34
33
  if (verbose) {
35
34
  logInfo(`Starting PR splitting for feature ID: ${featureId}`);
@@ -69,7 +69,8 @@ function isLockStale(lockPath) {
69
69
  const raw = readFileSync(lockPath, 'utf-8').trim();
70
70
  let ts;
71
71
  try {
72
- ts = JSON.parse(raw).ts;
72
+ ;
73
+ ({ ts } = JSON.parse(raw));
73
74
  }
74
75
  catch {
75
76
  ts = Number(raw);
@@ -134,6 +135,7 @@ function runSheetIsFresh(existing, template, tag) {
134
135
  }
135
136
  return true;
136
137
  }
138
+ // eslint-disable-next-line complexity -- orchestration with cache, clone, lock, render
137
139
  export async function runRunSheet(options) {
138
140
  const { releaseId, force, verbose } = options;
139
141
  let release;
@@ -170,7 +172,7 @@ export async function runRunSheet(options) {
170
172
  // Short-circuit cache: identical template + tag, no prior clone error.
171
173
  if (!force) {
172
174
  const existing = await getRunSheetByRelease(releaseId, verbose).catch(() => null);
173
- if (runSheetIsFresh(existing, template, release.tag)) {
175
+ if (existing && runSheetIsFresh(existing, template, release.tag)) {
174
176
  logInfo(`Run sheet for ${release.tag} is already up-to-date (template unchanged).`);
175
177
  return {
176
178
  status: 'cached',
@@ -193,10 +195,8 @@ export async function runRunSheet(options) {
193
195
  let commits = '';
194
196
  let commitsTruncated = false;
195
197
  let lockPath = null;
196
- if (repoConfigured) {
197
- const owner = gh.owner;
198
- const repo = gh.repo;
199
- const token = gh.token;
198
+ if (repoConfigured && gh.owner && gh.repo && gh.token) {
199
+ const { owner, repo, token } = gh;
200
200
  // Serialise concurrent CLI invocations for the *same release* by
201
201
  // taking a file lock next to the clone dir. Different releases get
202
202
  // different lock files (per-release workspace).
@@ -250,7 +250,7 @@ export async function runRunSheet(options) {
250
250
  previous_tag: release.previous_tag,
251
251
  previous_published_at: release.previous_published_at,
252
252
  diff_summary: release.diff_summary,
253
- diff_stats: (release.diff_stats ?? {}),
253
+ diff_stats: release.diff_stats ?? {},
254
254
  }, repoDir, commits);
255
255
  try {
256
256
  const saved = await upsertRunSheet({
@@ -38,7 +38,8 @@ export async function safeReadRepoFile(repoDir, relPath, remainingBudget) {
38
38
  if (!stat.isFile()) {
39
39
  return { content: null, bytes: 0, reason: 'not a file' };
40
40
  }
41
- size = stat.size;
41
+ ;
42
+ ({ size } = stat);
42
43
  }
43
44
  catch {
44
45
  return { content: null, bytes: 0, reason: 'not found' };
@@ -72,6 +73,7 @@ export async function safeReadRepoFile(repoDir, relPath, remainingBudget) {
72
73
  export function normalizeTemplate(template) {
73
74
  return template.replace(/\{\{([^}]*)\}\}/g, (_match, inner) => `{{${inner.replace(/\\([_.\-\\/])/g, '$1')}}}`);
74
75
  }
76
+ // eslint-disable-next-line complexity -- template expansion branches per placeholder type
75
77
  export async function renderTemplate(template, product, release, repoDir, commits) {
76
78
  const missing = [];
77
79
  const stats = release.diff_stats ?? {};
@@ -66,11 +66,9 @@ export async function executeSmokeTestQuery(systemPrompt, userPrompt, config, ve
66
66
  lastAssistant += `${content.text}\n`;
67
67
  logDebug(content.text, verbose);
68
68
  }
69
- else if (content.type === 'tool_use') {
69
+ else if (content.type === 'tool_use' && verbose) {
70
70
  const desc = content.input?.description || content.input?.command || 'Running...';
71
- if (verbose) {
72
- logInfo(`[Turn ${turnCount}] ${content.name}: ${typeof desc === 'string' ? desc.slice(0, 120) : 'Running...'}`);
73
- }
71
+ logInfo(`[Turn ${turnCount}] ${content.name}: ${typeof desc === 'string' ? desc.slice(0, 120) : 'Running...'}`);
74
72
  }
75
73
  }
76
74
  }
@@ -17,7 +17,15 @@ import { buildSmokeTestUserPrompt, createSmokeTestSystemPrompt, } from './prompt
17
17
  function isSnapshotRelease(release) {
18
18
  return !release.published_at && !release.url;
19
19
  }
20
- // eslint-disable-next-line complexity -- orchestration spans GitHub + DB + AI
20
+ function describeReleaseContext(release, isSnapshot) {
21
+ if (isSnapshot) {
22
+ return ` (snapshot ahead of ${release.previous_tag ?? 'first release'})`;
23
+ }
24
+ if (release.previous_tag) {
25
+ return ` (vs ${release.previous_tag})`;
26
+ }
27
+ return ' (first release)';
28
+ }
21
29
  export async function runSmokeTest(options, config) {
22
30
  const { releaseId, verbose } = options;
23
31
  if (verbose) {
@@ -216,11 +224,8 @@ async function runSmokeTestInner(ctx) {
216
224
  diff_stats: diffStats,
217
225
  generation_error: null,
218
226
  }, verbose);
219
- logSuccess(`Generated ${casesCount} smoke-test cases for ${release.tag}${isSnapshot
220
- ? ` (snapshot ahead of ${release.previous_tag ?? 'first release'})`
221
- : release.previous_tag
222
- ? ` (vs ${release.previous_tag})`
223
- : ' (first release)'}`);
227
+ const suffix = describeReleaseContext(release, isSnapshot);
228
+ logSuccess(`Generated ${casesCount} smoke-test cases for ${release.tag}${suffix}`);
224
229
  return {
225
230
  status: 'success',
226
231
  releaseId: release.id,
@@ -24,9 +24,8 @@ async function* prompt(analysisPrompt) {
24
24
  setTimeout(res, 10000);
25
25
  });
26
26
  }
27
- export const generateTechnicalDesign = async (options, config, checklistContext
28
27
  // eslint-disable-next-line complexity
29
- ) => {
28
+ export const generateTechnicalDesign = async (options, config, checklistContext) => {
30
29
  const { featureId, verbose } = options;
31
30
  if (verbose) {
32
31
  logInfo(`Starting technical design generation for feature ID: ${featureId}`);
@@ -6,9 +6,8 @@ import { executeTestCasesAnalysisQuery, parseAnalysisResult } from './agent.js';
6
6
  import { prepareTestCasesAnalysisContext } from './context.js';
7
7
  import { buildTestCasesAnalysisResult, deleteSpecificTestCases, deleteTestCaseArtifacts, getAllDraftTestCaseIds, resetReadyTestCasesToDraft, saveTestCasesAsDraft, updateTestCasesToReady, } from './outcome.js';
8
8
  import { createTestCasesAnalysisSystemPrompt } from './prompts.js';
9
- export const analyseTestCases = async (options, config, checklistContext
10
9
  // eslint-disable-next-line complexity
11
- ) => {
10
+ export const analyseTestCases = async (options, config, checklistContext) => {
12
11
  const { featureId, verbose } = options;
13
12
  if (verbose) {
14
13
  logInfo(`Starting test cases analysis for feature ID: ${featureId}`);
@@ -6,9 +6,8 @@ import { executeUserStoriesAnalysisQuery, parseAnalysisResult, } from './agent.j
6
6
  import { prepareUserStoriesAnalysisContext } from './context.js';
7
7
  import { buildUserStoriesAnalysisResult, deleteSpecificUserStories, deleteUserStoryArtifacts, getAllDraftUserStoryIds, resetReadyUserStoriesToDraft, saveUserStoriesAsDraft, updateUserStoriesToReady, } from './outcome.js';
8
8
  import { createUserStoriesAnalysisSystemPrompt } from './prompts.js';
9
- export const analyseUserStories = async (options, config, checklistContext
10
9
  // eslint-disable-next-line complexity
11
- ) => {
10
+ export const analyseUserStories = async (options, config, checklistContext) => {
12
11
  const { featureId, verbose } = options;
13
12
  if (verbose) {
14
13
  logInfo(`Starting user stories analysis for feature ID: ${featureId}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.45.1",
3
+ "version": "0.46.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"