@@ -160,6 +160,38 @@ function describeSources(state: SelfImproveLoopState): string {
160160 return enabled . length === 0 ? 'none configured' : enabled . join ( ', ' ) ;
161161}
162162
163+ function isRecord ( value : unknown ) : value is Record < string , unknown > {
164+ return typeof value === 'object' && value !== null && ! Array . isArray ( value ) ;
165+ }
166+
167+ function formatRunRef ( value : unknown ) : string | null {
168+ if ( value === undefined || value === null ) return null ;
169+
170+ if ( isRecord ( value ) ) {
171+ const runId = value [ 'runId' ] ;
172+ const status = value [ 'status' ] ;
173+ const runDoc = value [ 'runDoc' ] ;
174+ const parts : string [ ] = [ ] ;
175+ if ( typeof runId === 'string' && runId . trim ( ) ) {
176+ parts . push ( runId ) ;
177+ }
178+ if ( typeof status === 'string' && status . trim ( ) ) {
179+ parts . push ( `(${ status } )` ) ;
180+ }
181+ if ( typeof runDoc === 'string' && runDoc . trim ( ) ) {
182+ parts . push ( `- ${ runDoc } ` ) ;
183+ }
184+ return parts . length > 0 ? parts . join ( ' ' ) : JSON . stringify ( value ) ;
185+ }
186+
187+ if ( typeof value === 'string' && value . trim ( ) ) return value ;
188+ if ( typeof value === 'number' || typeof value === 'boolean' ) {
189+ return String ( value ) ;
190+ }
191+
192+ return null ;
193+ }
194+
163195function buildTickPrompt ( state : SelfImproveLoopState ) : string {
164196 const loopDir = getSelfImproveLoopDir ( state . repoRoot , state . loopId ) ;
165197 return `You are running one tick of the built-in /self-improve loop.
@@ -190,6 +222,18 @@ Hard rules:
1902229. Update ${ path . join ( loopDir , 'state.json' ) } as you progress, including currentRun, lastRun, stopRequested, and status.
19122310. If stopRequested is true when you read the state, do not start a new run; mark the loop stopped if appropriate and stop.
192224
225+ State file schema rules:
226+ - status must be one of: "running", "stopping", "stopped", or "stale".
227+ - Keep status as "running" after a successful tick if the loop should continue.
228+ - currentRun and lastRun must be objects when present:
229+ {
230+ "runId": "001-short-slug",
231+ "status": "implementing | testing | success | failed | blocked | cancelled",
232+ "worktreePath": "/absolute/path/to/worktree",
233+ "runDoc": "/absolute/path/to/run.md"
234+ }
235+ - Do not write primitive values such as numbers or timestamps to currentRun or lastRun.
236+
193237Task selection guidance:
194238- If GitHub issues are enabled, use gh to inspect open issues and prefer clear, unclaimed, locally verifiable bugs or small enhancements.
195239- If GitHub PRs are enabled, inspect relevant current-repo PRs for CI failures, review comments, and requested changes.
@@ -324,14 +368,10 @@ async function statusSelfImprove(config: Config): Promise<MessageActionReturn> {
324368 `Prompt: ${ state . prompt || '(none)' } ` ,
325369 `Cron job: ${ job ? job . id : 'none' } ` ,
326370 ] ;
327- if ( state . currentRun ) {
328- lines . push (
329- `Current run: ${ state . currentRun . runId } (${ state . currentRun . status } )` ,
330- ) ;
331- }
332- if ( state . lastRun ) {
333- lines . push ( `Last run: ${ state . lastRun . runId } (${ state . lastRun . status } )` ) ;
334- }
371+ const currentRun = formatRunRef ( state . currentRun ) ;
372+ if ( currentRun ) lines . push ( `Current run: ${ currentRun } ` ) ;
373+ const lastRun = formatRunRef ( state . lastRun ) ;
374+ if ( lastRun ) lines . push ( `Last run: ${ lastRun } ` ) ;
335375 return message ( 'info' , lines . join ( '\n' ) ) ;
336376}
337377
0 commit comments