wiggum-cli 0.13.1 → 0.14.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.
@@ -0,0 +1,84 @@
1
+ /**
2
+ * PR and Issue utilities for enhanced run summary.
3
+ */
4
+ import { execFileSync } from 'node:child_process';
5
+ import { logger } from '../../utils/logger.js';
6
+ /**
7
+ * Get PR information for a branch.
8
+ *
9
+ * @param projectRoot - Root directory of the git repository
10
+ * @param branchName - Branch name to look up
11
+ * @returns PR info object, or null if no PR exists for this branch
12
+ * @throws When gh CLI is unavailable or the command fails
13
+ */
14
+ export function getPrForBranch(projectRoot, branchName) {
15
+ // Use gh pr list to find PR for this branch
16
+ const output = execFileSync('gh', ['pr', 'list', '--head', branchName, '--state', 'all', '--json', 'number,url,state,title', '--limit', '1'], {
17
+ cwd: projectRoot,
18
+ encoding: 'utf-8',
19
+ timeout: 10_000,
20
+ }).trim();
21
+ if (!output) {
22
+ return null;
23
+ }
24
+ const prs = JSON.parse(output);
25
+ if (!Array.isArray(prs) || prs.length === 0) {
26
+ return null;
27
+ }
28
+ const pr = prs[0];
29
+ return {
30
+ number: pr.number,
31
+ url: pr.url,
32
+ state: pr.state,
33
+ title: pr.title,
34
+ };
35
+ }
36
+ /**
37
+ * Get linked issue for a branch by parsing the PR body for closing keywords
38
+ * (Closes/Fixes/Resolves #N).
39
+ *
40
+ * @param projectRoot - Root directory of the git repository
41
+ * @param branchName - Branch name to look up
42
+ * @param prInfo - Optional pre-fetched PR info to avoid redundant gh call
43
+ * @returns Issue info object, or null if not found or gh not available
44
+ */
45
+ export function getLinkedIssue(projectRoot, branchName, prInfo) {
46
+ try {
47
+ // Use provided PR info or fetch it
48
+ const pr = prInfo !== undefined ? prInfo : getPrForBranch(projectRoot, branchName);
49
+ if (!pr) {
50
+ return null;
51
+ }
52
+ // Get the PR body to look for issue references
53
+ const prBody = execFileSync('gh', ['pr', 'view', String(pr.number), '--json', 'body'], {
54
+ cwd: projectRoot,
55
+ encoding: 'utf-8',
56
+ timeout: 10_000,
57
+ }).trim();
58
+ const prData = JSON.parse(prBody);
59
+ const body = prData.body || '';
60
+ // Look for issue references in PR body (e.g., "Closes #123", "Fixes #456")
61
+ const issueMatch = body.match(/(?:closes|fixes|resolves)\s+#(\d+)/i);
62
+ if (!issueMatch) {
63
+ return null;
64
+ }
65
+ const issueNumber = parseInt(issueMatch[1], 10);
66
+ // Fetch the issue details
67
+ const issueOutput = execFileSync('gh', ['issue', 'view', String(issueNumber), '--json', 'number,url,state,title'], {
68
+ cwd: projectRoot,
69
+ encoding: 'utf-8',
70
+ timeout: 10_000,
71
+ }).trim();
72
+ const issue = JSON.parse(issueOutput);
73
+ return {
74
+ number: issue.number,
75
+ url: issue.url,
76
+ state: issue.state,
77
+ title: issue.title,
78
+ };
79
+ }
80
+ catch (err) {
81
+ logger.warn(`getLinkedIssue failed: ${err instanceof Error ? err.message : String(err)}`);
82
+ return null;
83
+ }
84
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Summary File Writer
3
+ * Persists enhanced run summaries to JSON files for later retrieval
4
+ */
5
+ import type { RunSummary } from '../tui/screens/RunScreen.js';
6
+ /**
7
+ * Write enhanced run summary to a JSON file in the temp directory
8
+ *
9
+ * @param featureName - The feature name used to construct the file path
10
+ * @param summary - The complete RunSummary object to persist
11
+ * @returns Promise that resolves when the file is written, or rejects on error
12
+ *
13
+ * Uses RALPH_SUMMARY_TMP_DIR environment variable if set, otherwise os.tmpdir().
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * await writeRunSummaryFile('my-feature', {
18
+ * feature: 'my-feature',
19
+ * exitCode: 0,
20
+ * // ... other RunSummary fields
21
+ * });
22
+ * // Writes to: <tmpdir>/ralph-loop-my-feature.summary.json
23
+ * ```
24
+ */
25
+ export declare function writeRunSummaryFile(featureName: string, summary: RunSummary): Promise<void>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Summary File Writer
3
+ * Persists enhanced run summaries to JSON files for later retrieval
4
+ */
5
+ import { writeFile } from 'node:fs/promises';
6
+ import { tmpdir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import { logger } from './logger.js';
9
+ /**
10
+ * Write enhanced run summary to a JSON file in the temp directory
11
+ *
12
+ * @param featureName - The feature name used to construct the file path
13
+ * @param summary - The complete RunSummary object to persist
14
+ * @returns Promise that resolves when the file is written, or rejects on error
15
+ *
16
+ * Uses RALPH_SUMMARY_TMP_DIR environment variable if set, otherwise os.tmpdir().
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * await writeRunSummaryFile('my-feature', {
21
+ * feature: 'my-feature',
22
+ * exitCode: 0,
23
+ * // ... other RunSummary fields
24
+ * });
25
+ * // Writes to: <tmpdir>/ralph-loop-my-feature.summary.json
26
+ * ```
27
+ */
28
+ export async function writeRunSummaryFile(featureName, summary) {
29
+ if (!/^[a-zA-Z0-9_-]+$/.test(featureName)) {
30
+ throw new Error(`Invalid feature name: "${featureName}"`);
31
+ }
32
+ const dir = process.env.RALPH_SUMMARY_TMP_DIR ?? tmpdir();
33
+ const filePath = join(dir, `ralph-loop-${featureName}.summary.json`);
34
+ const jsonContent = JSON.stringify(summary, null, 2);
35
+ await writeFile(filePath, jsonContent, 'utf8');
36
+ logger.debug(`Summary written to ${filePath}`);
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wiggum-cli",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
4
4
  "description": "AI-powered feature development loop CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -105,6 +105,8 @@ TOKENS_FILE="/tmp/ralph-loop-${1}.tokens"
105
105
  CLAUDE_OUTPUT="/tmp/ralph-loop-${1}.output"
106
106
  STATUS_FILE="/tmp/ralph-loop-${1}.status"
107
107
  FINAL_STATUS_FILE="/tmp/ralph-loop-${1}.final"
108
+ PHASES_FILE="/tmp/ralph-loop-${1}.phases"
109
+ BASELINE_FILE="/tmp/ralph-loop-${1}.baseline"
108
110
 
109
111
  # Initialize token tracking
110
112
  init_tokens() {
@@ -139,6 +141,33 @@ parse_and_accumulate_tokens() {
139
141
  # Initialize tokens
140
142
  init_tokens
141
143
 
144
+ # Phase tracking helpers
145
+ write_phase_start() {
146
+ local phase_id="$1"
147
+ local timestamp=$(date +%s)
148
+ echo "${phase_id}|started|${timestamp}|" >> "$PHASES_FILE"
149
+ }
150
+
151
+ write_phase_end() {
152
+ local phase_id="$1"
153
+ local status="$2" # success, failed, skipped
154
+ local timestamp=$(date +%s)
155
+
156
+ # Find the last line for this phase and update it
157
+ if [ -f "$PHASES_FILE" ]; then
158
+ # Get all lines except the last matching phase line
159
+ grep -v "^${phase_id}|started|" "$PHASES_FILE" > "${PHASES_FILE}.tmp" 2>/dev/null || true
160
+ # Find the start timestamp from the original file
161
+ local start_ts=$(grep "^${phase_id}|started|" "$PHASES_FILE" | tail -1 | cut -d'|' -f3)
162
+ # Write updated line
163
+ echo "${phase_id}|${status}|${start_ts}|${timestamp}" >> "${PHASES_FILE}.tmp"
164
+ mv "${PHASES_FILE}.tmp" "$PHASES_FILE"
165
+ fi
166
+ }
167
+
168
+ # Initialize phase tracking
169
+ > "$PHASES_FILE"
170
+
142
171
  FEATURE="${1:?Usage: ./feature-loop.sh <feature-name> [max-iterations] [max-e2e-attempts] [--worktree] [--resume] [--model MODEL]}"
143
172
  MAX_ITERATIONS="${2:-$DEFAULT_MAX_ITERATIONS}"
144
173
  MAX_E2E_ATTEMPTS="${3:-$DEFAULT_MAX_E2E}"
@@ -194,22 +223,42 @@ fi
194
223
  # Create output file for monitoring
195
224
  touch "$CLAUDE_OUTPUT"
196
225
 
226
+ # Record baseline commit
227
+ if git rev-parse --git-dir > /dev/null 2>&1; then
228
+ BASELINE_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "")
229
+ if [ -n "$BASELINE_COMMIT" ]; then
230
+ echo "$BASELINE_COMMIT" > "$BASELINE_FILE"
231
+ echo "Baseline commit: $BASELINE_COMMIT"
232
+ fi
233
+ fi
234
+
197
235
  # Phase 3: Planning (if no implementation plan exists)
198
236
  if [ ! -f "$PLAN_FILE" ]; then
199
237
  echo "======================== PLANNING PHASE ========================"
238
+ write_phase_start "planning"
200
239
  export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
201
240
  cat "$PROMPTS_DIR/PROMPT_feature.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT" || {
202
241
  echo "ERROR: Planning phase failed"
242
+ write_phase_end "planning" "failed"
203
243
  exit 1
204
244
  }
205
245
  parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
246
+ write_phase_end "planning" "success"
247
+ else
248
+ echo "Plan file exists, skipping planning phase"
249
+ write_phase_start "planning"
250
+ write_phase_end "planning" "skipped"
206
251
  fi
207
252
 
208
253
  # Phase 4: Implementation loop
209
254
  echo "======================== IMPLEMENTATION PHASE ========================"
255
+ write_phase_start "implementation"
256
+ IMPL_SUCCESS=true
210
257
  while true; do
211
258
  if [ $ITERATION -ge $MAX_ITERATIONS ]; then
212
259
  echo "Reached max iterations: $MAX_ITERATIONS"
260
+ IMPL_SUCCESS=false
261
+ write_phase_end "implementation" "failed"
213
262
  exit 1
214
263
  fi
215
264
 
@@ -231,13 +280,20 @@ while true; do
231
280
 
232
281
  sleep 2
233
282
  done
283
+ if [ "$IMPL_SUCCESS" = true ]; then
284
+ write_phase_end "implementation" "success"
285
+ fi
234
286
 
235
287
  # Phase 5: E2E Testing
236
288
  echo "======================== E2E TESTING PHASE ========================"
237
289
  E2E_TOTAL=$({ grep "^- \[.\].*E2E:" "$PLAN_FILE" 2>/dev/null || true; } | wc -l | tr -d ' ')
238
290
  if [ "$E2E_TOTAL" -eq 0 ]; then
239
291
  echo "No E2E scenarios defined, skipping E2E phase."
292
+ write_phase_start "e2e_testing"
293
+ write_phase_end "e2e_testing" "skipped"
240
294
  else
295
+ write_phase_start "e2e_testing"
296
+ E2E_SUCCESS=false
241
297
  E2E_ATTEMPT=0
242
298
  while [ $E2E_ATTEMPT -lt $MAX_E2E_ATTEMPTS ]; do
243
299
  E2E_ATTEMPT=$((E2E_ATTEMPT + 1))
@@ -253,6 +309,7 @@ else
253
309
 
254
310
  if [ "$E2E_FAILED" -eq 0 ] && [ "$E2E_PENDING" -eq 0 ]; then
255
311
  echo "All E2E tests passed!"
312
+ E2E_SUCCESS=true
256
313
  break
257
314
  fi
258
315
 
@@ -262,26 +319,46 @@ else
262
319
  parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
263
320
  fi
264
321
  done
322
+
323
+ if [ "$E2E_SUCCESS" = true ]; then
324
+ write_phase_end "e2e_testing" "success"
325
+ else
326
+ write_phase_end "e2e_testing" "failed"
327
+ fi
265
328
  fi
266
329
 
267
330
  # Phase 6: Spec Verification
268
331
  echo "======================== SPEC VERIFICATION PHASE ========================"
332
+ write_phase_start "verification"
269
333
  export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
270
- cat "$PROMPTS_DIR/PROMPT_verify.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT" || true
334
+ VERIFY_STATUS="success"
335
+ if ! cat "$PROMPTS_DIR/PROMPT_verify.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"; then
336
+ VERIFY_STATUS="failed"
337
+ fi
271
338
  parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
339
+ write_phase_end "verification" "$VERIFY_STATUS"
272
340
 
273
341
  # Phase 7: PR and Review
274
342
  echo "======================== PR & REVIEW PHASE ========================"
343
+ write_phase_start "pr_review"
275
344
  export FEATURE APP_DIR SPEC_DIR PROMPTS_DIR
345
+ PR_STATUS="success"
276
346
  if [ "$REVIEW_MODE" = "manual" ]; then
277
- cat "$PROMPTS_DIR/PROMPT_review_manual.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT" || true
347
+ if ! cat "$PROMPTS_DIR/PROMPT_review_manual.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"; then
348
+ PR_STATUS="failed"
349
+ fi
278
350
  else
279
- cat "$PROMPTS_DIR/PROMPT_review_auto.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT" || true
351
+ if ! cat "$PROMPTS_DIR/PROMPT_review_auto.md" | envsubst | $CLAUDE_CMD_OPUS 2>&1 | tee "$CLAUDE_OUTPUT"; then
352
+ PR_STATUS="failed"
353
+ fi
280
354
  fi
281
355
  parse_and_accumulate_tokens "$CLAUDE_OUTPUT"
356
+ write_phase_end "pr_review" "$PR_STATUS"
282
357
 
283
358
  # Persist final status for TUI summaries
284
- echo "$ITERATION|$MAX_ITERATIONS|$(date +%s)|done" > "$FINAL_STATUS_FILE" 2>/dev/null || true
359
+ if ! echo "$ITERATION|$MAX_ITERATIONS|$(date +%s)|done" > "$FINAL_STATUS_FILE"; then
360
+ echo "WARNING: Failed to write final status file: $FINAL_STATUS_FILE" >&2
361
+ fi
285
362
 
286
363
  # Cleanup temp files
287
364
  rm -f "$STATUS_FILE" 2>/dev/null || true