kenaz 0.3.21 → 0.3.23

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 (481) hide show
  1. package/core/.claude/commands/Ansuz.md +2 -0
  2. package/core/.claude/hooks/__tests__/peer-session-fill.test.js +37 -0
  3. package/core/.claude/hooks/_ansuz-conversation.js +32 -1
  4. package/core/.claude/hooks/_user-scope.js +1 -1
  5. package/core/.claude/hooks/core-resolve.js +1 -1
  6. package/core/.claude/hooks/peer-optin.test.js +74 -0
  7. package/core/.claude/hooks/peer-session-fill.js +38 -0
  8. package/core/.claude/hooks/turn-context.js +5 -4
  9. package/core/.claude/hooks/worktree-create.js +6 -6
  10. package/core/.claude/hooks/worktree-remove.js +4 -4
  11. package/core/.claude/settings.json +9 -0
  12. package/core/.collaboration/core/reconcile.js +16 -1
  13. package/core/.collaboration/core/task-2467-lifecycle.test.js +63 -0
  14. package/core/.collaboration/core/task-api.js +37 -7
  15. package/core/.formation/bifrost/launch.js +1 -1
  16. package/core/.formation/bifrost/openai/codex-hook-shim.js +21 -4
  17. package/core/.formation/bifrost/openai/codex-peer-pull.test.js +42 -0
  18. package/core/.formation/bifrost/provider-launch.js +2 -1
  19. package/core/.formation/tools/kenaz-stats.js +1 -1
  20. package/core/.formation/tools/post-complete/git-merge.js +1 -1
  21. package/core/.formation/tools/prompt.js +1 -1
  22. package/core/.kenaz/specialist/ansuz_rules.yaml +7 -51
  23. package/core/.kenaz/specialist/ansuz_session_handoff.schema.json +2 -6
  24. package/core/.rule/general/project-self-containment.yaml +2 -2
  25. package/core/.specialist/Amelia/README.md +1 -1
  26. package/core/.specialist/Amelia/rule.yaml +1 -1
  27. package/core/.specialist/Ansuz/RULES.md +1 -1
  28. package/core/.specialist/Ansuz/concepts/commitment_layer.yaml +1 -1
  29. package/core/.specialist/Ansuz/concepts/evolvable_guardrails.yaml +1 -1
  30. package/core/.specialist/Ansuz/knowledge.yaml +1 -3
  31. package/core/.specialist/tools/__tests__/task-2409-lessons.test.js +10 -0
  32. package/core/.specialist/tools/__tests__/task-2427-harvest.test.js +25 -0
  33. package/core/.specialist/tools/ansuz-boot-fast.js +1 -1
  34. package/core/.specialist/tools/failure-detector.js +4 -4
  35. package/core/.specialist/tools/handoff-doc.js +1 -1
  36. package/core/.specialist/tools/lessons.js +2 -2
  37. package/core/CLAUDE.md +5 -5
  38. package/core/KENAZ_CORE_VERSION +1 -1
  39. package/core/dashboard/scripts/install-kenaz-cli.js +471 -0
  40. package/core/dev/scripts/check-file-size.js +1 -1
  41. package/core/dev/scripts/ci-feedback.js +1 -1
  42. package/core/dev/scripts/gc-agent-worktrees.js +11 -11
  43. package/core/dev/scripts/kenaz-session.js +3 -3
  44. package/core/dev/scripts/peer-identity-e2e.js +3 -3
  45. package/core/dev/scripts/run-rust-tests.js +2 -2
  46. package/core/dev/scripts/skill-routing-replay.js +3 -3
  47. package/core/dev/scripts/task1961/selftest.js +2 -2
  48. package/core/dev/scripts/windows-hide-guard.test.js +97 -0
  49. package/core/plugins/kenaz/commands/Ansuz.md +2 -0
  50. package/core/plugins/kenaz/guidance.md +2 -0
  51. package/core/plugins/kenaz/hooks/core-resolve.js +1 -1
  52. package/core/plugins/kenaz/hooks/hooks.json +9 -0
  53. package/core/plugins/kenaz/hooks/kenaz-hook.js +53 -4
  54. package/core/plugins/kenaz/scripts/kenaz.test.js +72 -5
  55. package/core/plugins/kenaz/scripts/ygg.js +40 -4
  56. package/core/plugins/kenaz/scripts/ygg.test.js +63 -3
  57. package/core/plugins/kenaz-peer/hooks/register.test.ts +59 -2
  58. package/core/plugins/kenaz-peer/hooks/register.ts +24 -1
  59. package/lib/cli.js +9 -5
  60. package/package.json +6 -6
  61. package/vendor/install-kenaz-cli.js +21 -6
  62. package/core/.kenaz/designs/METAPLASTICITY_CONFIDENCE_DESIGN.md +0 -537
  63. package/core/.kenaz/designs/REFLEX_INITIATOR_DESIGN.md +0 -501
  64. package/core/.kenaz/designs/TASK_1385_handoff_mcp_design.md +0 -426
  65. package/core/.kenaz/designs/TASK_932_hippocampus_consolidation.md +0 -103
  66. package/core/.kenaz/designs/TASK_934_efe_signal_engine.md +0 -97
  67. package/core/.kenaz/designs/TASK_935_self_evolution_framework.md +0 -154
  68. package/core/.kenaz/designs/VISION_cognitive_architecture_roadmap.md +0 -159
  69. package/core/.kenaz/designs/metaplasticity_simulation_results.json +0 -696
  70. package/core/.kenaz/main-memory/README.md +0 -230
  71. package/core/.kenaz/main-memory/kenaz/cortex/001_cortex_overview.md +0 -168
  72. package/core/.kenaz/main-memory/kenaz/cortex/001_rag_engine_overview.md +0 -30
  73. package/core/.kenaz/main-memory/kenaz/memory-system/001_three_tier_architecture.md +0 -361
  74. package/core/.kenaz/main-memory/kenaz/pipeline/001_task_execution_flow.md +0 -117
  75. package/core/.kenaz/main-memory/kenaz/pipeline/001_task_granularity.md +0 -154
  76. package/core/.kenaz/main-memory/kenaz/pipeline/002_team_priority.md +0 -51
  77. package/core/.kenaz/main-memory/kenaz/pipeline/003_feature_id_system.md +0 -46
  78. package/core/.kenaz/main-memory/kenaz/rules/001_gen_007_self_containment.md +0 -108
  79. package/core/.kenaz/main-memory/kenaz/rules/001_rule_priority_system.md +0 -121
  80. package/core/.kenaz/main-memory/kenaz/rules/002_agt_006_pre_check.md +0 -0
  81. package/core/.kenaz/main-memory/kenaz/rules/003_ppl_002_task_archival.md +0 -48
  82. package/core/.kenaz/main-memory/kenaz/system/001_dual_track_task_system.md +0 -197
  83. package/core/.kenaz/main-memory/knowledge/patterns/001_json_bom_issue.md +0 -78
  84. package/core/.kenaz/main-memory/knowledge/patterns/001_leader_worker_pattern.md +0 -0
  85. package/core/.kenaz/main-memory/knowledge/patterns/002_scope_path_conflict.md +0 -115
  86. package/core/.kenaz/main-memory/knowledge/patterns/002_task_dependency_resolution.md +0 -223
  87. package/core/.kenaz/main-memory/knowledge/patterns/003_question_protocol.md +0 -242
  88. package/core/.kenaz/main-memory/knowledge/patterns/004_user_insight_value.md +0 -111
  89. package/core/.kenaz/main-memory/knowledge/patterns/005_file_lock_pattern.md +0 -274
  90. package/core/.kenaz/main-memory/knowledge/references/001_ppl_rules.md +0 -28
  91. package/core/.kenaz/main-memory/knowledge/solutions/001_json_bom_issue.md +0 -137
  92. package/core/.kenaz/main-memory/knowledge/solutions/001_team_invocation.md +0 -0
  93. package/core/.kenaz/main-memory/knowledge/solutions/002_passive_monitoring.md +0 -0
  94. package/core/.kenaz/main-memory/knowledge/solutions/003_windows_file_race_condition.md +0 -239
  95. package/core/.kenaz/main-memory/registry.json +0 -326
  96. package/core/.kenaz/main-memory/version.yaml +0 -58
  97. package/core/.kenaz/specialist/memory_guard_fired.json +0 -1
  98. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +0 -1
  99. package/core/.kenaz/specialist/memory_source_priority.yaml +0 -260
  100. package/core/.kenaz/specialist/prompt_1775.txt +0 -317
  101. package/core/.kenaz/specialist/prompt_1776.txt +0 -326
  102. package/core/.kenaz/specialist/prompt_1791.txt +0 -320
  103. package/core/.kenaz/specialist/prompt_1792.txt +0 -320
  104. package/core/.kenaz/specialist/prompt_1795.txt +0 -281
  105. package/core/.kenaz/specialist/prompt_1813.txt +0 -332
  106. package/core/.kenaz/specialist/prompt_1814.txt +0 -401
  107. package/core/.kenaz/specialist/prompt_1815.txt +0 -394
  108. package/core/.kenaz/specialist/prompt_1816.txt +0 -437
  109. package/core/.kenaz/specialist/prompt_1818.txt +0 -361
  110. package/core/.kenaz/specialist/prompt_1819.txt +0 -421
  111. package/core/.kenaz/specialist/prompt_1821.txt +0 -345
  112. package/core/.kenaz/specialist/prompt_1822.txt +0 -313
  113. package/core/.kenaz/specialist/prompt_1824.txt +0 -348
  114. package/core/.kenaz/specialist/prompt_1826.txt +0 -388
  115. package/core/.kenaz/specialist/rune_efe_salience_gap.md +0 -21
  116. package/core/.kenaz/specialist/task1825_update.json +0 -43
  117. package/core/.kenaz/specialist/taskgen/consolidation_i.json +0 -38
  118. package/core/.kenaz/specialist/taskgen/csharp_a.json +0 -44
  119. package/core/.kenaz/specialist/taskgen/csharp_b.json +0 -40
  120. package/core/.kenaz/specialist/taskgen/embedding_e.json +0 -34
  121. package/core/.kenaz/specialist/taskgen/hygiene_fg.json +0 -39
  122. package/core/.kenaz/specialist/taskgen/pathology_h.json +0 -36
  123. package/core/.kenaz/specialist/taskgen/unity_noise_d.json +0 -38
  124. package/core/.kenaz/specialist/taskgen/visibility_c.json +0 -38
  125. package/core/.specialist/Ansuz/decisions/ADR-requirement-graph-layer.md +0 -390
  126. package/core/.specialist/Ansuz/self_model_seed.backup.json +0 -100
  127. package/core/.specialist/ansuz_session_handoff.json +0 -96
  128. package/core/dev/ALAYA_FIELD_FEEDBACK_DESIGN.md +0 -126
  129. package/core/dev/ANSUZ_SCAFFOLD_DIET_2026-09-20.md +0 -231
  130. package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +0 -355
  131. package/core/dev/ANSUZ_WORKFLOW_MODERNIZATION.md +0 -143
  132. package/core/dev/CORTEX_META_GRAPH_ASSESSMENT.md +0 -99
  133. package/core/dev/FREYA_DOWNSTREAM_BUGS_2026-09-19.md +0 -44
  134. package/core/dev/GLOBAL_MEMORY_IDENTITY_CONSOLIDATION.md +0 -263
  135. package/core/dev/GUARD_EFFECTIVENESS_DESIGN.md +0 -180
  136. package/core/dev/HEALTH_GATE_VARIANCE_ASSESSMENT.md +0 -271
  137. package/core/dev/HOOK_DIET_2026-09-21.md +0 -126
  138. package/core/dev/JEV_INTEGRATION_PLAN.md +0 -189
  139. package/core/dev/KENAZ_AGENT_CONNECT_v1_SPEC.md +0 -139
  140. package/core/dev/MOBILE_DESIGN_SPEC.md +0 -569
  141. package/core/dev/MULTI_ANSUZ_SPEC.md +0 -136
  142. package/core/dev/MUNINN_RETIREMENT.md +0 -115
  143. package/core/dev/PHASE3_ATTRIBUTION_POPULATION_DIAGNOSIS.md +0 -211
  144. package/core/dev/PHASE3_P3-2B_TASKLEVEL_PROBE.md +0 -83
  145. package/core/dev/PHASE3_P3-2_POPULATION_DISJOINTNESS.md +0 -83
  146. package/core/dev/PHASE3_P3-2_REMEASURE_REPORT.md +0 -231
  147. package/core/dev/PREDICTION_GROUNDING_DESIGN.md +0 -376
  148. package/core/dev/PRODUCT_SPEC.md +0 -317
  149. package/core/dev/PROJECT_REGISTRY_DUPLICATE_ASSESSMENT.md +0 -86
  150. package/core/dev/STARTUP_TIMELINE_TASK_1995.md +0 -181
  151. package/core/dev/SYNAPSE_WORKER_PER_PROJECT.md +0 -881
  152. package/core/dev/TASK_1680_P3-3_DISPATCH_SPEC.md +0 -96
  153. package/core/dev/TASK_1684_CALIBRATION_IMPACT_ANALYSIS.md +0 -170
  154. package/core/dev/TASK_1684_FORK_A_GOODHART_DESIGN.md +0 -171
  155. package/core/dev/TASK_1684_P3_2_REMEASURE_VALIDATION.md +0 -262
  156. package/core/dev/TASK_1903_SIGNAL_AUC_MEASUREMENT.md +0 -207
  157. package/core/dev/TASK_1904_BRANCH_INTEGRITY_AUDIT.md +0 -146
  158. package/core/dev/ansuz-eval/README.md +0 -38
  159. package/core/dev/ansuz-eval/build-fixtures.js +0 -87
  160. package/core/dev/ansuz-eval/grade.js +0 -96
  161. package/core/dev/ansuz-eval/grader.test.js +0 -117
  162. package/core/dev/ansuz-eval/tasks.md +0 -32
  163. package/core/dev/archive/ansuz/BOOT_FULL.md +0 -141
  164. package/core/dev/archive/ansuz/agents/ansuz-boot.md +0 -176
  165. package/core/dev/archive/ansuz/designs/alaya-passive-surfacing.md +0 -84
  166. package/core/dev/archive/ansuz/designs/cc_memory_indexing.md +0 -307
  167. package/core/dev/archive/ansuz/designs/check_guard.md +0 -322
  168. package/core/dev/archive/ansuz/designs/consequence_loop.md +0 -227
  169. package/core/dev/archive/ansuz/designs/mcp_kuzudb_lock_architecture.md +0 -312
  170. package/core/dev/archive/ansuz/designs/phase0_git_enrichment.md +0 -278
  171. package/core/dev/archive/ansuz/designs/refactor_mcp_tools_synapse.md +0 -490
  172. package/core/dev/archive/ansuz/designs/semantic_markdown_extraction.md +0 -167
  173. package/core/dev/archive/ansuz/designs/task_1372_prompt_injects_into.md +0 -193
  174. package/core/dev/archive/ansuz/designs/thought_stack.md +0 -286
  175. package/core/dev/archive/ansuz/investigations/reachability_unified_api_2026-04-11.md +0 -330
  176. package/core/dev/archive/ansuz/investigations/synapse_orphans_indexing_bug_2026-04-11.md +0 -352
  177. package/core/dev/archive/ansuz/investigations/synapse_orphans_reindex_verification_2026-04-11.md +0 -48
  178. package/core/dev/archive/ansuz/investigations/synapse_orphans_verification_2026-04-11.md +0 -170
  179. package/core/dev/archive/ansuz/legacy/kernel.yaml +0 -399
  180. package/core/dev/archive/ansuz/legacy/phases/phase_0.yaml +0 -418
  181. package/core/dev/archive/ansuz/legacy/phases/phase_1.yaml +0 -376
  182. package/core/dev/archive/ansuz/legacy/phases/phase_10.yaml +0 -35
  183. package/core/dev/archive/ansuz/legacy/phases/phase_11.yaml +0 -582
  184. package/core/dev/archive/ansuz/legacy/phases/phase_2.yaml +0 -94
  185. package/core/dev/archive/ansuz/legacy/phases/phase_3.yaml +0 -274
  186. package/core/dev/archive/ansuz/legacy/phases/phase_4.yaml +0 -67
  187. package/core/dev/archive/ansuz/legacy/phases/phase_5.yaml +0 -32
  188. package/core/dev/archive/ansuz/legacy/phases/phase_6.yaml +0 -91
  189. package/core/dev/archive/ansuz/legacy/phases/phase_7.yaml +0 -58
  190. package/core/dev/archive/ansuz/legacy/phases/phase_8.yaml +0 -146
  191. package/core/dev/archive/ansuz/legacy/phases/phase_9.yaml +0 -424
  192. package/core/dev/archive/ansuz/specs/INDEX_bifrost_hook_parity.yaml +0 -92
  193. package/core/dev/archive/ansuz/specs/active-inference-formation.yaml +0 -453
  194. package/core/dev/archive/ansuz/specs/brief_TASK_1228_userpromptsubmit.yaml +0 -191
  195. package/core/dev/archive/ansuz/specs/brief_TASK_1229_pretooluse_posttooluse.yaml +0 -250
  196. package/core/dev/archive/ansuz/specs/brief_TASK_1230_sessionstart_stop.yaml +0 -301
  197. package/core/dev/archive/ansuz/specs/brief_TASK_1231_verification.yaml +0 -272
  198. package/core/dev/archive/skills/feature_agent/SKILL.md +0 -289
  199. package/core/dev/design/PHASE4_DESIGN.md +0 -559
  200. package/core/dev/design/dashboard_docs_integration_PLAN.md +0 -388
  201. package/core/dev/design/matrix_format_ABBREVIATIONS.md +0 -655
  202. package/core/dev/design/matrix_format_DESIGN_SPEC.md +0 -601
  203. package/core/dev/design/matrix_format_DISCUSSION_SUMMARY.md +0 -484
  204. package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +0 -13
  205. package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +0 -9
  206. package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +0 -14
  207. package/core/dev/design/matrix_format_SYNTAX_BNF.md +0 -754
  208. package/core/dev/design/matrix_format_TOKEN_COMPARISON.md +0 -484
  209. package/core/dev/design/matrix_v2_COGNITIVE_DESIGN.md +0 -503
  210. package/core/dev/design/matrix_v2_DISCUSSION_SUMMARY.md +0 -414
  211. package/core/dev/design/matrix_v2_EXAMPLE.matrix +0 -67
  212. package/core/dev/designs/EXPANDABLE_CARD_DESIGN_SPEC.md +0 -509
  213. package/core/dev/jev-eval/ACTIVE_CORRECTION.md +0 -52
  214. package/core/dev/jev-eval/ANNOTATION_GUIDE.md +0 -68
  215. package/core/dev/jev-eval/BASELINE_COMPARISON.md +0 -45
  216. package/core/dev/jev-eval/CONTEXT_REVIEW.md +0 -31
  217. package/core/dev/jev-eval/EVALUATION_STATUS.md +0 -39
  218. package/core/dev/jev-eval/HISTORICAL_CORPUS.md +0 -33
  219. package/core/dev/jev-eval/PACKAGING_VERIFICATION.md +0 -42
  220. package/core/dev/jev-eval/README.md +0 -101
  221. package/core/dev/jev-eval/THALAMUS_RANKING.md +0 -58
  222. package/core/dev/jev-eval/VALIDATION_PROGRESS.md +0 -54
  223. package/core/dev/jev-eval/cli.mjs +0 -50
  224. package/core/dev/jev-eval/context.mjs +0 -183
  225. package/core/dev/jev-eval/corpus.mjs +0 -186
  226. package/core/dev/jev-eval/fixtures/conversation-review.seed.jsonl +0 -6
  227. package/core/dev/jev-eval/fixtures/synthetic.jsonl +0 -11
  228. package/core/dev/jev-eval/metrics.mjs +0 -67
  229. package/core/dev/jev-eval/muninn-baseline.mjs +0 -119
  230. package/core/dev/jev-eval/providers.mjs +0 -95
  231. package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +0 -32
  232. package/core/dev/jev-eval/reports/context-review-2026-09-20.json +0 -48
  233. package/core/dev/jev-eval/reports/conversation-review.pending.json +0 -151
  234. package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +0 -52
  235. package/core/dev/jev-eval/reports/length-probe-2026-09-20.json +0 -133
  236. package/core/dev/jev-eval/reports/live-smoke-2026-09-20.json +0 -616
  237. package/core/dev/jev-eval/reports/muninn-baseline-2026-09-20.json +0 -52
  238. package/core/dev/jev-eval/reports/resilience-2026-09-20.json +0 -520
  239. package/core/dev/jev-eval/reports/rules-baseline.json +0 -197
  240. package/core/dev/jev-eval/reports/synthetic-review.pending.json +0 -357
  241. package/core/dev/jev-eval/resilience.mjs +0 -71
  242. package/core/dev/jev-eval/review.mjs +0 -78
  243. package/core/dev/jev-eval/schema.mjs +0 -87
  244. package/core/dev/jev-eval/test/context.test.mjs +0 -95
  245. package/core/dev/jev-eval/test/corpus.test.mjs +0 -102
  246. package/core/dev/jev-eval/test/eval.test.mjs +0 -146
  247. package/core/dev/jev-eval/test/muninn-baseline.test.mjs +0 -71
  248. package/core/dev/jev-eval/test/resilience.test.mjs +0 -72
  249. package/core/dev/jev-eval/test/review.test.mjs +0 -61
  250. package/core/dev/pipeline/PHASE3_PLANNING_SUMMARY.md +0 -338
  251. package/core/dev/pipeline/PROGRESS.md +0 -113
  252. package/core/dev/pipeline/PROGRESS_template.md +0 -860
  253. package/core/dev/pipeline/QA_REPORT_F008.md +0 -431
  254. package/core/dev/pipeline/QA_REPORT_LOCAL_CLAUDE_DIAGNOSIS.md +0 -583
  255. package/core/dev/pipeline/README.md +0 -210
  256. package/core/dev/pipeline/WORKFLOW_GUIDE.md +0 -824
  257. package/core/dev/pipeline/archived/TASK_015_tauri_init.yaml +0 -53
  258. package/core/dev/pipeline/archived/TASK_016_tauri_commands.yaml +0 -54
  259. package/core/dev/pipeline/archived/TASK_017_rust_pty.yaml +0 -54
  260. package/core/dev/pipeline/archived/TASK_018_tauri_bundle.yaml +0 -53
  261. package/core/dev/pipeline/archived/TASK_019_release_workflow.yaml +0 -56
  262. package/core/dev/pipeline/archived/TASK_020_update_ui.yaml +0 -54
  263. package/core/dev/pipeline/archived/TASK_021_update_notification_ui.yaml +0 -55
  264. package/core/dev/pipeline/archived/TASK_022_remove_node_pty.yaml +0 -65
  265. package/core/dev/pipeline/archived/TASK_023_api_routes_migration.yaml +0 -86
  266. package/core/dev/pipeline/archived/TASK_024_feature_id_system.yaml +0 -51
  267. package/core/dev/pipeline/archived/TASK_025_executable_acceptance.yaml +0 -66
  268. package/core/dev/pipeline/archived/TASK_026_spec_versioning.yaml +0 -56
  269. package/core/dev/pipeline/archived/TASK_027_drift_detection.yaml +0 -60
  270. package/core/dev/pipeline/archived/TASK_028_tauri_pty_manager.yaml +0 -60
  271. package/core/dev/pipeline/archived/TASK_029_tauri_pty_commands.yaml +0 -62
  272. package/core/dev/pipeline/archived/TASK_030_frontend_session_adapter.yaml +0 -54
  273. package/core/dev/pipeline/archived/TASK_031_terminal_tauri_events.yaml +0 -56
  274. package/core/dev/pipeline/archived/TASK_041_scheduler_data_models.yaml +0 -54
  275. package/core/dev/pipeline/archived/TASK_042_path_lock_manager.yaml +0 -53
  276. package/core/dev/pipeline/archived/TASK_043_process_manager.yaml +0 -57
  277. package/core/dev/pipeline/archived/TASK_044_git_integration.yaml +0 -85
  278. package/core/dev/pipeline/archived/TASK_045_task_queue_manager.yaml +0 -54
  279. package/core/dev/pipeline/archived/TASK_046_scheduler_dispatcher.yaml +0 -60
  280. package/core/dev/pipeline/archived/TASK_047_tauri_scheduler_commands.yaml +0 -55
  281. package/core/dev/pipeline/archived/TASK_048_realtime_status_push.yaml +0 -92
  282. package/core/dev/pipeline/archived/TASK_049_frontend_scheduler_integration.yaml +0 -58
  283. package/core/dev/pipeline/archived/TASK_050_error_recovery.yaml +0 -56
  284. package/core/dev/pipeline/archived/TASK_051_optimization_proposals_backend.yaml +0 -53
  285. package/core/dev/pipeline/archived/TASK_052_optimization_proposals_ui.yaml +0 -58
  286. package/core/dev/pipeline/archived/TASK_053_optimization_proposals_diff_view.yaml +0 -54
  287. package/core/dev/pipeline/archived/TASK_054_multi_project_scheduler_state.yaml +0 -76
  288. package/core/dev/pipeline/archived/TASK_055_scheduler_api_project_scope.yaml +0 -64
  289. package/core/dev/pipeline/archived/TASK_056_frontend_scheduler_project_scope.yaml +0 -62
  290. package/core/dev/pipeline/archived/TASK_057_scheduler_module_structure.yaml +0 -54
  291. package/core/dev/pipeline/archived/TASK_058_scheduler_models_extract.yaml +0 -51
  292. package/core/dev/pipeline/archived/TASK_059_scheduler_core_extract.yaml +0 -51
  293. package/core/dev/pipeline/archived/TASK_060_scheduler_commands_extract.yaml +0 -51
  294. package/core/dev/pipeline/archived/TASK_061_scheduler_specialized_commands.yaml +0 -53
  295. package/core/dev/pipeline/archived/TASK_062_process_manager_module_structure.yaml +0 -56
  296. package/core/dev/pipeline/archived/TASK_063_process_manager_core_extract.yaml +0 -56
  297. package/core/dev/pipeline/archived/TASK_064_recovery_module_structure.yaml +0 -57
  298. package/core/dev/pipeline/archived/TASK_065_recovery_core_extract.yaml +0 -56
  299. package/core/dev/pipeline/archived/TASK_066_self_enhancement_scheduler_models.yaml +0 -60
  300. package/core/dev/pipeline/archived/TASK_067_agents_section_quick_actions.yaml +0 -53
  301. package/core/dev/pipeline/archived/TASK_067_self_enhancement_scheduler_core.yaml +0 -65
  302. package/core/dev/pipeline/archived/TASK_068_agent_workflow_rule_enhancement.yaml +0 -46
  303. package/core/dev/pipeline/archived/TASK_068_self_enhancement_tauri_commands.yaml +0 -61
  304. package/core/dev/pipeline/archived/TASK_069_agent_wizard_responsive.yaml +0 -46
  305. package/core/dev/pipeline/archived/TASK_069_self_enhancement_ui.yaml +0 -62
  306. package/core/dev/pipeline/archived/TASK_070_preview_panel_redesign.yaml +0 -62
  307. package/core/dev/pipeline/archived/TASK_070_self_enhancement_integration.yaml +0 -57
  308. package/core/dev/pipeline/archived/TASK_071_preview_panel_qa.yaml +0 -98
  309. package/core/dev/pipeline/archived/TASK_071_single_agent_config.yaml +0 -57
  310. package/core/dev/pipeline/archived/TASK_072_preview_modal_esc_fix.yaml +0 -71
  311. package/core/dev/pipeline/archived/TASK_072_product_review_module.yaml +0 -57
  312. package/core/dev/pipeline/archived/TASK_073_agents_page_component_refactor.yaml +0 -68
  313. package/core/dev/pipeline/archived/TASK_073_rename_single_agent.yaml +0 -64
  314. package/core/dev/pipeline/archived/TASK_074_preview_mode_ui_diagnosis.yaml +0 -71
  315. package/core/dev/pipeline/archived/TASK_074_single_agent_ui_toggle.yaml +0 -56
  316. package/core/dev/pipeline/archived/TASK_075_agents_page_component_refactor.yaml +0 -69
  317. package/core/dev/pipeline/archived/TASK_075_single_agent_integration_test.yaml +0 -75
  318. package/core/dev/pipeline/archived/TASK_076_preview_mode_ui_diagnosis.yaml +0 -70
  319. package/core/dev/pipeline/archived/TASK_076_single_agent_continuous_mode.yaml +0 -65
  320. package/core/dev/pipeline/archived/TASK_077_failure_directory_structure.yaml +0 -51
  321. package/core/dev/pipeline/archived/TASK_077_single_agent_typescript_types.yaml +0 -59
  322. package/core/dev/pipeline/archived/TASK_078_fail_001_agent_no_output.yaml +0 -53
  323. package/core/dev/pipeline/archived/TASK_078_single_agent_ui_simplify.yaml +0 -74
  324. package/core/dev/pipeline/archived/TASK_079_dashboard_rwd_responsive.yaml +0 -63
  325. package/core/dev/pipeline/archived/TASK_079_fail_002_json_bom.yaml +0 -56
  326. package/core/dev/pipeline/archived/TASK_080_fail_003_queue_pollution.yaml +0 -56
  327. package/core/dev/pipeline/archived/TASK_080_http_server_module.yaml +0 -55
  328. package/core/dev/pipeline/archived/TASK_081_failure_logging_tools.yaml +0 -57
  329. package/core/dev/pipeline/archived/TASK_081_http_api_routes.yaml +0 -62
  330. package/core/dev/pipeline/archived/TASK_082_frontend_api_adapter.yaml +0 -51
  331. package/core/dev/pipeline/archived/TASK_082_integrate_failure_to_workflow.yaml +0 -60
  332. package/core/dev/pipeline/archived/TASK_083_cleanup_nextjs_api.yaml +0 -48
  333. package/core/dev/pipeline/archived/TASK_083_task_queue_cleanup.yaml +0 -67
  334. package/core/dev/pipeline/archived/TASK_084_rust_dead_code_cleanup.yaml +0 -66
  335. package/core/dev/pipeline/archived/TASK_084_scheduler_sync_logic_fix.yaml +0 -68
  336. package/core/dev/pipeline/archived/TASK_085_http_api_web_mode_fixes.yaml +0 -46
  337. package/core/dev/pipeline/archived/TASK_085_memory_consolidation_0204_0205.yaml +0 -98
  338. package/core/dev/pipeline/archived/TASK_086_collapsible_preview_panel.yaml +0 -161
  339. package/core/dev/pipeline/archived/TASK_086_web_mode_feature_parity.yaml +0 -62
  340. package/core/dev/pipeline/archived/TASK_087_manual_memory_archival.yaml +0 -163
  341. package/core/dev/pipeline/archived/TASK_087_mobile_http_control.yaml +0 -68
  342. package/core/dev/pipeline/archived/TASK_088_fail_003_to_ppl_002_rule.yaml +0 -63
  343. package/core/dev/pipeline/archived/TASK_088_mobile_http_qa.yaml +0 -121
  344. package/core/dev/pipeline/archived/TASK_089_agent_collaboration_topic.yaml +0 -60
  345. package/core/dev/pipeline/archived/TASK_089_web_mode_monitor_fix.yaml +0 -58
  346. package/core/dev/pipeline/archived/TASK_090_cli_detection_fix.yaml +0 -61
  347. package/core/dev/pipeline/archived/TASK_090_ui_ux_lessons_topic.yaml +0 -63
  348. package/core/dev/pipeline/archived/TASK_091_expandable_card_design.yaml +0 -56
  349. package/core/dev/pipeline/archived/TASK_091_move_agent_section_components.yaml +0 -65
  350. package/core/dev/pipeline/archived/TASK_092_expandable_card_component.yaml +0 -60
  351. package/core/dev/pipeline/archived/TASK_092_refactor_agent_create_wizard.yaml +0 -84
  352. package/core/dev/pipeline/archived/TASK_093_taskcard_detail_integration.yaml +0 -57
  353. package/core/dev/pipeline/archived/TASK_093_update_barrel_exports_test.yaml +0 -84
  354. package/core/dev/pipeline/archived/TASK_094_ai_agent_generation_enhancement.yaml +0 -115
  355. package/core/dev/pipeline/archived/TASK_094_proposalcard_detail_integration.yaml +0 -55
  356. package/core/dev/pipeline/archived/TASK_095_preview_fixes.yaml +0 -129
  357. package/core/dev/pipeline/archived/TASK_095_single_agent_task_selector.yaml +0 -60
  358. package/core/dev/pipeline/archived/TASK_096_agents_page_ux_improvements.yaml +0 -163
  359. package/core/dev/pipeline/archived/TASK_096_single_agent_status_sync.yaml +0 -60
  360. package/core/dev/pipeline/archived/TASK_097_local_claude_error_logging.yaml +0 -59
  361. package/core/dev/pipeline/archived/TASK_097_single_agent_monitor_events.yaml +0 -59
  362. package/core/dev/pipeline/archived/TASK_098_product_manager_team_phase2.yaml +0 -60
  363. package/core/dev/pipeline/archived/TASK_098_single_agent_refactor_test.yaml +0 -58
  364. package/core/dev/pipeline/archived/TASK_099_backend_team_creation.yaml +0 -258
  365. package/core/dev/pipeline/archived/TASK_100_frontend_team_creation.yaml +0 -61
  366. package/core/dev/pipeline/archived/TASK_101_orchestrator_system.yaml +0 -403
  367. package/core/dev/pipeline/archived/TASK_102_designer_sre_teams.yaml +0 -90
  368. package/core/dev/pipeline/archived/TASK_103_integration_testing.yaml +0 -86
  369. package/core/dev/pipeline/archived/TASK_128_integration_testing.yaml +0 -83
  370. package/core/dev/pipeline/archived/TASK_134_cortex_v2_phase3_file_watcher.yaml +0 -65
  371. package/core/dev/pipeline/archived/TASK_134_team_http_api.yaml +0 -96
  372. package/core/dev/pipeline/archived/TASK_135_cortex_v2_phase3_git_monitor.yaml +0 -63
  373. package/core/dev/pipeline/archived/TASK_136_cortex_v2_phase3_change_analyzer.yaml +0 -69
  374. package/core/dev/pipeline/archived/TASK_137_cortex_v2_phase3_cache_invalidator.yaml +0 -67
  375. package/core/dev/pipeline/archived/TASK_137_team_orchestrator_integration.yaml +0 -93
  376. package/core/dev/pipeline/archived/TASK_138_cortex_v2_phase3_distributed_cache.yaml +0 -68
  377. package/core/dev/pipeline/archived/TASK_138_matrix_folder_structure.yaml +0 -61
  378. package/core/dev/pipeline/archived/TASK_139_cortex_v2_phase3_cache_prewarming.yaml +0 -65
  379. package/core/dev/pipeline/archived/TASK_146_unified_documentation.yaml +0 -141
  380. package/core/dev/pipeline/archived/TASK_147_pipeline_execution_rules.yaml +0 -76
  381. package/core/dev/pipeline/archived/TASK_148_update_claude_md.yaml +0 -77
  382. package/core/dev/pipeline/archived/TASK_149_fix_nextjs_mdx_serialization.yaml +0 -63
  383. package/core/dev/pipeline/archived/TASK_149_update_team_docs.yaml +0 -77
  384. package/core/dev/pipeline/archived/TASK_150_pipeline_workflow_guide.yaml +0 -82
  385. package/core/dev/pipeline/archived/TASK_164_fix_smart_context_switch.yaml +0 -49
  386. package/core/dev/pipeline/archived/TASK_165_ctags_symbol_index.yaml +0 -53
  387. package/core/dev/pipeline/archived/TASK_166_symbol_search_mcp_tool.yaml +0 -48
  388. package/core/dev/pipeline/archived/TASK_167_llm_module_summary.yaml +0 -55
  389. package/core/dev/pipeline/archived/TASK_168_map_reduce_context.yaml +0 -51
  390. package/core/dev/pipeline/archived/TASK_169_v2_integration_testing.yaml +0 -52
  391. package/core/dev/pipeline/backups/archive_2026-02-04T22-07-22/TASK_QUEUE_backup.json +0 -95
  392. package/core/dev/pipeline/docs/multi-agent-coordination.md +0 -446
  393. package/core/dev/pipeline/docs/overview.md +0 -406
  394. package/core/dev/pipeline/docs/task-management.md +0 -412
  395. package/core/dev/pipeline/docs/workflows.md +0 -32
  396. package/core/dev/pipeline/orchestrator/schemas/task_queue_schema.json +0 -58
  397. package/core/dev/pipeline/orchestrator/schemas/task_schema.json +0 -139
  398. package/core/dev/pipeline/plans/PLAN_task_cli_refactor.md +0 -281
  399. package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +0 -27
  400. package/core/dev/plans/ANSUZ_SLIM_PLAN.md +0 -68
  401. package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +0 -120
  402. package/core/dev/plans/FORMATION_RETIREMENT_PLAN.md +0 -313
  403. package/core/dev/plans/KUZU_REPLACEMENT_PLAN.md +0 -126
  404. package/core/dev/scripts/CRASH_DIAGNOSIS.md +0 -47
  405. package/core/dev/scripts/README.md +0 -142
  406. package/core/dev/scripts/RUN_ONCE.bat +0 -40
  407. package/core/dev/scripts/START_DEV_AGENT.bat +0 -63
  408. package/core/dev/scripts/START_MULTI_AGENT.bat +0 -80
  409. package/core/dev/scripts/dev_agent_scheduler.ps1 +0 -536
  410. package/core/dev/scripts/init_git.ps1 +0 -396
  411. package/core/dev/scripts/init_git.sh +0 -337
  412. package/core/dev/scripts/init_project.ps1 +0 -851
  413. package/core/dev/scripts/init_project.sh +0 -477
  414. package/core/dev/scripts/multi_agent_scheduler.ps1 +0 -2021
  415. package/core/dev/scripts/multi_agent_scheduler.sh +0 -1344
  416. package/core/dev/scripts/run_once.sh +0 -47
  417. package/core/dev/scripts/setup_rag.ps1 +0 -304
  418. package/core/dev/scripts/setup_rag.sh +0 -230
  419. package/core/dev/scripts/start_dev_agent.sh +0 -137
  420. package/core/dev/scripts/start_multi_agent.sh +0 -41
  421. package/core/dev/scripts/sync_task_queue.ps1 +0 -505
  422. package/core/dev/scripts/sync_task_queue.sh +0 -392
  423. package/core/dev/specs/agent-connect/PROTOCOL.md +0 -177
  424. package/core/dev/specs/agent-connect/SDK_GUIDE.md +0 -915
  425. package/core/dev/specs/agent-connect/examples/minimal-site/README.md +0 -137
  426. package/core/dev/specs/agent-connect/examples/minimal-site/package.json +0 -12
  427. package/core/dev/specs/agent-connect/examples/minimal-site/public/index.html +0 -116
  428. package/core/dev/specs/agent-connect/examples/minimal-site/public/install-redirect.js +0 -73
  429. package/core/dev/specs/agent-connect/examples/minimal-site/server.js +0 -516
  430. package/core/dev/specs/agent-connect/examples/minimal-site/test/smoke.test.mjs +0 -263
  431. package/core/dev/specs/agent-connect/history/DEFECT_REPORT_kenaz-agent-hub_2026-09-09_v0.4-empty-prompt-no-first-turn.md +0 -77
  432. package/core/dev/specs/agent-connect/history/DEFECT_REPORT_kenaz-agent-hub_2026-09-09_v0.4-prompt-mojibake.md +0 -90
  433. package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-07.md +0 -61
  434. package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-08_relay-v1-conformance.md +0 -76
  435. package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-09_v0.4-session-channel.md +0 -87
  436. package/core/dev/specs/agent-connect/history/FLOWPIX_REPLY_agent-hub_2026-09-08_relay-v1-answers.md +0 -68
  437. package/core/dev/specs/agent-connect/history/FLOWPIX_bridge_CONTRACT.md +0 -642
  438. package/core/dev/specs/agent-connect/history/FLOWPIX_spikes_relay_spike_relay.mjs +0 -244
  439. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-07.md +0 -77
  440. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_direction-change-v0.3.md +0 -44
  441. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_relay-v1-answers-ack.md +0 -37
  442. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_relay-v1-conformance.md +0 -56
  443. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_annotations-install-coldstart.md +0 -44
  444. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_empty-prompt-first-turn.md +0 -56
  445. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_prompt-mojibake-fixed.md +0 -48
  446. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_session-channel.md +0 -57
  447. package/core/dev/specs/agent-connect/history/PROTOCOL_v0.3_snapshot.md +0 -379
  448. package/core/dev/specs/agent-connect/history/README.md +0 -47
  449. package/core/dev/specs/peer/CODEX_INTERACTIVE_PUSH.md +0 -65
  450. package/core/dev/specs/peer/CODEX_MANAGED_DAEMON.md +0 -54
  451. package/core/dev/specs/peer/CODEX_PEER_PULL_SPIKE.md +0 -54
  452. package/core/dev/specs/peer/MODS_SPIKE.md +0 -133
  453. package/core/dev/specs/peer/PER_PROJECT_ADDRESSING.md +0 -143
  454. package/core/dev/specs/peer/SWARM_PROTOCOLS.md +0 -256
  455. package/core/dev/specs/peer/codex-hook-shim-peer.test.js +0 -113
  456. package/core/dev/specs/peer/codex-push-launcher.test.js +0 -142
  457. package/core/dev/specs/peer/setup-codex-hooks.test.js +0 -60
  458. package/core/dev/specs/peer-ansuz/DELIVERY_SPIKE.md +0 -397
  459. package/core/dev/specs/peer-ansuz/DESIGN.md +0 -109
  460. package/core/dev/specs/peer-ansuz/spike/claude_poke.js +0 -129
  461. package/core/dev/specs/peer-ansuz/spike/claude_session_start_hook.js +0 -37
  462. package/core/dev/specs/peer-ansuz/spike/claude_settings_hook.json +0 -14
  463. package/core/dev/specs/peer-ansuz/spike/codex_appserver_client.js +0 -105
  464. package/core/dev/specs/peer-ansuz/spike/codex_appserver_query.js +0 -29
  465. package/core/dev/specs/peer-ansuz/spike/extract_timeline.js +0 -26
  466. package/core/dev/specs/synapse/SYMBOL_IDENTITY.md +0 -305
  467. package/core/dev/spikes/A4-cypher-varlen.md +0 -176
  468. package/core/dev/spikes/bloat-rounds.sh +0 -25
  469. package/core/dev/spikes/mods/clean-claude.js +0 -9
  470. package/core/dev/spikes/mods/drive-probe.js +0 -29
  471. package/core/dev/spikes/mods/e2e-peer.js +0 -71
  472. package/core/dev/spikes/mods/evidence/e2e-peer-control.json +0 -17
  473. package/core/dev/spikes/mods/evidence/e2e-peer-mod.json +0 -17
  474. package/core/dev/spikes/mods/evidence/probe-006a6e65-d807-4bc1-b648-fd7d1edfe0b4.jsonl +0 -4
  475. package/core/dev/spikes/mods/evidence/probe-cebdf1f7-f9c4-47a8-83cd-7c0786177404.jsonl +0 -12
  476. package/core/dev/spikes/mods/evidence/probe-eb21147d-10ce-4c32-93ed-37ca37d88d5a.jsonl +0 -11
  477. package/core/dev/spikes/mods/evidence/stream-cebdf1f7.jsonl +0 -18
  478. package/core/dev/spikes/mods/kenaz-mod-probe/.claude-plugin/plugin.json +0 -1
  479. package/core/dev/spikes/mods/kenaz-mod-probe/hooks/hooks.json +0 -1
  480. package/core/dev/spikes/mods/kenaz-mod-probe/hooks/register.ts +0 -65
  481. package/core/dev/spikes/mods/kenaz-mod-probe/tsconfig.json +0 -3
@@ -0,0 +1,471 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // TASK_2367: CLI-first install. node install-kenaz-cli.js <install|uninstall|status> [flags]
4
+ // --dry-run print the plan, change nothing
5
+ // --home <dir> user home (default os.homedir(); tests use a temp dir)
6
+ // --core-dir <dir> kenaz-core root holding plugins/kenaz (default KENAZ_CORE_DIR or repo root)
7
+ // --server-bin <p> kenaz-server binary (default: first existing candidate)
8
+ // --no-service register the plugin only
9
+ // --no-plugin install the service only
10
+ // Plugin registration mirrors dashboard/src-tauri/src/features/project/peer_mod.rs (same env var,
11
+ // merge-never-replace, one-time backup, atomic write, CLI >= 2.1.287 gate, KENAZ_SKIP_CLAUDE_PROVISION opt-out).
12
+ // thrift: duplicate of the Rust logic until it is unified (see dev/plans/CLI_FIRST_INSTALL_FLOW.md).
13
+ const fs = require('fs');
14
+ const os = require('os');
15
+ const path = require('path');
16
+ const http = require('http');
17
+ const { spawnSync } = require('child_process');
18
+
19
+ const ENV_KEY = 'CLAUDE_CODE_PLUGIN_DIRS';
20
+ const BACKUP = 'settings.json.bak-kenaz-peer'; // shared with the Rust path on purpose: one backup of the pristine file
21
+ const MIN_CLI = [2, 1, 287];
22
+ const SEP = process.platform === 'win32' ? ';' : ':';
23
+ const TASK_NAME = 'KenazServer';
24
+ const LABEL = 'ai.kenaz.server';
25
+ const BATTERY_PS = '$s = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero); Set-ScheduledTask -TaskName ' + TASK_NAME + ' -Settings $s';
26
+ const linuxUser = () => { try { return os.userInfo().username; } catch (_) { return process.env.USER || ''; } };
27
+
28
+ const norm = (e) => {
29
+ const s = String(e).trim().replace(/\\/g, '/').replace(/\/+$/, '');
30
+ return process.platform === 'win32' ? s.toLowerCase() : s;
31
+ };
32
+ const parseVersion = (s) => { const m = /(\d+)\.(\d+)\.(\d+)/.exec(s || ''); return m ? m.slice(1, 4).map(Number) : null; };
33
+ const cmpVersion = (a, b) => { for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] - b[i]; return 0; };
34
+
35
+ function loadSettings(file) {
36
+ let txt;
37
+ try { txt = fs.readFileSync(file, 'utf8'); } catch (e) { if (e.code === 'ENOENT') return {}; throw new Error('cannot read settings.json: ' + e.message); }
38
+ if (!txt.trim()) return {};
39
+ let v;
40
+ try { v = JSON.parse(txt); } catch (e) { throw new Error('settings.json is not valid JSON (' + e.message + '); left untouched'); }
41
+ if (!v || typeof v !== 'object' || Array.isArray(v)) throw new Error('settings.json is not a JSON object; left untouched');
42
+ return v;
43
+ }
44
+ function entriesOf(v) {
45
+ if (v.env === undefined) return [];
46
+ if (!v.env || typeof v.env !== 'object' || Array.isArray(v.env)) throw new Error('settings.json `env` is not an object; left untouched');
47
+ const s = v.env[ENV_KEY];
48
+ if (s === undefined) return [];
49
+ if (typeof s !== 'string') throw new Error('env.' + ENV_KEY + ' is not a string; left untouched');
50
+ return s.split(SEP).map((x) => x.trim()).filter(Boolean);
51
+ }
52
+ function saveSettings(file, v) {
53
+ fs.mkdirSync(path.dirname(file), { recursive: true });
54
+ const bak = path.join(path.dirname(file), BACKUP);
55
+ if (fs.existsSync(file) && !fs.existsSync(bak)) fs.copyFileSync(file, bak);
56
+ const tmp = file + '.kenaz-tmp';
57
+ fs.writeFileSync(tmp, JSON.stringify(v, null, 2));
58
+ fs.renameSync(tmp, file);
59
+ }
60
+ function setEntries(v, list) {
61
+ v.env = v.env || {};
62
+ if (list.length) v.env[ENV_KEY] = list.join(SEP); else delete v.env[ENV_KEY];
63
+ }
64
+ const isWorktreePath = (d) => /(^|\/)\.kenaz-worktrees(\/|$)/.test(norm(d));
65
+ // TASK_2384: a stale copy is another checkout of the same plugin (.../plugins/<name>): release core, worktree.
66
+ const staleCopy = (e, dir) => norm(e) !== norm(dir) && norm(e).endsWith('/plugins/' + norm(dir).split('/').pop());
67
+ /** One path per Kenaz plugin: older copies are replaced in place, never appended. A worktree checkout is
68
+ * never registered in user settings. @returns 'added'|'already'|'replaced' */
69
+ function registerPlugin(file, dir, dry) {
70
+ if (isWorktreePath(dir)) throw new Error('refusing to register a .kenaz-worktrees path in user settings: ' + dir);
71
+ const v = loadSettings(file), list = entriesOf(v);
72
+ const mine = (e) => norm(e) === norm(dir) || staleCopy(e, dir);
73
+ const has = list.some((e) => norm(e) === norm(dir));
74
+ if (has && list.filter(mine).length === 1) return 'already';
75
+ const first = list.findIndex(mine);
76
+ if (first < 0) { if (!dry) { list.push(dir); setEntries(v, list); saveSettings(file, v); } return 'added'; }
77
+ const next = list.map((e, i) => (i === first ? dir : e)).filter((e, i) => i === first || !mine(e));
78
+ if (!dry) { setEntries(v, next); saveSettings(file, v); }
79
+ return 'replaced';
80
+ }
81
+ /** @returns 'removed'|'absent' */
82
+ function unregisterPlugin(file, dir, dry) {
83
+ if (!fs.existsSync(file)) return 'absent';
84
+ const v = loadSettings(file), list = entriesOf(v);
85
+ const kept = list.filter((e) => norm(e) !== norm(dir));
86
+ if (kept.length === list.length) return 'absent';
87
+ if (!dry) { setEntries(v, kept); saveSettings(file, v); }
88
+ return 'removed';
89
+ }
90
+
91
+ // Status line with the project's task counts (plugins/kenaz/scripts/statusline.js). Set only when the user has no
92
+ // statusLine; a user's own is never replaced. Ours is recognised by the script path, so a moved core is updated.
93
+ const STATUSLINE_TAIL = 'plugins/kenaz/scripts/statusline.js';
94
+ const isOurStatusline = (s) => !!(s && typeof s.command === 'string' && norm(s.command).includes(STATUSLINE_TAIL));
95
+ /** @returns 'added'|'already'|'replaced'|'kept-user' */
96
+ function registerStatusline(file, core, dry) {
97
+ const v = loadSettings(file);
98
+ const command = 'node "' + path.join(core, ...STATUSLINE_TAIL.split('/')) + '"';
99
+ if (v.statusLine && !isOurStatusline(v.statusLine)) return 'kept-user';
100
+ if (v.statusLine && norm(v.statusLine.command) === norm(command)) return 'already';
101
+ const r = v.statusLine ? 'replaced' : 'added';
102
+ if (!dry) { v.statusLine = { type: 'command', command }; saveSettings(file, v); }
103
+ return r;
104
+ }
105
+ /** @returns 'removed'|'absent' */
106
+ function unregisterStatusline(file, dry) {
107
+ if (!fs.existsSync(file)) return 'absent';
108
+ const v = loadSettings(file);
109
+ if (!isOurStatusline(v.statusLine)) return 'absent';
110
+ if (!dry) { delete v.statusLine; saveSettings(file, v); }
111
+ return 'removed';
112
+ }
113
+
114
+ // TASK_2384: Dashboard-era user-level hooks (wrapped by .claude/hooks/_user-scope.js, direct .claude/hooks/*.js, or the
115
+ // agent-flow curl/node posts) duplicate what plugins/kenaz/hooks/hooks.json now runs. Single owner = the plugin, so
116
+ // once it is registered these are removed (first write backs up the pristine file to settings.json.bak-kenaz-peer).
117
+ const DASH_HOOK = /_user-scope\.js|[\\/]\.claude[\\/]hooks[\\/][\w.-]+\.js|\/api\/rust\/agent-flow\/hook/;
118
+ /** @returns number of hook commands retired */
119
+ function retireDashboardHooks(file, dry) {
120
+ if (!fs.existsSync(file)) return 0;
121
+ const v = loadSettings(file);
122
+ if (!v.hooks || typeof v.hooks !== 'object' || Array.isArray(v.hooks)) return 0;
123
+ let n = 0;
124
+ for (const ev of Object.keys(v.hooks)) {
125
+ if (!Array.isArray(v.hooks[ev])) continue;
126
+ const groups = [];
127
+ for (const g of v.hooks[ev]) {
128
+ if (!g || !Array.isArray(g.hooks)) { groups.push(g); continue; }
129
+ const kept = g.hooks.filter((h) => !(h && typeof h.command === 'string' && DASH_HOOK.test(h.command)));
130
+ n += g.hooks.length - kept.length;
131
+ if (kept.length) groups.push(kept.length === g.hooks.length ? g : { ...g, hooks: kept });
132
+ }
133
+ if (groups.length) v.hooks[ev] = groups; else delete v.hooks[ev];
134
+ }
135
+ if (n && !dry) { if (!Object.keys(v.hooks).length) delete v.hooks; saveSettings(file, v); }
136
+ return n;
137
+ }
138
+
139
+ function claudeVersion() {
140
+ const r = process.platform === 'win32' // one string with shell (.cmd shim): args + shell:true is deprecated (DEP0190)
141
+ ? spawnSync('claude --version', { windowsHide: true, encoding: 'utf8', shell: true, timeout: 15000 })
142
+ : spawnSync('claude', ['--version'], { windowsHide: true, encoding: 'utf8', timeout: 15000 });
143
+ return r.status === 0 ? parseVersion(r.stdout) : null;
144
+ }
145
+
146
+ // ---- service definitions (pure; the OS calls are in service* below) ----
147
+ function launcherPaths(home) {
148
+ const d = path.join(home, '.kenaz');
149
+ return { dir: d, vbs: path.join(d, 'kenaz-server.vbs'), log: path.join(d, 'kenaz-server.log') };
150
+ }
151
+ const q = (s) => '"' + s + '"';
152
+ function winVbs(bin, core) { // hidden window, env for task tools; server binds loopback by default
153
+ return 'Set sh = WScript.CreateObject("WScript.Shell")\r\nsh.Environment("Process")("KENAZ_SOURCE_DIR") = "' + core + '"\r\n' +
154
+ 'sh.Run """' + bin + '""", 0, False\r\n';
155
+ }
156
+ // launchd and systemd start the server with a bare PATH (/usr/bin:/bin...), so `node` from Homebrew,
157
+ // nvm or /usr/local was not found and every MCP tool that runs a node script failed with
158
+ // "No such file or directory" (field report, macOS 0.3.1). Put the node running this install first.
159
+ function servicePath(nodeExe = process.execPath) {
160
+ return [path.dirname(nodeExe), '/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin']
161
+ .filter((d, i, a) => a.indexOf(d) === i).join(':');
162
+ }
163
+ function launchdPlist(bin, core, log, nodeExe) {
164
+ return '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict>\n' +
165
+ ' <key>Label</key><string>' + LABEL + '</string>\n <key>ProgramArguments</key><array><string>' + bin + '</string></array>\n' +
166
+ ' <key>EnvironmentVariables</key><dict><key>KENAZ_SOURCE_DIR</key><string>' + core + '</string><key>PATH</key><string>' + servicePath(nodeExe) + '</string></dict>\n' +
167
+ ' <key>RunAtLoad</key><true/>\n <key>KeepAlive</key><true/>\n <key>StandardOutPath</key><string>' + log + '</string>\n <key>StandardErrorPath</key><string>' + log + '</string>\n</dict></plist>\n';
168
+ }
169
+ function systemdUnit(bin, core, nodeExe) {
170
+ return '[Unit]\nDescription=Kenaz server (headless backend)\nAfter=network.target\n\n[Service]\nExecStart=' + bin + '\nEnvironment=KENAZ_SOURCE_DIR=' + core + '\nEnvironment=PATH=' + servicePath(nodeExe) + '\nRestart=on-failure\n\n[Install]\nWantedBy=default.target\n';
171
+ }
172
+
173
+ // TASK_2413: no usable systemd user session (containers, WSL1, minimal servers) -> `systemctl --user enable --now`
174
+ // silently does nothing. Fall back to an @reboot crontab entry ('cron') or, without crontab, a manual start ('manual').
175
+ const CRON_MARK = '# kenaz-server (managed by kenaz install)';
176
+ const shq = (v) => "'" + String(v).replace(/'/g, "'\\''") + "'";
177
+ function cronStartCmd(bin, core, log, nodeExe) {
178
+ return 'KENAZ_SOURCE_DIR=' + shq(core) + ' PATH=' + shq(servicePath(nodeExe)) + ' ' + shq(bin) + ' >> ' + shq(log) + ' 2>&1';
179
+ }
180
+ function cronLine(bin, core, log, nodeExe) { // % is special in crontab
181
+ return '@reboot ' + cronStartCmd(bin, core, log, nodeExe).replace(/%/g, '\\%') + ' ' + CRON_MARK;
182
+ }
183
+ // stop exactly this binary (never by process name: another install's kenaz-server must survive). An upgraded
184
+ // process still runs the unlinked inode, which /proc reports as "<path> (deleted)".
185
+ function stopCmd(plan) {
186
+ const b = shq(plan.bin);
187
+ return 'for p in $(pgrep -x kenaz-server); do e=$(readlink /proc/$p/exe); ' +
188
+ '[ "$e" = ' + b + ' ] || [ "$e" = ' + shq(plan.bin + ' (deleted)') + ' ] && kill $p; done; true';
189
+ }
190
+ // start now, detached from the installer (new session when setsid exists), output to the log
191
+ function detachedStartCmd(plan) {
192
+ return 'if command -v setsid >/dev/null 2>&1; then S=setsid; else S=; fi; ' +
193
+ 'KENAZ_SOURCE_DIR=' + shq(plan.core) + ' PATH=' + shq(servicePath()) + ' $S nohup ' + shq(plan.bin) + ' >> ' + shq(plan.log) + ' 2>&1 < /dev/null &';
194
+ }
195
+
196
+ function servicePlan(home, bin, core, platform = process.platform, caps = { systemd: true }) {
197
+ const L = launcherPaths(home);
198
+ if (platform === 'win32') {
199
+ const roaming = home === os.homedir() && process.env.APPDATA ? process.env.APPDATA : path.join(home, 'AppData', 'Roaming');
200
+ const startup = path.join(roaming, 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup', 'KenazServer.vbs');
201
+ return { kind: 'windows', files: [[L.vbs, winVbs(bin, core)]], fallbackFile: startup };
202
+ }
203
+ if (platform === 'darwin') {
204
+ const p = path.join(home, 'Library', 'LaunchAgents', LABEL + '.plist');
205
+ return { kind: 'launchd', files: [[p, launchdPlist(bin, core, L.log)]], unit: p };
206
+ }
207
+ if (caps.systemd === false) {
208
+ return { kind: caps.crontab ? 'cron' : 'manual', files: [], bin, core, log: L.log, cronLine: cronLine(bin, core, L.log) };
209
+ }
210
+ const p = path.join(home, '.config', 'systemd', 'user', 'kenaz-server.service');
211
+ return { kind: 'systemd', files: [[p, systemdUnit(bin, core)]], unit: p };
212
+ }
213
+ const sh = (cmd, args, opts) => spawnSync(cmd, args, { windowsHide: true, encoding: 'utf8', ...(opts || {}) });
214
+
215
+ // crontab -l: null = no crontab binary; '' = binary present, no crontab yet. All through the injected `run`.
216
+ function readCron(run) {
217
+ const r = run('crontab', ['-l']);
218
+ if (!r || r.error || r.status === 127) return null;
219
+ return r.status === 0 ? String(r.stdout || '') : '';
220
+ }
221
+ function probeLinux(run) {
222
+ const r = run('systemctl', ['--user', 'show-environment']);
223
+ const systemd = !!r && !r.error && r.status === 0;
224
+ return { systemd, crontab: systemd ? undefined : readCron(run) !== null };
225
+ }
226
+ function writeCron(run, current, line) { // line null = remove our entry; keeps every other line; idempotent
227
+ const keep = current.split('\n').filter((l) => l.trim() && !l.includes(CRON_MARK));
228
+ const next = line ? keep.concat(line) : keep;
229
+ const text = next.length ? next.join('\n') + '\n' : '';
230
+ if (text === current) return false;
231
+ if (text) run('crontab', ['-'], { input: text }); else run('crontab', ['-r']);
232
+ return true;
233
+ }
234
+
235
+ // TASK_2418: loading/enabling a unit that is already loaded/active is a no-op, so an upgrade must restart it
236
+ // explicitly, after the new binary is in place. launchd KeepAlive respawns the OLD file in the window between
237
+ // stopOwnServer() and the copy (the process then keeps the unlinked inode); systemd `enable --now` does not
238
+ // restart an active unit. restartHint is also the command printed when the version check fails.
239
+ const defaultUid = () => (typeof process.getuid === 'function' ? process.getuid() : 501);
240
+ function restartHint(plan, uid = defaultUid()) {
241
+ if (plan.kind === 'launchd') return 'launchctl kickstart -k gui/' + uid + '/' + LABEL;
242
+ if (plan.kind === 'systemd') return 'systemctl --user restart kenaz-server';
243
+ if (plan.kind === 'cron' || plan.kind === 'manual') return stopCmd(plan) + '; ' + detachedStartCmd(plan);
244
+ // the launcher works for both the scheduled task and the Startup-folder fallback (no admin rights needed)
245
+ return 'end kenaz-server.exe in Task Manager, then run: wscript.exe ' + q(plan.files && plan.files[0] ? plan.files[0][0] : 'kenaz-server.vbs');
246
+ }
247
+ function serviceRestart(plan, dry, log, run = sh, uid = defaultUid()) {
248
+ if (plan.kind === 'cron' || plan.kind === 'manual') { // no supervisor: start it (stopOwnServer freed the old one)
249
+ log('start now (detached, log ' + plan.log + ')');
250
+ if (!dry) { fs.mkdirSync(path.dirname(plan.log), { recursive: true }); run('sh', ['-c', detachedStartCmd(plan)]); }
251
+ return;
252
+ }
253
+ if (plan.kind === 'windows') return; // /Create + /Run above start the just-copied exe (stopOwnServer freed it)
254
+ log(restartHint(plan, uid));
255
+ if (dry) return;
256
+ if (plan.kind === 'launchd') {
257
+ const r = run('launchctl', ['kickstart', '-k', 'gui/' + uid + '/' + LABEL]);
258
+ if (r && r.status !== 0) { // old macOS / no GUI session domain: unload + load restarts it the long way
259
+ log('kickstart failed (exit ' + r.status + '), falling back to launchctl unload + load');
260
+ run('launchctl', ['unload', plan.unit]); run('launchctl', ['load', '-w', plan.unit]);
261
+ }
262
+ } else run('systemctl', ['--user', 'restart', 'kenaz-server']);
263
+ }
264
+
265
+ /** One GET /api/health; resolves { ok, version }. */
266
+ function healthOnce(port, getFn = http.get) {
267
+ return new Promise((resolve) => {
268
+ const req = getFn({ host: '127.0.0.1', port, path: '/api/health', timeout: 2000 }, (res) => {
269
+ let b = ''; res.on('data', (d) => { b += d; });
270
+ res.on('end', () => { let v = null; try { v = JSON.parse(b).version || null; } catch (_) { /* body optional */ } resolve({ ok: res.statusCode < 500, version: v }); });
271
+ });
272
+ req.on('timeout', () => { req.destroy(); resolve({ ok: false, version: null }); });
273
+ req.on('error', () => resolve({ ok: false, version: null }));
274
+ });
275
+ }
276
+ /**
277
+ * TASK_2418: after the service (re)start, ask the running server for its version and compare with the one just
278
+ * installed. Retries (the server needs a moment to bind). Returns 'ok' | 'mismatch' | 'unreachable'; the last two
279
+ * print a warning with the exact restart command and do not fail the install.
280
+ */
281
+ async function verifyRuntime(plan, expected, log, opts = {}) {
282
+ const port = opts.port || process.env.KENAZ_HTTP_PORT || '18080';
283
+ const tries = opts.tries || 6, delay = opts.delayMs === undefined ? 1000 : opts.delayMs;
284
+ const probe = opts.health || ((p) => healthOnce(p));
285
+ let last = { ok: false, version: null };
286
+ for (let i = 0; i < tries; i++) {
287
+ last = await probe(port);
288
+ if (last.ok && last.version === expected) { log('server: running version ' + expected + ' on :' + port); return 'ok'; }
289
+ if (i < tries - 1 && delay) await new Promise((r) => setTimeout(r, delay));
290
+ }
291
+ const hint = restartHint(plan, opts.uid);
292
+ if (last.ok && last.version) {
293
+ log('WARNING: the server on :' + port + ' still reports version ' + last.version + ' but ' + expected + ' was just installed. Restart it: ' + hint);
294
+ return 'mismatch';
295
+ }
296
+ log('WARNING: could not confirm the new server (' + expected + ') on 127.0.0.1:' + port + (last.ok ? ' (no version in /api/health)' : ' (no answer)') + '. If it is not running: ' + hint);
297
+ return 'unreachable';
298
+ }
299
+
300
+ function serviceInstall(plan, dry, log, run = sh, uid) {
301
+ const sh = run; // injectable for tests; shadows the module-level spawnSync wrapper inside this function
302
+ for (const [f, body] of plan.files) { log('write ' + f); if (!dry) { fs.mkdirSync(path.dirname(f), { recursive: true }); fs.writeFileSync(f, body); } }
303
+ if (plan.kind === 'windows') {
304
+ const tr = 'wscript.exe ' + q(plan.files[0][0]);
305
+ log('schtasks /Create /TN ' + TASK_NAME + ' /SC ONLOGON /TR ' + tr + ' /F (fallback: ' + plan.fallbackFile + ')');
306
+ if (dry) return;
307
+ const r = sh('schtasks', ['/Create', '/TN', TASK_NAME, '/SC', 'ONLOGON', '/TR', tr, '/F']);
308
+ if (r.status === 0) {
309
+ // Task Scheduler defaults refuse to start on battery and stop the task when going on battery:
310
+ // a laptop unplugged at logon never got the server (TASK_2468). Re-running install fixes an old task.
311
+ const b = sh('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', BATTERY_PS]);
312
+ if (b.status !== 0) log('WARNING: could not set the task to run on battery (exit ' + b.status + '); on a laptop the server may not start unplugged. Run in PowerShell: ' + BATTERY_PS);
313
+ sh('schtasks', ['/Run', '/TN', TASK_NAME]);
314
+ } else { // ONLOGON tasks can need elevation: a Startup-folder launcher is the per-user equivalent
315
+ fs.mkdirSync(path.dirname(plan.fallbackFile), { recursive: true });
316
+ fs.copyFileSync(plan.files[0][0], plan.fallbackFile);
317
+ sh('wscript.exe', [plan.files[0][0]]);
318
+ // schtasks' stderr is in the console's OEM code page (garbled when echoed); the usual cause is
319
+ // missing admin rights for /SC ONLOGON. The Startup-folder launcher starts at logon the same way.
320
+ log('schtasks needs administrator rights here (exit ' + r.status + '); installed the Startup-folder launcher instead (also starts at logon)');
321
+ }
322
+ } else if (plan.kind === 'launchd') {
323
+ log('launchctl load -w ' + plan.unit); if (!dry) sh('launchctl', ['load', '-w', plan.unit]);
324
+ serviceRestart(plan, dry, log, sh, uid);
325
+ } else if (plan.kind === 'cron' || plan.kind === 'manual') {
326
+ if (plan.kind === 'cron') {
327
+ log('crontab: @reboot entry ' + plan.cronLine);
328
+ if (!dry) { const cur = readCron(sh); if (cur !== null) writeCron(sh, cur, plan.cronLine); }
329
+ } else {
330
+ log('WARNING: no systemd user session and no crontab here. Add this to your own init/boot script to start the server at boot: ' + cronStartCmd(plan.bin, plan.core, plan.log));
331
+ }
332
+ serviceRestart(plan, dry, log, sh, uid);
333
+ } else {
334
+ log('systemctl --user enable --now kenaz-server');
335
+ if (!dry) { sh('systemctl', ['--user', 'daemon-reload']); sh('systemctl', ['--user', 'enable', '--now', 'kenaz-server']); }
336
+ // Without linger the user service starts only at login (headless boxes: never). Best effort.
337
+ const u = linuxUser();
338
+ log('loginctl enable-linger ' + u);
339
+ if (!dry && sh('loginctl', ['enable-linger', u]).status !== 0) log('WARNING: could not enable linger; the server starts at boot only after you log in. Run: loginctl enable-linger ' + u + ' (may need sudo)');
340
+ serviceRestart(plan, dry, log, sh, uid);
341
+ }
342
+ }
343
+ function serviceUninstall(plan, dry, log, run = sh) {
344
+ if (plan.kind === 'windows') {
345
+ log('schtasks /End + /Delete /TN ' + TASK_NAME);
346
+ if (!dry) { run('schtasks', ['/End', '/TN', TASK_NAME]); run('schtasks', ['/Delete', '/TN', TASK_NAME, '/F']); }
347
+ if (fs.existsSync(plan.fallbackFile)) { log('remove ' + plan.fallbackFile); if (!dry) fs.rmSync(plan.fallbackFile, { force: true }); }
348
+ } else if (plan.kind === 'launchd') {
349
+ log('launchctl unload ' + plan.unit); if (!dry) run('launchctl', ['unload', '-w', plan.unit]);
350
+ } else if (plan.kind === 'cron' || plan.kind === 'manual') {
351
+ if (plan.kind === 'cron') { log('crontab: remove the kenaz-server @reboot entry'); if (!dry) { const cur = readCron(run); if (cur !== null) writeCron(run, cur, null); } }
352
+ log('stop ' + plan.bin); if (!dry) run('sh', ['-c', stopCmd(plan)]);
353
+ } else {
354
+ log('systemctl --user disable --now kenaz-server'); if (!dry) run('systemctl', ['--user', 'disable', '--now', 'kenaz-server']);
355
+ }
356
+ for (const [f] of plan.files) if (fs.existsSync(f)) { log('remove ' + f); if (!dry) fs.rmSync(f, { force: true }); }
357
+ }
358
+ function serviceStatus(plan, run = sh) {
359
+ if (plan.kind === 'cron') { const c = readCron(run); return c && c.includes(CRON_MARK) ? 'cron @reboot entry present' : 'not installed'; }
360
+ if (plan.kind === 'manual') return 'manual (no systemd user session, no crontab)';
361
+ if (plan.kind === 'windows') return run('schtasks', ['/Query', '/TN', TASK_NAME]).status === 0 ? 'scheduled task present' : (fs.existsSync(plan.fallbackFile) ? 'startup launcher present' : 'not installed');
362
+ if (plan.kind === 'systemd') return run('systemctl', ['--user', 'is-active', 'kenaz-server']).stdout.trim() || 'not installed';
363
+ return fs.existsSync(plan.unit) ? 'plist present' : 'not installed';
364
+ }
365
+
366
+ /** Which mechanism keeps the server running on this machine (for `kenaz doctor`). Read-only, all via `run`. */
367
+ function detectServiceMode(home, core, run = sh, platform = process.platform) {
368
+ if (platform === 'win32') {
369
+ const plan = servicePlan(home, '', core, 'win32');
370
+ if (run('schtasks', ['/Query', '/TN', TASK_NAME]).status === 0) return { mode: 'windows task', detail: 'scheduled task ' + TASK_NAME };
371
+ if (fs.existsSync(plan.fallbackFile)) return { mode: 'startup launcher', detail: plan.fallbackFile };
372
+ return { mode: 'manual', detail: 'no scheduled task or Startup launcher; run `kenaz install`' };
373
+ }
374
+ if (platform === 'darwin') {
375
+ const p = servicePlan(home, '', core, 'darwin').unit;
376
+ return fs.existsSync(p) ? { mode: 'launchd', detail: p } : { mode: 'manual', detail: 'no launchd agent; run `kenaz install`' };
377
+ }
378
+ const unit = servicePlan(home, '', core, 'linux').unit;
379
+ if (fs.existsSync(unit) && probeLinux(run).systemd) {
380
+ const u = linuxUser();
381
+ const off = /Linger=no/.test(run('loginctl', ['show-user', u, '-p', 'Linger']).stdout || '');
382
+ return off ? { mode: 'systemd', detail: unit + '; linger is off, the server starts only after login: run loginctl enable-linger ' + u, warn: true } : { mode: 'systemd', detail: unit };
383
+ }
384
+ const c = readCron(run);
385
+ if (c && c.includes(CRON_MARK)) return { mode: 'cron', detail: '@reboot crontab entry (no systemd user session)' };
386
+ return { mode: 'manual', detail: 'no systemd unit or cron entry; run `kenaz install`' };
387
+ }
388
+
389
+ function parseArgs(argv) {
390
+ const o = { cmd: argv[0], flags: {} };
391
+ for (let i = 1; i < argv.length; i++) {
392
+ const a = argv[i];
393
+ if (!a.startsWith('--')) continue;
394
+ const k = a.slice(2);
395
+ if (['home', 'core-dir', 'server-bin'].includes(k)) o.flags[k] = argv[++i]; else o.flags[k] = true;
396
+ }
397
+ return o;
398
+ }
399
+ function findServerBin(core, home) {
400
+ const exe = process.platform === 'win32' ? 'kenaz-server.exe' : 'kenaz-server';
401
+ const c = [path.join(core, '..', exe), path.join(core, 'bin', exe), path.join(core, 'dashboard', 'src-tauri', 'target', 'release', exe), path.join(home, '.kenaz', 'bin', exe)];
402
+ return c.map((p) => path.resolve(p)).find((p) => fs.existsSync(p)) || null;
403
+ }
404
+
405
+ function main(argv, out = console.log, getVersion = claudeVersion, deps = {}) { // getVersion, deps {sh, platform, uid, onPlan}: injectable for tests
406
+ const { cmd, flags } = parseArgs(argv);
407
+ if (!['install', 'uninstall', 'status'].includes(cmd)) {
408
+ out('usage: install-kenaz-cli.js <install|uninstall|status> [--dry-run] [--home d] [--core-dir d] [--server-bin p] [--no-service] [--no-plugin]');
409
+ return 2;
410
+ }
411
+ const dry = !!flags['dry-run'];
412
+ const home = path.resolve(flags.home || os.homedir());
413
+ const core = path.resolve(flags['core-dir'] || process.env.KENAZ_CORE_DIR || path.join(__dirname, '..', '..'));
414
+ // TASK_2380: kenaz-peer (idle push) rides along; same gate and merge rules as plugins/kenaz.
415
+ const pluginDirs = ['kenaz', 'kenaz-peer'].map((n) => path.join(core, 'plugins', n));
416
+ const settings = path.join(home, '.claude', 'settings.json');
417
+ const log = (m) => out((dry ? '[dry-run] ' : '') + m);
418
+ let rc = 0;
419
+
420
+ if (!flags['no-plugin']) {
421
+ try {
422
+ let ver;
423
+ for (const pluginDir of pluginDirs) {
424
+ const name = path.basename(pluginDir);
425
+ if (cmd === 'status') {
426
+ const list = entriesOf(loadSettings(settings));
427
+ out(name + ': ' + (list.some((e) => norm(e) === norm(pluginDir)) ? 'registered' : 'not registered') + ' (' + pluginDir + ')');
428
+ } else if (cmd === 'uninstall') {
429
+ log(name + ': ' + unregisterPlugin(settings, pluginDir, dry) + ' (' + settings + ')');
430
+ if (name === 'kenaz') log('statusline: ' + unregisterStatusline(settings, dry));
431
+ } else if (process.env.KENAZ_SKIP_CLAUDE_PROVISION === '1') {
432
+ log(name + ': skipped (KENAZ_SKIP_CLAUDE_PROVISION=1)');
433
+ } else if (!fs.existsSync(path.join(pluginDir, 'hooks', 'hooks.json'))) {
434
+ log(name + ': skipped, ' + pluginDir + ' has no hooks/hooks.json'); rc = 1;
435
+ } else {
436
+ if (ver === undefined) ver = getVersion();
437
+ if (!ver) log(name + ': skipped, claude CLI not found (install it, then rerun install)');
438
+ else if (cmpVersion(ver, MIN_CLI) < 0) log(name + ': skipped, claude ' + ver.join('.') + ' < ' + MIN_CLI.join('.') + ' (run `claude update`, then rerun install)');
439
+ else {
440
+ log(name + ': ' + registerPlugin(settings, pluginDir, dry) + ' (' + settings + ')');
441
+ if (name === 'kenaz') {
442
+ const sl = registerStatusline(settings, core, dry);
443
+ log('statusline: ' + (sl === 'kept-user' ? 'kept your own statusLine; to show Kenaz task counts, append the output of: node "' + path.join(core, ...STATUSLINE_TAIL.split('/')) + '"' : sl));
444
+ const n = retireDashboardHooks(settings, dry);
445
+ if (n) log('kenaz: retired ' + n + ' Dashboard-era user-level hook(s), now owned by the plugin (backup: ' + path.join(path.dirname(settings), BACKUP) + ')');
446
+ }
447
+ }
448
+ }
449
+ }
450
+ } catch (e) { out('plugin: ERROR ' + e.message); rc = 1; }
451
+ }
452
+
453
+ if (!flags['no-service']) {
454
+ const bin = flags['server-bin'] ? path.resolve(flags['server-bin']) : findServerBin(core, home);
455
+ const run = deps.sh || sh, platform = deps.platform || process.platform;
456
+ // the probe is read-only but still a system call: skipped on --dry-run (assumes systemd, as before)
457
+ const caps = platform === 'linux' && !dry ? probeLinux(run) : undefined;
458
+ const plan = servicePlan(home, bin || '<kenaz-server not found>', core, platform, caps);
459
+ if (deps.onPlan) deps.onPlan(plan);
460
+ if (cmd === 'status') out('service: ' + (dry ? 'skipped (dry-run)' : serviceStatus(plan, run)));
461
+ else if (cmd === 'uninstall') serviceUninstall(plan, dry, log, run);
462
+ else if (!bin) { out('service: skipped, kenaz-server binary not found (pass --server-bin)'); rc = 1; }
463
+ else serviceInstall(plan, dry, log, run, deps.uid);
464
+ }
465
+ if (cmd === 'install' && rc === 0) out('Next: cd <your project>; claude; /kenaz:kenaz init; /kenaz:Ansuz (Codex: run /hooks once to trust the kenaz hooks, then $kenaz status)');
466
+ if (cmd === 'uninstall') out('Project .kenaz/ data was left untouched.');
467
+ return rc;
468
+ }
469
+
470
+ if (require.main === module) process.exit(main(process.argv.slice(2)));
471
+ module.exports = { main, verifyRuntime, healthOnce, serviceInstall, serviceRestart, restartHint, claudeVersion, loadSettings, entriesOf, norm, MIN_CLI, registerPlugin, unregisterPlugin, registerStatusline, unregisterStatusline, retireDashboardHooks, servicePlan, detectServiceMode, probeLinux, cronLine, CRON_MARK, winVbs, launchdPlist, systemdUnit, parseVersion, cmpVersion };
@@ -103,7 +103,7 @@ function countLines(text) {
103
103
  }
104
104
 
105
105
  function git(args, cwd) {
106
- const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
106
+ const result = spawnSync('git', args, { windowsHide: true, cwd, encoding: 'utf8' });
107
107
  return result;
108
108
  }
109
109
 
@@ -40,7 +40,7 @@ const TASK_API = path.join(ROOT, '.collaboration', 'core', 'task-api.js');
40
40
  // ─────────────────────────────────────────
41
41
 
42
42
  function runTaskApi(...args) {
43
- const result = spawnSync('node', [TASK_API, ...args], {
43
+ const result = spawnSync('node', [TASK_API, ...args], { windowsHide: true,
44
44
  encoding: 'utf8',
45
45
  timeout: 15000,
46
46
  cwd: ROOT,
@@ -72,11 +72,11 @@ const DRY_RUN = !process.argv.includes('--apply');
72
72
  // ─── Git helpers (GEN-008: dynamic root, never hardcoded) ────────────────────
73
73
 
74
74
  function gitRoot() {
75
- return execSync('git rev-parse --show-toplevel', { encoding: 'utf8' }).trim();
75
+ return execSync('git rev-parse --show-toplevel', { windowsHide: true, encoding: 'utf8' }).trim();
76
76
  }
77
77
 
78
78
  function currentBranch() {
79
- return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
79
+ return execSync('git rev-parse --abbrev-ref HEAD', { windowsHide: true, encoding: 'utf8' }).trim();
80
80
  }
81
81
 
82
82
  /**
@@ -84,7 +84,7 @@ function currentBranch() {
84
84
  * Uses exit code of `git merge-base --is-ancestor <commit> HEAD`.
85
85
  */
86
86
  function isAncestorOfHead(tipCommit) {
87
- const result = spawnSync('git', ['merge-base', '--is-ancestor', tipCommit, 'HEAD'], {
87
+ const result = spawnSync('git', ['merge-base', '--is-ancestor', tipCommit, 'HEAD'], { windowsHide: true,
88
88
  encoding: 'utf8',
89
89
  });
90
90
  return result.status === 0;
@@ -95,7 +95,7 @@ function isAncestorOfHead(tipCommit) {
95
95
  * Each entry: { path, head, branch, locked, lockReason }
96
96
  */
97
97
  function listWorktrees() {
98
- const raw = execSync('git worktree list --porcelain', { encoding: 'utf8' });
98
+ const raw = execSync('git worktree list --porcelain', { windowsHide: true, encoding: 'utf8' });
99
99
  const entries = [];
100
100
  let current = null;
101
101
 
@@ -199,7 +199,7 @@ function listUntrackedAgentDirs(root, trackedPaths) {
199
199
  function listDanglingAgentBranches(trackedBranches) {
200
200
  let raw = '';
201
201
  try {
202
- raw = execSync('git branch --list "worktree-agent-*" --format="%(refname:short)"', { encoding: 'utf8' });
202
+ raw = execSync('git branch --list "worktree-agent-*" --format="%(refname:short)"', { windowsHide: true, encoding: 'utf8' });
203
203
  } catch {
204
204
  return [];
205
205
  }
@@ -209,7 +209,7 @@ function listDanglingAgentBranches(trackedBranches) {
209
209
  const b = line.trim();
210
210
  if (!b || trackedSet.has(b)) continue;
211
211
  let tip = '';
212
- try { tip = execSync(`git rev-parse "${b}"`, { encoding: 'utf8' }).trim(); } catch { /* ignore */ }
212
+ try { tip = execSync(`git rev-parse "${b}"`, { windowsHide: true, encoding: 'utf8' }).trim(); } catch { /* ignore */ }
213
213
  out.push({ branch: b, tip, integrated: tip ? isAncestorOfHead(tip) : false });
214
214
  }
215
215
  return out;
@@ -354,7 +354,7 @@ function main() {
354
354
  // Unlock if locked (harness may have left lock even after pid is gone)
355
355
  if (wt.locked) {
356
356
  try {
357
- execSync(`git worktree unlock "${wt.path}"`, { encoding: 'utf8' });
357
+ execSync(`git worktree unlock "${wt.path}"`, { windowsHide: true, encoding: 'utf8' });
358
358
  } catch {
359
359
  // unlock may fail if git disagrees — proceed, remove --force handles it
360
360
  }
@@ -374,7 +374,7 @@ function main() {
374
374
 
375
375
  // Remove worktree (--force bypasses remaining lock metadata)
376
376
  try {
377
- execSync(`git worktree remove --force "${wt.path}"`, { encoding: 'utf8' });
377
+ execSync(`git worktree remove --force "${wt.path}"`, { windowsHide: true, encoding: 'utf8' });
378
378
  } catch (err) {
379
379
  console.log(` FAILED (worktree remove): ${err.message.trim()}`);
380
380
  failedCount++;
@@ -384,7 +384,7 @@ function main() {
384
384
  // Delete the branch (best-effort; branch may already be deleted or missing)
385
385
  if (wt.branch) {
386
386
  try {
387
- execSync(`git branch -D "${wt.branch}"`, { encoding: 'utf8' });
387
+ execSync(`git branch -D "${wt.branch}"`, { windowsHide: true, encoding: 'utf8' });
388
388
  } catch {
389
389
  // Branch may already be deleted — not an error
390
390
  }
@@ -396,7 +396,7 @@ function main() {
396
396
 
397
397
  // Prune stale worktree admin dirs (idempotent, always safe)
398
398
  try {
399
- execSync('git worktree prune', { encoding: 'utf8' });
399
+ execSync('git worktree prune', { windowsHide: true, encoding: 'utf8' });
400
400
  console.log('\n git worktree prune done');
401
401
  } catch (err) {
402
402
  console.log(`\n git worktree prune FAILED (non-fatal): ${err.message.trim()}`);
@@ -419,7 +419,7 @@ function main() {
419
419
  for (const b of danglingBranches) {
420
420
  if (!b.integrated) continue; // keep not-in-HEAD branches for salvage
421
421
  try {
422
- execSync(`git branch -D "${b.branch}"`, { encoding: 'utf8' });
422
+ execSync(`git branch -D "${b.branch}"`, { windowsHide: true, encoding: 'utf8' });
423
423
  danglingDeleted++;
424
424
  } catch { /* already gone — not an error */ }
425
425
  }
@@ -218,7 +218,7 @@ function parseCliArgs(argv) {
218
218
  function resolveExecutableWindows(whereFn) {
219
219
  whereFn = whereFn || function runWhere() {
220
220
  try {
221
- return cp.execSync('where claude', { encoding: 'utf8' });
221
+ return cp.execSync('where claude', { windowsHide: true, encoding: 'utf8' });
222
222
  } catch (e) {
223
223
  return '';
224
224
  }
@@ -263,13 +263,13 @@ function escapeCmdExe(exe) {
263
263
 
264
264
  function launch(exec, cwd, argv) {
265
265
  if (exec.useShell) {
266
- return cp.spawn(escapeCmdExe(exec.command), argv.map(escapeCmdArg), {
266
+ return cp.spawn(escapeCmdExe(exec.command), argv.map(escapeCmdArg), { windowsHide: true,
267
267
  cwd,
268
268
  stdio: 'inherit',
269
269
  shell: true,
270
270
  });
271
271
  }
272
- return cp.spawn(exec.command, argv, { cwd, stdio: 'inherit', shell: false });
272
+ return cp.spawn(exec.command, argv, { windowsHide: true, cwd, stdio: 'inherit', shell: false });
273
273
  }
274
274
 
275
275
  // ---- main ----
@@ -59,12 +59,12 @@ function start(name, exe, args, env, cwd) {
59
59
  }
60
60
  async function stop(p) {
61
61
  if (!p || p.exited) return;
62
- if (process.platform === 'win32') spawnSync('taskkill', ['/PID', String(p.pid), '/T', '/F'], { stdio: 'ignore' }); // our PID only
62
+ if (process.platform === 'win32') spawnSync('taskkill', ['/PID', String(p.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' }); // our PID only
63
63
  else p.child.kill('SIGKILL');
64
64
  await until(`${p.name} exit`, () => p.exited, 15000, 100).catch(() => {});
65
65
  }
66
66
  function sh(cmd, args, env) {
67
- const r = spawnSync(cmd, args, { env: { ...process.env, ...env }, encoding: 'utf8' });
67
+ const r = spawnSync(cmd, args, { windowsHide: true, env: { ...process.env, ...env }, encoding: 'utf8' });
68
68
  return { code: r.status, out: `${r.stdout || ''}${r.stderr || ''}`.trim() };
69
69
  }
70
70
  function sqlite(file, readOnly = true) {
@@ -398,5 +398,5 @@ async function main() {
398
398
  return bad ? 1 : 0;
399
399
  }
400
400
 
401
- process.on('SIGINT', () => { for (const p of procs) if (!p.exited) { if (process.platform === 'win32') spawnSync('taskkill', ['/PID', String(p.pid), '/T', '/F'], { stdio: 'ignore' }); else p.child.kill('SIGKILL'); } process.exit(130); });
401
+ process.on('SIGINT', () => { for (const p of procs) if (!p.exited) { if (process.platform === 'win32') spawnSync('taskkill', ['/PID', String(p.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' }); else p.child.kill('SIGKILL'); } process.exit(130); });
402
402
  main().then((c) => process.exit(c), (e) => { console.error(e); process.exit(1); });