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
@@ -1,839 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * memory-action-log.js — Unified Memory-to-Action instrumentation (TASK_1072 + TASK_1078)
5
- *
6
- * Tracks whether a recalled memory item (handoff, episode, rune, sigil,
7
- * self-model lesson, lesson guard) later influenced a design, dispatch,
8
- * validation or fix-loop decision.
9
- *
10
- * Why this exists:
11
- * Recall has been a silent operation — we had no way to distinguish
12
- * "loaded into context" from "actually changed the agent's behaviour".
13
- * This module turns memory from passive context into a measurable
14
- * decision-influence signal.
15
- *
16
- * Relationship to existing infrastructure:
17
- * - Runes: synapse rune_usage_log (TASK_589) — we delegate; our
18
- * log cross-references rune_id but does not duplicate
19
- * the rune table.
20
- * - Lesson guards: lesson-guard.js (TASK_1071/1076) — we call its module
21
- * API so the legacy lesson_guard_acted_on.jsonl and the
22
- * new unified log stay in sync.
23
- * - Handoff/episode/sigil/self-model: NEW — no prior acted-on tracking.
24
- *
25
- * Storage:
26
- * .kenaz/specialist/memory_action_log.jsonl (append-only, JSONL)
27
- *
28
- * Schema (one JSON object per line):
29
- * {
30
- * "timestamp": "<ISO-8601>",
31
- * "event": "recall" | "acted_on",
32
- * "recall_id": "<stable ID, e.g. recall_1712345_abc123>", // always present
33
- * "source": "handoff" | "episode" | "rune" | "sigil" | "self_model" | "lesson_guard",
34
- * "source_id": "<stable id: rune_id | episode_id | sigil_id | B-XXX | L-XXX | handoff session_id>",
35
- * "task_id": "<TASK_XXXX or session id>",
36
- * "phase": <int phase> | null,
37
- * "action_type": "design_decision" | "dispatch_choice" | "validation_behavior"
38
- * | "fix_loop_decision" | "scope_adjustment" | "blocked_exit"
39
- * | null,
40
- * "reason": "<evidence text, >=20 chars for acted_on>",
41
- * "conflict_with": "<winner_source_id>" | null,
42
- * "priority_rank": <int from memory_source_priority.yaml> | null,
43
- * "recalled_from_ids": ["<recall_id>", ...] // for acted_on events: IDs of recall events that led to this decision
44
- * }
45
- *
46
- * CLI:
47
- * node memory-action-log.js record-recall --source=rune --source-id=rune_X --task=TASK_XXX [--phase=N] [--reason="text"]
48
- * node memory-action-log.js record-acted --source=rune --source-id=rune_X --task=TASK_XXX --action-type=design_decision --reason="why" [--recalled-from=recall_id1,recall_id2]
49
- * node memory-action-log.js report [--window-days=30] [--task=TASK_XXX] [--json]
50
- * node memory-action-log.js priority [--source-type=rune] [--type=decision] [--confidence=0.85]
51
- * node memory-action-log.js list-recalls --task=TASK_XXX [--phase=N] [--json]
52
- *
53
- * Module exports (for phase-gate.js and other callers):
54
- * recordRecall(entry) → { ok, recall_id, line?, error? }
55
- * recordActedOn(entry) → { ok, line?, error? }
56
- * getMetrics(opts) → { by_source, totals, window_days, link_rate }
57
- * getPriorityRank(descriptor) → { rank, tier, mode }
58
- * loadPriorityRules() → parsed yaml object (cached)
59
- * recordActedOnBatch(items, taskId, phase) → { recorded, failed[] }
60
- * listRecalls(taskId, phase?) → [recall entries]
61
- *
62
- * Graceful degradation contract:
63
- * Every write path is try/catch — a failure in the memory-action-log
64
- * must never block a phase-gate exit or cause a specialist to crash.
65
- * On failure the module returns { ok: false, error } and never throws.
66
- *
67
- * Exit codes (CLI):
68
- * 0 = success
69
- * 1 = fatal error (bad args, missing required field)
70
- */
71
-
72
- 'use strict';
73
-
74
- const fs = require('fs');
75
- const path = require('path');
76
- const crypto = require('crypto');
77
-
78
- // ═══════════════════════════════════════
79
- // Path resolution
80
- // ═══════════════════════════════════════
81
-
82
- function findProjectRoot() {
83
- let dir = __dirname;
84
- while (dir !== path.dirname(dir)) {
85
- const parent = path.dirname(dir);
86
- if (fs.existsSync(path.join(parent, '.specialist'))) return parent;
87
- dir = parent;
88
- }
89
- return process.cwd();
90
- }
91
-
92
- const BASE_DIR = findProjectRoot();
93
- const LOG_PATH = path.join(BASE_DIR, '.kenaz', 'specialist', 'memory_action_log.jsonl');
94
- const PRIORITY_PATH = path.join(BASE_DIR, '.kenaz', 'specialist', 'memory_source_priority.yaml');
95
- const LESSON_GUARD_JS = path.join(BASE_DIR, '.specialist', 'tools', 'lesson-guard.js');
96
-
97
- const VALID_SOURCES = new Set([
98
- 'handoff', 'episode', 'rune', 'sigil', 'self_model', 'lesson_guard',
99
- ]);
100
-
101
- const VALID_ACTION_TYPES = new Set([
102
- 'design_decision',
103
- 'dispatch_choice',
104
- 'validation_behavior',
105
- 'fix_loop_decision',
106
- 'scope_adjustment',
107
- 'blocked_exit',
108
- ]);
109
-
110
- const MIN_REASON_CHARS = 20;
111
-
112
- // ═══════════════════════════════════════
113
- // recall_id generation (TASK_1078)
114
- // ═══════════════════════════════════════
115
-
116
- /**
117
- * Generate a stable, unique recall ID.
118
- * Format: recall_<timestamp_ms>_<6-hex-chars>
119
- * Stable within the same millisecond window; globally unique enough to
120
- * cross-reference within a session log. Not a cryptographic guarantee.
121
- */
122
- function generateRecallId() {
123
- const ts = Date.now();
124
- const rnd = crypto.randomBytes(3).toString('hex');
125
- return `recall_${ts}_${rnd}`;
126
- }
127
-
128
- // ═══════════════════════════════════════
129
- // Minimal YAML parser for memory_source_priority.yaml
130
- // (same pattern as lesson-guard.js — no external deps)
131
- // ═══════════════════════════════════════
132
-
133
- let _priorityCache = null;
134
-
135
- /**
136
- * Parse the priority yaml into a JS object. We only implement the subset
137
- * needed to answer getPriorityRank() — nested maps with scalar leaves and
138
- * small lists. If the file is missing or unparseable we fall back to the
139
- * hard-coded defaults below.
140
- */
141
- function loadPriorityRules() {
142
- if (_priorityCache) return _priorityCache;
143
- const fallback = {
144
- version: 1,
145
- sources: {
146
- user_session_context: { priority_rank: 100, conflict_mode: 'strict' },
147
- verified_decision_rune: { priority_rank: 90, conflict_mode: 'strict', source_type: 'rune' },
148
- session_handoff: { priority_rank: 80, conflict_mode: 'advisory', source_type: 'handoff' },
149
- self_model_lesson: { priority_rank: 70, conflict_mode: 'advisory', source_type: 'self_model' },
150
- risk_or_fix_rune: { priority_rank: 60, conflict_mode: 'strict', source_type: 'rune' },
151
- sigil: { priority_rank: 50, conflict_mode: 'advisory', source_type: 'sigil' },
152
- insight_or_pattern_rune: { priority_rank: 40, conflict_mode: 'advisory', source_type: 'rune' },
153
- episode: { priority_rank: 30, conflict_mode: 'advisory', source_type: 'episode' },
154
- summary_or_ash_rune: { priority_rank: 20, conflict_mode: 'advisory', source_type: 'rune' },
155
- lesson_guard: { priority_rank: 10, conflict_mode: 'advisory', source_type: 'lesson_guard' },
156
- },
157
- acted_on_weights: {
158
- verified_decision_rune: 1.0, session_handoff: 1.0, self_model_lesson: 1.5,
159
- risk_or_fix_rune: 1.2, sigil: 0.8, insight_or_pattern_rune: 0.7,
160
- episode: 0.5, summary_or_ash_rune: 0.5, lesson_guard: 0.4,
161
- },
162
- };
163
-
164
- if (!fs.existsSync(PRIORITY_PATH)) {
165
- _priorityCache = fallback;
166
- return _priorityCache;
167
- }
168
-
169
- try {
170
- const raw = fs.readFileSync(PRIORITY_PATH, 'utf8');
171
- const parsed = parsePriorityYaml(raw);
172
- _priorityCache = parsed || fallback;
173
- return _priorityCache;
174
- } catch {
175
- _priorityCache = fallback;
176
- return _priorityCache;
177
- }
178
- }
179
-
180
- function parsePriorityYaml(content) {
181
- const out = { version: 1, sources: {}, acted_on_weights: {}, tie_breakers: [], acted_on_evidence_requirements: {} };
182
- const lines = content.split('\n');
183
-
184
- let section = null; // 'sources' | 'weights' | 'tiebreakers' | 'evidence'
185
- let currentSource = null;
186
- let currentNested = null; // 'rune_filter' | 'allowed_action_types' | etc.
187
-
188
- for (let i = 0; i < lines.length; i++) {
189
- const line = lines[i];
190
- const trimmed = line.trim();
191
- if (!trimmed || trimmed.startsWith('#')) continue;
192
-
193
- // Top-level: version
194
- const v = line.match(/^version:\s*(\d+)/);
195
- if (v) { out.version = parseInt(v[1], 10); continue; }
196
-
197
- // Top-level section markers
198
- if (/^sources:\s*$/.test(line)) { section = 'sources'; currentSource = null; continue; }
199
- if (/^acted_on_weights:\s*$/.test(line)) { section = 'weights'; currentSource = null; continue; }
200
- if (/^tie_breakers:\s*$/.test(line)) { section = 'tiebreakers'; currentSource = null; continue; }
201
- if (/^acted_on_evidence_requirements:\s*$/.test(line)) { section = 'evidence'; currentSource = null; continue; }
202
-
203
- if (section === 'sources') {
204
- // Source key: " name_of_source:"
205
- const srcMatch = line.match(/^ {2}([a-z_]+):\s*$/);
206
- if (srcMatch) {
207
- currentSource = srcMatch[1];
208
- out.sources[currentSource] = {};
209
- currentNested = null;
210
- continue;
211
- }
212
- if (!currentSource) continue;
213
-
214
- // Nested rune_filter block
215
- if (/^ {4}rune_filter:\s*$/.test(line)) {
216
- out.sources[currentSource].rune_filter = {};
217
- currentNested = 'rune_filter';
218
- continue;
219
- }
220
-
221
- // Scalar fields at 4-space indent
222
- const scalarMatch = line.match(/^ {4}([a-z_]+):\s*(.+?)\s*$/);
223
- if (scalarMatch && currentNested !== 'rune_filter') {
224
- const key = scalarMatch[1];
225
- let val = scalarMatch[2];
226
- // Strip trailing "> description" multi-line block continuations
227
- if (val === '>') {
228
- // Gather subsequent indented lines
229
- let buf = [];
230
- while (i + 1 < lines.length && /^ {6}/.test(lines[i + 1])) {
231
- buf.push(lines[++i].trim());
232
- }
233
- out.sources[currentSource][key] = buf.join(' ');
234
- continue;
235
- }
236
- val = val.replace(/^"|"$/g, '');
237
- if (/^-?\d+$/.test(val)) out.sources[currentSource][key] = parseInt(val, 10);
238
- else if (/^-?\d+\.\d+$/.test(val)) out.sources[currentSource][key] = parseFloat(val);
239
- else if (val === 'true' || val === 'false') out.sources[currentSource][key] = (val === 'true');
240
- else out.sources[currentSource][key] = val;
241
- continue;
242
- }
243
-
244
- // Inside rune_filter (6-space indent)
245
- if (currentNested === 'rune_filter') {
246
- const nested = line.match(/^ {6}([a-z_]+):\s*(.+?)\s*$/);
247
- if (nested) {
248
- const key = nested[1];
249
- let val = nested[2];
250
- if (/^\[.*\]$/.test(val)) {
251
- val = val.slice(1, -1).split(',').map(s => s.trim()).filter(Boolean);
252
- } else if (/^-?\d+\.\d+$/.test(val)) {
253
- val = parseFloat(val);
254
- } else {
255
- val = val.replace(/^"|"$/g, '');
256
- }
257
- out.sources[currentSource].rune_filter[key] = val;
258
- continue;
259
- }
260
- // End of nested if indent decreases
261
- if (/^ {4}[a-z_]/.test(line)) currentNested = null;
262
- }
263
- }
264
-
265
- if (section === 'weights') {
266
- const m = line.match(/^ {2}([a-z_]+):\s*([\d.]+)/);
267
- if (m) out.acted_on_weights[m[1]] = parseFloat(m[2]);
268
- }
269
- }
270
-
271
- return out;
272
- }
273
-
274
- /**
275
- * Return the priority tier that best matches a recalled item.
276
- *
277
- * descriptor = { source, rune_type?, confidence?, verified? }
278
- *
279
- * The match order is:
280
- * - Exact source_type tiers first (rune/handoff/episode/...).
281
- * - Within runes: match rune_filter (type + min_confidence + verified_not).
282
- * - Highest priority_rank wins on tie.
283
- * Returns { rank, tier, mode } or null if no tier matched.
284
- */
285
- function getPriorityRank(descriptor) {
286
- const rules = loadPriorityRules();
287
- const source = descriptor && descriptor.source;
288
- if (!source) return null;
289
-
290
- const matches = [];
291
- for (const [tierName, tier] of Object.entries(rules.sources)) {
292
- if (tier.source_type && tier.source_type !== source) continue;
293
- // For generic source tiers (no source_type), only match by name
294
- if (!tier.source_type && tierName !== source) continue;
295
-
296
- if (source === 'rune' && tier.rune_filter) {
297
- const f = tier.rune_filter;
298
- if (f.type && descriptor.rune_type !== f.type) continue;
299
- if (f.type_in && Array.isArray(f.type_in) && !f.type_in.includes(descriptor.rune_type)) continue;
300
- if (typeof f.min_confidence === 'number' && (descriptor.confidence || 0) < f.min_confidence) continue;
301
- if (f.verified_not && descriptor.verified === f.verified_not) continue;
302
- }
303
-
304
- matches.push({ tier: tierName, rank: tier.priority_rank || 0, mode: tier.conflict_mode || 'advisory' });
305
- }
306
-
307
- if (matches.length === 0) return null;
308
- matches.sort((a, b) => b.rank - a.rank);
309
- return matches[0];
310
- }
311
-
312
- // ═══════════════════════════════════════
313
- // Append-only JSONL writer
314
- // ═══════════════════════════════════════
315
-
316
- function ensureLogDir() {
317
- const dir = path.dirname(LOG_PATH);
318
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
319
- }
320
-
321
- function appendEntry(entry) {
322
- try {
323
- ensureLogDir();
324
- fs.appendFileSync(LOG_PATH, JSON.stringify(entry) + '\n', 'utf8');
325
- return { ok: true };
326
- } catch (e) {
327
- return { ok: false, error: e.message };
328
- }
329
- }
330
-
331
- // ═══════════════════════════════════════
332
- // recordRecall — module entry point
333
- // ═══════════════════════════════════════
334
-
335
- /**
336
- * Record a "memory was recalled into context" event.
337
- *
338
- * Minimal required fields: { source, source_id, task_id }
339
- * Accepted fields: phase, reason, rune_type, confidence, verified
340
- *
341
- * Does NOT enforce min-reason-chars — recall events are cheap, we log
342
- * everything. Action events have the stricter gate.
343
- *
344
- * Returns { ok, recall_id, line? } — recall_id is the stable ID for this
345
- * event, which can be passed into later recordActedOn calls via recalled_from_ids.
346
- */
347
- function recordRecall(input) {
348
- if (!input || typeof input !== 'object') {
349
- return { ok: false, error: 'input must be an object' };
350
- }
351
- const { source, source_id: sourceId, task_id: taskId } = input;
352
-
353
- if (!VALID_SOURCES.has(source)) {
354
- return { ok: false, error: `invalid source '${source}' (expected one of: ${[...VALID_SOURCES].join(', ')})` };
355
- }
356
- if (!sourceId) return { ok: false, error: 'source_id is required' };
357
- if (!taskId) return { ok: false, error: 'task_id is required' };
358
-
359
- const prio = getPriorityRank({
360
- source,
361
- rune_type: input.rune_type || null,
362
- confidence: input.confidence || null,
363
- verified: input.verified || null,
364
- });
365
-
366
- const recallId = generateRecallId();
367
-
368
- const entry = {
369
- timestamp: new Date().toISOString(),
370
- event: 'recall',
371
- recall_id: recallId,
372
- source,
373
- source_id: String(sourceId),
374
- task_id: String(taskId),
375
- phase: Number.isFinite(input.phase) ? input.phase : null,
376
- action_type: null,
377
- reason: input.reason ? String(input.reason) : null,
378
- conflict_with: null,
379
- priority_rank: prio ? prio.rank : null,
380
- priority_tier: prio ? prio.tier : null,
381
- conflict_mode: prio ? prio.mode : null,
382
- recalled_from_ids: [],
383
- };
384
-
385
- const write = appendEntry(entry);
386
- if (!write.ok) return write;
387
- return { ok: true, recall_id: recallId, line: entry };
388
- }
389
-
390
- // ═══════════════════════════════════════
391
- // recordActedOn — module entry point
392
- // ═══════════════════════════════════════
393
-
394
- /**
395
- * Record a "recalled memory influenced a concrete decision" event.
396
- *
397
- * Required: { source, source_id, task_id, action_type, reason }
398
- * Optional: recalled_from_ids (array of recall_ids from prior recordRecall calls
399
- * that directly led to this decision — enables linking recall→action)
400
- * reason must be >= MIN_REASON_CHARS to filter out rubber-stamping.
401
- *
402
- * Side effects:
403
- * - Appends to memory_action_log.jsonl
404
- * - If source=lesson_guard and source_id matches /^L-\d+$/, also calls
405
- * lesson-guard.js recordActedOn() so the legacy per-guard counter and
406
- * lesson_guard_acted_on.jsonl stay in sync.
407
- * - If source=rune, the Synapse rune_usage_log acted_on bit should be
408
- * set by the caller via synapse_inscribe related_rune_ids. We do NOT
409
- * attempt a direct HTTP call here because the MCP layer is outside
410
- * this tool's scope — we rely on the caller having already done it
411
- * when they inscribed the follow-up rune. If they have not, our log
412
- * still captures the event for reporting.
413
- */
414
- function recordActedOn(input) {
415
- if (!input || typeof input !== 'object') {
416
- return { ok: false, error: 'input must be an object' };
417
- }
418
- const { source, source_id: sourceId, task_id: taskId, action_type: actionType, reason } = input;
419
-
420
- if (!VALID_SOURCES.has(source)) {
421
- return { ok: false, error: `invalid source '${source}'` };
422
- }
423
- if (!sourceId) return { ok: false, error: 'source_id is required' };
424
- if (!taskId) return { ok: false, error: 'task_id is required' };
425
- if (!VALID_ACTION_TYPES.has(actionType)) {
426
- return { ok: false, error: `invalid action_type '${actionType}' (expected one of: ${[...VALID_ACTION_TYPES].join(', ')})` };
427
- }
428
- if (!reason || String(reason).trim().length < MIN_REASON_CHARS) {
429
- return { ok: false, error: `reason must be >= ${MIN_REASON_CHARS} chars (got ${reason ? String(reason).length : 0})` };
430
- }
431
-
432
- const prio = getPriorityRank({
433
- source,
434
- rune_type: input.rune_type || null,
435
- confidence: input.confidence || null,
436
- verified: input.verified || null,
437
- });
438
-
439
- // recalled_from_ids: array of recall_id strings that led to this decision
440
- const recalledFromIds = Array.isArray(input.recalled_from_ids)
441
- ? input.recalled_from_ids.filter(id => typeof id === 'string' && id.startsWith('recall_'))
442
- : [];
443
-
444
- const entry = {
445
- timestamp: new Date().toISOString(),
446
- event: 'acted_on',
447
- recall_id: generateRecallId(), // stable ID for this acted_on event too
448
- source,
449
- source_id: String(sourceId),
450
- task_id: String(taskId),
451
- phase: Number.isFinite(input.phase) ? input.phase : null,
452
- action_type: actionType,
453
- reason: String(reason),
454
- conflict_with: input.conflict_with || null,
455
- priority_rank: prio ? prio.rank : null,
456
- priority_tier: prio ? prio.tier : null,
457
- conflict_mode: prio ? prio.mode : null,
458
- recalled_from_ids: recalledFromIds,
459
- };
460
-
461
- const write = appendEntry(entry);
462
- if (!write.ok) return write;
463
-
464
- // Bridge to lesson-guard.js for L-XXX guards so the two logs agree.
465
- if (source === 'lesson_guard' && /^L-\d+$/.test(String(sourceId))) {
466
- try {
467
- const lg = require(LESSON_GUARD_JS);
468
- if (lg && typeof lg.recordActedOn === 'function') {
469
- lg.recordActedOn(String(sourceId), String(taskId), String(reason));
470
- }
471
- } catch { /* graceful degradation */ }
472
- }
473
-
474
- return { ok: true, line: entry };
475
- }
476
-
477
- /**
478
- * Batch helper used by phase-gate.js exit. Accepts an array of items the
479
- * specialist reports as having influenced the current phase's decisions,
480
- * records them all, and returns per-item status.
481
- *
482
- * Input shape:
483
- * items = [{ source, source_id, action_type, reason, phase?, rune_type?,
484
- * recalled_from_ids?: string[] }]
485
- * taskId = string
486
- * phase = int (default applied to items lacking phase)
487
- */
488
- function recordActedOnBatch(items, taskId, phase) {
489
- const result = { recorded: 0, failed: [] };
490
- if (!Array.isArray(items) || items.length === 0) return result;
491
- for (const raw of items) {
492
- if (!raw || typeof raw !== 'object') {
493
- result.failed.push({ item: raw, error: 'not an object' });
494
- continue;
495
- }
496
- const item = Object.assign({ task_id: taskId, phase }, raw);
497
- const r = recordActedOn(item);
498
- if (r.ok) result.recorded += 1;
499
- else result.failed.push({ item, error: r.error });
500
- }
501
- return result;
502
- }
503
-
504
- // ═══════════════════════════════════════
505
- // listRecalls — session recall registry (TASK_1078)
506
- // ═══════════════════════════════════════
507
-
508
- /**
509
- * Return all recall events for a given task_id, optionally filtered by phase.
510
- * Useful for an agent to enumerate recall_ids that can be passed into
511
- * recordActedOn calls when linking decisions back to the recalls that shaped them.
512
- *
513
- * Returns array of recall entries (event='recall') sorted by timestamp ascending.
514
- * Graceful on missing log (returns empty array).
515
- */
516
- function listRecalls(taskId, phase) {
517
- if (!taskId) return [];
518
- if (!fs.existsSync(LOG_PATH)) return [];
519
-
520
- let raw;
521
- try { raw = fs.readFileSync(LOG_PATH, 'utf8'); }
522
- catch { return []; }
523
-
524
- const results = [];
525
- for (const line of raw.split('\n')) {
526
- if (!line.trim()) continue;
527
- let entry;
528
- try { entry = JSON.parse(line); } catch { continue; }
529
- if (!entry || entry.event !== 'recall') continue;
530
- if (entry.task_id !== String(taskId)) continue;
531
- if (phase !== undefined && entry.phase !== phase) continue;
532
- results.push(entry);
533
- }
534
- return results;
535
- }
536
-
537
- // ═══════════════════════════════════════
538
- // Metrics / report
539
- // ═══════════════════════════════════════
540
-
541
- /**
542
- * Stream the jsonl log and aggregate recall-vs-impact counters.
543
- * Graceful on missing log (returns zeroed report).
544
- *
545
- * opts = { windowDays?: number, taskId?: string }
546
- * returns:
547
- * {
548
- * window_days, task_id, total_recalls, total_acted_on,
549
- * impact_rate, weighted_impact_score,
550
- * linked_acts, // acted_on events with non-empty recalled_from_ids
551
- * link_rate, // linked_acts / total_acted_on (traceability)
552
- * by_source: {
553
- * <source>: { recalls, acted_on, impact_rate, weighted_score, linked_acts }
554
- * },
555
- * by_action_type: { <action>: count },
556
- * unique_items_recalled: N,
557
- * unique_items_acted_on: N
558
- * }
559
- */
560
- function getMetrics(opts = {}) {
561
- const windowDays = Number.isFinite(opts.windowDays) ? opts.windowDays : 30;
562
- const taskFilter = opts.taskId || null;
563
- const cutoff = Date.now() - windowDays * 86400 * 1000;
564
-
565
- const report = {
566
- window_days: windowDays,
567
- task_id: taskFilter,
568
- total_recalls: 0,
569
- total_acted_on: 0,
570
- impact_rate: 0,
571
- weighted_impact_score: 0,
572
- linked_acts: 0,
573
- link_rate: 0,
574
- by_source: {},
575
- by_action_type: {},
576
- unique_items_recalled: 0,
577
- unique_items_acted_on: 0,
578
- };
579
-
580
- if (!fs.existsSync(LOG_PATH)) return report;
581
-
582
- const rules = loadPriorityRules();
583
- const weights = rules.acted_on_weights || {};
584
- const recalledSet = new Set();
585
- const actedSet = new Set();
586
-
587
- let raw;
588
- try { raw = fs.readFileSync(LOG_PATH, 'utf8'); }
589
- catch { return report; }
590
-
591
- for (const line of raw.split('\n')) {
592
- if (!line.trim()) continue;
593
- let entry;
594
- try { entry = JSON.parse(line); } catch { continue; }
595
- if (!entry || !entry.source) continue;
596
-
597
- const ts = Date.parse(entry.timestamp || '');
598
- if (!Number.isFinite(ts) || ts < cutoff) continue;
599
- if (taskFilter && entry.task_id !== taskFilter) continue;
600
-
601
- const srcBucket = report.by_source[entry.source] || {
602
- recalls: 0, acted_on: 0, impact_rate: 0, weighted_score: 0, linked_acts: 0,
603
- };
604
- const key = `${entry.source}:${entry.source_id}`;
605
-
606
- if (entry.event === 'recall') {
607
- report.total_recalls += 1;
608
- srcBucket.recalls += 1;
609
- recalledSet.add(key);
610
- } else if (entry.event === 'acted_on') {
611
- report.total_acted_on += 1;
612
- srcBucket.acted_on += 1;
613
- actedSet.add(key);
614
- if (entry.action_type) {
615
- report.by_action_type[entry.action_type] = (report.by_action_type[entry.action_type] || 0) + 1;
616
- }
617
- // linked_acts: this acted_on was explicitly traced to prior recall events
618
- if (Array.isArray(entry.recalled_from_ids) && entry.recalled_from_ids.length > 0) {
619
- report.linked_acts += 1;
620
- srcBucket.linked_acts += 1;
621
- }
622
- // Weighted score: use the tier weight if available, else source weight, else 1.0.
623
- const tierWeight = entry.priority_tier && weights[entry.priority_tier];
624
- const srcWeight = weights[entry.source];
625
- const w = Number.isFinite(tierWeight) ? tierWeight
626
- : Number.isFinite(srcWeight) ? srcWeight
627
- : 1.0;
628
- report.weighted_impact_score += w;
629
- srcBucket.weighted_score += w;
630
- }
631
-
632
- report.by_source[entry.source] = srcBucket;
633
- }
634
-
635
- report.unique_items_recalled = recalledSet.size;
636
- report.unique_items_acted_on = actedSet.size;
637
- report.impact_rate = report.total_recalls > 0
638
- ? +(report.total_acted_on / report.total_recalls).toFixed(4)
639
- : 0;
640
- report.link_rate = report.total_acted_on > 0
641
- ? +(report.linked_acts / report.total_acted_on).toFixed(4)
642
- : 0;
643
-
644
- for (const src of Object.keys(report.by_source)) {
645
- const b = report.by_source[src];
646
- b.impact_rate = b.recalls > 0 ? +(b.acted_on / b.recalls).toFixed(4) : 0;
647
- b.weighted_score = +b.weighted_score.toFixed(3);
648
- }
649
- report.weighted_impact_score = +report.weighted_impact_score.toFixed(3);
650
-
651
- return report;
652
- }
653
-
654
- // ═══════════════════════════════════════
655
- // CLI
656
- // ═══════════════════════════════════════
657
-
658
- function parseArgs(argv) {
659
- const args = { _: [] };
660
- for (const arg of argv.slice(2)) {
661
- const m = arg.match(/^--([^=]+)(?:=(.*))?$/);
662
- if (m) args[m[1]] = m[2] !== undefined ? m[2] : true;
663
- else args._.push(arg);
664
- }
665
- return args;
666
- }
667
-
668
- function cmdRecordRecall(args) {
669
- const entry = {
670
- source: args.source,
671
- source_id: args['source-id'],
672
- task_id: args.task,
673
- phase: args.phase !== undefined ? parseInt(args.phase, 10) : null,
674
- reason: args.reason || null,
675
- rune_type: args['rune-type'] || null,
676
- confidence: args.confidence !== undefined ? parseFloat(args.confidence) : null,
677
- verified: args.verified || null,
678
- };
679
- const r = recordRecall(entry);
680
- if (!r.ok) { console.error(JSON.stringify(r)); process.exit(1); }
681
- console.log(JSON.stringify(r, null, 2));
682
- }
683
-
684
- function cmdRecordActed(args) {
685
- // --recalled-from=id1,id2,id3 → array
686
- const recalledFrom = args['recalled-from']
687
- ? String(args['recalled-from']).split(',').map(s => s.trim()).filter(Boolean)
688
- : [];
689
-
690
- const entry = {
691
- source: args.source,
692
- source_id: args['source-id'],
693
- task_id: args.task,
694
- phase: args.phase !== undefined ? parseInt(args.phase, 10) : null,
695
- action_type: args['action-type'],
696
- reason: args.reason || '',
697
- rune_type: args['rune-type'] || null,
698
- confidence: args.confidence !== undefined ? parseFloat(args.confidence) : null,
699
- conflict_with: args['conflict-with'] || null,
700
- recalled_from_ids: recalledFrom,
701
- };
702
- const r = recordActedOn(entry);
703
- if (!r.ok) { console.error(JSON.stringify(r)); process.exit(1); }
704
- console.log(JSON.stringify(r, null, 2));
705
- }
706
-
707
- function cmdReport(args) {
708
- const windowDays = args['window-days'] !== undefined ? parseInt(args['window-days'], 10) : 30;
709
- const taskId = args.task || null;
710
- const report = getMetrics({ windowDays, taskId });
711
- if (args.json) {
712
- console.log(JSON.stringify(report, null, 2));
713
- return;
714
- }
715
- // Plain report
716
- const lines = [];
717
- lines.push(`Memory-to-Action report (window: ${windowDays}d${taskId ? `, task: ${taskId}` : ''})`);
718
- lines.push('');
719
- lines.push(` total_recalls: ${report.total_recalls}`);
720
- lines.push(` total_acted_on: ${report.total_acted_on}`);
721
- lines.push(` impact_rate: ${(report.impact_rate * 100).toFixed(1)}%`);
722
- lines.push(` weighted_impact_score: ${report.weighted_impact_score}`);
723
- lines.push(` linked_acts: ${report.linked_acts} (acted_on events with explicit recall linkage)`);
724
- lines.push(` link_rate: ${(report.link_rate * 100).toFixed(1)}% (traceability: linked_acts / total_acted_on)`);
725
- lines.push(` unique_items_recalled: ${report.unique_items_recalled}`);
726
- lines.push(` unique_items_acted_on: ${report.unique_items_acted_on}`);
727
- lines.push('');
728
- lines.push(' by source:');
729
- const sources = Object.keys(report.by_source).sort();
730
- if (sources.length === 0) lines.push(' (none)');
731
- for (const src of sources) {
732
- const b = report.by_source[src];
733
- lines.push(` ${src.padEnd(14)} recalls=${String(b.recalls).padStart(4)} acted_on=${String(b.acted_on).padStart(4)} rate=${(b.impact_rate * 100).toFixed(1).padStart(5)}% linked=${b.linked_acts} weight=${b.weighted_score}`);
734
- }
735
- lines.push('');
736
- lines.push(' by action_type:');
737
- const types = Object.keys(report.by_action_type).sort();
738
- if (types.length === 0) lines.push(' (none)');
739
- for (const t of types) lines.push(` ${t.padEnd(22)} ${report.by_action_type[t]}`);
740
- console.log(lines.join('\n'));
741
- }
742
-
743
- function cmdPriority(args) {
744
- const rank = getPriorityRank({
745
- source: args['source-type'] || args.source,
746
- rune_type: args.type || args['rune-type'],
747
- confidence: args.confidence !== undefined ? parseFloat(args.confidence) : null,
748
- verified: args.verified || null,
749
- });
750
- console.log(JSON.stringify({ ok: true, ...(rank || { rank: null, tier: null, mode: null }) }, null, 2));
751
- }
752
-
753
- function cmdListRecalls(args) {
754
- const taskId = args.task;
755
- const phase = args.phase !== undefined ? parseInt(args.phase, 10) : undefined;
756
- if (!taskId) { console.error(JSON.stringify({ error: '--task is required' })); process.exit(1); }
757
- const recalls = listRecalls(taskId, phase);
758
- if (args.json) {
759
- console.log(JSON.stringify(recalls, null, 2));
760
- return;
761
- }
762
- if (recalls.length === 0) {
763
- console.log(`No recall events found for task=${taskId}${phase !== undefined ? `, phase=${phase}` : ''}`);
764
- return;
765
- }
766
- console.log(`Recall events for task=${taskId}${phase !== undefined ? `, phase=${phase}` : ''} (${recalls.length} total):`);
767
- for (const r of recalls) {
768
- const phaseStr = r.phase !== null ? ` phase=${r.phase}` : '';
769
- console.log(` ${r.recall_id} source=${r.source}:${r.source_id}${phaseStr}`);
770
- }
771
- }
772
-
773
- // ═══════════════════════════════════════
774
- // Module exports
775
- // ═══════════════════════════════════════
776
-
777
- module.exports = {
778
- recordRecall,
779
- recordActedOn,
780
- recordActedOnBatch,
781
- getMetrics,
782
- listRecalls,
783
- getPriorityRank,
784
- loadPriorityRules,
785
- // internal paths exported for tests
786
- _LOG_PATH: LOG_PATH,
787
- _PRIORITY_PATH: PRIORITY_PATH,
788
- _VALID_SOURCES: VALID_SOURCES,
789
- _VALID_ACTION_TYPES: VALID_ACTION_TYPES,
790
- _MIN_REASON_CHARS: MIN_REASON_CHARS,
791
- };
792
-
793
- // ═══════════════════════════════════════
794
- // CLI entry point
795
- // ═══════════════════════════════════════
796
-
797
- if (require.main === module) {
798
- const args = parseArgs(process.argv);
799
- const cmd = args._[0];
800
- switch (cmd) {
801
- case 'record-recall': cmdRecordRecall(args); break;
802
- case 'record-acted': cmdRecordActed(args); break;
803
- case 'report': cmdReport(args); break;
804
- case 'priority': cmdPriority(args); break;
805
- case 'list-recalls': cmdListRecalls(args); break;
806
- default:
807
- console.error([
808
- 'Usage: memory-action-log.js <command> [options]',
809
- '',
810
- 'Commands:',
811
- ' record-recall --source=<src> --source-id=<id> --task=TASK_XXX',
812
- ' [--phase=N] [--reason="text"]',
813
- ' [--rune-type=<type>] [--confidence=0.85] [--verified=<state>]',
814
- ' Returns: { ok, recall_id } — save recall_id for later linkage',
815
- '',
816
- ' record-acted --source=<src> --source-id=<id> --task=TASK_XXX',
817
- ' --action-type=<type> --reason="why this recall changed the plan (>=20 chars)"',
818
- ' [--phase=N] [--conflict-with=<other_source_id>]',
819
- ' [--recalled-from=recall_id1,recall_id2,...]',
820
- '',
821
- ' list-recalls --task=TASK_XXX [--phase=N] [--json]',
822
- ' List all recall events for a task (shows recall_ids for linkage)',
823
- '',
824
- ' report [--window-days=30] [--task=TASK_XXX] [--json]',
825
- '',
826
- ' priority --source-type=<src> [--type=<rune_type>] [--confidence=N] [--verified=<state>]',
827
- '',
828
- 'Sources:',
829
- ` ${[...VALID_SOURCES].join(', ')}`,
830
- '',
831
- 'Action types:',
832
- ` ${[...VALID_ACTION_TYPES].join(', ')}`,
833
- '',
834
- 'Contract: .kenaz/specialist/memory_source_priority.yaml',
835
- 'Log: .kenaz/specialist/memory_action_log.jsonl',
836
- ].join('\n'));
837
- process.exit(1);
838
- }
839
- }