kenaz 0.3.3 → 0.3.5

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 (413) hide show
  1. package/core/.agents/skills/typesafe-ai/LICENSE +21 -21
  2. package/core/.agents/skills/typesafe-ai/SKILL.md +149 -149
  3. package/core/.claude/commands/Ansuz.md +14 -2
  4. package/core/.claude/hooks/__tests__/task-2395-project-registration.test.js +84 -0
  5. package/core/.claude/hooks/handoff-staleness.js +2 -2
  6. package/core/.claude/hooks/session-init.ps1 +49 -49
  7. package/core/.claude/hooks/turn-context.js +50 -2
  8. package/core/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -26
  9. package/core/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -97
  10. package/core/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -101
  11. package/core/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -31
  12. package/core/.claude/skills/ui-ux-pro-max/data/products.csv +96 -96
  13. package/core/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
  14. package/core/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
  15. package/core/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
  16. package/core/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
  17. package/core/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
  18. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
  19. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
  20. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
  21. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
  22. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
  23. package/core/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
  24. package/core/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
  25. package/core/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
  26. package/core/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
  27. package/core/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -68
  28. package/core/.claude/skills/ui-ux-pro-max/data/typography.csv +57 -57
  29. package/core/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
  30. package/core/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
  31. package/core/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
  32. package/core/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -253
  33. package/core/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
  34. package/core/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -106
  35. package/core/.collaboration/core/task-api.js +1 -4
  36. package/core/.formation/README.md +21 -146
  37. package/core/.formation/bifrost/codex-doctor.js +37 -2
  38. package/core/.formation/bifrost/codex-doctor.test.js +18 -0
  39. package/core/.formation/bifrost/launch.js +1 -1
  40. package/core/.formation/bifrost/openai/codex-hook-shim.js +6 -0
  41. package/core/.formation/bifrost/openai/codex-hook-shim.test.js +11 -1
  42. package/core/.formation/bifrost/setup-codex.js +3 -0
  43. package/core/.formation/spec/task_operations.parity.test.js +0 -1
  44. package/core/.formation/tests/hello.txt +2 -2
  45. package/core/.formation/tools/dashboard-models-chain.test.js +0 -6
  46. package/core/.formation/tools/dispatch-detail/cross-project-worktree.md +1 -9
  47. package/core/.formation/tools/dispatch-detail/worktree-subagent-mode.md +2 -2
  48. package/core/.formation/tools/dispatch.js +28 -549
  49. package/core/.formation/tools/mcp-allowlist.test.js +0 -1
  50. package/core/.formation/tools/post-complete/task2345.test.js +0 -5
  51. package/core/.formation/tools/pre-dispatch-snapshot.test.js +0 -100
  52. package/core/.formation/tools/prompt.js +29 -1623
  53. package/core/.formation/tools/prompt.test.js +26 -594
  54. package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -163
  55. package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -393
  56. package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -102
  57. package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -412
  58. package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -144
  59. package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -86
  60. package/core/.kenaz/muninn_episode_write.ps1 +17 -17
  61. package/core/.kenaz/specialist/field_report_watermark.json +1 -1
  62. package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
  63. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
  64. package/core/.kenaz/specialist/t1777_probe.txt +1 -1
  65. package/core/.matrix/index.matrix +5 -5
  66. package/core/.specialist/tools/__tests__/freya-cli-contract.test.js +0 -1
  67. package/core/KENAZ_CORE_VERSION +1 -1
  68. package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -355
  69. package/core/dev/JEV_INTEGRATION_PLAN.md +189 -189
  70. package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -13
  71. package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -9
  72. package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -14
  73. package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -67
  74. package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -52
  75. package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -31
  76. package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -39
  77. package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -33
  78. package/core/dev/jev-eval/README.md +99 -99
  79. package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -58
  80. package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -32
  81. package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -48
  82. package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -52
  83. package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -47
  84. package/core/dev/pipeline/tasks/.gitkeep +1 -1
  85. package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -27
  86. package/core/dev/scripts/init_git.sh +0 -0
  87. package/core/dev/scripts/init_project.sh +0 -0
  88. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -536
  89. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -1978
  90. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -140
  91. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -173
  92. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -168
  93. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -126
  94. package/core/dev/scripts/multi_agent_scheduler.sh +0 -0
  95. package/core/dev/scripts/run_once.sh +0 -0
  96. package/core/dev/scripts/setup_mcp.sh +0 -0
  97. package/core/dev/scripts/setup_rag.sh +0 -0
  98. package/core/dev/scripts/start_dev_agent.sh +0 -0
  99. package/core/dev/scripts/start_multi_agent.sh +0 -0
  100. package/core/dev/scripts/sync_task_queue.sh +0 -0
  101. package/core/plugins/kenaz/commands/Amelia.md +1 -1
  102. package/core/plugins/kenaz/commands/Ansuz.md +15 -3
  103. package/core/plugins/kenaz/scripts/kenaz-mode.js +11 -1
  104. package/core/plugins/kenaz/scripts/sync.js +6 -2
  105. package/core/plugins/kenaz/scripts/ygg.js +13 -1
  106. package/core/plugins/kenaz/skills/product_manager/SKILL.md +12 -12
  107. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -26
  108. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -97
  109. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -101
  110. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -31
  111. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +96 -96
  112. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
  113. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
  114. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
  115. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
  116. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
  117. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
  118. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
  119. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
  120. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
  121. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
  122. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
  123. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
  124. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
  125. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
  126. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -68
  127. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +57 -57
  128. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
  129. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
  130. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
  131. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -253
  132. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
  133. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -106
  134. package/lib/cli.js +11 -4
  135. package/package.json +11 -7
  136. package/vendor/install-kenaz-cli.js +18 -7
  137. package/core/.formation/tools/broadcast.js +0 -103
  138. package/core/.formation/tools/dispatch-dryrun.test.js +0 -54
  139. package/core/.formation/tools/dispatch-notifier.js +0 -84
  140. package/core/.formation/tools/formations.js +0 -1203
  141. package/core/.formation/tools/headless-branch-recovery.test.js +0 -64
  142. package/core/.formation/tools/headless-dryrun.test.js +0 -79
  143. package/core/.formation/tools/headless-v2.js +0 -659
  144. package/core/.formation/tools/headless.js +0 -1071
  145. package/core/.formation/tools/legacy-scheduler.js +0 -1994
  146. package/core/.formation/tools/logger.js +0 -273
  147. package/core/.formation/tools/memory-loader.js +0 -422
  148. package/core/.formation/tools/process-state.js +0 -33
  149. package/core/.formation/tools/remote-conflict.js +0 -98
  150. package/core/.formation/tools/runner.js +0 -1332
  151. package/core/.formation/tools/task-utils.js +0 -344
  152. package/core/.formation/tools/task2028-headless-worktree.test.js +0 -112
  153. package/core/.formation/tools/tests/headless-mcp-args-plumbing.test.js +0 -123
  154. package/core/.formation/tools/worktree-leak.test.js +0 -162
  155. package/core/k-cli/dist/alaya/run.js +0 -91
  156. package/core/k-cli/dist/core/output.js +0 -24
  157. package/core/k-cli/dist/core/process.js +0 -38
  158. package/core/k-cli/dist/core/project.js +0 -80
  159. package/core/k-cli/dist/core/spawn.js +0 -268
  160. package/core/k-cli/dist/core/types.js +0 -2
  161. package/core/k-cli/dist/dashboard/update.js +0 -53
  162. package/core/k-cli/dist/dashboard/version.js +0 -20
  163. package/core/k-cli/dist/dispatch/run.js +0 -241
  164. package/core/k-cli/dist/dispatch/status.js +0 -103
  165. package/core/k-cli/dist/index.js +0 -283
  166. package/core/k-cli/dist/project/initialize.js +0 -301
  167. package/core/k-cli/dist/project/install.js +0 -89
  168. package/core/k-cli/dist/project/status.js +0 -138
  169. package/core/k-cli/dist/specialist/list.js +0 -100
  170. package/core/k-cli/dist/specialist/run.js +0 -93
  171. package/core/k-cli/dist/task/create.js +0 -194
  172. package/core/k-cli/dist/task/delete.js +0 -98
  173. package/core/k-cli/dist/task/get.js +0 -55
  174. package/core/k-cli/dist/task/list.js +0 -58
  175. package/core/k-cli/dist/task/submit.js +0 -156
  176. package/core/k-cli/dist/ygg/run.js +0 -51
  177. package/core/k-cli/node_modules/.bin/yaml +0 -16
  178. package/core/k-cli/node_modules/.bin/yaml.cmd +0 -17
  179. package/core/k-cli/node_modules/.bin/yaml.ps1 +0 -28
  180. package/core/k-cli/node_modules/.package-lock.json +0 -23
  181. package/core/k-cli/node_modules/yaml/LICENSE +0 -13
  182. package/core/k-cli/node_modules/yaml/README.md +0 -172
  183. package/core/k-cli/node_modules/yaml/bin.mjs +0 -11
  184. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-collection.js +0 -88
  185. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-doc.js +0 -43
  186. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-node.js +0 -109
  187. package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-scalar.js +0 -86
  188. package/core/k-cli/node_modules/yaml/browser/dist/compose/composer.js +0 -217
  189. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-map.js +0 -115
  190. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +0 -198
  191. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +0 -49
  192. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-end.js +0 -37
  193. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +0 -207
  194. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +0 -223
  195. package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-props.js +0 -146
  196. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-contains-newline.js +0 -34
  197. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +0 -26
  198. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +0 -15
  199. package/core/k-cli/node_modules/yaml/browser/dist/compose/util-map-includes.js +0 -13
  200. package/core/k-cli/node_modules/yaml/browser/dist/doc/Document.js +0 -335
  201. package/core/k-cli/node_modules/yaml/browser/dist/doc/anchors.js +0 -71
  202. package/core/k-cli/node_modules/yaml/browser/dist/doc/applyReviver.js +0 -55
  203. package/core/k-cli/node_modules/yaml/browser/dist/doc/createNode.js +0 -88
  204. package/core/k-cli/node_modules/yaml/browser/dist/doc/directives.js +0 -176
  205. package/core/k-cli/node_modules/yaml/browser/dist/errors.js +0 -57
  206. package/core/k-cli/node_modules/yaml/browser/dist/index.js +0 -17
  207. package/core/k-cli/node_modules/yaml/browser/dist/log.js +0 -11
  208. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Alias.js +0 -114
  209. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Collection.js +0 -147
  210. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Node.js +0 -38
  211. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Pair.js +0 -36
  212. package/core/k-cli/node_modules/yaml/browser/dist/nodes/Scalar.js +0 -24
  213. package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLMap.js +0 -144
  214. package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +0 -113
  215. package/core/k-cli/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +0 -63
  216. package/core/k-cli/node_modules/yaml/browser/dist/nodes/identity.js +0 -36
  217. package/core/k-cli/node_modules/yaml/browser/dist/nodes/toJS.js +0 -37
  218. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-scalar.js +0 -214
  219. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-stringify.js +0 -61
  220. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-visit.js +0 -97
  221. package/core/k-cli/node_modules/yaml/browser/dist/parse/cst.js +0 -98
  222. package/core/k-cli/node_modules/yaml/browser/dist/parse/lexer.js +0 -717
  223. package/core/k-cli/node_modules/yaml/browser/dist/parse/line-counter.js +0 -39
  224. package/core/k-cli/node_modules/yaml/browser/dist/parse/parser.js +0 -967
  225. package/core/k-cli/node_modules/yaml/browser/dist/public-api.js +0 -102
  226. package/core/k-cli/node_modules/yaml/browser/dist/schema/Schema.js +0 -37
  227. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/map.js +0 -17
  228. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/null.js +0 -15
  229. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/seq.js +0 -17
  230. package/core/k-cli/node_modules/yaml/browser/dist/schema/common/string.js +0 -14
  231. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/bool.js +0 -19
  232. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/float.js +0 -43
  233. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/int.js +0 -38
  234. package/core/k-cli/node_modules/yaml/browser/dist/schema/core/schema.js +0 -23
  235. package/core/k-cli/node_modules/yaml/browser/dist/schema/json/schema.js +0 -62
  236. package/core/k-cli/node_modules/yaml/browser/dist/schema/tags.js +0 -96
  237. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +0 -58
  238. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +0 -26
  239. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +0 -46
  240. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +0 -71
  241. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +0 -64
  242. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +0 -74
  243. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +0 -78
  244. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +0 -39
  245. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +0 -93
  246. package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +0 -101
  247. package/core/k-cli/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +0 -146
  248. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringify.js +0 -129
  249. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +0 -153
  250. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyComment.js +0 -20
  251. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +0 -85
  252. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +0 -24
  253. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyPair.js +0 -150
  254. package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyString.js +0 -336
  255. package/core/k-cli/node_modules/yaml/browser/dist/util.js +0 -11
  256. package/core/k-cli/node_modules/yaml/browser/dist/visit.js +0 -233
  257. package/core/k-cli/node_modules/yaml/browser/index.js +0 -5
  258. package/core/k-cli/node_modules/yaml/browser/package.json +0 -3
  259. package/core/k-cli/node_modules/yaml/dist/cli.d.ts +0 -8
  260. package/core/k-cli/node_modules/yaml/dist/cli.mjs +0 -201
  261. package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.d.ts +0 -11
  262. package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.js +0 -90
  263. package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.d.ts +0 -7
  264. package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.js +0 -45
  265. package/core/k-cli/node_modules/yaml/dist/compose/compose-node.d.ts +0 -29
  266. package/core/k-cli/node_modules/yaml/dist/compose/compose-node.js +0 -112
  267. package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.d.ts +0 -5
  268. package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.js +0 -88
  269. package/core/k-cli/node_modules/yaml/dist/compose/composer.d.ts +0 -63
  270. package/core/k-cli/node_modules/yaml/dist/compose/composer.js +0 -222
  271. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.d.ts +0 -6
  272. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.js +0 -117
  273. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +0 -11
  274. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.js +0 -200
  275. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +0 -6
  276. package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.js +0 -51
  277. package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.d.ts +0 -6
  278. package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.js +0 -39
  279. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +0 -7
  280. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.js +0 -209
  281. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +0 -10
  282. package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.js +0 -225
  283. package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.d.ts +0 -23
  284. package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.js +0 -148
  285. package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.d.ts +0 -2
  286. package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.js +0 -36
  287. package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +0 -2
  288. package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.js +0 -28
  289. package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +0 -3
  290. package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.js +0 -17
  291. package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.d.ts +0 -4
  292. package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.js +0 -15
  293. package/core/k-cli/node_modules/yaml/dist/doc/Document.d.ts +0 -141
  294. package/core/k-cli/node_modules/yaml/dist/doc/Document.js +0 -337
  295. package/core/k-cli/node_modules/yaml/dist/doc/anchors.d.ts +0 -24
  296. package/core/k-cli/node_modules/yaml/dist/doc/anchors.js +0 -76
  297. package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.d.ts +0 -9
  298. package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.js +0 -57
  299. package/core/k-cli/node_modules/yaml/dist/doc/createNode.d.ts +0 -17
  300. package/core/k-cli/node_modules/yaml/dist/doc/createNode.js +0 -90
  301. package/core/k-cli/node_modules/yaml/dist/doc/directives.d.ts +0 -49
  302. package/core/k-cli/node_modules/yaml/dist/doc/directives.js +0 -178
  303. package/core/k-cli/node_modules/yaml/dist/errors.d.ts +0 -21
  304. package/core/k-cli/node_modules/yaml/dist/errors.js +0 -62
  305. package/core/k-cli/node_modules/yaml/dist/index.d.ts +0 -25
  306. package/core/k-cli/node_modules/yaml/dist/index.js +0 -50
  307. package/core/k-cli/node_modules/yaml/dist/log.d.ts +0 -3
  308. package/core/k-cli/node_modules/yaml/dist/log.js +0 -19
  309. package/core/k-cli/node_modules/yaml/dist/nodes/Alias.d.ts +0 -29
  310. package/core/k-cli/node_modules/yaml/dist/nodes/Alias.js +0 -116
  311. package/core/k-cli/node_modules/yaml/dist/nodes/Collection.d.ts +0 -73
  312. package/core/k-cli/node_modules/yaml/dist/nodes/Collection.js +0 -151
  313. package/core/k-cli/node_modules/yaml/dist/nodes/Node.d.ts +0 -53
  314. package/core/k-cli/node_modules/yaml/dist/nodes/Node.js +0 -40
  315. package/core/k-cli/node_modules/yaml/dist/nodes/Pair.d.ts +0 -22
  316. package/core/k-cli/node_modules/yaml/dist/nodes/Pair.js +0 -39
  317. package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.d.ts +0 -43
  318. package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.js +0 -27
  319. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.d.ts +0 -53
  320. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.js +0 -147
  321. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +0 -60
  322. package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.js +0 -115
  323. package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +0 -4
  324. package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.js +0 -65
  325. package/core/k-cli/node_modules/yaml/dist/nodes/identity.d.ts +0 -23
  326. package/core/k-cli/node_modules/yaml/dist/nodes/identity.js +0 -53
  327. package/core/k-cli/node_modules/yaml/dist/nodes/toJS.d.ts +0 -29
  328. package/core/k-cli/node_modules/yaml/dist/nodes/toJS.js +0 -39
  329. package/core/k-cli/node_modules/yaml/dist/options.d.ts +0 -350
  330. package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.d.ts +0 -64
  331. package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.js +0 -218
  332. package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.d.ts +0 -8
  333. package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.js +0 -63
  334. package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.d.ts +0 -39
  335. package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.js +0 -99
  336. package/core/k-cli/node_modules/yaml/dist/parse/cst.d.ts +0 -109
  337. package/core/k-cli/node_modules/yaml/dist/parse/cst.js +0 -112
  338. package/core/k-cli/node_modules/yaml/dist/parse/lexer.d.ts +0 -87
  339. package/core/k-cli/node_modules/yaml/dist/parse/lexer.js +0 -719
  340. package/core/k-cli/node_modules/yaml/dist/parse/line-counter.d.ts +0 -22
  341. package/core/k-cli/node_modules/yaml/dist/parse/line-counter.js +0 -41
  342. package/core/k-cli/node_modules/yaml/dist/parse/parser.d.ts +0 -84
  343. package/core/k-cli/node_modules/yaml/dist/parse/parser.js +0 -972
  344. package/core/k-cli/node_modules/yaml/dist/public-api.d.ts +0 -44
  345. package/core/k-cli/node_modules/yaml/dist/public-api.js +0 -107
  346. package/core/k-cli/node_modules/yaml/dist/schema/Schema.d.ts +0 -17
  347. package/core/k-cli/node_modules/yaml/dist/schema/Schema.js +0 -39
  348. package/core/k-cli/node_modules/yaml/dist/schema/common/map.d.ts +0 -2
  349. package/core/k-cli/node_modules/yaml/dist/schema/common/map.js +0 -19
  350. package/core/k-cli/node_modules/yaml/dist/schema/common/null.d.ts +0 -4
  351. package/core/k-cli/node_modules/yaml/dist/schema/common/null.js +0 -17
  352. package/core/k-cli/node_modules/yaml/dist/schema/common/seq.d.ts +0 -2
  353. package/core/k-cli/node_modules/yaml/dist/schema/common/seq.js +0 -19
  354. package/core/k-cli/node_modules/yaml/dist/schema/common/string.d.ts +0 -2
  355. package/core/k-cli/node_modules/yaml/dist/schema/common/string.js +0 -16
  356. package/core/k-cli/node_modules/yaml/dist/schema/core/bool.d.ts +0 -4
  357. package/core/k-cli/node_modules/yaml/dist/schema/core/bool.js +0 -21
  358. package/core/k-cli/node_modules/yaml/dist/schema/core/float.d.ts +0 -4
  359. package/core/k-cli/node_modules/yaml/dist/schema/core/float.js +0 -47
  360. package/core/k-cli/node_modules/yaml/dist/schema/core/int.d.ts +0 -4
  361. package/core/k-cli/node_modules/yaml/dist/schema/core/int.js +0 -42
  362. package/core/k-cli/node_modules/yaml/dist/schema/core/schema.d.ts +0 -1
  363. package/core/k-cli/node_modules/yaml/dist/schema/core/schema.js +0 -25
  364. package/core/k-cli/node_modules/yaml/dist/schema/json/schema.d.ts +0 -2
  365. package/core/k-cli/node_modules/yaml/dist/schema/json/schema.js +0 -64
  366. package/core/k-cli/node_modules/yaml/dist/schema/json-schema.d.ts +0 -69
  367. package/core/k-cli/node_modules/yaml/dist/schema/tags.d.ts +0 -48
  368. package/core/k-cli/node_modules/yaml/dist/schema/tags.js +0 -99
  369. package/core/k-cli/node_modules/yaml/dist/schema/types.d.ts +0 -92
  370. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +0 -2
  371. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.js +0 -70
  372. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +0 -7
  373. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.js +0 -29
  374. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +0 -4
  375. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.js +0 -50
  376. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +0 -5
  377. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.js +0 -76
  378. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +0 -9
  379. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.js +0 -68
  380. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +0 -22
  381. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.js +0 -77
  382. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +0 -10
  383. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +0 -82
  384. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +0 -1
  385. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.js +0 -41
  386. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +0 -28
  387. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.js +0 -96
  388. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +0 -6
  389. package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +0 -105
  390. package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +0 -34
  391. package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.js +0 -151
  392. package/core/k-cli/node_modules/yaml/dist/stringify/stringify.d.ts +0 -21
  393. package/core/k-cli/node_modules/yaml/dist/stringify/stringify.js +0 -132
  394. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +0 -17
  395. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.js +0 -155
  396. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.d.ts +0 -10
  397. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.js +0 -24
  398. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +0 -4
  399. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.js +0 -87
  400. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +0 -2
  401. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.js +0 -26
  402. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.d.ts +0 -3
  403. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.js +0 -152
  404. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.d.ts +0 -9
  405. package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.js +0 -338
  406. package/core/k-cli/node_modules/yaml/dist/test-events.d.ts +0 -4
  407. package/core/k-cli/node_modules/yaml/dist/test-events.js +0 -134
  408. package/core/k-cli/node_modules/yaml/dist/util.d.ts +0 -16
  409. package/core/k-cli/node_modules/yaml/dist/util.js +0 -28
  410. package/core/k-cli/node_modules/yaml/dist/visit.d.ts +0 -102
  411. package/core/k-cli/node_modules/yaml/dist/visit.js +0 -236
  412. package/core/k-cli/node_modules/yaml/package.json +0 -97
  413. package/core/k-cli/node_modules/yaml/util.js +0 -2
@@ -1,1203 +0,0 @@
1
- 'use strict';
2
- /**
3
- * Formations
4
- *
5
- * Formation launcher functions extracted from headless.js (TASK_890).
6
- * Contains all launchXxx functions and worktree management helpers.
7
- *
8
- * Exports: launchHuginnThink, launchUnit, launchSolo, launchSquad,
9
- * launchLegion, launchLegacy, createWorktree, removeWorktree,
10
- * cleanupAllWorktrees
11
- *
12
- * Internal: squadSynthesis, waitAll, printResults, hasSuccess
13
- */
14
-
15
- const fs = require('fs');
16
- const path = require('path');
17
- const { execSync } = require('child_process');
18
- const {
19
- buildSquadSynthesisPrompt, buildSquadDebatePrompts,
20
- buildLegionLeadPrompt, HEADLESS_PREFIX,
21
- } = require('./prompt');
22
- const { runHeadless, runLegionLead, runner: formationRunner } = require('./runner');
23
- const {
24
- ROOT, getLogDir,
25
- createWorktree: createTaskWorktree, removeWorktree: removeTaskWorktree,
26
- ensureWorktreeDirIgnored, resolveWorktreeDecision,
27
- dispatchLockPath, tryAcquireDispatchLock,
28
- buildWorktreeGitAddCommand,
29
- unlinkNodeModulesBestEffort,
30
- } = require('./utils');
31
- const { postComplete } = require('./post-complete');
32
- const {
33
- waitForDispatchSlot, registerDispatch, unregisterDispatch,
34
- logAnalytics, _decrementParallelCount, isProcessAlive,
35
- } = require('./dispatch-registry');
36
- const { enqueueDispatchNotification } = require('./dispatch-notifier');
37
- const { _activeWorktrees, trackChild } = require('./process-state');
38
- const { broadcastStatus, broadcastStatusSync, syncCommit } = require('./broadcast');
39
- const { checkAndDispatchFix } = require('./task-utils');
40
-
41
- let canary;
42
- try { canary = require('../../.specialist/tools/canary'); } catch { canary = null; }
43
-
44
- function buildDispatchCompletionEvent({
45
- taskId,
46
- sessionId,
47
- originSessionId,
48
- formation,
49
- skill,
50
- projectPath,
51
- logPath,
52
- finalCode,
53
- postCompleteResult,
54
- }) {
55
- if (!taskId || !originSessionId) return null;
56
-
57
- const result = postCompleteResult || null;
58
- const git = result?.git || null;
59
- let status = 'failed';
60
- let summary = `Task ${taskId} ended with exit code ${finalCode ?? 0}.`;
61
-
62
- if ((finalCode ?? 0) !== 0) {
63
- status = 'failed';
64
- summary = `Task ${taskId} agent execution failed with exit code ${finalCode}.`;
65
- } else if (git?.mergeConflict) {
66
- status = 'merge_conflict';
67
- summary = `Task ${taskId} hit a merge conflict and requires manual resolution.`;
68
- } else if (git?.missingTaskBranch || git?.scopePathsMissing) {
69
- status = 'failed';
70
- summary = `Task ${taskId} did not produce a mergeable task branch.`;
71
- } else if (result?.taskCompleted || git?.merged || (git?.noChanges && (git?.memoryOnly || git?.noGitRepo))) {
72
- status = 'completed';
73
- summary = `Task ${taskId} completed successfully.`;
74
- } else if (git && !git.merged && !git.noChanges && !git.noGitRepo) {
75
- status = 'manual_resolution';
76
- summary = `Task ${taskId} finished agent execution but still needs manual follow-up.`;
77
- } else if (git?.noChanges) {
78
- status = 'completed';
79
- summary = `Task ${taskId} completed without code changes.`;
80
- }
81
-
82
- return {
83
- originSessionId,
84
- taskId,
85
- status,
86
- summary,
87
- formation,
88
- skill,
89
- dispatchSessionId: sessionId,
90
- projectPath: projectPath || null,
91
- logPath: logPath || null,
92
- metadata: {
93
- merged: git?.merged === true,
94
- taskCompleted: result?.taskCompleted === true,
95
- userBranch: git?.userBranch || git?.originalBranch || null,
96
- regressionFixTask: git?.regressionFixTask || null,
97
- acceptanceFixTasks: git?.acceptanceFixTasks || [],
98
- integrationFixTask: git?.integrationFixTask || null,
99
- worktreePath: git?.conflictWorktreePath || null,
100
- conflictSummary: git?.conflictSummary || null,
101
- },
102
- };
103
- }
104
-
105
- function emitDispatchCompletion(event) {
106
- if (!event) return;
107
- try {
108
- enqueueDispatchNotification(event, ROOT);
109
- } catch {}
110
- }
111
-
112
- // ─────────────────────────────────────────
113
- // Worktree Management (squad member worktrees)
114
- // ─────────────────────────────────────────
115
-
116
- /**
117
- * Create an isolated git worktree for a squad member.
118
- * Returns the absolute worktree path, or null on failure (graceful fallback to shared dir).
119
- * Includes .git/index.lock detection and retry with exponential backoff for Windows
120
- * parallel-load resilience (ETIMEDOUT from competing git processes).
121
- */
122
- function createWorktree(sessionId, memberId, projectRoot) {
123
- const worktreesDir = path.join(projectRoot, '.formation', 'worktrees');
124
- const worktreePath = path.join(worktreesDir, `${sessionId}-${memberId}`);
125
- const ts_start = Date.now();
126
-
127
- if (!fs.existsSync(worktreesDir)) {
128
- try { fs.mkdirSync(worktreesDir, { recursive: true }); }
129
- catch (err) {
130
- console.warn(`⚠️ Failed to create worktrees dir for ${memberId}: ${err.message}`);
131
- return null;
132
- }
133
- }
134
-
135
- // Wait for .git/index.lock to clear (Windows parallel git lock contention)
136
- const indexLock = path.join(projectRoot, '.git', 'index.lock');
137
- const waitForIndexLock = (maxMs) => {
138
- const deadline = Date.now() + maxMs;
139
- while (fs.existsSync(indexLock) && Date.now() < deadline) {
140
- const w = Date.now() + 200; while (Date.now() < w) {}
141
- }
142
- if (fs.existsSync(indexLock)) {
143
- console.warn(` ⚠️ .git/index.lock still held after ${maxMs}ms for ${memberId} — proceeding anyway`);
144
- }
145
- };
146
-
147
- waitForIndexLock(15000);
148
-
149
- // Retry with exponential backoff: 3 retries at 2s, 4s, 8s
150
- const retryDelays = [2000, 4000, 8000];
151
- let lastErr;
152
- for (let attempt = 0; attempt <= retryDelays.length; attempt++) {
153
- if (attempt > 0) waitForIndexLock(10000);
154
- try {
155
- // --detach avoids branch naming conflicts; HEAD starts at same commit as main tree
156
- execSync(`git worktree add --detach "${worktreePath}"`, {
157
- cwd: projectRoot, encoding: 'utf8', timeout: 30000, windowsHide: true, stdio: 'pipe',
158
- });
159
- const duration = Date.now() - ts_start;
160
- _activeWorktrees.set(worktreePath, projectRoot);
161
- console.log(` 🌿 Worktree created: ${path.relative(projectRoot, worktreePath)} (${duration}ms, attempt ${attempt + 1})`);
162
- return worktreePath;
163
- } catch (err) {
164
- lastErr = err;
165
- if (attempt < retryDelays.length) {
166
- const delay = retryDelays[attempt];
167
- console.warn(` ⚠️ Worktree attempt ${attempt + 1} failed for ${memberId}: ${(err.message || '').split('\n')[0]}`);
168
- console.warn(` Retrying in ${delay / 1000}s...`);
169
- const w = Date.now() + delay; while (Date.now() < w) {}
170
- }
171
- }
172
- }
173
-
174
- const duration = Date.now() - ts_start;
175
- console.warn(`⚠️ Worktree creation failed for ${memberId} after ${retryDelays.length + 1} attempts (${duration}ms): ${lastErr.message}`);
176
- console.warn(` Falling back to shared directory.`);
177
- return null;
178
- }
179
-
180
- /**
181
- * Remove a single worktree and unregister it from _activeWorktrees.
182
- */
183
- function removeWorktree(worktreePath, projectRoot) {
184
- // TASK_1844/TASK_2030: MUST run before any `git worktree remove` -- Git for
185
- // Windows recurses through a node_modules junction/symlink on removal and
186
- // empties the link target (the main checkout's node_modules).
187
- unlinkNodeModulesBestEffort(worktreePath);
188
- try {
189
- execSync(`git worktree remove --force "${worktreePath}"`, {
190
- cwd: projectRoot,
191
- encoding: 'utf8',
192
- timeout: 30000,
193
- windowsHide: true,
194
- stdio: 'pipe',
195
- });
196
- console.log(` 🧹 Worktree removed: ${path.relative(projectRoot, worktreePath)}`);
197
- } catch (err) {
198
- console.warn(`⚠️ Worktree removal failed for ${worktreePath}: ${err.message}`);
199
- }
200
- _activeWorktrees.delete(worktreePath);
201
- }
202
-
203
- /**
204
- * Clean up all registered worktrees.
205
- * Called after squad synthesis completes (and from exit handler).
206
- */
207
- function cleanupAllWorktrees() {
208
- if (_activeWorktrees.size === 0) return;
209
- console.log(`\n🧹 Cleaning up ${_activeWorktrees.size} worktree(s)...`);
210
- for (const [worktreePath, projectRoot] of [..._activeWorktrees]) {
211
- removeWorktree(worktreePath, projectRoot);
212
- }
213
- }
214
-
215
- // ─────────────────────────────────────────
216
- // 共用工具函數
217
- // ─────────────────────────────────────────
218
-
219
- function waitAll(children) {
220
- return Promise.all(
221
- children.map(child =>
222
- new Promise((resolve) => {
223
- let resolved = false;
224
- const done = (code) => {
225
- if (resolved) return;
226
- resolved = true;
227
- resolve({
228
- memberId: child._formation.memberId,
229
- exitCode: code ?? 0,
230
- logPath: child._formation.logPath
231
- });
232
- };
233
- child.on('close', done);
234
- child.on('formation:done', done);
235
- })
236
- )
237
- );
238
- }
239
-
240
- function printResults(label, results, total) {
241
- console.log(`\n⚔️ ${label}: ${results.length}/${total}`);
242
- for (const r of results) {
243
- const status = r.exitCode === 0 ? '✅' : '❌';
244
- console.log(` ${status} [${r.memberId}] exit ${r.exitCode} → ${r.logPath}`);
245
- }
246
- }
247
-
248
- function hasSuccess(results) {
249
- const successCount = results.filter(r => r.exitCode === 0).length;
250
- if (successCount === 0) {
251
- console.log('\n⚠️ All members failed. Stopping.');
252
- return false;
253
- }
254
- if (successCount < results.length) {
255
- console.log(`\n⚠️ ${results.length - successCount} member(s) failed.`);
256
- }
257
- return true;
258
- }
259
-
260
- // ═════════════════════════════════════════
261
- // Huginn Think:Prefrontal 思考引擎(無任務、無 skill YAML)
262
- // ═════════════════════════════════════════
263
-
264
- function launchHuginnThink({ trigger, focus, query, sessionId, projectPath, dryRun, inProcess, depth, model = null }) {
265
- // Read skill workflow from .claude/skills/huginn/SKILL.md (single source of truth)
266
- const skillMdPath = path.join(ROOT, '.claude', 'skills', 'huginn', 'SKILL.md');
267
- let skillMd = '';
268
- try { skillMd = fs.readFileSync(skillMdPath, 'utf-8'); } catch { /* not found — workflow still in prompt */ }
269
-
270
- const ctxLines = [
271
- `trigger: ${trigger || 'manual'}`,
272
- `project_path: ${projectPath || ROOT}`,
273
- ];
274
- if (focus) ctxLines.push(`focus: ${focus}`);
275
- if (query) ctxLines.push(`query: ${query}`);
276
- const context = ctxLines.join('\n');
277
-
278
- const depthFlag = depth ? ` --depth=${depth}` : '';
279
- const prompt = `${HEADLESS_PREFIX}
280
-
281
- ## Role
282
- You are Huginn ᚺᚢ, the Prefrontal Thinking Engine of KenazAI.
283
- Execute: node .specialist/tools/session.js prompt Huginn --mode=assembled${depthFlag}
284
- Follow the assembled persona output before proceeding with the workflow below.
285
-
286
- ## Context
287
- ${context}
288
-
289
- ## Workflow
290
- ${skillMd || '(See .specialist/Huginn/core.yaml for the full workflow)'}
291
- `;
292
-
293
- console.log(`ᚺᚢ Formation Headless Launch (Huginn Think)`);
294
- console.log(` Trigger: ${trigger || 'manual'}`);
295
- console.log(` Session: ${sessionId}`);
296
- if (focus) console.log(` Focus: ${focus}`);
297
- if (query) console.log(` Query: ${query}`);
298
- if (projectPath) console.log(` Project: ${projectPath}`);
299
- console.log('');
300
-
301
- if (dryRun) {
302
- console.log('--- HUGINN THINK DRY RUN ---');
303
- console.log(prompt);
304
- console.log('--- END ---');
305
- return null;
306
- }
307
-
308
- // Broadcast online
309
- broadcastStatus(projectPath, { is_online: true, current_task: 'huginn-think', touching_files: [] });
310
- if (!inProcess) {
311
- const handleSignal = (code) => {
312
- broadcastStatusSync(projectPath, { is_online: false, current_task: null, touching_files: [] });
313
- process.exit(code);
314
- };
315
- process.on('SIGINT', () => handleSignal(130));
316
- process.on('SIGTERM', () => handleSignal(143));
317
- }
318
-
319
- const child = runHeadless({ prompt, formation: 'huginn', skill: 'huginn-think', sessionId, projectPath, model });
320
- trackChild(child);
321
-
322
- const logPath = child._formation?.logPath;
323
- registerDispatch({ sessionId, logPath, taskId: null, formation: 'huginn', projectPath });
324
-
325
- if (!inProcess) {
326
- process.on('exit', () => unregisterDispatch(sessionId));
327
- }
328
-
329
- let cleanedUp = false;
330
- const onDone = (code) => {
331
- if (cleanedUp) return;
332
- cleanedUp = true;
333
- unregisterDispatch(sessionId);
334
- const finalCode = child._formationExitCode ?? code ?? 0;
335
-
336
- if (code === 0) {
337
- console.log('\nᚺᚢ Huginn thinking session complete — proposals written, no post-complete needed');
338
- } else {
339
- console.log(`\n⚠️ Huginn think exited with code ${code}`);
340
- }
341
-
342
- broadcastStatusSync(projectPath, { is_online: false, current_task: null, touching_files: [] });
343
- setTimeout(() => process.exit(code || 0), 500);
344
- };
345
-
346
- child.on('close', onDone);
347
- child.on('formation:done', onDone);
348
-
349
- return inProcess ? { child, sessionId, logPath, taskId: null } : child;
350
- }
351
-
352
- // ═════════════════════════════════════════
353
- // Unit:單一 agent 直接執行
354
- // ═════════════════════════════════════════
355
-
356
- function launchUnit(opts) {
357
- const { rawPrompt, skill, sessionId, projectPath, taskId, taskScopePaths, role, rigor, skipPostComplete, skipTests, inProcess,
358
- worktreePath = null, countFile = null, countLock = null, originSessionId = null,
359
- dispatchModel = null,
360
- // TASK_1929 (Agent Hub M2): see headless.js CLI + runner.js spawnClaude/
361
- // spawnBifrost. Defaults are "no override" — zero behavior change for
362
- // every dispatch path that doesn't pass these (which is all of them
363
- // except agent_hub/launch.rs's `hub` skill launches).
364
- mcpConfigExtra = [], allowedToolsOverride = null } = opts;
365
- const ts_start = Date.now();
366
-
367
- let finalPrompt = HEADLESS_PREFIX + rawPrompt;
368
- try {
369
- const { block } = canary.generate({
370
- session: sessionId,
371
- specialist: 'unit',
372
- task: taskId || 'none',
373
- scope: (taskScopePaths || []).join(','),
374
- }, projectPath);
375
- finalPrompt = block + '\n\n' + finalPrompt;
376
- } catch (err) {
377
- console.warn(`⚠️ Canary injection skipped: ${err.message}`);
378
- }
379
-
380
- // 等待 concurrency 插槽 BEFORE spawning claude.
381
- // CRITICAL: Atomics.wait blocks the Node.js event loop. If called AFTER
382
- // spawnClaude, child.stdin.write(prompt) I/O flush never executes → claude
383
- // gets no prompt → hangs → zero output for 60 min (deadlock root cause).
384
- waitForDispatchSlot('unit');
385
-
386
- const child = runHeadless({
387
- prompt: finalPrompt,
388
- formation: 'unit',
389
- skill,
390
- sessionId,
391
- projectPath,
392
- rigor, // passed to runner.js for activity timeout scaling (high=10min, medium=7min, low=5min)
393
- worktreeDir: worktreePath, // null = run in projectPath/ROOT (serial fallback or non-task)
394
- model: dispatchModel, // Quick Actions → Default Models → Dispatch agent
395
- // TASK_1929: extraMcpConfigs=[] and no allowedTools key when unset —
396
- // spawnClaude's own defaults (empty array / its built-in allowedTools
397
- // list) then apply exactly as before this task.
398
- extraMcpConfigs: mcpConfigExtra,
399
- ...(allowedToolsOverride ? { allowedTools: allowedToolsOverride } : {}),
400
- });
401
- trackChild(child);
402
-
403
- // 註冊 dispatch(Dashboard Agent Monitor 用)
404
- const logPath = child._formation?.logPath;
405
- registerDispatch({
406
- sessionId,
407
- logPath,
408
- taskId,
409
- formation: 'unit',
410
- projectPath,
411
- worktreePath,
412
- originSessionId,
413
- originTransport: originSessionId ? 'mcp' : null,
414
- originNotificationMethod: originSessionId ? 'notifications/kenaz/dispatch_complete' : null,
415
- pid: child.pid || null,
416
- });
417
-
418
- // 保底清理:父進程被 kill 時確保 unregister(防止 .active-dispatches.json 殘留)
419
- // inProcess 模式跳過 — 不能在 MCP server 上註冊 process.on('exit')
420
- if (!inProcess) {
421
- process.on('exit', () => unregisterDispatch(sessionId));
422
- }
423
-
424
- // 閉環:agent 完成後自動執行 post-complete + Yggdrasil cleanup
425
- // Guard: 確保只執行一次(close + formation:done 可能都觸發)
426
- let cleanedUp = false;
427
- const onDone = (code) => {
428
- if (cleanedUp) return;
429
- cleanedUp = true;
430
- unregisterDispatch(sessionId);
431
- const finalCode = child._formationExitCode ?? code ?? 0;
432
-
433
- const willPostComplete = Boolean(
434
- taskId && taskId.startsWith('TASK_') && !skipPostComplete && finalCode === 0
435
- );
436
-
437
- // Worktree cleanup on agent failure or read-only/skip-post-complete sessions.
438
- // On success with real task: let postComplete handle cleanup via worktree merge path
439
- // to avoid double-decrement of the parallel semaphore.
440
- if (worktreePath && !willPostComplete) {
441
- // Rescue commit: if the agent wrote code but exited non-zero (e.g. cargo test
442
- // timeout), save uncommitted changes to the task branch before deleting the
443
- // worktree. Without this, all agent work is silently lost on non-zero exit.
444
- if (taskId && finalCode !== 0) {
445
- try {
446
- const { execSync: rescueExec } = require('child_process');
447
- const status = rescueExec('git status --porcelain', {
448
- cwd: worktreePath, encoding: 'utf8', timeout: 10000, windowsHide: true, stdio: 'pipe',
449
- }).trim();
450
- if (status.length > 0) {
451
- // TASK_1957: exclude telemetry/daemon-noise paths (same list as the
452
- // dispatch prompt's commit step, buildWorktreeGitAddCommand) so a
453
- // rescue commit doesn't sweep canary/session/progress-note/dispatch
454
- // files into the task branch either.
455
- rescueExec(buildWorktreeGitAddCommand(), { cwd: worktreePath, timeout: 15000, windowsHide: true, stdio: 'pipe' });
456
- rescueExec(`git commit -m "[${taskId}] rescue commit (agent exit code ${finalCode})"`, {
457
- cwd: worktreePath, timeout: 15000, windowsHide: true, stdio: 'pipe',
458
- });
459
- console.log(` 🆘 Rescue commit saved for ${taskId} (exit ${finalCode}, ${status.split('\n').length} files)`);
460
- }
461
- } catch (e) {
462
- console.log(` ⚠️ Rescue commit failed: ${e.message}`);
463
- }
464
- }
465
- try { removeTaskWorktree(projectPath || ROOT, worktreePath); } catch {}
466
- if (countFile && countLock) _decrementParallelCount(countFile, countLock);
467
- }
468
-
469
- if (taskId && taskId.startsWith('TASK_') && !skipPostComplete) {
470
- if (finalCode === 0) {
471
- console.log(`\n⚔️ Auto Post-Complete: ${taskId}`);
472
- // Pass worktreePath so postComplete uses worktree merge path (merge task branch + cleanup).
473
- // postComplete handles worktree removal + semaphore decrement via cleanupWorktree.
474
- let postCompleteResult = null;
475
- try {
476
- postCompleteResult = postComplete({ taskId, projectPath, skipTests, logPath, formation: 'unit', skill, worktreePath: worktreePath || undefined });
477
- // Note: postComplete internally calls commit-sync after successful git commit
478
- } catch (e) {
479
- console.error(`❌ Post-complete failed: ${e.message}`);
480
- }
481
- emitDispatchCompletion(buildDispatchCompletionEvent({
482
- taskId,
483
- sessionId,
484
- originSessionId,
485
- formation: 'unit',
486
- skill,
487
- projectPath,
488
- logPath,
489
- finalCode,
490
- postCompleteResult,
491
- }));
492
- // Branch recovery guarantee: postComplete's _gitMergeFromWorktree finally block
493
- // may silently fail to checkout userBranch (throws:false). Explicitly recover here
494
- // so main repo never stays stuck on a task/ branch after worktree dispatch.
495
- if (worktreePath) {
496
- try {
497
- const { execSync: brEs } = require('child_process');
498
- const { ROOT: brROOT } = require('./utils');
499
- const gitRoot = projectPath || brROOT;
500
- const dispatchFile = path.join(gitRoot, '.formation', '.dispatch-branch');
501
- const dispatchBranch = require('fs').existsSync(dispatchFile)
502
- ? require('fs').readFileSync(dispatchFile, 'utf8').trim()
503
- : null;
504
- let targetBranch = (dispatchBranch && !dispatchBranch.startsWith('task/')) ? dispatchBranch : null;
505
- if (!targetBranch) {
506
- // Dynamic current-branch resolution — correct for ANY user/branch.
507
- // Previously `git config user.name`, which assumed the working branch
508
- // was named after the git user and produced phantom branches (e.g.
509
- // user.name "xyz9516" vs branch "xyz951610"), silently falling through
510
- // to hardcoded `main`. Branch must NEVER be hardcoded: production builds
511
- // run as different users on differently-named branches. Mirrors
512
- // post-complete.js resolveUserBranch().
513
- try {
514
- const cur = brEs('git rev-parse --abbrev-ref HEAD', {
515
- cwd: gitRoot, encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: 'pipe',
516
- }).trim();
517
- if (cur && cur !== 'HEAD' && !cur.startsWith('task/')) targetBranch = cur;
518
- } catch {}
519
- }
520
- if (!targetBranch) {
521
- try {
522
- targetBranch = brEs('git rev-parse --verify main', {
523
- cwd: gitRoot, encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: 'pipe',
524
- }) ? 'main' : null;
525
- } catch {}
526
- }
527
- if (!targetBranch) targetBranch = 'master';
528
- const currentBranch = brEs('git rev-parse --abbrev-ref HEAD', {
529
- cwd: gitRoot, encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: 'pipe',
530
- }).trim();
531
- if (currentBranch.startsWith('task/')) {
532
- brEs(`git checkout ${targetBranch}`, {
533
- cwd: gitRoot, encoding: 'utf8', timeout: 10000, windowsHide: true, stdio: 'pipe',
534
- });
535
- console.log(` ✅ Branch recovery: ${currentBranch} → ${targetBranch}`);
536
- }
537
- } catch { /* non-fatal — best-effort recovery */ }
538
- }
539
- // 閉環: 檢查 FIX queue,自動 re-dispatch
540
- checkAndDispatchFix({ formation: 'unit', skill, projectPath });
541
- } else {
542
- console.log(`\n⚠️ Agent exited with code ${finalCode} — skipping post-complete`);
543
- emitDispatchCompletion(buildDispatchCompletionEvent({
544
- taskId,
545
- sessionId,
546
- originSessionId,
547
- formation: 'unit',
548
- skill,
549
- projectPath,
550
- logPath,
551
- finalCode,
552
- postCompleteResult: null,
553
- }));
554
- }
555
- } else if (finalCode === 0) {
556
- // skipPostComplete or no TASK_ — still sync commits if agent succeeded
557
- syncCommit(taskId, projectPath);
558
- } else {
559
- emitDispatchCompletion(buildDispatchCompletionEvent({
560
- taskId,
561
- sessionId,
562
- originSessionId,
563
- formation: 'squad',
564
- skill,
565
- projectPath,
566
- logPath: child._formation?.logPath,
567
- finalCode: code,
568
- postCompleteResult: null,
569
- }));
570
- }
571
-
572
- // TASK_1785: snapshot lifecycle follows the dispatch — remove AFTER both
573
- // consumers (runner leak check during 'close', postComplete rescue above)
574
- // have had their chance to read it. Best-effort, never throws.
575
- try {
576
- const { removeDispatchDirtySnapshot } = require('./git-salvage');
577
- removeDispatchDirtySnapshot(projectPath || ROOT, { sessionId });
578
- if (projectPath && path.resolve(projectPath) !== path.resolve(ROOT)) {
579
- removeDispatchDirtySnapshot(ROOT, { sessionId });
580
- }
581
- } catch { /* non-fatal */ }
582
-
583
- // Analytics: record dispatch outcome
584
- logAnalytics({ ts_start, taskId, skill, formation: 'unit', role, projectPath, exit_code: finalCode });
585
-
586
- // Yggdrasil: mark offline + clear task (synchronous HTTP to ensure delivery before exit)
587
- broadcastStatusSync(projectPath, { is_online: false, current_task: null, touching_files: [] });
588
-
589
- // Exit the process after cleanup — signal handlers and timers keep the event loop alive,
590
- // so Node.js won't exit naturally. Small delay to let sync HTTP complete.
591
- setTimeout(() => process.exit(finalCode || 0), 500);
592
- };
593
-
594
- child.on('close', onDone);
595
- // Fallback: formation:done fires when result is received, even if process lingers on Windows
596
- child.on('formation:done', onDone);
597
-
598
- return inProcess ? { child, sessionId, logPath, taskId } : child;
599
- }
600
-
601
- // ═════════════════════════════════════════
602
- // Solo:單一 agent 多視角分析
603
- // ═════════════════════════════════════════
604
-
605
- function launchSolo(opts) {
606
- const { rawPrompt, skill, sessionId, projectPath, taskId, taskScopePaths, role, skipPostComplete, skipTests, inProcess, originSessionId = null } = opts;
607
- const ts_start = Date.now();
608
-
609
- let finalPrompt = HEADLESS_PREFIX + rawPrompt;
610
- try {
611
- const { block } = canary.generate({
612
- session: sessionId,
613
- specialist: 'solo',
614
- task: taskId || 'none',
615
- scope: (taskScopePaths || []).join(','),
616
- }, projectPath);
617
- finalPrompt = block + '\n\n' + finalPrompt;
618
- } catch (err) {
619
- console.warn(`⚠️ Canary injection skipped: ${err.message}`);
620
- }
621
-
622
- // Wait for slot BEFORE spawn — Atomics.wait blocks event loop (see launchUnit comment)
623
- waitForDispatchSlot('solo');
624
-
625
- const child = runHeadless({
626
- prompt: finalPrompt,
627
- formation: 'solo',
628
- skill,
629
- sessionId,
630
- projectPath
631
- });
632
- trackChild(child);
633
-
634
- const logPath = child._formation?.logPath;
635
- registerDispatch({
636
- sessionId,
637
- logPath,
638
- taskId,
639
- formation: 'solo',
640
- projectPath,
641
- originSessionId,
642
- originTransport: originSessionId ? 'mcp' : null,
643
- originNotificationMethod: originSessionId ? 'notifications/kenaz/dispatch_complete' : null,
644
- pid: child.pid || null,
645
- });
646
-
647
- // 保底清理:父進程被 kill 時確保 unregister
648
- if (!inProcess) {
649
- process.on('exit', () => unregisterDispatch(sessionId));
650
- }
651
-
652
- // 閉環:agent 完成後自動執行 post-complete + Yggdrasil cleanup
653
- // Guard: 確保只執行一次
654
- let cleanedUp = false;
655
- const onDone = (code) => {
656
- if (cleanedUp) return;
657
- cleanedUp = true;
658
- unregisterDispatch(sessionId);
659
- const finalCode = child._formationExitCode ?? code ?? 0;
660
-
661
- if (taskId && taskId.startsWith('TASK_') && !skipPostComplete) {
662
- if (finalCode === 0) {
663
- console.log(`\n⚔️ Auto Post-Complete: ${taskId}`);
664
- // ensureTaskComplete removed — post-complete.js marks done AFTER merge
665
- let postCompleteResult = null;
666
- try {
667
- postCompleteResult = postComplete({ taskId, projectPath, skipTests, logPath, formation: 'solo', skill });
668
- } catch (e) {
669
- console.error(`❌ Post-complete failed: ${e.message}`);
670
- }
671
- emitDispatchCompletion(buildDispatchCompletionEvent({
672
- taskId,
673
- sessionId,
674
- originSessionId,
675
- formation: 'solo',
676
- skill,
677
- projectPath,
678
- logPath,
679
- finalCode,
680
- postCompleteResult,
681
- }));
682
- checkAndDispatchFix({ formation: 'solo', skill, projectPath });
683
- } else {
684
- console.log(`\n⚠️ Agent exited with code ${finalCode} — skipping post-complete`);
685
- emitDispatchCompletion(buildDispatchCompletionEvent({
686
- taskId,
687
- sessionId,
688
- originSessionId,
689
- formation: 'solo',
690
- skill,
691
- projectPath,
692
- logPath,
693
- finalCode,
694
- postCompleteResult: null,
695
- }));
696
- }
697
- } else if (finalCode === 0) {
698
- syncCommit(taskId, projectPath);
699
- }
700
-
701
- // Analytics: record dispatch outcome
702
- logAnalytics({ ts_start, taskId, skill, formation: 'solo', role, projectPath, exit_code: finalCode });
703
-
704
- broadcastStatusSync(projectPath, { is_online: false, current_task: null, touching_files: [] });
705
-
706
- setTimeout(() => process.exit(finalCode || 0), 500);
707
- };
708
-
709
- child.on('close', onDone);
710
- child.on('formation:done', onDone);
711
-
712
- return inProcess ? { child, sessionId, logPath, taskId } : child;
713
- }
714
-
715
- // ═════════════════════════════════════════
716
- // Squad:多 agent 並行 + 辯論 + 整合
717
- // ═════════════════════════════════════════
718
-
719
- function launchSquad(opts) {
720
- const { rawPrompt, skill, sessionId, skillDef, projectPath, taskId, taskScopePaths, role, skipPostComplete, skipTests, originSessionId = null } = opts;
721
- const reportDir = `squad-${skill}`;
722
-
723
- // 替換 {session} 佔位符 + inject canary per member
724
- const memberPrompts = rawPrompt.map(m => {
725
- let prompt = m.prompt.replace(/\{session\}/g, reportDir);
726
- try {
727
- const { block } = canary.generate({
728
- session: sessionId,
729
- specialist: `squad-${m.memberId}`,
730
- task: taskId || 'none',
731
- scope: (taskScopePaths || []).join(','),
732
- }, projectPath);
733
- prompt = block + '\n\n' + prompt;
734
- } catch (err) {
735
- console.warn(`⚠️ Canary injection skipped for ${m.memberId}: ${err.message}`);
736
- }
737
- return { ...m, prompt };
738
- });
739
-
740
- const debateEnabled = skillDef.debate?.enabled;
741
- const totalRounds = debateEnabled ? 2 : 1;
742
-
743
- console.log(`⚔️ Squad Launch: ${memberPrompts.length} members × ${totalRounds} round(s)\n`);
744
-
745
- // ── Round 1: 獨立分析(並行)──
746
- console.log(`── Round 1: Independent Analysis ──\n`);
747
- const projectRoot = projectPath || ROOT;
748
- const r1Children = memberPrompts.map(m => {
749
- // Create isolated worktree for each squad member; fallback to shared dir on failure
750
- const worktreeDir = createWorktree(sessionId, m.memberId, projectRoot) || undefined;
751
- const child = runHeadless({
752
- prompt: HEADLESS_PREFIX + m.prompt,
753
- formation: 'squad',
754
- skill: m.skill || skill,
755
- sessionId,
756
- memberId: m.memberId,
757
- silent: true,
758
- projectPath,
759
- worktreeDir,
760
- });
761
- trackChild(child);
762
- return child;
763
- });
764
-
765
- waitAll(r1Children).then(r1Results => {
766
- printResults('Round 1 Complete', r1Results, memberPrompts.length);
767
- if (!hasSuccess(r1Results)) return;
768
-
769
- // ── Round 2: 交叉辯論(如果啟用)──
770
- if (debateEnabled) {
771
- console.log(`\n── Round 2: Cross-Debate ──\n`);
772
- const debatePrompts = buildSquadDebatePrompts(skillDef, reportDir);
773
-
774
- const r2Children = debatePrompts.map(m => {
775
- const child = runHeadless({
776
- prompt: HEADLESS_PREFIX + m.prompt,
777
- formation: 'squad',
778
- skill: m.skill || skill,
779
- sessionId,
780
- memberId: `${m.memberId}-r2`,
781
- silent: true,
782
- projectPath
783
- });
784
- trackChild(child);
785
- return child;
786
- });
787
-
788
- waitAll(r2Children).then(r2Results => {
789
- printResults('Round 2 Complete', r2Results, debatePrompts.length);
790
- if (!hasSuccess(r2Results)) return;
791
- squadSynthesis({ skill, sessionId, skillDef, reportDir, projectPath, taskId, role, skipPostComplete, skipTests, originSessionId });
792
- });
793
- } else {
794
- squadSynthesis({ skill, sessionId, skillDef, reportDir, projectPath, taskId, role, skipPostComplete, skipTests, originSessionId });
795
- }
796
- });
797
-
798
- return r1Children;
799
- }
800
-
801
- /**
802
- * Squad Synthesis:讀取所有報告,產出整合報告
803
- */
804
- function squadSynthesis(opts) {
805
- const { skill, sessionId, skillDef, reportDir, projectPath, taskId, role, skipPostComplete, skipTests, originSessionId = null } = opts;
806
- if (!skillDef?.merge || !reportDir) return;
807
- const ts_start = Date.now();
808
-
809
- console.log(`\n── Synthesis ──\n`);
810
- const synthPrompt = buildSquadSynthesisPrompt(skillDef, reportDir);
811
-
812
- // Wait for slot BEFORE spawn — Atomics.wait blocks event loop (see launchUnit comment)
813
- waitForDispatchSlot('squad');
814
-
815
- const child = runHeadless({
816
- prompt: HEADLESS_PREFIX + synthPrompt,
817
- formation: 'squad',
818
- skill: `${skill}-synthesis`,
819
- sessionId,
820
- memberId: 'synthesis',
821
- projectPath
822
- });
823
- trackChild(child);
824
-
825
- registerDispatch({
826
- sessionId,
827
- logPath: child._formation?.logPath,
828
- taskId,
829
- formation: 'squad',
830
- projectPath,
831
- originSessionId,
832
- originTransport: originSessionId ? 'mcp' : null,
833
- originNotificationMethod: originSessionId ? 'notifications/kenaz/dispatch_complete' : null,
834
- pid: child.pid || null,
835
- });
836
-
837
- // 保底清理:父進程被 kill 時確保 unregister
838
- process.on('exit', () => unregisterDispatch(sessionId));
839
-
840
- // Guard: 確保只執行一次
841
- let cleanedUp = false;
842
- const onDone = (code) => {
843
- if (cleanedUp) return;
844
- cleanedUp = true;
845
- unregisterDispatch(sessionId);
846
- // Cleanup worktrees created for Round 1 squad members
847
- cleanupAllWorktrees();
848
- const status = code === 0 ? '✅' : '❌';
849
- console.log(`\n⚔️ Synthesis ${status} (exit ${code})`);
850
- if (code === 0) {
851
- console.log(` Report: .formation/reports/${reportDir}/synthesis.md`);
852
-
853
- // 閉環:squad synthesis 完成後自動執行 post-complete
854
- if (taskId && taskId.startsWith('TASK_') && !skipPostComplete) {
855
- console.log(`\n⚔️ Auto Post-Complete: ${taskId}`);
856
- // ensureTaskComplete removed — post-complete.js marks done AFTER merge
857
- try {
858
- var postCompleteResult = null;
859
- postCompleteResult = postComplete({ taskId, projectPath, skipTests, logPath: child._formation?.logPath, formation: 'squad', skill });
860
- } catch (e) {
861
- console.error(`❌ Post-complete failed: ${e.message}`);
862
- }
863
- emitDispatchCompletion(buildDispatchCompletionEvent({
864
- taskId,
865
- sessionId,
866
- originSessionId,
867
- formation: 'squad',
868
- skill,
869
- projectPath,
870
- logPath: child._formation?.logPath,
871
- finalCode: code,
872
- postCompleteResult,
873
- }));
874
- checkAndDispatchFix({ formation: 'squad', skill, projectPath });
875
- } else {
876
- syncCommit(taskId, projectPath);
877
- }
878
- }
879
-
880
- // Analytics: record dispatch outcome
881
- logAnalytics({ ts_start, taskId, skill, formation: 'squad', role, projectPath, exit_code: code });
882
-
883
- // Yggdrasil: mark offline (synchronous to ensure delivery before exit)
884
- broadcastStatusSync(projectPath, { is_online: false, current_task: null, touching_files: [] });
885
-
886
- setTimeout(() => process.exit(code || 0), 500);
887
- };
888
-
889
- child.on('close', onDone);
890
- child.on('formation:done', onDone);
891
- }
892
-
893
- // ═════════════════════════════════════════
894
- // Legion:Agent Teams 啟動
895
- // ═════════════════════════════════════════
896
-
897
- function launchLegion(opts) {
898
- const { rawPrompt: legionConfig, skill, sessionId, projectPath, taskId, role } = opts;
899
- const ts_start = Date.now();
900
-
901
- let leadPrompt = buildLegionLeadPrompt(legionConfig);
902
-
903
- // Inject canary block for Legion Lead (long-lived agent needs context integrity)
904
- try {
905
- const resolvedTaskId = opts.taskId
906
- || (legionConfig.lead?.prompt?.match(/\*\*Task ID\*\*:\s*(TASK_\d+)/) || [])[1]
907
- || 'none';
908
- const { block } = canary.generate({
909
- session: sessionId,
910
- specialist: 'legion-lead',
911
- task: resolvedTaskId,
912
- scope: (opts.taskScopePaths || []).join(','),
913
- }, projectPath);
914
- leadPrompt = block + '\n\n' + leadPrompt;
915
- } catch (err) {
916
- console.warn(`⚠️ Canary injection skipped: ${err.message}`);
917
- }
918
-
919
- console.log(`⚔️ Legion Launch: ${legionConfig.members.length} members planned\n`);
920
-
921
- // FormationRunner facade: single entry point over launchLegion→runLegionLead→spawn*
922
- // Reduces Legion dispatch boundary crossings from ~9 to 4-5 steps (TASK_1559).
923
- // Behavior-preserving: delegates to runLegionLead internally, no semantic change.
924
- const child = formationRunner.run('legion-lead', {
925
- prompt: leadPrompt,
926
- formation: 'legion',
927
- skill,
928
- sessionId,
929
- memberId: 'lead',
930
- projectPath
931
- });
932
- trackChild(child);
933
-
934
- // Analytics: record dispatch outcome when legion lead exits
935
- let legionAnalyticsDone = false;
936
- const onLegionDone = (code) => {
937
- if (legionAnalyticsDone) return;
938
- legionAnalyticsDone = true;
939
- logAnalytics({ ts_start, taskId, skill, formation: 'legion', role, projectPath, exit_code: code ?? 0 });
940
- };
941
- child.on('close', onLegionDone);
942
- child.on('formation:done', onLegionDone);
943
-
944
- return child;
945
- }
946
-
947
- // ═════════════════════════════════════════
948
- // Legacy:連續排程器(Rust SingleAgentScheduler 對等實作)
949
- // ═════════════════════════════════════════
950
-
951
- function launchLegacy(opts) {
952
- const { skillDef, concurrency, projectPath } = opts;
953
-
954
- // ── Pre-check: is a scheduler already running for this project? ──
955
- // Uses heartbeat-based zombie detection (matching legacy-scheduler.js guard)
956
- const HEARTBEAT_STALE_MS = 5 * 60 * 1000;
957
- const logDir = projectPath
958
- ? path.join(getLogDir(projectPath), 'legacy')
959
- : path.join(ROOT, '.formation', 'logs', 'legacy');
960
- const pidFile = path.join(logDir, 'scheduler.pid');
961
- if (fs.existsSync(pidFile)) {
962
- try {
963
- const existing = JSON.parse(fs.readFileSync(pidFile, 'utf8'));
964
- if (existing.pid && existing.pid !== process.pid) {
965
- let isAlive = false;
966
- try { process.kill(existing.pid, 0); isAlive = true; } catch {}
967
- if (isAlive) {
968
- const heartbeat = existing.heartbeat ? new Date(existing.heartbeat).getTime() : 0;
969
- const stale = Date.now() - heartbeat > HEARTBEAT_STALE_MS;
970
- if (!stale) {
971
- console.log(`ℹ️ Scheduler already running (PID ${existing.pid}). Skipping duplicate launch.`);
972
- return null;
973
- }
974
- console.log(`⚠️ Stale scheduler detected (PID ${existing.pid}). Killing zombie...`);
975
- try {
976
- if (process.platform === 'win32') {
977
- require('child_process').execSync(`taskkill /F /PID ${existing.pid}`, { stdio: 'ignore', timeout: 5000, windowsHide: true });
978
- } else {
979
- process.kill(existing.pid, 'SIGTERM');
980
- }
981
- } catch {}
982
- try { fs.unlinkSync(pidFile); } catch {}
983
- }
984
- }
985
- } catch {}
986
- }
987
-
988
- const legacyScheduler = require('./legacy-scheduler');
989
-
990
- // 從 skillDef.config 提取 CLI 可覆蓋的參數
991
- const overrides = {};
992
- if (skillDef.config) {
993
- if (skillDef.config.initial_delay_secs != null) overrides.initialDelay = skillDef.config.initial_delay_secs;
994
- if (skillDef.config.interval_minutes != null) overrides.interval = skillDef.config.interval_minutes;
995
- if (skillDef.config.dev_timeout_minutes != null) overrides.devTimeout = skillDef.config.dev_timeout_minutes;
996
- if (skillDef.config.enable_self_enhancement != null) overrides.selfEnhancement = skillDef.config.enable_self_enhancement;
997
- }
998
-
999
- // [merge: main + {git_user}] instance-id (main) + concurrency/projectPath ({git_user})
1000
- // Pass through --instance-id from Dashboard (Rust) CLI args
1001
- const instanceIdArg = process.argv.find(a => a.startsWith('--instance-id='));
1002
- if (instanceIdArg) overrides.instanceId = instanceIdArg.split('=')[1];
1003
-
1004
- // CLI --concurrency 覆蓋 YAML 設定
1005
- if (concurrency != null) overrides.concurrency = concurrency;
1006
-
1007
- // 傳遞子專案路徑
1008
- if (projectPath) overrides.projectPath = projectPath;
1009
-
1010
- // legacy scheduler 是自己的 async 迴圈,不回傳 child process
1011
- // 必須 catch rejection,否則 headless 模式下 unhandledRejection 會靜默殺掉進程
1012
- legacyScheduler.start(overrides).catch(err => {
1013
- console.error(`❌ Legacy Scheduler fatal error: ${err.message}`);
1014
- console.error(err.stack);
1015
- process.exit(1);
1016
- });
1017
- return null;
1018
- }
1019
-
1020
- // ─────────────────────────────────────────
1021
- // Unit Worktree Isolation Setup
1022
- // ─────────────────────────────────────────
1023
-
1024
- /**
1025
- * Set up an isolated git worktree for a unit/solo task dispatch.
1026
- * Implements parallel semaphore (up to MAX_PARALLEL=5) with serial dispatch-lock fallback.
1027
- * Registers process.on('exit'/'SIGTERM'/'SIGINT') cleanup handlers.
1028
- * TASK_1775: use_worktree:false in the dispatched project's .kenaz/project.yaml
1029
- * skips worktree entirely and routes straight to serial dispatch-lock mode.
1030
- *
1031
- * Extracted from launch() in headless.js (TASK_891) to slim the orchestrator.
1032
- *
1033
- * @param {object} opts
1034
- * @param {string} opts.projectRoot - Absolute project root path
1035
- * @param {string|null} opts.projectPath - Sub-project path (may differ from projectRoot)
1036
- * @param {string} opts.taskId - Task ID (e.g. TASK_891)
1037
- * @param {string} opts.sessionId - Session ID for lock identification
1038
- * @param {boolean|null} [opts.useWorktreeOverride] - TASK_1833 dispatch-time toggle
1039
- * (true/false forces the mode; null falls through to env/project.yaml/default)
1040
- * @returns {{ worktreePath: string|null, countFile: string|null, countLock: string|null }}
1041
- */
1042
- function setupUnitWorktree({ projectRoot, projectPath, taskId, sessionId, useWorktreeOverride = null }) {
1043
- // TASK_1785 (DEFECT-C/D): snapshot the dirty paths of the dispatched project's
1044
- // working tree BEFORE any worktree/lock work. The runner leak detector and the
1045
- // post-complete rescue scan use this to consider ONLY paths that became dirty
1046
- // during the dispatch window — pre-existing user WIP is never swept/salvaged.
1047
- // Best-effort: a capture failure degrades those consumers to legacy behavior.
1048
- try {
1049
- const { captureDispatchDirtySnapshot } = require('./git-salvage');
1050
- const snap = captureDispatchDirtySnapshot(projectRoot, { sessionId, taskId });
1051
- if (snap) {
1052
- console.log(` 📸 [TASK_1785] Pre-dispatch dirty snapshot: ${snap.dirtyPaths.length} pre-existing dirty path(s) recorded`);
1053
- } else {
1054
- console.warn(` ⚠️ [TASK_1785] Pre-dispatch dirty snapshot capture failed — leak/rescue will use legacy semantics`);
1055
- }
1056
- // The runner's leak detector audits the CORE root working tree (mainRoot=ROOT)
1057
- // even for sub-project dispatches — snapshot core too when it differs.
1058
- if (path.resolve(projectRoot) !== path.resolve(ROOT)) {
1059
- captureDispatchDirtySnapshot(ROOT, { sessionId, taskId });
1060
- }
1061
- } catch (e) {
1062
- console.warn(` ⚠️ [TASK_1785] Pre-dispatch dirty snapshot error (non-fatal): ${e.message}`);
1063
- }
1064
-
1065
- const worktreeName = `task_${taskId.toLowerCase()}`;
1066
- const absoluteWorktreePath = path.join(projectRoot, '.formation', 'worktrees', worktreeName);
1067
- // KENAZ_PARALLEL_COUNT_FILE: test-only override so tests never touch the real semaphore.
1068
- const countFile = process.env.KENAZ_PARALLEL_COUNT_FILE || path.join(ROOT, '.formation', '.parallel-count');
1069
- const countLock = process.env.KENAZ_PARALLEL_COUNT_FILE
1070
- ? process.env.KENAZ_PARALLEL_COUNT_FILE + '-lock'
1071
- : path.join(ROOT, '.formation', '.parallel-count-lock');
1072
- const MAX_PARALLEL = 5;
1073
- const SEMAPHORE_POLL_MS = 5000;
1074
- const SEMAPHORE_WAIT_TIMEOUT_MS = 1800000; // 30 min
1075
-
1076
- // TASK_1775: explicit worktree opt-out — read the DISPATCHED project's
1077
- // .kenaz/project.yaml (projectPath), not core's. use_worktree:false routes
1078
- // straight to serial dispatch-lock mode (huge repos / single-editor-bound
1079
- // projects where a worktree checkout is architecturally wrong).
1080
- // TASK_1833: decision now flows through utils.resolveWorktreeDecision — the
1081
- // SAME function the sub-agent dispatch path uses (dispatch-time override >
1082
- // env FORMATION_USE_WORKTREE > project.yaml > default true).
1083
- const wtDecision = resolveWorktreeDecision(projectPath || projectRoot, useWorktreeOverride);
1084
- const worktreeEnabled = wtDecision.useWorktree;
1085
- if (!worktreeEnabled) {
1086
- console.log(` ⏭️ Worktree opt-out (${wtDecision.source}) — serial dispatch-lock mode`);
1087
- } else {
1088
- // Ensure .formation/worktrees/ is in .gitignore (idempotent)
1089
- ensureWorktreeDirIgnored(projectRoot);
1090
- }
1091
-
1092
- // Semaphore: wait for a parallel slot (max MAX_PARALLEL concurrent worktree dispatches)
1093
- let slotAcquired = false;
1094
- const semaphoreDeadline = Date.now() + SEMAPHORE_WAIT_TIMEOUT_MS;
1095
- while (worktreeEnabled && Date.now() < semaphoreDeadline) {
1096
- let lockFd = null;
1097
- try {
1098
- lockFd = fs.openSync(countLock, 'wx'); // O_EXCL atomic create
1099
- try {
1100
- const count = fs.existsSync(countFile) ? (parseInt(fs.readFileSync(countFile, 'utf-8').trim()) || 0) : 0;
1101
- if (count < MAX_PARALLEL) {
1102
- fs.writeFileSync(countFile, String(count + 1));
1103
- slotAcquired = true;
1104
- } else {
1105
- console.log(`⏳ Max parallel dispatches reached (${count}/${MAX_PARALLEL}), waiting...`);
1106
- }
1107
- } finally {
1108
- fs.closeSync(lockFd);
1109
- try { fs.unlinkSync(countLock); } catch {}
1110
- }
1111
- if (slotAcquired) break;
1112
- } catch {
1113
- /* count-lock held — retry */
1114
- }
1115
- const wait = Date.now() + SEMAPHORE_POLL_MS;
1116
- while (Date.now() < wait) { /* busy wait */ }
1117
- }
1118
-
1119
- if (slotAcquired) {
1120
- // Attempt to create an isolated git worktree for this task
1121
- const branchName = `task/${taskId.toLowerCase()}`;
1122
- try {
1123
- createTaskWorktree(projectRoot, absoluteWorktreePath, branchName);
1124
- console.log(` 🌿 Unit worktree: .formation/worktrees/${worktreeName}`);
1125
-
1126
- // Register cleanup on exit (worktree + semaphore release)
1127
- const releaseWorktree = () => {
1128
- try { removeTaskWorktree(projectRoot, absoluteWorktreePath); } catch {}
1129
- _decrementParallelCount(countFile, countLock);
1130
- };
1131
- process.on('exit', releaseWorktree);
1132
- process.on('SIGTERM', () => { releaseWorktree(); process.exit(1); });
1133
- process.on('SIGINT', () => { releaseWorktree(); process.exit(1); });
1134
-
1135
- return { worktreePath: absoluteWorktreePath, countFile, countLock };
1136
- } catch (err) {
1137
- // Worktree creation failed — release the semaphore slot and fall back to serial lock
1138
- console.warn(`⚠️ Worktree creation failed: ${err.message}`);
1139
- console.warn(` Falling back to serial dispatch-lock mode.`);
1140
- _decrementParallelCount(countFile, countLock);
1141
- }
1142
- }
1143
-
1144
- // Serial dispatch-lock fallback: used when worktree is disabled, creation fails, or slot not acquired
1145
- // TASK_1833: lock path + acquisition/stale-breaking are shared with the
1146
- // sub-agent dispatch path (dispatch.js serial_lock mode) via
1147
- // utils.dispatchLockPath / tryAcquireDispatchLock — Formation and sub-agent
1148
- // dispatches against the same project serialize against EACH OTHER.
1149
- const lockFile = dispatchLockPath(projectPath);
1150
- const LOCK_WAIT_TIMEOUT_MS = 1800000; // 30 minutes max wait
1151
- const LOCK_POLL_MS = 5000;
1152
- const lockDeadline = Date.now() + LOCK_WAIT_TIMEOUT_MS;
1153
-
1154
- while (Date.now() < lockDeadline) {
1155
- const acq = tryAcquireDispatchLock(lockFile, { sessionId, taskId, pid: process.pid });
1156
- if (acq.acquired) {
1157
- if (acq.broke) {
1158
- console.log(` ⚠️ Broke ${acq.brokeReason} dispatch lock from ${acq.broke.taskId || acq.broke.sessionId || '<corrupt>'}`);
1159
- }
1160
- break; // lock acquired
1161
- }
1162
- if (acq.error) break; // unexpected error — proceed without lock (legacy behavior)
1163
- if (acq.holder) {
1164
- console.log(`⏳ Waiting for dispatch lock (held by ${acq.holder.taskId || acq.holder.sessionId})...`);
1165
- }
1166
- const wait = Date.now() + LOCK_POLL_MS;
1167
- while (Date.now() < wait) { /* busy wait */ }
1168
- }
1169
-
1170
- // TASK_1391: retry on EBUSY (Windows AV/watcher race). Silent on ENOENT.
1171
- const releaseLock = () => {
1172
- for (let i = 0; i < 3; i++) {
1173
- try { fs.unlinkSync(lockFile); return; }
1174
- catch (e) {
1175
- if (e.code === 'ENOENT') return;
1176
- if (e.code === 'EBUSY' && i < 2) {
1177
- const wait = Date.now() + 100;
1178
- while (Date.now() < wait) {}
1179
- continue;
1180
- }
1181
- return;
1182
- }
1183
- }
1184
- };
1185
- process.on('exit', releaseLock);
1186
- process.on('SIGTERM', () => { releaseLock(); process.exit(1); });
1187
- process.on('SIGINT', () => { releaseLock(); process.exit(1); });
1188
-
1189
- return { worktreePath: null, countFile: null, countLock: null };
1190
- }
1191
-
1192
- module.exports = {
1193
- launchHuginnThink,
1194
- launchUnit,
1195
- launchSolo,
1196
- launchSquad,
1197
- launchLegion,
1198
- launchLegacy,
1199
- createWorktree,
1200
- removeWorktree,
1201
- cleanupAllWorktrees,
1202
- setupUnitWorktree,
1203
- };