wiggum-cli 0.1.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.
- package/LICENSE +21 -0
- package/README.md +341 -0
- package/bin/ralph.js +8 -0
- package/dist/ai/enhancer.d.ts +100 -0
- package/dist/ai/enhancer.d.ts.map +1 -0
- package/dist/ai/enhancer.js +233 -0
- package/dist/ai/enhancer.js.map +1 -0
- package/dist/ai/index.d.ts +8 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +11 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/prompts.d.ts +26 -0
- package/dist/ai/prompts.d.ts.map +1 -0
- package/dist/ai/prompts.js +201 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/ai/providers.d.ts +35 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +104 -0
- package/dist/ai/providers.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/init.d.ts +16 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +124 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/monitor.d.ts +17 -0
- package/dist/commands/monitor.d.ts.map +1 -0
- package/dist/commands/monitor.js +342 -0
- package/dist/commands/monitor.js.map +1 -0
- package/dist/commands/new.d.ts +19 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +272 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/run.d.ts +16 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +175 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/generator/config.d.ts +59 -0
- package/dist/generator/config.d.ts.map +1 -0
- package/dist/generator/config.js +68 -0
- package/dist/generator/config.js.map +1 -0
- package/dist/generator/index.d.ts +64 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +147 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/templates.d.ts +70 -0
- package/dist/generator/templates.d.ts.map +1 -0
- package/dist/generator/templates.js +296 -0
- package/dist/generator/templates.js.map +1 -0
- package/dist/generator/writer.d.ts +93 -0
- package/dist/generator/writer.d.ts.map +1 -0
- package/dist/generator/writer.js +213 -0
- package/dist/generator/writer.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/scanner/detectors/core/framework.d.ts +11 -0
- package/dist/scanner/detectors/core/framework.d.ts.map +1 -0
- package/dist/scanner/detectors/core/framework.js +275 -0
- package/dist/scanner/detectors/core/framework.js.map +1 -0
- package/dist/scanner/detectors/core/packageManager.d.ts +11 -0
- package/dist/scanner/detectors/core/packageManager.d.ts.map +1 -0
- package/dist/scanner/detectors/core/packageManager.js +74 -0
- package/dist/scanner/detectors/core/packageManager.js.map +1 -0
- package/dist/scanner/detectors/core/styling.d.ts +12 -0
- package/dist/scanner/detectors/core/styling.d.ts.map +1 -0
- package/dist/scanner/detectors/core/styling.js +230 -0
- package/dist/scanner/detectors/core/styling.js.map +1 -0
- package/dist/scanner/detectors/core/testing.d.ts +12 -0
- package/dist/scanner/detectors/core/testing.d.ts.map +1 -0
- package/dist/scanner/detectors/core/testing.js +190 -0
- package/dist/scanner/detectors/core/testing.js.map +1 -0
- package/dist/scanner/detectors/data/api.d.ts +12 -0
- package/dist/scanner/detectors/data/api.d.ts.map +1 -0
- package/dist/scanner/detectors/data/api.js +261 -0
- package/dist/scanner/detectors/data/api.js.map +1 -0
- package/dist/scanner/detectors/data/database.d.ts +12 -0
- package/dist/scanner/detectors/data/database.d.ts.map +1 -0
- package/dist/scanner/detectors/data/database.js +213 -0
- package/dist/scanner/detectors/data/database.js.map +1 -0
- package/dist/scanner/detectors/data/orm.d.ts +12 -0
- package/dist/scanner/detectors/data/orm.d.ts.map +1 -0
- package/dist/scanner/detectors/data/orm.js +160 -0
- package/dist/scanner/detectors/data/orm.js.map +1 -0
- package/dist/scanner/detectors/frontend/formHandling.d.ts +12 -0
- package/dist/scanner/detectors/frontend/formHandling.d.ts.map +1 -0
- package/dist/scanner/detectors/frontend/formHandling.js +211 -0
- package/dist/scanner/detectors/frontend/formHandling.js.map +1 -0
- package/dist/scanner/detectors/frontend/stateManagement.d.ts +12 -0
- package/dist/scanner/detectors/frontend/stateManagement.d.ts.map +1 -0
- package/dist/scanner/detectors/frontend/stateManagement.js +221 -0
- package/dist/scanner/detectors/frontend/stateManagement.js.map +1 -0
- package/dist/scanner/detectors/frontend/uiComponents.d.ts +12 -0
- package/dist/scanner/detectors/frontend/uiComponents.d.ts.map +1 -0
- package/dist/scanner/detectors/frontend/uiComponents.js +285 -0
- package/dist/scanner/detectors/frontend/uiComponents.js.map +1 -0
- package/dist/scanner/detectors/infra/deployment.d.ts +12 -0
- package/dist/scanner/detectors/infra/deployment.d.ts.map +1 -0
- package/dist/scanner/detectors/infra/deployment.js +301 -0
- package/dist/scanner/detectors/infra/deployment.js.map +1 -0
- package/dist/scanner/detectors/infra/monorepo.d.ts +12 -0
- package/dist/scanner/detectors/infra/monorepo.d.ts.map +1 -0
- package/dist/scanner/detectors/infra/monorepo.js +219 -0
- package/dist/scanner/detectors/infra/monorepo.js.map +1 -0
- package/dist/scanner/detectors/mcp/mcpProject.d.ts +12 -0
- package/dist/scanner/detectors/mcp/mcpProject.d.ts.map +1 -0
- package/dist/scanner/detectors/mcp/mcpProject.js +154 -0
- package/dist/scanner/detectors/mcp/mcpProject.js.map +1 -0
- package/dist/scanner/detectors/mcp/mcpServers.d.ts +17 -0
- package/dist/scanner/detectors/mcp/mcpServers.d.ts.map +1 -0
- package/dist/scanner/detectors/mcp/mcpServers.js +193 -0
- package/dist/scanner/detectors/mcp/mcpServers.js.map +1 -0
- package/dist/scanner/detectors/services/analytics.d.ts +12 -0
- package/dist/scanner/detectors/services/analytics.d.ts.map +1 -0
- package/dist/scanner/detectors/services/analytics.js +236 -0
- package/dist/scanner/detectors/services/analytics.js.map +1 -0
- package/dist/scanner/detectors/services/auth.d.ts +12 -0
- package/dist/scanner/detectors/services/auth.d.ts.map +1 -0
- package/dist/scanner/detectors/services/auth.js +217 -0
- package/dist/scanner/detectors/services/auth.js.map +1 -0
- package/dist/scanner/detectors/services/email.d.ts +12 -0
- package/dist/scanner/detectors/services/email.d.ts.map +1 -0
- package/dist/scanner/detectors/services/email.js +211 -0
- package/dist/scanner/detectors/services/email.js.map +1 -0
- package/dist/scanner/detectors/services/payments.d.ts +12 -0
- package/dist/scanner/detectors/services/payments.d.ts.map +1 -0
- package/dist/scanner/detectors/services/payments.js +185 -0
- package/dist/scanner/detectors/services/payments.js.map +1 -0
- package/dist/scanner/detectors/utils.d.ts +160 -0
- package/dist/scanner/detectors/utils.d.ts.map +1 -0
- package/dist/scanner/detectors/utils.js +222 -0
- package/dist/scanner/detectors/utils.js.map +1 -0
- package/dist/scanner/index.d.ts +42 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +282 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/registry.d.ts +43 -0
- package/dist/scanner/registry.d.ts.map +1 -0
- package/dist/scanner/registry.js +243 -0
- package/dist/scanner/registry.js.map +1 -0
- package/dist/scanner/types.d.ts +112 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +6 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/templates/config/ralph.config.js.tmpl +38 -0
- package/dist/templates/guides/AGENTS.md.tmpl +100 -0
- package/dist/templates/guides/FRONTEND.md.tmpl +523 -0
- package/dist/templates/guides/PERFORMANCE.md.tmpl +264 -0
- package/dist/templates/guides/SECURITY.md.tmpl +100 -0
- package/dist/templates/prompts/PROMPT.md.tmpl +77 -0
- package/dist/templates/prompts/PROMPT_e2e.md.tmpl +234 -0
- package/dist/templates/prompts/PROMPT_feature.md.tmpl +83 -0
- package/dist/templates/prompts/PROMPT_review.md.tmpl +167 -0
- package/dist/templates/prompts/PROMPT_verify.md.tmpl +72 -0
- package/dist/templates/root/.gitignore.tmpl +5 -0
- package/dist/templates/root/LEARNINGS.md.tmpl +24 -0
- package/dist/templates/root/README.md.tmpl +61 -0
- package/dist/templates/scripts/feature-loop.sh.tmpl +267 -0
- package/dist/templates/scripts/loop.sh.tmpl +59 -0
- package/dist/templates/scripts/ralph-monitor.sh.tmpl +244 -0
- package/dist/templates/specs/README.md.tmpl +57 -0
- package/dist/templates/specs/_example.md.tmpl +71 -0
- package/dist/utils/config.d.ts +95 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +148 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/header.d.ts +5 -0
- package/dist/utils/header.d.ts.map +1 -0
- package/dist/utils/header.js +15 -0
- package/dist/utils/header.js.map +1 -0
- package/dist/utils/logger.d.ts +11 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +24 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +44 -0
- package/src/ai/enhancer.ts +350 -0
- package/src/ai/index.ts +38 -0
- package/src/ai/prompts.ts +217 -0
- package/src/ai/providers.ts +136 -0
- package/src/cli.ts +255 -0
- package/src/commands/init.ts +149 -0
- package/src/commands/monitor.ts +412 -0
- package/src/commands/new.ts +312 -0
- package/src/commands/run.ts +214 -0
- package/src/generator/config.ts +116 -0
- package/src/generator/index.ts +227 -0
- package/src/generator/templates.ts +412 -0
- package/src/generator/writer.ts +293 -0
- package/src/index.ts +41 -0
- package/src/scanner/detectors/core/framework.ts +332 -0
- package/src/scanner/detectors/core/packageManager.ts +91 -0
- package/src/scanner/detectors/core/styling.ts +261 -0
- package/src/scanner/detectors/core/testing.ts +221 -0
- package/src/scanner/detectors/data/api.ts +303 -0
- package/src/scanner/detectors/data/database.ts +245 -0
- package/src/scanner/detectors/data/orm.ts +180 -0
- package/src/scanner/detectors/frontend/formHandling.ts +244 -0
- package/src/scanner/detectors/frontend/stateManagement.ts +261 -0
- package/src/scanner/detectors/frontend/uiComponents.ts +328 -0
- package/src/scanner/detectors/infra/deployment.ts +343 -0
- package/src/scanner/detectors/infra/monorepo.ts +251 -0
- package/src/scanner/detectors/mcp/mcpProject.ts +176 -0
- package/src/scanner/detectors/mcp/mcpServers.ts +237 -0
- package/src/scanner/detectors/services/analytics.ts +273 -0
- package/src/scanner/detectors/services/auth.ts +254 -0
- package/src/scanner/detectors/services/email.ts +244 -0
- package/src/scanner/detectors/services/payments.ts +213 -0
- package/src/scanner/detectors/utils.ts +251 -0
- package/src/scanner/index.ts +354 -0
- package/src/scanner/registry.ts +301 -0
- package/src/scanner/types.ts +152 -0
- package/src/templates/config/ralph.config.js.tmpl +38 -0
- package/src/templates/guides/AGENTS.md.tmpl +100 -0
- package/src/templates/guides/FRONTEND.md.tmpl +523 -0
- package/src/templates/guides/PERFORMANCE.md.tmpl +264 -0
- package/src/templates/guides/SECURITY.md.tmpl +100 -0
- package/src/templates/prompts/PROMPT.md.tmpl +77 -0
- package/src/templates/prompts/PROMPT_e2e.md.tmpl +234 -0
- package/src/templates/prompts/PROMPT_feature.md.tmpl +83 -0
- package/src/templates/prompts/PROMPT_review.md.tmpl +167 -0
- package/src/templates/prompts/PROMPT_verify.md.tmpl +72 -0
- package/src/templates/root/.gitignore.tmpl +5 -0
- package/src/templates/root/LEARNINGS.md.tmpl +24 -0
- package/src/templates/root/README.md.tmpl +61 -0
- package/src/templates/scripts/feature-loop.sh.tmpl +267 -0
- package/src/templates/scripts/loop.sh.tmpl +59 -0
- package/src/templates/scripts/ralph-monitor.sh.tmpl +244 -0
- package/src/templates/specs/README.md.tmpl +57 -0
- package/src/templates/specs/_example.md.tmpl +71 -0
- package/src/utils/config.ts +221 -0
- package/src/utils/header.ts +15 -0
- package/src/utils/logger.ts +28 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init Command
|
|
3
|
+
* Initialize Ralph in the current project - scans and generates configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
import { Scanner, formatScanResult, type ScanResult } from '../scanner/index.js';
|
|
8
|
+
import { Generator, formatGenerationResult } from '../generator/index.js';
|
|
9
|
+
import {
|
|
10
|
+
AIEnhancer,
|
|
11
|
+
formatAIAnalysis,
|
|
12
|
+
type AIProvider,
|
|
13
|
+
type EnhancedScanResult,
|
|
14
|
+
} from '../ai/index.js';
|
|
15
|
+
import * as prompts from '@clack/prompts';
|
|
16
|
+
import pc from 'picocolors';
|
|
17
|
+
|
|
18
|
+
export interface InitOptions {
|
|
19
|
+
ai?: boolean;
|
|
20
|
+
provider?: AIProvider;
|
|
21
|
+
yes?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Initialize Ralph in the current project
|
|
26
|
+
* Scans the project and generates configuration
|
|
27
|
+
*/
|
|
28
|
+
export async function initCommand(options: InitOptions): Promise<void> {
|
|
29
|
+
const projectRoot = process.cwd();
|
|
30
|
+
|
|
31
|
+
logger.info('Initializing Ralph...');
|
|
32
|
+
logger.info(`Project: ${projectRoot}`);
|
|
33
|
+
console.log('');
|
|
34
|
+
|
|
35
|
+
// Step 1: Scan the project
|
|
36
|
+
const spinner = prompts.spinner();
|
|
37
|
+
spinner.start('Scanning project...');
|
|
38
|
+
|
|
39
|
+
const scanner = new Scanner();
|
|
40
|
+
let scanResult: ScanResult;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
scanResult = await scanner.scan(projectRoot);
|
|
44
|
+
spinner.stop('Project scanned successfully');
|
|
45
|
+
} catch (error) {
|
|
46
|
+
spinner.stop('Scan failed');
|
|
47
|
+
logger.error(`Failed to scan project: ${error instanceof Error ? error.message : String(error)}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Display scan results
|
|
52
|
+
console.log('');
|
|
53
|
+
console.log(pc.cyan('--- Scan Results ---'));
|
|
54
|
+
console.log(formatScanResult(scanResult));
|
|
55
|
+
console.log('');
|
|
56
|
+
|
|
57
|
+
// Step 2: AI Enhancement (if enabled)
|
|
58
|
+
let enhancedResult: EnhancedScanResult | undefined;
|
|
59
|
+
|
|
60
|
+
if (options.ai) {
|
|
61
|
+
const provider = options.provider || 'anthropic';
|
|
62
|
+
console.log(pc.cyan(`--- AI Enhancement (${provider}) ---`));
|
|
63
|
+
|
|
64
|
+
const aiEnhancer = new AIEnhancer({
|
|
65
|
+
provider,
|
|
66
|
+
verbose: true,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Check if API key is available
|
|
70
|
+
if (!aiEnhancer.isAvailable()) {
|
|
71
|
+
const envVar = aiEnhancer.getRequiredEnvVar();
|
|
72
|
+
logger.warn(`AI enhancement skipped: ${envVar} not set`);
|
|
73
|
+
logger.info(`To enable AI enhancement, set the ${envVar} environment variable`);
|
|
74
|
+
console.log('');
|
|
75
|
+
} else {
|
|
76
|
+
spinner.start('Running AI analysis...');
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
enhancedResult = await aiEnhancer.enhance(scanResult);
|
|
80
|
+
|
|
81
|
+
if (enhancedResult.aiEnhanced && enhancedResult.aiAnalysis) {
|
|
82
|
+
spinner.stop('AI analysis complete');
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log(formatAIAnalysis(enhancedResult.aiAnalysis));
|
|
85
|
+
|
|
86
|
+
// Use enhanced result for generation
|
|
87
|
+
scanResult = enhancedResult;
|
|
88
|
+
} else if (enhancedResult.aiError) {
|
|
89
|
+
spinner.stop('AI analysis failed');
|
|
90
|
+
logger.warn(`AI enhancement error: ${enhancedResult.aiError}`);
|
|
91
|
+
console.log('');
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
spinner.stop('AI analysis failed');
|
|
95
|
+
logger.warn(`AI enhancement error: ${error instanceof Error ? error.message : String(error)}`);
|
|
96
|
+
console.log('');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Step 3: Confirm with user (unless --yes)
|
|
102
|
+
if (!options.yes) {
|
|
103
|
+
const shouldContinue = await prompts.confirm({
|
|
104
|
+
message: 'Generate Ralph configuration files?',
|
|
105
|
+
initialValue: true,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (prompts.isCancel(shouldContinue) || !shouldContinue) {
|
|
109
|
+
logger.info('Initialization cancelled');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Step 4: Generate configuration files
|
|
115
|
+
console.log('');
|
|
116
|
+
spinner.start('Generating configuration files...');
|
|
117
|
+
|
|
118
|
+
const generator = new Generator({
|
|
119
|
+
existingFiles: 'backup',
|
|
120
|
+
generateConfig: true,
|
|
121
|
+
verbose: false,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const generationResult = await generator.generate(scanResult);
|
|
126
|
+
spinner.stop('Configuration files generated');
|
|
127
|
+
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log(pc.cyan('--- Generation Results ---'));
|
|
130
|
+
console.log(formatGenerationResult(generationResult));
|
|
131
|
+
|
|
132
|
+
if (generationResult.success) {
|
|
133
|
+
console.log('');
|
|
134
|
+
logger.success('Ralph initialized successfully!');
|
|
135
|
+
console.log('');
|
|
136
|
+
console.log('Next steps:');
|
|
137
|
+
console.log(' 1. Review the generated files in .ralph/');
|
|
138
|
+
console.log(' 2. Customize the prompts in .ralph/prompts/');
|
|
139
|
+
console.log(' 3. Run "ralph new <feature>" to create a feature spec');
|
|
140
|
+
console.log(' 4. Run "ralph run <feature>" to start development');
|
|
141
|
+
} else {
|
|
142
|
+
logger.warn('Initialization completed with some errors');
|
|
143
|
+
}
|
|
144
|
+
} catch (error) {
|
|
145
|
+
spinner.stop('Generation failed');
|
|
146
|
+
logger.error(`Failed to generate files: ${error instanceof Error ? error.message : String(error)}`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monitor Command
|
|
3
|
+
* Display real-time status of a feature loop
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn, execFileSync } from 'node:child_process';
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import { logger } from '../utils/logger.js';
|
|
10
|
+
import { loadConfigWithDefaults, hasConfig } from '../utils/config.js';
|
|
11
|
+
import pc from 'picocolors';
|
|
12
|
+
|
|
13
|
+
export interface MonitorOptions {
|
|
14
|
+
/** Use bash script monitor instead of built-in */
|
|
15
|
+
bash?: boolean;
|
|
16
|
+
/** Use Python TUI monitor */
|
|
17
|
+
python?: boolean;
|
|
18
|
+
/** Refresh interval in seconds */
|
|
19
|
+
interval?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface LoopStatus {
|
|
23
|
+
running: boolean;
|
|
24
|
+
phase: string;
|
|
25
|
+
iteration: number;
|
|
26
|
+
maxIterations: number;
|
|
27
|
+
tokensInput: number;
|
|
28
|
+
tokensOutput: number;
|
|
29
|
+
tasksDone: number;
|
|
30
|
+
tasksPending: number;
|
|
31
|
+
e2eDone: number;
|
|
32
|
+
e2ePending: number;
|
|
33
|
+
branch: string;
|
|
34
|
+
elapsed: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Find the ralph-monitor.sh script
|
|
39
|
+
*/
|
|
40
|
+
function findMonitorScript(projectRoot: string): string | null {
|
|
41
|
+
// Check .ralph/scripts first
|
|
42
|
+
const localScript = join(projectRoot, '.ralph', 'scripts', 'ralph-monitor.sh');
|
|
43
|
+
if (existsSync(localScript)) {
|
|
44
|
+
return localScript;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check for ralph directory as sibling (development setup)
|
|
48
|
+
const siblingRalph = join(projectRoot, '..', 'ralph', 'ralph-monitor.sh');
|
|
49
|
+
if (existsSync(siblingRalph)) {
|
|
50
|
+
return siblingRalph;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check in current directory (ralph repo)
|
|
54
|
+
const currentRalph = join(projectRoot, 'ralph-monitor.sh');
|
|
55
|
+
if (existsSync(currentRalph)) {
|
|
56
|
+
return currentRalph;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if a process matching pattern is running
|
|
64
|
+
* Uses pgrep with -f flag for full command line matching
|
|
65
|
+
*/
|
|
66
|
+
function isProcessRunning(pattern: string): boolean {
|
|
67
|
+
try {
|
|
68
|
+
// Use execFileSync for safer execution
|
|
69
|
+
const result = execFileSync('pgrep', ['-f', pattern], { encoding: 'utf-8' });
|
|
70
|
+
return result.trim().length > 0;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Detect current phase of the loop
|
|
78
|
+
*/
|
|
79
|
+
function detectPhase(feature: string): string {
|
|
80
|
+
if (isProcessRunning('PROMPT_feature.md')) return 'Planning';
|
|
81
|
+
if (isProcessRunning('PROMPT_e2e.md')) return 'E2E Testing';
|
|
82
|
+
if (isProcessRunning('PROMPT_verify.md')) return 'Verification';
|
|
83
|
+
if (isProcessRunning('PROMPT_review.md')) return 'PR Review';
|
|
84
|
+
if (isProcessRunning('PROMPT.md')) return 'Implementation';
|
|
85
|
+
if (isProcessRunning(`feature-loop.sh.*${feature}`)) return 'Running';
|
|
86
|
+
return 'Idle';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Read status from temp files
|
|
91
|
+
*/
|
|
92
|
+
function readStatus(feature: string): LoopStatus {
|
|
93
|
+
const statusFile = `/tmp/ralph-loop-${feature}.status`;
|
|
94
|
+
const tokensFile = `/tmp/ralph-loop-${feature}.tokens`;
|
|
95
|
+
|
|
96
|
+
let iteration = 0;
|
|
97
|
+
let maxIterations = 50;
|
|
98
|
+
|
|
99
|
+
// Read status file
|
|
100
|
+
if (existsSync(statusFile)) {
|
|
101
|
+
try {
|
|
102
|
+
const content = readFileSync(statusFile, 'utf-8').trim();
|
|
103
|
+
const parts = content.split('|');
|
|
104
|
+
iteration = parseInt(parts[0]) || 0;
|
|
105
|
+
maxIterations = parseInt(parts[1]) || 50;
|
|
106
|
+
} catch {
|
|
107
|
+
// Ignore errors
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Read tokens file
|
|
112
|
+
let tokensInput = 0;
|
|
113
|
+
let tokensOutput = 0;
|
|
114
|
+
if (existsSync(tokensFile)) {
|
|
115
|
+
try {
|
|
116
|
+
const content = readFileSync(tokensFile, 'utf-8').trim();
|
|
117
|
+
const parts = content.split('|');
|
|
118
|
+
tokensInput = parseInt(parts[0]) || 0;
|
|
119
|
+
tokensOutput = parseInt(parts[1]) || 0;
|
|
120
|
+
} catch {
|
|
121
|
+
// Ignore errors
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
running: isProcessRunning(`feature-loop.sh.*${feature}`),
|
|
127
|
+
phase: detectPhase(feature),
|
|
128
|
+
iteration,
|
|
129
|
+
maxIterations,
|
|
130
|
+
tokensInput,
|
|
131
|
+
tokensOutput,
|
|
132
|
+
tasksDone: 0,
|
|
133
|
+
tasksPending: 0,
|
|
134
|
+
e2eDone: 0,
|
|
135
|
+
e2ePending: 0,
|
|
136
|
+
branch: '',
|
|
137
|
+
elapsed: '',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Parse implementation plan for task counts
|
|
143
|
+
*/
|
|
144
|
+
async function parseImplementationPlan(
|
|
145
|
+
projectRoot: string,
|
|
146
|
+
feature: string
|
|
147
|
+
): Promise<{ tasksDone: number; tasksPending: number; e2eDone: number; e2ePending: number }> {
|
|
148
|
+
const config = await loadConfigWithDefaults(projectRoot);
|
|
149
|
+
const planPath = join(projectRoot, config.paths.specs, `${feature}-implementation-plan.md`);
|
|
150
|
+
|
|
151
|
+
let tasksDone = 0;
|
|
152
|
+
let tasksPending = 0;
|
|
153
|
+
let e2eDone = 0;
|
|
154
|
+
let e2ePending = 0;
|
|
155
|
+
|
|
156
|
+
if (existsSync(planPath)) {
|
|
157
|
+
try {
|
|
158
|
+
const content = readFileSync(planPath, 'utf-8');
|
|
159
|
+
const lines = content.split('\n');
|
|
160
|
+
|
|
161
|
+
for (const line of lines) {
|
|
162
|
+
if (line.match(/^- \[x\]/)) {
|
|
163
|
+
if (line.includes('E2E:')) {
|
|
164
|
+
e2eDone++;
|
|
165
|
+
} else {
|
|
166
|
+
tasksDone++;
|
|
167
|
+
}
|
|
168
|
+
} else if (line.match(/^- \[ \]/)) {
|
|
169
|
+
if (line.includes('E2E:')) {
|
|
170
|
+
e2ePending++;
|
|
171
|
+
} else {
|
|
172
|
+
tasksPending++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} catch {
|
|
177
|
+
// Ignore errors
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { tasksDone, tasksPending, e2eDone, e2ePending };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get current git branch
|
|
186
|
+
*/
|
|
187
|
+
function getGitBranch(projectRoot: string): string {
|
|
188
|
+
try {
|
|
189
|
+
// Try app directory first
|
|
190
|
+
const appDir = join(projectRoot, '..', 'app');
|
|
191
|
+
if (existsSync(appDir)) {
|
|
192
|
+
return execFileSync('git', ['branch', '--show-current'], {
|
|
193
|
+
cwd: appDir,
|
|
194
|
+
encoding: 'utf-8',
|
|
195
|
+
}).trim();
|
|
196
|
+
}
|
|
197
|
+
return execFileSync('git', ['branch', '--show-current'], {
|
|
198
|
+
cwd: projectRoot,
|
|
199
|
+
encoding: 'utf-8',
|
|
200
|
+
}).trim();
|
|
201
|
+
} catch {
|
|
202
|
+
return '-';
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Format number with K/M suffix
|
|
208
|
+
*/
|
|
209
|
+
function formatNumber(num: number): string {
|
|
210
|
+
if (num >= 1000000) {
|
|
211
|
+
return (num / 1000000).toFixed(1) + 'M';
|
|
212
|
+
}
|
|
213
|
+
if (num >= 1000) {
|
|
214
|
+
return (num / 1000).toFixed(1) + 'K';
|
|
215
|
+
}
|
|
216
|
+
return String(num);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Create a progress bar
|
|
221
|
+
*/
|
|
222
|
+
function progressBar(percent: number, width: number = 15): string {
|
|
223
|
+
const filled = Math.round((percent / 100) * width);
|
|
224
|
+
const empty = width - filled;
|
|
225
|
+
return pc.green('\u2588'.repeat(filled)) + pc.dim('\u2591'.repeat(empty));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Display built-in monitor dashboard
|
|
230
|
+
*/
|
|
231
|
+
async function displayDashboard(feature: string, projectRoot: string, interval: number = 5): Promise<void> {
|
|
232
|
+
const status = readStatus(feature);
|
|
233
|
+
const tasks = await parseImplementationPlan(projectRoot, feature);
|
|
234
|
+
const branch = getGitBranch(projectRoot);
|
|
235
|
+
|
|
236
|
+
// Calculate progress
|
|
237
|
+
const totalTasks = tasks.tasksDone + tasks.tasksPending;
|
|
238
|
+
const totalE2e = tasks.e2eDone + tasks.e2ePending;
|
|
239
|
+
const totalAll = totalTasks + totalE2e;
|
|
240
|
+
const doneAll = tasks.tasksDone + tasks.e2eDone;
|
|
241
|
+
|
|
242
|
+
const percentTasks = totalTasks > 0 ? Math.round((tasks.tasksDone / totalTasks) * 100) : 0;
|
|
243
|
+
const percentE2e = totalE2e > 0 ? Math.round((tasks.e2eDone / totalE2e) * 100) : 0;
|
|
244
|
+
const percentAll = totalAll > 0 ? Math.round((doneAll / totalAll) * 100) : 0;
|
|
245
|
+
|
|
246
|
+
// Clear screen
|
|
247
|
+
console.clear();
|
|
248
|
+
|
|
249
|
+
// Header
|
|
250
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
251
|
+
console.log(pc.bold('='.repeat(78)));
|
|
252
|
+
console.log(
|
|
253
|
+
pc.bold(' ') +
|
|
254
|
+
pc.cyan('RALPH MONITOR') +
|
|
255
|
+
`: ${pc.bold(feature)}` +
|
|
256
|
+
` ${pc.dim(timestamp)}`
|
|
257
|
+
);
|
|
258
|
+
console.log(pc.bold('='.repeat(78)));
|
|
259
|
+
console.log('');
|
|
260
|
+
|
|
261
|
+
// Status line
|
|
262
|
+
const phaseColors: Record<string, (s: string) => string> = {
|
|
263
|
+
Planning: pc.blue,
|
|
264
|
+
Implementation: pc.yellow,
|
|
265
|
+
'E2E Testing': pc.cyan,
|
|
266
|
+
Verification: pc.magenta,
|
|
267
|
+
'PR Review': pc.green,
|
|
268
|
+
Idle: pc.dim,
|
|
269
|
+
Running: pc.white,
|
|
270
|
+
};
|
|
271
|
+
const phaseColor = phaseColors[status.phase] || pc.white;
|
|
272
|
+
|
|
273
|
+
console.log(
|
|
274
|
+
` Phase: ${phaseColor(pc.bold(status.phase))}` +
|
|
275
|
+
` | Iter: ${pc.bold(String(status.iteration))}/${pc.dim(String(status.maxIterations))}` +
|
|
276
|
+
` | Branch: ${pc.cyan(branch)}`
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
const totalTokens = status.tokensInput + status.tokensOutput;
|
|
280
|
+
console.log(
|
|
281
|
+
` Tokens: ${pc.magenta(formatNumber(totalTokens))}` +
|
|
282
|
+
pc.dim(` (in:${formatNumber(status.tokensInput)} out:${formatNumber(status.tokensOutput)})`)
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
console.log(pc.dim(' ' + '-'.repeat(74)));
|
|
286
|
+
|
|
287
|
+
// Progress
|
|
288
|
+
console.log('');
|
|
289
|
+
console.log(
|
|
290
|
+
` ${pc.bold('Implementation:')} ${progressBar(percentTasks)} ${pc.bold(percentTasks + '%')}` +
|
|
291
|
+
` ${pc.green('\u2713 ' + tasks.tasksDone)} / ${pc.yellow('\u25cb ' + tasks.tasksPending)}`
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
if (totalE2e > 0) {
|
|
295
|
+
console.log(
|
|
296
|
+
` ${pc.bold('E2E Tests: ')} ${progressBar(percentE2e)} ${pc.bold(percentE2e + '%')}` +
|
|
297
|
+
` ${pc.green('\u2713 ' + tasks.e2eDone)} / ${pc.yellow('\u25cb ' + tasks.e2ePending)}`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
console.log(pc.dim(' ' + '-'.repeat(40)));
|
|
302
|
+
console.log(
|
|
303
|
+
` ${pc.bold('Overall: ')} ${progressBar(percentAll)} ${pc.bold(percentAll + '%')}` +
|
|
304
|
+
` ${pc.green('\u2713 ' + doneAll)} / ${pc.yellow('\u25cb ' + (totalAll - doneAll))}`
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// Status indicator
|
|
308
|
+
console.log('');
|
|
309
|
+
if (status.running) {
|
|
310
|
+
console.log(pc.green(' \u25cf Loop is running'));
|
|
311
|
+
} else {
|
|
312
|
+
console.log(pc.yellow(' \u25cb Loop is not running'));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
console.log('');
|
|
316
|
+
console.log(pc.dim(` Refreshing every ${interval}s | Press Ctrl+C to exit`));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Launch the monitoring dashboard for a feature
|
|
321
|
+
*/
|
|
322
|
+
export async function monitorCommand(feature: string, options: MonitorOptions = {}): Promise<void> {
|
|
323
|
+
const projectRoot = process.cwd();
|
|
324
|
+
|
|
325
|
+
// Validate feature name
|
|
326
|
+
if (!feature || typeof feature !== 'string') {
|
|
327
|
+
logger.error('Feature name is required');
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Sanitize feature name (allow alphanumeric, hyphens, underscores)
|
|
332
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(feature)) {
|
|
333
|
+
logger.error('Feature name must contain only letters, numbers, hyphens, and underscores');
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Validate interval
|
|
338
|
+
if (options.interval !== undefined && (options.interval < 1 || options.interval > 60)) {
|
|
339
|
+
logger.warn('Interval should be between 1 and 60 seconds. Using default (5).');
|
|
340
|
+
options.interval = 5;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
logger.info(`Monitoring feature: ${pc.bold(feature)}`);
|
|
344
|
+
console.log('');
|
|
345
|
+
|
|
346
|
+
// Check for bash monitor option
|
|
347
|
+
if (options.bash) {
|
|
348
|
+
const monitorScript = findMonitorScript(projectRoot);
|
|
349
|
+
if (!monitorScript) {
|
|
350
|
+
logger.error('ralph-monitor.sh script not found');
|
|
351
|
+
logger.info('The script should be in .ralph/scripts/ or the ralph/ directory');
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
logger.info(`Using bash monitor: ${monitorScript}`);
|
|
356
|
+
console.log('');
|
|
357
|
+
|
|
358
|
+
const child = spawn('bash', [monitorScript, feature], {
|
|
359
|
+
cwd: dirname(monitorScript),
|
|
360
|
+
stdio: 'inherit',
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
return new Promise((resolve, reject) => {
|
|
364
|
+
child.on('error', reject);
|
|
365
|
+
child.on('close', () => resolve());
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Python TUI option
|
|
370
|
+
if (options.python) {
|
|
371
|
+
logger.warn('Python TUI monitor not yet implemented');
|
|
372
|
+
logger.info('Using built-in monitor instead');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Built-in monitor
|
|
376
|
+
const intervalSeconds = options.interval || 5;
|
|
377
|
+
const intervalMs = intervalSeconds * 1000;
|
|
378
|
+
|
|
379
|
+
// Initial display
|
|
380
|
+
try {
|
|
381
|
+
await displayDashboard(feature, projectRoot, intervalSeconds);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
logger.error(`Failed to display dashboard: ${error instanceof Error ? error.message : String(error)}`);
|
|
384
|
+
if (process.env.DEBUG && error instanceof Error) {
|
|
385
|
+
console.error(error.stack);
|
|
386
|
+
}
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Refresh loop
|
|
391
|
+
const refreshTimer = setInterval(async () => {
|
|
392
|
+
try {
|
|
393
|
+
await displayDashboard(feature, projectRoot, intervalSeconds);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
// Log error but continue monitoring
|
|
396
|
+
logger.debug(`Dashboard refresh error: ${error instanceof Error ? error.message : String(error)}`);
|
|
397
|
+
}
|
|
398
|
+
}, intervalMs);
|
|
399
|
+
|
|
400
|
+
// Return a Promise that resolves on SIGINT
|
|
401
|
+
return new Promise<void>((resolve) => {
|
|
402
|
+
const cleanup = () => {
|
|
403
|
+
clearInterval(refreshTimer);
|
|
404
|
+
console.log('');
|
|
405
|
+
logger.info('Monitor stopped');
|
|
406
|
+
resolve();
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
process.on('SIGINT', cleanup);
|
|
410
|
+
process.on('SIGTERM', cleanup);
|
|
411
|
+
});
|
|
412
|
+
}
|