kenaz 0.3.9 → 0.3.11

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 (117) hide show
  1. package/core/.claude/commands/Ansuz.md +12 -16
  2. package/core/.claude/hooks/memory-guard.js +13 -0
  3. package/core/.claude/skills/code_reviewer/SKILL.md +0 -1
  4. package/core/.claude/skills/code_reviewer/patterns.yaml +0 -8
  5. package/core/.claude/skills/dev_agent/SKILL.md +3 -6
  6. package/core/.claude/skills/heimdall/SKILL.md +5 -6
  7. package/core/.claude/skills/huginn/SKILL.md +11 -38
  8. package/core/.collaboration/core/task-api.js +2 -218
  9. package/core/.collaboration/core/task-api.test.js +1 -138
  10. package/core/.formation/bifrost/launch.js +2 -2
  11. package/core/.formation/bifrost/launch.test.js +2 -2
  12. package/core/.formation/bifrost/shared/ansuz-boot-policy.js +3 -3
  13. package/core/.formation/bifrost/shared/codex-injection.js +3 -17
  14. package/core/.formation/bifrost/shared/consolidate.js +1 -51
  15. package/core/.formation/bifrost/sync.js +4 -5
  16. package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +1 -1
  17. package/core/.formation/tools/dispatch.js +2 -2
  18. package/core/.formation/tools/kenaz-stats.js +259 -0
  19. package/core/.formation/tools/kenaz-stats.test.js +180 -0
  20. package/core/.formation/tools/mcp-allowlist.test.js +5 -3
  21. package/core/.formation/tools/post-complete/bounded-http.js +1 -40
  22. package/core/.formation/tools/post-complete/bounded-http.test.js +2 -89
  23. package/core/.formation/tools/post-complete/cognitive.js +2 -119
  24. package/core/.formation/tools/post-complete/testing.js +3 -21
  25. package/core/.formation/tools/post-complete.js +2 -428
  26. package/core/.formation/tools/post-complete.test.js +0 -135
  27. package/core/.formation/tools/prompt.js +14 -0
  28. package/core/.formation/tools/route-audit.test.js +8 -20
  29. package/core/.rule/INDEX.yaml +3 -3
  30. package/core/.rule/general/ddx-synapse-stack.yaml +11 -11
  31. package/core/.rule/general/specialist-identity-contract.yaml +5 -5
  32. package/core/.rule/general/synapse-code-intelligence.yaml +25 -111
  33. package/core/.rule/general/worktree-isolation.yaml +2 -2
  34. package/core/.rule/pipeline/task-execution.yaml +6 -9
  35. package/core/.specialist/Amelia/README.md +5 -5
  36. package/core/.specialist/Amelia/capability.yaml +10 -18
  37. package/core/.specialist/Amelia/knowledge.yaml +15 -66
  38. package/core/.specialist/Amelia/workflow.yaml +12 -21
  39. package/core/.specialist/Ansuz/RULES.md +6 -0
  40. package/core/.specialist/Ansuz/capability.yaml +12 -87
  41. package/core/.specialist/Ansuz/concepts/autonomous_agenda.yaml +1 -1
  42. package/core/.specialist/Ansuz/concepts/autonomy_contract.yaml +2 -3
  43. package/core/.specialist/Ansuz/concepts/capability_pointers.yaml +1 -3
  44. package/core/.specialist/Ansuz/concepts/cognitive_fluency_contract.yaml +8 -21
  45. package/core/.specialist/Ansuz/concepts/cognitive_systems_overview.yaml +0 -4
  46. package/core/.specialist/Ansuz/concepts/cognitive_systems_turn_context.yaml +1 -1
  47. package/core/.specialist/Ansuz/concepts/completion_tracking.yaml +0 -1
  48. package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +0 -3
  49. package/core/.specialist/Ansuz/concepts/evolvable_guardrails.yaml +3 -3
  50. package/core/.specialist/Ansuz/concepts/fatigue_handoff.yaml +1 -7
  51. package/core/.specialist/Ansuz/concepts/guardrails_immutable.yaml +3 -4
  52. package/core/.specialist/Ansuz/concepts/identity.yaml +1 -2
  53. package/core/.specialist/Ansuz/concepts/phase9_verification.yaml +0 -1
  54. package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +3 -4
  55. package/core/.specialist/Ansuz/core.yaml +3 -6
  56. package/core/.specialist/Ansuz/elixir.yaml +6 -6
  57. package/core/.specialist/Ansuz/exo_cortex_tools.yaml +5 -9
  58. package/core/.specialist/Ansuz/guards/blind_spot_guards.yaml +2 -2
  59. package/core/.specialist/Ansuz/guards/lesson_guards.yaml +6 -19
  60. package/core/.specialist/Ansuz/identity_contract.yaml +4 -4
  61. package/core/.specialist/Ansuz/knowledge.yaml +13 -40
  62. package/core/.specialist/Ansuz/modules/ddx.yaml +2 -2
  63. package/core/.specialist/Ansuz/modules/prophecy.yaml +19 -19
  64. package/core/.specialist/Ansuz/modules/pulse_empty.yaml +6 -17
  65. package/core/.specialist/Ansuz/tools/ansuz-handoff-snapshot.js +3 -102
  66. package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.js +2 -44
  67. package/core/.specialist/Ansuz/workflow.yaml +32 -76
  68. package/core/.specialist/Forseti/core.yaml +2 -7
  69. package/core/.specialist/Forseti/rule.yaml +1 -1
  70. package/core/.specialist/Heimdall/SKILL.md +2 -3
  71. package/core/.specialist/Heimdall/capability.yaml +22 -45
  72. package/core/.specialist/Heimdall/core.yaml +24 -126
  73. package/core/.specialist/Heimdall/principles.yaml +8 -11
  74. package/core/.specialist/Heimdall/rule.yaml +2 -2
  75. package/core/.specialist/Huginn/capability.yaml +31 -52
  76. package/core/.specialist/Huginn/core.yaml +43 -123
  77. package/core/.specialist/Huginn/principles.yaml +5 -5
  78. package/core/.specialist/Huginn/rule.yaml +3 -9
  79. package/core/.specialist/REFLECTION_TO_CONTROL.md +3 -4
  80. package/core/.specialist/shared/guard_schema.json +1 -1
  81. package/core/.specialist/shared/principles.yaml +6 -6
  82. package/core/.specialist/tools/__tests__/task-2409-lessons.test.js +95 -0
  83. package/core/.specialist/tools/ansuz-boot-fast.js +1 -0
  84. package/core/.specialist/tools/lessons.js +234 -0
  85. package/core/.specialist/tools/phase-gate.js +10 -230
  86. package/core/.specialist/tools/phase-gate.test.js +2 -2
  87. package/core/.specialist/tools/prompt-files.test.js +1 -1
  88. package/core/.specialist/tools/session.js +2 -89
  89. package/core/.system/memory/README.md +3 -0
  90. package/core/CLAUDE.md +6 -7
  91. package/core/KENAZ_CORE_VERSION +1 -1
  92. package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +26 -0
  93. package/core/plugins/kenaz/INVENTORY.md +1 -1
  94. package/core/plugins/kenaz/commands/Ansuz.md +12 -16
  95. package/core/plugins/kenaz/scripts/kenaz.test.js +2 -1
  96. package/core/plugins/kenaz/scripts/ygg.js +2 -0
  97. package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +0 -1
  98. package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +0 -8
  99. package/core/plugins/kenaz/skills/dev_agent/SKILL.md +3 -6
  100. package/lib/cli.js +13 -3
  101. package/package.json +6 -6
  102. package/core/.claude/hooks/handoff-snapshot-on-decision.js +0 -188
  103. package/core/.claude/skills/synapse_maintain/SKILL.md +0 -84
  104. package/core/.formation/tools/alaya-gate.js +0 -198
  105. package/core/.formation/tools/post-complete/health.js +0 -464
  106. package/core/.formation/tools/post-complete/task2038.test.js +0 -246
  107. package/core/.formation/tools/post-complete/task2341.test.js +0 -74
  108. package/core/.rule/general/cortex-tools.yaml +0 -181
  109. package/core/.rule/general/memory-language.yaml +0 -67
  110. package/core/.rule/general/memory-source-priority.yaml +0 -207
  111. package/core/.rule/general/synapse-cognitive-fusion.yaml +0 -301
  112. package/core/.specialist/Ansuz/concepts/cognitive_systems_hippocampus.yaml +0 -14
  113. package/core/.specialist/Ansuz/concepts/write_verification.yaml +0 -26
  114. package/core/.specialist/Ansuz/modules/recall.yaml +0 -45
  115. package/core/.specialist/tools/MEMORY_RECALL_TRIGGERS.md +0 -156
  116. package/core/.specialist/tools/context-injection.js +0 -395
  117. package/core/dev/scripts/probe/hippocampus-concurrency.js +0 -247
@@ -14,7 +14,7 @@ ARGUMENTS 含 `[專案: X]` 或 `--project-path X` → PROJECT_PATH = X,下方
14
14
  - **自己做**:小而明確的修正(約 30 行內、單一檔案或少數檔案)→ 直接改,跑最近的測試,提交。
15
15
  - **開卡派工**:中型、可平行、或高風險路徑的工作:
16
16
  1. 用 `kenaz_task_create` 開卡(`projectPath` = 目前專案;寫清楚 requirements、acceptance criteria、scope paths)。查卡、改狀態用 `kenaz_task_list` / `kenaz_task_get` / `kenaz_task_update`。
17
- 2. 產生派工內容:`node .formation/tools/dispatch.js --skill=do --task=TASK_XXX --dispatch-mode=subagent`(加 `--project-path`;Codex 加 `--harness=codex`)。把輸出 JSON 的 `promptText` 交給 subagent,`agentParams` 照用(模型不要自己換)。
17
+ 2. 產生派工內容:`node .formation/tools/dispatch.js --skill=do --task=TASK_XXX --dispatch-mode=subagent`(有 PROJECT_PATH 加 `--project-path`)。把輸出 JSON 的 `promptText` 交給 subagent,`agentParams` 照用(模型不要自己換)。
18
18
  3. subagent 在 worktree 裡改完並 commit;你驗證後合併。
19
19
  - 開卡前先確認 scope 與驗收條件對準真正的問題,不要照著錯的假設派工。
20
20
 
@@ -23,24 +23,20 @@ ARGUMENTS 含 `[專案: X]` 或 `--project-path X` → PROJECT_PATH = X,下方
23
23
  - 有**對照組**:證明測試在舊程式上會失敗,或修改前後結果不同。
24
24
  - subagent 回報「未驗證」的項目,由你補驗或明講仍未驗證。
25
25
  - 合併:`git merge` 後跑相關測試(或 `node .specialist/Ansuz/tools/verify-and-land.js`),用這個專案自己的測試指令。
26
- - 檔案長度:verify-and-land 合併前會跑 `check-file-size`(警告 1000 / 上限 2000 行,既有超長檔只擋「未配套拆分的增長」);被擋就按職責拆成 module / component / hook。
27
- - 只在 Kenaz 核心 repo 本身:Rust 測試走 `npm run test:rust` / `dev/scripts/rust-verify-worktree.js`。
28
26
 
29
- ## 回報
30
- 用用戶的語言。說清楚:做了什麼、怎麼驗證的、還有什麼沒驗證或需要用戶決定。不說「應該可以」。
27
+ ## 回報與提問
28
+ - 一律用用戶的語言(中文訊息 → 中文回覆,含提問選項)。
29
+ - 需要用戶決定時用 `AskUserQuestion`(2-4 個選項、建議的放第一個),不要在文字裡列選項等回覆;有證據能自己判斷的事不問。
30
+ - 回報說清楚:做了什麼、怎麼驗證的、還有什麼沒驗證或需要用戶決定。不說「應該可以」。
31
31
 
32
- ## Kenaz 本身出問題時:先回報再繞過(wish / field report)
33
- Kenaz 的工具、hook、安裝、peer 訊息、派工擋住或拖慢工作時(指令失敗、回傳錯的資料、少了能力、行為不如預期),**繞過之前**先呼叫 `kenaz_wish_create`。scope=kenaz 的 wish 會自動送到 Kenaz 團隊成為 field report。
34
- - `projectPath`:目前專案路徑;`scope`:`kenaz`(Kenaz 本身的問題)或 `project`(只跟這個專案有關)
35
- - `wish_type`:`bug`(壞了)/`feature`(缺能力,內容寫:需求、現在哪裡不夠、建議做法、還有誰受益)/`question`
36
- - `content`:一行重現步驟 + 預期 vs 實際 + 版本(`npx kenaz doctor` 那幾行);附上 session id。不要貼金鑰或 token。
37
- - 一個問題只報一次;報完照常用替代做法把工作完成,並在回報裡告訴用戶已送出 wish。
38
- - 用戶直接說「回報這個問題」時也用同一個工具(`source`=`user`)。
32
+ ## Kenaz 本身出問題時:先回報再繞過
33
+ Kenaz 的工具、hook、安裝、peer、派工擋住或拖慢工作時,**繞過之前**先 `kenaz_wish_create`(`projectPath`;`scope` kenaz|project;`wish_type` bug|feature|question;`content` = 一行重現 + 預期 vs 實際 + `npx kenaz doctor` 版本 + session id,不貼金鑰)。一個問題報一次,報完照常完成工作並告訴用戶;用戶說「回報這個問題」也用它(`source`=`user`)。
39
34
 
40
35
  ## 自主模式
41
- 用戶明確授權自主(「自主」「不用問我」「自己判斷著做」)時:立刻執行 `node .specialist/tools/autonomy.js grant --session <id> --scope "<授權範圍>"`,再讀 RULES.md 的 Autonomy 一節。之後 Turn Context 會顯示 `[autonomy] ON`,結束回合會被 Stop hook 擋回:自己決定、一路做完,不在回合結尾問「要我…嗎?」;只有 RULES.md 列的必要停點才 `pause`,做完才 `done`。
36
+ 用戶明確授權自主時:立刻 `node .specialist/tools/autonomy.js grant --session <id> --scope "<範圍>"`,讀 RULES.md 的 Autonomy。之後結束回合會被 Stop hook 擋回:自己決定、一路做完,不在結尾問「要我…嗎?」;只有必要停點才 `pause`,做完才 `done`。
42
37
 
43
- ## 參考產品(REA)
44
- banner 的 `[rea]` 行說明 REA 是否可用:用戶提到想參考某產品的功能時,可用就提議用 REA 分析;沒裝就提示一次 `npx rea-agents setup`,用戶說不要就照 `[rea]` 行的 decline 指令記下來。範圍見 RULES.md 的 REA 一節。
38
+ ## 參考產品(REA)與學習教訓
39
+ - banner `[rea]` 行:用戶提到參考產品時,有 REA 就提議分析,沒有就提示一次 `npx rea-agents setup`(範圍見 RULES.md 的 REA)。
40
+ - 用戶糾正/重複指示、推翻決定、或驗證抓到失敗時,自動寫教訓並在回報點名一行:`node .specialist/tools/lessons.js add --scope user|project --type pref|fact|pitfall|check --text "..."`(規則見 RULES.md 的 Lessons)。
45
41
 
46
- 需要更多規則時(用戶授權自主、派工前、合併前、提到參考產品時)讀 `.specialist/Ansuz/RULES.md`。
42
+ 需要更多規則時(授權自主、派工前、合併前、參考產品、寫教訓)讀 `.specialist/Ansuz/RULES.md`。
@@ -88,11 +88,18 @@ function activeKnowledgeDir(projectDir) {
88
88
  }
89
89
  }
90
90
 
91
+ /** ~/.kenaz/knowledge: the user-preference layer written by `kenaz learn --scope user` (local, never synced). */
92
+ function userKnowledgeDir() {
93
+ const home = process.env.HOME || process.env.USERPROFILE;
94
+ return home ? path.join(home, '.kenaz', 'knowledge') : path.join(PROJECT_DIR, '.kenaz', 'knowledge');
95
+ }
96
+
91
97
  function defaultSourceDirs(projectDir) {
92
98
  const dirs = [];
93
99
  const memDir = defaultMemoryDir(projectDir);
94
100
  if (memDir) dirs.push(memDir);
95
101
  dirs.push(activeKnowledgeDir(projectDir));
102
+ dirs.push(userKnowledgeDir()); // TASK_2409: lessons that apply to every project of this user
96
103
  return dirs;
97
104
  }
98
105
 
@@ -433,6 +440,10 @@ function match(toolName, toolInput, options) {
433
440
  { kind: 'guard', file: r.file, sessionId: options.sessionId },
434
441
  { projectDir, usageLogPath: options.usageLogPath, logUsage: options.logUsage },
435
442
  );
443
+ // TASK_2409: user-layer lessons keep their fire stats next to the lesson (`kenaz learn list`).
444
+ if (options.sessionId && path.dirname(r.absPath) === userKnowledgeDir()) {
445
+ appendUsageLog({ kind: 'guard', file: r.file, sessionId: options.sessionId }, { usageLogPath: path.join(userKnowledgeDir(), '.usage.jsonl') });
446
+ }
436
447
  }
437
448
  return results;
438
449
  }
@@ -703,6 +714,8 @@ function main() {
703
714
 
704
715
  module.exports = {
705
716
  loadTriggers,
717
+ compileTrigger,
718
+ userKnowledgeDir,
706
719
  match,
707
720
  formatGuardLines,
708
721
  stripHeredocBodies,
@@ -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(定時器持續觸發) |
@@ -17,14 +17,6 @@ mutation_counterpart:
17
17
  # orphan = logical state / lifecycle not terminated
18
18
 
19
19
  pairs:
20
- - pair: hippocampus_episode
21
- mutation: 'hippocampus_episode_start'
22
- counterpart: 'hippocampus_episode_close'
23
- severity: leak
24
- rationale: 'episode never closed -> consolidation never fires, unarchived memory accumulates'
25
- grep_mutation: 'hippocampus_episode_start'
26
- grep_counterpart: 'hippocampus_episode_close'
27
-
28
20
  - pair: task_lifecycle
29
21
  mutation: 'task-api.js claim'
30
22
  counterpart: 'task-api.js complete | task-api.js move'
@@ -256,8 +256,6 @@ description: 開發協調、Pipeline管理、任務執行、進度追蹤、Agent
256
256
  | `kenaz_lsp_references` | 所有引用 / 修改影響範圍 | 修改前評估 blast radius |
257
257
  | `kenaz_lsp_definition` | 定義位置 | 跳到宣告處 |
258
258
  | `kenaz_lsp_symbols` | 檔案內 symbol 列表 | 了解檔案結構 |
259
- | `synapse_recall` | 歷史 rune / 已知風險 | 修改前召回 |
260
- | `synapse_status` | 記憶系統狀態 | 任務開始前檢查 |
261
259
 
262
260
  文字與概念搜尋用 Grep / Glob。
263
261
 
@@ -281,8 +279,7 @@ description: 開發協調、Pipeline管理、任務執行、進度追蹤、Agent
281
279
  **在開始任何開發任務前,建議執行:**
282
280
 
283
281
  1. **獲取專案 Context**:
284
- - 使用 `synapse_status` 了解記憶系統狀態
285
- - 使用 `synapse_recall` 召回相關已知風險與決策
282
+ - 讀取任務卡與 `kenaz learn list` 的相關教訓
286
283
 
287
284
  2. **搜索相關代碼**:
288
285
  - 使用 Grep / Glob 找到相關實現
@@ -335,10 +332,10 @@ description: 開發協調、Pipeline管理、任務執行、進度追蹤、Agent
335
332
  2. 重新讀取 Skill 文件
336
333
  3. 如果問題持續,直接應用該 Agent 的核心職責邏輯
337
334
 
338
- ### LSP / Synapse 查詢失敗時
335
+ ### LSP 查詢失敗時
339
336
  1. 繼續執行任務(降級為 Grep/Glob)
340
337
  2. 確認 Dashboard 已啟動(依賴 Rust HTTP server on port 18080)
341
- 3. 使用 `synapse_status` 檢查記憶系統狀態
338
+ 3. 使用 `kenaz_ping` 檢查服務狀態
342
339
 
343
340
  ---
344
341
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: Heimdall
3
- description: 代碼疆域守望者 — git 驅動的代碼健康審查(kenaz_lsp_callers + synapse_recall),生成 OptimizationProposal;記憶健康僅記錄趨勢,不做 KEEP/REVERT
3
+ description: 代碼疆域守望者 — git 驅動的代碼健康審查(kenaz_lsp_callers),生成 OptimizationProposal;記憶健康僅記錄趨勢,不做 KEEP/REVERT
4
4
  ---
5
5
 
6
6
  # Heimdall ᚺ — 守望者
@@ -39,17 +39,16 @@ trigger=manual target_symbol=<SYMBOL> project_path=<PATH>
39
39
 
40
40
  ### Attention Trigger
41
41
 
42
- `trigger=attention`(舊的注意力聚合工具已移除):改以 git 近期變更檔 + 已知 rune 作為調查起點。
42
+ `trigger=attention`(舊的注意力聚合工具已移除):改以 git 近期變更檔 + 作為調查起點。
43
43
 
44
44
  1. **取得焦點**:`git log --since=7.days --name-only` 統計最常被修改的檔案(top 15)→ `kenaz_lsp_symbols(file)` 取得其中的 symbols
45
45
  2. **深度調查每個 symbol**:
46
46
  - `kenaz_lsp_references(symbol, depth=2)` → 爆炸半徑
47
- - `synapse_recall(query=symbol)` → 已知風險/歷史 rune
48
47
  - `kenaz_lsp_callers(symbol)` → 呼叫者
49
- 3. **交叉比對**:合成 blast_radius + known_risks + callers → 產生 finding
50
- 4. **嚴重性分級**:references >= 20 或有未修復 risk rune → HIGH,>= 8 → MEDIUM,else → LOW
48
+ 3. **交叉比對**:合成 blast_radius + callers → 產生 finding
49
+ 4. **嚴重性分級**:references >= 20 → HIGH,>= 8 → MEDIUM,else → LOW
51
50
  5. **空結果**:若無近期變更焦點 → 輸出 `ᚺ Heimdall: clean scan. No attention signals.` 並結束
52
- 6. **降級**:LSP 不可用 → 降級到 `scheduled_trigger` 流程(Grep/Glob + synapse_recall)
51
+ 6. **降級**:LSP 不可用 → 降級到 `scheduled_trigger` 流程(Grep/Glob)
53
52
 
54
53
  ---
55
54
 
@@ -25,58 +25,40 @@ Converting an advisory into a task is **Ansuz exclusive decision**, and Ansuz MU
25
25
 
26
26
  1. Parse context block: `trigger`, `project_path`, `focus` (optional), `query` (optional)
27
27
  2. Confirm `project_path` is set -- abort if missing
28
- 3. Call `synapse_status({ projectPath: project_path })` -- if unavailable, output:
29
- `Huginn: Synapse unavailable. Cannot think without perception. Abort.` and exit
28
+ 3. (memory probe removed in TASK_2411 -- Huginn perceives code, tasks, Heimdall and wishes only)
30
29
  4. **Read loop mode** -- read `{project_path}/.kenaz/loop/loop.json` (fallback: `.kenaz/loop.json`, then `.collaboration/loop.json`).
31
- Canonical writer: `readLoopConfig` in `.formation/tools/post-complete/health.js:28`.
32
30
  If no file found, default to `{ mode: "manual" }`.
33
31
  Store as `loop_mode` ("manual" | "auto"). Loop mode read is harmless; output routing is identical for both branches -- advisory file only.
34
32
  5. **Sanity cap** -- no quota API call needed. Huginn targets <= 5 advisories per run (quality cap, not a hard abort).
35
- 6. Output: `trigger_type`, `loop_mode`, `synapse_available`
33
+ 6. Output: `trigger_type`, `loop_mode`
36
34
 
37
35
  ---
38
36
 
39
37
  ## Phase 1 - Perceive (parallel data collection)
40
38
 
41
- Collect raw signals from 7 sources. All MCP calls MUST include `projectPath`.
39
+ Collect raw signals from 5 sources. All MCP calls MUST include `projectPath`.
42
40
 
43
41
  ### 1a. Code Perception
44
42
  git log hot files (last 14 days), Grep for TODO/FIXME/HACK, kenaz_lsp_references on the top hot symbols
45
43
  Output: graph_signals (name kept for downstream phases)
46
44
 
47
- ### 1b. Cognitive Perception
48
- synapse_recall, synapse_sigils
49
- - **COGNITIVE DEBT pre-emption**: For each hot symbol from 1a with zero runes returned by synapse_recall AND a high kenaz_lsp_references count:
50
- tag it as COGNITIVE DEBT in cognitive_signals. In Phase 4, draft an insight rune at confidence 0.5
51
- for the top-1 COGNITIVE DEBT symbol (draft only). Do NOT re-propose these symbols if they appear in
52
- `.kenaz/huginn-proposed-symbols.json` within the last 30 days (see Phase 3 Repeat Guard).
53
- Output: cognitive_signals
54
-
55
- ### 1c. Episodic Perception
56
- hippocampus_episode_recall difficulties, hippocampus_episode_recall successes
57
- Output: episodic_signals
58
-
59
- ### 1d. Cross-Project Perception (if Cortex available)
60
- cortex_recall architecture patterns
61
- Output: cross_project_signals
62
-
63
- ### 1e. Task Perception
45
+ ### 1b. Task Perception
64
46
  - Read .kenaz/tasks/done/ or .collaboration/tasks/done/ last 30 days
65
47
  - Read blocked tasks
66
48
  - Cluster by scope_paths
67
49
  Output: task_signals
68
50
 
69
- ### 1f. Heimdall Perception
51
+ ### 1c. Heimdall Perception
70
52
  - **Health snapshots**: `Bash: ls -t {project_path}/.kenaz/health_snapshots/*_after.json 2>/dev/null | head -5`
71
53
  - Read each JSON -> extract `score`, `sub_metrics`, `delta`
72
- - **Health trend**: post-complete only records memory health (`post_complete_verdict: RECORDED`, never reverts). Use the `score` / `delta` series from the snapshots above to spot a declining trend; a falling score points at memory-store hygiene (stale runes, episode backlog), not at the task's code.
54
+ - **Health trend**: post-complete only records memory health (`post_complete_verdict: RECORDED`, never reverts). Use the `score` / `delta` series from the snapshots above to spot a declining trend; a falling score is a memory-store signal, not a code signal.
73
55
  - **OptimizationProposals**: `Bash: curl -s "http://127.0.0.1:18080/api/proposals/list?projectPath={project_path}&status=pending" 2>/dev/null`
74
56
  - Each proposal becomes a finding with `source: "heimdall"` -- Huginn should refine + prioritize alongside other signals
75
57
  - Dismiss processed proposals via /ignore endpoint (permanently dismissed, no task created).
76
58
  - If no verdict yet, skip marking -- GC auto-expires Huginn-generated pending proposals after 30 days.
77
59
  Output: heimdall_signals: { health_trend, optimization_proposals[] }
78
60
 
79
- ### 1g. Wish Perception
61
+ ### 1d. Wish Perception
80
62
  - Fetch pending wishes: `Bash: curl -s http://127.0.0.1:18080/api/wishes/list?status=pending&project_path={project_path}`
81
63
  - If pending wishes exist:
82
64
  - Mark each as picked_up via `PUT /api/wishes/{id}`
@@ -155,7 +137,7 @@ Each finding is appended as one JSON line (JSONL format, English only -- MEM-LAN
155
137
  - `ts` -- ISO8601 timestamp of this Huginn run
156
138
  - `title` -- < 80 chars, English, descriptive; no "Create task" / "Open PR" phrasing
157
139
  - `insight` -- What was observed and why it matters (1-2 sentences, English)
158
- - `evidence` -- `{ sources: [tool_name,...], runes: [rune_id,...], metrics: {} }`
140
+ - `evidence` -- `{ sources: [tool_name,...], metrics: {} }`
159
141
  - `confidence` -- 0.0-1.0 from scoring step
160
142
  - `source_signals` -- array of signal categories that contributed
161
143
  - `status` -- always "unread" on write; Ansuz sets to "read" / "converted" / "dismissed"
@@ -169,18 +151,9 @@ Example line (compact, one object per line):
169
151
 
170
152
  **Rotation rule (bounded file)**: On every write, the combined array of existing + new entries is sliced to the 100 most recent entries (`.slice(-100)`) before writing back. This keeps the file bounded at <= 100 lines regardless of run frequency. File size stays under ~200 KB under normal operation. Huginn itself performs the rotation inline -- no external cron needed.
171
153
 
172
- ## Phase 4 - Inscribe (cognitive persistence)
154
+ ## Phase 4 - Record
173
155
 
174
- For each accepted finding involved symbols:
175
- - synapse_recall to check existing runes
176
- - synapse_inscribe type=insight, confidence >= 0.7
177
-
178
- If 3+ symbols form unnamed concept: synapse_forge Sigil
179
-
180
- Rules:
181
- - English only (MEM-LANG-001)
182
- - confidence >= 0.7 (higher than standard 0.6 -- derived knowledge)
183
- - Do NOT inscribe every finding -- only long-term value
156
+ Huginn is advisory-only: findings go to the advisory file (Phase 3). Durable lessons are recorded by Ansuz with `kenaz learn add`; Huginn does not write memory.
184
157
 
185
158
  ---
186
159
 
@@ -193,7 +166,7 @@ Output session report (<= 20 lines):
193
166
  Trigger: {scheduled | milestone | manual}
194
167
  Loop Mode: {manual | auto}
195
168
  Duration: {thinking time}
196
- Sources: synapse: OK/FAIL, hippocampus: OK/FAIL, cortex: OK/FAIL, tasks: OK
169
+ Sources: code: OK/FAIL, tasks: OK/FAIL, heimdall: OK/FAIL, wishes: OK/FAIL
197
170
 
198
171
  Advisories written: {N} (file: .kenaz/specialist/huginn_advisory.jsonl)
199
172
  Drafts skipped: {N}
@@ -170,101 +170,6 @@ function releaseClaimMarker(taskId, projectRoot) {
170
170
  try { require('fs').unlinkSync(markerPath); } catch { /* already gone */ }
171
171
  }
172
172
 
173
- // ─────────────────────────────────────────
174
- // TASK_1861: Dispatch-path-agnostic health snapshot capture
175
- // _before at activation (move/claim/update→active), _after + observe-only verdict
176
- // at completion (complete AND bare `move X done`). Fires a detached child so the
177
- // short-lived CLI never blocks, fails, or slows on Dashboard state. Every capture
178
- // attempt is audited in .kenaz/health_snapshots/snapshot_events.jsonl (never silent).
179
- // Reuses .formation/tools/post-complete/health.js (measureHealthScoreSync etc.) —
180
- // no third implementation. Does NOT touch guardian_counter.json (TASK_1720 periodic
181
- // counter semantics unchanged; verdict stays observe-only, no git action).
182
- // ─────────────────────────────────────────
183
-
184
- function _appendSnapshotEvent(projectRoot, taskId, phase, action, reason) {
185
- try {
186
- const dir = path.join(projectRoot, '.kenaz', 'health_snapshots');
187
- fsSync.mkdirSync(dir, { recursive: true });
188
- const line = JSON.stringify({
189
- task_id: taskId,
190
- ts: new Date().toISOString(),
191
- snapshot_type: phase,
192
- action,
193
- ...(reason ? { reason } : {}),
194
- });
195
- fsSync.appendFileSync(path.join(dir, 'snapshot_events.jsonl'), line + '\n', 'utf8');
196
- } catch { /* audit is best-effort — must never block task ops */ }
197
- }
198
-
199
- function scheduleHealthSnapshotCapture(taskId, phase, projectRoot) {
200
- try {
201
- if (!taskId || !projectRoot) return;
202
- const healthJs = path.join(__dirname, '..', '..', '.formation', 'tools', 'post-complete', 'health.js');
203
- if (!fsSync.existsSync(healthJs)) {
204
- _appendSnapshotEvent(projectRoot, taskId, phase, 'skipped', 'health_module_missing');
205
- return;
206
- }
207
- const cfg = { taskId, phase, projectRoot, healthJs };
208
- // ASCII-only inline script (Windows argv codepage safety). Child owns the full
209
- // outcome audit: written | skipped(reason). measureHealthScoreSync already
210
- // handles Dashboard-down gracefully (returns null within its own timeout).
211
- const script = [
212
- `const CFG = ${JSON.stringify(cfg)};`,
213
- "const fs = require('fs');",
214
- "const path = require('path');",
215
- "function logEvent(action, reason) {",
216
- " try {",
217
- " const dir = path.join(CFG.projectRoot, '.kenaz', 'health_snapshots');",
218
- " fs.mkdirSync(dir, { recursive: true });",
219
- " const line = JSON.stringify({ task_id: CFG.taskId, ts: new Date().toISOString(), snapshot_type: CFG.phase, action, ...(reason ? { reason } : {}) });",
220
- " fs.appendFileSync(path.join(dir, 'snapshot_events.jsonl'), line + '\\n', 'utf8');",
221
- " } catch (_e) { /* best-effort */ }",
222
- "}",
223
- "try {",
224
- " const snapFile = path.join(CFG.projectRoot, '.kenaz', 'health_snapshots', CFG.taskId + '_' + CFG.phase + '.json');",
225
- " if (fs.existsSync(snapFile)) { logEvent('skipped', 'snapshot_already_exists'); process.exit(0); }",
226
- " const h = require(CFG.healthJs);",
227
- " const health = h.measureHealthScoreSync(CFG.projectRoot);",
228
- " if (!health || typeof health.score !== 'number') { logEvent('skipped', 'dashboard_unreachable'); process.exit(0); }",
229
- // TASK_1891: capture the written payload (delta_from_previous) for
230
- // observability threading into computeHealthVerdict below -- see
231
- // health.js's computeHealthVerdict doc comment for why that field
232
- // never gates the verdict itself.
233
- " const afterPayload = h.saveTaskHealthSnapshot(CFG.taskId, CFG.phase, health, CFG.projectRoot);",
234
- " logEvent('written');",
235
- " if (CFG.phase === 'after') {",
236
- " const pre = h.readPreTaskHealthSnapshot(CFG.taskId, CFG.projectRoot);",
237
- " if (pre && typeof pre.score === 'number') {",
238
- " const loopCfg = h.readLoopConfig(CFG.projectRoot);",
239
- " const threshold = typeof loopCfg.revert_threshold === 'number' ? loopCfg.revert_threshold : h.DEFAULT_REVERT_THRESHOLD;",
240
- // TASK_1890/1891: same computeHealthVerdict as post-complete.js's
241
- // auto/manual arms -- no third re-implementation of the delta/trust-gate
242
- // logic. No git access here (no isScopeUnchanged callback), so guard 3
243
- // (stale snapshot + zero scope diff) is a safe no-op for this call
244
- // site, same as it always was before TASK_1890. TASK_1891: pass the
245
- // full `pre` object (not pre.score) so ordering + symmetric trust can
246
- // be checked -- this is one of the two call sites that actually runs
247
- // in production (readLoopConfig's TASK_1885 note).
248
- " const vr = h.computeHealthVerdict(pre, health, threshold, { projectPath: CFG.projectRoot, trendDeltaFromPrevious: afterPayload ? afterPayload.delta_from_previous : null });",
249
- " const reason = vr.verdict === 'REVERT'",
250
- " ? vr.reason + ' - observe-only capture at task completion, no git action taken'",
251
- " : vr.reason;",
252
- " h.writeVerdictToTask(CFG.taskId, vr.verdict, vr.delta, reason, CFG.projectRoot);",
253
- " }",
254
- " }",
255
- "} catch (e) { logEvent('skipped', 'error: ' + String(e && e.message || e)); }",
256
- ].join('\n');
257
- const { spawn } = require('child_process');
258
- const child = spawn(process.execPath, ['-e', script], {
259
- detached: true,
260
- stdio: 'ignore',
261
- windowsHide: true,
262
- });
263
- child.unref();
264
- } catch (e) {
265
- _appendSnapshotEvent(projectRoot, taskId, phase, 'skipped', 'spawn_failed: ' + String(e && e.message || e));
266
- }
267
- }
268
173
  const yaml = (() => {
269
174
  try { return require('js-yaml'); } catch {
270
175
  // Fallback: resolve .vendor/js-yaml.cjs relative to this script's real location.
@@ -1770,9 +1675,7 @@ class TaskAPI {
1770
1675
  */
1771
1676
  async moveTask(taskId, newStatus, taskData = null, options = {}) {
1772
1677
  await this._ensureStatusDirs();
1773
- // TASK_2264: skipHealthAfter = the caller (completeTask) knows post-complete will measure and
1774
- // save the 'after' snapshot itself, so scheduling a second detached measurement is pure waste.
1775
- const { skipValidation = false, forceClaim = false, skipHealthAfter = false } = options;
1678
+ const { skipValidation = false, forceClaim = false } = options;
1776
1679
 
1777
1680
  // 取得任務資料
1778
1681
  let task = taskData;
@@ -1919,20 +1822,6 @@ class TaskAPI {
1919
1822
  releaseClaimMarker(taskId, _projectRootForClaim);
1920
1823
  }
1921
1824
 
1922
- // TASK_1861: dispatch-path-agnostic health capture. moveTask is the single choke
1923
- // point every activation/completion path funnels through (claim → moveTask,
1924
- // update --status → moveTask, complete → moveTask, bare move). Fire-and-forget
1925
- // detached child; idempotent only against snapshots that ALREADY exist when the child
1926
- // starts (it can race a concurrent writer). TASK_2264: completeTask therefore passes
1927
- // skipHealthAfter when its own in-process postComplete will measure 'after', so that
1928
- // path is measured exactly once; paths with no post-complete (bare move, update --status,
1929
- // complete --skip-post-complete) keep this capture as their only one.
1930
- if (targetDir === 'active') {
1931
- scheduleHealthSnapshotCapture(taskId, 'before', _projectRootForClaim);
1932
- } else if (targetDir === 'done' && !skipHealthAfter) {
1933
- scheduleHealthSnapshotCapture(taskId, 'after', _projectRootForClaim);
1934
- }
1935
-
1936
1825
  // Yggdrasil: sync status change to kenaz_tasks
1937
1826
  const sync = getTaskSync();
1938
1827
  if (sync) {
@@ -2126,13 +2015,8 @@ class TaskAPI {
2126
2015
  updatedTask.actual_hours = options.actual_hours;
2127
2016
  }
2128
2017
 
2129
- // TASK_2264: decide up front whether post-complete (below) will measure 'after' in-process.
2130
- // TASK_2288 single-winner rule: whenever a post-complete run is expected for this completion,
2131
- // post-complete's own measurement is the ONE 'after' capture (it runs after merge and feeds the
2132
- // verdict), so moveTask must not also spawn one. Expected = in-process (below).
2133
- // moveTask's detached capture stays the sole one only when nobody measures: --skip-post-complete.
2134
2018
  const postCompleteRuns = !options.skipPostComplete;
2135
- const result = await this.moveTask(taskId, 'done', updatedTask, { skipHealthAfter: postCompleteRuns });
2019
+ const result = await this.moveTask(taskId, 'done', updatedTask);
2136
2020
 
2137
2021
  try {
2138
2022
  this._autoPopThoughtsForCompletedTask(taskId, now);
@@ -2520,100 +2404,6 @@ class TaskAPI {
2520
2404
  }
2521
2405
  }
2522
2406
 
2523
- // ============================================================================
2524
- // TASK_1992/TASK_2104 — Alaya process-lessons echo (best-effort, create-only)
2525
- // ============================================================================
2526
- // Mirrors prompt.js's appendAlayaLessons: same query, same alaya_recall
2527
- // shape, and (TASK_2104) the same TASK_2101 cosine-floor + stack-match
2528
- // gate — loaded from the dependency-free .formation/tools/alaya-gate.js
2529
- // module (require() resolved relative to __dirname; GEN-008) instead of
2530
- // the hand-copied query builder that used to live here with no gate at all.
2531
- // Never affects create's exit code: any failure is swallowed silently.
2532
- // Opt-out: KENAZ_NO_ALAYA_ECHO=1.
2533
-
2534
- // Best-effort: null (not a throw) when unavailable — e.g. a sub-project
2535
- // with no .formation/ checkout — so the caller prints nothing rather than
2536
- // falling back to ungated results.
2537
- function loadAlayaGate() {
2538
- try {
2539
- return require('../../.formation/tools/alaya-gate');
2540
- } catch {
2541
- return null;
2542
- }
2543
- }
2544
-
2545
- // fetchImpl/gateImpl: injectable test seams; default to the real fetch and loadAlayaGate().
2546
- async function echoAlayaLessons(taskData, fetchImpl, gateImpl) {
2547
- if (process.env.KENAZ_NO_ALAYA_ECHO === '1') return;
2548
-
2549
- const doFetch = fetchImpl || (typeof fetch !== 'undefined' ? fetch : null);
2550
- if (!doFetch) return;
2551
-
2552
- const gate = gateImpl !== undefined ? gateImpl : loadAlayaGate();
2553
- if (!gate) return; // shared gate unavailable — never print ungated results
2554
-
2555
- const query = gate.buildTaskContextQuery(taskData);
2556
- if (!query) return;
2557
-
2558
- const taskStack = gate.deriveTaskStack((taskData && taskData.scope_paths) || []);
2559
-
2560
- const controller = new AbortController();
2561
- const timeoutId = setTimeout(() => controller.abort(), 3000);
2562
- try {
2563
- const payload = JSON.stringify({
2564
- jsonrpc: '2.0',
2565
- id: 1,
2566
- method: 'tools/call',
2567
- params: {
2568
- name: 'alaya_recall',
2569
- arguments: {
2570
- query, mode: 'hybrid', seed_types: ['failure_seed', 'lesson_seed'],
2571
- limit: gate.ALAYA_CANDIDATE_POOL, min_cosine: gate.ALAYA_MIN_COSINE_DEFAULT,
2572
- },
2573
- },
2574
- });
2575
- const res = await doFetch('http://127.0.0.1:18080/api/mcp', {
2576
- method: 'POST',
2577
- headers: { 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream' },
2578
- body: payload,
2579
- signal: controller.signal,
2580
- });
2581
- if (!res || !res.ok) return;
2582
- const result = await res.json();
2583
- if (!result || result.error) return;
2584
-
2585
- const content = result.result && result.result.content;
2586
- if (!content || !Array.isArray(content)) return;
2587
- const textPart = content.find(c => c.type === 'text');
2588
- if (!textPart || !textPart.text) return;
2589
-
2590
- let recallData;
2591
- try { recallData = JSON.parse(textPart.text); } catch { return; }
2592
- if (!recallData) return;
2593
-
2594
- let bijas = recallData.bijas || [];
2595
- if (bijas.length === 0) return;
2596
-
2597
- bijas = gate.dedupBijas(bijas);
2598
- // TASK_2104: same relevance gate as prompt.js's appendAlayaLessons —
2599
- // the server already applied min_cosine, but re-checking (+ the
2600
- // stack-match half, which the server does not apply) keeps this
2601
- // correct against a stub/older server and drops stack mismatches.
2602
- bijas = bijas.filter(b => gate.passesAlayaRelevanceGate(b, taskStack, gate.ALAYA_MIN_COSINE_DEFAULT)).slice(0, 3);
2603
- if (bijas.length === 0) return;
2604
-
2605
- console.log(' Alaya lessons for this card:');
2606
- for (const b of bijas) {
2607
- const preview = (b.content || '').replace(/\s+/g, ' ').trim().slice(0, 160);
2608
- console.log(` - [${b.seed_type || 'seed'}/${b.origin || 'unknown'}] ${preview}`);
2609
- }
2610
- } catch {
2611
- // Best-effort: MCP down, timeout, network error, malformed response — silent.
2612
- } finally {
2613
- clearTimeout(timeoutId);
2614
- }
2615
- }
2616
-
2617
2407
  // TASK_2257: estimated_hours is a finite number >= 0 or null. '' (flag present, empty value)
2618
2408
  // CLEARS the field (null) -- same "clearable" contract as --description/--notes. Anything
2619
2409
  // non-numeric or negative exits 1 instead of persisting NaN / a nonsense value.
@@ -3198,8 +2988,6 @@ Create 選項:
3198
2988
  if (task.estimated_hours != null) console.log(`預估工時: ${task.estimated_hours}h`);
3199
2989
  if (task.branch) console.log(`分支: ${task.branch}`);
3200
2990
 
3201
- await echoAlayaLessons(taskData);
3202
-
3203
2991
  } else if (command === 'claim') {
3204
2992
  const taskId = args[1];
3205
2993
  if (!taskId) {
@@ -3737,10 +3525,6 @@ module.exports = {
3737
3525
  parseYAML,
3738
3526
  toYAML,
3739
3527
  resolveTasksDir,
3740
- // TASK_1992/TASK_2104: exported for direct unit-testing with a mocked
3741
- // fetch (and an injectable alaya-gate module) — no live MCP dependency.
3742
- loadAlayaGate,
3743
- echoAlayaLessons,
3744
3528
  // TASK_2142 R2: test seam for injecting a fake pool-status-push module.
3745
3529
  setPoolStatusPush,
3746
3530
  };