Files
Codeman/src/ralph-config.ts
T
arkonandClaude Opus 4.7 02e2f3e8b5 refactor: remove dead code and narrow internal exports (knip sweep)
Knip-driven cleanup. All changes verified with tsc --noEmit, lint, and
build.

Removed (zero consumers):
- VERIFICATION_PROMPT constant + its barrel re-export
- createInitialOrchestratorPersistState factory
- transcriptWatcher singleton export
- createAnsiPatternFull / createAnsiPatternSimple factories
- TimerInfo interface + unused AiCheckResult/AiPlanCheckResult imports
  in respawn-controller.ts
- 35 unused Zod z.infer \`*Input\` types in schemas.ts
- Dead re-exports: SessionMode from session.ts, AuthSessionRecord from
  web/ports/index.ts, EnhancedPlanTask/CheckpointReview from
  ralph-tracker.ts, 7 unused entries in utils/index.ts
- 14 event/config interfaces that lived only as JSDoc hints (no TS type
  position usage): Session/Respawn/RalphLoop/RalphTracker/
  SessionManager/SessionAutoOps/Subagent/TaskQueue/TaskTracker/
  TranscriptWatcher/Image/OrchestratorLoop Events + RespawnPreset +
  SessionOutput

Narrowed to module scope (kept but no longer exported):
- buildPermissionArgs in session-cli-builder.ts
- 28 type/interface declarations used only within their own file:
  Ai{Idle,Plan}Check{Config,State}, BashToolParser{Events,Config},
  FileStream/CreateStream{Options,Result}, PlanSubagentEvent,
  SubagentCallback, RalphLoopConfig, RalphLoop{Events,Options},
  ActiveTimerInfo, DetectionStatus, ActionLogEntry, AutoOpsCallbacks,
  TunnelStatus, Timer/LRUMap/StaleExpirationMap Options, AuthState,
  SessionListenerDeps, SseStreamManagerDeps, and 8 more

Docs: CLAUDE.md advice for global-regex `lastIndex` now points to the
remaining `execPattern()` helper instead of the deleted factories.

Knip delta: unused files 42→0, unused exports 161→16, unused types 92→0.
The 16 remaining exports are a mobile-test helper toolkit intentionally
kept for upcoming tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:57:00 +02:00

153 lines
4.5 KiB
TypeScript

/**
* @fileoverview Ralph Wiggum configuration parser
*
* Parses the official Ralph Wiggum plugin state file (.claude/ralph-loop.local.md)
* and extracts configuration for session tracking.
*
* @module ralph-config
*/
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { execPattern } from './utils/index.js';
// Pattern to extract completion phrase from CLAUDE.md
// Matches <promise>PHRASE</promise> with optional whitespace
// Allows: letters, numbers, underscores, hyphens (e.g., TESTS-PASS, ALL_DONE)
const CLAUDE_MD_PROMISE_PATTERN = /<promise>\s*([A-Z0-9_-]+)\s*<\/promise>/gi;
// Pattern to parse YAML frontmatter from ralph-loop.local.md
// Extracts key: value pairs from between --- markers
const YAML_FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---/;
const YAML_LINE_PATTERN = /^([a-zA-Z_-]+):\s*"?([^"\n]+)"?\s*$/gm;
/**
* Ralph Loop configuration from .claude/ralph-loop.local.md
*/
interface RalphLoopConfig {
enabled: boolean;
iteration: number;
maxIterations: number | null;
completionPromise: string | null;
}
/**
* Parse Ralph Wiggum loop config from .claude/ralph-loop.local.md
*
* The official Ralph Wiggum plugin stores state in this file with YAML frontmatter:
* ```yaml
* ---
* enabled: true
* iteration: 5
* max-iterations: 50
* completion-promise: "COMPLETE"
* ---
* # Original Prompt
* ...
* ```
*
* @param workingDir - Working directory to search in
* @returns Parsed config or null if file doesn't exist or is invalid
*/
export function parseRalphLoopConfig(workingDir: string): RalphLoopConfig | null {
const configPath = join(workingDir, '.claude', 'ralph-loop.local.md');
try {
if (!existsSync(configPath)) return null;
const content = readFileSync(configPath, 'utf-8');
return parseRalphLoopConfigFromContent(content);
} catch (err) {
console.error(`[ralph-config] Error reading ralph-loop.local.md: ${err}`);
return null;
}
}
/**
* Parse Ralph Wiggum loop config from file content.
* Exported for testing.
*
* @param content - File content to parse
* @returns Parsed config or null if content is invalid
*/
export function parseRalphLoopConfigFromContent(content: string): RalphLoopConfig | null {
// Extract YAML frontmatter
const frontmatterMatch = content.match(YAML_FRONTMATTER_PATTERN);
if (!frontmatterMatch) return null;
const yaml = frontmatterMatch[1];
const config: RalphLoopConfig = {
enabled: false,
iteration: 0,
maxIterations: null,
completionPromise: null,
};
// Parse each YAML line
execPattern(YAML_LINE_PATTERN, yaml, (match) => {
const key = match[1].toLowerCase();
const value = match[2].trim();
switch (key) {
case 'enabled':
config.enabled = value.toLowerCase() === 'true' || value.toLowerCase() === 'yes';
break;
case 'iteration':
config.iteration = parseInt(value, 10) || 0;
break;
case 'max-iterations':
config.maxIterations = parseInt(value, 10) || null;
break;
case 'completion-promise':
config.completionPromise = value.toUpperCase();
break;
}
});
return config;
}
/**
* Extract completion phrase from CLAUDE.md content.
* Looks for <promise>PHRASE</promise> pattern.
*
* Handles multiple variations:
* - Raw text: <promise>PHRASE</promise>
* - In backticks: `<promise>PHRASE</promise>`
* - With whitespace: <promise> PHRASE </promise>
* - Multiple occurrences: returns the first one
*
* @param claudeMdPath - Path to CLAUDE.md file
* @returns The completion phrase (uppercase), or null if not found
*/
export function extractCompletionPhrase(claudeMdPath: string): string | null {
try {
if (!existsSync(claudeMdPath)) return null;
const content = readFileSync(claudeMdPath, 'utf-8');
return extractCompletionPhraseFromContent(content);
} catch (err) {
console.error(`[ralph-config] Error reading CLAUDE.md: ${err}`);
return null;
}
}
/**
* Extract completion phrase from CLAUDE.md content string.
* Exported for testing.
*
* @param content - CLAUDE.md content to parse
* @returns The completion phrase (uppercase), or null if not found
*/
export function extractCompletionPhraseFromContent(content: string): string | null {
// Reset regex state (global flag)
CLAUDE_MD_PROMISE_PATTERN.lastIndex = 0;
// Find all matches and return the first one
const match = CLAUDE_MD_PROMISE_PATTERN.exec(content);
if (match && match[1]) {
return match[1].trim().toUpperCase();
}
return null;
}