kenaz 0.3.10 → 0.3.12

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 (131) hide show
  1. package/core/.claude/skills/code_reviewer/SKILL.md +0 -1
  2. package/core/.claude/skills/code_reviewer/patterns.yaml +0 -8
  3. package/core/.claude/skills/dev_agent/SKILL.md +3 -6
  4. package/core/.claude/skills/heimdall/SKILL.md +5 -6
  5. package/core/.claude/skills/huginn/SKILL.md +11 -38
  6. package/core/.collaboration/core/task-2288-followups.test.js +2 -37
  7. package/core/.collaboration/core/task-2421-replica.test.js +229 -0
  8. package/core/.collaboration/core/task-api.js +223 -230
  9. package/core/.collaboration/core/task-api.test.js +1 -138
  10. package/core/.collaboration/core/task-replica.js +189 -0
  11. package/core/.formation/.merge-lock +1 -0
  12. package/core/.formation/bifrost/codex-doctor.js +12 -2
  13. package/core/.formation/bifrost/codex-doctor.test.js +15 -0
  14. package/core/.formation/bifrost/launch.js +2 -2
  15. package/core/.formation/bifrost/launch.test.js +2 -2
  16. package/core/.formation/bifrost/setup-codex.js +84 -16
  17. package/core/.formation/bifrost/shared/ansuz-boot-policy.js +3 -3
  18. package/core/.formation/bifrost/shared/codex-injection.js +3 -17
  19. package/core/.formation/bifrost/shared/consolidate.js +1 -51
  20. package/core/.formation/bifrost/sync.js +4 -5
  21. package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +1 -1
  22. package/core/.formation/tools/dispatch.js +2 -2
  23. package/core/.formation/tools/kenaz-stats.js +1 -1
  24. package/core/.formation/tools/mcp-allowlist.test.js +5 -3
  25. package/core/.formation/tools/post-complete/bounded-http.js +1 -40
  26. package/core/.formation/tools/post-complete/bounded-http.test.js +2 -89
  27. package/core/.formation/tools/post-complete/cognitive.js +2 -119
  28. package/core/.formation/tools/post-complete/task2420.test.js +129 -0
  29. package/core/.formation/tools/post-complete/testing.js +3 -21
  30. package/core/.formation/tools/post-complete.js +131 -436
  31. package/core/.formation/tools/post-complete.test.js +0 -135
  32. package/core/.formation/tools/route-audit.test.js +8 -20
  33. package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
  34. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
  35. package/core/.rule/INDEX.yaml +3 -3
  36. package/core/.rule/general/ddx-synapse-stack.yaml +11 -11
  37. package/core/.rule/general/specialist-identity-contract.yaml +5 -5
  38. package/core/.rule/general/synapse-code-intelligence.yaml +25 -111
  39. package/core/.rule/general/worktree-isolation.yaml +2 -2
  40. package/core/.rule/pipeline/task-execution.yaml +6 -9
  41. package/core/.specialist/Amelia/README.md +5 -5
  42. package/core/.specialist/Amelia/capability.yaml +10 -18
  43. package/core/.specialist/Amelia/knowledge.yaml +15 -66
  44. package/core/.specialist/Amelia/workflow.yaml +12 -21
  45. package/core/.specialist/Ansuz/capability.yaml +12 -87
  46. package/core/.specialist/Ansuz/concepts/autonomous_agenda.yaml +1 -1
  47. package/core/.specialist/Ansuz/concepts/autonomy_contract.yaml +2 -3
  48. package/core/.specialist/Ansuz/concepts/capability_pointers.yaml +1 -3
  49. package/core/.specialist/Ansuz/concepts/cognitive_fluency_contract.yaml +8 -21
  50. package/core/.specialist/Ansuz/concepts/cognitive_systems_overview.yaml +0 -4
  51. package/core/.specialist/Ansuz/concepts/cognitive_systems_turn_context.yaml +1 -1
  52. package/core/.specialist/Ansuz/concepts/completion_tracking.yaml +0 -1
  53. package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +0 -3
  54. package/core/.specialist/Ansuz/concepts/evolvable_guardrails.yaml +3 -3
  55. package/core/.specialist/Ansuz/concepts/fatigue_handoff.yaml +1 -7
  56. package/core/.specialist/Ansuz/concepts/guardrails_immutable.yaml +3 -4
  57. package/core/.specialist/Ansuz/concepts/identity.yaml +1 -2
  58. package/core/.specialist/Ansuz/concepts/phase9_verification.yaml +0 -1
  59. package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +3 -4
  60. package/core/.specialist/Ansuz/core.yaml +3 -6
  61. package/core/.specialist/Ansuz/elixir.yaml +6 -6
  62. package/core/.specialist/Ansuz/exo_cortex_tools.yaml +5 -9
  63. package/core/.specialist/Ansuz/guards/blind_spot_guards.yaml +2 -2
  64. package/core/.specialist/Ansuz/guards/lesson_guards.yaml +6 -19
  65. package/core/.specialist/Ansuz/identity_contract.yaml +4 -4
  66. package/core/.specialist/Ansuz/knowledge.yaml +13 -40
  67. package/core/.specialist/Ansuz/modules/ddx.yaml +2 -2
  68. package/core/.specialist/Ansuz/modules/prophecy.yaml +19 -19
  69. package/core/.specialist/Ansuz/modules/pulse_empty.yaml +6 -17
  70. package/core/.specialist/Ansuz/tools/ansuz-handoff-snapshot.js +3 -102
  71. package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.js +2 -44
  72. package/core/.specialist/Ansuz/workflow.yaml +32 -76
  73. package/core/.specialist/Forseti/core.yaml +2 -7
  74. package/core/.specialist/Forseti/rule.yaml +1 -1
  75. package/core/.specialist/Heimdall/SKILL.md +2 -3
  76. package/core/.specialist/Heimdall/capability.yaml +22 -45
  77. package/core/.specialist/Heimdall/core.yaml +24 -126
  78. package/core/.specialist/Heimdall/principles.yaml +8 -11
  79. package/core/.specialist/Heimdall/rule.yaml +2 -2
  80. package/core/.specialist/Huginn/capability.yaml +31 -52
  81. package/core/.specialist/Huginn/core.yaml +43 -123
  82. package/core/.specialist/Huginn/principles.yaml +5 -5
  83. package/core/.specialist/Huginn/rule.yaml +3 -9
  84. package/core/.specialist/REFLECTION_TO_CONTROL.md +3 -4
  85. package/core/.specialist/shared/guard_schema.json +1 -1
  86. package/core/.specialist/shared/principles.yaml +6 -6
  87. package/core/.specialist/tools/__tests__/rea-detect.test.js +10 -0
  88. package/core/.specialist/tools/phase-gate.js +10 -230
  89. package/core/.specialist/tools/phase-gate.test.js +2 -2
  90. package/core/.specialist/tools/prompt-files.test.js +1 -1
  91. package/core/.specialist/tools/rea-detect.js +6 -2
  92. package/core/.specialist/tools/session.js +2 -89
  93. package/core/.synapse/memory-shared.json.sync +1 -0
  94. package/core/.system/memory/README.md +3 -0
  95. package/core/CLAUDE.md +6 -7
  96. package/core/KENAZ_CORE_VERSION +1 -1
  97. package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +75 -83
  98. package/core/dev/specs/peer/CODEX_MANAGED_DAEMON.md +54 -0
  99. package/core/dev/specs/peer/setup-codex-hooks.test.js +38 -0
  100. package/core/dev/spikes/task2145-evidence-20261004/backend-log-excerpt.txt +56 -0
  101. package/core/dev/spikes/task2145-evidence-20261004/graph.shadow +0 -0
  102. package/core/dev/spikes/task2145-evidence-20261004/graph.wal +0 -0
  103. package/core/dev/spikes/task2145-evidence-20261004/graph_corrupt_suspect.marker +1 -0
  104. package/core/plugins/kenaz/INVENTORY.md +1 -1
  105. package/core/plugins/kenaz/scripts/kenaz.test.js +70 -1
  106. package/core/plugins/kenaz/scripts/ygg.js +45 -4
  107. package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +0 -1
  108. package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +0 -8
  109. package/core/plugins/kenaz/skills/dev_agent/SKILL.md +3 -6
  110. package/lib/cli.js +17 -9
  111. package/lib/cli.test.js +10 -0
  112. package/lib/dashboard.test.js +3 -3
  113. package/package.json +6 -6
  114. package/vendor/install-kenaz-cli.js +167 -16
  115. package/core/.claude/hooks/handoff-snapshot-on-decision.js +0 -188
  116. package/core/.claude/skills/synapse_maintain/SKILL.md +0 -84
  117. package/core/.collaboration/core/task-2264-single-after-capture.test.js +0 -61
  118. package/core/.formation/tools/alaya-gate.js +0 -198
  119. package/core/.formation/tools/post-complete/health.js +0 -464
  120. package/core/.formation/tools/post-complete/task2038.test.js +0 -246
  121. package/core/.formation/tools/post-complete/task2341.test.js +0 -74
  122. package/core/.rule/general/cortex-tools.yaml +0 -181
  123. package/core/.rule/general/memory-language.yaml +0 -67
  124. package/core/.rule/general/memory-source-priority.yaml +0 -207
  125. package/core/.rule/general/synapse-cognitive-fusion.yaml +0 -301
  126. package/core/.specialist/Ansuz/concepts/cognitive_systems_hippocampus.yaml +0 -14
  127. package/core/.specialist/Ansuz/concepts/write_verification.yaml +0 -26
  128. package/core/.specialist/Ansuz/modules/recall.yaml +0 -45
  129. package/core/.specialist/tools/MEMORY_RECALL_TRIGGERS.md +0 -156
  130. package/core/.specialist/tools/context-injection.js +0 -395
  131. package/core/dev/scripts/probe/hippocampus-concurrency.js +0 -247
@@ -1,128 +1,120 @@
1
- # CLI-first install and first-run flow (TASK_2367)
1
+ # CLI-first 安裝與首次使用流程(TASK_2367 / TASK_2376)
2
2
 
3
- Status: DRAFT for user review. Target flow: install once -> `claude` in the project folder -> `/kenaz init` -> `/Ansuz`.
4
- Yggdrasil via `/kenaz ygg join`, Alaya via `k-cli alaya register` (device code), Dashboard only for viewing.
3
+ 狀態:**待用戶審閱**(2026-10-11 改寫為中文並更新到現況)。
4
+ 目標流程:`npx kenaz install` 一次 → 在專案資料夾開 `claude` → `/kenaz init` → `/kenaz:Ansuz`。
5
+ Yggdrasil 用 `kenaz ygg create` / `kenaz ygg join <key>`;Alaya 用 `kenaz alaya register`(機碼註冊,不需登入)。Dashboard 已退役。
5
6
 
6
- ## 1. Pieces and where they live
7
+ ## 1. 組成與位置
7
8
 
8
- | Piece | What | Location |
9
+ | 元件 | 是什麼 | 位置 |
9
10
  |---|---|---|
10
- | kenaz-server | headless backend (loopback, port 18080 with fallback), MCP + HTTP | binary `kenaz-server[.exe]` (cargo `--no-default-features --features server`) |
11
- | kenaz-core | plugins, hooks, scripts, rules the plugin and server read | dir holding `plugins/kenaz`; the server reads tasks via `KENAZ_SOURCE_DIR=<kenaz-core>` |
12
- | kenaz plugin | `/kenaz` command + hooks | `<kenaz-core>/plugins/kenaz` |
13
- | installer | `node dashboard/scripts/install-kenaz-cli.js install\|uninstall\|status` | this card |
14
- | user state | service launcher, logs, `~/.kenaz/` | per-user home |
15
- | project state | `.kenaz/` in each project | never touched by uninstall |
11
+ | npm `kenaz` | CLI(install / uninstall / status / doctor / init / ygg / alaya / model / stats / learn)+打包好的 kenaz-core | `npx kenaz ...` |
12
+ | `@kenazai/server-<os>-<cpu>` | kenaz-server 執行檔(無視窗後端:MCP + HTTP,只聽本機,port 18080,被占用時往後找) | 安裝後在 `~/.kenaz/bin/` |
13
+ | kenaz-core | plugin、hooks、腳本、規則(plugin 與 server 都讀它) | 安裝後在 `~/.kenaz/core/` |
14
+ | kenaz plugin | `/kenaz` 指令、`/kenaz:Ansuz` 與 hooks | `~/.kenaz/core/plugins/kenaz` |
15
+ | 使用者層資料 | 服務啟動器、log、專案清單、憑證、教訓(`~/.kenaz/knowledge`) | `~/.kenaz/` |
16
+ | 專案資料 | 任務、日誌、專案設定 | 每個專案的 `.kenaz/`,**解除安裝永遠不動** |
16
17
 
17
- kenaz-core location: the repo root in dev/mother installs; otherwise the bundled `kenaz-core` next to the binary (same candidates the Rust `peer_mod::resolve_plugin_dir` uses). Installer: `--core-dir`, else `KENAZ_CORE_DIR`, else the repo root of the script. Binary: `--server-bin`, else `<core>/../kenaz-server`, `<core>/bin/`, `dashboard/src-tauri/target/release/`, `~/.kenaz/bin/`.
18
+ ## 2. 以服務方式執行的東西
18
19
 
19
- ## 2. What runs as a service
20
+ 只有 `kenaz-server`:每個使用者一份(不需系統管理員、不是系統服務),登入時啟動,只聽本機。
20
21
 
21
- Only `kenaz-server`, per user (no admin, no system service), started at logon, loopback only. The installer writes a launcher/unit and starts it.
22
-
23
- | OS | Mechanism | Files |
22
+ | 作業系統 | 方式 | 檔案 |
24
23
  |---|---|---|
25
- | Windows (implemented, real) | per-user Scheduled Task `KenazServer`, trigger at logon, runs `wscript.exe ~\.kenaz\kenaz-server.vbs` (hidden window, sets `KENAZ_SOURCE_DIR`). If `schtasks /Create` is refused (ONLOGON may need elevation) falls back to a `KenazServer.vbs` in the user Startup folder and starts it now | `~\.kenaz\kenaz-server.vbs` |
26
- | macOS (template written, untested) | launchd user agent `ai.kenaz.server`, RunAtLoad + KeepAlive, `launchctl load -w` | `~/Library/LaunchAgents/ai.kenaz.server.plist` |
27
- | Linux (template written, untested) | `systemd --user` unit `kenaz-server.service`, `enable --now`; headless boxes need `loginctl enable-linger $USER` | `~/.config/systemd/user/kenaz-server.service` |
28
-
29
- Templates are generated by `install-kenaz-cli.js` (`launchdPlist`, `systemdUnit`, `winVbs`) and unit-tested as strings; only Windows is exercised for real.
24
+ | Windows | 使用者層排程工作 `KenazServer`(登入時觸發,執行隱藏視窗的 `wscript.exe ~\.kenaz\kenaz-server.vbs`)。若 `schtasks /Create` 被拒(登入觸發可能需要管理員權限),改放「啟動」資料夾的 `KenazServer.vbs` 並立即啟動 | `~\.kenaz\kenaz-server.vbs` |
25
+ | macOS | launchd 使用者代理 `ai.kenaz.server`(RunAtLoad + KeepAlive) | `~/Library/LaunchAgents/ai.kenaz.server.plist` |
26
+ | Linux(有 systemd) | `systemd --user` 的 `kenaz-server.service`;無人登入的主機需 `loginctl enable-linger $USER` | `~/.config/systemd/user/kenaz-server.service` |
27
+ | Linux(無 systemd:容器、WSL1、精簡主機)(TASK_2413) | crontab 加一行標記過的 `@reboot`(只取代自己那行,其他保留)並立即在背景啟動;連 crontab 都沒有時,立即啟動並印出要加進開機腳本的指令 | `~/.kenaz/kenaz-server.log` |
30
28
 
31
- mcp.json: the server follows the owner rule in `server_boot.rs` (it does not rewrite `~/.claude/mcp.json` when the Dashboard owns it).
29
+ `kenaz doctor` 會顯示目前的服務模式(systemd / launchd / windows task / startup launcher / cron / manual)。
32
30
 
33
- ## 3. Plugin registration
31
+ ## 3. Plugin 登錄(Claude Code)
34
32
 
35
- Chosen path: `CLAUDE_CODE_PLUGIN_DIRS` in `~/.claude/settings.json` `env`, same as kenaz-peer (proven 2026-10-03). The installer appends `<kenaz-core>/plugins/kenaz`:
36
- - merge, never replace; idempotent; slash/case-insensitive identity (Windows `;`, else `:` separator)
37
- - one-time backup `settings.json.bak-kenaz-peer` (same name as Rust, so there is one pristine backup), atomic tmp+rename
38
- - malformed JSON or odd shape is reported and left untouched
39
- - gates: `claude --version` >= 2.1.287 (else skipped with a "run `claude update`" message, rerun install afterwards), `KENAZ_SKIP_CLAUDE_PROVISION=1` opt-out, plugin dir must contain `hooks/hooks.json`
33
+ 在 `~/.claude/settings.json` 的 `env.CLAUDE_CODE_PLUGIN_DIRS` 加入 `~/.kenaz/core/plugins/kenaz`:
34
+ - 只合併、不覆蓋;重複執行結果相同;路徑比對不分斜線方向與大小寫
35
+ - 第一次修改前備份一份 `settings.json.bak-kenaz-peer`,寫入用暫存檔+改名(不會寫一半)
36
+ - JSON 格式壞掉時只回報、不動它
37
+ - 前提:`claude --version` >= 2.1.287(太舊會跳過並提示 `claude update`)
40
38
 
41
- Alternative considered: a Claude Code plugin marketplace. Better update story and no settings edit, but needs a published marketplace repo and consent flow; revisit when kenaz-core is distributed publicly. The env-var path needs nothing external.
39
+ Codex:`setup-codex.js` 寫入 `~/.codex/config.toml` 的 MCP 設定、原生 hooks 與 `/prompts:kenaz`。升級時會把舊路徑、舊參數的 kenaz hooks 換成新的(TASK_2418),不動使用者自己的 hooks。
42
40
 
43
- Unification debt: this duplicates the Rust `features/project/peer_mod.rs` logic in Node (installer must work before any Rust binary runs). Later: have the Rust path register `plugins/kenaz` too (or call a shared CLI subcommand) and drop the Node copy. Both write the same env var and backup, so they coexist.
41
+ ## 4. 首次使用
44
42
 
45
- ## 4. First run
43
+ 1. 在任何資料夾執行 `claude`(plugin 自動載入,不需 `--plugin-dir`)。
44
+ 2. `/kenaz init`:建立 `.kenaz/project.yaml`、確認後端有回應,並列出可選步驟(不強制):
45
+ - 團隊協作:`kenaz ygg create`(自建房間金鑰並加入,把金鑰給隊友)或 `kenaz ygg join <key>`
46
+ - Alaya:`kenaz alaya register`(機碼註冊,立即可用)
47
+ - 下一步:`/kenaz:Ansuz`
48
+ 3. 後端沒回應時,init 會說明並指向 `npx kenaz install`。
46
49
 
47
- 1. `claude` in any folder (plugin auto-loaded, no `--plugin-dir`).
48
- 2. `/kenaz init` creates `.kenaz/project.yaml`, probes `/api/health`, and prints the optional steps and the next step. It does not force them:
49
- - Yggdrasil: `/kenaz ygg join <key>` (also `status`, `leave`)
50
- - Alaya: `k-cli alaya register` (device code, approve in any browser)
51
- - Next: `/Ansuz`
52
- 3. If the backend is down, init says so and points at `install-kenaz-cli.js install`.
50
+ ## 5. 升級與解除安裝
53
51
 
54
- ## 5. Upgrade and uninstall
52
+ - **自動更新**:kenaz-server 會從簽章過的 feed 自行更新(執行檔+kenaz-core+plugin),重新啟動後生效。
53
+ - **手動升級**:`npx kenaz@latest install`。會先停掉「這個路徑」的舊 server(不會依名稱砍其他程序)、換新檔、**明確重啟服務**,再用 `/api/health` 確認跑的是新版本;版本不符會警告並印出重啟指令(TASK_2418)。
54
+ - **解除安裝**:`npx kenaz uninstall` 只移除我們加的 plugin 項目、服務啟動器與 `~/.kenaz/core`、`~/.kenaz/bin`。**專案的 `.kenaz/` 與 `~/.kenaz` 的使用者資料保留**,settings 備份也保留。
55
+ - `status` 顯示 plugin 登錄與服務狀態;任何指令加 `--dry-run` 只印計畫、不改東西。
55
56
 
56
- - Upgrade: replace kenaz-core and the binary, rerun `install` (idempotent: registration stays "already", launcher is rewritten, task recreated with /F). Stop the old server first on Windows (`schtasks /End /TN KenazServer`) if the binary is locked.
57
- - Uninstall: `install-kenaz-cli.js uninstall` removes only our entry from `CLAUDE_CODE_PLUGIN_DIRS`, deletes the scheduled task / Startup launcher / plist / unit and the launcher file. Project `.kenaz/` data and `~/.kenaz/` memory data stay. The settings backup stays.
58
- - `status` reports plugin registration and service state. `--dry-run` prints the plan for any command and changes nothing.
57
+ ## 6. 驗收
59
58
 
60
- ## 6. Verification
59
+ 自動化(已完成):installer、CLI、Codex hooks 的 node 測試只用暫存 home,不碰真實環境。
61
60
 
62
- Automated (done): `node --test dashboard/scripts/install-kenaz-cli.test.js plugins/kenaz/scripts/kenaz.test.js` (temp home only).
61
+ **乾淨 Windows 環境實測(待用戶執行,等下一次發版後)**:
62
+ 1. 準備乾淨環境:Windows Sandbox(控制台 → 開啟或關閉 Windows 功能 → 勾「Windows 沙箱」→ 重開機),或新建一個本機 Windows 帳號。
63
+ 2. 在裡面安裝 Node.js(LTS)與 Claude Code,登入 Claude。
64
+ 3. `npx kenaz install`。預期:plugin 已登錄、排程工作或啟動資料夾啟動器、`http://127.0.0.1:18080/api/health` 有回應、最後的版本確認為 ok。
65
+ 4. 新建空資料夾 → `claude` → `/kenaz init` → `/kenaz:Ansuz`。預期:Ansuz 正常開機,不需 `--plugin-dir`。
66
+ 5. `npx kenaz doctor`:每列應為 ok 或 skip。
67
+ 6. `npx kenaz uninstall`。預期:plugin 已移除、服務啟動器已刪、測試資料夾的 `.kenaz` 仍在。
68
+ 7. 記下每一步卡住、看不懂、或需要額外動作的地方,回報給 Ansuz。
63
69
 
64
- Acceptance "clean Windows user profile" is PENDING and must be run by the user:
65
- 1. New Windows user (or VM), install Node and `claude` >= 2.1.287; copy kenaz-core and `kenaz-server.exe`.
66
- 2. `node dashboard\scripts\install-kenaz-cli.js install --dry-run --core-dir <core> --server-bin <exe>` and read the plan.
67
- 3. Rerun without `--dry-run`. Expect: `plugin: added`, scheduled task `KenazServer` (or Startup launcher), server answering `http://127.0.0.1:18080/api/health`.
68
- 4. `mkdir test-proj; cd test-proj; claude` then `/kenaz init`, then `/Ansuz`. Expect Ansuz boots with no `--plugin-dir`.
69
- 5. `... uninstall`. Expect `plugin: removed`, task gone (`schtasks /Query /TN KenazServer` fails), `test-proj\.kenaz` still present.
70
+ ## 7. 未決事項
70
71
 
71
- ## 7. Open points
72
+ - macOS 與 Linux 的服務步驟目前只有單元測試,真機只有 Windows 實際跑過(field report 顯示 macOS 用戶已在使用,升級重啟問題已修,待下一版驗證)。
73
+ - Windows 在沒有管理員權限時會走「啟動資料夾」路線;乾淨環境實測要確認實際走哪一條。
74
+ - Codex 桌面版(managed daemon)目前無法即時推送 peer 訊息,改為下一次提問時拉取(TASK_2419,調查記錄見 `dev/specs/peer/CODEX_MANAGED_DAEMON.md`)。
72
75
 
73
- - macOS/Linux paths are untested; ONLOGON elevation behaviour on Windows needs the clean-profile run to confirm which branch is taken.
74
- - Packaging: who ships kenaz-core + binary to `~/.kenaz/bin` (release pipeline, not this card).
75
- - `k-cli` itself is not yet installed by this script; Alaya registration assumes it is on PATH.
76
+ ## 8. 已定案的決策(用戶,2026-10-09 起)
76
77
 
77
- ## 8. Decisions (user, 2026-10-09)
78
- - **Distribution: npm package** - `npx kenaz install` (name `kenaz` is free on npm as of 2026-10-09; publishing needs the user's npm account and explicit OK). Per-platform `kenaz-server` binaries ship as optional platform packages (esbuild pattern: `@kenazai/server-win32-x64`, `-darwin-arm64`, `-darwin-x64`, `-linux-x64`, `-linux-arm64`).
79
- - **Both harnesses:** the installer detects Claude Code and/or Codex. Claude Code -> plugin registration (this doc, section 3). Codex -> `.formation/bifrost/setup-codex.js` (MCP in ~/.codex/config.toml, native hooks, AGENTS.md).
80
- - **Platforms: Windows, macOS and Linux in the first release.**
81
- - **Node.js is a prerequisite** (both harnesses already need it); the installer checks the version and explains how to get it.
82
- - **Updates: kenaz-server self-updates** (binary + kenaz-core + plugin) from the release feed, with signature verification.
83
- - **Codex parity with Claude Code:** MCP tools, Yggdrasil commands, Ansuz flow and Codex-native subagent dispatch (TASK_2315).
78
+ - **發佈:npm**。`npx kenaz install`;各平台執行檔是選用的平台套件(`@kenazai/server-win32-x64`、`-darwin-arm64`、`-darwin-x64`、`-linux-x64`、`-linux-arm64`)。
79
+ - **兩種 harness 都支援**:偵測到 Claude Code 就登錄 plugin,偵測到 Codex 就設定 MCP 與 hooks。
80
+ - **平台**:Windows、macOS、Linux 首版就支援。
81
+ - **Node.js 是前提**(兩種 harness 本來就需要);installer 會檢查版本並說明怎麼裝。
82
+ - **更新**:kenaz-server 自行更新,有簽章驗證。
83
+ - **Codex 與 Claude Code 對等**:MCP 工具、Yggdrasil 指令、Ansuz 流程、Codex 原生 subagent 派工。
84
+ - **發版**:等用戶說「發版」才發(緊急修正除外)。Windows 與 Linux 在本機建置,只有 macOS 在 GitHub Actions 建置以節省費用(流程見記憶 `reference_npm_cli_release`)。
84
85
 
85
- ## 9. npm package `kenaz` (TASK_2377)
86
+ ## 9. npm 套件結構(TASK_2377)
86
87
 
87
- Layout: `packages/kenaz` (CLI + staged kenaz-core), `packages/server-{win32-x64,darwin-arm64,darwin-x64,linux-x64,linux-arm64}` (`@kenazai/server-*`, `bin/` filled by CI, gitignored). All versions move in lockstep with `dashboard/src-tauri/Cargo.toml`.
88
+ - `packages/kenaz`:CLI+打包的 kenaz-core(`prepack` 跑 `scripts/stage.js`,只收 git 追蹤的白名單檔案)。
89
+ - `packages/server-*`:各平台執行檔;版本與 `dashboard/src-tauri/Cargo.toml` 同步。
90
+ - install:檢查 Node 版本 → 複製 kenaz-core 到 `~/.kenaz/core`、執行檔到 `~/.kenaz/bin` → 記錄 `~/.kenaz/install.json` → Claude Code 與 Codex 各自設定 → 啟動並確認服務。
91
+ - doctor:Node 版本、core 版本、server 執行檔、`/api/health`、服務模式、Claude plugin、Codex MCP 與 hooks;任何 FAIL 以代碼 1 結束。
92
+ - 本機檢查:`npm pack` 後裝到暫存前綴,用 `--home <暫存目錄>` 執行(絕不用真實 home)。
88
93
 
89
- - `kenaz install|uninstall|status|doctor [--dry-run] [--home d] [--no-service] [--no-claude] [--no-codex] [--server-bin p]`.
90
- - install: Node >= 18 check; copy packaged kenaz-core to `~/.kenaz/core` and the platform binary dir to `~/.kenaz/bin`; record `~/.kenaz/install.json`; Claude Code (if `claude` on PATH) via `install-kenaz-cli.js` (both plugins + service); Codex (if `codex` on PATH) via `setup-codex.js` (MCP, native hooks, `/prompts:kenaz`). Neither is reimplemented.
91
- - uninstall reverses both (`setup-codex.js uninstall()` removes only the MCP entry, shim hook blocks and prompt it added) and deletes core/bin/install.json; project `.kenaz/` is never touched.
92
- - doctor: node version, core version, server binary, `/api/health` reachable, Claude plugins registered, Codex MCP + hooks. Exit 1 on any FAIL.
93
- - `prepack` runs `scripts/stage.js`: stages core with the same git-tracked whitelist as the Dashboard bundle (`kenaz-core-staging.js`, ~19 MB) and vendors `install-kenaz-cli.js`.
94
- - Release: `.gitlab-ci.yml` cross-builds the 5 targets (native runners; darwin-x64 cross-compiled on the M1 runner), packs tarballs as artifacts, `publish` is manual and does not call npm.
95
- - Local check: `npm pack ./packages/kenaz` etc., install into a prefix, run with `--home <tmp>` (never the real home).
96
- - Open: the server loads `libonnxruntime` via `ORT_DYLIB_PATH`; CI ships it next to the binary in `bin/`, but the service launchers do not set `ORT_DYLIB_PATH` yet. macOS/Linux service steps and all 5 CI build jobs are unverified until a pipeline/Mac run.
94
+ ## 10. 從 Dashboard 遷移(TASK_2389 / TASK_2396)
97
95
 
98
- ## 10. 從 Dashboard 遷移 (TASK_2389)
99
-
100
- 範圍:只偵測並提示。CLI 不會結束 Dashboard 行程、不改登錄檔或 Startup 資料夾。原因:`kenaz-server` 與 Dashboard 都綁 port 18080,不能同時執行。
96
+ Dashboard(Tauri 桌面 app)已於 2026-10-11 從 repo 移除,kenaz-server 是唯一後端。已安裝的 Dashboard 可繼續用但不再更新。兩者都綁 port 18080,不能同時執行;CLI 只偵測並提示,不會替你結束 Dashboard。
101
97
 
102
98
  ### 給使用者的步驟(可直接貼進公告)
103
99
 
104
- 1. 在系統匣(右下角)對 Kenaz Dashboard(BLOCK ACTION)按右鍵,選「結束 / Quit」,確認程式完全關閉。
105
- 2. 開啟 Dashboard 設定,關閉「開機時啟動 / Launch at startup」,避免登入後它又佔回 port 18080。
100
+ 1. 在系統匣(右下角)對 Kenaz Dashboard 按右鍵,選「結束 / Quit」,確認程式完全關閉。
101
+ 2. 開啟 Dashboard 設定,關閉「開機時啟動」,避免登入後它又占回 port 18080。
106
102
  3. 在終端機執行:`npx kenaz install`
107
103
  4. 到專案資料夾執行 `claude`,再輸入 `/kenaz init`。
108
104
  5. 驗證:`npx kenaz doctor`,每一列應為 ok 或 skip。
109
105
 
110
- 若 Dashboard 還在執行,`npx kenaz install` 會印出上面的步驟並以非零碼結束,不會啟動服務。只想先裝 plugin 與 core、暫不啟動服務:加 `--keep-dashboard`。
111
- 要改回 Dashboard:從開始選單啟動它,並在設定中重新開啟「開機時啟動」。`kenaz uninstall` 結束時會印出同樣的提示。
106
+ Dashboard 還在執行時,`npx kenaz install` 會印出上面的步驟並結束,不會啟動服務;只想先裝 plugin 與 core:加 `--keep-dashboard`。
112
107
 
113
- ### 資料共用,不需搬移(已讀程式確認)
108
+ ### 資料共用,不需搬移
114
109
 
115
110
  | 資料 | 位置 |
116
111
  |---|---|
117
112
  | 專案清單 | `~/.kenaz/projects.json` |
118
113
  | 憑證 / relay key / 各專案 Yggdrasil key | `~/.kenaz/credentials.yaml` |
119
114
  | 偏好設定 | `~/.kenaz/config.json` |
120
- | Synapse 記憶 | `<專案>/.synapse/db/memory.db` |
121
115
  | 任務、日誌 | `<專案>/.kenaz/` |
122
116
  | Peer 收件匣 | `peer_dir(project_root)/inbox.sqlite` |
123
117
 
124
- 只有 Dashboard 自己的 webview、LSP 快取與 `.env.local` 放在 `%LOCALAPPDATA%\Kenaz Dashboard`,CLI 不使用。Dashboard 不寫使用者層級的 hooks,只在啟動時補各專案的 post-commit hook(`hook_installer.rs`),所以不需要 marker 檔。
125
-
126
- ### doctor 為什麼檢查 plugin 路徑
118
+ 記憶系統(Synapse 等)已於 2026-10-11 移除(TASK_2411),舊的 `.synapse/` 與 `~/.kenaz/cortex/*.db` 留在磁碟上、不再使用;教訓改用 `kenaz learn`。
127
119
 
128
- Dashboard 執行時(`peer_mod.rs`)會重新登錄自己 kenaz-core 內的 `kenaz-peer`;自 TASK_2384 起登錄會取代舊副本,所以重新啟動 Dashboard 會把 `CLAUDE_CODE_PLUGIN_DIRS` 的 `kenaz-peer` 改回它的路徑。`kenaz doctor` 會列出:Dashboard 是否執行 / 已安裝、18080 的持有者、`CLAUDE_CODE_PLUGIN_DIRS` 是否指向 Dashboard 的 core。
120
+ Dashboard 各項行為的去向(哪些已搬進 kenaz-server、哪些刻意移除)記錄在 git 歷史中此檔 2026-10-11 之前的版本。
@@ -0,0 +1,54 @@
1
+ # Codex desktop managed daemon as a peer push target (TASK_2419, field report d200d31d)
2
+
3
+ ## Problem
4
+
5
+ The Codex desktop "Work" app (codex 0.162.x) talks to a managed `codex app-server` daemon. Kenaz's Bifrost launcher
6
+ (`.formation/bifrost/launch.js`, TASK_2167) only registers the ws_url of an app-server it starts itself
7
+ (`.kenaz/codex/sessions.json`). With the desktop app nothing is registered, so `/api/peer/status` showed
8
+ `delivery_mode=none, sessions=[]` and the poller logged NO_TARGET, although mail was stored and the
9
+ UserPromptSubmit `--pull` hook (`codex-hook-shim.js`) does deliver it on the next prompt.
10
+
11
+ ## Findings (evidence, codex-cli 0.162.0 on this machine, read-only)
12
+
13
+ 1. `codex app-server --help`: `--listen <URL>` accepts `stdio://` (default), `unix://`, `unix://PATH`, `ws://IP:PORT`, `off`.
14
+ Subcommands: `daemon` (bootstrap/start/restart/stop/version/enable-remote-control), `proxy` ("Proxy stdio bytes to the
15
+ running app-server control socket", `--sock <SOCKET_PATH>`). WS auth flags: `--ws-auth capability-token|signed-bearer-token`,
16
+ `--ws-token-file`, `--ws-shared-secret-file` (apply to ws listeners).
17
+ 2. The managed daemon is discoverable on disk, no env var needed: `$CODEX_HOME/app-server-control/app-server-control.sock`
18
+ (default `~/.codex/app-server-control/`), plus `$CODEX_HOME/app-server-daemon/daemon.pid`, `settings.json`
19
+ (`remoteControlEnabled:false`). Both existed here. It is a unix-domain socket (default path), not a ws://host:port.
20
+ 3. Protocol (`codex app-server generate-json-schema --out <tmp>`, ClientRequest.json) has the methods needed:
21
+ `initialize`, `thread/list`, `thread/loaded/list`, `thread/read`, `thread/resume`, `turn/start`, `turn/steer`,
22
+ `turn/interrupt`, `thread/inject_items`. So injection into a thread is possible in principle (the same methods
23
+ `codex_wake.rs::deliver_interactive` already uses over ws for launcher-started servers).
24
+ 4. Auth on the unix control socket: none visible; access control is filesystem permission of the socket owner.
25
+
26
+ ## Why Kenaz does not push into it (decision)
27
+
28
+ - Unverified transport: whether the `unix://` listener speaks the same WebSocket framing as `ws://` is not confirmed here
29
+ (no daemon was started or connected; real user state must not be touched). `codex_wake.rs` uses `tokio_tungstenite::connect_async`
30
+ (TCP ws only); a unix-socket client would be new, and tokio `UnixStream` does not exist on Windows (AF_UNIX there needs another crate).
31
+ - Target ambiguity: the desktop app owns many threads; `thread/loaded/list` does not say which one the user is looking at. Guessing
32
+ wrongly injects a turn into the wrong conversation.
33
+ - Safety: push is deliberately opt-in (`codex_push: true`, user decision 2026-10-02) because an injected turn drives a full-access
34
+ agent from untrusted peer text. A socket that any same-user process can drive, with a guessed thread, widens that.
35
+
36
+ Conclusion: not supported safely today. Degraded mode is made explicit instead.
37
+
38
+ ## What changed
39
+
40
+ - `GET /api/peer/status`: when no push/pull session exists but a managed daemon is detected
41
+ (`codex_managed_daemon_in`, `peer_delivery_mode.rs`) a session `{agent:"codex", session_id:"codex-managed-daemon",
42
+ delivery_mode:"pull-on-prompt"}` is added; `delivery_mode` becomes `pull-on-prompt` with a Codex-specific note
43
+ (no push channel; mail arrives on the next prompt via `--pull`; run `node .formation/bifrost/codex-doctor.js`).
44
+ `kenaz ygg status` (plugins/kenaz/scripts/ygg.js) relays that mode/note unchanged.
45
+ - NO_TARGET log detail (`peer_delivery_state::no_claude_reason`): TASK_2410 had already replaced the misleading
46
+ "claude not found on PATH" for Codex nodes; it now says "Codex managed daemon detected: no push channel ... via the --pull hook"
47
+ when the daemon is present, instead of "Codex not started via the Kenaz launcher".
48
+ - `codex-doctor.js`: `diagnose()` returns `managed_daemon` and adds an informational note (does not fail `ok`).
49
+
50
+ ## Possible future push (not done)
51
+
52
+ Confirm ws-over-unix framing against a temp `CODEX_HOME` daemon, add a unix transport to `AppServerClient`, require
53
+ `codex_push: true`, and pick the thread via an explicit user choice (or the thread whose last hook-shim pull was most recent)
54
+ rather than guessing. Until then use the pull path, or start Codex via `launch.js` with `codex_push: true`.
@@ -20,3 +20,41 @@ test('missingHookEvents reports unregistered events and registerHooks fills exac
20
20
  const cfg = fs.readFileSync(path.join(home, 'config.toml'), 'utf8');
21
21
  assert.equal((cfg.match(/\[\[hooks\.UserPromptSubmit\.hooks\]\]/g) || []).length, 1);
22
22
  });
23
+
24
+ // TASK_2418: a kenaz block from an old checkout, or a UserPromptSubmit block without --pull, is stale: rewritten once.
25
+ test('stale kenaz hook blocks (old checkout path, missing --pull) are detected and replaced exactly once; user hooks stay', () => {
26
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-home-'));
27
+ process.env.CODEX_HOME = home;
28
+ const s = require('../../../.formation/bifrost/setup-codex.js');
29
+ const cur = s.getHookShimPath(process.cwd()).split('\\').join('/');
30
+ const old = '/old/checkout/.formation/bifrost/openai/codex-hook-shim.js';
31
+ const h = (ev, cmd) => ['[[hooks.' + ev + ']]', '', '[[hooks.' + ev + '.hooks]]', 'type = "command"', "command = '" + cmd + "'", 'timeout = 15', ''];
32
+ const cfgPath = path.join(home, 'config.toml');
33
+ fs.writeFileSync(cfgPath, [
34
+ 'model = "x"', '',
35
+ ...h('Stop', 'node user-hook.js'),
36
+ '# kenaz-dashboard native hooks (TASK_1733) -- auto-registered by setup-codex.js',
37
+ ...h('PreToolUse', 'node "' + old + '"'),
38
+ ...h('PostToolUse', 'node "' + cur + '"'),
39
+ ...h('UserPromptSubmit', 'node "' + cur + '"'),
40
+ ...h('SessionStart', 'node "' + cur + '"'),
41
+ ...h('Stop', 'node "' + old + '"'),
42
+ ].join('\n'));
43
+ assert.deepEqual(s.missingHookEvents(), ['PreToolUse', 'UserPromptSubmit', 'Stop']);
44
+ assert.equal(s.areHooksAlreadyRegistered(process.cwd()), true); // PostToolUse/SessionStart are current
45
+ s.registerHooks(() => {}, process.cwd(), s.missingHookEvents());
46
+ assert.deepEqual(s.missingHookEvents(), []);
47
+ const cfg = fs.readFileSync(cfgPath, 'utf8');
48
+ for (const ev of ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionStart', 'Stop']) {
49
+ const n = (cfg.match(new RegExp('\\[\\[hooks\\.' + ev + '\\.hooks\\]\\][^\\[]*codex-hook-shim\\.js', 'g')) || []).length;
50
+ assert.equal(n, 1, ev + ' has exactly one kenaz handler');
51
+ }
52
+ assert.ok(!cfg.includes('/old/checkout'));
53
+ assert.match(cfg, /UserPromptSubmit\.hooks\]\][^\[]*--pull/);
54
+ assert.ok(cfg.includes('node user-hook.js') && cfg.includes('model = "x"'));
55
+ assert.equal((cfg.match(/kenaz-dashboard native hooks/g) || []).length, 1);
56
+ // uninstall removes the rewritten blocks and still keeps the user's
57
+ s.uninstall({ silent: true });
58
+ const after = fs.readFileSync(cfgPath, 'utf8');
59
+ assert.ok(!after.includes('codex-hook-shim.js') && after.includes('node user-hook.js'));
60
+ });
@@ -0,0 +1,56 @@
1
+ 2026-10-03T21:07:10.515249Z INFO MCP tool call tool="synapse_efe_feedback" plugin=None
2
+ 2026-10-03T21:07:45.377199Z INFO MCP tool call tool="alaya_recall" plugin=None
3
+ 2026-10-03T21:07:53.574088Z WARN [session_watcher] reflex check: role=assistant, text_len=119, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
4
+ 2026-10-03T21:07:53.574900Z WARN [session_watcher] reflex check: role=assistant, text_len=119, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
5
+ 2026-10-03T21:07:54.168238Z INFO MCP tool call tool="cortex_recall" plugin=None
6
+ 2026-10-03T21:07:54.372461Z INFO MCP tool call tool="alaya_recall" plugin=None
7
+ 2026-10-03T21:07:54.728901Z INFO MCP tool call tool="hippocampus_episode_recall" plugin=None
8
+ 2026-10-03T21:07:55.213271Z INFO MCP tool call tool="synapse_health" plugin=None
9
+ 2026-10-03T21:08:37.929534Z WARN [session_watcher] reflex check: role=assistant, text_len=2101, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
10
+ 2026-10-03T21:08:37.930488Z WARN [session_watcher] reflex check: role=assistant, text_len=2101, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
11
+ 2026-10-03T21:10:36.990645Z INFO MCP tool call tool="synapse_efe_feedback" plugin=None
12
+ 2026-10-03T21:10:37.101533Z INFO MCP tool call tool="synapse_efe_feedback" plugin=None
13
+ 2026-10-03T21:10:47.558693Z WARN [session_watcher] reflex check: role=assistant, text_len=153, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
14
+ 2026-10-03T21:10:47.558790Z WARN [session_watcher] reflex check: role=assistant, text_len=153, blocks=1, project_path=Some("C:/Users/user/Documents/GitHub/KenazAI")
15
+ 2026-10-03T21:12:34.601726Z INFO backend logging initialized version="0.2.263" pid=8024 started_at=2026-10-04 05:12:34.601 profile="debug" log_path=C:\Users\user\AppData\Local\Kenaz Dashboard\backend.log
16
+ 2026-10-03T21:12:34.601803Z INFO startup mark label="after_init_tracing" t_ms=3 thread=main
17
+ 2026-10-03T21:12:35.010413Z INFO startup mark label="setup_entry" t_ms=411 thread=main
18
+ 2026-10-03T21:12:35.015743Z INFO startup mark label="mcp_servers_spawn_issued" t_ms=417 thread=main
19
+ 2026-10-03T21:12:35.015846Z INFO startup mark label="mcp_servers_task_entered" t_ms=417 thread=tokio-runtime-worker
20
+ 2026-10-03T21:12:35.016550Z INFO startup mark label="mcp_servers_start_done" t_ms=417 thread=tokio-runtime-worker
21
+ 2026-10-03T21:12:35.262343Z INFO startup mark label="onnx_preload_dylib_done" t_ms=663 thread=main
22
+ 2026-10-03T21:12:35.262445Z INFO startup mark label="onnx_warmup_thread_spawn_issued" t_ms=663 thread=main
23
+ 2026-10-03T21:12:35.262574Z INFO startup mark label="delta_graph_recovery_thread_spawn_issued" t_ms=664 thread=main
24
+ 2026-10-03T21:12:35.262763Z INFO startup mark label="sleep_cycle_daemon_spawn_issued" t_ms=664 thread=main
25
+ 2026-10-03T21:12:35.262754Z INFO startup mark label="onnx_warmup_begin" t_ms=664 thread=onnx-warmup
26
+ 2026-10-03T21:12:35.262886Z INFO startup mark label="delta_graph_recovery_begin" t_ms=664 thread=delta-gap-recovery
27
+ 2026-10-03T21:12:35.265586Z INFO startup mark label="http_sidecar_task_spawn_issued" t_ms=667 thread=main
28
+ 2026-10-03T21:12:35.265647Z INFO startup mark label="auto_index_watcher_spawn_issued" t_ms=667 thread=main
29
+ 2026-10-03T21:12:35.265727Z INFO startup mark label="http_sidecar_task_entered" t_ms=667 thread=tokio-runtime-worker
30
+ 2026-10-03T21:12:35.265789Z INFO startup mark label="setup_fn_returning" t_ms=667 thread=main
31
+ 2026-10-03T21:12:35.265824Z INFO startup mark label="auto_index_watcher_task_entered" t_ms=667 thread=tokio-runtime-worker
32
+ 2026-10-03T21:12:35.291199Z INFO [synapse] IS_INDEXING set — indexing started project=C:/Users/user/Documents/GitHub/KenazAI mode="incremental"
33
+ 2026-10-03T21:12:35.291229Z INFO [synapse] reindex phase transition phase="init" project=C:/Users/user/Documents/GitHub/KenazAI
34
+ 2026-10-03T21:12:35.358777Z INFO Loading ONNX embedding model [query] (ArcticEmbedXs, dim=384) from C:\Users\user\Documents\GitHub\KenazAI\dashboard\src-tauri\target\debug\resources\onnx\arctic-embed-xs
35
+ 2026-10-03T21:12:35.635924Z INFO Embedding engine [query] loaded successfully (ArcticEmbedXs, dim=384, outputs=["last_hidden_state"], token_type_ids=true)
36
+ 2026-10-03T21:12:35.636005Z INFO startup mark label="onnx_warmup_done" t_ms=1037 thread=onnx-warmup
37
+ 2026-10-03T21:12:36.101980Z INFO startup mark label="http_server_start_begin" t_ms=1503 thread=tokio-runtime-worker
38
+ 2026-10-03T21:12:36.119887Z INFO startup mark label="http_server_start_ready" t_ms=1521 thread=tokio-runtime-worker
39
+ 2026-10-03T21:12:36.122223Z INFO synapse worker forwarder configuration workers_enabled=true current_project=C:/Users/user/Documents/GitHub/KenazAI
40
+ 2026-10-03T21:12:36.122290Z INFO reaper armed leaseless_idle_secs=300 leased_idle_secs=900 tick_secs=60 per_worker_rss_cap_mb=400 total_rss_cap_mb=1200 max_workers=3
41
+ 2026-10-03T21:12:36.275465Z INFO startup mark label="ready_wait_end" t_ms=1676 thread=tokio-runtime-worker
42
+ 2026-10-03T21:12:37.479820Z INFO [synapse] reindex phase transition phase="kuzu_write_start:2_files" project=C:/Users/user/Documents/GitHub/KenazAI
43
+ 2026-10-03T21:12:37.815038Z ERROR [conn pool] GRAPH CORRUPT SUSPECT for C:/Users/user/Documents/GitHub/KenazAI\.synapse\db\graph: Failed to DETACH DELETE Symbol nodes for dashboard/src-tauri/src/features/synapse/scanner.rs: Query execution failed: kuzu native exception 0xC0000005 — no further kuzu calls will be issued against this Database for the rest of this process's lifetime (persisted; the next start will also refuse it). Recovery: rebuild_graph.
44
+ 2026-10-03T21:12:37.815639Z ERROR [synapse] dashboard/src-tauri/src/features/synapse/scanner.rs write hit a native ACCESS_VIOLATION — graph marked corrupt_suspect, aborting the round instead of quarantining just this file: Failed to DETACH DELETE Symbol nodes for dashboard/src-tauri/src/features/synapse/scanner.rs: Query execution failed: kuzu native exception 0xC0000005
45
+ 2026-10-03T21:12:37.815845Z ERROR Incremental index round aborted mid-batch (with_connection closure returned Err): Failed to DETACH DELETE Symbol nodes for dashboard/src-tauri/src/features/synapse/scanner.rs: Query execution failed: kuzu native exception 0xC0000005. Recording phase=failed and NOT advancing last_commit_hash, so the file(s) that caused this are retried (re-appear in the git diff) on the next incremental cycle instead of being permanently skipped.
46
+ 2026-10-03T21:12:37.823713Z INFO [synapse] IS_INDEXING cleared project=C:/Users/user/Documents/GitHub/KenazAI
47
+ 2026-10-03T21:12:39.477700Z INFO [synapse spool] drain: attempted=1 replayed=0 still_pending=1 dead_lettered=0
48
+ 2026-10-03T21:12:40.139564Z INFO startup mark label="session_watcher_start_begin" t_ms=5541 thread=tokio-runtime-worker
49
+ 2026-10-03T21:12:40.139634Z INFO startup mark label="session_watcher_start_done" t_ms=5541 thread=tokio-runtime-worker
50
+ 2026-10-03T21:12:40.139740Z INFO [session_watcher] Watching "C:\\Users\\user\\.claude\\projects\\C--Users-user-Documents-GitHub-KenazAI" (notify + polling fallback)
51
+ 2026-10-03T21:12:44.502961Z INFO startup mark label="synapse_kuzu_memorydb_open_done" t_ms=9904 thread=delta-gap-recovery
52
+ 2026-10-03T21:12:44.680900Z INFO startup mark label="delta_graph_recovery_done" t_ms=10082 thread=delta-gap-recovery
53
+ 2026-10-03T21:12:44.706667Z INFO startup mark label="heat_cache_load_done" t_ms=10108 thread=delta-gap-recovery
54
+ 2026-10-03T21:13:01.645377Z INFO MCP tool call tool="synapse_efe_feedback" plugin=None
55
+ 2026-10-03T21:13:01.645462Z INFO MCP tool call tool="synapse_efe_feedback" plugin=None
56
+ 2026-10-03T21:13:01.650906Z INFO MCP tool call tool="synapse_cypher" plugin=None
@@ -0,0 +1 @@
1
+ Failed to DETACH DELETE Symbol nodes for dashboard/src-tauri/src/features/synapse/scanner.rs: Query execution failed: kuzu native exception 0xC0000005
@@ -37,7 +37,7 @@ Hooks only act in Kenaz projects (`.kenaz/project.yaml`, `_project-guard.js`): r
37
37
  | Class | Skills |
38
38
  |---|---|
39
39
  | ship (34) | architect_agent, assessment_agent, backend_agent, backend_engineer, code_reviewer, dev_agent, designer, developer, feature_agent, frontend_agent, incident_response, product_manager, qa_engineer, security_agent, spec_optimizer, sre_agent, adapt, animate, audit, bolder, clarify, colorize, critique, delight, distill, extract, frontend-design, harden, normalize, onboard, optimize, polish, quieter, ui-ux-pro-max |
40
- | core | heimdall, huginn, synapse_maintain, sunny, optimization_agent |
40
+ | core | heimdall, huginn, sunny, optimization_agent |
41
41
  | retire | legacy_dev |
42
42
  | third-party / domain (not shipped; install separately from .agents/skills) | vercel-react-best-practices, supabase-postgres-best-practices, remotion-best-practices, pcb-review |
43
43
 
@@ -59,7 +59,7 @@ test('ygg status fixture: masked key, room, peers, mail (TASK_2366)', () => {
59
59
  const s = buildStatus(
60
60
  { enabled: true, effective_project_key: 'acme/app', node_name: 'laptop', publishable_key: key, key_source: 'project', collaboration_projects: ['acme/app'] },
61
61
  { connected: true, nodes: [{ node_name: 'laptop', is_online: true, updated_at: new Date().toISOString() }, { node_name: 'desk', is_online: true, updated_at: new Date().toISOString() }, { node_name: 'old', is_online: false }, { node_name: 'BA-Cheng', is_online: true, updated_at: '2026-06-30T00:00:00Z' }] },
62
- { pending_count: 2, undeliverable_count: 1 });
62
+ { pending_count: 2, waiting_count: 2, undeliverable_count: 1 });
63
63
  const out = renderStatus(s);
64
64
  assert.ok(!out.includes('SECRETSECRET') && !JSON.stringify(s).includes('SECRETSECRET'));
65
65
  assert.match(out, /ygk_\*{4}1234 \(source: project\)/);
@@ -67,6 +67,7 @@ test('ygg status fixture: masked key, room, peers, mail (TASK_2366)', () => {
67
67
  assert.match(out, /Relay:\s+connected/);
68
68
  assert.match(out, /Peers online:\s+desk$/m);
69
69
  assert.match(out, /Pending mail:\s+2/);
70
+ assert.match(out, /Waiting for a session: 2/);
70
71
  assert.match(out, /Undeliverable:\s+1/);
71
72
  const down = renderStatus(buildStatus({ enabled: false }, { connected: false, reason: 'relay unreachable', nodes: [] }, null));
72
73
  assert.match(down, /NOT connected \(relay unreachable\)/);
@@ -121,3 +122,71 @@ test('ygg join activates the room in collaboration_projects; status explains how
121
122
  const { buildStatus, renderStatus } = require('./ygg.js');
122
123
  assert.match(renderStatus(buildStatus(cfg, { connected: false, nodes: [] }, null)), /not active in this project - run "ygg join <key>"/);
123
124
  });
125
+
126
+ // TASK_2416: `ygg create` posts to the relay (config relay_url), then joins with the returned key.
127
+ // One local stub plays both the Kenaz backend and the relay; nothing touches ~/.kenaz or ~/.claude.
128
+ async function withStub(relayReply, fn) {
129
+ const http = require('http');
130
+ const seen = { relay: [], config: [] };
131
+ const cfg = { enabled: false, effective_project_key: 'tmp-room', node_name: 'n', publishable_key: '', collaboration_projects: [], relay_url: '' };
132
+ const srv = http.createServer((req, res) => {
133
+ let b = ''; req.on('data', (d) => { b += d; });
134
+ req.on('end', () => {
135
+ res.setHeader('content-type', 'application/json');
136
+ if (req.url === '/api/v1/teams') {
137
+ seen.relay.push({ method: req.method, body: b });
138
+ res.statusCode = relayReply.status; return res.end(JSON.stringify(relayReply.body));
139
+ }
140
+ if (req.url.startsWith('/api/nodes')) return res.end('[]');
141
+ if (req.url.startsWith('/api/yggdrasil/config')) {
142
+ if (req.method === 'POST') { seen.config.push(JSON.parse(b)); return res.end(JSON.stringify({ ok: true })); }
143
+ return res.end(JSON.stringify(cfg));
144
+ }
145
+ res.end(JSON.stringify({ success: true, created: false }));
146
+ });
147
+ });
148
+ await new Promise((r) => srv.listen(0, '127.0.0.1', r));
149
+ cfg.relay_url = 'ws://127.0.0.1:' + srv.address().port;
150
+ const prev = process.env.KENAZ_HTTP_PORT;
151
+ process.env.KENAZ_HTTP_PORT = String(srv.address().port);
152
+ try { return await fn(seen, cfg); } finally {
153
+ if (prev === undefined) delete process.env.KENAZ_HTTP_PORT; else process.env.KENAZ_HTTP_PORT = prev;
154
+ srv.close();
155
+ }
156
+ }
157
+
158
+ test('ygg create posts the name to the relay, joins with the new key, prints it once (TASK_2416)', async () => {
159
+ const KEY = 'ygk_' + '2'.repeat(32);
160
+ await withStub({ status: 201, body: { ok: true, team_id: 't1', team_name: 'Acme', key: KEY, max_nodes: 10 } }, async (seen) => {
161
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ygg-create-'));
162
+ const { createTeam, renderStatus } = require('./ygg.js');
163
+ const s = await createTeam(dir, 'Acme', 'project');
164
+ assert.deepStrictEqual(JSON.parse(seen.relay[0].body), { name: 'Acme' });
165
+ assert.strictEqual(seen.config[0].publishable_key, KEY);
166
+ assert.strictEqual(seen.config[0].enabled, true);
167
+ assert.deepStrictEqual(seen.config[0].collaboration_projects, ['tmp-room']);
168
+ const out = renderStatus(s);
169
+ assert.match(out, new RegExp('share with teammates: kenaz ygg join ' + KEY));
170
+ assert.ok(!out.includes('Key: ' + KEY), 'status line keeps the key masked');
171
+ });
172
+ });
173
+
174
+ test('ygg create explains 429, 503, missing relay_url and unreachable relay (TASK_2416)', async () => {
175
+ const { createTeam } = require('./ygg.js');
176
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ygg-create-err-'));
177
+ await withStub({ status: 429, body: { ok: false, error: 'Too many team creations (limit 3 per hour)' } }, async (seen) => {
178
+ await assert.rejects(createTeam(dir, '', 'project'), /rate limited by the relay: Too many team creations/);
179
+ assert.strictEqual(seen.config.length, 0, 'no join after a failed create');
180
+ });
181
+ await withStub({ status: 503, body: { ok: false, error: 'REGISTRY_SERVICE_KEY not configured' } }, async () => {
182
+ await assert.rejects(createTeam(dir, '', 'project'), /does not offer self-serve keys: REGISTRY_SERVICE_KEY/);
183
+ });
184
+ await withStub({ status: 201, body: {} }, async (seen, cfg) => {
185
+ cfg.relay_url = '';
186
+ await assert.rejects(createTeam(dir, '', 'project'), /no relay_url configured/);
187
+ });
188
+ await withStub({ status: 201, body: {} }, async (seen, cfg) => {
189
+ cfg.relay_url = 'ws://127.0.0.1:1'; // nothing listens
190
+ await assert.rejects(createTeam(dir, '', 'project'), /cannot reach the relay at http:\/\/127\.0\.0\.1:1/);
191
+ });
192
+ });
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- // node ygg.js <join <key> [--project|--global] | status | leave> [--project-path <dir>] [--json]
3
+ // node ygg.js <join <key> [--project|--global] | create [--name N] [--project|--global] | status | leave> [--project-path <dir>] [--json]
4
4
  // Yggdrasil setup without the Dashboard (TASK_2366). Thin client over the Rust backend's
5
5
  // /api/yggdrasil/config + /api/peer/status (same per-project/global key rules as the Dashboard,
6
6
  // TASK_2321/2349). k-cli `ygg` shells out to this file - the one implementation.
@@ -73,6 +73,7 @@ function buildStatus(cfg, relay, peer) {
73
73
  ? 'Room "' + cfg.effective_project_key + '" is a generic name; ' + online.join(', ') + ' may be an unrelated project. For a private room set yggdrasil_project: "' + cfg.effective_project_key + '-<unique-id>" in .kenaz/yggdrasil.yaml.'
74
74
  : '',
75
75
  pending_mail: peer && typeof peer.pending_count === 'number' ? peer.pending_count : null,
76
+ waiting_mail: peer && typeof peer.waiting_count === 'number' ? peer.waiting_count : null,
76
77
  undeliverable_mail: peer && typeof peer.undeliverable_count === 'number' ? peer.undeliverable_count : null,
77
78
  // TASK_2380: real delivery mode per live session (push | pull-on-prompt | none), never a bare 'enabled'.
78
79
  delivery_mode: peer && peer.delivery_mode ? peer.delivery_mode : 'unknown',
@@ -92,10 +93,15 @@ function renderStatus(s) {
92
93
  'Relay: ' + (s.relay_connected ? 'connected' : 'NOT connected' + (s.relay_note ? ' (' + s.relay_note + ')' : '')),
93
94
  'Peers online: ' + (s.peers_online.length ? s.peers_online.join(', ') : 'none'),
94
95
  'Pending mail: ' + (s.pending_mail == null ? 'unknown' : s.pending_mail),
96
+ 'Waiting for a session: ' + (s.waiting_mail == null ? 'unknown' : s.waiting_mail) + ' (stored on this node, readable)',
95
97
  'Undeliverable: ' + (s.undeliverable_mail == null ? 'unknown' : s.undeliverable_mail),
96
98
  'Delivery: ' + s.delivery_mode + (s.sessions.length ? '' : ' (no live sessions)'),
97
99
  ];
98
100
  for (const x of s.sessions) L.push(' - ' + x.agent + ' ' + String(x.session_id).slice(0, 8) + ': ' + x.delivery_mode);
101
+ if (s.created) {
102
+ L.push('', 'New team key (shown once, save it): ' + s.created.key + (s.created.team_name ? ' [' + s.created.team_name + ']' : ''));
103
+ L.push('share with teammates: kenaz ygg join ' + s.created.key);
104
+ }
99
105
  if (s.room_warning) L.push('WARNING: ' + s.room_warning);
100
106
  if (s.delivery_note) L.push('Note: ' + s.delivery_note);
101
107
  return L.join('\n');
@@ -141,6 +147,38 @@ async function join(dir, key, scope) {
141
147
  return status(dir);
142
148
  }
143
149
 
150
+ // TASK_2416: self-serve room key. POST <relay>/api/v1/teams (anonymous, per-IP limited), then join
151
+ // with the new key so this node is in the room immediately. The full key is returned once.
152
+ async function createTeam(dir, name, scope) {
153
+ const cfg = await getConfig(dir);
154
+ if (cfg.error) throw new Error(cfg.error);
155
+ const base = relayHttpBase(cfg.relay_url);
156
+ if (!base) throw new Error('no relay_url configured - cannot reach the relay to create a key (run "npx kenaz@latest install" or set relay_url in .kenaz/yggdrasil.yaml)');
157
+ let res;
158
+ try {
159
+ res = await fetch(base + '/api/v1/teams', {
160
+ method: 'POST', headers: { 'content-type': 'application/json' },
161
+ body: JSON.stringify(name ? { name } : {}), signal: AbortSignal.timeout(15000),
162
+ });
163
+ } catch (e) {
164
+ throw new Error('cannot reach the relay at ' + base + ' (' + (e.cause && e.cause.code || e.message) + '); check your network and try again');
165
+ }
166
+ let data = null;
167
+ try { data = await res.json(); } catch (e) { /* non-JSON body */ }
168
+ if (!res.ok || !data || !data.key) {
169
+ const why = data && data.error ? data.error : 'HTTP ' + res.status;
170
+ if (res.status === 429) throw new Error('rate limited by the relay: ' + why);
171
+ if (res.status === 503) throw new Error('this relay does not offer self-serve keys: ' + why);
172
+ throw new Error('relay refused to create a key (HTTP ' + res.status + '): ' + why);
173
+ }
174
+ let s;
175
+ try { s = await join(dir, data.key, scope); } catch (e) {
176
+ // The key exists on the relay now; never lose it because the local join failed.
177
+ throw new Error('key created (' + data.key + ') but joining failed: ' + e.message + ' - retry with: kenaz ygg join ' + data.key);
178
+ }
179
+ return Object.assign(s, { created: { key: data.key, team_name: data.team_name || name || '' } });
180
+ }
181
+
144
182
  async function leave(dir) {
145
183
  const cfg = await getConfig(dir);
146
184
  await postConfig(dir, postBody(cfg, { key_scope: 'clear_project', collaboration_projects: withCollab(cfg, false) }));
@@ -148,7 +186,7 @@ async function leave(dir) {
148
186
  }
149
187
 
150
188
  async function main(argv) {
151
- const rest = []; let dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); let scope = 'project'; let json = false;
189
+ const rest = []; let dir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); let scope = 'project'; let json = false; let name = '';
152
190
  for (let i = 0; i < argv.length; i++) {
153
191
  const a = argv[i];
154
192
  if (a === '--project-path' && argv[i + 1]) dir = argv[++i];
@@ -156,6 +194,8 @@ async function main(argv) {
156
194
  else if (a === '--global') scope = 'global';
157
195
  else if (a === '--project') scope = 'project';
158
196
  else if (a === '--json') json = true;
197
+ else if (a === '--name' && argv[i + 1]) name = argv[++i];
198
+ else if (a.startsWith('--name=')) name = a.slice(7);
159
199
  else rest.push(a);
160
200
  }
161
201
  dir = path.resolve(dir);
@@ -163,9 +203,10 @@ async function main(argv) {
163
203
  let s;
164
204
  try {
165
205
  if (sub === 'join') s = await join(dir, rest[1], scope);
206
+ else if (sub === 'create') s = await createTeam(dir, name, scope);
166
207
  else if (sub === 'leave') s = await leave(dir);
167
208
  else if (sub === 'status') s = await status(dir);
168
- else throw new Error('unknown ygg subcommand: ' + sub + ' (join <key> | status | leave)');
209
+ else throw new Error('unknown ygg subcommand: ' + sub + ' (join <key> | create [--name N] | status | leave)');
169
210
  } catch (e) {
170
211
  console.error('ygg: ' + e.message + (/fetch failed|timeout|abort/i.test(e.message) ? ' (is the Kenaz backend running on ' + BASE() + '?)' : ''));
171
212
  process.exit(1);
@@ -173,5 +214,5 @@ async function main(argv) {
173
214
  console.log(json ? JSON.stringify(s, null, 2) : renderStatus(s));
174
215
  }
175
216
 
176
- module.exports = { nodeIsOnline, maskKey, buildStatus, renderStatus, join };
217
+ module.exports = { nodeIsOnline, maskKey, buildStatus, renderStatus, join, createTeam };
177
218
  if (require.main === module) main(process.argv.slice(2));
@@ -232,7 +232,6 @@ description: 程式碼審查、三級問題分類(Blocker/Suggestion/Nit)、
232
232
 
233
233
  | Mutation | Counterpart | 缺失嚴重度 |
234
234
  |----------|-------------|-----------|
235
- | `hippocampus_episode_start` | `hippocampus_episode_close` | leak(episode 永不歸檔) |
236
235
  | `task-api.js claim` | `task-api.js complete` 或 `move blocked` | orphan(task 卡在 active/) |
237
236
  | `addEventListener` | `removeEventListener` | leak(listener 記憶體洩漏) |
238
237
  | `setInterval` | `clearInterval` | leak(定時器持續觸發) |