kenaz 0.3.18 → 0.3.20

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 (329) hide show
  1. package/core/.claude/commands/agent-worker.md +1 -1
  2. package/core/.claude/commands/architect.md +1 -2
  3. package/core/.claude/commands/spec.md +1 -1
  4. package/core/.claude/commands//347/224/242/345/223/201.md +1 -1
  5. package/core/.claude/hooks/__tests__/task-1977-dedupe-guard.test.js +8 -8
  6. package/core/.claude/skills/architect_agent/SKILL.md +11 -92
  7. package/core/.claude/skills/assessment_agent/SKILL.md +0 -1
  8. package/core/.claude/skills/backend_engineer/SKILL.md +3 -6
  9. package/core/.claude/skills/code_reviewer/SKILL.md +1 -1
  10. package/core/.claude/skills/code_reviewer/patterns.yaml +1 -1
  11. package/core/.claude/skills/designer/SKILL.md +3 -6
  12. package/core/.claude/skills/dev_agent/SKILL.md +30 -491
  13. package/core/.claude/skills/developer/SKILL.md +3 -6
  14. package/core/.claude/skills/feature_agent/SKILL.md +3 -17
  15. package/core/.claude/skills/product_manager/SKILL.md +29 -1064
  16. package/core/.claude/skills/qa_engineer/SKILL.md +12 -17
  17. package/core/.claude/skills/spec_optimizer/SKILL.md +25 -275
  18. package/core/.claude/skills/sre_agent/SKILL.md +3 -6
  19. package/core/.collaboration/README.md +2 -3
  20. package/core/.collaboration/core/task-api.js +5 -138
  21. package/core/.collaboration/docs/CLI.md +0 -2
  22. package/core/.failure/INDEX.yaml +6 -40
  23. package/core/.failure/agent-execution/FAIL-013-search-cwd-drift-and-case-mismatch.yaml +1 -1
  24. package/core/.failure/tools/README.md +9 -227
  25. package/core/.formation/bifrost/launch.js +1 -6
  26. package/core/.formation/bifrost/launch.test.js +1 -1
  27. package/core/.formation/bifrost/shared/ansuz-boot-policy.js +3 -3
  28. package/core/.formation/bifrost/shared/ansuz-boot-policy.test.js +1 -2
  29. package/core/.formation/tools/__tests__/arms.test.js +41 -0
  30. package/core/.formation/tools/__tests__/diagnose-bug.test.js +10 -0
  31. package/core/.formation/tools/__tests__/m1-hmap-convid.test.js +1 -124
  32. package/core/.formation/tools/arms.js +48 -0
  33. package/core/.formation/tools/dispatch.js +24 -15
  34. package/core/.formation/tools/kenaz-stats.js +32 -8
  35. package/core/.formation/tools/kenaz-stats.test.js +15 -2
  36. package/core/.formation/tools/loader.js +17 -140
  37. package/core/.formation/tools/post-complete.js +0 -31
  38. package/core/.formation/tools/prompt.js +16 -1
  39. package/core/.formation/tools/prompt.test.js +20 -0
  40. package/core/.rule/INDEX.yaml +0 -18
  41. package/core/.rule/general/agent-workflow.yaml +14 -164
  42. package/core/.rule/general/project-self-containment.yaml +8 -19
  43. package/core/.rule/general/rust-verify-target.yaml +6 -5
  44. package/core/.rule/general/synapse-code-intelligence.yaml +3 -3
  45. package/core/.specialist/Amelia/capability.yaml +12 -12
  46. package/core/.specialist/Amelia/workflow.yaml +2 -2
  47. package/core/.specialist/Ansuz/SYSTEM_DOC.md +20 -578
  48. package/core/.specialist/Ansuz/cognitive_principles.yaml +2 -3
  49. package/core/.specialist/Ansuz/concepts/boot_rules_summary.yaml +0 -1
  50. package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +1 -2
  51. package/core/.specialist/Ansuz/core.yaml +6 -108
  52. package/core/.specialist/Ansuz/guards/lesson_guards.yaml +0 -3
  53. package/core/.specialist/Ansuz/identity_contract.yaml +0 -1
  54. package/core/.specialist/Ansuz/knowledge.yaml +0 -4
  55. package/core/.specialist/Ansuz/rule.yaml +0 -1
  56. package/core/.specialist/Ansuz/tools/handoff-session-identity.test.js +6 -150
  57. package/core/.specialist/tests/runtime-discovery/README.md +1 -1
  58. package/core/.specialist/tools/__tests__/task-2374-usage-counter.test.js +1 -12
  59. package/core/.specialist/tools/ansuz-boot-fast.js +7 -4
  60. package/core/.specialist/tools/ansuz-boot-fast.test.js +10 -3
  61. package/core/.specialist/tools/prompt-files.test.js +1 -0
  62. package/core/.specialist/tools/task-bridge.js +1 -66
  63. package/core/.system/README.md +11 -552
  64. package/core/.system/collaboration/README.md +10 -174
  65. package/core/CLAUDE.md +1 -6
  66. package/core/KENAZ_CORE_VERSION +1 -1
  67. package/core/dev/scripts/js-yaml-resolution.test.js +1 -1
  68. package/core/dev/scripts/peer-identity-e2e.js +402 -0
  69. package/core/dev/scripts/peer-identity-e2e.md +47 -0
  70. package/core/dev/scripts/relay-admin.js +94 -0
  71. package/core/dev/scripts/relay-admin.test.js +131 -0
  72. package/core/dev/scripts/run-rust-tests.js +9 -45
  73. package/core/dev/scripts/rust-verify-worktree.js +4 -4
  74. package/core/dev/scripts/verify-target-dir.js +6 -6
  75. package/core/plugins/kenaz/INVENTORY.md +8 -8
  76. package/core/plugins/kenaz/README.md +10 -0
  77. package/core/plugins/kenaz/commands/architect.md +1 -2
  78. package/core/plugins/kenaz/commands/spec.md +1 -1
  79. package/core/plugins/kenaz/commands//347/224/242/345/223/201.md +1 -1
  80. package/core/plugins/kenaz/guidance.md +1 -1
  81. package/core/plugins/kenaz/hooks/kenaz-hook.js +2 -1
  82. package/core/plugins/kenaz/hooks/sdk-gate.js +9 -0
  83. package/core/plugins/kenaz/scripts/kenaz.test.js +15 -3
  84. package/core/plugins/kenaz/scripts/sync.js +1 -1
  85. package/core/plugins/kenaz/scripts/ygg.js +8 -0
  86. package/core/plugins/kenaz/scripts/ygg.test.js +72 -0
  87. package/core/plugins/kenaz/skills/architect_agent/SKILL.md +11 -92
  88. package/core/plugins/kenaz/skills/assessment_agent/SKILL.md +0 -1
  89. package/core/plugins/kenaz/skills/backend_engineer/SKILL.md +3 -6
  90. package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +1 -1
  91. package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +1 -1
  92. package/core/plugins/kenaz/skills/designer/SKILL.md +3 -6
  93. package/core/plugins/kenaz/skills/dev_agent/SKILL.md +30 -491
  94. package/core/plugins/kenaz/skills/developer/SKILL.md +3 -6
  95. package/core/plugins/kenaz/skills/feature_agent/SKILL.md +3 -17
  96. package/core/plugins/kenaz/skills/product_manager/SKILL.md +29 -1064
  97. package/core/plugins/kenaz/skills/qa_engineer/SKILL.md +12 -17
  98. package/core/plugins/kenaz/skills/spec_optimizer/SKILL.md +25 -275
  99. package/core/plugins/kenaz/skills/sre_agent/SKILL.md +3 -6
  100. package/core/plugins/kenaz-peer/hooks/register.test.ts +32 -0
  101. package/core/plugins/kenaz-peer/hooks/register.ts +9 -0
  102. package/package.json +6 -6
  103. package/core/.claude/commands/archive.md +0 -24
  104. package/core/.claude/commands/init.md +0 -112
  105. package/core/.claude/commands/legacy_dev.md +0 -18
  106. package/core/.claude/commands/product-review.md +0 -131
  107. package/core/.claude/commands/strategy.md +0 -141
  108. package/core/.claude/commands/sunny.md +0 -1
  109. package/core/.claude/commands//347/224/242/345/223/201/346/252/242/350/246/226.md +0 -129
  110. package/core/.claude/commands//351/200/262/345/272/246.md +0 -15
  111. package/core/.claude/hooks/__tests__/substrate.test.js +0 -122
  112. package/core/.claude/hooks/_substrate-paths.js +0 -95
  113. package/core/.claude/hooks/context-check.js +0 -51
  114. package/core/.claude/hooks/fatigue-check.js +0 -52
  115. package/core/.claude/hooks/handoff-snapshot-on-commit.js +0 -154
  116. package/core/.claude/hooks/handoff-snapshot-on-question.js +0 -158
  117. package/core/.claude/hooks/handoff-staleness.js +0 -197
  118. package/core/.claude/hooks/jev-thalamus-context.js +0 -17
  119. package/core/.claude/hooks/jev-thalamus-context.test.js +0 -41
  120. package/core/.claude/hooks/lease-release.js +0 -119
  121. package/core/.claude/hooks/lease-touch.js +0 -96
  122. package/core/.claude/hooks/reflex-feedback.js +0 -5
  123. package/core/.claude/hooks/session-init.ps1 +0 -49
  124. package/core/.claude/hooks/sound-event.js +0 -70
  125. package/core/.claude/hooks/sound-event.sh +0 -37
  126. package/core/.claude/hooks/stream-delta.js +0 -169
  127. package/core/.claude/no-hooks.settings.json +0 -3
  128. package/core/.claude/skills/legacy_dev/SKILL.md +0 -202
  129. package/core/.claude/skills/sunny/SKILL.md +0 -74
  130. package/core/.collaboration/cli/commands/assign.js +0 -39
  131. package/core/.collaboration/cli/commands/claim.js +0 -33
  132. package/core/.collaboration/cli/commands/complete.js +0 -44
  133. package/core/.collaboration/cli/commands/create.js +0 -110
  134. package/core/.collaboration/cli/commands/get.js +0 -83
  135. package/core/.collaboration/cli/commands/index.js +0 -52
  136. package/core/.collaboration/cli/commands/list.js +0 -51
  137. package/core/.collaboration/cli/commands/lock.js +0 -45
  138. package/core/.collaboration/cli/commands/rebuild.js +0 -39
  139. package/core/.collaboration/cli/commands/search.js +0 -61
  140. package/core/.collaboration/cli/commands/stats.js +0 -61
  141. package/core/.collaboration/cli/commands/transfer.js +0 -41
  142. package/core/.collaboration/cli/commands/unlock.js +0 -48
  143. package/core/.collaboration/cli/formatters/help-formatter.js +0 -70
  144. package/core/.collaboration/cli/formatters/index.js +0 -7
  145. package/core/.collaboration/cli/task-cli.js +0 -46
  146. package/core/.collaboration/cli/utils/arg-parser.js +0 -74
  147. package/core/.collaboration/cli/utils/git-user.js +0 -25
  148. package/core/.collaboration/cli/utils/index.js +0 -9
  149. package/core/.collaboration/cli/utils/output.js +0 -10
  150. package/core/.collaboration/config/default.yaml +0 -189
  151. package/core/.collaboration/core/conflict-resolver.js +0 -461
  152. package/core/.collaboration/core/conflict-resolver.test.js +0 -389
  153. package/core/.collaboration/core/migrate-legacy.js +0 -260
  154. package/core/.collaboration/docs/API.md +0 -787
  155. package/core/.collaboration/docs/ARCHITECTURE.md +0 -596
  156. package/core/.collaboration/docs/FAQ.md +0 -695
  157. package/core/.collaboration/docs/MIGRATION.md +0 -804
  158. package/core/.collaboration/docs/examples/README.md +0 -320
  159. package/core/.collaboration/docs/examples/advanced-queries.js +0 -261
  160. package/core/.collaboration/docs/examples/basic-usage.js +0 -184
  161. package/core/.collaboration/docs/examples/team-workflow.js +0 -352
  162. package/core/.failure/agent-execution/FAIL-001-agent-no-output.yaml +0 -391
  163. package/core/.failure/agent-execution/FAIL-007-pm-lead-wrong-team-invocation.yaml +0 -0
  164. package/core/.failure/agent-execution/FAIL-010-phase0-poem-format-skipped.yaml +0 -28
  165. package/core/.failure/agent-execution/FAIL-010-skipping-cognitive-checkpoint-after-verification.yaml +0 -59
  166. package/core/.failure/agent-execution/FAIL-012-grep-shortcut-during-investigation.yaml +0 -85
  167. package/core/.failure/architecture/FAIL-006-python-version-compatibility.yaml +0 -445
  168. package/core/.failure/pipeline/FAIL-003-queue-pollution.yaml +0 -335
  169. package/core/.failure/pipeline/README.md +0 -95
  170. package/core/.failure/retired/FAIL-009-skipping-synapse-pulse-before-code-change.yaml +0 -49
  171. package/core/.failure/tools/USAGE_GUIDE.md +0 -323
  172. package/core/.failure/tools/check_failures.ps1 +0 -387
  173. package/core/.failure/tools/log_failure.ps1 +0 -474
  174. package/core/.formation/.episode-input.json +0 -10
  175. package/core/.formation/.merge-lock +0 -1
  176. package/core/.formation/lead.yaml +0 -118
  177. package/core/.formation/legacy.bak/designer.yaml +0 -57
  178. package/core/.formation/legacy.bak/pm.yaml +0 -57
  179. package/core/.formation/legacy.bak/qa.yaml +0 -57
  180. package/core/.formation/legacy.bak/review.yaml +0 -62
  181. package/core/.formation/legacy.bak/scheduler.yaml +0 -129
  182. package/core/.formation/legacy.bak/sre.yaml +0 -57
  183. package/core/.formation/role-map.yaml +0 -274
  184. package/core/.formation/roles/_shared/modules/code_review.yaml +0 -98
  185. package/core/.formation/roles/_shared/modules/security.yaml +0 -103
  186. package/core/.formation/roles/_shared/modules/testing.yaml +0 -14
  187. package/core/.formation/roles/backend/role.yaml +0 -93
  188. package/core/.formation/roles/code_reviewer/role.yaml +0 -102
  189. package/core/.formation/roles/database_optimizer/role.yaml +0 -107
  190. package/core/.formation/roles/designer/role.yaml +0 -66
  191. package/core/.formation/roles/developer/role.yaml +0 -75
  192. package/core/.formation/roles/frontend/role.yaml +0 -134
  193. package/core/.formation/roles/incident_response/role.yaml +0 -89
  194. package/core/.formation/roles/qa/role.yaml +0 -89
  195. package/core/.formation/roles/security/role.yaml +0 -87
  196. package/core/.formation/roles/sre/role.yaml +0 -66
  197. package/core/.formation/skills/coordinator.yaml +0 -52
  198. package/core/.formation/solo/diagnosis.yaml +0 -78
  199. package/core/.formation/solo/strategy.yaml +0 -121
  200. package/core/.formation/tests/hello.txt +0 -2
  201. package/core/.formation/tools/coordinator.js +0 -350
  202. package/core/.formation/tools/jev-correction.js +0 -214
  203. package/core/.formation/tools/jev-correction.test.js +0 -178
  204. package/core/.formation/tools/log-reader.js +0 -232
  205. package/core/.formation/tools/revert-daemon-state.js +0 -162
  206. package/core/.formation/tools/test-drift.js +0 -148
  207. package/core/.formation/unit/heimdall.yaml +0 -96
  208. package/core/.formation/unit/plan.yaml +0 -67
  209. package/core/.formation/unit/think.yaml +0 -58
  210. package/core/.kenaz/MUNINN_SNAPSHOT9_EPISODE.json +0 -32
  211. package/core/.kenaz/MUNINN_SNAPSHOT9_RUNE.json +0 -23
  212. package/core/.kenaz/MUNINN_SNAPSHOT9_SUMMARY.md +0 -89
  213. package/core/.kenaz/ash_TASK_1428-20260507.json +0 -14
  214. package/core/.kenaz/muninn_episode_write.ps1 +0 -17
  215. package/core/.kenaz/muninn_snapshot11_rune_candidates.md +0 -63
  216. package/core/.kenaz/muninn_snapshot1_rune_candidates.md +0 -49
  217. package/core/.kenaz/runes_TASK_1428.json +0 -57
  218. package/core/.kenaz/tmp_heimdall_proposal_8d3e392.json +0 -13
  219. package/core/.kenaz/tmp_props.json +0 -1
  220. package/core/.kenaz/tmp_sounddrop_t1_prompt.txt +0 -371
  221. package/core/.rule/MECHANIZED.md +0 -61
  222. package/core/.rule/general/cortex-memory-recall.yaml +0 -446
  223. package/core/.rule/general/pipeline.yaml +0 -246
  224. package/core/.rule/general/sunny-access-control.yaml +0 -60
  225. package/core/.rule/general/system-invariants.yaml +0 -231
  226. package/core/.rule/pipeline/PPL-002-completed-task-archival.yaml +0 -528
  227. package/core/.rule/pipeline/auto-update.yaml +0 -282
  228. package/core/.rule/pipeline/task-execution.yaml +0 -365
  229. package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +0 -65
  230. package/core/.specialist/Ansuz/experiments/boot_ablation_replay.js +0 -197
  231. package/core/.specialist/Ansuz/tools/bootstrap-anchors.js +0 -289
  232. package/core/.specialist/Ansuz/workflow.yaml +0 -1225
  233. package/core/.specialist/experiments/rg_benchmark.js +0 -523
  234. package/core/.specialist/experiments/rg_benchmark_results.json +0 -27
  235. package/core/.specialist/tools/jev-optional-context.js +0 -107
  236. package/core/.specialist/tools/jev-optional-context.test.js +0 -152
  237. package/core/.specialist/tools/memory-action-log.js +0 -839
  238. package/core/.specialist/tools/phase-gate.js +0 -3534
  239. package/core/.specialist/tools/phase-gate.test.js +0 -85
  240. package/core/.specialist/tools/precompute-runner.js +0 -444
  241. package/core/.specialist/tools/rule-engine.js +0 -621
  242. package/core/.specialist/tools/task-bridge-jev.test.js +0 -170
  243. package/core/.synapse/memory-shared.json.sync +0 -1
  244. package/core/.system/agent-skills/README.md +0 -724
  245. package/core/.system/cli-tools/README.md +0 -724
  246. package/core/.system/cortex/MODULES.md +0 -1046
  247. package/core/.system/dashboard/FRONTEND.md +0 -812
  248. package/core/.system/dashboard/README.md +0 -737
  249. package/core/.system/failure/README.md +0 -723
  250. package/core/.system/formation/LEGACY_SKILLS.md +0 -974
  251. package/core/.system/formation/POST_COMPLETE.md +0 -602
  252. package/core/.system/formation/README.md +0 -1094
  253. package/core/.system/formation/TOOLS_REFERENCE.md +0 -1145
  254. package/core/.system/kenaz/README.md +0 -787
  255. package/core/.system/memory/README.md +0 -719
  256. package/core/.system/pipeline/README.md +0 -712
  257. package/core/.system/rule/README.md +0 -794
  258. package/core/.system/sunny/README.md +0 -476
  259. package/core/.system/sunny/RULES.json +0 -89
  260. package/core/.system/sunny/knowledge/INDEX.md +0 -37
  261. package/core/.system/sunny/knowledge/agent-teams-api.md +0 -0
  262. package/core/.system/sunny/knowledge/ansuz-specialist.md +0 -500
  263. package/core/.system/sunny/knowledge/claude-cli-spawn.md +0 -177
  264. package/core/.system/sunny/knowledge/collaboration-api.md +0 -925
  265. package/core/.system/sunny/teammates/README.md +0 -107
  266. package/core/.system/sunny/teammates/how_to_use.yaml +0 -101
  267. package/core/.system/sunny/teammates/tools/headless/launcher.js +0 -318
  268. package/core/.system/sunny/teammates/tools/powershell/launcher.js +0 -241
  269. package/core/.system/sunny/teammates/tools/powershell/start.bat +0 -50
  270. package/core/.system/sunny/teammates/tools/utils/env.js +0 -96
  271. package/core/.system/sunny/teammates/tools/utils/format-output.js +0 -329
  272. package/core/.system/sunny/tools.json +0 -101
  273. package/core/dev/scripts/cleanup-queue.js +0 -184
  274. package/core/dev/scripts/cleanup_queue.js +0 -86
  275. package/core/dev/scripts/legacy/README.md +0 -39
  276. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +0 -536
  277. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +0 -1978
  278. package/core/dev/scripts/matrix/CLI_README.md +0 -214
  279. package/core/dev/scripts/matrix/README.md +0 -310
  280. package/core/dev/scripts/matrix/package-lock.json +0 -4266
  281. package/core/dev/scripts/matrix/package.json +0 -55
  282. package/core/dev/scripts/matrix/src/abbreviations.ts +0 -295
  283. package/core/dev/scripts/matrix/src/cli/commands/convert.ts +0 -211
  284. package/core/dev/scripts/matrix/src/cli/commands/create.ts +0 -95
  285. package/core/dev/scripts/matrix/src/cli/commands/delete.ts +0 -102
  286. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +0 -140
  287. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +0 -173
  288. package/core/dev/scripts/matrix/src/cli/commands/query.ts +0 -261
  289. package/core/dev/scripts/matrix/src/cli/commands/read.ts +0 -77
  290. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +0 -168
  291. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +0 -126
  292. package/core/dev/scripts/matrix/src/cli/commands/update.ts +0 -147
  293. package/core/dev/scripts/matrix/src/cli/commands/validate.ts +0 -275
  294. package/core/dev/scripts/matrix/src/cli/index.ts +0 -350
  295. package/core/dev/scripts/matrix/src/cli/utils/file-handler.ts +0 -72
  296. package/core/dev/scripts/matrix/src/cli/utils/logger.ts +0 -65
  297. package/core/dev/scripts/matrix/src/index.ts +0 -62
  298. package/core/dev/scripts/matrix/src/indexer/tag-indexer.ts +0 -165
  299. package/core/dev/scripts/matrix/src/parser.ts +0 -281
  300. package/core/dev/scripts/matrix/src/serializer.ts +0 -151
  301. package/core/dev/scripts/matrix/src/validator.ts +0 -301
  302. package/core/dev/scripts/matrix/tests/abbreviations.test.ts +0 -122
  303. package/core/dev/scripts/matrix/tests/cli.test.ts +0 -177
  304. package/core/dev/scripts/matrix/tests/convert.test.ts +0 -143
  305. package/core/dev/scripts/matrix/tests/parser.test.ts +0 -203
  306. package/core/dev/scripts/matrix/tests/query.test.ts +0 -85
  307. package/core/dev/scripts/matrix/tests/serializer.test.ts +0 -197
  308. package/core/dev/scripts/matrix/tests/validate.test.ts +0 -141
  309. package/core/dev/scripts/matrix/tests/validator.test.ts +0 -167
  310. package/core/dev/scripts/matrix/tsconfig.json +0 -20
  311. package/core/dev/scripts/matrix/types/index.d.ts +0 -111
  312. package/core/dev/scripts/matrix/types/index.d.ts.map +0 -1
  313. package/core/dev/scripts/matrix/types/index.js +0 -8
  314. package/core/dev/scripts/matrix/types/index.js.map +0 -1
  315. package/core/dev/scripts/matrix/types/index.ts +0 -124
  316. package/core/dev/scripts/memory-usage-report.js +0 -152
  317. package/core/dev/scripts/probe/ansuz-scaffold-ablation.js +0 -579
  318. package/core/dev/scripts/probe/ansuz-scaffold-ablation.test.js +0 -244
  319. package/core/dev/scripts/probe/hook-latency-probe.js +0 -315
  320. package/core/dev/scripts/probe/hook-latency-probe.test.js +0 -105
  321. package/core/dev/scripts/probe/prediction-signal-auc.js +0 -483
  322. package/core/dev/scripts/probe/prediction-signal-auc.test.js +0 -200
  323. package/core/dev/scripts/test-pty-ws-tunnel.js +0 -229
  324. package/core/dev/scripts/validate_pipeline.js +0 -197
  325. package/core/dev/spikes/task2145-evidence-20261004/backend-log-excerpt.txt +0 -56
  326. package/core/dev/spikes/task2145-evidence-20261004/graph.shadow +0 -0
  327. package/core/dev/spikes/task2145-evidence-20261004/graph.wal +0 -0
  328. package/core/dev/spikes/task2145-evidence-20261004/graph_corrupt_suspect.marker +0 -1
  329. package/core/plugins/kenaz/commands/strategy.md +0 -141
@@ -0,0 +1,131 @@
1
+ 'use strict';
2
+ // TASK_2451: relay-admin.js against a local fixture that mimics the relay admin endpoints
3
+ // (same routes, X-Admin-Key check, approve-by-fingerprint semantics as node_admin.rs). Every
4
+ // command in HELP is executed through the real CLI process.
5
+ const test = require('node:test');
6
+ const assert = require('node:assert');
7
+ const http = require('http');
8
+ const fs = require('fs');
9
+ const os = require('os');
10
+ const path = require('path');
11
+ const { spawn, spawnSync } = require('child_process');
12
+ const { HELP } = require('./relay-admin');
13
+
14
+ const KEY = 'sekret-admin-key';
15
+ const CLI = path.join(__dirname, 'relay-admin.js');
16
+ let server, url, tokens, blocks = [], protect = false;
17
+
18
+ test.before(async () => {
19
+ tokens = [
20
+ { team_id: 't1', node_name: 'n1', fingerprint: 'AAAA-1111', status: 'pending', created_at: '2026-10-13T00:00:00Z' },
21
+ { team_id: 't1', node_name: 'n1', fingerprint: 'BBBB-2222', status: 'active', created_at: '2026-10-12T00:00:00Z' },
22
+ ];
23
+ server = http.createServer((req, res) => {
24
+ let b = '';
25
+ req.on('data', (c) => (b += c));
26
+ req.on('end', () => {
27
+ const send = (s, o) => { res.statusCode = s; res.end(JSON.stringify(o || {})); };
28
+ if (req.headers['x-admin-key'] !== KEY) return send(401);
29
+ const r = req.url.replace('/api/admin/node-credentials/', '');
30
+ const body = b ? JSON.parse(b) : {};
31
+ if (r === 'pending') return send(200, { pending: tokens.filter((t) => t.status === 'pending') });
32
+ if (r === 'approve') {
33
+ const t = tokens.find((x) => x.status === 'pending' && x.team_id === body.team_id && x.node_name === body.node_name && x.fingerprint === body.fingerprint);
34
+ if (!t) return send(404);
35
+ let n = 0;
36
+ for (const o of tokens) if (o !== t && o.team_id === t.team_id && o.node_name === t.node_name && o.status !== 'revoked') { o.status = 'revoked'; n++; }
37
+ t.status = 'active';
38
+ return send(200, { status: 'active', revoked_others: n });
39
+ }
40
+ if (r === 'revoke') {
41
+ let n = 0;
42
+ for (const o of tokens) if (o.team_id === body.team_id && o.node_name === body.node_name && o.status !== 'revoked') { o.status = 'revoked'; n++; }
43
+ return send(200, { status: 'revoked', revoked: n });
44
+ }
45
+ if (r === 'ban') { blocks.push(body); return send(200, { banned: true, revoked: 1, protect_bound_names: protect }); }
46
+ if (r === 'unban') { blocks = []; return send(200, { unbanned: 1 }); }
47
+ if (r === 'blocked') return send(200, { blocked: blocks.map((b) => ({ team_id: b.team_id, kind: 'name', value: b.node_name, reason: b.reason, created_at: 'now' })) });
48
+ send(404);
49
+ });
50
+ });
51
+ await new Promise((r) => server.listen(0, '127.0.0.1', r));
52
+ url = `http://127.0.0.1:${server.address().port}`;
53
+ });
54
+ test.after(() => server.close());
55
+
56
+ const cli = (args, env = { RELAY_ADMIN_KEY: KEY }) =>
57
+ new Promise((resolve) => {
58
+ const p = spawn(process.execPath, [CLI, '--relay', url, ...args], { env: { ...process.env, RELAY_ADMIN_KEY: '', RELAY_ADMIN_KEY_FILE: '', ...env } });
59
+ let out = ''; p.stdout.on('data', (d) => (out += d)); p.stderr.on('data', (d) => (out += d));
60
+ p.on('close', (code) => resolve({ code, out }));
61
+ });
62
+
63
+ test('list -> wrong fingerprint refused -> approve activates and reports revoked tokens', async () => {
64
+ let r = await cli(['list']);
65
+ assert.strictEqual(r.code, 0); assert.match(r.out, /t1\tn1\tAAAA-1111/);
66
+ r = await cli(['approve', 't1', 'n1', 'ZZZZ-0000']);
67
+ assert.strictEqual(r.code, 1); assert.match(r.out, /HTTP 404/);
68
+ assert.strictEqual(tokens[0].status, 'pending');
69
+ r = await cli(['approve', 't1', 'n1', 'AAAA-1111']);
70
+ assert.strictEqual(r.code, 0); assert.match(r.out, /revoked 1 other token/);
71
+ assert.strictEqual(tokens[0].status, 'active'); assert.strictEqual(tokens[1].status, 'revoked');
72
+ r = await cli(['list']);
73
+ assert.match(r.out, /no pending/);
74
+ });
75
+
76
+ test('revoke', async () => {
77
+ const r = await cli(['revoke', 't1', 'n1']);
78
+ assert.strictEqual(r.code, 0); assert.match(r.out, /revoked 1 token/);
79
+ assert.strictEqual(tokens[0].status, 'revoked');
80
+ });
81
+
82
+ test('wrong admin key and key file', async () => {
83
+ let r = await cli(['list'], { RELAY_ADMIN_KEY: 'nope' });
84
+ assert.strictEqual(r.code, 1); assert.match(r.out, /wrong admin key/);
85
+ const f = path.join(os.tmpdir(), `relay-admin-key-${process.pid}`);
86
+ fs.writeFileSync(f, KEY + '\n');
87
+ r = await cli(['list'], { RELAY_ADMIN_KEY_FILE: f });
88
+ fs.unlinkSync(f);
89
+ assert.strictEqual(r.code, 0);
90
+ });
91
+
92
+ test('admin key on argv is refused (control: same call via env works)', async () => {
93
+ for (const a of [['--admin-key', KEY, 'list'], ['--key=' + KEY, 'list']]) {
94
+ const r = await cli(a, {});
95
+ assert.strictEqual(r.code, 2); assert.match(r.out, /must not be passed/);
96
+ }
97
+ assert.strictEqual((await cli(['list'])).code, 0);
98
+ });
99
+
100
+ test('missing key refused; help text commands are all executed by this suite', async () => {
101
+ assert.strictEqual((await cli(['list'], {})).code, 2);
102
+ const cmds = [...HELP.matchAll(/^ {2}(list|approve|revoke|ban|unban|blocked)\b/gm)].map((m) => m[1]);
103
+ assert.deepStrictEqual(cmds, ['list', 'approve', 'revoke', 'ban', 'unban', 'blocked']);
104
+ const argsFor = { list: ['list'], approve: ['approve', 't', 'n', 'F'], revoke: ['revoke', 't', 'n'], ban: ['ban', 't', 'n'], unban: ['unban', 't', 'n'], blocked: ['blocked'] };
105
+ for (const c of cmds) assert.notStrictEqual((await cli(argsFor[c])).code, 2, c);
106
+ assert.strictEqual(spawnSync(process.execPath, [CLI, 'bogus']).status, 2);
107
+ });
108
+
109
+ test('ban prints the tokenless-access warning when the flag is off; no warning when on; blocked/unban', async () => {
110
+ protect = false;
111
+ let r = await cli(['ban', 't1', 'evil', 'because']);
112
+ assert.strictEqual(r.code, 0); assert.match(r.out, /banned t1\/evil/);
113
+ assert.match(r.out, /WARNING: protect_bound_names is OFF.*anonymously/);
114
+ assert.strictEqual(blocks[0].reason, 'because');
115
+ protect = true;
116
+ r = await cli(['ban', 't1', 'evil2', '--machine', 'abc']);
117
+ assert.doesNotMatch(r.out, /WARNING/); assert.match(r.out, /machine abc/);
118
+ assert.strictEqual(blocks[1].machine_id_hash, 'abc');
119
+ r = await cli(['blocked']);
120
+ assert.match(r.out, /t1\tname\tevil\t/);
121
+ r = await cli(['unban', 't1', 'evil']);
122
+ assert.match(r.out, /unbanned 1 entry/);
123
+ assert.match((await cli(['blocked'])).out, /nothing blocked/);
124
+ assert.strictEqual((await cli(['ban', 't1'])).code, 2);
125
+ });
126
+
127
+ test('list shows self-reported machine info only when sent', async () => {
128
+ tokens.push({ team_id: 't1', node_name: 'm1', fingerprint: 'CCCC', status: 'pending', created_at: 'x', hostname: 'box', os: 'linux', machine_id_hash: 'abcdef0123456789' });
129
+ const r = await cli(['list']);
130
+ assert.match(r.out, /m1\tCCCC\tx\thost=box os=linux machine=abcdef012345 \(self-reported\)/);
131
+ });
@@ -1,24 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * run-rust-tests.js — make `cargo test --lib` actually runnable on Windows (TASK_1619).
3
+ * run-rust-tests.js — build the kenaz-dashboard lib test binary and run it (TASK_1619).
4
4
  *
5
- * Root cause: the kenaz-dashboard lib unittest binary needs the Common-Controls v6
6
- * manifest (rfd crate statically imports comctl32!TaskDialogIndirect, which only
7
- * exists in the v6 SxS assembly). build.rs emits `cargo:rustc-link-arg-tests=
8
- * /MANIFEST:EMBED + /MANIFESTINPUT:test.manifest`, but cargo applies those flags
9
- * only to integration test targets (tests/), NOT to the lib unittest unit —
10
- * verified empirically 2026-06-11: the build-script output file contains the
11
- * directives while the rustc -v link invocation does not. Result: the loader
12
- * binds comctl32 5.82 from WinSxS and dies with STATUS_ENTRYPOINT_NOT_FOUND
13
- * (0xc0000139, bash exit 127) before any test runs.
14
- *
15
- * A global `cargo:rustc-link-arg` is NOT safe: tauri-build already embeds an
16
- * RT_MANIFEST resource for the app binary; adding /MANIFESTINPUT there creates
17
- * a duplicate-manifest link failure.
18
- *
19
- * Fix here: build with --no-run, copy the test exe to a stable fresh name
20
- * (the copy's new timestamp busts the per-path SxS activation-context cache),
21
- * drop an external .manifest beside it, then exec it with passthrough args.
5
+ * Builds with --no-run (so compiler errors can be rendered readably from the JSON
6
+ * message stream), locates the lib unittest executable and execs it with the filter
7
+ * and passthrough args. The Windows Common-Controls manifest workaround this script
8
+ * used to carry was only needed for rfd's static TaskDialogIndirect import; rfd is
9
+ * gone and the test exe starts fine without any manifest.
22
10
  *
23
11
  * Usage:
24
12
  * node dev/scripts/run-rust-tests.js [test-filter] [-- extra-harness-args]
@@ -36,7 +24,6 @@ const { verifyTargetDir } = require('./verify-target-dir.js');
36
24
 
37
25
  const repoRoot = path.resolve(__dirname, '..', '..');
38
26
  const crateDir = path.join(repoRoot, 'dashboard', 'src-tauri');
39
- const manifestSrc = path.join(crateDir, 'test.manifest');
40
27
 
41
28
  // TASK_2058: parse `[test-filter] [-- extra-harness-args]`.
42
29
  //
@@ -137,7 +124,7 @@ function main() {
137
124
  const exeArgs = buildExeArgs(process.argv.slice(2));
138
125
 
139
126
  // TASK_1879: default to the canonical shared verify target dir. Avoids both
140
- // Windows MAX_PATH (vendor/kuzu CMake) and the live tauri-dev lock on the
127
+ // Windows MAX_PATH and the live tauri-dev lock on the
141
128
  // repo's own target/. Explicit CARGO_TARGET_DIR still wins.
142
129
  const env = { ...process.env };
143
130
  if (!env.CARGO_TARGET_DIR) {
@@ -192,31 +179,8 @@ function main() {
192
179
  fail(`could not locate lib test executable (got: ${testExe})`);
193
180
  }
194
181
 
195
- // 3. Non-Windows: no manifest problem — run the artifact directly.
196
- if (process.platform !== 'win32') {
197
- const direct = spawnSync(testExe, exeArgs, { stdio: 'inherit' });
198
- process.exit(direct.status ?? 1);
199
- }
200
-
201
- // 4. Windows: copy to a stable name + external manifest (cache-busting copy).
202
- if (!fs.existsSync(manifestSrc)) {
203
- fail(`test.manifest not found at ${manifestSrc}`);
204
- }
205
- const runDir = path.dirname(testExe);
206
- const runExe = path.join(runDir, 'kenaz_dashboard_unit_tests.exe');
207
- fs.copyFileSync(testExe, runExe); // fresh timestamp invalidates SxS manifest cache
208
- fs.copyFileSync(manifestSrc, `${runExe}.manifest`);
209
- // Stage the PDB too so panic backtraces keep symbols (best effort).
210
- const pdb = testExe.replace(/\.exe$/i, '.pdb');
211
- if (fs.existsSync(pdb)) {
212
- try {
213
- fs.copyFileSync(pdb, runExe.replace(/\.exe$/i, '.pdb'));
214
- } catch {
215
- /* non-fatal */
216
- }
217
- }
218
-
219
- const run = spawnSync(runExe, exeArgs, { stdio: 'inherit' });
182
+ // 3. Run the artifact directly.
183
+ const run = spawnSync(testExe, exeArgs, { stdio: 'inherit' });
220
184
  process.exit(run.status ?? 1);
221
185
  }
222
186
 
@@ -5,8 +5,8 @@
5
5
  *
6
6
  * PROBLEM: a Rust-scoped worktree subagent cannot safely run `cargo`/
7
7
  * `run-rust-tests.js` in its OWN task worktree. That worktree is a fresh
8
- * source root, so kuzu (a [patch] path dependency, see verify-target-dir.js)
9
- * has to be rebuilt from scratch via CMake — 10+ minutes, which blows the
8
+ * source root, so the whole dependency tree (see verify-target-dir.js)
9
+ * has to be rebuilt from scratch — minutes, which blows the
10
10
  * Bash foreground timeout and, per prompt.js rule 13/feedback_agent_orphan_
11
11
  * build_holds_lock, leaves an orphan cargo holding the shared target-dir
12
12
  * lock forever.
@@ -14,7 +14,7 @@
14
14
  * FIX: ONE canonical, reused worktree (this script owns it) that agents
15
15
  * check out their branch into, run a single narrow test filter in, and
16
16
  * hand back. The cache stays warm across dispatches because it is always
17
- * the SAME source root (kuzu's build artifacts are keyed per source root).
17
+ * the SAME source root (build artifacts are keyed per source root).
18
18
  *
19
19
  * Marker file `.kenaz-rust-verify` inside the worktree is the safety
20
20
  * invariant: every subcommand that would touch (create/checkout/lock in)
@@ -67,7 +67,7 @@ const RUNNER_REL_PATH = path.join('dev', 'scripts', 'run-rust-tests.js');
67
67
  // and the old `path.resolve(__dirname, '..', '..')` returned the WORKTREE
68
68
  // root, not the main repo — deriving a stray `<repoRoot>/.kenaz-worktrees/
69
69
  // <repoRoot-basename>/rust-verify` path nested inside the task worktree
70
- // and paying a cold kuzu build, exactly what this script exists to avoid.
70
+ // and paying a cold build, exactly what this script exists to avoid.
71
71
  //
72
72
  // Fix: ask git. `git rev-parse --git-common-dir` always resolves to the
73
73
  // MAIN repo's shared `.git` directory, even when run from a linked
@@ -6,9 +6,9 @@
6
6
  * generators, taskgen templates, verify scripts, formation runner/integration)
7
7
  * must take the value from here instead of inventing its own directory.
8
8
  *
9
- * Why a short path at all (Windows): vendor/kuzu's CMake object paths exceed
10
- * MAX_PATH (260) when the target dir sits under the repo or a worktree
11
- * (worktree paths add ~40 chars). A short root like C:/t/... keeps CMake alive.
9
+ * Why a short path at all (Windows): deep C/C++ dependency object paths
10
+ * can approach MAX_PATH (260) when the target dir sits under the repo or a
11
+ * worktree (worktree paths add ~40 chars). A short root like C:/t/... avoids it.
12
12
  *
13
13
  * Why ONE shared directory (user decision 2026-08-22): the previous convention
14
14
  * — "each dispatch invents a unique dir (C:/t/vis, C:/t/emb, C:/t1599, ...)
@@ -27,10 +27,10 @@
27
27
  * cargo.exe / build-script-build.exe at 0% CPU with NO rustc children
28
28
  * (nothing is compiling). Only in the orphan case kill the holder.
29
29
  *
30
- * Cache-heat caveat: kuzu is a [patch] path dependency, so its build artifacts
31
- * are keyed per source root. Verifies from the SAME checkout (main repo, or a
30
+ * Cache-heat caveat: workspace crates' build artifacts are keyed per
31
+ * source root. Verifies from the SAME checkout (main repo, or a
32
32
  * reused worktree) get the warm cache; a verify from a NEW worktree path
33
- * recompiles kuzu but coexists in the same dir without invalidating others.
33
+ * recompiles them but coexists in the same dir without invalidating others.
34
34
  * The dir therefore still grows across many distinct worktree roots — when it
35
35
  * gets too big, delete the whole canonical dir while no cargo is running; it
36
36
  * is pure cache and rebuilds itself.
@@ -13,7 +13,7 @@ off = every hook exits before reading stdin. assist = silent telemetry/bookkeepi
13
13
  | agent-flow POST (Notification, Pre/PostToolUse, PostToolUseFailure, SessionEnd, Stop, SubagentStart/Stop; user-level curl) | ship | assist | skipped silently when backend down |
14
14
  | SessionStart agent-flow ping | ship | assist | |
15
15
  | SessionStart guidance (new, replaces injecting CLAUDE.md) | ship | full | `guidance.md` |
16
- | _run-user-prompt.js (runs handoff-staleness, fatigue-check, daemon-outbox, turn-context, task-bridge, lease-touch, stream-delta) | ship | full | prints context |
16
+ | _run-user-prompt.js (runs daemon-outbox, turn-context, task-bridge, correction-detect; see DEFAULT_HOOKS) | ship | full | prints context |
17
17
  | memory-guard.js | ship | full | |
18
18
  | synapse-first-investigation.js | ship | full | |
19
19
  | task-bridge.js (PostToolUse Task*) | ship | assist | |
@@ -26,19 +26,19 @@ off = every hook exits before reading stdin. assist = silent telemetry/bookkeepi
26
26
 
27
27
  Hooks only act in Kenaz projects (`.kenaz/project.yaml`, `_project-guard.js`): run `/kenaz init` once in a fresh project.
28
28
 
29
- ## Commands (25)
29
+ ## Commands (13)
30
30
  | Class | Commands |
31
31
  |---|---|
32
- | ship (12 + new `/kenaz`) | Ansuz, Amelia, architect, dev, incident, review, security, spec, strategy, 產品, 設計, 評估 |
33
- | core | Huginn, sunny, lead, agent-worker, canary |
34
- | retire | archive, init, 進度, 開發, legacy_dev, 產品檢視, product-review, multi-agent, dev, lead (user retired 2026-10-09 with Formation) |
32
+ | ship (11 + new `/kenaz`) | Ansuz, Amelia, architect, incident, review, security, spec, 產品, 設計, 評估 |
33
+ | core | Huginn, canary, agent-worker (invoked by the Rust scheduler with `--task`/`--agent`) |
34
+ | removed | strategy (Strategy Agent skill gone), archive, init, 進度, legacy_dev, 產品檢視, product-review, sunny (Pipeline / TASK_QUEUE / retired-subsystem commands; `/dev`, `/lead`, `/開發`, `multi-agent` retired earlier with Formation) |
35
35
 
36
- ## Skills (45)
36
+ ## Skills
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, sunny, optimization_agent |
41
- | retire | legacy_dev |
40
+ | core | heimdall, huginn, optimization_agent |
41
+ | removed | legacy_dev, sunny |
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
 
44
44
  ## Open items / caveats
@@ -48,8 +48,18 @@ Independent Codex sessions use **pull-on-prompt**: in `full` mode with trusted h
48
48
 
49
49
  Resolution: env `KENAZ_MODE` > `<project>/.kenaz/kenaz.yaml` > `~/.kenaz/kenaz.yaml` > `full`. Hooks fail open (backend down or core not found: no output, exit 0).
50
50
 
51
+ ## Agent SDK sessions
52
+
53
+ Programs built on `claude_agent_sdk` (`CLAUDE_CODE_ENTRYPOINT` `sdk-py` / `sdk-ts`) load `~/.claude/settings.json` too, so they would get this plugin. Hooks (`hooks/sdk-gate.js`) and the kenaz-peer mod therefore do nothing for those sessions. `claude -p` (`sdk-cli`) and interactive entrypoints are unchanged. Set `KENAZ_IN_SDK=1` to opt a program in.
54
+
55
+ The MCP tools cannot be gated: `.mcp.json` is a plain HTTP server entry, not a launcher that could exit, and the connection is made before any hook runs. SDK users who do not want the 34 tools pass `setting_sources=[]` (or `[]` plus the sources they need) and `strict_mcp_config` (`extra_args={"strict-mcp-config": None}`) in `ClaudeAgentOptions`.
56
+
51
57
  ## CLI version gate
52
58
 
53
59
  Not needed: the 2.1.287 gate (TASK_2234, `peer_mod.rs`) exists because kenaz-peer is a mod module (`hooks/register.ts`). This plugin uses only plain hooks, commands, skills and MCP, which older CLIs support. Automatic registration into `CLAUDE_CODE_PLUGIN_DIRS` (as kenaz-peer gets) is a follow-up in `peer_mod.rs`; until then use `--plugin-dir`.
54
60
 
55
61
  Tests: `node --test plugins/kenaz/scripts/kenaz.test.js`. Classification: `INVENTORY.md`.
62
+
63
+ ## Peer node identity
64
+
65
+ kenaz-server requests a relay node token on its own; it starts pending until the relay operator approves its fingerprint. `GET http://localhost:18080/api/peer/status?projectPath=<project>` shows `node_identity` (`none` / `pending` with the fingerprint / `active`). Send the fingerprint to the operator out of band. No token or an old relay keeps today behaviour. Operator commands, the enforcement flag and the `X-Node-Token` header for non-Claude agents that call the relay directly: `yggdrasil-relay/DESIGN.md`, section "Node tokens and approval".
@@ -17,8 +17,7 @@
17
17
  - 撰寫模組接口規範
18
18
 
19
19
  4. **產出文件**:
20
- - 更新 `dev/pipeline/TASK_QUEUE.json`
21
- - 建立任務文件至 `dev/pipeline/tasks/`
20
+ - 用 `node "${CLAUDE_PLUGIN_ROOT}/hooks/core-resolve.js" .collaboration/core/task-api.js create` 建立任務卡(`.kenaz/tasks/backlog/`)
22
21
  - 撰寫接口文檔至 `docs/`
23
22
 
24
23
  請開始執行系統架構設計。
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 使用場景
6
6
 
7
- - 完成重要里程碑後,需要整理記憶點
7
+ - 完成重要里程碑後,需要整理文件與任務狀態
8
8
  - 需要更新 PRODUCT_SPEC.md 的 Feature 狀態
9
9
  - 需要從 .failure/ 提取失敗經驗
10
10
  - 定期維護文件(建議每週)
@@ -10,6 +10,6 @@
10
10
  - 深度理解用戶需求
11
11
  - 進行需求分析
12
12
  - 將需求拆解為開發任務
13
- - 將任務寫入 Pipeline 系統
13
+ - 用 `node "${CLAUDE_PLUGIN_ROOT}/hooks/core-resolve.js" .collaboration/core/task-api.js create` 建立任務卡(`.kenaz/tasks/backlog/`),完成後建議交給 `/Ansuz` 派工
14
14
 
15
15
  請開始執行產品需求分析。
@@ -1,5 +1,5 @@
1
1
  [Kenaz plugin: mode full] Kenaz MCP tools (kenaz-dashboard) are available when kenaz-server runs on localhost:18080.
2
2
  - Code navigation: kenaz_lsp_callers / kenaz_lsp_references / kenaz_lsp_definition / kenaz_lsp_symbols (always pass projectPath). Text search: Grep/Glob.
3
3
  - Collaboration: kenaz_peer_send / kenaz_peer_threads (other sessions, other machines, Codex), /kenaz ygg status|join.
4
- - Reply in the user's language. Commands: /Ansuz, /Amelia, /review, /security, /incident, /spec, /strategy.
4
+ - Reply in the user's language. Commands: /Ansuz, /Amelia, /review, /security, /incident, /spec.
5
5
  - Switch: /kenaz off | assist | full (or env KENAZ_MODE). off disables every Kenaz hook.
@@ -15,6 +15,7 @@ const net = require('net');
15
15
  const http = require('http');
16
16
  const { spawnSync } = require('child_process');
17
17
  const { resolveMode, allows, projectDir } = require('./mode');
18
+ const { isSdkSession } = require('./sdk-gate');
18
19
 
19
20
  const HOST = '127.0.0.1';
20
21
  const PORT = Number(process.env.KENAZ_BACKEND_PORT) || 18080;
@@ -86,7 +87,7 @@ function run(spec, stdin, dir) {
86
87
 
87
88
  function main() {
88
89
  const spec = HOOKS[process.argv[2]];
89
- if (!spec) return;
90
+ if (!spec || isSdkSession()) return; // TASK_2455: SDK sessions get nothing, before stdin or any write
90
91
  const dir = projectDir();
91
92
  // A project that ships the Kenaz hooks itself (the core repo) registers them in its own
92
93
  // .claude/settings.json; the plugin yields so no hook runs twice (same rule as _user-scope.js).
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+ // TASK_2455: claude-agent-sdk sessions (CLAUDE_CODE_ENTRYPOINT sdk-py / sdk-ts / ...) must not get Kenaz.
3
+ // sdk-cli is `claude -p` (Kenaz's own dispatch, headless nodes) and keeps working. KENAZ_IN_SDK=1 opts in.
4
+ function isSdkSession(env) {
5
+ env = env || process.env;
6
+ const ep = String(env.CLAUDE_CODE_ENTRYPOINT || '');
7
+ return ep.startsWith('sdk-') && ep !== 'sdk-cli' && env.KENAZ_IN_SDK !== '1';
8
+ }
9
+ module.exports = { isSdkSession };
@@ -19,6 +19,18 @@ function hook(name, dir, env, input) {
19
19
  });
20
20
  }
21
21
 
22
+ test('TASK_2455: SDK sessions get nothing; cli / sdk-cli / KENAZ_IN_SDK=1 keep today\'s output', () => {
23
+ const dir = proj();
24
+ for (const n of ['guidance', 'prompt-runner', 'memory-guard', 'flow-post', 'session-start', 'session-stop']) {
25
+ for (const ep of ['sdk-py', 'sdk-ts']) {
26
+ const r = hook(n, dir, { CLAUDE_CODE_ENTRYPOINT: ep });
27
+ assert.strictEqual(r.status, 0); assert.strictEqual(r.stdout, ''); assert.strictEqual(r.stderr, '');
28
+ }
29
+ }
30
+ for (const ep of ['cli', 'sdk-cli']) assert.match(hook('guidance', dir, { CLAUDE_CODE_ENTRYPOINT: ep }).stdout, /Kenaz plugin/);
31
+ assert.match(hook('guidance', dir, { CLAUDE_CODE_ENTRYPOINT: 'sdk-py', KENAZ_IN_SDK: '1' }).stdout, /Kenaz plugin/);
32
+ });
33
+
22
34
  test('full prints guidance (control for off)', () => {
23
35
  const r = hook('guidance', proj());
24
36
  assert.strictEqual(r.status, 0);
@@ -102,7 +114,7 @@ test('ygg join activates the room in collaboration_projects; status explains how
102
114
  const srv = http.createServer((req, res) => {
103
115
  let b = ''; req.on('data', (d) => { b += d; });
104
116
  req.on('end', () => {
105
- if (req.method === 'POST') posts.push(JSON.parse(b));
117
+ if (req.method === 'POST') { posts.push(JSON.parse(b)); cfg.collaboration_projects = posts[posts.length - 1].collaboration_projects; }
106
118
  res.setHeader('content-type', 'application/json');
107
119
  res.end(JSON.stringify(req.method === 'POST' ? { ok: true } : cfg));
108
120
  });
@@ -120,7 +132,7 @@ test('ygg join activates the room in collaboration_projects; status explains how
120
132
  srv.close();
121
133
  }
122
134
  const { buildStatus, renderStatus } = require('./ygg.js');
123
- assert.match(renderStatus(buildStatus(cfg, { connected: false, nodes: [] }, null)), /not active in this project - run "ygg join <key>"/);
135
+ assert.match(renderStatus(buildStatus(Object.assign({}, cfg, { collaboration_projects: ['other'] }), { connected: false, nodes: [] }, null)), /not active in this project - run "ygg join <key>"/);
124
136
  });
125
137
 
126
138
  // TASK_2416: `ygg create` posts to the relay (config relay_url), then joins with the returned key.
@@ -139,7 +151,7 @@ async function withStub(relayReply, fn) {
139
151
  }
140
152
  if (req.url.startsWith('/api/nodes')) return res.end('[]');
141
153
  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 })); }
154
+ if (req.method === 'POST') { seen.config.push(JSON.parse(b)); cfg.collaboration_projects = seen.config[seen.config.length - 1].collaboration_projects; return res.end(JSON.stringify({ ok: true })); }
143
155
  return res.end(JSON.stringify(cfg));
144
156
  }
145
157
  res.end(JSON.stringify({ success: true, created: false }));
@@ -16,7 +16,7 @@ const SKILLS = [
16
16
  'adapt', 'animate', 'audit', 'bolder', 'clarify', 'colorize', 'critique', 'delight', 'distill', 'extract',
17
17
  'frontend-design', 'harden', 'normalize', 'onboard', 'optimize', 'polish', 'quieter', 'ui-ux-pro-max',
18
18
  ];
19
- const COMMANDS = ['Amelia', 'Ansuz', 'architect', 'incident', 'review', 'security', 'spec', 'strategy', '產品', '設計', '評估'];
19
+ const COMMANDS = ['Amelia', 'Ansuz', 'architect', 'incident', 'review', 'security', 'spec', '產品', '設計', '評估'];
20
20
 
21
21
  // Core-relative references become plugin-relative / core-resolved.
22
22
  // In a user's project (npm install) the core lives in ~/.kenaz/core, not the project folder, so a
@@ -149,7 +149,15 @@ function postBody(cfg, patch) {
149
149
  async function join(dir, key, scope) {
150
150
  if (!key || /\s/.test(key)) throw new Error('usage: ygg join <key> [--project|--global]');
151
151
  const cfg = await getConfig(dir);
152
+ // TASK_2441: no room key to activate = the POST below would save an empty list and "succeed".
153
+ if (!cfg.effective_project_key) throw new Error("cannot work out this project's room (" + (cfg.error || 'no effective_project_key') + '); does ' + dir + ' have a repo with a remote?');
152
154
  await postConfig(dir, postBody(cfg, { enabled: true, publishable_key: key, key_scope: scope, collaboration_projects: withCollab(cfg, true) }));
155
+ // Verify by reading back: a save that returned ok but did not land must not print a status that says "run join".
156
+ const after = await getConfig(dir);
157
+ if (!(after.collaboration_projects || []).includes(cfg.effective_project_key)) {
158
+ throw new Error('join saved the key but room "' + cfg.effective_project_key + '" is not in collaboration_projects after re-reading the config; expected in ' +
159
+ path.join(dir, '.kenaz', 'yggdrasil.local.yaml') + ' (collaboration_projects: "' + cfg.effective_project_key + '"); the server reports ' + JSON.stringify(after.collaboration_projects || []));
160
+ }
153
161
  return status(dir);
154
162
  }
155
163
 
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+ // node --test plugins/kenaz/scripts/ygg.test.js
3
+ // TASK_2441: ygg join must read the config back and fail loudly when the room did not land.
4
+ const test = require('node:test');
5
+ const assert = require('node:assert');
6
+ const fs = require('fs');
7
+ const os = require('os');
8
+ const path = require('path');
9
+ const http = require('http');
10
+ const { spawn } = require('child_process');
11
+
12
+ const YGG = path.join(__dirname, 'ygg.js');
13
+ const ROOM = 'github.com/o/r';
14
+
15
+ async function withBackend(persist, fn) {
16
+ const cfg = { enabled: false, effective_project_key: ROOM, node_name: 'Alice', publishable_key: '', collaboration_projects: [], relay_url: '' };
17
+ const srv = http.createServer((req, res) => {
18
+ let b = ''; req.on('data', (d) => { b += d; });
19
+ req.on('end', () => {
20
+ res.setHeader('content-type', 'application/json');
21
+ if (req.url.startsWith('/api/yggdrasil/config') && req.method === 'POST') {
22
+ if (persist) Object.assign(cfg, JSON.parse(b));
23
+ return res.end(JSON.stringify({ ok: true }));
24
+ }
25
+ res.end(JSON.stringify(req.url.startsWith('/api/yggdrasil/config') ? cfg : req.url.startsWith('/api/projects') ? { success: true } : {}));
26
+ });
27
+ });
28
+ await new Promise((r) => srv.listen(0, '127.0.0.1', r));
29
+ try { return await fn(srv.address().port); } finally { srv.close(); }
30
+ }
31
+
32
+ function cli(port, dir) {
33
+ return new Promise((resolve) => {
34
+ const c = spawn(process.execPath, [YGG, 'join', 'ygk_abc', '--project-path', dir],
35
+ { env: Object.assign({}, process.env, { KENAZ_HTTP_PORT: String(port) }) });
36
+ let out = ''; let err = '';
37
+ c.stdout.on('data', (d) => { out += d; }); c.stderr.on('data', (d) => { err += d; });
38
+ c.on('close', (code) => resolve({ code, out, err }));
39
+ });
40
+ }
41
+
42
+ test('join exits non-zero naming the file when the room is not in collaboration_projects after the POST', async () => {
43
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ygg2441-'));
44
+ await withBackend(false, async (port) => {
45
+ const r = await cli(port, dir);
46
+ assert.notStrictEqual(r.code, 0);
47
+ assert.match(r.err, /yggdrasil\.local\.yaml/);
48
+ assert.ok(r.err.includes(ROOM), r.err);
49
+ assert.doesNotMatch(r.out, /not active in this project/);
50
+ });
51
+ });
52
+
53
+ test('control: join against a backend that persists the room prints "active in this project"', async () => {
54
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ygg2441-'));
55
+ await withBackend(true, async (port) => {
56
+ const r = await cli(port, dir);
57
+ assert.strictEqual(r.code, 0, r.err);
58
+ assert.match(r.out, /active in this project/);
59
+ assert.doesNotMatch(r.out, /not active/);
60
+ });
61
+ });
62
+
63
+ test('join refuses when the backend reports no effective_project_key (nothing to activate)', async () => {
64
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ygg2441-'));
65
+ const srv = http.createServer((req, res) => { res.setHeader('content-type', 'application/json'); res.end(JSON.stringify({ enabled: false, error: 'yggdrasil.yaml not found' })); });
66
+ await new Promise((r) => srv.listen(0, '127.0.0.1', r));
67
+ try {
68
+ const r = await cli(srv.address().port, dir);
69
+ assert.notStrictEqual(r.code, 0);
70
+ assert.match(r.err, /cannot work out this project's room/);
71
+ } finally { srv.close(); }
72
+ });