kenaz 0.3.18 → 0.3.19

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