ralphinator 1.0.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,82 @@
1
+ # Step 6: Verify and Finalize Target
2
+
3
+ You are in Step 6 of a legacy code cleanup workflow. Execute this task immediately without asking for confirmation.
4
+
5
+ ## Your Task (Execute Now)
6
+
7
+ 1. Read CURRENT_TARGET.md to understand what was cleaned
8
+ 2. Read REFACTORING_LOG.md to see all changes made
9
+ 3. Read CHARACTERIZATION_TESTS.md to see test coverage added
10
+ 4. Run full test suite to verify everything passes
11
+ 5. Use Playwright MCP to verify UI behavior if the target involves UI components
12
+ 6. Review the cleaned code for quality
13
+
14
+ ## Verification Checklist:
15
+
16
+ ### Tests
17
+ - [ ] All characterization tests pass
18
+ - [ ] All existing tests still pass
19
+ - [ ] No test was deleted or weakened
20
+ - [ ] Test coverage is adequate for the cleaned area
21
+
22
+ ### Code Quality
23
+ - [ ] Dependencies are properly broken/injected
24
+ - [ ] Code is more readable than before
25
+ - [ ] No new code smells introduced
26
+ - [ ] Changes follow project conventions
27
+
28
+ ### Behavior Review
29
+ - [ ] Do the observed behaviors make sense?
30
+ - [ ] Are there any behaviors that seem like bugs rather than features?
31
+ - [ ] Any suspicious patterns (e.g., silent error swallowing, race conditions, security issues)?
32
+ - [ ] Does the code do what it claims to do?
33
+
34
+ ### UI Verification (if applicable)
35
+ Use Playwright MCP to:
36
+ - [ ] Verify the UI still renders correctly
37
+ - [ ] Test key user interactions work as before
38
+ - [ ] Check for visual regressions
39
+ - [ ] Confirm navigation and state changes behave correctly
40
+
41
+ If you identify concerning behaviors, create **CONCERNS.md** documenting:
42
+ - What behavior seems wrong
43
+ - Why it's concerning
44
+ - Suggested investigation or fix (for future iterations)
45
+
46
+ Note: Concerns don't block verification - they're observations for future cleanup targets.
47
+
48
+ ### Documentation
49
+ - [ ] REFACTORING_LOG.md accurately reflects changes
50
+ - [ ] Any necessary code comments added
51
+ - [ ] README updated if public API changed
52
+
53
+ ## After Verification:
54
+
55
+ 1. Add the completed target to CLEANED_TARGETS.md with:
56
+ - What was cleaned
57
+ - Summary of improvements
58
+ - Number of characterization tests added
59
+ - Key refactorings performed
60
+
61
+ 2. Clean up workflow files:
62
+ - Delete CURRENT_TARGET.md
63
+ - Delete BEHAVIOR.md
64
+ - Delete CHARACTERIZATION_TESTS.md
65
+ - Delete REFACTORING_LOG.md
66
+ - Delete TARGET_COMPLETE.md
67
+
68
+ 3. Create VERIFICATION_COMPLETE.md (empty file)
69
+
70
+ ## If Issues Found:
71
+
72
+ If verification reveals problems:
73
+ 1. Document issues in ISSUES.md
74
+ 2. Do NOT delete workflow files
75
+ 3. Create NEEDS_REWORK.md with description of what's wrong
76
+ 4. The workflow will return to an earlier step
77
+
78
+ ## Important:
79
+ - Be thorough - this is the quality gate
80
+ - Don't approve incomplete work
81
+ - The goal is sustainable improvement, not perfection
82
+ - Do NOT ask for confirmation - execute immediately and create the output file
package/src/build.js ADDED
@@ -0,0 +1,162 @@
1
+ import {
2
+ logInfo,
3
+ logSuccess,
4
+ logError,
5
+ logStep,
6
+ fileExists,
7
+ fileNotEmpty,
8
+ removeFile,
9
+ ensureFile,
10
+ validateEnvironment,
11
+ invokeRalph,
12
+ sleep,
13
+ } from './utils.js';
14
+
15
+ async function executeStep1() {
16
+ logStep('STEP 1: Generate Story or Check Project Completion');
17
+ logInfo('CEO Ralph deciding: Project done? Or what story next?');
18
+
19
+ removeFile('STORY_COMPLETE.md');
20
+ await invokeRalph('ceo-ralph');
21
+
22
+ if (fileExists('PROJECT_COMPLETE.md')) {
23
+ logSuccess('Project marked as complete!');
24
+ } else if (fileExists('NEXT_STORY.md')) {
25
+ logSuccess('Next story generated');
26
+ } else {
27
+ logError('Step 1 failed');
28
+ process.exit(1);
29
+ }
30
+ }
31
+
32
+ async function executeStep2() {
33
+ logStep('STEP 2: Break Down User Story into Use Cases');
34
+ logInfo('Business Analyst Ralph making use cases!');
35
+
36
+ await invokeRalph('business-analyst-ralph');
37
+
38
+ if (fileExists('NEXT_USE_CASES.md') && !fileExists('NEXT_STORY.md')) {
39
+ logSuccess('Use cases generated');
40
+ } else {
41
+ logError('Step 2 failed');
42
+ process.exit(1);
43
+ }
44
+ }
45
+
46
+ async function executeStep3() {
47
+ logStep('STEP 3: Select Next Use Case');
48
+ logInfo('Product Owner Ralph picking which use case to do!');
49
+
50
+ await invokeRalph('product-owner-ralph');
51
+
52
+ if (!fileExists('NEXT_USE_CASES.md')) {
53
+ logSuccess('All use cases complete! Moving to refactoring.');
54
+ } else if (fileExists('CURRENT_USE_CASE.md')) {
55
+ logSuccess('Use case selected');
56
+ } else {
57
+ logError('Step 3 failed');
58
+ process.exit(1);
59
+ }
60
+ }
61
+
62
+ async function executeStep3_5() {
63
+ logStep('STEP 3.5: Implement One Task');
64
+ logInfo('Developer Ralph coding! ONE small thing!');
65
+
66
+ await invokeRalph('developer-ralph');
67
+
68
+ if (!fileExists('CURRENT_USE_CASE.md')) {
69
+ logSuccess('Use case completed');
70
+ } else {
71
+ logSuccess('Task completed');
72
+ }
73
+ }
74
+
75
+ async function executeStep4() {
76
+ logStep('STEP 4: Refactoring');
77
+ logInfo('Code Reviewer Ralph looking at code!');
78
+
79
+ ensureFile('REFACTORING.md');
80
+ await invokeRalph('code-reviewer-ralph');
81
+
82
+ if (!fileExists('REFACTORING.md')) {
83
+ logSuccess('Refactoring complete');
84
+ } else {
85
+ logSuccess('Refactoring iteration complete');
86
+ }
87
+ }
88
+
89
+ async function executeStep5() {
90
+ logStep('STEP 5: Story Completion Notification');
91
+ logInfo('Project Manager Ralph sending update!');
92
+
93
+ await invokeRalph('project-manager-ralph');
94
+
95
+ if (fileExists('STORY_COMPLETE.md')) {
96
+ logSuccess('Notification sent');
97
+ } else {
98
+ logError('Step 5 failed');
99
+ process.exit(1);
100
+ }
101
+ }
102
+
103
+ export async function runBuildWorkflow(options) {
104
+ logInfo('Starting Ralph Wiggum Software Corporation!');
105
+ logInfo('Me build software! One small task at a time!');
106
+ logInfo(`Topic: ${options.topic}`);
107
+ console.log();
108
+
109
+ validateEnvironment('PROMPT.md');
110
+
111
+ ensureFile('IMPLEMENTED_STORIES.md', '# Implemented User Stories\n');
112
+ ensureFile('IMPLEMENTED_CASES.md', '# Implemented Use Cases\n');
113
+
114
+ const maxIterations = parseInt(options.maxIterations, 10);
115
+ let iteration = 0;
116
+
117
+ while (true) {
118
+ iteration++;
119
+ logInfo(`=== Iteration ${iteration} ===`);
120
+
121
+ if (iteration > maxIterations) {
122
+ logError('Maximum iterations reached');
123
+ process.exit(1);
124
+ }
125
+
126
+ if (fileExists('PROJECT_COMPLETE.md')) {
127
+ logSuccess('Project complete! Ralph Wiggum Software Corporation successful!');
128
+ break;
129
+ }
130
+
131
+ if (fileExists('CURRENT_USE_CASE.md')) {
132
+ await executeStep3_5();
133
+ } else if (fileExists('REFACTORING.md')) {
134
+ await executeStep4();
135
+ } else if (fileNotEmpty('NEXT_USE_CASES.md')) {
136
+ await executeStep3();
137
+ } else if (fileExists('NEXT_USE_CASES.md') && !fileNotEmpty('NEXT_USE_CASES.md')) {
138
+ removeFile('NEXT_USE_CASES.md');
139
+ await executeStep4();
140
+ } else if (fileExists('NEXT_STORY.md')) {
141
+ await executeStep2();
142
+ } else if (
143
+ fileExists('STORY_COMPLETE.md') ||
144
+ (!fileExists('NEXT_STORY.md') &&
145
+ !fileExists('NEXT_USE_CASES.md') &&
146
+ !fileExists('REFACTORING_COMPLETE.md') &&
147
+ !fileExists('CURRENT_USE_CASE.md') &&
148
+ !fileExists('REFACTORING.md'))
149
+ ) {
150
+ await executeStep1();
151
+ } else if (fileExists('REFACTORING_COMPLETE.md')) {
152
+ await executeStep5();
153
+ } else {
154
+ logError('Unexpected state');
155
+ process.exit(1);
156
+ }
157
+
158
+ await sleep(1000);
159
+ }
160
+
161
+ logSuccess(`Ralph finished after ${iteration} iterations!`);
162
+ }
package/src/cli.js ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { select, input } from '@inquirer/prompts';
4
+ import { runBuildWorkflow } from './build.js';
5
+ import { runFixWorkflow } from './fix.js';
6
+
7
+ console.log();
8
+ console.log('🔧 Ralph Wiggum Software Corporation');
9
+ console.log(' "Me build software! One small task at a time!"');
10
+ console.log();
11
+
12
+ async function main() {
13
+ const workflow = await select({
14
+ message: 'What would you like to do?',
15
+ choices: [
16
+ { name: 'Build new software (requires PROMPT.md)', value: 'build' },
17
+ { name: 'Fix legacy code (requires LEGACY_PROMPT.md)', value: 'fix' },
18
+ ],
19
+ });
20
+
21
+ let topic = process.env.NTFY_TOPIC;
22
+ if (!topic) {
23
+ topic = await input({
24
+ message: 'Enter your ntfy.sh topic for notifications:',
25
+ validate: (value) => value.length > 0 || 'Topic is required',
26
+ });
27
+ }
28
+
29
+ const maxIterations = await input({
30
+ message: 'Maximum iterations:',
31
+ default: '1000',
32
+ });
33
+
34
+ const options = { topic, maxIterations };
35
+
36
+ if (workflow === 'build') {
37
+ await runBuildWorkflow(options);
38
+ } else {
39
+ await runFixWorkflow(options);
40
+ }
41
+ }
42
+
43
+ main().catch((err) => {
44
+ if (err.name === 'ExitPromptError') {
45
+ console.log('\nGoodbye!');
46
+ process.exit(0);
47
+ }
48
+ console.error(err);
49
+ process.exit(1);
50
+ });
package/src/fix.js ADDED
@@ -0,0 +1,177 @@
1
+ import {
2
+ logInfo,
3
+ logSuccess,
4
+ logError,
5
+ logStep,
6
+ fileExists,
7
+ removeFile,
8
+ ensureFile,
9
+ validateEnvironment,
10
+ invokeRalph,
11
+ sleep,
12
+ } from './utils.js';
13
+
14
+ async function executeSurveyor() {
15
+ logStep('SURVEYOR RALPH: Me look at code! Me find messy parts!');
16
+ removeFile('CHRONICLE_COMPLETE.md');
17
+ await invokeRalph('surveyor-ralph');
18
+
19
+ if (fileExists('CLEANUP_COMPLETE.md')) {
20
+ logSuccess('All cleanup complete!');
21
+ } else if (fileExists('NEXT_TARGET.md')) {
22
+ logSuccess('Next target identified');
23
+ } else {
24
+ logError('Surveyor Ralph failed');
25
+ process.exit(1);
26
+ }
27
+ }
28
+
29
+ async function executeArchaeologist() {
30
+ logStep('ARCHAEOLOGIST RALPH: Me dig into code! Me understand what it does!');
31
+ await invokeRalph('archaeologist-ralph');
32
+
33
+ if (
34
+ (fileExists('TRIVIAL_FIX.md') || fileExists('BEHAVIOR.md')) &&
35
+ fileExists('CURRENT_TARGET.md') &&
36
+ !fileExists('NEXT_TARGET.md')
37
+ ) {
38
+ logSuccess('Behavior documented');
39
+ } else {
40
+ logError('Archaeologist Ralph failed');
41
+ process.exit(1);
42
+ }
43
+ }
44
+
45
+ async function executeTestWriter() {
46
+ logStep('TEST WRITER RALPH: Me write ONE test! Me capture behavior!');
47
+ await invokeRalph('test-writer-ralph');
48
+
49
+ if (fileExists('TESTS_COMPLETE.md')) {
50
+ logSuccess('Characterization tests complete');
51
+ } else {
52
+ logSuccess('Characterization test written');
53
+ }
54
+ }
55
+
56
+ async function executeSeamFinder() {
57
+ logStep('SEAM FINDER RALPH: Me find seams! Me plan how to break dependencies!');
58
+ await invokeRalph('seam-finder-ralph');
59
+
60
+ if (fileExists('SEAM_PLAN.md') && !fileExists('TESTS_COMPLETE.md')) {
61
+ logSuccess('Seam plan created');
62
+ } else {
63
+ logError('Seam Finder Ralph failed');
64
+ process.exit(1);
65
+ }
66
+ }
67
+
68
+ async function executeArchitect() {
69
+ logStep('ARCHITECT RALPH: Me look at big picture!');
70
+ await invokeRalph('architect-ralph');
71
+
72
+ if (fileExists('ARCHITECTURE_PLAN.md') || fileExists('REFACTORING_APPROVED.md')) {
73
+ logSuccess('Architectural review complete');
74
+ } else {
75
+ logError('Architect Ralph failed');
76
+ process.exit(1);
77
+ }
78
+ }
79
+
80
+ async function executeSurgeon() {
81
+ logStep('SURGEON RALPH: Me make ONE tiny change! Me run tests! Me commit!');
82
+ await invokeRalph('surgeon-ralph');
83
+
84
+ if (fileExists('TARGET_COMPLETE.md')) {
85
+ logSuccess('Target cleanup complete');
86
+ } else {
87
+ logSuccess('Refactoring step completed');
88
+ }
89
+ }
90
+
91
+ async function executeVerifier() {
92
+ logStep('VERIFIER RALPH: Me check everything! Me make sure it good!');
93
+ await invokeRalph('verifier-ralph');
94
+
95
+ if (fileExists('VERIFICATION_COMPLETE.md')) {
96
+ logSuccess('Verification passed');
97
+ } else if (fileExists('NEEDS_REWORK.md')) {
98
+ logInfo('Rework needed - returning to earlier step');
99
+ } else {
100
+ logError('Verifier Ralph failed');
101
+ process.exit(1);
102
+ }
103
+ }
104
+
105
+ async function executeChronicler() {
106
+ logStep('CHRONICLER RALPH: Me write down what happened! Me tell everyone!');
107
+ await invokeRalph('chronicler-ralph');
108
+
109
+ if (fileExists('CHRONICLE_COMPLETE.md')) {
110
+ logSuccess('Chronicle complete');
111
+ } else {
112
+ logError('Chronicler Ralph failed');
113
+ process.exit(1);
114
+ }
115
+ }
116
+
117
+ export async function runFixWorkflow(options) {
118
+ logInfo('Starting Ralph Wiggum Legacy Code Cleanup!');
119
+ logInfo('Me make old code better! With tests first!');
120
+ logInfo(`Topic: ${options.topic}`);
121
+ console.log();
122
+
123
+ validateEnvironment('LEGACY_PROMPT.md');
124
+ ensureFile('CLEANED_TARGETS.md', '# Cleaned Targets\n');
125
+
126
+ const maxIterations = parseInt(options.maxIterations, 10);
127
+ let iteration = 0;
128
+
129
+ while (true) {
130
+ iteration++;
131
+ logInfo(`=== Iteration ${iteration} ===`);
132
+
133
+ if (iteration > maxIterations) {
134
+ logError('Maximum iterations reached');
135
+ process.exit(1);
136
+ }
137
+
138
+ if (fileExists('CLEANUP_COMPLETE.md')) {
139
+ logSuccess('Legacy cleanup complete! Me made old code better!');
140
+ break;
141
+ }
142
+
143
+ if (fileExists('NEEDS_REWORK.md')) {
144
+ removeFile('NEEDS_REWORK.md');
145
+ removeFile('TARGET_COMPLETE.md');
146
+ removeFile('SEAM_PLAN.md');
147
+ removeFile('ARCHITECTURE_PLAN.md');
148
+ removeFile('REFACTORING_APPROVED.md');
149
+ await executeTestWriter();
150
+ } else if (fileExists('VERIFICATION_COMPLETE.md')) {
151
+ await executeChronicler();
152
+ } else if (fileExists('TARGET_COMPLETE.md')) {
153
+ await executeVerifier();
154
+ } else if (fileExists('TRIVIAL_FIX.md') && fileExists('CURRENT_TARGET.md')) {
155
+ await executeSurgeon();
156
+ } else if (fileExists('ARCHITECTURE_PLAN.md') || fileExists('REFACTORING_APPROVED.md')) {
157
+ await executeSurgeon();
158
+ } else if (fileExists('SEAM_PLAN.md')) {
159
+ await executeArchitect();
160
+ } else if (fileExists('TESTS_COMPLETE.md')) {
161
+ await executeSeamFinder();
162
+ } else if (fileExists('BEHAVIOR.md') && fileExists('CURRENT_TARGET.md')) {
163
+ await executeTestWriter();
164
+ } else if (fileExists('NEXT_TARGET.md')) {
165
+ await executeArchaeologist();
166
+ } else if (fileExists('CHRONICLE_COMPLETE.md') || !fileExists('CURRENT_TARGET.md')) {
167
+ await executeSurveyor();
168
+ } else {
169
+ logError('Unexpected state');
170
+ process.exit(1);
171
+ }
172
+
173
+ await sleep(1000);
174
+ }
175
+
176
+ logSuccess(`Ralph cleaned legacy code after ${iteration} iterations!`);
177
+ }
package/src/utils.js ADDED
@@ -0,0 +1,122 @@
1
+ import { existsSync, statSync, unlinkSync, writeFileSync } from 'fs';
2
+ import { execSync, spawn } from 'child_process';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ export const RALPHS_DIR = join(__dirname, '..', 'ralphs');
10
+
11
+ // Colors
12
+ const colors = {
13
+ red: '\x1b[31m',
14
+ green: '\x1b[32m',
15
+ blue: '\x1b[34m',
16
+ yellow: '\x1b[33m',
17
+ reset: '\x1b[0m',
18
+ };
19
+
20
+ export function logInfo(msg) {
21
+ console.log(`${colors.blue}[INFO]${colors.reset} ${msg}`);
22
+ }
23
+
24
+ export function logSuccess(msg) {
25
+ console.log(`${colors.green}[SUCCESS]${colors.reset} ${msg}`);
26
+ }
27
+
28
+ export function logError(msg) {
29
+ console.error(`${colors.red}[ERROR]${colors.reset} ${msg}`);
30
+ }
31
+
32
+ export function logStep(msg) {
33
+ console.log();
34
+ console.log(`${colors.green}=======================================================${colors.reset}`);
35
+ console.log(`${colors.green}${msg}${colors.reset}`);
36
+ console.log(`${colors.green}=======================================================${colors.reset}`);
37
+ console.log();
38
+ }
39
+
40
+ export function fileExists(path) {
41
+ return existsSync(path);
42
+ }
43
+
44
+ export function fileNotEmpty(path) {
45
+ if (!existsSync(path)) return false;
46
+ const stats = statSync(path);
47
+ return stats.size > 0;
48
+ }
49
+
50
+ export function removeFile(path) {
51
+ if (existsSync(path)) unlinkSync(path);
52
+ }
53
+
54
+ export function ensureFile(path, content = '') {
55
+ if (!existsSync(path)) {
56
+ writeFileSync(path, content);
57
+ }
58
+ }
59
+
60
+ export function validateEnvironment(requiredPrompt) {
61
+ logInfo('Validating environment...');
62
+
63
+ try {
64
+ execSync('which claude', { stdio: 'ignore' });
65
+ } catch {
66
+ logError('Claude Code is not installed or not in PATH');
67
+ logError('Install with: npm install -g @anthropic-ai/claude-code');
68
+ process.exit(1);
69
+ }
70
+
71
+ if (!existsSync('.git')) {
72
+ logError('Not a git repository. Please run "git init" first.');
73
+ process.exit(1);
74
+ }
75
+
76
+ try {
77
+ execSync('git config user.name', { stdio: 'ignore' });
78
+ execSync('git config user.email', { stdio: 'ignore' });
79
+ } catch {
80
+ logError('Git user.name and user.email must be configured');
81
+ process.exit(1);
82
+ }
83
+
84
+ if (!existsSync(requiredPrompt)) {
85
+ logError(`${requiredPrompt} not found`);
86
+ process.exit(1);
87
+ }
88
+
89
+ logSuccess('Environment validation complete');
90
+ }
91
+
92
+ export function invokeRalph(ralphName) {
93
+ const promptPath = join(RALPHS_DIR, `${ralphName}.md`);
94
+
95
+ if (!existsSync(promptPath)) {
96
+ logError(`Ralph prompt not found: ${promptPath}`);
97
+ process.exit(1);
98
+ }
99
+
100
+ logInfo(`Invoking Claude Code for ${ralphName}...`);
101
+
102
+ return new Promise((resolve, reject) => {
103
+ const child = spawn('claude', ['-p', '--dangerously-skip-permissions', promptPath], {
104
+ stdio: 'inherit',
105
+ shell: true,
106
+ });
107
+
108
+ child.on('close', (code) => {
109
+ if (code === 0) {
110
+ resolve();
111
+ } else {
112
+ reject(new Error(`Claude exited with code ${code}`));
113
+ }
114
+ });
115
+
116
+ child.on('error', reject);
117
+ });
118
+ }
119
+
120
+ export function sleep(ms) {
121
+ return new Promise((resolve) => setTimeout(resolve, ms));
122
+ }