mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 06:29:42 +02:00
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>
147 lines
5.1 KiB
TypeScript
147 lines
5.1 KiB
TypeScript
/**
|
||
* @fileoverview AI-Powered Plan Mode Checker for Auto-Accept
|
||
*
|
||
* Spawns a fresh Claude CLI session in a tmux session to analyze terminal output
|
||
* and determine if Claude Code is showing a plan mode approval prompt.
|
||
* Used as a confirmation gate before auto-accepting prompts.
|
||
*
|
||
* ## How It Works
|
||
*
|
||
* 1. Generate temp file path for output capture
|
||
* 2. Spawn tmux: `tmux new-session -d -s codeman-plancheck-<short> bash -c 'claude -p ...'`
|
||
* 3. Poll the temp file every 500ms for `__PLANCHECK_DONE__` marker
|
||
* 4. Parse the file content for PLAN_MODE/NOT_PLAN_MODE on the first line
|
||
* 5. Kill tmux session and delete temp file
|
||
*
|
||
* ## Error Handling
|
||
*
|
||
* - Tmux spawn fails: 30s cooldown, increment error counter
|
||
* - Check times out (60s): Kill session, 30s cooldown
|
||
* - Can't parse verdict: Treat as NOT_PLAN_MODE, 30s cooldown
|
||
* - 3 consecutive errors: Disable AI plan check
|
||
*
|
||
* @module ai-plan-checker
|
||
*/
|
||
|
||
import {
|
||
AiCheckerBase,
|
||
type AiCheckerConfigBase,
|
||
type AiCheckerResultBase,
|
||
type AiCheckerStateBase,
|
||
} from './ai-checker-base.js';
|
||
import {
|
||
AI_CHECK_MODEL,
|
||
AI_PLAN_CHECK_MAX_CONTEXT,
|
||
AI_PLAN_CHECK_TIMEOUT_MS,
|
||
AI_PLAN_CHECK_COOLDOWN_MS,
|
||
AI_PLAN_CHECK_ERROR_COOLDOWN_MS,
|
||
AI_CHECK_MAX_CONSECUTIVE_ERRORS,
|
||
} from './config/ai-defaults.js';
|
||
|
||
// ========== Types ==========
|
||
|
||
type AiPlanCheckConfig = AiCheckerConfigBase;
|
||
|
||
export type AiPlanCheckVerdict = 'PLAN_MODE' | 'NOT_PLAN_MODE' | 'ERROR';
|
||
|
||
export type AiPlanCheckResult = AiCheckerResultBase<AiPlanCheckVerdict>;
|
||
|
||
type AiPlanCheckState = AiCheckerStateBase<AiPlanCheckVerdict>;
|
||
|
||
// ========== Constants ==========
|
||
|
||
const DEFAULT_PLAN_CHECK_CONFIG: AiPlanCheckConfig = {
|
||
enabled: true,
|
||
model: AI_CHECK_MODEL,
|
||
maxContextChars: AI_PLAN_CHECK_MAX_CONTEXT,
|
||
checkTimeoutMs: AI_PLAN_CHECK_TIMEOUT_MS,
|
||
cooldownMs: AI_PLAN_CHECK_COOLDOWN_MS,
|
||
errorCooldownMs: AI_PLAN_CHECK_ERROR_COOLDOWN_MS,
|
||
maxConsecutiveErrors: AI_CHECK_MAX_CONSECUTIVE_ERRORS,
|
||
};
|
||
|
||
/** Pattern to match PLAN_MODE or NOT_PLAN_MODE as the first word(s) of output */
|
||
const VERDICT_PATTERN = /^\s*(PLAN_MODE|NOT_PLAN_MODE)\b/i;
|
||
|
||
/** The prompt sent to the AI plan checker */
|
||
const AI_PLAN_CHECK_PROMPT = `Analyze this terminal output from a running Claude Code session. Determine if the terminal is currently showing a PLAN MODE APPROVAL PROMPT or not.
|
||
|
||
A plan mode approval prompt is a numbered selection menu that Claude Code shows when it wants the user to approve a plan before proceeding. It typically has these characteristics:
|
||
- A numbered list of options (e.g., "1. Yes", "2. No", "3. Type your own")
|
||
- A selection indicator arrow (❯ or >) pointing to one of the options
|
||
- Text asking for approval like "Would you like to proceed?" or "Ready to implement?"
|
||
- The prompt appears at the BOTTOM of the output (most recent content)
|
||
|
||
NOT a plan mode prompt:
|
||
- Claude actively working (spinners, "Thinking", tool execution)
|
||
- A completed response with no selection menu
|
||
- An AskUserQuestion/elicitation dialog (different format, free-text input)
|
||
- Network lag or mid-output pause
|
||
- Any state without a visible numbered selection menu
|
||
|
||
Terminal output (most recent at bottom):
|
||
---
|
||
{TERMINAL_BUFFER}
|
||
---
|
||
|
||
Answer with EXACTLY one of these on the first line: PLAN_MODE or NOT_PLAN_MODE
|
||
Then optionally explain briefly why.`;
|
||
|
||
// ========== AiPlanChecker Class ==========
|
||
|
||
/**
|
||
* Manages AI-powered plan mode detection by spawning a fresh Claude CLI session
|
||
* to analyze terminal output and confirm plan mode approval prompts.
|
||
*/
|
||
export class AiPlanChecker extends AiCheckerBase<
|
||
AiPlanCheckVerdict,
|
||
AiPlanCheckConfig,
|
||
AiPlanCheckResult,
|
||
AiPlanCheckState
|
||
> {
|
||
protected readonly muxNamePrefix = 'codeman-plancheck-';
|
||
protected readonly doneMarker = '__PLANCHECK_DONE__';
|
||
protected readonly tempFilePrefix = 'codeman-plancheck';
|
||
protected readonly logPrefix = '[AiPlanChecker]';
|
||
protected readonly checkDescription = 'AI plan check';
|
||
|
||
constructor(sessionId: string, config: Partial<AiPlanCheckConfig> = {}) {
|
||
super(sessionId, DEFAULT_PLAN_CHECK_CONFIG, config);
|
||
}
|
||
|
||
protected buildPrompt(terminalBuffer: string): string {
|
||
return AI_PLAN_CHECK_PROMPT.replace('{TERMINAL_BUFFER}', terminalBuffer);
|
||
}
|
||
|
||
protected parseVerdict(output: string): { verdict: AiPlanCheckVerdict; reasoning: string } | null {
|
||
const match = output.match(VERDICT_PATTERN);
|
||
if (!match) return null;
|
||
|
||
const verdict = match[1].toUpperCase() as 'PLAN_MODE' | 'NOT_PLAN_MODE';
|
||
const lines = output.split('\n');
|
||
const reasoning = lines.slice(1).join('\n').trim() || `AI determined: ${verdict}`;
|
||
|
||
return { verdict, reasoning };
|
||
}
|
||
|
||
protected getPositiveVerdict(): AiPlanCheckVerdict {
|
||
return 'PLAN_MODE';
|
||
}
|
||
|
||
protected getNegativeVerdict(): AiPlanCheckVerdict {
|
||
return 'NOT_PLAN_MODE';
|
||
}
|
||
|
||
protected getErrorVerdict(): AiPlanCheckVerdict {
|
||
return 'ERROR';
|
||
}
|
||
|
||
protected createErrorResult(reasoning: string, durationMs: number): AiPlanCheckResult {
|
||
return { verdict: 'ERROR', reasoning, durationMs };
|
||
}
|
||
|
||
protected createResult(verdict: AiPlanCheckVerdict, reasoning: string, durationMs: number): AiPlanCheckResult {
|
||
return { verdict, reasoning, durationMs };
|
||
}
|
||
}
|