ralph-starter 0.0.1 → 0.1.1-beta.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.
- package/README.md +707 -17
- package/dist/auth/browser.d.ts +10 -0
- package/dist/auth/browser.d.ts.map +1 -0
- package/dist/auth/browser.js +56 -0
- package/dist/auth/browser.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth-server.d.ts +20 -0
- package/dist/auth/oauth-server.d.ts.map +1 -0
- package/dist/auth/oauth-server.js +168 -0
- package/dist/auth/oauth-server.js.map +1 -0
- package/dist/auth/pkce.d.ts +46 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +57 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/providers/base.d.ts +52 -0
- package/dist/auth/providers/base.d.ts.map +1 -0
- package/dist/auth/providers/base.js +70 -0
- package/dist/auth/providers/base.js.map +1 -0
- package/dist/auth/providers/index.d.ts +23 -0
- package/dist/auth/providers/index.d.ts.map +1 -0
- package/dist/auth/providers/index.js +39 -0
- package/dist/auth/providers/index.js.map +1 -0
- package/dist/auth/providers/linear.d.ts +37 -0
- package/dist/auth/providers/linear.d.ts.map +1 -0
- package/dist/auth/providers/linear.js +46 -0
- package/dist/auth/providers/linear.js.map +1 -0
- package/dist/auth/providers/notion.d.ts +36 -0
- package/dist/auth/providers/notion.d.ts.map +1 -0
- package/dist/auth/providers/notion.js +75 -0
- package/dist/auth/providers/notion.js.map +1 -0
- package/dist/auth/providers/todoist.d.ts +29 -0
- package/dist/auth/providers/todoist.d.ts.map +1 -0
- package/dist/auth/providers/todoist.js +40 -0
- package/dist/auth/providers/todoist.js.map +1 -0
- package/dist/automation/git.d.ts +15 -0
- package/dist/automation/git.d.ts.map +1 -0
- package/dist/automation/git.js +73 -0
- package/dist/automation/git.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +192 -19
- package/dist/cli.js.map +1 -0
- package/dist/commands/auth.d.ts +14 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +243 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/check.d.ts +12 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +124 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/config.d.ts +13 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +374 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/init.d.ts +31 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +353 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/integrations.d.ts +17 -0
- package/dist/commands/integrations.d.ts.map +1 -0
- package/dist/commands/integrations.js +193 -0
- package/dist/commands/integrations.js.map +1 -0
- package/dist/commands/plan.d.ts +5 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/plan.js +80 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/run.d.ts +26 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +351 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/scaffold.d.ts +9 -0
- package/dist/commands/scaffold.d.ts.map +1 -0
- package/dist/commands/scaffold.js +128 -0
- package/dist/commands/scaffold.js.map +1 -0
- package/dist/commands/setup.d.ts +12 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +27 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/skill.d.ts +6 -0
- package/dist/commands/skill.d.ts.map +1 -0
- package/dist/commands/skill.js +151 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/commands/source.d.ts +17 -0
- package/dist/commands/source.d.ts.map +1 -0
- package/dist/commands/source.js +173 -0
- package/dist/commands/source.js.map +1 -0
- package/dist/config/manager.d.ts +70 -0
- package/dist/config/manager.d.ts.map +1 -0
- package/dist/config/manager.js +227 -0
- package/dist/config/manager.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -2
- package/dist/index.js.map +1 -0
- package/dist/integrations/_template/auth.d.ts +29 -0
- package/dist/integrations/_template/auth.d.ts.map +1 -0
- package/dist/integrations/_template/auth.js +42 -0
- package/dist/integrations/_template/auth.js.map +1 -0
- package/dist/integrations/_template/index.d.ts +8 -0
- package/dist/integrations/_template/index.d.ts.map +1 -0
- package/dist/integrations/_template/index.js +8 -0
- package/dist/integrations/_template/index.js.map +1 -0
- package/dist/integrations/_template/source.d.ts +34 -0
- package/dist/integrations/_template/source.d.ts.map +1 -0
- package/dist/integrations/_template/source.js +124 -0
- package/dist/integrations/_template/source.js.map +1 -0
- package/dist/integrations/base.d.ts +158 -0
- package/dist/integrations/base.d.ts.map +1 -0
- package/dist/integrations/base.js +109 -0
- package/dist/integrations/base.js.map +1 -0
- package/dist/integrations/github/index.d.ts +8 -0
- package/dist/integrations/github/index.d.ts.map +1 -0
- package/dist/integrations/github/index.js +8 -0
- package/dist/integrations/github/index.js.map +1 -0
- package/dist/integrations/github/source.d.ts +26 -0
- package/dist/integrations/github/source.d.ts.map +1 -0
- package/dist/integrations/github/source.js +190 -0
- package/dist/integrations/github/source.js.map +1 -0
- package/dist/integrations/index.d.ts +37 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +71 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/linear/auth.d.ts +37 -0
- package/dist/integrations/linear/auth.d.ts.map +1 -0
- package/dist/integrations/linear/auth.js +45 -0
- package/dist/integrations/linear/auth.js.map +1 -0
- package/dist/integrations/linear/index.d.ts +9 -0
- package/dist/integrations/linear/index.d.ts.map +1 -0
- package/dist/integrations/linear/index.js +9 -0
- package/dist/integrations/linear/index.js.map +1 -0
- package/dist/integrations/linear/source.d.ts +26 -0
- package/dist/integrations/linear/source.d.ts.map +1 -0
- package/dist/integrations/linear/source.js +248 -0
- package/dist/integrations/linear/source.js.map +1 -0
- package/dist/integrations/notion/index.d.ts +8 -0
- package/dist/integrations/notion/index.d.ts.map +1 -0
- package/dist/integrations/notion/index.js +8 -0
- package/dist/integrations/notion/index.js.map +1 -0
- package/dist/integrations/notion/source.d.ts +53 -0
- package/dist/integrations/notion/source.d.ts.map +1 -0
- package/dist/integrations/notion/source.js +463 -0
- package/dist/integrations/notion/source.js.map +1 -0
- package/dist/llm/api.d.ts +29 -0
- package/dist/llm/api.d.ts.map +1 -0
- package/dist/llm/api.js +152 -0
- package/dist/llm/api.js.map +1 -0
- package/dist/llm/index.d.ts +7 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +7 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/providers.d.ts +24 -0
- package/dist/llm/providers.d.ts.map +1 -0
- package/dist/llm/providers.js +51 -0
- package/dist/llm/providers.js.map +1 -0
- package/dist/loop/__tests__/agents.test.d.ts +2 -0
- package/dist/loop/__tests__/agents.test.d.ts.map +1 -0
- package/dist/loop/__tests__/agents.test.js +183 -0
- package/dist/loop/__tests__/agents.test.js.map +1 -0
- package/dist/loop/__tests__/circuit-breaker.test.d.ts +2 -0
- package/dist/loop/__tests__/circuit-breaker.test.d.ts.map +1 -0
- package/dist/loop/__tests__/circuit-breaker.test.js +161 -0
- package/dist/loop/__tests__/circuit-breaker.test.js.map +1 -0
- package/dist/loop/__tests__/cost-tracker.test.d.ts +2 -0
- package/dist/loop/__tests__/cost-tracker.test.d.ts.map +1 -0
- package/dist/loop/__tests__/cost-tracker.test.js +200 -0
- package/dist/loop/__tests__/cost-tracker.test.js.map +1 -0
- package/dist/loop/__tests__/rate-limiter.test.d.ts +2 -0
- package/dist/loop/__tests__/rate-limiter.test.d.ts.map +1 -0
- package/dist/loop/__tests__/rate-limiter.test.js +198 -0
- package/dist/loop/__tests__/rate-limiter.test.js.map +1 -0
- package/dist/loop/__tests__/validation.test.d.ts +2 -0
- package/dist/loop/__tests__/validation.test.d.ts.map +1 -0
- package/dist/loop/__tests__/validation.test.js +234 -0
- package/dist/loop/__tests__/validation.test.js.map +1 -0
- package/dist/loop/agents.d.ts +26 -0
- package/dist/loop/agents.d.ts.map +1 -0
- package/dist/loop/agents.js +188 -0
- package/dist/loop/agents.js.map +1 -0
- package/dist/loop/circuit-breaker.d.ts +61 -0
- package/dist/loop/circuit-breaker.d.ts.map +1 -0
- package/dist/loop/circuit-breaker.js +143 -0
- package/dist/loop/circuit-breaker.js.map +1 -0
- package/dist/loop/cost-tracker.d.ts +90 -0
- package/dist/loop/cost-tracker.d.ts.map +1 -0
- package/dist/loop/cost-tracker.js +229 -0
- package/dist/loop/cost-tracker.js.map +1 -0
- package/dist/loop/estimator.d.ts +20 -0
- package/dist/loop/estimator.d.ts.map +1 -0
- package/dist/loop/estimator.js +123 -0
- package/dist/loop/estimator.js.map +1 -0
- package/dist/loop/executor.d.ts +44 -0
- package/dist/loop/executor.d.ts.map +1 -0
- package/dist/loop/executor.js +646 -0
- package/dist/loop/executor.js.map +1 -0
- package/dist/loop/progress.d.ts +34 -0
- package/dist/loop/progress.d.ts.map +1 -0
- package/dist/loop/progress.js +186 -0
- package/dist/loop/progress.js.map +1 -0
- package/dist/loop/rate-limiter.d.ts +71 -0
- package/dist/loop/rate-limiter.d.ts.map +1 -0
- package/dist/loop/rate-limiter.js +151 -0
- package/dist/loop/rate-limiter.js.map +1 -0
- package/dist/loop/semantic-analyzer.d.ts +33 -0
- package/dist/loop/semantic-analyzer.d.ts.map +1 -0
- package/dist/loop/semantic-analyzer.js +153 -0
- package/dist/loop/semantic-analyzer.js.map +1 -0
- package/dist/loop/skills.d.ts +29 -0
- package/dist/loop/skills.d.ts.map +1 -0
- package/dist/loop/skills.js +174 -0
- package/dist/loop/skills.js.map +1 -0
- package/dist/loop/step-detector.d.ts +17 -0
- package/dist/loop/step-detector.d.ts.map +1 -0
- package/dist/loop/step-detector.js +280 -0
- package/dist/loop/step-detector.js.map +1 -0
- package/dist/loop/task-counter.d.ts +41 -0
- package/dist/loop/task-counter.d.ts.map +1 -0
- package/dist/loop/task-counter.js +99 -0
- package/dist/loop/task-counter.js.map +1 -0
- package/dist/loop/validation.d.ts +28 -0
- package/dist/loop/validation.d.ts.map +1 -0
- package/dist/loop/validation.js +138 -0
- package/dist/loop/validation.js.map +1 -0
- package/dist/mcp/core/init.d.ts +15 -0
- package/dist/mcp/core/init.d.ts.map +1 -0
- package/dist/mcp/core/init.js +272 -0
- package/dist/mcp/core/init.js.map +1 -0
- package/dist/mcp/core/plan.d.ts +15 -0
- package/dist/mcp/core/plan.d.ts.map +1 -0
- package/dist/mcp/core/plan.js +90 -0
- package/dist/mcp/core/plan.js.map +1 -0
- package/dist/mcp/core/run.d.ts +26 -0
- package/dist/mcp/core/run.d.ts.map +1 -0
- package/dist/mcp/core/run.js +114 -0
- package/dist/mcp/core/run.js.map +1 -0
- package/dist/mcp/prompts.d.ts +10 -0
- package/dist/mcp/prompts.d.ts.map +1 -0
- package/dist/mcp/prompts.js +163 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/resources.d.ts +16 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +114 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +73 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +15 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +316 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/presets/index.d.ts +36 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +236 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/setup/agent-detector.d.ts +57 -0
- package/dist/setup/agent-detector.d.ts.map +1 -0
- package/dist/setup/agent-detector.js +170 -0
- package/dist/setup/agent-detector.js.map +1 -0
- package/dist/setup/index.d.ts +7 -0
- package/dist/setup/index.d.ts.map +1 -0
- package/dist/setup/index.js +7 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/setup/llm-tester.d.ts +33 -0
- package/dist/setup/llm-tester.d.ts.map +1 -0
- package/dist/setup/llm-tester.js +105 -0
- package/dist/setup/llm-tester.js.map +1 -0
- package/dist/setup/wizard.d.ts +19 -0
- package/dist/setup/wizard.d.ts.map +1 -0
- package/dist/setup/wizard.js +313 -0
- package/dist/setup/wizard.js.map +1 -0
- package/dist/sources/__tests__/file.test.d.ts +2 -0
- package/dist/sources/__tests__/file.test.d.ts.map +1 -0
- package/dist/sources/__tests__/file.test.js +126 -0
- package/dist/sources/__tests__/file.test.js.map +1 -0
- package/dist/sources/__tests__/github.test.d.ts +2 -0
- package/dist/sources/__tests__/github.test.d.ts.map +1 -0
- package/dist/sources/__tests__/github.test.js +157 -0
- package/dist/sources/__tests__/github.test.js.map +1 -0
- package/dist/sources/base.d.ts +72 -0
- package/dist/sources/base.d.ts.map +1 -0
- package/dist/sources/base.js +127 -0
- package/dist/sources/base.js.map +1 -0
- package/dist/sources/builtin/file.d.ts +21 -0
- package/dist/sources/builtin/file.d.ts.map +1 -0
- package/dist/sources/builtin/file.js +129 -0
- package/dist/sources/builtin/file.js.map +1 -0
- package/dist/sources/builtin/github-scraper.d.ts +65 -0
- package/dist/sources/builtin/github-scraper.d.ts.map +1 -0
- package/dist/sources/builtin/github-scraper.js +324 -0
- package/dist/sources/builtin/github-scraper.js.map +1 -0
- package/dist/sources/builtin/pdf.d.ts +24 -0
- package/dist/sources/builtin/pdf.d.ts.map +1 -0
- package/dist/sources/builtin/pdf.js +174 -0
- package/dist/sources/builtin/pdf.js.map +1 -0
- package/dist/sources/builtin/url.d.ts +47 -0
- package/dist/sources/builtin/url.d.ts.map +1 -0
- package/dist/sources/builtin/url.js +429 -0
- package/dist/sources/builtin/url.js.map +1 -0
- package/dist/sources/config.d.ts +72 -0
- package/dist/sources/config.d.ts.map +1 -0
- package/dist/sources/config.js +215 -0
- package/dist/sources/config.js.map +1 -0
- package/dist/sources/index.d.ts +47 -0
- package/dist/sources/index.d.ts.map +1 -0
- package/dist/sources/index.js +210 -0
- package/dist/sources/index.js.map +1 -0
- package/dist/sources/integrations/github.d.ts +24 -0
- package/dist/sources/integrations/github.d.ts.map +1 -0
- package/dist/sources/integrations/github.js +193 -0
- package/dist/sources/integrations/github.js.map +1 -0
- package/dist/sources/integrations/linear.d.ts +18 -0
- package/dist/sources/integrations/linear.d.ts.map +1 -0
- package/dist/sources/integrations/linear.js +197 -0
- package/dist/sources/integrations/linear.js.map +1 -0
- package/dist/sources/integrations/notion.d.ts +39 -0
- package/dist/sources/integrations/notion.d.ts.map +1 -0
- package/dist/sources/integrations/notion.js +343 -0
- package/dist/sources/integrations/notion.js.map +1 -0
- package/dist/sources/integrations/todoist.d.ts +18 -0
- package/dist/sources/integrations/todoist.d.ts.map +1 -0
- package/dist/sources/integrations/todoist.js +154 -0
- package/dist/sources/integrations/todoist.js.map +1 -0
- package/dist/sources/types.d.ts +106 -0
- package/dist/sources/types.d.ts.map +1 -0
- package/dist/sources/types.js +9 -0
- package/dist/sources/types.js.map +1 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/progress-renderer.d.ts +54 -0
- package/dist/ui/progress-renderer.d.ts.map +1 -0
- package/dist/ui/progress-renderer.js +118 -0
- package/dist/ui/progress-renderer.js.map +1 -0
- package/dist/ui/shimmer.d.ts +16 -0
- package/dist/ui/shimmer.d.ts.map +1 -0
- package/dist/ui/shimmer.js +31 -0
- package/dist/ui/shimmer.js.map +1 -0
- package/dist/wizard/ascii-art.d.ts +93 -0
- package/dist/wizard/ascii-art.d.ts.map +1 -0
- package/dist/wizard/ascii-art.js +378 -0
- package/dist/wizard/ascii-art.js.map +1 -0
- package/dist/wizard/idea-prompts.d.ts +18 -0
- package/dist/wizard/idea-prompts.d.ts.map +1 -0
- package/dist/wizard/idea-prompts.js +154 -0
- package/dist/wizard/idea-prompts.js.map +1 -0
- package/dist/wizard/idea-ui.d.ts +34 -0
- package/dist/wizard/idea-ui.d.ts.map +1 -0
- package/dist/wizard/idea-ui.js +225 -0
- package/dist/wizard/idea-ui.js.map +1 -0
- package/dist/wizard/ideas.d.ts +27 -0
- package/dist/wizard/ideas.d.ts.map +1 -0
- package/dist/wizard/ideas.js +511 -0
- package/dist/wizard/ideas.js.map +1 -0
- package/dist/wizard/index.d.ts +11 -0
- package/dist/wizard/index.d.ts.map +1 -0
- package/dist/wizard/index.js +472 -0
- package/dist/wizard/index.js.map +1 -0
- package/dist/wizard/llm.d.ts +14 -0
- package/dist/wizard/llm.d.ts.map +1 -0
- package/dist/wizard/llm.js +420 -0
- package/dist/wizard/llm.js.map +1 -0
- package/dist/wizard/prompts.d.ts +75 -0
- package/dist/wizard/prompts.d.ts.map +1 -0
- package/dist/wizard/prompts.js +455 -0
- package/dist/wizard/prompts.js.map +1 -0
- package/dist/wizard/spec-generator.d.ts +14 -0
- package/dist/wizard/spec-generator.d.ts.map +1 -0
- package/dist/wizard/spec-generator.js +200 -0
- package/dist/wizard/spec-generator.js.map +1 -0
- package/dist/wizard/types.d.ts +53 -0
- package/dist/wizard/types.d.ts.map +1 -0
- package/dist/wizard/types.js +10 -0
- package/dist/wizard/types.js.map +1 -0
- package/dist/wizard/ui.d.ts +57 -0
- package/dist/wizard/ui.d.ts.map +1 -0
- package/dist/wizard/ui.js +211 -0
- package/dist/wizard/ui.js.map +1 -0
- package/package.json +67 -8
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { createPullRequest, gitCommit, gitPush, hasUncommittedChanges } from '../automation/git.js';
|
|
6
|
+
import { ProgressRenderer } from '../ui/progress-renderer.js';
|
|
7
|
+
import { runAgent } from './agents.js';
|
|
8
|
+
import { CircuitBreaker } from './circuit-breaker.js';
|
|
9
|
+
import { CostTracker, formatCost } from './cost-tracker.js';
|
|
10
|
+
import { estimateLoop, formatEstimateDetailed } from './estimator.js';
|
|
11
|
+
import { checkFileBasedCompletion, createProgressTracker } from './progress.js';
|
|
12
|
+
import { RateLimiter } from './rate-limiter.js';
|
|
13
|
+
import { analyzeResponse, hasExitSignal } from './semantic-analyzer.js';
|
|
14
|
+
import { detectClaudeSkills, formatSkillsForPrompt } from './skills.js';
|
|
15
|
+
import { detectStepFromOutput } from './step-detector.js';
|
|
16
|
+
import { getCurrentTask, parsePlanTasks } from './task-counter.js';
|
|
17
|
+
import { detectValidationCommands, formatValidationFeedback, runAllValidations, } from './validation.js';
|
|
18
|
+
/**
|
|
19
|
+
* Sleep for a given number of milliseconds
|
|
20
|
+
*/
|
|
21
|
+
function sleep(ms) {
|
|
22
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Strip markdown formatting from task names
|
|
26
|
+
*/
|
|
27
|
+
function cleanTaskName(name) {
|
|
28
|
+
return name
|
|
29
|
+
.replace(/\*\*/g, '') // Remove bold **
|
|
30
|
+
.replace(/\*/g, '') // Remove italic *
|
|
31
|
+
.replace(/`/g, '') // Remove code backticks
|
|
32
|
+
.trim();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get the latest modification time in a directory (recursive)
|
|
36
|
+
*/
|
|
37
|
+
async function getLatestMtime(dir) {
|
|
38
|
+
let latestMtime = 0;
|
|
39
|
+
try {
|
|
40
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
// Skip hidden files, node_modules, and .git
|
|
43
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules')
|
|
44
|
+
continue;
|
|
45
|
+
const fullPath = join(dir, entry.name);
|
|
46
|
+
try {
|
|
47
|
+
const stats = await stat(fullPath);
|
|
48
|
+
if (stats.mtimeMs > latestMtime) {
|
|
49
|
+
latestMtime = stats.mtimeMs;
|
|
50
|
+
}
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
const subMtime = await getLatestMtime(fullPath);
|
|
53
|
+
if (subMtime > latestMtime) {
|
|
54
|
+
latestMtime = subMtime;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Ignore stat errors (file might have been deleted)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Ignore readdir errors
|
|
65
|
+
}
|
|
66
|
+
return latestMtime;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Wait for filesystem to settle (no new writes)
|
|
70
|
+
*/
|
|
71
|
+
async function waitForFilesystemQuiescence(dir, timeoutMs = 3000) {
|
|
72
|
+
const startTime = Date.now();
|
|
73
|
+
let lastMtime = 0;
|
|
74
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
75
|
+
const currentMtime = await getLatestMtime(dir);
|
|
76
|
+
if (currentMtime === lastMtime && lastMtime > 0) {
|
|
77
|
+
// No changes for 500ms, check again
|
|
78
|
+
await sleep(500);
|
|
79
|
+
const afterWait = await getLatestMtime(dir);
|
|
80
|
+
if (afterWait === currentMtime) {
|
|
81
|
+
return; // Filesystem is stable
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
lastMtime = currentMtime;
|
|
85
|
+
await sleep(100);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Completion markers that indicate the task is done
|
|
89
|
+
const COMPLETION_MARKERS = [
|
|
90
|
+
'<TASK_DONE>',
|
|
91
|
+
'<TASK_COMPLETE>',
|
|
92
|
+
'TASK COMPLETED',
|
|
93
|
+
'All tasks completed',
|
|
94
|
+
'Successfully completed',
|
|
95
|
+
];
|
|
96
|
+
// Blocked markers that indicate the task cannot continue
|
|
97
|
+
const BLOCKED_MARKERS = ['<TASK_BLOCKED>', 'TASK BLOCKED', 'Cannot proceed', 'Blocked:'];
|
|
98
|
+
function detectCompletion(output, options = {}) {
|
|
99
|
+
const { completionPromise, requireExitSignal = false, minCompletionIndicators = 1 } = options;
|
|
100
|
+
// 1. Check explicit completion promise first (highest priority)
|
|
101
|
+
if (completionPromise && output.includes(completionPromise)) {
|
|
102
|
+
return 'done';
|
|
103
|
+
}
|
|
104
|
+
// 2. Check for <promise>COMPLETE</promise> tag
|
|
105
|
+
if (/<promise>COMPLETE<\/promise>/i.test(output)) {
|
|
106
|
+
return 'done';
|
|
107
|
+
}
|
|
108
|
+
// 3. Use semantic analyzer for more nuanced detection
|
|
109
|
+
const analysis = analyzeResponse(output);
|
|
110
|
+
// Check for blocked status
|
|
111
|
+
if (analysis.stuckScore >= 0.7 && analysis.confidence !== 'low') {
|
|
112
|
+
return 'blocked';
|
|
113
|
+
}
|
|
114
|
+
// Check blocked markers (legacy support)
|
|
115
|
+
const upperOutput = output.toUpperCase();
|
|
116
|
+
for (const marker of BLOCKED_MARKERS) {
|
|
117
|
+
if (upperOutput.includes(marker.toUpperCase())) {
|
|
118
|
+
return 'blocked';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Check for explicit EXIT_SIGNAL
|
|
122
|
+
const hasExplicitSignal = hasExitSignal(output);
|
|
123
|
+
// If exit signal is required, check for it
|
|
124
|
+
if (requireExitSignal) {
|
|
125
|
+
if (hasExplicitSignal && analysis.indicators.completion.length >= minCompletionIndicators) {
|
|
126
|
+
return 'done';
|
|
127
|
+
}
|
|
128
|
+
// Continue if no explicit signal
|
|
129
|
+
if (!hasExplicitSignal) {
|
|
130
|
+
return 'continue';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Check completion indicators
|
|
134
|
+
if (analysis.completionScore >= 0.7 &&
|
|
135
|
+
analysis.indicators.completion.length >= minCompletionIndicators) {
|
|
136
|
+
return 'done';
|
|
137
|
+
}
|
|
138
|
+
// Explicit exit signals always count
|
|
139
|
+
if (hasExplicitSignal) {
|
|
140
|
+
return 'done';
|
|
141
|
+
}
|
|
142
|
+
// Legacy marker support
|
|
143
|
+
for (const marker of COMPLETION_MARKERS) {
|
|
144
|
+
if (upperOutput.includes(marker.toUpperCase())) {
|
|
145
|
+
return 'done';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return 'continue';
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get human-readable reason for completion (UX 3)
|
|
152
|
+
*/
|
|
153
|
+
function getCompletionReason(output, options) {
|
|
154
|
+
const { completionPromise, requireExitSignal } = options;
|
|
155
|
+
// Check explicit completion promise first
|
|
156
|
+
if (completionPromise && output.includes(completionPromise)) {
|
|
157
|
+
return `Found completion promise: "${completionPromise}"`;
|
|
158
|
+
}
|
|
159
|
+
// Check for <promise>COMPLETE</promise> tag
|
|
160
|
+
if (/<promise>COMPLETE<\/promise>/i.test(output)) {
|
|
161
|
+
return 'Found <promise>COMPLETE</promise> marker';
|
|
162
|
+
}
|
|
163
|
+
// Check for explicit EXIT_SIGNAL
|
|
164
|
+
if (hasExitSignal(output)) {
|
|
165
|
+
return 'Found EXIT_SIGNAL: true';
|
|
166
|
+
}
|
|
167
|
+
// Check completion markers
|
|
168
|
+
const upperOutput = output.toUpperCase();
|
|
169
|
+
for (const marker of COMPLETION_MARKERS) {
|
|
170
|
+
if (upperOutput.includes(marker.toUpperCase())) {
|
|
171
|
+
return `Found completion marker: "${marker}"`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Use semantic analysis
|
|
175
|
+
const analysis = analyzeResponse(output);
|
|
176
|
+
if (analysis.completionScore >= 0.7) {
|
|
177
|
+
const indicators = analysis.indicators.completion.slice(0, 3);
|
|
178
|
+
return `Semantic analysis (${Math.round(analysis.completionScore * 100)}% confident): ${indicators.join(', ')}`;
|
|
179
|
+
}
|
|
180
|
+
return 'Task marked as complete by agent';
|
|
181
|
+
}
|
|
182
|
+
function summarizeChanges(output) {
|
|
183
|
+
// Try to extract a meaningful summary from the output
|
|
184
|
+
const lines = output.split('\n').filter((l) => l.trim());
|
|
185
|
+
// Look for common patterns
|
|
186
|
+
for (const line of lines) {
|
|
187
|
+
if (line.includes('Created') || line.includes('Added') || line.includes('Updated')) {
|
|
188
|
+
return line.slice(0, 50).trim();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Fallback to first meaningful line
|
|
192
|
+
return lines[0]?.slice(0, 50) || 'Update from ralph loop';
|
|
193
|
+
}
|
|
194
|
+
export async function runLoop(options) {
|
|
195
|
+
const spinner = ora();
|
|
196
|
+
const maxIterations = options.maxIterations || 50;
|
|
197
|
+
const commits = [];
|
|
198
|
+
const startTime = Date.now();
|
|
199
|
+
let validationFailures = 0;
|
|
200
|
+
let exitReason = 'max_iterations';
|
|
201
|
+
let finalIteration = maxIterations;
|
|
202
|
+
// Initialize circuit breaker
|
|
203
|
+
const circuitBreaker = new CircuitBreaker(options.circuitBreaker);
|
|
204
|
+
// Initialize rate limiter
|
|
205
|
+
const rateLimiter = options.rateLimit
|
|
206
|
+
? new RateLimiter({ maxCallsPerHour: options.rateLimit })
|
|
207
|
+
: null;
|
|
208
|
+
// Initialize progress tracker
|
|
209
|
+
const progressTracker = options.trackProgress
|
|
210
|
+
? createProgressTracker(options.cwd, options.task)
|
|
211
|
+
: null;
|
|
212
|
+
// Initialize cost tracker
|
|
213
|
+
const costTracker = options.trackCost
|
|
214
|
+
? new CostTracker({
|
|
215
|
+
model: options.model || 'claude-3-sonnet',
|
|
216
|
+
maxIterations: maxIterations,
|
|
217
|
+
})
|
|
218
|
+
: null;
|
|
219
|
+
// Detect validation commands if validation is enabled
|
|
220
|
+
const validationCommands = options.validate ? detectValidationCommands(options.cwd) : [];
|
|
221
|
+
// Detect Claude Code skills
|
|
222
|
+
const detectedSkills = detectClaudeSkills(options.cwd);
|
|
223
|
+
let taskWithSkills = options.task;
|
|
224
|
+
if (detectedSkills.length > 0) {
|
|
225
|
+
const skillsPrompt = formatSkillsForPrompt(detectedSkills);
|
|
226
|
+
taskWithSkills = `${options.task}\n\n${skillsPrompt}`;
|
|
227
|
+
}
|
|
228
|
+
// Completion detection options
|
|
229
|
+
const completionOptions = {
|
|
230
|
+
completionPromise: options.completionPromise,
|
|
231
|
+
requireExitSignal: options.requireExitSignal,
|
|
232
|
+
minCompletionIndicators: options.minCompletionIndicators,
|
|
233
|
+
};
|
|
234
|
+
// Get initial task count for estimates
|
|
235
|
+
const initialTaskCount = parsePlanTasks(options.cwd);
|
|
236
|
+
console.log();
|
|
237
|
+
console.log(chalk.cyan.bold('Starting Ralph Wiggum Loop'));
|
|
238
|
+
console.log(chalk.dim(`Agent: ${options.agent.name}`));
|
|
239
|
+
// Show task count and estimates if we have tasks
|
|
240
|
+
if (initialTaskCount.total > 0) {
|
|
241
|
+
console.log(chalk.dim(`Tasks: ${initialTaskCount.pending} pending, ${initialTaskCount.completed} completed`));
|
|
242
|
+
// Show estimate
|
|
243
|
+
const estimate = estimateLoop(initialTaskCount);
|
|
244
|
+
console.log();
|
|
245
|
+
console.log(chalk.yellow.bold('📋 Estimate:'));
|
|
246
|
+
for (const line of formatEstimateDetailed(estimate)) {
|
|
247
|
+
console.log(chalk.yellow(` ${line}`));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
console.log(chalk.dim(`Task: ${options.task.slice(0, 60)}${options.task.length > 60 ? '...' : ''}`));
|
|
252
|
+
}
|
|
253
|
+
console.log();
|
|
254
|
+
if (validationCommands.length > 0) {
|
|
255
|
+
console.log(chalk.dim(`Validation: ${validationCommands.map((c) => c.name).join(', ')}`));
|
|
256
|
+
}
|
|
257
|
+
if (detectedSkills.length > 0) {
|
|
258
|
+
console.log(chalk.dim(`Skills: ${detectedSkills.map((s) => s.name).join(', ')}`));
|
|
259
|
+
}
|
|
260
|
+
if (options.completionPromise) {
|
|
261
|
+
console.log(chalk.dim(`Completion promise: ${options.completionPromise}`));
|
|
262
|
+
}
|
|
263
|
+
if (rateLimiter) {
|
|
264
|
+
console.log(chalk.dim(`Rate limit: ${options.rateLimit}/hour`));
|
|
265
|
+
}
|
|
266
|
+
console.log();
|
|
267
|
+
// Track completed tasks to show progress diff between iterations
|
|
268
|
+
let previousCompletedTasks = initialTaskCount.completed;
|
|
269
|
+
for (let i = 1; i <= maxIterations; i++) {
|
|
270
|
+
const iterationStart = Date.now();
|
|
271
|
+
// Check circuit breaker
|
|
272
|
+
if (circuitBreaker.isTripped()) {
|
|
273
|
+
const reason = circuitBreaker.getTripReason();
|
|
274
|
+
spinner.fail(chalk.red(`Circuit breaker tripped: ${reason}`));
|
|
275
|
+
finalIteration = i - 1;
|
|
276
|
+
exitReason = 'circuit_breaker';
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
// Check rate limiter with countdown (UX 10)
|
|
280
|
+
if (rateLimiter && !rateLimiter.canMakeCall()) {
|
|
281
|
+
const waitTimeMs = rateLimiter.getWaitTime();
|
|
282
|
+
const waitTimeSec = Math.ceil(waitTimeMs / 1000);
|
|
283
|
+
console.log(chalk.yellow(`\n⏳ Rate limited. Waiting ${waitTimeSec}s...`));
|
|
284
|
+
console.log(chalk.dim(rateLimiter.formatStats()));
|
|
285
|
+
// Show countdown
|
|
286
|
+
const countdownInterval = setInterval(() => {
|
|
287
|
+
const remaining = rateLimiter.getWaitTime();
|
|
288
|
+
if (remaining > 0) {
|
|
289
|
+
process.stdout.write(chalk.dim(`\r ⏱ ${Math.ceil(remaining / 1000)}s remaining... `));
|
|
290
|
+
}
|
|
291
|
+
}, 1000);
|
|
292
|
+
const acquired = await rateLimiter.waitAndAcquire(60000); // Wait up to 1 minute
|
|
293
|
+
clearInterval(countdownInterval);
|
|
294
|
+
process.stdout.write(`\r${' '.repeat(40)}\r`); // Clear countdown line
|
|
295
|
+
if (!acquired) {
|
|
296
|
+
console.log(chalk.red('✗ Rate limit timeout - stopping loop'));
|
|
297
|
+
finalIteration = i - 1;
|
|
298
|
+
exitReason = 'rate_limit';
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
console.log(chalk.green('✓ Rate limit cleared, continuing...\n'));
|
|
302
|
+
}
|
|
303
|
+
else if (rateLimiter) {
|
|
304
|
+
rateLimiter.recordCall();
|
|
305
|
+
}
|
|
306
|
+
// Check for file-based completion signals
|
|
307
|
+
if (options.checkFileCompletion) {
|
|
308
|
+
const fileCompletion = await checkFileBasedCompletion(options.cwd);
|
|
309
|
+
if (fileCompletion.completed) {
|
|
310
|
+
spinner.succeed(chalk.green(`File-based completion: ${fileCompletion.reason}`));
|
|
311
|
+
finalIteration = i - 1;
|
|
312
|
+
exitReason = 'file_signal';
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Log iteration warnings
|
|
317
|
+
const progressPercent = (i / maxIterations) * 100;
|
|
318
|
+
if (progressPercent >= 90 && progressPercent < 95) {
|
|
319
|
+
console.log(chalk.yellow(`⚠️ Warning: 90% of iterations used (${i}/${maxIterations})`));
|
|
320
|
+
}
|
|
321
|
+
else if (progressPercent >= 80 && progressPercent < 85) {
|
|
322
|
+
console.log(chalk.yellow(`⚠️ Warning: 80% of iterations used (${i}/${maxIterations})`));
|
|
323
|
+
}
|
|
324
|
+
// Track progress entry
|
|
325
|
+
let progressEntry = null;
|
|
326
|
+
if (progressTracker) {
|
|
327
|
+
progressEntry = {
|
|
328
|
+
timestamp: new Date().toISOString(),
|
|
329
|
+
iteration: i,
|
|
330
|
+
status: 'started',
|
|
331
|
+
summary: '',
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
// Get current task from implementation plan (re-parse to catch updates)
|
|
335
|
+
const currentTask = getCurrentTask(options.cwd);
|
|
336
|
+
const taskInfo = parsePlanTasks(options.cwd);
|
|
337
|
+
const totalTasks = taskInfo.total;
|
|
338
|
+
const completedTasks = taskInfo.completed;
|
|
339
|
+
// Check if tasks were completed since last iteration
|
|
340
|
+
const newlyCompleted = completedTasks - previousCompletedTasks;
|
|
341
|
+
if (newlyCompleted > 0 && i > 1) {
|
|
342
|
+
// Get names of newly completed tasks (strip markdown)
|
|
343
|
+
const completedNames = taskInfo.tasks
|
|
344
|
+
.filter((t) => t.completed && t.index >= previousCompletedTasks && t.index < completedTasks)
|
|
345
|
+
.map((t) => {
|
|
346
|
+
const clean = cleanTaskName(t.name);
|
|
347
|
+
return clean.length > 25 ? `${clean.slice(0, 22)}...` : clean;
|
|
348
|
+
});
|
|
349
|
+
if (completedNames.length > 0) {
|
|
350
|
+
console.log(chalk.green(` ✓ Completed ${newlyCompleted} task(s): ${completedNames.join(', ')}`));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
previousCompletedTasks = completedTasks;
|
|
354
|
+
// Show loop header with task info
|
|
355
|
+
console.log(chalk.cyan(`\n═══════════════════════════════════════════════════════════════`));
|
|
356
|
+
if (currentTask && totalTasks > 0) {
|
|
357
|
+
const taskNum = completedTasks + 1;
|
|
358
|
+
const cleanName = cleanTaskName(currentTask.name);
|
|
359
|
+
const taskName = cleanName.length > 40 ? `${cleanName.slice(0, 37)}...` : cleanName;
|
|
360
|
+
console.log(chalk.cyan.bold(` Task ${taskNum}/${totalTasks} │ ${taskName}`));
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
console.log(chalk.cyan.bold(` Loop ${i}/${maxIterations} │ Running ${options.agent.name}`));
|
|
364
|
+
}
|
|
365
|
+
console.log(chalk.cyan(`═══════════════════════════════════════════════════════════════\n`));
|
|
366
|
+
// Create progress renderer for this iteration
|
|
367
|
+
const iterProgress = new ProgressRenderer();
|
|
368
|
+
iterProgress.start('Working...');
|
|
369
|
+
// Run the agent with step detection (include skills in task)
|
|
370
|
+
const agentOptions = {
|
|
371
|
+
task: taskWithSkills,
|
|
372
|
+
cwd: options.cwd,
|
|
373
|
+
auto: options.auto,
|
|
374
|
+
maxTurns: 10, // Limit turns per loop iteration
|
|
375
|
+
streamOutput: false, // Don't dump raw JSON - use progress renderer instead
|
|
376
|
+
onOutput: (line) => {
|
|
377
|
+
const step = detectStepFromOutput(line);
|
|
378
|
+
if (step) {
|
|
379
|
+
iterProgress.updateStep(step);
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
const result = await runAgent(options.agent, agentOptions);
|
|
384
|
+
iterProgress.stop('Iteration complete');
|
|
385
|
+
// Track cost for this iteration (silent - summary shown at end)
|
|
386
|
+
if (costTracker) {
|
|
387
|
+
costTracker.recordIteration(options.task, result.output);
|
|
388
|
+
}
|
|
389
|
+
// Check for completion using enhanced detection
|
|
390
|
+
let status = detectCompletion(result.output, completionOptions);
|
|
391
|
+
// Verify completion - check if files were actually changed
|
|
392
|
+
if (status === 'done' && i === 1) {
|
|
393
|
+
// On first iteration, verify that files were actually created/modified
|
|
394
|
+
const hasChanges = await hasUncommittedChanges(options.cwd);
|
|
395
|
+
if (!hasChanges) {
|
|
396
|
+
console.log(chalk.yellow(' Agent reported done but no files changed - continuing...'));
|
|
397
|
+
status = 'continue';
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
// Wait for filesystem to settle before declaring done
|
|
401
|
+
await waitForFilesystemQuiescence(options.cwd, 2000);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (status === 'blocked') {
|
|
405
|
+
// Detect specific block reasons for better user feedback
|
|
406
|
+
const output = result.output.toLowerCase();
|
|
407
|
+
const isRateLimit = output.includes('rate limit') ||
|
|
408
|
+
output.includes('usage limit') ||
|
|
409
|
+
output.includes('100%') ||
|
|
410
|
+
output.includes('exceeded') ||
|
|
411
|
+
output.includes('too many requests');
|
|
412
|
+
const isPermission = output.includes('permission') || output.includes('unauthorized') || output.includes('403');
|
|
413
|
+
console.log();
|
|
414
|
+
if (isRateLimit) {
|
|
415
|
+
console.log(chalk.red.bold(' ⚠ Claude rate limit reached'));
|
|
416
|
+
console.log();
|
|
417
|
+
console.log(chalk.yellow(' Your Claude session usage is at 100%.'));
|
|
418
|
+
console.log(chalk.yellow(' Wait for your rate limit to reset, then run again:'));
|
|
419
|
+
console.log(chalk.dim(' ralph-starter run'));
|
|
420
|
+
console.log();
|
|
421
|
+
console.log(chalk.dim(' Tip: Check your limits at https://claude.ai/settings'));
|
|
422
|
+
}
|
|
423
|
+
else if (isPermission) {
|
|
424
|
+
console.log(chalk.red.bold(' ⚠ Permission denied'));
|
|
425
|
+
console.log();
|
|
426
|
+
console.log(chalk.yellow(' Claude Code requires permission to continue.'));
|
|
427
|
+
console.log(chalk.dim(' Run without --auto flag to approve permissions interactively.'));
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
console.log(chalk.red(` ✗ Task blocked - cannot continue`));
|
|
431
|
+
console.log();
|
|
432
|
+
console.log(chalk.dim(' The AI agent was blocked from continuing.'));
|
|
433
|
+
console.log(chalk.dim(' This may be due to rate limits or permissions.'));
|
|
434
|
+
}
|
|
435
|
+
console.log();
|
|
436
|
+
if (progressTracker && progressEntry) {
|
|
437
|
+
progressEntry.status = 'blocked';
|
|
438
|
+
progressEntry.summary = isRateLimit
|
|
439
|
+
? 'Rate limit reached'
|
|
440
|
+
: isPermission
|
|
441
|
+
? 'Permission denied'
|
|
442
|
+
: 'Task blocked';
|
|
443
|
+
progressEntry.duration = Date.now() - iterationStart;
|
|
444
|
+
await progressTracker.appendEntry(progressEntry);
|
|
445
|
+
}
|
|
446
|
+
finalIteration = i;
|
|
447
|
+
exitReason = 'blocked';
|
|
448
|
+
return {
|
|
449
|
+
success: false,
|
|
450
|
+
iterations: i,
|
|
451
|
+
commits,
|
|
452
|
+
error: isRateLimit
|
|
453
|
+
? 'Rate limit reached - wait and try again'
|
|
454
|
+
: isPermission
|
|
455
|
+
? 'Permission denied'
|
|
456
|
+
: 'Task blocked - cannot continue',
|
|
457
|
+
exitReason,
|
|
458
|
+
stats: {
|
|
459
|
+
totalDuration: Date.now() - startTime,
|
|
460
|
+
avgIterationDuration: (Date.now() - startTime) / i,
|
|
461
|
+
validationFailures,
|
|
462
|
+
circuitBreakerStats: circuitBreaker.getStats(),
|
|
463
|
+
costStats: costTracker?.getStats(),
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
// Run validation (backpressure) if enabled and there are changes
|
|
468
|
+
let _validationPassed = true;
|
|
469
|
+
let validationResults = [];
|
|
470
|
+
if (validationCommands.length > 0 && (await hasUncommittedChanges(options.cwd))) {
|
|
471
|
+
spinner.start(chalk.yellow(`Loop ${i}: Running validation...`));
|
|
472
|
+
validationResults = await runAllValidations(options.cwd, validationCommands);
|
|
473
|
+
const allPassed = validationResults.every((r) => r.success);
|
|
474
|
+
if (!allPassed) {
|
|
475
|
+
_validationPassed = false;
|
|
476
|
+
validationFailures++;
|
|
477
|
+
const feedback = formatValidationFeedback(validationResults);
|
|
478
|
+
spinner.fail(chalk.red(`Loop ${i}: Validation failed`));
|
|
479
|
+
// Show which validations failed (UX 4: specific validation errors)
|
|
480
|
+
for (const vr of validationResults) {
|
|
481
|
+
if (!vr.success) {
|
|
482
|
+
console.log(chalk.red(` ✗ ${vr.command}`));
|
|
483
|
+
if (vr.error) {
|
|
484
|
+
const errorLines = vr.error.split('\n').slice(0, 5);
|
|
485
|
+
for (const line of errorLines) {
|
|
486
|
+
console.log(chalk.dim(` ${line}`));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
else if (vr.output) {
|
|
490
|
+
const outputLines = vr.output.split('\n').slice(0, 5);
|
|
491
|
+
for (const line of outputLines) {
|
|
492
|
+
console.log(chalk.dim(` ${line}`));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Record failure in circuit breaker
|
|
498
|
+
const errorMsg = validationResults
|
|
499
|
+
.filter((r) => !r.success)
|
|
500
|
+
.map((r) => r.error?.slice(0, 200) || r.output?.slice(0, 200) || r.command)
|
|
501
|
+
.join('\n');
|
|
502
|
+
const tripped = circuitBreaker.recordFailure(errorMsg);
|
|
503
|
+
if (tripped) {
|
|
504
|
+
const reason = circuitBreaker.getTripReason();
|
|
505
|
+
console.log(chalk.red(`Circuit breaker tripped: ${reason}`));
|
|
506
|
+
if (progressTracker && progressEntry) {
|
|
507
|
+
progressEntry.status = 'failed';
|
|
508
|
+
progressEntry.summary = `Circuit breaker tripped: ${reason}`;
|
|
509
|
+
progressEntry.validationResults = validationResults;
|
|
510
|
+
progressEntry.duration = Date.now() - iterationStart;
|
|
511
|
+
await progressTracker.appendEntry(progressEntry);
|
|
512
|
+
}
|
|
513
|
+
finalIteration = i;
|
|
514
|
+
exitReason = 'circuit_breaker';
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
if (progressTracker && progressEntry) {
|
|
518
|
+
progressEntry.status = 'validation_failed';
|
|
519
|
+
progressEntry.summary = 'Validation failed';
|
|
520
|
+
progressEntry.validationResults = validationResults;
|
|
521
|
+
progressEntry.duration = Date.now() - iterationStart;
|
|
522
|
+
await progressTracker.appendEntry(progressEntry);
|
|
523
|
+
}
|
|
524
|
+
// Continue loop with validation feedback
|
|
525
|
+
taskWithSkills = `${taskWithSkills}\n\n${feedback}`;
|
|
526
|
+
continue; // Go to next iteration to fix issues
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
// Validation passed - record success
|
|
530
|
+
spinner.succeed(chalk.green(`Loop ${i}: Validation passed`));
|
|
531
|
+
circuitBreaker.recordSuccess();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// Auto-commit if enabled and there are changes
|
|
535
|
+
let committed = false;
|
|
536
|
+
let commitMsg = '';
|
|
537
|
+
// Get current task info for display
|
|
538
|
+
const displayTaskInfo = parsePlanTasks(options.cwd);
|
|
539
|
+
const iterationLabel = displayTaskInfo.total > 0
|
|
540
|
+
? `Task ${displayTaskInfo.completed}/${displayTaskInfo.total}`
|
|
541
|
+
: `Iteration ${i}`;
|
|
542
|
+
if (options.commit && (await hasUncommittedChanges(options.cwd))) {
|
|
543
|
+
const summary = summarizeChanges(result.output);
|
|
544
|
+
commitMsg = `feat: ${summary}`;
|
|
545
|
+
try {
|
|
546
|
+
await gitCommit(options.cwd, commitMsg);
|
|
547
|
+
commits.push(commitMsg);
|
|
548
|
+
committed = true;
|
|
549
|
+
console.log(chalk.green(`✓ ${iterationLabel}: Committed - ${commitMsg}`));
|
|
550
|
+
}
|
|
551
|
+
catch (_error) {
|
|
552
|
+
console.log(chalk.yellow(`⚠ ${iterationLabel}: Completed (commit failed)`));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
console.log(chalk.green(`✓ ${iterationLabel}: Completed`));
|
|
557
|
+
}
|
|
558
|
+
// Update progress entry
|
|
559
|
+
if (progressTracker && progressEntry) {
|
|
560
|
+
progressEntry.status = status === 'done' ? 'completed' : 'completed';
|
|
561
|
+
progressEntry.summary = summarizeChanges(result.output);
|
|
562
|
+
progressEntry.validationResults =
|
|
563
|
+
validationResults.length > 0 ? validationResults : undefined;
|
|
564
|
+
progressEntry.commitHash = committed ? commitMsg : undefined;
|
|
565
|
+
progressEntry.duration = Date.now() - iterationStart;
|
|
566
|
+
// Add cost info from tracker
|
|
567
|
+
if (costTracker) {
|
|
568
|
+
const lastCost = costTracker.getLastIterationCost();
|
|
569
|
+
if (lastCost) {
|
|
570
|
+
progressEntry.cost = lastCost.cost;
|
|
571
|
+
progressEntry.tokens = lastCost.tokens;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
await progressTracker.appendEntry(progressEntry);
|
|
575
|
+
}
|
|
576
|
+
if (status === 'done') {
|
|
577
|
+
console.log();
|
|
578
|
+
console.log(chalk.green.bold('═══════════════════════════════════════════════════════════════'));
|
|
579
|
+
console.log(chalk.green.bold(' ✓ Task completed successfully!'));
|
|
580
|
+
console.log(chalk.green.bold('═══════════════════════════════════════════════════════════════'));
|
|
581
|
+
// Show completion reason (UX 3: clear completion signals)
|
|
582
|
+
const completionReason = getCompletionReason(result.output, completionOptions);
|
|
583
|
+
console.log(chalk.dim(` Reason: ${completionReason}`));
|
|
584
|
+
console.log(chalk.dim(` Iterations: ${i}`));
|
|
585
|
+
if (costTracker) {
|
|
586
|
+
const stats = costTracker.getStats();
|
|
587
|
+
console.log(chalk.dim(` Total cost: ${formatCost(stats.totalCost.totalCost)}`));
|
|
588
|
+
}
|
|
589
|
+
const duration = Date.now() - startTime;
|
|
590
|
+
const minutes = Math.floor(duration / 60000);
|
|
591
|
+
const seconds = Math.floor((duration % 60000) / 1000);
|
|
592
|
+
console.log(chalk.dim(` Time: ${minutes}m ${seconds}s`));
|
|
593
|
+
console.log();
|
|
594
|
+
finalIteration = i;
|
|
595
|
+
exitReason = 'completed';
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
598
|
+
// Small delay between iterations
|
|
599
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
600
|
+
}
|
|
601
|
+
// Post-loop actions
|
|
602
|
+
if (options.push && commits.length > 0) {
|
|
603
|
+
spinner.start('Pushing to remote...');
|
|
604
|
+
try {
|
|
605
|
+
await gitPush(options.cwd);
|
|
606
|
+
spinner.succeed('Pushed to remote');
|
|
607
|
+
}
|
|
608
|
+
catch (_error) {
|
|
609
|
+
spinner.fail('Failed to push');
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (options.pr && commits.length > 0) {
|
|
613
|
+
spinner.start('Creating pull request...');
|
|
614
|
+
try {
|
|
615
|
+
const prUrl = await createPullRequest(options.cwd, {
|
|
616
|
+
title: options.prTitle || `Ralph: ${options.task.slice(0, 50)}`,
|
|
617
|
+
body: `Automated PR created by ralph-starter\n\n## Task\n${options.task}\n\n## Commits\n${commits.map((c) => `- ${c}`).join('\n')}`,
|
|
618
|
+
});
|
|
619
|
+
spinner.succeed(`Created PR: ${prUrl}`);
|
|
620
|
+
}
|
|
621
|
+
catch (_error) {
|
|
622
|
+
spinner.fail('Failed to create PR');
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const totalDuration = Date.now() - startTime;
|
|
626
|
+
// Print cost summary if tracking enabled
|
|
627
|
+
if (costTracker) {
|
|
628
|
+
console.log();
|
|
629
|
+
console.log(chalk.cyan('💰 Cost Summary:'));
|
|
630
|
+
console.log(chalk.dim(costTracker.formatStats()));
|
|
631
|
+
}
|
|
632
|
+
return {
|
|
633
|
+
success: exitReason === 'completed' || exitReason === 'file_signal',
|
|
634
|
+
iterations: finalIteration,
|
|
635
|
+
commits,
|
|
636
|
+
exitReason,
|
|
637
|
+
stats: {
|
|
638
|
+
totalDuration,
|
|
639
|
+
avgIterationDuration: totalDuration / finalIteration,
|
|
640
|
+
validationFailures,
|
|
641
|
+
circuitBreakerStats: circuitBreaker.getStats(),
|
|
642
|
+
costStats: costTracker?.getStats(),
|
|
643
|
+
},
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
//# sourceMappingURL=executor.js.map
|