kenaz 0.3.18 → 0.3.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/core/.claude/commands/agent-worker.md +1 -1
  2. package/core/.claude/commands/architect.md +1 -2
  3. package/core/.claude/commands/spec.md +1 -1
  4. package/core/.claude/commands//347/224/242/345/223/201.md +1 -1
  5. package/core/.claude/hooks/__tests__/task-1977-dedupe-guard.test.js +8 -8
  6. package/core/.claude/skills/architect_agent/SKILL.md +11 -92
  7. package/core/.claude/skills/assessment_agent/SKILL.md +0 -1
  8. package/core/.claude/skills/backend_engineer/SKILL.md +3 -6
  9. package/core/.claude/skills/code_reviewer/SKILL.md +1 -1
  10. package/core/.claude/skills/code_reviewer/patterns.yaml +1 -1
  11. package/core/.claude/skills/designer/SKILL.md +3 -6
  12. package/core/.claude/skills/dev_agent/SKILL.md +30 -491
  13. package/core/.claude/skills/developer/SKILL.md +3 -6
  14. package/core/.claude/skills/feature_agent/SKILL.md +3 -17
  15. package/core/.claude/skills/product_manager/SKILL.md +29 -1064
  16. package/core/.claude/skills/qa_engineer/SKILL.md +12 -17
  17. package/core/.claude/skills/spec_optimizer/SKILL.md +25 -275
  18. package/core/.claude/skills/sre_agent/SKILL.md +3 -6
  19. package/core/.collaboration/README.md +2 -3
  20. package/core/.collaboration/core/task-api.js +5 -138
  21. package/core/.collaboration/docs/CLI.md +0 -2
  22. package/core/.failure/INDEX.yaml +6 -40
  23. package/core/.failure/agent-execution/FAIL-013-search-cwd-drift-and-case-mismatch.yaml +1 -1
  24. package/core/.failure/tools/README.md +9 -227
  25. package/core/.formation/bifrost/launch.js +1 -6
  26. package/core/.formation/bifrost/launch.test.js +1 -1
  27. package/core/.formation/bifrost/shared/ansuz-boot-policy.js +3 -3
  28. package/core/.formation/bifrost/shared/ansuz-boot-policy.test.js +1 -2
  29. package/core/.formation/tools/__tests__/arms.test.js +41 -0
  30. package/core/.formation/tools/__tests__/diagnose-bug.test.js +10 -0
  31. package/core/.formation/tools/__tests__/m1-hmap-convid.test.js +1 -124
  32. package/core/.formation/tools/arms.js +48 -0
  33. package/core/.formation/tools/dispatch.js +24 -15
  34. package/core/.formation/tools/kenaz-stats.js +32 -8
  35. package/core/.formation/tools/kenaz-stats.test.js +15 -2
  36. package/core/.formation/tools/loader.js +17 -140
  37. package/core/.formation/tools/post-complete.js +0 -31
  38. package/core/.formation/tools/prompt.js +16 -1
  39. package/core/.formation/tools/prompt.test.js +20 -0
  40. package/core/.rule/INDEX.yaml +0 -18
  41. package/core/.rule/general/agent-workflow.yaml +14 -164
  42. package/core/.rule/general/project-self-containment.yaml +8 -19
  43. package/core/.rule/general/rust-verify-target.yaml +6 -5
  44. package/core/.rule/general/synapse-code-intelligence.yaml +3 -3
  45. package/core/.specialist/Amelia/capability.yaml +12 -12
  46. package/core/.specialist/Amelia/workflow.yaml +2 -2
  47. package/core/.specialist/Ansuz/SYSTEM_DOC.md +20 -578
  48. package/core/.specialist/Ansuz/cognitive_principles.yaml +2 -3
  49. package/core/.specialist/Ansuz/concepts/boot_rules_summary.yaml +0 -1
  50. package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +1 -2
  51. package/core/.specialist/Ansuz/core.yaml +6 -108
  52. package/core/.specialist/Ansuz/guards/lesson_guards.yaml +0 -3
  53. package/core/.specialist/Ansuz/identity_contract.yaml +0 -1
  54. package/core/.specialist/Ansuz/knowledge.yaml +0 -4
  55. package/core/.specialist/Ansuz/rule.yaml +0 -1
  56. package/core/.specialist/Ansuz/tools/handoff-session-identity.test.js +6 -150
  57. package/core/.specialist/tests/runtime-discovery/README.md +1 -1
  58. package/core/.specialist/tools/__tests__/task-2374-usage-counter.test.js +1 -12
  59. package/core/.specialist/tools/ansuz-boot-fast.js +7 -4
  60. package/core/.specialist/tools/ansuz-boot-fast.test.js +10 -3
  61. package/core/.specialist/tools/prompt-files.test.js +1 -0
  62. package/core/.specialist/tools/task-bridge.js +1 -66
  63. package/core/.system/README.md +11 -552
  64. package/core/.system/collaboration/README.md +10 -174
  65. package/core/CLAUDE.md +1 -6
  66. package/core/KENAZ_CORE_VERSION +1 -1
  67. package/core/dev/scripts/js-yaml-resolution.test.js +1 -1
  68. package/core/dev/scripts/peer-identity-e2e.js +402 -0
  69. package/core/dev/scripts/peer-identity-e2e.md +47 -0
  70. package/core/dev/scripts/relay-admin.js +94 -0
  71. package/core/dev/scripts/relay-admin.test.js +131 -0
  72. package/core/dev/scripts/run-rust-tests.js +9 -45
  73. package/core/dev/scripts/rust-verify-worktree.js +4 -4
  74. package/core/dev/scripts/verify-target-dir.js +6 -6
  75. package/core/plugins/kenaz/INVENTORY.md +8 -8
  76. package/core/plugins/kenaz/README.md +10 -0
  77. package/core/plugins/kenaz/commands/architect.md +1 -2
  78. package/core/plugins/kenaz/commands/spec.md +1 -1
  79. package/core/plugins/kenaz/commands//347/224/242/345/223/201.md +1 -1
  80. package/core/plugins/kenaz/guidance.md +1 -1
  81. package/core/plugins/kenaz/hooks/kenaz-hook.js +2 -1
  82. package/core/plugins/kenaz/hooks/sdk-gate.js +9 -0
  83. package/core/plugins/kenaz/scripts/kenaz.test.js +15 -3
  84. package/core/plugins/kenaz/scripts/sync.js +1 -1
  85. package/core/plugins/kenaz/scripts/ygg.js +8 -0
  86. package/core/plugins/kenaz/scripts/ygg.test.js +72 -0
  87. package/core/plugins/kenaz/skills/architect_agent/SKILL.md +11 -92
  88. package/core/plugins/kenaz/skills/assessment_agent/SKILL.md +0 -1
  89. package/core/plugins/kenaz/skills/backend_engineer/SKILL.md +3 -6
  90. package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +1 -1
  91. package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +1 -1
  92. package/core/plugins/kenaz/skills/designer/SKILL.md +3 -6
  93. package/core/plugins/kenaz/skills/dev_agent/SKILL.md +30 -491
  94. package/core/plugins/kenaz/skills/developer/SKILL.md +3 -6
  95. package/core/plugins/kenaz/skills/feature_agent/SKILL.md +3 -17
  96. package/core/plugins/kenaz/skills/product_manager/SKILL.md +29 -1064
  97. package/core/plugins/kenaz/skills/qa_engineer/SKILL.md +12 -17
  98. package/core/plugins/kenaz/skills/spec_optimizer/SKILL.md +25 -275
  99. package/core/plugins/kenaz/skills/sre_agent/SKILL.md +3 -6
  100. package/core/plugins/kenaz-peer/hooks/register.test.ts +32 -0
  101. package/core/plugins/kenaz-peer/hooks/register.ts +9 -0
  102. package/package.json +6 -6
  103. package/core/.claude/commands/archive.md +0 -24
  104. package/core/.claude/commands/init.md +0 -112
  105. package/core/.claude/commands/legacy_dev.md +0 -18
  106. package/core/.claude/commands/product-review.md +0 -131
  107. package/core/.claude/commands/strategy.md +0 -141
  108. package/core/.claude/commands/sunny.md +0 -1
  109. package/core/.claude/commands//347/224/242/345/223/201/346/252/242/350/246/226.md +0 -129
  110. package/core/.claude/commands//351/200/262/345/272/246.md +0 -15
  111. package/core/.claude/hooks/__tests__/substrate.test.js +0 -122
  112. package/core/.claude/hooks/_substrate-paths.js +0 -95
  113. package/core/.claude/hooks/context-check.js +0 -51
  114. package/core/.claude/hooks/fatigue-check.js +0 -52
  115. package/core/.claude/hooks/handoff-snapshot-on-commit.js +0 -154
  116. package/core/.claude/hooks/handoff-snapshot-on-question.js +0 -158
  117. package/core/.claude/hooks/handoff-staleness.js +0 -197
  118. package/core/.claude/hooks/jev-thalamus-context.js +0 -17
  119. package/core/.claude/hooks/jev-thalamus-context.test.js +0 -41
  120. package/core/.claude/hooks/lease-release.js +0 -119
  121. package/core/.claude/hooks/lease-touch.js +0 -96
  122. package/core/.claude/hooks/reflex-feedback.js +0 -5
  123. package/core/.claude/hooks/session-init.ps1 +0 -49
  124. package/core/.claude/hooks/sound-event.js +0 -70
  125. package/core/.claude/hooks/sound-event.sh +0 -37
  126. package/core/.claude/hooks/stream-delta.js +0 -169
  127. package/core/.claude/no-hooks.settings.json +0 -3
  128. package/core/.claude/skills/legacy_dev/SKILL.md +0 -202
  129. package/core/.claude/skills/sunny/SKILL.md +0 -74
  130. package/core/.collaboration/cli/commands/assign.js +0 -39
  131. package/core/.collaboration/cli/commands/claim.js +0 -33
  132. package/core/.collaboration/cli/commands/complete.js +0 -44
  133. package/core/.collaboration/cli/commands/create.js +0 -110
  134. package/core/.collaboration/cli/commands/get.js +0 -83
  135. package/core/.collaboration/cli/commands/index.js +0 -52
  136. package/core/.collaboration/cli/commands/list.js +0 -51
  137. package/core/.collaboration/cli/commands/lock.js +0 -45
  138. package/core/.collaboration/cli/commands/rebuild.js +0 -39
  139. package/core/.collaboration/cli/commands/search.js +0 -61
  140. package/core/.collaboration/cli/commands/stats.js +0 -61
  141. package/core/.collaboration/cli/commands/transfer.js +0 -41
  142. package/core/.collaboration/cli/commands/unlock.js +0 -48
  143. package/core/.collaboration/cli/formatters/help-formatter.js +0 -70
  144. package/core/.collaboration/cli/formatters/index.js +0 -7
  145. package/core/.collaboration/cli/task-cli.js +0 -46
  146. package/core/.collaboration/cli/utils/arg-parser.js +0 -74
  147. package/core/.collaboration/cli/utils/git-user.js +0 -25
  148. package/core/.collaboration/cli/utils/index.js +0 -9
  149. package/core/.collaboration/cli/utils/output.js +0 -10
  150. package/core/.collaboration/config/default.yaml +0 -189
  151. package/core/.collaboration/core/conflict-resolver.js +0 -461
  152. package/core/.collaboration/core/conflict-resolver.test.js +0 -389
  153. package/core/.collaboration/core/migrate-legacy.js +0 -260
  154. package/core/.collaboration/docs/API.md +0 -787
  155. package/core/.collaboration/docs/ARCHITECTURE.md +0 -596
  156. package/core/.collaboration/docs/FAQ.md +0 -695
  157. package/core/.collaboration/docs/MIGRATION.md +0 -804
  158. package/core/.collaboration/docs/examples/README.md +0 -320
  159. package/core/.collaboration/docs/examples/advanced-queries.js +0 -261
  160. package/core/.collaboration/docs/examples/basic-usage.js +0 -184
  161. package/core/.collaboration/docs/examples/team-workflow.js +0 -352
  162. package/core/.failure/agent-execution/FAIL-001-agent-no-output.yaml +0 -391
  163. package/core/.failure/agent-execution/FAIL-007-pm-lead-wrong-team-invocation.yaml +0 -0
  164. package/core/.failure/agent-execution/FAIL-010-phase0-poem-format-skipped.yaml +0 -28
  165. package/core/.failure/agent-execution/FAIL-010-skipping-cognitive-checkpoint-after-verification.yaml +0 -59
  166. package/core/.failure/agent-execution/FAIL-012-grep-shortcut-during-investigation.yaml +0 -85
  167. package/core/.failure/architecture/FAIL-006-python-version-compatibility.yaml +0 -445
  168. package/core/.failure/pipeline/FAIL-003-queue-pollution.yaml +0 -335
  169. package/core/.failure/pipeline/README.md +0 -95
  170. package/core/.failure/retired/FAIL-009-skipping-synapse-pulse-before-code-change.yaml +0 -49
  171. package/core/.failure/tools/USAGE_GUIDE.md +0 -323
  172. package/core/.failure/tools/check_failures.ps1 +0 -387
  173. package/core/.failure/tools/log_failure.ps1 +0 -474
  174. package/core/.formation/.episode-input.json +0 -10
  175. package/core/.formation/.merge-lock +0 -1
  176. package/core/.formation/lead.yaml +0 -118
  177. package/core/.formation/legacy.bak/designer.yaml +0 -57
  178. package/core/.formation/legacy.bak/pm.yaml +0 -57
  179. package/core/.formation/legacy.bak/qa.yaml +0 -57
  180. package/core/.formation/legacy.bak/review.yaml +0 -62
  181. package/core/.formation/legacy.bak/scheduler.yaml +0 -129
  182. package/core/.formation/legacy.bak/sre.yaml +0 -57
  183. package/core/.formation/role-map.yaml +0 -274
  184. package/core/.formation/roles/_shared/modules/code_review.yaml +0 -98
  185. package/core/.formation/roles/_shared/modules/security.yaml +0 -103
  186. package/core/.formation/roles/_shared/modules/testing.yaml +0 -14
  187. package/core/.formation/roles/backend/role.yaml +0 -93
  188. package/core/.formation/roles/code_reviewer/role.yaml +0 -102
  189. package/core/.formation/roles/database_optimizer/role.yaml +0 -107
  190. package/core/.formation/roles/designer/role.yaml +0 -66
  191. package/core/.formation/roles/developer/role.yaml +0 -75
  192. package/core/.formation/roles/frontend/role.yaml +0 -134
  193. package/core/.formation/roles/incident_response/role.yaml +0 -89
  194. package/core/.formation/roles/qa/role.yaml +0 -89
  195. package/core/.formation/roles/security/role.yaml +0 -87
  196. package/core/.formation/roles/sre/role.yaml +0 -66
  197. package/core/.formation/skills/coordinator.yaml +0 -52
  198. package/core/.formation/solo/diagnosis.yaml +0 -78
  199. package/core/.formation/solo/strategy.yaml +0 -121
  200. package/core/.formation/tests/hello.txt +0 -2
  201. package/core/.formation/tools/coordinator.js +0 -350
  202. package/core/.formation/tools/jev-correction.js +0 -214
  203. package/core/.formation/tools/jev-correction.test.js +0 -178
  204. package/core/.formation/tools/log-reader.js +0 -232
  205. package/core/.formation/tools/revert-daemon-state.js +0 -162
  206. package/core/.formation/tools/test-drift.js +0 -148
  207. package/core/.formation/unit/heimdall.yaml +0 -96
  208. package/core/.formation/unit/plan.yaml +0 -67
  209. package/core/.formation/unit/think.yaml +0 -58
  210. package/core/.kenaz/MUNINN_SNAPSHOT9_EPISODE.json +0 -32
  211. package/core/.kenaz/MUNINN_SNAPSHOT9_RUNE.json +0 -23
  212. package/core/.kenaz/MUNINN_SNAPSHOT9_SUMMARY.md +0 -89
  213. package/core/.kenaz/ash_TASK_1428-20260507.json +0 -14
  214. package/core/.kenaz/muninn_episode_write.ps1 +0 -17
  215. package/core/.kenaz/muninn_snapshot11_rune_candidates.md +0 -63
  216. package/core/.kenaz/muninn_snapshot1_rune_candidates.md +0 -49
  217. package/core/.kenaz/runes_TASK_1428.json +0 -57
  218. package/core/.kenaz/tmp_heimdall_proposal_8d3e392.json +0 -13
  219. package/core/.kenaz/tmp_props.json +0 -1
  220. package/core/.kenaz/tmp_sounddrop_t1_prompt.txt +0 -371
  221. package/core/.rule/MECHANIZED.md +0 -61
  222. package/core/.rule/general/cortex-memory-recall.yaml +0 -446
  223. package/core/.rule/general/pipeline.yaml +0 -246
  224. package/core/.rule/general/sunny-access-control.yaml +0 -60
  225. package/core/.rule/general/system-invariants.yaml +0 -231
  226. package/core/.rule/pipeline/PPL-002-completed-task-archival.yaml +0 -528
  227. package/core/.rule/pipeline/auto-update.yaml +0 -282
  228. package/core/.rule/pipeline/task-execution.yaml +0 -365
  229. package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +0 -65
  230. package/core/.specialist/Ansuz/experiments/boot_ablation_replay.js +0 -197
  231. package/core/.specialist/Ansuz/tools/bootstrap-anchors.js +0 -289
  232. package/core/.specialist/Ansuz/workflow.yaml +0 -1225
  233. package/core/.specialist/experiments/rg_benchmark.js +0 -523
  234. package/core/.specialist/experiments/rg_benchmark_results.json +0 -27
  235. package/core/.specialist/tools/jev-optional-context.js +0 -107
  236. package/core/.specialist/tools/jev-optional-context.test.js +0 -152
  237. package/core/.specialist/tools/memory-action-log.js +0 -839
  238. package/core/.specialist/tools/phase-gate.js +0 -3534
  239. package/core/.specialist/tools/phase-gate.test.js +0 -85
  240. package/core/.specialist/tools/precompute-runner.js +0 -444
  241. package/core/.specialist/tools/rule-engine.js +0 -621
  242. package/core/.specialist/tools/task-bridge-jev.test.js +0 -170
  243. package/core/.synapse/memory-shared.json.sync +0 -1
  244. package/core/.system/agent-skills/README.md +0 -724
  245. package/core/.system/cli-tools/README.md +0 -724
  246. package/core/.system/cortex/MODULES.md +0 -1046
  247. package/core/.system/dashboard/FRONTEND.md +0 -812
  248. package/core/.system/dashboard/README.md +0 -737
  249. package/core/.system/failure/README.md +0 -723
  250. package/core/.system/formation/LEGACY_SKILLS.md +0 -974
  251. package/core/.system/formation/POST_COMPLETE.md +0 -602
  252. package/core/.system/formation/README.md +0 -1094
  253. package/core/.system/formation/TOOLS_REFERENCE.md +0 -1145
  254. package/core/.system/kenaz/README.md +0 -787
  255. package/core/.system/memory/README.md +0 -719
  256. package/core/.system/pipeline/README.md +0 -712
  257. package/core/.system/rule/README.md +0 -794
  258. package/core/.system/sunny/README.md +0 -476
  259. package/core/.system/sunny/RULES.json +0 -89
  260. package/core/.system/sunny/knowledge/INDEX.md +0 -37
  261. package/core/.system/sunny/knowledge/agent-teams-api.md +0 -0
  262. package/core/.system/sunny/knowledge/ansuz-specialist.md +0 -500
  263. package/core/.system/sunny/knowledge/claude-cli-spawn.md +0 -177
  264. package/core/.system/sunny/knowledge/collaboration-api.md +0 -925
  265. package/core/.system/sunny/teammates/README.md +0 -107
  266. package/core/.system/sunny/teammates/how_to_use.yaml +0 -101
  267. package/core/.system/sunny/teammates/tools/headless/launcher.js +0 -318
  268. package/core/.system/sunny/teammates/tools/powershell/launcher.js +0 -241
  269. package/core/.system/sunny/teammates/tools/powershell/start.bat +0 -50
  270. package/core/.system/sunny/teammates/tools/utils/env.js +0 -96
  271. package/core/.system/sunny/teammates/tools/utils/format-output.js +0 -329
  272. package/core/.system/sunny/tools.json +0 -101
  273. package/core/dev/scripts/cleanup-queue.js +0 -184
  274. package/core/dev/scripts/cleanup_queue.js +0 -86
  275. package/core/dev/scripts/legacy/README.md +0 -39
  276. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +0 -536
  277. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +0 -1978
  278. package/core/dev/scripts/matrix/CLI_README.md +0 -214
  279. package/core/dev/scripts/matrix/README.md +0 -310
  280. package/core/dev/scripts/matrix/package-lock.json +0 -4266
  281. package/core/dev/scripts/matrix/package.json +0 -55
  282. package/core/dev/scripts/matrix/src/abbreviations.ts +0 -295
  283. package/core/dev/scripts/matrix/src/cli/commands/convert.ts +0 -211
  284. package/core/dev/scripts/matrix/src/cli/commands/create.ts +0 -95
  285. package/core/dev/scripts/matrix/src/cli/commands/delete.ts +0 -102
  286. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +0 -140
  287. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +0 -173
  288. package/core/dev/scripts/matrix/src/cli/commands/query.ts +0 -261
  289. package/core/dev/scripts/matrix/src/cli/commands/read.ts +0 -77
  290. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +0 -168
  291. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +0 -126
  292. package/core/dev/scripts/matrix/src/cli/commands/update.ts +0 -147
  293. package/core/dev/scripts/matrix/src/cli/commands/validate.ts +0 -275
  294. package/core/dev/scripts/matrix/src/cli/index.ts +0 -350
  295. package/core/dev/scripts/matrix/src/cli/utils/file-handler.ts +0 -72
  296. package/core/dev/scripts/matrix/src/cli/utils/logger.ts +0 -65
  297. package/core/dev/scripts/matrix/src/index.ts +0 -62
  298. package/core/dev/scripts/matrix/src/indexer/tag-indexer.ts +0 -165
  299. package/core/dev/scripts/matrix/src/parser.ts +0 -281
  300. package/core/dev/scripts/matrix/src/serializer.ts +0 -151
  301. package/core/dev/scripts/matrix/src/validator.ts +0 -301
  302. package/core/dev/scripts/matrix/tests/abbreviations.test.ts +0 -122
  303. package/core/dev/scripts/matrix/tests/cli.test.ts +0 -177
  304. package/core/dev/scripts/matrix/tests/convert.test.ts +0 -143
  305. package/core/dev/scripts/matrix/tests/parser.test.ts +0 -203
  306. package/core/dev/scripts/matrix/tests/query.test.ts +0 -85
  307. package/core/dev/scripts/matrix/tests/serializer.test.ts +0 -197
  308. package/core/dev/scripts/matrix/tests/validate.test.ts +0 -141
  309. package/core/dev/scripts/matrix/tests/validator.test.ts +0 -167
  310. package/core/dev/scripts/matrix/tsconfig.json +0 -20
  311. package/core/dev/scripts/matrix/types/index.d.ts +0 -111
  312. package/core/dev/scripts/matrix/types/index.d.ts.map +0 -1
  313. package/core/dev/scripts/matrix/types/index.js +0 -8
  314. package/core/dev/scripts/matrix/types/index.js.map +0 -1
  315. package/core/dev/scripts/matrix/types/index.ts +0 -124
  316. package/core/dev/scripts/memory-usage-report.js +0 -152
  317. package/core/dev/scripts/probe/ansuz-scaffold-ablation.js +0 -579
  318. package/core/dev/scripts/probe/ansuz-scaffold-ablation.test.js +0 -244
  319. package/core/dev/scripts/probe/hook-latency-probe.js +0 -315
  320. package/core/dev/scripts/probe/hook-latency-probe.test.js +0 -105
  321. package/core/dev/scripts/probe/prediction-signal-auc.js +0 -483
  322. package/core/dev/scripts/probe/prediction-signal-auc.test.js +0 -200
  323. package/core/dev/scripts/test-pty-ws-tunnel.js +0 -229
  324. package/core/dev/scripts/validate_pipeline.js +0 -197
  325. package/core/dev/spikes/task2145-evidence-20261004/backend-log-excerpt.txt +0 -56
  326. package/core/dev/spikes/task2145-evidence-20261004/graph.shadow +0 -0
  327. package/core/dev/spikes/task2145-evidence-20261004/graph.wal +0 -0
  328. package/core/dev/spikes/task2145-evidence-20261004/graph_corrupt_suspect.marker +0 -1
  329. package/core/plugins/kenaz/commands/strategy.md +0 -141
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+ // TASK_2440: the one registry of prompt-level A/B arms (dev/research/self-improvement/DESIGN.md, "New prompt-level changes").
3
+ // A new prompt change = one entry here + its prompt branch in dispatch.js. Not a framework: a table and a hash.
4
+ const MS_DAY = 86400000;
5
+
6
+ // applies: which cards are eligible ('bug' | 'criteria'). salt '' is the TASK_2429 hash kept as it was; new arms use `${name}:`.
7
+ // minSample counts merged cards per arm; metric is the arm's own named metric besides first-pass merge.
8
+ const ARMS = [
9
+ { name: 'diagnose', field: 'diagnose_arm', on: 'with', off: 'without', salt: '', flag: '--diagnose', applies: 'bug',
10
+ start: '2026-10-11', end: null, decide: '2026-11-11', minSample: 10, metric: 'first-pass', change: 'Bug diagnosis section appended to the prompt' },
11
+ { name: 'contract', field: 'contract_arm', on: 'contract', off: 'generic', salt: 'contract:', flag: '--contract', applies: 'criteria',
12
+ start: '2026-10-12', end: null, decide: '2026-11-11', minSample: 20, metric: 'rework commits per merged card', change: 'numbered acceptance criteria answered one line each in the report' },
13
+ ];
14
+
15
+ /** Stable FNV-1a bit of `${salt}${taskId}`: true -> on arm. */
16
+ function hashOn(taskId, salt) {
17
+ let h = 2166136261;
18
+ for (const ch of salt + String(taskId || '')) { h ^= ch.charCodeAt(0); h = Math.imul(h, 16777619) >>> 0; }
19
+ return (h >>> 7) % 2 === 0;
20
+ }
21
+
22
+ function active(arm, now) {
23
+ const t = +now;
24
+ return t >= Date.parse(arm.start) && (!arm.end || t < Date.parse(arm.end) + MS_DAY);
25
+ }
26
+
27
+ /** Label for one arm ('on'/'off' override forces it). Does not check eligibility or dates. */
28
+ function armLabel(arm, taskId, override) {
29
+ if (override === 'on') return arm.on;
30
+ if (override === 'off') return arm.off;
31
+ return hashOn(taskId, arm.salt) ? arm.on : arm.off;
32
+ }
33
+
34
+ /**
35
+ * All arms for one card: { [arm.name]: label }. `eligible` = { bug: bool, criteria: bool }; `overrides` = { [name]: 'on'|'off' }.
36
+ * Arms outside [start, end], or not applying to the card, are absent. Each arm hashes on its own salt, so arms are independent.
37
+ */
38
+ function assignArms(taskId, eligible, { now = Date.now(), overrides = {}, arms = ARMS } = {}) {
39
+ const out = {};
40
+ for (const arm of arms) {
41
+ if (!eligible[arm.applies]) continue;
42
+ if (!overrides[arm.name] && !active(arm, now)) continue;
43
+ out[arm.name] = armLabel(arm, taskId, overrides[arm.name]);
44
+ }
45
+ return out;
46
+ }
47
+
48
+ module.exports = { ARMS, assignArms, armLabel, hashOn, active };
@@ -14,10 +14,11 @@ const path = require('path');
14
14
  const fs = require('fs');
15
15
  const { execSync } = require('child_process');
16
16
  const { loadSkill, loadTask } = require('./loader');
17
+ const { ARMS, assignArms, armLabel } = require('./arms');
17
18
  const {
18
19
  buildPrompt, SUBAGENT_PREFIX, SUBAGENT_PREFIX_DIRECT, buildSubagentWorktreePrefix,
19
20
  promptProfile, extractRigor, extractComplexity, splitPromptSections,
20
- applyRustCheckToPrefix,
21
+ applyRustCheckToPrefix, cardCriteria,
21
22
  } = require('./prompt');
22
23
  const {
23
24
  ROOT, loadDashboardModels,
@@ -648,14 +649,12 @@ function isBugCard(taskData) {
648
649
  return /^\s*((fix(es|ed)?|bug)\b|修)/i.test(String(t.title || ''));
649
650
  }
650
651
 
651
- /** 'with' | 'without': stable 50/50 split by task id (FNV-1a), unless forced by --diagnose on|off. */
652
- function diagnoseArmFor(taskId, override) {
653
- if (override === 'on') return 'with';
654
- if (override === 'off') return 'without';
655
- let h = 2166136261;
656
- for (const ch of String(taskId || '')) { h ^= ch.charCodeAt(0); h = Math.imul(h, 16777619) >>> 0; }
657
- return (h >>> 7) % 2 === 0 ? 'with' : 'without';
658
- }
652
+ // TASK_2440: arms live in arms.js (one registry); these keep the old names and the exact same hashes.
653
+ const armNamed = (n) => ARMS.find((a) => a.name === n);
654
+ /** 'with' | 'without': stable 50/50 split by task id, unless forced by --diagnose on|off. */
655
+ function diagnoseArmFor(taskId, override) { return armLabel(armNamed('diagnose'), taskId, override); }
656
+ /** 'contract' | 'generic' report arm (TASK_2439), salted so it is independent of the diagnosis arm; --contract on|off forces it. */
657
+ function contractArmFor(taskId, override) { return armLabel(armNamed('contract'), taskId, override); }
659
658
 
660
659
  const BUG_DIAGNOSIS_SECTION = [
661
660
  '## Bug diagnosis',
@@ -682,7 +681,7 @@ const BUG_DIAGNOSIS_SECTION = [
682
681
  * complexity (TASK_1998 two-axis policy): the normalized 'simple'|'medium'|'complex'
683
682
  * CONTEXT-volume key (extractComplexity() default). Optional for older readers.
684
683
  */
685
- function appendDispatchPromptStats({ taskId, rigor, complexity, formation, promptText, model, modelSource, projectPath, diagnoseArm }) {
684
+ function appendDispatchPromptStats({ taskId, rigor, complexity, formation, promptText, model, modelSource, projectPath, diagnoseArm, contractArm, arms }) {
686
685
  const root = projectPath || ROOT;
687
686
  const dir = path.join(root, '.kenaz', 'specialist');
688
687
  try {
@@ -698,6 +697,8 @@ function appendDispatchPromptStats({ taskId, rigor, complexity, formation, promp
698
697
  model,
699
698
  model_source: modelSource || null,
700
699
  diagnose_arm: diagnoseArm || null,
700
+ contract_arm: contractArm || null,
701
+ arms: arms || null, // TASK_2440: every arm assigned to the card, {name: label}
701
702
  });
702
703
  fs.appendFileSync(path.join(dir, 'dispatch_prompt_stats.jsonl'), line + '\n', 'utf8');
703
704
  } catch { /* best-effort — telemetry must never block dispatch */ }
@@ -785,6 +786,7 @@ Options:
785
786
  --formation <name> Skill folder: unit (default) or solo
786
787
  --skill <name> Skill to use (YAML filename without extension)
787
788
  --task <value> Task ID (TASK_XXX) or free-text description
789
+ --contract on|off Force the report-contract A/B arm for a card with acceptance criteria (default: stable hash of the task id)
788
790
  --diagnose on|off Force the bug-diagnosis A/B arm for a bug card (default: stable hash of the task id)
789
791
  --dry-run Build and print the payload with no side effects
790
792
  --dispatch-mode <mode> Required: subagent (JSON params for the Agent tool)
@@ -813,7 +815,7 @@ Options:
813
815
  (with --release-lock: release a lock this session
814
816
  cannot prove it owns)
815
817
 
816
- Skills: unit: do, think, plan; solo: strategy, diagnosis.
818
+ Skills: unit: do.
817
819
 
818
820
  Sub-agent dispatch (Ansuz billing-aware path):
819
821
  node .formation/tools/dispatch.js --skill=do --task=TASK_172 --dispatch-mode=subagent
@@ -915,7 +917,7 @@ Sub-agent dispatch (Ansuz billing-aware path):
915
917
  console.error('❌ --dispatch-mode=subagent is required (Formation headless/squad/legion dispatch is retired)');
916
918
  process.exit(1);
917
919
  }
918
- getSubagentDispatchParams({ formation, skill, task, projectPath, force, useWorktree: useWorktreeCli, dispatchModelOverride: dispatchModelCli, dryRun, harness: harnessCli, diagnose: getArg('--diagnose') }).then(result => {
920
+ getSubagentDispatchParams({ formation, skill, task, projectPath, force, useWorktree: useWorktreeCli, dispatchModelOverride: dispatchModelCli, dryRun, harness: harnessCli, diagnose: getArg('--diagnose'), contract: getArg('--contract') }).then(result => {
919
921
  if (!result) { process.exitCode = 1; return; }
920
922
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
921
923
  }).catch(e => {
@@ -989,6 +991,7 @@ async function getSubagentDispatchParams(options) {
989
991
  dryRun = false,
990
992
  // TASK_2315: 'claude' (Agent tool) | 'codex' (Codex-native spawn_agent). Selects the payload shape.
991
993
  harness = 'claude',
994
+ contract = null, // TASK_2439: --contract on|off forces the report-contract arm
992
995
  diagnose = null, // TASK_2429: --diagnose on|off forces the bug-diagnosis arm
993
996
  } = options;
994
997
  if (harness !== 'claude' && harness !== 'codex') {
@@ -1246,9 +1249,13 @@ async function getSubagentDispatchParams(options) {
1246
1249
  if (projectPath) promptOptions.projectPath = projectPath;
1247
1250
  if (taskInput && taskInput.startsWith('TASK_')) promptOptions.taskId = taskInput;
1248
1251
 
1252
+ // TASK_2439: only cards with acceptance criteria get a contract arm; others keep today's generic line.
1253
+ const armSet = assignArms(taskInput, { bug: isBugCard(taskData), criteria: cardCriteria(taskData).length > 0 }, { overrides: { diagnose, contract } });
1254
+ const contractArm = armSet.contract || null;
1255
+ if (contractArm === 'contract') promptOptions.contract = true;
1249
1256
  let rawPrompt = buildPrompt(skillDef, taskInput, taskData, promptOptions);
1250
1257
  // TASK_2429: only bug cards get an arm; any other prompt stays byte-for-byte unchanged.
1251
- const diagnoseArm = isBugCard(taskData) ? diagnoseArmFor(taskInput, diagnose) : null;
1258
+ const diagnoseArm = armSet.diagnose || null;
1252
1259
  if (diagnoseArm === 'with' && typeof rawPrompt === 'string') rawPrompt = rawPrompt + '\n\n' + BUG_DIAGNOSIS_SECTION + '\n';
1253
1260
 
1254
1261
  // 4. Cortex context (same as Formation dispatch)
@@ -1394,7 +1401,7 @@ async function getSubagentDispatchParams(options) {
1394
1401
  // by dashboard vs dashboard-by-rigor vs cli without re-deriving it.
1395
1402
  // Two-axis policy: complexity rides along too, so dispatch-stats.js can
1396
1403
  // group by (rigor, complexity) instead of rigor alone.
1397
- if (!dryRun) appendDispatchPromptStats({ taskId: taskInput, rigor: resolution.rigorKey, complexity, formation, promptText, model: dispatchModel || rawModel, modelSource: resolution.source, projectPath, diagnoseArm });
1404
+ if (!dryRun) appendDispatchPromptStats({ taskId: taskInput, rigor: resolution.rigorKey, complexity, formation, promptText, model: dispatchModel || rawModel, modelSource: resolution.source, projectPath, diagnoseArm, contractArm, arms: Object.keys(armSet).length ? armSet : null });
1398
1405
 
1399
1406
  // TASK_2032: mirror headless.js's formation-path auto-activation (~L272-290)
1400
1407
  // on the subagent path — every gate/guard above has passed and this function
@@ -1455,6 +1462,8 @@ async function getSubagentDispatchParams(options) {
1455
1462
  ...(isCodex ? { harness: 'codex', cwd: crossWorktree ? crossWorktree.worktreePath : null, worktreePath: crossWorktree ? crossWorktree.worktreePath : null } : {}),
1456
1463
  modelTranslation, isolation, ...(agentParams ? { agentParams } : {}), ...(executor ? { executor } : {}), rigor: resolution.rigorKey, complexity, modelSource: resolution.source,
1457
1464
  ...(diagnoseArm ? { diagnoseArm } : {}),
1465
+ ...(contractArm ? { contractArm } : {}),
1466
+ ...(Object.keys(armSet).length ? { arms: armSet } : {}),
1458
1467
  ...(dryRun ? { dryRun: true } : {}),
1459
1468
  };
1460
1469
  }
@@ -1505,4 +1514,4 @@ function setupSubagentCrossProjectWorktree(projectPath, taskInput) {
1505
1514
  };
1506
1515
  }
1507
1516
 
1508
- module.exports = { isBugCard, diagnoseArmFor, BUG_DIAGNOSIS_SECTION, appendDispatchPromptStats, resolveDispatchModelForRigor, translateModelForAgentTool, DEFAULT_DISPATCH_MODEL, getSubagentDispatchParams, checkDispatchScopeGate, releaseDispatchLock, resolveDispatchIsolation, _getProvenSessionId, _getOwnConversationId, _isOwnClaimMarker, _buildCortexContext: buildCortexContext };
1517
+ module.exports = { isBugCard, diagnoseArmFor, contractArmFor, BUG_DIAGNOSIS_SECTION, appendDispatchPromptStats, resolveDispatchModelForRigor, translateModelForAgentTool, DEFAULT_DISPATCH_MODEL, getSubagentDispatchParams, checkDispatchScopeGate, releaseDispatchLock, resolveDispatchIsolation, _getProvenSessionId, _getOwnConversationId, _isOwnClaimMarker, _buildCortexContext: buildCortexContext };
@@ -13,6 +13,7 @@ const fs = require('fs');
13
13
  const os = require('os');
14
14
  const path = require('path');
15
15
  const { execFileSync } = require('child_process');
16
+ const { ARMS } = require('./arms');
16
17
 
17
18
  const DAY = 86400000;
18
19
  const NO_DATA = 'no data';
@@ -211,21 +212,40 @@ function lessonStats({ home, cwd, now, days }) {
211
212
 
212
213
  /** TASK_2429: bug cards per diagnosis arm (dispatch_prompt_stats.jsonl diagnose_arm) joined with the git first-pass verdicts of dispatchStats. */
213
214
  function diagnoseAbStats({ cwd, now, days, dispatch }) {
215
+ return armStats({ cwd, now, days, dispatch, field: 'diagnose_arm', names: ['with', 'without'] });
216
+ }
217
+
218
+ /** TASK_2439: cards with acceptance criteria per report-contract arm, plus rework commits per arm. */
219
+ function contractAbStats({ cwd, now, days, dispatch }) {
220
+ return armStats({ cwd, now, days, dispatch, field: 'contract_arm', names: ['contract', 'generic'], commits: true });
221
+ }
222
+
223
+ /** TASK_2440: one row per registered arm (arms.js): cards, merged, first-pass and rework commits on/off, minimum sample, decision date. */
224
+ function armsStats({ cwd, now, days, dispatch }) {
225
+ return ARMS.map((arm) => {
226
+ const r = armStats({ cwd, now, days, dispatch, field: arm.field, names: [arm.on, arm.off], commits: true });
227
+ return { name: arm.name, metric: arm.metric, minSample: arm.minSample, decide: arm.decide, decisionPassed: now >= Date.parse(arm.decide), on: { label: arm.on, ...r[arm.on] }, off: { label: arm.off, ...r[arm.off] } };
228
+ });
229
+ }
230
+
231
+ function armStats({ cwd, now, days, dispatch, field, names, commits }) {
214
232
  const arms = {}; // card number -> arm (latest dispatch record wins)
215
233
  try {
216
234
  for (const l of fs.readFileSync(path.join(cwd, '.kenaz', 'specialist', 'dispatch_prompt_stats.jsonl'), 'utf8').split('\n')) {
217
235
  let r; try { r = JSON.parse(l); } catch (_) { continue; }
218
- const m = r && r.diagnose_arm && /^TASK_(\d+)$/i.exec(r.task_id || '');
219
- if (m && (ms(r.ts) || 0) >= now - days * DAY) arms[m[1]] = r.diagnose_arm;
236
+ const m = r && r[field] && /^TASK_(\d+)$/i.exec(r.task_id || '');
237
+ if (m && (ms(r.ts) || 0) >= now - days * DAY) arms[m[1]] = r[field];
220
238
  }
221
239
  } catch (_) { /* no records */ }
222
240
  const rework = new Set(((dispatch && dispatch.rework) || []).map((x) => x.card));
223
241
  const merged = new Set((dispatch && dispatch.mergedCards) || []);
242
+ const shas = new Map(((dispatch && dispatch.rework) || []).map((x) => [x.card, (x.shas || []).length]));
224
243
  const out = {};
225
- for (const arm of ['with', 'without']) {
244
+ for (const arm of names) {
226
245
  const cards = Object.keys(arms).filter((n) => arms[n] === arm).map((n) => 'TASK_' + n);
227
246
  const m = cards.filter((c) => merged.has(c));
228
247
  out[arm] = { cards: cards.length, merged: m.length, firstPass: m.filter((c) => !rework.has(c)).length };
248
+ if (commits) out[arm].reworkCommits = m.reduce((n, c) => n + (shas.get(c) || 0), 0);
229
249
  }
230
250
  return out;
231
251
  }
@@ -247,7 +267,7 @@ function selfImprovementStats({ home, cwd, now, days }) {
247
267
  const prev = dispatchStats({ repo: cwd, now: now - days * DAY, days, until: now - days * DAY });
248
268
  const pick = (d) => (d.firstPassRate == null ? null : { rate: d.firstPassRate, merged: d.merged });
249
269
  return { source: src, ...require('../../.specialist/tools/lessons-harvest.js').selfStats({ projectPath: cwd, home, now, days }),
250
- firstPass: { window: pick(cur), previous: pick(prev) }, diagnoseAb: diagnoseAbStats({ cwd, now, days, dispatch: cur }),
270
+ firstPass: { window: pick(cur), previous: pick(prev) }, arms: armsStats({ cwd, now, days, dispatch: cur }),
251
271
  glossary: glossaryStats({ cwd, now, days, cur }) };
252
272
  } catch (e) { return { source: src, note: `${NO_DATA} (${e.message})` }; }
253
273
  }
@@ -314,9 +334,13 @@ function render(s) {
314
334
  ` lessons by source: ${Object.entries(si.bySource).map(([k, v]) => `${k}=${v.lessons} (fired ${v.fired}, recurred before first fire ${v.recurBefore}, after ${v.recurAfter})`).join('; ') || 'none'}`,
315
335
  ` graduated ${si.graduated}; harvest items open ${si.harvestOpen}, recorded ${si.harvestRecorded}`,
316
336
  ` first-pass merge rate: this window ${r(si.firstPass.window)}, previous window ${r(si.firstPass.previous)}`);
317
- if (si.diagnoseAb) {
318
- const a = (x) => `${x.cards} bug cards, first-pass ${x.firstPass}/${x.merged}${x.merged ? ` (${(100 * x.firstPass / x.merged).toFixed(0)}%)` : ''}`;
319
- L.push(` Bug diagnosis A/B: with: ${a(si.diagnoseAb.with)}; without: ${a(si.diagnoseAb.without)}${Math.min(si.diagnoseAb.with.merged, si.diagnoseAb.without.merged) < 10 ? ' (below 10 merged per arm: keep collecting)' : ''}`);
337
+ if (si.arms && si.arms.length) {
338
+ const c = (x) => `${x.label} ${x.cards} cards, ${x.merged} merged, first-pass ${x.firstPass}/${x.merged}${x.merged ? ` (${(100 * x.firstPass / x.merged).toFixed(0)}%)` : ''}, ${x.reworkCommits} rework commits`;
339
+ L.push(' Prompt arms (.formation/tools/arms.js):');
340
+ for (const a of si.arms) {
341
+ const low = Math.min(a.on.merged, a.off.merged) < a.minSample;
342
+ L.push(` ${a.name}: ${c(a.on)} | ${c(a.off)}; own metric: ${a.metric}; decision date ${a.decide} ${a.decisionPassed ? 'passed' : 'not yet'}${low ? ` (below ${a.minSample} merged per arm: keep collecting, do not decide)` : ''}`);
343
+ }
320
344
  }
321
345
  const g = si.glossary;
322
346
  if (g) L.push(` glossary: ${g.terms} terms, ${g.added} added in window; ${g.injections} injections in ${g.cards.length} cards`,
@@ -336,4 +360,4 @@ function main(argv, out = console.log, opts = {}) {
336
360
  }
337
361
 
338
362
  if (require.main === module) process.exit(main(process.argv.slice(2)));
339
- module.exports = { diagnoseAbStats, main, collect, peerStats, dispatchStats, git, fieldStats, alayaStats, render };
363
+ module.exports = { armsStats, diagnoseAbStats, contractAbStats, main, collect, peerStats, dispatchStats, git, fieldStats, alayaStats, render };
@@ -174,8 +174,21 @@ test('diagnose A/B: bug cards and first-pass per arm from fake records (TASK_242
174
174
  assert.deepStrictEqual(r.without, { cards: 3, merged: 2, firstPass: 1 });
175
175
  assert.deepStrictEqual(S.diagnoseAbStats({ cwd: path.join(tmp, 'none'), now: NOW, days: 14, dispatch }).with, { cards: 0, merged: 0, firstPass: 0 });
176
176
  const text = S.render({ days: 14, peer: { source: '-', note: 'no data' }, dispatch: { source: '-', note: 'no data' }, fieldReports: { source: '-', note: 'no data' }, alaya: { source: '-', note: 'no data' },
177
- selfImprovement: { source: '-', nudges: 0, converted: 0, bySource: {}, graduated: 0, harvestOpen: 0, harvestRecorded: 0, firstPass: {}, diagnoseAb: r } });
178
- assert.ok(text.includes('Bug diagnosis A/B: with: 2 bug cards, first-pass 1/2 (50%); without: 3 bug cards, first-pass 1/2 (50%) (below 10 merged per arm: keep collecting)'), text);
177
+ selfImprovement: { source: '-', nudges: 0, converted: 0, bySource: {}, graduated: 0, harvestOpen: 0, harvestRecorded: 0, firstPass: {}, arms: S.armsStats({ cwd, now: NOW, days: 14, dispatch }) } });
178
+ assert.ok(text.includes('Prompt arms') && text.includes('diagnose: with 2 cards, 2 merged, first-pass 1/2 (50%), 0 rework commits | without 3 cards, 2 merged, first-pass 1/2 (50%), 0 rework commits; own metric: first-pass; decision date 2026-11-11'), text);
179
+ assert.ok(text.includes('(below 10 merged per arm: keep collecting, do not decide)') && text.includes('contract: contract 0 cards'), text);
180
+ });
181
+
182
+ test('contract A/B: cards, first-pass and rework commits per arm (TASK_2439)', () => {
183
+ const cwd = path.join(tmp, 'ct-proj');
184
+ const f = path.join(cwd, '.kenaz', 'specialist', 'dispatch_prompt_stats.jsonl');
185
+ fs.mkdirSync(path.dirname(f), { recursive: true });
186
+ const rec = (id, arm) => JSON.stringify({ ts: iso(1), task_id: id, contract_arm: arm, diagnose_arm: 'with' });
187
+ fs.writeFileSync(f, [rec('TASK_1', 'contract'), rec('TASK_2', 'contract'), rec('TASK_3', 'generic'), rec('TASK_4', 'generic'), rec('TASK_5', 'generic')].join('\n') + '\n');
188
+ const dispatch = { mergedCards: ['TASK_1', 'TASK_2', 'TASK_3', 'TASK_4'], rework: [{ card: 'TASK_2', shas: ['a'] }, { card: 'TASK_3', shas: ['b', 'c'] }, { card: 'TASK_4', shas: ['d'] }] };
189
+ const r = S.contractAbStats({ cwd, now: NOW, days: 14, dispatch });
190
+ assert.deepStrictEqual(r.contract, { cards: 2, merged: 2, firstPass: 1, reworkCommits: 1 });
191
+ assert.deepStrictEqual(r.generic, { cards: 3, merged: 2, firstPass: 0, reworkCommits: 3 });
179
192
  });
180
193
 
181
194
  test('main: --json parses and all four sections print; bad --days exits 2', () => {
@@ -1,11 +1,7 @@
1
1
  /**
2
2
  * Formation Loader
3
- * YAML 技能定義 + 任務載入(支援新舊兩套任務系統)
4
- *
5
- * 任務系統:
6
- * 新系統 (.collaboration/tasks/) — TASK_151+ 目錄即狀態,YAML 格式
7
- * 舊系統 (dev/pipeline/) — TASK_134~187 JSON 索引 + YAML 檔
8
- * 查詢順序:新系統 → 舊系統 YAML → 舊系統 TASK_QUEUE.json
3
+ * YAML skill definitions + task loading (via task-api.js, which resolves
4
+ * .kenaz/tasks/ first and .collaboration/tasks/ as the legacy fallback).
9
5
  */
10
6
 
11
7
  const { execSync } = require('child_process');
@@ -14,14 +10,10 @@ const fs = require('fs');
14
10
  const yaml = (() => { try { return require('js-yaml'); } catch { return require('../../.vendor/js-yaml.cjs'); } })();
15
11
  const { FORMATION_DIR, COLLABORATION_API, ROOT } = require('./utils');
16
12
 
17
- // 舊系統路徑
18
- const LEGACY_PIPELINE_DIR = path.join(ROOT, 'dev', 'pipeline');
19
- const LEGACY_TASK_QUEUE = path.join(LEGACY_PIPELINE_DIR, 'TASK_QUEUE.json');
20
-
21
13
  /**
22
- * 載入 skill YAML 定義
23
- * @param {string} formation - 陣型名稱 (unit/solo/squad/legion)
24
- * @param {string} skill - 技能名稱
14
+ * Load a skill YAML definition (.formation/<formation>/<skill>.yaml).
15
+ * @param {string} formation - formation name (unit)
16
+ * @param {string} skill - skill name
25
17
  * @returns {object} parsed YAML
26
18
  */
27
19
  function loadSkill(formation, skill) {
@@ -34,48 +26,33 @@ function loadSkill(formation, skill) {
34
26
  }
35
27
 
36
28
  /**
37
- * 載入任務資料(新系統優先,fallback 舊系統)
38
- *
39
- * 查詢順序:
40
- * 1. .collaboration/tasks/ 或 .kenaz/tasks/ (新系統 — task-api.js get)
41
- * 2. dev/pipeline/completed/ + tasks/ (舊系統 YAML 檔)
42
- * 3. dev/pipeline/TASK_QUEUE.json (舊系統 JSON 索引)
29
+ * Load task data through task-api.js.
43
30
  *
44
- * @param {string} taskId - TASK_XXX 格式的任務 ID
45
- * @param {object} [options] - 可選參數
46
- * @param {string} [options.projectPath] - 子專案路徑(優先在此查找任務)
47
- * @returns {string|null} 任務 YAML/JSON 文本,或 null
31
+ * @param {string} taskId - TASK_XXX
32
+ * @param {object} [options]
33
+ * @param {string} [options.projectPath] - sub-project path (looked up first)
34
+ * @returns {string|null} task YAML text, or null
48
35
  */
49
36
  function loadTask(taskId, options) {
50
37
  if (!taskId || !taskId.startsWith('TASK_')) return null;
51
38
 
52
39
  const projectRoot = options?.projectPath || ROOT;
53
40
 
54
- // 1. 新系統 (.collaboration / .kenaz)
55
- const fromCollab = loadTaskFromCollaboration(taskId, projectRoot);
56
- if (fromCollab) return fromCollab;
57
-
58
- // 2. 舊系統 YAML 檔 (dev/pipeline/completed/ + tasks/)
59
- const fromYaml = loadTaskFromLegacyYaml(taskId, projectRoot);
60
- if (fromYaml) return fromYaml;
61
-
62
- // 3. 舊系統 JSON 索引 (TASK_QUEUE.json)
63
- const fromQueue = loadTaskFromLegacyQueue(taskId, projectRoot);
64
- if (fromQueue) return fromQueue;
41
+ const fromApi = loadTaskFromApi(taskId, projectRoot);
42
+ if (fromApi) return fromApi;
65
43
 
66
- console.warn(`⚠️ Task ${taskId} not found in any system (root: ${projectRoot})`);
44
+ console.warn(`⚠️ Task ${taskId} not found (root: ${projectRoot})`);
67
45
  return null;
68
46
  }
69
47
 
70
48
  /**
71
- * 從新系統 .collaboration 載入
72
49
  * @param {string} taskId
73
- * @param {string} projectRoot - 專案根目錄
50
+ * @param {string} projectRoot
74
51
  */
75
- function loadTaskFromCollaboration(taskId, projectRoot) {
52
+ function loadTaskFromApi(taskId, projectRoot) {
76
53
  if (!fs.existsSync(COLLABORATION_API)) return null;
77
54
  try {
78
- // 當 projectRoot 與 core ROOT 不同時,使用 --project-path
55
+ // When projectRoot differs from the core ROOT, pass --project-path
79
56
  const projectArg = projectRoot !== ROOT ? ` --project-path "${projectRoot}"` : '';
80
57
  const result = execSync(
81
58
  `node "${COLLABORATION_API}"${projectArg} get ${taskId}`,
@@ -87,104 +64,4 @@ function loadTaskFromCollaboration(taskId, projectRoot) {
87
64
  return null;
88
65
  }
89
66
 
90
- /**
91
- * 從舊系統 YAML 檔載入 (dev/pipeline/completed/ + tasks/)
92
- * @param {string} taskId
93
- * @param {string} projectRoot - 專案根目錄
94
- */
95
- function loadTaskFromLegacyYaml(taskId, projectRoot) {
96
- const pipelineDir = path.join(projectRoot, 'dev', 'pipeline');
97
- const dirs = ['completed', 'tasks'];
98
- for (const dir of dirs) {
99
- const dirPath = path.join(pipelineDir, dir);
100
- if (!fs.existsSync(dirPath)) continue;
101
- try {
102
- const files = fs.readdirSync(dirPath);
103
- const match = files.find(f => f.startsWith(taskId) && f.endsWith('.yaml'));
104
- if (match) {
105
- return fs.readFileSync(path.join(dirPath, match), 'utf8').trim();
106
- }
107
- } catch (e) {}
108
- }
109
- return null;
110
- }
111
-
112
- /**
113
- * 從舊系統 TASK_QUEUE.json 載入
114
- * @param {string} taskId
115
- * @param {string} projectRoot - 專案根目錄
116
- */
117
- function loadTaskFromLegacyQueue(taskId, projectRoot) {
118
- const queuePath = path.join(projectRoot, 'dev', 'pipeline', 'TASK_QUEUE.json');
119
- if (!fs.existsSync(queuePath)) return null;
120
- try {
121
- const queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
122
- const task = (queue.tasks || []).find(t => t.id === taskId);
123
- if (task) {
124
- return yaml.dump(task, { lineWidth: -1 });
125
- }
126
- } catch (e) {}
127
- return null;
128
- }
129
-
130
- /**
131
- * 列出某個 formation 下所有可用的 skill
132
- * @param {string} formation - 陣型名稱
133
- * @returns {string[]} skill 名稱列表
134
- */
135
- function listSkills(formation) {
136
- const dir = path.join(FORMATION_DIR, formation);
137
- if (!fs.existsSync(dir)) return [];
138
- return fs.readdirSync(dir)
139
- .filter(f => f.endsWith('.yaml'))
140
- .map(f => f.replace('.yaml', ''));
141
- }
142
-
143
- /**
144
- * 載入 specialist 定義(5 個 YAML 檔案)
145
- * @param {string} name - specialist 名稱 (e.g., "Ansuz", "Amelia")
146
- * @returns {object} { principles, workflow, knowledge, rule, capability }
147
- */
148
- function loadSpecialist(name) {
149
- const specialistDir = path.join(ROOT, '.specialist', name);
150
- if (!fs.existsSync(specialistDir)) {
151
- console.error(`❌ Specialist not found: ${specialistDir}`);
152
- process.exit(1);
153
- }
154
-
155
- const files = ['principles', 'workflow', 'knowledge', 'rule', 'capability'];
156
- const specialist = { name };
157
-
158
- for (const file of files) {
159
- const filePath = path.join(specialistDir, `${file}.yaml`);
160
- if (!fs.existsSync(filePath)) {
161
- console.warn(`⚠️ Missing ${file}.yaml for specialist ${name}`);
162
- specialist[file] = null;
163
- continue;
164
- }
165
- try {
166
- specialist[file] = yaml.load(fs.readFileSync(filePath, 'utf8'));
167
- } catch (e) {
168
- console.error(`❌ Failed to parse ${filePath}: ${e.message}`);
169
- process.exit(1);
170
- }
171
- }
172
-
173
- return specialist;
174
- }
175
-
176
- /**
177
- * 列出所有可用的 specialist
178
- * @returns {string[]} specialist 名稱列表
179
- */
180
- function listSpecialists() {
181
- const specialistDir = path.join(ROOT, '.specialist');
182
- if (!fs.existsSync(specialistDir)) return [];
183
- return fs.readdirSync(specialistDir)
184
- .filter(f => {
185
- const stat = fs.statSync(path.join(specialistDir, f));
186
- return stat.isDirectory();
187
- });
188
- }
189
-
190
- module.exports = { loadSkill, loadTask, listSkills, loadSpecialist, listSpecialists };
67
+ module.exports = { loadSkill, loadTask };
@@ -54,7 +54,6 @@
54
54
  * | 2.5d | Safety net: force-move to done | merge succeeded but complete() failed | spawnSync | task stays in active |
55
55
  * | 2.6b | Ansuz inbox signal | result.taskCompleted && !dryRun | catch (silent) | Ansuz not notified |
56
56
  * | 2.6c | Relay task_completed event (TASK_1409) | result.taskCompleted && !dryRun && relay | catch (silent) | PM inbox not notified cross-node |
57
- * | 3 | Memory agent onTaskCompleted/onError | memoryAgent && taskId | catch (log warning) | memory event not queued |
58
57
  *
59
58
  * A.2 gitBranchAndMerge() — serial mode (L2344)
60
59
  *
@@ -301,12 +300,6 @@ const { verify: verifyAcceptance } = require('./verify-acceptance');
301
300
  // LLM-based acceptance verification (./verify-acceptance.js) remains and is the
302
301
  // one whose result (result.git.acceptance) is actually read.
303
302
 
304
- // Memory Agent(記憶系統整合)
305
- let memoryAgent = null;
306
- try {
307
- memoryAgent = require('../../.memory/tools/memory-agent');
308
- } catch { /* memory-agent not available — graceful degradation */ }
309
-
310
303
  // Design Reasoning Attribution (TASK_1074)
311
304
  let designReasoning = null;
312
305
  try {
@@ -587,7 +580,6 @@ function listDaemonManagedPaths(projectRoot) {
587
580
 
588
581
  scanDir('.synapse');
589
582
  scanDir('.prefrontal');
590
- scanDir('.memory');
591
583
  if (fs.existsSync(path.join(projectRoot, '.tmp'))) discovered.add('.tmp/');
592
584
 
593
585
  return Array.from(discovered);
@@ -3003,29 +2995,6 @@ function postComplete(opts = {}) {
3003
2995
  } catch { /* fire-and-forget — never blocks post-complete */ }
3004
2996
  }
3005
2997
 
3006
- // Step 3: Memory Agent(記憶系統整合)
3007
- if (memoryAgent && taskId) {
3008
- try {
3009
- const testsFailed = result.git?.regression && !result.git.regression.passed && !result.git.regression.skipped;
3010
- if (testsFailed) {
3011
- // 回歸測試失敗 → onError(fire-and-forget)
3012
- memoryAgent.onError({
3013
- event: 'regression_test_failure',
3014
- taskId,
3015
- error: `Regression tests failed after ${taskId} completion`,
3016
- summary: result.git.regression.output?.slice(-500),
3017
- }).catch(() => {});
3018
- console.log(' 🧠 Memory: error event queued (regression failure)');
3019
- } else {
3020
- // 任務成功完成 → onTaskCompleted(fire-and-forget)
3021
- memoryAgent.onTaskCompleted(taskId).catch(() => {});
3022
- console.log(' 🧠 Memory: task completion event queued');
3023
- }
3024
- } catch (e) {
3025
- console.log(` ⚠️ Memory agent skipped: ${e.message}`);
3026
- }
3027
- }
3028
-
3029
2998
  // Step 4.6: Guard Failure Detector (TASK_1653)
3030
2999
  // Runs shadow-mode failure detectors for active lesson guards that carry failure_signature.
3031
3000
  // Reads guard snapshots directly from lesson_guards.yaml (not the task chart) because
@@ -145,6 +145,14 @@ function buildPrompt(skillDef, taskInput, taskData, options = {}) {
145
145
  return buildSubagentPrompt(skillDef, taskInput, taskData, options);
146
146
  }
147
147
 
148
+ /** Acceptance criteria of a JSON card (empty for YAML/raw or none). */
149
+ function cardCriteria(taskData) {
150
+ try {
151
+ const t = typeof taskData === 'string' ? JSON.parse(taskData) : taskData;
152
+ return t && Array.isArray(t.acceptance_criteria) ? t.acceptance_criteria.map(String) : [];
153
+ } catch { return []; }
154
+ }
155
+
148
156
  /**
149
157
  * Short prompt for Agent-tool subagent dispatch (both harnesses): task card,
150
158
  * commit command, verification expectation, report format. The worktree hard
@@ -187,7 +195,13 @@ function buildSubagentPrompt(skillDef, taskInput, taskData, options = {}) {
187
195
  lines.push(`Blocked: \`${buildTaskApiRewriter(options)(onBlocked.run).replace(/\{task_id\}/g, taskId)}\` and explain.`);
188
196
  }
189
197
  for (const c of learned.checks) lines.push(`Project check: ${c.slice(2)}`);
190
- lines.push('Final report <= 20 lines: files touched, tests run, commit sha, unverified items.');
198
+ const criteria = options.contract ? cardCriteria(taskData) : [];
199
+ if (criteria.length) {
200
+ // TASK_2439: the report answers each acceptance criterion with evidence (A/B arm, DESIGN.md).
201
+ lines.push('Final report <= 20 lines: files touched, tests run, commit sha, unverified items, and one line per criterion:');
202
+ criteria.forEach((ac, i) => lines.push(`${i + 1}. ${ac}`));
203
+ lines.push('Answer each as met / not met / not verified + evidence (test name and result, file:line, or command output).');
204
+ } else lines.push('Final report <= 20 lines: files touched, tests run, commit sha, unverified items.');
191
205
  lines.push('');
192
206
  appendDispatchOverrides(lines, taskData);
193
207
  return lines.join('\n');
@@ -666,6 +680,7 @@ Details: .formation/tools/dispatch-detail/cross-project-worktree.md (Read only i
666
680
  }
667
681
 
668
682
  module.exports = {
683
+ cardCriteria,
669
684
  recordSkillUsage,
670
685
  buildPrompt,
671
686
  SUBAGENT_PREFIX,
@@ -328,6 +328,26 @@ test('TASK_2391: subagent prompt keeps card + commit rule + report, drops boiler
328
328
  assert.ok(body.length < 2500, `body too long: ${body.length}`);
329
329
  });
330
330
 
331
+ test('TASK_2439: contract arm lists numbered criteria; off arm and cards without criteria keep the generic line', () => {
332
+ const card = { id: 'TASK_9002', title: 'T', description: 'D', scope_paths: ['src/a.js'], acceptance_criteria: ['AC-A', 'AC-B', 'AC-C'] };
333
+ const was = process.cwd();
334
+ const build = (c, contract) => buildPrompt(loadDoSkill(), 'TASK_9002', JSON.stringify(c), { isSubagent: true, taskId: 'TASK_9002', ...(contract ? { contract: true } : {}) });
335
+ let on, off, plain, noAcOn, noAcOff;
336
+ try {
337
+ process.chdir(fs.mkdtempSync(path.join(os.tmpdir(), 'p2439-')));
338
+ on = build(card, true); off = build(card, false); plain = build(card);
339
+ const bare = { ...card }; delete bare.acceptance_criteria;
340
+ noAcOn = build(bare, true); noAcOff = build(bare, false);
341
+ } finally { process.chdir(was); }
342
+ const generic = 'Final report <= 20 lines: files touched, tests run, commit sha, unverified items.\n';
343
+ assert.ok(on.includes('1. AC-A\n2. AC-B\n3. AC-C\n'));
344
+ assert.ok(on.includes('met / not met / not verified + evidence'));
345
+ assert.ok(!on.includes(generic));
346
+ assert.strictEqual(off, plain, "off arm = today's prompt");
347
+ assert.ok(off.includes(generic) && !off.includes('not verified +'));
348
+ assert.ok(noAcOn.includes(generic) && noAcOn === noAcOff);
349
+ });
350
+
331
351
  test('TASK_2391: subagent prompt gets the Rust check only for a Rust-scoped card', () => {
332
352
  const rustCard = JSON.stringify({ id: 'TASK_9002', title: 'R', scope_paths: ['dashboard/src-tauri/src/x.rs'] });
333
353
  const withRust = buildPrompt(loadDoSkill(), 'TASK_9002', rustCard, { isSubagent: true, taskId: 'TASK_9002', rustCoreCheck: () => true });
@@ -24,8 +24,6 @@ load_order:
24
24
  description: "GEN-009: 跨平台相容原則 - Windows/macOS/Linux 三平台相容性要求"
25
25
  - path: "general/agent-workflow.yaml"
26
26
  description: "Agent 工作流程 - 記憶點優先 + 邊界意識 + Context Integrity Canary(AGT-001 至 AGT-009)"
27
- - path: "general/sunny-access-control.yaml"
28
- description: "SEC-010: Sunny 系統存取控制 - 禁止其他 Agent 存取 .system/sunny/"
29
27
  - path: "general/synapse-code-intelligence.yaml"
30
28
  description: "SYN-001~007: 代碼導航 — kenaz_lsp_* 代碼導航、Grep/Glob 文字與概念搜尋。記憶層 synapse_* 工具已於 2026-10-11 移除(TASK_2411)"
31
29
  # 2026-10-11 (TASK_2411): synapse-cognitive-fusion.yaml, cortex-tools.yaml, memory-source-priority.yaml, memory-language.yaml deleted - memory system removed.
@@ -41,14 +39,6 @@ load_order:
41
39
  description: "VERIFY-TGT-001~002: Rust 驗證正典 CARGO_TARGET_DIR — 單一共用目錄(dev/scripts/verify-target-dir.js),禁止自創目錄;並行驗證被 cargo 檔案鎖串行化屬正常等待"
42
40
  - path: "general/task-pool-routing.yaml"
43
41
  description: "POOL-001~003: 本地任務 vs 任務池 路由規則 — 詞彙驅動路由、術語一致性、Pool 操作安全(publish/claim 破壞性語義)"
44
- - path: "general/pipeline.yaml"
45
- description: "Pipeline 系統規則 - 任務管理規範"
46
- - path: "pipeline/PPL-002-completed-task-archival.yaml"
47
- description: "已完成任務封存規則 - 防止佇列污染"
48
- - path: "pipeline/auto-update.yaml"
49
- description: "PPL-003 - 任務完成後必須更新 Pipeline 系統(Critical)"
50
- - path: "pipeline/task-execution.yaml"
51
- description: "PPL-101 至 PPL-113 - Pipeline 任務執行規範(拆分、依賴、並行、回歸保護等)"
52
42
 
53
43
  # Code 規則組 - 從核心原則到具體實踐
54
44
  - path: "code/principles.yaml"
@@ -70,14 +60,6 @@ load_order:
70
60
 
71
61
  - path: "design/ui.yaml"
72
62
  description: "UI 設計規範"
73
- - path: "design/ux.yaml"
74
- description: "UX 設計原則"
75
- - path: "design/colors.yaml"
76
- description: "色彩系統"
77
- - path: "architecture/decisions.yaml"
78
- description: "架構決策記錄"
79
- - path: "business/constraints.yaml"
80
- description: "業務邏輯約束"
81
63
 
82
64
  # 嚴重程度定義
83
65
  severity_levels: