Commit current progress as a returnable checkpoint. Returns the SHA or null.
(label: string, signal?: AbortSignal)
| 270 | |
| 271 | /** Commit current progress as a returnable checkpoint. Returns the SHA or null. */ |
| 272 | async checkpoint(label: string, signal?: AbortSignal): Promise<string | null> { |
| 273 | if (!this.state?.active) return null; |
| 274 | try { |
| 275 | // Guard: only commit if HEAD is still our sandbox branch. If something moved |
| 276 | // us off it (a concurrent op, a tool that switched branches), committing here |
| 277 | // would dirty the wrong branch. |
| 278 | const cur = await this.currentBranch(signal); |
| 279 | if (cur !== this.state.branch) { |
| 280 | logger.warn('GitSandbox.checkpoint: HEAD is not the sandbox branch — skipping', { expected: this.state.branch, actual: cur }); |
| 281 | return null; |
| 282 | } |
| 283 | await git(['add', '-A'], { cwd: this.cwd, signal }); |
| 284 | // Allow empty so a checkpoint with no changes still marks a return point. |
| 285 | const c = await git(['commit', '--no-verify', '--allow-empty', '-m', `qodex checkpoint: ${label}`], { cwd: this.cwd, signal }); |
| 286 | if (!(c.exitCode === 0)) return null; |
| 287 | const sha = await this.headSha(signal); |
| 288 | if (sha) this.state.checkpoints.push(sha); |
| 289 | return sha; |
| 290 | } catch (e: any) { |
| 291 | logger.debug('GitSandbox.checkpoint failed', { err: e?.message }); |
| 292 | return null; |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | /** |
| 297 | * Autonomous backtrack: hard-reset to the last checkpoint (or the sandbox base |