edsger 0.26.3 → 0.26.4

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.
@@ -25,9 +25,11 @@ The \`dev/\` prefix indicates this is a development branch. A PR will be created
25
25
 
26
26
  Feature ID for this feature: ${featureId}
27
27
 
28
- ## Output Format
28
+ ## CRITICAL - Output Format
29
29
 
30
- You MUST respond with a valid JSON object in this exact format:
30
+ You MUST return ONLY a JSON object with your branch planning results.
31
+ Do NOT include any explanatory text, summary, or commentary before or after the JSON.
32
+ Return ONLY the JSON in this EXACT format:
31
33
 
32
34
  \`\`\`json
33
35
  {
@@ -269,78 +269,48 @@ async function executeAgentQuery(userPrompt, systemPrompt, config, verbose) {
269
269
  };
270
270
  }
271
271
  /**
272
- * Parse JSON response from Claude
272
+ * Extract a balanced JSON object starting from a given index using brace counting.
273
+ * Handles nested braces and strings with escaped characters correctly.
273
274
  */
274
- function parseJsonResponse(responseText, verbose) {
275
- // Try to extract JSON from markdown code block
276
- const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
277
- if (jsonBlockMatch) {
278
- try {
279
- const parsed = JSON.parse(jsonBlockMatch[1]);
280
- if (parsed.pr_splitting_result) {
281
- return {
282
- pullRequests: parsed.pr_splitting_result.pull_requests || [],
283
- summary: parsed.pr_splitting_result.summary,
284
- rationale: parsed.pr_splitting_result.rationale,
285
- };
286
- }
287
- if (parsed.pull_requests) {
288
- return {
289
- pullRequests: parsed.pull_requests,
290
- summary: parsed.summary,
291
- rationale: parsed.rationale,
292
- };
293
- }
294
- return parsed;
275
+ function extractBalancedJson(text, startIndex) {
276
+ if (text[startIndex] !== '{')
277
+ return null;
278
+ let braceCount = 0;
279
+ let inString = false;
280
+ let escape = false;
281
+ for (let i = startIndex; i < text.length; i++) {
282
+ const ch = text[i];
283
+ if (escape) {
284
+ escape = false;
285
+ continue;
295
286
  }
296
- catch (e) {
297
- if (verbose) {
298
- logError(`Failed to parse JSON from code block: ${e}`);
299
- }
287
+ if (ch === '\\' && inString) {
288
+ escape = true;
289
+ continue;
300
290
  }
301
- }
302
- // Try to find JSON object containing "pr_splitting_result"
303
- const resultMatch = responseText.match(/\{[\s\S]*"pr_splitting_result"[\s\S]*\}/);
304
- if (resultMatch) {
305
- try {
306
- let braceCount = 0;
307
- let startIndex = -1;
308
- let endIndex = -1;
309
- for (let i = 0; i < responseText.length; i++) {
310
- if (responseText[i] === '{') {
311
- if (startIndex === -1)
312
- startIndex = i;
313
- braceCount++;
314
- }
315
- else if (responseText[i] === '}') {
316
- braceCount--;
317
- if (braceCount === 0 && startIndex !== -1) {
318
- endIndex = i;
319
- break;
320
- }
321
- }
322
- }
323
- if (startIndex !== -1 && endIndex !== -1) {
324
- const jsonStr = responseText.substring(startIndex, endIndex + 1);
325
- const parsed = JSON.parse(jsonStr);
326
- if (parsed.pr_splitting_result) {
327
- return {
328
- pullRequests: parsed.pr_splitting_result.pull_requests || [],
329
- summary: parsed.pr_splitting_result.summary,
330
- rationale: parsed.pr_splitting_result.rationale,
331
- };
332
- }
333
- }
291
+ if (ch === '"') {
292
+ inString = !inString;
293
+ continue;
334
294
  }
335
- catch (e) {
336
- if (verbose) {
337
- logError(`Failed to extract JSON from response: ${e}`);
295
+ if (inString)
296
+ continue;
297
+ if (ch === '{')
298
+ braceCount++;
299
+ else if (ch === '}') {
300
+ braceCount--;
301
+ if (braceCount === 0) {
302
+ return text.substring(startIndex, i + 1);
338
303
  }
339
304
  }
340
305
  }
341
- // Try to parse the entire response as JSON
306
+ return null;
307
+ }
308
+ /**
309
+ * Try to parse a JSON string and extract PR splitting result
310
+ */
311
+ function tryParsePRResult(jsonStr) {
342
312
  try {
343
- const parsed = JSON.parse(responseText);
313
+ const parsed = JSON.parse(jsonStr);
344
314
  if (parsed.pr_splitting_result) {
345
315
  return {
346
316
  pullRequests: parsed.pr_splitting_result.pull_requests || [],
@@ -355,12 +325,73 @@ function parseJsonResponse(responseText, verbose) {
355
325
  rationale: parsed.rationale,
356
326
  };
357
327
  }
328
+ return null;
329
+ }
330
+ catch {
331
+ return null;
332
+ }
333
+ }
334
+ /**
335
+ * Parse JSON response from Claude
336
+ * Handles: JSON in code blocks, raw JSON mixed with markdown, pure JSON
337
+ */
338
+ function parseJsonResponse(responseText, verbose) {
339
+ // Strategy 1: Find JSON code blocks and try each one (handles multiple code blocks)
340
+ const codeBlockRegex = /```(?:json)?\s*\n([\s\S]*?)\n\s*```/g;
341
+ let match;
342
+ while ((match = codeBlockRegex.exec(responseText)) !== null) {
343
+ const content = match[1].trim();
344
+ if (content.startsWith('{')) {
345
+ const result = tryParsePRResult(content);
346
+ if (result && result.pullRequests.length > 0) {
347
+ return result;
348
+ }
349
+ }
358
350
  }
359
- catch (e) {
360
- if (verbose) {
361
- logError(`Failed to parse response as JSON: ${e}`);
351
+ // Strategy 2: Find "pr_splitting_result" and extract the enclosing JSON object
352
+ // Walk backwards from the keyword to find the outermost opening brace
353
+ const keyword = '"pr_splitting_result"';
354
+ const keywordIndex = responseText.indexOf(keyword);
355
+ if (keywordIndex !== -1) {
356
+ // Find the opening { before the keyword
357
+ for (let i = keywordIndex - 1; i >= 0; i--) {
358
+ if (responseText[i] === '{') {
359
+ const jsonStr = extractBalancedJson(responseText, i);
360
+ if (jsonStr) {
361
+ const result = tryParsePRResult(jsonStr);
362
+ if (result && result.pullRequests.length > 0) {
363
+ return result;
364
+ }
365
+ }
366
+ break;
367
+ }
362
368
  }
363
369
  }
370
+ // Strategy 3: Find any { that starts a JSON object with "pull_requests" key
371
+ const pullRequestsKeyword = '"pull_requests"';
372
+ const prIndex = responseText.indexOf(pullRequestsKeyword);
373
+ if (prIndex !== -1) {
374
+ for (let i = prIndex - 1; i >= 0; i--) {
375
+ if (responseText[i] === '{') {
376
+ const jsonStr = extractBalancedJson(responseText, i);
377
+ if (jsonStr) {
378
+ const result = tryParsePRResult(jsonStr);
379
+ if (result && result.pullRequests.length > 0) {
380
+ return result;
381
+ }
382
+ }
383
+ break;
384
+ }
385
+ }
386
+ }
387
+ // Strategy 4: Try to parse the entire response as JSON
388
+ const result = tryParsePRResult(responseText.trim());
389
+ if (result) {
390
+ return result;
391
+ }
392
+ if (verbose) {
393
+ logError(`Failed to extract PR splitting JSON from response (length: ${responseText.length})`);
394
+ }
364
395
  return null;
365
396
  }
366
397
  // Re-export types and functions for external use
@@ -30,9 +30,11 @@ Branch names MUST follow this pattern:
30
30
  - \`pr/${featureId}/2-{short-description}\`
31
31
  - etc.
32
32
 
33
- ## Output Format
33
+ ## CRITICAL - Output Format
34
34
 
35
- You MUST respond with a valid JSON object in this exact format:
35
+ You MUST return ONLY a JSON object with your PR splitting results.
36
+ Do NOT include any explanatory text, summary, or commentary before or after the JSON.
37
+ Return ONLY the JSON in this EXACT format:
36
38
 
37
39
  \`\`\`json
38
40
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.26.3",
3
+ "version": "0.26.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"