kenaz 0.0.0-stage → 0.3.2

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 (1641) hide show
  1. package/bin/kenaz.js +3 -0
  2. package/core/.agents/skills/remotion-best-practices/SKILL.md +61 -0
  3. package/core/.agents/skills/remotion-best-practices/rules/3d.md +86 -0
  4. package/core/.agents/skills/remotion-best-practices/rules/animations.md +27 -0
  5. package/core/.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  6. package/core/.agents/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  7. package/core/.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +103 -0
  8. package/core/.agents/skills/remotion-best-practices/rules/assets.md +78 -0
  9. package/core/.agents/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
  10. package/core/.agents/skills/remotion-best-practices/rules/audio.md +169 -0
  11. package/core/.agents/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
  12. package/core/.agents/skills/remotion-best-practices/rules/can-decode.md +81 -0
  13. package/core/.agents/skills/remotion-best-practices/rules/charts.md +120 -0
  14. package/core/.agents/skills/remotion-best-practices/rules/compositions.md +154 -0
  15. package/core/.agents/skills/remotion-best-practices/rules/display-captions.md +184 -0
  16. package/core/.agents/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  17. package/core/.agents/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
  18. package/core/.agents/skills/remotion-best-practices/rules/fonts.md +152 -0
  19. package/core/.agents/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  20. package/core/.agents/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  21. package/core/.agents/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
  22. package/core/.agents/skills/remotion-best-practices/rules/gifs.md +141 -0
  23. package/core/.agents/skills/remotion-best-practices/rules/images.md +134 -0
  24. package/core/.agents/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
  25. package/core/.agents/skills/remotion-best-practices/rules/light-leaks.md +73 -0
  26. package/core/.agents/skills/remotion-best-practices/rules/lottie.md +70 -0
  27. package/core/.agents/skills/remotion-best-practices/rules/maps.md +412 -0
  28. package/core/.agents/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
  29. package/core/.agents/skills/remotion-best-practices/rules/measuring-text.md +140 -0
  30. package/core/.agents/skills/remotion-best-practices/rules/parameters.md +109 -0
  31. package/core/.agents/skills/remotion-best-practices/rules/sequencing.md +118 -0
  32. package/core/.agents/skills/remotion-best-practices/rules/sfx.md +30 -0
  33. package/core/.agents/skills/remotion-best-practices/rules/subtitles.md +36 -0
  34. package/core/.agents/skills/remotion-best-practices/rules/tailwind.md +11 -0
  35. package/core/.agents/skills/remotion-best-practices/rules/text-animations.md +20 -0
  36. package/core/.agents/skills/remotion-best-practices/rules/timing.md +179 -0
  37. package/core/.agents/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
  38. package/core/.agents/skills/remotion-best-practices/rules/transitions.md +197 -0
  39. package/core/.agents/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
  40. package/core/.agents/skills/remotion-best-practices/rules/trimming.md +51 -0
  41. package/core/.agents/skills/remotion-best-practices/rules/videos.md +171 -0
  42. package/core/.agents/skills/remotion-best-practices/rules/voiceover.md +99 -0
  43. package/core/.agents/skills/supabase-postgres-best-practices/AGENTS.md +90 -0
  44. package/core/.agents/skills/supabase-postgres-best-practices/CLAUDE.md +1 -0
  45. package/core/.agents/skills/supabase-postgres-best-practices/SKILL.md +64 -0
  46. package/core/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
  47. package/core/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
  48. package/core/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
  49. package/core/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
  50. package/core/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
  51. package/core/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
  52. package/core/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
  53. package/core/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
  54. package/core/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
  55. package/core/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
  56. package/core/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
  57. package/core/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
  58. package/core/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
  59. package/core/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
  60. package/core/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
  61. package/core/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
  62. package/core/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
  63. package/core/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
  64. package/core/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
  65. package/core/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +45 -0
  66. package/core/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
  67. package/core/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
  68. package/core/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
  69. package/core/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
  70. package/core/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
  71. package/core/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
  72. package/core/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
  73. package/core/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
  74. package/core/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
  75. package/core/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
  76. package/core/.agents/skills/typesafe-ai/LICENSE +21 -0
  77. package/core/.agents/skills/typesafe-ai/SKILL.md +149 -0
  78. package/core/.agents/skills/vercel-react-best-practices/AGENTS.md +2934 -0
  79. package/core/.agents/skills/vercel-react-best-practices/SKILL.md +136 -0
  80. package/core/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  81. package/core/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
  82. package/core/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
  83. package/core/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  84. package/core/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  85. package/core/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
  86. package/core/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  87. package/core/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  88. package/core/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  89. package/core/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  90. package/core/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  91. package/core/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  92. package/core/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  93. package/core/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  94. package/core/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
  95. package/core/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  96. package/core/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  97. package/core/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
  98. package/core/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  99. package/core/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  100. package/core/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  101. package/core/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  102. package/core/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  103. package/core/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  104. package/core/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  105. package/core/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  106. package/core/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  107. package/core/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  108. package/core/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  109. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  110. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  111. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  112. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  113. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  114. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  115. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  116. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  117. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  118. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  119. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  120. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  121. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  122. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  123. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  124. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  125. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  126. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  127. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  128. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  129. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  130. package/core/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  131. package/core/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
  132. package/core/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  133. package/core/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
  134. package/core/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
  135. package/core/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
  136. package/core/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  137. package/core/.claude/agents/pcb-reviewer.md +146 -0
  138. package/core/.claude/commands/Amelia.md +5 -0
  139. package/core/.claude/commands/Ansuz.md +38 -0
  140. package/core/.claude/commands/Huginn.md +25 -0
  141. package/core/.claude/commands/agent-worker.md +168 -0
  142. package/core/.claude/commands/architect.md +24 -0
  143. package/core/.claude/commands/archive.md +24 -0
  144. package/core/.claude/commands/canary.md +17 -0
  145. package/core/.claude/commands/incident.md +15 -0
  146. package/core/.claude/commands/init.md +112 -0
  147. package/core/.claude/commands/legacy_dev.md +18 -0
  148. package/core/.claude/commands/product-review.md +131 -0
  149. package/core/.claude/commands/review.md +14 -0
  150. package/core/.claude/commands/security.md +15 -0
  151. package/core/.claude/commands/spec.md +22 -0
  152. package/core/.claude/commands/strategy.md +141 -0
  153. package/core/.claude/commands/sunny.md +1 -0
  154. package/core/.claude/commands//347/224/242/345/223/201.md +15 -0
  155. package/core/.claude/commands//347/224/242/345/223/201/346/252/242/350/246/226.md +129 -0
  156. package/core/.claude/commands//350/250/255/350/250/210.md +14 -0
  157. package/core/.claude/commands//350/251/225/344/274/260.md +16 -0
  158. package/core/.claude/commands//351/200/262/345/272/246.md +15 -0
  159. package/core/.claude/hooks/__tests__/substrate.test.js +122 -0
  160. package/core/.claude/hooks/__tests__/task-1866-cli-guard.test.js +193 -0
  161. package/core/.claude/hooks/__tests__/task-1977-dedupe-guard.test.js +125 -0
  162. package/core/.claude/hooks/__tests__/task-2087-mark-landed-idempotent.test.js +128 -0
  163. package/core/.claude/hooks/__tests__/task-2253-null-tip-marker-reuse.test.js +84 -0
  164. package/core/.claude/hooks/__tests__/task-2381-project-guard-walkup.test.js +53 -0
  165. package/core/.claude/hooks/_ansuz-conversation.js +183 -0
  166. package/core/.claude/hooks/_ansuz-conversation.test.js +90 -0
  167. package/core/.claude/hooks/_dedupe-guard.js +75 -0
  168. package/core/.claude/hooks/_peer-project-root.js +113 -0
  169. package/core/.claude/hooks/_peer-project-root.test.js +121 -0
  170. package/core/.claude/hooks/_peer-waiting-line.js +27 -0
  171. package/core/.claude/hooks/_project-guard.js +45 -0
  172. package/core/.claude/hooks/_run-user-prompt.js +134 -0
  173. package/core/.claude/hooks/_run-user-prompt.test.js +80 -0
  174. package/core/.claude/hooks/_session-identity.js +62 -0
  175. package/core/.claude/hooks/_session-pin.js +34 -0
  176. package/core/.claude/hooks/_session-pin.test.js +89 -0
  177. package/core/.claude/hooks/_signal-fence.js +17 -0
  178. package/core/.claude/hooks/_substrate-paths.js +95 -0
  179. package/core/.claude/hooks/_user-scope.js +23 -0
  180. package/core/.claude/hooks/ansuz-session-pin-via-turn-context.test.js +52 -0
  181. package/core/.claude/hooks/ansuz-session-pin.js +171 -0
  182. package/core/.claude/hooks/ansuz-session-pin.test.js +140 -0
  183. package/core/.claude/hooks/context-check.js +51 -0
  184. package/core/.claude/hooks/core-resolve.js +230 -0
  185. package/core/.claude/hooks/daemon-outbox-hook.js +139 -0
  186. package/core/.claude/hooks/fatigue-check.js +52 -0
  187. package/core/.claude/hooks/handoff-snapshot-on-commit.js +154 -0
  188. package/core/.claude/hooks/handoff-snapshot-on-decision.js +188 -0
  189. package/core/.claude/hooks/handoff-snapshot-on-question.js +158 -0
  190. package/core/.claude/hooks/handoff-staleness.js +197 -0
  191. package/core/.claude/hooks/jev-thalamus-context.js +17 -0
  192. package/core/.claude/hooks/jev-thalamus-context.test.js +41 -0
  193. package/core/.claude/hooks/lease-release.js +119 -0
  194. package/core/.claude/hooks/lease-touch.js +96 -0
  195. package/core/.claude/hooks/memory-guard.js +723 -0
  196. package/core/.claude/hooks/memory-guard.test.js +574 -0
  197. package/core/.claude/hooks/reflex-feedback.js +5 -0
  198. package/core/.claude/hooks/session-init.ps1 +49 -0
  199. package/core/.claude/hooks/sound-event.js +70 -0
  200. package/core/.claude/hooks/sound-event.sh +37 -0
  201. package/core/.claude/hooks/stream-delta.js +169 -0
  202. package/core/.claude/hooks/synapse-first-investigation.js +326 -0
  203. package/core/.claude/hooks/synapse-first-investigation.test.js +126 -0
  204. package/core/.claude/hooks/task-bridge.js +33 -0
  205. package/core/.claude/hooks/turn-context-peer.test.js +115 -0
  206. package/core/.claude/hooks/turn-context-session-line.test.js +69 -0
  207. package/core/.claude/hooks/turn-context.js +215 -0
  208. package/core/.claude/hooks/worktree-create.js +1232 -0
  209. package/core/.claude/hooks/worktree-remove.js +181 -0
  210. package/core/.claude/no-hooks.settings.json +3 -0
  211. package/core/.claude/settings.json +180 -0
  212. package/core/.claude/skills/adapt/SKILL.md +199 -0
  213. package/core/.claude/skills/animate/SKILL.md +190 -0
  214. package/core/.claude/skills/architect_agent/SKILL.md +408 -0
  215. package/core/.claude/skills/assessment_agent/SKILL.md +289 -0
  216. package/core/.claude/skills/audit/SKILL.md +129 -0
  217. package/core/.claude/skills/backend_agent/SKILL.md +407 -0
  218. package/core/.claude/skills/backend_engineer/SKILL.md +788 -0
  219. package/core/.claude/skills/bolder/SKILL.md +132 -0
  220. package/core/.claude/skills/clarify/SKILL.md +180 -0
  221. package/core/.claude/skills/code_reviewer/SKILL.md +305 -0
  222. package/core/.claude/skills/code_reviewer/patterns.yaml +85 -0
  223. package/core/.claude/skills/colorize/SKILL.md +158 -0
  224. package/core/.claude/skills/critique/SKILL.md +118 -0
  225. package/core/.claude/skills/delight/SKILL.md +317 -0
  226. package/core/.claude/skills/designer/SKILL.md +845 -0
  227. package/core/.claude/skills/dev_agent/SKILL.md +503 -0
  228. package/core/.claude/skills/developer/SKILL.md +792 -0
  229. package/core/.claude/skills/distill/SKILL.md +137 -0
  230. package/core/.claude/skills/extract/SKILL.md +95 -0
  231. package/core/.claude/skills/feature_agent/SKILL.md +303 -0
  232. package/core/.claude/skills/frontend-design/SKILL.md +127 -0
  233. package/core/.claude/skills/frontend-design/reference/color-and-contrast.md +132 -0
  234. package/core/.claude/skills/frontend-design/reference/interaction-design.md +123 -0
  235. package/core/.claude/skills/frontend-design/reference/motion-design.md +99 -0
  236. package/core/.claude/skills/frontend-design/reference/responsive-design.md +114 -0
  237. package/core/.claude/skills/frontend-design/reference/spatial-design.md +100 -0
  238. package/core/.claude/skills/frontend-design/reference/typography.md +131 -0
  239. package/core/.claude/skills/frontend-design/reference/ux-writing.md +107 -0
  240. package/core/.claude/skills/frontend_agent/SKILL.md +327 -0
  241. package/core/.claude/skills/harden/SKILL.md +358 -0
  242. package/core/.claude/skills/heimdall/SKILL.md +84 -0
  243. package/core/.claude/skills/huginn/SKILL.md +208 -0
  244. package/core/.claude/skills/incident_response/SKILL.md +315 -0
  245. package/core/.claude/skills/legacy_dev/SKILL.md +202 -0
  246. package/core/.claude/skills/normalize/SKILL.md +67 -0
  247. package/core/.claude/skills/onboard/SKILL.md +243 -0
  248. package/core/.claude/skills/optimization_agent/SKILL.md +340 -0
  249. package/core/.claude/skills/optimize/SKILL.md +269 -0
  250. package/core/.claude/skills/pcb-review/SKILL.md +39 -0
  251. package/core/.claude/skills/polish/SKILL.md +202 -0
  252. package/core/.claude/skills/product_manager/SKILL.md +1091 -0
  253. package/core/.claude/skills/qa_engineer/SKILL.md +1269 -0
  254. package/core/.claude/skills/quieter/SKILL.md +118 -0
  255. package/core/.claude/skills/remotion-best-practices/SKILL.md +61 -0
  256. package/core/.claude/skills/remotion-best-practices/rules/3d.md +86 -0
  257. package/core/.claude/skills/remotion-best-practices/rules/animations.md +27 -0
  258. package/core/.claude/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  259. package/core/.claude/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  260. package/core/.claude/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +103 -0
  261. package/core/.claude/skills/remotion-best-practices/rules/assets.md +78 -0
  262. package/core/.claude/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
  263. package/core/.claude/skills/remotion-best-practices/rules/audio.md +169 -0
  264. package/core/.claude/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
  265. package/core/.claude/skills/remotion-best-practices/rules/can-decode.md +81 -0
  266. package/core/.claude/skills/remotion-best-practices/rules/charts.md +120 -0
  267. package/core/.claude/skills/remotion-best-practices/rules/compositions.md +154 -0
  268. package/core/.claude/skills/remotion-best-practices/rules/display-captions.md +184 -0
  269. package/core/.claude/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  270. package/core/.claude/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
  271. package/core/.claude/skills/remotion-best-practices/rules/fonts.md +152 -0
  272. package/core/.claude/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  273. package/core/.claude/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  274. package/core/.claude/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
  275. package/core/.claude/skills/remotion-best-practices/rules/gifs.md +141 -0
  276. package/core/.claude/skills/remotion-best-practices/rules/images.md +134 -0
  277. package/core/.claude/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
  278. package/core/.claude/skills/remotion-best-practices/rules/light-leaks.md +73 -0
  279. package/core/.claude/skills/remotion-best-practices/rules/lottie.md +70 -0
  280. package/core/.claude/skills/remotion-best-practices/rules/maps.md +412 -0
  281. package/core/.claude/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
  282. package/core/.claude/skills/remotion-best-practices/rules/measuring-text.md +140 -0
  283. package/core/.claude/skills/remotion-best-practices/rules/parameters.md +109 -0
  284. package/core/.claude/skills/remotion-best-practices/rules/sequencing.md +118 -0
  285. package/core/.claude/skills/remotion-best-practices/rules/sfx.md +30 -0
  286. package/core/.claude/skills/remotion-best-practices/rules/subtitles.md +36 -0
  287. package/core/.claude/skills/remotion-best-practices/rules/tailwind.md +11 -0
  288. package/core/.claude/skills/remotion-best-practices/rules/text-animations.md +20 -0
  289. package/core/.claude/skills/remotion-best-practices/rules/timing.md +179 -0
  290. package/core/.claude/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
  291. package/core/.claude/skills/remotion-best-practices/rules/transitions.md +197 -0
  292. package/core/.claude/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
  293. package/core/.claude/skills/remotion-best-practices/rules/trimming.md +51 -0
  294. package/core/.claude/skills/remotion-best-practices/rules/videos.md +171 -0
  295. package/core/.claude/skills/remotion-best-practices/rules/voiceover.md +99 -0
  296. package/core/.claude/skills/security_agent/SKILL.md +312 -0
  297. package/core/.claude/skills/spec_optimizer/SKILL.md +299 -0
  298. package/core/.claude/skills/sre_agent/SKILL.md +1347 -0
  299. package/core/.claude/skills/sunny/SKILL.md +74 -0
  300. package/core/.claude/skills/supabase-postgres-best-practices/AGENTS.md +90 -0
  301. package/core/.claude/skills/supabase-postgres-best-practices/CLAUDE.md +1 -0
  302. package/core/.claude/skills/supabase-postgres-best-practices/SKILL.md +64 -0
  303. package/core/.claude/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
  304. package/core/.claude/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
  305. package/core/.claude/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
  306. package/core/.claude/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
  307. package/core/.claude/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
  308. package/core/.claude/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
  309. package/core/.claude/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
  310. package/core/.claude/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
  311. package/core/.claude/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
  312. package/core/.claude/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
  313. package/core/.claude/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
  314. package/core/.claude/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
  315. package/core/.claude/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
  316. package/core/.claude/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
  317. package/core/.claude/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
  318. package/core/.claude/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
  319. package/core/.claude/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
  320. package/core/.claude/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
  321. package/core/.claude/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
  322. package/core/.claude/skills/supabase-postgres-best-practices/references/query-index-types.md +45 -0
  323. package/core/.claude/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
  324. package/core/.claude/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
  325. package/core/.claude/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
  326. package/core/.claude/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
  327. package/core/.claude/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
  328. package/core/.claude/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
  329. package/core/.claude/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
  330. package/core/.claude/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
  331. package/core/.claude/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
  332. package/core/.claude/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
  333. package/core/.claude/skills/synapse_maintain/SKILL.md +84 -0
  334. package/core/.claude/skills/ui-ux-pro-max/SKILL.md +377 -0
  335. package/core/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  336. package/core/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
  337. package/core/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
  338. package/core/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
  339. package/core/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
  340. package/core/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  341. package/core/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  342. package/core/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  343. package/core/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  344. package/core/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  345. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  346. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  347. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  348. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  349. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  350. package/core/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  351. package/core/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  352. package/core/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  353. package/core/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  354. package/core/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
  355. package/core/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
  356. package/core/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  357. package/core/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  358. package/core/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  359. package/core/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
  360. package/core/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  361. package/core/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -0
  362. package/core/.claude/skills/vercel-react-best-practices/AGENTS.md +2934 -0
  363. package/core/.claude/skills/vercel-react-best-practices/SKILL.md +136 -0
  364. package/core/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  365. package/core/.claude/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
  366. package/core/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
  367. package/core/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  368. package/core/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  369. package/core/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
  370. package/core/.claude/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  371. package/core/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  372. package/core/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  373. package/core/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  374. package/core/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  375. package/core/.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  376. package/core/.claude/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  377. package/core/.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  378. package/core/.claude/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
  379. package/core/.claude/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  380. package/core/.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  381. package/core/.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
  382. package/core/.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  383. package/core/.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  384. package/core/.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  385. package/core/.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  386. package/core/.claude/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  387. package/core/.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  388. package/core/.claude/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  389. package/core/.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  390. package/core/.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  391. package/core/.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  392. package/core/.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  393. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  394. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  395. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  396. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  397. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  398. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  399. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  400. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  401. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  402. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  403. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  404. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  405. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  406. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  407. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  408. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  409. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  410. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  411. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  412. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  413. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  414. package/core/.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  415. package/core/.claude/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
  416. package/core/.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  417. package/core/.claude/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
  418. package/core/.claude/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
  419. package/core/.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
  420. package/core/.claude/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  421. package/core/.collaboration/README.md +258 -0
  422. package/core/.collaboration/cli/commands/assign.js +39 -0
  423. package/core/.collaboration/cli/commands/claim.js +33 -0
  424. package/core/.collaboration/cli/commands/complete.js +44 -0
  425. package/core/.collaboration/cli/commands/create.js +110 -0
  426. package/core/.collaboration/cli/commands/get.js +83 -0
  427. package/core/.collaboration/cli/commands/index.js +52 -0
  428. package/core/.collaboration/cli/commands/list.js +51 -0
  429. package/core/.collaboration/cli/commands/lock.js +45 -0
  430. package/core/.collaboration/cli/commands/rebuild.js +39 -0
  431. package/core/.collaboration/cli/commands/search.js +61 -0
  432. package/core/.collaboration/cli/commands/stats.js +61 -0
  433. package/core/.collaboration/cli/commands/transfer.js +41 -0
  434. package/core/.collaboration/cli/commands/unlock.js +48 -0
  435. package/core/.collaboration/cli/formatters/help-formatter.js +70 -0
  436. package/core/.collaboration/cli/formatters/index.js +7 -0
  437. package/core/.collaboration/cli/task-cli.js +46 -0
  438. package/core/.collaboration/cli/utils/arg-parser.js +74 -0
  439. package/core/.collaboration/cli/utils/git-user.js +25 -0
  440. package/core/.collaboration/cli/utils/index.js +9 -0
  441. package/core/.collaboration/cli/utils/output.js +10 -0
  442. package/core/.collaboration/config/default.yaml +189 -0
  443. package/core/.collaboration/core/conflict-resolver.js +461 -0
  444. package/core/.collaboration/core/conflict-resolver.test.js +389 -0
  445. package/core/.collaboration/core/id-generator.js +303 -0
  446. package/core/.collaboration/core/migrate-legacy.js +260 -0
  447. package/core/.collaboration/core/package.json +1 -0
  448. package/core/.collaboration/core/task-2257-estimated-hours.test.js +72 -0
  449. package/core/.collaboration/core/task-2260-dir-layout-move.test.js +60 -0
  450. package/core/.collaboration/core/task-2264-single-after-capture.test.js +61 -0
  451. package/core/.collaboration/core/task-2288-followups.test.js +121 -0
  452. package/core/.collaboration/core/task-api-json.js +211 -0
  453. package/core/.collaboration/core/task-api.js +3729 -0
  454. package/core/.collaboration/core/task-api.test.js +1268 -0
  455. package/core/.collaboration/docs/API.md +787 -0
  456. package/core/.collaboration/docs/ARCHITECTURE.md +596 -0
  457. package/core/.collaboration/docs/CLI.md +821 -0
  458. package/core/.collaboration/docs/FAQ.md +695 -0
  459. package/core/.collaboration/docs/MIGRATION.md +804 -0
  460. package/core/.collaboration/docs/examples/README.md +320 -0
  461. package/core/.collaboration/docs/examples/advanced-queries.js +261 -0
  462. package/core/.collaboration/docs/examples/basic-usage.js +184 -0
  463. package/core/.collaboration/docs/examples/team-workflow.js +352 -0
  464. package/core/.collaboration/hooks/README.md +70 -0
  465. package/core/.collaboration/hooks/config.json +10 -0
  466. package/core/.collaboration/hooks/install.js +400 -0
  467. package/core/.collaboration/hooks/post-merge.js +163 -0
  468. package/core/.collaboration/hooks/pre-commit.js +264 -0
  469. package/core/.collaboration/hooks/synapse-hook.js +486 -0
  470. package/core/.collaboration/hooks/synapse-hook.test.js +69 -0
  471. package/core/.failure/INDEX.yaml +231 -0
  472. package/core/.failure/agent-execution/FAIL-001-agent-no-output.yaml +391 -0
  473. package/core/.failure/agent-execution/FAIL-004_agent_platform_specific_commands.md +194 -0
  474. package/core/.failure/agent-execution/FAIL-005_background_agent_permission_denied.md +204 -0
  475. package/core/.failure/agent-execution/FAIL-007-pm-lead-wrong-team-invocation.yaml +0 -0
  476. package/core/.failure/agent-execution/FAIL-008-bypass-api-direct-yaml-write.yaml +94 -0
  477. package/core/.failure/agent-execution/FAIL-010-phase0-poem-format-skipped.yaml +28 -0
  478. package/core/.failure/agent-execution/FAIL-010-skipping-cognitive-checkpoint-after-verification.yaml +59 -0
  479. package/core/.failure/agent-execution/FAIL-012-grep-shortcut-during-investigation.yaml +85 -0
  480. package/core/.failure/agent-execution/FAIL-013-search-cwd-drift-and-case-mismatch.yaml +103 -0
  481. package/core/.failure/agent-execution/FAIL-014-shared-worktree-premature-death-verdict.yaml +35 -0
  482. package/core/.failure/agent-execution/FAIL-AUTO-TASK_1413.yaml +31 -0
  483. package/core/.failure/agent-execution/FAIL-AUTO-TASK_1427.yaml +41 -0
  484. package/core/.failure/agent-execution/FAIL-AUTO-TASK_904.yaml +30 -0
  485. package/core/.failure/agent-execution/README.md +68 -0
  486. package/core/.failure/architecture/FAIL-004_session_based_skill_recognition.md +128 -0
  487. package/core/.failure/architecture/FAIL-006-python-version-compatibility.yaml +445 -0
  488. package/core/.failure/architecture/README.md +87 -0
  489. package/core/.failure/code-quality/README.md +89 -0
  490. package/core/.failure/data-integrity/FAIL-002-json-bom.yaml +220 -0
  491. package/core/.failure/data-integrity/README.md +141 -0
  492. package/core/.failure/pipeline/FAIL-003-queue-pollution.yaml +335 -0
  493. package/core/.failure/pipeline/README.md +95 -0
  494. package/core/.failure/retired/FAIL-009-skipping-synapse-pulse-before-code-change.yaml +49 -0
  495. package/core/.failure/tools/README.md +230 -0
  496. package/core/.failure/tools/USAGE_GUIDE.md +323 -0
  497. package/core/.failure/tools/check_bom.js +101 -0
  498. package/core/.failure/tools/check_failures.ps1 +387 -0
  499. package/core/.failure/tools/fix_json.js +58 -0
  500. package/core/.failure/tools/log_failure.ps1 +474 -0
  501. package/core/.failure/tools/remove_bom.js +46 -0
  502. package/core/.failure/tools/validate_json.js +50 -0
  503. package/core/.formation/.episode-input.json +10 -0
  504. package/core/.formation/README.md +188 -0
  505. package/core/.formation/bifrost/CGDD_hook_lifecycle.js +575 -0
  506. package/core/.formation/bifrost/HOOK_PARITY_VERIFICATION.md +241 -0
  507. package/core/.formation/bifrost/README.md +71 -0
  508. package/core/.formation/bifrost/codex-doctor.js +106 -0
  509. package/core/.formation/bifrost/codex-doctor.test.js +51 -0
  510. package/core/.formation/bifrost/launch.js +510 -0
  511. package/core/.formation/bifrost/launch.test.js +217 -0
  512. package/core/.formation/bifrost/openai/codex-hook-shim.js +171 -0
  513. package/core/.formation/bifrost/openai/codex-hook-shim.test.js +107 -0
  514. package/core/.formation/bifrost/openai/models.json +36 -0
  515. package/core/.formation/bifrost/openai/relay.js +781 -0
  516. package/core/.formation/bifrost/openai/relay.test.js +71 -0
  517. package/core/.formation/bifrost/session.js +382 -0
  518. package/core/.formation/bifrost/session.test.js +121 -0
  519. package/core/.formation/bifrost/setup-codex.js +685 -0
  520. package/core/.formation/bifrost/setup-codex.test.js +102 -0
  521. package/core/.formation/bifrost/shared/ansuz-boot-policy.js +31 -0
  522. package/core/.formation/bifrost/shared/ansuz-boot-policy.test.js +40 -0
  523. package/core/.formation/bifrost/shared/boot-context.js +68 -0
  524. package/core/.formation/bifrost/shared/boot-context.test.js +67 -0
  525. package/core/.formation/bifrost/shared/codex-appserver.js +191 -0
  526. package/core/.formation/bifrost/shared/codex-injection.js +126 -0
  527. package/core/.formation/bifrost/shared/codex-session-registry.js +105 -0
  528. package/core/.formation/bifrost/shared/codex-session-registry.test.js +126 -0
  529. package/core/.formation/bifrost/shared/consolidate.js +286 -0
  530. package/core/.formation/bifrost/shared/stream-json.js +103 -0
  531. package/core/.formation/bifrost/shared/tools.js +278 -0
  532. package/core/.formation/bifrost/sync.js +142 -0
  533. package/core/.formation/briefs/scheduler-evolution.md +209 -0
  534. package/core/.formation/lead.yaml +118 -0
  535. package/core/.formation/legacy.bak/designer.yaml +57 -0
  536. package/core/.formation/legacy.bak/pm.yaml +57 -0
  537. package/core/.formation/legacy.bak/qa.yaml +57 -0
  538. package/core/.formation/legacy.bak/review.yaml +62 -0
  539. package/core/.formation/legacy.bak/scheduler.yaml +129 -0
  540. package/core/.formation/legacy.bak/sre.yaml +57 -0
  541. package/core/.formation/legion/forge.yaml +299 -0
  542. package/core/.formation/legion/hive.yaml +238 -0
  543. package/core/.formation/native/claude/README.md +61 -0
  544. package/core/.formation/native/claude/models.json +42 -0
  545. package/core/.formation/role-map.yaml +274 -0
  546. package/core/.formation/roles/_shared/modules/code_review.yaml +98 -0
  547. package/core/.formation/roles/_shared/modules/security.yaml +103 -0
  548. package/core/.formation/roles/_shared/modules/testing.yaml +14 -0
  549. package/core/.formation/roles/backend/role.yaml +93 -0
  550. package/core/.formation/roles/code_reviewer/role.yaml +102 -0
  551. package/core/.formation/roles/database_optimizer/role.yaml +107 -0
  552. package/core/.formation/roles/designer/role.yaml +66 -0
  553. package/core/.formation/roles/developer/role.yaml +75 -0
  554. package/core/.formation/roles/frontend/role.yaml +134 -0
  555. package/core/.formation/roles/incident_response/role.yaml +89 -0
  556. package/core/.formation/roles/qa/role.yaml +89 -0
  557. package/core/.formation/roles/security/role.yaml +87 -0
  558. package/core/.formation/roles/sre/role.yaml +66 -0
  559. package/core/.formation/skills/coordinator.yaml +52 -0
  560. package/core/.formation/solo/diagnosis.yaml +78 -0
  561. package/core/.formation/solo/strategy.yaml +121 -0
  562. package/core/.formation/spec/task_operations.parity.test.js +426 -0
  563. package/core/.formation/spec/task_operations.yaml +291 -0
  564. package/core/.formation/squad/blitz.yaml +192 -0
  565. package/core/.formation/squad/strategy-tribunal.yaml +354 -0
  566. package/core/.formation/squad/strategy.yaml +349 -0
  567. package/core/.formation/squad/sweep.yaml +208 -0
  568. package/core/.formation/tests/hello.txt +2 -0
  569. package/core/.formation/tools/__tests__/m1-claim-gate.test.js +175 -0
  570. package/core/.formation/tools/__tests__/m1-hmap-convid.test.js +216 -0
  571. package/core/.formation/tools/__tests__/release-lock.test.js +176 -0
  572. package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +234 -0
  573. package/core/.formation/tools/__tests__/subagent-codex-harness.test.js +96 -0
  574. package/core/.formation/tools/__tests__/task-1863-claim-late-release.test.js +160 -0
  575. package/core/.formation/tools/__tests__/task-2265-singleton-identity.test.js +134 -0
  576. package/core/.formation/tools/__tests__/task-2281-singleton-readers.test.js +64 -0
  577. package/core/.formation/tools/__tests__/task2030-worktree-node-modules.test.js +221 -0
  578. package/core/.formation/tools/__tests__/task2067-worktree-branch-reuse.test.js +148 -0
  579. package/core/.formation/tools/alaya-gate.js +198 -0
  580. package/core/.formation/tools/analytics.js +172 -0
  581. package/core/.formation/tools/broadcast.js +103 -0
  582. package/core/.formation/tools/commit-sync.js +154 -0
  583. package/core/.formation/tools/coordinator.js +350 -0
  584. package/core/.formation/tools/dashboard-models-chain.test.js +60 -0
  585. package/core/.formation/tools/dispatch-cortex-context.test.js +52 -0
  586. package/core/.formation/tools/dispatch-detail/cross-project-worktree.md +40 -0
  587. package/core/.formation/tools/dispatch-detail/testing-module.md +53 -0
  588. package/core/.formation/tools/dispatch-detail/worktree-subagent-mode.md +72 -0
  589. package/core/.formation/tools/dispatch-dryrun.test.js +54 -0
  590. package/core/.formation/tools/dispatch-model-policy.test.js +165 -0
  591. package/core/.formation/tools/dispatch-notifier.js +84 -0
  592. package/core/.formation/tools/dispatch-registry.js +346 -0
  593. package/core/.formation/tools/dispatch.js +1931 -0
  594. package/core/.formation/tools/formations.js +1203 -0
  595. package/core/.formation/tools/forseti-verify.js +1170 -0
  596. package/core/.formation/tools/git-salvage.js +269 -0
  597. package/core/.formation/tools/guard-attribution.js +224 -0
  598. package/core/.formation/tools/headless-branch-recovery.test.js +64 -0
  599. package/core/.formation/tools/headless-dryrun.test.js +79 -0
  600. package/core/.formation/tools/headless-v2.js +659 -0
  601. package/core/.formation/tools/headless.js +1071 -0
  602. package/core/.formation/tools/integration.js +528 -0
  603. package/core/.formation/tools/jev-correction.js +214 -0
  604. package/core/.formation/tools/jev-correction.test.js +178 -0
  605. package/core/.formation/tools/legacy-scheduler.js +1994 -0
  606. package/core/.formation/tools/lib/exec-tree.js +92 -0
  607. package/core/.formation/tools/lib/exec-tree.test.js +82 -0
  608. package/core/.formation/tools/loader.js +190 -0
  609. package/core/.formation/tools/log-reader.js +232 -0
  610. package/core/.formation/tools/logger.js +273 -0
  611. package/core/.formation/tools/mcp-allowlist.test.js +40 -0
  612. package/core/.formation/tools/memory-loader.js +422 -0
  613. package/core/.formation/tools/merge-lock-heartbeat.test.js +35 -0
  614. package/core/.formation/tools/no-hooks.settings.json +3 -0
  615. package/core/.formation/tools/pool-status-push.js +121 -0
  616. package/core/.formation/tools/pool-status-push.test.js +165 -0
  617. package/core/.formation/tools/post-complete/bounded-http.js +102 -0
  618. package/core/.formation/tools/post-complete/bounded-http.test.js +130 -0
  619. package/core/.formation/tools/post-complete/cognitive.js +270 -0
  620. package/core/.formation/tools/post-complete/git-merge.js +2845 -0
  621. package/core/.formation/tools/post-complete/health.js +464 -0
  622. package/core/.formation/tools/post-complete/integration-gaps.js +98 -0
  623. package/core/.formation/tools/post-complete/outcome.js +874 -0
  624. package/core/.formation/tools/post-complete/staging.js +390 -0
  625. package/core/.formation/tools/post-complete/subagent-transcript-stats.js +460 -0
  626. package/core/.formation/tools/post-complete/subagent-transcript-stats.test.js +680 -0
  627. package/core/.formation/tools/post-complete/task1536.test.js +627 -0
  628. package/core/.formation/tools/post-complete/task1543.test.js +261 -0
  629. package/core/.formation/tools/post-complete/task1876.test.js +287 -0
  630. package/core/.formation/tools/post-complete/task1957.test.js +293 -0
  631. package/core/.formation/tools/post-complete/task1997.test.js +401 -0
  632. package/core/.formation/tools/post-complete/task2028.test.js +232 -0
  633. package/core/.formation/tools/post-complete/task2029.test.js +275 -0
  634. package/core/.formation/tools/post-complete/task2036.test.js +330 -0
  635. package/core/.formation/tools/post-complete/task2038.test.js +246 -0
  636. package/core/.formation/tools/post-complete/task2071.test.js +231 -0
  637. package/core/.formation/tools/post-complete/task2233.test.js +100 -0
  638. package/core/.formation/tools/post-complete/task2240.test.js +134 -0
  639. package/core/.formation/tools/post-complete/task2244.test.js +199 -0
  640. package/core/.formation/tools/post-complete/task2255.test.js +28 -0
  641. package/core/.formation/tools/post-complete/task2268.test.js +106 -0
  642. package/core/.formation/tools/post-complete/task2341.test.js +74 -0
  643. package/core/.formation/tools/post-complete/task2345.test.js +38 -0
  644. package/core/.formation/tools/post-complete/testing.js +482 -0
  645. package/core/.formation/tools/post-complete/wish.js +177 -0
  646. package/core/.formation/tools/post-complete/worktree-rescue.test.js +283 -0
  647. package/core/.formation/tools/post-complete.js +3660 -0
  648. package/core/.formation/tools/post-complete.test.js +1002 -0
  649. package/core/.formation/tools/pre-dispatch-snapshot.test.js +328 -0
  650. package/core/.formation/tools/process-state.js +33 -0
  651. package/core/.formation/tools/prompt.js +2183 -0
  652. package/core/.formation/tools/prompt.test.js +875 -0
  653. package/core/.formation/tools/remote-conflict.js +98 -0
  654. package/core/.formation/tools/revert-daemon-state.js +162 -0
  655. package/core/.formation/tools/route-audit.test.js +55 -0
  656. package/core/.formation/tools/runner.js +1332 -0
  657. package/core/.formation/tools/supabase-client.js +104 -0
  658. package/core/.formation/tools/task-sync.js +236 -0
  659. package/core/.formation/tools/task-utils.js +344 -0
  660. package/core/.formation/tools/task2028-headless-worktree.test.js +112 -0
  661. package/core/.formation/tools/test-drift.js +148 -0
  662. package/core/.formation/tools/tests/headless-mcp-args-plumbing.test.js +123 -0
  663. package/core/.formation/tools/tests/relay-mcp-overrides.test.js +112 -0
  664. package/core/.formation/tools/transcript-recorder.js +574 -0
  665. package/core/.formation/tools/utils-claude-cmd.test.js +28 -0
  666. package/core/.formation/tools/utils-worktree-config.test.js +44 -0
  667. package/core/.formation/tools/utils.js +1020 -0
  668. package/core/.formation/tools/verify-acceptance.js +585 -0
  669. package/core/.formation/tools/worktree-leak.test.js +162 -0
  670. package/core/.formation/tools/worktree-reclaim.js +259 -0
  671. package/core/.formation/tools/worktree-reclaim.test.js +182 -0
  672. package/core/.formation/tools/yggdrasil-local.js +97 -0
  673. package/core/.formation/unit/do.yaml +78 -0
  674. package/core/.formation/unit/heimdall.yaml +96 -0
  675. package/core/.formation/unit/plan.yaml +67 -0
  676. package/core/.formation/unit/think.yaml +58 -0
  677. package/core/.kenaz/MUNINN_SNAPSHOT9_EPISODE.json +32 -0
  678. package/core/.kenaz/MUNINN_SNAPSHOT9_RUNE.json +23 -0
  679. package/core/.kenaz/MUNINN_SNAPSHOT9_SUMMARY.md +89 -0
  680. package/core/.kenaz/ash_TASK_1428-20260507.json +14 -0
  681. package/core/.kenaz/cache/native-models/claude.json +42 -0
  682. package/core/.kenaz/claude-hooks-manifest.json +119 -0
  683. package/core/.kenaz/cortex.yaml +100 -0
  684. package/core/.kenaz/curator_state.json +7 -0
  685. package/core/.kenaz/dashboard-quick-prompts.json +9 -0
  686. package/core/.kenaz/designs/METAPLASTICITY_CONFIDENCE_DESIGN.md +537 -0
  687. package/core/.kenaz/designs/REFLEX_INITIATOR_DESIGN.md +501 -0
  688. package/core/.kenaz/designs/TASK_1385_handoff_mcp_design.md +426 -0
  689. package/core/.kenaz/designs/TASK_932_hippocampus_consolidation.md +103 -0
  690. package/core/.kenaz/designs/TASK_934_efe_signal_engine.md +97 -0
  691. package/core/.kenaz/designs/TASK_935_self_evolution_framework.md +154 -0
  692. package/core/.kenaz/designs/VISION_cognitive_architecture_roadmap.md +159 -0
  693. package/core/.kenaz/designs/metaplasticity_simulation_results.json +696 -0
  694. package/core/.kenaz/docs/INDEX.md +214 -0
  695. package/core/.kenaz/docs/README.md +290 -0
  696. package/core/.kenaz/docs/registry.json +962 -0
  697. package/core/.kenaz/docs/schema.json +144 -0
  698. package/core/.kenaz/docs/tools/GIT_HOOK_GUIDE.md +203 -0
  699. package/core/.kenaz/docs/tools/RAG_INTEGRATION_GUIDE.md +306 -0
  700. package/core/.kenaz/docs/tools/README.md +99 -0
  701. package/core/.kenaz/docs/tools/TIME_ESTIMATION_GUIDE.md +299 -0
  702. package/core/.kenaz/docs/tools/package-lock.json +618 -0
  703. package/core/.kenaz/docs/tools/package.json +34 -0
  704. package/core/.kenaz/docs/tools/src/cli.ts +365 -0
  705. package/core/.kenaz/docs/tools/src/field-extractor.ts +152 -0
  706. package/core/.kenaz/docs/tools/src/index.ts +14 -0
  707. package/core/.kenaz/docs/tools/src/registry.ts +664 -0
  708. package/core/.kenaz/docs/tools/src/search.ts +195 -0
  709. package/core/.kenaz/docs/tools/src/types.ts +88 -0
  710. package/core/.kenaz/docs/tools/test-git-hook.sh +79 -0
  711. package/core/.kenaz/docs/tools/tsconfig.json +20 -0
  712. package/core/.kenaz/docs/tools/verify.sh +106 -0
  713. package/core/.kenaz/episodes/SESSION_ansuz-prompt-1785087304784.yaml +68 -0
  714. package/core/.kenaz/huginn-exploration.json +8 -0
  715. package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -0
  716. package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -0
  717. package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -0
  718. package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -0
  719. package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -0
  720. package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -0
  721. package/core/.kenaz/main-memory/README.md +230 -0
  722. package/core/.kenaz/main-memory/kenaz/cortex/001_cortex_overview.md +168 -0
  723. package/core/.kenaz/main-memory/kenaz/cortex/001_rag_engine_overview.md +30 -0
  724. package/core/.kenaz/main-memory/kenaz/memory-system/001_three_tier_architecture.md +361 -0
  725. package/core/.kenaz/main-memory/kenaz/pipeline/001_task_execution_flow.md +117 -0
  726. package/core/.kenaz/main-memory/kenaz/pipeline/001_task_granularity.md +154 -0
  727. package/core/.kenaz/main-memory/kenaz/pipeline/002_team_priority.md +51 -0
  728. package/core/.kenaz/main-memory/kenaz/pipeline/003_feature_id_system.md +46 -0
  729. package/core/.kenaz/main-memory/kenaz/rules/001_gen_007_self_containment.md +108 -0
  730. package/core/.kenaz/main-memory/kenaz/rules/001_rule_priority_system.md +121 -0
  731. package/core/.kenaz/main-memory/kenaz/rules/002_agt_006_pre_check.md +0 -0
  732. package/core/.kenaz/main-memory/kenaz/rules/003_ppl_002_task_archival.md +48 -0
  733. package/core/.kenaz/main-memory/kenaz/system/001_dual_track_task_system.md +197 -0
  734. package/core/.kenaz/main-memory/knowledge/patterns/001_json_bom_issue.md +78 -0
  735. package/core/.kenaz/main-memory/knowledge/patterns/001_leader_worker_pattern.md +0 -0
  736. package/core/.kenaz/main-memory/knowledge/patterns/002_scope_path_conflict.md +115 -0
  737. package/core/.kenaz/main-memory/knowledge/patterns/002_task_dependency_resolution.md +223 -0
  738. package/core/.kenaz/main-memory/knowledge/patterns/003_question_protocol.md +242 -0
  739. package/core/.kenaz/main-memory/knowledge/patterns/004_user_insight_value.md +111 -0
  740. package/core/.kenaz/main-memory/knowledge/patterns/005_file_lock_pattern.md +274 -0
  741. package/core/.kenaz/main-memory/knowledge/references/001_ppl_rules.md +28 -0
  742. package/core/.kenaz/main-memory/knowledge/solutions/001_json_bom_issue.md +137 -0
  743. package/core/.kenaz/main-memory/knowledge/solutions/001_team_invocation.md +0 -0
  744. package/core/.kenaz/main-memory/knowledge/solutions/002_passive_monitoring.md +0 -0
  745. package/core/.kenaz/main-memory/knowledge/solutions/003_windows_file_race_condition.md +239 -0
  746. package/core/.kenaz/main-memory/registry.json +326 -0
  747. package/core/.kenaz/main-memory/version.yaml +58 -0
  748. package/core/.kenaz/muninn_episode_write.ps1 +17 -0
  749. package/core/.kenaz/muninn_snapshot11_rune_candidates.md +63 -0
  750. package/core/.kenaz/muninn_snapshot1_rune_candidates.md +49 -0
  751. package/core/.kenaz/orphan_audit_2026-05.md +181 -0
  752. package/core/.kenaz/ownership.yaml +155 -0
  753. package/core/.kenaz/peer.yaml +72 -0
  754. package/core/.kenaz/project.yaml +62 -0
  755. package/core/.kenaz/rune_ansuz_literature_validation_strategy.md +40 -0
  756. package/core/.kenaz/rune_efe_active_inference_validation.md +36 -0
  757. package/core/.kenaz/rune_yggdrasil_filesystem_scoped_project.md +41 -0
  758. package/core/.kenaz/rune_yggdrasil_project_identity_folder_name.md +41 -0
  759. package/core/.kenaz/rune_yggdrasil_relay_protocol_project_mismatch.md +39 -0
  760. package/core/.kenaz/runes_TASK_1428.json +57 -0
  761. package/core/.kenaz/specialist/ansuz_motivational_state.schema.json +138 -0
  762. package/core/.kenaz/specialist/ansuz_rules.yaml +241 -0
  763. package/core/.kenaz/specialist/ansuz_session_handoff.schema.json +128 -0
  764. package/core/.kenaz/specialist/chart_TASK_1657.json +50 -0
  765. package/core/.kenaz/specialist/cognitive-hub-e2e.test.js +494 -0
  766. package/core/.kenaz/specialist/daemon-manifest.yaml +168 -0
  767. package/core/.kenaz/specialist/empty_ac_audit_report.md +63 -0
  768. package/core/.kenaz/specialist/episode_muninn_completion_1785056865691.json +91 -0
  769. package/core/.kenaz/specialist/field_report_watermark.json +1 -0
  770. package/core/.kenaz/specialist/gc1826_driver.js +118 -0
  771. package/core/.kenaz/specialist/heimdall_proposal_1017c07f.json +13 -0
  772. package/core/.kenaz/specialist/heimdall_proposal_3dfaa8b9.json +12 -0
  773. package/core/.kenaz/specialist/heimdall_proposal_57af18f9.json +15 -0
  774. package/core/.kenaz/specialist/heimdall_proposal_57b1aa1b.json +14 -0
  775. package/core/.kenaz/specialist/heimdall_proposal_5a3c9473.json +13 -0
  776. package/core/.kenaz/specialist/identity_verdicts/TASK_1073.json +75 -0
  777. package/core/.kenaz/specialist/identity_verdicts/TASK_TEST.json +77 -0
  778. package/core/.kenaz/specialist/memory_guard_fired.json +1 -0
  779. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -0
  780. package/core/.kenaz/specialist/memory_source_priority.yaml +260 -0
  781. package/core/.kenaz/specialist/motivational_state.schema.json +62 -0
  782. package/core/.kenaz/specialist/prompt_1775.txt +317 -0
  783. package/core/.kenaz/specialist/prompt_1776.txt +326 -0
  784. package/core/.kenaz/specialist/prompt_1791.txt +320 -0
  785. package/core/.kenaz/specialist/prompt_1792.txt +320 -0
  786. package/core/.kenaz/specialist/prompt_1795.txt +281 -0
  787. package/core/.kenaz/specialist/prompt_1813.txt +332 -0
  788. package/core/.kenaz/specialist/prompt_1814.txt +401 -0
  789. package/core/.kenaz/specialist/prompt_1815.txt +394 -0
  790. package/core/.kenaz/specialist/prompt_1816.txt +437 -0
  791. package/core/.kenaz/specialist/prompt_1818.txt +361 -0
  792. package/core/.kenaz/specialist/prompt_1819.txt +421 -0
  793. package/core/.kenaz/specialist/prompt_1821.txt +345 -0
  794. package/core/.kenaz/specialist/prompt_1822.txt +313 -0
  795. package/core/.kenaz/specialist/prompt_1824.txt +348 -0
  796. package/core/.kenaz/specialist/prompt_1826.txt +388 -0
  797. package/core/.kenaz/specialist/retired_organs/README.md +58 -0
  798. package/core/.kenaz/specialist/rune_calibration_poc.md +175 -0
  799. package/core/.kenaz/specialist/rune_efe_salience_gap.md +21 -0
  800. package/core/.kenaz/specialist/rune_ember_autonomous_dispatch_gate.md +24 -0
  801. package/core/.kenaz/specialist/rune_post_dispatch_verification_pattern.md +31 -0
  802. package/core/.kenaz/specialist/rune_surprise_proxy_improvement.md +28 -0
  803. package/core/.kenaz/specialist/rune_worktree_dispatch_pattern.md +31 -0
  804. package/core/.kenaz/specialist/rune_yaml_filter_desync_fix.md +26 -0
  805. package/core/.kenaz/specialist/rune_yggdrasil_project_identity_folder_name.md +24 -0
  806. package/core/.kenaz/specialist/sessions/chart_TASK_1599.json +41 -0
  807. package/core/.kenaz/specialist/sessions/hmap_028aa781-aad2-48c0-9a61-5540565d4a7f.json +5 -0
  808. package/core/.kenaz/specialist/sessions/hmap_20210387-0c64-400c-9d0a-53042a0cbd89.json +5 -0
  809. package/core/.kenaz/specialist/sessions/hmap_c1375c5d-fd9f-44bd-a83e-353ecbb0392f.json +5 -0
  810. package/core/.kenaz/specialist/sessions/scratch_028aa781-aad2-48c0-9a61-5540565d4a7f.json +6 -0
  811. package/core/.kenaz/specialist/sessions/scratch_0acc0fb3-24f9-453f-9e92-6ef1cdf03a7c.json +6 -0
  812. package/core/.kenaz/specialist/sessions/scratch_20210387-0c64-400c-9d0a-53042a0cbd89.json +6 -0
  813. package/core/.kenaz/specialist/sessions/scratch_2f10623e-b7a1-4647-905e-9dc7c4d99f9c.json +6 -0
  814. package/core/.kenaz/specialist/sessions/scratch_5a38b62d-608a-488c-82f0-7e47e95e0905.json +6 -0
  815. package/core/.kenaz/specialist/sessions/scratch_5d9758ef-d9b6-4ffe-a017-3ed26d0a313b.json +6 -0
  816. package/core/.kenaz/specialist/sessions/scratch_c1375c5d-fd9f-44bd-a83e-353ecbb0392f.json +6 -0
  817. package/core/.kenaz/specialist/sessions/scratch_c9f3c711-b73d-4fcd-ad2a-7b2bc024ee77.json +6 -0
  818. package/core/.kenaz/specialist/sessions/scratch_e72e5274-1c81-4655-a05a-f87d1ecb2917.json +6 -0
  819. package/core/.kenaz/specialist/sessions/scratch_f740cdbe-f756-41fc-98fb-2b1413d51901.json +6 -0
  820. package/core/.kenaz/specialist/t1777_probe.txt +1 -0
  821. package/core/.kenaz/specialist/task1825_update.json +43 -0
  822. package/core/.kenaz/specialist/task1827_update.json +43 -0
  823. package/core/.kenaz/specialist/task1828_update.json +43 -0
  824. package/core/.kenaz/specialist/taskgen/consolidation_i.json +38 -0
  825. package/core/.kenaz/specialist/taskgen/csharp_a.json +44 -0
  826. package/core/.kenaz/specialist/taskgen/csharp_b.json +40 -0
  827. package/core/.kenaz/specialist/taskgen/embedding_e.json +34 -0
  828. package/core/.kenaz/specialist/taskgen/hygiene_fg.json +39 -0
  829. package/core/.kenaz/specialist/taskgen/pathology_h.json +36 -0
  830. package/core/.kenaz/specialist/taskgen/unity_noise_d.json +38 -0
  831. package/core/.kenaz/specialist/taskgen/visibility_c.json +38 -0
  832. package/core/.kenaz/specialist/verify_1837.js +302 -0
  833. package/core/.kenaz/specialist/verify_1838.js +124 -0
  834. package/core/.kenaz/tmp/heimdall_proposal_fc7e0944.json +10 -0
  835. package/core/.kenaz/tmp_heimdall_proposal_8d3e392.json +13 -0
  836. package/core/.kenaz/tmp_props.json +1 -0
  837. package/core/.kenaz/tmp_sounddrop_t1_prompt.txt +371 -0
  838. package/core/.kenaz/user_model.schema.json +107 -0
  839. package/core/.matrix/README.md +257 -0
  840. package/core/.matrix/index.matrix +5 -0
  841. package/core/.mcp.json +8 -0
  842. package/core/.prefrontal/.gitkeep +0 -0
  843. package/core/.prefrontal/proposals/.gitkeep +0 -0
  844. package/core/.rule/INDEX.yaml +99 -0
  845. package/core/.rule/MECHANIZED.md +61 -0
  846. package/core/.rule/code/README.md +211 -0
  847. package/core/.rule/code/backend.yaml +294 -0
  848. package/core/.rule/code/checklist.yaml +258 -0
  849. package/core/.rule/code/maintainability.yaml +227 -0
  850. package/core/.rule/code/naming.yaml +106 -0
  851. package/core/.rule/code/principles.yaml +71 -0
  852. package/core/.rule/code/reusability.yaml +192 -0
  853. package/core/.rule/code/structure.yaml +218 -0
  854. package/core/.rule/code/style.yaml +197 -0
  855. package/core/.rule/design/ui.yaml +79 -0
  856. package/core/.rule/general/agent-workflow.yaml +572 -0
  857. package/core/.rule/general/cortex-memory-recall.yaml +446 -0
  858. package/core/.rule/general/cortex-tools.yaml +181 -0
  859. package/core/.rule/general/cross-platform.yaml +107 -0
  860. package/core/.rule/general/ddx-synapse-stack.yaml +162 -0
  861. package/core/.rule/general/file-size-threshold.yaml +45 -0
  862. package/core/.rule/general/identity-contract.schema.yaml +251 -0
  863. package/core/.rule/general/memory-language.yaml +67 -0
  864. package/core/.rule/general/memory-source-priority.yaml +207 -0
  865. package/core/.rule/general/pipeline.yaml +246 -0
  866. package/core/.rule/general/principles.yaml +77 -0
  867. package/core/.rule/general/project-self-containment.yaml +253 -0
  868. package/core/.rule/general/rust-verify-target.yaml +80 -0
  869. package/core/.rule/general/specialist-identity-contract.yaml +153 -0
  870. package/core/.rule/general/sunny-access-control.yaml +60 -0
  871. package/core/.rule/general/synapse-code-intelligence.yaml +375 -0
  872. package/core/.rule/general/synapse-cognitive-fusion.yaml +301 -0
  873. package/core/.rule/general/system-invariants.yaml +248 -0
  874. package/core/.rule/general/task-pool-routing.yaml +102 -0
  875. package/core/.rule/general/worktree-isolation.yaml +79 -0
  876. package/core/.rule/pipeline/PPL-002-completed-task-archival.yaml +528 -0
  877. package/core/.rule/pipeline/auto-update.yaml +282 -0
  878. package/core/.rule/pipeline/task-execution.yaml +368 -0
  879. package/core/.rule/tools/check-permissions.js +197 -0
  880. package/core/.specialist/Amelia/README.md +329 -0
  881. package/core/.specialist/Amelia/capability.yaml +386 -0
  882. package/core/.specialist/Amelia/knowledge.yaml +217 -0
  883. package/core/.specialist/Amelia/principles.yaml +10 -0
  884. package/core/.specialist/Amelia/rule.yaml +79 -0
  885. package/core/.specialist/Amelia/workflow.yaml +241 -0
  886. package/core/.specialist/Ansuz/HEURISTIC_BOOT_ROUTING.md +39 -0
  887. package/core/.specialist/Ansuz/ROUTING.md +35 -0
  888. package/core/.specialist/Ansuz/RULES.md +79 -0
  889. package/core/.specialist/Ansuz/SYSTEM_DOC.md +611 -0
  890. package/core/.specialist/Ansuz/VERIFY.md +38 -0
  891. package/core/.specialist/Ansuz/capability.yaml +338 -0
  892. package/core/.specialist/Ansuz/cognitive_principles.yaml +62 -0
  893. package/core/.specialist/Ansuz/concepts/agent_types.yaml +22 -0
  894. package/core/.specialist/Ansuz/concepts/autonomous_agenda.yaml +170 -0
  895. package/core/.specialist/Ansuz/concepts/autonomy_contract.yaml +126 -0
  896. package/core/.specialist/Ansuz/concepts/boot_ritual.yaml +107 -0
  897. package/core/.specialist/Ansuz/concepts/boot_rules_summary.yaml +16 -0
  898. package/core/.specialist/Ansuz/concepts/capability_pointers.yaml +21 -0
  899. package/core/.specialist/Ansuz/concepts/cognitive_fluency_contract.yaml +264 -0
  900. package/core/.specialist/Ansuz/concepts/cognitive_systems_competence_map.yaml +14 -0
  901. package/core/.specialist/Ansuz/concepts/cognitive_systems_hippocampus.yaml +14 -0
  902. package/core/.specialist/Ansuz/concepts/cognitive_systems_overview.yaml +27 -0
  903. package/core/.specialist/Ansuz/concepts/cognitive_systems_serendipity.yaml +14 -0
  904. package/core/.specialist/Ansuz/concepts/cognitive_systems_turn_context.yaml +13 -0
  905. package/core/.specialist/Ansuz/concepts/commitment_layer.yaml +150 -0
  906. package/core/.specialist/Ansuz/concepts/completion_tracking.yaml +29 -0
  907. package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +28 -0
  908. package/core/.specialist/Ansuz/concepts/evolvable_guardrails.yaml +20 -0
  909. package/core/.specialist/Ansuz/concepts/fatigue_handoff.yaml +84 -0
  910. package/core/.specialist/Ansuz/concepts/formation_quick_ref.yaml +28 -0
  911. package/core/.specialist/Ansuz/concepts/guardrails_immutable.yaml +31 -0
  912. package/core/.specialist/Ansuz/concepts/identity.yaml +48 -0
  913. package/core/.specialist/Ansuz/concepts/interaction_protocol.yaml +42 -0
  914. package/core/.specialist/Ansuz/concepts/meta_evolvable_tier.yaml +28 -0
  915. package/core/.specialist/Ansuz/concepts/mindset.yaml +18 -0
  916. package/core/.specialist/Ansuz/concepts/phase9_verification.yaml +21 -0
  917. package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +66 -0
  918. package/core/.specialist/Ansuz/concepts/principles.yaml +65 -0
  919. package/core/.specialist/Ansuz/concepts/self_evolution_overview.yaml +53 -0
  920. package/core/.specialist/Ansuz/concepts/task_first_protocol.yaml +7 -0
  921. package/core/.specialist/Ansuz/concepts/task_pool.yaml +119 -0
  922. package/core/.specialist/Ansuz/concepts/task_system.yaml +22 -0
  923. package/core/.specialist/Ansuz/concepts/write_verification.yaml +26 -0
  924. package/core/.specialist/Ansuz/core.yaml +224 -0
  925. package/core/.specialist/Ansuz/decisions/ADR-requirement-graph-layer.md +390 -0
  926. package/core/.specialist/Ansuz/elixir.yaml +70 -0
  927. package/core/.specialist/Ansuz/exo_cortex_tools.yaml +13 -0
  928. package/core/.specialist/Ansuz/experiments/boot_ablation_replay.js +197 -0
  929. package/core/.specialist/Ansuz/guards/blind_spot_guards.yaml +28 -0
  930. package/core/.specialist/Ansuz/guards/lesson_guards.yaml +203 -0
  931. package/core/.specialist/Ansuz/identity_contract.yaml +375 -0
  932. package/core/.specialist/Ansuz/knowledge.yaml +284 -0
  933. package/core/.specialist/Ansuz/modules/amelia_review.yaml +112 -0
  934. package/core/.specialist/Ansuz/modules/ddx.yaml +47 -0
  935. package/core/.specialist/Ansuz/modules/design_checks.yaml +47 -0
  936. package/core/.specialist/Ansuz/modules/identity_verification.yaml +119 -0
  937. package/core/.specialist/Ansuz/modules/loop_guard.yaml +36 -0
  938. package/core/.specialist/Ansuz/modules/prevention.yaml +84 -0
  939. package/core/.specialist/Ansuz/modules/prophecy.yaml +95 -0
  940. package/core/.specialist/Ansuz/modules/pulse_empty.yaml +36 -0
  941. package/core/.specialist/Ansuz/modules/qa_intake.yaml +29 -0
  942. package/core/.specialist/Ansuz/modules/recall.yaml +45 -0
  943. package/core/.specialist/Ansuz/principles.yaml +31 -0
  944. package/core/.specialist/Ansuz/research/EMBER_architecture_comparison.md +142 -0
  945. package/core/.specialist/Ansuz/rule.yaml +97 -0
  946. package/core/.specialist/Ansuz/self_model_seed.backup.json +100 -0
  947. package/core/.specialist/Ansuz/self_model_seed.json +100 -0
  948. package/core/.specialist/Ansuz/self_model_seed.sha256 +1 -0
  949. package/core/.specialist/Ansuz/tools/ansuz-handoff-snapshot.js +626 -0
  950. package/core/.specialist/Ansuz/tools/ansuz-handoff.js +437 -0
  951. package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.js +1254 -0
  952. package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.test.js +137 -0
  953. package/core/.specialist/Ansuz/tools/ansuz-state.js +288 -0
  954. package/core/.specialist/Ansuz/tools/bootstrap-anchors.js +289 -0
  955. package/core/.specialist/Ansuz/tools/handoff-freshness-milestone.test.js +73 -0
  956. package/core/.specialist/Ansuz/tools/handoff-resolver.js +207 -0
  957. package/core/.specialist/Ansuz/tools/handoff-session-identity.test.js +333 -0
  958. package/core/.specialist/Ansuz/tools/handoff-snapshot-args.test.js +25 -0
  959. package/core/.specialist/Ansuz/tools/install-claude-hooks.js +354 -0
  960. package/core/.specialist/Ansuz/tools/install-claude-hooks.test.js +110 -0
  961. package/core/.specialist/Ansuz/tools/package.json +16 -0
  962. package/core/.specialist/Ansuz/tools/preservation-coordinator.js +504 -0
  963. package/core/.specialist/Ansuz/tools/verify-and-land.js +1012 -0
  964. package/core/.specialist/Ansuz/tools/verify-and-land.test.js +1099 -0
  965. package/core/.specialist/Ansuz/workflow.yaml +1273 -0
  966. package/core/.specialist/Forseti/capability.yaml +210 -0
  967. package/core/.specialist/Forseti/core.yaml +176 -0
  968. package/core/.specialist/Forseti/principles.yaml +88 -0
  969. package/core/.specialist/Forseti/rule.yaml +86 -0
  970. package/core/.specialist/Heimdall/SKILL.md +65 -0
  971. package/core/.specialist/Heimdall/capability.yaml +134 -0
  972. package/core/.specialist/Heimdall/core.yaml +275 -0
  973. package/core/.specialist/Heimdall/principles.yaml +66 -0
  974. package/core/.specialist/Heimdall/rule.yaml +68 -0
  975. package/core/.specialist/Huginn/capability.yaml +234 -0
  976. package/core/.specialist/Huginn/core.yaml +482 -0
  977. package/core/.specialist/Huginn/principles.yaml +57 -0
  978. package/core/.specialist/Huginn/rule.yaml +121 -0
  979. package/core/.specialist/REFLECTION_TO_CONTROL.md +347 -0
  980. package/core/.specialist/ansuz_session_handoff.json +96 -0
  981. package/core/.specialist/experiments/rg_benchmark.js +523 -0
  982. package/core/.specialist/experiments/rg_benchmark_results.json +27 -0
  983. package/core/.specialist/shared/cognitive-primer.md +15 -0
  984. package/core/.specialist/shared/guard_schema.json +290 -0
  985. package/core/.specialist/shared/principles.yaml +213 -0
  986. package/core/.specialist/tests/runtime-discovery/README.md +224 -0
  987. package/core/.specialist/tests/runtime-discovery/active-project-polling.test.js +495 -0
  988. package/core/.specialist/tests/runtime-discovery/port-binding.test.js +418 -0
  989. package/core/.specialist/tests/runtime-discovery/resolver-priority.test.js +693 -0
  990. package/core/.specialist/tests/runtime-discovery/resolver-skip-on-invalid.test.js +467 -0
  991. package/core/.specialist/tests/runtime-discovery/resolver-symmetry.test.js +679 -0
  992. package/core/.specialist/tests/runtime-discovery/tail-cli-project-path.test.js +231 -0
  993. package/core/.specialist/tools/MEMORY_RECALL_TRIGGERS.md +156 -0
  994. package/core/.specialist/tools/README.md +129 -0
  995. package/core/.specialist/tools/__tests__/freya-cli-contract.test.js +156 -0
  996. package/core/.specialist/tools/__tests__/task-1863-boot-lease-reuse.test.js +99 -0
  997. package/core/.specialist/tools/__tests__/task-1917-orphan-detection.test.js +175 -0
  998. package/core/.specialist/tools/__tests__/task-1977-banner-cap.test.js +129 -0
  999. package/core/.specialist/tools/__tests__/task-1983-lease-project-path.test.js +96 -0
  1000. package/core/.specialist/tools/__tests__/task-2218-boot-session-id.test.js +118 -0
  1001. package/core/.specialist/tools/__tests__/task-2222-session-identity-interleave.test.js +203 -0
  1002. package/core/.specialist/tools/__tests__/task-2246-reap-honesty.test.js +96 -0
  1003. package/core/.specialist/tools/__tests__/task-2254-handoff-archive-contract.test.js +60 -0
  1004. package/core/.specialist/tools/__tests__/task-2272-open-card-status-quote.test.js +26 -0
  1005. package/core/.specialist/tools/__tests__/task-2374-usage-counter.test.js +34 -0
  1006. package/core/.specialist/tools/ansuz-boot-fast.js +1120 -0
  1007. package/core/.specialist/tools/ansuz-boot-fast.test.js +705 -0
  1008. package/core/.specialist/tools/canary.js +501 -0
  1009. package/core/.specialist/tools/context-injection.js +395 -0
  1010. package/core/.specialist/tools/design-reasoning.js +493 -0
  1011. package/core/.specialist/tools/discovery-fixtures.js +308 -0
  1012. package/core/.specialist/tools/discovery-runner.js +460 -0
  1013. package/core/.specialist/tools/failure-detector.js +1132 -0
  1014. package/core/.specialist/tools/identity-scorer.js +714 -0
  1015. package/core/.specialist/tools/jev-optional-context.js +107 -0
  1016. package/core/.specialist/tools/jev-optional-context.test.js +152 -0
  1017. package/core/.specialist/tools/lesson-guard.js +1084 -0
  1018. package/core/.specialist/tools/lib/chart-path.js +159 -0
  1019. package/core/.specialist/tools/lib/chart-path.test.js +171 -0
  1020. package/core/.specialist/tools/lib/project-resolver.js +200 -0
  1021. package/core/.specialist/tools/lib/project-resolver.test.js +241 -0
  1022. package/core/.specialist/tools/memory-action-log.js +839 -0
  1023. package/core/.specialist/tools/phase-gate.js +3754 -0
  1024. package/core/.specialist/tools/phase-gate.test.js +85 -0
  1025. package/core/.specialist/tools/phase9-policy.test.js +109 -0
  1026. package/core/.specialist/tools/precompute-runner.js +444 -0
  1027. package/core/.specialist/tools/prompt-files.test.js +169 -0
  1028. package/core/.specialist/tools/replay-detectors.js +290 -0
  1029. package/core/.specialist/tools/rule-engine.js +621 -0
  1030. package/core/.specialist/tools/session-boot-ownership.test.js +117 -0
  1031. package/core/.specialist/tools/session-project-state.test.js +65 -0
  1032. package/core/.specialist/tools/session.js +1866 -0
  1033. package/core/.specialist/tools/stream-lease.js +263 -0
  1034. package/core/.specialist/tools/task-bridge-jev.test.js +170 -0
  1035. package/core/.specialist/tools/task-bridge.js +765 -0
  1036. package/core/.specialist/tools/verify-identity.js +453 -0
  1037. package/core/.specialist/tools/wish-mandate-delivery.test.js +76 -0
  1038. package/core/.system/README.md +555 -0
  1039. package/core/.system/agent-skills/README.md +724 -0
  1040. package/core/.system/archive/README.md +499 -0
  1041. package/core/.system/assessment/README.md +645 -0
  1042. package/core/.system/cli-tools/README.md +724 -0
  1043. package/core/.system/collaboration/KNOWLEDGE.md +65 -0
  1044. package/core/.system/collaboration/README.md +570 -0
  1045. package/core/.system/cortex/MODULES.md +1046 -0
  1046. package/core/.system/dashboard/FRONTEND.md +812 -0
  1047. package/core/.system/dashboard/README.md +737 -0
  1048. package/core/.system/external-skills/README.md +522 -0
  1049. package/core/.system/failure/README.md +723 -0
  1050. package/core/.system/formation/LEGACY_SKILLS.md +972 -0
  1051. package/core/.system/formation/POST_COMPLETE.md +600 -0
  1052. package/core/.system/formation/README.md +1092 -0
  1053. package/core/.system/formation/TOOLS_REFERENCE.md +1145 -0
  1054. package/core/.system/githooks/README.md +846 -0
  1055. package/core/.system/kenaz/README.md +787 -0
  1056. package/core/.system/memory/README.md +716 -0
  1057. package/core/.system/pipeline/README.md +712 -0
  1058. package/core/.system/rule/README.md +794 -0
  1059. package/core/.system/specialist/README.md +669 -0
  1060. package/core/.system/specialist/STRUCTURE.md +262 -0
  1061. package/core/.system/sunny/README.md +476 -0
  1062. package/core/.system/sunny/RULES.json +89 -0
  1063. package/core/.system/sunny/knowledge/INDEX.md +37 -0
  1064. package/core/.system/sunny/knowledge/agent-teams-api.md +0 -0
  1065. package/core/.system/sunny/knowledge/ansuz-specialist.md +500 -0
  1066. package/core/.system/sunny/knowledge/claude-cli-spawn.md +177 -0
  1067. package/core/.system/sunny/knowledge/collaboration-api.md +925 -0
  1068. package/core/.system/sunny/teammates/README.md +107 -0
  1069. package/core/.system/sunny/teammates/how_to_use.yaml +101 -0
  1070. package/core/.system/sunny/teammates/tools/headless/launcher.js +318 -0
  1071. package/core/.system/sunny/teammates/tools/powershell/launcher.js +241 -0
  1072. package/core/.system/sunny/teammates/tools/powershell/start.bat +50 -0
  1073. package/core/.system/sunny/teammates/tools/utils/env.js +96 -0
  1074. package/core/.system/sunny/teammates/tools/utils/format-output.js +329 -0
  1075. package/core/.system/sunny/tools.json +101 -0
  1076. package/core/.vendor/js-yaml.cjs +2795 -0
  1077. package/core/.vendor/require-yaml.cjs +8 -0
  1078. package/core/CLAUDE.md +172 -0
  1079. package/core/KENAZ_CORE_VERSION +1 -0
  1080. package/core/dev/ALAYA_FIELD_FEEDBACK_DESIGN.md +126 -0
  1081. package/core/dev/ANSUZ_SCAFFOLD_DIET_2026-09-20.md +231 -0
  1082. package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -0
  1083. package/core/dev/ANSUZ_WORKFLOW_MODERNIZATION.md +143 -0
  1084. package/core/dev/CORTEX_META_GRAPH_ASSESSMENT.md +99 -0
  1085. package/core/dev/FREYA_DOWNSTREAM_BUGS_2026-09-19.md +44 -0
  1086. package/core/dev/GLOBAL_MEMORY_IDENTITY_CONSOLIDATION.md +263 -0
  1087. package/core/dev/GUARD_EFFECTIVENESS_DESIGN.md +180 -0
  1088. package/core/dev/HEALTH_GATE_VARIANCE_ASSESSMENT.md +271 -0
  1089. package/core/dev/HOOK_DIET_2026-09-21.md +126 -0
  1090. package/core/dev/JEV_INTEGRATION_PLAN.md +189 -0
  1091. package/core/dev/KENAZ_AGENT_CONNECT_v1_SPEC.md +139 -0
  1092. package/core/dev/MOBILE_DESIGN_SPEC.md +569 -0
  1093. package/core/dev/MULTI_ANSUZ_SPEC.md +136 -0
  1094. package/core/dev/MUNINN_RETIREMENT.md +115 -0
  1095. package/core/dev/PHASE3_ATTRIBUTION_POPULATION_DIAGNOSIS.md +211 -0
  1096. package/core/dev/PHASE3_P3-2B_TASKLEVEL_PROBE.md +83 -0
  1097. package/core/dev/PHASE3_P3-2_POPULATION_DISJOINTNESS.md +83 -0
  1098. package/core/dev/PHASE3_P3-2_REMEASURE_REPORT.md +231 -0
  1099. package/core/dev/PREDICTION_GROUNDING_DESIGN.md +376 -0
  1100. package/core/dev/PRODUCT_SPEC.md +317 -0
  1101. package/core/dev/PRODUCT_SPEC_FOR_AI_tepmlate.md +716 -0
  1102. package/core/dev/PROJECT_REGISTRY_DUPLICATE_ASSESSMENT.md +86 -0
  1103. package/core/dev/STARTUP_TIMELINE_TASK_1995.md +181 -0
  1104. package/core/dev/SYNAPSE_WORKER_PER_PROJECT.md +881 -0
  1105. package/core/dev/TASK_1680_P3-3_DISPATCH_SPEC.md +96 -0
  1106. package/core/dev/TASK_1684_CALIBRATION_IMPACT_ANALYSIS.md +170 -0
  1107. package/core/dev/TASK_1684_FORK_A_GOODHART_DESIGN.md +171 -0
  1108. package/core/dev/TASK_1684_P3_2_REMEASURE_VALIDATION.md +262 -0
  1109. package/core/dev/TASK_1903_SIGNAL_AUC_MEASUREMENT.md +207 -0
  1110. package/core/dev/TASK_1904_BRANCH_INTEGRITY_AUDIT.md +146 -0
  1111. package/core/dev/ansuz-eval/README.md +38 -0
  1112. package/core/dev/ansuz-eval/build-fixtures.js +87 -0
  1113. package/core/dev/ansuz-eval/grade.js +96 -0
  1114. package/core/dev/ansuz-eval/grader.test.js +117 -0
  1115. package/core/dev/ansuz-eval/tasks.md +32 -0
  1116. package/core/dev/archive/ansuz/BOOT_FULL.md +141 -0
  1117. package/core/dev/archive/ansuz/agents/ansuz-boot.md +176 -0
  1118. package/core/dev/archive/ansuz/designs/alaya-passive-surfacing.md +84 -0
  1119. package/core/dev/archive/ansuz/designs/cc_memory_indexing.md +307 -0
  1120. package/core/dev/archive/ansuz/designs/check_guard.md +322 -0
  1121. package/core/dev/archive/ansuz/designs/consequence_loop.md +227 -0
  1122. package/core/dev/archive/ansuz/designs/mcp_kuzudb_lock_architecture.md +312 -0
  1123. package/core/dev/archive/ansuz/designs/phase0_git_enrichment.md +278 -0
  1124. package/core/dev/archive/ansuz/designs/refactor_mcp_tools_synapse.md +490 -0
  1125. package/core/dev/archive/ansuz/designs/semantic_markdown_extraction.md +167 -0
  1126. package/core/dev/archive/ansuz/designs/task_1372_prompt_injects_into.md +193 -0
  1127. package/core/dev/archive/ansuz/designs/thought_stack.md +286 -0
  1128. package/core/dev/archive/ansuz/investigations/reachability_unified_api_2026-04-11.md +330 -0
  1129. package/core/dev/archive/ansuz/investigations/synapse_orphans_indexing_bug_2026-04-11.md +352 -0
  1130. package/core/dev/archive/ansuz/investigations/synapse_orphans_reindex_verification_2026-04-11.md +48 -0
  1131. package/core/dev/archive/ansuz/investigations/synapse_orphans_verification_2026-04-11.md +170 -0
  1132. package/core/dev/archive/ansuz/legacy/kernel.yaml +399 -0
  1133. package/core/dev/archive/ansuz/legacy/phases/phase_0.yaml +418 -0
  1134. package/core/dev/archive/ansuz/legacy/phases/phase_1.yaml +376 -0
  1135. package/core/dev/archive/ansuz/legacy/phases/phase_10.yaml +35 -0
  1136. package/core/dev/archive/ansuz/legacy/phases/phase_11.yaml +582 -0
  1137. package/core/dev/archive/ansuz/legacy/phases/phase_2.yaml +94 -0
  1138. package/core/dev/archive/ansuz/legacy/phases/phase_3.yaml +274 -0
  1139. package/core/dev/archive/ansuz/legacy/phases/phase_4.yaml +67 -0
  1140. package/core/dev/archive/ansuz/legacy/phases/phase_5.yaml +32 -0
  1141. package/core/dev/archive/ansuz/legacy/phases/phase_6.yaml +91 -0
  1142. package/core/dev/archive/ansuz/legacy/phases/phase_7.yaml +58 -0
  1143. package/core/dev/archive/ansuz/legacy/phases/phase_8.yaml +146 -0
  1144. package/core/dev/archive/ansuz/legacy/phases/phase_9.yaml +424 -0
  1145. package/core/dev/archive/ansuz/specs/INDEX_bifrost_hook_parity.yaml +92 -0
  1146. package/core/dev/archive/ansuz/specs/active-inference-formation.yaml +453 -0
  1147. package/core/dev/archive/ansuz/specs/brief_TASK_1228_userpromptsubmit.yaml +191 -0
  1148. package/core/dev/archive/ansuz/specs/brief_TASK_1229_pretooluse_posttooluse.yaml +250 -0
  1149. package/core/dev/archive/ansuz/specs/brief_TASK_1230_sessionstart_stop.yaml +301 -0
  1150. package/core/dev/archive/ansuz/specs/brief_TASK_1231_verification.yaml +272 -0
  1151. package/core/dev/design/PHASE4_DESIGN.md +559 -0
  1152. package/core/dev/design/dashboard_docs_integration_PLAN.md +388 -0
  1153. package/core/dev/design/matrix_format_ABBREVIATIONS.md +655 -0
  1154. package/core/dev/design/matrix_format_DESIGN_SPEC.md +601 -0
  1155. package/core/dev/design/matrix_format_DISCUSSION_SUMMARY.md +484 -0
  1156. package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -0
  1157. package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -0
  1158. package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -0
  1159. package/core/dev/design/matrix_format_SYNTAX_BNF.md +754 -0
  1160. package/core/dev/design/matrix_format_TOKEN_COMPARISON.md +484 -0
  1161. package/core/dev/design/matrix_v2_COGNITIVE_DESIGN.md +503 -0
  1162. package/core/dev/design/matrix_v2_DISCUSSION_SUMMARY.md +414 -0
  1163. package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -0
  1164. package/core/dev/designs/EXPANDABLE_CARD_DESIGN_SPEC.md +509 -0
  1165. package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -0
  1166. package/core/dev/jev-eval/ANNOTATION_GUIDE.md +68 -0
  1167. package/core/dev/jev-eval/BASELINE_COMPARISON.md +45 -0
  1168. package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -0
  1169. package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -0
  1170. package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -0
  1171. package/core/dev/jev-eval/PACKAGING_VERIFICATION.md +42 -0
  1172. package/core/dev/jev-eval/README.md +101 -0
  1173. package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -0
  1174. package/core/dev/jev-eval/VALIDATION_PROGRESS.md +54 -0
  1175. package/core/dev/jev-eval/cli.mjs +50 -0
  1176. package/core/dev/jev-eval/context.mjs +183 -0
  1177. package/core/dev/jev-eval/corpus.mjs +186 -0
  1178. package/core/dev/jev-eval/fixtures/conversation-review.seed.jsonl +6 -0
  1179. package/core/dev/jev-eval/fixtures/synthetic.jsonl +11 -0
  1180. package/core/dev/jev-eval/metrics.mjs +67 -0
  1181. package/core/dev/jev-eval/muninn-baseline.mjs +119 -0
  1182. package/core/dev/jev-eval/providers.mjs +95 -0
  1183. package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -0
  1184. package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -0
  1185. package/core/dev/jev-eval/reports/conversation-review.pending.json +151 -0
  1186. package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -0
  1187. package/core/dev/jev-eval/reports/length-probe-2026-09-20.json +133 -0
  1188. package/core/dev/jev-eval/reports/live-smoke-2026-09-20.json +616 -0
  1189. package/core/dev/jev-eval/reports/muninn-baseline-2026-09-20.json +52 -0
  1190. package/core/dev/jev-eval/reports/resilience-2026-09-20.json +520 -0
  1191. package/core/dev/jev-eval/reports/rules-baseline.json +197 -0
  1192. package/core/dev/jev-eval/reports/synthetic-review.pending.json +357 -0
  1193. package/core/dev/jev-eval/resilience.mjs +71 -0
  1194. package/core/dev/jev-eval/review.mjs +78 -0
  1195. package/core/dev/jev-eval/schema.mjs +87 -0
  1196. package/core/dev/jev-eval/test/context.test.mjs +95 -0
  1197. package/core/dev/jev-eval/test/corpus.test.mjs +102 -0
  1198. package/core/dev/jev-eval/test/eval.test.mjs +146 -0
  1199. package/core/dev/jev-eval/test/muninn-baseline.test.mjs +71 -0
  1200. package/core/dev/jev-eval/test/resilience.test.mjs +72 -0
  1201. package/core/dev/jev-eval/test/review.test.mjs +61 -0
  1202. package/core/dev/pipeline/LOCK_STATE.json +4 -0
  1203. package/core/dev/pipeline/OPTIMIZATION_PROPOSALS.json +32 -0
  1204. package/core/dev/pipeline/PHASE3_PLANNING_SUMMARY.md +338 -0
  1205. package/core/dev/pipeline/PIPELINE_CONFIG.json +46 -0
  1206. package/core/dev/pipeline/PROGRESS.md +113 -0
  1207. package/core/dev/pipeline/PROGRESS_template.md +860 -0
  1208. package/core/dev/pipeline/QA_REPORT_F008.md +431 -0
  1209. package/core/dev/pipeline/QA_REPORT_LOCAL_CLAUDE_DIAGNOSIS.md +583 -0
  1210. package/core/dev/pipeline/README.md +210 -0
  1211. package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -0
  1212. package/core/dev/pipeline/TASK_QUEUE.json +192 -0
  1213. package/core/dev/pipeline/WORKFLOW_GUIDE.md +824 -0
  1214. package/core/dev/pipeline/archived/TASK_015_tauri_init.yaml +53 -0
  1215. package/core/dev/pipeline/archived/TASK_016_tauri_commands.yaml +54 -0
  1216. package/core/dev/pipeline/archived/TASK_017_rust_pty.yaml +54 -0
  1217. package/core/dev/pipeline/archived/TASK_018_tauri_bundle.yaml +53 -0
  1218. package/core/dev/pipeline/archived/TASK_019_release_workflow.yaml +56 -0
  1219. package/core/dev/pipeline/archived/TASK_020_update_ui.yaml +54 -0
  1220. package/core/dev/pipeline/archived/TASK_021_update_notification_ui.yaml +55 -0
  1221. package/core/dev/pipeline/archived/TASK_022_remove_node_pty.yaml +65 -0
  1222. package/core/dev/pipeline/archived/TASK_023_api_routes_migration.yaml +86 -0
  1223. package/core/dev/pipeline/archived/TASK_024_feature_id_system.yaml +51 -0
  1224. package/core/dev/pipeline/archived/TASK_025_executable_acceptance.yaml +66 -0
  1225. package/core/dev/pipeline/archived/TASK_026_spec_versioning.yaml +56 -0
  1226. package/core/dev/pipeline/archived/TASK_027_drift_detection.yaml +60 -0
  1227. package/core/dev/pipeline/archived/TASK_028_tauri_pty_manager.yaml +60 -0
  1228. package/core/dev/pipeline/archived/TASK_029_tauri_pty_commands.yaml +62 -0
  1229. package/core/dev/pipeline/archived/TASK_030_frontend_session_adapter.yaml +54 -0
  1230. package/core/dev/pipeline/archived/TASK_031_terminal_tauri_events.yaml +56 -0
  1231. package/core/dev/pipeline/archived/TASK_041_scheduler_data_models.yaml +54 -0
  1232. package/core/dev/pipeline/archived/TASK_042_path_lock_manager.yaml +53 -0
  1233. package/core/dev/pipeline/archived/TASK_043_process_manager.yaml +57 -0
  1234. package/core/dev/pipeline/archived/TASK_044_git_integration.yaml +85 -0
  1235. package/core/dev/pipeline/archived/TASK_045_task_queue_manager.yaml +54 -0
  1236. package/core/dev/pipeline/archived/TASK_046_scheduler_dispatcher.yaml +60 -0
  1237. package/core/dev/pipeline/archived/TASK_047_tauri_scheduler_commands.yaml +55 -0
  1238. package/core/dev/pipeline/archived/TASK_048_realtime_status_push.yaml +92 -0
  1239. package/core/dev/pipeline/archived/TASK_049_frontend_scheduler_integration.yaml +58 -0
  1240. package/core/dev/pipeline/archived/TASK_050_error_recovery.yaml +56 -0
  1241. package/core/dev/pipeline/archived/TASK_051_optimization_proposals_backend.yaml +53 -0
  1242. package/core/dev/pipeline/archived/TASK_052_optimization_proposals_ui.yaml +58 -0
  1243. package/core/dev/pipeline/archived/TASK_053_optimization_proposals_diff_view.yaml +54 -0
  1244. package/core/dev/pipeline/archived/TASK_054_multi_project_scheduler_state.yaml +76 -0
  1245. package/core/dev/pipeline/archived/TASK_055_scheduler_api_project_scope.yaml +64 -0
  1246. package/core/dev/pipeline/archived/TASK_056_frontend_scheduler_project_scope.yaml +62 -0
  1247. package/core/dev/pipeline/archived/TASK_057_scheduler_module_structure.yaml +54 -0
  1248. package/core/dev/pipeline/archived/TASK_058_scheduler_models_extract.yaml +51 -0
  1249. package/core/dev/pipeline/archived/TASK_059_scheduler_core_extract.yaml +51 -0
  1250. package/core/dev/pipeline/archived/TASK_060_scheduler_commands_extract.yaml +51 -0
  1251. package/core/dev/pipeline/archived/TASK_061_scheduler_specialized_commands.yaml +53 -0
  1252. package/core/dev/pipeline/archived/TASK_062_process_manager_module_structure.yaml +56 -0
  1253. package/core/dev/pipeline/archived/TASK_063_process_manager_core_extract.yaml +56 -0
  1254. package/core/dev/pipeline/archived/TASK_064_recovery_module_structure.yaml +57 -0
  1255. package/core/dev/pipeline/archived/TASK_065_recovery_core_extract.yaml +56 -0
  1256. package/core/dev/pipeline/archived/TASK_066_self_enhancement_scheduler_models.yaml +60 -0
  1257. package/core/dev/pipeline/archived/TASK_067_agents_section_quick_actions.yaml +53 -0
  1258. package/core/dev/pipeline/archived/TASK_067_self_enhancement_scheduler_core.yaml +65 -0
  1259. package/core/dev/pipeline/archived/TASK_068_agent_workflow_rule_enhancement.yaml +46 -0
  1260. package/core/dev/pipeline/archived/TASK_068_self_enhancement_tauri_commands.yaml +61 -0
  1261. package/core/dev/pipeline/archived/TASK_069_agent_wizard_responsive.yaml +46 -0
  1262. package/core/dev/pipeline/archived/TASK_069_self_enhancement_ui.yaml +62 -0
  1263. package/core/dev/pipeline/archived/TASK_070_preview_panel_redesign.yaml +62 -0
  1264. package/core/dev/pipeline/archived/TASK_070_self_enhancement_integration.yaml +57 -0
  1265. package/core/dev/pipeline/archived/TASK_071_preview_panel_qa.yaml +98 -0
  1266. package/core/dev/pipeline/archived/TASK_071_single_agent_config.yaml +57 -0
  1267. package/core/dev/pipeline/archived/TASK_072_preview_modal_esc_fix.yaml +71 -0
  1268. package/core/dev/pipeline/archived/TASK_072_product_review_module.yaml +57 -0
  1269. package/core/dev/pipeline/archived/TASK_073_agents_page_component_refactor.yaml +68 -0
  1270. package/core/dev/pipeline/archived/TASK_073_rename_single_agent.yaml +64 -0
  1271. package/core/dev/pipeline/archived/TASK_074_preview_mode_ui_diagnosis.yaml +71 -0
  1272. package/core/dev/pipeline/archived/TASK_074_single_agent_ui_toggle.yaml +56 -0
  1273. package/core/dev/pipeline/archived/TASK_075_agents_page_component_refactor.yaml +69 -0
  1274. package/core/dev/pipeline/archived/TASK_075_single_agent_integration_test.yaml +75 -0
  1275. package/core/dev/pipeline/archived/TASK_076_preview_mode_ui_diagnosis.yaml +70 -0
  1276. package/core/dev/pipeline/archived/TASK_076_single_agent_continuous_mode.yaml +65 -0
  1277. package/core/dev/pipeline/archived/TASK_077_failure_directory_structure.yaml +51 -0
  1278. package/core/dev/pipeline/archived/TASK_077_single_agent_typescript_types.yaml +59 -0
  1279. package/core/dev/pipeline/archived/TASK_078_fail_001_agent_no_output.yaml +53 -0
  1280. package/core/dev/pipeline/archived/TASK_078_single_agent_ui_simplify.yaml +74 -0
  1281. package/core/dev/pipeline/archived/TASK_079_dashboard_rwd_responsive.yaml +63 -0
  1282. package/core/dev/pipeline/archived/TASK_079_fail_002_json_bom.yaml +56 -0
  1283. package/core/dev/pipeline/archived/TASK_080_fail_003_queue_pollution.yaml +56 -0
  1284. package/core/dev/pipeline/archived/TASK_080_http_server_module.yaml +55 -0
  1285. package/core/dev/pipeline/archived/TASK_081_failure_logging_tools.yaml +57 -0
  1286. package/core/dev/pipeline/archived/TASK_081_http_api_routes.yaml +62 -0
  1287. package/core/dev/pipeline/archived/TASK_082_frontend_api_adapter.yaml +51 -0
  1288. package/core/dev/pipeline/archived/TASK_082_integrate_failure_to_workflow.yaml +60 -0
  1289. package/core/dev/pipeline/archived/TASK_083_cleanup_nextjs_api.yaml +48 -0
  1290. package/core/dev/pipeline/archived/TASK_083_task_queue_cleanup.yaml +67 -0
  1291. package/core/dev/pipeline/archived/TASK_084_rust_dead_code_cleanup.yaml +66 -0
  1292. package/core/dev/pipeline/archived/TASK_084_scheduler_sync_logic_fix.yaml +68 -0
  1293. package/core/dev/pipeline/archived/TASK_085_http_api_web_mode_fixes.yaml +46 -0
  1294. package/core/dev/pipeline/archived/TASK_085_memory_consolidation_0204_0205.yaml +98 -0
  1295. package/core/dev/pipeline/archived/TASK_086_collapsible_preview_panel.yaml +161 -0
  1296. package/core/dev/pipeline/archived/TASK_086_web_mode_feature_parity.yaml +62 -0
  1297. package/core/dev/pipeline/archived/TASK_087_manual_memory_archival.yaml +163 -0
  1298. package/core/dev/pipeline/archived/TASK_087_mobile_http_control.yaml +68 -0
  1299. package/core/dev/pipeline/archived/TASK_088_fail_003_to_ppl_002_rule.yaml +63 -0
  1300. package/core/dev/pipeline/archived/TASK_088_mobile_http_qa.yaml +121 -0
  1301. package/core/dev/pipeline/archived/TASK_089_agent_collaboration_topic.yaml +60 -0
  1302. package/core/dev/pipeline/archived/TASK_089_web_mode_monitor_fix.yaml +58 -0
  1303. package/core/dev/pipeline/archived/TASK_090_cli_detection_fix.yaml +61 -0
  1304. package/core/dev/pipeline/archived/TASK_090_ui_ux_lessons_topic.yaml +63 -0
  1305. package/core/dev/pipeline/archived/TASK_091_expandable_card_design.yaml +56 -0
  1306. package/core/dev/pipeline/archived/TASK_091_move_agent_section_components.yaml +65 -0
  1307. package/core/dev/pipeline/archived/TASK_092_expandable_card_component.yaml +60 -0
  1308. package/core/dev/pipeline/archived/TASK_092_refactor_agent_create_wizard.yaml +84 -0
  1309. package/core/dev/pipeline/archived/TASK_093_taskcard_detail_integration.yaml +57 -0
  1310. package/core/dev/pipeline/archived/TASK_093_update_barrel_exports_test.yaml +84 -0
  1311. package/core/dev/pipeline/archived/TASK_094_ai_agent_generation_enhancement.yaml +115 -0
  1312. package/core/dev/pipeline/archived/TASK_094_proposalcard_detail_integration.yaml +55 -0
  1313. package/core/dev/pipeline/archived/TASK_095_preview_fixes.yaml +129 -0
  1314. package/core/dev/pipeline/archived/TASK_095_single_agent_task_selector.yaml +60 -0
  1315. package/core/dev/pipeline/archived/TASK_096_agents_page_ux_improvements.yaml +163 -0
  1316. package/core/dev/pipeline/archived/TASK_096_single_agent_status_sync.yaml +60 -0
  1317. package/core/dev/pipeline/archived/TASK_097_local_claude_error_logging.yaml +59 -0
  1318. package/core/dev/pipeline/archived/TASK_097_single_agent_monitor_events.yaml +59 -0
  1319. package/core/dev/pipeline/archived/TASK_098_product_manager_team_phase2.yaml +60 -0
  1320. package/core/dev/pipeline/archived/TASK_098_single_agent_refactor_test.yaml +58 -0
  1321. package/core/dev/pipeline/archived/TASK_099_backend_team_creation.yaml +258 -0
  1322. package/core/dev/pipeline/archived/TASK_100_frontend_team_creation.yaml +61 -0
  1323. package/core/dev/pipeline/archived/TASK_101_orchestrator_system.yaml +403 -0
  1324. package/core/dev/pipeline/archived/TASK_102_designer_sre_teams.yaml +90 -0
  1325. package/core/dev/pipeline/archived/TASK_103_integration_testing.yaml +86 -0
  1326. package/core/dev/pipeline/archived/TASK_128_integration_testing.yaml +83 -0
  1327. package/core/dev/pipeline/archived/TASK_134_cortex_v2_phase3_file_watcher.yaml +65 -0
  1328. package/core/dev/pipeline/archived/TASK_134_team_http_api.yaml +96 -0
  1329. package/core/dev/pipeline/archived/TASK_135_cortex_v2_phase3_git_monitor.yaml +63 -0
  1330. package/core/dev/pipeline/archived/TASK_136_cortex_v2_phase3_change_analyzer.yaml +69 -0
  1331. package/core/dev/pipeline/archived/TASK_137_cortex_v2_phase3_cache_invalidator.yaml +67 -0
  1332. package/core/dev/pipeline/archived/TASK_137_team_orchestrator_integration.yaml +93 -0
  1333. package/core/dev/pipeline/archived/TASK_138_cortex_v2_phase3_distributed_cache.yaml +68 -0
  1334. package/core/dev/pipeline/archived/TASK_138_matrix_folder_structure.yaml +61 -0
  1335. package/core/dev/pipeline/archived/TASK_139_cortex_v2_phase3_cache_prewarming.yaml +65 -0
  1336. package/core/dev/pipeline/archived/TASK_146_unified_documentation.yaml +141 -0
  1337. package/core/dev/pipeline/archived/TASK_147_pipeline_execution_rules.yaml +76 -0
  1338. package/core/dev/pipeline/archived/TASK_148_update_claude_md.yaml +77 -0
  1339. package/core/dev/pipeline/archived/TASK_149_fix_nextjs_mdx_serialization.yaml +63 -0
  1340. package/core/dev/pipeline/archived/TASK_149_update_team_docs.yaml +77 -0
  1341. package/core/dev/pipeline/archived/TASK_150_pipeline_workflow_guide.yaml +82 -0
  1342. package/core/dev/pipeline/archived/TASK_164_fix_smart_context_switch.yaml +49 -0
  1343. package/core/dev/pipeline/archived/TASK_165_ctags_symbol_index.yaml +53 -0
  1344. package/core/dev/pipeline/archived/TASK_166_symbol_search_mcp_tool.yaml +48 -0
  1345. package/core/dev/pipeline/archived/TASK_167_llm_module_summary.yaml +55 -0
  1346. package/core/dev/pipeline/archived/TASK_168_map_reduce_context.yaml +51 -0
  1347. package/core/dev/pipeline/archived/TASK_169_v2_integration_testing.yaml +52 -0
  1348. package/core/dev/pipeline/backups/archive_2026-02-04T22-07-22/TASK_QUEUE_backup.json +95 -0
  1349. package/core/dev/pipeline/docs/multi-agent-coordination.md +446 -0
  1350. package/core/dev/pipeline/docs/overview.md +406 -0
  1351. package/core/dev/pipeline/docs/task-management.md +412 -0
  1352. package/core/dev/pipeline/docs/workflows.md +32 -0
  1353. package/core/dev/pipeline/orchestrator/schemas/task_queue_schema.json +58 -0
  1354. package/core/dev/pipeline/orchestrator/schemas/task_schema.json +139 -0
  1355. package/core/dev/pipeline/plans/PLAN_task_cli_refactor.md +281 -0
  1356. package/core/dev/pipeline/tasks/.gitkeep +1 -0
  1357. package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -0
  1358. package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +128 -0
  1359. package/core/dev/plans/FORMATION_RETIREMENT_PLAN.md +313 -0
  1360. package/core/dev/plans/KUZU_REPLACEMENT_PLAN.md +126 -0
  1361. package/core/dev/schemas/PRODUCT_SPEC.example.yaml +382 -0
  1362. package/core/dev/schemas/PRODUCT_SPEC.schema.json +800 -0
  1363. package/core/dev/schemas/README.md +517 -0
  1364. package/core/dev/schemas/TASK.schema.json +226 -0
  1365. package/core/dev/schemas/dataflows_ground_truth.yaml +173 -0
  1366. package/core/dev/schemas/dataflows_spec.yaml +160 -0
  1367. package/core/dev/schemas/extends_ground_truth.yaml +66 -0
  1368. package/core/dev/schemas/extends_spec.yaml +121 -0
  1369. package/core/dev/schemas/implements_ground_truth.yaml +77 -0
  1370. package/core/dev/schemas/implements_spec.yaml +140 -0
  1371. package/core/dev/scripts/CRASH_DIAGNOSIS.md +47 -0
  1372. package/core/dev/scripts/README.md +142 -0
  1373. package/core/dev/scripts/RUN_ONCE.bat +40 -0
  1374. package/core/dev/scripts/START_DEV_AGENT.bat +63 -0
  1375. package/core/dev/scripts/START_MULTI_AGENT.bat +80 -0
  1376. package/core/dev/scripts/check-alaya-report-isolation.js +255 -0
  1377. package/core/dev/scripts/check-file-size.js +268 -0
  1378. package/core/dev/scripts/ci-feedback.js +251 -0
  1379. package/core/dev/scripts/clean-verify-targets.js +132 -0
  1380. package/core/dev/scripts/cleanup-queue.js +184 -0
  1381. package/core/dev/scripts/cleanup_queue.js +86 -0
  1382. package/core/dev/scripts/dev_agent_scheduler.ps1 +536 -0
  1383. package/core/dev/scripts/dispatch-stats.js +384 -0
  1384. package/core/dev/scripts/dispatch-stats.test.js +162 -0
  1385. package/core/dev/scripts/gc-agent-worktrees.js +461 -0
  1386. package/core/dev/scripts/gc-agent-worktrees.test.js +48 -0
  1387. package/core/dev/scripts/hooks/README.md +148 -0
  1388. package/core/dev/scripts/init_git.ps1 +396 -0
  1389. package/core/dev/scripts/init_git.sh +337 -0
  1390. package/core/dev/scripts/init_project.ps1 +851 -0
  1391. package/core/dev/scripts/init_project.sh +477 -0
  1392. package/core/dev/scripts/js-yaml-resolution.test.js +48 -0
  1393. package/core/dev/scripts/kenaz-session.js +354 -0
  1394. package/core/dev/scripts/kenaz-session.test.js +234 -0
  1395. package/core/dev/scripts/legacy/README.md +39 -0
  1396. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -0
  1397. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -0
  1398. package/core/dev/scripts/matrix/CLI_README.md +214 -0
  1399. package/core/dev/scripts/matrix/README.md +310 -0
  1400. package/core/dev/scripts/matrix/package-lock.json +4266 -0
  1401. package/core/dev/scripts/matrix/package.json +55 -0
  1402. package/core/dev/scripts/matrix/src/abbreviations.ts +295 -0
  1403. package/core/dev/scripts/matrix/src/cli/commands/convert.ts +211 -0
  1404. package/core/dev/scripts/matrix/src/cli/commands/create.ts +95 -0
  1405. package/core/dev/scripts/matrix/src/cli/commands/delete.ts +102 -0
  1406. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -0
  1407. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -0
  1408. package/core/dev/scripts/matrix/src/cli/commands/query.ts +261 -0
  1409. package/core/dev/scripts/matrix/src/cli/commands/read.ts +77 -0
  1410. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -0
  1411. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -0
  1412. package/core/dev/scripts/matrix/src/cli/commands/update.ts +147 -0
  1413. package/core/dev/scripts/matrix/src/cli/commands/validate.ts +275 -0
  1414. package/core/dev/scripts/matrix/src/cli/index.ts +350 -0
  1415. package/core/dev/scripts/matrix/src/cli/utils/file-handler.ts +72 -0
  1416. package/core/dev/scripts/matrix/src/cli/utils/logger.ts +65 -0
  1417. package/core/dev/scripts/matrix/src/index.ts +62 -0
  1418. package/core/dev/scripts/matrix/src/indexer/tag-indexer.ts +165 -0
  1419. package/core/dev/scripts/matrix/src/parser.ts +281 -0
  1420. package/core/dev/scripts/matrix/src/serializer.ts +151 -0
  1421. package/core/dev/scripts/matrix/src/validator.ts +301 -0
  1422. package/core/dev/scripts/matrix/tests/abbreviations.test.ts +122 -0
  1423. package/core/dev/scripts/matrix/tests/cli.test.ts +177 -0
  1424. package/core/dev/scripts/matrix/tests/convert.test.ts +143 -0
  1425. package/core/dev/scripts/matrix/tests/parser.test.ts +203 -0
  1426. package/core/dev/scripts/matrix/tests/query.test.ts +85 -0
  1427. package/core/dev/scripts/matrix/tests/serializer.test.ts +197 -0
  1428. package/core/dev/scripts/matrix/tests/validate.test.ts +141 -0
  1429. package/core/dev/scripts/matrix/tests/validator.test.ts +167 -0
  1430. package/core/dev/scripts/matrix/tsconfig.json +20 -0
  1431. package/core/dev/scripts/matrix/types/index.d.ts +111 -0
  1432. package/core/dev/scripts/matrix/types/index.d.ts.map +1 -0
  1433. package/core/dev/scripts/matrix/types/index.js +8 -0
  1434. package/core/dev/scripts/matrix/types/index.js.map +1 -0
  1435. package/core/dev/scripts/matrix/types/index.ts +124 -0
  1436. package/core/dev/scripts/memory-guard-eval.js +330 -0
  1437. package/core/dev/scripts/memory-usage-report.js +152 -0
  1438. package/core/dev/scripts/migrate-task-dependencies.js +198 -0
  1439. package/core/dev/scripts/multi_agent_scheduler.ps1 +2021 -0
  1440. package/core/dev/scripts/multi_agent_scheduler.sh +1344 -0
  1441. package/core/dev/scripts/probe/ansuz-scaffold-ablation.js +579 -0
  1442. package/core/dev/scripts/probe/ansuz-scaffold-ablation.test.js +244 -0
  1443. package/core/dev/scripts/probe/hippocampus-concurrency.js +247 -0
  1444. package/core/dev/scripts/probe/hook-latency-probe.js +315 -0
  1445. package/core/dev/scripts/probe/hook-latency-probe.test.js +105 -0
  1446. package/core/dev/scripts/probe/prediction-signal-auc.js +483 -0
  1447. package/core/dev/scripts/probe/prediction-signal-auc.test.js +200 -0
  1448. package/core/dev/scripts/reset_tasks.js +99 -0
  1449. package/core/dev/scripts/run-rust-tests.js +207 -0
  1450. package/core/dev/scripts/run-rust-tests.test.js +154 -0
  1451. package/core/dev/scripts/run_once.sh +47 -0
  1452. package/core/dev/scripts/rust-verify-worktree.js +591 -0
  1453. package/core/dev/scripts/rust-verify-worktree.test.js +574 -0
  1454. package/core/dev/scripts/seh_probe.rs +26 -0
  1455. package/core/dev/scripts/setup_mcp.ps1 +192 -0
  1456. package/core/dev/scripts/setup_mcp.sh +159 -0
  1457. package/core/dev/scripts/setup_rag.ps1 +304 -0
  1458. package/core/dev/scripts/setup_rag.sh +230 -0
  1459. package/core/dev/scripts/start_dev_agent.sh +137 -0
  1460. package/core/dev/scripts/start_multi_agent.sh +41 -0
  1461. package/core/dev/scripts/sync_task_queue.ps1 +505 -0
  1462. package/core/dev/scripts/sync_task_queue.sh +392 -0
  1463. package/core/dev/scripts/task1961/RUNBOOK.md +300 -0
  1464. package/core/dev/scripts/task1961/fixtures/never-listens.server.js +54 -0
  1465. package/core/dev/scripts/task1961/fixtures/slow-serve.server.js +134 -0
  1466. package/core/dev/scripts/task1961/run-fixture.js +659 -0
  1467. package/core/dev/scripts/task1961/selftest.js +247 -0
  1468. package/core/dev/scripts/test-pty-ws-tunnel.js +229 -0
  1469. package/core/dev/scripts/validate_pipeline.js +197 -0
  1470. package/core/dev/scripts/verify-isbar-capture.js +100 -0
  1471. package/core/dev/scripts/verify-target-dir.js +69 -0
  1472. package/core/dev/scripts/verify-task-1839.js +141 -0
  1473. package/core/dev/specs/agent-connect/PROTOCOL.md +177 -0
  1474. package/core/dev/specs/agent-connect/SDK_GUIDE.md +915 -0
  1475. package/core/dev/specs/agent-connect/examples/minimal-site/README.md +137 -0
  1476. package/core/dev/specs/agent-connect/examples/minimal-site/package.json +12 -0
  1477. package/core/dev/specs/agent-connect/examples/minimal-site/public/index.html +116 -0
  1478. package/core/dev/specs/agent-connect/examples/minimal-site/public/install-redirect.js +73 -0
  1479. package/core/dev/specs/agent-connect/examples/minimal-site/server.js +516 -0
  1480. package/core/dev/specs/agent-connect/examples/minimal-site/test/smoke.test.mjs +263 -0
  1481. package/core/dev/specs/agent-connect/history/DEFECT_REPORT_kenaz-agent-hub_2026-09-09_v0.4-empty-prompt-no-first-turn.md +77 -0
  1482. package/core/dev/specs/agent-connect/history/DEFECT_REPORT_kenaz-agent-hub_2026-09-09_v0.4-prompt-mojibake.md +90 -0
  1483. package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-07.md +61 -0
  1484. package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-08_relay-v1-conformance.md +76 -0
  1485. package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-09_v0.4-session-channel.md +87 -0
  1486. package/core/dev/specs/agent-connect/history/FLOWPIX_REPLY_agent-hub_2026-09-08_relay-v1-answers.md +68 -0
  1487. package/core/dev/specs/agent-connect/history/FLOWPIX_bridge_CONTRACT.md +642 -0
  1488. package/core/dev/specs/agent-connect/history/FLOWPIX_spikes_relay_spike_relay.mjs +244 -0
  1489. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-07.md +77 -0
  1490. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_direction-change-v0.3.md +44 -0
  1491. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_relay-v1-answers-ack.md +37 -0
  1492. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_relay-v1-conformance.md +56 -0
  1493. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_annotations-install-coldstart.md +44 -0
  1494. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_empty-prompt-first-turn.md +56 -0
  1495. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_prompt-mojibake-fixed.md +48 -0
  1496. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_session-channel.md +57 -0
  1497. package/core/dev/specs/agent-connect/history/PROTOCOL_v0.3_snapshot.md +379 -0
  1498. package/core/dev/specs/agent-connect/history/README.md +47 -0
  1499. package/core/dev/specs/peer/CODEX_INTERACTIVE_PUSH.md +65 -0
  1500. package/core/dev/specs/peer/CODEX_PEER_PULL_SPIKE.md +54 -0
  1501. package/core/dev/specs/peer/MODS_SPIKE.md +133 -0
  1502. package/core/dev/specs/peer/PER_PROJECT_ADDRESSING.md +143 -0
  1503. package/core/dev/specs/peer/SWARM_PROTOCOLS.md +256 -0
  1504. package/core/dev/specs/peer/codex-hook-shim-peer.test.js +101 -0
  1505. package/core/dev/specs/peer/codex-push-launcher.test.js +142 -0
  1506. package/core/dev/specs/peer/setup-codex-hooks.test.js +22 -0
  1507. package/core/dev/specs/peer-ansuz/DELIVERY_SPIKE.md +397 -0
  1508. package/core/dev/specs/peer-ansuz/DESIGN.md +109 -0
  1509. package/core/dev/specs/peer-ansuz/spike/claude_poke.js +129 -0
  1510. package/core/dev/specs/peer-ansuz/spike/claude_session_start_hook.js +37 -0
  1511. package/core/dev/specs/peer-ansuz/spike/claude_settings_hook.json +14 -0
  1512. package/core/dev/specs/peer-ansuz/spike/codex_appserver_client.js +105 -0
  1513. package/core/dev/specs/peer-ansuz/spike/codex_appserver_query.js +29 -0
  1514. package/core/dev/specs/peer-ansuz/spike/extract_timeline.js +26 -0
  1515. package/core/dev/specs/synapse/SYMBOL_IDENTITY.md +305 -0
  1516. package/core/dev/spikes/A4-cypher-varlen.md +176 -0
  1517. package/core/dev/spikes/bloat-rounds.sh +25 -0
  1518. package/core/dev/spikes/mods/clean-claude.js +9 -0
  1519. package/core/dev/spikes/mods/drive-probe.js +29 -0
  1520. package/core/dev/spikes/mods/e2e-peer.js +71 -0
  1521. package/core/dev/spikes/mods/evidence/e2e-peer-control.json +17 -0
  1522. package/core/dev/spikes/mods/evidence/e2e-peer-mod.json +17 -0
  1523. package/core/dev/spikes/mods/evidence/probe-006a6e65-d807-4bc1-b648-fd7d1edfe0b4.jsonl +4 -0
  1524. package/core/dev/spikes/mods/evidence/probe-cebdf1f7-f9c4-47a8-83cd-7c0786177404.jsonl +12 -0
  1525. package/core/dev/spikes/mods/evidence/probe-eb21147d-10ce-4c32-93ed-37ca37d88d5a.jsonl +11 -0
  1526. package/core/dev/spikes/mods/evidence/stream-cebdf1f7.jsonl +18 -0
  1527. package/core/dev/spikes/mods/kenaz-mod-probe/.claude-plugin/plugin.json +1 -0
  1528. package/core/dev/spikes/mods/kenaz-mod-probe/hooks/hooks.json +1 -0
  1529. package/core/dev/spikes/mods/kenaz-mod-probe/hooks/register.ts +65 -0
  1530. package/core/dev/spikes/mods/kenaz-mod-probe/tsconfig.json +3 -0
  1531. package/core/k-cli/README.md +423 -0
  1532. package/core/k-cli/package.json +21 -0
  1533. package/core/k-cli/templates/k-cli.md +91 -0
  1534. package/core/k-cli/test/TEST_SPEC.md +134 -0
  1535. package/core/k-cli/test/run.sh +469 -0
  1536. package/core/plugins/kenaz/.claude-plugin/plugin.json +1 -0
  1537. package/core/plugins/kenaz/.mcp.json +5 -0
  1538. package/core/plugins/kenaz/INVENTORY.md +48 -0
  1539. package/core/plugins/kenaz/README.md +55 -0
  1540. package/core/plugins/kenaz/commands/Amelia.md +5 -0
  1541. package/core/plugins/kenaz/commands/Ansuz.md +38 -0
  1542. package/core/plugins/kenaz/commands/architect.md +24 -0
  1543. package/core/plugins/kenaz/commands/incident.md +15 -0
  1544. package/core/plugins/kenaz/commands/kenaz.md +12 -0
  1545. package/core/plugins/kenaz/commands/review.md +14 -0
  1546. package/core/plugins/kenaz/commands/security.md +15 -0
  1547. package/core/plugins/kenaz/commands/spec.md +22 -0
  1548. package/core/plugins/kenaz/commands/strategy.md +141 -0
  1549. package/core/plugins/kenaz/commands//347/224/242/345/223/201.md +15 -0
  1550. package/core/plugins/kenaz/commands//350/250/255/350/250/210.md +14 -0
  1551. package/core/plugins/kenaz/commands//350/251/225/344/274/260.md +16 -0
  1552. package/core/plugins/kenaz/guidance.md +5 -0
  1553. package/core/plugins/kenaz/hooks/core-resolve.js +230 -0
  1554. package/core/plugins/kenaz/hooks/hooks.json +31 -0
  1555. package/core/plugins/kenaz/hooks/kenaz-hook.js +112 -0
  1556. package/core/plugins/kenaz/hooks/mode.js +39 -0
  1557. package/core/plugins/kenaz/scripts/kenaz-mode.js +46 -0
  1558. package/core/plugins/kenaz/scripts/kenaz.test.js +123 -0
  1559. package/core/plugins/kenaz/scripts/sync.js +45 -0
  1560. package/core/plugins/kenaz/scripts/ygg.js +157 -0
  1561. package/core/plugins/kenaz/skills/adapt/SKILL.md +199 -0
  1562. package/core/plugins/kenaz/skills/animate/SKILL.md +190 -0
  1563. package/core/plugins/kenaz/skills/architect_agent/SKILL.md +408 -0
  1564. package/core/plugins/kenaz/skills/assessment_agent/SKILL.md +289 -0
  1565. package/core/plugins/kenaz/skills/audit/SKILL.md +129 -0
  1566. package/core/plugins/kenaz/skills/backend_agent/SKILL.md +407 -0
  1567. package/core/plugins/kenaz/skills/backend_engineer/SKILL.md +788 -0
  1568. package/core/plugins/kenaz/skills/bolder/SKILL.md +132 -0
  1569. package/core/plugins/kenaz/skills/clarify/SKILL.md +180 -0
  1570. package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +305 -0
  1571. package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +85 -0
  1572. package/core/plugins/kenaz/skills/colorize/SKILL.md +158 -0
  1573. package/core/plugins/kenaz/skills/critique/SKILL.md +118 -0
  1574. package/core/plugins/kenaz/skills/delight/SKILL.md +317 -0
  1575. package/core/plugins/kenaz/skills/designer/SKILL.md +845 -0
  1576. package/core/plugins/kenaz/skills/dev_agent/SKILL.md +503 -0
  1577. package/core/plugins/kenaz/skills/developer/SKILL.md +792 -0
  1578. package/core/plugins/kenaz/skills/distill/SKILL.md +137 -0
  1579. package/core/plugins/kenaz/skills/extract/SKILL.md +95 -0
  1580. package/core/plugins/kenaz/skills/feature_agent/SKILL.md +303 -0
  1581. package/core/plugins/kenaz/skills/frontend-design/SKILL.md +127 -0
  1582. package/core/plugins/kenaz/skills/frontend-design/reference/color-and-contrast.md +132 -0
  1583. package/core/plugins/kenaz/skills/frontend-design/reference/interaction-design.md +123 -0
  1584. package/core/plugins/kenaz/skills/frontend-design/reference/motion-design.md +99 -0
  1585. package/core/plugins/kenaz/skills/frontend-design/reference/responsive-design.md +114 -0
  1586. package/core/plugins/kenaz/skills/frontend-design/reference/spatial-design.md +100 -0
  1587. package/core/plugins/kenaz/skills/frontend-design/reference/typography.md +131 -0
  1588. package/core/plugins/kenaz/skills/frontend-design/reference/ux-writing.md +107 -0
  1589. package/core/plugins/kenaz/skills/frontend_agent/SKILL.md +327 -0
  1590. package/core/plugins/kenaz/skills/harden/SKILL.md +358 -0
  1591. package/core/plugins/kenaz/skills/incident_response/SKILL.md +315 -0
  1592. package/core/plugins/kenaz/skills/normalize/SKILL.md +67 -0
  1593. package/core/plugins/kenaz/skills/onboard/SKILL.md +243 -0
  1594. package/core/plugins/kenaz/skills/optimize/SKILL.md +269 -0
  1595. package/core/plugins/kenaz/skills/polish/SKILL.md +202 -0
  1596. package/core/plugins/kenaz/skills/product_manager/SKILL.md +1091 -0
  1597. package/core/plugins/kenaz/skills/qa_engineer/SKILL.md +1269 -0
  1598. package/core/plugins/kenaz/skills/quieter/SKILL.md +118 -0
  1599. package/core/plugins/kenaz/skills/security_agent/SKILL.md +312 -0
  1600. package/core/plugins/kenaz/skills/spec_optimizer/SKILL.md +299 -0
  1601. package/core/plugins/kenaz/skills/sre_agent/SKILL.md +1347 -0
  1602. package/core/plugins/kenaz/skills/ui-ux-pro-max/SKILL.md +377 -0
  1603. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -0
  1604. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -0
  1605. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -0
  1606. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -0
  1607. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +97 -0
  1608. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  1609. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  1610. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  1611. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  1612. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  1613. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  1614. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  1615. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  1616. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  1617. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  1618. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  1619. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  1620. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  1621. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  1622. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -0
  1623. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +58 -0
  1624. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  1625. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  1626. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  1627. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -0
  1628. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  1629. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -0
  1630. package/core/plugins/kenaz-peer/.claude-plugin/plugin.json +1 -0
  1631. package/core/plugins/kenaz-peer/hooks/hooks.json +1 -0
  1632. package/core/plugins/kenaz-peer/hooks/register.test.ts +62 -0
  1633. package/core/plugins/kenaz-peer/hooks/register.ts +146 -0
  1634. package/core/plugins/kenaz-peer/tsconfig.json +3 -0
  1635. package/lib/cli.js +251 -0
  1636. package/lib/cli.test.js +59 -0
  1637. package/lib/dashboard.js +80 -0
  1638. package/lib/dashboard.test.js +59 -0
  1639. package/package.json +28 -4
  1640. package/vendor/install-kenaz-cli.js +268 -0
  1641. package/README.md +0 -3
@@ -0,0 +1,3660 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Formation Post-Complete
4
+ * 任務完成後的自動化處理:Archive 清理 + Git 分支管理
5
+ *
6
+ * 支援多專案:projectPath 指定子專案時,所有 git/archive/task 操作
7
+ * 都在子專案的 repo 中執行(而非 KenazAI core)。
8
+ *
9
+ * 流程:
10
+ * 1. Archive 檢查:done 任務超過 N 個時,將最舊的移到 archived/
11
+ * 2. Git 分支:建立 task branch → commit → merge 到用戶分支
12
+ *
13
+ * Usage:
14
+ * node .formation/tools/post-complete.js --task=TASK_XXX
15
+ * node .formation/tools/post-complete.js --task=TASK_XXX --project-path=/path/to/sub
16
+ * node .formation/tools/post-complete.js --task=TASK_XXX --keep=10
17
+ * node .formation/tools/post-complete.js --task=TASK_XXX --skip-git
18
+ * node .formation/tools/post-complete.js --task=TASK_XXX --skip-archive
19
+ * node .formation/tools/post-complete.js --task=TASK_XXX --dry-run
20
+ * node .formation/tools/post-complete.js --task=TASK_XXX \
21
+ * --test-cmd "cd dashboard && npm test" \
22
+ * --test-cmd "cd dashboard/pty-server && cargo test"
23
+ */
24
+
25
+ /* =============================================================================
26
+ * STRUCTURAL AUDIT — post-complete pipeline (TASK_1137, 2026-04-11)
27
+ * ============================================================================
28
+ *
29
+ * Context: ~5939-line orchestrator accumulated 13 incremental bug fixes
30
+ * (B1-B3, B5-B13) across B1-B14 fix chain sessions. Health score stuck at
31
+ * D (52.1-52.4) despite fixes. High fix velocity + stagnant health = symptom-
32
+ * patching without addressing root causes. This audit maps the full flow,
33
+ * catalogs each fix, analyzes concurrency correctness, and proposes a
34
+ * prioritized refactor plan.
35
+ *
36
+ * ─────────────────────────────────────────────────────────────────────────────
37
+ * SECTION A — EXECUTION FLOW MAP
38
+ * ─────────────────────────────────────────────────────────────────────────────
39
+ *
40
+ * A.1 postComplete() — top-level orchestrator (L4865)
41
+ *
42
+ * | Step | Description | Branch Condition | Error Handler | Failure Mode |
43
+ * |-------|------------------------------------------|-------------------------------------------|----------------------------|-------------------------------------|
44
+ * | GC | gcStaleWorktrees() | always | try/catch (non-fatal) | orphan worktrees accumulate |
45
+ * | Guard | Duplicate-suppress guard | recent worktree pass for same taskId (<2m) | try/catch (non-fatal) | double execution |
46
+ * | 0 | Hippocampus episode start | taskId && !dryRun | try/catch (non-fatal) | episodes not started |
47
+ * | 1 | archiveCheck() | !skipArchive | inline logging | none — optional |
48
+ * | 2 | gitBranchAndMerge() or _gitMergeFromWorktree() | !skipGit && taskId | throws (caught at CLI) | task stuck in active |
49
+ * | 2.5a | Yggdrasil commit-sync (fire-and-forget) | gitResult.committed && !dryRun | catch (silent) | sync missed silently |
50
+ * | 2.6a | Passive rune extraction | gitResult.committed && !dryRun | catch (silent) | runes not extracted |
51
+ * | 2.7 | Outcome recording + surprise detection | !dryRun | catch (silent) | no outcome metrics recorded |
52
+ * | 2.8 | Skill matrix update | !dryRun | catch (log warning) | metrics not updated |
53
+ * | 2.9 | Cognitive attribution | !dryRun && mergeSucceeded | catch (silent) | attribution missed |
54
+ * | 2.5b | Complete/block task via task-api | merge success or failure | spawnSync; safety net move | task stuck in active |
55
+ * | 2.5c | Record AC failures (ACCEPTANCE_FAILED) | acFailures.length > 0 | catch (log) | AC failures invisible |
56
+ * | 2.5d | Safety net: force-move to done | merge succeeded but complete() failed | spawnSync | task stays in active |
57
+ * | 2.5e | Passive rune extraction (completed) | result.taskCompleted && !dryRun | catch (silent) | completion runes not tagged |
58
+ * | 2.6b | Ansuz inbox signal | result.taskCompleted && !dryRun | catch (silent) | Ansuz not notified |
59
+ * | 2.6c | Relay task_completed event (TASK_1409) | result.taskCompleted && !dryRun && relay | catch (silent) | PM inbox not notified cross-node |
60
+ * | 3 | Memory agent onTaskCompleted/onError | memoryAgent && taskId | catch (log warning) | memory event not queued |
61
+ * | 3b | Provenance runes (synapse_inscribe) | !dryRun && git.merged | catch (silent) | provenance not tagged |
62
+ * | 4 | Guardian health check + KEEP/REVERT | taskId (always) | catch (log) | no feedback signal for loop |
63
+ * | 6 | Hippocampus episode close | hippocampusEpisodeId set | catch (log warning) | episode not closed properly |
64
+ *
65
+ * A.2 gitBranchAndMerge() — serial mode (L2344)
66
+ *
67
+ * | Step | Description | Branch Condition | Error Handler | Failure Mode |
68
+ * |---------|--------------------------------------------|----------------------------------------------|-----------------------------|------------------------------------|
69
+ * | Worktree| Delegates to _gitMergeFromWorktree() | worktreePath truthy | delegated | — |
70
+ * | 0 | Initial commit for empty repo | !hasInitialCommit() | catch (returns) | hard fail |
71
+ * | 0.1 | Check for changes (scope-aware, TASK_2029) | !hasChangesInScope() / !hasChanges() | isMemoryOnlyTask() check | returns noChanges |
72
+ * | 0.2 | Scope paths safety guard | scope_paths not found on disk | warn + return | returns scopePathsMissing |
73
+ * | 0.2.1 | Preflight unmerged index check | getUnmergedPaths().length > 0 | return blockedReason | returns unmerged_index |
74
+ * | 0.2.2 | Acquire git lock | lock file exists (EEXIST) | PID liveness; timeout force | deadlock risk at timeout boundary |
75
+ * | 0.3 | Snapshot task files | always | inline (try/finally) | state corruption on branch change |
76
+ * | 0.4 | Pre-commit task YAML files | existingTaskDirs non-empty | throws=false | task files not tracked before stash|
77
+ * | 0.4b | Remove SQLite journal files | trackedJournals non-empty | throws=false | modify/delete conflicts at merge |
78
+ * | 0.45 | Revert runtime/binary files | RUNTIME_REVERT_PATHS exist | throws=false | merge conflicts on runtime files |
79
+ * | 0.5 | Stash dirty files (stash push -u) | always | throws=false | stash never created (silent) |
80
+ * | 1 | Create task branch (checkout -b) | branchExists? salvage/delete first (B1) | catch branchOpError | stuck on wrong branch |
81
+ * | 1.5 | Restore stash (stash pop) | didStash | 3-step recovery | changes lost if recovery fails |
82
+ * | 1.6 | Remove DB runtime files from index | dbFilesOnBranch non-empty | throws=false | modify/delete at merge |
83
+ * | 2 | Regression tests (PPL-113) | !skipTests | createRegressionFixTask() | creates FIX task, continues |
84
+ * | 2.1 | Stage + commit (gitAddSafe scope-aware) | always (scope or blanket) | catch; reset + return | returns uncommitted |
85
+ * | 2.2 | Scope verification (canary module) | taskScopePaths.length > 0 | log violations | violations visible but not blocking|
86
+ * | 2.2b | Staged scope coverage verify | taskScopePaths non-empty | reset + checkout + return | blocks commit if missing scope file|
87
+ * | 2.5 | Acceptance criteria verify (LLM) | !skipTests | createFixTasks(); indeterminate→report only | FIX tasks (fix-tagged: note marker only); timeout/parse=INDETERMINATE not pass |
88
+ * | 2.6 | Integration gap detection | !skipTests | catch (log) | wiring gaps not detected |
89
+ * | 3 | Checkout user branch | branchExists? create from baseBranch | throws | stuck on task branch |
90
+ * | 4 | Merge task branch → user branch | always | merge --abort + salvage | returns mergeConflict |
91
+ * | 4b | verify_merge_landed() | merged === true | ensureSalvageBranch() | false success without verification |
92
+ * | 5 | Stay on user branch | always | — | — |
93
+ * | finally | Branch recovery (3 attempts) + git lock | cur !== originalBranch | 3-attempt recovery | permanent branch drift on fail |
94
+ *
95
+ * A.3 _gitMergeFromWorktree() — parallel mode (L2908)
96
+ *
97
+ * | Step | Description | Branch Condition | Error Handler | Failure Mode |
98
+ * |-------|------------------------------------------------|------------------------------------------|-----------------------------|-----------------------------------|
99
+ * | 0 | Verify taskBranch exists in main repo | !branchExists(taskBranch) | recovery commit in worktree | returns missingTaskBranch |
100
+ * | 0.1 | Runtime-only smoke task check | runtimeOnlySmoke && clean worktree | cleanupWorktree() | returns memoryOnly |
101
+ * | 0.2 | Snapshot task files | always | inline (try/finally) | state corruption |
102
+ * | 0.3 | Rescue misplaced files (main repo → worktree) | mainStatus has M/? candidates in scope | catch (log) | agent work lost on worktree cleanup|
103
+ * | 0.4 | Scrub Codex 'claude' empty artifact | claude file exists && 0 bytes | catch (log) | artifact enters git index |
104
+ * | 0.5 | git add -A in worktree (unconditional) | always | — | — |
105
+ * | B10 | Unstage daemon-managed files | getDaemonPathsToUnstage() non-empty | throws=false | daemon noise enters commit |
106
+ * | 0.6 | Commit if staged diff non-empty | stagedAfter non-empty | catch "nothing to commit" | throws on real commit error |
107
+ * | Rebase| rebase --autostash onto userBranch | branchExists(userBranch) | abort; check conflict count | returns rebaseConflict or fallthrough|
108
+ * | Rev | Last-chance recovery commit | 0 unique commits but worktree has changes| catch (log) | recovery may fail silently |
109
+ * | Lock | acquireMergeLock() | always | timeout force-acquire | deadlock risk at timeout boundary |
110
+ * | MrgPre| Revert daemon-managed files in main repo | listDaemonManagedPaths() non-empty | throws=false | merge blocked by daemon drift |
111
+ * | MrgChk| Check unmerged index in main repo | getUnmergedPaths().length > 0 | releaseMergeLock(); return | returns unmerged_index |
112
+ * | Stash | Stash main repo dirty files | always | throws=false | stash not created (silent) |
113
+ * | Chk | Checkout userBranch in main repo | branchExists? create | throws | stuck on wrong branch |
114
+ * | DmPre | Revert DAEMON_STATE_FILES + WORKTREE_STATIC | iterate both lists | throws=false | merge blocked by daemon drift |
115
+ * | Merge | Merge taskBranch → userBranch (--no-ff) | always | merge --abort + salvage | returns mergeConflict |
116
+ * | 4b | verify_merge_landed() | merged === true | ensureSalvageBranch() | false success |
117
+ * | GC | cleanupWorktree() on success | verify_merge_landed === true | (inline) | worktree leaked on partial success|
118
+ * | finally| restoreTaskFiles + releaseMergeLock + stashPop | always | catch (log) | dirty main repo on failure |
119
+ *
120
+ * ─────────────────────────────────────────────────────────────────────────────
121
+ * SECTION B — B1-B14 FIX CATALOG
122
+ * ─────────────────────────────────────────────────────────────────────────────
123
+ *
124
+ * Note: B4 does not exist (numbering gap in original commit).
125
+ *
126
+ * | Fix | Code Location (file:line) | Classification | Rationale |
127
+ * |-------|--------------------------------------------------|-------------------|--------------------------------------------------------------------------------|
128
+ * | B1 | post-complete.js:2530-2550 | root-cause-fix | Prevents force-delete of salvageable agent commits; renames to _salvage_<ts> |
129
+ * | B2 | post-complete.js:656-694 (getPostCompleteChangedFiles) | symptom-patch | HEAD~1 for HEAD^1 Windows fix; correct but doesn't fix race that misattributes files |
130
+ * | B3 | post-complete.js:3928 | root-cause-fix | Dropped /m flag in outcome regex; prevents YAML corruption — real fix |
131
+ * | B4 | N/A (skipped in numbering) | N/A | Does not exist |
132
+ * | B5 | post-complete.js:2505-2517 (RUNTIME_REVERT_PATHS)| symptom-patch | Added turn_context_state.json for serial mode; doesn't generalize to new daemons|
133
+ * | B6 | post-complete.js:2527-2595 | symptom-patch | try/catch around branch ops to prevent stash leak; doesn't fix the race |
134
+ * | B7 | post-complete.js:3295-3299 (worktreeCheckoutRevertPaths) | symptom-patch | turn_context_state.json for worktree mode; same issue as B5, different path |
135
+ * | B8 | post-complete.js:1260-1325 (DAEMON_STATE_FILES) | symptom-patch | Expanded hardcoded list from 1→22 daemon paths; list is still finite/incomplete|
136
+ * | B9 | post-complete.js:660-710 | symptom-patch | taskBranch diff range when merge fails; masks race not fix it |
137
+ * | B10 | post-complete.js:3135-3157 | symptom-patch | Unstage daemon files after git add -A; downstream of hardcoded list root cause |
138
+ * | B11 | post-complete.js:4634-4696 (gcStaleWorktrees) | symptom-patch | GC orphan worktrees; cleans aftermath, doesn't prevent creation |
139
+ * | B12 | task-api.js (YAML null-safe + filename id fallback) | symptom-patch | Defensive coding; no structural change to loading logic |
140
+ * | B13 | post-complete.js:1415-1493 (detectAllTestCommands) | root-cause-fix | Scope-aware Rust test skip; solves the false cargo failure root cause |
141
+ * | B14+ | post-complete.js:3182-3400 (TASK_1111/1112/1113/1130/1132) | mixed | rebase --autostash replaces manual stash+rebase; autostash is correct approach but rebase still runs outside merge lock |
142
+ * | B15 | post-complete.js (TASK_1416 D.5 pre-merge gate) | root-cause-fix | [REMOVED in TASK_2341: health no longer gates or reverts] git reset HEAD~1 cascade bug (rune_867d6afe). Replaced by D.5 pre-merge health gate in git-merge.js. Post-merge REVERT feature-flagged behind HEIMDALL_POSTMERGE_REVERT=true; if re-enabled uses git revert -m 1 (non-destructive). ansuz_session_handoff.json added to daemon-manifest.yaml (TASK_1413 conflict source). |
143
+ *
144
+ * Classification summary: 4 root-cause-fixes (B1, B3, B13, B15), 10 symptom-patches (B2, B5-B12, B14+).
145
+ * Health D-grade plateau explained: symptom patches outnumber root-cause fixes 10:3.
146
+ *
147
+ * ─────────────────────────────────────────────────────────────────────────────
148
+ * SECTION C — CONCURRENCY ANALYSIS
149
+ * ─────────────────────────────────────────────────────────────────────────────
150
+ *
151
+ * C.1 Git lock race at timeout boundary (CRITICAL)
152
+ * File:line acquireGitLock():L959-965
153
+ * Pattern: Timeout forces lock acquisition via unlink+writeFileSync.
154
+ * Race: Two concurrent post-completes both time out (e.g., under system
155
+ * load), both call unlink, then both call writeFileSync in rapid
156
+ * succession — neither wins atomically. writeFileSync is NOT atomic
157
+ * on Windows; the second write overwrites the first.
158
+ * Result: Both processes believe they hold the lock simultaneously → index
159
+ * corruption possible if both write task branches and try to checkout.
160
+ * Evidence: acquireGitLock():L959 "Timed out — force acquire".
161
+ *
162
+ * C.2 Rebase outside merge lock — stale rebase window (HIGH)
163
+ * File:line _gitMergeFromWorktree():L3199-3256 (rebase), L3292-3294 (lock acquire)
164
+ * Pattern: rebase --autostash runs BEFORE acquireMergeLock().
165
+ * Race: Peer task merges new commits into userBranch between rebase start
166
+ * and merge lock acquisition. When current task finally merges, its
167
+ * rebase is onto an older userBranch HEAD → merge may see "new file
168
+ * in peer's commit not present in our rebased tree" → false conflict.
169
+ * Result: Merge failures that look like genuine conflicts but are actually
170
+ * sequencing artifacts. Estimated ~20-30% residual parallel failures.
171
+ * Evidence: L3182-3198 (rebase rationale), L3292 (lock acquire).
172
+ *
173
+ * C.3 DAEMON_STATE_FILES hardcoded list — incomplete coverage (HIGH)
174
+ * File:line L1260-1294 (DAEMON_STATE_FILES), L1296-1325 (WORKTREE_STATIC_DAEMON_PATHS)
175
+ * Pattern: Explicit file enumeration for daemon noise filtering.
176
+ * Race: New daemon-managed files added in future sessions (e.g., new
177
+ * specialist/ subsystems) are NOT in either list. They pass through
178
+ * unstage check (getDaemonPathsToUnstage) undetected until another
179
+ * B8-style fix is added.
180
+ * Result: New daemon file causes recurring merge conflicts until explicitly
181
+ * listed. Permanent low-level structural debt.
182
+ * Evidence: L1274 (TASK_1112 follow-up), L1280 (TASK_1130), L1284 (TASK_1130 audit).
183
+ *
184
+ * C.4 ansuz_inbox.json non-atomic write (MEDIUM)
185
+ * File:line postComplete():L5321-5332
186
+ * Pattern: readFileSync → push → writeFileSync (no tmp+rename).
187
+ * Race: Two concurrent post-completes both read the same inbox, both push
188
+ * their entry, both write — last writer wins, first entry lost.
189
+ * Result: Ansuz misses task_completed notifications under parallel dispatch.
190
+ * Compare: ambient_signals.json at L3890-3892 correctly uses tmp+rename.
191
+ *
192
+ * C.5 post-complete-guard.json non-atomic read-modify-write (LOW)
193
+ * File:line postComplete():L4892-4908
194
+ * Pattern: readFileSync → mutate → writeFileSync (no tmp+rename).
195
+ * Race: Same as C.4 but for deduplication guard state. Low impact because
196
+ * the guard only fires within 120s window and failures are silent.
197
+ * Evidence: L4906 direct writeFileSync.
198
+ *
199
+ * C.6 Merge lock held too briefly — daemon writes between stash and merge (MEDIUM)
200
+ * File:line _gitMergeFromWorktree():L3314-3342
201
+ * Pattern: Stash main repo → checkout userBranch → iterate DAEMON_STATE_FILES →
202
+ * git merge. Between the stash (L3314) and daemon revert (L3340), daemon
203
+ * process can write new content to tracked daemon files. The DAEMON_STATE_FILES
204
+ * revert at L3340 covers this window IF the file is in the list (C.3 dependency).
205
+ * Result: If daemon writes to an unlisted file in this window, "would be overwritten
206
+ * by merge" error → merge abort.
207
+ * Evidence: L3331-3342 (TASK_1112 comment).
208
+ *
209
+ * C.7 No ordering guarantee between parallel _gitMergeFromWorktree calls (MEDIUM)
210
+ * File:line acquireMergeLock():L988-1031
211
+ * Pattern: FIFO order is NOT guaranteed — first process to retry after
212
+ * MERGE_LOCK_POLL_MS (500ms) wins. Under load, later tasks may
213
+ * merge before earlier tasks even though the earlier task has more
214
+ * complete work.
215
+ * Result: Non-deterministic merge ordering. Low practical impact but
216
+ * complicates debugging.
217
+ *
218
+ * ─────────────────────────────────────────────────────────────────────────────
219
+ * SECTION D — PRIORITIZED REFACTOR PLAN
220
+ * ─────────────────────────────────────────────────────────────────────────────
221
+ *
222
+ * D.1 Rebase-under-lock with stale detection [P1 / High complexity]
223
+ * Addresses: C.2 (stale rebase window)
224
+ * Scope path: .formation/tools/post-complete.js (_gitMergeFromWorktree L3199-3294)
225
+ * Change:
226
+ * 1. Call acquireMergeLock() BEFORE rebase (currently called AFTER).
227
+ * 2. After successful rebase, verify userBranch HEAD is unchanged:
228
+ * capturedHead = git rev-parse userBranch; after rebase: if HEAD changed,
229
+ * release lock, wait briefly, re-acquire, re-rebase.
230
+ * 3. Max 2 re-rebase attempts before marking as blocked.
231
+ * Complexity: High — must not increase merge lock hold time excessively
232
+ * (rebase can take 1-10s; holds others from merging during that time).
233
+ * Dependency: None (standalone within _gitMergeFromWorktree).
234
+ * Expected health delta: +2 to +4 pts (reduces parallel merge conflict rate).
235
+ *
236
+ * D.2 Daemon-state manifest replacing hardcoded lists [P1 / Medium complexity]
237
+ * Addresses: C.3 (hardcoded DAEMON_STATE_FILES)
238
+ * Scope path: .formation/tools/post-complete.js (L1252-1403),
239
+ * .kenaz/specialist/daemon-manifest.yaml (new file)
240
+ * Change:
241
+ * 1. Create .kenaz/specialist/daemon-manifest.yaml with current DAEMON_STATE_FILES
242
+ * + WORKTREE_STATIC_DAEMON_PATHS content.
243
+ * 2. listDaemonManagedPaths() reads manifest + dynamic discovery (current .synapse scan).
244
+ * 3. isDaemonManagedPath() and getDaemonPathsToUnstage() delegate to manifest.
245
+ * 4. New daemon files need only a manifest YAML update, not a code change.
246
+ * Complexity: Medium — YAML loading is straightforward; must handle missing manifest.
247
+ * Dependency: None.
248
+ * Expected health delta: +1 to +2 pts (prevents future B8-style patches).
249
+ *
250
+ * D.3 Atomic writes for shared state files [P2 / Low complexity]
251
+ * Addresses: C.4 (ansuz_inbox.json), C.5 (post-complete-guard.json)
252
+ * Scope path: .formation/tools/post-complete.js (L5321-5332, L4892-4908)
253
+ * Change:
254
+ * 1. Extract atomic write helper: writeJsonAtomic(filePath, data) →
255
+ * writeFileSync(tmpPath, JSON.stringify), renameSync(tmpPath, filePath).
256
+ * 2. Replace direct writeFileSync calls at L5332 and L4906.
257
+ * Complexity: Low — pattern already established at L3891-3892 (ambient_signals.json).
258
+ * Dependency: None.
259
+ * Expected health delta: +1 pt (code quality, reduces race window).
260
+ *
261
+ * D.4 Extract post-complete.js into focused modules [P2 / High complexity]
262
+ * Addresses: Code size root cause of low health score (5939 lines, cyclomatic complexity)
263
+ * Scope path: .formation/tools/post-complete.js,
264
+ * .formation/tools/git-ops.js (new),
265
+ * .formation/tools/daemon-filter.js (new),
266
+ * .formation/tools/outcome-recording.js (new)
267
+ * Proposed split:
268
+ * - post-complete.js : orchestration only (~800 lines)
269
+ * - git-ops.js : gitBranchAndMerge + _gitMergeFromWorktree + locks (~1800 lines)
270
+ * - daemon-filter.js : DAEMON_STATE_FILES + isDaemonManagedPath + getDaemonPaths (~200 lines)
271
+ * - outcome-recording.js : recordOutcome + compareOutcomePrediction + ambient signals (~400 lines)
272
+ * Complexity: High — many cross-function dependencies; requires careful export surface design.
273
+ * Dependency: D.2 (daemon manifest) and D.3 (atomic writes) should be completed first
274
+ * so the extracted modules start clean.
275
+ * Expected health delta: +8 to +12 pts (D-grade → C-grade from complexity reduction alone).
276
+ *
277
+ * D.5 Pre-merge health gate replacing post-merge REVERT [P3 / Medium complexity]
278
+ * Addresses: post-merge REVERT requires git reset after landing — risky, disruptive
279
+ * Scope path: .formation/tools/post-complete.js (_gitMergeFromWorktree, Step 4 guardian)
280
+ * Change:
281
+ * 1. After rebase (D.1), before merge, run measureHealthScoreSync() in worktree context.
282
+ * 2. Compare predicted delta: if preSn.score - worktreeHealth.score > threshold, open
283
+ * a BLOCK note on the task and abort merge instead of committing.
284
+ * 3. Remove post-merge REVERT path (loop.js git reset) when this gate is active.
285
+ * Complexity: Medium — adds latency to merge path (~3-5s for health snapshot).
286
+ * Dependency: D.1 (rebase-under-lock must be stable before adding health gate on top).
287
+ * Expected health delta: indirect — prevents regressions from landing in first place.
288
+ *
289
+ * Dependency ordering: D.3 → D.2 → D.1 → D.4 → D.5
290
+ * Total estimated health recovery: D (52.4) → C (64-68) with D.1+D.2+D.4.
291
+ * ============================================================================
292
+ */
293
+
294
+ const { execSync, spawnSync } = require('child_process');
295
+ const path = require('path');
296
+ const fs = require('fs');
297
+ const {
298
+ ROOT, COLLABORATION_API, LOG_DIR, getLogDir, preserveUntrackedStashCommand,
299
+ removeWorktree,
300
+ } = require('./utils');
301
+ const { verify: verifyAcceptance } = require('./verify-acceptance');
302
+ // TASK_1825: the TASK_1482 runtime HTTP acceptance verifier
303
+ // (post-complete/verify-acceptance.js, Step 1.5) was REMOVED. Measurement:
304
+ // 7/6713 criteria (0.10%) were tool-call-shaped, its verdict field on the
305
+ // post-complete result object had zero readers, and its writeVerdictToTask
306
+ // never landed a single acceptance_verdict note. Net effect on the system: zero.
307
+ // LLM-based acceptance verification (./verify-acceptance.js) remains and is the
308
+ // one whose result (result.git.acceptance) is actually read.
309
+
310
+ // Memory Agent(記憶系統整合)
311
+ let memoryAgent = null;
312
+ try {
313
+ memoryAgent = require('../../.memory/tools/memory-agent');
314
+ } catch { /* memory-agent not available — graceful degradation */ }
315
+
316
+ // Design Reasoning Attribution (TASK_1074)
317
+ let designReasoning = null;
318
+ try {
319
+ designReasoning = require('../../.specialist/tools/design-reasoning');
320
+ } catch { /* design-reasoning not available — graceful degradation */ }
321
+
322
+ // TASK_2078: read-only fallback deriving duration_ms/tool_uses/first_edit_s
323
+ // from the subagent's own Claude Code transcript when agent_result_<TASK>.json
324
+ // is missing or incomplete.
325
+ let deriveSubagentStats = null;
326
+ try {
327
+ ({ deriveSubagentStats } = require('./post-complete/subagent-transcript-stats'));
328
+ } catch { /* subagent-transcript-stats not available — graceful degradation */ }
329
+
330
+ // ─────────────────────────────────────────
331
+ // 配置
332
+ // ─────────────────────────────────────────
333
+
334
+ const CONFIG = {
335
+ DEFAULT_KEEP: 10,
336
+ GUARDIAN_INTERVAL: 10, // Run synapse_health snapshot every N completed tasks
337
+ };
338
+
339
+ // ?????????????????????????????????????????????????????????????????????????????
340
+ // SECTION: UTILITIES
341
+ // ?????????????????????????????????????????????????????????????????????????????
342
+ /**
343
+ * 解析專案根目錄(git 操作、檔案掃描的 cwd)
344
+ * @param {string|null} projectPath - 專案路徑
345
+ * @returns {string|null} - git repo root, or null if not a git repo (skip git ops)
346
+ */
347
+ function resolveProjectRoot(projectPath) {
348
+ let resolved = projectPath || ROOT;
349
+
350
+ // Production fallback: ROOT may be AppData\kenaz-core (not a git repo).
351
+ // Try reading .kenaz/project.yaml → project_path to find the real repo.
352
+ if (!fs.existsSync(path.join(resolved, '.git'))) {
353
+ const yamlPath = path.join(resolved, '.kenaz', 'project.yaml');
354
+ if (fs.existsSync(yamlPath)) {
355
+ try {
356
+ const content = fs.readFileSync(yamlPath, 'utf8');
357
+ const match = content.match(/^project_path:\s*"?([^"\n]+)"?/m);
358
+ if (match) {
359
+ const candidate = path.resolve(match[1].trim());
360
+ if (fs.existsSync(path.join(candidate, '.git'))) {
361
+ resolved = candidate;
362
+ }
363
+ }
364
+ } catch {}
365
+ }
366
+ }
367
+
368
+ if (!fs.existsSync(path.join(resolved, '.git'))) {
369
+ // Not a git repo — return null so callers can skip git operations gracefully
370
+ return null;
371
+ }
372
+ return resolved;
373
+ }
374
+
375
+ /**
376
+ * 解析任務檔案基底目錄
377
+ * 所有專案(含 KenazAI 本體):有 .kenaz/tasks/ 就用它
378
+ */
379
+ function resolveTasksBase(projectPath) {
380
+ const root = projectPath || ROOT;
381
+ const kenazTasks = path.join(root, '.kenaz', 'tasks');
382
+ if (fs.existsSync(kenazTasks)) return kenazTasks;
383
+ return path.join(root, '.collaboration', 'tasks');
384
+ }
385
+
386
+ /**
387
+ * 解析任務目錄的相對路徑前綴(用於 git 指令)
388
+ */
389
+ function resolveTasksRelative(projectPath) {
390
+ const root = projectPath || ROOT;
391
+ const kenazTasks = path.join(root, '.kenaz', 'tasks');
392
+ if (fs.existsSync(kenazTasks)) return '.kenaz/tasks';
393
+ return '.collaboration/tasks';
394
+ }
395
+
396
+ // ─────────────────────────────────────────
397
+ // Phase 9 outcome log (TASK_1732)
398
+ // ─────────────────────────────────────────
399
+
400
+ /**
401
+ * Append one structured JSONL line to .kenaz/specialist/phase9_dispatch_outcomes.jsonl.
402
+ * Called at Step 2.7b in postComplete() after merge verdict is definitively known.
403
+ * Respects project pinning: writes under the effective project root (projectPath || ROOT).
404
+ *
405
+ * Format: { task_id, verify_merge_landed: bool, ts, [duration_ms], [tool_uses], [turns], [first_edit_s], [corrections], [correction_heads], [informational], [informational_heads] }
406
+ *
407
+ * (formerly read by the removed efe-drift-monitor) primary source for phase9_verify_fail_rate
408
+ * (subagent-path dispatches write no dispatch_*.log — this is the replacement source).
409
+ *
410
+ * TASK_1998 R1c: the four bracketed fields are OPTIONAL turn-count telemetry.
411
+ * Ansuz writes `.kenaz/specialist/agent_result_<TASK_ID>.json` from the Agent
412
+ * tool's notification payload (wall duration, tool-use count, turn count,
413
+ * time-to-first-edit) before calling post-complete; when that file exists we
414
+ * fold whichever of these numeric fields it carries straight into this same
415
+ * JSONL line, so dev/scripts/dispatch-stats.js can read turns/duration/tool_uses
416
+ * without a separate join against dispatch_prompt_stats.jsonl. Missing file,
417
+ * unparseable JSON, or a non-numeric field are all silently ignored — this
418
+ * telemetry must never block post-complete.
419
+ * Shape (agent_result_<TASK_ID>.json, any subset of these keys):
420
+ * { "duration_ms": number, "tool_uses": number, "turns": number, "first_edit_s": number }
421
+ *
422
+ * TASK_2078: agent_result_<TASK_ID>.json still wins field-by-field when it
423
+ * carries a field. For whichever of duration_ms/tool_uses/first_edit_s it
424
+ * LACKS, fall back to deriveSubagentStats (reads the subagent's own Claude
425
+ * Code transcript — see subagent-transcript-stats.js). `turns` has no
426
+ * transcript-derived counterpart and stays agent_result-only. Records
427
+ * `timing_source` ('agent_result' | 'transcript' | 'mixed') and `spawns`
428
+ * (re-dispatch count) whenever transcript data contributed anything.
429
+ *
430
+ * TASK_2097: `corrections` (count of distinct Ansuz correction/review
431
+ * messages delivered mid-dispatch) and `correction_heads` (their first-120-
432
+ * char heads, capped at 10) have NO agent_result counterpart at all — they
433
+ * only ever come from deriveSubagentStats — so the transcript is read
434
+ * whenever it is available, even when agent_result already covers every
435
+ * timing field (the `stillMissing` timing check below only gates the
436
+ * timing fields, never the corrections read).
437
+ *
438
+ * Best-effort throughout — must never throw or block post-complete.
439
+ */
440
+ function appendPhase9OutcomeLog(taskId, verifyMergeLanded, projectPath) {
441
+ const root = projectPath || ROOT;
442
+ const dir = path.join(root, '.kenaz', 'specialist');
443
+ const file = path.join(dir, 'phase9_dispatch_outcomes.jsonl');
444
+ if (!fs.existsSync(dir)) {
445
+ try { fs.mkdirSync(dir, { recursive: true }); } catch { return; }
446
+ }
447
+ const record = { task_id: taskId, verify_merge_landed: !!verifyMergeLanded, ts: new Date().toISOString() };
448
+ const fromAgentResult = new Set();
449
+ try {
450
+ const agentResultFile = path.join(dir, `agent_result_${taskId}.json`);
451
+ if (fs.existsSync(agentResultFile)) {
452
+ const raw = JSON.parse(fs.readFileSync(agentResultFile, 'utf8'));
453
+ for (const field of ['duration_ms', 'tool_uses', 'turns', 'first_edit_s']) {
454
+ if (typeof raw[field] === 'number' && Number.isFinite(raw[field])) {
455
+ record[field] = raw[field];
456
+ fromAgentResult.add(field);
457
+ }
458
+ }
459
+ }
460
+ } catch { /* best-effort — malformed/missing agent_result file is fine */ }
461
+
462
+ const fromTranscript = new Set();
463
+ try {
464
+ const transcriptFields = ['duration_ms', 'tool_uses', 'first_edit_s'];
465
+ const stillMissing = transcriptFields.some((f) => !fromAgentResult.has(f));
466
+ if (typeof deriveSubagentStats === 'function') {
467
+ const derived = deriveSubagentStats(taskId, root);
468
+ if (derived) {
469
+ if (stillMissing) {
470
+ for (const field of transcriptFields) {
471
+ if (fromAgentResult.has(field)) continue;
472
+ if (typeof derived[field] === 'number' && Number.isFinite(derived[field])) {
473
+ record[field] = derived[field];
474
+ fromTranscript.add(field);
475
+ }
476
+ }
477
+ if (fromTranscript.size > 0 && typeof derived.spawns === 'number') {
478
+ record.spawns = derived.spawns;
479
+ }
480
+ }
481
+ if (typeof derived.corrections === 'number' && Number.isFinite(derived.corrections)) {
482
+ record.corrections = derived.corrections;
483
+ record.correction_heads = Array.isArray(derived.correction_heads)
484
+ ? derived.correction_heads.slice(0, 10)
485
+ : [];
486
+ if (!(typeof record.spawns === 'number') && typeof derived.spawns === 'number') {
487
+ record.spawns = derived.spawns;
488
+ }
489
+ }
490
+ // TASK_2109: informational coordinator messages (FYI/context, no
491
+ // rework demanded) are tracked separately and never folded into
492
+ // `corrections` — see subagent-transcript-stats.js classifyCoordinatorMessage.
493
+ if (typeof derived.informational === 'number' && Number.isFinite(derived.informational)) {
494
+ record.informational = derived.informational;
495
+ record.informational_heads = Array.isArray(derived.informational_heads)
496
+ ? derived.informational_heads.slice(0, 10)
497
+ : [];
498
+ }
499
+ }
500
+ }
501
+ } catch { /* best-effort — transcript fallback must never block post-complete */ }
502
+
503
+ if (fromAgentResult.size > 0 || fromTranscript.size > 0) {
504
+ record.timing_source = (fromAgentResult.size > 0 && fromTranscript.size > 0) ? 'mixed'
505
+ : (fromAgentResult.size > 0) ? 'agent_result' : 'transcript';
506
+ }
507
+
508
+ const line = JSON.stringify(record);
509
+ try { fs.appendFileSync(file, line + '\n', 'utf8'); } catch { /* best-effort */ }
510
+ }
511
+
512
+ // ─────────────────────────────────────────
513
+ // Git Lock(序列化 post-complete git 操作)
514
+ // ─────────────────────────────────────────
515
+
516
+ const GIT_LOCK_TIMEOUT_MS = 120000; // 2 minutes
517
+ const GIT_LOCK_POLL_MS = 1000;
518
+
519
+ function getGitLockPath(projectRoot) {
520
+ const dotGit = path.join(projectRoot, '.git');
521
+ let stat;
522
+ try { stat = fs.statSync(dotGit); } catch (e) { stat = null; }
523
+ if (stat && stat.isFile()) {
524
+ // git worktree: .git is a file containing "gitdir: <path>"
525
+ const contents = fs.readFileSync(dotGit, 'utf8');
526
+ const match = contents.match(/^gitdir:\s*(.+)/m);
527
+ if (match) {
528
+ const realGitDir = path.resolve(projectRoot, match[1].trim());
529
+ return path.join(realGitDir, 'post-complete.lock');
530
+ }
531
+ }
532
+ return path.join(projectRoot, '.git', 'post-complete.lock');
533
+ }
534
+
535
+ function normalizeRepoPath(relPath) {
536
+ const normalized = String(relPath || '')
537
+ .replace(/\\/g, '/')
538
+ .replace(/^\.\//, '')
539
+ .replace(/\/+/g, '/');
540
+ return normalized.endsWith('/') ? normalized : normalized.replace(/\/$/, '');
541
+ }
542
+
543
+ function isDaemonManagedPath(relPath) {
544
+ const normalized = normalizeRepoPath(relPath);
545
+ if (!normalized) return false;
546
+ if (getDaemonStateFiles().includes(normalized) || getDaemonStaticPaths().includes(normalized)) return true;
547
+ // TASK_2255: a static_paths entry ending in '/' is a directory prefix — its children match.
548
+ // (normalizeRepoPath already unified separators; TS twin: DIR_PREFIXES loop.)
549
+ for (const entry of getDaemonStaticPaths()) {
550
+ if (typeof entry === 'string' && entry.endsWith('/') && normalized.startsWith(normalizeRepoPath(entry))) return true;
551
+ }
552
+ if (normalized.startsWith('.tmp/')) return true;
553
+ if (normalized.startsWith('.formation/state/')) return true;
554
+ if (normalized.startsWith('.formation/analytics/')) return true;
555
+ if (normalized.startsWith('.formation/signals/')) return true;
556
+ if (normalized.startsWith('.synapse/') && /(?:^|\/)(?:.+_cache\.json|memory-shared\.json)$/i.test(normalized)) return true;
557
+ if (normalized === '.memory/registry.json') return true;
558
+ if (normalized === '.prefrontal/generalize_counter.json') return true;
559
+ // TASK_1622: one-off probe/episode-writer scripts in .formation/ root
560
+ // These are created by Ansuz during live sessions and must never
561
+ // be swept into task commits by the blanket git add -A in worktree staging.
562
+ if (/^[.]formation[/]tmp_[^/]+[.](js|json)$/.test(normalized)) return true;
563
+ if (/^[.]formation[/](?:muninn_snapshot_[0-9]+|muninn_rune_check|episode_writer(?:_v[0-9]+)?)[.]js$/.test(normalized)) return true;
564
+ return false;
565
+ }
566
+
567
+ function listDaemonManagedPaths(projectRoot) {
568
+ const discovered = new Set([
569
+ ...getDaemonStateFiles(),
570
+ ...getDaemonStaticPaths(),
571
+ ]);
572
+
573
+ const scanDir = (relDir) => {
574
+ const absDir = path.join(projectRoot, relDir);
575
+ if (!fs.existsSync(absDir)) return;
576
+ const stat = fs.statSync(absDir);
577
+ if (!stat.isDirectory()) {
578
+ if (isDaemonManagedPath(relDir)) discovered.add(normalizeRepoPath(relDir));
579
+ return;
580
+ }
581
+ const stack = [{ abs: absDir, rel: normalizeRepoPath(relDir).replace(/\/$/, '') }];
582
+ while (stack.length > 0) {
583
+ const current = stack.pop();
584
+ for (const entry of fs.readdirSync(current.abs, { withFileTypes: true })) {
585
+ const relPath = normalizeRepoPath(path.posix.join(current.rel, entry.name));
586
+ if (entry.isDirectory()) {
587
+ stack.push({ abs: path.join(current.abs, entry.name), rel: relPath });
588
+ continue;
589
+ }
590
+ if (isDaemonManagedPath(relPath)) discovered.add(relPath);
591
+ }
592
+ }
593
+ };
594
+
595
+ scanDir('.synapse');
596
+ scanDir('.prefrontal');
597
+ scanDir('.memory');
598
+ if (fs.existsSync(path.join(projectRoot, '.tmp'))) discovered.add('.tmp/');
599
+
600
+ return Array.from(discovered);
601
+ }
602
+
603
+ function pathExistsInHead(repoRoot, relPath) {
604
+ const normalized = normalizeRepoPath(relPath).replace(/\/$/, '');
605
+ if (!normalized) return false;
606
+ const out = git(`ls-tree -r --name-only HEAD -- "${normalized}"`, { cwd: repoRoot, throws: false }) || '';
607
+ return out.split(/\r?\n/).some(line => normalizeRepoPath(line) === normalized);
608
+ }
609
+
610
+ function getDaemonPathsToUnstage(worktreePath) {
611
+ const staged = (git('diff --cached --name-only', { cwd: worktreePath, throws: false }) || '')
612
+ .split(/\r?\n/)
613
+ .map(line => normalizeRepoPath(line))
614
+ .filter(Boolean);
615
+ const discovered = staged.filter(relPath => isDaemonManagedPath(relPath) && pathExistsInHead(worktreePath, relPath));
616
+ return Array.from(new Set([
617
+ ...getDaemonStateFiles(),
618
+ ...getDaemonStaticPaths(),
619
+ ...discovered,
620
+ ]));
621
+ }
622
+
623
+ /** Strip ANSI escape codes from string */
624
+
625
+ function stripAnsi(str) {
626
+ // eslint-disable-next-line no-control-regex
627
+ return str.replace(/\u001b\[[0-9;]*[a-zA-Z]/g, '');
628
+ }
629
+
630
+ /**
631
+ * Strip CJK (Chinese/Japanese/Korean) characters from a string.
632
+ * MEM-LANG-001: SQLite FTS5 unicode61 tokenizer cannot tokenize CJK properly,
633
+ * causing synapse_recall to return 0 results for runes containing CJK text.
634
+ *
635
+ * @param {string} text - input text possibly containing CJK characters
636
+ * @returns {string} text with CJK chars removed, extra whitespace collapsed
637
+ */
638
+ function stripCJK(text) {
639
+ if (!text) return '';
640
+ // Remove CJK Unified Ideographs (U+4E00–9FFF) and common CJK extension blocks
641
+ return text
642
+ .replace(/[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\u3000-\u303F\uFF00-\uFFEF]/g, ' ')
643
+ .replace(/\s+/g, ' ')
644
+ .trim();
645
+ }
646
+
647
+ /**
648
+ * Load scope_paths from task YAML for scope verification.
649
+ * @param {string} taskId - TASK_XXX
650
+ * @param {string} [projectPath] - Sub-project path
651
+ * @returns {string[]} scope_paths array (empty if not found)
652
+ */
653
+ function loadTaskScopePaths(taskId, projectPath) {
654
+ if (!taskId) return [];
655
+ const tasksBase = resolveTasksBase(projectPath);
656
+
657
+ const dirs = ['done', 'active', 'backlog', 'blocked'];
658
+ for (const dir of dirs) {
659
+ const dirPath = path.join(tasksBase, dir);
660
+ if (!fs.existsSync(dirPath)) continue;
661
+ const files = fs.readdirSync(dirPath).filter(f => f.startsWith(taskId) && f.endsWith('.yaml'));
662
+ if (files.length === 0) continue;
663
+
664
+ try {
665
+ const content = fs.readFileSync(path.join(dirPath, files[0]), 'utf8');
666
+ const match = content.match(/^scope_paths:\s*\n((?:\s+-\s+.+\n?)*)/m);
667
+ if (match) {
668
+ return match[1].split('\n')
669
+ .map(line => line.match(/^\s+-\s+"?([^"]+)"?\s*$/))
670
+ .filter(Boolean)
671
+ .map(m => m[1].trim());
672
+ }
673
+ } catch { /* skip */ }
674
+ }
675
+ return [];
676
+ }
677
+
678
+ /**
679
+ * Detect if a task is memory-only (no git-trackable changes expected).
680
+ * A task is memory-only if:
681
+ * - tags contain 'synapse' or 'memory', OR
682
+ * - all scope_paths are under .synapse/
683
+ * @param {string} taskId - TASK_XXX
684
+ * @param {string} [projectPath] - Sub-project path
685
+ * @returns {boolean}
686
+ */
687
+ function isMemoryOnlyTask(taskId, projectPath) {
688
+ if (!taskId) return false;
689
+ const tasksBase = resolveTasksBase(projectPath);
690
+
691
+ const dirs = ['active', 'done', 'backlog', 'blocked'];
692
+ for (const dir of dirs) {
693
+ const dirPath = path.join(tasksBase, dir);
694
+ if (!fs.existsSync(dirPath)) continue;
695
+ const files = fs.readdirSync(dirPath).filter(f => f.startsWith(taskId) && f.endsWith('.yaml'));
696
+ if (files.length === 0) continue;
697
+
698
+ try {
699
+ const content = fs.readFileSync(path.join(dirPath, files[0]), 'utf8');
700
+
701
+ // Check tags for 'synapse' or 'memory'
702
+ const tagsMatch = content.match(/^tags:\s*\n((?:\s+-\s+.+\n?)*)/m);
703
+ if (tagsMatch) {
704
+ const tags = tagsMatch[1].split('\n')
705
+ .map(line => line.match(/^\s+-\s+"?([^"]+)"?\s*$/))
706
+ .filter(Boolean)
707
+ .map(m => m[1].trim().toLowerCase());
708
+ if (tags.includes('synapse') || tags.includes('memory')) return true;
709
+ }
710
+
711
+ // Check scope_paths — all must be under .synapse/
712
+ const scopeMatch = content.match(/^scope_paths:\s*\n((?:\s+-\s+.+\n?)*)/m);
713
+ if (scopeMatch) {
714
+ const paths = scopeMatch[1].split('\n')
715
+ .map(line => line.match(/^\s+-\s+"?([^"]+)"?\s*$/))
716
+ .filter(Boolean)
717
+ .map(m => m[1].trim());
718
+ if (paths.length > 0 && paths.every(p => p.startsWith('.synapse/'))) return true;
719
+ }
720
+ } catch { /* skip */ }
721
+ break; // found the file, stop searching
722
+ }
723
+ return false;
724
+ }
725
+
726
+ /**
727
+ * Detect runtime-only smoke tasks that intentionally forbid source edits and
728
+ * therefore should not rescue unrelated dirty root files into a worktree.
729
+ * @param {string} taskId
730
+ * @param {string} [projectPath]
731
+ * @returns {boolean}
732
+ */
733
+ function isRuntimeOnlySmokeTask(taskId, projectPath) {
734
+ if (!taskId) return false;
735
+ const tasksBase = resolveTasksBase(projectPath);
736
+ const dirs = ['active', 'done', 'backlog', 'blocked'];
737
+
738
+ for (const dir of dirs) {
739
+ const dirPath = path.join(tasksBase, dir);
740
+ if (!fs.existsSync(dirPath)) continue;
741
+ const files = fs.readdirSync(dirPath).filter(f => f.startsWith(taskId) && f.endsWith('.yaml'));
742
+ if (files.length === 0) continue;
743
+
744
+ try {
745
+ const content = fs.readFileSync(path.join(dirPath, files[0]), 'utf8');
746
+ const tagsMatch = content.match(/^tags:\s*\n((?:\s+-\s+.+\n?)*)/m);
747
+ const requirementMatch = content.match(/^requirements:\s*\n((?:\s+-\s+.+\n?)*)/m);
748
+
749
+ const tags = tagsMatch
750
+ ? tagsMatch[1].split('\n')
751
+ .map(line => line.match(/^\s+-\s+"?([^"]+)"?\s*$/))
752
+ .filter(Boolean)
753
+ .map(m => m[1].trim().toLowerCase())
754
+ : [];
755
+
756
+ const requirements = requirementMatch
757
+ ? requirementMatch[1].split('\n')
758
+ .map(line => line.match(/^\s+-\s+"?([^"]+)"?\s*$/))
759
+ .filter(Boolean)
760
+ .map(m => m[1].trim().toLowerCase())
761
+ : [];
762
+
763
+ return tags.includes('smoke')
764
+ && tags.includes('runtime')
765
+ && requirements.some(req =>
766
+ req.includes('do not modify source files') || req.includes('no source files are modified')
767
+ );
768
+ } catch {
769
+ return false;
770
+ }
771
+ }
772
+
773
+ return false;
774
+ }
775
+
776
+ /**
777
+ * Rank changed files by scope-match first, then git diff --stat order.
778
+ * Returns up to `limit` file paths.
779
+ * @param {string} taskId
780
+ * @param {string} projectRoot
781
+ * @param {string[]} scopePaths
782
+ * @param {number} limit
783
+ * @returns {string[]}
784
+ */
785
+ function rankTouchedFiles(projectRoot, scopePaths, limit) {
786
+ // Get changed files via diff --stat (most-changed first)
787
+ let changedFiles = [];
788
+ try {
789
+ // HEAD~1 (not HEAD^1): on Windows cmd.exe, ^ is the escape character and ^1 becomes 1.
790
+ const statOut = execSync('git diff --stat HEAD~1 HEAD', {
791
+ cwd: projectRoot, encoding: 'utf8', timeout: 8000, windowsHide: true,
792
+ });
793
+ // Parse lines like " path/to/file | 42 ++++++-----"
794
+ const lines = statOut.split('\n').filter(l => l.includes('|'));
795
+ const parsed = lines.map(l => {
796
+ const m = l.match(/^\s*(.+?)\s*\|\s*(\d+)/);
797
+ if (!m) return null;
798
+ return { file: m[1].trim(), changes: parseInt(m[2], 10) };
799
+ }).filter(Boolean);
800
+ parsed.sort((a, b) => b.changes - a.changes);
801
+ changedFiles = parsed.map(p => p.file);
802
+ } catch {
803
+ // Fallback to plain --name-only
804
+ changedFiles = getPostCompleteChangedFiles(projectRoot, null);
805
+ if (changedFiles.length === 0) return [];
806
+ }
807
+
808
+ // Rank: scope-matching first, then diff-stat order
809
+ const normScope = (scopePaths || []).map(p => p.replace(/\\/g, '/'));
810
+ const inScope = [];
811
+ const outOfScope = [];
812
+ for (const f of changedFiles) {
813
+ const norm = f.replace(/\\/g, '/');
814
+ if (normScope.some(s => norm === s || norm.startsWith(s + '/') || s.endsWith('/') && norm.startsWith(s))) {
815
+ inScope.push(f);
816
+ } else {
817
+ outOfScope.push(f);
818
+ }
819
+ }
820
+ return [...inScope, ...outOfScope].slice(0, limit);
821
+ }
822
+
823
+ function writeJsonAtomic(filePath, data) {
824
+ const tmpFile = filePath + '.tmp.' + process.pid;
825
+ fs.writeFileSync(tmpFile, JSON.stringify(data, null, 2), 'utf8');
826
+ fs.renameSync(tmpFile, filePath);
827
+ }
828
+
829
+ /**
830
+ * TASK_1828: resolve the freshest handoff snapshot for a project root.
831
+ *
832
+ * The M3 migration (TASK_1760, ansuz-handoff-snapshot.js) moved handoff writes
833
+ * to per-session slots under .kenaz/specialist/handoffs/<sessionId>.json; the
834
+ * legacy single slot .kenaz/specialist/ansuz_session_handoff.json is read-only
835
+ * back-compat (nothing writes it anymore). Selection rule: newest valid
836
+ * generated_at wins. Two filename families coexist in handoffs/ by design
837
+ * (<conversation-uuid>.json and ansuz-prompt-<timestamp>.json — different
838
+ * callers derive sessionId differently), so filename ordering is meaningless;
839
+ * generated_at is the only reliable ordering.
840
+ *
841
+ * TASK_2254 contract: ansuz-boot-fast.js RENAMES every handoffs/ slot (and the
842
+ * legacy slot) into .kenaz/specialist/handoff_archive/ on boot, so after a boot
843
+ * the live dir is empty. Fallback order: handoffs/ -> newest (generated_at)
844
+ * valid handoff_archive/ slot -> legacy slot. Archived files keep their
845
+ * original content, so generated_at ordering works identically.
846
+ *
847
+ * @param {string} root project root
848
+ * @returns {object|null} parsed handoff object, or null. Never throws.
849
+ */
850
+ function readLatestHandoff(root) {
851
+ let best = null;
852
+ let bestTs = -Infinity;
853
+ try {
854
+ const handoffsDir = path.join(root, '.kenaz', 'specialist', 'handoffs');
855
+ for (const f of fs.readdirSync(handoffsDir)) {
856
+ if (!f.endsWith('.json')) continue;
857
+ try {
858
+ const data = JSON.parse(fs.readFileSync(path.join(handoffsDir, f), 'utf8'));
859
+ const ts = Date.parse(data && data.generated_at);
860
+ if (Number.isFinite(ts) && ts > bestTs) { bestTs = ts; best = data; }
861
+ } catch { /* skip corrupt slot */ }
862
+ }
863
+ } catch { /* handoffs/ missing — fall through to archive */ }
864
+ if (best) return best;
865
+ try {
866
+ const archiveDir = path.join(root, '.kenaz', 'specialist', 'handoff_archive');
867
+ for (const f of fs.readdirSync(archiveDir)) {
868
+ if (!f.endsWith('.json')) continue;
869
+ try {
870
+ const data = JSON.parse(fs.readFileSync(path.join(archiveDir, f), 'utf8'));
871
+ const ts = Date.parse(data && data.generated_at);
872
+ if (Number.isFinite(ts) && ts > bestTs) { bestTs = ts; best = data; }
873
+ } catch { /* skip corrupt archived slot */ }
874
+ }
875
+ } catch { /* handoff_archive/ missing — fall through to legacy */ }
876
+ if (best) return best;
877
+ try {
878
+ const legacy = path.join(root, '.kenaz', 'specialist', 'ansuz_session_handoff.json');
879
+ if (fs.existsSync(legacy)) return JSON.parse(fs.readFileSync(legacy, 'utf8'));
880
+ } catch { /* corrupt legacy — nothing usable */ }
881
+ return null;
882
+ }
883
+
884
+ /**
885
+ * Reads, evaluates, and updates the post-complete dedup guard file.
886
+ * Returns a skip-result object if this invocation is a duplicate; null if it should proceed.
887
+ * Extracted for unit-testability.
888
+ *
889
+ * TASK_1593: Guard entries now store `lastOutcome: 'success'|'failure'` (written by
890
+ * updatePostCompleteGuardOutcome after the pass completes). The worktree->null
891
+ * suppression window only fires when the PREVIOUS worktree pass SUCCEEDED — a failed
892
+ * worktree pass must allow a retry through, otherwise the legitimate retry after a
893
+ * failed/ENOENT worktree pass is silently eaten (observed live TASK_1590/1591).
894
+ */
895
+ function checkAndUpdatePostCompleteGuard(guardFile, taskId, worktreePath) {
896
+ try {
897
+ const dir = path.dirname(guardFile);
898
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
899
+ let guard = {};
900
+ if (fs.existsSync(guardFile)) {
901
+ try { guard = JSON.parse(fs.readFileSync(guardFile, 'utf8')); } catch { guard = {}; }
902
+ }
903
+ const now = Date.now();
904
+ const previous = taskId ? guard[taskId] : null;
905
+
906
+ // Guard 1: worktree->null transition within 120s.
907
+ // TASK_1593: Only suppress when the previous worktree pass SUCCEEDED.
908
+ // If the previous pass failed (e.g. worktree_missing ENOENT), the null-path
909
+ // retry is legitimate and must NOT be suppressed.
910
+ if (taskId && previous && previous.worktreePath && !worktreePath && (now - previous.ts) < 120000) {
911
+ if (previous.lastOutcome !== 'failure') {
912
+ console.log(` Duplicate post-complete suppressed for ${taskId} (recent worktree pass already handled it)`);
913
+ return { taskId, skipped: true, duplicateSuppressed: true, previousWorktreePath: previous.worktreePath };
914
+ } else {
915
+ console.log(` Allowing retry for ${taskId}: previous worktree pass outcome=failure (not suppressing)`);
916
+ }
917
+ }
918
+
919
+ // Guard 2: null->null retry within 30s — the prod crash pattern (TASK_1430)
920
+ // Also only suppress when the previous pass succeeded.
921
+ if (taskId && previous && !previous.worktreePath && !worktreePath && (now - previous.ts) < 30000) {
922
+ if (previous.lastOutcome !== 'failure') {
923
+ console.log(` Duplicate post-complete suppressed (null-null retry) for ${taskId}`);
924
+ return { taskId, skipped: true, duplicateSuppressed: true, nullNullRetry: true };
925
+ } else {
926
+ console.log(` Allowing retry for ${taskId}: previous null pass outcome=failure (not suppressing)`);
927
+ }
928
+ }
929
+
930
+ if (taskId) {
931
+ // lastOutcome is null until updatePostCompleteGuardOutcome() records the result
932
+ guard[taskId] = { ts: now, worktreePath: worktreePath || null, lastOutcome: null };
933
+ writeJsonAtomic(guardFile, guard);
934
+ }
935
+ return null; // caller should proceed normally
936
+ } catch (e) {
937
+ console.log(` post-complete guard skipped: ${e.message}`);
938
+ return null;
939
+ }
940
+ }
941
+
942
+ /**
943
+ * Records the outcome of a post-complete pass into the guard file.
944
+ * Call this after the pass completes (success or failure) so the next
945
+ * Guard 1/2 evaluation can distinguish success from failure.
946
+ *
947
+ * TASK_1593: Closes the dedup loop — guard only suppresses retries when
948
+ * the prior pass actually succeeded (lastOutcome='success').
949
+ *
950
+ * @param {string} guardFile
951
+ * @param {string} taskId
952
+ * @param {'success'|'failure'} outcome
953
+ */
954
+ function updatePostCompleteGuardOutcome(guardFile, taskId, outcome) {
955
+ if (!taskId) return;
956
+ try {
957
+ let guard = {};
958
+ if (fs.existsSync(guardFile)) {
959
+ try { guard = JSON.parse(fs.readFileSync(guardFile, 'utf8')); } catch { guard = {}; }
960
+ }
961
+ if (guard[taskId]) {
962
+ guard[taskId].lastOutcome = outcome;
963
+ writeJsonAtomic(guardFile, guard);
964
+ }
965
+ } catch { /* best-effort — guard update failure is non-fatal */ }
966
+ }
967
+
968
+ /**
969
+ * Writes a task_completed entry to ansuz_inbox.json with idempotency.
970
+ * Skips if an unprocessed entry with the same (type, task_id) already exists.
971
+ * Returns { written: true } or { skipped: true, reason: 'already-present' }.
972
+ * Extracted for unit-testability.
973
+ */
974
+ function writeInboxEntryIdempotent(inboxFile, specialistDir, entry) {
975
+ let inbox = { messages: [] };
976
+ try {
977
+ if (fs.existsSync(inboxFile)) {
978
+ inbox = JSON.parse(fs.readFileSync(inboxFile, 'utf8'));
979
+ if (!Array.isArray(inbox.messages)) inbox.messages = [];
980
+ }
981
+ } catch { inbox = { messages: [] }; }
982
+
983
+ const processedIds = inbox.processed_ids || [];
984
+ const alreadyPresent = inbox.messages.some(
985
+ m => m.type === entry.type && m.task_id === entry.task_id && !processedIds.includes(m.id)
986
+ );
987
+ if (alreadyPresent) {
988
+ console.log(` 📬 Ansuz inbox: task_completed for ${entry.task_id} already present — skipped (idempotent)`);
989
+ return { skipped: true, reason: 'already-present' };
990
+ }
991
+
992
+ inbox.messages.push(entry);
993
+ fs.mkdirSync(specialistDir, { recursive: true });
994
+ writeJsonAtomic(inboxFile, inbox);
995
+ console.log(` 📬 Ansuz inbox: task_completed signal written for ${entry.task_id}`);
996
+ return { written: true };
997
+ }
998
+
999
+ function getPostCompleteChangedFiles(projectRoot, gitResult) {
1000
+ // Use HEAD~1 not HEAD^1: on Windows cmd.exe, ^ is the escape character and ^1 strips to 1,
1001
+ // producing "fatal: ambiguous argument 'HEAD1'". HEAD~1 is shell-safe everywhere.
1002
+ //
1003
+ // B9 (2026-04-09): when worktree merge fails, the agent's actual commit lives on
1004
+ // taskBranch~1..taskBranch — NOT on the main repo HEAD~1..HEAD which captures
1005
+ // whatever unrelated commits happened in the main branch during the dispatch
1006
+ // (e.g. concurrent post-complete bug fixes). Prefer taskBranch range whenever
1007
+ // we know the branch and it still exists in the shared git object database.
1008
+ const candidateCmds = [];
1009
+ const taskBranch = gitResult && gitResult.taskBranch;
1010
+
1011
+ // Check if taskBranch still exists (might be deleted after a successful merge cleanup)
1012
+ const taskBranchExists = taskBranch
1013
+ ? (() => {
1014
+ try {
1015
+ execSync(`git rev-parse --verify --quiet ${taskBranch}`, {
1016
+ cwd: projectRoot, encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
1017
+ });
1018
+ return true;
1019
+ } catch { return false; }
1020
+ })()
1021
+ : false;
1022
+
1023
+ if (isVerifiedMergeSuccess(gitResult)) {
1024
+ // Merge succeeded — the merge commit is at HEAD, its first parent is HEAD~1.
1025
+ // taskBranch~1..taskBranch would also work but is identical to HEAD~1..HEAD here.
1026
+ candidateCmds.push('git diff --name-only HEAD~1 HEAD');
1027
+ } else if (taskBranchExists) {
1028
+ // Merge failed or not attempted but agent committed on a task branch — use the
1029
+ // branch's own range so unrelated main-repo activity does not pollute outcome.
1030
+ candidateCmds.push(`git diff --name-only ${taskBranch}~1 ${taskBranch}`);
1031
+ }
1032
+
1033
+ if (gitResult && gitResult.committed) {
1034
+ candidateCmds.push('git diff --name-only HEAD~1 HEAD');
1035
+ }
1036
+ // Last-resort fallback (memory-only tasks, no taskBranch, etc.)
1037
+ candidateCmds.push('git diff --name-only HEAD~1 HEAD');
1038
+
1039
+ for (const cmd of candidateCmds) {
1040
+ try {
1041
+ const raw = execSync(cmd, {
1042
+ cwd: projectRoot,
1043
+ encoding: 'utf8',
1044
+ timeout: 8000,
1045
+ windowsHide: true,
1046
+ }).trim();
1047
+ if (raw) return raw.split('\n').filter(Boolean);
1048
+ } catch {
1049
+ // try next candidate
1050
+ }
1051
+ }
1052
+
1053
+ return [];
1054
+ }
1055
+
1056
+ /**
1057
+ * Find the most recent formation log file for a given task ID.
1058
+ * Scans LOG_DIR (or project-specific log dir) for files containing the task ID.
1059
+ * @param {string} taskId
1060
+ * @param {string} [projectPath]
1061
+ * @returns {string|null} log file path, or null if not found
1062
+ */
1063
+ function findLogForTask(taskId, projectPath) {
1064
+ const logRoot = projectPath ? getLogDir(projectPath) : LOG_DIR;
1065
+ if (!fs.existsSync(logRoot)) return null;
1066
+
1067
+ // Scan formation subdirs (unit/, solo/, squad/, etc.)
1068
+ let candidates = [];
1069
+ try {
1070
+ const subdirs = fs.readdirSync(logRoot, { withFileTypes: true });
1071
+ for (const d of subdirs) {
1072
+ if (!d.isDirectory()) continue;
1073
+ const formDir = path.join(logRoot, d.name);
1074
+ const files = fs.readdirSync(formDir).filter(f => f.endsWith('.log'));
1075
+ for (const f of files) {
1076
+ const fullPath = path.join(formDir, f);
1077
+ // Check if log content references this task
1078
+ try {
1079
+ const head = fs.readFileSync(fullPath, 'utf-8').slice(0, 500);
1080
+ if (head.includes(taskId)) {
1081
+ const stat = fs.statSync(fullPath);
1082
+ candidates.push({ path: fullPath, mtime: stat.mtimeMs });
1083
+ }
1084
+ } catch { /* unreadable file */ }
1085
+ }
1086
+ }
1087
+ } catch { /* log dir scan failed */ }
1088
+
1089
+ if (candidates.length === 0) return null;
1090
+ // Return the most recent match
1091
+ candidates.sort((a, b) => b.mtime - a.mtime);
1092
+ return candidates[0].path;
1093
+ }
1094
+
1095
+ // ─────────────────────────────────────────
1096
+ // Cognitive Checkpoint Detection
1097
+ // ─────────────────────────────────────────
1098
+
1099
+ /**
1100
+ * B11: Conservative stale worktree GC. Scans .formation/worktrees/ for orphan
1101
+ * dirs whose owning task is already in done/cancel/archived state. Safe by
1102
+ * design — never touches a worktree whose task is still active/backlog/blocked,
1103
+ * never touches the worktree currently being processed, never recurses outside
1104
+ * .formation/worktrees/. Runs at the start of every postComplete invocation so
1105
+ * the system self-heals from any historical merge-failure leftovers without
1106
+ * waiting for manual cleanup.
1107
+ *
1108
+ * Triggered by historical observation: 11 stale worktrees / 2.9 GB accumulated
1109
+ * across multiple sessions of the post-complete bug chain (B1-B10).
1110
+ *
1111
+ * TASK_2028: a worktree interrupted mid-`git worktree add` (Windows process-
1112
+ * tree kill on timeout, session end) is LOCKED by git itself with reason
1113
+ * "initializing", and `git worktree prune` never prunes a locked entry even
1114
+ * after its directory is gone (see utils.js createWorktree/
1115
+ * cleanupFailedWorktreeAttempt for the same pattern on the creation side).
1116
+ * A raw `fs.rmSync` on the directory therefore only removes the working
1117
+ * copy — the `.git/worktrees/<name>/locked` registration in the MAIN repo
1118
+ * survives, permanently marking the task branch "checked out" and blocking
1119
+ * any future retry with "already checked out" / "branch already exists".
1120
+ * reclaimWorktreeDir() below unlocks first, then prefers `git worktree
1121
+ * remove --force` (utils.removeWorktree, which also prunes as a fallback)
1122
+ * over a bare directory delete, so GC actually clears the git-level lock
1123
+ * instead of leaving a ghost registration behind.
1124
+ */
1125
+ function reclaimWorktreeDir(repoRoot, worktreesRoot, name) {
1126
+ const fullPath = path.join(worktreesRoot, name);
1127
+ try {
1128
+ execSync(`git worktree unlock "${fullPath}"`, {
1129
+ cwd: repoRoot, encoding: 'utf8', timeout: 10000, windowsHide: true,
1130
+ stdio: ['pipe', 'pipe', 'pipe'],
1131
+ });
1132
+ } catch { /* not locked / not registered — fine */ }
1133
+ const removed = removeWorktree(repoRoot, fullPath);
1134
+ if (!removed && fs.existsSync(fullPath)) {
1135
+ // Last resort: directory delete + explicit prune so the admin metadata
1136
+ // (and its lock) doesn't outlive the working copy.
1137
+ fs.rmSync(fullPath, { recursive: true, force: true });
1138
+ try {
1139
+ execSync('git worktree prune', {
1140
+ cwd: repoRoot, encoding: 'utf8', timeout: 10000, windowsHide: true,
1141
+ stdio: ['pipe', 'pipe', 'pipe'],
1142
+ });
1143
+ } catch { /* prune failure is non-fatal */ }
1144
+ }
1145
+ }
1146
+
1147
+ function gcStaleWorktrees(currentTaskId, projectPath) {
1148
+ try {
1149
+ const worktreesRoot = path.join(ROOT, '.formation', 'worktrees');
1150
+ if (!fs.existsSync(worktreesRoot)) return;
1151
+ const entries = fs.readdirSync(worktreesRoot, { withFileTypes: true });
1152
+ let removedCount = 0;
1153
+ let preservedCount = 0;
1154
+ for (const ent of entries) {
1155
+ if (!ent.isDirectory()) continue;
1156
+ // Pattern: task_task_<taskidsuffix> — extract the TASK_ID
1157
+ const m = ent.name.match(/^task_task_(.+)$/);
1158
+ if (!m) continue;
1159
+ const taskIdSuffix = m[1];
1160
+ const fullTaskId = taskIdSuffix.startsWith('TASK_') ? taskIdSuffix : `TASK_${taskIdSuffix}`;
1161
+ // Never remove the worktree for the task currently being processed
1162
+ if (currentTaskId && (fullTaskId === currentTaskId || taskIdSuffix === currentTaskId)) {
1163
+ preservedCount++;
1164
+ continue;
1165
+ }
1166
+ // Probe task status via task-api — only act on done/cancel/archived
1167
+ try {
1168
+ const apiArgs = projectPath ? ['--project-path', projectPath] : [];
1169
+ apiArgs.push('get', fullTaskId);
1170
+ const result = spawnSync('node', [COLLABORATION_API, ...apiArgs], {
1171
+ encoding: 'utf8', timeout: 5000, cwd: ROOT, windowsHide: true,
1172
+ });
1173
+ if (result.status !== 0) {
1174
+ // Task does not exist (deleted, never created) → safe to remove orphan worktree
1175
+ reclaimWorktreeDir(ROOT, worktreesRoot, ent.name);
1176
+ removedCount++;
1177
+ continue;
1178
+ }
1179
+ const parsed = JSON.parse(result.stdout || '{}');
1180
+ const status = parsed._status_dir || parsed.status_dir || (parsed.task && parsed.task._status_dir) || null;
1181
+ if (status && ['done', 'cancel', 'archived'].includes(status)) {
1182
+ reclaimWorktreeDir(ROOT, worktreesRoot, ent.name);
1183
+ removedCount++;
1184
+ } else {
1185
+ preservedCount++;
1186
+ }
1187
+ } catch {
1188
+ // Task probe failed — leave the worktree alone (conservative)
1189
+ preservedCount++;
1190
+ }
1191
+ }
1192
+ if (removedCount > 0) {
1193
+ console.log(` 🧹 Stale worktree GC: removed ${removedCount} dir(s) from completed tasks${preservedCount > 0 ? ` (${preservedCount} preserved for active work)` : ''}`);
1194
+ }
1195
+ } catch (e) {
1196
+ console.log(` ⚠️ Stale worktree GC failed (non-fatal): ${e.message}`);
1197
+ }
1198
+ }
1199
+
1200
+ // ============================================================================
1201
+ // Cognitive tool usage tracking (TASK_1105)
1202
+ // Records which cognitive tools (synapse_recall, kenaz_lsp_*, ddx, etc.) were
1203
+ // invoked during the task, aggregates into .kenaz/specialist/ansuz_self_model.json
1204
+ // under cognitive_competence, and flags negative-trend tools as mirror blind spots.
1205
+ // ============================================================================
1206
+
1207
+ const COGNITIVE_TOOLS = [
1208
+ 'synapse_recall',
1209
+ 'kenaz_lsp_callers',
1210
+ 'kenaz_lsp_references',
1211
+ 'synapse_inscribe',
1212
+ 'synapse_forge',
1213
+ 'ddx',
1214
+ 'hippocampus_episode_start',
1215
+ 'cortex_recall',
1216
+ ];
1217
+
1218
+ function sleepSync(ms) {
1219
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1220
+ }
1221
+
1222
+ /** Returns true if cargo stderr indicates a transient file lock contention. */
1223
+ // ?????????????????????????????????????????????????????????????????????????????
1224
+ // SECTION: GIT_PRIMITIVES
1225
+ // ?????????????????????????????????????????????????????????????????????????????
1226
+ function git(cmd, opts = {}) {
1227
+ if (!opts.cwd) {
1228
+ throw new Error(`git(): cwd is required — refusing to default to ROOT to prevent cross-project contamination. Command: git ${cmd}`);
1229
+ }
1230
+
1231
+ // 子專案 git 隔離:GIT_CEILING_DIRECTORIES 阻止 git 向上遍歷
1232
+ // 即使 cwd 設錯,git 也不會找到上層的 .git/
1233
+ const env = { ...process.env };
1234
+ if (opts.cwd && opts.cwd !== ROOT) {
1235
+ env.GIT_CEILING_DIRECTORIES = path.dirname(opts.cwd);
1236
+ }
1237
+
1238
+ try {
1239
+ const out = execSync(`git ${cmd}`, {
1240
+ encoding: 'utf8',
1241
+ timeout: 30000,
1242
+ stdio: ['pipe', 'pipe', 'pipe'], // suppress stderr noise to parent
1243
+ env,
1244
+ ...opts, windowsHide: true
1245
+ });
1246
+ // opts.raw: caller needs byte-exact stdout (e.g. porcelain parsing where
1247
+ // the first line's leading space encodes index-status — trim() would
1248
+ // strip it and corrupt slice-based field extraction).
1249
+ return opts.raw ? out.replace(/\r?\n$/, '') : out.trim();
1250
+ } catch (e) {
1251
+ if (opts.throws !== false) throw e;
1252
+ return null;
1253
+ }
1254
+ }
1255
+
1256
+ function gitPreservingUntracked(cmd, opts = {}) {
1257
+ const safeCmd = preserveUntrackedStashCommand(cmd);
1258
+ if (safeCmd !== cmd && !opts.silentPreserveUntrackedLog) {
1259
+ console.log(' preserve untracked: stash command downgraded to tracked-only for workspace safety');
1260
+ }
1261
+ return git(safeCmd, opts);
1262
+ }
1263
+
1264
+ function getCurrentBranch(projectRoot) {
1265
+ return git('rev-parse --abbrev-ref HEAD', { cwd: projectRoot, throws: false });
1266
+ }
1267
+
1268
+ /**
1269
+ * 取得 dispatch 時保存的原始分支名。
1270
+ * 並行 dispatch 時 getCurrentBranch() 可能回傳其他 task 的分支,
1271
+ * 這個函數確保 post-complete 總是能回到正確的用戶分支。
1272
+ */
1273
+ function getDispatchBranch(projectRoot) {
1274
+ const dispatchBranchFile = path.join(projectRoot, '.formation', '.dispatch-branch');
1275
+ try {
1276
+ if (fs.existsSync(dispatchBranchFile)) {
1277
+ const branch = fs.readFileSync(dispatchBranchFile, 'utf-8').trim();
1278
+ if (branch && !branch.startsWith('task/')) return branch;
1279
+ }
1280
+ } catch { /* ignore */ }
1281
+ return null;
1282
+ }
1283
+
1284
+ /**
1285
+ * 檢查 repo 是否有至少一個 commit(HEAD 是否存在)
1286
+ */
1287
+ function hasInitialCommit(projectRoot) {
1288
+ return git('rev-parse HEAD', { cwd: projectRoot, throws: false }) !== null;
1289
+ }
1290
+
1291
+ function branchExists(branch, projectRoot) {
1292
+ return git(`rev-parse --verify ${branch}`, { cwd: projectRoot, throws: false }) !== null;
1293
+ }
1294
+
1295
+ function getGitUserBranch(projectRoot) {
1296
+ // NAME IS A LIE — kept for call-site stability. This returns the main
1297
+ // worktree's branch (the user's originating branch), NOT git config user.name.
1298
+ // The old heuristic (git config user.name) assumed the user named their branch
1299
+ // after their git username and produced phantom branches like "xyz9516" when
1300
+ // the real branch was "xyz951610", causing post-complete checkout failures.
1301
+ // `git worktree list --porcelain` first stanza is always the main worktree.
1302
+ const raw = git('worktree list --porcelain', { cwd: projectRoot, throws: false, raw: true });
1303
+ if (!raw) return null;
1304
+ for (const line of raw.split(/\r?\n/)) {
1305
+ const m = line.match(/^branch refs\/heads\/(.+)$/);
1306
+ if (m && !m[1].startsWith('task/')) return m[1];
1307
+ }
1308
+ return null;
1309
+ }
1310
+
1311
+ function getBaseBranch(projectRoot) {
1312
+ if (branchExists('main', projectRoot)) return 'main';
1313
+ if (branchExists('master', projectRoot)) return 'master';
1314
+ return getCurrentBranch(projectRoot) || 'HEAD';
1315
+ }
1316
+
1317
+ function resolveUserBranch(projectRoot) {
1318
+ const currentBranch = getCurrentBranch(projectRoot);
1319
+ const gitUserBranch = getGitUserBranch(projectRoot);
1320
+ const dispatchBranch = getDispatchBranch(projectRoot);
1321
+ // dispatchBranch is ground truth: recorded at dispatch time from the main
1322
+ // repo's HEAD. currentBranch can be task/* when post-complete runs from
1323
+ // the worktree. gitUserBranch (git config user.name) is a weak heuristic
1324
+ // that assumes the branch is named after the user — fails for branches
1325
+ // like "xyz951610" when user.name is "xyz9516" (phantom-branch checkout).
1326
+ if (dispatchBranch) return dispatchBranch;
1327
+ if (currentBranch && !currentBranch.startsWith('task/')) return currentBranch;
1328
+ if (gitUserBranch) return gitUserBranch;
1329
+ return getBaseBranch(projectRoot);
1330
+ }
1331
+
1332
+ function hasChanges(projectRoot) {
1333
+ const status = git('status --porcelain', { cwd: projectRoot });
1334
+ return status && status.length > 0;
1335
+ }
1336
+
1337
+ function getUnmergedPaths(projectRoot) {
1338
+ const out = git('diff --name-only --diff-filter=U', { cwd: projectRoot, throws: false });
1339
+ if (!out || !out.trim()) return [];
1340
+ return out.trim().split('\n').map(s => s.trim()).filter(Boolean);
1341
+ }
1342
+
1343
+ function acquireGitLock(taskId, projectRoot) {
1344
+ const lockPath = getGitLockPath(projectRoot);
1345
+ const deadline = Date.now() + GIT_LOCK_TIMEOUT_MS;
1346
+ while (Date.now() < deadline) {
1347
+ try {
1348
+ // wx = exclusive create — atomic, fails if file already exists
1349
+ fs.writeFileSync(lockPath, JSON.stringify({
1350
+ pid: process.pid,
1351
+ taskId,
1352
+ acquiredAt: new Date().toISOString(),
1353
+ }), { flag: 'wx' });
1354
+ console.log(` 🔒 Git lock acquired for ${taskId}`);
1355
+ return true;
1356
+ } catch (e) {
1357
+ if (e.code === 'EEXIST') {
1358
+ // Check if lock holder process is still alive
1359
+ try {
1360
+ const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
1361
+ process.kill(lock.pid, 0); // throws if process is dead
1362
+ // Process alive — wait and retry
1363
+ console.log(` ⏳ Waiting for git lock (held by ${lock.taskId}, pid ${lock.pid})...`);
1364
+ } catch {
1365
+ // Lock holder is dead — steal the lock
1366
+ try { fs.unlinkSync(lockPath); } catch {}
1367
+ continue;
1368
+ }
1369
+ const wait = Date.now() + GIT_LOCK_POLL_MS;
1370
+ while (Date.now() < wait) { /* busy wait */ }
1371
+ } else {
1372
+ throw e;
1373
+ }
1374
+ }
1375
+ }
1376
+ // Timed out — force acquire to avoid permanent deadlock
1377
+ console.log(' ⚠️ Timed out waiting for git lock, forcing acquire');
1378
+ try { fs.unlinkSync(lockPath); } catch {}
1379
+ fs.writeFileSync(lockPath, JSON.stringify({
1380
+ pid: process.pid, taskId, acquiredAt: new Date().toISOString(),
1381
+ }));
1382
+ return true;
1383
+ }
1384
+
1385
+ function releaseGitLock(projectRoot) {
1386
+ try {
1387
+ fs.unlinkSync(getGitLockPath(projectRoot));
1388
+ console.log(' 🔓 Git lock released');
1389
+ } catch {}
1390
+ }
1391
+
1392
+ // ─────────────────────────────────────────
1393
+ // Merge Lock(輕量鎖,只序列化 merge 步驟)
1394
+ // Used in worktree mode — replaces broad git lock for parallel dispatch.
1395
+ // Short-lived (seconds, not minutes). Only held during `git merge`.
1396
+ // ─────────────────────────────────────────
1397
+
1398
+ const MERGE_LOCK_TIMEOUT_MS = 60000; // 1 minute max wait
1399
+ const MERGE_LOCK_POLL_MS = 500;
1400
+
1401
+ function getMergeLockPath(projectRoot) {
1402
+ return path.join(projectRoot, '.formation', '.merge-lock');
1403
+ }
1404
+
1405
+ function acquireMergeLock(taskId, projectRoot) {
1406
+ const lockPath = getMergeLockPath(projectRoot);
1407
+ const formDir = path.join(projectRoot, '.formation');
1408
+ if (!fs.existsSync(formDir)) fs.mkdirSync(formDir, { recursive: true });
1409
+ const deadline = Date.now() + MERGE_LOCK_TIMEOUT_MS;
1410
+ while (Date.now() < deadline) {
1411
+ try {
1412
+ fs.writeFileSync(lockPath, JSON.stringify({
1413
+ pid: process.pid, taskId, acquiredAt: new Date().toISOString(),
1414
+ }), { flag: 'wx' });
1415
+ console.log(` 🔒 Merge lock acquired for ${taskId}`);
1416
+ return true;
1417
+ } catch (e) {
1418
+ if (e.code === 'EEXIST') {
1419
+ try {
1420
+ const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
1421
+ try {
1422
+ process.kill(lock.pid, 0); // throws if process is dead
1423
+ console.log(` ⏳ Waiting for merge lock (held by ${lock.taskId}, pid ${lock.pid})...`);
1424
+ } catch {
1425
+ // Lock holder is dead — steal the lock
1426
+ try { fs.unlinkSync(lockPath); } catch {}
1427
+ continue;
1428
+ }
1429
+ } catch {
1430
+ // Corrupt lock file — steal it
1431
+ try { fs.unlinkSync(lockPath); } catch {}
1432
+ continue;
1433
+ }
1434
+ const wait = Date.now() + MERGE_LOCK_POLL_MS;
1435
+ while (Date.now() < wait) { /* busy wait */ }
1436
+ } else {
1437
+ throw e;
1438
+ }
1439
+ }
1440
+ }
1441
+ // Timed out — force acquire to avoid deadlock
1442
+ console.log(' ⚠️ Timed out waiting for merge lock, forcing acquire');
1443
+ try { fs.unlinkSync(lockPath); } catch {}
1444
+ fs.writeFileSync(lockPath, JSON.stringify({
1445
+ pid: process.pid, taskId, acquiredAt: new Date().toISOString(),
1446
+ }));
1447
+ return true;
1448
+ }
1449
+
1450
+ function releaseMergeLock(projectRoot) {
1451
+ // TASK_1391: retry on EBUSY (Windows: AV / file watcher may briefly hold a handle).
1452
+ // Silent on ENOENT (already released). Warn (not silent) on persistent failure so
1453
+ // operators can spot the leak without breaking dispatch flow.
1454
+ const lockPath = getMergeLockPath(projectRoot);
1455
+ for (let i = 0; i < 3; i++) {
1456
+ try {
1457
+ fs.unlinkSync(lockPath);
1458
+ console.log(' 🔓 Merge lock released');
1459
+ return true;
1460
+ } catch (e) {
1461
+ if (e.code === 'ENOENT') return true;
1462
+ if (e.code === 'EBUSY' && i < 2) {
1463
+ const wait = Date.now() + 100;
1464
+ while (Date.now() < wait) {}
1465
+ continue;
1466
+ }
1467
+ console.warn(` ⚠️ releaseMergeLock failed (${e.code || e.message}) — lock may persist`);
1468
+ return false;
1469
+ }
1470
+ }
1471
+ return false;
1472
+ }
1473
+
1474
+ /**
1475
+ * Atomically decrement the parallel dispatch semaphore.
1476
+ * Mirrors _decrementParallelCount in headless.js.
1477
+ * Called after successful worktree merge + removal.
1478
+ */
1479
+ function decrementParallelSemaphore() {
1480
+ const countFile = path.join(ROOT, '.formation', '.parallel-count');
1481
+ const countLock = path.join(ROOT, '.formation', '.parallel-count-lock');
1482
+ const deadline = Date.now() + 10000;
1483
+ while (Date.now() < deadline) {
1484
+ let lockFd = null;
1485
+ try {
1486
+ lockFd = fs.openSync(countLock, 'wx');
1487
+ try {
1488
+ const count = fs.existsSync(countFile) ? (parseInt(fs.readFileSync(countFile, 'utf-8').trim()) || 0) : 0;
1489
+ fs.writeFileSync(countFile, String(Math.max(0, count - 1)));
1490
+ } finally {
1491
+ fs.closeSync(lockFd);
1492
+ try { fs.unlinkSync(countLock); } catch {}
1493
+ }
1494
+ console.log(' 📉 Parallel semaphore decremented');
1495
+ return;
1496
+ } catch {
1497
+ if (lockFd !== null) { try { fs.closeSync(lockFd); } catch {} }
1498
+ const wait = Date.now() + 200;
1499
+ while (Date.now() < wait) { /* busy wait */ }
1500
+ }
1501
+ }
1502
+ console.log(' ⚠️ Semaphore decrement timed out — slot may be leaked');
1503
+ }
1504
+
1505
+ /**
1506
+ * Remove worktree and decrement parallel semaphore.
1507
+ * Called after successful merge in worktree mode.
1508
+ * On conflict (merge failed), worktree is preserved — do NOT call this.
1509
+ * @param {string} worktreePath - absolute path to worktree directory
1510
+ * @param {string} projectRoot - main repo root
1511
+ */
1512
+ function cleanupWorktree(worktreePath, projectRoot) {
1513
+ if (!worktreePath) return;
1514
+ try {
1515
+ const utils = require('./utils');
1516
+ const removed = utils.removeWorktree(projectRoot, worktreePath);
1517
+ console.log(removed
1518
+ ? ` 🧹 Worktree removed: ${path.relative(projectRoot, worktreePath)}`
1519
+ : ` ⚠️ Worktree removal failed or already gone: ${path.basename(worktreePath)}`
1520
+ );
1521
+ } catch (e) {
1522
+ console.log(` ⚠️ Worktree cleanup error: ${e.message}`);
1523
+ }
1524
+ decrementParallelSemaphore();
1525
+ }
1526
+
1527
+ function isVerifiedMergeSuccess(gitResult) {
1528
+ return Boolean(gitResult && gitResult.merged === true && gitResult.verify_merge_landed === true);
1529
+ }
1530
+
1531
+ function ensureSalvageBranch(projectRoot, taskBranch) {
1532
+ if (!projectRoot || !taskBranch || !branchExists(taskBranch, projectRoot)) return null;
1533
+
1534
+ const salvageBranch = `${taskBranch}_salvage_${Date.now()}`;
1535
+ const created = git(`branch ${salvageBranch} ${taskBranch}`, { cwd: projectRoot, throws: false });
1536
+ if (created === null) return null;
1537
+
1538
+ console.log(` 💾 Salvage branch created: ${salvageBranch}`);
1539
+ return salvageBranch;
1540
+ }
1541
+
1542
+ /**
1543
+ * Convert a leaked stash entry into a discoverable salvage branch + drop the stash.
1544
+ * Used when stash pop fails (typically dirty WD overlapping with merged content)
1545
+ * — without this, the stash silently accumulates (29-stash-leak bug, 2026-05-05).
1546
+ *
1547
+ * Implementation: a stash IS a commit (stash@{N} resolves to a SHA representing
1548
+ * the WIP tree). We point a branch ref at that SHA, then `stash drop` to clear
1549
+ * the entry. Recovery is then `git stash apply <salvage_branch>` or
1550
+ * `git checkout <salvage_branch>` to view the WIP state.
1551
+ *
1552
+ * @returns branch name on success, null on failure (stash kept intact)
1553
+ */
1554
+ function salvageStashAsBranch(projectRoot, taskId, stashRef) {
1555
+ if (!projectRoot) return null;
1556
+ const ref = stashRef || 'stash@{0}';
1557
+ const sha = git(`rev-parse ${ref}`, { cwd: projectRoot, throws: false });
1558
+ if (!sha || !sha.trim()) return null;
1559
+
1560
+ const stashSha = sha.trim();
1561
+ const salvage = `stash-salvage/${taskId || 'unknown'}-${Date.now()}`;
1562
+ const created = git(`branch ${salvage} ${stashSha}`, { cwd: projectRoot, throws: false });
1563
+ if (created === null) return null;
1564
+
1565
+ // Drop the stash only after the branch ref is in place — protects against
1566
+ // the unconditional-drop bug at the recovery path (changes lost when restore failed).
1567
+ git(`stash drop ${ref}`, { cwd: projectRoot, throws: false });
1568
+
1569
+ console.log(` 💾 Stash salvaged → branch ${salvage}`);
1570
+ console.log(` 💡 Recovery: git stash apply ${salvage} (or checkout to inspect)`);
1571
+ return salvage;
1572
+ }
1573
+
1574
+ function verify_merge_landed(projectRoot, taskBranch, targetBranch, taskBranchCommit) {
1575
+ // TASK_1525: Distinguish 'unknown' (attempted, failed) from null (not attempted).
1576
+ // taskBranchCommit may already be 'unknown' from the caller's capture step.
1577
+ let commitHash = '';
1578
+ if (taskBranchCommit && taskBranchCommit !== 'unknown') {
1579
+ commitHash = taskBranchCommit.trim();
1580
+ } else if (!taskBranchCommit) {
1581
+ // Not yet captured by caller — attempt here as fallback
1582
+ const raw = git(`rev-parse ${taskBranch}`, { cwd: projectRoot, throws: false });
1583
+ commitHash = (raw || '').trim();
1584
+ if (raw === null) {
1585
+ console.log(` ⚠️ [TASK_1525] verify_merge_landed: rev-parse ${taskBranch} failed — commitHash unknown`);
1586
+ }
1587
+ }
1588
+ // else taskBranchCommit === 'unknown' — commitHash stays ''
1589
+
1590
+ let targetHead = 'unknown';
1591
+ const rawTarget = git(`rev-parse ${targetBranch}`, { cwd: projectRoot, throws: false });
1592
+ if (rawTarget !== null) {
1593
+ targetHead = rawTarget.trim() || 'unknown';
1594
+ } else {
1595
+ console.log(` ⚠️ [TASK_1525] verify_merge_landed: rev-parse ${targetBranch} failed — targetHead unknown`);
1596
+ }
1597
+
1598
+ if (!commitHash) {
1599
+ return {
1600
+ ok: false,
1601
+ reason: taskBranchCommit === 'unknown' ? 'task_branch_commit_capture_failed' : 'missing_task_branch_commit',
1602
+ taskBranchCommit: taskBranchCommit === 'unknown' ? 'unknown' : null,
1603
+ targetHead,
1604
+ };
1605
+ }
1606
+
1607
+ const probe = spawnSync('git', ['merge-base', '--is-ancestor', commitHash, targetBranch], {
1608
+ cwd: projectRoot,
1609
+ encoding: 'utf8',
1610
+ timeout: 8000,
1611
+ windowsHide: true,
1612
+ });
1613
+
1614
+ if (probe.status !== 0) {
1615
+ return {
1616
+ ok: false,
1617
+ reason: (probe.stderr || probe.stdout || 'merge commit not reachable from target HEAD').trim(),
1618
+ taskBranchCommit: commitHash,
1619
+ targetHead,
1620
+ };
1621
+ }
1622
+
1623
+ // TASK_1593: Content check — verify task branch files actually arrived in targetBranch.
1624
+ // Ancestor check above only proves the merge commit exists; it does NOT prove the
1625
+ // files changed by the task branch are present in targetBranch HEAD. In the regression
1626
+ // scenario (TASK_1590/1591) a false-positive merged=true was reported while real task
1627
+ // content was missing and only transcript/daemon noise had been committed.
1628
+ //
1629
+ // Strategy: list files changed by taskBranch vs its merge-base with targetBranch,
1630
+ // exclude daemon/transcript noise, then diff commitHash..targetHead -- <agentFiles>.
1631
+ // An empty diff means content landed; non-empty means merge_content_missing.
1632
+ // Non-fatal on errors: if we cannot compute, fall through to ancestor_verified.
1633
+ try {
1634
+ const mbOut = spawnSync('git', ['merge-base', commitHash, targetBranch], {
1635
+ cwd: projectRoot, encoding: 'utf8', timeout: 8000, windowsHide: true,
1636
+ });
1637
+ const mergeBase = mbOut.status === 0 ? mbOut.stdout.trim() : '';
1638
+ if (mergeBase) {
1639
+ const changedOut = spawnSync('git', ['diff', '--name-only', mergeBase, commitHash, '--'], {
1640
+ cwd: projectRoot, encoding: 'utf8', timeout: 8000, windowsHide: true,
1641
+ });
1642
+ const taskFiles = changedOut.status === 0
1643
+ ? changedOut.stdout.trim().split('\n').filter(Boolean)
1644
+ : [];
1645
+ // Exclude daemon/transcript noise paths from the content check
1646
+ const CONTENT_NOISE_PREFIXES = [
1647
+ '.kenaz/transcripts/', '.kenaz/specialist/', '.kenaz/efe_snapshots/',
1648
+ '.kenaz/logs/', '.formation/signals/', '.synapse/', '.formation/muninn_snapshot',
1649
+ ];
1650
+ const CONTENT_NOISE_EXACT = [
1651
+ 'dashboard/package.json', 'dashboard/src-tauri/Cargo.lock', 'package-lock.json',
1652
+ '.formation/.active-dispatches.json',
1653
+ ];
1654
+ const agentFiles = taskFiles.filter(f => {
1655
+ if (!f) return false;
1656
+ if (CONTENT_NOISE_EXACT.includes(f)) return false;
1657
+ return !CONTENT_NOISE_PREFIXES.some(p => f.startsWith(p));
1658
+ });
1659
+ if (agentFiles.length > 0) {
1660
+ const diffArgs = ['diff', '--name-only', commitHash, targetHead, '--'].concat(agentFiles);
1661
+ const contentDiff = spawnSync('git', diffArgs, {
1662
+ cwd: projectRoot, encoding: 'utf8', timeout: 10000, windowsHide: true,
1663
+ });
1664
+ const missingFiles = contentDiff.status === 0
1665
+ ? contentDiff.stdout.trim().split('\n').filter(Boolean)
1666
+ : [];
1667
+ if (missingFiles.length > 0) {
1668
+ console.log(' [TASK_1593] content_check FAILED: ' + missingFiles.length + ' agent file(s) missing from ' + targetBranch);
1669
+ for (const mf of missingFiles.slice(0, 5)) {
1670
+ console.log(' missing: ' + mf);
1671
+ }
1672
+ return {
1673
+ ok: false,
1674
+ reason: 'merge_content_missing',
1675
+ taskBranchCommit: commitHash,
1676
+ targetHead,
1677
+ missingFiles,
1678
+ };
1679
+ }
1680
+ console.log(' [TASK_1593] content_check OK: ' + agentFiles.length + ' agent file(s) verified in ' + targetBranch);
1681
+ }
1682
+ }
1683
+ } catch (ccErr) {
1684
+ // Non-fatal: ancestor check already passed; don't block on content-check failure
1685
+ console.log(' [TASK_1593] content_check error (non-fatal): ' + (ccErr.message || '').split('\n')[0]);
1686
+ }
1687
+
1688
+ return {
1689
+ ok: true,
1690
+ reason: 'ancestor_verified',
1691
+ taskBranchCommit: commitHash,
1692
+ targetHead,
1693
+ };
1694
+ }
1695
+
1696
+ // B10: Daemon-managed cognitive state files. Touched as a side effect of any
1697
+ // Ansuz session running (counters, observations, inbox polls), never as
1698
+ // intentional task work. Used by:
1699
+ // - worktreeCheckoutRevertPaths (revert in main repo before worktree merge)
1700
+ // - worktree commit step (reset out of index after `git add -A` so the
1701
+ // agent's intended work is captured but daemon noise is not)
1702
+ // Listed explicitly (not wildcarded) so a task that legitimately scopes one
1703
+ // of these files still has its change committed via the scoped-add path.
1704
+ // D.2 (TASK_1210): Daemon-managed paths loaded from YAML manifest.
1705
+ // Add new daemon files to .kenaz/specialist/daemon-manifest.yaml — no code change needed.
1706
+ let _daemonManifestCache = null;
1707
+ function loadDaemonManifest() {
1708
+ if (_daemonManifestCache) return _daemonManifestCache;
1709
+ const manifestPath = path.join(ROOT, '.kenaz', 'specialist', 'daemon-manifest.yaml');
1710
+ try {
1711
+ let yaml;
1712
+ try { yaml = require('js-yaml'); } catch {
1713
+ const candidates = [
1714
+ path.resolve(__dirname, '../../.vendor/js-yaml.cjs'),
1715
+ path.resolve(__dirname, '.vendor/js-yaml.cjs'),
1716
+ ];
1717
+ for (const c of candidates) { try { yaml = require(c); break; } catch {} }
1718
+ }
1719
+ if (!yaml) throw new Error('js-yaml not found');
1720
+ const raw = yaml.load(fs.readFileSync(manifestPath, 'utf8'));
1721
+ _daemonManifestCache = {
1722
+ stateFiles: Array.isArray(raw?.state_files) ? raw.state_files : [],
1723
+ staticPaths: Array.isArray(raw?.static_paths) ? raw.static_paths : [],
1724
+ };
1725
+ } catch (e) {
1726
+ console.log(` ⚠️ daemon-manifest.yaml load failed (${e.message}), using inline fallback`);
1727
+ _daemonManifestCache = {
1728
+ stateFiles: [
1729
+ '.kenaz/specialist/turn_context_state.json', '.kenaz/specialist/ansuz_rules.yaml',
1730
+ '.kenaz/specialist/ansuz_session.json', '.kenaz/specialist/ansuz_state.json',
1731
+ '.kenaz/specialist/ansuz_inbox.json', '.kenaz/specialist/ambient_signals.json',
1732
+ '.kenaz/huginn-attention.json', '.kenaz/specialist/ritual_state.json',
1733
+ ],
1734
+ staticPaths: [
1735
+ '.formation/.parallel-count', '.formation/.active-dispatches.json',
1736
+ '.synapse/db/index_state.json', '.tmp/', 'claude', '.formation/signals/',
1737
+ ],
1738
+ };
1739
+ }
1740
+ return _daemonManifestCache;
1741
+ }
1742
+ function getDaemonStateFiles() { return loadDaemonManifest().stateFiles; }
1743
+ function getDaemonStaticPaths() { return loadDaemonManifest().staticPaths; }
1744
+ // ?????????????????????????????????????????????????????????????????????????????
1745
+ // SECTION: TASK_UTILITIES
1746
+ // ?????????????????????????????????????????????????????????????????????????????
1747
+ /**
1748
+ * Write merge conflict info to task YAML notes field.
1749
+ * Preserves worktree path for manual resolution.
1750
+ */
1751
+ function writeConflictNotesToTask(taskId, taskBranch, userBranch, errMsg, worktreePath, projectPath) {
1752
+ try {
1753
+ const worktreeNote = worktreePath ? ` Worktree at: ${worktreePath}` : '';
1754
+ // TASK_1957: verbatim git stderr (not just the first line) so a working-tree
1755
+ // collision ("local changes to X would be overwritten") is diagnosable from
1756
+ // the task notes alone, without re-running the merge by hand.
1757
+ const fullErr = String(errMsg || '').trim();
1758
+ const note = `Merge failed: ${taskBranch} -> ${userBranch}. ${fullErr}.${worktreeNote} Resolve manually then run post-complete.`;
1759
+ const updateArgs = projectPath ? ['--project-path', projectPath] : [];
1760
+ updateArgs.push('update', taskId, '--notes', note);
1761
+ spawnSync('node', [COLLABORATION_API, ...updateArgs], {
1762
+ encoding: 'utf8', timeout: 10000, cwd: ROOT, windowsHide: true,
1763
+ });
1764
+ } catch { /* best-effort */ }
1765
+ }
1766
+
1767
+ /**
1768
+ * TASK_2233: failure_reason for a git step that did not land. 'merge_conflict' only when
1769
+ * git reported a conflict (mergeConflict bucket / conflictFiles) — never as the catch-all.
1770
+ */
1771
+ function classifyMergeFailureReason(g) {
1772
+ if (g?.failure_reason) return g.failure_reason;
1773
+ if (g?.rebaseConflict) return 'rebase_conflict';
1774
+ if (g?.checkoutBlocked) return 'checkout_blocked';
1775
+ if (g?.missingTaskBranch) return 'task_branch_missing';
1776
+ if (/author identity unknown|please tell me who you are|empty ident name/i.test(`${g?.mergeErrorText || ''} ${g?.gitException || ''}`)) return 'git_identity_missing';
1777
+ if (g?.mergeConflict || (g?.conflictFiles && g.conflictFiles.length)) return 'merge_conflict';
1778
+ return 'git_step_failed';
1779
+ }
1780
+
1781
+ function blockTaskForMergeFailure(taskId, projectPath, gitResult) {
1782
+ if (!taskId) return false;
1783
+
1784
+ const isRebaseConflict = gitResult?.rebaseConflict === true;
1785
+ const isLastChanceOrphan = gitResult?.lastChanceRecoveryOrphan === true;
1786
+ const vitals = {
1787
+ // TASK_1588: checkoutBlocked = step-3 checkout threw; no merge was attempted
1788
+ // TASK_1591: also check missingTaskBranch (ansuz-wt-* branch naming) and gitResult.failure_reason (set by git-merge.js)
1789
+ failure_reason: classifyMergeFailureReason(gitResult),
1790
+ merge_failure_mode: gitResult?.blockedReason || (isRebaseConflict ? 'rebase_conflict' : (gitResult?.verify_merge_landed === false ? (gitResult?.verify_merge_landed_reason || 'verify_merge_landed') : 'git_merge_failed')),
1791
+ conflict_files: gitResult?.conflictFiles || null,
1792
+ verify_merge_landed: gitResult?.verify_merge_landed === true,
1793
+ // TASK_1456: propagate verify_merge_landed_reason into vitals so task YAML records it
1794
+ ...(gitResult?.verify_merge_landed_reason ? { verify_merge_landed_reason: gitResult.verify_merge_landed_reason } : {}),
1795
+ task_branch: gitResult?.taskBranch || null,
1796
+ // TASK_1525: preserve 'unknown' sentinel (attempted but failed) vs null (not attempted)
1797
+ task_branch_commit: gitResult?.taskBranchCommit ?? null,
1798
+ target_branch: gitResult?.verify_merge_target || gitResult?.userBranch || null,
1799
+ // TASK_1525: preserve 'unknown' sentinel from verify_merge_landed
1800
+ target_head: gitResult?.targetHead ?? null,
1801
+ salvage_branch: gitResult?.salvageBranch || null,
1802
+ stash_salvage_branch: gitResult?.stashSalvageBranch || null,
1803
+ conflict_worktree_path: gitResult?.conflictWorktreePath || null,
1804
+ // TASK_1957: verbatim git error text (working-tree collisions and other
1805
+ // non-conflict merge failures were previously indistinguishable from a
1806
+ // real content conflict once reduced to failure_reason='merge_conflict').
1807
+ ...(gitResult?.mergeErrorText ? { git_error_text: gitResult.mergeErrorText } : {}),
1808
+ // TASK_1456: include last-chance recovery orphan details for investigation
1809
+ ...(isLastChanceOrphan ? {
1810
+ last_chance_recovery_orphan: true,
1811
+ last_chance_recovery_hash: gitResult?.lastChanceRecoveryHash || null,
1812
+ } : {}),
1813
+ // TASK_1525: propagate git ref capture failure notes so debugging is possible
1814
+ ...(gitResult?.notes ? { notes: gitResult.notes } : {}),
1815
+ };
1816
+
1817
+ const note = [
1818
+ 'post-complete merge failure: task moved to blocked',
1819
+ vitals.target_branch ? `target=${vitals.target_branch}` : null,
1820
+ vitals.task_branch_commit ? `task_commit=${vitals.task_branch_commit}` : null,
1821
+ gitResult?.conflictSummary ? `summary=${gitResult.conflictSummary}` : null,
1822
+ // TASK_1957: full verbatim git error text (do not truncate to conflictSummary's
1823
+ // first line) — this is what turns "failure_reason=merge_conflict" into an
1824
+ // actionable diagnosis instead of a re-run-it-yourself label.
1825
+ gitResult?.mergeErrorText ? `git_error=${gitResult.mergeErrorText.replace(/\n/g, ' \\n ')}` : null,
1826
+ gitResult?.verify_merge_landed === false ? `verify_merge_landed=${gitResult.verify_merge_landed_reason || 'false'}` : null,
1827
+ gitResult?.salvageBranch ? `salvage_branch=${gitResult.salvageBranch}` : null,
1828
+ gitResult?.conflictWorktreePath ? `worktree=${gitResult.conflictWorktreePath}` : null,
1829
+ ].filter(Boolean).join(' | ');
1830
+
1831
+ const payload = { taskId, projectPath, vitals, note };
1832
+
1833
+ const script = `
1834
+ const { TaskAPI } = require(${JSON.stringify(path.join(ROOT, '.collaboration', 'core', 'task-api.js'))});
1835
+ (async () => {
1836
+ const payload = JSON.parse(process.argv[1]);
1837
+ const api = new TaskAPI(payload.projectPath ? { projectPath: payload.projectPath } : {});
1838
+ const task = await api.getTask(payload.taskId);
1839
+ if (!task) throw new Error('task not found');
1840
+ const updatedTask = {
1841
+ ...task,
1842
+ status: 'blocked',
1843
+ notes: [task.notes, payload.note].filter(Boolean).join('\\n'),
1844
+ vitals: { ...(task.vitals || {}), ...(payload.vitals || {}) },
1845
+ };
1846
+ await api.moveTask(payload.taskId, 'blocked', updatedTask, { skipValidation: true });
1847
+ })().catch((err) => {
1848
+ console.error(err.message || String(err));
1849
+ process.exit(1);
1850
+ });
1851
+ `;
1852
+ const updateResult = spawnSync('node', ['-e', script, JSON.stringify(payload)], {
1853
+ cwd: ROOT,
1854
+ encoding: 'utf8',
1855
+ timeout: 15000,
1856
+ windowsHide: true,
1857
+ });
1858
+
1859
+ if (updateResult.status !== 0) {
1860
+ const errMsg = (updateResult.stderr || updateResult.stdout || '').trim();
1861
+ console.log(` ⚠️ Failed to move ${taskId} to blocked/: ${errMsg.split('\n')[0] || 'unknown error'}`);
1862
+ return false;
1863
+ }
1864
+
1865
+ console.log(` 🚫 Task ${taskId} moved to blocked/ (failure_reason=${vitals.failure_reason})`);
1866
+ return true;
1867
+ }
1868
+
1869
+ // Write merge_landed=true/false to task YAML after completion so consumers can filter accurately.
1870
+ // Best-effort — never blocks post-complete.
1871
+ function annotateMergeLanded(taskId, projectPath, landed) {
1872
+ if (!taskId) return;
1873
+ try {
1874
+ const updateArgs = projectPath ? ['--project-path', projectPath] : [];
1875
+ updateArgs.push('update', taskId, `--merge-landed=${landed ? 'true' : 'false'}`);
1876
+ spawnSync('node', [COLLABORATION_API, ...updateArgs], {
1877
+ encoding: 'utf8', timeout: 10000, cwd: ROOT, windowsHide: true,
1878
+ });
1879
+ } catch { /* best-effort */ }
1880
+ }
1881
+
1882
+ // Regression Guard constants moved to ./post-complete/testing.js
1883
+
1884
+ /** Synchronous sleep using Atomics.wait (safe in Node.js main thread). */
1885
+
1886
+ /**
1887
+ * 快照 tasks/ 所有 YAML 狀態
1888
+ * git checkout 到舊 branch 會還原已提交的 task 檔案位置,
1889
+ * 快照讓我們在 git 操作後恢復正確狀態。
1890
+ */
1891
+ function snapshotTaskFiles(tasksBase) {
1892
+ const snapshot = {};
1893
+ for (const dir of TASK_STATUS_DIRS) {
1894
+ const full = path.join(tasksBase, dir);
1895
+ if (!fs.existsSync(full)) continue;
1896
+ snapshot[dir] = {};
1897
+ for (const f of fs.readdirSync(full).filter(x => x.endsWith('.yaml'))) {
1898
+ snapshot[dir][f] = fs.readFileSync(path.join(full, f), 'utf8');
1899
+ }
1900
+ }
1901
+ return snapshot;
1902
+ }
1903
+
1904
+ /**
1905
+ * 從快照恢復 task 檔案狀態,修正 git checkout 造成的位移
1906
+ */
1907
+ function restoreTaskFiles(snapshot, tasksBase) {
1908
+ // Build lookup: fileName → correct dir
1909
+ const correctLocation = {};
1910
+ for (const [dir, files] of Object.entries(snapshot)) {
1911
+ for (const f of Object.keys(files)) {
1912
+ correctLocation[f] = dir;
1913
+ }
1914
+ }
1915
+
1916
+ for (const dir of TASK_STATUS_DIRS) {
1917
+ const full = path.join(tasksBase, dir);
1918
+ if (!fs.existsSync(full)) {
1919
+ if (snapshot[dir] && Object.keys(snapshot[dir]).length > 0) {
1920
+ fs.mkdirSync(full, { recursive: true });
1921
+ } else {
1922
+ continue;
1923
+ }
1924
+ }
1925
+
1926
+ // Remove files that git checkout restored into the wrong dir
1927
+ for (const f of fs.readdirSync(full).filter(x => x.endsWith('.yaml'))) {
1928
+ if (correctLocation[f] === dir) continue; // correct location
1929
+ if (correctLocation[f]) {
1930
+ // File belongs in a different dir — remove from here
1931
+ try { fs.unlinkSync(path.join(full, f)); } catch {}
1932
+ }
1933
+ // Files not in snapshot (newly created by another agent) — leave them
1934
+ }
1935
+
1936
+ // Restore files that should be in this dir
1937
+ if (snapshot[dir]) {
1938
+ for (const [f, content] of Object.entries(snapshot[dir])) {
1939
+ const filePath = path.join(full, f);
1940
+ if (!fs.existsSync(filePath)) {
1941
+ fs.writeFileSync(filePath, content, 'utf8');
1942
+ }
1943
+ }
1944
+ }
1945
+ }
1946
+
1947
+ // Dedup pass: if a TASK_ID appears in multiple dirs (e.g., git merge restored
1948
+ // a file that was created by a parallel agent AFTER the snapshot), keep only
1949
+ // the copy in the most-progressed status dir.
1950
+ const STATUS_PRIORITY = { cancel: 0, blocked: 1, backlog: 2, active: 3, done: 4, archived: 5 };
1951
+ const taskLocations = {}; // taskId → [{ dir, fileName, priority }]
1952
+ for (const dir of TASK_STATUS_DIRS) {
1953
+ const full = path.join(tasksBase, dir);
1954
+ if (!fs.existsSync(full)) continue;
1955
+ for (const f of fs.readdirSync(full).filter(x => x.endsWith('.yaml'))) {
1956
+ const match = f.match(/^(TASK_\d+)/);
1957
+ if (!match) continue;
1958
+ const taskId = match[1];
1959
+ if (!taskLocations[taskId]) taskLocations[taskId] = [];
1960
+ taskLocations[taskId].push({ dir, fileName: f, priority: STATUS_PRIORITY[dir] ?? -1 });
1961
+ }
1962
+ }
1963
+ for (const [, locations] of Object.entries(taskLocations)) {
1964
+ if (locations.length <= 1) continue;
1965
+ // Keep the highest-priority (most progressed) copy
1966
+ locations.sort((a, b) => b.priority - a.priority);
1967
+ const keep = locations[0];
1968
+ for (let i = 1; i < locations.length; i++) {
1969
+ const dup = locations[i];
1970
+ try {
1971
+ fs.unlinkSync(path.join(tasksBase, dup.dir, dup.fileName));
1972
+ console.log(` 🧹 Dedup: removed ${dup.dir}/${dup.fileName} (kept in ${keep.dir}/)`);
1973
+ } catch {}
1974
+ }
1975
+ }
1976
+ }
1977
+
1978
+ // ─────────────────────────────────────────
1979
+ // Git:建立分支、commit、merge 到用戶分支
1980
+ // ─────────────────────────────────────────
1981
+
1982
+ /**
1983
+ * 收集所有 pending 任務(backlog/ active/ blocked/)的依賴 ID
1984
+ * 回傳一個 Set,包含仍被其他任務依賴的 task ID
1985
+ * @param {string} tasksBase - 任務根目錄(如 .collaboration/tasks/ 或 .kenaz/tasks/)
1986
+ * @returns {Set<string>} 被依賴的 task ID 集合
1987
+ */
1988
+ function getDependedIds(tasksBase) {
1989
+ const depended = new Set();
1990
+ const pendingDirs = ['backlog', 'active', 'blocked', 'todo'];
1991
+ for (const dir of pendingDirs) {
1992
+ const dirPath = path.join(tasksBase, dir);
1993
+ if (!fs.existsSync(dirPath)) continue;
1994
+ const files = fs.readdirSync(dirPath).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
1995
+ for (const file of files) {
1996
+ try {
1997
+ const content = fs.readFileSync(path.join(dirPath, file), 'utf8');
1998
+ // 簡易 YAML 解析 dependencies 陣列,避免引入 yaml lib 依賴
1999
+ const match = content.match(/^dependencies:\s*\n((?:\s+-\s+.+\n?)*)/m);
2000
+ if (match) {
2001
+ const deps = match[1].split('\n')
2002
+ .map(line => line.match(/^\s+-\s+"?(TASK_\d+)"?\s*$/))
2003
+ .filter(Boolean)
2004
+ .map(m => m[1]);
2005
+ for (const dep of deps) depended.add(dep);
2006
+ }
2007
+ } catch { /* skip unreadable files */ }
2008
+ }
2009
+ }
2010
+ return depended;
2011
+ }
2012
+ // SECTION: TESTING (extracted to ./post-complete/testing.js)
2013
+ // ?????????????????????????????????????????????????????????????????????????????
2014
+ const {
2015
+ initTesting,
2016
+ detectAllTestCommands,
2017
+ runRegressionTests,
2018
+ hasOpenRegressionFixTask,
2019
+ createRegressionFixTask,
2020
+ } = require('./post-complete/testing');
2021
+ initTesting({ stripAnsi, sleepSync });
2022
+ // ?????????????????????????????????????????????????????????????????????????????
2023
+ // SECTION: STAGING_AND_COVERAGE (extracted to ./post-complete/staging.js)
2024
+ // ?????????????????????????????????????????????????????????????????????????????
2025
+ const {
2026
+ initStaging,
2027
+ setConfig: setStagingConfig,
2028
+ gitAddSafe,
2029
+ verifyStagedScopeCoverage,
2030
+ archiveDir,
2031
+ archiveCheck,
2032
+ } = require('./post-complete/staging');
2033
+ initStaging({ git, resolveTasksBase, getDependedIds });
2034
+ setStagingConfig(CONFIG);
2035
+ // ?????????????????????????????????????????????????????????????????????????????
2036
+
2037
+ // ─────────────────────────────────────────
2038
+ // Task File Protection(防止 git 操作破壞 task 狀態)
2039
+ // ─────────────────────────────────────────
2040
+
2041
+ const TASK_STATUS_DIRS = ['backlog', 'todo', 'active', 'done', 'blocked'];
2042
+ // ?????????????????????????????????????????????????????????????????????????????
2043
+ // SECTION: HEALTH_AND_GUARDIAN (extracted to ./post-complete/health.js)
2044
+ // ?????????????????????????????????????????????????????????????????????????????
2045
+ const {
2046
+ resolveMcpProjectPath,
2047
+ readLoopConfig,
2048
+ measureHealthScoreSync,
2049
+ readPreTaskHealthSnapshot,
2050
+ saveTaskHealthSnapshot,
2051
+ computeHealthVerdict,
2052
+ writeVerdictToTask,
2053
+ readGuardianCounter,
2054
+ writeGuardianCounter,
2055
+ triggerGuardianHealthCheck,
2056
+ HEALTH_SNAPSHOTS_DIR_NAME,
2057
+ } = require('./post-complete/health');
2058
+
2059
+ // TASK_1885: deliberately NOT the same knob as loopCfg.revert_threshold (DEFAULT_REVERT_THRESHOLD
2060
+ // in health.js). This is a fixed calibration point for the Design Reasoning Attribution valence
2061
+ // classifier (feeds skill matrix / EFE learning signals) -- it answers "was this outcome bad
2062
+ // enough to learn from as a negative example", not "should Guardian auto-revert this commit".
2063
+ // revert_threshold is a user-tunable Dashboard Loop Control setting; wiring the attribution
2064
+ // classifier to it would mean a user loosening the auto-revert threshold (e.g. to -5, "only
2065
+ // revert on big regressions") would silently also start labeling -3/-4 outcomes as "neutral"
2066
+ // in the long-term learning data, corrupting historical comparability of skill/EFE signals for
2067
+ // a reason unrelated to learning. The two concerns share a numeric coincidence (-2) today, not
2068
+ // a shared meaning -- kept as a separate named constant so the coincidence doesn't calcify into
2069
+ // an accidental coupling next time DEFAULT_REVERT_THRESHOLD changes.
2070
+ const ATTRIBUTION_NEGATIVE_VALENCE_THRESHOLD = -2;
2071
+ // ?????????????????????????????????????????????????????????????????????????????
2072
+ // SECTION: COGNITIVE (extracted to ./post-complete/cognitive.js)
2073
+ // ?????????????????????????????????????????????????????????????????????????????
2074
+ const {
2075
+ initCognitive,
2076
+ parseCognitiveCheckpoint,
2077
+ detectCognitiveCheckpoint,
2078
+ collectThinkingTags,
2079
+ triggerPassiveRuneExtraction,
2080
+ callHippocampusMcpSync,
2081
+ } = require('./post-complete/cognitive');
2082
+ initCognitive({ findLogForTask, getPostCompleteChangedFiles, rankTouchedFiles, stripCJK });
2083
+
2084
+ // ?????????????????????????????????????????????????????????????????????????????
2085
+ // SECTION: OUTCOME_AND_SKILL_MATRIX (extracted to ./post-complete/outcome.js)
2086
+ // ?????????????????????????????????????????????????????????????????????????????
2087
+ const {
2088
+ initOutcome,
2089
+ recordOutcome,
2090
+ compareOutcomePrediction,
2091
+ updateCompetenceMap,
2092
+ classifySkillKey,
2093
+ inferSkillKey,
2094
+ updateSkillMatrix,
2095
+ accumulateValence,
2096
+ } = require('./post-complete/outcome');
2097
+ initOutcome({ resolveProjectRoot, resolveTasksBase, loadTaskScopePaths, getPostCompleteChangedFiles, callHippocampusMcpSync });
2098
+
2099
+ // ?????????????????????????????????????????????????????????????????????????????
2100
+ // SECTION: INTEGRATION_GAPS (extracted to ./post-complete/integration-gaps.js)
2101
+ // ?????????????????????????????????????????????????????????????????????????????
2102
+ const { detectIntegrationGaps } = require('./post-complete/integration-gaps');
2103
+
2104
+ // ─────────────────────────────────────────
2105
+ // Outcome Recording + Surprise Detection (TASK_931)
2106
+ // ─────────────────────────────────────────
2107
+ // ?????????????????????????????????????????????????????????????????????????????
2108
+ // SECTION: GIT_MERGE
2109
+ // ?????????????????????????????????????????????????????????????????????????????
2110
+ // ?????????????????????????????????????????????????????????????????????????????
2111
+ // SECTION: GIT_MERGE (extracted to ./post-complete/git-merge.js)
2112
+ // ?????????????????????????????????????????????????????????????????????????????
2113
+ const {
2114
+ initGitMerge,
2115
+ setTaskStatusDirs: setGitMergeTaskStatusDirs,
2116
+ gitBranchAndMerge,
2117
+ _gitMergeFromWorktree,
2118
+ } = require('./post-complete/git-merge');
2119
+ initGitMerge({
2120
+ resolveProjectRoot, resolveTasksBase, resolveTasksRelative,
2121
+ getCurrentBranch, getDispatchBranch, resolveUserBranch,
2122
+ hasInitialCommit, hasChanges, getUnmergedPaths, branchExists, getBaseBranch,
2123
+ git: gitPreservingUntracked, gitAddSafe, verifyStagedScopeCoverage,
2124
+ loadTaskScopePaths, isMemoryOnlyTask, isRuntimeOnlySmokeTask,
2125
+ writeConflictNotesToTask, blockTaskForMergeFailure,
2126
+ snapshotTaskFiles, restoreTaskFiles,
2127
+ verify_merge_landed, ensureSalvageBranch,
2128
+ getDaemonPathsToUnstage, sleepSync,
2129
+ acquireGitLock, releaseGitLock, acquireMergeLock, releaseMergeLock,
2130
+ decrementParallelSemaphore, cleanupWorktree,
2131
+ runRegressionTests, createRegressionFixTask, hasOpenRegressionFixTask,
2132
+ isVerifiedMergeSuccess, listDaemonManagedPaths, detectIntegrationGaps,
2133
+ // Stash leak → salvage branch (post-complete-stash-accumulation-bug, 2026-05-05)
2134
+ salvageStashAsBranch,
2135
+ });
2136
+ setGitMergeTaskStatusDirs(TASK_STATUS_DIRS);
2137
+ // ?????????????????????????????????????????????????????????????????????????????
2138
+
2139
+
2140
+ // ?????????????????????????????????????????????????????????????????????????????
2141
+ // SECTION: ORCHESTRATOR
2142
+ // ?????????????????????????????????????????????????????????????????????????????
2143
+ function postComplete(opts = {}) {
2144
+ const {
2145
+ taskId,
2146
+ keep = CONFIG.DEFAULT_KEEP,
2147
+ skipArchive = false,
2148
+ skipGit = false,
2149
+ skipTests = false,
2150
+ dryRun = false,
2151
+ message,
2152
+ projectPath,
2153
+ testCommands = [],
2154
+ logPath,
2155
+ formation,
2156
+ skill,
2157
+ worktreePath = process.env.FORMATION_WORKTREE_PATH || null,
2158
+ } = opts;
2159
+
2160
+ // B11: opportunistic GC of orphan worktrees from prior failed dispatches
2161
+ gcStaleWorktrees(taskId, projectPath);
2162
+
2163
+ // TASK_1593: guardFile hoisted so updatePostCompleteGuardOutcome can record
2164
+ // outcome on the same entry before postComplete returns.
2165
+ const guardRoot = projectPath || ROOT;
2166
+ const guardFile = path.join(guardRoot, '.kenaz', 'logs', 'post-complete-guard.json');
2167
+ {
2168
+ const skipResult = checkAndUpdatePostCompleteGuard(guardFile, taskId, worktreePath);
2169
+ if (skipResult) return skipResult;
2170
+ }
2171
+
2172
+ console.log(`\n⚔️ Post-Complete: ${taskId || '(no task)'}`);
2173
+ console.log(` Keep: ${keep} | Archive: ${!skipArchive} | Git: ${!skipGit} | Tests: ${!skipTests} | DryRun: ${dryRun}`);
2174
+ if (projectPath) console.log(` Project: ${projectPath}`);
2175
+ console.log(` WorktreePath: ${worktreePath || '(null — serial mode)'}`);
2176
+ // Debug: write to persistent log
2177
+ try {
2178
+ const debugLog = path.join(projectPath || ROOT, '.kenaz', 'logs', 'post-complete-debug.log');
2179
+ fs.appendFileSync(debugLog, `[${new Date().toISOString()}] postComplete: taskId=${taskId} worktreePath=${worktreePath || 'null'} projectPath=${projectPath || 'null'}\n`);
2180
+ } catch {}
2181
+ console.log('');
2182
+
2183
+ const result = { archive: null, git: null };
2184
+
2185
+ // Step 0: Hippocampus Episode Start (episodic memory — TASK_716)
2186
+ // TASK_963: Resolve touched file_paths so Rust can create TOUCHES edges to Symbols
2187
+ let hippocampusEpisodeId = null;
2188
+ if (taskId && !dryRun) {
2189
+ try {
2190
+ // Collect file_paths from worktree git diff or task scope_paths
2191
+ let filePaths = [];
2192
+ const diffRoot = worktreePath || resolveProjectRoot(projectPath);
2193
+ if (diffRoot) {
2194
+ try {
2195
+ const diffCmd = worktreePath
2196
+ ? 'git diff --name-only main...HEAD'
2197
+ : 'git diff --name-only HEAD~1 HEAD';
2198
+ const diffOut = spawnSync('git', diffCmd.split(' ').slice(1), {
2199
+ cwd: diffRoot, encoding: 'utf8', timeout: 8000, windowsHide: true,
2200
+ });
2201
+ if (diffOut.status === 0 && diffOut.stdout) {
2202
+ filePaths = diffOut.stdout.trim().split('\n').filter(Boolean);
2203
+ }
2204
+ } catch { /* git diff failed — fall through to scope_paths */ }
2205
+ }
2206
+ if (filePaths.length === 0) {
2207
+ filePaths = loadTaskScopePaths(taskId, projectPath);
2208
+ }
2209
+
2210
+ const epArgs = {
2211
+ task_id: taskId,
2212
+ scope: `post-complete:${taskId}`,
2213
+ agent_role: formation || 'unit',
2214
+ agent_skill: skill || 'do',
2215
+ ...(filePaths.length > 0 ? { file_paths: filePaths } : {}),
2216
+ // TASK_2025: hippocampus_episode_start is now a write tool that requires
2217
+ // an explicit projectPath (mcp.rs resolve_project_for_write) -- it no
2218
+ // longer silently falls back to the Dashboard's currently-selected
2219
+ // project. projectPath is only set here when --project-path was passed
2220
+ // (sub-project dispatch); core-project dispatches never pass it, so
2221
+ // fall back to ROOT (this script's own core project root) rather than
2222
+ // omitting the param and hard-failing every core-project run.
2223
+ projectPath: projectPath || ROOT,
2224
+ };
2225
+ const epResult = callHippocampusMcpSync('hippocampus_episode_start', epArgs);
2226
+ if (epResult && epResult.episode_id) {
2227
+ hippocampusEpisodeId = epResult.episode_id;
2228
+ console.log(` 🧠 Hippocampus: episode ${hippocampusEpisodeId} started (${filePaths.length} file_paths)`);
2229
+ }
2230
+ } catch (e) {
2231
+ console.log(` ⚠️ Hippocampus episode start skipped: ${e.message}`);
2232
+ }
2233
+ }
2234
+
2235
+ // Step 1: Archive
2236
+ if (!skipArchive) {
2237
+ console.log('── Archive Check ──');
2238
+ const archiveResult = archiveCheck({ keep, dryRun, projectPath });
2239
+ result.archive = archiveResult;
2240
+
2241
+ // 新系統(或子專案的 .kenaz/tasks/)
2242
+ const c = archiveResult.collab;
2243
+ if (c.archived.length > 0) {
2244
+ console.log(` 📦 Archived ${c.archived.length} tasks (${c.total} → ${c.kept.length}):`);
2245
+ for (const f of c.archived) console.log(` → ${f}`);
2246
+ } else {
2247
+ console.log(` ✅ done: ${c.total}/${keep} — no archive needed`);
2248
+ }
2249
+ if (c.skippedDeps && c.skippedDeps.length > 0) {
2250
+ console.log(` 🔗 Skipped ${c.skippedDeps.length} depended task(s):`);
2251
+ for (const f of c.skippedDeps) console.log(` ⏭ ${f}`);
2252
+ }
2253
+
2254
+ // 舊系統(只在 core)
2255
+ if (!projectPath) {
2256
+ const l = archiveResult.legacy;
2257
+ if (l.archived.length > 0) {
2258
+ console.log(` 📦 [legacy] Archived ${l.archived.length} tasks (${l.total} → ${l.kept.length}):`);
2259
+ for (const f of l.archived) console.log(` → ${f}`);
2260
+ } else {
2261
+ console.log(` ✅ [legacy] completed: ${l.total}/${keep} — no archive needed`);
2262
+ }
2263
+ if (l.skippedDeps && l.skippedDeps.length > 0) {
2264
+ console.log(` 🔗 [legacy] Skipped ${l.skippedDeps.length} depended task(s)`);
2265
+ }
2266
+ }
2267
+ console.log('');
2268
+ }
2269
+
2270
+ // Step 1.5 (Runtime Acceptance Verification, TASK_1482) removed by TASK_1825
2271
+ // — see comment at the top-of-file require block for the measurement.
2272
+
2273
+ // Step 1.9: Pre-capture git commit snapshot for the guard failure detector (TASK_1896)
2274
+ // Step 4.6 below (git_commit_pattern detector) needs commits on `task/<taskId>`,
2275
+ // but Step 2's merge just below is a read-after-mutation hazard for it: the
2276
+ // worktree-mode merge rebases task/<taskId> onto main before merging, which
2277
+ // makes `git log <branch> --not main` (what the live detector queries) come
2278
+ // back EMPTY once Step 2 has run — the branch itself is NOT deleted; the
2279
+ // rebase's `git branch -D` + immediate `checkout -b` at git-merge.js ~849/852
2280
+ // is stale-branch cleanup from a PRIOR attempt, followed by recreating the
2281
+ // branch fresh, and is unrelated to this. Confirmed by direct execution
2282
+ // (TASK_1896 verification): after a successful worktree merge the branch
2283
+ // still exists but has zero commits ahead of main, so Step 4.6 (which runs
2284
+ // after Step 2, in the same postComplete() call) saw `no_commits_on_branch`
2285
+ // for real completed tasks. This fix captures the commit data here, before
2286
+ // Step 2 can rebase it away, so Step 4.6 evaluates the real pre-merge commits
2287
+ // instead. (A separate, still-open question: most of guard_detector_shadow.jsonl's
2288
+ // historical entries are `branch_not_found`, not `no_commits_on_branch` — that
2289
+ // reason means the branch didn't exist at query time at all, which this fix
2290
+ // does not explain or address. The likely cause is that those tasks never
2291
+ // went through this git path in the first place, so task/<taskId> was never
2292
+ // created — unverified; needs checking against those specific task IDs before
2293
+ // claiming this fix resolves them.) This does NOT change branch -D's own
2294
+ // behavior or timing — it only reads (rev-parse, log). Lifetime: this single
2295
+ // postComplete() call only — held on `result`, never persisted to disk, never
2296
+ // reused across invocations, so there is no cache-staleness surface to manage.
2297
+ // Only runs on the path that can actually rebase the branch (!skipGit &&
2298
+ // taskId); on --skip-git, Step 2 never runs and detectGitCommitPattern's own
2299
+ // live-query fallback already sees the branch untouched, so no snapshot is
2300
+ // needed there.
2301
+ if (taskId && !skipGit && !dryRun) {
2302
+ try {
2303
+ const detectorMod = require(path.join(ROOT, '.specialist', 'tools', 'failure-detector.js'));
2304
+ result.gitCommitSnapshot = { taskId, ...detectorMod.captureGitCommitSnapshot(taskId) };
2305
+ } catch (e) {
2306
+ console.log(` [warn] Git commit snapshot capture skipped: ${e.message}`);
2307
+ }
2308
+ }
2309
+
2310
+ // Step 2: Git
2311
+ if (!skipGit && taskId) {
2312
+ console.log('── Git Branch & Merge ──');
2313
+ if (worktreePath) console.log(` Worktree mode: ${worktreePath}`);
2314
+ // TASK_1536 Part A: wrap gitBranchAndMerge in try/catch so git exceptions
2315
+ // (e.g. gitlink checkout failure, lock contention, unexpected git error) do NOT
2316
+ // propagate out of post-complete entirely — which was bypassing the
2317
+ // blockTaskForMergeFailure call and leaving a self-completed task in done/ with
2318
+ // no code landed. Catching here ensures the reconciliation gate below always runs.
2319
+ let gitResult;
2320
+ try {
2321
+ // TASK_1759 (Multi-Ansuz M2): opt-in integration queue (bors-lite).
2322
+ // KENAZ_INTEGRATION_QUEUE=1 delegates the merge step to integration.js:
2323
+ // in-lock rebase + re-verify + ff-only merge + task YAML active->done.
2324
+ // No `git add -A` sweep and no autostash on this path (only the task
2325
+ // branch commits + the YAML move enter main).
2326
+ // Flag absent (default) => legacy gitBranchAndMerge path, byte-identical
2327
+ // to previous behavior — rollback is trivial. Dry runs stay on legacy.
2328
+ if (process.env.KENAZ_INTEGRATION_QUEUE === '1' && !dryRun) {
2329
+ gitResult = require('./integration.js').integrateForPostComplete({ taskId, projectPath, message });
2330
+ } else {
2331
+ gitResult = gitBranchAndMerge({ taskId, message, dryRun, projectPath, skipTests, testCommands, worktreePath });
2332
+ }
2333
+ } catch (gitException) {
2334
+ const exMsg = (gitException.message || String(gitException)).split('\n')[0];
2335
+ console.log(` ❌ gitBranchAndMerge threw: ${exMsg}`);
2336
+ gitResult = {
2337
+ taskBranch: taskId ? `task/${taskId.toLowerCase()}` : null,
2338
+ userBranch: null,
2339
+ originalBranch: null,
2340
+ committed: true,
2341
+ merged: false,
2342
+ verify_merge_landed: false,
2343
+ failure_reason: 'git_exception',
2344
+ gitException: exMsg,
2345
+ };
2346
+ }
2347
+ result.git = gitResult;
2348
+ const mergeSucceeded = isVerifiedMergeSuccess(gitResult);
2349
+ console.log('');
2350
+
2351
+ // Step 2.5: Yggdrasil commit-sync(fire-and-forget)
2352
+ if (gitResult.committed && !dryRun) {
2353
+ try {
2354
+ const { syncLatestCommit } = require('./commit-sync');
2355
+ syncLatestCommit({ taskId, projectPath }).catch(() => {});
2356
+ } catch { /* commit-sync not available — graceful degradation */ }
2357
+ }
2358
+
2359
+ // Step 2.6: Passive Rune Extraction(fire-and-forget)
2360
+ if (gitResult.committed && !dryRun) {
2361
+ try {
2362
+ const commitMsg = message || `[${taskId}] task completed`;
2363
+ const projectRoot = resolveProjectRoot(projectPath);
2364
+ if (projectRoot) {
2365
+ triggerPassiveRuneExtraction(commitMsg, projectRoot, projectPath);
2366
+ }
2367
+ } catch { /* rune extraction failed — never block post-complete */ }
2368
+ }
2369
+
2370
+ // Step 2.7: Outcome Recording + Surprise Detection(fire-and-forget)
2371
+ if (!dryRun) {
2372
+ try {
2373
+ recordOutcome(taskId, projectPath, gitResult);
2374
+ } catch { /* outcome recording failed — never block post-complete */ }
2375
+ }
2376
+
2377
+ // Step 2.7b: Phase 9 outcome log — record structured verdict
2378
+ // (TASK_1732) Subagent-path dispatches write no dispatch_*.log, so phase9_verify_fail_rate
2379
+ // lost its data source after 2026-06-09 routing change. This JSONL write is the replacement:
2380
+ // { task_id, verify_merge_landed: bool, ts } appended to .kenaz/specialist/phase9_dispatch_outcomes.jsonl
2381
+ if (!dryRun && taskId) {
2382
+ try {
2383
+ appendPhase9OutcomeLog(taskId, mergeSucceeded, projectPath);
2384
+ } catch { /* never block post-complete */ }
2385
+ }
2386
+
2387
+ // Step 2.8: Skill Matrix Update (TASK_986)
2388
+ if (!dryRun) {
2389
+ try {
2390
+ const success = gitResult ? mergeSucceeded : true;
2391
+ const fixCycles = gitResult?.acceptanceFixTasks?.length || 0;
2392
+
2393
+ // Pull file prediction accuracy from ansuz_accuracy.json if available
2394
+ let filePredAcc = null;
2395
+ try {
2396
+ const root = projectPath || ROOT;
2397
+ const accFile = path.join(root, '.kenaz', 'specialist', 'ansuz_accuracy.json');
2398
+ if (fs.existsSync(accFile)) {
2399
+ const accData = JSON.parse(fs.readFileSync(accFile, 'utf8'));
2400
+ if (Array.isArray(accData.comparison_history)) {
2401
+ const match = accData.comparison_history.find(h => h.task_id === taskId);
2402
+ if (match) filePredAcc = match.file_jaccard;
2403
+ }
2404
+ }
2405
+ } catch { /* accuracy data not available */ }
2406
+
2407
+ updateSkillMatrix({ taskId, projectPath, success, fixCycles, filePredictionAccuracy: filePredAcc });
2408
+ } catch (e) { console.log(` ⚠️ Skill matrix update failed: ${e.message}`); }
2409
+ }
2410
+
2411
+ // Step 2.9 (cognitive attribution via /api/synapse/link-cognitive) removed in TASK_2341: route deleted with the graph.
2412
+ }
2413
+
2414
+ // Step 2.5: Complete task if git merge succeeded (AC is signal, not gate)
2415
+ // Previously, AC failure prevented task completion even when code was already
2416
+ // committed and merged into the user branch. This caused tasks to pile up in
2417
+ // active/ on the Mission Board. Now: git merge success = task complete.
2418
+ // AC failures are recorded in task metadata for visibility.
2419
+ if (taskId && !dryRun && result.git && isVerifiedMergeSuccess(result.git)) {
2420
+ // Record AC failures in task metadata if any.
2421
+ // TASK_1825: the note starts with the canonical token ACCEPTANCE_FAILED so
2422
+ // "cards that failed acceptance but still went to done/" is one query away:
2423
+ // grep -rln "ACCEPTANCE_FAILED" .kenaz/tasks/
2424
+ // For fix-tagged cards (verify-acceptance loop guard: no FIX card is created)
2425
+ // this note is the failure's ONLY home besides the .formation/reports/verify/
2426
+ // report — the fix disposition line makes that explicit.
2427
+ // Written here (after merge) rather than inside verify-acceptance, because a
2428
+ // task-api update mid-merge would dirty the working tree between commit and
2429
+ // checkout (the exact failure TASK_1588 fixed for hooks). Notes only — never
2430
+ // move card status here; TASK_1536 reconciliation owns status moves.
2431
+ const acFailures = result.git?.acceptance?.results?.filter(r => !r.pass) || [];
2432
+ if (acFailures.length > 0) {
2433
+ try {
2434
+ const failureNotes = acFailures.map(f => `❌ ${f.criterion}: ${f.reason || 'failed'}`).join('\n');
2435
+ const ac = result.git.acceptance || {};
2436
+ const fixDisposition = ac.fixSkipped
2437
+ ? `fix_disposition: NO_FIX_CARD (${ac.fixSkipReason || 'fix-tag loop guard'})`
2438
+ : (result.git.acceptanceFixTasks && result.git.acceptanceFixTasks.length > 0
2439
+ ? `fix_disposition: FIX_CARDS ${result.git.acceptanceFixTasks.join(',')}`
2440
+ : 'fix_disposition: NO_FIX_CARD (creation failed or no failed criteria)');
2441
+ const updateArgs = projectPath ? ['--project-path', projectPath] : [];
2442
+ updateArgs.push('update', taskId,
2443
+ '--notes', `ACCEPTANCE_FAILED (signal — code merged, task completed)\n${fixDisposition}\n${failureNotes}`);
2444
+ spawnSync('node', [COLLABORATION_API, ...updateArgs], {
2445
+ encoding: 'utf8', timeout: 15000, cwd: ROOT, windowsHide: true
2446
+ });
2447
+ console.log(` 📝 Recorded ${acFailures.length} AC failure(s) in task metadata (ACCEPTANCE_FAILED marker)`);
2448
+ } catch (e) {
2449
+ console.log(` ⚠️ Failed to record AC failures: ${e.message}`);
2450
+ }
2451
+ }
2452
+
2453
+ // Complete the task (move to done/)
2454
+ // TASK_1622 ROOT CAUSE: --skip-post-complete is mandatory here. Without it the
2455
+ // nested task-api complete re-enters the FULL postComplete pipeline, whose
2456
+ // second gitBranchAndMerge gets killed mid-flight by this spawnSync's 15s
2457
+ // timeout — leaving HEAD stranded on task/task_XXXX with an orphan transcript
2458
+ // commit (observed on TASK_1620/1621/1622; reflog: checkout→commit→killed).
2459
+ try {
2460
+ const completeArgs = projectPath ? ['--project-path', projectPath] : [];
2461
+ completeArgs.push('complete', taskId, '--skip-post-complete');
2462
+ const completeResult = spawnSync('node', [COLLABORATION_API, ...completeArgs], {
2463
+ encoding: 'utf8', timeout: 15000, cwd: ROOT, windowsHide: true
2464
+ });
2465
+ if (completeResult.status === 0) {
2466
+ result.taskCompleted = true;
2467
+ console.log(` ✅ Task ${taskId} marked as done (code merged → complete)`);
2468
+ } else {
2469
+ const errMsg = (completeResult.stderr || completeResult.stdout || '').trim();
2470
+ // Task may already be in done/ (idempotent)
2471
+ if (errMsg.includes('already') || errMsg.includes('done')) {
2472
+ result.taskCompleted = true;
2473
+ console.log(` ✅ Task ${taskId} already in done/`);
2474
+ } else {
2475
+ console.log(` ⚠️ Failed to complete task: ${errMsg.split('\n')[0]}`);
2476
+ }
2477
+ }
2478
+ } catch (e) {
2479
+ console.log(` ⚠️ Task complete error: ${e.message}`);
2480
+ }
2481
+ } else if (taskId && !dryRun && result.git && result.git.noChanges) {
2482
+ // No git changes to merge — either a memory-only task (git changes were
2483
+ // never expected) or, since TASK_2029's scope-aware hasChanges gate, a
2484
+ // task whose declared scope_paths had zero changes on disk. Either way
2485
+ // there is nothing to merge and nothing to block on. Before this fix,
2486
+ // the non-memory-only case fell through every branch here and the card
2487
+ // stayed stuck in active/ forever (it neither completed nor blocked).
2488
+ const noChangeReason = result.git.gitSkipped ? `Git step skipped (${result.git.gitSkipReason})` : (result.git.memoryOnly ? 'Memory-only task' : `No changes within ${taskId}'s declared scope`);
2489
+ console.log(` 🧠 ${noChangeReason} — completing ${taskId} without git merge`);
2490
+ try {
2491
+ const completeArgs = projectPath ? ['--project-path', projectPath] : [];
2492
+ // TASK_1622: prevent nested postComplete re-entry (see merge-success path above)
2493
+ completeArgs.push('complete', taskId, '--skip-post-complete');
2494
+ const completeResult = spawnSync('node', [COLLABORATION_API, ...completeArgs], {
2495
+ encoding: 'utf8', timeout: 15000, cwd: ROOT, windowsHide: true
2496
+ });
2497
+ if (completeResult.status === 0) {
2498
+ result.taskCompleted = true;
2499
+ console.log(` ✅ Task ${taskId} marked as done (no changes → complete)`);
2500
+ } else {
2501
+ const errMsg = (completeResult.stderr || completeResult.stdout || '').trim();
2502
+ if (errMsg.includes('already') || errMsg.includes('done')) {
2503
+ result.taskCompleted = true;
2504
+ console.log(` ✅ Task ${taskId} already in done/`);
2505
+ } else {
2506
+ console.log(` ⚠️ Failed to complete task: ${errMsg.split('\n')[0]}`);
2507
+ }
2508
+ }
2509
+ } catch (e) {
2510
+ console.log(` ⚠️ Task complete error: ${e.message}`);
2511
+ }
2512
+ } else if (taskId && result.git && !isVerifiedMergeSuccess(result.git) && !result.git.noGitRepo && !result.git.noChanges) {
2513
+ result.taskBlocked = blockTaskForMergeFailure(taskId, projectPath, result.git);
2514
+ if (result.taskBlocked) {
2515
+ console.log(` ⚠️ Git merge failed — task ${taskId} moved to blocked/`);
2516
+ }
2517
+ if (!result.taskBlocked) {
2518
+ console.log(` ⚠️ Git merge failed — task ${taskId} could not be moved to blocked/`);
2519
+ }
2520
+ }
2521
+
2522
+ // TASK_1536 Part A: Reconciliation gate.
2523
+ // A worktree-dispatched agent may have self-completed via `task-api complete`
2524
+ // (moving the task to done/) BEFORE post-complete's merge runs. If the merge
2525
+ // then did NOT land (merged=false / verify_merge_landed!=true / exception), the
2526
+ // task is now falsely in done/ with no code on the user branch.
2527
+ //
2528
+ // This gate catches that scenario regardless of how the merge failure was reported:
2529
+ // (a) gitBranchAndMerge returned merged:false — blockTaskForMergeFailure above
2530
+ // moved active→blocked but the task was already in done/ so nothing happened
2531
+ // (b) gitBranchAndMerge threw — caught above as git_exception result
2532
+ //
2533
+ // Action: when the merge did not land AND the task is currently in done/ (agent
2534
+ // self-completed), force-move it done→blocked with an explicit failure_reason and
2535
+ // vitals so the false-positive is visible and actionable.
2536
+ //
2537
+ // We bypass the normal done→blocked state-machine guard via the inline task-api
2538
+ // script (same pattern as blockTaskForMergeFailure) rather than the CLI path
2539
+ // which honours transition guards and would refuse the done→blocked move.
2540
+ if (!dryRun && taskId && result.git && !isVerifiedMergeSuccess(result.git) && !result.git.noGitRepo && !result.git.gitSkipped) {
2541
+ try {
2542
+ // Check whether the task is currently in done/ (agent self-completed)
2543
+ const reconcileScript = `
2544
+ const { TaskAPI } = require(${JSON.stringify(path.join(ROOT, '.collaboration', 'core', 'task-api.js'))});
2545
+ (async () => {
2546
+ const payload = JSON.parse(process.argv[1]);
2547
+ const api = new TaskAPI(payload.projectPath ? { projectPath: payload.projectPath } : {});
2548
+ const task = await api.getTask(payload.taskId);
2549
+ if (!task) { process.exit(0); } // task not found — nothing to reconcile
2550
+ if (task.status !== 'completed' && task._status_dir !== 'done') {
2551
+ // task is not in done/ — already blocked or active, no reconciliation needed
2552
+ process.exit(0);
2553
+ }
2554
+ // Task IS in done/ but merge did not land — move done→blocked
2555
+ const vitals = payload.vitals;
2556
+ const updatedTask = {
2557
+ ...task,
2558
+ status: 'blocked',
2559
+ notes: [task.notes, payload.note].filter(Boolean).join('\\n'),
2560
+ vitals: { ...(task.vitals || {}), ...vitals },
2561
+ };
2562
+ await api.moveTask(payload.taskId, 'blocked', updatedTask, { skipValidation: true });
2563
+ console.log('[reconcile] moved done → blocked: ' + payload.taskId);
2564
+ })().catch((err) => {
2565
+ console.error(err.message || String(err));
2566
+ process.exit(1);
2567
+ });
2568
+ `;
2569
+ const gitResult = result.git;
2570
+ const reconcileVitals = {
2571
+ // TASK_1588: checkoutBlocked = step-3 checkout threw; no merge ran
2572
+ // TASK_1591: missingTaskBranch (ansuz-wt-* naming) gets distinct label, not merge_conflict
2573
+ failure_reason: gitResult.failure_reason || (gitResult.gitException ? 'git_exception' : (classifyMergeFailureReason(gitResult) === 'git_step_failed' ? 'merge_not_landed' : classifyMergeFailureReason(gitResult))),
2574
+ verify_merge_landed: false,
2575
+ merge_failure_mode: gitResult.blockedReason || gitResult.verify_merge_landed_reason || gitResult.failure_reason || 'unknown',
2576
+ task_branch: gitResult.taskBranch || null,
2577
+ task_branch_commit: gitResult.taskBranchCommit || null,
2578
+ target_branch: gitResult.userBranch || null,
2579
+ salvage_branch: gitResult.salvageBranch || gitResult.outOfScopeSalvageBranch || null,
2580
+ out_of_scope_salvage_branch: gitResult.outOfScopeSalvageBranch || null,
2581
+ git_exception: gitResult.gitException || null,
2582
+ };
2583
+ const reconcileNote = [
2584
+ '[TASK_1536] post-complete reconciliation: agent self-completed but merge did not land',
2585
+ `failure_reason=${reconcileVitals.failure_reason}`,
2586
+ gitResult.taskBranch ? `task_branch=${gitResult.taskBranch}` : null,
2587
+ gitResult.salvageBranch ? `salvage_branch=${gitResult.salvageBranch}` : null,
2588
+ gitResult.gitException ? `git_exception=${gitResult.gitException}` : null,
2589
+ ].filter(Boolean).join(' | ');
2590
+ const reconcilePayload = { taskId, projectPath, vitals: reconcileVitals, note: reconcileNote };
2591
+ const reconcileResult = spawnSync('node', ['-e', reconcileScript, JSON.stringify(reconcilePayload)], {
2592
+ cwd: ROOT,
2593
+ encoding: 'utf8',
2594
+ timeout: 15000,
2595
+ windowsHide: true,
2596
+ });
2597
+ if (reconcileResult.status === 0 && (reconcileResult.stdout || '').includes('[reconcile] moved done → blocked')) {
2598
+ result.reconciliationApplied = true;
2599
+ console.log(` 🚨 [TASK_1536] Reconciliation: task ${taskId} was in done/ but merge did not land — moved to blocked/ (failure_reason=${reconcileVitals.failure_reason})`);
2600
+ } else if (reconcileResult.status === 0) {
2601
+ // task was not in done/ — already blocked or active, nothing to reconcile
2602
+ console.log(` ✅ [TASK_1536] Reconciliation check: task ${taskId} not in done/ — no reconciliation needed`);
2603
+ } else {
2604
+ const errMsg = (reconcileResult.stderr || reconcileResult.stdout || '').trim();
2605
+ console.log(` ⚠️ [TASK_1536] Reconciliation failed: ${errMsg.split('\n')[0] || 'unknown error'}`);
2606
+ }
2607
+ } catch (reconcileErr) {
2608
+ console.log(` ⚠️ [TASK_1536] Reconciliation error (non-fatal): ${(reconcileErr.message || '').split('\n')[0]}`);
2609
+ }
2610
+ }
2611
+
2612
+ // Safety net: if merge succeeded but complete failed (e.g. state-machine rejection),
2613
+ // fall back to move --force so the task never stalls in active/ after a successful merge.
2614
+ //
2615
+ // INVARIANT (TASK_1825 road sign — behavior intentionally unchanged): the only
2616
+ // condition is isVerifiedMergeSuccess && !taskCompleted. It does NOT require that
2617
+ // complete() was ever attempted. Therefore any future gate that tries to hold a
2618
+ // card out of done/ by simply not setting result.taskCompleted (e.g. on acceptance
2619
+ // failure) will be silently bypassed here — the card gets force-moved to done/
2620
+ // anyway. If you ever need a gate that survives this safety net, it must set an
2621
+ // explicit result flag that this condition checks, not just withhold taskCompleted.
2622
+ if (taskId && !dryRun && result.git && isVerifiedMergeSuccess(result.git) && !result.taskCompleted) {
2623
+ console.log(` 🔁 Safety net: forcefully completing ${taskId} (merge succeeded, complete failed)`);
2624
+ try {
2625
+ const moveArgs = projectPath ? ['--project-path', projectPath] : [];
2626
+ moveArgs.push('move', taskId, 'done', '--force');
2627
+ const moveResult = spawnSync('node', [COLLABORATION_API, ...moveArgs], {
2628
+ encoding: 'utf8', timeout: 15000, cwd: ROOT, windowsHide: true
2629
+ });
2630
+ if (moveResult.status === 0) {
2631
+ result.taskCompleted = true;
2632
+ console.log(` ✅ Task ${taskId} force-moved to done/ (safety net)`);
2633
+ } else {
2634
+ const errMsg = (moveResult.stderr || moveResult.stdout || '').trim();
2635
+ if (errMsg.includes('already') || errMsg.includes('done')) {
2636
+ result.taskCompleted = true;
2637
+ console.log(` ✅ Task ${taskId} already in done/`);
2638
+ } else {
2639
+ console.log(` ⚠️ Safety net move failed: ${errMsg.split('\n')[0]}`);
2640
+ }
2641
+ }
2642
+ } catch (e) {
2643
+ console.log(` ⚠️ Safety net error: ${e.message}`);
2644
+ }
2645
+ }
2646
+
2647
+ // Annotate merge_landed=true so task-api list can filter accurately.
2648
+ // Runs after all completion paths so result.taskCompleted is final.
2649
+ if (result.taskCompleted && taskId && !dryRun) {
2650
+ annotateMergeLanded(taskId, projectPath, true);
2651
+ }
2652
+
2653
+ // Step 2.4: Forseti Level 0 — Structural integrity checks (auto, no spec needed)
2654
+ // Catches: MCP wiring gaps, destructive diffs, build artifact pollution.
2655
+ // Runs after merge+complete so it checks the actual merged state, not the worktree.
2656
+ if (result.taskCompleted && taskId && !dryRun) {
2657
+ try {
2658
+ const { runLevel0 } = require('./forseti-verify');
2659
+ const projectRoot = resolveProjectRoot(projectPath);
2660
+ if (projectRoot) {
2661
+ console.log('── Forseti Level 0 ──');
2662
+ const l0 = runLevel0(taskId, projectRoot, projectPath);
2663
+ result.forsetiL0 = l0;
2664
+ if (l0.status === 'FAIL') {
2665
+ console.log(` ⚠️ Forseti L0: ${l0.failures.length} structural issue(s) detected`);
2666
+ for (const f of l0.failures) {
2667
+ console.log(` ↳ ${f.check}: ${f.message}`);
2668
+ }
2669
+ const failSummary = `Forseti L0 FAIL for ${taskId}: ${l0.failures.map(f => f.message).join('; ')}`;
2670
+
2671
+ // Ambient signals → Phase 0 (cross-session persistence)
2672
+ try {
2673
+ const signalFile = path.join(projectRoot, '.kenaz', 'specialist', 'ambient_signals.json');
2674
+ let signals = { signals: [] };
2675
+ try { signals = JSON.parse(fs.readFileSync(signalFile, 'utf8')); } catch {}
2676
+ signals.signals.push({
2677
+ detected_at: new Date().toISOString(),
2678
+ source: 'forseti_level0',
2679
+ signal_type: 'structural_issue',
2680
+ summary: failSummary,
2681
+ symbols: l0.failures.map(f => f.check),
2682
+ urgent: true,
2683
+ routed_weight: 2.0,
2684
+ });
2685
+ signals.signals = signals.signals.slice(-10);
2686
+ fs.writeFileSync(signalFile, JSON.stringify(signals, null, 2));
2687
+ } catch {}
2688
+ } else if (l0.status === 'PASS') {
2689
+ console.log(` ✅ Forseti L0: ${l0.checks.length} structural check(s) passed`);
2690
+ }
2691
+ }
2692
+ } catch (e) {
2693
+ console.log(` ⚠️ Forseti L0 error (non-blocking): ${e.message}`);
2694
+ }
2695
+ }
2696
+
2697
+ // Step 2.5: Passive Rune Extraction — task completion signal (TASK_747)
2698
+ if (result.taskCompleted && taskId && !dryRun) {
2699
+ try {
2700
+ const runeScope = loadTaskScopePaths(taskId, projectPath);
2701
+ if (runeScope.length > 0) {
2702
+ // Read task title and requirements via task-api get
2703
+ let runeContent = `Task ${taskId}: completed.`;
2704
+ try {
2705
+ const getArgs = projectPath ? ['--project-path', projectPath] : [];
2706
+ getArgs.push('get', taskId);
2707
+ const getResult = spawnSync('node', [COLLABORATION_API, ...getArgs], {
2708
+ encoding: 'utf8', timeout: 10000, cwd: ROOT, windowsHide: true,
2709
+ });
2710
+ if (getResult.status === 0 && getResult.stdout) {
2711
+ const taskData = JSON.parse(getResult.stdout.trim());
2712
+ const rawTitle = taskData.title || '';
2713
+ const reqs = Array.isArray(taskData.requirements) ? taskData.requirements : [];
2714
+ // MEM-LANG-001: strip CJK so FTS5 unicode61 tokenizer can index the rune
2715
+ const title = stripCJK(rawTitle);
2716
+ const reqSummary = stripCJK(reqs.slice(0, 3).join('; '));
2717
+ // Fallback: if English portion of title is too short, use task ID as title
2718
+ const effectiveTitle = title.length >= 10 ? title : `${taskId} task`;
2719
+ runeContent = `Task ${taskId}: ${effectiveTitle}. Requirements: ${reqSummary}`;
2720
+ if (runeContent.length > 300) runeContent = runeContent.slice(0, 297) + '...';
2721
+ }
2722
+ } catch { /* skip — use fallback content */ }
2723
+
2724
+ // Fire-and-forget synapse_inscribe for each scope path
2725
+ const http = require('http');
2726
+ let fired = 0;
2727
+ for (const scopePath of runeScope) {
2728
+ try {
2729
+ const inscribeArgs = {
2730
+ symbol_id: scopePath,
2731
+ type: 'summary',
2732
+ content: runeContent,
2733
+ confidence: 0.65,
2734
+ linked_task: taskId,
2735
+ // TASK_2025: write tool -- must not omit projectPath (see epArgs comment above).
2736
+ projectPath: projectPath || ROOT,
2737
+ };
2738
+ const payload = JSON.stringify({
2739
+ jsonrpc: '2.0',
2740
+ id: `task-rune-${taskId}-${fired}`,
2741
+ method: 'tools/call',
2742
+ params: { name: 'synapse_inscribe', arguments: inscribeArgs },
2743
+ });
2744
+ const req = http.request({
2745
+ hostname: '127.0.0.1', port: 18080, path: '/api/mcp',
2746
+ method: 'POST',
2747
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
2748
+ timeout: 10000,
2749
+ }, (res) => {
2750
+ res.on('data', () => {});
2751
+ res.on('end', () => {});
2752
+ });
2753
+ req.on('error', (err) => { console.warn(` ⚠️ synapse_inscribe HTTP error: ${err.message}`); });
2754
+ req.on('timeout', () => { req.destroy(); });
2755
+ req.write(payload);
2756
+ req.end();
2757
+ fired++;
2758
+ } catch { /* skip this scope path */ }
2759
+ }
2760
+ console.log(` 🧠 Rune extraction: ${fired} scope symbols for ${taskId}`);
2761
+ }
2762
+ } catch { /* skip — never blocks post-complete */ }
2763
+ }
2764
+
2765
+ // Step 2.55 (cognitive tool usage -> cognitive_competence) was removed in TASK_2348: its only
2766
+ // data source, turn_feedback.jsonl, was written by the retired reflex hook.
2767
+
2768
+ // Step 2.6: Ansuz Inbox Signal — notify Ansuz that this task completed (TASK_947)
2769
+ if (result.taskCompleted && taskId && !dryRun) {
2770
+ try {
2771
+ const root = projectPath || ROOT;
2772
+ const specialistDir = path.join(root, '.kenaz', 'specialist');
2773
+ const inboxFile = path.join(specialistDir, 'ansuz_inbox.json');
2774
+
2775
+ // Fetch task title (best-effort)
2776
+ let taskTitle = taskId;
2777
+ try {
2778
+ const getArgs = projectPath ? ['--project-path', projectPath] : [];
2779
+ getArgs.push('get', taskId);
2780
+ const getResult = spawnSync('node', [COLLABORATION_API, ...getArgs], {
2781
+ encoding: 'utf8', timeout: 10000, cwd: ROOT, windowsHide: true,
2782
+ });
2783
+ if (getResult.status === 0 && getResult.stdout) {
2784
+ const taskData = JSON.parse(getResult.stdout.trim());
2785
+ if (taskData.title) taskTitle = taskData.title;
2786
+ }
2787
+ } catch { /* use taskId as fallback */ }
2788
+
2789
+ // Determine branch
2790
+ const branch = (result.git && (result.git.userBranch || result.git.originalBranch)) || null;
2791
+
2792
+ // Build inbox entry — deterministic ID: commit SHA (preferred) or 30s date bucket (fallback)
2793
+ // so that concurrent or retry calls collide on the same ID rather than producing N unique entries.
2794
+ const commitSha = (result.git && result.git.taskBranchCommit) || null;
2795
+ const idSuffix = commitSha ? commitSha.slice(0, 12) : String(Math.floor(Date.now() / 30000) * 30000);
2796
+ const entry = {
2797
+ id: `task_completed_${taskId}_${idSuffix}`,
2798
+ type: 'task_completed',
2799
+ task_id: taskId,
2800
+ task_title: taskTitle,
2801
+ completed_at: new Date().toISOString(),
2802
+ branch,
2803
+ };
2804
+
2805
+ writeInboxEntryIdempotent(inboxFile, specialistDir, entry);
2806
+ } catch { /* fire-and-forget — never blocks post-complete */ }
2807
+ }
2808
+
2809
+ // Step 2.6c: Cross-node task_completed event → yggdrasil relay /task_events (TASK_1409)
2810
+ // Payload schema v1: { event_type, task_id, from_node, to_node, payload: { schema_version,
2811
+ // commit_hash, branch, summary, status } }, idempotency_key = sha256(task_id:commit_hash:from_node).
2812
+ // Consumer: TASK_1413 PM Inbox Events tab — GET /task_events?to_node=PM_node.
2813
+ // Skip conditions: relay unconfigured, created_by missing (legacy task), self-dispatch,
2814
+ // internal infra task tag ([chore]/[FIX-INFRA]/no_relay_notify).
2815
+ if (result.taskCompleted && taskId && !dryRun) {
2816
+ try {
2817
+ // 1. Load yggdrasil config (relay_url + node_name)
2818
+ let yggConfig = null;
2819
+ try {
2820
+ const { loadYggdrasilConfig } = require('./supabase-client');
2821
+ yggConfig = loadYggdrasilConfig(projectPath);
2822
+ } catch { /* supabase-client unavailable */ }
2823
+
2824
+ // Per-project activation gate (TASK_1544/TASK_1546):
2825
+ // Derive the task's effective project key and check it is in collaboration_projects.
2826
+ // Mirrors TS computeEffectiveProjectKey (dashboard/src/lib/yggdrasil-project-key.ts:29-41)
2827
+ // and Rust derive_project_key (dashboard/src-tauri/.../yggdrasil.rs:164-179).
2828
+ // Priority: (1) explicit yggdrasil_project override, (2) git remote origin normalized,
2829
+ // (3) folder name of project root.
2830
+ // When collaboration_projects is absent or empty (legacy / not-yet-configured), the gate
2831
+ // is a no-op so existing single-yaml deployments keep working.
2832
+ let _yggProjectActivated = true; // assume activated unless gate says otherwise
2833
+ // _effectiveKey hoisted here so it is available for both the gate check AND the
2834
+ // relay emit body (Bug 2 fix — single derivation, no duplication).
2835
+ // Mirrors TS computeEffectiveProjectKey and Rust derive_project_key.
2836
+ // Priority: (1) explicit yggdrasil_project override, (2) git remote origin normalized,
2837
+ // (3) folder name of project root.
2838
+ let _effectiveKey = null;
2839
+ if (yggConfig && yggConfig.relay_url) {
2840
+ const _projectRoot = projectPath || ROOT;
2841
+
2842
+ // 1) explicit override
2843
+ const _explicitOverride = yggConfig.yggdrasil_project
2844
+ ? String(yggConfig.yggdrasil_project).trim() : '';
2845
+ if (_explicitOverride) {
2846
+ _effectiveKey = _explicitOverride;
2847
+ }
2848
+
2849
+ // 2) git remote origin, normalized — mirrors TS deriveProjectKey (lines 14-22) and
2850
+ // Rust strip_git_protocol (yggdrasil.rs:786-811).
2851
+ if (!_effectiveKey) {
2852
+ try {
2853
+ const _gitRemote = require('child_process').execSync(
2854
+ 'git remote get-url origin',
2855
+ { cwd: _projectRoot, encoding: 'utf8', timeout: 3000, windowsHide: true }
2856
+ ).trim();
2857
+ if (_gitRemote) {
2858
+ let _k = _gitRemote
2859
+ .replace(/^https?:\/\//, '') // strip https:// or http://
2860
+ .replace(/^git@/, '') // strip git@
2861
+ .replace(/^ssh:\/\/[^@]*@/, '') // strip ssh://user@
2862
+ .replace(/^ssh:\/\//, '') // strip ssh:// (no user)
2863
+ .replace(/:(?!\/)/, '/') // git@host:org → host/org
2864
+ .replace(/\/+$/, '') // trailing slashes
2865
+ .replace(/\.git$/, '') // .git suffix
2866
+ .replace(/\/+$/, ''); // trailing slashes after .git strip
2867
+ if (_k) _effectiveKey = _k;
2868
+ }
2869
+ } catch { /* no git remote — fall through */ }
2870
+ }
2871
+
2872
+ // 3) folder name of project root
2873
+ if (!_effectiveKey) {
2874
+ _effectiveKey = path.basename(_projectRoot) || 'default';
2875
+ }
2876
+
2877
+ const _collabProjectsRaw = (yggConfig.collaboration_projects != null)
2878
+ ? String(yggConfig.collaboration_projects) : '';
2879
+ const _collabProjects = _collabProjectsRaw
2880
+ .split(',').map(s => s.trim()).filter(s => s.length > 0);
2881
+
2882
+ if (_collabProjects.length > 0) {
2883
+ if (!_collabProjects.includes(_effectiveKey)) {
2884
+ console.log(` [yggdrasil] skip relay emit: project "${_effectiveKey}" not in collaboration_projects`);
2885
+ _yggProjectActivated = false;
2886
+ }
2887
+ } else if (String(yggConfig.collaboration_mode) !== 'true') {
2888
+ // No per-project set configured AND not a legacy collaboration_mode=true install.
2889
+ // Per-project activation (TASK_1544) defaults OFF, so a relay-configured but
2890
+ // not-yet-activated project must stay silent (single-machine / unactivated).
2891
+ // Legacy collaboration_mode=true installs are exempt (backwards compat) until they
2892
+ // re-save settings and seed collaboration_projects.
2893
+ console.log(` [yggdrasil] skip relay emit: no project activated (collaboration_projects empty)`);
2894
+ _yggProjectActivated = false;
2895
+ }
2896
+ }
2897
+
2898
+ if (yggConfig && yggConfig.relay_url && _yggProjectActivated) {
2899
+ // 2. Read publishable_key from ~/.kenaz/credentials.yaml (user-scoped, NOT project yaml)
2900
+ const os = require('os');
2901
+ let publishableKey = process.env.YGGDRASIL_PUBLISHABLE_KEY || null;
2902
+ if (!publishableKey) {
2903
+ try {
2904
+ const credPath = path.join(os.homedir(), '.kenaz', 'credentials.yaml');
2905
+ if (fs.existsSync(credPath)) {
2906
+ const credRaw = fs.readFileSync(credPath, 'utf8');
2907
+ const credMatch = credRaw.match(/^publishable_key:\s*"?([^"\n]+)"?/m);
2908
+ if (credMatch) publishableKey = credMatch[1].trim();
2909
+ }
2910
+ } catch { /* credentials not available */ }
2911
+ }
2912
+
2913
+ if (publishableKey) {
2914
+ // 3. Read task YAML: extract created_by.git_user (to_node) + infra skip signals
2915
+ let toNode = null;
2916
+ let taskTitleForSkip = taskId;
2917
+ let taskTagsForSkip = [];
2918
+ try {
2919
+ const taskGetArgs = projectPath ? ['--project-path', projectPath] : [];
2920
+ taskGetArgs.push('get', taskId);
2921
+ const taskGetResult = spawnSync('node', [COLLABORATION_API, ...taskGetArgs], {
2922
+ encoding: 'utf8', timeout: 10000, cwd: ROOT, windowsHide: true,
2923
+ });
2924
+ if (taskGetResult.status === 0 && taskGetResult.stdout) {
2925
+ const td = JSON.parse(taskGetResult.stdout.trim());
2926
+ // ROUTING KEY (TASK_1546 + TASK_1547): to_node must equal the publisher's actual
2927
+ // relay node_name. Priority order:
2928
+ // 1. pool_publisher_node — carried from relay record at claim time (TASK_1547);
2929
+ // robust even when node_name is overridden via yggdrasil.yaml.
2930
+ // 2. created_by.name — git user.name stamped at task creation; works when
2931
+ // node_name equals git user.name (the common case, non-pool/legacy tasks).
2932
+ // 3. created_by.git_user — last-resort fallback (email-derived, lower fidelity).
2933
+ toNode = (td && td.pool_publisher_node) || (td && td.created_by && (td.created_by.name || td.created_by.git_user)) || null;
2934
+ taskTitleForSkip = (td && td.title) || taskId;
2935
+ taskTagsForSkip = Array.isArray(td && td.tags) ? td.tags : [];
2936
+ }
2937
+ } catch { /* task fetch optional — skip emit on failure */ }
2938
+
2939
+ // Guard: created_by.name missing → legacy task (no cross-node routing key)
2940
+ if (toNode) {
2941
+ const fromNode = yggConfig.node_name;
2942
+
2943
+ // Guard: self-dispatch — PM dispatches to themselves; they see done/ directly
2944
+ if (fromNode !== toNode) {
2945
+ // Guard: internal infra tasks → reduce PM inbox noise
2946
+ const INFRA_PREFIXES = ['[chore]', '[fix-infra]', '[infra]', '[hotfix]'];
2947
+ const titleLower = taskTitleForSkip.toLowerCase();
2948
+ const commitLower = (message || '').toLowerCase();
2949
+ const isInfra = INFRA_PREFIXES.some(p => titleLower.startsWith(p) || commitLower.startsWith(p))
2950
+ || taskTagsForSkip.includes('no_relay_notify');
2951
+
2952
+ if (!isInfra) {
2953
+ const commitHash = (result.git && result.git.taskBranchCommit) || null;
2954
+ const branch = (result.git && (result.git.taskBranch || result.git.userBranch)) || null;
2955
+
2956
+ // Summary: reuse handoff snapshot current_topic (TASK_1409: do NOT regenerate)
2957
+ // TASK_1828: read via readLatestHandoff (handoffs/ per-session slots first,
2958
+ // legacy single slot fallback) — the old singular-file read went dead after
2959
+ // the M3 writer migration and every relay summary degraded to the fallback.
2960
+ let summary = message || `[${taskId}] task completed`;
2961
+ try {
2962
+ const handoffData = readLatestHandoff(projectPath || ROOT);
2963
+ const topic = handoffData && handoffData.isbar && handoffData.isbar.situation
2964
+ && handoffData.isbar.situation.current_topic;
2965
+ if (topic && String(topic).length > 5) summary = String(topic).substring(0, 120);
2966
+ } catch { /* use fallback summary */ }
2967
+
2968
+ // Idempotency key: sha256(task_id:commit_hash:from_node) — relay deduplicates on retry
2969
+ const crypto = require('crypto');
2970
+ const idempotencyKey = crypto.createHash('sha256')
2971
+ .update(`${taskId}:${commitHash || ''}:${fromNode}`)
2972
+ .digest('hex');
2973
+
2974
+ // Read the completed task's raw yaml so the PM can write it to their local DONE
2975
+ // on Accept (TASK_1551). Best-effort: never throw; omit done_yaml if not found.
2976
+ let doneYaml = null;
2977
+ try {
2978
+ const doneBase = path.join(projectPath || ROOT, '.kenaz', 'tasks', 'done');
2979
+ if (fs.existsSync(doneBase)) {
2980
+ const doneFiles = fs.readdirSync(doneBase);
2981
+ const match = doneFiles.find(f => f.startsWith(taskId));
2982
+ if (match) doneYaml = fs.readFileSync(path.join(doneBase, match), 'utf8');
2983
+ }
2984
+ } catch { /* best-effort — never fail the relay emit */ }
2985
+
2986
+ const relayEventBody = JSON.stringify({
2987
+ event_type: 'task_completed',
2988
+ task_id: taskId,
2989
+ from_node: fromNode,
2990
+ to_node: toNode,
2991
+ project: _effectiveKey,
2992
+ payload: {
2993
+ schema_version: 1,
2994
+ commit_hash: commitHash,
2995
+ branch,
2996
+ summary,
2997
+ status: 'merged',
2998
+ ...(doneYaml ? { done_yaml: doneYaml } : {}),
2999
+ },
3000
+ idempotency_key: idempotencyKey,
3001
+ });
3002
+
3003
+ // Convert ws(s) → http(s) for REST POST
3004
+ const relayHttpBase = yggConfig.relay_url
3005
+ .replace(/^wss:\/\//, 'https://')
3006
+ .replace(/^ws:\/\//, 'http://');
3007
+ let relayHostname = null, relayPort = 443, relayIsHttps = true;
3008
+ try {
3009
+ const relayParsed = new URL(relayHttpBase);
3010
+ relayHostname = relayParsed.hostname;
3011
+ relayIsHttps = relayParsed.protocol === 'https:';
3012
+ relayPort = relayParsed.port
3013
+ ? Number(relayParsed.port)
3014
+ : (relayIsHttps ? 443 : 80);
3015
+ } catch { /* malformed relay_url — skip emit */ }
3016
+
3017
+ if (relayHostname) {
3018
+ const relayModule = relayIsHttps ? require('https') : require('http');
3019
+ const relayReq = relayModule.request({
3020
+ hostname: relayHostname,
3021
+ port: relayPort,
3022
+ path: '/api/task_events',
3023
+ method: 'POST',
3024
+ headers: {
3025
+ 'Content-Type': 'application/json',
3026
+ 'Content-Length': Buffer.byteLength(relayEventBody),
3027
+ Authorization: `Bearer ${publishableKey}`,
3028
+ },
3029
+ timeout: 5000,
3030
+ });
3031
+ relayReq.on('error', (err) => {
3032
+ console.warn(` ⚠️ relay /api/task_events unreachable (non-blocking): ${err.message}`);
3033
+ });
3034
+ relayReq.on('timeout', () => {
3035
+ relayReq.destroy();
3036
+ console.warn(` ⚠️ relay /api/task_events timed out (non-blocking)`);
3037
+ });
3038
+ relayReq.on('response', (relayRes) => {
3039
+ relayRes.resume(); // drain body
3040
+ if (relayRes.statusCode >= 200 && relayRes.statusCode < 300) {
3041
+ console.log(` 📡 Relay: task_completed → ${toNode} (${taskId})`);
3042
+ } else {
3043
+ console.warn(` ⚠️ relay /api/task_events returned HTTP ${relayRes.statusCode} (non-blocking)`);
3044
+ }
3045
+ });
3046
+ relayReq.write(relayEventBody);
3047
+ relayReq.end();
3048
+ }
3049
+ }
3050
+ }
3051
+ }
3052
+ }
3053
+ }
3054
+ } catch { /* fire-and-forget — never blocks post-complete */ }
3055
+ }
3056
+
3057
+ // Step 3: Memory Agent(記憶系統整合)
3058
+ if (memoryAgent && taskId) {
3059
+ try {
3060
+ const testsFailed = result.git?.regression && !result.git.regression.passed && !result.git.regression.skipped;
3061
+ if (testsFailed) {
3062
+ // 回歸測試失敗 → onError(fire-and-forget)
3063
+ memoryAgent.onError({
3064
+ event: 'regression_test_failure',
3065
+ taskId,
3066
+ error: `Regression tests failed after ${taskId} completion`,
3067
+ summary: result.git.regression.output?.slice(-500),
3068
+ }).catch(() => {});
3069
+ console.log(' 🧠 Memory: error event queued (regression failure)');
3070
+ } else {
3071
+ // 任務成功完成 → onTaskCompleted(fire-and-forget)
3072
+ memoryAgent.onTaskCompleted(taskId).catch(() => {});
3073
+ console.log(' 🧠 Memory: task completion event queued');
3074
+ }
3075
+ } catch (e) {
3076
+ console.log(` ⚠️ Memory agent skipped: ${e.message}`);
3077
+ }
3078
+ }
3079
+
3080
+ // Step 3b: Provenance rune — record which task + role modified scope paths (Cognee-inspired)
3081
+ if (taskId && !dryRun && result.git && result.git.merged) {
3082
+ try {
3083
+ const scopePaths = loadTaskScopePaths(taskId, projectPath);
3084
+ const role = result.taskRole || 'unknown';
3085
+ if (scopePaths.length > 0 && scopePaths.length <= 5) {
3086
+ const http = require('http');
3087
+ for (const scopePath of scopePaths) {
3088
+ try {
3089
+ const payload = JSON.stringify({
3090
+ jsonrpc: '2.0', id: `provenance-${taskId}`,
3091
+ method: 'tools/call',
3092
+ params: { name: 'synapse_inscribe', arguments: {
3093
+ symbol_id: scopePath,
3094
+ type: 'summary',
3095
+ content: `Modified by ${taskId} (${role}). Scope: ${scopePaths.join(', ')}`,
3096
+ confidence: 0.6,
3097
+ author: `agent:${role}`,
3098
+ linked_task: taskId,
3099
+ // TASK_2025: write tool -- must not omit projectPath (see epArgs comment above).
3100
+ projectPath: projectPath || ROOT,
3101
+ }},
3102
+ });
3103
+ const req = http.request({
3104
+ hostname: '127.0.0.1', port: 18080, path: '/api/mcp',
3105
+ method: 'POST',
3106
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
3107
+ timeout: 5000,
3108
+ }, (res) => { res.on('data', () => {}); res.on('end', () => {}); });
3109
+ req.on('error', () => {});
3110
+ req.on('timeout', () => { req.destroy(); });
3111
+ req.write(payload);
3112
+ req.end();
3113
+ } catch { /* skip */ }
3114
+ }
3115
+ console.log(` 📝 Provenance runes: ${scopePaths.length} scope paths tagged with ${taskId}/${role}`);
3116
+ }
3117
+ } catch { /* never blocks */ }
3118
+ }
3119
+
3120
+ // Step 4: Guardian Health Check + KEEP/REVERT delta (TASK_706)
3121
+ if (taskId) {
3122
+ const counter = readGuardianCounter();
3123
+ counter.completed_since_last_health = (counter.completed_since_last_health || 0) + 1;
3124
+
3125
+ const loopCfg = readLoopConfig(projectPath);
3126
+ const regressionSkipVerdictReason = result.git?.regression?.skipVerdictReason || '';
3127
+ const shouldSkipHealthVerdict = !!result.git?.regression?.skipVerdict;
3128
+
3129
+ // TASK_2341: synapse_health now scores MEMORY (rune/episode/conflict backlog), not code,
3130
+ // so it can never revert, block or penalise a task. Both modes only RECORD the trend.
3131
+ if (loopCfg.mode === 'auto' && shouldSkipHealthVerdict) {
3132
+ console.log('── Guardian Memory-Health Trend (loop auto mode) ──');
3133
+ console.log(` ⚠️ Guardian: health snapshot skipped — ${regressionSkipVerdictReason}`);
3134
+ result.healthVerdictSkipped = { reason: regressionSkipVerdictReason };
3135
+ counter.completed_since_last_health = 0;
3136
+ counter.last_health_check = new Date().toISOString();
3137
+ writeGuardianCounter(counter);
3138
+ } else {
3139
+ const preSn = readPreTaskHealthSnapshot(taskId, projectPath);
3140
+ if (preSn && typeof preSn.score === 'number') {
3141
+ console.log(`── Guardian Memory-Health Trend (${loopCfg.mode} mode — record only, never reverts) ──`);
3142
+ const postHealth = measureHealthScoreSync(projectPath);
3143
+ if (postHealth) {
3144
+ const afterPayload = saveTaskHealthSnapshot(taskId, 'after', postHealth, projectPath);
3145
+ const vr = computeHealthVerdict(preSn, postHealth, loopCfg.revert_threshold, {
3146
+ trendDeltaFromPrevious: afterPayload ? afterPayload.delta_from_previous : null,
3147
+ });
3148
+ console.log(` 🛡️ Memory health: ${postHealth.score}/100 (${vr.reason})`);
3149
+ if (typeof vr.trendDeltaFromPrevious === 'number') {
3150
+ console.log(` 📈 Trend (delta_from_previous): ${vr.trendDeltaFromPrevious >= 0 ? '+' : ''}${vr.trendDeltaFromPrevious}`);
3151
+ }
3152
+ writeVerdictToTask(taskId, vr.verdict, vr.delta, vr.reason, projectPath);
3153
+ result.healthVerdict = { verdict: vr.verdict, delta: vr.delta, reason: vr.reason };
3154
+ // Auto-acted_on: mark runes recalled during this task as acted_on (closes the
3155
+ // recall->acted_on->confidence_boost loop). Route still exists (synapse.rs).
3156
+ if (loopCfg.mode === 'auto') {
3157
+ try {
3158
+ const actedOnPayload = JSON.stringify({ taskId });
3159
+ const actReq = require('http').request({
3160
+ hostname: '127.0.0.1',
3161
+ port: 18080,
3162
+ path: '/api/synapse/runes/mark-acted-on-by-task',
3163
+ method: 'POST',
3164
+ headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(actedOnPayload) },
3165
+ timeout: 3000,
3166
+ }, (res) => {
3167
+ let body = '';
3168
+ res.on('data', c => body += c);
3169
+ res.on('end', () => {
3170
+ try {
3171
+ const r = JSON.parse(body);
3172
+ if (r.marked > 0) console.log(` 🔗 Acted-on: ${r.marked} runes marked for ${taskId}`);
3173
+ } catch { /* parse error — silent */ }
3174
+ });
3175
+ });
3176
+ actReq.on('error', () => { /* Dashboard offline — silent skip */ });
3177
+ actReq.write(actedOnPayload);
3178
+ actReq.end();
3179
+ } catch { /* never blocks post-complete */ }
3180
+ }
3181
+ counter.completed_since_last_health = 0;
3182
+ counter.last_health_check = new Date().toISOString();
3183
+ } else {
3184
+ console.log(` 🛡️ Guardian: Dashboard not running — after-snapshot skipped for ${taskId}`);
3185
+ }
3186
+ } else if (loopCfg.mode === 'auto') {
3187
+ const postHealth = measureHealthScoreSync(projectPath);
3188
+ if (postHealth) {
3189
+ saveTaskHealthSnapshot(taskId, 'before', postHealth, projectPath);
3190
+ console.log(` 🛡️ Guardian: memory health=${postHealth.score}/100 (${postHealth.grade}) — saved as baseline (no pre-task snapshot found)`);
3191
+ } else {
3192
+ console.log(' 🛡️ Guardian: Dashboard not running — health snapshot skipped');
3193
+ }
3194
+ counter.completed_since_last_health = 0;
3195
+ counter.last_health_check = new Date().toISOString();
3196
+ } else if (counter.completed_since_last_health >= CONFIG.GUARDIAN_INTERVAL) {
3197
+ console.log(`── Guardian Health Check (every ${CONFIG.GUARDIAN_INTERVAL} tasks) ──`);
3198
+ triggerGuardianHealthCheck(projectPath);
3199
+ counter.completed_since_last_health = 0;
3200
+ counter.last_health_check = new Date().toISOString();
3201
+ } else {
3202
+ const remaining = CONFIG.GUARDIAN_INTERVAL - counter.completed_since_last_health;
3203
+ console.log(` 🛡️ Guardian: ${remaining} tasks until next health snapshot (no pre-dispatch snapshot for ${taskId})`);
3204
+ }
3205
+ }
3206
+
3207
+ if (!shouldSkipHealthVerdict || loopCfg.mode !== 'auto') {
3208
+ writeGuardianCounter(counter);
3209
+ }
3210
+ }
3211
+
3212
+ // Step 4.6: Guard Failure Detector (TASK_1653)
3213
+ // Runs shadow-mode failure detectors for active lesson guards that carry failure_signature.
3214
+ // Reads guard snapshots directly from lesson_guards.yaml (not the task chart) because
3215
+ // lesson_guards_injected is recorded in the Ansuz session chart -- not in dispatched
3216
+ // worker task charts (chart_TASK_XXXX.json). Reading from YAML is the reliable source.
3217
+ // Shadow mode: detector_mode='shadow' guards log to guard_detector_shadow.jsonl only.
3218
+ // Non-fatal: any failure is swallowed -- must never block post-complete.
3219
+ if (taskId && !dryRun) {
3220
+ try {
3221
+ const detectorMod = require(path.join(ROOT, '.specialist', 'tools', 'failure-detector.js'));
3222
+ const lgMod = require(path.join(ROOT, '.specialist', 'tools', 'lesson-guard.js'));
3223
+ // thrift: build minimal snapshots from active guards with failure_signature only
3224
+ const guardsData = lgMod.readGuards();
3225
+ const guardSnapshots = Object.entries(guardsData.guards)
3226
+ .filter(([, g]) => g.status === 'active' && g.failure_signature != null)
3227
+ .map(([id, g]) => ({ id, failure_signature: g.failure_signature, phase: null }));
3228
+ if (guardSnapshots.length > 0) {
3229
+ detectorMod.runDetectorsForTask(taskId, guardSnapshots, result || null);
3230
+ }
3231
+ } catch (e) { console.log(` [warn] Guard detector skipped: ${e.message}`); }
3232
+ }
3233
+
3234
+ // Step 5: Cognitive Checkpoint Detection
3235
+ if (taskId) {
3236
+ const cogResult = detectCognitiveCheckpoint(taskId, logPath, projectPath);
3237
+ result.cognitive = cogResult;
3238
+ if (cogResult.checked) {
3239
+ for (const w of cogResult.warnings) {
3240
+ console.log(` ${w}`);
3241
+ }
3242
+ } else if (cogResult.warnings.length > 0) {
3243
+ console.log(` 🧠 Cognitive: ${cogResult.warnings[0]}`);
3244
+ }
3245
+ }
3246
+
3247
+ // Step 5.5: Design Reasoning Attribution (TASK_1074)
3248
+ // Correlates Phase 3 design inputs (principles, guards, formation) with actual outcomes.
3249
+ // Non-fatal: all errors swallowed; attribution must never block post-complete.
3250
+ if (taskId && !dryRun && designReasoning) {
3251
+ try {
3252
+ const projectRoot = resolveProjectRoot(projectPath);
3253
+ if (projectRoot) {
3254
+ // Collect thinking tags from this task (already computed for cognitive attribution above)
3255
+ const { tags: thinkingTags } = collectThinkingTags(taskId, projectRoot);
3256
+
3257
+ // Resolve outcome data
3258
+ const outcomeSuccess = result.git ? (result.git.merged === true) : true;
3259
+ // TASK_2341: the recorded health score is MEMORY-only (not a code-outcome signal), so it
3260
+ // never feeds valence; valence comes from merge success alone.
3261
+ const healthDelta = null;
3262
+ const attrValence = outcomeSuccess ? 'positive' : 'negative';
3263
+
3264
+ // Read surprise score from ansuz_accuracy.json if available
3265
+ let surpriseScore = null;
3266
+ try {
3267
+ const accFile = path.join(projectRoot, '.kenaz', 'specialist', 'ansuz_accuracy.json');
3268
+ if (fs.existsSync(accFile)) {
3269
+ const accData = JSON.parse(fs.readFileSync(accFile, 'utf8'));
3270
+ if (Array.isArray(accData.comparison_history)) {
3271
+ const match = accData.comparison_history.find(h => h.task_id === taskId);
3272
+ if (match) surpriseScore = parseFloat((1.0 - (match.file_jaccard || 1)).toFixed(3));
3273
+ }
3274
+ }
3275
+ } catch { /* surprise score optional */ }
3276
+
3277
+ const designInputs = typeof designReasoning.readDesignReasoning === 'function'
3278
+ ? designReasoning.readDesignReasoning(taskId, projectPath)
3279
+ : null;
3280
+ const usingCompressionFallback = designInputs && designInputs.source === 'compression_fallback';
3281
+
3282
+ const report = designReasoning.generateAttributionReport({
3283
+ taskId,
3284
+ projectPath,
3285
+ outcomeSuccess,
3286
+ healthDelta,
3287
+ surpriseScore,
3288
+ valence: attrValence,
3289
+ thinkingTags,
3290
+ });
3291
+
3292
+ if (usingCompressionFallback) {
3293
+ console.log(` ℹ️ Attribution: compression-fallback prediction used for ${taskId} — full Phase 3 tracking was compressed`);
3294
+ }
3295
+
3296
+ if (usingCompressionFallback) {
3297
+ // Already logged the compression fallback attribution notice above.
3298
+ } else if (report) {
3299
+ const principleCount = report.principle_correlations.length;
3300
+ const guardCount = report.guard_correlations.length;
3301
+ console.log(` 🔗 Attribution: ${principleCount} principle(s), ${guardCount} guard(s) → valence=${attrValence}`);
3302
+ if (report.heuristic_signal) {
3303
+ console.log(` 📐 Signal: ${report.heuristic_signal.slice(0, 120)}`);
3304
+ }
3305
+ } else {
3306
+ console.log(` 🔗 Attribution: no Phase 3 design inputs found for ${taskId} — skipped`);
3307
+ }
3308
+ }
3309
+ } catch (e) {
3310
+ console.log(` ⚠️ Design attribution failed: ${e.message}`);
3311
+ }
3312
+ }
3313
+
3314
+ // Summary
3315
+ console.log('── Summary ──');
3316
+ if (result.archive) {
3317
+ const ca = result.archive.collab;
3318
+ console.log(` Archive: ${ca.archived.length} archived, ${ca.kept.length} kept`);
3319
+ if (!projectPath) {
3320
+ const la = result.archive.legacy;
3321
+ console.log(` Archive [legacy]: ${la.archived.length} archived, ${la.kept.length} kept`);
3322
+ }
3323
+ }
3324
+ if (result.git) {
3325
+ if (result.git.regression) {
3326
+ const r = result.git.regression;
3327
+ if (r.skipVerdict) {
3328
+ console.log(` Tests: transient cargo lock contention — verdict skipped (${r.skipVerdictReason})`);
3329
+ } else if (r.skipped) {
3330
+ console.log(' Tests: skipped (none found)');
3331
+ } else if (r.results && r.results.length > 0) {
3332
+ const passed = r.results.filter(t => t.passed).length;
3333
+ const failed = r.results.filter(t => !t.passed).length;
3334
+ console.log(` Tests: ${passed} passed, ${failed} failed ${r.passed ? '✅' : '❌'}`);
3335
+ for (const t of r.results) {
3336
+ console.log(` ${t.passed ? '✅' : '❌'} ${t.label}`);
3337
+ }
3338
+ } else {
3339
+ console.log(` Tests: ${r.passed ? 'PASSED ✅' : 'FAILED ❌'}`);
3340
+ }
3341
+ if (result.git.regressionFixTask) {
3342
+ console.log(` Fix task: ${result.git.regressionFixTask}`);
3343
+ }
3344
+ }
3345
+ // Acceptance criteria summary
3346
+ if (result.git.acceptance) {
3347
+ const ac = result.git.acceptance;
3348
+ if (ac.skipped) {
3349
+ console.log(` Acceptance: skipped (${ac.message || 'n/a'})`);
3350
+ } else if (ac.indeterminate) {
3351
+ // TASK_1825: timeout/parse failure must reach the summary as non-pass
3352
+ console.log(` Acceptance: INDETERMINATE ⚠️ (${ac.error || 'unknown'}) — no verdict (NOT a pass); report in .formation/reports/verify/`);
3353
+ } else if (ac.pass) {
3354
+ const count = ac.results ? ac.results.length : 0;
3355
+ console.log(` Acceptance: ${count} criteria ALL PASS ✅`);
3356
+ } else {
3357
+ const passed = (ac.results || []).filter(r => r.pass).length;
3358
+ const failed = (ac.results || []).filter(r => !r.pass).length;
3359
+ console.log(` Acceptance: ${passed} passed, ${failed} failed ❌`);
3360
+ if (result.git.acceptanceFixTasks && result.git.acceptanceFixTasks.length > 0) {
3361
+ console.log(` Acceptance fix: ${result.git.acceptanceFixTasks.join(', ')}`);
3362
+ }
3363
+ }
3364
+ }
3365
+ if (result.git.integrationGaps && result.git.integrationGaps.orphans?.length > 0) {
3366
+ console.log(` Integration: ${result.git.integrationGaps.orphans.length} orphan export(s) detected`);
3367
+ if (result.git.integrationFixTask) {
3368
+ console.log(` Wiring task: ${result.git.integrationFixTask}`);
3369
+ }
3370
+ }
3371
+ console.log(` Git: branch=${result.git.taskBranch} committed=${result.git.committed} merged=${result.git.merged} verify_merge_landed=${result.git.verify_merge_landed === true}`);
3372
+ if (result.git.scopeViolations && result.git.scopeViolations.length > 0) {
3373
+ console.log(` ⚠️ Scope violations: ${result.git.scopeViolations.length} file(s) outside scope_paths`);
3374
+ }
3375
+ if (isVerifiedMergeSuccess(result.git)) {
3376
+ console.log(` 👉 On branch: ${result.git.userBranch} — merge to main when ready`);
3377
+ }
3378
+ }
3379
+ // Health verdict summary (TASK_706)
3380
+ if (result.healthVerdict) {
3381
+ const hv = result.healthVerdict;
3382
+ console.log(` Memory Health: ${hv.verdict} (trend=${typeof hv.delta === 'number' ? hv.delta.toFixed(1) : 'n/a'}) — ${hv.reason}`);
3383
+ } else if (result.healthVerdictSkipped) {
3384
+ console.log(` Health Verdict: SKIPPED ⚠️ — ${result.healthVerdictSkipped.reason}`);
3385
+ }
3386
+
3387
+ // Cognitive checkpoint summary
3388
+ if (result.cognitive) {
3389
+ const cog = result.cognitive;
3390
+ if (cog.checked) {
3391
+ const pre = cog.preCode || 'MISSING';
3392
+ const post = cog.postCode || 'MISSING';
3393
+ const icon = (cog.preCode && cog.postCode) ? '✅' : '⚠️';
3394
+ console.log(` Cognitive: Pre=${pre} Post=${post} ${icon}`);
3395
+ } else {
3396
+ console.log(` Cognitive: not checked (no log)`);
3397
+ }
3398
+ }
3399
+ console.log('');
3400
+
3401
+ // Step 8a: Hippocampus Episode Append — record task outcome (TASK_969)
3402
+ if (hippocampusEpisodeId && !dryRun) {
3403
+ try {
3404
+ const contentParts = [`Task ${taskId} outcome:`];
3405
+
3406
+ // Changed files
3407
+ const changedFileList = result.git?.diffStat || [];
3408
+ if (changedFileList.length > 0) {
3409
+ contentParts.push(`Changed files: ${changedFileList.join(', ')}`);
3410
+ } else if (result.git?.committed) {
3411
+ contentParts.push('Changes committed (file list unavailable).');
3412
+ } else if (result.git?.noChanges) {
3413
+ contentParts.push('No file changes detected.');
3414
+ }
3415
+
3416
+ // Health delta (regression test results)
3417
+ if (result.git?.regression?.skipVerdict) {
3418
+ contentParts.push(`Health verdict skipped: ${result.git.regression.skipVerdictReason}.`);
3419
+ } else if (result.git?.regression && !result.git.regression.skipped) {
3420
+ contentParts.push(result.git.regression.passed
3421
+ ? 'Health: tests passed.'
3422
+ : `Health: tests FAILED.${result.git.regressionFixTask ? ` Fix task: ${result.git.regressionFixTask}` : ''}`);
3423
+ }
3424
+
3425
+ // Fix loop status
3426
+ if (result.git?.acceptanceFixTasks && result.git.acceptanceFixTasks.length > 0) {
3427
+ contentParts.push(`Fix loop triggered: ${result.git.acceptanceFixTasks.join(', ')}`);
3428
+ }
3429
+
3430
+ // Git merge status
3431
+ if (isVerifiedMergeSuccess(result.git)) {
3432
+ contentParts.push(`Merged to ${result.git.userBranch}.`);
3433
+ }
3434
+
3435
+ const appendArgs = {
3436
+ episode_id: hippocampusEpisodeId,
3437
+ phase: 'outcome',
3438
+ content: contentParts.join(' '),
3439
+ confidence: 0.8,
3440
+ outcome: isVerifiedMergeSuccess(result.git) ? 'success' : 'partial',
3441
+ // TASK_2025: write tool -- must not omit projectPath (see epArgs comment above).
3442
+ projectPath: projectPath || ROOT,
3443
+ };
3444
+ callHippocampusMcpSync('hippocampus_episode_append', appendArgs);
3445
+ console.log(` 🧠 Hippocampus: episode ${hippocampusEpisodeId} appended outcome entry`);
3446
+ } catch (e) {
3447
+ console.log(` ⚠️ Hippocampus episode append skipped: ${e.message}`);
3448
+ }
3449
+ }
3450
+
3451
+ // Step 8b: Hippocampus Episode Close (episodic memory — TASK_716)
3452
+ if (hippocampusEpisodeId && !dryRun) {
3453
+ try {
3454
+ const outcome = isVerifiedMergeSuccess(result.git)
3455
+ ? 'success'
3456
+ : (result.git?.noChanges || result.git?.memoryOnly ? 'success' : 'partial');
3457
+ const summaryParts = [`Task ${taskId} post-complete finished.`];
3458
+ if (isVerifiedMergeSuccess(result.git)) summaryParts.push(`Git merged to ${result.git.userBranch}.`);
3459
+ if (result.taskCompleted) summaryParts.push('Task marked done.');
3460
+ if (result.git?.regression?.skipVerdict) {
3461
+ summaryParts.push('Health verdict skipped due to cargo lock contention.');
3462
+ } else if (result.git?.regression && !result.git.regression.skipped) {
3463
+ summaryParts.push(result.git.regression.passed ? 'Tests passed.' : 'Tests failed — fix task created.');
3464
+ }
3465
+ const closeArgs = {
3466
+ episode_id: hippocampusEpisodeId,
3467
+ outcome,
3468
+ summary: summaryParts.join(' '),
3469
+ // TASK_2025: write tool -- must not omit projectPath (see epArgs comment above).
3470
+ projectPath: projectPath || ROOT,
3471
+ };
3472
+ callHippocampusMcpSync('hippocampus_episode_close', closeArgs);
3473
+ console.log(` 🧠 Hippocampus: episode ${hippocampusEpisodeId} closed (${outcome})`);
3474
+ } catch (e) {
3475
+ console.log(` ⚠️ Hippocampus episode close skipped: ${e.message}`);
3476
+ }
3477
+ }
3478
+
3479
+ // TASK_1593: Record outcome in dedup guard so a failed pass does not
3480
+ // suppress the next legitimate retry (Guard 1 worktree->null check).
3481
+ if (taskId && !dryRun) {
3482
+ const passSucceeded = isVerifiedMergeSuccess(result.git) ||
3483
+ (result.git && (result.git.noChanges || result.git.memoryOnly));
3484
+ updatePostCompleteGuardOutcome(guardFile, taskId, passSucceeded ? 'success' : 'failure');
3485
+ }
3486
+
3487
+ return result;
3488
+ }
3489
+
3490
+ // ─────────────────────────────────────────
3491
+ // CLI
3492
+ // ─────────────────────────────────────────
3493
+ if (require.main === module) {
3494
+ const args = process.argv.slice(2);
3495
+
3496
+ if (args.includes('--help') || args.includes('-h')) {
3497
+ console.log(`
3498
+ ⚔️ Formation Post-Complete
3499
+
3500
+ 任務完成後自動化:Archive 清理 + Git 分支管理
3501
+
3502
+ Usage:
3503
+ node .formation/tools/post-complete.js --task=TASK_XXX [options]
3504
+
3505
+ Options:
3506
+ --task TASK_XXX 任務 ID(必填,除非 --skip-git)
3507
+ --project-path PATH 子專案路徑(預設: core KenazAI)
3508
+ --worktree-path PATH Worktree 路徑(並行 dispatch 模式,env: FORMATION_WORKTREE_PATH)
3509
+ --keep N 保留最新 N 個 done 任務(預設 10)
3510
+ --skip-archive 跳過 archive 檢查
3511
+ --skip-git 跳過 git 分支管理
3512
+ --skip-tests 跳過回歸測試(PPL-113)
3513
+ --test-cmd "CMD" Agent 指定的測試指令(可多次指定,PPL-113)
3514
+ --message "MSG" 自訂 commit message
3515
+ --dry-run 模擬執行(不修改檔案/git)
3516
+
3517
+ PPL-113 Regression Guard:
3518
+ Agent 透過 --test-cmd 傳入測試指令,腳本依序執行。
3519
+ 未傳 --test-cmd 時 fallback 到自動偵測所有測試套件。
3520
+ 測試失敗 → 自動開 FIX 單(閉環),繼續 commit。
3521
+
3522
+ Acceptance Criteria Verification:
3523
+ Commit 後自動呼叫 verify-acceptance.js 驗證 task 的 acceptance_criteria。
3524
+ 用 Sonnet 讀取 git diff + source code,逐條判斷 PASS/FAIL。
3525
+ 驗證失敗 → 自動開 FIX 單(含失敗原因),繼續 merge。
3526
+ 原卡帶 fix tag → 不開 FIX 單(循環防護),改在原卡 notes 寫 ACCEPTANCE_FAILED 標記。
3527
+ 逾時 / 輸出無法解析 → INDETERMINATE(不是 pass),報告存 .formation/reports/verify/。
3528
+ --skip-tests 同時跳過回歸測試和 acceptance 驗證。
3529
+
3530
+ Git Flow:
3531
+ 1. 建立 task/task_xxx branch
3532
+ 2. Stage + commit 所有變更
3533
+ 2.5. Acceptance criteria verification(verify-acceptance)
3534
+ 3. Merge 到用戶分支(git user name, e.g. xyz9516)
3535
+ 4. 用戶手動 merge 到 main
3536
+
3537
+ Archive Flow:
3538
+ Core: .collaboration/tasks/done/ → archived/ + dev/pipeline/completed/ → archived/
3539
+ Sub: .kenaz/tasks/done/ → archived/
3540
+ 超過 --keep 個 → 最舊的移到 archived/
3541
+
3542
+ Examples:
3543
+ node .formation/tools/post-complete.js --task=TASK_205
3544
+ node .formation/tools/post-complete.js --task=TASK_9 --project-path=C:\\Users\\Wei\\Desktop\\Project\\assembly
3545
+ node .formation/tools/post-complete.js --task=TASK_205 --keep=15
3546
+ node .formation/tools/post-complete.js --task=TASK_205 --dry-run
3547
+ node .formation/tools/post-complete.js --task=TASK_205 --test-cmd "cd dashboard && npm test" --test-cmd "cd dashboard/pty-server && cargo test"
3548
+ node .formation/tools/post-complete.js --skip-git # 只做 archive
3549
+ `);
3550
+ process.exit(0);
3551
+ }
3552
+
3553
+ const getArg = (flag) => {
3554
+ for (let i = 0; i < args.length; i++) {
3555
+ if (args[i].startsWith(`${flag}=`)) return args[i].split('=').slice(1).join('=');
3556
+ if (args[i] === flag && args[i + 1]) return args[i + 1];
3557
+ }
3558
+ return null;
3559
+ };
3560
+
3561
+ const taskId = getArg('--task');
3562
+ const keep = getArg('--keep') ? parseInt(getArg('--keep'), 10) : CONFIG.DEFAULT_KEEP;
3563
+ const skipArchive = args.includes('--skip-archive');
3564
+ const skipGit = args.includes('--skip-git');
3565
+ const skipTests = args.includes('--skip-tests');
3566
+ const dryRun = args.includes('--dry-run');
3567
+ const message = getArg('--message');
3568
+ const projectPath = getArg('--project-path');
3569
+ const worktreePath = getArg('--worktree-path') || process.env.FORMATION_WORKTREE_PATH || null;
3570
+
3571
+ // Collect all --test-cmd values (Agent-specified test commands)
3572
+ const testCommands = [];
3573
+ for (let i = 0; i < args.length; i++) {
3574
+ if (args[i] === '--test-cmd' && args[i + 1]) {
3575
+ testCommands.push(args[++i]);
3576
+ } else if (args[i].startsWith('--test-cmd=')) {
3577
+ testCommands.push(args[i].split('=').slice(1).join('='));
3578
+ }
3579
+ }
3580
+
3581
+ if (!taskId && !skipGit) {
3582
+ console.error('❌ --task is required (or use --skip-git for archive-only mode)');
3583
+ process.exit(1);
3584
+ }
3585
+
3586
+ try {
3587
+ postComplete({ taskId, keep, skipArchive, skipGit, skipTests, dryRun, message, projectPath, testCommands, worktreePath });
3588
+ } catch (e) {
3589
+ console.error(`❌ Post-complete failed: ${e.message}`);
3590
+ process.exit(1);
3591
+ }
3592
+ }
3593
+ // ?????????????????????????????????????????????????????????????????????????????
3594
+ // SECTION: EXPORTS
3595
+ // ?????????????????????????????????????????????????????????????????????????????
3596
+ // ─────────────────────────────────────────
3597
+ // Sub-agent post-complete (lightweight equivalent for Agent tool dispatch)
3598
+ // ─────────────────────────────────────────
3599
+
3600
+ /**
3601
+ * Lightweight post-complete for tasks dispatched via Agent tool (isolation=worktree).
3602
+ * The Agent tool handles worktree git isolation automatically, so git merge is skipped.
3603
+ * Health scoring is deferred to the next Heimdall cycle (dashboard must be running
3604
+ * for measureHealthScoreSync to work; safe to call from Ansuz interactive session).
3605
+ *
3606
+ * Call this after the Agent tool sub-agent returns to record the health delta.
3607
+ *
3608
+ * @param {string} taskId - Task ID (TASK_XXX)
3609
+ * @param {string|null} projectPath - Sub-project path (null = core)
3610
+ * @returns {Promise<{ healthDelta: number|null, grade: string|null }>}
3611
+ */
3612
+ async function subagentPostComplete(taskId, projectPath) {
3613
+ const result = { healthDelta: null, grade: null };
3614
+
3615
+ try {
3616
+ // Record post-task health snapshot (for audit trail + Heimdall comparison)
3617
+ const postHealth = measureHealthScoreSync(projectPath);
3618
+ if (postHealth && taskId) {
3619
+ saveTaskHealthSnapshot(taskId, 'after', postHealth, projectPath);
3620
+ const preSn = readPreTaskHealthSnapshot(taskId, projectPath);
3621
+ if (preSn?.score != null) {
3622
+ // TASK_2341: null when the baseline is graph-era (different score_version)
3623
+ result.healthDelta = computeHealthVerdict(preSn, postHealth).delta;
3624
+ result.grade = postHealth.grade;
3625
+ console.log(
3626
+ ` 🛡️ Sub-agent Guardian: health=${postHealth.score}/100 (${postHealth.grade})` +
3627
+ ` trend=${typeof result.healthDelta === 'number' ? (result.healthDelta >= 0 ? '+' : '') + result.healthDelta : 'n/a'}`
3628
+ );
3629
+ } else {
3630
+ // Save as baseline for future comparison
3631
+ saveTaskHealthSnapshot(taskId, 'before', postHealth, projectPath);
3632
+ result.grade = postHealth.grade;
3633
+ console.log(` 🛡️ Sub-agent Guardian: health=${postHealth.score}/100 (${postHealth.grade}) — saved as baseline`);
3634
+ }
3635
+ }
3636
+ } catch { /* never block Ansuz on health check failure */ }
3637
+
3638
+ return result;
3639
+ }
3640
+
3641
+ module.exports = {
3642
+ classifyMergeFailureReason,
3643
+ postComplete,
3644
+ archiveCheck,
3645
+ gitBranchAndMerge,
3646
+ measureHealthScoreSync,
3647
+ saveTaskHealthSnapshot,
3648
+ classifySkillKey,
3649
+ isDaemonManagedPath,
3650
+ listDaemonManagedPaths,
3651
+ getDaemonPathsToUnstage,
3652
+ verifyStagedScopeCoverage,
3653
+ checkAndUpdatePostCompleteGuard,
3654
+ updatePostCompleteGuardOutcome,
3655
+ writeInboxEntryIdempotent,
3656
+ subagentPostComplete,
3657
+ readLatestHandoff, // TASK_1828: handoffs/-first resolver for relay summaries
3658
+ reclaimWorktreeDir, // TASK_2028: unlock + git-level reclaim for stale/interrupted worktrees
3659
+ appendPhase9OutcomeLog, // TASK_2078: exported for agent_result/transcript precedence unit testing
3660
+ };