kenaz 0.3.17 → 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 (316) 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/hooks/synapse-first-investigation.js +15 -0
  7. package/core/.claude/skills/architect_agent/SKILL.md +11 -92
  8. package/core/.claude/skills/assessment_agent/SKILL.md +0 -1
  9. package/core/.claude/skills/backend_engineer/SKILL.md +3 -6
  10. package/core/.claude/skills/code_reviewer/SKILL.md +1 -1
  11. package/core/.claude/skills/code_reviewer/patterns.yaml +1 -1
  12. package/core/.claude/skills/designer/SKILL.md +3 -6
  13. package/core/.claude/skills/dev_agent/SKILL.md +30 -491
  14. package/core/.claude/skills/developer/SKILL.md +3 -6
  15. package/core/.claude/skills/feature_agent/SKILL.md +3 -17
  16. package/core/.claude/skills/product_manager/SKILL.md +29 -1064
  17. package/core/.claude/skills/qa_engineer/SKILL.md +12 -17
  18. package/core/.claude/skills/spec_optimizer/SKILL.md +25 -275
  19. package/core/.claude/skills/sre_agent/SKILL.md +3 -6
  20. package/core/.collaboration/README.md +2 -3
  21. package/core/.collaboration/core/task-api.js +5 -138
  22. package/core/.collaboration/docs/CLI.md +0 -2
  23. package/core/.failure/INDEX.yaml +6 -40
  24. package/core/.failure/agent-execution/FAIL-013-search-cwd-drift-and-case-mismatch.yaml +1 -1
  25. package/core/.failure/tools/README.md +9 -227
  26. package/core/.formation/bifrost/exec.js +29 -4
  27. package/core/.formation/bifrost/launch.js +1 -6
  28. package/core/.formation/bifrost/launch.test.js +1 -1
  29. package/core/.formation/bifrost/provider-launch.js +18 -11
  30. package/core/.formation/bifrost/provider-launch.test.js +26 -2
  31. package/core/.formation/bifrost/shared/ansuz-boot-policy.js +3 -3
  32. package/core/.formation/bifrost/shared/ansuz-boot-policy.test.js +1 -2
  33. package/core/.formation/tools/__tests__/dispatch-cwd-project.test.js +42 -0
  34. package/core/.formation/tools/__tests__/m1-hmap-convid.test.js +1 -124
  35. package/core/.formation/tools/__tests__/subagent-executor.test.js +3 -2
  36. package/core/.formation/tools/dispatch.js +15 -2
  37. package/core/.formation/tools/loader.js +17 -140
  38. package/core/.formation/tools/post-complete.js +0 -31
  39. package/core/.rule/INDEX.yaml +0 -18
  40. package/core/.rule/general/agent-workflow.yaml +14 -164
  41. package/core/.rule/general/project-self-containment.yaml +8 -19
  42. package/core/.rule/general/rust-verify-target.yaml +6 -5
  43. package/core/.rule/general/synapse-code-intelligence.yaml +3 -3
  44. package/core/.specialist/Amelia/capability.yaml +12 -12
  45. package/core/.specialist/Amelia/workflow.yaml +2 -2
  46. package/core/.specialist/Ansuz/SYSTEM_DOC.md +20 -578
  47. package/core/.specialist/Ansuz/cognitive_principles.yaml +2 -3
  48. package/core/.specialist/Ansuz/concepts/boot_rules_summary.yaml +0 -1
  49. package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +1 -2
  50. package/core/.specialist/Ansuz/core.yaml +6 -108
  51. package/core/.specialist/Ansuz/guards/lesson_guards.yaml +0 -3
  52. package/core/.specialist/Ansuz/identity_contract.yaml +0 -1
  53. package/core/.specialist/Ansuz/knowledge.yaml +0 -4
  54. package/core/.specialist/Ansuz/rule.yaml +0 -1
  55. package/core/.specialist/Ansuz/tools/handoff-session-identity.test.js +6 -150
  56. package/core/.specialist/tests/runtime-discovery/README.md +1 -1
  57. package/core/.specialist/tools/__tests__/task-2374-usage-counter.test.js +1 -12
  58. package/core/.specialist/tools/prompt-files.test.js +1 -0
  59. package/core/.specialist/tools/task-bridge.js +1 -66
  60. package/core/.system/README.md +11 -552
  61. package/core/.system/collaboration/README.md +10 -174
  62. package/core/CLAUDE.md +1 -6
  63. package/core/KENAZ_CORE_VERSION +1 -1
  64. package/core/dev/scripts/js-yaml-resolution.test.js +1 -1
  65. package/core/dev/scripts/run-rust-tests.js +30 -46
  66. package/core/dev/scripts/run-rust-tests.test.js +17 -0
  67. package/core/dev/scripts/rust-verify-worktree.js +4 -4
  68. package/core/dev/scripts/verify-target-dir.js +6 -6
  69. package/core/plugins/kenaz/INVENTORY.md +8 -8
  70. package/core/plugins/kenaz/commands/architect.md +1 -2
  71. package/core/plugins/kenaz/commands/spec.md +1 -1
  72. package/core/plugins/kenaz/commands//347/224/242/345/223/201.md +1 -1
  73. package/core/plugins/kenaz/guidance.md +1 -1
  74. package/core/plugins/kenaz/scripts/sync.js +1 -1
  75. package/core/plugins/kenaz/scripts/ygg.js +6 -0
  76. package/core/plugins/kenaz/skills/architect_agent/SKILL.md +11 -92
  77. package/core/plugins/kenaz/skills/assessment_agent/SKILL.md +0 -1
  78. package/core/plugins/kenaz/skills/backend_engineer/SKILL.md +3 -6
  79. package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +1 -1
  80. package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +1 -1
  81. package/core/plugins/kenaz/skills/designer/SKILL.md +3 -6
  82. package/core/plugins/kenaz/skills/dev_agent/SKILL.md +30 -491
  83. package/core/plugins/kenaz/skills/developer/SKILL.md +3 -6
  84. package/core/plugins/kenaz/skills/feature_agent/SKILL.md +3 -17
  85. package/core/plugins/kenaz/skills/product_manager/SKILL.md +29 -1064
  86. package/core/plugins/kenaz/skills/qa_engineer/SKILL.md +12 -17
  87. package/core/plugins/kenaz/skills/spec_optimizer/SKILL.md +25 -275
  88. package/core/plugins/kenaz/skills/sre_agent/SKILL.md +3 -6
  89. package/package.json +6 -6
  90. package/core/.claude/commands/archive.md +0 -24
  91. package/core/.claude/commands/init.md +0 -112
  92. package/core/.claude/commands/legacy_dev.md +0 -18
  93. package/core/.claude/commands/product-review.md +0 -131
  94. package/core/.claude/commands/strategy.md +0 -141
  95. package/core/.claude/commands/sunny.md +0 -1
  96. package/core/.claude/commands//347/224/242/345/223/201/346/252/242/350/246/226.md +0 -129
  97. package/core/.claude/commands//351/200/262/345/272/246.md +0 -15
  98. package/core/.claude/hooks/__tests__/substrate.test.js +0 -122
  99. package/core/.claude/hooks/_substrate-paths.js +0 -95
  100. package/core/.claude/hooks/context-check.js +0 -51
  101. package/core/.claude/hooks/fatigue-check.js +0 -52
  102. package/core/.claude/hooks/handoff-snapshot-on-commit.js +0 -154
  103. package/core/.claude/hooks/handoff-snapshot-on-question.js +0 -158
  104. package/core/.claude/hooks/handoff-staleness.js +0 -197
  105. package/core/.claude/hooks/jev-thalamus-context.js +0 -17
  106. package/core/.claude/hooks/jev-thalamus-context.test.js +0 -41
  107. package/core/.claude/hooks/lease-release.js +0 -119
  108. package/core/.claude/hooks/lease-touch.js +0 -96
  109. package/core/.claude/hooks/reflex-feedback.js +0 -5
  110. package/core/.claude/hooks/session-init.ps1 +0 -49
  111. package/core/.claude/hooks/sound-event.js +0 -70
  112. package/core/.claude/hooks/sound-event.sh +0 -37
  113. package/core/.claude/hooks/stream-delta.js +0 -169
  114. package/core/.claude/no-hooks.settings.json +0 -3
  115. package/core/.claude/skills/legacy_dev/SKILL.md +0 -202
  116. package/core/.claude/skills/sunny/SKILL.md +0 -74
  117. package/core/.collaboration/cli/commands/assign.js +0 -39
  118. package/core/.collaboration/cli/commands/claim.js +0 -33
  119. package/core/.collaboration/cli/commands/complete.js +0 -44
  120. package/core/.collaboration/cli/commands/create.js +0 -110
  121. package/core/.collaboration/cli/commands/get.js +0 -83
  122. package/core/.collaboration/cli/commands/index.js +0 -52
  123. package/core/.collaboration/cli/commands/list.js +0 -51
  124. package/core/.collaboration/cli/commands/lock.js +0 -45
  125. package/core/.collaboration/cli/commands/rebuild.js +0 -39
  126. package/core/.collaboration/cli/commands/search.js +0 -61
  127. package/core/.collaboration/cli/commands/stats.js +0 -61
  128. package/core/.collaboration/cli/commands/transfer.js +0 -41
  129. package/core/.collaboration/cli/commands/unlock.js +0 -48
  130. package/core/.collaboration/cli/formatters/help-formatter.js +0 -70
  131. package/core/.collaboration/cli/formatters/index.js +0 -7
  132. package/core/.collaboration/cli/task-cli.js +0 -46
  133. package/core/.collaboration/cli/utils/arg-parser.js +0 -74
  134. package/core/.collaboration/cli/utils/git-user.js +0 -25
  135. package/core/.collaboration/cli/utils/index.js +0 -9
  136. package/core/.collaboration/cli/utils/output.js +0 -10
  137. package/core/.collaboration/config/default.yaml +0 -189
  138. package/core/.collaboration/core/conflict-resolver.js +0 -461
  139. package/core/.collaboration/core/conflict-resolver.test.js +0 -389
  140. package/core/.collaboration/core/migrate-legacy.js +0 -260
  141. package/core/.collaboration/docs/API.md +0 -787
  142. package/core/.collaboration/docs/ARCHITECTURE.md +0 -596
  143. package/core/.collaboration/docs/FAQ.md +0 -695
  144. package/core/.collaboration/docs/MIGRATION.md +0 -804
  145. package/core/.collaboration/docs/examples/README.md +0 -320
  146. package/core/.collaboration/docs/examples/advanced-queries.js +0 -261
  147. package/core/.collaboration/docs/examples/basic-usage.js +0 -184
  148. package/core/.collaboration/docs/examples/team-workflow.js +0 -352
  149. package/core/.failure/agent-execution/FAIL-001-agent-no-output.yaml +0 -391
  150. package/core/.failure/agent-execution/FAIL-007-pm-lead-wrong-team-invocation.yaml +0 -0
  151. package/core/.failure/agent-execution/FAIL-010-phase0-poem-format-skipped.yaml +0 -28
  152. package/core/.failure/agent-execution/FAIL-010-skipping-cognitive-checkpoint-after-verification.yaml +0 -59
  153. package/core/.failure/agent-execution/FAIL-012-grep-shortcut-during-investigation.yaml +0 -85
  154. package/core/.failure/architecture/FAIL-006-python-version-compatibility.yaml +0 -445
  155. package/core/.failure/pipeline/FAIL-003-queue-pollution.yaml +0 -335
  156. package/core/.failure/pipeline/README.md +0 -95
  157. package/core/.failure/retired/FAIL-009-skipping-synapse-pulse-before-code-change.yaml +0 -49
  158. package/core/.failure/tools/USAGE_GUIDE.md +0 -323
  159. package/core/.failure/tools/check_failures.ps1 +0 -387
  160. package/core/.failure/tools/log_failure.ps1 +0 -474
  161. package/core/.formation/.episode-input.json +0 -10
  162. package/core/.formation/.merge-lock +0 -1
  163. package/core/.formation/lead.yaml +0 -118
  164. package/core/.formation/legacy.bak/designer.yaml +0 -57
  165. package/core/.formation/legacy.bak/pm.yaml +0 -57
  166. package/core/.formation/legacy.bak/qa.yaml +0 -57
  167. package/core/.formation/legacy.bak/review.yaml +0 -62
  168. package/core/.formation/legacy.bak/scheduler.yaml +0 -129
  169. package/core/.formation/legacy.bak/sre.yaml +0 -57
  170. package/core/.formation/role-map.yaml +0 -274
  171. package/core/.formation/roles/_shared/modules/code_review.yaml +0 -98
  172. package/core/.formation/roles/_shared/modules/security.yaml +0 -103
  173. package/core/.formation/roles/_shared/modules/testing.yaml +0 -14
  174. package/core/.formation/roles/backend/role.yaml +0 -93
  175. package/core/.formation/roles/code_reviewer/role.yaml +0 -102
  176. package/core/.formation/roles/database_optimizer/role.yaml +0 -107
  177. package/core/.formation/roles/designer/role.yaml +0 -66
  178. package/core/.formation/roles/developer/role.yaml +0 -75
  179. package/core/.formation/roles/frontend/role.yaml +0 -134
  180. package/core/.formation/roles/incident_response/role.yaml +0 -89
  181. package/core/.formation/roles/qa/role.yaml +0 -89
  182. package/core/.formation/roles/security/role.yaml +0 -87
  183. package/core/.formation/roles/sre/role.yaml +0 -66
  184. package/core/.formation/skills/coordinator.yaml +0 -52
  185. package/core/.formation/solo/diagnosis.yaml +0 -78
  186. package/core/.formation/solo/strategy.yaml +0 -121
  187. package/core/.formation/tests/hello.txt +0 -2
  188. package/core/.formation/tools/coordinator.js +0 -350
  189. package/core/.formation/tools/jev-correction.js +0 -214
  190. package/core/.formation/tools/jev-correction.test.js +0 -178
  191. package/core/.formation/tools/log-reader.js +0 -232
  192. package/core/.formation/tools/revert-daemon-state.js +0 -162
  193. package/core/.formation/tools/test-drift.js +0 -148
  194. package/core/.formation/unit/heimdall.yaml +0 -96
  195. package/core/.formation/unit/plan.yaml +0 -67
  196. package/core/.formation/unit/think.yaml +0 -58
  197. package/core/.kenaz/MUNINN_SNAPSHOT9_EPISODE.json +0 -32
  198. package/core/.kenaz/MUNINN_SNAPSHOT9_RUNE.json +0 -23
  199. package/core/.kenaz/MUNINN_SNAPSHOT9_SUMMARY.md +0 -89
  200. package/core/.kenaz/ash_TASK_1428-20260507.json +0 -14
  201. package/core/.kenaz/muninn_episode_write.ps1 +0 -17
  202. package/core/.kenaz/muninn_snapshot11_rune_candidates.md +0 -63
  203. package/core/.kenaz/muninn_snapshot1_rune_candidates.md +0 -49
  204. package/core/.kenaz/runes_TASK_1428.json +0 -57
  205. package/core/.kenaz/tmp_heimdall_proposal_8d3e392.json +0 -13
  206. package/core/.kenaz/tmp_props.json +0 -1
  207. package/core/.kenaz/tmp_sounddrop_t1_prompt.txt +0 -371
  208. package/core/.rule/MECHANIZED.md +0 -61
  209. package/core/.rule/general/cortex-memory-recall.yaml +0 -446
  210. package/core/.rule/general/pipeline.yaml +0 -246
  211. package/core/.rule/general/sunny-access-control.yaml +0 -60
  212. package/core/.rule/general/system-invariants.yaml +0 -231
  213. package/core/.rule/pipeline/PPL-002-completed-task-archival.yaml +0 -528
  214. package/core/.rule/pipeline/auto-update.yaml +0 -282
  215. package/core/.rule/pipeline/task-execution.yaml +0 -365
  216. package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +0 -65
  217. package/core/.specialist/Ansuz/experiments/boot_ablation_replay.js +0 -197
  218. package/core/.specialist/Ansuz/tools/bootstrap-anchors.js +0 -289
  219. package/core/.specialist/Ansuz/workflow.yaml +0 -1225
  220. package/core/.specialist/experiments/rg_benchmark.js +0 -523
  221. package/core/.specialist/experiments/rg_benchmark_results.json +0 -27
  222. package/core/.specialist/tools/jev-optional-context.js +0 -107
  223. package/core/.specialist/tools/jev-optional-context.test.js +0 -152
  224. package/core/.specialist/tools/memory-action-log.js +0 -839
  225. package/core/.specialist/tools/phase-gate.js +0 -3534
  226. package/core/.specialist/tools/phase-gate.test.js +0 -85
  227. package/core/.specialist/tools/precompute-runner.js +0 -444
  228. package/core/.specialist/tools/rule-engine.js +0 -621
  229. package/core/.specialist/tools/task-bridge-jev.test.js +0 -170
  230. package/core/.synapse/memory-shared.json.sync +0 -1
  231. package/core/.system/agent-skills/README.md +0 -724
  232. package/core/.system/cli-tools/README.md +0 -724
  233. package/core/.system/cortex/MODULES.md +0 -1046
  234. package/core/.system/dashboard/FRONTEND.md +0 -812
  235. package/core/.system/dashboard/README.md +0 -737
  236. package/core/.system/failure/README.md +0 -723
  237. package/core/.system/formation/LEGACY_SKILLS.md +0 -974
  238. package/core/.system/formation/POST_COMPLETE.md +0 -602
  239. package/core/.system/formation/README.md +0 -1094
  240. package/core/.system/formation/TOOLS_REFERENCE.md +0 -1145
  241. package/core/.system/kenaz/README.md +0 -787
  242. package/core/.system/memory/README.md +0 -719
  243. package/core/.system/pipeline/README.md +0 -712
  244. package/core/.system/rule/README.md +0 -794
  245. package/core/.system/sunny/README.md +0 -476
  246. package/core/.system/sunny/RULES.json +0 -89
  247. package/core/.system/sunny/knowledge/INDEX.md +0 -37
  248. package/core/.system/sunny/knowledge/agent-teams-api.md +0 -0
  249. package/core/.system/sunny/knowledge/ansuz-specialist.md +0 -500
  250. package/core/.system/sunny/knowledge/claude-cli-spawn.md +0 -177
  251. package/core/.system/sunny/knowledge/collaboration-api.md +0 -925
  252. package/core/.system/sunny/teammates/README.md +0 -107
  253. package/core/.system/sunny/teammates/how_to_use.yaml +0 -101
  254. package/core/.system/sunny/teammates/tools/headless/launcher.js +0 -318
  255. package/core/.system/sunny/teammates/tools/powershell/launcher.js +0 -241
  256. package/core/.system/sunny/teammates/tools/powershell/start.bat +0 -50
  257. package/core/.system/sunny/teammates/tools/utils/env.js +0 -96
  258. package/core/.system/sunny/teammates/tools/utils/format-output.js +0 -329
  259. package/core/.system/sunny/tools.json +0 -101
  260. package/core/dev/scripts/cleanup-queue.js +0 -184
  261. package/core/dev/scripts/cleanup_queue.js +0 -86
  262. package/core/dev/scripts/legacy/README.md +0 -39
  263. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +0 -536
  264. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +0 -1978
  265. package/core/dev/scripts/matrix/CLI_README.md +0 -214
  266. package/core/dev/scripts/matrix/README.md +0 -310
  267. package/core/dev/scripts/matrix/package-lock.json +0 -4266
  268. package/core/dev/scripts/matrix/package.json +0 -55
  269. package/core/dev/scripts/matrix/src/abbreviations.ts +0 -295
  270. package/core/dev/scripts/matrix/src/cli/commands/convert.ts +0 -211
  271. package/core/dev/scripts/matrix/src/cli/commands/create.ts +0 -95
  272. package/core/dev/scripts/matrix/src/cli/commands/delete.ts +0 -102
  273. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +0 -140
  274. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +0 -173
  275. package/core/dev/scripts/matrix/src/cli/commands/query.ts +0 -261
  276. package/core/dev/scripts/matrix/src/cli/commands/read.ts +0 -77
  277. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +0 -168
  278. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +0 -126
  279. package/core/dev/scripts/matrix/src/cli/commands/update.ts +0 -147
  280. package/core/dev/scripts/matrix/src/cli/commands/validate.ts +0 -275
  281. package/core/dev/scripts/matrix/src/cli/index.ts +0 -350
  282. package/core/dev/scripts/matrix/src/cli/utils/file-handler.ts +0 -72
  283. package/core/dev/scripts/matrix/src/cli/utils/logger.ts +0 -65
  284. package/core/dev/scripts/matrix/src/index.ts +0 -62
  285. package/core/dev/scripts/matrix/src/indexer/tag-indexer.ts +0 -165
  286. package/core/dev/scripts/matrix/src/parser.ts +0 -281
  287. package/core/dev/scripts/matrix/src/serializer.ts +0 -151
  288. package/core/dev/scripts/matrix/src/validator.ts +0 -301
  289. package/core/dev/scripts/matrix/tests/abbreviations.test.ts +0 -122
  290. package/core/dev/scripts/matrix/tests/cli.test.ts +0 -177
  291. package/core/dev/scripts/matrix/tests/convert.test.ts +0 -143
  292. package/core/dev/scripts/matrix/tests/parser.test.ts +0 -203
  293. package/core/dev/scripts/matrix/tests/query.test.ts +0 -85
  294. package/core/dev/scripts/matrix/tests/serializer.test.ts +0 -197
  295. package/core/dev/scripts/matrix/tests/validate.test.ts +0 -141
  296. package/core/dev/scripts/matrix/tests/validator.test.ts +0 -167
  297. package/core/dev/scripts/matrix/tsconfig.json +0 -20
  298. package/core/dev/scripts/matrix/types/index.d.ts +0 -111
  299. package/core/dev/scripts/matrix/types/index.d.ts.map +0 -1
  300. package/core/dev/scripts/matrix/types/index.js +0 -8
  301. package/core/dev/scripts/matrix/types/index.js.map +0 -1
  302. package/core/dev/scripts/matrix/types/index.ts +0 -124
  303. package/core/dev/scripts/memory-usage-report.js +0 -152
  304. package/core/dev/scripts/probe/ansuz-scaffold-ablation.js +0 -579
  305. package/core/dev/scripts/probe/ansuz-scaffold-ablation.test.js +0 -244
  306. package/core/dev/scripts/probe/hook-latency-probe.js +0 -315
  307. package/core/dev/scripts/probe/hook-latency-probe.test.js +0 -105
  308. package/core/dev/scripts/probe/prediction-signal-auc.js +0 -483
  309. package/core/dev/scripts/probe/prediction-signal-auc.test.js +0 -200
  310. package/core/dev/scripts/test-pty-ws-tunnel.js +0 -229
  311. package/core/dev/scripts/validate_pipeline.js +0 -197
  312. package/core/dev/spikes/task2145-evidence-20261004/backend-log-excerpt.txt +0 -56
  313. package/core/dev/spikes/task2145-evidence-20261004/graph.shadow +0 -0
  314. package/core/dev/spikes/task2145-evidence-20261004/graph.wal +0 -0
  315. package/core/dev/spikes/task2145-evidence-20261004/graph_corrupt_suspect.marker +0 -1
  316. package/core/plugins/kenaz/commands/strategy.md +0 -141
@@ -1,178 +0,0 @@
1
- 'use strict';
2
- const { test } = require('node:test');
3
- const assert = require('node:assert/strict');
4
- const fs = require('fs');
5
- const os = require('os');
6
- const path = require('path');
7
- const vm = require('vm');
8
- const helper = require('./jev-correction');
9
- const { applyJevCorrection, buildBatch, makeRequest, makeBatchRequest, requestJev, requestBatch, safeText, MODEL } = helper;
10
- const KEY = 'synthetic-test-credential';
11
- function setup(t) {
12
- const projectPath = fs.mkdtempSync(path.join(os.tmpdir(), 'jev-correction-'));
13
- t.after(() => fs.rmSync(projectPath, { recursive: true, force: true }));
14
- const config = path.join(projectPath, '.kenaz', 'dashboard-models.json');
15
- fs.mkdirSync(path.dirname(config));
16
- const toggle = value => fs.writeFileSync(config, JSON.stringify({ jevCorrectionEnabled: value }));
17
- toggle(true);
18
- const lines = ['[12:00:00][Agent] I will change production.', '[12:00:01][User] Change the development version instead.', '[12:00:02][Agent] I will inspect the development files.', '[12:00:03][User] What is the current status?'];
19
- const userMessages = [{ idx: 0, lineIndex: 1, text: lines[1].split('[User] ')[1] }, { idx: 1, lineIndex: 3, text: lines[3].split('[User] ')[1] }];
20
- const baseline = [{ idx: 0, translated: 'Existing English translation.', labels: ['delegation'] }, { idx: 1, translated: 'Existing second translation.', labels: ['correction', 'question', 'length_too_short'] }];
21
- return { projectPath, config, toggle, lines, userMessages, baseline };
22
- }
23
- function body(ids = ['message_0', 'message_1'], probabilities = [0.9, 0.1]) {
24
- return { model: MODEL, answers: Object.fromEntries(ids.map((id, i) => [id, { type: 'noul', noul: probabilities[i] ?? 0.7 }])), usage: { input_tokens: 10, output_tokens: 2 } };
25
- }
26
- const spawned = (response = body()) => ({ status: 0, stdout: JSON.stringify({ status: 'ok', body: response }) });
27
- const options = spawnSync => ({ key: KEY, spawnSync });
28
-
29
- test('OFF, malformed config, missing/invalid key and another project never spawn', t => {
30
- const ctx = setup(t); let calls = 0;
31
- const deps = options(() => { calls++; throw Error('must not spawn'); });
32
- for (const flag of [false, undefined, 'true', 1]) { ctx.toggle(flag); assert.equal(applyJevCorrection(ctx, deps), ctx.baseline); }
33
- fs.writeFileSync(ctx.config, '{'); assert.equal(applyJevCorrection(ctx, deps), ctx.baseline);
34
- fs.unlinkSync(ctx.config); assert.equal(applyJevCorrection(ctx, deps), ctx.baseline);
35
- ctx.toggle(true);
36
- for (const key of ['', ' ', '\n', 'value\n', 'a b']) assert.equal(applyJevCorrection(ctx, { ...deps, key }), ctx.baseline);
37
- assert.equal(applyJevCorrection({ ...ctx, projectPath: path.join(ctx.projectPath, 'other') }, deps), ctx.baseline);
38
- assert.equal(applyJevCorrection({ ...ctx, projectPath: undefined }, deps), ctx.baseline);
39
- assert.equal(calls, 0);
40
- });
41
- test('active decisions add/remove correction, preserve other labels/translations, never mutate baseline', t => {
42
- const ctx = setup(t), original = structuredClone(ctx.baseline);
43
- const result = applyJevCorrection(ctx, options(() => spawned()));
44
- assert.deepEqual(result.map(x => x.labels), [['correction', 'delegation'], ['question', 'length_too_short']]);
45
- assert.deepEqual(result.map(x => x.translated), original.map(x => x.translated));
46
- assert.deepEqual(ctx.baseline, original);
47
- assert.notEqual(result, ctx.baseline);
48
- });
49
- test('existing secondary correction becomes primary without reordering remaining labels or fields', t => {
50
- const ctx = setup(t);
51
- ctx.baseline[0] = { ...ctx.baseline[0], labels: ['delegation', 'question', 'correction', 'challenge'], extra: 'preserved' };
52
- const original = structuredClone(ctx.baseline);
53
- const result = applyJevCorrection(ctx, options(() => spawned()));
54
- assert.deepEqual(result[0], { ...original[0], labels: ['correction', 'delegation', 'question', 'challenge'] });
55
- assert.deepEqual(ctx.baseline, original);
56
- ctx.toggle(false);
57
- assert.equal(applyJevCorrection(ctx, options(() => spawned())), ctx.baseline);
58
- ctx.toggle(true);
59
- assert.equal(applyJevCorrection(ctx, options(() => ({ status: 1 }))), ctx.baseline);
60
- });
61
- test('null baseline supplies only positive correction without invented translation; threshold is inclusive', t => {
62
- const ctx = setup(t);
63
- assert.deepEqual(applyJevCorrection({ ...ctx, baseline: null }, options(() => spawned(body(undefined, [0.5, 0.499])))), [{ idx: 0, labels: ['correction'] }]);
64
- assert.deepEqual(applyJevCorrection({ ...ctx, baseline: [] }, options(() => spawned())), [{ idx: 0, labels: ['correction'] }]);
65
- const partial = applyJevCorrection({ ...ctx, baseline: [ctx.baseline[1]] }, options(() => spawned()));
66
- assert.deepEqual(partial.find(item => item.idx === 0), { idx: 0, labels: ['correction'] });
67
- });
68
- test('hidden bounded Node child uses stdin; no credential or message in argv', t => {
69
- const ctx = setup(t); let calls = 0;
70
- applyJevCorrection(ctx, options((command, args, opts) => {
71
- calls++;
72
- assert.equal(command, process.execPath); assert.equal(args.length, 2); assert.equal(args[1], '--worker');
73
- assert.equal(opts.timeout, 1500); assert.equal(opts.maxBuffer, 65536); assert.equal(opts.windowsHide, true); assert.equal(opts.shell, false);
74
- assert.equal(JSON.stringify(args).includes(ctx.userMessages[0].text), false);
75
- assert.equal(JSON.stringify({ args, opts }).includes(KEY), false);
76
- const packet = JSON.parse(opts.input); assert.equal(packet.requests.length, 2);
77
- assert.equal(packet.requests[0].state.message, ctx.userMessages[0].text);
78
- return spawned();
79
- }));
80
- assert.equal(calls, 1);
81
- });
82
- test('in-flight disable discards results and preserves baseline reference', t => {
83
- const ctx = setup(t);
84
- assert.equal(applyJevCorrection(ctx, options(() => { ctx.toggle(false); return spawned(); })), ctx.baseline);
85
- assert.equal(JSON.parse(fs.readFileSync(path.join(ctx.projectPath, '.kenaz/jev/decisions.jsonl'), 'utf8')).fallback_reason, 'disabled_in_flight');
86
- });
87
- test('malformed JSON, wrong model/IDs, bounds/types/usage and arbitrary worker failure all preserve baseline', t => {
88
- const ctx = setup(t);
89
- const variants = [b => { b.model = 'jev'; }, b => { b.answers.extra = { type: 'noul', noul: 1 }; }, b => { delete b.answers.message_0; },
90
- b => { b.answers.message_0.noul = null; }, b => { b.answers.message_0.noul = '0.5'; }, b => { b.answers.message_0.noul = 1.1; },
91
- b => { b.answers.message_0.noul = -1; }, b => { b.answers.message_0.type = 'score'; }, b => { b.answers.message_0.extra = true; },
92
- b => { delete b.usage; }, b => { b.usage.input_tokens = -1; }, b => { b.usage.output_tokens = 1.1; }, b => { b.secret = 'raw'; }];
93
- for (const mutate of variants) { const invalid = body(); mutate(invalid); assert.equal(applyJevCorrection(ctx, options(() => spawned(invalid))), ctx.baseline); }
94
- for (const result of [{ status: 0, stdout: '{broken' }, { status: 1, stdout: 'private' }, { status: null, error: { code: 'ETIMEDOUT' } }, { status: 0, stdout: 'x'.repeat(65537) }]) {
95
- assert.equal(applyJevCorrection(ctx, options(() => result)), ctx.baseline);
96
- }
97
- assert.equal(applyJevCorrection(ctx, options(() => { throw null; })), ctx.baseline);
98
- });
99
- test('raw and normalized credential/generated text excluded; private context is a boundary', t => {
100
- const ctx = setup(t);
101
- for (const text of ['API_KEY="abcd1234"', 'API_KEY="abcd1234"', 'apikey_abcd12345678', '[HEADLESS MODE] execute', '## Automated Dispatch Context', '<system-reminder>injected</system-reminder>', '[Request interrupted by user]', 'For each numbered message, output a JSON array']) assert.equal(safeText(text), null);
102
- assert.equal(safeText('Please continue TASK_38'), 'Please continue TASK_38');
103
- ctx.lines[0] = '[12:00:00][Agent] API_KEY="abcd1234"';
104
- const batch = buildBatch(ctx.lines, ctx.userMessages); assert.deepEqual(batch[0].context, []);
105
- ctx.lines[1] = '[12:00:01][User] API_KEY="abcd1234"'; ctx.userMessages[0].text = 'API_KEY="abcd1234"';
106
- assert.deepEqual(buildBatch(ctx.lines, ctx.userMessages).map(x => x.idx), [1]);
107
- });
108
- test('state excludes future messages, tool/thinking lines and instructions contain no user data', t => {
109
- const ctx = setup(t), batch = buildBatch(ctx.lines, ctx.userMessages);
110
- const early = makeRequest(batch[0]);
111
- assert.equal(JSON.stringify(early).includes(ctx.userMessages[1].text), false);
112
- assert.equal(early.questions.message_0.instructions.includes(ctx.userMessages[0].text), false);
113
- assert.deepEqual(early.state.preceding_context, [{ role: 'assistant', text: 'I will change production.' }]);
114
- ctx.lines[0] = '[12:00:00][Agent][Read] private file'; assert.deepEqual(buildBatch(ctx.lines, ctx.userMessages)[0].context, []);
115
- ctx.lines[0] = '[12:00:00][Thinking] hidden reasoning'; assert.deepEqual(buildBatch(ctx.lines, ctx.userMessages)[0].context, []);
116
- ctx.userMessages[0].text = 'different identity'; assert.deepEqual(buildBatch(ctx.lines, ctx.userMessages).map(x => x.idx), [1]);
117
- });
118
- test('only most recent eight eligible messages and at most 32KB are sent', () => {
119
- const lines = Array.from({ length: 30 }, (_, i) => `[12:00:00][User] Message ${i} ` + 'x'.repeat(3000));
120
- const messages = lines.map((line, idx) => ({ idx, lineIndex: idx, text: line.split('[User] ')[1] }));
121
- const batch = buildBatch(lines, messages);
122
- assert.ok(batch.length > 0 && batch.length <= 8); assert.equal(batch.at(-1).idx, 29);
123
- assert.ok(Buffer.byteLength(JSON.stringify(makeBatchRequest(batch))) <= 32768);
124
- });
125
- test('typed Noul request fixed endpoint and redirect rejection; no network without valid key', async () => {
126
- const request = makeRequest({ idx: 0, message: 'test correction', context: [] }); let calls = 0;
127
- const fetchImpl = async (url, options) => {
128
- calls++; assert.equal(url, helper.ENDPOINT); assert.equal(options.redirect, 'error'); assert.equal(options.method, 'POST');
129
- assert.equal(options.headers.Authorization, `Bearer ${KEY}`);
130
- return new Response(JSON.stringify(body(['message_0'])));
131
- };
132
- assert.equal((await requestJev(request, { key: '', fetchImpl })).status, 'missing_key'); assert.equal(calls, 0);
133
- assert.equal((await requestJev(request, { key: KEY, fetchImpl })).status, 'ok'); assert.equal(calls, 1);
134
- });
135
- test('401/429/529 differentiated; malformed JSON and oversize streamed body fail safely', async () => {
136
- const request = makeRequest({ idx: 0, message: 'test correction', context: [] });
137
- for (const status of [401, 429, 529]) assert.equal((await requestJev(request, { key: KEY, fetchImpl: async () => new Response('private body', { status }) })).status, `http_${status}`);
138
- for (const text of ['{broken', 'x'.repeat(65537)]) {
139
- const result = await requestJev(request, { key: KEY, fetchImpl: async () => new Response(text) });
140
- assert.deepEqual(result, { status: 'invalid_response' });
141
- }
142
- });
143
- test('deadline covers stalled fetch AND stalled body, even if mock ignores abort', async () => {
144
- const request = makeRequest({ idx: 0, message: 'test correction', context: [] });
145
- for (const fetchImpl of [() => new Promise(() => {}), async () => new Response(new ReadableStream({ start() {} }))]) {
146
- const start = Date.now(); const result = await requestJev(request, { key: KEY, deadlineMs: 25, fetchImpl });
147
- assert.deepEqual(result, { status: 'timeout' }); assert.ok(Date.now() - start < 1500);
148
- }
149
- });
150
- test('parallel isolated calls bounded to eight, all-or-none failure, aggregate usage', async t => {
151
- const ctx = setup(t), packet = makeBatchRequest(buildBatch(ctx.lines, ctx.userMessages)); let calls = 0;
152
- const fetchImpl = async (_, options) => {
153
- calls++; const r = JSON.parse(options.body); const id = Object.keys(r.questions)[0];
154
- assert.equal(Object.keys(r.questions).length, 1);
155
- if (id === 'message_0') assert.equal(JSON.stringify(r).includes(ctx.userMessages[1].text), false);
156
- return new Response(JSON.stringify(body([id])));
157
- };
158
- const result = await requestBatch(packet, { key: KEY, fetchImpl });
159
- assert.equal(result.status, 'ok'); assert.equal(calls, 2); assert.deepEqual(result.body.usage, { input_tokens: 20, output_tokens: 4 });
160
- assert.equal((await requestBatch(packet, { key: KEY, fetchImpl: async () => new Response('', { status: 429 }) })).status, 'http_429');
161
- assert.equal((await requestBatch({ ...packet, requests: Array(9).fill(packet.requests[0]) }, { key: KEY, fetchImpl })).status, 'invalid_response');
162
- const start = Date.now();
163
- assert.equal((await requestBatch(packet, { key: KEY, deadlineMs: 30, fetchImpl: () => new Promise(() => {}) })).status, 'timeout');
164
- assert.ok(Date.now() - start < 500, 'parallel calls share the deadline, not sequential timeouts');
165
- });
166
- test('bounded metadata-only telemetry and logging failure cannot break decisions', t => {
167
- const ctx = setup(t), deps = options(() => spawned());
168
- for (let i = 0; i < 160; i++) applyJevCorrection(ctx, deps);
169
- const file = path.join(ctx.projectPath, '.kenaz', 'jev', 'decisions.jsonl'), text = fs.readFileSync(file, 'utf8');
170
- assert.ok(Buffer.byteLength(text) <= 65536);
171
- for (const privateText of [KEY, ctx.projectPath, ...ctx.userMessages.map(x => x.text), 'Existing English']) assert.equal(text.includes(privateText), false);
172
- for (const line of text.trim().split('\n')) {
173
- const row = JSON.parse(line); assert.equal(row.mode, 'active'); assert.equal(row.status, 'applied');
174
- assert.ok(row.decisions.every(x => /^[a-f0-9]{64}$/.test(x.message_id)));
175
- }
176
- fs.rmSync(path.dirname(file), { recursive: true }); fs.writeFileSync(path.dirname(file), 'obstruction');
177
- assert.ok(applyJevCorrection(ctx, deps)[0].labels.includes('correction'));
178
- });
@@ -1,232 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Formation Log Reader
4
- *
5
- * 讀取 Formation log 檔案的尾端/頭端/錯誤行
6
- * 用途:讓 Ansuz 按需讀取 log,避免載入完整 log 爆 context
7
- *
8
- * Usage:
9
- * node .formation/tools/log-reader.js --log <path> --tail 50
10
- * node .formation/tools/log-reader.js --task TASK_XXX --tail 50
11
- * node .formation/tools/log-reader.js --latest unit --tail 30
12
- * node .formation/tools/log-reader.js --log <path> --errors
13
- *
14
- * Options:
15
- * --log <path> 直接讀指定 log 檔
16
- * --task TASK_XXX 在 .formation/logs/ 下搜尋包含 TASK_XXX 的最新 log
17
- * --latest <formation> 讀指定陣型目錄下最新的 log
18
- * --tail N 只輸出最後 N 行(預設 50)
19
- * --head N 只輸出前 N 行
20
- * --errors 只輸出包含 ERROR/FAILED/❌ 的行
21
- */
22
-
23
- const fs = require('fs');
24
- const path = require('path');
25
- const { LOG_DIR, getLogDir } = require('./utils');
26
-
27
- // ─────────────────────────────────────────
28
- // 尋找 log 檔案
29
- // ─────────────────────────────────────────
30
-
31
- /**
32
- * 搜尋包含 taskId 的最新 log 檔案
33
- * @param {string} taskId - TASK_XXX
34
- * @param {string} [projectPath] - 專案路徑(多專案支援)
35
- * @returns {string|null}
36
- */
37
- function findTaskLog(taskId, projectPath) {
38
- const logDir = projectPath ? getLogDir(projectPath) : LOG_DIR;
39
- const formations = ['unit', 'solo', 'squad', 'legion', 'misc'];
40
- let candidates = [];
41
-
42
- for (const formation of formations) {
43
- const dir = path.join(logDir, formation);
44
- if (!fs.existsSync(dir)) continue;
45
-
46
- const files = fs.readdirSync(dir)
47
- .filter(f => f.endsWith('.log'))
48
- .sort((a, b) => {
49
- const aErr = a.endsWith('.err.log') ? 1 : 0;
50
- const bErr = b.endsWith('.err.log') ? 1 : 0;
51
- return aErr - bErr;
52
- });
53
- for (const file of files) {
54
- const filePath = path.join(dir, file);
55
- try {
56
- const content = fs.readFileSync(filePath, 'utf-8');
57
- if (content.includes(taskId)) {
58
- const stats = fs.statSync(filePath);
59
- candidates.push({ path: filePath, mtime: stats.mtime });
60
- }
61
- } catch (e) {
62
- // skip unreadable files
63
- }
64
- }
65
- }
66
-
67
- if (candidates.length === 0) return null;
68
-
69
- // 回傳最新的
70
- candidates.sort((a, b) => b.mtime - a.mtime);
71
- return candidates[0].path;
72
- }
73
-
74
- /**
75
- * 取得指定 formation 的最新 log
76
- * @param {string} formation - unit / solo / squad / legion
77
- * @param {string} [projectPath] - 專案路徑(多專案支援)
78
- * @returns {string|null}
79
- */
80
- function findLatestLog(formation, projectPath) {
81
- const logDir = projectPath ? getLogDir(projectPath) : LOG_DIR;
82
- const dir = path.join(logDir, formation);
83
- if (!fs.existsSync(dir)) return null;
84
-
85
- const files = fs.readdirSync(dir)
86
- .filter(f => f.endsWith('.log'))
87
- .sort((a, b) => {
88
- const aErr = a.endsWith('.err.log') ? 1 : 0;
89
- const bErr = b.endsWith('.err.log') ? 1 : 0;
90
- return aErr - bErr;
91
- })
92
- .map(f => {
93
- const filePath = path.join(dir, f);
94
- const stats = fs.statSync(filePath);
95
- return { path: filePath, mtime: stats.mtime };
96
- });
97
-
98
- if (files.length === 0) return null;
99
-
100
- files.sort((a, b) => b.mtime - a.mtime);
101
- return files[0].path;
102
- }
103
-
104
- // ─────────────────────────────────────────
105
- // 讀取與過濾
106
- // ─────────────────────────────────────────
107
-
108
- /**
109
- * 讀取 log 並根據選項過濾
110
- * @param {string} logPath
111
- * @param {object} opts
112
- * @param {number} [opts.tail] - 最後 N 行
113
- * @param {number} [opts.head] - 前 N 行
114
- * @param {boolean} [opts.errors] - 只顯示錯誤
115
- */
116
- function readLog(logPath, opts = {}) {
117
- const { tail, head, errors } = opts;
118
-
119
- if (!fs.existsSync(logPath)) {
120
- console.error(`❌ Log file not found: ${logPath}`);
121
- process.exit(1);
122
- }
123
-
124
- const content = fs.readFileSync(logPath, 'utf-8');
125
- let lines = content.split('\n');
126
-
127
- // 過濾錯誤行
128
- if (errors) {
129
- // TASK_1712: Added REFUSED to surface injection-refusal runs in --errors filter
130
- const errorKeywords = ['ERROR', 'FAILED', 'FAIL', 'REFUSED', '❌', 'exit 1', 'Exit Code: 1'];
131
- lines = lines.filter(line => {
132
- const upper = line.toUpperCase();
133
- return errorKeywords.some(kw => upper.includes(kw.toUpperCase()));
134
- });
135
- }
136
-
137
- // 取前/後 N 行
138
- if (head) {
139
- lines = lines.slice(0, head);
140
- } else if (tail) {
141
- lines = lines.slice(-tail);
142
- }
143
-
144
- console.log(`─── Log: ${logPath} ───`);
145
- if (tail) console.log(`─── Last ${tail} lines ───`);
146
- if (head) console.log(`─── First ${head} lines ───`);
147
- if (errors) console.log(`─── Error lines only ───`);
148
- console.log('');
149
-
150
- console.log(lines.join('\n'));
151
- }
152
-
153
- // ─────────────────────────────────────────
154
- // CLI
155
- // ─────────────────────────────────────────
156
-
157
- if (require.main === module) {
158
- const args = process.argv.slice(2);
159
-
160
- if (args.includes('--help') || args.includes('-h')) {
161
- console.log(`
162
- ⚔️ Formation Log Reader
163
-
164
- 讀取 Formation log 檔案的尾端/頭端/錯誤行
165
-
166
- Usage:
167
- node .formation/tools/log-reader.js --log <path> --tail 50
168
- node .formation/tools/log-reader.js --task TASK_XXX --tail 50
169
- node .formation/tools/log-reader.js --latest unit --tail 30
170
-
171
- Options:
172
- --log <path> 直接讀指定 log 檔
173
- --task TASK_XXX 在 .formation/logs/ 下搜尋包含 TASK_XXX 的最新 log
174
- --latest <formation> 讀指定陣型目錄下最新的 log
175
- --tail N 只輸出最後 N 行(預設 50)
176
- --head N 只輸出前 N 行
177
- --errors 只輸出包含 ERROR/FAILED/❌ 的行
178
-
179
- Examples:
180
- node .formation/tools/log-reader.js --log .formation/logs/unit/do_abc123.log --tail 30
181
- node .formation/tools/log-reader.js --task TASK_248 --errors
182
- node .formation/tools/log-reader.js --latest squad --tail 50
183
- `);
184
- process.exit(0);
185
- }
186
-
187
- const getArg = (flag) => {
188
- for (let i = 0; i < args.length; i++) {
189
- if (args[i].startsWith(`${flag}=`)) return args[i].split('=').slice(1).join('=');
190
- if (args[i] === flag && args[i + 1]) return args[i + 1];
191
- }
192
- return null;
193
- };
194
-
195
- const logPath = getArg('--log');
196
- const taskId = getArg('--task');
197
- const latest = getArg('--latest');
198
- const projectPath = getArg('--project-path');
199
- const tail = parseInt(getArg('--tail') || '50', 10);
200
- const head = parseInt(getArg('--head') || '0', 10);
201
- const errors = args.includes('--errors');
202
-
203
- let resolvedPath = null;
204
-
205
- if (logPath) {
206
- resolvedPath = logPath;
207
- } else if (taskId) {
208
- resolvedPath = findTaskLog(taskId, projectPath);
209
- if (!resolvedPath) {
210
- console.error(`❌ No log found for task: ${taskId}`);
211
- process.exit(1);
212
- }
213
- } else if (latest) {
214
- resolvedPath = findLatestLog(latest, projectPath);
215
- if (!resolvedPath) {
216
- console.error(`❌ No log found in formation: ${latest}`);
217
- process.exit(1);
218
- }
219
- } else {
220
- console.error('❌ Must specify one of: --log, --task, --latest');
221
- process.exit(1);
222
- }
223
-
224
- const opts = {};
225
- if (head > 0) opts.head = head;
226
- else if (tail > 0) opts.tail = tail;
227
- if (errors) opts.errors = true;
228
-
229
- readLog(resolvedPath, opts);
230
- }
231
-
232
- module.exports = { findTaskLog, findLatestLog, readLog };
@@ -1,162 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * revert-daemon-state.js
4
- *
5
- * On-demand cleanup for daemon-drift on the user branch between task merges.
6
- *
7
- * The post-complete merge pipeline already auto-reverts daemon-managed paths
8
- * via .kenaz/specialist/daemon-manifest.yaml + isDaemonManagedPath() inside
9
- * post-complete.js (worktree merge step). But that only fires DURING a task
10
- * merge. Between sessions, daemons keep writing to those files and
11
- * `git status` shows them as modified — pure noise on the user branch.
12
- *
13
- * This CLI uses the same manifest as the merge pipeline, but runs ad-hoc:
14
- * 1. Load .kenaz/specialist/daemon-manifest.yaml
15
- * 2. For every enrolled state_files entry + every file matching a static_paths
16
- * directory entry, check `git status --porcelain` for modification
17
- * 3. Run `git checkout HEAD -- <path>` to revert each
18
- *
19
- * Untracked files are left alone. Files that are not enrolled in the manifest
20
- * are left alone. The CLI is idempotent and never touches scope_paths or
21
- * non-daemon files.
22
- *
23
- * Usage:
24
- * node .formation/tools/revert-daemon-state.js # do it
25
- * node .formation/tools/revert-daemon-state.js --dry # show plan only
26
- * node .formation/tools/revert-daemon-state.js --quiet # only summary line
27
- *
28
- * Exit code: 0 on success (even if nothing reverted), 1 on manifest load
29
- * failure or git command failure.
30
- */
31
-
32
- 'use strict';
33
-
34
- const fs = require('fs');
35
- const path = require('path');
36
- const { execSync, spawnSync } = require('child_process');
37
-
38
- const ROOT = path.resolve(__dirname, '..', '..');
39
- const MANIFEST = path.join(ROOT, '.kenaz', 'specialist', 'daemon-manifest.yaml');
40
-
41
- const argv = process.argv.slice(2);
42
- const DRY = argv.includes('--dry') || argv.includes('--dry-run');
43
- const QUIET = argv.includes('--quiet');
44
-
45
- function log(...args) {
46
- if (!QUIET) console.log(...args);
47
- }
48
-
49
- function loadYaml(filePath) {
50
- let yaml;
51
- try {
52
- yaml = require('js-yaml');
53
- } catch {
54
- const candidates = [
55
- path.resolve(__dirname, '../../.vendor/js-yaml.cjs'),
56
- path.resolve(__dirname, '.vendor/js-yaml.cjs'),
57
- path.resolve(__dirname, '..', '.vendor', 'js-yaml.cjs'),
58
- ];
59
- for (const c of candidates) {
60
- try { yaml = require(c); break; } catch {}
61
- }
62
- }
63
- if (!yaml) throw new Error('js-yaml not available — check .formation/.vendor/');
64
- return yaml.load(fs.readFileSync(filePath, 'utf8'));
65
- }
66
-
67
- function loadManifest() {
68
- if (!fs.existsSync(MANIFEST)) {
69
- throw new Error(`daemon-manifest.yaml not found at ${MANIFEST}`);
70
- }
71
- const raw = loadYaml(MANIFEST);
72
- return {
73
- stateFiles: Array.isArray(raw?.state_files) ? raw.state_files : [],
74
- staticPaths: Array.isArray(raw?.static_paths) ? raw.static_paths : [],
75
- };
76
- }
77
-
78
- function normalizeRepoPath(p) {
79
- return String(p || '')
80
- .replace(/\\/g, '/')
81
- .replace(/^\.\//, '')
82
- .replace(/\/+/g, '/');
83
- }
84
-
85
- function listModifiedTracked() {
86
- const out = execSync('git status --porcelain --untracked-files=no', {
87
- cwd: ROOT, encoding: 'utf8', windowsHide: true,
88
- });
89
- const modified = [];
90
- for (const line of out.split(/\r?\n/)) {
91
- if (!line) continue;
92
- const xy = line.slice(0, 2);
93
- const rel = normalizeRepoPath(line.slice(3).trim());
94
- if (xy.includes('M') || xy.includes('A') || xy.includes('D') || xy.includes('R')) {
95
- modified.push(rel);
96
- }
97
- }
98
- return modified;
99
- }
100
-
101
- function isManagedByManifest(relPath, manifest) {
102
- const norm = normalizeRepoPath(relPath);
103
- if (!norm) return false;
104
- if (manifest.stateFiles.includes(norm)) return true;
105
- for (const sp of manifest.staticPaths) {
106
- const spn = normalizeRepoPath(sp);
107
- if (spn === norm) return true;
108
- if (spn.endsWith('/') && norm.startsWith(spn)) return true;
109
- }
110
- return false;
111
- }
112
-
113
- function gitCheckoutHead(paths) {
114
- if (paths.length === 0) return { ok: true, stderr: '' };
115
- // Use spawnSync with --pathspec-file via stdin to avoid command-line length limits
116
- const args = ['checkout', 'HEAD', '--', ...paths];
117
- const r = spawnSync('git', args, { cwd: ROOT, encoding: 'utf8', windowsHide: true });
118
- return { ok: r.status === 0, stderr: r.stderr || '' };
119
- }
120
-
121
- function main() {
122
- let manifest;
123
- try { manifest = loadManifest(); }
124
- catch (e) {
125
- console.error(`✗ ${e.message}`);
126
- process.exit(1);
127
- }
128
-
129
- const modified = listModifiedTracked();
130
- const managed = modified.filter(p => isManagedByManifest(p, manifest));
131
- const unmanaged = modified.filter(p => !isManagedByManifest(p, manifest));
132
-
133
- if (managed.length === 0) {
134
- log(`✓ No daemon-managed drift in working tree (${modified.length} other modified file(s) untouched).`);
135
- return;
136
- }
137
-
138
- log(`Daemon-managed drift detected (${managed.length} file(s)):`);
139
- for (const p of managed) log(` ~ ${p}`);
140
-
141
- if (unmanaged.length > 0 && !QUIET) {
142
- log('');
143
- log(`Non-daemon modifications (left alone):`);
144
- for (const p of unmanaged) log(` ${p}`);
145
- }
146
-
147
- if (DRY) {
148
- log('');
149
- log('[--dry] No changes made. Re-run without --dry to revert.');
150
- return;
151
- }
152
-
153
- log('');
154
- const result = gitCheckoutHead(managed);
155
- if (!result.ok) {
156
- console.error(`✗ git checkout failed:\n${result.stderr.trim()}`);
157
- process.exit(1);
158
- }
159
- console.log(`✓ Reverted ${managed.length} daemon-managed file(s) to HEAD.`);
160
- }
161
-
162
- main();