aiwcli 0.10.2 → 0.11.0

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 (249) hide show
  1. package/bin/run.js +1 -1
  2. package/dist/commands/clear.d.ts +11 -6
  3. package/dist/commands/clear.js +229 -381
  4. package/dist/commands/init/index.d.ts +1 -17
  5. package/dist/commands/init/index.js +22 -107
  6. package/dist/lib/gitignore-manager.d.ts +32 -0
  7. package/dist/lib/gitignore-manager.js +141 -2
  8. package/dist/lib/template-installer.d.ts +7 -12
  9. package/dist/lib/template-installer.js +69 -193
  10. package/dist/lib/template-settings-reconstructor.d.ts +35 -0
  11. package/dist/lib/template-settings-reconstructor.js +130 -0
  12. package/dist/templates/CLAUDE.md +8 -8
  13. package/dist/templates/_shared/.claude/commands/handoff-resume.md +64 -0
  14. package/dist/templates/_shared/.claude/commands/handoff.md +16 -10
  15. package/dist/templates/_shared/.claude/settings.json +7 -7
  16. package/dist/templates/_shared/hooks-ts/_utils/git-state.ts +2 -0
  17. package/dist/templates/_shared/hooks-ts/archive_plan.ts +159 -0
  18. package/dist/templates/_shared/hooks-ts/context_monitor.ts +147 -0
  19. package/dist/templates/_shared/hooks-ts/file-suggestion.ts +130 -0
  20. package/dist/templates/_shared/hooks-ts/pre_compact.ts +49 -0
  21. package/dist/templates/_shared/hooks-ts/session_end.ts +104 -0
  22. package/dist/templates/_shared/hooks-ts/session_start.ts +144 -0
  23. package/dist/templates/_shared/hooks-ts/task_create_capture.ts +48 -0
  24. package/dist/templates/_shared/hooks-ts/task_update_capture.ts +74 -0
  25. package/dist/templates/_shared/hooks-ts/user_prompt_submit.ts +83 -0
  26. package/dist/templates/_shared/lib-ts/CLAUDE.md +318 -0
  27. package/dist/templates/_shared/lib-ts/base/atomic-write.ts +138 -0
  28. package/dist/templates/_shared/lib-ts/base/constants.ts +306 -0
  29. package/dist/templates/_shared/lib-ts/base/git-state.ts +58 -0
  30. package/dist/templates/_shared/lib-ts/base/hook-utils.ts +439 -0
  31. package/dist/templates/_shared/lib-ts/base/inference.ts +252 -0
  32. package/dist/templates/_shared/lib-ts/base/logger.ts +250 -0
  33. package/dist/templates/_shared/lib-ts/base/state-io.ts +116 -0
  34. package/dist/templates/_shared/lib-ts/base/stop-words.ts +184 -0
  35. package/dist/templates/_shared/lib-ts/base/subprocess-utils.ts +162 -0
  36. package/dist/templates/_shared/lib-ts/base/utils.ts +184 -0
  37. package/dist/templates/_shared/lib-ts/context/context-formatter.ts +438 -0
  38. package/dist/templates/_shared/lib-ts/context/context-selector.ts +515 -0
  39. package/dist/templates/_shared/lib-ts/context/context-store.ts +707 -0
  40. package/dist/templates/_shared/lib-ts/context/plan-manager.ts +316 -0
  41. package/dist/templates/_shared/lib-ts/context/task-tracker.ts +185 -0
  42. package/dist/templates/_shared/lib-ts/handoff/document-generator.ts +216 -0
  43. package/dist/templates/_shared/lib-ts/handoff/handoff-reader.ts +159 -0
  44. package/dist/templates/_shared/lib-ts/package.json +21 -0
  45. package/dist/templates/_shared/lib-ts/templates/formatters.ts +104 -0
  46. package/dist/templates/_shared/{lib/templates/plan_context.py → lib-ts/templates/plan-context.ts} +14 -22
  47. package/dist/templates/_shared/lib-ts/tsconfig.json +13 -0
  48. package/dist/templates/_shared/lib-ts/types.ts +164 -0
  49. package/dist/templates/_shared/scripts/resolve_context.ts +24 -0
  50. package/dist/templates/_shared/scripts/resume_handoff.ts +321 -0
  51. package/dist/templates/_shared/scripts/save_handoff.ts +359 -0
  52. package/dist/templates/_shared/scripts/status_line.ts +733 -0
  53. package/dist/templates/cc-native/.claude/settings.json +175 -185
  54. package/dist/templates/cc-native/TEMPLATE-SCHEMA.md +15 -17
  55. package/dist/templates/cc-native/_cc-native/agents/ARCH-EVOLUTION.md +63 -0
  56. package/dist/templates/cc-native/_cc-native/agents/ARCH-PATTERNS.md +62 -0
  57. package/dist/templates/cc-native/_cc-native/agents/ARCH-STRUCTURE.md +63 -0
  58. package/dist/templates/cc-native/_cc-native/agents/{ASSUMPTION-CHAIN-TRACER.md → ASSUMPTION-TRACER.md} +6 -10
  59. package/dist/templates/cc-native/_cc-native/agents/CLARITY-AUDITOR.md +6 -10
  60. package/dist/templates/cc-native/_cc-native/agents/CLAUDE.md +74 -3
  61. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-FEASIBILITY.md +67 -0
  62. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-GAPS.md +71 -0
  63. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-ORDERING.md +63 -0
  64. package/dist/templates/cc-native/_cc-native/agents/CONSTRAINT-VALIDATOR.md +73 -0
  65. package/dist/templates/cc-native/_cc-native/agents/DESIGN-ADR-VALIDATOR.md +62 -0
  66. package/dist/templates/cc-native/_cc-native/agents/DESIGN-SCALE-MATCHER.md +65 -0
  67. package/dist/templates/cc-native/_cc-native/agents/DEVILS-ADVOCATE.md +6 -9
  68. package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-PHILOSOPHY.md +87 -0
  69. package/dist/templates/cc-native/_cc-native/agents/HANDOFF-READINESS.md +5 -9
  70. package/dist/templates/cc-native/_cc-native/agents/{HIDDEN-COMPLEXITY-DETECTOR.md → HIDDEN-COMPLEXITY.md} +6 -10
  71. package/dist/templates/cc-native/_cc-native/agents/INCREMENTAL-DELIVERY.md +67 -0
  72. package/dist/templates/cc-native/_cc-native/agents/PLAN-ORCHESTRATOR.md +91 -18
  73. package/dist/templates/cc-native/_cc-native/agents/RISK-DEPENDENCY.md +63 -0
  74. package/dist/templates/cc-native/_cc-native/agents/RISK-FMEA.md +67 -0
  75. package/dist/templates/cc-native/_cc-native/agents/RISK-PREMORTEM.md +72 -0
  76. package/dist/templates/cc-native/_cc-native/agents/RISK-REVERSIBILITY.md +75 -0
  77. package/dist/templates/cc-native/_cc-native/agents/SCOPE-BOUNDARY.md +78 -0
  78. package/dist/templates/cc-native/_cc-native/agents/SIMPLICITY-GUARDIAN.md +5 -9
  79. package/dist/templates/cc-native/_cc-native/agents/SKEPTIC.md +16 -12
  80. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-BEHAVIOR-AUDITOR.md +62 -0
  81. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-CHARACTERIZATION.md +72 -0
  82. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-FIRST-VALIDATOR.md +62 -0
  83. package/dist/templates/cc-native/_cc-native/agents/TESTDRIVEN-PYRAMID-ANALYZER.md +62 -0
  84. package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-COSTS.md +68 -0
  85. package/dist/templates/cc-native/_cc-native/agents/TRADEOFF-STAKEHOLDERS.md +66 -0
  86. package/dist/templates/cc-native/_cc-native/agents/VERIFY-COVERAGE.md +75 -0
  87. package/dist/templates/cc-native/_cc-native/agents/VERIFY-STRENGTH.md +70 -0
  88. package/dist/templates/cc-native/_cc-native/hooks/CLAUDE.md +109 -135
  89. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.ts +119 -0
  90. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.ts +921 -0
  91. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.ts +61 -0
  92. package/dist/templates/cc-native/_cc-native/lib-ts/aggregate-agents.ts +157 -0
  93. package/dist/templates/cc-native/_cc-native/lib-ts/artifacts.ts +709 -0
  94. package/dist/templates/cc-native/_cc-native/lib-ts/cc-native-state.ts +199 -0
  95. package/dist/templates/cc-native/_cc-native/lib-ts/cli-output-parser.ts +124 -0
  96. package/dist/templates/cc-native/_cc-native/lib-ts/config.ts +57 -0
  97. package/dist/templates/cc-native/_cc-native/lib-ts/constants.ts +83 -0
  98. package/dist/templates/cc-native/_cc-native/lib-ts/debug.ts +80 -0
  99. package/dist/templates/cc-native/_cc-native/lib-ts/index.ts +119 -0
  100. package/dist/templates/cc-native/_cc-native/lib-ts/json-parser.ts +162 -0
  101. package/dist/templates/cc-native/_cc-native/lib-ts/nul +3 -0
  102. package/dist/templates/cc-native/_cc-native/lib-ts/orchestrator.ts +249 -0
  103. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/agent.ts +155 -0
  104. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/codex.ts +130 -0
  105. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/gemini.ts +106 -0
  106. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/index.ts +10 -0
  107. package/dist/templates/cc-native/_cc-native/lib-ts/reviewers/types.ts +23 -0
  108. package/dist/templates/cc-native/_cc-native/lib-ts/state.ts +243 -0
  109. package/dist/templates/cc-native/_cc-native/lib-ts/tsconfig.json +18 -0
  110. package/dist/templates/cc-native/_cc-native/lib-ts/types.ts +310 -0
  111. package/dist/templates/cc-native/_cc-native/lib-ts/verdict.ts +72 -0
  112. package/dist/templates/cc-native/_cc-native/plan-review.config.json +12 -16
  113. package/oclif.manifest.json +1 -1
  114. package/package.json +1 -1
  115. package/dist/lib/template-merger.d.ts +0 -47
  116. package/dist/lib/template-merger.js +0 -162
  117. package/dist/templates/_shared/hooks/__init__.py +0 -16
  118. package/dist/templates/_shared/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/dist/templates/_shared/hooks/__pycache__/archive_plan.cpython-313.pyc +0 -0
  120. package/dist/templates/_shared/hooks/__pycache__/context_enforcer.cpython-313.pyc +0 -0
  121. package/dist/templates/_shared/hooks/__pycache__/context_monitor.cpython-313.pyc +0 -0
  122. package/dist/templates/_shared/hooks/__pycache__/file-suggestion.cpython-313.pyc +0 -0
  123. package/dist/templates/_shared/hooks/__pycache__/pre_compact.cpython-313.pyc +0 -0
  124. package/dist/templates/_shared/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
  125. package/dist/templates/_shared/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
  126. package/dist/templates/_shared/hooks/__pycache__/task_create_atomicity.cpython-313.pyc +0 -0
  127. package/dist/templates/_shared/hooks/__pycache__/task_create_capture.cpython-313.pyc +0 -0
  128. package/dist/templates/_shared/hooks/__pycache__/task_update_capture.cpython-313.pyc +0 -0
  129. package/dist/templates/_shared/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  130. package/dist/templates/_shared/hooks/archive_plan.py +0 -169
  131. package/dist/templates/_shared/hooks/context_monitor.py +0 -270
  132. package/dist/templates/_shared/hooks/file-suggestion.py +0 -215
  133. package/dist/templates/_shared/hooks/pre_compact.py +0 -104
  134. package/dist/templates/_shared/hooks/session_end.py +0 -173
  135. package/dist/templates/_shared/hooks/session_start.py +0 -206
  136. package/dist/templates/_shared/hooks/task_create_capture.py +0 -108
  137. package/dist/templates/_shared/hooks/task_update_capture.py +0 -145
  138. package/dist/templates/_shared/hooks/user_prompt_submit.py +0 -139
  139. package/dist/templates/_shared/lib/__init__.py +0 -1
  140. package/dist/templates/_shared/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  141. package/dist/templates/_shared/lib/base/__init__.py +0 -65
  142. package/dist/templates/_shared/lib/base/__pycache__/__init__.cpython-313.pyc +0 -0
  143. package/dist/templates/_shared/lib/base/__pycache__/atomic_write.cpython-313.pyc +0 -0
  144. package/dist/templates/_shared/lib/base/__pycache__/constants.cpython-313.pyc +0 -0
  145. package/dist/templates/_shared/lib/base/__pycache__/hook_utils.cpython-313.pyc +0 -0
  146. package/dist/templates/_shared/lib/base/__pycache__/inference.cpython-313.pyc +0 -0
  147. package/dist/templates/_shared/lib/base/__pycache__/logger.cpython-313.pyc +0 -0
  148. package/dist/templates/_shared/lib/base/__pycache__/stop_words.cpython-313.pyc +0 -0
  149. package/dist/templates/_shared/lib/base/__pycache__/subprocess_utils.cpython-313.pyc +0 -0
  150. package/dist/templates/_shared/lib/base/__pycache__/utils.cpython-313.pyc +0 -0
  151. package/dist/templates/_shared/lib/base/atomic_write.py +0 -180
  152. package/dist/templates/_shared/lib/base/constants.py +0 -358
  153. package/dist/templates/_shared/lib/base/hook_utils.py +0 -341
  154. package/dist/templates/_shared/lib/base/inference.py +0 -318
  155. package/dist/templates/_shared/lib/base/logger.py +0 -291
  156. package/dist/templates/_shared/lib/base/stop_words.py +0 -213
  157. package/dist/templates/_shared/lib/base/subprocess_utils.py +0 -46
  158. package/dist/templates/_shared/lib/base/utils.py +0 -242
  159. package/dist/templates/_shared/lib/context/__init__.py +0 -102
  160. package/dist/templates/_shared/lib/context/__pycache__/__init__.cpython-313.pyc +0 -0
  161. package/dist/templates/_shared/lib/context/__pycache__/cache.cpython-313.pyc +0 -0
  162. package/dist/templates/_shared/lib/context/__pycache__/context_extractor.cpython-313.pyc +0 -0
  163. package/dist/templates/_shared/lib/context/__pycache__/context_formatter.cpython-313.pyc +0 -0
  164. package/dist/templates/_shared/lib/context/__pycache__/context_manager.cpython-313.pyc +0 -0
  165. package/dist/templates/_shared/lib/context/__pycache__/context_selector.cpython-313.pyc +0 -0
  166. package/dist/templates/_shared/lib/context/__pycache__/context_store.cpython-313.pyc +0 -0
  167. package/dist/templates/_shared/lib/context/__pycache__/discovery.cpython-313.pyc +0 -0
  168. package/dist/templates/_shared/lib/context/__pycache__/event_log.cpython-313.pyc +0 -0
  169. package/dist/templates/_shared/lib/context/__pycache__/plan_archive.cpython-313.pyc +0 -0
  170. package/dist/templates/_shared/lib/context/__pycache__/plan_manager.cpython-313.pyc +0 -0
  171. package/dist/templates/_shared/lib/context/__pycache__/task_sync.cpython-313.pyc +0 -0
  172. package/dist/templates/_shared/lib/context/__pycache__/task_tracker.cpython-313.pyc +0 -0
  173. package/dist/templates/_shared/lib/context/context_formatter.py +0 -317
  174. package/dist/templates/_shared/lib/context/context_selector.py +0 -508
  175. package/dist/templates/_shared/lib/context/context_store.py +0 -653
  176. package/dist/templates/_shared/lib/context/plan_manager.py +0 -204
  177. package/dist/templates/_shared/lib/context/task_tracker.py +0 -188
  178. package/dist/templates/_shared/lib/handoff/__init__.py +0 -22
  179. package/dist/templates/_shared/lib/handoff/__pycache__/__init__.cpython-313.pyc +0 -0
  180. package/dist/templates/_shared/lib/handoff/__pycache__/document_generator.cpython-313.pyc +0 -0
  181. package/dist/templates/_shared/lib/handoff/document_generator.py +0 -278
  182. package/dist/templates/_shared/lib/templates/README.md +0 -206
  183. package/dist/templates/_shared/lib/templates/__init__.py +0 -36
  184. package/dist/templates/_shared/lib/templates/__pycache__/__init__.cpython-313.pyc +0 -0
  185. package/dist/templates/_shared/lib/templates/__pycache__/formatters.cpython-313.pyc +0 -0
  186. package/dist/templates/_shared/lib/templates/__pycache__/persona_questions.cpython-313.pyc +0 -0
  187. package/dist/templates/_shared/lib/templates/__pycache__/plan_context.cpython-313.pyc +0 -0
  188. package/dist/templates/_shared/lib/templates/formatters.py +0 -146
  189. package/dist/templates/_shared/scripts/__pycache__/save_handoff.cpython-313.pyc +0 -0
  190. package/dist/templates/_shared/scripts/__pycache__/status_line.cpython-313.pyc +0 -0
  191. package/dist/templates/_shared/scripts/save_handoff.py +0 -357
  192. package/dist/templates/_shared/scripts/status_line.py +0 -701
  193. package/dist/templates/cc-native/.claude/commands/cc-native/fresh-perspective.md +0 -8
  194. package/dist/templates/cc-native/.windsurf/workflows/cc-native/fresh-perspective.md +0 -8
  195. package/dist/templates/cc-native/MIGRATION.md +0 -86
  196. package/dist/templates/cc-native/_cc-native/agents/ACCESSIBILITY-TESTER.md +0 -79
  197. package/dist/templates/cc-native/_cc-native/agents/ARCHITECT-REVIEWER.md +0 -48
  198. package/dist/templates/cc-native/_cc-native/agents/CODE-REVIEWER.md +0 -70
  199. package/dist/templates/cc-native/_cc-native/agents/COMPLETENESS-CHECKER.md +0 -59
  200. package/dist/templates/cc-native/_cc-native/agents/CONTEXT-EXTRACTOR.md +0 -92
  201. package/dist/templates/cc-native/_cc-native/agents/DOCUMENTATION-REVIEWER.md +0 -51
  202. package/dist/templates/cc-native/_cc-native/agents/FEASIBILITY-ANALYST.md +0 -57
  203. package/dist/templates/cc-native/_cc-native/agents/FRESH-PERSPECTIVE.md +0 -54
  204. package/dist/templates/cc-native/_cc-native/agents/INCENTIVE-MAPPER.md +0 -61
  205. package/dist/templates/cc-native/_cc-native/agents/PENETRATION-TESTER.md +0 -79
  206. package/dist/templates/cc-native/_cc-native/agents/PERFORMANCE-ENGINEER.md +0 -75
  207. package/dist/templates/cc-native/_cc-native/agents/PRECEDENT-FINDER.md +0 -70
  208. package/dist/templates/cc-native/_cc-native/agents/REVERSIBILITY-ANALYST.md +0 -61
  209. package/dist/templates/cc-native/_cc-native/agents/RISK-ASSESSOR.md +0 -58
  210. package/dist/templates/cc-native/_cc-native/agents/SECOND-ORDER-ANALYST.md +0 -61
  211. package/dist/templates/cc-native/_cc-native/agents/STAKEHOLDER-ADVOCATE.md +0 -55
  212. package/dist/templates/cc-native/_cc-native/agents/TRADE-OFF-ILLUMINATOR.md +0 -204
  213. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/add_plan_context.cpython-313.pyc +0 -0
  214. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/cc-native-plan-review.cpython-313.pyc +0 -0
  215. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/mark_questions_asked.cpython-313.pyc +0 -0
  216. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_accepted.cpython-313.pyc +0 -0
  217. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/plan_questions_early.cpython-313.pyc +0 -0
  218. package/dist/templates/cc-native/_cc-native/hooks/__pycache__/suggest-fresh-perspective.cpython-313.pyc +0 -0
  219. package/dist/templates/cc-native/_cc-native/hooks/add_plan_context.py +0 -130
  220. package/dist/templates/cc-native/_cc-native/hooks/cc-native-plan-review.py +0 -869
  221. package/dist/templates/cc-native/_cc-native/hooks/plan_questions_early.py +0 -81
  222. package/dist/templates/cc-native/_cc-native/hooks/suggest-fresh-perspective.py +0 -340
  223. package/dist/templates/cc-native/_cc-native/lib/CLAUDE.md +0 -265
  224. package/dist/templates/cc-native/_cc-native/lib/__init__.py +0 -53
  225. package/dist/templates/cc-native/_cc-native/lib/__pycache__/__init__.cpython-313.pyc +0 -0
  226. package/dist/templates/cc-native/_cc-native/lib/__pycache__/atomic_write.cpython-313.pyc +0 -0
  227. package/dist/templates/cc-native/_cc-native/lib/__pycache__/constants.cpython-313.pyc +0 -0
  228. package/dist/templates/cc-native/_cc-native/lib/__pycache__/debug.cpython-313.pyc +0 -0
  229. package/dist/templates/cc-native/_cc-native/lib/__pycache__/orchestrator.cpython-313.pyc +0 -0
  230. package/dist/templates/cc-native/_cc-native/lib/__pycache__/state.cpython-313.pyc +0 -0
  231. package/dist/templates/cc-native/_cc-native/lib/__pycache__/utils.cpython-313.pyc +0 -0
  232. package/dist/templates/cc-native/_cc-native/lib/constants.py +0 -45
  233. package/dist/templates/cc-native/_cc-native/lib/debug.py +0 -139
  234. package/dist/templates/cc-native/_cc-native/lib/orchestrator.py +0 -362
  235. package/dist/templates/cc-native/_cc-native/lib/reviewers/__init__.py +0 -28
  236. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/__init__.cpython-313.pyc +0 -0
  237. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/agent.cpython-313.pyc +0 -0
  238. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/base.cpython-313.pyc +0 -0
  239. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/codex.cpython-313.pyc +0 -0
  240. package/dist/templates/cc-native/_cc-native/lib/reviewers/__pycache__/gemini.cpython-313.pyc +0 -0
  241. package/dist/templates/cc-native/_cc-native/lib/reviewers/agent.py +0 -215
  242. package/dist/templates/cc-native/_cc-native/lib/reviewers/base.py +0 -88
  243. package/dist/templates/cc-native/_cc-native/lib/reviewers/codex.py +0 -124
  244. package/dist/templates/cc-native/_cc-native/lib/reviewers/gemini.py +0 -108
  245. package/dist/templates/cc-native/_cc-native/lib/state.py +0 -268
  246. package/dist/templates/cc-native/_cc-native/lib/utils.py +0 -1027
  247. package/dist/templates/cc-native/_cc-native/scripts/__pycache__/aggregate_agents.cpython-313.pyc +0 -0
  248. package/dist/templates/cc-native/_cc-native/scripts/aggregate_agents.py +0 -168
  249. package/dist/templates/cc-native/_cc-native/workflows/fresh-perspective.md +0 -134
@@ -0,0 +1,709 @@
1
+ /**
2
+ * Review artifact writing and formatting.
3
+ * See cc-native-plan-review-spec.md §4.3
4
+ */
5
+
6
+ import * as fs from "node:fs";
7
+ import * as path from "node:path";
8
+
9
+ import { ENABLE_ROBUST_PLAN_WRITES } from "./constants.js";
10
+ import type {
11
+ CombinedReviewResult,
12
+ DisplaySettings,
13
+ ReviewerResult,
14
+ } from "./types.js";
15
+ import { DEFAULT_DISPLAY } from "./types.js";
16
+ import { atomicWrite } from "../../_shared/lib-ts/base/atomic-write.js";
17
+ import { sanitizeFilename } from "../../_shared/lib-ts/base/constants.js";
18
+ import { logDebug, logError, logWarn } from "../../_shared/lib-ts/base/logger.js";
19
+ import { nowIso as _nowIso } from "../../_shared/lib-ts/base/utils.js";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Markdown Formatting
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /**
26
+ * Format review results as markdown (legacy compat format).
27
+ */
28
+ export function formatReviewMarkdown(
29
+ results: ReviewerResult[],
30
+ overall: string,
31
+ title = "CC-Native Plan Review",
32
+ settings?: Record<string, unknown>,
33
+ ): string {
34
+ const display = resolveDisplay(settings);
35
+
36
+ const lines: string[] = [];
37
+ lines.push(`# ${title}\n`);
38
+ lines.push(`**Overall verdict:** \`${overall.toUpperCase()}\`\n`);
39
+
40
+ for (const r of results) {
41
+ const displayName = r.name === r.name.toLowerCase() ? titleCase(r.name) : r.name;
42
+ lines.push(`## ${displayName}\n`, `- ok: \`${r.ok}\``, `- verdict: \`${r.verdict}\``);
43
+
44
+ if (r.data && Object.keys(r.data).length > 0) {
45
+ const summary = String(r.data.summary ?? "").trim();
46
+ if (r.data.summary_source === "default") {
47
+ lines.push(`- summary: ⚠️ ${summary} *(reviewer did not return summary)*`);
48
+ } else {
49
+ lines.push(`- summary: ${summary}`);
50
+ }
51
+
52
+ appendReviewDetails(lines, r.data, display);
53
+ } else {
54
+ lines.push(`- note: ${r.err || "no structured output"}`);
55
+ }
56
+
57
+ lines.push("");
58
+ }
59
+
60
+ return lines.join("\n").trim() + "\n";
61
+ }
62
+
63
+ /**
64
+ * Format combined review result as a single markdown document.
65
+ */
66
+ export function formatCombinedMarkdown(
67
+ result: CombinedReviewResult,
68
+ settings?: Record<string, unknown>,
69
+ ): string {
70
+ const display = resolveDisplay(settings);
71
+
72
+ const lines: string[] = [];
73
+ lines.push("# CC-Native Plan Review\n");
74
+ lines.push(`**Overall Verdict:** \`${result.overall_verdict.toUpperCase()}\``, `**Plan Hash:** \`${result.plan_hash}\`\n`, "---\n");
75
+
76
+ // CLI Reviewers section
77
+ if (Object.keys(result.cli_reviewers).length > 0) {
78
+ lines.push("## CLI Reviewers\n");
79
+ for (const [name, r] of Object.entries(result.cli_reviewers)) {
80
+ lines.push(`### ${titleCase(name)}\n`, `- verdict: \`${r.verdict}\``);
81
+ if (r.data && Object.keys(r.data).length > 0) {
82
+ appendSummaryLine(lines, r.data);
83
+ appendReviewDetails(lines, r.data, display);
84
+ } else if (r.err) {
85
+ lines.push(`- error: ${r.err}`);
86
+ }
87
+
88
+ lines.push("");
89
+ }
90
+ }
91
+
92
+ // Orchestration section
93
+ if (result.orchestration) {
94
+ lines.push("---\n", "## Orchestration\n", `- **Complexity:** \`${result.orchestration.complexity}\``, `- **Category:** \`${result.orchestration.category}\``);
95
+ const agentsStr =
96
+ result.orchestration.selected_agents.length > 0
97
+ ? result.orchestration.selected_agents.join(", ")
98
+ : "None";
99
+ lines.push(`- **Agents Selected:** ${agentsStr}`, `- **Reasoning:** ${result.orchestration.reasoning}`);
100
+ if (result.orchestration.skip_reason) {
101
+ lines.push(`- **Skip Reason:** ${result.orchestration.skip_reason}`);
102
+ }
103
+
104
+ if (result.orchestration.error) {
105
+ lines.push(`- **Error:** ${result.orchestration.error}`);
106
+ }
107
+
108
+ lines.push("");
109
+ }
110
+
111
+ // Agent Reviews section
112
+ if (Object.keys(result.agents).length > 0) {
113
+ lines.push("---\n", "## Agent Reviews\n");
114
+ for (const [name, r] of Object.entries(result.agents)) {
115
+ lines.push(`### ${name}\n`, `- verdict: \`${r.verdict}\``);
116
+ if (r.data && Object.keys(r.data).length > 0) {
117
+ appendSummaryLine(lines, r.data);
118
+ appendReviewDetails(lines, r.data, display);
119
+ } else if (r.err) {
120
+ lines.push(`- error: ${r.err}`);
121
+ }
122
+
123
+ lines.push("");
124
+ }
125
+ }
126
+
127
+ return lines.join("\n").trim() + "\n";
128
+ }
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // Inline Summaries
132
+ // ---------------------------------------------------------------------------
133
+
134
+ /**
135
+ * Build compact inline summary of HIGH-severity findings for additionalContext.
136
+ */
137
+ export function buildInlineReviewSummary(
138
+ combined: CombinedReviewResult,
139
+ maxIssues = 5,
140
+ maxChars = 800,
141
+ ): string {
142
+ const allReviewers = [
143
+ ...Object.values(combined.cli_reviewers),
144
+ ...Object.values(combined.agents),
145
+ ];
146
+
147
+ const highIssues: Array<Record<string, unknown>> = [];
148
+ for (const r of allReviewers) {
149
+ if (!r.data) continue;
150
+ const issues = r.data.issues as Array<Record<string, unknown>> | undefined;
151
+ if (!issues) continue;
152
+ for (const issue of issues) {
153
+ if (issue.severity === "high") {
154
+ highIssues.push({ ...issue, _reviewer: r.name });
155
+ }
156
+ }
157
+ }
158
+
159
+ const parts: string[] = [];
160
+
161
+ // Overall verdict line
162
+ const issueCount = highIssues.length;
163
+ const countSuffix =
164
+ issueCount > 0
165
+ ? ` (${issueCount} high-severity issue${issueCount === 1 ? "" : "s"})`
166
+ : "";
167
+ parts.push(`**Plan Review: ${combined.overall_verdict.toUpperCase()}**${countSuffix}`);
168
+
169
+ // High-severity issue bullets
170
+ for (const issue of highIssues.slice(0, maxIssues)) {
171
+ const cat = (issue.category as string) ?? "general";
172
+ const text = (issue.issue as string) ?? "";
173
+ const fix = (issue.suggested_fix as string) ?? "";
174
+ const reviewer = (issue._reviewer as string) ?? "unknown";
175
+ let line = `- [${cat}] ${text}`;
176
+ if (fix) line += ` \u2192 ${fix}`;
177
+ line += ` (${reviewer})`;
178
+ parts.push(line);
179
+ }
180
+
181
+ const remaining = highIssues.length - maxIssues;
182
+ if (remaining > 0) {
183
+ parts.push(` ...and ${remaining} more`);
184
+ }
185
+
186
+ let result = parts.join("\n");
187
+ if (result.length > maxChars) {
188
+ result = result.slice(0, maxChars - 3) + "...";
189
+ }
190
+
191
+ return result;
192
+ }
193
+
194
+ /**
195
+ * Extract top issues as compact text for permissionDecisionReason.
196
+ */
197
+ export function extractTopIssuesText(
198
+ combined: CombinedReviewResult,
199
+ maxCount = 3,
200
+ severity = "high",
201
+ ): string {
202
+ const allReviewers = [
203
+ ...Object.values(combined.cli_reviewers),
204
+ ...Object.values(combined.agents),
205
+ ];
206
+
207
+ const issues: string[] = [];
208
+ for (const r of allReviewers) {
209
+ if (!r.data) continue;
210
+ const issueList = r.data.issues as Array<Record<string, unknown>> | undefined;
211
+ if (!issueList) continue;
212
+ for (const issue of issueList) {
213
+ if (issue.severity === severity) {
214
+ const text = String(issue.issue ?? "").trim();
215
+ if (text) {
216
+ issues.push(`[${r.name}] ${text}`);
217
+ break; // first high issue per reviewer only
218
+ }
219
+ }
220
+ }
221
+
222
+ if (issues.length >= maxCount) break;
223
+ }
224
+
225
+ if (issues.length === 0) return "Review found critical issues";
226
+ return issues.join("; ");
227
+ }
228
+
229
+ /**
230
+ * Build markdown document containing ONLY high-severity issues.
231
+ */
232
+ export function buildHighIssuesDocument(
233
+ combined: CombinedReviewResult,
234
+ ): string {
235
+ const lines = ["# High-Severity Issues\n"];
236
+ const allReviewers = [
237
+ ...Object.values(combined.cli_reviewers),
238
+ ...Object.values(combined.agents),
239
+ ];
240
+
241
+ let foundAny = false;
242
+ for (const r of allReviewers) {
243
+ if (!r.data) continue;
244
+ const issues = r.data.issues as Array<Record<string, unknown>> | undefined;
245
+ if (!issues) continue;
246
+
247
+ const highIssues = issues.filter((i) => i.severity === "high");
248
+ if (highIssues.length === 0) continue;
249
+
250
+ foundAny = true;
251
+ lines.push(`## ${r.name} (${r.verdict})\n`);
252
+ for (const issue of highIssues) {
253
+ const cat = (issue.category as string) ?? "general";
254
+ const text = String(issue.issue ?? "").trim();
255
+ const fix = String(issue.suggested_fix ?? "").trim();
256
+ lines.push(`- **[${cat}]** ${text}`);
257
+ if (fix) lines.push(` - Fix: ${fix}`);
258
+ }
259
+
260
+ lines.push(""); // blank line between agents
261
+ }
262
+
263
+ if (!foundAny) {
264
+ lines.push("No high-severity issues found.\n");
265
+ }
266
+
267
+ return lines.join("\n");
268
+ }
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // Index Generation
272
+ // ---------------------------------------------------------------------------
273
+
274
+ /**
275
+ * Generate index.md for a review folder.
276
+ */
277
+ export function generateReviewIndex(
278
+ result: CombinedReviewResult,
279
+ iteration?: number,
280
+ _settings?: Record<string, unknown>,
281
+ ): string {
282
+ const now = new Date();
283
+
284
+ const lines = [
285
+ "---",
286
+ `type: review`,
287
+ `plan_hash: ${result.plan_hash}`,
288
+ `overall_verdict: ${result.overall_verdict}`,
289
+ `created_at: ${result.timestamp}`,
290
+ ];
291
+ if (iteration) lines.push(`iteration: ${iteration}`);
292
+ lines.push(
293
+ "---",
294
+ "",
295
+ `# Plan Review - ${formatDate(now)}`,
296
+ "",
297
+ `**Overall Verdict:** \`${result.overall_verdict.toUpperCase()}\``,
298
+ );
299
+
300
+ if (iteration) lines.push(`**Iteration:** ${iteration}`);
301
+ lines.push(`**Plan Hash:** \`${result.plan_hash}\``, "");
302
+
303
+ // Summary from orchestrator
304
+ if (result.orchestration) {
305
+ lines.push(
306
+ "## Analysis",
307
+ `- **Complexity:** \`${result.orchestration.complexity}\``,
308
+ `- **Category:** \`${result.orchestration.category}\``,
309
+ `- **Reasoning:** ${result.orchestration.reasoning}`,
310
+ "",
311
+ );
312
+ }
313
+
314
+ // Navigation table
315
+ lines.push(
316
+ "## Review Files",
317
+ "",
318
+ "| File | Description |",
319
+ "|------|-------------|",
320
+ "| [review.md](./review.md) | Full review details |",
321
+ "| [review.json](./review.json) | Structured review data |",
322
+ "| [plan.md](./plan.md) | Plan snapshot at review time |",
323
+ );
324
+
325
+ for (const name of Object.keys(result.cli_reviewers)) {
326
+ lines.push(
327
+ `| [${name}.json](./reviewer-output/${name}.json) | ${titleCase(name)} reviewer output |`,
328
+ );
329
+ }
330
+
331
+ for (const name of Object.keys(result.agents)) {
332
+ const safeName = sanitizeFilename(name);
333
+ lines.push(
334
+ `| [${safeName}.json](./reviewer-output/${safeName}.json) | ${name} agent output |`,
335
+ );
336
+ }
337
+
338
+ lines.push(
339
+ "",
340
+ "## Verdicts Summary",
341
+ "",
342
+ "| Reviewer | Verdict |",
343
+ "|----------|---------|",
344
+ );
345
+
346
+ for (const [name, r] of Object.entries(result.cli_reviewers)) {
347
+ lines.push(`| ${titleCase(name)} | \`${r.verdict}\` |`);
348
+ }
349
+
350
+ for (const [name, r] of Object.entries(result.agents)) {
351
+ lines.push(`| ${name} | \`${r.verdict}\` |`);
352
+ }
353
+
354
+ lines.push("");
355
+
356
+ return lines.join("\n");
357
+ }
358
+
359
+ // ---------------------------------------------------------------------------
360
+ // JSON Output
361
+ // ---------------------------------------------------------------------------
362
+
363
+ /**
364
+ * Build combined JSON output structure.
365
+ */
366
+ export function buildCombinedJson(
367
+ result: CombinedReviewResult,
368
+ ): Record<string, unknown> {
369
+ const output: Record<string, unknown> = {
370
+ metadata: {
371
+ timestamp: result.timestamp,
372
+ plan_hash: result.plan_hash,
373
+ },
374
+ overall: {
375
+ verdict: result.overall_verdict,
376
+ },
377
+ };
378
+
379
+ // CLI reviewers
380
+ if (Object.keys(result.cli_reviewers).length > 0) {
381
+ const cliReviewers: Record<string, unknown> = {};
382
+ output.cliReviewers = cliReviewers;
383
+ for (const [name, r] of Object.entries(result.cli_reviewers)) {
384
+ cliReviewers[name] = {
385
+ verdict: r.verdict,
386
+ summary: r.data?.summary ?? null,
387
+ summarySource: r.data?.summary_source ?? null,
388
+ issues: r.data
389
+ ? ((r.data.issues as Array<Record<string, unknown>>) ?? []).filter(
390
+ (i) => i.severity !== "low",
391
+ )
392
+ : [],
393
+ ok: r.ok,
394
+ error: r.err || null,
395
+ };
396
+ }
397
+ }
398
+
399
+ // Orchestration
400
+ if (result.orchestration) {
401
+ output.orchestration = {
402
+ complexity: result.orchestration.complexity,
403
+ category: result.orchestration.category,
404
+ selectedAgents: result.orchestration.selected_agents,
405
+ reasoning: result.orchestration.reasoning,
406
+ skipReason: result.orchestration.skip_reason ?? null,
407
+ error: result.orchestration.error ?? null,
408
+ };
409
+ }
410
+
411
+ // Agents
412
+ if (Object.keys(result.agents).length > 0) {
413
+ const agents: Record<string, unknown> = {};
414
+ output.agents = agents;
415
+ for (const [name, r] of Object.entries(result.agents)) {
416
+ agents[name] = {
417
+ verdict: r.verdict,
418
+ summary: r.data?.summary ?? null,
419
+ summarySource: r.data?.summary_source ?? null,
420
+ issues: r.data
421
+ ? ((r.data.issues as Array<Record<string, unknown>>) ?? []).filter(
422
+ (i) => i.severity !== "low",
423
+ )
424
+ : [],
425
+ missing_sections: r.data?.missing_sections ?? [],
426
+ questions: r.data?.questions ?? [],
427
+ ok: r.ok,
428
+ error: r.err || null,
429
+ };
430
+ }
431
+ }
432
+
433
+ return output;
434
+ }
435
+
436
+ // ---------------------------------------------------------------------------
437
+ // Artifact Writing
438
+ // ---------------------------------------------------------------------------
439
+
440
+ /**
441
+ * Write combined review artifacts to context reviews folder.
442
+ * Uses atomic writes for critical files when ENABLE_ROBUST_PLAN_WRITES is true.
443
+ */
444
+ export function writeCombinedArtifacts(
445
+ base: string,
446
+ plan: string,
447
+ result: CombinedReviewResult,
448
+ payload: Record<string, unknown>,
449
+ settings?: Record<string, unknown>,
450
+ contextReviewsDir?: string,
451
+ reviewFolder?: string,
452
+ iteration?: number,
453
+ ): string {
454
+ const outDir = reviewFolder ?? contextReviewsDir;
455
+ if (!outDir) {
456
+ throw new Error("Either contextReviewsDir or reviewFolder is required");
457
+ }
458
+
459
+ logDebug("utils", `Using review folder: ${outDir}`);
460
+
461
+ // Create directory
462
+ try {
463
+ fs.mkdirSync(outDir, { recursive: true });
464
+ } catch (error: unknown) {
465
+ logError("utils", `Cannot create directory ${outDir}: ${error}`);
466
+ throw error;
467
+ }
468
+
469
+ // JSON write
470
+ const jsonPath = path.join(outDir, "review.json");
471
+ const jsonData = buildCombinedJson(result);
472
+ writeFile(jsonPath, JSON.stringify(jsonData, null, 2));
473
+
474
+ // Markdown write
475
+ const mdPath = path.join(outDir, "review.md");
476
+ const mdContent = formatCombinedMarkdown(result, settings);
477
+ writeFile(mdPath, mdContent);
478
+
479
+ // Individual reviewer writes (non-critical) — in reviewer-output/ subfolder
480
+ const reviewerOutputDir = path.join(outDir, "reviewer-output");
481
+ try {
482
+ fs.mkdirSync(reviewerOutputDir, { recursive: true });
483
+ } catch {
484
+ // Best-effort — non-critical
485
+ }
486
+
487
+ for (const [name, r] of Object.entries(result.cli_reviewers)) {
488
+ if (r.data) {
489
+ writeFileNonCritical(
490
+ path.join(reviewerOutputDir, `${name}.json`),
491
+ JSON.stringify(r.data, null, 2),
492
+ );
493
+ }
494
+ }
495
+
496
+ for (const [name, r] of Object.entries(result.agents)) {
497
+ if (r.data) {
498
+ writeFileNonCritical(
499
+ path.join(reviewerOutputDir, `${sanitizeFilename(name)}.json`),
500
+ JSON.stringify(r.data, null, 2),
501
+ );
502
+ }
503
+ }
504
+
505
+ // Generate index.md for folder-based reviews
506
+ if (reviewFolder) {
507
+ const indexContent = generateReviewIndex(result, iteration, settings);
508
+ writeFileNonCritical(path.join(outDir, "index.md"), indexContent);
509
+ return path.join(outDir, "index.md");
510
+ }
511
+
512
+ return mdPath;
513
+ }
514
+
515
+ // ---------------------------------------------------------------------------
516
+ // Helpers
517
+ // ---------------------------------------------------------------------------
518
+
519
+ function resolveDisplay(
520
+ settings?: Record<string, unknown>,
521
+ ): DisplaySettings {
522
+ if (!settings) return { ...DEFAULT_DISPLAY };
523
+ const display = (settings.display as Partial<DisplaySettings>) ?? {};
524
+ return { ...DEFAULT_DISPLAY, ...display };
525
+ }
526
+
527
+ function appendSummaryLine(lines: string[], data: Record<string, unknown>): void {
528
+ const summary = String(data.summary ?? "").trim();
529
+ if (data.summary_source === "default") {
530
+ lines.push(`- summary: ⚠️ ${summary} *(reviewer did not return summary)*`);
531
+ } else {
532
+ lines.push(`- summary: ${summary}`);
533
+ }
534
+ }
535
+
536
+ function appendReviewDetails(
537
+ lines: string[],
538
+ data: Record<string, unknown>,
539
+ display: DisplaySettings,
540
+ ): void {
541
+ const issues = ((data.issues as Array<Record<string, unknown>>) ?? []).filter(
542
+ (i) => i.severity !== "low",
543
+ );
544
+ if (issues.length > 0) {
545
+ lines.push("\n**Issues:**");
546
+ for (const it of issues.slice(0, display.maxIssues)) {
547
+ const sev = (it.severity as string) ?? "medium";
548
+ const cat = (it.category as string) ?? "general";
549
+ const issue = (it.issue as string) ?? "";
550
+ const fix = (it.suggested_fix as string) ?? "";
551
+ lines.push(`- **[${sev}] ${cat}**: ${issue}`);
552
+ if (fix) lines.push(` - fix: ${fix}`);
553
+ }
554
+ }
555
+
556
+ const missing = (data.missing_sections as string[]) ?? [];
557
+ if (missing.length > 0) {
558
+ lines.push("\n**Missing Sections:**");
559
+ for (const m of missing.slice(0, display.maxMissingSections)) {
560
+ lines.push(`- ${m}`);
561
+ }
562
+ }
563
+
564
+ const qs = (data.questions as string[]) ?? [];
565
+ if (qs.length > 0) {
566
+ lines.push("\n**Questions:**");
567
+ for (const q of qs.slice(0, display.maxQuestions)) {
568
+ lines.push(`- ${q}`);
569
+ }
570
+ }
571
+ }
572
+
573
+ function writeFile(filePath: string, content: string): void {
574
+ try {
575
+ if (ENABLE_ROBUST_PLAN_WRITES) {
576
+ const [success, error] = atomicWrite(filePath, content);
577
+ if (!success) throw new Error(`Atomic write failed: ${error}`);
578
+ } else {
579
+ fs.writeFileSync(filePath, content, "utf-8");
580
+ }
581
+ } catch (error: unknown) {
582
+ logError("utils", `Failed to write ${path.basename(filePath)}: ${error}`);
583
+ throw error;
584
+ }
585
+ }
586
+
587
+ function writeFileNonCritical(filePath: string, content: string): void {
588
+ try {
589
+ if (ENABLE_ROBUST_PLAN_WRITES) {
590
+ const [success, error] = atomicWrite(filePath, content);
591
+ if (!success) {
592
+ logWarn("utils", `Failed to write ${path.basename(filePath)}: ${error}`);
593
+ }
594
+ } else {
595
+ fs.writeFileSync(filePath, content, "utf-8");
596
+ }
597
+ } catch (error: unknown) {
598
+ logWarn("utils", `Failed to write ${path.basename(filePath)}: ${error}`);
599
+ }
600
+ }
601
+
602
+ function titleCase(s: string): string {
603
+ return s.charAt(0).toUpperCase() + s.slice(1);
604
+ }
605
+
606
+ function formatDate(d: Date): string {
607
+ const year = d.getFullYear();
608
+ const month = String(d.getMonth() + 1).padStart(2, "0");
609
+ const day = String(d.getDate()).padStart(2, "0");
610
+ const hours = String(d.getHours()).padStart(2, "0");
611
+ const minutes = String(d.getMinutes()).padStart(2, "0");
612
+ return `${year}-${month}-${day} ${hours}:${minutes}`;
613
+ }
614
+
615
+ // ---------------------------------------------------------------------------
616
+ // Review Tracker
617
+ // ---------------------------------------------------------------------------
618
+
619
+ export interface ReviewTrackerEntry {
620
+ decision: string;
621
+ iteration: number;
622
+ planHash: string;
623
+ reviewFolder: string;
624
+ score: number;
625
+ timestamp: string;
626
+ topIssues: string[];
627
+ verdict: string;
628
+ }
629
+
630
+ /**
631
+ * Build or update the review-tracker.md in the cc-native reviews directory.
632
+ * This file provides a human-readable summary of all review iterations,
633
+ * making it easy to see whether feedback was acted on.
634
+ */
635
+ export function writeReviewTracker(
636
+ ccNativeReviewsDir: string,
637
+ entry: ReviewTrackerEntry,
638
+ ): void {
639
+ const trackerPath = path.join(ccNativeReviewsDir, "review-tracker.md");
640
+
641
+ // Read existing tracker entries if present
642
+ let existingContent = "";
643
+ try {
644
+ if (fs.existsSync(trackerPath)) {
645
+ existingContent = fs.readFileSync(trackerPath, "utf8");
646
+ }
647
+ } catch {
648
+ // Fresh start
649
+ }
650
+
651
+ // Parse existing entries to detect plan changes
652
+ const previousHashes = extractPreviousHashes(existingContent);
653
+ const hashChanged = previousHashes.length > 0 &&
654
+ previousHashes.at(-1) !== entry.planHash;
655
+
656
+ // Build the new entry section
657
+ const lines: string[] = [];
658
+ const verdictEmoji = entry.decision === "allow" ? "\u2705" : "\u274C";
659
+ const changeNote = previousHashes.length > 0
660
+ ? (hashChanged ? "\u2705 Plan was revised (hash changed)" : "\u26A0\uFE0F Plan unchanged since last review")
661
+ : "Initial review";
662
+
663
+ lines.push(`## Iteration ${entry.iteration} \u2014 ${entry.timestamp} \u2014 ${verdictEmoji} ${entry.verdict.toUpperCase()}`, "", `- **Decision:** ${entry.decision}`);
664
+ lines.push(`- **Score:** ${entry.score.toFixed(2)}`, `- **Plan hash:** \`${entry.planHash}\``, `- **Status:** ${changeNote}`);
665
+ lines.push(`- **Full review:** [\`${path.basename(entry.reviewFolder)}/\`](${path.basename(entry.reviewFolder)}/index.md)`);
666
+
667
+ if (entry.topIssues.length > 0) {
668
+ lines.push("", "**Top issues:**");
669
+ for (const issue of entry.topIssues) {
670
+ lines.push(`- ${issue}`);
671
+ }
672
+ }
673
+
674
+ lines.push("");
675
+
676
+ // Build full file
677
+ let output: string;
678
+ if (!existingContent || !existingContent.includes("# Plan Review Tracker")) {
679
+ // New file
680
+ output = [
681
+ "# Plan Review Tracker",
682
+ "",
683
+ "> Auto-generated by plan review hook. Shows review lifecycle across iterations.",
684
+ "> Check `plan.md` in each iteration folder to diff plan changes.",
685
+ "",
686
+ ...lines,
687
+ ].join("\n");
688
+ } else {
689
+ // Append to existing
690
+ output = existingContent.trimEnd() + "\n\n" + lines.join("\n");
691
+ }
692
+
693
+ try {
694
+ fs.writeFileSync(trackerPath, output, "utf-8");
695
+ } catch (error) {
696
+ logWarn("artifacts", `Failed to write review tracker: ${error}`);
697
+ }
698
+ }
699
+
700
+ function extractPreviousHashes(content: string): string[] {
701
+ const hashes: string[] = [];
702
+ const regex = /\*\*Plan hash:\*\* `([a-f0-9]+)`/g;
703
+ let match: null | RegExpExecArray;
704
+ while ((match = regex.exec(content)) !== null) {
705
+ hashes.push(match[1]!);
706
+ }
707
+
708
+ return hashes;
709
+ }