kenaz 0.3.3 → 0.3.6
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.
- package/core/.agents/skills/typesafe-ai/LICENSE +21 -21
- package/core/.agents/skills/typesafe-ai/SKILL.md +149 -149
- package/core/.claude/commands/Ansuz.md +15 -2
- package/core/.claude/hooks/__tests__/task-2395-project-registration.test.js +84 -0
- package/core/.claude/hooks/handoff-staleness.js +2 -2
- package/core/.claude/hooks/session-init.ps1 +49 -49
- package/core/.claude/hooks/turn-context.js +50 -2
- package/core/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -26
- package/core/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -97
- package/core/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -101
- package/core/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -31
- package/core/.claude/skills/ui-ux-pro-max/data/products.csv +96 -96
- package/core/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
- package/core/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -68
- package/core/.claude/skills/ui-ux-pro-max/data/typography.csv +57 -57
- package/core/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
- package/core/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/core/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
- package/core/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -253
- package/core/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
- package/core/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -106
- package/core/.collaboration/core/task-api.js +1 -4
- package/core/.formation/README.md +21 -146
- package/core/.formation/bifrost/codex-doctor.js +37 -2
- package/core/.formation/bifrost/codex-doctor.test.js +18 -0
- package/core/.formation/bifrost/launch.js +1 -1
- package/core/.formation/bifrost/openai/codex-hook-shim.js +6 -0
- package/core/.formation/bifrost/openai/codex-hook-shim.test.js +11 -1
- package/core/.formation/bifrost/setup-codex.js +3 -0
- package/core/.formation/native/claude/README.md +1 -1
- package/core/.formation/role-map.yaml +3 -3
- package/core/.formation/spec/task_operations.parity.test.js +0 -1
- package/core/.formation/spec/task_operations.yaml +3 -3
- package/core/.formation/tests/hello.txt +2 -2
- package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +4 -4
- package/core/.formation/tools/dashboard-models-chain.test.js +0 -6
- package/core/.formation/tools/dispatch-detail/cross-project-worktree.md +1 -9
- package/core/.formation/tools/dispatch-detail/worktree-subagent-mode.md +2 -2
- package/core/.formation/tools/dispatch.js +33 -554
- package/core/.formation/tools/mcp-allowlist.test.js +0 -1
- package/core/.formation/tools/post-complete/task2345.test.js +0 -5
- package/core/.formation/tools/pre-dispatch-snapshot.test.js +0 -100
- package/core/.formation/tools/prompt.js +33 -1623
- package/core/.formation/tools/prompt.test.js +26 -594
- package/core/.formation/unit/do.yaml +2 -4
- package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -163
- package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -393
- package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -102
- package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -412
- package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -144
- package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -86
- package/core/.kenaz/muninn_episode_write.ps1 +17 -17
- package/core/.kenaz/specialist/field_report_watermark.json +1 -1
- package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
- package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
- package/core/.kenaz/specialist/t1777_probe.txt +1 -1
- package/core/.matrix/index.matrix +5 -5
- package/core/.rule/general/agent-workflow.yaml +4 -4
- package/core/.rule/general/file-size-threshold.yaml +16 -4
- package/core/.rule/general/rust-verify-target.yaml +0 -1
- package/core/.rule/general/system-invariants.yaml +8 -25
- package/core/.specialist/Ansuz/SYSTEM_DOC.md +17 -42
- package/core/.specialist/Ansuz/capability.yaml +7 -14
- package/core/.specialist/Ansuz/concepts/formation_quick_ref.yaml +4 -11
- package/core/.specialist/Ansuz/elixir.yaml +3 -4
- package/core/.specialist/Ansuz/identity_contract.yaml +1 -1
- package/core/.specialist/Ansuz/knowledge.yaml +7 -10
- package/core/.specialist/Ansuz/rule.yaml +2 -2
- package/core/.specialist/Ansuz/tools/verify-and-land.js +22 -0
- package/core/.specialist/Ansuz/tools/verify-and-land.test.js +59 -5
- package/core/.specialist/Ansuz/workflow.yaml +7 -11
- package/core/.specialist/Forseti/core.yaml +1 -1
- package/core/.specialist/Heimdall/SKILL.md +1 -1
- package/core/.specialist/Heimdall/core.yaml +2 -2
- package/core/.specialist/Huginn/rule.yaml +1 -1
- package/core/.specialist/tools/README.md +2 -2
- package/core/.specialist/tools/__tests__/freya-cli-contract.test.js +0 -1
- package/core/.system/collaboration/README.md +1 -1
- package/core/.system/formation/LEGACY_SKILLS.md +2 -0
- package/core/.system/formation/POST_COMPLETE.md +2 -0
- package/core/.system/formation/README.md +2 -0
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -355
- package/core/dev/JEV_INTEGRATION_PLAN.md +189 -189
- package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -13
- package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -9
- package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -14
- package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -67
- package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -52
- package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -31
- package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -39
- package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -33
- package/core/dev/jev-eval/README.md +99 -99
- package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -58
- package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -32
- package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -48
- package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -52
- package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -47
- package/core/dev/pipeline/tasks/.gitkeep +1 -1
- package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -27
- package/core/dev/scripts/check-file-size.js +44 -17
- package/core/dev/scripts/init_git.sh +0 -0
- package/core/dev/scripts/init_project.sh +0 -0
- package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -536
- package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -1978
- package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -140
- package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -173
- package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -168
- package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -126
- package/core/dev/scripts/multi_agent_scheduler.sh +0 -0
- package/core/dev/scripts/run_once.sh +0 -0
- package/core/dev/scripts/setup_mcp.sh +0 -0
- package/core/dev/scripts/setup_rag.sh +0 -0
- package/core/dev/scripts/start_dev_agent.sh +0 -0
- package/core/dev/scripts/start_multi_agent.sh +0 -0
- package/core/dev/scripts/sync_task_queue.sh +0 -0
- package/core/k-cli/README.md +1 -94
- package/core/k-cli/test/TEST_SPEC.md +5 -22
- package/core/k-cli/test/run.sh +1 -67
- package/core/plugins/kenaz/commands/Amelia.md +1 -1
- package/core/plugins/kenaz/commands/Ansuz.md +16 -3
- package/core/plugins/kenaz/scripts/kenaz-mode.js +11 -1
- package/core/plugins/kenaz/scripts/sync.js +6 -2
- package/core/plugins/kenaz/scripts/ygg.js +13 -1
- package/core/plugins/kenaz/skills/product_manager/SKILL.md +12 -12
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -26
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -97
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -101
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -31
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +96 -96
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -45
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -54
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -53
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -56
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -53
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -53
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -51
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -59
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -52
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -54
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -61
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -54
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -51
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -50
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -68
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +57 -57
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -101
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +99 -99
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -31
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -253
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -1067
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -106
- package/lib/cli.js +11 -4
- package/package.json +11 -7
- package/vendor/install-kenaz-cli.js +18 -7
- package/core/.formation/legion/forge.yaml +0 -299
- package/core/.formation/legion/hive.yaml +0 -238
- package/core/.formation/squad/blitz.yaml +0 -192
- package/core/.formation/squad/strategy-tribunal.yaml +0 -354
- package/core/.formation/squad/strategy.yaml +0 -349
- package/core/.formation/squad/sweep.yaml +0 -208
- package/core/.formation/tools/broadcast.js +0 -103
- package/core/.formation/tools/dispatch-dryrun.test.js +0 -54
- package/core/.formation/tools/dispatch-notifier.js +0 -84
- package/core/.formation/tools/formations.js +0 -1203
- package/core/.formation/tools/headless-branch-recovery.test.js +0 -64
- package/core/.formation/tools/headless-dryrun.test.js +0 -79
- package/core/.formation/tools/headless-v2.js +0 -659
- package/core/.formation/tools/headless.js +0 -1071
- package/core/.formation/tools/legacy-scheduler.js +0 -1994
- package/core/.formation/tools/logger.js +0 -273
- package/core/.formation/tools/memory-loader.js +0 -422
- package/core/.formation/tools/process-state.js +0 -33
- package/core/.formation/tools/remote-conflict.js +0 -98
- package/core/.formation/tools/runner.js +0 -1332
- package/core/.formation/tools/task-utils.js +0 -344
- package/core/.formation/tools/task2028-headless-worktree.test.js +0 -112
- package/core/.formation/tools/tests/headless-mcp-args-plumbing.test.js +0 -123
- package/core/.formation/tools/worktree-leak.test.js +0 -162
- package/core/k-cli/dist/alaya/run.js +0 -91
- package/core/k-cli/dist/core/output.js +0 -24
- package/core/k-cli/dist/core/process.js +0 -38
- package/core/k-cli/dist/core/project.js +0 -80
- package/core/k-cli/dist/core/spawn.js +0 -268
- package/core/k-cli/dist/core/types.js +0 -2
- package/core/k-cli/dist/dashboard/update.js +0 -53
- package/core/k-cli/dist/dashboard/version.js +0 -20
- package/core/k-cli/dist/dispatch/run.js +0 -241
- package/core/k-cli/dist/dispatch/status.js +0 -103
- package/core/k-cli/dist/index.js +0 -283
- package/core/k-cli/dist/project/initialize.js +0 -301
- package/core/k-cli/dist/project/install.js +0 -89
- package/core/k-cli/dist/project/status.js +0 -138
- package/core/k-cli/dist/specialist/list.js +0 -100
- package/core/k-cli/dist/specialist/run.js +0 -93
- package/core/k-cli/dist/task/create.js +0 -194
- package/core/k-cli/dist/task/delete.js +0 -98
- package/core/k-cli/dist/task/get.js +0 -55
- package/core/k-cli/dist/task/list.js +0 -58
- package/core/k-cli/dist/task/submit.js +0 -156
- package/core/k-cli/dist/ygg/run.js +0 -51
- package/core/k-cli/node_modules/.bin/yaml +0 -16
- package/core/k-cli/node_modules/.bin/yaml.cmd +0 -17
- package/core/k-cli/node_modules/.bin/yaml.ps1 +0 -28
- package/core/k-cli/node_modules/.package-lock.json +0 -23
- package/core/k-cli/node_modules/yaml/LICENSE +0 -13
- package/core/k-cli/node_modules/yaml/README.md +0 -172
- package/core/k-cli/node_modules/yaml/bin.mjs +0 -11
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-collection.js +0 -88
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-doc.js +0 -43
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-node.js +0 -109
- package/core/k-cli/node_modules/yaml/browser/dist/compose/compose-scalar.js +0 -86
- package/core/k-cli/node_modules/yaml/browser/dist/compose/composer.js +0 -217
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-map.js +0 -115
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +0 -198
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +0 -49
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-end.js +0 -37
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +0 -207
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +0 -223
- package/core/k-cli/node_modules/yaml/browser/dist/compose/resolve-props.js +0 -146
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-contains-newline.js +0 -34
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +0 -26
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +0 -15
- package/core/k-cli/node_modules/yaml/browser/dist/compose/util-map-includes.js +0 -13
- package/core/k-cli/node_modules/yaml/browser/dist/doc/Document.js +0 -335
- package/core/k-cli/node_modules/yaml/browser/dist/doc/anchors.js +0 -71
- package/core/k-cli/node_modules/yaml/browser/dist/doc/applyReviver.js +0 -55
- package/core/k-cli/node_modules/yaml/browser/dist/doc/createNode.js +0 -88
- package/core/k-cli/node_modules/yaml/browser/dist/doc/directives.js +0 -176
- package/core/k-cli/node_modules/yaml/browser/dist/errors.js +0 -57
- package/core/k-cli/node_modules/yaml/browser/dist/index.js +0 -17
- package/core/k-cli/node_modules/yaml/browser/dist/log.js +0 -11
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Alias.js +0 -114
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Collection.js +0 -147
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Node.js +0 -38
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Pair.js +0 -36
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/Scalar.js +0 -24
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLMap.js +0 -144
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +0 -113
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +0 -63
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/identity.js +0 -36
- package/core/k-cli/node_modules/yaml/browser/dist/nodes/toJS.js +0 -37
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-scalar.js +0 -214
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-stringify.js +0 -61
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst-visit.js +0 -97
- package/core/k-cli/node_modules/yaml/browser/dist/parse/cst.js +0 -98
- package/core/k-cli/node_modules/yaml/browser/dist/parse/lexer.js +0 -717
- package/core/k-cli/node_modules/yaml/browser/dist/parse/line-counter.js +0 -39
- package/core/k-cli/node_modules/yaml/browser/dist/parse/parser.js +0 -967
- package/core/k-cli/node_modules/yaml/browser/dist/public-api.js +0 -102
- package/core/k-cli/node_modules/yaml/browser/dist/schema/Schema.js +0 -37
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/map.js +0 -17
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/null.js +0 -15
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/seq.js +0 -17
- package/core/k-cli/node_modules/yaml/browser/dist/schema/common/string.js +0 -14
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/bool.js +0 -19
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/float.js +0 -43
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/int.js +0 -38
- package/core/k-cli/node_modules/yaml/browser/dist/schema/core/schema.js +0 -23
- package/core/k-cli/node_modules/yaml/browser/dist/schema/json/schema.js +0 -62
- package/core/k-cli/node_modules/yaml/browser/dist/schema/tags.js +0 -96
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +0 -58
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +0 -26
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +0 -46
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +0 -71
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +0 -64
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +0 -74
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +0 -78
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +0 -39
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +0 -93
- package/core/k-cli/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +0 -101
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +0 -146
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringify.js +0 -129
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +0 -153
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyComment.js +0 -20
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +0 -85
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +0 -24
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyPair.js +0 -150
- package/core/k-cli/node_modules/yaml/browser/dist/stringify/stringifyString.js +0 -336
- package/core/k-cli/node_modules/yaml/browser/dist/util.js +0 -11
- package/core/k-cli/node_modules/yaml/browser/dist/visit.js +0 -233
- package/core/k-cli/node_modules/yaml/browser/index.js +0 -5
- package/core/k-cli/node_modules/yaml/browser/package.json +0 -3
- package/core/k-cli/node_modules/yaml/dist/cli.d.ts +0 -8
- package/core/k-cli/node_modules/yaml/dist/cli.mjs +0 -201
- package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.d.ts +0 -11
- package/core/k-cli/node_modules/yaml/dist/compose/compose-collection.js +0 -90
- package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.d.ts +0 -7
- package/core/k-cli/node_modules/yaml/dist/compose/compose-doc.js +0 -45
- package/core/k-cli/node_modules/yaml/dist/compose/compose-node.d.ts +0 -29
- package/core/k-cli/node_modules/yaml/dist/compose/compose-node.js +0 -112
- package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.d.ts +0 -5
- package/core/k-cli/node_modules/yaml/dist/compose/compose-scalar.js +0 -88
- package/core/k-cli/node_modules/yaml/dist/compose/composer.d.ts +0 -63
- package/core/k-cli/node_modules/yaml/dist/compose/composer.js +0 -222
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.d.ts +0 -6
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-map.js +0 -117
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +0 -11
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-scalar.js +0 -200
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +0 -6
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-block-seq.js +0 -51
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.d.ts +0 -6
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-end.js +0 -39
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +0 -7
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-collection.js +0 -209
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +0 -10
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-flow-scalar.js +0 -225
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.d.ts +0 -23
- package/core/k-cli/node_modules/yaml/dist/compose/resolve-props.js +0 -148
- package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/compose/util-contains-newline.js +0 -36
- package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/compose/util-empty-scalar-position.js +0 -28
- package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +0 -3
- package/core/k-cli/node_modules/yaml/dist/compose/util-flow-indent-check.js +0 -17
- package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/compose/util-map-includes.js +0 -15
- package/core/k-cli/node_modules/yaml/dist/doc/Document.d.ts +0 -141
- package/core/k-cli/node_modules/yaml/dist/doc/Document.js +0 -337
- package/core/k-cli/node_modules/yaml/dist/doc/anchors.d.ts +0 -24
- package/core/k-cli/node_modules/yaml/dist/doc/anchors.js +0 -76
- package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.d.ts +0 -9
- package/core/k-cli/node_modules/yaml/dist/doc/applyReviver.js +0 -57
- package/core/k-cli/node_modules/yaml/dist/doc/createNode.d.ts +0 -17
- package/core/k-cli/node_modules/yaml/dist/doc/createNode.js +0 -90
- package/core/k-cli/node_modules/yaml/dist/doc/directives.d.ts +0 -49
- package/core/k-cli/node_modules/yaml/dist/doc/directives.js +0 -178
- package/core/k-cli/node_modules/yaml/dist/errors.d.ts +0 -21
- package/core/k-cli/node_modules/yaml/dist/errors.js +0 -62
- package/core/k-cli/node_modules/yaml/dist/index.d.ts +0 -25
- package/core/k-cli/node_modules/yaml/dist/index.js +0 -50
- package/core/k-cli/node_modules/yaml/dist/log.d.ts +0 -3
- package/core/k-cli/node_modules/yaml/dist/log.js +0 -19
- package/core/k-cli/node_modules/yaml/dist/nodes/Alias.d.ts +0 -29
- package/core/k-cli/node_modules/yaml/dist/nodes/Alias.js +0 -116
- package/core/k-cli/node_modules/yaml/dist/nodes/Collection.d.ts +0 -73
- package/core/k-cli/node_modules/yaml/dist/nodes/Collection.js +0 -151
- package/core/k-cli/node_modules/yaml/dist/nodes/Node.d.ts +0 -53
- package/core/k-cli/node_modules/yaml/dist/nodes/Node.js +0 -40
- package/core/k-cli/node_modules/yaml/dist/nodes/Pair.d.ts +0 -22
- package/core/k-cli/node_modules/yaml/dist/nodes/Pair.js +0 -39
- package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.d.ts +0 -43
- package/core/k-cli/node_modules/yaml/dist/nodes/Scalar.js +0 -27
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.d.ts +0 -53
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLMap.js +0 -147
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +0 -60
- package/core/k-cli/node_modules/yaml/dist/nodes/YAMLSeq.js +0 -115
- package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/nodes/addPairToJSMap.js +0 -65
- package/core/k-cli/node_modules/yaml/dist/nodes/identity.d.ts +0 -23
- package/core/k-cli/node_modules/yaml/dist/nodes/identity.js +0 -53
- package/core/k-cli/node_modules/yaml/dist/nodes/toJS.d.ts +0 -29
- package/core/k-cli/node_modules/yaml/dist/nodes/toJS.js +0 -39
- package/core/k-cli/node_modules/yaml/dist/options.d.ts +0 -350
- package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.d.ts +0 -64
- package/core/k-cli/node_modules/yaml/dist/parse/cst-scalar.js +0 -218
- package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.d.ts +0 -8
- package/core/k-cli/node_modules/yaml/dist/parse/cst-stringify.js +0 -63
- package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.d.ts +0 -39
- package/core/k-cli/node_modules/yaml/dist/parse/cst-visit.js +0 -99
- package/core/k-cli/node_modules/yaml/dist/parse/cst.d.ts +0 -109
- package/core/k-cli/node_modules/yaml/dist/parse/cst.js +0 -112
- package/core/k-cli/node_modules/yaml/dist/parse/lexer.d.ts +0 -87
- package/core/k-cli/node_modules/yaml/dist/parse/lexer.js +0 -719
- package/core/k-cli/node_modules/yaml/dist/parse/line-counter.d.ts +0 -22
- package/core/k-cli/node_modules/yaml/dist/parse/line-counter.js +0 -41
- package/core/k-cli/node_modules/yaml/dist/parse/parser.d.ts +0 -84
- package/core/k-cli/node_modules/yaml/dist/parse/parser.js +0 -972
- package/core/k-cli/node_modules/yaml/dist/public-api.d.ts +0 -44
- package/core/k-cli/node_modules/yaml/dist/public-api.js +0 -107
- package/core/k-cli/node_modules/yaml/dist/schema/Schema.d.ts +0 -17
- package/core/k-cli/node_modules/yaml/dist/schema/Schema.js +0 -39
- package/core/k-cli/node_modules/yaml/dist/schema/common/map.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/schema/common/map.js +0 -19
- package/core/k-cli/node_modules/yaml/dist/schema/common/null.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/schema/common/null.js +0 -17
- package/core/k-cli/node_modules/yaml/dist/schema/common/seq.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/schema/common/seq.js +0 -19
- package/core/k-cli/node_modules/yaml/dist/schema/common/string.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/schema/common/string.js +0 -16
- package/core/k-cli/node_modules/yaml/dist/schema/core/bool.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/schema/core/bool.js +0 -21
- package/core/k-cli/node_modules/yaml/dist/schema/core/float.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/schema/core/float.js +0 -47
- package/core/k-cli/node_modules/yaml/dist/schema/core/int.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/schema/core/int.js +0 -42
- package/core/k-cli/node_modules/yaml/dist/schema/core/schema.d.ts +0 -1
- package/core/k-cli/node_modules/yaml/dist/schema/core/schema.js +0 -25
- package/core/k-cli/node_modules/yaml/dist/schema/json/schema.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/schema/json/schema.js +0 -64
- package/core/k-cli/node_modules/yaml/dist/schema/json-schema.d.ts +0 -69
- package/core/k-cli/node_modules/yaml/dist/schema/tags.d.ts +0 -48
- package/core/k-cli/node_modules/yaml/dist/schema/tags.js +0 -99
- package/core/k-cli/node_modules/yaml/dist/schema/types.d.ts +0 -92
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/binary.js +0 -70
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +0 -7
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/bool.js +0 -29
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/float.js +0 -50
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +0 -5
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/int.js +0 -76
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +0 -9
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/merge.js +0 -68
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +0 -22
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/omap.js +0 -77
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +0 -10
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +0 -82
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +0 -1
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/schema.js +0 -41
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +0 -28
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/set.js +0 -96
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +0 -6
- package/core/k-cli/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +0 -105
- package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +0 -34
- package/core/k-cli/node_modules/yaml/dist/stringify/foldFlowLines.js +0 -151
- package/core/k-cli/node_modules/yaml/dist/stringify/stringify.d.ts +0 -21
- package/core/k-cli/node_modules/yaml/dist/stringify/stringify.js +0 -132
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +0 -17
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyCollection.js +0 -155
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.d.ts +0 -10
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyComment.js +0 -24
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyDocument.js +0 -87
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +0 -2
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyNumber.js +0 -26
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.d.ts +0 -3
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyPair.js +0 -152
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.d.ts +0 -9
- package/core/k-cli/node_modules/yaml/dist/stringify/stringifyString.js +0 -338
- package/core/k-cli/node_modules/yaml/dist/test-events.d.ts +0 -4
- package/core/k-cli/node_modules/yaml/dist/test-events.js +0 -134
- package/core/k-cli/node_modules/yaml/dist/util.d.ts +0 -16
- package/core/k-cli/node_modules/yaml/dist/util.js +0 -28
- package/core/k-cli/node_modules/yaml/dist/visit.d.ts +0 -102
- package/core/k-cli/node_modules/yaml/dist/visit.js +0 -236
- package/core/k-cli/node_modules/yaml/package.json +0 -97
- 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
|
-
};
|