@@ -11,10 +11,14 @@ const DEFAULT_STATE: State = {
1111 phase : "init" ,
1212 taskIndex : 0 ,
1313 lastUpdate : "" ,
14+ currentIdeaPath : undefined ,
15+ currentIdeaFilename : undefined ,
1416}
1517
1618/**
17- * Load state from file, or return default state if file doesn't exist
19+ * Load state from file, or return default state if file doesn't exist.
20+ * @param stateFile - Path to the state JSON file
21+ * @returns RuntimeState loaded from file or default state
1822 */
1923export async function loadState ( stateFile : string ) : Promise < RuntimeState > {
2024 const content = await readFileOrNull ( stateFile )
@@ -26,24 +30,65 @@ export async function loadState(stateFile: string): Promise<RuntimeState> {
2630 try {
2731 const parsed = JSON . parse ( content ) as Partial < State >
2832
33+ // Validate cycle is a positive number
34+ if ( parsed . cycle !== undefined && ( typeof parsed . cycle !== "number" || parsed . cycle < 1 ) ) {
35+ console . warn (
36+ `Warning: Invalid cycle value in ${ stateFile } (expected positive number, got ${ parsed . cycle } ). Using default.` ,
37+ )
38+ parsed . cycle = DEFAULT_STATE . cycle
39+ }
40+
41+ // Validate taskIndex is a non-negative number
42+ if (
43+ parsed . taskIndex !== undefined &&
44+ ( typeof parsed . taskIndex !== "number" || parsed . taskIndex < 0 )
45+ ) {
46+ console . warn (
47+ `Warning: Invalid taskIndex in ${ stateFile } (expected non-negative number, got ${ parsed . taskIndex } ). Using default.` ,
48+ )
49+ parsed . taskIndex = DEFAULT_STATE . taskIndex
50+ }
51+
52+ // Validate phase
53+ const validatedPhase = validatePhase ( parsed . phase )
54+ if ( parsed . phase !== undefined && validatedPhase === null ) {
55+ console . warn (
56+ `Warning: Invalid phase in ${ stateFile } (got "${ parsed . phase } ", expected one of: init, plan, build, evaluation). Using default.` ,
57+ )
58+ }
59+
2960 // Merge with defaults to handle missing fields
3061 const state : State = {
3162 cycle : parsed . cycle ?? DEFAULT_STATE . cycle ,
32- phase : validatePhase ( parsed . phase ) ?? DEFAULT_STATE . phase ,
63+ phase : validatedPhase ?? DEFAULT_STATE . phase ,
3364 taskIndex : parsed . taskIndex ?? DEFAULT_STATE . taskIndex ,
3465 sessionId : parsed . sessionId ,
3566 lastUpdate : parsed . lastUpdate ?? DEFAULT_STATE . lastUpdate ,
67+ currentIdeaPath : parsed . currentIdeaPath ,
68+ currentIdeaFilename : parsed . currentIdeaFilename ,
3669 }
3770
3871 return toRuntimeState ( state )
3972 } catch ( err ) {
40- console . warn ( `Warning: Failed to parse state file, using defaults: ${ err } ` )
73+ // Provide specific guidance based on error type
74+ if ( err instanceof SyntaxError ) {
75+ console . warn (
76+ `Warning: Failed to parse ${ stateFile } - invalid JSON syntax. ` +
77+ `The file may be corrupted. Using default state. Error: ${ err . message } ` ,
78+ )
79+ } else {
80+ console . warn (
81+ `Warning: Failed to load state from ${ stateFile } . Using default state. Error: ${ err } ` ,
82+ )
83+ }
4184 return toRuntimeState ( DEFAULT_STATE )
4285 }
4386}
4487
4588/**
46- * Save state to file
89+ * Save state to file.
90+ * @param stateFile - Path to the state JSON file
91+ * @param state - RuntimeState to persist
4792 */
4893export async function saveState ( stateFile : string , state : RuntimeState ) : Promise < void > {
4994 // Only persist the State fields, not RuntimeState extras
@@ -53,6 +98,8 @@ export async function saveState(stateFile: string, state: RuntimeState): Promise
5398 taskIndex : state . taskIndex ,
5499 sessionId : state . sessionId ,
55100 lastUpdate : getISOTimestamp ( ) ,
101+ currentIdeaPath : state . currentIdeaPath ,
102+ currentIdeaFilename : state . currentIdeaFilename ,
56103 }
57104
58105 const content = JSON . stringify ( persistedState , null , 2 )
@@ -83,7 +130,8 @@ function validatePhase(phase: unknown): Phase | null {
83130}
84131
85132/**
86- * Reset state to initial values for a new run
133+ * Reset state to initial values for a new run.
134+ * @returns Fresh RuntimeState with cycle 1 and init phase
87135 */
88136export function resetState ( ) : RuntimeState {
89137 return toRuntimeState ( {
@@ -93,7 +141,9 @@ export function resetState(): RuntimeState {
93141}
94142
95143/**
96- * Create a fresh state for starting a new cycle
144+ * Create a fresh state for starting a new cycle.
145+ * @param currentCycle - The current cycle number
146+ * @returns Partial RuntimeState with incremented cycle and reset task fields
97147 */
98148export function newCycleState ( currentCycle : number ) : Partial < RuntimeState > {
99149 return {
@@ -104,5 +154,7 @@ export function newCycleState(currentCycle: number): Partial<RuntimeState> {
104154 totalTasks : 0 ,
105155 currentTaskNum : 0 ,
106156 currentTaskDesc : "" ,
157+ currentIdeaPath : undefined ,
158+ currentIdeaFilename : undefined ,
107159 }
108160}
0 commit comments