kenaz 0.3.18 → 0.3.19

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 (308) 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__/m1-hmap-convid.test.js +1 -124
  30. package/core/.formation/tools/dispatch.js +1 -1
  31. package/core/.formation/tools/loader.js +17 -140
  32. package/core/.formation/tools/post-complete.js +0 -31
  33. package/core/.rule/INDEX.yaml +0 -18
  34. package/core/.rule/general/agent-workflow.yaml +14 -164
  35. package/core/.rule/general/project-self-containment.yaml +8 -19
  36. package/core/.rule/general/rust-verify-target.yaml +6 -5
  37. package/core/.rule/general/synapse-code-intelligence.yaml +3 -3
  38. package/core/.specialist/Amelia/capability.yaml +12 -12
  39. package/core/.specialist/Amelia/workflow.yaml +2 -2
  40. package/core/.specialist/Ansuz/SYSTEM_DOC.md +20 -578
  41. package/core/.specialist/Ansuz/cognitive_principles.yaml +2 -3
  42. package/core/.specialist/Ansuz/concepts/boot_rules_summary.yaml +0 -1
  43. package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +1 -2
  44. package/core/.specialist/Ansuz/core.yaml +6 -108
  45. package/core/.specialist/Ansuz/guards/lesson_guards.yaml +0 -3
  46. package/core/.specialist/Ansuz/identity_contract.yaml +0 -1
  47. package/core/.specialist/Ansuz/knowledge.yaml +0 -4
  48. package/core/.specialist/Ansuz/rule.yaml +0 -1
  49. package/core/.specialist/Ansuz/tools/handoff-session-identity.test.js +6 -150
  50. package/core/.specialist/tests/runtime-discovery/README.md +1 -1
  51. package/core/.specialist/tools/__tests__/task-2374-usage-counter.test.js +1 -12
  52. package/core/.specialist/tools/prompt-files.test.js +1 -0
  53. package/core/.specialist/tools/task-bridge.js +1 -66
  54. package/core/.system/README.md +11 -552
  55. package/core/.system/collaboration/README.md +10 -174
  56. package/core/CLAUDE.md +1 -6
  57. package/core/KENAZ_CORE_VERSION +1 -1
  58. package/core/dev/scripts/js-yaml-resolution.test.js +1 -1
  59. package/core/dev/scripts/run-rust-tests.js +9 -45
  60. package/core/dev/scripts/rust-verify-worktree.js +4 -4
  61. package/core/dev/scripts/verify-target-dir.js +6 -6
  62. package/core/plugins/kenaz/INVENTORY.md +8 -8
  63. package/core/plugins/kenaz/commands/architect.md +1 -2
  64. package/core/plugins/kenaz/commands/spec.md +1 -1
  65. package/core/plugins/kenaz/commands//347/224/242/345/223/201.md +1 -1
  66. package/core/plugins/kenaz/guidance.md +1 -1
  67. package/core/plugins/kenaz/scripts/sync.js +1 -1
  68. package/core/plugins/kenaz/skills/architect_agent/SKILL.md +11 -92
  69. package/core/plugins/kenaz/skills/assessment_agent/SKILL.md +0 -1
  70. package/core/plugins/kenaz/skills/backend_engineer/SKILL.md +3 -6
  71. package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +1 -1
  72. package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +1 -1
  73. package/core/plugins/kenaz/skills/designer/SKILL.md +3 -6
  74. package/core/plugins/kenaz/skills/dev_agent/SKILL.md +30 -491
  75. package/core/plugins/kenaz/skills/developer/SKILL.md +3 -6
  76. package/core/plugins/kenaz/skills/feature_agent/SKILL.md +3 -17
  77. package/core/plugins/kenaz/skills/product_manager/SKILL.md +29 -1064
  78. package/core/plugins/kenaz/skills/qa_engineer/SKILL.md +12 -17
  79. package/core/plugins/kenaz/skills/spec_optimizer/SKILL.md +25 -275
  80. package/core/plugins/kenaz/skills/sre_agent/SKILL.md +3 -6
  81. package/package.json +6 -6
  82. package/core/.claude/commands/archive.md +0 -24
  83. package/core/.claude/commands/init.md +0 -112
  84. package/core/.claude/commands/legacy_dev.md +0 -18
  85. package/core/.claude/commands/product-review.md +0 -131
  86. package/core/.claude/commands/strategy.md +0 -141
  87. package/core/.claude/commands/sunny.md +0 -1
  88. package/core/.claude/commands//347/224/242/345/223/201/346/252/242/350/246/226.md +0 -129
  89. package/core/.claude/commands//351/200/262/345/272/246.md +0 -15
  90. package/core/.claude/hooks/__tests__/substrate.test.js +0 -122
  91. package/core/.claude/hooks/_substrate-paths.js +0 -95
  92. package/core/.claude/hooks/context-check.js +0 -51
  93. package/core/.claude/hooks/fatigue-check.js +0 -52
  94. package/core/.claude/hooks/handoff-snapshot-on-commit.js +0 -154
  95. package/core/.claude/hooks/handoff-snapshot-on-question.js +0 -158
  96. package/core/.claude/hooks/handoff-staleness.js +0 -197
  97. package/core/.claude/hooks/jev-thalamus-context.js +0 -17
  98. package/core/.claude/hooks/jev-thalamus-context.test.js +0 -41
  99. package/core/.claude/hooks/lease-release.js +0 -119
  100. package/core/.claude/hooks/lease-touch.js +0 -96
  101. package/core/.claude/hooks/reflex-feedback.js +0 -5
  102. package/core/.claude/hooks/session-init.ps1 +0 -49
  103. package/core/.claude/hooks/sound-event.js +0 -70
  104. package/core/.claude/hooks/sound-event.sh +0 -37
  105. package/core/.claude/hooks/stream-delta.js +0 -169
  106. package/core/.claude/no-hooks.settings.json +0 -3
  107. package/core/.claude/skills/legacy_dev/SKILL.md +0 -202
  108. package/core/.claude/skills/sunny/SKILL.md +0 -74
  109. package/core/.collaboration/cli/commands/assign.js +0 -39
  110. package/core/.collaboration/cli/commands/claim.js +0 -33
  111. package/core/.collaboration/cli/commands/complete.js +0 -44
  112. package/core/.collaboration/cli/commands/create.js +0 -110
  113. package/core/.collaboration/cli/commands/get.js +0 -83
  114. package/core/.collaboration/cli/commands/index.js +0 -52
  115. package/core/.collaboration/cli/commands/list.js +0 -51
  116. package/core/.collaboration/cli/commands/lock.js +0 -45
  117. package/core/.collaboration/cli/commands/rebuild.js +0 -39
  118. package/core/.collaboration/cli/commands/search.js +0 -61
  119. package/core/.collaboration/cli/commands/stats.js +0 -61
  120. package/core/.collaboration/cli/commands/transfer.js +0 -41
  121. package/core/.collaboration/cli/commands/unlock.js +0 -48
  122. package/core/.collaboration/cli/formatters/help-formatter.js +0 -70
  123. package/core/.collaboration/cli/formatters/index.js +0 -7
  124. package/core/.collaboration/cli/task-cli.js +0 -46
  125. package/core/.collaboration/cli/utils/arg-parser.js +0 -74
  126. package/core/.collaboration/cli/utils/git-user.js +0 -25
  127. package/core/.collaboration/cli/utils/index.js +0 -9
  128. package/core/.collaboration/cli/utils/output.js +0 -10
  129. package/core/.collaboration/config/default.yaml +0 -189
  130. package/core/.collaboration/core/conflict-resolver.js +0 -461
  131. package/core/.collaboration/core/conflict-resolver.test.js +0 -389
  132. package/core/.collaboration/core/migrate-legacy.js +0 -260
  133. package/core/.collaboration/docs/API.md +0 -787
  134. package/core/.collaboration/docs/ARCHITECTURE.md +0 -596
  135. package/core/.collaboration/docs/FAQ.md +0 -695
  136. package/core/.collaboration/docs/MIGRATION.md +0 -804
  137. package/core/.collaboration/docs/examples/README.md +0 -320
  138. package/core/.collaboration/docs/examples/advanced-queries.js +0 -261
  139. package/core/.collaboration/docs/examples/basic-usage.js +0 -184
  140. package/core/.collaboration/docs/examples/team-workflow.js +0 -352
  141. package/core/.failure/agent-execution/FAIL-001-agent-no-output.yaml +0 -391
  142. package/core/.failure/agent-execution/FAIL-007-pm-lead-wrong-team-invocation.yaml +0 -0
  143. package/core/.failure/agent-execution/FAIL-010-phase0-poem-format-skipped.yaml +0 -28
  144. package/core/.failure/agent-execution/FAIL-010-skipping-cognitive-checkpoint-after-verification.yaml +0 -59
  145. package/core/.failure/agent-execution/FAIL-012-grep-shortcut-during-investigation.yaml +0 -85
  146. package/core/.failure/architecture/FAIL-006-python-version-compatibility.yaml +0 -445
  147. package/core/.failure/pipeline/FAIL-003-queue-pollution.yaml +0 -335
  148. package/core/.failure/pipeline/README.md +0 -95
  149. package/core/.failure/retired/FAIL-009-skipping-synapse-pulse-before-code-change.yaml +0 -49
  150. package/core/.failure/tools/USAGE_GUIDE.md +0 -323
  151. package/core/.failure/tools/check_failures.ps1 +0 -387
  152. package/core/.failure/tools/log_failure.ps1 +0 -474
  153. package/core/.formation/.episode-input.json +0 -10
  154. package/core/.formation/.merge-lock +0 -1
  155. package/core/.formation/lead.yaml +0 -118
  156. package/core/.formation/legacy.bak/designer.yaml +0 -57
  157. package/core/.formation/legacy.bak/pm.yaml +0 -57
  158. package/core/.formation/legacy.bak/qa.yaml +0 -57
  159. package/core/.formation/legacy.bak/review.yaml +0 -62
  160. package/core/.formation/legacy.bak/scheduler.yaml +0 -129
  161. package/core/.formation/legacy.bak/sre.yaml +0 -57
  162. package/core/.formation/role-map.yaml +0 -274
  163. package/core/.formation/roles/_shared/modules/code_review.yaml +0 -98
  164. package/core/.formation/roles/_shared/modules/security.yaml +0 -103
  165. package/core/.formation/roles/_shared/modules/testing.yaml +0 -14
  166. package/core/.formation/roles/backend/role.yaml +0 -93
  167. package/core/.formation/roles/code_reviewer/role.yaml +0 -102
  168. package/core/.formation/roles/database_optimizer/role.yaml +0 -107
  169. package/core/.formation/roles/designer/role.yaml +0 -66
  170. package/core/.formation/roles/developer/role.yaml +0 -75
  171. package/core/.formation/roles/frontend/role.yaml +0 -134
  172. package/core/.formation/roles/incident_response/role.yaml +0 -89
  173. package/core/.formation/roles/qa/role.yaml +0 -89
  174. package/core/.formation/roles/security/role.yaml +0 -87
  175. package/core/.formation/roles/sre/role.yaml +0 -66
  176. package/core/.formation/skills/coordinator.yaml +0 -52
  177. package/core/.formation/solo/diagnosis.yaml +0 -78
  178. package/core/.formation/solo/strategy.yaml +0 -121
  179. package/core/.formation/tests/hello.txt +0 -2
  180. package/core/.formation/tools/coordinator.js +0 -350
  181. package/core/.formation/tools/jev-correction.js +0 -214
  182. package/core/.formation/tools/jev-correction.test.js +0 -178
  183. package/core/.formation/tools/log-reader.js +0 -232
  184. package/core/.formation/tools/revert-daemon-state.js +0 -162
  185. package/core/.formation/tools/test-drift.js +0 -148
  186. package/core/.formation/unit/heimdall.yaml +0 -96
  187. package/core/.formation/unit/plan.yaml +0 -67
  188. package/core/.formation/unit/think.yaml +0 -58
  189. package/core/.kenaz/MUNINN_SNAPSHOT9_EPISODE.json +0 -32
  190. package/core/.kenaz/MUNINN_SNAPSHOT9_RUNE.json +0 -23
  191. package/core/.kenaz/MUNINN_SNAPSHOT9_SUMMARY.md +0 -89
  192. package/core/.kenaz/ash_TASK_1428-20260507.json +0 -14
  193. package/core/.kenaz/muninn_episode_write.ps1 +0 -17
  194. package/core/.kenaz/muninn_snapshot11_rune_candidates.md +0 -63
  195. package/core/.kenaz/muninn_snapshot1_rune_candidates.md +0 -49
  196. package/core/.kenaz/runes_TASK_1428.json +0 -57
  197. package/core/.kenaz/tmp_heimdall_proposal_8d3e392.json +0 -13
  198. package/core/.kenaz/tmp_props.json +0 -1
  199. package/core/.kenaz/tmp_sounddrop_t1_prompt.txt +0 -371
  200. package/core/.rule/MECHANIZED.md +0 -61
  201. package/core/.rule/general/cortex-memory-recall.yaml +0 -446
  202. package/core/.rule/general/pipeline.yaml +0 -246
  203. package/core/.rule/general/sunny-access-control.yaml +0 -60
  204. package/core/.rule/general/system-invariants.yaml +0 -231
  205. package/core/.rule/pipeline/PPL-002-completed-task-archival.yaml +0 -528
  206. package/core/.rule/pipeline/auto-update.yaml +0 -282
  207. package/core/.rule/pipeline/task-execution.yaml +0 -365
  208. package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +0 -65
  209. package/core/.specialist/Ansuz/experiments/boot_ablation_replay.js +0 -197
  210. package/core/.specialist/Ansuz/tools/bootstrap-anchors.js +0 -289
  211. package/core/.specialist/Ansuz/workflow.yaml +0 -1225
  212. package/core/.specialist/experiments/rg_benchmark.js +0 -523
  213. package/core/.specialist/experiments/rg_benchmark_results.json +0 -27
  214. package/core/.specialist/tools/jev-optional-context.js +0 -107
  215. package/core/.specialist/tools/jev-optional-context.test.js +0 -152
  216. package/core/.specialist/tools/memory-action-log.js +0 -839
  217. package/core/.specialist/tools/phase-gate.js +0 -3534
  218. package/core/.specialist/tools/phase-gate.test.js +0 -85
  219. package/core/.specialist/tools/precompute-runner.js +0 -444
  220. package/core/.specialist/tools/rule-engine.js +0 -621
  221. package/core/.specialist/tools/task-bridge-jev.test.js +0 -170
  222. package/core/.synapse/memory-shared.json.sync +0 -1
  223. package/core/.system/agent-skills/README.md +0 -724
  224. package/core/.system/cli-tools/README.md +0 -724
  225. package/core/.system/cortex/MODULES.md +0 -1046
  226. package/core/.system/dashboard/FRONTEND.md +0 -812
  227. package/core/.system/dashboard/README.md +0 -737
  228. package/core/.system/failure/README.md +0 -723
  229. package/core/.system/formation/LEGACY_SKILLS.md +0 -974
  230. package/core/.system/formation/POST_COMPLETE.md +0 -602
  231. package/core/.system/formation/README.md +0 -1094
  232. package/core/.system/formation/TOOLS_REFERENCE.md +0 -1145
  233. package/core/.system/kenaz/README.md +0 -787
  234. package/core/.system/memory/README.md +0 -719
  235. package/core/.system/pipeline/README.md +0 -712
  236. package/core/.system/rule/README.md +0 -794
  237. package/core/.system/sunny/README.md +0 -476
  238. package/core/.system/sunny/RULES.json +0 -89
  239. package/core/.system/sunny/knowledge/INDEX.md +0 -37
  240. package/core/.system/sunny/knowledge/agent-teams-api.md +0 -0
  241. package/core/.system/sunny/knowledge/ansuz-specialist.md +0 -500
  242. package/core/.system/sunny/knowledge/claude-cli-spawn.md +0 -177
  243. package/core/.system/sunny/knowledge/collaboration-api.md +0 -925
  244. package/core/.system/sunny/teammates/README.md +0 -107
  245. package/core/.system/sunny/teammates/how_to_use.yaml +0 -101
  246. package/core/.system/sunny/teammates/tools/headless/launcher.js +0 -318
  247. package/core/.system/sunny/teammates/tools/powershell/launcher.js +0 -241
  248. package/core/.system/sunny/teammates/tools/powershell/start.bat +0 -50
  249. package/core/.system/sunny/teammates/tools/utils/env.js +0 -96
  250. package/core/.system/sunny/teammates/tools/utils/format-output.js +0 -329
  251. package/core/.system/sunny/tools.json +0 -101
  252. package/core/dev/scripts/cleanup-queue.js +0 -184
  253. package/core/dev/scripts/cleanup_queue.js +0 -86
  254. package/core/dev/scripts/legacy/README.md +0 -39
  255. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +0 -536
  256. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +0 -1978
  257. package/core/dev/scripts/matrix/CLI_README.md +0 -214
  258. package/core/dev/scripts/matrix/README.md +0 -310
  259. package/core/dev/scripts/matrix/package-lock.json +0 -4266
  260. package/core/dev/scripts/matrix/package.json +0 -55
  261. package/core/dev/scripts/matrix/src/abbreviations.ts +0 -295
  262. package/core/dev/scripts/matrix/src/cli/commands/convert.ts +0 -211
  263. package/core/dev/scripts/matrix/src/cli/commands/create.ts +0 -95
  264. package/core/dev/scripts/matrix/src/cli/commands/delete.ts +0 -102
  265. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +0 -140
  266. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +0 -173
  267. package/core/dev/scripts/matrix/src/cli/commands/query.ts +0 -261
  268. package/core/dev/scripts/matrix/src/cli/commands/read.ts +0 -77
  269. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +0 -168
  270. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +0 -126
  271. package/core/dev/scripts/matrix/src/cli/commands/update.ts +0 -147
  272. package/core/dev/scripts/matrix/src/cli/commands/validate.ts +0 -275
  273. package/core/dev/scripts/matrix/src/cli/index.ts +0 -350
  274. package/core/dev/scripts/matrix/src/cli/utils/file-handler.ts +0 -72
  275. package/core/dev/scripts/matrix/src/cli/utils/logger.ts +0 -65
  276. package/core/dev/scripts/matrix/src/index.ts +0 -62
  277. package/core/dev/scripts/matrix/src/indexer/tag-indexer.ts +0 -165
  278. package/core/dev/scripts/matrix/src/parser.ts +0 -281
  279. package/core/dev/scripts/matrix/src/serializer.ts +0 -151
  280. package/core/dev/scripts/matrix/src/validator.ts +0 -301
  281. package/core/dev/scripts/matrix/tests/abbreviations.test.ts +0 -122
  282. package/core/dev/scripts/matrix/tests/cli.test.ts +0 -177
  283. package/core/dev/scripts/matrix/tests/convert.test.ts +0 -143
  284. package/core/dev/scripts/matrix/tests/parser.test.ts +0 -203
  285. package/core/dev/scripts/matrix/tests/query.test.ts +0 -85
  286. package/core/dev/scripts/matrix/tests/serializer.test.ts +0 -197
  287. package/core/dev/scripts/matrix/tests/validate.test.ts +0 -141
  288. package/core/dev/scripts/matrix/tests/validator.test.ts +0 -167
  289. package/core/dev/scripts/matrix/tsconfig.json +0 -20
  290. package/core/dev/scripts/matrix/types/index.d.ts +0 -111
  291. package/core/dev/scripts/matrix/types/index.d.ts.map +0 -1
  292. package/core/dev/scripts/matrix/types/index.js +0 -8
  293. package/core/dev/scripts/matrix/types/index.js.map +0 -1
  294. package/core/dev/scripts/matrix/types/index.ts +0 -124
  295. package/core/dev/scripts/memory-usage-report.js +0 -152
  296. package/core/dev/scripts/probe/ansuz-scaffold-ablation.js +0 -579
  297. package/core/dev/scripts/probe/ansuz-scaffold-ablation.test.js +0 -244
  298. package/core/dev/scripts/probe/hook-latency-probe.js +0 -315
  299. package/core/dev/scripts/probe/hook-latency-probe.test.js +0 -105
  300. package/core/dev/scripts/probe/prediction-signal-auc.js +0 -483
  301. package/core/dev/scripts/probe/prediction-signal-auc.test.js +0 -200
  302. package/core/dev/scripts/test-pty-ws-tunnel.js +0 -229
  303. package/core/dev/scripts/validate_pipeline.js +0 -197
  304. package/core/dev/spikes/task2145-evidence-20261004/backend-log-excerpt.txt +0 -56
  305. package/core/dev/spikes/task2145-evidence-20261004/graph.shadow +0 -0
  306. package/core/dev/spikes/task2145-evidence-20261004/graph.wal +0 -0
  307. package/core/dev/spikes/task2145-evidence-20261004/graph_corrupt_suspect.marker +0 -1
  308. 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
- }