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,1071 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Formation Headless Launcher — Orchestrator (L5)
4
- *
5
- * 無頭模式啟動器:由 Formation Lead 配好任務後直接呼叫。
6
- * subagent 不會問問題,直接執行。
7
- *
8
- * ┌─────────────────────────────────────────────────────────────────┐
9
- * │ Module Dependency Graph (L3–L5 layer) │
10
- * │ │
11
- * │ headless.js (L5 orchestrator) │
12
- * │ ├── formations.js — all launchXxx + setupUnitWorktree │
13
- * │ ├── task-utils.js — task helpers + verifyTaskInProject │
14
- * │ ├── broadcast.js — Supabase status broadcasting │
15
- * │ ├── dispatch-registry.js— zombie pruning + parallel slots │
16
- * │ ├── process-state.js — _activeChildren, _activeWorktrees │
17
- * │ ├── prompt.js (L3) — prompt assembly + HEADLESS_PREFIX │
18
- * │ ├── runner.js (L4) — runHeadless / killProcessTree │
19
- * │ ├── loader.js — skill + task YAML loading │
20
- * │ ├── utils.js — ROOT, generateSessionId, git ops │
21
- * │ └── post-complete.js — (used by formations.js, not here) │
22
- * └─────────────────────────────────────────────────────────────────┘
23
- *
24
- * 5 種陣型(implemented in formations.js, dispatched here):
25
- * launchUnit — 單一 agent 直接執行
26
- * launchSolo — 單一 agent 多視角分析
27
- * launchSquad — 多 agent 並行 + 辯論 + 整合
28
- * launchLegion — Agent Teams(TeamCreate / SendMessage)
29
- * launchLegacy — 連續排程器(Rust SingleAgentScheduler 對等實作)
30
- *
31
- * Usage:
32
- * node .formation/tools/headless.js --skill=do --task="修正按鈕顏色"
33
- * node .formation/tools/headless.js --formation=solo --skill=strategy --task="評估快取方案"
34
- * node .formation/tools/headless.js --formation=squad --skill=blitz --task=TASK_201
35
- * node .formation/tools/headless.js --formation=legion --skill=forge --task=TASK_202
36
- */
37
-
38
- const fs = require('fs');
39
- const path = require('path');
40
- const { execSync } = require('child_process');
41
- const { loadSkill, loadTask } = require('./loader');
42
- const {
43
- buildPrompt, buildSquadSynthesisPrompt, buildSquadDebatePrompts,
44
- buildLegionLeadPrompt, HEADLESS_PREFIX,
45
- } = require('./prompt');
46
- const { runSingle, runHeadless, killProcessTree } = require('./runner');
47
- const {
48
- generateSessionId, ROOT, LOG_DIR, getLogDir, ensureGitReady, dispatchLockPath, dispatchLockStaleness,
49
- unlinkNodeModulesBestEffort, loadDashboardModels,
50
- } = require('./utils');
51
- const { pruneZombieDispatches, cleanupStaleLocks, isProcessAlive, readActiveDispatches, writeActiveDispatches } = require('./dispatch-registry');
52
- const { _activeChildren, _activeWorktrees } = require('./process-state');
53
- const formations = require('./formations');
54
-
55
- // ─────────────────────────────────────────
56
- // NOTE: ensureTaskComplete was REMOVED.
57
- // Task must NOT be marked done before git merge completes.
58
- // post-complete.js handles task completion internally:
59
- // - git merge success → task → done (line ~2247 in post-complete.js)
60
- // - git merge failure → task stays in active
61
- // This ensures the user branch always has the code before task is closed.
62
- // ─────────────────────────────────────────
63
-
64
- process.on('exit', () => {
65
- for (const child of _activeChildren) {
66
- try { killProcessTree(child); } catch {}
67
- }
68
- // Cleanup dangling worktrees (process.on('exit') must be sync — use execSync)
69
- for (const [worktreePath, projectRoot] of _activeWorktrees) {
70
- // TASK_1844/TASK_2030: MUST run before any `git worktree remove` -- Git for
71
- // Windows recurses through a node_modules junction/symlink on removal and
72
- // empties the link target (the main checkout's node_modules). Sync — this
73
- // handler runs inside process.on('exit').
74
- try { unlinkNodeModulesBestEffort(worktreePath); } catch {}
75
- try {
76
- execSync(`git worktree remove --force "${worktreePath}"`, {
77
- cwd: projectRoot, encoding: 'utf8', timeout: 10000, windowsHide: true, stdio: 'pipe',
78
- });
79
- } catch {}
80
- }
81
- });
82
-
83
- const { broadcastStatus, broadcastStatusSync } = require('./broadcast');
84
- const {
85
- FIX_QUEUE_FILE, checkAndDispatchFix, checkTaskDependencies,
86
- recoverStaleTasks, findNextTask, verifyTaskInProject,
87
- } = require('./task-utils');
88
-
89
- // TASK_2243: recovery extracted so tests can run it against throwaway repos.
90
- // Kenaz dispatch branches are exactly task/task_<digits> (dispatch.js, formations.js,
91
- // headless-v2.js, git-merge.js all build `task/${taskId.toLowerCase()}`). Any other
92
- // task/* branch (a user's task/600-foo) is NOT ours and must never be moved.
93
- const DISPATCH_BRANCH_RE = /^task\/task_(\d+)$/i;
94
-
95
- // Conservative by design: plain `git checkout` only -- no -f, no stash. A dirty
96
- // tracked tree, a live dispatch lock for the same card, or any git failure ABORTS
97
- // the recovery and leaves the branch and the edits untouched.
98
- function recoverStaleDispatchBranch(gitRoot, opts = {}) {
99
- const es = (cmd) => String(execSync(cmd, { cwd: gitRoot, encoding: 'utf8', timeout: 10000, windowsHide: true, stdio: 'pipe' }));
100
- const log = opts.log || console.log;
101
- const currentBranch = opts.currentBranch || es('git rev-parse --abbrev-ref HEAD').trim();
102
- const m = DISPATCH_BRANCH_RE.exec(currentBranch);
103
- if (!m) {
104
- log(` ℹ️ Pre-dispatch: ${currentBranch} is not a Kenaz dispatch branch, no recovery`);
105
- return { recovered: false, reason: 'not-dispatch-branch' };
106
- }
107
- // Cheap live-dispatch signal: the project dispatch lock names the card it serves.
108
- try {
109
- const holder = JSON.parse(fs.readFileSync(opts.lockFile || dispatchLockPath(opts.projectPath || null), 'utf8'));
110
- const { stale, pidDead } = dispatchLockStaleness(holder);
111
- const holderId = String((holder && holder.taskId) || '').replace(/\D/g, '');
112
- if (!stale && !pidDead && holderId === m[1]) {
113
- log(` ⚠️ Pre-dispatch: ${currentBranch} is held by a live dispatch, recovery skipped`);
114
- return { recovered: false, reason: 'live-dispatch' };
115
- }
116
- } catch { /* no/corrupt lock file -- no live-dispatch signal */ }
117
- try {
118
- if (es('git status --porcelain --untracked-files=no').trim()) {
119
- log(` ⚠️ Pre-dispatch: ${currentBranch} has uncommitted tracked changes, recovery ABORTED (staying on branch, work untouched)`);
120
- return { recovered: false, reason: 'dirty-tree' };
121
- }
122
- const targetBranch = opts.targetBranch || resolveDispatchTargetBranch(gitRoot);
123
- log(` ⚠️ Pre-dispatch: stuck on ${currentBranch}, recovering to ${targetBranch}`);
124
- es(`git checkout ${targetBranch}`);
125
- log(` ✅ Pre-dispatch: recovered to ${targetBranch}`);
126
- return { recovered: true, reason: 'recovered' };
127
- } catch (e) {
128
- log(` ⚠️ Pre-dispatch: recovery ABORTED (${String((e && e.message) || e).split('\n')[0]}); staying on ${currentBranch}, work untouched`);
129
- return { recovered: false, reason: 'checkout-failed' };
130
- }
131
- }
132
-
133
- function resolveDispatchTargetBranch(gitRoot) {
134
- try {
135
- const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
136
- cwd: gitRoot, encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: 'pipe',
137
- }).trim();
138
- if (currentBranch && !currentBranch.startsWith('task/')) return currentBranch;
139
- } catch {}
140
-
141
- // If HEAD is a task/* branch (e.g. dispatch invoked from inside a worktree),
142
- // ask git for the main worktree's branch instead of guessing from user.name.
143
- // `git worktree list --porcelain` first stanza is always the main worktree.
144
- try {
145
- const worktreeList = execSync('git worktree list --porcelain', {
146
- cwd: gitRoot, encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: 'pipe',
147
- });
148
- for (const line of worktreeList.split(/\r?\n/)) {
149
- const m = line.match(/^branch refs\/heads\/(.+)$/);
150
- if (m && !m[1].startsWith('task/')) return m[1];
151
- }
152
- } catch {}
153
-
154
- try {
155
- const dispatchFile = path.join(gitRoot, '.formation', '.dispatch-branch');
156
- if (fs.existsSync(dispatchFile)) {
157
- const savedBranch = fs.readFileSync(dispatchFile, 'utf8').trim();
158
- if (savedBranch && !savedBranch.startsWith('task/')) return savedBranch;
159
- }
160
- } catch {}
161
-
162
- return 'main';
163
- }
164
-
165
- function persistDispatchBranch(branchName, projectPath) {
166
- if (!branchName) return;
167
- try {
168
- fs.writeFileSync(path.join(ROOT, '.formation', '.dispatch-branch'), branchName);
169
- if (projectPath && path.resolve(projectPath) !== path.resolve(ROOT)) {
170
- const projFormation = path.join(projectPath, '.formation');
171
- if (!fs.existsSync(projFormation)) fs.mkdirSync(projFormation, { recursive: true });
172
- fs.writeFileSync(path.join(projFormation, '.dispatch-branch'), branchName);
173
- }
174
- } catch {}
175
- }
176
-
177
- function resolveLaunchBifrostAdapter(explicitAdapter) {
178
- const normalizedExplicit = explicitAdapter?.trim();
179
- if (normalizedExplicit) return { adapter: normalizedExplicit, source: 'dispatch' };
180
-
181
- const envAdapter = process.env.BIFROST_ADAPTER?.trim();
182
- if (envAdapter) return { adapter: envAdapter, source: 'env' };
183
-
184
- if (process.env.CODEX_THREAD_ID) {
185
- return { adapter: 'openai', source: 'codex_thread' };
186
- }
187
-
188
- try {
189
- const signalPath = path.join(ROOT, '.formation', '.bifrost-active');
190
- if (fs.existsSync(signalPath)) {
191
- const signal = JSON.parse(fs.readFileSync(signalPath, 'utf8'));
192
- // Self-heal: validate signal.pid is alive before honoring. A stale signal
193
- // (process crashed without cleanup, force-kill, etc.) silently mis-routes
194
- // every subsequent dispatch to the wrong adapter — observed 8-day silent
195
- // failure on 2026-05-01 where pid=5700 from 2026-04-23 forced openai.
196
- if (signal.pid != null && !isProcessAlive(signal.pid)) {
197
- try { fs.unlinkSync(signalPath); } catch {}
198
- // fall through to adapter:none — caller will use native Claude CLI
199
- } else {
200
- const signalAdapter = signal.adapter?.trim();
201
- if (signalAdapter) return { adapter: signalAdapter, source: 'signal' };
202
- }
203
- }
204
- } catch {}
205
-
206
- return { adapter: null, source: 'none' };
207
- }
208
-
209
- function attachBifrostAdapterCleanup(result, restoreEnv) {
210
- const targets = [];
211
- if (result?.child?.on) {
212
- targets.push(result.child);
213
- } else if (Array.isArray(result)) {
214
- for (const child of result) {
215
- if (child?.on) targets.push(child);
216
- }
217
- } else if (result?.on) {
218
- targets.push(result);
219
- }
220
-
221
- if (targets.length === 0) {
222
- restoreEnv();
223
- return result;
224
- }
225
-
226
- let pending = targets.length;
227
- let restored = false;
228
- const finish = () => {
229
- if (restored) return;
230
- pending -= 1;
231
- if (pending > 0) return;
232
- restored = true;
233
- restoreEnv();
234
- };
235
-
236
- for (const target of targets) {
237
- target.once('close', finish);
238
- target.once('formation:done', finish);
239
- }
240
-
241
- return result;
242
- }
243
-
244
- // ─────────────────────────────────────────
245
- // 主調度
246
- // ─────────────────────────────────────────
247
-
248
- function launch(options) {
249
- const {
250
- formation = 'unit',
251
- skill = 'do',
252
- task,
253
- dryRun = false,
254
- mode,
255
- concurrency,
256
- projectPath = null,
257
- skipPostComplete = false,
258
- skipTests = false,
259
- inProcess = false,
260
- trigger = null,
261
- focus = null,
262
- query = null,
263
- originSessionId = null,
264
- bifrostAdapter = null,
265
- // Dashboard-driven model override (Quick Actions → Default Models →
266
- // Dispatch agent). When set, takes precedence over per-skill
267
- // model_override and over the parent session inheritance pattern.
268
- // Read by kenaz_dispatch backend route from .kenaz/dashboard-models.json.
269
- dispatchModel = null,
270
- depth = 'kernel', // Inert: forwarded as promptOpts.depth, but prompt.js buildPrompt never reads it.
271
- // Dispatch agents get a skill brief (skill + principles + task), never kernel.yaml or elixir.
272
- // TASK_1929 (Agent Hub M2): see headless.js CLI section + runner.js
273
- // spawnClaude / spawnBifrost for how these two flow into the actual
274
- // child process. Both default to "no override" so every pre-existing
275
- // caller of launch() (nothing sets these) behaves identically.
276
- mcpConfigExtra = [],
277
- allowedToolsOverride = null,
278
- } = options;
279
-
280
- const sessionId = process.env.KENAZ_SESSION_ID?.trim() || generateSessionId();
281
- const previousAdapter = process.env.BIFROST_ADAPTER;
282
- const adapterSelection = resolveLaunchBifrostAdapter(bifrostAdapter);
283
- if (adapterSelection.adapter) {
284
- process.env.BIFROST_ADAPTER = adapterSelection.adapter;
285
- console.log(` Bifrost adapter: ${adapterSelection.adapter} (source=${adapterSelection.source})`);
286
- }
287
-
288
- // 0.-1 Bifröst auto-detect: if launch.js wrote .bifrost-active signal, route through Codex relay.
289
- // Signal file is created by launch.js on start and deleted on cleanup — reliable across all shells.
290
- if (false && !process.env.BIFROST_ADAPTER) {
291
- try {
292
- const signalPath = path.join(ROOT, '.formation', '.bifrost-active');
293
- if (fs.existsSync(signalPath)) {
294
- const signal = JSON.parse(fs.readFileSync(signalPath, 'utf8'));
295
- if (signal.adapter) {
296
- process.env.BIFROST_ADAPTER = signal.adapter;
297
- console.log(` 🌈 Bifröst: active session detected (${signal.adapter}) — routing agents through relay`);
298
- }
299
- }
300
- } catch { /* non-fatal — default to Claude */ }
301
- }
302
-
303
- // 0. Cleanup: kill orphaned claude.exe processes + recover stale tasks
304
- try {
305
- const { cleanOrphanedClaude } = require('../../.specialist/tools/session');
306
- const orphanResult = cleanOrphanedClaude();
307
- if (orphanResult.killed > 0) {
308
- console.log(`🧹 Cleaned ${orphanResult.killed} orphaned claude.exe process(es)`);
309
- }
310
- } catch { /* session.js not available — skip */ }
311
- recoverStaleTasks(projectPath);
312
- pruneZombieDispatches();
313
- cleanupStaleLocks(projectPath || ROOT); // TASK_1391: clear stale .merge-lock + .dispatch-lock before spawn
314
-
315
- // 0.0 Parallel dispatch — worktree isolation mode (with serial dispatch-lock fallback)
316
- // IMPORTANT: Only real task executions (TASK_XXX) get worktrees.
317
- // Coordinator sessions (Ansuz via loop.js) skip — they dispatch sub-tasks, not code changes.
318
- const isRealTask = task && /^TASK_\d+$/i.test(task);
319
-
320
- // 0.0.0 Auto-activate task: move backlog → active and git-commit BEFORE worktree creation
321
- // Root cause: uncommitted filesystem changes are invisible to git worktrees (worktree forks from HEAD).
322
- // Fix: commit the status change so the forked worktree sees the correct active state.
323
- // Guard: only for real task dispatches (not coordinator sessions, not legacy formation).
324
- if (isRealTask && formation !== 'legacy' && dryRun) {
325
- // TASK_2283: dry-run must not move the task or commit.
326
- console.log(` [dry-run] would auto-activate ${task} (task-api move active + git commit NOT performed)`);
327
- } else if (isRealTask && formation !== 'legacy') {
328
- try {
329
- const activateProjectPath = projectPath
330
- || (fs.existsSync(path.join(ROOT, '.kenaz', 'tasks')) ? ROOT : null);
331
- const activateArgs = [];
332
- if (activateProjectPath) activateArgs.push('--project-path', activateProjectPath);
333
- activateArgs.push('move', task, 'active');
334
- execSync(
335
- `node "${path.join(ROOT, '.collaboration', 'core', 'task-api.js')}" ${activateArgs.join(' ')}`,
336
- { encoding: 'utf8', timeout: 10000, cwd: ROOT, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }
337
- );
338
- console.log(` 📋 Auto-activated: ${task} → active/`);
339
- } catch (e) {
340
- // Non-fatal: task may already be active, completed, or in an incompatible state
341
- const msg = (e.stderr || e.message || '').toString().split('\n')[0];
342
- console.log(` ⚠️ Auto-activate skipped: ${msg}`);
343
- }
344
- // Commit the status change to git so the worktree forks from the correct state
345
- try {
346
- const gitRoot = projectPath || ROOT;
347
- execSync('git add .kenaz/tasks/ .collaboration/tasks/', {
348
- cwd: gitRoot, encoding: 'utf8', timeout: 10000, windowsHide: true, stdio: 'pipe',
349
- });
350
- execSync(`git commit -m "[Formation] Auto-activate: ${task} -> active"`, {
351
- cwd: gitRoot, encoding: 'utf8', timeout: 15000, windowsHide: true, stdio: 'pipe',
352
- });
353
- console.log(` 💾 Committed: ${task} status → active`);
354
- } catch (commitErr) {
355
- // Non-fatal: nothing to commit (already active/committed) or git not available
356
- const msg = (commitErr.stderr || commitErr.message || '').toString().split('\n')[0];
357
- console.log(` ⚠️ Status commit skipped: ${msg}`);
358
- }
359
- }
360
-
361
- // Fix: always record dispatch branch BEFORE worktree creation.
362
- // Root cause: the post-dispatch branch-write block (line ~282) skips worktree mode,
363
- // so .dispatch-branch was only updated during non-worktree (read-only) dispatches.
364
- // Since all real tasks use worktree mode, the file could lag by days.
365
- if (dryRun) {
366
- console.log(' [dry-run] would record dispatch branch (.dispatch-branch NOT written)');
367
- } else try {
368
- const gitRoot = projectPath || ROOT;
369
- const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', {
370
- cwd: gitRoot, encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: 'pipe',
371
- }).trim();
372
- persistDispatchBranch(currentBranch && !currentBranch.startsWith('task/')
373
- ? currentBranch
374
- : resolveDispatchTargetBranch(gitRoot), projectPath);
375
- } catch { /* non-fatal — post-complete has its own fallback */ }
376
-
377
- // Special routing: huginn-think — Prefrontal Thinking Engine (no task, no skill YAML).
378
- // TASK_2028: MUST stay above the worktree-setup block below: --task can make isRealTask true,
379
- // and this route returns without ever using (or cleaning) a worktree.
380
- if (skill === 'huginn-think') {
381
- const huginnModel = resolveAgentModel('huginnModel');
382
- return formations.launchHuginnThink({ trigger, focus, query, sessionId, projectPath, dryRun, inProcess, depth, model: huginnModel });
383
- }
384
-
385
- // TASK_2029: skills that are READ-ONLY (never touch git, produce no commits) must never get
386
- // a worktree. huginn-think already avoids it via its early return above.
387
- // heimdall: code_watch.rs spawns it in the user's live repo on EVERY commit
388
- // (post-commit hook -> /api/code_watch/trigger). Its skill only produces
389
- // proposals, never git writes; without this entry the pre-dispatch branch
390
- // recovery ran 8 s after each commit and switched a user's task/* branch to
391
- // main (field report 2026-10-03, gamem, wish-301c720c; see TASK_2243).
392
- const READ_ONLY_SKILLS = ['huginn-think', 'heimdall'];
393
-
394
- // Worktree state — passed through to launchUnit for cleanup in onDone
395
- let _unitWorktreePath = null; // non-null = worktree mode active
396
- let _unitCountFile = null;
397
- let _unitCountLock = null;
398
-
399
- if (formation !== 'legacy' && isRealTask && !READ_ONLY_SKILLS.includes(skill) && dryRun) {
400
- // TASK_2283: dry-run only reports. setupUnitWorktree writes a dirty snapshot, takes the
401
- // parallel-count lock, creates the worktree + task branch and may recover a stale branch.
402
- // _unitWorktreePath stays null; the dry-run exit below never reaches launch/cleanup.
403
- const wtPath = path.join(projectPath || ROOT, '.formation', 'worktrees', `task_${task.toLowerCase()}`);
404
- console.log(` [dry-run] would create unit worktree: ${wtPath} (parallel-count lock NOT taken, no branch recovery)`);
405
- } else if (formation !== 'legacy' && isRealTask && !READ_ONLY_SKILLS.includes(skill)) {
406
- const result = formations.setupUnitWorktree({
407
- projectRoot: projectPath || ROOT,
408
- projectPath,
409
- taskId: task,
410
- sessionId,
411
- });
412
- _unitWorktreePath = result.worktreePath;
413
- _unitCountFile = result.countFile;
414
- _unitCountLock = result.countLock;
415
- }
416
-
417
- // 0.1 自動尋找任務(當沒有指定 --task 且非 legacy 時)
418
- // 使用 projectPath 查找當前專案的任務
419
- let resolvedTask = task;
420
- if (!resolvedTask && formation !== 'legacy') {
421
- const nextTask = findNextTask(projectPath);
422
- if (!nextTask) {
423
- console.log('✅ No pending tasks found in backlog. Nothing to do.');
424
- if (projectPath) console.log(` (searched in: ${projectPath})`);
425
- if (inProcess) return null;
426
- process.exit(0);
427
- }
428
- resolvedTask = nextTask.id;
429
- console.log(`🔍 Auto-selected: [${nextTask.id}] ${nextTask.title} (${['P0','P1','P2','P3'][nextTask.priority] || 'P?'})\n`);
430
- }
431
-
432
- // 1. 載入(支援 projectPath)
433
- const skillDef = loadSkill(formation, skill);
434
- const loadOpts = projectPath ? { projectPath } : undefined;
435
- const taskData = resolvedTask && resolvedTask.startsWith('TASK_') ? loadTask(resolvedTask, loadOpts) : null;
436
- const taskInput = resolvedTask || '';
437
-
438
- // 1.0.0 護欄:明確指定 TASK_ID 但任務不存在 → 早期終止,不啟動 agent
439
- if (resolvedTask?.startsWith('TASK_') && !taskData) {
440
- const searchPaths = projectPath
441
- ? [path.join(projectPath, '.kenaz/tasks'), path.join(projectPath, '.collaboration/tasks')]
442
- : ['.kenaz/tasks', '.collaboration/tasks'];
443
- console.error(`❌ Task ${resolvedTask} not found — task does not exist.`);
444
- console.error(` Searched: ${searchPaths.join(', ')}`);
445
- console.error(` Aborting: will not start agent for a non-existent task.`);
446
- if (!inProcess) process.exitCode = 1;
447
- return null;
448
- }
449
-
450
- // 1.0.1 跨專案安全護欄:驗證 task YAML 確實屬於指定專案
451
- // 防止 task 被誤 dispatch 到錯誤專案(2026-03-17 incident: TASK_549 → wrong repo)
452
- if (!verifyTaskInProject(taskData, resolvedTask, projectPath)) {
453
- if (!inProcess) process.exitCode = 1;
454
- return null;
455
- }
456
-
457
- // 1.1 依賴預檢:確認所有依賴都已完成(legacy scheduler 有自己的依賴邏輯,跳過)
458
- if (taskData && formation !== 'legacy') {
459
- const depCheck = checkTaskDependencies(taskData, projectPath);
460
- if (depCheck.blocked) {
461
- console.error(`❌ Dispatch blocked: ${taskInput} has unmet dependencies`);
462
- for (const dep of depCheck.unmet) {
463
- if (depCheck.blockedDeps && depCheck.blockedDeps.includes(dep)) {
464
- console.error(` ⛔ ${dep} — blocked`);
465
- } else {
466
- console.error(` ⛔ ${dep} — not in done/completed`);
467
- }
468
- }
469
- console.error(` → Complete the dependencies first, or use --force to override`);
470
- if (!options.force) {
471
- if (!inProcess) process.exitCode = 1;
472
- return null;
473
- }
474
- console.warn(` ⚠️ --force: dispatching despite unmet dependencies`);
475
- }
476
- }
477
-
478
- const promptOpts = { analysisMode: mode };
479
- if (projectPath) promptOpts.projectPath = projectPath;
480
- promptOpts.depth = depth || 'kernel';
481
- // TASK_1819: thread worktree-mode awareness into prompt assembly. Serial
482
- // (non-worktree) dispatches share the working tree with the user/Ansuz, so
483
- // appendWorkflow must not emit a blanket `git add -A` commit step for them
484
- // (TASK_188: swept 392 unrelated files into one commit). Worktree setup runs
485
- // BEFORE this point, so _unitWorktreePath already reflects reality — including
486
- // the fallback-to-serial case when worktree creation fails.
487
- promptOpts.isWorktree = !!_unitWorktreePath;
488
- const rawPrompt = buildPrompt(skillDef, taskInput, taskData, promptOpts);
489
-
490
- // 1.5. Parse taskData for canary scope binding + analytics role
491
- let taskScopePaths = [];
492
- let taskRole = null;
493
- let taskRigor = null;
494
- if (taskData) {
495
- try {
496
- const parsed = JSON.parse(taskData);
497
- taskScopePaths = parsed.scope_paths || [];
498
- taskRole = parsed.role || null;
499
- taskRigor = parsed.rigor || null;
500
- } catch { /* raw YAML or non-JSON — no structured scope */ }
501
- }
502
-
503
- // 2. Banner
504
- console.log(`⚔️ Formation Headless Launch`);
505
- console.log(` Formation: ${formation}`);
506
- console.log(` Skill: ${skill}`);
507
- console.log(` Task: ${taskInput.slice(0, 60)}${taskInput.length > 60 ? '...' : ''}`);
508
- console.log(` Session: ${sessionId}`);
509
- if (projectPath) console.log(` Project: ${projectPath}`);
510
- console.log('');
511
-
512
- // 3. Dry run
513
- if (dryRun) {
514
- return printDryRun(formation, rawPrompt, skillDef, skill);
515
- }
516
-
517
- // 4. 確保子專案 git 已就緒
518
- ensureGitReady(projectPath);
519
-
520
- // 4.1. Pre-dispatch branch recovery: if stuck on a task branch from a previous
521
- // failed dispatch, switch back before doing anything else.
522
- // This is the FIRST LINE OF DEFENSE against "stuck on task branch" incidents.
523
- // Skip for read-only skills (huginn-think, heimdall) — they don't need git and
524
- // their branch recovery can race with parallel post-complete git ops.
525
- // Skip for worktree mode — worktree starts clean on its own task branch, no recovery needed.
526
- // (READ_ONLY_SKILLS declared once, above the worktree-setup gate — TASK_2029.)
527
- if (READ_ONLY_SKILLS.includes(skill) || _unitWorktreePath) {
528
- // Skip git recovery — read-only skills don't touch git; worktree mode starts clean
529
- } else try {
530
- const { execSync: es } = require('child_process');
531
- const gitRoot = projectPath || ROOT;
532
- const currentBranch = es('git rev-parse --abbrev-ref HEAD', {
533
- cwd: gitRoot, encoding: 'utf8', timeout: 5000, windowsHide: true,
534
- }).trim();
535
-
536
- if (currentBranch.startsWith('task/')) {
537
- recoverStaleDispatchBranch(gitRoot, { currentBranch, projectPath });
538
- }
539
-
540
- // Save current branch for post-complete recovery (re-read after potential recovery)
541
- const branchAfter = es('git rev-parse --abbrev-ref HEAD', {
542
- cwd: gitRoot, encoding: 'utf8', timeout: 5000, windowsHide: true,
543
- }).trim();
544
- persistDispatchBranch(
545
- branchAfter.startsWith('task/') ? resolveDispatchTargetBranch(gitRoot) : branchAfter,
546
- projectPath,
547
- );
548
- } catch { /* ignore — post-complete will fallback to getCurrentBranch() */ }
549
-
550
- // 4.2. Auto-claim task: move from backlog → active before agent starts
551
- // Agents may forget or delay claiming, causing dashboard to show task stuck in backlog
552
- // Auto-detect .kenaz/tasks/ presence: if cwd has .kenaz/tasks/, use cwd as --project-path
553
- // even when projectPath is not explicitly passed (core project with .kenaz/ layout)
554
- if (resolvedTask && resolvedTask.startsWith('TASK_') && formation !== 'legacy') {
555
- try {
556
- const apiArgs = [];
557
- const effectiveProjectPath = projectPath
558
- || (fs.existsSync(path.join(ROOT, '.kenaz', 'tasks')) ? ROOT : null);
559
- if (effectiveProjectPath) apiArgs.push('--project-path', effectiveProjectPath);
560
-
561
- // TASK_1787 (DEFECT-F): read-only self-claim guard.
562
- // The dispatching Ansuz stream typically pre-claims the task before spawning us.
563
- // The claim subprocess below is a different pid, so task-api's re-entrancy check
564
- // (owner_session AND owner_pid) can never match cross-process → it emits a spurious
565
- // "already claimed" warning indistinguishable from a real foreign-stream collision.
566
- // Detect our own marker here (anchor: owner_conversation_id; fallback: owner_session
567
- // when conv id is null; never owner_pid) and skip the claim subprocess entirely.
568
- // Foreign, absent, or corrupt markers fall through to the existing claim path untouched.
569
- let selfClaimed = false;
570
- if (effectiveProjectPath) {
571
- try {
572
- const marker = JSON.parse(fs.readFileSync(
573
- path.join(effectiveProjectPath, '.kenaz', 'locks', `claim_${resolvedTask}.json`), 'utf8'));
574
- selfClaimed = require('./dispatch')._isOwnClaimMarker(marker, effectiveProjectPath);
575
- } catch { /* no marker / no stream identity (worker context) / corrupt → normal claim path */ }
576
- }
577
- if (selfClaimed) {
578
- console.log(` 📋 Auto-claim: ${resolvedTask} already claimed by this stream — skipping`);
579
- } else {
580
- apiArgs.push('claim', resolvedTask, '--by=system', `--git-user=${resolvedTask}`);
581
- execSync(
582
- `node "${path.join(ROOT, '.collaboration', 'core', 'task-api.js')}" ${apiArgs.join(' ')}`,
583
- { encoding: 'utf8', timeout: 10000, cwd: ROOT, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }
584
- );
585
- console.log(` 📋 Auto-claimed: ${resolvedTask} → active/`);
586
- }
587
- } catch (e) {
588
- // Claim may fail if already active — non-fatal
589
- console.log(` ⚠️ Auto-claim skipped: ${(e.stderr || e.message || '').split('\n')[0]}`);
590
- }
591
- }
592
-
593
- // 4.5 Yggdrasil: 廣播上線狀態(非 legacy,legacy 自己廣播)
594
- if (formation !== 'legacy') {
595
- broadcastStatus(projectPath, {
596
- is_online: true,
597
- current_task: resolvedTask || null,
598
- touching_files: taskScopePaths || [],
599
- });
600
-
601
- // Signal handlers: await cleanup 確保 Supabase 更新完成
602
- // Note: process.on('exit') 不支援 async,由 child.on('close') 處理 cleanup
603
- // inProcess 模式跳過 — 不能在 MCP server 上註冊 signal handlers
604
- if (!inProcess) {
605
- const handleSignal = (code) => {
606
- broadcastStatusSync(projectPath, { is_online: false, current_task: null, touching_files: [] });
607
- process.exit(code);
608
- };
609
- process.on('SIGINT', () => handleSignal(130));
610
- process.on('SIGTERM', () => handleSignal(143));
611
- }
612
- }
613
-
614
- // 4.6 TASK_904: Agent Attention SNN — pre-dispatch conflict detection.
615
- // Query the Thalamus HTTP endpoint for agent conflict warnings on scope files.
616
- // If conflicts exist, log a warning. In --strict-conflict mode, block dispatch.
617
- if (taskScopePaths.length > 0 && formation !== 'legacy') {
618
- try {
619
- const http = require('http');
620
- const conflictCheck = new Promise((resolve) => {
621
- const postData = JSON.stringify({ file_paths: taskScopePaths });
622
- const req = http.request({
623
- hostname: '127.0.0.1',
624
- port: 54321,
625
- path: '/api/rust/agent-flow/agent-conflicts',
626
- method: 'POST',
627
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) },
628
- timeout: 3000,
629
- }, (res) => {
630
- let data = '';
631
- res.on('data', (chunk) => { data += chunk; });
632
- res.on('end', () => {
633
- try { resolve(JSON.parse(data)); } catch { resolve(null); }
634
- });
635
- });
636
- req.on('error', () => resolve(null));
637
- req.on('timeout', () => { req.destroy(); resolve(null); });
638
- req.write(postData);
639
- req.end();
640
- });
641
-
642
- // Synchronous wait with timeout (non-blocking for 3s max)
643
- const conflictResult = (() => {
644
- try {
645
- // Use execSync to make a synchronous HTTP call (simpler for pre-dispatch check)
646
- const curlCmd = process.platform === 'win32'
647
- ? `powershell -Command "try { $r = Invoke-RestMethod -Uri 'http://127.0.0.1:54321/api/rust/agent-flow/agent-conflicts' -Method POST -ContentType 'application/json' -Body '${JSON.stringify({ file_paths: taskScopePaths }).replace(/'/g, "''")}' -TimeoutSec 3; $r | ConvertTo-Json -Compress } catch { '{}' }"`
648
- : `curl -s --max-time 3 -X POST -H 'Content-Type: application/json' -d '${JSON.stringify({ file_paths: taskScopePaths })}' http://127.0.0.1:54321/api/rust/agent-flow/agent-conflicts`;
649
- const result = execSync(curlCmd, { encoding: 'utf8', timeout: 5000, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
650
- return result ? JSON.parse(result) : null;
651
- } catch { return null; }
652
- })();
653
-
654
- if (conflictResult && Array.isArray(conflictResult.conflicts) && conflictResult.conflicts.length > 0) {
655
- console.log(` ⚠️ Agent Conflict Warning (TASK_904):`);
656
- for (const c of conflictResult.conflicts) {
657
- console.log(` 🔥 ${c.file_path} — ${c.fire_count} conflict fire(s)`);
658
- }
659
- if (options.strictConflict) {
660
- console.error(` ❌ Dispatch blocked: --strict-conflict is set and ${conflictResult.conflicts.length} file(s) have active agent conflicts`);
661
- if (!inProcess) process.exitCode = 1;
662
- return null;
663
- }
664
- console.log(` → Proceeding with warning (use --strict-conflict to block)`);
665
- }
666
- } catch { /* conflict check failed — non-fatal, proceed with dispatch */ }
667
- }
668
-
669
- // 4.7 TASK_944: Pre-dispatch health snapshot — take a fresh before-snapshot
670
- // at dispatch time so Heimdall compares accurate baselines (not stale ones).
671
- if (resolvedTask && resolvedTask.startsWith('TASK_') && formation !== 'legacy') {
672
- try {
673
- const { measureHealthScoreSync, saveTaskHealthSnapshot } = require('./post-complete');
674
- const health = measureHealthScoreSync(projectPath);
675
- if (health) {
676
- saveTaskHealthSnapshot(resolvedTask, 'before', health, projectPath);
677
- console.log(` 🛡️ Pre-dispatch health snapshot: ${health.score}/100 (${health.grade}) — saved as ${resolvedTask}_before.json`);
678
- } else {
679
- console.log(` 🛡️ Pre-dispatch health snapshot: Dashboard not running — skipped`);
680
- }
681
- } catch { /* non-fatal — post-complete will handle missing snapshot */ }
682
- }
683
-
684
- // 5. 分發到 5 種陣型(各自獨立)
685
- const launchers = {
686
- unit: formations.launchUnit,
687
- solo: formations.launchSolo,
688
- squad: formations.launchSquad,
689
- legion: formations.launchLegion,
690
- legacy: formations.launchLegacy,
691
- };
692
-
693
- const launcher = launchers[formation];
694
- if (!launcher) {
695
- console.error(`❌ Unknown formation: ${formation}`);
696
- process.exit(1);
697
- }
698
-
699
- const restoreAdapterEnv = () => {
700
- if (previousAdapter === undefined) {
701
- delete process.env.BIFROST_ADAPTER;
702
- } else {
703
- process.env.BIFROST_ADAPTER = previousAdapter;
704
- }
705
- };
706
-
707
- // Per-agent model override: if the task role or skill matches a known agent
708
- // with a dashboard model override, use that instead of the global dispatchModel.
709
- let effectiveDispatchModel = dispatchModel;
710
- const agentModelKey =
711
- (taskRole === 'heimdall' || skill === 'heimdall') ? 'heimdallModel' :
712
- (taskRole === 'forseti' || skill === 'forseti') ? 'forsetiModel' :
713
- (taskRole === 'huginn' || skill === 'huginn-think') ? 'huginnModel' :
714
- null;
715
- if (agentModelKey) {
716
- const agentModel = resolveAgentModel(agentModelKey);
717
- if (agentModel) effectiveDispatchModel = agentModel;
718
- }
719
-
720
- // Early dispatch registration: write entry BEFORE calling launcher() to eliminate the
721
- // race window where a status check arrives between headless.js start and launchUnit()
722
- // calling registerDispatch(). The entry is written with pid=null; formations.js will
723
- // call registerDispatch() again with the real PID. The post-launch dedup block below
724
- // merges these two entries into one authoritative record with the real PID so that
725
- // zombie pruning can correctly detect process exit.
726
- if (formation !== 'legacy' && skill !== 'huginn-think') {
727
- try {
728
- const logRoot = projectPath ? getLogDir(projectPath) : LOG_DIR;
729
- const expectedLogDir = path.join(logRoot, formation || 'misc');
730
- const expectedLogPath = path.join(expectedLogDir, `${skill}_${sessionId}.log`);
731
- const list = readActiveDispatches();
732
- if (!list.find(d => d.sessionId === sessionId)) {
733
- list.push({
734
- sessionId,
735
- logPath: expectedLogPath,
736
- logPathStatus: 'provisional',
737
- taskId: resolvedTask || null,
738
- formation,
739
- startedAt: new Date().toISOString(),
740
- projectPath: projectPath || null,
741
- originSessionId: originSessionId || null,
742
- originTransport: originSessionId ? 'mcp' : null,
743
- originNotificationMethod: originSessionId ? 'notifications/kenaz/dispatch_complete' : null,
744
- pid: null,
745
- });
746
- writeActiveDispatches(list);
747
- console.log(` 📡 Pre-registered dispatch: ${sessionId} (task=${resolvedTask || formation})`);
748
- }
749
- } catch { /* non-fatal — formations.launchUnit will register on its own */ }
750
- }
751
-
752
- const launchResult = launcher({ rawPrompt, skill, sessionId, skillDef, mode, concurrency, projectPath, taskId: resolvedTask, taskScopePaths, role: taskRole, rigor: taskRigor, skipPostComplete, skipTests, inProcess, worktreePath: _unitWorktreePath, countFile: _unitCountFile, countLock: _unitCountLock, originSessionId, dispatchModel: effectiveDispatchModel, mcpConfigExtra, allowedToolsOverride });
753
- attachBifrostAdapterCleanup(launchResult, restoreAdapterEnv);
754
-
755
- // Post-launch dedup: formations.js launchUnit() calls registerDispatch() with the real PID,
756
- // but the early registration above already pushed an entry with pid=null. This creates
757
- // two entries for the same sessionId. The first (pid=null) wins dispatch-status .find(),
758
- // making zombie pruning unable to detect process exit — isRunning stays true for 60 minutes.
759
- // Fix: merge all entries for this sessionId into one authoritative entry with the real PID.
760
- if (launchResult && formation !== 'legacy' && skill !== 'huginn-think') {
761
- try {
762
- const realPid = launchResult?.pid || launchResult?.child?.pid || null;
763
- const realLogPath = launchResult.logPath || null;
764
- if (realPid) {
765
- const allEntries = readActiveDispatches();
766
- const mine = allEntries.filter(d => d.sessionId === sessionId);
767
- if (mine.length > 1 || (mine.length === 1 && mine[0].pid === null)) {
768
- const others = allEntries.filter(d => d.sessionId !== sessionId);
769
- // Prefer the entry that already has a real pid; fall back to last entry.
770
- const base = mine.find(d => d.pid !== null) || mine[mine.length - 1];
771
- base.pid = realPid;
772
- if (realLogPath) { base.logPath = realLogPath; delete base.logPathStatus; }
773
- writeActiveDispatches([...others, base]);
774
- if (mine.length > 1) {
775
- console.log(` 🔄 Deduped ${mine.length} dispatch entries → 1 (pid=${realPid})`);
776
- } else {
777
- console.log(` 🔄 Updated pre-registered dispatch with real PID (pid=${realPid})`);
778
- }
779
- }
780
- }
781
- } catch { /* non-fatal — does not interrupt dispatch */ }
782
- }
783
-
784
- if (launchResult && typeof launchResult === 'object' && !Array.isArray(launchResult)) {
785
- launchResult.adapter = adapterSelection.adapter;
786
- // Mark log path as provisional: the .log file is created when the agent
787
- // first writes output, not at launch time. Callers should not assume the
788
- // file exists immediately after launch() returns.
789
- if (launchResult.logPath && !('logPathStatus' in launchResult)) {
790
- launchResult.logPathStatus = 'provisional';
791
- }
792
- }
793
-
794
- return launchResult;
795
- }
796
-
797
- function printDryRun(formation, rawPrompt, skillDef, skill) {
798
- console.log(`--- DRY RUN ---\n`);
799
-
800
- if (formation === 'legion') {
801
- const legionPrompt = buildLegionLeadPrompt(rawPrompt);
802
- console.log(`[Legion Lead Prompt]\n${legionPrompt}\n`);
803
- } else if (formation === 'squad' && Array.isArray(rawPrompt)) {
804
- const reportDir = `squad-${skill}`;
805
- for (const m of rawPrompt) {
806
- const prompt = m.prompt.replace(/\{session\}/g, reportDir);
807
- console.log(`[Round 1 - Member: ${m.memberId}]\n${HEADLESS_PREFIX}${prompt}\n`);
808
- }
809
- if (skillDef.debate?.enabled) {
810
- console.log(`--- Round 2: Debate ---\n`);
811
- const debatePrompts = buildSquadDebatePrompts(skillDef, reportDir);
812
- for (const m of debatePrompts) {
813
- console.log(`[Round 2 - Member: ${m.memberId}]\n${HEADLESS_PREFIX}${m.prompt}\n`);
814
- }
815
- }
816
- if (skillDef.merge) {
817
- const synthPrompt = buildSquadSynthesisPrompt(skillDef, reportDir);
818
- console.log(`[Synthesis Agent]\n${HEADLESS_PREFIX}${synthPrompt}\n`);
819
- }
820
- } else {
821
- console.log(`${HEADLESS_PREFIX}${rawPrompt}`);
822
- }
823
-
824
- console.log(`--- END ---`);
825
- }
826
-
827
- // ─────────────────────────────────────────
828
- // CLI
829
- // ─────────────────────────────────────────
830
-
831
- if (require.main === module) {
832
- const args = process.argv.slice(2);
833
-
834
- if (args.includes('--help') || args.includes('-h')) {
835
- console.log(`
836
- ⚔️ Formation Headless Launcher
837
-
838
- 無頭模式:Formation Lead 配好任務後直接呼叫,subagent 不問問題。
839
-
840
- Usage:
841
- node .formation/tools/headless.js --formation=<type> --skill=<name> --task=<value>
842
-
843
- Options:
844
- --formation <name> 陣型: unit (default), solo, squad, legion, legacy
845
- --skill <name> 技能 (YAML filename without extension)
846
- --task <value> TASK_ID 或任務描述
847
- --concurrency <n> 並行執行數(預設 3,上限 5,僅 legacy)
848
- --project-path <p> 子專案路徑(任務從此專案查找)
849
- --mode <name> 分析模式: optimization, innovation (squad only)
850
- --dry-run 只印 prompt,不啟動進程
851
- --skip-post-complete 跳過 post-complete(手動管理 archive/git/verify)
852
- --skip-tests 跳過回歸測試 + acceptance 驗證(傳遞給 post-complete)
853
- --depth <mode> Specialist prompt depth: capsule (≤12KB) | kernel (default) | standard (full)
854
- --bifrost-adapter <a> Bifröst adapter override (e.g. openai) — route agent through relay
855
-
856
- Formation-Skill:
857
- unit: do, think, plan, heimdall, huginn-think
858
- solo: strategy, diagnosis
859
- squad: blitz, sweep
860
- legion: forge, hive
861
- legacy: scheduler (連續排程器)
862
-
863
- Huginn Think (special skill — no task required):
864
- --trigger <type> manual (default)
865
- --focus <dim> architecture | agent | knowledge (optional)
866
- --query <topic> directed thinking topic (optional)
867
-
868
- Logs: .formation/logs/{formation}/
869
-
870
- Examples:
871
- node .formation/tools/headless.js --skill=do --task="修正按鈕顏色"
872
- node .formation/tools/headless.js --formation=solo --skill=strategy --task="評估快取方案"
873
- node .formation/tools/headless.js --formation=squad --skill=blitz --task=TASK_201
874
- node .formation/tools/headless.js --formation=legion --skill=forge --task=TASK_202 --dry-run
875
- `);
876
- process.exit(0);
877
- }
878
-
879
- const getArg = (flag) => {
880
- for (let i = 0; i < args.length; i++) {
881
- if (args[i].startsWith(`${flag}=`)) return args[i].split('=').slice(1).join('=');
882
- if (args[i] === flag && args[i + 1]) return args[i + 1];
883
- }
884
- return null;
885
- };
886
-
887
- // TASK_1929 (Agent Hub M2): repeatable-flag variant of getArg — collects
888
- // EVERY occurrence, not just the first. Used by --mcp-config-extra, which
889
- // agent_hub/launch.rs's tokio::process::Command may pass more than once
890
- // in a future multi-endpoint launch (today it passes at most one).
891
- const getArgAll = (flag) => {
892
- const out = [];
893
- for (let i = 0; i < args.length; i++) {
894
- if (args[i].startsWith(`${flag}=`)) out.push(args[i].split('=').slice(1).join('='));
895
- else if (args[i] === flag && args[i + 1]) out.push(args[i + 1]);
896
- }
897
- return out;
898
- };
899
-
900
- const formation = getArg('--formation') || 'unit';
901
- const skill = getArg('--skill');
902
- const task = getArg('--task');
903
- const dryRun = args.includes('--dry-run');
904
- const mode = getArg('--mode');
905
- const concurrency = getArg('--concurrency');
906
- const projectPath = getArg('--project-path');
907
- const originSessionId = getArg('--origin-session-id');
908
- const depth = getArg('--depth');
909
- // TASK_1774: Bifröst adapter override from the dispatch caller (e.g. dashboard
910
- // kenaz_dispatch backend with dispatchFamily=bifrost/dispatchAdapter=openai).
911
- // Threaded into launch() opts — resolveLaunchBifrostAdapter treats it as the
912
- // highest-priority source (logged as source=dispatch).
913
- const bifrostAdapter = getArg('--bifrost-adapter');
914
- // TASK_1929 (Agent Hub M2): extra --mcp-config file(s) to register alongside
915
- // the core .mcp.json (native claude path) / translate into codex `-c`
916
- // overrides (bifrost/openai/relay.js) — see runner.js spawnClaude and
917
- // relay.js buildCodexMcpOverrides. Empty array = zero behavior change.
918
- const mcpConfigExtra = getArgAll('--mcp-config-extra');
919
- // TASK_1929: full override of spawnClaude's default allowedTools list —
920
- // used by agent_hub/launch.rs's `hub` skill launches to restrict a
921
- // website-triggered agent to Read/Glob/Grep + the site's own MCP server,
922
- // never the default Bash/Edit/Write/synapse_* toolkit. null = unchanged
923
- // default (every other dispatch path never passes this flag).
924
- const allowedToolsArg = getArg('--allowed-tools');
925
- // Dashboard dispatch model override (Quick Actions → Default Models →
926
- // Dispatch agent). Set by the kenaz_dispatch backend route after
927
- // reading .kenaz/dashboard-models.json. Takes precedence over the
928
- // legacy session-inheritance pattern in runner.js spawnClaude.
929
- let dispatchModel = getArg('--dispatch-model');
930
- // TASK_2249: ONE parsed object (first parseable file wins) feeds both dispatchModel
931
- // and per-agent models — never mixed across project/core files.
932
- const _dashboardModels = loadDashboardModels(projectPath);
933
- if (dispatchModel === null && _dashboardModels
934
- && Object.prototype.hasOwnProperty.call(_dashboardModels, 'dispatchModel')) {
935
- dispatchModel = _dashboardModels.dispatchModel;
936
- }
937
-
938
- /**
939
- * Resolve per-agent model from dashboard-models.json.
940
- * Returns the agent-specific model if set (non-empty string), else null.
941
- * @param {string} agentKey - e.g. 'huginnModel', 'forsetiModel', 'heimdallModel'
942
- * @returns {string|null}
943
- */
944
- function resolveAgentModel(agentKey) {
945
- const val = _dashboardModels?.[agentKey];
946
- // Empty string = use agent built-in default (return null to not override)
947
- return (typeof val === 'string' && val.length > 0) ? val : null;
948
- }
949
-
950
- const skipPostComplete = args.includes('--skip-post-complete');
951
- const skipTests = args.includes('--skip-tests');
952
- const force = args.includes('--force');
953
-
954
- // Huginn Think args
955
- const trigger = getArg('--trigger');
956
- const focus = getArg('--focus');
957
- const query = getArg('--query');
958
- // ─────────────────────────────────────────
959
- // Ansuz Wake / Sleep (Continuous Ansuz Daemon — TASK_900)
960
- // Spawned by Tauri lib.rs on Dashboard start/close.
961
- // ─────────────────────────────────────────
962
- const ansuzWake = args.includes('--ansuz-wake');
963
- const ansuzSleep = args.includes('--ansuz-sleep');
964
-
965
- if (ansuzWake || ansuzSleep) {
966
- const effectiveProjectPath = projectPath || ROOT;
967
- const pidFile = path.join(effectiveProjectPath, '.kenaz', 'specialist', 'ansuz_pid.txt');
968
-
969
- if (ansuzSleep) {
970
- if (fs.existsSync(pidFile)) {
971
- const existingPid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
972
- if (existingPid && isProcessAlive(existingPid)) {
973
- try {
974
- if (process.platform === 'win32') {
975
- require('child_process').execSync(
976
- `taskkill /T /F /PID ${existingPid}`,
977
- { stdio: 'ignore', timeout: 10000, windowsHide: true }
978
- );
979
- } else {
980
- process.kill(existingPid, 'SIGTERM');
981
- }
982
- console.log(`[Ansuz sleep] Terminated session (PID: ${existingPid})`);
983
- } catch (e) {
984
- console.error(`[Ansuz sleep] Kill failed: ${e.message}`);
985
- }
986
- } else {
987
- console.log('[Ansuz sleep] PID in file is stale — clearing');
988
- }
989
- try { fs.unlinkSync(pidFile); } catch {}
990
- } else {
991
- console.log('[Ansuz sleep] No active session');
992
- }
993
- process.exit(0);
994
- }
995
-
996
- // Wake: delegate to ansuz-sdk-runner.js (TASK_912 — multi-turn SDK mode)
997
- // Spawn as detached process to escape Windows Job Object
998
- (async () => {
999
- const { spawn: nodeSpawn } = require('child_process');
1000
- // SDK runner lives in KenazAI core (ROOT), not in the selected project
1001
- const sdkRunner = path.join(ROOT, '.specialist', 'Ansuz', 'tools', 'ansuz-sdk-runner.js');
1002
-
1003
- // Zombie guard
1004
- if (fs.existsSync(pidFile)) {
1005
- try {
1006
- const existingPid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
1007
- if (existingPid && isProcessAlive(existingPid)) {
1008
- console.log(`[Ansuz wake] SDK runner already alive (PID: ${existingPid}) — skipping`);
1009
- process.exit(0);
1010
- }
1011
- } catch { /* ignore */ }
1012
- try { fs.unlinkSync(pidFile); } catch {}
1013
- }
1014
-
1015
- if (!fs.existsSync(sdkRunner)) {
1016
- console.error(`[Ansuz wake] SDK runner not found: ${sdkRunner}`);
1017
- process.exit(1);
1018
- }
1019
-
1020
- const nodeExe = process.execPath;
1021
- const httpPort = process.env.KENAZ_HTTP_PORT || '18080';
1022
- let child, spawnedPid;
1023
-
1024
- if (process.platform === 'win32') {
1025
- // PowerShell Start-Process escapes Node.js Job Object on Windows
1026
- const ps1File = path.join(require('os').tmpdir(), `ansuz_sdk_wake_${Date.now()}.ps1`);
1027
- const ps1Content = [
1028
- `$env:KENAZ_HTTP_PORT = '${httpPort}'`,
1029
- `Start-Process -FilePath '${nodeExe.replace(/'/g, "''")}' -ArgumentList '"${sdkRunner.replace(/"/g, '\\"')}"','--project-path=${effectiveProjectPath.replace(/'/g, "''")}' -WindowStyle Hidden`,
1030
- `Remove-Item '${ps1File.replace(/'/g, "''")}' -ErrorAction SilentlyContinue`,
1031
- ].join('\n');
1032
- fs.writeFileSync(ps1File, ps1Content, 'utf8');
1033
- child = nodeSpawn('powershell.exe', ['-WindowStyle', 'Hidden', '-NonInteractive', '-File', ps1File], {
1034
- detached: true, stdio: 'ignore', windowsHide: true,
1035
- });
1036
- child.unref();
1037
- spawnedPid = child.pid;
1038
- } else {
1039
- child = nodeSpawn(nodeExe, [sdkRunner, `--project-path=${effectiveProjectPath}`], {
1040
- detached: true, stdio: 'ignore', cwd: effectiveProjectPath,
1041
- env: { ...process.env, KENAZ_HTTP_PORT: httpPort },
1042
- });
1043
- child.unref();
1044
- spawnedPid = child.pid;
1045
- }
1046
-
1047
- fs.mkdirSync(path.dirname(pidFile), { recursive: true });
1048
- fs.writeFileSync(pidFile, String(spawnedPid));
1049
- console.log(`[Ansuz wake] SDK runner started (PID: ${spawnedPid})`);
1050
- process.exit(0);
1051
- })().catch(e => {
1052
- console.error(`[Ansuz wake] Unexpected error: ${e.message}`);
1053
- process.exit(1);
1054
- });
1055
- return;
1056
- }
1057
-
1058
- if (!skill) { console.error('❌ --skill is required'); process.exit(1); }
1059
-
1060
- const opts = { formation, skill, task, dryRun, mode, projectPath, skipPostComplete, skipTests, force, trigger, focus, query, originSessionId, depth, dispatchModel, bifrostAdapter };
1061
- if (concurrency != null) opts.concurrency = parseInt(concurrency, 10);
1062
- // TASK_1929: only set when actually provided — omitting the key (rather
1063
- // than passing an empty array / null) keeps every pre-existing dispatch's
1064
- // opts object byte-identical to before this task.
1065
- if (mcpConfigExtra.length > 0) opts.mcpConfigExtra = mcpConfigExtra;
1066
- if (allowedToolsArg) opts.allowedToolsOverride = allowedToolsArg;
1067
-
1068
- launch(opts);
1069
- }
1070
-
1071
- module.exports = { launch, recoverStaleDispatchBranch };