prizmkit 1.1.57 → 1.1.60

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 (188) hide show
  1. package/bin/create-prizmkit.js +8 -6
  2. package/bundled/VERSION.json +3 -3
  3. package/bundled/adapters/codex/agent-adapter.js +38 -0
  4. package/bundled/adapters/codex/paths.js +27 -0
  5. package/bundled/adapters/codex/rules-adapter.js +30 -0
  6. package/bundled/adapters/codex/settings-adapter.js +27 -0
  7. package/bundled/adapters/codex/skill-adapter.js +65 -0
  8. package/bundled/adapters/codex/team-adapter.js +37 -0
  9. package/bundled/dev-pipeline/.env.example +2 -1
  10. package/bundled/dev-pipeline/README.md +10 -7
  11. package/bundled/dev-pipeline/lib/common.sh +278 -37
  12. package/bundled/dev-pipeline/run-bugfix.sh +10 -61
  13. package/bundled/dev-pipeline/run-feature.sh +10 -78
  14. package/bundled/dev-pipeline/run-recovery.sh +10 -46
  15. package/bundled/dev-pipeline/run-refactor.sh +10 -61
  16. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +17 -7
  17. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +9 -3
  18. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +9 -3
  19. package/bundled/dev-pipeline/scripts/utils.py +6 -4
  20. package/bundled/dev-pipeline-windows/.env.example +28 -0
  21. package/bundled/dev-pipeline-windows/README.md +30 -0
  22. package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +525 -0
  23. package/bundled/dev-pipeline-windows/assets/feature-list-example.json +146 -0
  24. package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +138 -0
  25. package/bundled/dev-pipeline-windows/launch-bugfix-daemon.ps1 +9 -0
  26. package/bundled/dev-pipeline-windows/launch-feature-daemon.ps1 +9 -0
  27. package/bundled/dev-pipeline-windows/launch-refactor-daemon.ps1 +9 -0
  28. package/bundled/dev-pipeline-windows/lib/common.ps1 +432 -0
  29. package/bundled/dev-pipeline-windows/lib/daemon.ps1 +140 -0
  30. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +446 -0
  31. package/bundled/dev-pipeline-windows/lib/reset.ps1 +87 -0
  32. package/bundled/dev-pipeline-windows/reset-bug.ps1 +9 -0
  33. package/bundled/dev-pipeline-windows/reset-feature.ps1 +9 -0
  34. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +9 -0
  35. package/bundled/dev-pipeline-windows/run-bugfix.ps1 +9 -0
  36. package/bundled/dev-pipeline-windows/run-feature.ps1 +9 -0
  37. package/bundled/dev-pipeline-windows/run-recovery.ps1 +76 -0
  38. package/bundled/dev-pipeline-windows/run-refactor.ps1 +9 -0
  39. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +228 -0
  40. package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +192 -0
  41. package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +530 -0
  42. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +1737 -0
  43. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +685 -0
  44. package/bundled/dev-pipeline-windows/scripts/generate-recovery-prompt.py +805 -0
  45. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +763 -0
  46. package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +316 -0
  47. package/bundled/dev-pipeline-windows/scripts/init-dev-team.py +134 -0
  48. package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +380 -0
  49. package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +399 -0
  50. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +388 -0
  51. package/bundled/dev-pipeline-windows/scripts/patch-completion-notes.py +191 -0
  52. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +864 -0
  53. package/bundled/dev-pipeline-windows/scripts/update-checkpoint.py +173 -0
  54. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +1501 -0
  55. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +1073 -0
  56. package/bundled/dev-pipeline-windows/scripts/utils.py +542 -0
  57. package/bundled/dev-pipeline-windows/templates/agent-prompts/critic-plan-challenge.md +7 -0
  58. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-fix.md +7 -0
  59. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +30 -0
  60. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-resume.md +5 -0
  61. package/bundled/dev-pipeline-windows/templates/agent-prompts/reviewer-review.md +7 -0
  62. package/bundled/dev-pipeline-windows/templates/bootstrap-prompt.md +46 -0
  63. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +43 -0
  64. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +43 -0
  65. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +43 -0
  66. package/bundled/dev-pipeline-windows/templates/bug-fix-list-schema.json +263 -0
  67. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +320 -0
  68. package/bundled/dev-pipeline-windows/templates/feature-list-schema.json +237 -0
  69. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +331 -0
  70. package/bundled/dev-pipeline-windows/templates/refactor-list-schema.json +270 -0
  71. package/bundled/dev-pipeline-windows/templates/sections/ac-verification-checklist.md +13 -0
  72. package/bundled/dev-pipeline-windows/templates/sections/checkpoint-system.md +91 -0
  73. package/bundled/dev-pipeline-windows/templates/sections/context-budget-rules.md +33 -0
  74. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-agent.md +10 -0
  75. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-full.md +12 -0
  76. package/bundled/dev-pipeline-windows/templates/sections/critical-paths-lite.md +7 -0
  77. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-agent.md +8 -0
  78. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-full.md +9 -0
  79. package/bundled/dev-pipeline-windows/templates/sections/directory-convention-lite.md +6 -0
  80. package/bundled/dev-pipeline-windows/templates/sections/failure-capture.md +21 -0
  81. package/bundled/dev-pipeline-windows/templates/sections/feature-context.md +31 -0
  82. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-auto.md +72 -0
  83. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification-opencli.md +63 -0
  84. package/bundled/dev-pipeline-windows/templates/sections/phase-browser-verification.md +62 -0
  85. package/bundled/dev-pipeline-windows/templates/sections/phase-commit-full.md +71 -0
  86. package/bundled/dev-pipeline-windows/templates/sections/phase-commit.md +64 -0
  87. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-agent-suffix.md +23 -0
  88. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-base.md +24 -0
  89. package/bundled/dev-pipeline-windows/templates/sections/phase-context-snapshot-lite-suffix.md +12 -0
  90. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan-full.md +53 -0
  91. package/bundled/dev-pipeline-windows/templates/sections/phase-critic-plan.md +32 -0
  92. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-agent.md +37 -0
  93. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-full.md +50 -0
  94. package/bundled/dev-pipeline-windows/templates/sections/phase-implement-lite.md +52 -0
  95. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-agent.md +27 -0
  96. package/bundled/dev-pipeline-windows/templates/sections/phase-plan-lite.md +27 -0
  97. package/bundled/dev-pipeline-windows/templates/sections/phase-review-agent.md +27 -0
  98. package/bundled/dev-pipeline-windows/templates/sections/phase-review-full.md +29 -0
  99. package/bundled/dev-pipeline-windows/templates/sections/phase-specify-plan-full.md +77 -0
  100. package/bundled/dev-pipeline-windows/templates/sections/phase0-init.md +13 -0
  101. package/bundled/dev-pipeline-windows/templates/sections/phase0-test-baseline.md +23 -0
  102. package/bundled/dev-pipeline-windows/templates/sections/session-context.md +5 -0
  103. package/bundled/dev-pipeline-windows/templates/sections/subagent-timeout-recovery.md +6 -0
  104. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-agent.md +67 -0
  105. package/bundled/dev-pipeline-windows/templates/sections/test-failure-recovery-lite.md +58 -0
  106. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +83 -0
  107. package/bundled/skills/_metadata.json +1 -1
  108. package/bundled/skills/app-planner/SKILL.md +26 -18
  109. package/bundled/skills/app-planner/references/architecture-decisions.md +9 -5
  110. package/bundled/skills/app-planner/references/frontend-design-guide.md +1 -1
  111. package/bundled/skills/feature-planner/SKILL.md +9 -2
  112. package/bundled/skills/prizmkit-init/SKILL.md +7 -6
  113. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +2 -0
  114. package/bundled/skills-windows/app-planner/SKILL.md +639 -0
  115. package/bundled/skills-windows/app-planner/assets/app-design-guide.md +101 -0
  116. package/bundled/skills-windows/app-planner/references/architecture-decisions.md +52 -0
  117. package/bundled/skills-windows/app-planner/references/brainstorm-guide.md +101 -0
  118. package/bundled/skills-windows/app-planner/references/frontend-design-guide.md +71 -0
  119. package/bundled/skills-windows/app-planner/references/project-brief-guide.md +82 -0
  120. package/bundled/skills-windows/app-planner/references/red-team-checklist.md +40 -0
  121. package/bundled/skills-windows/app-planner/references/rules/backend/derivation-rules.md +609 -0
  122. package/bundled/skills-windows/app-planner/references/rules/backend/fixed-rules.md +285 -0
  123. package/bundled/skills-windows/app-planner/references/rules/backend/question-bank.md +249 -0
  124. package/bundled/skills-windows/app-planner/references/rules/backend/template.md +173 -0
  125. package/bundled/skills-windows/app-planner/references/rules/database/derivation-rules.md +373 -0
  126. package/bundled/skills-windows/app-planner/references/rules/database/fixed-rules.md +211 -0
  127. package/bundled/skills-windows/app-planner/references/rules/database/question-bank.md +184 -0
  128. package/bundled/skills-windows/app-planner/references/rules/database/template.md +158 -0
  129. package/bundled/skills-windows/app-planner/references/rules/frontend/derivation-rules.md +810 -0
  130. package/bundled/skills-windows/app-planner/references/rules/frontend/fixed-rules.md +188 -0
  131. package/bundled/skills-windows/app-planner/references/rules/frontend/question-bank.md +302 -0
  132. package/bundled/skills-windows/app-planner/references/rules/frontend/template.md +320 -0
  133. package/bundled/skills-windows/app-planner/references/rules/mobile/derivation-rules.md +639 -0
  134. package/bundled/skills-windows/app-planner/references/rules/mobile/fixed-rules.md +290 -0
  135. package/bundled/skills-windows/app-planner/references/rules/mobile/question-bank.md +232 -0
  136. package/bundled/skills-windows/app-planner/references/rules/mobile/template.md +175 -0
  137. package/bundled/skills-windows/bug-fix-workflow/SKILL.md +415 -0
  138. package/bundled/skills-windows/bug-planner/SKILL.md +395 -0
  139. package/bundled/skills-windows/bug-planner/assets/bug-confirmation-template.md +43 -0
  140. package/bundled/skills-windows/bug-planner/references/critic-and-verification.md +44 -0
  141. package/bundled/skills-windows/bug-planner/references/error-recovery.md +73 -0
  142. package/bundled/skills-windows/bug-planner/references/input-formats.md +53 -0
  143. package/bundled/skills-windows/bug-planner/references/schema-validation.md +25 -0
  144. package/bundled/skills-windows/bug-planner/references/severity-rules.md +16 -0
  145. package/bundled/skills-windows/bug-planner/scripts/validate-bug-list.py +322 -0
  146. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +380 -0
  147. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +441 -0
  148. package/bundled/skills-windows/feature-pipeline-launcher/scripts/preflight-check.py +462 -0
  149. package/bundled/skills-windows/feature-planner/SKILL.md +401 -0
  150. package/bundled/skills-windows/feature-planner/assets/evaluation-guide.md +64 -0
  151. package/bundled/skills-windows/feature-planner/assets/planning-guide.md +214 -0
  152. package/bundled/skills-windows/feature-planner/references/browser-interaction.md +59 -0
  153. package/bundled/skills-windows/feature-planner/references/completeness-review.md +57 -0
  154. package/bundled/skills-windows/feature-planner/references/decomposition-patterns.md +75 -0
  155. package/bundled/skills-windows/feature-planner/references/error-recovery.md +90 -0
  156. package/bundled/skills-windows/feature-planner/references/incremental-feature-planning.md +112 -0
  157. package/bundled/skills-windows/feature-planner/references/new-project-planning.md +85 -0
  158. package/bundled/skills-windows/feature-planner/scripts/validate-and-generate.py +1029 -0
  159. package/bundled/skills-windows/feature-workflow/SKILL.md +531 -0
  160. package/bundled/skills-windows/prizmkit-init/SKILL.md +356 -0
  161. package/bundled/skills-windows/prizmkit-init/assets/project-brief-template.md +82 -0
  162. package/bundled/skills-windows/prizmkit-init/references/config-schema.md +68 -0
  163. package/bundled/skills-windows/prizmkit-init/references/rules/layer-detection.md +41 -0
  164. package/bundled/skills-windows/prizmkit-init/references/tech-stack-catalog.md +13 -0
  165. package/bundled/skills-windows/prizmkit-init/references/update-supplement.md +9 -0
  166. package/bundled/skills-windows/recovery-workflow/SKILL.md +456 -0
  167. package/bundled/skills-windows/recovery-workflow/evals/evals.json +46 -0
  168. package/bundled/skills-windows/recovery-workflow/scripts/detect-recovery-state.py +544 -0
  169. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +406 -0
  170. package/bundled/skills-windows/refactor-planner/SKILL.md +540 -0
  171. package/bundled/skills-windows/refactor-planner/assets/planning-guide.md +292 -0
  172. package/bundled/skills-windows/refactor-planner/references/behavior-preservation.md +301 -0
  173. package/bundled/skills-windows/refactor-planner/references/refactor-scoping-guide.md +221 -0
  174. package/bundled/skills-windows/refactor-planner/scripts/validate-and-generate-refactor.py +858 -0
  175. package/bundled/skills-windows/refactor-workflow/SKILL.md +503 -0
  176. package/package.json +3 -2
  177. package/src/clean.js +73 -2
  178. package/src/config.js +159 -50
  179. package/src/detect-platform.js +16 -8
  180. package/src/external-skills.js +26 -19
  181. package/src/index.js +31 -9
  182. package/src/manifest.js +6 -2
  183. package/src/metadata.js +43 -5
  184. package/src/platforms.js +36 -0
  185. package/src/prompts.js +31 -6
  186. package/src/runtimes.js +20 -0
  187. package/src/scaffold.js +314 -110
  188. package/src/upgrade.js +81 -41
@@ -0,0 +1,388 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ parse-stream-progress.py - Real-time stream-json progress parser
4
+
5
+ Continuously reads an AI CLI session log (JSONL from --output-format stream-json),
6
+ extracts tool calls, phase changes, and activity metrics, and writes structured
7
+ progress to a progress.json file for heartbeat monitoring.
8
+
9
+ Usage:
10
+ python3 parse-stream-progress.py --session-log <path> --progress-file <path>
11
+
12
+ The script runs until:
13
+ - The session log stops growing and the CLI process exits
14
+ - It receives SIGTERM/SIGINT
15
+ """
16
+
17
+ import argparse
18
+ import json
19
+ import os
20
+ import signal
21
+ import sys
22
+ import tempfile
23
+ import time
24
+ from collections import Counter
25
+ from datetime import datetime, timezone
26
+
27
+
28
+ # Ordered pipeline phases — index defines forward-only progression.
29
+ # Phase detection is monotonic: once a phase is reached, earlier phases
30
+ # cannot be re-entered (prevents false positives from file content mentions).
31
+ PHASE_ORDER = ["plan", "implement", "code-review", "retrospective", "commit"]
32
+
33
+ # Keywords for phase detection.
34
+ # "strong" keywords are skill invocations — high confidence, but still
35
+ # forward-only (a mention of prizmkit-plan while in commit phase is noise).
36
+ # "weak" keywords are contextual hints — also forward-only, used when no
37
+ # strong keyword matches.
38
+ PHASE_KEYWORDS = {
39
+ "plan": {
40
+ "strong": ["prizmkit-plan"],
41
+ "weak": ["spec.md", "plan.md", "specification", "architecture", "task checklist", "task breakdown", "gathering requirements"],
42
+ },
43
+ "implement": {
44
+ "strong": ["prizmkit-implement"],
45
+ "weak": ["implement", "writing code", "TDD", "coding"],
46
+ },
47
+ "code-review": {
48
+ "strong": ["prizmkit-code-review"],
49
+ "weak": ["code review", "review verdict", "reviewing"],
50
+ },
51
+ "retrospective": {
52
+ "strong": ["prizmkit-retrospective"],
53
+ "weak": ["retrospective", "structural sync", "architecture knowledge", ".prizmkit/prizm-docs/ sync"],
54
+ },
55
+ "commit": {
56
+ "strong": ["prizmkit-committer"],
57
+ "weak": ["git commit", "committing", "feat(", "fix("],
58
+ },
59
+ }
60
+
61
+
62
+ class ProgressTracker:
63
+ """Tracks progress state from stream-json events."""
64
+
65
+ def __init__(self):
66
+ self.message_count = 0
67
+ self.current_tool = None
68
+ self.current_tool_input_summary = ""
69
+ self.current_phase = None
70
+ self.detected_phases = []
71
+ self.tool_call_counts = Counter()
72
+ self.total_tool_calls = 0
73
+ self.last_text_snippet = ""
74
+ self.is_active = True
75
+ self.errors = []
76
+ self._text_buffer = ""
77
+ self._in_tool_use = False
78
+ self._current_tool_input_parts = []
79
+
80
+ def process_event(self, event):
81
+ """Process a single stream-json event and update state.
82
+
83
+ Supports two formats:
84
+ 1. Claude API raw stream: message_start, content_block_start, content_block_delta, etc.
85
+ 2. Claude Code verbose stream-json: assistant, user, tool_result, system, etc.
86
+ (produced by claude/claude-internal --verbose --output-format stream-json)
87
+ """
88
+ event_type = event.get("type", "")
89
+
90
+ # ── Claude Code verbose format ──────────────────────────────
91
+ if event_type == "assistant":
92
+ self.message_count += 1
93
+ self.is_active = True
94
+ message = event.get("message", {})
95
+ content_blocks = message.get("content", [])
96
+ for block in content_blocks:
97
+ block_type = block.get("type", "")
98
+ if block_type == "tool_use":
99
+ tool_name = block.get("name", "unknown")
100
+ self.current_tool = tool_name
101
+ self.tool_call_counts[tool_name] += 1
102
+ self.total_tool_calls += 1
103
+ # Extract summary from input
104
+ tool_input = block.get("input", {})
105
+ if isinstance(tool_input, dict):
106
+ self._extract_tool_summary_from_dict(tool_input)
107
+ self._detect_phase(json.dumps(tool_input, ensure_ascii=False)[:500])
108
+ elif block_type == "text":
109
+ text = block.get("text", "")
110
+ if text.strip():
111
+ self.last_text_snippet = text.strip()[:120]
112
+ self._detect_phase(text)
113
+
114
+ elif event_type == "tool_result" or event_type == "user":
115
+ # tool_result contains output from tool execution
116
+ self.is_active = True
117
+
118
+ elif event_type == "system":
119
+ # System events (hooks, init, etc.) — track but don't count as messages
120
+ subtype = event.get("subtype", "")
121
+ if subtype == "init":
122
+ self.is_active = True
123
+
124
+ # ── Claude API raw stream format ────────────────────────────
125
+ elif event_type == "message_start":
126
+ self.message_count += 1
127
+ self.is_active = True
128
+
129
+ elif event_type == "message_stop":
130
+ self.current_tool = None
131
+ self.current_tool_input_summary = ""
132
+ self._in_tool_use = False
133
+ self._current_tool_input_parts = []
134
+
135
+ elif event_type == "content_block_start":
136
+ content_block = event.get("content_block", {})
137
+ block_type = content_block.get("type", "")
138
+
139
+ if block_type == "tool_use":
140
+ tool_name = content_block.get("name", "unknown")
141
+ self.current_tool = tool_name
142
+ self.current_tool_input_summary = ""
143
+ self.tool_call_counts[tool_name] += 1
144
+ self.total_tool_calls += 1
145
+ self._in_tool_use = True
146
+ self._current_tool_input_parts = []
147
+
148
+ elif block_type == "text":
149
+ self._text_buffer = ""
150
+ self._in_tool_use = False
151
+
152
+ elif event_type == "content_block_delta":
153
+ delta = event.get("delta", {})
154
+ delta_type = delta.get("type", "")
155
+
156
+ if delta_type == "text_delta":
157
+ text = delta.get("text", "")
158
+ self._text_buffer += text
159
+ # Keep last meaningful snippet
160
+ stripped = text.strip()
161
+ if stripped:
162
+ self.last_text_snippet = stripped[:120]
163
+ # Try to detect phase from text
164
+ self._detect_phase(text)
165
+
166
+ elif delta_type == "input_json_delta":
167
+ partial = delta.get("partial_json", "")
168
+ self._current_tool_input_parts.append(partial)
169
+ # Build a summary from accumulated input
170
+ accumulated = "".join(self._current_tool_input_parts)
171
+ self.current_tool_input_summary = accumulated[:150]
172
+
173
+ elif event_type == "content_block_stop":
174
+ if self._in_tool_use:
175
+ # Try to extract a better summary from complete tool input
176
+ full_input = "".join(self._current_tool_input_parts)
177
+ self._extract_tool_summary(full_input)
178
+ self._detect_phase(full_input)
179
+ else:
180
+ # Text block finished - detect phase from accumulated text
181
+ if self._text_buffer:
182
+ self._detect_phase(self._text_buffer)
183
+ self._in_tool_use = False
184
+ self._current_tool_input_parts = []
185
+
186
+ elif event_type == "error":
187
+ error_msg = event.get("error", {}).get("message", "Unknown error")
188
+ self.errors.append(error_msg)
189
+
190
+ # Check for subagent indicator
191
+ if event.get("parent_tool_use_id"):
192
+ # This is a sub-agent event; tool name is still tracked normally
193
+ pass
194
+
195
+ def _detect_phase(self, text):
196
+ """Detect pipeline phase from text content.
197
+
198
+ Uses monotonic (forward-only) progression to avoid false positives.
199
+ Strong keywords (skill invocations) always trigger; weak keywords
200
+ only trigger for phases at or ahead of the current position.
201
+ """
202
+ text_lower = text.lower()
203
+ current_idx = (
204
+ PHASE_ORDER.index(self.current_phase)
205
+ if self.current_phase in PHASE_ORDER
206
+ else -1
207
+ )
208
+
209
+ for phase, kw_groups in PHASE_KEYWORDS.items():
210
+ phase_idx = PHASE_ORDER.index(phase)
211
+
212
+ # Check strong keywords — high confidence but still forward-only
213
+ for keyword in kw_groups.get("strong", []):
214
+ if keyword.lower() in text_lower:
215
+ if phase_idx >= current_idx:
216
+ self.current_phase = phase
217
+ if phase not in self.detected_phases:
218
+ self.detected_phases.append(phase)
219
+ return
220
+ # Backward strong match — skip this phase but do NOT
221
+ # early-return, so forward weak matches can still fire.
222
+ break
223
+
224
+ # Check weak keywords — only trigger for forward phases
225
+ if phase_idx >= current_idx:
226
+ for keyword in kw_groups.get("weak", []):
227
+ if keyword.lower() in text_lower:
228
+ self.current_phase = phase
229
+ if phase not in self.detected_phases:
230
+ self.detected_phases.append(phase)
231
+ return
232
+
233
+ def _extract_tool_summary(self, raw_input):
234
+ """Extract a human-readable summary from tool input JSON string."""
235
+ try:
236
+ data = json.loads(raw_input)
237
+ self._extract_tool_summary_from_dict(data)
238
+ except (json.JSONDecodeError, TypeError):
239
+ # Keep whatever partial summary we had
240
+ pass
241
+
242
+ def _extract_tool_summary_from_dict(self, data):
243
+ """Extract a human-readable summary from tool input dict."""
244
+ if isinstance(data, dict):
245
+ # Common patterns in tool inputs
246
+ if "description" in data:
247
+ self.current_tool_input_summary = str(data["description"])[:100]
248
+ elif "command" in data:
249
+ self.current_tool_input_summary = str(data["command"])[:100]
250
+ elif "file_path" in data:
251
+ self.current_tool_input_summary = str(data["file_path"])[:100]
252
+ elif "pattern" in data:
253
+ self.current_tool_input_summary = str(data["pattern"])[:100]
254
+ elif "query" in data:
255
+ self.current_tool_input_summary = str(data["query"])[:100]
256
+ elif "prompt" in data:
257
+ self.current_tool_input_summary = str(data["prompt"])[:100]
258
+
259
+ def to_dict(self):
260
+ """Export current state as a dictionary for JSON serialization."""
261
+ tool_calls = [
262
+ {"name": name, "count": count}
263
+ for name, count in self.tool_call_counts.most_common()
264
+ ]
265
+ return {
266
+ "updated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
267
+ "message_count": self.message_count,
268
+ "current_tool": self.current_tool,
269
+ "current_tool_input_summary": self.current_tool_input_summary,
270
+ "current_phase": self.current_phase,
271
+ "detected_phases": self.detected_phases,
272
+ "tool_calls": tool_calls,
273
+ "total_tool_calls": self.total_tool_calls,
274
+ "last_text_snippet": self.last_text_snippet,
275
+ "is_active": self.is_active,
276
+ "errors": self.errors[-10:], # Keep last 10 errors
277
+ }
278
+
279
+
280
+ def atomic_write_json(data, filepath):
281
+ """Write JSON data atomically using tmp file + rename."""
282
+ dir_path = os.path.dirname(filepath)
283
+ tmp_path = None
284
+ try:
285
+ fd, tmp_path = tempfile.mkstemp(dir=dir_path, suffix=".tmp")
286
+ with os.fdopen(fd, "w") as f:
287
+ json.dump(data, f, indent=2)
288
+ f.write("\n")
289
+ os.rename(tmp_path, filepath)
290
+ except OSError:
291
+ # Best effort - remove tmp file if rename failed
292
+ if tmp_path:
293
+ try:
294
+ os.unlink(tmp_path)
295
+ except OSError:
296
+ pass
297
+
298
+
299
+ def tail_and_parse(session_log, progress_file, poll_interval=0.5):
300
+ """Tail session log and parse stream-json events."""
301
+ tracker = ProgressTracker()
302
+ last_write_state = None
303
+
304
+ # Wait for log file to appear
305
+ wait_count = 0
306
+ while not os.path.exists(session_log):
307
+ time.sleep(poll_interval)
308
+ wait_count += 1
309
+ if wait_count > 120: # 60 seconds max wait
310
+ sys.stderr.write(f"Timeout waiting for {session_log}\n")
311
+ sys.exit(1)
312
+
313
+ with open(session_log, "r") as f:
314
+ idle_count = 0
315
+ while True:
316
+ line = f.readline()
317
+ if line:
318
+ idle_count = 0
319
+ line = line.strip()
320
+ if not line:
321
+ continue
322
+ try:
323
+ event = json.loads(line)
324
+ tracker.process_event(event)
325
+ except json.JSONDecodeError:
326
+ # Not a JSON line (could be stderr mixed in)
327
+ # Use it as a text snippet if meaningful
328
+ stripped = line.strip()
329
+ if stripped and len(stripped) > 5:
330
+ tracker.last_text_snippet = stripped[:120]
331
+ continue
332
+
333
+ # Write progress if state changed
334
+ current_state = tracker.to_dict()
335
+ state_key = (
336
+ current_state["message_count"],
337
+ current_state["current_tool"],
338
+ current_state["current_phase"],
339
+ current_state["total_tool_calls"],
340
+ )
341
+ if state_key != last_write_state:
342
+ atomic_write_json(current_state, progress_file)
343
+ last_write_state = state_key
344
+ else:
345
+ idle_count += 1
346
+ # After 2 seconds of no new data, write current state anyway
347
+ # (ensures progress.json stays fresh)
348
+ if idle_count == 4:
349
+ current_state = tracker.to_dict()
350
+ atomic_write_json(current_state, progress_file)
351
+
352
+ # After 3600 idle cycles (30 min), mark inactive and exit
353
+ if idle_count > 3600:
354
+ tracker.is_active = False
355
+ atomic_write_json(tracker.to_dict(), progress_file)
356
+ break
357
+
358
+ time.sleep(poll_interval)
359
+
360
+ # Final write
361
+ tracker.is_active = False
362
+ atomic_write_json(tracker.to_dict(), progress_file)
363
+
364
+
365
+ def main():
366
+ parser = argparse.ArgumentParser(description="Parse stream-json progress")
367
+ parser.add_argument("--session-log", required=True, help="Path to session.log (JSONL)")
368
+ parser.add_argument("--progress-file", required=True, help="Path to write progress.json")
369
+ args = parser.parse_args()
370
+
371
+ # Handle graceful shutdown
372
+ def handle_signal(signum, frame):
373
+ sys.exit(0)
374
+
375
+ signal.signal(signal.SIGTERM, handle_signal)
376
+ signal.signal(signal.SIGINT, handle_signal)
377
+
378
+ try:
379
+ tail_and_parse(args.session_log, args.progress_file)
380
+ except SystemExit:
381
+ pass
382
+ except Exception as e:
383
+ sys.stderr.write(f"parse-stream-progress.py error: {e}\n")
384
+ sys.exit(1)
385
+
386
+
387
+ if __name__ == "__main__":
388
+ main()
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env python3
2
+ """Patch completion_notes into feature-list.json, refactor-list.json, or bug-fix-list.json.
3
+
4
+ Reads a completion-summary.json file written by the AI session and patches
5
+ the corresponding item in the task list with the completion_notes field.
6
+
7
+ This enables rich dependency context propagation: when a downstream task's
8
+ bootstrap prompt is generated, it can read completion_notes from its
9
+ completed dependencies to understand what was built/changed.
10
+
11
+ Usage:
12
+ python3 patch-completion-notes.py \
13
+ --feature-list .prizmkit/plans/feature-list.json \
14
+ --feature-id F-001 \
15
+ --summary .prizmkit/specs/001-my-feature/completion-summary.json
16
+
17
+ python3 patch-completion-notes.py \
18
+ --refactor-list .prizmkit/plans/refactor-list.json \
19
+ --refactor-id R-001 \
20
+ --summary <path-to-summary>
21
+ """
22
+
23
+ import argparse
24
+ import json
25
+ import os
26
+ import sys
27
+
28
+ from utils import load_json_file, write_json_file, setup_logging
29
+
30
+ LOGGER = setup_logging("patch-completion-notes")
31
+
32
+
33
+ def parse_args():
34
+ parser = argparse.ArgumentParser(
35
+ description="Patch completion_notes into a task list from completion-summary.json."
36
+ )
37
+ parser.add_argument(
38
+ "--feature-list",
39
+ default=None,
40
+ help="Path to .prizmkit/plans/feature-list.json",
41
+ )
42
+ parser.add_argument(
43
+ "--refactor-list",
44
+ default=None,
45
+ help="Path to .prizmkit/plans/refactor-list.json",
46
+ )
47
+ parser.add_argument(
48
+ "--bug-list",
49
+ default=None,
50
+ help="Path to .prizmkit/plans/bug-fix-list.json",
51
+ )
52
+ parser.add_argument(
53
+ "--feature-id",
54
+ default=None,
55
+ help="Feature ID to patch (e.g. F-001)",
56
+ )
57
+ parser.add_argument(
58
+ "--refactor-id",
59
+ default=None,
60
+ help="Refactor ID to patch (e.g. R-001)",
61
+ )
62
+ parser.add_argument(
63
+ "--bug-id",
64
+ default=None,
65
+ help="Bug ID to patch (e.g. B-001)",
66
+ )
67
+ parser.add_argument(
68
+ "--summary",
69
+ required=True,
70
+ help="Path to completion-summary.json file",
71
+ )
72
+ return parser.parse_args()
73
+
74
+
75
+ def read_completion_notes(summary_path):
76
+ """Read completion_notes from a completion-summary.json file.
77
+
78
+ Returns a list of strings, or an empty list if the file is missing
79
+ or malformed.
80
+ """
81
+ if not os.path.isfile(summary_path):
82
+ LOGGER.warning("Summary file not found: %s", summary_path)
83
+ return []
84
+
85
+ data, err = load_json_file(summary_path)
86
+ if err:
87
+ LOGGER.warning("Failed to read summary: %s", err)
88
+ return []
89
+
90
+ notes = data.get("completion_notes", [])
91
+ if not isinstance(notes, list):
92
+ LOGGER.warning("completion_notes is not a list in %s", summary_path)
93
+ return []
94
+
95
+ # Filter: only keep non-empty strings
96
+ return [n for n in notes if isinstance(n, str) and n.strip()]
97
+
98
+
99
+ def patch_list(list_path, item_id, item_key, notes):
100
+ """Patch completion_notes into a task list JSON file.
101
+
102
+ Args:
103
+ list_path: Path to the JSON list file
104
+ item_id: ID of the item to patch (e.g. "F-001" or "R-001")
105
+ item_key: Key for the items array (e.g. "features" or "refactors")
106
+ notes: List of completion note strings
107
+ """
108
+ data, err = load_json_file(list_path)
109
+ if err:
110
+ LOGGER.error("Failed to read list: %s", err)
111
+ return False
112
+
113
+ items = data.get(item_key, [])
114
+ found = False
115
+ for item in items:
116
+ if isinstance(item, dict) and item.get("id") == item_id:
117
+ item["completion_notes"] = notes
118
+ found = True
119
+ break
120
+
121
+ if not found:
122
+ LOGGER.error("Item %s not found in %s", item_id, list_path)
123
+ return False
124
+
125
+ err = write_json_file(list_path, data)
126
+ if err:
127
+ LOGGER.error("Failed to write list: %s", err)
128
+ return False
129
+
130
+ LOGGER.info(
131
+ "Patched %d completion notes for %s in %s",
132
+ len(notes), item_id, list_path,
133
+ )
134
+ return True
135
+
136
+
137
+ def main():
138
+ args = parse_args()
139
+
140
+ # Determine mode: feature, refactor, or bug
141
+ if args.feature_list and args.feature_id:
142
+ list_path = args.feature_list
143
+ item_id = args.feature_id
144
+ item_key = "features"
145
+ elif args.refactor_list and args.refactor_id:
146
+ list_path = args.refactor_list
147
+ item_id = args.refactor_id
148
+ item_key = "refactors"
149
+ elif args.bug_list and args.bug_id:
150
+ list_path = args.bug_list
151
+ item_id = args.bug_id
152
+ item_key = "bugs"
153
+ else:
154
+ print(
155
+ "Error: must provide either (--feature-list + --feature-id) "
156
+ "or (--refactor-list + --refactor-id) "
157
+ "or (--bug-list + --bug-id)",
158
+ file=sys.stderr,
159
+ )
160
+ sys.exit(1)
161
+
162
+ # Read completion notes
163
+ notes = read_completion_notes(args.summary)
164
+ if not notes:
165
+ LOGGER.info("No completion notes to patch for %s", item_id)
166
+ sys.exit(0)
167
+
168
+ # Patch the list
169
+ if not patch_list(list_path, item_id, item_key, notes):
170
+ sys.exit(1)
171
+
172
+ # Output result
173
+ result = {
174
+ "item_id": item_id,
175
+ "notes_count": len(notes),
176
+ "list_path": os.path.abspath(list_path),
177
+ }
178
+ print(json.dumps(result, indent=2, ensure_ascii=False))
179
+
180
+
181
+ if __name__ == "__main__":
182
+ try:
183
+ main()
184
+ except KeyboardInterrupt:
185
+ sys.exit(130)
186
+ except SystemExit:
187
+ raise
188
+ except Exception as exc:
189
+ LOGGER.exception("Unhandled exception")
190
+ print("Error: {}".format(exc), file=sys.stderr)
191
+ sys.exit(1)