prizmkit 1.1.99 → 1.1.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/lib/common.sh +244 -25
  3. package/bundled/dev-pipeline/reset-bug.sh +30 -0
  4. package/bundled/dev-pipeline/reset-feature.sh +30 -0
  5. package/bundled/dev-pipeline/reset-refactor.sh +30 -0
  6. package/bundled/dev-pipeline/run-bugfix.sh +128 -13
  7. package/bundled/dev-pipeline/run-feature.sh +130 -14
  8. package/bundled/dev-pipeline/run-refactor.sh +128 -13
  9. package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
  10. package/bundled/dev-pipeline/scripts/continuation.py +374 -0
  11. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +27 -30
  12. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +15 -20
  13. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -20
  14. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +146 -16
  15. package/bundled/dev-pipeline/scripts/update-bug-status.py +214 -6
  16. package/bundled/dev-pipeline/scripts/update-feature-status.py +237 -6
  17. package/bundled/dev-pipeline/scripts/update-refactor-status.py +214 -6
  18. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +1 -1
  19. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +2 -2
  20. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +3 -3
  21. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +3 -3
  22. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
  23. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
  24. package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +31 -66
  25. package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
  26. package/bundled/dev-pipeline/tests/conftest.py +1 -0
  27. package/bundled/dev-pipeline/tests/test_auto_skip.py +510 -0
  28. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +103 -0
  29. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +60 -0
  30. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
  31. package/bundled/dev-pipeline-windows/lib/common.ps1 +172 -10
  32. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +93 -9
  33. package/bundled/dev-pipeline-windows/lib/reset.ps1 +34 -0
  34. package/bundled/dev-pipeline-windows/reset-bug.ps1 +1 -0
  35. package/bundled/dev-pipeline-windows/reset-feature.ps1 +1 -0
  36. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +1 -0
  37. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +74 -1
  38. package/bundled/dev-pipeline-windows/scripts/continuation.py +374 -0
  39. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +27 -30
  40. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +16 -20
  41. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +16 -20
  42. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +146 -16
  43. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +214 -6
  44. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +237 -6
  45. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +214 -6
  46. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +1 -1
  47. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +2 -2
  48. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +3 -3
  49. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +3 -3
  50. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +1 -1
  51. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -3
  52. package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +31 -68
  53. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +1 -1
  54. package/bundled/skills/_metadata.json +1 -1
  55. package/package.json +1 -1
  56. package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
  57. package/bundled/dev-pipeline-windows/scripts/monitor-log.ps1 +0 -102
@@ -1,5 +1,8 @@
1
1
  """Tests for generate-bugfix-prompt.py core functions."""
2
2
 
3
+ from argparse import Namespace
4
+ from pathlib import Path
5
+
3
6
  from generate_bugfix_prompt import (
4
7
  find_bug,
5
8
  format_acceptance_criteria,
@@ -8,7 +11,9 @@ from generate_bugfix_prompt import (
8
11
  format_environment,
9
12
  process_conditional_blocks,
10
13
  render_template,
14
+ load_log_size_section,
11
15
  )
16
+ from continuation import append_continuation_handoff
12
17
 
13
18
 
14
19
  # ---------------------------------------------------------------------------
@@ -166,3 +171,58 @@ class TestSharedHelpers:
166
171
  def test_format_global_context_dict(self):
167
172
  result = format_global_context({"lang": "Python"})
168
173
  assert "**lang**: Python" in result
174
+
175
+
176
+ # ---------------------------------------------------------------------------
177
+ # Active compaction harness rollback
178
+ # ---------------------------------------------------------------------------
179
+
180
+ class TestHeadlessRecoveryReframing:
181
+ def test_bugfix_log_size_section_is_passive_checkpoint_guidance(self):
182
+ content = load_log_size_section(str(Path("dev-pipeline/scripts").resolve()))
183
+
184
+ assert "passive checkpoint guidance" in content
185
+ assert "Headless recovery must rely on durable checkpoints" in content
186
+ assert "fresh continuation session" in content
187
+ assert "optional interactive convenience only" in content
188
+ assert "COMPACT_NEEDED" not in content
189
+ assert "monitor-log" not in content
190
+ assert "background monitoring subagent" not in content
191
+ assert "Execute `/compact`" not in content
192
+ assert "run `/compact`" not in content.lower()
193
+
194
+ def test_bugfix_template_does_not_spawn_monitor_or_require_compact(self):
195
+ content = Path("dev-pipeline/templates/bugfix-bootstrap-prompt.md").read_text(encoding="utf-8")
196
+
197
+ assert "Do not spawn persistent background subagents for headless recovery" in content
198
+ assert "log-size monitor" not in content
199
+ assert "monitor-log" not in content
200
+ assert "COMPACT_NEEDED" not in content
201
+ assert "Run this command and keep it running" not in content
202
+ assert "Execute `/compact`" not in content
203
+ assert "run `/compact` after" not in content
204
+
205
+
206
+ class TestBugfixContinuationHandoff:
207
+ def test_writes_specs_and_task_local_summaries(self, tmp_path):
208
+ args = Namespace(
209
+ output=str(tmp_path / ".prizmkit/state/bugfix/B-007/sessions/B-007-new/bootstrap-prompt.md"),
210
+ state_dir=str(tmp_path / ".prizmkit/state/bugfix"),
211
+ continuation_mode="context_overflow",
212
+ previous_session_id="B-007-old",
213
+ continuation_count="1",
214
+ context_overflow_count="1",
215
+ active_dev_branch="bugfix/B-007",
216
+ base_branch="main",
217
+ continuation_summary_path=".prizmkit/specs/B-007/continuation-summary.md",
218
+ )
219
+ prompt = append_continuation_handoff(
220
+ "BUGFIX PROMPT", args, str(tmp_path), "bugfix", "B-007", "B-007",
221
+ {"steps": [{"id": "S01", "skill": "diagnose", "status": "pending"}]},
222
+ )
223
+
224
+ assert prompt.startswith("BUGFIX PROMPT")
225
+ assert "This is an automatic continuation after context overflow." in prompt
226
+ assert "Do not attempt `/compact` as the recovery mechanism." in prompt
227
+ assert (tmp_path / ".prizmkit/specs/B-007/continuation-summary.md").exists()
228
+ assert (tmp_path / ".prizmkit/bugfix/B-007/continuation-summary.md").exists()
@@ -0,0 +1,43 @@
1
+ """Tests for generate-refactor-prompt.py continuation behavior."""
2
+
3
+ from argparse import Namespace
4
+
5
+ from continuation import append_continuation_handoff
6
+ from generate_refactor_prompt import generate_refactor_checkpoint, merge_refactor_checkpoint_state
7
+
8
+
9
+ class TestRefactorContinuationHandoff:
10
+ def test_writes_specs_and_task_local_summaries(self, tmp_path):
11
+ args = Namespace(
12
+ output=str(tmp_path / ".prizmkit/state/refactor/R-007/sessions/R-007-new/bootstrap-prompt.md"),
13
+ state_dir=str(tmp_path / ".prizmkit/state/refactor"),
14
+ continuation_mode="context_overflow",
15
+ previous_session_id="R-007-old",
16
+ continuation_count="1",
17
+ context_overflow_count="1",
18
+ active_dev_branch="refactor/R-007",
19
+ base_branch="main",
20
+ continuation_summary_path=".prizmkit/specs/R-007/continuation-summary.md",
21
+ )
22
+ prompt = append_continuation_handoff(
23
+ "REFACTOR PROMPT", args, str(tmp_path), "refactor", "R-007", "R-007",
24
+ {"steps": [{"id": "S01", "skill": "plan", "status": "pending"}]},
25
+ )
26
+
27
+ assert prompt.startswith("REFACTOR PROMPT")
28
+ assert "This is a fresh headless AI CLI session for the same task." in prompt
29
+ assert "Do not attempt `/compact` as the recovery mechanism." in prompt
30
+ assert (tmp_path / ".prizmkit/specs/R-007/continuation-summary.md").exists()
31
+ assert (tmp_path / ".prizmkit/refactor/R-007/continuation-summary.md").exists()
32
+
33
+
34
+ class TestRefactorCheckpointMerge:
35
+ def test_preserves_skipped_without_artifacts(self, tmp_path):
36
+ fresh = generate_refactor_checkpoint("R-008", "R-008-new")
37
+ existing = generate_refactor_checkpoint("R-008", "R-008-old")
38
+ existing["steps"][0]["status"] = "skipped"
39
+
40
+ merged = merge_refactor_checkpoint_state(existing, fresh, str(tmp_path))
41
+
42
+ assert merged["steps"][0]["status"] == "skipped"
43
+ assert merged["steps"][1]["status"] == "pending"
@@ -175,19 +175,30 @@ function Test-PrizmInfraError {
175
175
  return ($haystack -match '(?i)auth_unavailable|no auth available|502 Bad Gateway|503 Service Unavailable|504 Gateway Timeout|gateway timeout|upstream (connect )?error|connection reset|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|rate limit|rate_limit|temporarily unavailable|overloaded')
176
176
  }
177
177
 
178
- function Test-PrizmAiRuntimeError {
178
+ function Test-PrizmContextOverflowError {
179
179
  param([string]$SessionLog, [string]$ProgressJson)
180
180
 
181
+ $parts = @()
182
+ $codeParts = @()
183
+ $progressErrorLike = $false
184
+
181
185
  if ($ProgressJson -and (Test-Path $ProgressJson)) {
182
186
  try {
183
187
  $progress = Get-Content $ProgressJson -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
184
- if ($progress.PSObject.Properties['fatal_error_code'] -and $progress.fatal_error_code) {
185
- return $true
188
+ foreach ($name in @('fatal_error_code', 'api_error_code', 'error_code', 'stop_reason')) {
189
+ if ($progress.PSObject.Properties[$name] -and $progress.$name) { $codeParts += [string]$progress.$name }
190
+ }
191
+ if (($progress.PSObject.Properties['last_result_is_error'] -and $progress.last_result_is_error) -or ($progress.PSObject.Properties['api_error_status'] -and $progress.api_error_status)) {
192
+ $progressErrorLike = $true
186
193
  }
194
+ $parts += ($progress | ConvertTo-Json -Depth 12 -Compress)
187
195
  } catch {}
188
196
  }
189
197
 
190
- $parts = @()
198
+ $codeText = $codeParts -join ' '
199
+ $codePattern = '(?i)\b(context_overflow|context_too_large|context_length_exceeded|model_context_window_exceeded)\b'
200
+ if ($codeText -match $codePattern) { return $true }
201
+
191
202
  if ($SessionLog -and (Test-Path $SessionLog)) {
192
203
  try {
193
204
  $text = Get-Content $SessionLog -Raw -ErrorAction Stop
@@ -195,15 +206,166 @@ function Test-PrizmAiRuntimeError {
195
206
  $parts += $text
196
207
  } catch {}
197
208
  }
198
- if ($ProgressJson -and (Test-Path $ProgressJson)) {
199
- try { $parts += (Get-Content $ProgressJson -Raw -ErrorAction Stop) } catch {}
200
- }
201
209
  if ($parts.Count -eq 0) { return $false }
202
210
 
203
211
  $haystack = $parts -join "`n"
204
- $contextPattern = '(?i)context_too_large|model_context_window_exceeded|input exceeds the context window|context window of this model|context window (was )?exceeded|exceeded (the )?context window|invalid_request_error.*context window|context window.*invalid_request_error'
205
- $errorPattern = '(?i)api error|invalid_request_error|api_error_status|api_error_code|status\s*[:=]?\s*(400|413)|last_result_is_error\s*["'':=]?\s*true|is_error\s*["'':=]?\s*true'
206
- if (($haystack -match $contextPattern) -and ($haystack -match $errorPattern)) { return $true }
212
+ $wrapperPattern = '(?i)\bPRIZMKIT_FATAL_ERROR\s*=\s*context_overflow\b'
213
+ if ($haystack -match $wrapperPattern) { return $true }
214
+ if ($haystack -match $codePattern) { return $true }
215
+
216
+ $errorPattern = '(?i)\bapi error\b|invalid_request_error|\bbad request\b|\bstatus\s*[:=]?\s*(400|413)\b|\bapi_error_status\b|\bapi_error_code\b|\blast_result_is_error\b\s*["'':=]*\s*true\b|\bis_error\b\s*["'':=]*\s*true\b|\berror\s*[:=]|\bfatal\s*[:=]'
217
+ $hasErrorContext = $progressErrorLike -or ($haystack -match $errorPattern)
218
+ if (-not $hasErrorContext) { return $false }
219
+
220
+ $weakPattern = '(?i)Your input exceeds the context window|input exceeds (?:the )?(?:model )?context window|context window of this model|context window (?:was )?exceeded|exceeded (?:the )?context window|maximum context length exceeded|prompt is too long|too many tokens|input token count exceeds|input tokens? exceeds?|token count exceeds'
221
+ if ($haystack -match $weakPattern) { return $true }
222
+
223
+ $requestTooLargePattern = '(?i)\brequest (?:is )?too large\b'
224
+ $requestSemanticsPattern = '(?i)\b(context|token|tokens|prompt|input|model)\b'
225
+ if (($haystack -match $requestTooLargePattern) -and ($haystack -match $requestSemanticsPattern)) { return $true }
226
+
227
+ return $false
228
+ }
229
+
230
+ function Test-PrizmAiRuntimeError {
231
+ param([string]$SessionLog, [string]$ProgressJson)
232
+
233
+ if (Test-PrizmContextOverflowError -SessionLog $SessionLog -ProgressJson $ProgressJson) { return $false }
234
+
235
+ if ($ProgressJson -and (Test-Path $ProgressJson)) {
236
+ try {
237
+ $progress = Get-Content $ProgressJson -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
238
+ if ($progress.PSObject.Properties['fatal_error_code'] -and $progress.fatal_error_code) {
239
+ return $true
240
+ }
241
+ } catch {}
242
+ }
243
+
244
+ return $false
245
+ }
246
+
247
+ function Get-PrizmContextProgressFingerprint {
248
+ param(
249
+ [string]$ProjectRoot,
250
+ [string]$TaskType,
251
+ [string]$TaskId,
252
+ [string]$ArtifactDir = '',
253
+ [string]$CheckpointFile = ''
254
+ )
255
+
256
+ $projectRootResolved = if ($ProjectRoot) { $ProjectRoot } else { (Get-Location).Path }
257
+ $artifactArg = if ($ArtifactDir) { $ArtifactDir } else { '' }
258
+ $checkpointArg = if ($CheckpointFile) { $CheckpointFile } else { '' }
259
+ $python = @'
260
+ import hashlib
261
+ import json
262
+ import os
263
+ import subprocess
264
+ import sys
265
+
266
+ project_root, task_type, task_id, artifact_dir, checkpoint_file = sys.argv[1:6]
267
+ project_root = os.path.abspath(project_root or ".")
268
+ artifact_dir = os.path.abspath(os.path.join(project_root, artifact_dir)) if artifact_dir and not os.path.isabs(artifact_dir) else artifact_dir
269
+ checkpoint_file = os.path.abspath(os.path.join(project_root, checkpoint_file)) if checkpoint_file and not os.path.isabs(checkpoint_file) else checkpoint_file
270
+
271
+ def run_git(args):
272
+ try:
273
+ return subprocess.check_output(["git", "-C", project_root, *args], stderr=subprocess.DEVNULL)
274
+ except Exception:
275
+ return b""
276
+
277
+ def sha_bytes(*parts):
278
+ h = hashlib.sha256()
279
+ for part in parts:
280
+ if isinstance(part, str):
281
+ part = part.encode("utf-8", errors="surrogateescape")
282
+ h.update(part or b"")
283
+ h.update(b"\0")
284
+ return h.hexdigest()
285
+
286
+ def checkpoint_cursor(path):
287
+ if not path or not os.path.exists(path):
288
+ return "missing"
289
+ try:
290
+ with open(path, encoding="utf-8") as fh:
291
+ data = json.load(fh)
292
+ except Exception as exc:
293
+ return "unreadable:{}".format(type(exc).__name__)
294
+ steps = data.get("steps") if isinstance(data, dict) else None
295
+ if not isinstance(steps, list):
296
+ return "no-steps"
297
+ counts = {"completed": 0, "skipped": 0, "pending": 0, "in_progress": 0, "failed": 0}
298
+ first_open = None
299
+ for step in steps:
300
+ if not isinstance(step, dict):
301
+ continue
302
+ status = str(step.get("status", "pending"))
303
+ counts[status] = counts.get(status, 0) + 1
304
+ if first_open is None and status in ("pending", "in_progress"):
305
+ first_open = "{}:{}:{}".format(step.get("id", ""), step.get("skill", ""), status)
306
+ return "first_open={};counts={}".format(first_open or "none", ",".join("{}:{}".format(k, counts[k]) for k in sorted(counts)))
307
+
308
+ def artifact_fingerprint(path):
309
+ if not path or not os.path.isdir(path):
310
+ return "missing"
311
+ h = hashlib.sha256()
312
+ for root, dirs, files in os.walk(path):
313
+ dirs[:] = sorted(d for d in dirs if d not in {".git", "node_modules", "__pycache__"})
314
+ for name in sorted(files):
315
+ file_path = os.path.join(root, name)
316
+ rel = os.path.relpath(file_path, path).replace(os.sep, "/")
317
+ h.update(rel.encode("utf-8", errors="surrogateescape"))
318
+ h.update(b"\0")
319
+ try:
320
+ with open(file_path, "rb") as fh:
321
+ for chunk in iter(lambda: fh.read(65536), b""):
322
+ h.update(chunk)
323
+ except Exception as exc:
324
+ h.update("unreadable:{}".format(type(exc).__name__).encode("utf-8"))
325
+ h.update(b"\0")
326
+ return h.hexdigest()
327
+
328
+ fingerprint = {
329
+ "task_type": task_type,
330
+ "task_id": task_id,
331
+ "git_diff_fingerprint": sha_bytes(run_git(["status", "--porcelain", "--untracked-files=all", "--", "."]), run_git(["diff", "--binary", "HEAD", "--", "."])),
332
+ "checkpoint_cursor": checkpoint_cursor(checkpoint_file),
333
+ "artifact_fingerprint": artifact_fingerprint(artifact_dir),
334
+ "head_commit": run_git(["rev-parse", "HEAD"]).decode("utf-8", errors="ignore").strip(),
335
+ }
336
+ print(json.dumps(fingerprint, sort_keys=True, ensure_ascii=False))
337
+ '@
338
+ $scriptPath = [System.IO.Path]::GetTempFileName()
339
+ try {
340
+ Set-Content -Path $scriptPath -Value $python -Encoding UTF8
341
+ $pythonCmd = Get-Command python -ErrorAction SilentlyContinue
342
+ if ($pythonCmd) {
343
+ $output = & $pythonCmd.Source $scriptPath $projectRootResolved $TaskType $TaskId $artifactArg $checkpointArg
344
+ } else {
345
+ $pyCmd = Get-Command py -ErrorAction SilentlyContinue
346
+ if (-not $pyCmd) { throw 'Python 3 is required for progress fingerprinting.' }
347
+ $output = & $pyCmd.Source -3 $scriptPath $projectRootResolved $TaskType $TaskId $artifactArg $checkpointArg
348
+ }
349
+ if ($LASTEXITCODE -ne 0) { throw 'progress fingerprint python helper failed' }
350
+ return ($output -join "`n")
351
+ } finally {
352
+ Remove-Item -Force $scriptPath -ErrorAction SilentlyContinue
353
+ }
354
+ }
355
+
356
+ function Test-PrizmContextProgressChanged {
357
+ param([string]$BeforeJson, [string]$AfterJson)
358
+ try {
359
+ $before = if ($BeforeJson) { $BeforeJson | ConvertFrom-Json } else { $null }
360
+ $after = if ($AfterJson) { $AfterJson | ConvertFrom-Json } else { $null }
361
+ } catch {
362
+ return $true
363
+ }
364
+ foreach ($key in @('git_diff_fingerprint', 'checkpoint_cursor', 'artifact_fingerprint', 'head_commit')) {
365
+ $beforeValue = if ($before -and $before.PSObject.Properties[$key]) { [string]$before.$key } else { '' }
366
+ $afterValue = if ($after -and $after.PSObject.Properties[$key]) { [string]$after.$key } else { '' }
367
+ if ($beforeValue -ne $afterValue) { return $true }
368
+ }
207
369
  return $false
208
370
  }
209
371
 
@@ -50,8 +50,7 @@ function Invoke-PrizmPipeline {
50
50
  Write-Host ""
51
51
  Write-Host "Environment Variables:"
52
52
  Write-Host " PRIZMKIT_EFFORT AI reasoning effort (low|medium|high|xhigh|max; max is Claude Code only)"
53
- Write-Host " MAX_LOG_SIZE Session log size threshold for compact warning (default: 2097152 = 2MB, set 0 to disable)"
54
- Write-Host " LOG_MONITOR_POLL_INTERVAL Log monitor poll interval in seconds (default: 30)"
53
+ Write-Host " MAX_LOG_SIZE Include passive log-size checkpoint guidance above this threshold (default: 2097152 = 2MB, set 0 to disable)"
55
54
  $global:PRIZM_EXIT_CODE = 0
56
55
  return
57
56
  }
@@ -626,7 +625,10 @@ function Invoke-PrizmPipeline {
626
625
  }
627
626
 
628
627
  function Invoke-PrizmPipelineItem {
629
- param([string]$CurrentItemId)
628
+ param(
629
+ [string]$CurrentItemId,
630
+ [object]$ContinuationState = $null
631
+ )
630
632
  $script:PRIZM_ITEM_EXIT_CODE = 0
631
633
 
632
634
  $sessionId = New-PrizmSessionId $Kind
@@ -636,6 +638,27 @@ function Invoke-PrizmPipeline {
636
638
  $isGitRepository = if (-not $dryRun) { Test-PrizmGitRepository $paths.ProjectRoot } else { $false }
637
639
  $originalBranch = if ($isGitRepository) { Get-PrizmCurrentBranch $paths.ProjectRoot } else { '' }
638
640
  $devBranchName = ''
641
+ $activeDevBranch = ''
642
+ $continuationPending = $false
643
+ $continuationReason = ''
644
+ $contextOverflowCount = 0
645
+ $continuationCount = 0
646
+ $previousSessionId = ''
647
+ $noProgressCount = 0
648
+ if ($ContinuationState) {
649
+ if ($ContinuationState.PSObject.Properties['active_dev_branch'] -and $ContinuationState.active_dev_branch) { $activeDevBranch = [string]$ContinuationState.active_dev_branch }
650
+ if ($ContinuationState.PSObject.Properties['base_branch'] -and $ContinuationState.base_branch) { $originalBranch = [string]$ContinuationState.base_branch }
651
+ if ($ContinuationState.PSObject.Properties['continuation_pending']) { $continuationPending = [bool]$ContinuationState.continuation_pending }
652
+ if ($ContinuationState.PSObject.Properties['continuation_reason'] -and $ContinuationState.continuation_reason) { $continuationReason = [string]$ContinuationState.continuation_reason }
653
+ if ($ContinuationState.PSObject.Properties['context_overflow_count'] -and $ContinuationState.context_overflow_count -ne $null) { $contextOverflowCount = [int]$ContinuationState.context_overflow_count }
654
+ if ($ContinuationState.PSObject.Properties['continuation_count'] -and $ContinuationState.continuation_count -ne $null) { $continuationCount = [int]$ContinuationState.continuation_count }
655
+ if ($ContinuationState.PSObject.Properties['last_context_overflow_session_id'] -and $ContinuationState.last_context_overflow_session_id) { $previousSessionId = [string]$ContinuationState.last_context_overflow_session_id }
656
+ if ($ContinuationState.PSObject.Properties['no_progress_count'] -and $ContinuationState.no_progress_count -ne $null) { $noProgressCount = [int]$ContinuationState.no_progress_count }
657
+ }
658
+ if ($continuationPending -and $activeDevBranch) {
659
+ Write-PrizmInfo "Continuation pending ($continuationReason); reusing dev branch: $activeDevBranch"
660
+ Write-PrizmInfo "Continuation count: $continuationCount (context overflows: $contextOverflowCount)"
661
+ }
639
662
  $hadDirtyBaseline = if ($isGitRepository) { Test-PrizmGitWorkDirty $paths.ProjectRoot $stateDir $listPath } else { $false }
640
663
  if ($hadDirtyBaseline) {
641
664
  if ($dryRun) {
@@ -664,7 +687,7 @@ function Invoke-PrizmPipeline {
664
687
 
665
688
  if ($isGitRepository -and $originalBranch) {
666
689
  Invoke-PrizmGitCommitPath $paths.ProjectRoot $listPath "chore($CurrentItemId): mark in_progress" | Out-Null
667
- $candidateBranch = if ($devBranchOverride) { $devBranchOverride } else { New-PrizmDefaultDevBranchName $Kind $CurrentItemId }
690
+ $candidateBranch = if ($devBranchOverride) { $devBranchOverride } elseif ($continuationPending -and $activeDevBranch) { $activeDevBranch } else { New-PrizmDefaultDevBranchName $Kind $CurrentItemId }
668
691
  if (New-PrizmDevBranch $paths.ProjectRoot $candidateBranch $originalBranch) {
669
692
  $devBranchName = $candidateBranch
670
693
  Write-PrizmInfo "Dev branch: $devBranchName"
@@ -679,6 +702,7 @@ function Invoke-PrizmPipeline {
679
702
 
680
703
  $baseCommit = if ($isGitRepository) { Get-PrizmGitHead $paths.ProjectRoot } else { '' }
681
704
 
705
+ while ($true) {
682
706
  $sessionDir = if ($dryRun) {
683
707
  Join-Path ([System.IO.Path]::GetTempPath()) "prizmkit-dry-run\$sessionId"
684
708
  } else {
@@ -697,6 +721,15 @@ function Invoke-PrizmPipeline {
697
721
  }
698
722
 
699
723
  $promptArgs = @((Join-Path $paths.ScriptsDir $promptScript), $listOption, $listPath, $idOption, $CurrentItemId, '--session-id', $sessionId, '--run-id', $runId, '--retry-count', $retryCount, '--resume-phase', $resumePhase, '--state-dir', $stateDir, '--output', $promptPath)
724
+ $continuationSummaryPath = ".prizmkit/$Kind/$CurrentItemId/continuation-summary.md"
725
+ if ($Kind -eq 'feature') {
726
+ $featureSlug = Get-PrizmFeatureSlugFromList $listPath $CurrentItemId
727
+ if ($featureSlug) { $continuationSummaryPath = ".prizmkit/specs/$featureSlug/continuation-summary.md" }
728
+ }
729
+ if ($continuationPending) {
730
+ $modeForContinuation = if ($continuationReason) { $continuationReason } else { 'context_overflow' }
731
+ $promptArgs += @('--continuation-mode', $modeForContinuation, '--previous-session-id', $previousSessionId, '--continuation-count', [string]$continuationCount, '--context-overflow-count', [string]$contextOverflowCount, '--task-type', $Kind, '--active-dev-branch', $activeDevBranch, '--base-branch', $originalBranch, '--continuation-summary-path', $continuationSummaryPath)
732
+ }
700
733
  if ($mode) { $promptArgs += @('--mode', $mode) }
701
734
  if ($critic) { $promptArgs += @('--critic', $critic) }
702
735
  if ($dryRun) { $promptArgs += '--no-checkpoint' }
@@ -717,6 +750,10 @@ function Invoke-PrizmPipeline {
717
750
 
718
751
  Write-PrizmInfo "Starting $cli session for $CurrentItemId ($sessionId)"
719
752
 
753
+ $artifactDirForFingerprint = if ($Kind -eq 'feature') { Split-Path $continuationSummaryPath -Parent } else { ".prizmkit/$Kind/$CurrentItemId" }
754
+ $checkpointForFingerprint = Join-Path $artifactDirForFingerprint 'workflow-checkpoint.json'
755
+ $progressBefore = Get-PrizmContextProgressFingerprint -ProjectRoot $paths.ProjectRoot -TaskType $Kind -TaskId $CurrentItemId -ArtifactDir $artifactDirForFingerprint -CheckpointFile $checkpointForFingerprint
756
+
720
757
  $progressJson = Join-Path $logsDir 'progress.json'
721
758
  $parserProcess = Start-PrizmProgressParser -PythonCommand $python -ScriptsDir $paths.ScriptsDir -SessionLog $sessionLog -ProgressFile $progressJson -CliCommand $cli
722
759
 
@@ -798,22 +835,34 @@ function Invoke-PrizmPipeline {
798
835
  }
799
836
  Stop-PrizmProgressParser $parserProcess
800
837
 
838
+ $wasContextOverflow = Test-PrizmContextOverflowError -SessionLog $sessionLog -ProgressJson $progressJson
801
839
  $wasInfraError = ($exitCode -ne 0 -and (Test-PrizmInfraError -SessionLog $sessionLog -ProgressJson $progressJson))
802
840
  $wasAiRuntimeError = Test-PrizmAiRuntimeError -SessionLog $sessionLog -ProgressJson $progressJson
803
841
  $semanticCompletion = if ($Kind -eq 'feature' -and $isGitRepository) {
804
842
  Get-PrizmFeatureSemanticCompletion $paths.ProjectRoot $listPath $CurrentItemId $baseCommit $paths.PrizmkitDir
805
843
  } else { $null }
806
844
 
845
+ $hasSuccessfulCommit = $false
846
+ if ($Kind -ne 'feature' -and $isGitRepository) {
847
+ $hasSuccessfulCommit = Test-PrizmGitCommitsSince $paths.ProjectRoot $baseCommit
848
+ }
849
+
807
850
  $status = 'crashed'
808
851
  if ($semanticCompletion) {
809
852
  $status = 'success'
810
- if ($exitCode -ne 0 -or $wasStaleKilled -or $wasAiRuntimeError) {
853
+ if ($exitCode -ne 0 -or $wasStaleKilled -or $wasContextOverflow -or $wasAiRuntimeError) {
811
854
  Write-PrizmWarn "Session ended with a failure signal after semantic completion; treating as finalized success"
812
855
  Write-PrizmWarn "Semantic completion commit: $($semanticCompletion.CommitSha)"
813
856
  }
857
+ } elseif ($hasSuccessfulCommit) {
858
+ $status = 'success'
859
+ } elseif ($wasContextOverflow) {
860
+ $status = 'context_overflow'
861
+ Write-PrizmWarn "AI session failed because the AI CLI exceeded the model context window"
862
+ Write-PrizmWarn "Context overflow is retried without consuming code retry budget"
814
863
  } elseif ($wasAiRuntimeError) {
815
864
  $status = 'infra_error'
816
- Write-PrizmWarn "AI session failed due to structured AI runtime/context error"
865
+ Write-PrizmWarn "AI session failed due to structured AI runtime error"
817
866
  Write-PrizmWarn "AI runtime errors are retried without consuming code retry budget"
818
867
  } elseif ($wasInfraError) {
819
868
  $status = 'infra_error'
@@ -841,6 +890,38 @@ function Invoke-PrizmPipeline {
841
890
 
842
891
  $mergeSucceeded = $true
843
892
  $itemListStatus = ''
893
+ $activeDevBranchForStatus = $devBranchName
894
+ $progressAfter = ''
895
+ $statusContinuationArgs = @('--active-dev-branch', $activeDevBranchForStatus, '--base-branch', $originalBranch, '--last-fatal-error-code', 'context_overflow', '--continuation-summary-path', $continuationSummaryPath)
896
+ if ($status -eq 'context_overflow') {
897
+ $progressAfter = Get-PrizmContextProgressFingerprint -ProjectRoot $paths.ProjectRoot -TaskType $Kind -TaskId $CurrentItemId -ArtifactDir $artifactDirForFingerprint -CheckpointFile $checkpointForFingerprint
898
+ if (Test-PrizmContextProgressChanged -BeforeJson $progressBefore -AfterJson $progressAfter) {
899
+ $noProgressCount = 0
900
+ Write-PrizmInfo "Context-overflow session made observable progress; continuing on $devBranchName"
901
+ } else {
902
+ $noProgressCount++
903
+ Write-PrizmWarn "Context-overflow session made no observable progress ($noProgressCount consecutive)"
904
+ }
905
+ $statusContinuationArgs = @('--active-dev-branch', $activeDevBranchForStatus, '--base-branch', $originalBranch, '--last-fatal-error-code', 'context_overflow', '--continuation-summary-path', $continuationSummaryPath, '--no-progress-count', [string]$noProgressCount, '--progress-fingerprint', $progressAfter)
906
+ if ($noProgressCount -ge 2) {
907
+ Write-PrizmWarn 'Pausing automatic continuation: context_overflow_without_git_checkpoint_or_artifact_progress'
908
+ $status = 'stalled_context_continuation'
909
+ } else {
910
+ $updateResult = Invoke-PrizmPythonJson $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'update', $idOption, $CurrentItemId, '--session-id', $sessionId, '--session-status', 'context_overflow') + $maxRetryArgs + $maxInfraRetryArgs + $statusContinuationArgs)
911
+ if ($updateResult -and $updateResult.PSObject.Properties['new_status']) { $itemListStatus = [string]$updateResult.new_status }
912
+ $continuationPending = $true
913
+ $continuationReason = 'context_overflow'
914
+ $previousSessionId = $sessionId
915
+ $activeDevBranch = $devBranchName
916
+ $contextOverflowCount++
917
+ $continuationCount++
918
+ $sessionId = New-PrizmSessionId $Kind
919
+ $runId = "run-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
920
+ Write-PrizmInfo "Launching automatic continuation for $CurrentItemId on same branch/worktree"
921
+ continue
922
+ }
923
+ }
924
+
844
925
  if ($status -eq 'success') {
845
926
  if (Test-PrizmGitDirty $paths.ProjectRoot) {
846
927
  Write-PrizmInfo "Auto-committing remaining session artifacts."
@@ -848,7 +929,7 @@ function Invoke-PrizmPipeline {
848
929
  }
849
930
 
850
931
  if ($status -eq 'success') {
851
- $updateResult = Invoke-PrizmPythonJson $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'update', $idOption, $CurrentItemId, '--session-id', $sessionId, '--session-status', $status) + $maxRetryArgs + $maxInfraRetryArgs)
932
+ $updateResult = Invoke-PrizmPythonJson $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'update', $idOption, $CurrentItemId, '--session-id', $sessionId, '--session-status', $status) + $maxRetryArgs + $maxInfraRetryArgs + $statusContinuationArgs)
852
933
  if ($updateResult -and $updateResult.PSObject.Properties['new_status']) {
853
934
  $itemListStatus = [string]$updateResult.new_status
854
935
  }
@@ -885,7 +966,7 @@ function Invoke-PrizmPipeline {
885
966
  Write-PrizmRuntimeFailureLog $failureLog $CurrentItemId $sessionId $status $exitCode $staleKillMarker $progressJson $checkpointPath $paths.ProjectRoot $baseCommit
886
967
  }
887
968
  }
888
- $updateResult = Invoke-PrizmPythonJson $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'update', $idOption, $CurrentItemId, '--session-id', $sessionId, '--session-status', $status) + $maxRetryArgs + $maxInfraRetryArgs)
969
+ $updateResult = Invoke-PrizmPythonJson $python (@((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', 'update', $idOption, $CurrentItemId, '--session-id', $sessionId, '--session-status', $status) + $maxRetryArgs + $maxInfraRetryArgs + $statusContinuationArgs)
889
970
  if ($updateResult -and $updateResult.PSObject.Properties['new_status']) {
890
971
  $itemListStatus = [string]$updateResult.new_status
891
972
  }
@@ -894,6 +975,9 @@ function Invoke-PrizmPipeline {
894
975
  }
895
976
  }
896
977
 
978
+ break
979
+ }
980
+
897
981
  if ($status -eq 'success' -and $mergeSucceeded) {
898
982
  Write-PrizmSuccess "$Kind item completed: $CurrentItemId"
899
983
  } else {
@@ -951,7 +1035,7 @@ function Invoke-PrizmPipeline {
951
1035
  }
952
1036
 
953
1037
  $processedCount++
954
- Invoke-PrizmPipelineItem $nextItemId
1038
+ Invoke-PrizmPipelineItem $nextItemId $next
955
1039
  $lastExitCode = $script:PRIZM_ITEM_EXIT_CODE
956
1040
  if ($dryRun) {
957
1041
  $global:PRIZM_EXIT_CODE = $lastExitCode
@@ -14,6 +14,35 @@ function Get-PrizmFeatureSlug {
14
14
  return "$numeric-$slug"
15
15
  }
16
16
 
17
+ function Write-PrizmContinuationResetWarning {
18
+ param([string]$StatusPath, [string]$ItemId, [string]$Label)
19
+ if (-not (Test-Path $StatusPath)) { return }
20
+ try {
21
+ $status = Get-Content $StatusPath -Raw | ConvertFrom-Json
22
+ } catch {
23
+ return
24
+ }
25
+ if ($status.continuation_pending -eq $true) {
26
+ $reason = $status.continuation_reason
27
+ if (-not $reason) { $reason = 'context_overflow' }
28
+ Write-PrizmWarn "Continuation pending for $Label $ItemId ($reason). Manual reset will abandon automatic continuation progress."
29
+ if ($status.active_dev_branch) {
30
+ Write-PrizmWarn "Pending continuation branch: $($status.active_dev_branch)"
31
+ }
32
+ }
33
+ }
34
+
35
+ function Write-PrizmContinuationResetWarningsForState {
36
+ param([string]$StateDir, [string]$Label)
37
+ if (-not (Test-Path $StateDir)) { return }
38
+ Get-ChildItem -Path $StateDir -Directory -ErrorAction SilentlyContinue | ForEach-Object {
39
+ Write-PrizmContinuationResetWarning `
40
+ -StatusPath (Join-Path $_.FullName 'status.json') `
41
+ -ItemId $_.Name `
42
+ -Label $Label
43
+ }
44
+ }
45
+
17
46
  function Invoke-PrizmReset {
18
47
  param(
19
48
  [ValidateSet('feature','bugfix','refactor')][string]$Kind,
@@ -60,6 +89,7 @@ function Invoke-PrizmReset {
60
89
  }
61
90
 
62
91
  if (-not $itemId) {
92
+ Write-PrizmContinuationResetWarningsForState -StateDir $stateDir -Label $label
63
93
  & (Join-Path $paths.PipelineDir $runScript) reset
64
94
  $global:PRIZM_RESET_EXIT_CODE = $LASTEXITCODE
65
95
  return
@@ -67,6 +97,10 @@ function Invoke-PrizmReset {
67
97
 
68
98
  if (-not (Test-Path $listPath)) { throw "List file not found: $listPath" }
69
99
  New-Item -ItemType Directory -Force -Path $stateDir | Out-Null
100
+ Write-PrizmContinuationResetWarning `
101
+ -StatusPath (Join-Path (Join-Path $stateDir $itemId) 'status.json') `
102
+ -ItemId $itemId `
103
+ -Label $label
70
104
 
71
105
  $action = if ($clean) { 'clean' } else { 'reset' }
72
106
  $resetArgs = @((Join-Path $paths.ScriptsDir $updateScript), $listOption, $listPath, '--state-dir', $stateDir, '--action', $action, $idOption, $itemId)
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env pwsh
2
+ # Manual reset entrypoint; shared warning/reset behavior lives in lib/reset.ps1.
2
3
  . "$PSScriptRoot\lib\reset.ps1"
3
4
  try {
4
5
  Invoke-PrizmReset -Kind bugfix -ScriptRoot $PSScriptRoot -Args $args
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env pwsh
2
+ # Manual reset entrypoint; shared warning/reset behavior lives in lib/reset.ps1.
2
3
  . "$PSScriptRoot\lib\reset.ps1"
3
4
  try {
4
5
  Invoke-PrizmReset -Kind feature -ScriptRoot $PSScriptRoot -Args $args
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env pwsh
2
+ # Manual reset entrypoint; shared warning/reset behavior lives in lib/reset.ps1.
2
3
  . "$PSScriptRoot\lib\reset.ps1"
3
4
  try {
4
5
  Invoke-PrizmReset -Kind refactor -ScriptRoot $PSScriptRoot -Args $args