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.
- package/core/.claude/commands/Ansuz.md +2 -0
- package/core/.claude/hooks/__tests__/peer-session-fill.test.js +37 -0
- package/core/.claude/hooks/_ansuz-conversation.js +32 -1
- package/core/.claude/hooks/_user-scope.js +1 -1
- package/core/.claude/hooks/core-resolve.js +1 -1
- package/core/.claude/hooks/peer-optin.test.js +74 -0
- package/core/.claude/hooks/peer-session-fill.js +38 -0
- package/core/.claude/hooks/turn-context.js +5 -4
- package/core/.claude/hooks/worktree-create.js +6 -6
- package/core/.claude/hooks/worktree-remove.js +4 -4
- package/core/.claude/settings.json +9 -0
- package/core/.collaboration/core/reconcile.js +16 -1
- package/core/.collaboration/core/task-2467-lifecycle.test.js +63 -0
- package/core/.collaboration/core/task-api.js +37 -7
- package/core/.formation/bifrost/launch.js +1 -1
- package/core/.formation/bifrost/openai/codex-hook-shim.js +21 -4
- package/core/.formation/bifrost/openai/codex-peer-pull.test.js +42 -0
- package/core/.formation/bifrost/provider-launch.js +2 -1
- package/core/.formation/tools/kenaz-stats.js +1 -1
- package/core/.formation/tools/post-complete/git-merge.js +1 -1
- package/core/.formation/tools/prompt.js +1 -1
- package/core/.kenaz/specialist/ansuz_rules.yaml +7 -51
- package/core/.kenaz/specialist/ansuz_session_handoff.schema.json +2 -6
- package/core/.rule/general/project-self-containment.yaml +2 -2
- package/core/.specialist/Amelia/README.md +1 -1
- package/core/.specialist/Amelia/rule.yaml +1 -1
- package/core/.specialist/Ansuz/RULES.md +1 -1
- package/core/.specialist/Ansuz/concepts/commitment_layer.yaml +1 -1
- package/core/.specialist/Ansuz/concepts/evolvable_guardrails.yaml +1 -1
- package/core/.specialist/Ansuz/knowledge.yaml +1 -3
- package/core/.specialist/tools/__tests__/task-2409-lessons.test.js +10 -0
- package/core/.specialist/tools/__tests__/task-2427-harvest.test.js +25 -0
- package/core/.specialist/tools/ansuz-boot-fast.js +1 -1
- package/core/.specialist/tools/failure-detector.js +4 -4
- package/core/.specialist/tools/handoff-doc.js +1 -1
- package/core/.specialist/tools/lessons.js +2 -2
- package/core/CLAUDE.md +5 -5
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/dashboard/scripts/install-kenaz-cli.js +471 -0
- package/core/dev/scripts/check-file-size.js +1 -1
- package/core/dev/scripts/ci-feedback.js +1 -1
- package/core/dev/scripts/gc-agent-worktrees.js +11 -11
- package/core/dev/scripts/kenaz-session.js +3 -3
- package/core/dev/scripts/peer-identity-e2e.js +3 -3
- package/core/dev/scripts/run-rust-tests.js +2 -2
- package/core/dev/scripts/skill-routing-replay.js +3 -3
- package/core/dev/scripts/task1961/selftest.js +2 -2
- package/core/dev/scripts/windows-hide-guard.test.js +97 -0
- package/core/plugins/kenaz/commands/Ansuz.md +2 -0
- package/core/plugins/kenaz/guidance.md +2 -0
- package/core/plugins/kenaz/hooks/core-resolve.js +1 -1
- package/core/plugins/kenaz/hooks/hooks.json +9 -0
- package/core/plugins/kenaz/hooks/kenaz-hook.js +53 -4
- package/core/plugins/kenaz/scripts/kenaz.test.js +72 -5
- package/core/plugins/kenaz/scripts/ygg.js +40 -4
- package/core/plugins/kenaz/scripts/ygg.test.js +63 -3
- package/core/plugins/kenaz-peer/hooks/register.test.ts +59 -2
- package/core/plugins/kenaz-peer/hooks/register.ts +24 -1
- package/lib/cli.js +9 -5
- package/package.json +6 -6
- package/vendor/install-kenaz-cli.js +21 -6
- package/core/.kenaz/designs/METAPLASTICITY_CONFIDENCE_DESIGN.md +0 -537
- package/core/.kenaz/designs/REFLEX_INITIATOR_DESIGN.md +0 -501
- package/core/.kenaz/designs/TASK_1385_handoff_mcp_design.md +0 -426
- package/core/.kenaz/designs/TASK_932_hippocampus_consolidation.md +0 -103
- package/core/.kenaz/designs/TASK_934_efe_signal_engine.md +0 -97
- package/core/.kenaz/designs/TASK_935_self_evolution_framework.md +0 -154
- package/core/.kenaz/designs/VISION_cognitive_architecture_roadmap.md +0 -159
- package/core/.kenaz/designs/metaplasticity_simulation_results.json +0 -696
- package/core/.kenaz/main-memory/README.md +0 -230
- package/core/.kenaz/main-memory/kenaz/cortex/001_cortex_overview.md +0 -168
- package/core/.kenaz/main-memory/kenaz/cortex/001_rag_engine_overview.md +0 -30
- package/core/.kenaz/main-memory/kenaz/memory-system/001_three_tier_architecture.md +0 -361
- package/core/.kenaz/main-memory/kenaz/pipeline/001_task_execution_flow.md +0 -117
- package/core/.kenaz/main-memory/kenaz/pipeline/001_task_granularity.md +0 -154
- package/core/.kenaz/main-memory/kenaz/pipeline/002_team_priority.md +0 -51
- package/core/.kenaz/main-memory/kenaz/pipeline/003_feature_id_system.md +0 -46
- package/core/.kenaz/main-memory/kenaz/rules/001_gen_007_self_containment.md +0 -108
- package/core/.kenaz/main-memory/kenaz/rules/001_rule_priority_system.md +0 -121
- package/core/.kenaz/main-memory/kenaz/rules/002_agt_006_pre_check.md +0 -0
- package/core/.kenaz/main-memory/kenaz/rules/003_ppl_002_task_archival.md +0 -48
- package/core/.kenaz/main-memory/kenaz/system/001_dual_track_task_system.md +0 -197
- package/core/.kenaz/main-memory/knowledge/patterns/001_json_bom_issue.md +0 -78
- package/core/.kenaz/main-memory/knowledge/patterns/001_leader_worker_pattern.md +0 -0
- package/core/.kenaz/main-memory/knowledge/patterns/002_scope_path_conflict.md +0 -115
- package/core/.kenaz/main-memory/knowledge/patterns/002_task_dependency_resolution.md +0 -223
- package/core/.kenaz/main-memory/knowledge/patterns/003_question_protocol.md +0 -242
- package/core/.kenaz/main-memory/knowledge/patterns/004_user_insight_value.md +0 -111
- package/core/.kenaz/main-memory/knowledge/patterns/005_file_lock_pattern.md +0 -274
- package/core/.kenaz/main-memory/knowledge/references/001_ppl_rules.md +0 -28
- package/core/.kenaz/main-memory/knowledge/solutions/001_json_bom_issue.md +0 -137
- package/core/.kenaz/main-memory/knowledge/solutions/001_team_invocation.md +0 -0
- package/core/.kenaz/main-memory/knowledge/solutions/002_passive_monitoring.md +0 -0
- package/core/.kenaz/main-memory/knowledge/solutions/003_windows_file_race_condition.md +0 -239
- package/core/.kenaz/main-memory/registry.json +0 -326
- package/core/.kenaz/main-memory/version.yaml +0 -58
- package/core/.kenaz/specialist/memory_guard_fired.json +0 -1
- package/core/.kenaz/specialist/memory_guard_trigger_cache.json +0 -1
- package/core/.kenaz/specialist/memory_source_priority.yaml +0 -260
- package/core/.kenaz/specialist/prompt_1775.txt +0 -317
- package/core/.kenaz/specialist/prompt_1776.txt +0 -326
- package/core/.kenaz/specialist/prompt_1791.txt +0 -320
- package/core/.kenaz/specialist/prompt_1792.txt +0 -320
- package/core/.kenaz/specialist/prompt_1795.txt +0 -281
- package/core/.kenaz/specialist/prompt_1813.txt +0 -332
- package/core/.kenaz/specialist/prompt_1814.txt +0 -401
- package/core/.kenaz/specialist/prompt_1815.txt +0 -394
- package/core/.kenaz/specialist/prompt_1816.txt +0 -437
- package/core/.kenaz/specialist/prompt_1818.txt +0 -361
- package/core/.kenaz/specialist/prompt_1819.txt +0 -421
- package/core/.kenaz/specialist/prompt_1821.txt +0 -345
- package/core/.kenaz/specialist/prompt_1822.txt +0 -313
- package/core/.kenaz/specialist/prompt_1824.txt +0 -348
- package/core/.kenaz/specialist/prompt_1826.txt +0 -388
- package/core/.kenaz/specialist/rune_efe_salience_gap.md +0 -21
- package/core/.kenaz/specialist/task1825_update.json +0 -43
- package/core/.kenaz/specialist/taskgen/consolidation_i.json +0 -38
- package/core/.kenaz/specialist/taskgen/csharp_a.json +0 -44
- package/core/.kenaz/specialist/taskgen/csharp_b.json +0 -40
- package/core/.kenaz/specialist/taskgen/embedding_e.json +0 -34
- package/core/.kenaz/specialist/taskgen/hygiene_fg.json +0 -39
- package/core/.kenaz/specialist/taskgen/pathology_h.json +0 -36
- package/core/.kenaz/specialist/taskgen/unity_noise_d.json +0 -38
- package/core/.kenaz/specialist/taskgen/visibility_c.json +0 -38
- package/core/.specialist/Ansuz/decisions/ADR-requirement-graph-layer.md +0 -390
- package/core/.specialist/Ansuz/self_model_seed.backup.json +0 -100
- package/core/.specialist/ansuz_session_handoff.json +0 -96
- package/core/dev/ALAYA_FIELD_FEEDBACK_DESIGN.md +0 -126
- package/core/dev/ANSUZ_SCAFFOLD_DIET_2026-09-20.md +0 -231
- package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +0 -355
- package/core/dev/ANSUZ_WORKFLOW_MODERNIZATION.md +0 -143
- package/core/dev/CORTEX_META_GRAPH_ASSESSMENT.md +0 -99
- package/core/dev/FREYA_DOWNSTREAM_BUGS_2026-09-19.md +0 -44
- package/core/dev/GLOBAL_MEMORY_IDENTITY_CONSOLIDATION.md +0 -263
- package/core/dev/GUARD_EFFECTIVENESS_DESIGN.md +0 -180
- package/core/dev/HEALTH_GATE_VARIANCE_ASSESSMENT.md +0 -271
- package/core/dev/HOOK_DIET_2026-09-21.md +0 -126
- package/core/dev/JEV_INTEGRATION_PLAN.md +0 -189
- package/core/dev/KENAZ_AGENT_CONNECT_v1_SPEC.md +0 -139
- package/core/dev/MOBILE_DESIGN_SPEC.md +0 -569
- package/core/dev/MULTI_ANSUZ_SPEC.md +0 -136
- package/core/dev/MUNINN_RETIREMENT.md +0 -115
- package/core/dev/PHASE3_ATTRIBUTION_POPULATION_DIAGNOSIS.md +0 -211
- package/core/dev/PHASE3_P3-2B_TASKLEVEL_PROBE.md +0 -83
- package/core/dev/PHASE3_P3-2_POPULATION_DISJOINTNESS.md +0 -83
- package/core/dev/PHASE3_P3-2_REMEASURE_REPORT.md +0 -231
- package/core/dev/PREDICTION_GROUNDING_DESIGN.md +0 -376
- package/core/dev/PRODUCT_SPEC.md +0 -317
- package/core/dev/PROJECT_REGISTRY_DUPLICATE_ASSESSMENT.md +0 -86
- package/core/dev/STARTUP_TIMELINE_TASK_1995.md +0 -181
- package/core/dev/SYNAPSE_WORKER_PER_PROJECT.md +0 -881
- package/core/dev/TASK_1680_P3-3_DISPATCH_SPEC.md +0 -96
- package/core/dev/TASK_1684_CALIBRATION_IMPACT_ANALYSIS.md +0 -170
- package/core/dev/TASK_1684_FORK_A_GOODHART_DESIGN.md +0 -171
- package/core/dev/TASK_1684_P3_2_REMEASURE_VALIDATION.md +0 -262
- package/core/dev/TASK_1903_SIGNAL_AUC_MEASUREMENT.md +0 -207
- package/core/dev/TASK_1904_BRANCH_INTEGRITY_AUDIT.md +0 -146
- package/core/dev/ansuz-eval/README.md +0 -38
- package/core/dev/ansuz-eval/build-fixtures.js +0 -87
- package/core/dev/ansuz-eval/grade.js +0 -96
- package/core/dev/ansuz-eval/grader.test.js +0 -117
- package/core/dev/ansuz-eval/tasks.md +0 -32
- package/core/dev/archive/ansuz/BOOT_FULL.md +0 -141
- package/core/dev/archive/ansuz/agents/ansuz-boot.md +0 -176
- package/core/dev/archive/ansuz/designs/alaya-passive-surfacing.md +0 -84
- package/core/dev/archive/ansuz/designs/cc_memory_indexing.md +0 -307
- package/core/dev/archive/ansuz/designs/check_guard.md +0 -322
- package/core/dev/archive/ansuz/designs/consequence_loop.md +0 -227
- package/core/dev/archive/ansuz/designs/mcp_kuzudb_lock_architecture.md +0 -312
- package/core/dev/archive/ansuz/designs/phase0_git_enrichment.md +0 -278
- package/core/dev/archive/ansuz/designs/refactor_mcp_tools_synapse.md +0 -490
- package/core/dev/archive/ansuz/designs/semantic_markdown_extraction.md +0 -167
- package/core/dev/archive/ansuz/designs/task_1372_prompt_injects_into.md +0 -193
- package/core/dev/archive/ansuz/designs/thought_stack.md +0 -286
- package/core/dev/archive/ansuz/investigations/reachability_unified_api_2026-04-11.md +0 -330
- package/core/dev/archive/ansuz/investigations/synapse_orphans_indexing_bug_2026-04-11.md +0 -352
- package/core/dev/archive/ansuz/investigations/synapse_orphans_reindex_verification_2026-04-11.md +0 -48
- package/core/dev/archive/ansuz/investigations/synapse_orphans_verification_2026-04-11.md +0 -170
- package/core/dev/archive/ansuz/legacy/kernel.yaml +0 -399
- package/core/dev/archive/ansuz/legacy/phases/phase_0.yaml +0 -418
- package/core/dev/archive/ansuz/legacy/phases/phase_1.yaml +0 -376
- package/core/dev/archive/ansuz/legacy/phases/phase_10.yaml +0 -35
- package/core/dev/archive/ansuz/legacy/phases/phase_11.yaml +0 -582
- package/core/dev/archive/ansuz/legacy/phases/phase_2.yaml +0 -94
- package/core/dev/archive/ansuz/legacy/phases/phase_3.yaml +0 -274
- package/core/dev/archive/ansuz/legacy/phases/phase_4.yaml +0 -67
- package/core/dev/archive/ansuz/legacy/phases/phase_5.yaml +0 -32
- package/core/dev/archive/ansuz/legacy/phases/phase_6.yaml +0 -91
- package/core/dev/archive/ansuz/legacy/phases/phase_7.yaml +0 -58
- package/core/dev/archive/ansuz/legacy/phases/phase_8.yaml +0 -146
- package/core/dev/archive/ansuz/legacy/phases/phase_9.yaml +0 -424
- package/core/dev/archive/ansuz/specs/INDEX_bifrost_hook_parity.yaml +0 -92
- package/core/dev/archive/ansuz/specs/active-inference-formation.yaml +0 -453
- package/core/dev/archive/ansuz/specs/brief_TASK_1228_userpromptsubmit.yaml +0 -191
- package/core/dev/archive/ansuz/specs/brief_TASK_1229_pretooluse_posttooluse.yaml +0 -250
- package/core/dev/archive/ansuz/specs/brief_TASK_1230_sessionstart_stop.yaml +0 -301
- package/core/dev/archive/ansuz/specs/brief_TASK_1231_verification.yaml +0 -272
- package/core/dev/archive/skills/feature_agent/SKILL.md +0 -289
- package/core/dev/design/PHASE4_DESIGN.md +0 -559
- package/core/dev/design/dashboard_docs_integration_PLAN.md +0 -388
- package/core/dev/design/matrix_format_ABBREVIATIONS.md +0 -655
- package/core/dev/design/matrix_format_DESIGN_SPEC.md +0 -601
- package/core/dev/design/matrix_format_DISCUSSION_SUMMARY.md +0 -484
- package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +0 -13
- package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +0 -9
- package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +0 -14
- package/core/dev/design/matrix_format_SYNTAX_BNF.md +0 -754
- package/core/dev/design/matrix_format_TOKEN_COMPARISON.md +0 -484
- package/core/dev/design/matrix_v2_COGNITIVE_DESIGN.md +0 -503
- package/core/dev/design/matrix_v2_DISCUSSION_SUMMARY.md +0 -414
- package/core/dev/design/matrix_v2_EXAMPLE.matrix +0 -67
- package/core/dev/designs/EXPANDABLE_CARD_DESIGN_SPEC.md +0 -509
- package/core/dev/jev-eval/ACTIVE_CORRECTION.md +0 -52
- package/core/dev/jev-eval/ANNOTATION_GUIDE.md +0 -68
- package/core/dev/jev-eval/BASELINE_COMPARISON.md +0 -45
- package/core/dev/jev-eval/CONTEXT_REVIEW.md +0 -31
- package/core/dev/jev-eval/EVALUATION_STATUS.md +0 -39
- package/core/dev/jev-eval/HISTORICAL_CORPUS.md +0 -33
- package/core/dev/jev-eval/PACKAGING_VERIFICATION.md +0 -42
- package/core/dev/jev-eval/README.md +0 -101
- package/core/dev/jev-eval/THALAMUS_RANKING.md +0 -58
- package/core/dev/jev-eval/VALIDATION_PROGRESS.md +0 -54
- package/core/dev/jev-eval/cli.mjs +0 -50
- package/core/dev/jev-eval/context.mjs +0 -183
- package/core/dev/jev-eval/corpus.mjs +0 -186
- package/core/dev/jev-eval/fixtures/conversation-review.seed.jsonl +0 -6
- package/core/dev/jev-eval/fixtures/synthetic.jsonl +0 -11
- package/core/dev/jev-eval/metrics.mjs +0 -67
- package/core/dev/jev-eval/muninn-baseline.mjs +0 -119
- package/core/dev/jev-eval/providers.mjs +0 -95
- package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +0 -32
- package/core/dev/jev-eval/reports/context-review-2026-09-20.json +0 -48
- package/core/dev/jev-eval/reports/conversation-review.pending.json +0 -151
- package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +0 -52
- package/core/dev/jev-eval/reports/length-probe-2026-09-20.json +0 -133
- package/core/dev/jev-eval/reports/live-smoke-2026-09-20.json +0 -616
- package/core/dev/jev-eval/reports/muninn-baseline-2026-09-20.json +0 -52
- package/core/dev/jev-eval/reports/resilience-2026-09-20.json +0 -520
- package/core/dev/jev-eval/reports/rules-baseline.json +0 -197
- package/core/dev/jev-eval/reports/synthetic-review.pending.json +0 -357
- package/core/dev/jev-eval/resilience.mjs +0 -71
- package/core/dev/jev-eval/review.mjs +0 -78
- package/core/dev/jev-eval/schema.mjs +0 -87
- package/core/dev/jev-eval/test/context.test.mjs +0 -95
- package/core/dev/jev-eval/test/corpus.test.mjs +0 -102
- package/core/dev/jev-eval/test/eval.test.mjs +0 -146
- package/core/dev/jev-eval/test/muninn-baseline.test.mjs +0 -71
- package/core/dev/jev-eval/test/resilience.test.mjs +0 -72
- package/core/dev/jev-eval/test/review.test.mjs +0 -61
- package/core/dev/pipeline/PHASE3_PLANNING_SUMMARY.md +0 -338
- package/core/dev/pipeline/PROGRESS.md +0 -113
- package/core/dev/pipeline/PROGRESS_template.md +0 -860
- package/core/dev/pipeline/QA_REPORT_F008.md +0 -431
- package/core/dev/pipeline/QA_REPORT_LOCAL_CLAUDE_DIAGNOSIS.md +0 -583
- package/core/dev/pipeline/README.md +0 -210
- package/core/dev/pipeline/WORKFLOW_GUIDE.md +0 -824
- package/core/dev/pipeline/archived/TASK_015_tauri_init.yaml +0 -53
- package/core/dev/pipeline/archived/TASK_016_tauri_commands.yaml +0 -54
- package/core/dev/pipeline/archived/TASK_017_rust_pty.yaml +0 -54
- package/core/dev/pipeline/archived/TASK_018_tauri_bundle.yaml +0 -53
- package/core/dev/pipeline/archived/TASK_019_release_workflow.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_020_update_ui.yaml +0 -54
- package/core/dev/pipeline/archived/TASK_021_update_notification_ui.yaml +0 -55
- package/core/dev/pipeline/archived/TASK_022_remove_node_pty.yaml +0 -65
- package/core/dev/pipeline/archived/TASK_023_api_routes_migration.yaml +0 -86
- package/core/dev/pipeline/archived/TASK_024_feature_id_system.yaml +0 -51
- package/core/dev/pipeline/archived/TASK_025_executable_acceptance.yaml +0 -66
- package/core/dev/pipeline/archived/TASK_026_spec_versioning.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_027_drift_detection.yaml +0 -60
- package/core/dev/pipeline/archived/TASK_028_tauri_pty_manager.yaml +0 -60
- package/core/dev/pipeline/archived/TASK_029_tauri_pty_commands.yaml +0 -62
- package/core/dev/pipeline/archived/TASK_030_frontend_session_adapter.yaml +0 -54
- package/core/dev/pipeline/archived/TASK_031_terminal_tauri_events.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_041_scheduler_data_models.yaml +0 -54
- package/core/dev/pipeline/archived/TASK_042_path_lock_manager.yaml +0 -53
- package/core/dev/pipeline/archived/TASK_043_process_manager.yaml +0 -57
- package/core/dev/pipeline/archived/TASK_044_git_integration.yaml +0 -85
- package/core/dev/pipeline/archived/TASK_045_task_queue_manager.yaml +0 -54
- package/core/dev/pipeline/archived/TASK_046_scheduler_dispatcher.yaml +0 -60
- package/core/dev/pipeline/archived/TASK_047_tauri_scheduler_commands.yaml +0 -55
- package/core/dev/pipeline/archived/TASK_048_realtime_status_push.yaml +0 -92
- package/core/dev/pipeline/archived/TASK_049_frontend_scheduler_integration.yaml +0 -58
- package/core/dev/pipeline/archived/TASK_050_error_recovery.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_051_optimization_proposals_backend.yaml +0 -53
- package/core/dev/pipeline/archived/TASK_052_optimization_proposals_ui.yaml +0 -58
- package/core/dev/pipeline/archived/TASK_053_optimization_proposals_diff_view.yaml +0 -54
- package/core/dev/pipeline/archived/TASK_054_multi_project_scheduler_state.yaml +0 -76
- package/core/dev/pipeline/archived/TASK_055_scheduler_api_project_scope.yaml +0 -64
- package/core/dev/pipeline/archived/TASK_056_frontend_scheduler_project_scope.yaml +0 -62
- package/core/dev/pipeline/archived/TASK_057_scheduler_module_structure.yaml +0 -54
- package/core/dev/pipeline/archived/TASK_058_scheduler_models_extract.yaml +0 -51
- package/core/dev/pipeline/archived/TASK_059_scheduler_core_extract.yaml +0 -51
- package/core/dev/pipeline/archived/TASK_060_scheduler_commands_extract.yaml +0 -51
- package/core/dev/pipeline/archived/TASK_061_scheduler_specialized_commands.yaml +0 -53
- package/core/dev/pipeline/archived/TASK_062_process_manager_module_structure.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_063_process_manager_core_extract.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_064_recovery_module_structure.yaml +0 -57
- package/core/dev/pipeline/archived/TASK_065_recovery_core_extract.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_066_self_enhancement_scheduler_models.yaml +0 -60
- package/core/dev/pipeline/archived/TASK_067_agents_section_quick_actions.yaml +0 -53
- package/core/dev/pipeline/archived/TASK_067_self_enhancement_scheduler_core.yaml +0 -65
- package/core/dev/pipeline/archived/TASK_068_agent_workflow_rule_enhancement.yaml +0 -46
- package/core/dev/pipeline/archived/TASK_068_self_enhancement_tauri_commands.yaml +0 -61
- package/core/dev/pipeline/archived/TASK_069_agent_wizard_responsive.yaml +0 -46
- package/core/dev/pipeline/archived/TASK_069_self_enhancement_ui.yaml +0 -62
- package/core/dev/pipeline/archived/TASK_070_preview_panel_redesign.yaml +0 -62
- package/core/dev/pipeline/archived/TASK_070_self_enhancement_integration.yaml +0 -57
- package/core/dev/pipeline/archived/TASK_071_preview_panel_qa.yaml +0 -98
- package/core/dev/pipeline/archived/TASK_071_single_agent_config.yaml +0 -57
- package/core/dev/pipeline/archived/TASK_072_preview_modal_esc_fix.yaml +0 -71
- package/core/dev/pipeline/archived/TASK_072_product_review_module.yaml +0 -57
- package/core/dev/pipeline/archived/TASK_073_agents_page_component_refactor.yaml +0 -68
- package/core/dev/pipeline/archived/TASK_073_rename_single_agent.yaml +0 -64
- package/core/dev/pipeline/archived/TASK_074_preview_mode_ui_diagnosis.yaml +0 -71
- package/core/dev/pipeline/archived/TASK_074_single_agent_ui_toggle.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_075_agents_page_component_refactor.yaml +0 -69
- package/core/dev/pipeline/archived/TASK_075_single_agent_integration_test.yaml +0 -75
- package/core/dev/pipeline/archived/TASK_076_preview_mode_ui_diagnosis.yaml +0 -70
- package/core/dev/pipeline/archived/TASK_076_single_agent_continuous_mode.yaml +0 -65
- package/core/dev/pipeline/archived/TASK_077_failure_directory_structure.yaml +0 -51
- package/core/dev/pipeline/archived/TASK_077_single_agent_typescript_types.yaml +0 -59
- package/core/dev/pipeline/archived/TASK_078_fail_001_agent_no_output.yaml +0 -53
- package/core/dev/pipeline/archived/TASK_078_single_agent_ui_simplify.yaml +0 -74
- package/core/dev/pipeline/archived/TASK_079_dashboard_rwd_responsive.yaml +0 -63
- package/core/dev/pipeline/archived/TASK_079_fail_002_json_bom.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_080_fail_003_queue_pollution.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_080_http_server_module.yaml +0 -55
- package/core/dev/pipeline/archived/TASK_081_failure_logging_tools.yaml +0 -57
- package/core/dev/pipeline/archived/TASK_081_http_api_routes.yaml +0 -62
- package/core/dev/pipeline/archived/TASK_082_frontend_api_adapter.yaml +0 -51
- package/core/dev/pipeline/archived/TASK_082_integrate_failure_to_workflow.yaml +0 -60
- package/core/dev/pipeline/archived/TASK_083_cleanup_nextjs_api.yaml +0 -48
- package/core/dev/pipeline/archived/TASK_083_task_queue_cleanup.yaml +0 -67
- package/core/dev/pipeline/archived/TASK_084_rust_dead_code_cleanup.yaml +0 -66
- package/core/dev/pipeline/archived/TASK_084_scheduler_sync_logic_fix.yaml +0 -68
- package/core/dev/pipeline/archived/TASK_085_http_api_web_mode_fixes.yaml +0 -46
- package/core/dev/pipeline/archived/TASK_085_memory_consolidation_0204_0205.yaml +0 -98
- package/core/dev/pipeline/archived/TASK_086_collapsible_preview_panel.yaml +0 -161
- package/core/dev/pipeline/archived/TASK_086_web_mode_feature_parity.yaml +0 -62
- package/core/dev/pipeline/archived/TASK_087_manual_memory_archival.yaml +0 -163
- package/core/dev/pipeline/archived/TASK_087_mobile_http_control.yaml +0 -68
- package/core/dev/pipeline/archived/TASK_088_fail_003_to_ppl_002_rule.yaml +0 -63
- package/core/dev/pipeline/archived/TASK_088_mobile_http_qa.yaml +0 -121
- package/core/dev/pipeline/archived/TASK_089_agent_collaboration_topic.yaml +0 -60
- package/core/dev/pipeline/archived/TASK_089_web_mode_monitor_fix.yaml +0 -58
- package/core/dev/pipeline/archived/TASK_090_cli_detection_fix.yaml +0 -61
- package/core/dev/pipeline/archived/TASK_090_ui_ux_lessons_topic.yaml +0 -63
- package/core/dev/pipeline/archived/TASK_091_expandable_card_design.yaml +0 -56
- package/core/dev/pipeline/archived/TASK_091_move_agent_section_components.yaml +0 -65
- package/core/dev/pipeline/archived/TASK_092_expandable_card_component.yaml +0 -60
- package/core/dev/pipeline/archived/TASK_092_refactor_agent_create_wizard.yaml +0 -84
- package/core/dev/pipeline/archived/TASK_093_taskcard_detail_integration.yaml +0 -57
- package/core/dev/pipeline/archived/TASK_093_update_barrel_exports_test.yaml +0 -84
- package/core/dev/pipeline/archived/TASK_094_ai_agent_generation_enhancement.yaml +0 -115
- package/core/dev/pipeline/archived/TASK_094_proposalcard_detail_integration.yaml +0 -55
- package/core/dev/pipeline/archived/TASK_095_preview_fixes.yaml +0 -129
- package/core/dev/pipeline/archived/TASK_095_single_agent_task_selector.yaml +0 -60
- package/core/dev/pipeline/archived/TASK_096_agents_page_ux_improvements.yaml +0 -163
- package/core/dev/pipeline/archived/TASK_096_single_agent_status_sync.yaml +0 -60
- package/core/dev/pipeline/archived/TASK_097_local_claude_error_logging.yaml +0 -59
- package/core/dev/pipeline/archived/TASK_097_single_agent_monitor_events.yaml +0 -59
- package/core/dev/pipeline/archived/TASK_098_product_manager_team_phase2.yaml +0 -60
- package/core/dev/pipeline/archived/TASK_098_single_agent_refactor_test.yaml +0 -58
- package/core/dev/pipeline/archived/TASK_099_backend_team_creation.yaml +0 -258
- package/core/dev/pipeline/archived/TASK_100_frontend_team_creation.yaml +0 -61
- package/core/dev/pipeline/archived/TASK_101_orchestrator_system.yaml +0 -403
- package/core/dev/pipeline/archived/TASK_102_designer_sre_teams.yaml +0 -90
- package/core/dev/pipeline/archived/TASK_103_integration_testing.yaml +0 -86
- package/core/dev/pipeline/archived/TASK_128_integration_testing.yaml +0 -83
- package/core/dev/pipeline/archived/TASK_134_cortex_v2_phase3_file_watcher.yaml +0 -65
- package/core/dev/pipeline/archived/TASK_134_team_http_api.yaml +0 -96
- package/core/dev/pipeline/archived/TASK_135_cortex_v2_phase3_git_monitor.yaml +0 -63
- package/core/dev/pipeline/archived/TASK_136_cortex_v2_phase3_change_analyzer.yaml +0 -69
- package/core/dev/pipeline/archived/TASK_137_cortex_v2_phase3_cache_invalidator.yaml +0 -67
- package/core/dev/pipeline/archived/TASK_137_team_orchestrator_integration.yaml +0 -93
- package/core/dev/pipeline/archived/TASK_138_cortex_v2_phase3_distributed_cache.yaml +0 -68
- package/core/dev/pipeline/archived/TASK_138_matrix_folder_structure.yaml +0 -61
- package/core/dev/pipeline/archived/TASK_139_cortex_v2_phase3_cache_prewarming.yaml +0 -65
- package/core/dev/pipeline/archived/TASK_146_unified_documentation.yaml +0 -141
- package/core/dev/pipeline/archived/TASK_147_pipeline_execution_rules.yaml +0 -76
- package/core/dev/pipeline/archived/TASK_148_update_claude_md.yaml +0 -77
- package/core/dev/pipeline/archived/TASK_149_fix_nextjs_mdx_serialization.yaml +0 -63
- package/core/dev/pipeline/archived/TASK_149_update_team_docs.yaml +0 -77
- package/core/dev/pipeline/archived/TASK_150_pipeline_workflow_guide.yaml +0 -82
- package/core/dev/pipeline/archived/TASK_164_fix_smart_context_switch.yaml +0 -49
- package/core/dev/pipeline/archived/TASK_165_ctags_symbol_index.yaml +0 -53
- package/core/dev/pipeline/archived/TASK_166_symbol_search_mcp_tool.yaml +0 -48
- package/core/dev/pipeline/archived/TASK_167_llm_module_summary.yaml +0 -55
- package/core/dev/pipeline/archived/TASK_168_map_reduce_context.yaml +0 -51
- package/core/dev/pipeline/archived/TASK_169_v2_integration_testing.yaml +0 -52
- package/core/dev/pipeline/backups/archive_2026-02-04T22-07-22/TASK_QUEUE_backup.json +0 -95
- package/core/dev/pipeline/docs/multi-agent-coordination.md +0 -446
- package/core/dev/pipeline/docs/overview.md +0 -406
- package/core/dev/pipeline/docs/task-management.md +0 -412
- package/core/dev/pipeline/docs/workflows.md +0 -32
- package/core/dev/pipeline/orchestrator/schemas/task_queue_schema.json +0 -58
- package/core/dev/pipeline/orchestrator/schemas/task_schema.json +0 -139
- package/core/dev/pipeline/plans/PLAN_task_cli_refactor.md +0 -281
- package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +0 -27
- package/core/dev/plans/ANSUZ_SLIM_PLAN.md +0 -68
- package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +0 -120
- package/core/dev/plans/FORMATION_RETIREMENT_PLAN.md +0 -313
- package/core/dev/plans/KUZU_REPLACEMENT_PLAN.md +0 -126
- package/core/dev/scripts/CRASH_DIAGNOSIS.md +0 -47
- package/core/dev/scripts/README.md +0 -142
- package/core/dev/scripts/RUN_ONCE.bat +0 -40
- package/core/dev/scripts/START_DEV_AGENT.bat +0 -63
- package/core/dev/scripts/START_MULTI_AGENT.bat +0 -80
- package/core/dev/scripts/dev_agent_scheduler.ps1 +0 -536
- package/core/dev/scripts/init_git.ps1 +0 -396
- package/core/dev/scripts/init_git.sh +0 -337
- package/core/dev/scripts/init_project.ps1 +0 -851
- package/core/dev/scripts/init_project.sh +0 -477
- package/core/dev/scripts/multi_agent_scheduler.ps1 +0 -2021
- package/core/dev/scripts/multi_agent_scheduler.sh +0 -1344
- package/core/dev/scripts/run_once.sh +0 -47
- package/core/dev/scripts/setup_rag.ps1 +0 -304
- package/core/dev/scripts/setup_rag.sh +0 -230
- package/core/dev/scripts/start_dev_agent.sh +0 -137
- package/core/dev/scripts/start_multi_agent.sh +0 -41
- package/core/dev/scripts/sync_task_queue.ps1 +0 -505
- package/core/dev/scripts/sync_task_queue.sh +0 -392
- package/core/dev/specs/agent-connect/PROTOCOL.md +0 -177
- package/core/dev/specs/agent-connect/SDK_GUIDE.md +0 -915
- package/core/dev/specs/agent-connect/examples/minimal-site/README.md +0 -137
- package/core/dev/specs/agent-connect/examples/minimal-site/package.json +0 -12
- package/core/dev/specs/agent-connect/examples/minimal-site/public/index.html +0 -116
- package/core/dev/specs/agent-connect/examples/minimal-site/public/install-redirect.js +0 -73
- package/core/dev/specs/agent-connect/examples/minimal-site/server.js +0 -516
- package/core/dev/specs/agent-connect/examples/minimal-site/test/smoke.test.mjs +0 -263
- 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
- package/core/dev/specs/agent-connect/history/DEFECT_REPORT_kenaz-agent-hub_2026-09-09_v0.4-prompt-mojibake.md +0 -90
- package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-07.md +0 -61
- package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-08_relay-v1-conformance.md +0 -76
- package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-09_v0.4-session-channel.md +0 -87
- package/core/dev/specs/agent-connect/history/FLOWPIX_REPLY_agent-hub_2026-09-08_relay-v1-answers.md +0 -68
- package/core/dev/specs/agent-connect/history/FLOWPIX_bridge_CONTRACT.md +0 -642
- package/core/dev/specs/agent-connect/history/FLOWPIX_spikes_relay_spike_relay.mjs +0 -244
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-07.md +0 -77
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_direction-change-v0.3.md +0 -44
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_relay-v1-answers-ack.md +0 -37
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_relay-v1-conformance.md +0 -56
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_annotations-install-coldstart.md +0 -44
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_empty-prompt-first-turn.md +0 -56
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_prompt-mojibake-fixed.md +0 -48
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_session-channel.md +0 -57
- package/core/dev/specs/agent-connect/history/PROTOCOL_v0.3_snapshot.md +0 -379
- package/core/dev/specs/agent-connect/history/README.md +0 -47
- package/core/dev/specs/peer/CODEX_INTERACTIVE_PUSH.md +0 -65
- package/core/dev/specs/peer/CODEX_MANAGED_DAEMON.md +0 -54
- package/core/dev/specs/peer/CODEX_PEER_PULL_SPIKE.md +0 -54
- package/core/dev/specs/peer/MODS_SPIKE.md +0 -133
- package/core/dev/specs/peer/PER_PROJECT_ADDRESSING.md +0 -143
- package/core/dev/specs/peer/SWARM_PROTOCOLS.md +0 -256
- package/core/dev/specs/peer/codex-hook-shim-peer.test.js +0 -113
- package/core/dev/specs/peer/codex-push-launcher.test.js +0 -142
- package/core/dev/specs/peer/setup-codex-hooks.test.js +0 -60
- package/core/dev/specs/peer-ansuz/DELIVERY_SPIKE.md +0 -397
- package/core/dev/specs/peer-ansuz/DESIGN.md +0 -109
- package/core/dev/specs/peer-ansuz/spike/claude_poke.js +0 -129
- package/core/dev/specs/peer-ansuz/spike/claude_session_start_hook.js +0 -37
- package/core/dev/specs/peer-ansuz/spike/claude_settings_hook.json +0 -14
- package/core/dev/specs/peer-ansuz/spike/codex_appserver_client.js +0 -105
- package/core/dev/specs/peer-ansuz/spike/codex_appserver_query.js +0 -29
- package/core/dev/specs/peer-ansuz/spike/extract_timeline.js +0 -26
- package/core/dev/specs/synapse/SYMBOL_IDENTITY.md +0 -305
- package/core/dev/spikes/A4-cypher-varlen.md +0 -176
- package/core/dev/spikes/bloat-rounds.sh +0 -25
- package/core/dev/spikes/mods/clean-claude.js +0 -9
- package/core/dev/spikes/mods/drive-probe.js +0 -29
- package/core/dev/spikes/mods/e2e-peer.js +0 -71
- package/core/dev/spikes/mods/evidence/e2e-peer-control.json +0 -17
- package/core/dev/spikes/mods/evidence/e2e-peer-mod.json +0 -17
- package/core/dev/spikes/mods/evidence/probe-006a6e65-d807-4bc1-b648-fd7d1edfe0b4.jsonl +0 -4
- package/core/dev/spikes/mods/evidence/probe-cebdf1f7-f9c4-47a8-83cd-7c0786177404.jsonl +0 -12
- package/core/dev/spikes/mods/evidence/probe-eb21147d-10ce-4c32-93ed-37ca37d88d5a.jsonl +0 -11
- package/core/dev/spikes/mods/evidence/stream-cebdf1f7.jsonl +0 -18
- package/core/dev/spikes/mods/kenaz-mod-probe/.claude-plugin/plugin.json +0 -1
- package/core/dev/spikes/mods/kenaz-mod-probe/hooks/hooks.json +0 -1
- package/core/dev/spikes/mods/kenaz-mod-probe/hooks/register.ts +0 -65
- 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); });
|