kenaz 0.0.0-stage → 0.3.2

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 (1641) hide show
  1. package/bin/kenaz.js +3 -0
  2. package/core/.agents/skills/remotion-best-practices/SKILL.md +61 -0
  3. package/core/.agents/skills/remotion-best-practices/rules/3d.md +86 -0
  4. package/core/.agents/skills/remotion-best-practices/rules/animations.md +27 -0
  5. package/core/.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  6. package/core/.agents/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  7. package/core/.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +103 -0
  8. package/core/.agents/skills/remotion-best-practices/rules/assets.md +78 -0
  9. package/core/.agents/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
  10. package/core/.agents/skills/remotion-best-practices/rules/audio.md +169 -0
  11. package/core/.agents/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
  12. package/core/.agents/skills/remotion-best-practices/rules/can-decode.md +81 -0
  13. package/core/.agents/skills/remotion-best-practices/rules/charts.md +120 -0
  14. package/core/.agents/skills/remotion-best-practices/rules/compositions.md +154 -0
  15. package/core/.agents/skills/remotion-best-practices/rules/display-captions.md +184 -0
  16. package/core/.agents/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  17. package/core/.agents/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
  18. package/core/.agents/skills/remotion-best-practices/rules/fonts.md +152 -0
  19. package/core/.agents/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  20. package/core/.agents/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  21. package/core/.agents/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
  22. package/core/.agents/skills/remotion-best-practices/rules/gifs.md +141 -0
  23. package/core/.agents/skills/remotion-best-practices/rules/images.md +134 -0
  24. package/core/.agents/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
  25. package/core/.agents/skills/remotion-best-practices/rules/light-leaks.md +73 -0
  26. package/core/.agents/skills/remotion-best-practices/rules/lottie.md +70 -0
  27. package/core/.agents/skills/remotion-best-practices/rules/maps.md +412 -0
  28. package/core/.agents/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
  29. package/core/.agents/skills/remotion-best-practices/rules/measuring-text.md +140 -0
  30. package/core/.agents/skills/remotion-best-practices/rules/parameters.md +109 -0
  31. package/core/.agents/skills/remotion-best-practices/rules/sequencing.md +118 -0
  32. package/core/.agents/skills/remotion-best-practices/rules/sfx.md +30 -0
  33. package/core/.agents/skills/remotion-best-practices/rules/subtitles.md +36 -0
  34. package/core/.agents/skills/remotion-best-practices/rules/tailwind.md +11 -0
  35. package/core/.agents/skills/remotion-best-practices/rules/text-animations.md +20 -0
  36. package/core/.agents/skills/remotion-best-practices/rules/timing.md +179 -0
  37. package/core/.agents/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
  38. package/core/.agents/skills/remotion-best-practices/rules/transitions.md +197 -0
  39. package/core/.agents/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
  40. package/core/.agents/skills/remotion-best-practices/rules/trimming.md +51 -0
  41. package/core/.agents/skills/remotion-best-practices/rules/videos.md +171 -0
  42. package/core/.agents/skills/remotion-best-practices/rules/voiceover.md +99 -0
  43. package/core/.agents/skills/supabase-postgres-best-practices/AGENTS.md +90 -0
  44. package/core/.agents/skills/supabase-postgres-best-practices/CLAUDE.md +1 -0
  45. package/core/.agents/skills/supabase-postgres-best-practices/SKILL.md +64 -0
  46. package/core/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
  47. package/core/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
  48. package/core/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
  49. package/core/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
  50. package/core/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
  51. package/core/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
  52. package/core/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
  53. package/core/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
  54. package/core/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
  55. package/core/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
  56. package/core/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
  57. package/core/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
  58. package/core/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
  59. package/core/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
  60. package/core/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
  61. package/core/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
  62. package/core/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
  63. package/core/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
  64. package/core/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
  65. package/core/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +45 -0
  66. package/core/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
  67. package/core/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
  68. package/core/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
  69. package/core/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
  70. package/core/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
  71. package/core/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
  72. package/core/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
  73. package/core/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
  74. package/core/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
  75. package/core/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
  76. package/core/.agents/skills/typesafe-ai/LICENSE +21 -0
  77. package/core/.agents/skills/typesafe-ai/SKILL.md +149 -0
  78. package/core/.agents/skills/vercel-react-best-practices/AGENTS.md +2934 -0
  79. package/core/.agents/skills/vercel-react-best-practices/SKILL.md +136 -0
  80. package/core/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  81. package/core/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
  82. package/core/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
  83. package/core/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  84. package/core/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  85. package/core/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
  86. package/core/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  87. package/core/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  88. package/core/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  89. package/core/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  90. package/core/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  91. package/core/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  92. package/core/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  93. package/core/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  94. package/core/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
  95. package/core/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  96. package/core/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  97. package/core/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
  98. package/core/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  99. package/core/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  100. package/core/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  101. package/core/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  102. package/core/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  103. package/core/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  104. package/core/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  105. package/core/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  106. package/core/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  107. package/core/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  108. package/core/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  109. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  110. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  111. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  112. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  113. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  114. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  115. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  116. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  117. package/core/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  118. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  119. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  120. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  121. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  122. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  123. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  124. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  125. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  126. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  127. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  128. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  129. package/core/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  130. package/core/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  131. package/core/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
  132. package/core/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  133. package/core/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
  134. package/core/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
  135. package/core/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
  136. package/core/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  137. package/core/.claude/agents/pcb-reviewer.md +146 -0
  138. package/core/.claude/commands/Amelia.md +5 -0
  139. package/core/.claude/commands/Ansuz.md +38 -0
  140. package/core/.claude/commands/Huginn.md +25 -0
  141. package/core/.claude/commands/agent-worker.md +168 -0
  142. package/core/.claude/commands/architect.md +24 -0
  143. package/core/.claude/commands/archive.md +24 -0
  144. package/core/.claude/commands/canary.md +17 -0
  145. package/core/.claude/commands/incident.md +15 -0
  146. package/core/.claude/commands/init.md +112 -0
  147. package/core/.claude/commands/legacy_dev.md +18 -0
  148. package/core/.claude/commands/product-review.md +131 -0
  149. package/core/.claude/commands/review.md +14 -0
  150. package/core/.claude/commands/security.md +15 -0
  151. package/core/.claude/commands/spec.md +22 -0
  152. package/core/.claude/commands/strategy.md +141 -0
  153. package/core/.claude/commands/sunny.md +1 -0
  154. package/core/.claude/commands//347/224/242/345/223/201.md +15 -0
  155. package/core/.claude/commands//347/224/242/345/223/201/346/252/242/350/246/226.md +129 -0
  156. package/core/.claude/commands//350/250/255/350/250/210.md +14 -0
  157. package/core/.claude/commands//350/251/225/344/274/260.md +16 -0
  158. package/core/.claude/commands//351/200/262/345/272/246.md +15 -0
  159. package/core/.claude/hooks/__tests__/substrate.test.js +122 -0
  160. package/core/.claude/hooks/__tests__/task-1866-cli-guard.test.js +193 -0
  161. package/core/.claude/hooks/__tests__/task-1977-dedupe-guard.test.js +125 -0
  162. package/core/.claude/hooks/__tests__/task-2087-mark-landed-idempotent.test.js +128 -0
  163. package/core/.claude/hooks/__tests__/task-2253-null-tip-marker-reuse.test.js +84 -0
  164. package/core/.claude/hooks/__tests__/task-2381-project-guard-walkup.test.js +53 -0
  165. package/core/.claude/hooks/_ansuz-conversation.js +183 -0
  166. package/core/.claude/hooks/_ansuz-conversation.test.js +90 -0
  167. package/core/.claude/hooks/_dedupe-guard.js +75 -0
  168. package/core/.claude/hooks/_peer-project-root.js +113 -0
  169. package/core/.claude/hooks/_peer-project-root.test.js +121 -0
  170. package/core/.claude/hooks/_peer-waiting-line.js +27 -0
  171. package/core/.claude/hooks/_project-guard.js +45 -0
  172. package/core/.claude/hooks/_run-user-prompt.js +134 -0
  173. package/core/.claude/hooks/_run-user-prompt.test.js +80 -0
  174. package/core/.claude/hooks/_session-identity.js +62 -0
  175. package/core/.claude/hooks/_session-pin.js +34 -0
  176. package/core/.claude/hooks/_session-pin.test.js +89 -0
  177. package/core/.claude/hooks/_signal-fence.js +17 -0
  178. package/core/.claude/hooks/_substrate-paths.js +95 -0
  179. package/core/.claude/hooks/_user-scope.js +23 -0
  180. package/core/.claude/hooks/ansuz-session-pin-via-turn-context.test.js +52 -0
  181. package/core/.claude/hooks/ansuz-session-pin.js +171 -0
  182. package/core/.claude/hooks/ansuz-session-pin.test.js +140 -0
  183. package/core/.claude/hooks/context-check.js +51 -0
  184. package/core/.claude/hooks/core-resolve.js +230 -0
  185. package/core/.claude/hooks/daemon-outbox-hook.js +139 -0
  186. package/core/.claude/hooks/fatigue-check.js +52 -0
  187. package/core/.claude/hooks/handoff-snapshot-on-commit.js +154 -0
  188. package/core/.claude/hooks/handoff-snapshot-on-decision.js +188 -0
  189. package/core/.claude/hooks/handoff-snapshot-on-question.js +158 -0
  190. package/core/.claude/hooks/handoff-staleness.js +197 -0
  191. package/core/.claude/hooks/jev-thalamus-context.js +17 -0
  192. package/core/.claude/hooks/jev-thalamus-context.test.js +41 -0
  193. package/core/.claude/hooks/lease-release.js +119 -0
  194. package/core/.claude/hooks/lease-touch.js +96 -0
  195. package/core/.claude/hooks/memory-guard.js +723 -0
  196. package/core/.claude/hooks/memory-guard.test.js +574 -0
  197. package/core/.claude/hooks/reflex-feedback.js +5 -0
  198. package/core/.claude/hooks/session-init.ps1 +49 -0
  199. package/core/.claude/hooks/sound-event.js +70 -0
  200. package/core/.claude/hooks/sound-event.sh +37 -0
  201. package/core/.claude/hooks/stream-delta.js +169 -0
  202. package/core/.claude/hooks/synapse-first-investigation.js +326 -0
  203. package/core/.claude/hooks/synapse-first-investigation.test.js +126 -0
  204. package/core/.claude/hooks/task-bridge.js +33 -0
  205. package/core/.claude/hooks/turn-context-peer.test.js +115 -0
  206. package/core/.claude/hooks/turn-context-session-line.test.js +69 -0
  207. package/core/.claude/hooks/turn-context.js +215 -0
  208. package/core/.claude/hooks/worktree-create.js +1232 -0
  209. package/core/.claude/hooks/worktree-remove.js +181 -0
  210. package/core/.claude/no-hooks.settings.json +3 -0
  211. package/core/.claude/settings.json +180 -0
  212. package/core/.claude/skills/adapt/SKILL.md +199 -0
  213. package/core/.claude/skills/animate/SKILL.md +190 -0
  214. package/core/.claude/skills/architect_agent/SKILL.md +408 -0
  215. package/core/.claude/skills/assessment_agent/SKILL.md +289 -0
  216. package/core/.claude/skills/audit/SKILL.md +129 -0
  217. package/core/.claude/skills/backend_agent/SKILL.md +407 -0
  218. package/core/.claude/skills/backend_engineer/SKILL.md +788 -0
  219. package/core/.claude/skills/bolder/SKILL.md +132 -0
  220. package/core/.claude/skills/clarify/SKILL.md +180 -0
  221. package/core/.claude/skills/code_reviewer/SKILL.md +305 -0
  222. package/core/.claude/skills/code_reviewer/patterns.yaml +85 -0
  223. package/core/.claude/skills/colorize/SKILL.md +158 -0
  224. package/core/.claude/skills/critique/SKILL.md +118 -0
  225. package/core/.claude/skills/delight/SKILL.md +317 -0
  226. package/core/.claude/skills/designer/SKILL.md +845 -0
  227. package/core/.claude/skills/dev_agent/SKILL.md +503 -0
  228. package/core/.claude/skills/developer/SKILL.md +792 -0
  229. package/core/.claude/skills/distill/SKILL.md +137 -0
  230. package/core/.claude/skills/extract/SKILL.md +95 -0
  231. package/core/.claude/skills/feature_agent/SKILL.md +303 -0
  232. package/core/.claude/skills/frontend-design/SKILL.md +127 -0
  233. package/core/.claude/skills/frontend-design/reference/color-and-contrast.md +132 -0
  234. package/core/.claude/skills/frontend-design/reference/interaction-design.md +123 -0
  235. package/core/.claude/skills/frontend-design/reference/motion-design.md +99 -0
  236. package/core/.claude/skills/frontend-design/reference/responsive-design.md +114 -0
  237. package/core/.claude/skills/frontend-design/reference/spatial-design.md +100 -0
  238. package/core/.claude/skills/frontend-design/reference/typography.md +131 -0
  239. package/core/.claude/skills/frontend-design/reference/ux-writing.md +107 -0
  240. package/core/.claude/skills/frontend_agent/SKILL.md +327 -0
  241. package/core/.claude/skills/harden/SKILL.md +358 -0
  242. package/core/.claude/skills/heimdall/SKILL.md +84 -0
  243. package/core/.claude/skills/huginn/SKILL.md +208 -0
  244. package/core/.claude/skills/incident_response/SKILL.md +315 -0
  245. package/core/.claude/skills/legacy_dev/SKILL.md +202 -0
  246. package/core/.claude/skills/normalize/SKILL.md +67 -0
  247. package/core/.claude/skills/onboard/SKILL.md +243 -0
  248. package/core/.claude/skills/optimization_agent/SKILL.md +340 -0
  249. package/core/.claude/skills/optimize/SKILL.md +269 -0
  250. package/core/.claude/skills/pcb-review/SKILL.md +39 -0
  251. package/core/.claude/skills/polish/SKILL.md +202 -0
  252. package/core/.claude/skills/product_manager/SKILL.md +1091 -0
  253. package/core/.claude/skills/qa_engineer/SKILL.md +1269 -0
  254. package/core/.claude/skills/quieter/SKILL.md +118 -0
  255. package/core/.claude/skills/remotion-best-practices/SKILL.md +61 -0
  256. package/core/.claude/skills/remotion-best-practices/rules/3d.md +86 -0
  257. package/core/.claude/skills/remotion-best-practices/rules/animations.md +27 -0
  258. package/core/.claude/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
  259. package/core/.claude/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
  260. package/core/.claude/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +103 -0
  261. package/core/.claude/skills/remotion-best-practices/rules/assets.md +78 -0
  262. package/core/.claude/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
  263. package/core/.claude/skills/remotion-best-practices/rules/audio.md +169 -0
  264. package/core/.claude/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
  265. package/core/.claude/skills/remotion-best-practices/rules/can-decode.md +81 -0
  266. package/core/.claude/skills/remotion-best-practices/rules/charts.md +120 -0
  267. package/core/.claude/skills/remotion-best-practices/rules/compositions.md +154 -0
  268. package/core/.claude/skills/remotion-best-practices/rules/display-captions.md +184 -0
  269. package/core/.claude/skills/remotion-best-practices/rules/extract-frames.md +229 -0
  270. package/core/.claude/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
  271. package/core/.claude/skills/remotion-best-practices/rules/fonts.md +152 -0
  272. package/core/.claude/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
  273. package/core/.claude/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
  274. package/core/.claude/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
  275. package/core/.claude/skills/remotion-best-practices/rules/gifs.md +141 -0
  276. package/core/.claude/skills/remotion-best-practices/rules/images.md +134 -0
  277. package/core/.claude/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
  278. package/core/.claude/skills/remotion-best-practices/rules/light-leaks.md +73 -0
  279. package/core/.claude/skills/remotion-best-practices/rules/lottie.md +70 -0
  280. package/core/.claude/skills/remotion-best-practices/rules/maps.md +412 -0
  281. package/core/.claude/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
  282. package/core/.claude/skills/remotion-best-practices/rules/measuring-text.md +140 -0
  283. package/core/.claude/skills/remotion-best-practices/rules/parameters.md +109 -0
  284. package/core/.claude/skills/remotion-best-practices/rules/sequencing.md +118 -0
  285. package/core/.claude/skills/remotion-best-practices/rules/sfx.md +30 -0
  286. package/core/.claude/skills/remotion-best-practices/rules/subtitles.md +36 -0
  287. package/core/.claude/skills/remotion-best-practices/rules/tailwind.md +11 -0
  288. package/core/.claude/skills/remotion-best-practices/rules/text-animations.md +20 -0
  289. package/core/.claude/skills/remotion-best-practices/rules/timing.md +179 -0
  290. package/core/.claude/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
  291. package/core/.claude/skills/remotion-best-practices/rules/transitions.md +197 -0
  292. package/core/.claude/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
  293. package/core/.claude/skills/remotion-best-practices/rules/trimming.md +51 -0
  294. package/core/.claude/skills/remotion-best-practices/rules/videos.md +171 -0
  295. package/core/.claude/skills/remotion-best-practices/rules/voiceover.md +99 -0
  296. package/core/.claude/skills/security_agent/SKILL.md +312 -0
  297. package/core/.claude/skills/spec_optimizer/SKILL.md +299 -0
  298. package/core/.claude/skills/sre_agent/SKILL.md +1347 -0
  299. package/core/.claude/skills/sunny/SKILL.md +74 -0
  300. package/core/.claude/skills/supabase-postgres-best-practices/AGENTS.md +90 -0
  301. package/core/.claude/skills/supabase-postgres-best-practices/CLAUDE.md +1 -0
  302. package/core/.claude/skills/supabase-postgres-best-practices/SKILL.md +64 -0
  303. package/core/.claude/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
  304. package/core/.claude/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
  305. package/core/.claude/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
  306. package/core/.claude/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
  307. package/core/.claude/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
  308. package/core/.claude/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
  309. package/core/.claude/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
  310. package/core/.claude/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
  311. package/core/.claude/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
  312. package/core/.claude/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
  313. package/core/.claude/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
  314. package/core/.claude/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
  315. package/core/.claude/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
  316. package/core/.claude/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
  317. package/core/.claude/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
  318. package/core/.claude/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
  319. package/core/.claude/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
  320. package/core/.claude/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
  321. package/core/.claude/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
  322. package/core/.claude/skills/supabase-postgres-best-practices/references/query-index-types.md +45 -0
  323. package/core/.claude/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
  324. package/core/.claude/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
  325. package/core/.claude/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
  326. package/core/.claude/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
  327. package/core/.claude/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
  328. package/core/.claude/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
  329. package/core/.claude/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
  330. package/core/.claude/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
  331. package/core/.claude/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
  332. package/core/.claude/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
  333. package/core/.claude/skills/synapse_maintain/SKILL.md +84 -0
  334. package/core/.claude/skills/ui-ux-pro-max/SKILL.md +377 -0
  335. package/core/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
  336. package/core/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
  337. package/core/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
  338. package/core/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
  339. package/core/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
  340. package/core/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  341. package/core/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  342. package/core/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  343. package/core/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  344. package/core/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  345. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  346. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  347. package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  348. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  349. package/core/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  350. package/core/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  351. package/core/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  352. package/core/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  353. package/core/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  354. package/core/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
  355. package/core/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
  356. package/core/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  357. package/core/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  358. package/core/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  359. package/core/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
  360. package/core/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  361. package/core/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -0
  362. package/core/.claude/skills/vercel-react-best-practices/AGENTS.md +2934 -0
  363. package/core/.claude/skills/vercel-react-best-practices/SKILL.md +136 -0
  364. package/core/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  365. package/core/.claude/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
  366. package/core/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
  367. package/core/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  368. package/core/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  369. package/core/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
  370. package/core/.claude/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  371. package/core/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  372. package/core/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  373. package/core/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  374. package/core/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  375. package/core/.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  376. package/core/.claude/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  377. package/core/.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  378. package/core/.claude/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
  379. package/core/.claude/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  380. package/core/.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  381. package/core/.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
  382. package/core/.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  383. package/core/.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  384. package/core/.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  385. package/core/.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  386. package/core/.claude/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  387. package/core/.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  388. package/core/.claude/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  389. package/core/.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  390. package/core/.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  391. package/core/.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  392. package/core/.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  393. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  394. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  395. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  396. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  397. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  398. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  399. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  400. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  401. package/core/.claude/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  402. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  403. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  404. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  405. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  406. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  407. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  408. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  409. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  410. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  411. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  412. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  413. package/core/.claude/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  414. package/core/.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  415. package/core/.claude/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
  416. package/core/.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  417. package/core/.claude/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
  418. package/core/.claude/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
  419. package/core/.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
  420. package/core/.claude/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  421. package/core/.collaboration/README.md +258 -0
  422. package/core/.collaboration/cli/commands/assign.js +39 -0
  423. package/core/.collaboration/cli/commands/claim.js +33 -0
  424. package/core/.collaboration/cli/commands/complete.js +44 -0
  425. package/core/.collaboration/cli/commands/create.js +110 -0
  426. package/core/.collaboration/cli/commands/get.js +83 -0
  427. package/core/.collaboration/cli/commands/index.js +52 -0
  428. package/core/.collaboration/cli/commands/list.js +51 -0
  429. package/core/.collaboration/cli/commands/lock.js +45 -0
  430. package/core/.collaboration/cli/commands/rebuild.js +39 -0
  431. package/core/.collaboration/cli/commands/search.js +61 -0
  432. package/core/.collaboration/cli/commands/stats.js +61 -0
  433. package/core/.collaboration/cli/commands/transfer.js +41 -0
  434. package/core/.collaboration/cli/commands/unlock.js +48 -0
  435. package/core/.collaboration/cli/formatters/help-formatter.js +70 -0
  436. package/core/.collaboration/cli/formatters/index.js +7 -0
  437. package/core/.collaboration/cli/task-cli.js +46 -0
  438. package/core/.collaboration/cli/utils/arg-parser.js +74 -0
  439. package/core/.collaboration/cli/utils/git-user.js +25 -0
  440. package/core/.collaboration/cli/utils/index.js +9 -0
  441. package/core/.collaboration/cli/utils/output.js +10 -0
  442. package/core/.collaboration/config/default.yaml +189 -0
  443. package/core/.collaboration/core/conflict-resolver.js +461 -0
  444. package/core/.collaboration/core/conflict-resolver.test.js +389 -0
  445. package/core/.collaboration/core/id-generator.js +303 -0
  446. package/core/.collaboration/core/migrate-legacy.js +260 -0
  447. package/core/.collaboration/core/package.json +1 -0
  448. package/core/.collaboration/core/task-2257-estimated-hours.test.js +72 -0
  449. package/core/.collaboration/core/task-2260-dir-layout-move.test.js +60 -0
  450. package/core/.collaboration/core/task-2264-single-after-capture.test.js +61 -0
  451. package/core/.collaboration/core/task-2288-followups.test.js +121 -0
  452. package/core/.collaboration/core/task-api-json.js +211 -0
  453. package/core/.collaboration/core/task-api.js +3729 -0
  454. package/core/.collaboration/core/task-api.test.js +1268 -0
  455. package/core/.collaboration/docs/API.md +787 -0
  456. package/core/.collaboration/docs/ARCHITECTURE.md +596 -0
  457. package/core/.collaboration/docs/CLI.md +821 -0
  458. package/core/.collaboration/docs/FAQ.md +695 -0
  459. package/core/.collaboration/docs/MIGRATION.md +804 -0
  460. package/core/.collaboration/docs/examples/README.md +320 -0
  461. package/core/.collaboration/docs/examples/advanced-queries.js +261 -0
  462. package/core/.collaboration/docs/examples/basic-usage.js +184 -0
  463. package/core/.collaboration/docs/examples/team-workflow.js +352 -0
  464. package/core/.collaboration/hooks/README.md +70 -0
  465. package/core/.collaboration/hooks/config.json +10 -0
  466. package/core/.collaboration/hooks/install.js +400 -0
  467. package/core/.collaboration/hooks/post-merge.js +163 -0
  468. package/core/.collaboration/hooks/pre-commit.js +264 -0
  469. package/core/.collaboration/hooks/synapse-hook.js +486 -0
  470. package/core/.collaboration/hooks/synapse-hook.test.js +69 -0
  471. package/core/.failure/INDEX.yaml +231 -0
  472. package/core/.failure/agent-execution/FAIL-001-agent-no-output.yaml +391 -0
  473. package/core/.failure/agent-execution/FAIL-004_agent_platform_specific_commands.md +194 -0
  474. package/core/.failure/agent-execution/FAIL-005_background_agent_permission_denied.md +204 -0
  475. package/core/.failure/agent-execution/FAIL-007-pm-lead-wrong-team-invocation.yaml +0 -0
  476. package/core/.failure/agent-execution/FAIL-008-bypass-api-direct-yaml-write.yaml +94 -0
  477. package/core/.failure/agent-execution/FAIL-010-phase0-poem-format-skipped.yaml +28 -0
  478. package/core/.failure/agent-execution/FAIL-010-skipping-cognitive-checkpoint-after-verification.yaml +59 -0
  479. package/core/.failure/agent-execution/FAIL-012-grep-shortcut-during-investigation.yaml +85 -0
  480. package/core/.failure/agent-execution/FAIL-013-search-cwd-drift-and-case-mismatch.yaml +103 -0
  481. package/core/.failure/agent-execution/FAIL-014-shared-worktree-premature-death-verdict.yaml +35 -0
  482. package/core/.failure/agent-execution/FAIL-AUTO-TASK_1413.yaml +31 -0
  483. package/core/.failure/agent-execution/FAIL-AUTO-TASK_1427.yaml +41 -0
  484. package/core/.failure/agent-execution/FAIL-AUTO-TASK_904.yaml +30 -0
  485. package/core/.failure/agent-execution/README.md +68 -0
  486. package/core/.failure/architecture/FAIL-004_session_based_skill_recognition.md +128 -0
  487. package/core/.failure/architecture/FAIL-006-python-version-compatibility.yaml +445 -0
  488. package/core/.failure/architecture/README.md +87 -0
  489. package/core/.failure/code-quality/README.md +89 -0
  490. package/core/.failure/data-integrity/FAIL-002-json-bom.yaml +220 -0
  491. package/core/.failure/data-integrity/README.md +141 -0
  492. package/core/.failure/pipeline/FAIL-003-queue-pollution.yaml +335 -0
  493. package/core/.failure/pipeline/README.md +95 -0
  494. package/core/.failure/retired/FAIL-009-skipping-synapse-pulse-before-code-change.yaml +49 -0
  495. package/core/.failure/tools/README.md +230 -0
  496. package/core/.failure/tools/USAGE_GUIDE.md +323 -0
  497. package/core/.failure/tools/check_bom.js +101 -0
  498. package/core/.failure/tools/check_failures.ps1 +387 -0
  499. package/core/.failure/tools/fix_json.js +58 -0
  500. package/core/.failure/tools/log_failure.ps1 +474 -0
  501. package/core/.failure/tools/remove_bom.js +46 -0
  502. package/core/.failure/tools/validate_json.js +50 -0
  503. package/core/.formation/.episode-input.json +10 -0
  504. package/core/.formation/README.md +188 -0
  505. package/core/.formation/bifrost/CGDD_hook_lifecycle.js +575 -0
  506. package/core/.formation/bifrost/HOOK_PARITY_VERIFICATION.md +241 -0
  507. package/core/.formation/bifrost/README.md +71 -0
  508. package/core/.formation/bifrost/codex-doctor.js +106 -0
  509. package/core/.formation/bifrost/codex-doctor.test.js +51 -0
  510. package/core/.formation/bifrost/launch.js +510 -0
  511. package/core/.formation/bifrost/launch.test.js +217 -0
  512. package/core/.formation/bifrost/openai/codex-hook-shim.js +171 -0
  513. package/core/.formation/bifrost/openai/codex-hook-shim.test.js +107 -0
  514. package/core/.formation/bifrost/openai/models.json +36 -0
  515. package/core/.formation/bifrost/openai/relay.js +781 -0
  516. package/core/.formation/bifrost/openai/relay.test.js +71 -0
  517. package/core/.formation/bifrost/session.js +382 -0
  518. package/core/.formation/bifrost/session.test.js +121 -0
  519. package/core/.formation/bifrost/setup-codex.js +685 -0
  520. package/core/.formation/bifrost/setup-codex.test.js +102 -0
  521. package/core/.formation/bifrost/shared/ansuz-boot-policy.js +31 -0
  522. package/core/.formation/bifrost/shared/ansuz-boot-policy.test.js +40 -0
  523. package/core/.formation/bifrost/shared/boot-context.js +68 -0
  524. package/core/.formation/bifrost/shared/boot-context.test.js +67 -0
  525. package/core/.formation/bifrost/shared/codex-appserver.js +191 -0
  526. package/core/.formation/bifrost/shared/codex-injection.js +126 -0
  527. package/core/.formation/bifrost/shared/codex-session-registry.js +105 -0
  528. package/core/.formation/bifrost/shared/codex-session-registry.test.js +126 -0
  529. package/core/.formation/bifrost/shared/consolidate.js +286 -0
  530. package/core/.formation/bifrost/shared/stream-json.js +103 -0
  531. package/core/.formation/bifrost/shared/tools.js +278 -0
  532. package/core/.formation/bifrost/sync.js +142 -0
  533. package/core/.formation/briefs/scheduler-evolution.md +209 -0
  534. package/core/.formation/lead.yaml +118 -0
  535. package/core/.formation/legacy.bak/designer.yaml +57 -0
  536. package/core/.formation/legacy.bak/pm.yaml +57 -0
  537. package/core/.formation/legacy.bak/qa.yaml +57 -0
  538. package/core/.formation/legacy.bak/review.yaml +62 -0
  539. package/core/.formation/legacy.bak/scheduler.yaml +129 -0
  540. package/core/.formation/legacy.bak/sre.yaml +57 -0
  541. package/core/.formation/legion/forge.yaml +299 -0
  542. package/core/.formation/legion/hive.yaml +238 -0
  543. package/core/.formation/native/claude/README.md +61 -0
  544. package/core/.formation/native/claude/models.json +42 -0
  545. package/core/.formation/role-map.yaml +274 -0
  546. package/core/.formation/roles/_shared/modules/code_review.yaml +98 -0
  547. package/core/.formation/roles/_shared/modules/security.yaml +103 -0
  548. package/core/.formation/roles/_shared/modules/testing.yaml +14 -0
  549. package/core/.formation/roles/backend/role.yaml +93 -0
  550. package/core/.formation/roles/code_reviewer/role.yaml +102 -0
  551. package/core/.formation/roles/database_optimizer/role.yaml +107 -0
  552. package/core/.formation/roles/designer/role.yaml +66 -0
  553. package/core/.formation/roles/developer/role.yaml +75 -0
  554. package/core/.formation/roles/frontend/role.yaml +134 -0
  555. package/core/.formation/roles/incident_response/role.yaml +89 -0
  556. package/core/.formation/roles/qa/role.yaml +89 -0
  557. package/core/.formation/roles/security/role.yaml +87 -0
  558. package/core/.formation/roles/sre/role.yaml +66 -0
  559. package/core/.formation/skills/coordinator.yaml +52 -0
  560. package/core/.formation/solo/diagnosis.yaml +78 -0
  561. package/core/.formation/solo/strategy.yaml +121 -0
  562. package/core/.formation/spec/task_operations.parity.test.js +426 -0
  563. package/core/.formation/spec/task_operations.yaml +291 -0
  564. package/core/.formation/squad/blitz.yaml +192 -0
  565. package/core/.formation/squad/strategy-tribunal.yaml +354 -0
  566. package/core/.formation/squad/strategy.yaml +349 -0
  567. package/core/.formation/squad/sweep.yaml +208 -0
  568. package/core/.formation/tests/hello.txt +2 -0
  569. package/core/.formation/tools/__tests__/m1-claim-gate.test.js +175 -0
  570. package/core/.formation/tools/__tests__/m1-hmap-convid.test.js +216 -0
  571. package/core/.formation/tools/__tests__/release-lock.test.js +176 -0
  572. package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +234 -0
  573. package/core/.formation/tools/__tests__/subagent-codex-harness.test.js +96 -0
  574. package/core/.formation/tools/__tests__/task-1863-claim-late-release.test.js +160 -0
  575. package/core/.formation/tools/__tests__/task-2265-singleton-identity.test.js +134 -0
  576. package/core/.formation/tools/__tests__/task-2281-singleton-readers.test.js +64 -0
  577. package/core/.formation/tools/__tests__/task2030-worktree-node-modules.test.js +221 -0
  578. package/core/.formation/tools/__tests__/task2067-worktree-branch-reuse.test.js +148 -0
  579. package/core/.formation/tools/alaya-gate.js +198 -0
  580. package/core/.formation/tools/analytics.js +172 -0
  581. package/core/.formation/tools/broadcast.js +103 -0
  582. package/core/.formation/tools/commit-sync.js +154 -0
  583. package/core/.formation/tools/coordinator.js +350 -0
  584. package/core/.formation/tools/dashboard-models-chain.test.js +60 -0
  585. package/core/.formation/tools/dispatch-cortex-context.test.js +52 -0
  586. package/core/.formation/tools/dispatch-detail/cross-project-worktree.md +40 -0
  587. package/core/.formation/tools/dispatch-detail/testing-module.md +53 -0
  588. package/core/.formation/tools/dispatch-detail/worktree-subagent-mode.md +72 -0
  589. package/core/.formation/tools/dispatch-dryrun.test.js +54 -0
  590. package/core/.formation/tools/dispatch-model-policy.test.js +165 -0
  591. package/core/.formation/tools/dispatch-notifier.js +84 -0
  592. package/core/.formation/tools/dispatch-registry.js +346 -0
  593. package/core/.formation/tools/dispatch.js +1931 -0
  594. package/core/.formation/tools/formations.js +1203 -0
  595. package/core/.formation/tools/forseti-verify.js +1170 -0
  596. package/core/.formation/tools/git-salvage.js +269 -0
  597. package/core/.formation/tools/guard-attribution.js +224 -0
  598. package/core/.formation/tools/headless-branch-recovery.test.js +64 -0
  599. package/core/.formation/tools/headless-dryrun.test.js +79 -0
  600. package/core/.formation/tools/headless-v2.js +659 -0
  601. package/core/.formation/tools/headless.js +1071 -0
  602. package/core/.formation/tools/integration.js +528 -0
  603. package/core/.formation/tools/jev-correction.js +214 -0
  604. package/core/.formation/tools/jev-correction.test.js +178 -0
  605. package/core/.formation/tools/legacy-scheduler.js +1994 -0
  606. package/core/.formation/tools/lib/exec-tree.js +92 -0
  607. package/core/.formation/tools/lib/exec-tree.test.js +82 -0
  608. package/core/.formation/tools/loader.js +190 -0
  609. package/core/.formation/tools/log-reader.js +232 -0
  610. package/core/.formation/tools/logger.js +273 -0
  611. package/core/.formation/tools/mcp-allowlist.test.js +40 -0
  612. package/core/.formation/tools/memory-loader.js +422 -0
  613. package/core/.formation/tools/merge-lock-heartbeat.test.js +35 -0
  614. package/core/.formation/tools/no-hooks.settings.json +3 -0
  615. package/core/.formation/tools/pool-status-push.js +121 -0
  616. package/core/.formation/tools/pool-status-push.test.js +165 -0
  617. package/core/.formation/tools/post-complete/bounded-http.js +102 -0
  618. package/core/.formation/tools/post-complete/bounded-http.test.js +130 -0
  619. package/core/.formation/tools/post-complete/cognitive.js +270 -0
  620. package/core/.formation/tools/post-complete/git-merge.js +2845 -0
  621. package/core/.formation/tools/post-complete/health.js +464 -0
  622. package/core/.formation/tools/post-complete/integration-gaps.js +98 -0
  623. package/core/.formation/tools/post-complete/outcome.js +874 -0
  624. package/core/.formation/tools/post-complete/staging.js +390 -0
  625. package/core/.formation/tools/post-complete/subagent-transcript-stats.js +460 -0
  626. package/core/.formation/tools/post-complete/subagent-transcript-stats.test.js +680 -0
  627. package/core/.formation/tools/post-complete/task1536.test.js +627 -0
  628. package/core/.formation/tools/post-complete/task1543.test.js +261 -0
  629. package/core/.formation/tools/post-complete/task1876.test.js +287 -0
  630. package/core/.formation/tools/post-complete/task1957.test.js +293 -0
  631. package/core/.formation/tools/post-complete/task1997.test.js +401 -0
  632. package/core/.formation/tools/post-complete/task2028.test.js +232 -0
  633. package/core/.formation/tools/post-complete/task2029.test.js +275 -0
  634. package/core/.formation/tools/post-complete/task2036.test.js +330 -0
  635. package/core/.formation/tools/post-complete/task2038.test.js +246 -0
  636. package/core/.formation/tools/post-complete/task2071.test.js +231 -0
  637. package/core/.formation/tools/post-complete/task2233.test.js +100 -0
  638. package/core/.formation/tools/post-complete/task2240.test.js +134 -0
  639. package/core/.formation/tools/post-complete/task2244.test.js +199 -0
  640. package/core/.formation/tools/post-complete/task2255.test.js +28 -0
  641. package/core/.formation/tools/post-complete/task2268.test.js +106 -0
  642. package/core/.formation/tools/post-complete/task2341.test.js +74 -0
  643. package/core/.formation/tools/post-complete/task2345.test.js +38 -0
  644. package/core/.formation/tools/post-complete/testing.js +482 -0
  645. package/core/.formation/tools/post-complete/wish.js +177 -0
  646. package/core/.formation/tools/post-complete/worktree-rescue.test.js +283 -0
  647. package/core/.formation/tools/post-complete.js +3660 -0
  648. package/core/.formation/tools/post-complete.test.js +1002 -0
  649. package/core/.formation/tools/pre-dispatch-snapshot.test.js +328 -0
  650. package/core/.formation/tools/process-state.js +33 -0
  651. package/core/.formation/tools/prompt.js +2183 -0
  652. package/core/.formation/tools/prompt.test.js +875 -0
  653. package/core/.formation/tools/remote-conflict.js +98 -0
  654. package/core/.formation/tools/revert-daemon-state.js +162 -0
  655. package/core/.formation/tools/route-audit.test.js +55 -0
  656. package/core/.formation/tools/runner.js +1332 -0
  657. package/core/.formation/tools/supabase-client.js +104 -0
  658. package/core/.formation/tools/task-sync.js +236 -0
  659. package/core/.formation/tools/task-utils.js +344 -0
  660. package/core/.formation/tools/task2028-headless-worktree.test.js +112 -0
  661. package/core/.formation/tools/test-drift.js +148 -0
  662. package/core/.formation/tools/tests/headless-mcp-args-plumbing.test.js +123 -0
  663. package/core/.formation/tools/tests/relay-mcp-overrides.test.js +112 -0
  664. package/core/.formation/tools/transcript-recorder.js +574 -0
  665. package/core/.formation/tools/utils-claude-cmd.test.js +28 -0
  666. package/core/.formation/tools/utils-worktree-config.test.js +44 -0
  667. package/core/.formation/tools/utils.js +1020 -0
  668. package/core/.formation/tools/verify-acceptance.js +585 -0
  669. package/core/.formation/tools/worktree-leak.test.js +162 -0
  670. package/core/.formation/tools/worktree-reclaim.js +259 -0
  671. package/core/.formation/tools/worktree-reclaim.test.js +182 -0
  672. package/core/.formation/tools/yggdrasil-local.js +97 -0
  673. package/core/.formation/unit/do.yaml +78 -0
  674. package/core/.formation/unit/heimdall.yaml +96 -0
  675. package/core/.formation/unit/plan.yaml +67 -0
  676. package/core/.formation/unit/think.yaml +58 -0
  677. package/core/.kenaz/MUNINN_SNAPSHOT9_EPISODE.json +32 -0
  678. package/core/.kenaz/MUNINN_SNAPSHOT9_RUNE.json +23 -0
  679. package/core/.kenaz/MUNINN_SNAPSHOT9_SUMMARY.md +89 -0
  680. package/core/.kenaz/ash_TASK_1428-20260507.json +14 -0
  681. package/core/.kenaz/cache/native-models/claude.json +42 -0
  682. package/core/.kenaz/claude-hooks-manifest.json +119 -0
  683. package/core/.kenaz/cortex.yaml +100 -0
  684. package/core/.kenaz/curator_state.json +7 -0
  685. package/core/.kenaz/dashboard-quick-prompts.json +9 -0
  686. package/core/.kenaz/designs/METAPLASTICITY_CONFIDENCE_DESIGN.md +537 -0
  687. package/core/.kenaz/designs/REFLEX_INITIATOR_DESIGN.md +501 -0
  688. package/core/.kenaz/designs/TASK_1385_handoff_mcp_design.md +426 -0
  689. package/core/.kenaz/designs/TASK_932_hippocampus_consolidation.md +103 -0
  690. package/core/.kenaz/designs/TASK_934_efe_signal_engine.md +97 -0
  691. package/core/.kenaz/designs/TASK_935_self_evolution_framework.md +154 -0
  692. package/core/.kenaz/designs/VISION_cognitive_architecture_roadmap.md +159 -0
  693. package/core/.kenaz/designs/metaplasticity_simulation_results.json +696 -0
  694. package/core/.kenaz/docs/INDEX.md +214 -0
  695. package/core/.kenaz/docs/README.md +290 -0
  696. package/core/.kenaz/docs/registry.json +962 -0
  697. package/core/.kenaz/docs/schema.json +144 -0
  698. package/core/.kenaz/docs/tools/GIT_HOOK_GUIDE.md +203 -0
  699. package/core/.kenaz/docs/tools/RAG_INTEGRATION_GUIDE.md +306 -0
  700. package/core/.kenaz/docs/tools/README.md +99 -0
  701. package/core/.kenaz/docs/tools/TIME_ESTIMATION_GUIDE.md +299 -0
  702. package/core/.kenaz/docs/tools/package-lock.json +618 -0
  703. package/core/.kenaz/docs/tools/package.json +34 -0
  704. package/core/.kenaz/docs/tools/src/cli.ts +365 -0
  705. package/core/.kenaz/docs/tools/src/field-extractor.ts +152 -0
  706. package/core/.kenaz/docs/tools/src/index.ts +14 -0
  707. package/core/.kenaz/docs/tools/src/registry.ts +664 -0
  708. package/core/.kenaz/docs/tools/src/search.ts +195 -0
  709. package/core/.kenaz/docs/tools/src/types.ts +88 -0
  710. package/core/.kenaz/docs/tools/test-git-hook.sh +79 -0
  711. package/core/.kenaz/docs/tools/tsconfig.json +20 -0
  712. package/core/.kenaz/docs/tools/verify.sh +106 -0
  713. package/core/.kenaz/episodes/SESSION_ansuz-prompt-1785087304784.yaml +68 -0
  714. package/core/.kenaz/huginn-exploration.json +8 -0
  715. package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -0
  716. package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -0
  717. package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -0
  718. package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -0
  719. package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -0
  720. package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -0
  721. package/core/.kenaz/main-memory/README.md +230 -0
  722. package/core/.kenaz/main-memory/kenaz/cortex/001_cortex_overview.md +168 -0
  723. package/core/.kenaz/main-memory/kenaz/cortex/001_rag_engine_overview.md +30 -0
  724. package/core/.kenaz/main-memory/kenaz/memory-system/001_three_tier_architecture.md +361 -0
  725. package/core/.kenaz/main-memory/kenaz/pipeline/001_task_execution_flow.md +117 -0
  726. package/core/.kenaz/main-memory/kenaz/pipeline/001_task_granularity.md +154 -0
  727. package/core/.kenaz/main-memory/kenaz/pipeline/002_team_priority.md +51 -0
  728. package/core/.kenaz/main-memory/kenaz/pipeline/003_feature_id_system.md +46 -0
  729. package/core/.kenaz/main-memory/kenaz/rules/001_gen_007_self_containment.md +108 -0
  730. package/core/.kenaz/main-memory/kenaz/rules/001_rule_priority_system.md +121 -0
  731. package/core/.kenaz/main-memory/kenaz/rules/002_agt_006_pre_check.md +0 -0
  732. package/core/.kenaz/main-memory/kenaz/rules/003_ppl_002_task_archival.md +48 -0
  733. package/core/.kenaz/main-memory/kenaz/system/001_dual_track_task_system.md +197 -0
  734. package/core/.kenaz/main-memory/knowledge/patterns/001_json_bom_issue.md +78 -0
  735. package/core/.kenaz/main-memory/knowledge/patterns/001_leader_worker_pattern.md +0 -0
  736. package/core/.kenaz/main-memory/knowledge/patterns/002_scope_path_conflict.md +115 -0
  737. package/core/.kenaz/main-memory/knowledge/patterns/002_task_dependency_resolution.md +223 -0
  738. package/core/.kenaz/main-memory/knowledge/patterns/003_question_protocol.md +242 -0
  739. package/core/.kenaz/main-memory/knowledge/patterns/004_user_insight_value.md +111 -0
  740. package/core/.kenaz/main-memory/knowledge/patterns/005_file_lock_pattern.md +274 -0
  741. package/core/.kenaz/main-memory/knowledge/references/001_ppl_rules.md +28 -0
  742. package/core/.kenaz/main-memory/knowledge/solutions/001_json_bom_issue.md +137 -0
  743. package/core/.kenaz/main-memory/knowledge/solutions/001_team_invocation.md +0 -0
  744. package/core/.kenaz/main-memory/knowledge/solutions/002_passive_monitoring.md +0 -0
  745. package/core/.kenaz/main-memory/knowledge/solutions/003_windows_file_race_condition.md +239 -0
  746. package/core/.kenaz/main-memory/registry.json +326 -0
  747. package/core/.kenaz/main-memory/version.yaml +58 -0
  748. package/core/.kenaz/muninn_episode_write.ps1 +17 -0
  749. package/core/.kenaz/muninn_snapshot11_rune_candidates.md +63 -0
  750. package/core/.kenaz/muninn_snapshot1_rune_candidates.md +49 -0
  751. package/core/.kenaz/orphan_audit_2026-05.md +181 -0
  752. package/core/.kenaz/ownership.yaml +155 -0
  753. package/core/.kenaz/peer.yaml +72 -0
  754. package/core/.kenaz/project.yaml +62 -0
  755. package/core/.kenaz/rune_ansuz_literature_validation_strategy.md +40 -0
  756. package/core/.kenaz/rune_efe_active_inference_validation.md +36 -0
  757. package/core/.kenaz/rune_yggdrasil_filesystem_scoped_project.md +41 -0
  758. package/core/.kenaz/rune_yggdrasil_project_identity_folder_name.md +41 -0
  759. package/core/.kenaz/rune_yggdrasil_relay_protocol_project_mismatch.md +39 -0
  760. package/core/.kenaz/runes_TASK_1428.json +57 -0
  761. package/core/.kenaz/specialist/ansuz_motivational_state.schema.json +138 -0
  762. package/core/.kenaz/specialist/ansuz_rules.yaml +241 -0
  763. package/core/.kenaz/specialist/ansuz_session_handoff.schema.json +128 -0
  764. package/core/.kenaz/specialist/chart_TASK_1657.json +50 -0
  765. package/core/.kenaz/specialist/cognitive-hub-e2e.test.js +494 -0
  766. package/core/.kenaz/specialist/daemon-manifest.yaml +168 -0
  767. package/core/.kenaz/specialist/empty_ac_audit_report.md +63 -0
  768. package/core/.kenaz/specialist/episode_muninn_completion_1785056865691.json +91 -0
  769. package/core/.kenaz/specialist/field_report_watermark.json +1 -0
  770. package/core/.kenaz/specialist/gc1826_driver.js +118 -0
  771. package/core/.kenaz/specialist/heimdall_proposal_1017c07f.json +13 -0
  772. package/core/.kenaz/specialist/heimdall_proposal_3dfaa8b9.json +12 -0
  773. package/core/.kenaz/specialist/heimdall_proposal_57af18f9.json +15 -0
  774. package/core/.kenaz/specialist/heimdall_proposal_57b1aa1b.json +14 -0
  775. package/core/.kenaz/specialist/heimdall_proposal_5a3c9473.json +13 -0
  776. package/core/.kenaz/specialist/identity_verdicts/TASK_1073.json +75 -0
  777. package/core/.kenaz/specialist/identity_verdicts/TASK_TEST.json +77 -0
  778. package/core/.kenaz/specialist/memory_guard_fired.json +1 -0
  779. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -0
  780. package/core/.kenaz/specialist/memory_source_priority.yaml +260 -0
  781. package/core/.kenaz/specialist/motivational_state.schema.json +62 -0
  782. package/core/.kenaz/specialist/prompt_1775.txt +317 -0
  783. package/core/.kenaz/specialist/prompt_1776.txt +326 -0
  784. package/core/.kenaz/specialist/prompt_1791.txt +320 -0
  785. package/core/.kenaz/specialist/prompt_1792.txt +320 -0
  786. package/core/.kenaz/specialist/prompt_1795.txt +281 -0
  787. package/core/.kenaz/specialist/prompt_1813.txt +332 -0
  788. package/core/.kenaz/specialist/prompt_1814.txt +401 -0
  789. package/core/.kenaz/specialist/prompt_1815.txt +394 -0
  790. package/core/.kenaz/specialist/prompt_1816.txt +437 -0
  791. package/core/.kenaz/specialist/prompt_1818.txt +361 -0
  792. package/core/.kenaz/specialist/prompt_1819.txt +421 -0
  793. package/core/.kenaz/specialist/prompt_1821.txt +345 -0
  794. package/core/.kenaz/specialist/prompt_1822.txt +313 -0
  795. package/core/.kenaz/specialist/prompt_1824.txt +348 -0
  796. package/core/.kenaz/specialist/prompt_1826.txt +388 -0
  797. package/core/.kenaz/specialist/retired_organs/README.md +58 -0
  798. package/core/.kenaz/specialist/rune_calibration_poc.md +175 -0
  799. package/core/.kenaz/specialist/rune_efe_salience_gap.md +21 -0
  800. package/core/.kenaz/specialist/rune_ember_autonomous_dispatch_gate.md +24 -0
  801. package/core/.kenaz/specialist/rune_post_dispatch_verification_pattern.md +31 -0
  802. package/core/.kenaz/specialist/rune_surprise_proxy_improvement.md +28 -0
  803. package/core/.kenaz/specialist/rune_worktree_dispatch_pattern.md +31 -0
  804. package/core/.kenaz/specialist/rune_yaml_filter_desync_fix.md +26 -0
  805. package/core/.kenaz/specialist/rune_yggdrasil_project_identity_folder_name.md +24 -0
  806. package/core/.kenaz/specialist/sessions/chart_TASK_1599.json +41 -0
  807. package/core/.kenaz/specialist/sessions/hmap_028aa781-aad2-48c0-9a61-5540565d4a7f.json +5 -0
  808. package/core/.kenaz/specialist/sessions/hmap_20210387-0c64-400c-9d0a-53042a0cbd89.json +5 -0
  809. package/core/.kenaz/specialist/sessions/hmap_c1375c5d-fd9f-44bd-a83e-353ecbb0392f.json +5 -0
  810. package/core/.kenaz/specialist/sessions/scratch_028aa781-aad2-48c0-9a61-5540565d4a7f.json +6 -0
  811. package/core/.kenaz/specialist/sessions/scratch_0acc0fb3-24f9-453f-9e92-6ef1cdf03a7c.json +6 -0
  812. package/core/.kenaz/specialist/sessions/scratch_20210387-0c64-400c-9d0a-53042a0cbd89.json +6 -0
  813. package/core/.kenaz/specialist/sessions/scratch_2f10623e-b7a1-4647-905e-9dc7c4d99f9c.json +6 -0
  814. package/core/.kenaz/specialist/sessions/scratch_5a38b62d-608a-488c-82f0-7e47e95e0905.json +6 -0
  815. package/core/.kenaz/specialist/sessions/scratch_5d9758ef-d9b6-4ffe-a017-3ed26d0a313b.json +6 -0
  816. package/core/.kenaz/specialist/sessions/scratch_c1375c5d-fd9f-44bd-a83e-353ecbb0392f.json +6 -0
  817. package/core/.kenaz/specialist/sessions/scratch_c9f3c711-b73d-4fcd-ad2a-7b2bc024ee77.json +6 -0
  818. package/core/.kenaz/specialist/sessions/scratch_e72e5274-1c81-4655-a05a-f87d1ecb2917.json +6 -0
  819. package/core/.kenaz/specialist/sessions/scratch_f740cdbe-f756-41fc-98fb-2b1413d51901.json +6 -0
  820. package/core/.kenaz/specialist/t1777_probe.txt +1 -0
  821. package/core/.kenaz/specialist/task1825_update.json +43 -0
  822. package/core/.kenaz/specialist/task1827_update.json +43 -0
  823. package/core/.kenaz/specialist/task1828_update.json +43 -0
  824. package/core/.kenaz/specialist/taskgen/consolidation_i.json +38 -0
  825. package/core/.kenaz/specialist/taskgen/csharp_a.json +44 -0
  826. package/core/.kenaz/specialist/taskgen/csharp_b.json +40 -0
  827. package/core/.kenaz/specialist/taskgen/embedding_e.json +34 -0
  828. package/core/.kenaz/specialist/taskgen/hygiene_fg.json +39 -0
  829. package/core/.kenaz/specialist/taskgen/pathology_h.json +36 -0
  830. package/core/.kenaz/specialist/taskgen/unity_noise_d.json +38 -0
  831. package/core/.kenaz/specialist/taskgen/visibility_c.json +38 -0
  832. package/core/.kenaz/specialist/verify_1837.js +302 -0
  833. package/core/.kenaz/specialist/verify_1838.js +124 -0
  834. package/core/.kenaz/tmp/heimdall_proposal_fc7e0944.json +10 -0
  835. package/core/.kenaz/tmp_heimdall_proposal_8d3e392.json +13 -0
  836. package/core/.kenaz/tmp_props.json +1 -0
  837. package/core/.kenaz/tmp_sounddrop_t1_prompt.txt +371 -0
  838. package/core/.kenaz/user_model.schema.json +107 -0
  839. package/core/.matrix/README.md +257 -0
  840. package/core/.matrix/index.matrix +5 -0
  841. package/core/.mcp.json +8 -0
  842. package/core/.prefrontal/.gitkeep +0 -0
  843. package/core/.prefrontal/proposals/.gitkeep +0 -0
  844. package/core/.rule/INDEX.yaml +99 -0
  845. package/core/.rule/MECHANIZED.md +61 -0
  846. package/core/.rule/code/README.md +211 -0
  847. package/core/.rule/code/backend.yaml +294 -0
  848. package/core/.rule/code/checklist.yaml +258 -0
  849. package/core/.rule/code/maintainability.yaml +227 -0
  850. package/core/.rule/code/naming.yaml +106 -0
  851. package/core/.rule/code/principles.yaml +71 -0
  852. package/core/.rule/code/reusability.yaml +192 -0
  853. package/core/.rule/code/structure.yaml +218 -0
  854. package/core/.rule/code/style.yaml +197 -0
  855. package/core/.rule/design/ui.yaml +79 -0
  856. package/core/.rule/general/agent-workflow.yaml +572 -0
  857. package/core/.rule/general/cortex-memory-recall.yaml +446 -0
  858. package/core/.rule/general/cortex-tools.yaml +181 -0
  859. package/core/.rule/general/cross-platform.yaml +107 -0
  860. package/core/.rule/general/ddx-synapse-stack.yaml +162 -0
  861. package/core/.rule/general/file-size-threshold.yaml +45 -0
  862. package/core/.rule/general/identity-contract.schema.yaml +251 -0
  863. package/core/.rule/general/memory-language.yaml +67 -0
  864. package/core/.rule/general/memory-source-priority.yaml +207 -0
  865. package/core/.rule/general/pipeline.yaml +246 -0
  866. package/core/.rule/general/principles.yaml +77 -0
  867. package/core/.rule/general/project-self-containment.yaml +253 -0
  868. package/core/.rule/general/rust-verify-target.yaml +80 -0
  869. package/core/.rule/general/specialist-identity-contract.yaml +153 -0
  870. package/core/.rule/general/sunny-access-control.yaml +60 -0
  871. package/core/.rule/general/synapse-code-intelligence.yaml +375 -0
  872. package/core/.rule/general/synapse-cognitive-fusion.yaml +301 -0
  873. package/core/.rule/general/system-invariants.yaml +248 -0
  874. package/core/.rule/general/task-pool-routing.yaml +102 -0
  875. package/core/.rule/general/worktree-isolation.yaml +79 -0
  876. package/core/.rule/pipeline/PPL-002-completed-task-archival.yaml +528 -0
  877. package/core/.rule/pipeline/auto-update.yaml +282 -0
  878. package/core/.rule/pipeline/task-execution.yaml +368 -0
  879. package/core/.rule/tools/check-permissions.js +197 -0
  880. package/core/.specialist/Amelia/README.md +329 -0
  881. package/core/.specialist/Amelia/capability.yaml +386 -0
  882. package/core/.specialist/Amelia/knowledge.yaml +217 -0
  883. package/core/.specialist/Amelia/principles.yaml +10 -0
  884. package/core/.specialist/Amelia/rule.yaml +79 -0
  885. package/core/.specialist/Amelia/workflow.yaml +241 -0
  886. package/core/.specialist/Ansuz/HEURISTIC_BOOT_ROUTING.md +39 -0
  887. package/core/.specialist/Ansuz/ROUTING.md +35 -0
  888. package/core/.specialist/Ansuz/RULES.md +79 -0
  889. package/core/.specialist/Ansuz/SYSTEM_DOC.md +611 -0
  890. package/core/.specialist/Ansuz/VERIFY.md +38 -0
  891. package/core/.specialist/Ansuz/capability.yaml +338 -0
  892. package/core/.specialist/Ansuz/cognitive_principles.yaml +62 -0
  893. package/core/.specialist/Ansuz/concepts/agent_types.yaml +22 -0
  894. package/core/.specialist/Ansuz/concepts/autonomous_agenda.yaml +170 -0
  895. package/core/.specialist/Ansuz/concepts/autonomy_contract.yaml +126 -0
  896. package/core/.specialist/Ansuz/concepts/boot_ritual.yaml +107 -0
  897. package/core/.specialist/Ansuz/concepts/boot_rules_summary.yaml +16 -0
  898. package/core/.specialist/Ansuz/concepts/capability_pointers.yaml +21 -0
  899. package/core/.specialist/Ansuz/concepts/cognitive_fluency_contract.yaml +264 -0
  900. package/core/.specialist/Ansuz/concepts/cognitive_systems_competence_map.yaml +14 -0
  901. package/core/.specialist/Ansuz/concepts/cognitive_systems_hippocampus.yaml +14 -0
  902. package/core/.specialist/Ansuz/concepts/cognitive_systems_overview.yaml +27 -0
  903. package/core/.specialist/Ansuz/concepts/cognitive_systems_serendipity.yaml +14 -0
  904. package/core/.specialist/Ansuz/concepts/cognitive_systems_turn_context.yaml +13 -0
  905. package/core/.specialist/Ansuz/concepts/commitment_layer.yaml +150 -0
  906. package/core/.specialist/Ansuz/concepts/completion_tracking.yaml +29 -0
  907. package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +28 -0
  908. package/core/.specialist/Ansuz/concepts/evolvable_guardrails.yaml +20 -0
  909. package/core/.specialist/Ansuz/concepts/fatigue_handoff.yaml +84 -0
  910. package/core/.specialist/Ansuz/concepts/formation_quick_ref.yaml +28 -0
  911. package/core/.specialist/Ansuz/concepts/guardrails_immutable.yaml +31 -0
  912. package/core/.specialist/Ansuz/concepts/identity.yaml +48 -0
  913. package/core/.specialist/Ansuz/concepts/interaction_protocol.yaml +42 -0
  914. package/core/.specialist/Ansuz/concepts/meta_evolvable_tier.yaml +28 -0
  915. package/core/.specialist/Ansuz/concepts/mindset.yaml +18 -0
  916. package/core/.specialist/Ansuz/concepts/phase9_verification.yaml +21 -0
  917. package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +66 -0
  918. package/core/.specialist/Ansuz/concepts/principles.yaml +65 -0
  919. package/core/.specialist/Ansuz/concepts/self_evolution_overview.yaml +53 -0
  920. package/core/.specialist/Ansuz/concepts/task_first_protocol.yaml +7 -0
  921. package/core/.specialist/Ansuz/concepts/task_pool.yaml +119 -0
  922. package/core/.specialist/Ansuz/concepts/task_system.yaml +22 -0
  923. package/core/.specialist/Ansuz/concepts/write_verification.yaml +26 -0
  924. package/core/.specialist/Ansuz/core.yaml +224 -0
  925. package/core/.specialist/Ansuz/decisions/ADR-requirement-graph-layer.md +390 -0
  926. package/core/.specialist/Ansuz/elixir.yaml +70 -0
  927. package/core/.specialist/Ansuz/exo_cortex_tools.yaml +13 -0
  928. package/core/.specialist/Ansuz/experiments/boot_ablation_replay.js +197 -0
  929. package/core/.specialist/Ansuz/guards/blind_spot_guards.yaml +28 -0
  930. package/core/.specialist/Ansuz/guards/lesson_guards.yaml +203 -0
  931. package/core/.specialist/Ansuz/identity_contract.yaml +375 -0
  932. package/core/.specialist/Ansuz/knowledge.yaml +284 -0
  933. package/core/.specialist/Ansuz/modules/amelia_review.yaml +112 -0
  934. package/core/.specialist/Ansuz/modules/ddx.yaml +47 -0
  935. package/core/.specialist/Ansuz/modules/design_checks.yaml +47 -0
  936. package/core/.specialist/Ansuz/modules/identity_verification.yaml +119 -0
  937. package/core/.specialist/Ansuz/modules/loop_guard.yaml +36 -0
  938. package/core/.specialist/Ansuz/modules/prevention.yaml +84 -0
  939. package/core/.specialist/Ansuz/modules/prophecy.yaml +95 -0
  940. package/core/.specialist/Ansuz/modules/pulse_empty.yaml +36 -0
  941. package/core/.specialist/Ansuz/modules/qa_intake.yaml +29 -0
  942. package/core/.specialist/Ansuz/modules/recall.yaml +45 -0
  943. package/core/.specialist/Ansuz/principles.yaml +31 -0
  944. package/core/.specialist/Ansuz/research/EMBER_architecture_comparison.md +142 -0
  945. package/core/.specialist/Ansuz/rule.yaml +97 -0
  946. package/core/.specialist/Ansuz/self_model_seed.backup.json +100 -0
  947. package/core/.specialist/Ansuz/self_model_seed.json +100 -0
  948. package/core/.specialist/Ansuz/self_model_seed.sha256 +1 -0
  949. package/core/.specialist/Ansuz/tools/ansuz-handoff-snapshot.js +626 -0
  950. package/core/.specialist/Ansuz/tools/ansuz-handoff.js +437 -0
  951. package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.js +1254 -0
  952. package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.test.js +137 -0
  953. package/core/.specialist/Ansuz/tools/ansuz-state.js +288 -0
  954. package/core/.specialist/Ansuz/tools/bootstrap-anchors.js +289 -0
  955. package/core/.specialist/Ansuz/tools/handoff-freshness-milestone.test.js +73 -0
  956. package/core/.specialist/Ansuz/tools/handoff-resolver.js +207 -0
  957. package/core/.specialist/Ansuz/tools/handoff-session-identity.test.js +333 -0
  958. package/core/.specialist/Ansuz/tools/handoff-snapshot-args.test.js +25 -0
  959. package/core/.specialist/Ansuz/tools/install-claude-hooks.js +354 -0
  960. package/core/.specialist/Ansuz/tools/install-claude-hooks.test.js +110 -0
  961. package/core/.specialist/Ansuz/tools/package.json +16 -0
  962. package/core/.specialist/Ansuz/tools/preservation-coordinator.js +504 -0
  963. package/core/.specialist/Ansuz/tools/verify-and-land.js +1012 -0
  964. package/core/.specialist/Ansuz/tools/verify-and-land.test.js +1099 -0
  965. package/core/.specialist/Ansuz/workflow.yaml +1273 -0
  966. package/core/.specialist/Forseti/capability.yaml +210 -0
  967. package/core/.specialist/Forseti/core.yaml +176 -0
  968. package/core/.specialist/Forseti/principles.yaml +88 -0
  969. package/core/.specialist/Forseti/rule.yaml +86 -0
  970. package/core/.specialist/Heimdall/SKILL.md +65 -0
  971. package/core/.specialist/Heimdall/capability.yaml +134 -0
  972. package/core/.specialist/Heimdall/core.yaml +275 -0
  973. package/core/.specialist/Heimdall/principles.yaml +66 -0
  974. package/core/.specialist/Heimdall/rule.yaml +68 -0
  975. package/core/.specialist/Huginn/capability.yaml +234 -0
  976. package/core/.specialist/Huginn/core.yaml +482 -0
  977. package/core/.specialist/Huginn/principles.yaml +57 -0
  978. package/core/.specialist/Huginn/rule.yaml +121 -0
  979. package/core/.specialist/REFLECTION_TO_CONTROL.md +347 -0
  980. package/core/.specialist/ansuz_session_handoff.json +96 -0
  981. package/core/.specialist/experiments/rg_benchmark.js +523 -0
  982. package/core/.specialist/experiments/rg_benchmark_results.json +27 -0
  983. package/core/.specialist/shared/cognitive-primer.md +15 -0
  984. package/core/.specialist/shared/guard_schema.json +290 -0
  985. package/core/.specialist/shared/principles.yaml +213 -0
  986. package/core/.specialist/tests/runtime-discovery/README.md +224 -0
  987. package/core/.specialist/tests/runtime-discovery/active-project-polling.test.js +495 -0
  988. package/core/.specialist/tests/runtime-discovery/port-binding.test.js +418 -0
  989. package/core/.specialist/tests/runtime-discovery/resolver-priority.test.js +693 -0
  990. package/core/.specialist/tests/runtime-discovery/resolver-skip-on-invalid.test.js +467 -0
  991. package/core/.specialist/tests/runtime-discovery/resolver-symmetry.test.js +679 -0
  992. package/core/.specialist/tests/runtime-discovery/tail-cli-project-path.test.js +231 -0
  993. package/core/.specialist/tools/MEMORY_RECALL_TRIGGERS.md +156 -0
  994. package/core/.specialist/tools/README.md +129 -0
  995. package/core/.specialist/tools/__tests__/freya-cli-contract.test.js +156 -0
  996. package/core/.specialist/tools/__tests__/task-1863-boot-lease-reuse.test.js +99 -0
  997. package/core/.specialist/tools/__tests__/task-1917-orphan-detection.test.js +175 -0
  998. package/core/.specialist/tools/__tests__/task-1977-banner-cap.test.js +129 -0
  999. package/core/.specialist/tools/__tests__/task-1983-lease-project-path.test.js +96 -0
  1000. package/core/.specialist/tools/__tests__/task-2218-boot-session-id.test.js +118 -0
  1001. package/core/.specialist/tools/__tests__/task-2222-session-identity-interleave.test.js +203 -0
  1002. package/core/.specialist/tools/__tests__/task-2246-reap-honesty.test.js +96 -0
  1003. package/core/.specialist/tools/__tests__/task-2254-handoff-archive-contract.test.js +60 -0
  1004. package/core/.specialist/tools/__tests__/task-2272-open-card-status-quote.test.js +26 -0
  1005. package/core/.specialist/tools/__tests__/task-2374-usage-counter.test.js +34 -0
  1006. package/core/.specialist/tools/ansuz-boot-fast.js +1120 -0
  1007. package/core/.specialist/tools/ansuz-boot-fast.test.js +705 -0
  1008. package/core/.specialist/tools/canary.js +501 -0
  1009. package/core/.specialist/tools/context-injection.js +395 -0
  1010. package/core/.specialist/tools/design-reasoning.js +493 -0
  1011. package/core/.specialist/tools/discovery-fixtures.js +308 -0
  1012. package/core/.specialist/tools/discovery-runner.js +460 -0
  1013. package/core/.specialist/tools/failure-detector.js +1132 -0
  1014. package/core/.specialist/tools/identity-scorer.js +714 -0
  1015. package/core/.specialist/tools/jev-optional-context.js +107 -0
  1016. package/core/.specialist/tools/jev-optional-context.test.js +152 -0
  1017. package/core/.specialist/tools/lesson-guard.js +1084 -0
  1018. package/core/.specialist/tools/lib/chart-path.js +159 -0
  1019. package/core/.specialist/tools/lib/chart-path.test.js +171 -0
  1020. package/core/.specialist/tools/lib/project-resolver.js +200 -0
  1021. package/core/.specialist/tools/lib/project-resolver.test.js +241 -0
  1022. package/core/.specialist/tools/memory-action-log.js +839 -0
  1023. package/core/.specialist/tools/phase-gate.js +3754 -0
  1024. package/core/.specialist/tools/phase-gate.test.js +85 -0
  1025. package/core/.specialist/tools/phase9-policy.test.js +109 -0
  1026. package/core/.specialist/tools/precompute-runner.js +444 -0
  1027. package/core/.specialist/tools/prompt-files.test.js +169 -0
  1028. package/core/.specialist/tools/replay-detectors.js +290 -0
  1029. package/core/.specialist/tools/rule-engine.js +621 -0
  1030. package/core/.specialist/tools/session-boot-ownership.test.js +117 -0
  1031. package/core/.specialist/tools/session-project-state.test.js +65 -0
  1032. package/core/.specialist/tools/session.js +1866 -0
  1033. package/core/.specialist/tools/stream-lease.js +263 -0
  1034. package/core/.specialist/tools/task-bridge-jev.test.js +170 -0
  1035. package/core/.specialist/tools/task-bridge.js +765 -0
  1036. package/core/.specialist/tools/verify-identity.js +453 -0
  1037. package/core/.specialist/tools/wish-mandate-delivery.test.js +76 -0
  1038. package/core/.system/README.md +555 -0
  1039. package/core/.system/agent-skills/README.md +724 -0
  1040. package/core/.system/archive/README.md +499 -0
  1041. package/core/.system/assessment/README.md +645 -0
  1042. package/core/.system/cli-tools/README.md +724 -0
  1043. package/core/.system/collaboration/KNOWLEDGE.md +65 -0
  1044. package/core/.system/collaboration/README.md +570 -0
  1045. package/core/.system/cortex/MODULES.md +1046 -0
  1046. package/core/.system/dashboard/FRONTEND.md +812 -0
  1047. package/core/.system/dashboard/README.md +737 -0
  1048. package/core/.system/external-skills/README.md +522 -0
  1049. package/core/.system/failure/README.md +723 -0
  1050. package/core/.system/formation/LEGACY_SKILLS.md +972 -0
  1051. package/core/.system/formation/POST_COMPLETE.md +600 -0
  1052. package/core/.system/formation/README.md +1092 -0
  1053. package/core/.system/formation/TOOLS_REFERENCE.md +1145 -0
  1054. package/core/.system/githooks/README.md +846 -0
  1055. package/core/.system/kenaz/README.md +787 -0
  1056. package/core/.system/memory/README.md +716 -0
  1057. package/core/.system/pipeline/README.md +712 -0
  1058. package/core/.system/rule/README.md +794 -0
  1059. package/core/.system/specialist/README.md +669 -0
  1060. package/core/.system/specialist/STRUCTURE.md +262 -0
  1061. package/core/.system/sunny/README.md +476 -0
  1062. package/core/.system/sunny/RULES.json +89 -0
  1063. package/core/.system/sunny/knowledge/INDEX.md +37 -0
  1064. package/core/.system/sunny/knowledge/agent-teams-api.md +0 -0
  1065. package/core/.system/sunny/knowledge/ansuz-specialist.md +500 -0
  1066. package/core/.system/sunny/knowledge/claude-cli-spawn.md +177 -0
  1067. package/core/.system/sunny/knowledge/collaboration-api.md +925 -0
  1068. package/core/.system/sunny/teammates/README.md +107 -0
  1069. package/core/.system/sunny/teammates/how_to_use.yaml +101 -0
  1070. package/core/.system/sunny/teammates/tools/headless/launcher.js +318 -0
  1071. package/core/.system/sunny/teammates/tools/powershell/launcher.js +241 -0
  1072. package/core/.system/sunny/teammates/tools/powershell/start.bat +50 -0
  1073. package/core/.system/sunny/teammates/tools/utils/env.js +96 -0
  1074. package/core/.system/sunny/teammates/tools/utils/format-output.js +329 -0
  1075. package/core/.system/sunny/tools.json +101 -0
  1076. package/core/.vendor/js-yaml.cjs +2795 -0
  1077. package/core/.vendor/require-yaml.cjs +8 -0
  1078. package/core/CLAUDE.md +172 -0
  1079. package/core/KENAZ_CORE_VERSION +1 -0
  1080. package/core/dev/ALAYA_FIELD_FEEDBACK_DESIGN.md +126 -0
  1081. package/core/dev/ANSUZ_SCAFFOLD_DIET_2026-09-20.md +231 -0
  1082. package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -0
  1083. package/core/dev/ANSUZ_WORKFLOW_MODERNIZATION.md +143 -0
  1084. package/core/dev/CORTEX_META_GRAPH_ASSESSMENT.md +99 -0
  1085. package/core/dev/FREYA_DOWNSTREAM_BUGS_2026-09-19.md +44 -0
  1086. package/core/dev/GLOBAL_MEMORY_IDENTITY_CONSOLIDATION.md +263 -0
  1087. package/core/dev/GUARD_EFFECTIVENESS_DESIGN.md +180 -0
  1088. package/core/dev/HEALTH_GATE_VARIANCE_ASSESSMENT.md +271 -0
  1089. package/core/dev/HOOK_DIET_2026-09-21.md +126 -0
  1090. package/core/dev/JEV_INTEGRATION_PLAN.md +189 -0
  1091. package/core/dev/KENAZ_AGENT_CONNECT_v1_SPEC.md +139 -0
  1092. package/core/dev/MOBILE_DESIGN_SPEC.md +569 -0
  1093. package/core/dev/MULTI_ANSUZ_SPEC.md +136 -0
  1094. package/core/dev/MUNINN_RETIREMENT.md +115 -0
  1095. package/core/dev/PHASE3_ATTRIBUTION_POPULATION_DIAGNOSIS.md +211 -0
  1096. package/core/dev/PHASE3_P3-2B_TASKLEVEL_PROBE.md +83 -0
  1097. package/core/dev/PHASE3_P3-2_POPULATION_DISJOINTNESS.md +83 -0
  1098. package/core/dev/PHASE3_P3-2_REMEASURE_REPORT.md +231 -0
  1099. package/core/dev/PREDICTION_GROUNDING_DESIGN.md +376 -0
  1100. package/core/dev/PRODUCT_SPEC.md +317 -0
  1101. package/core/dev/PRODUCT_SPEC_FOR_AI_tepmlate.md +716 -0
  1102. package/core/dev/PROJECT_REGISTRY_DUPLICATE_ASSESSMENT.md +86 -0
  1103. package/core/dev/STARTUP_TIMELINE_TASK_1995.md +181 -0
  1104. package/core/dev/SYNAPSE_WORKER_PER_PROJECT.md +881 -0
  1105. package/core/dev/TASK_1680_P3-3_DISPATCH_SPEC.md +96 -0
  1106. package/core/dev/TASK_1684_CALIBRATION_IMPACT_ANALYSIS.md +170 -0
  1107. package/core/dev/TASK_1684_FORK_A_GOODHART_DESIGN.md +171 -0
  1108. package/core/dev/TASK_1684_P3_2_REMEASURE_VALIDATION.md +262 -0
  1109. package/core/dev/TASK_1903_SIGNAL_AUC_MEASUREMENT.md +207 -0
  1110. package/core/dev/TASK_1904_BRANCH_INTEGRITY_AUDIT.md +146 -0
  1111. package/core/dev/ansuz-eval/README.md +38 -0
  1112. package/core/dev/ansuz-eval/build-fixtures.js +87 -0
  1113. package/core/dev/ansuz-eval/grade.js +96 -0
  1114. package/core/dev/ansuz-eval/grader.test.js +117 -0
  1115. package/core/dev/ansuz-eval/tasks.md +32 -0
  1116. package/core/dev/archive/ansuz/BOOT_FULL.md +141 -0
  1117. package/core/dev/archive/ansuz/agents/ansuz-boot.md +176 -0
  1118. package/core/dev/archive/ansuz/designs/alaya-passive-surfacing.md +84 -0
  1119. package/core/dev/archive/ansuz/designs/cc_memory_indexing.md +307 -0
  1120. package/core/dev/archive/ansuz/designs/check_guard.md +322 -0
  1121. package/core/dev/archive/ansuz/designs/consequence_loop.md +227 -0
  1122. package/core/dev/archive/ansuz/designs/mcp_kuzudb_lock_architecture.md +312 -0
  1123. package/core/dev/archive/ansuz/designs/phase0_git_enrichment.md +278 -0
  1124. package/core/dev/archive/ansuz/designs/refactor_mcp_tools_synapse.md +490 -0
  1125. package/core/dev/archive/ansuz/designs/semantic_markdown_extraction.md +167 -0
  1126. package/core/dev/archive/ansuz/designs/task_1372_prompt_injects_into.md +193 -0
  1127. package/core/dev/archive/ansuz/designs/thought_stack.md +286 -0
  1128. package/core/dev/archive/ansuz/investigations/reachability_unified_api_2026-04-11.md +330 -0
  1129. package/core/dev/archive/ansuz/investigations/synapse_orphans_indexing_bug_2026-04-11.md +352 -0
  1130. package/core/dev/archive/ansuz/investigations/synapse_orphans_reindex_verification_2026-04-11.md +48 -0
  1131. package/core/dev/archive/ansuz/investigations/synapse_orphans_verification_2026-04-11.md +170 -0
  1132. package/core/dev/archive/ansuz/legacy/kernel.yaml +399 -0
  1133. package/core/dev/archive/ansuz/legacy/phases/phase_0.yaml +418 -0
  1134. package/core/dev/archive/ansuz/legacy/phases/phase_1.yaml +376 -0
  1135. package/core/dev/archive/ansuz/legacy/phases/phase_10.yaml +35 -0
  1136. package/core/dev/archive/ansuz/legacy/phases/phase_11.yaml +582 -0
  1137. package/core/dev/archive/ansuz/legacy/phases/phase_2.yaml +94 -0
  1138. package/core/dev/archive/ansuz/legacy/phases/phase_3.yaml +274 -0
  1139. package/core/dev/archive/ansuz/legacy/phases/phase_4.yaml +67 -0
  1140. package/core/dev/archive/ansuz/legacy/phases/phase_5.yaml +32 -0
  1141. package/core/dev/archive/ansuz/legacy/phases/phase_6.yaml +91 -0
  1142. package/core/dev/archive/ansuz/legacy/phases/phase_7.yaml +58 -0
  1143. package/core/dev/archive/ansuz/legacy/phases/phase_8.yaml +146 -0
  1144. package/core/dev/archive/ansuz/legacy/phases/phase_9.yaml +424 -0
  1145. package/core/dev/archive/ansuz/specs/INDEX_bifrost_hook_parity.yaml +92 -0
  1146. package/core/dev/archive/ansuz/specs/active-inference-formation.yaml +453 -0
  1147. package/core/dev/archive/ansuz/specs/brief_TASK_1228_userpromptsubmit.yaml +191 -0
  1148. package/core/dev/archive/ansuz/specs/brief_TASK_1229_pretooluse_posttooluse.yaml +250 -0
  1149. package/core/dev/archive/ansuz/specs/brief_TASK_1230_sessionstart_stop.yaml +301 -0
  1150. package/core/dev/archive/ansuz/specs/brief_TASK_1231_verification.yaml +272 -0
  1151. package/core/dev/design/PHASE4_DESIGN.md +559 -0
  1152. package/core/dev/design/dashboard_docs_integration_PLAN.md +388 -0
  1153. package/core/dev/design/matrix_format_ABBREVIATIONS.md +655 -0
  1154. package/core/dev/design/matrix_format_DESIGN_SPEC.md +601 -0
  1155. package/core/dev/design/matrix_format_DISCUSSION_SUMMARY.md +484 -0
  1156. package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -0
  1157. package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -0
  1158. package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -0
  1159. package/core/dev/design/matrix_format_SYNTAX_BNF.md +754 -0
  1160. package/core/dev/design/matrix_format_TOKEN_COMPARISON.md +484 -0
  1161. package/core/dev/design/matrix_v2_COGNITIVE_DESIGN.md +503 -0
  1162. package/core/dev/design/matrix_v2_DISCUSSION_SUMMARY.md +414 -0
  1163. package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -0
  1164. package/core/dev/designs/EXPANDABLE_CARD_DESIGN_SPEC.md +509 -0
  1165. package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -0
  1166. package/core/dev/jev-eval/ANNOTATION_GUIDE.md +68 -0
  1167. package/core/dev/jev-eval/BASELINE_COMPARISON.md +45 -0
  1168. package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -0
  1169. package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -0
  1170. package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -0
  1171. package/core/dev/jev-eval/PACKAGING_VERIFICATION.md +42 -0
  1172. package/core/dev/jev-eval/README.md +101 -0
  1173. package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -0
  1174. package/core/dev/jev-eval/VALIDATION_PROGRESS.md +54 -0
  1175. package/core/dev/jev-eval/cli.mjs +50 -0
  1176. package/core/dev/jev-eval/context.mjs +183 -0
  1177. package/core/dev/jev-eval/corpus.mjs +186 -0
  1178. package/core/dev/jev-eval/fixtures/conversation-review.seed.jsonl +6 -0
  1179. package/core/dev/jev-eval/fixtures/synthetic.jsonl +11 -0
  1180. package/core/dev/jev-eval/metrics.mjs +67 -0
  1181. package/core/dev/jev-eval/muninn-baseline.mjs +119 -0
  1182. package/core/dev/jev-eval/providers.mjs +95 -0
  1183. package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -0
  1184. package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -0
  1185. package/core/dev/jev-eval/reports/conversation-review.pending.json +151 -0
  1186. package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -0
  1187. package/core/dev/jev-eval/reports/length-probe-2026-09-20.json +133 -0
  1188. package/core/dev/jev-eval/reports/live-smoke-2026-09-20.json +616 -0
  1189. package/core/dev/jev-eval/reports/muninn-baseline-2026-09-20.json +52 -0
  1190. package/core/dev/jev-eval/reports/resilience-2026-09-20.json +520 -0
  1191. package/core/dev/jev-eval/reports/rules-baseline.json +197 -0
  1192. package/core/dev/jev-eval/reports/synthetic-review.pending.json +357 -0
  1193. package/core/dev/jev-eval/resilience.mjs +71 -0
  1194. package/core/dev/jev-eval/review.mjs +78 -0
  1195. package/core/dev/jev-eval/schema.mjs +87 -0
  1196. package/core/dev/jev-eval/test/context.test.mjs +95 -0
  1197. package/core/dev/jev-eval/test/corpus.test.mjs +102 -0
  1198. package/core/dev/jev-eval/test/eval.test.mjs +146 -0
  1199. package/core/dev/jev-eval/test/muninn-baseline.test.mjs +71 -0
  1200. package/core/dev/jev-eval/test/resilience.test.mjs +72 -0
  1201. package/core/dev/jev-eval/test/review.test.mjs +61 -0
  1202. package/core/dev/pipeline/LOCK_STATE.json +4 -0
  1203. package/core/dev/pipeline/OPTIMIZATION_PROPOSALS.json +32 -0
  1204. package/core/dev/pipeline/PHASE3_PLANNING_SUMMARY.md +338 -0
  1205. package/core/dev/pipeline/PIPELINE_CONFIG.json +46 -0
  1206. package/core/dev/pipeline/PROGRESS.md +113 -0
  1207. package/core/dev/pipeline/PROGRESS_template.md +860 -0
  1208. package/core/dev/pipeline/QA_REPORT_F008.md +431 -0
  1209. package/core/dev/pipeline/QA_REPORT_LOCAL_CLAUDE_DIAGNOSIS.md +583 -0
  1210. package/core/dev/pipeline/README.md +210 -0
  1211. package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -0
  1212. package/core/dev/pipeline/TASK_QUEUE.json +192 -0
  1213. package/core/dev/pipeline/WORKFLOW_GUIDE.md +824 -0
  1214. package/core/dev/pipeline/archived/TASK_015_tauri_init.yaml +53 -0
  1215. package/core/dev/pipeline/archived/TASK_016_tauri_commands.yaml +54 -0
  1216. package/core/dev/pipeline/archived/TASK_017_rust_pty.yaml +54 -0
  1217. package/core/dev/pipeline/archived/TASK_018_tauri_bundle.yaml +53 -0
  1218. package/core/dev/pipeline/archived/TASK_019_release_workflow.yaml +56 -0
  1219. package/core/dev/pipeline/archived/TASK_020_update_ui.yaml +54 -0
  1220. package/core/dev/pipeline/archived/TASK_021_update_notification_ui.yaml +55 -0
  1221. package/core/dev/pipeline/archived/TASK_022_remove_node_pty.yaml +65 -0
  1222. package/core/dev/pipeline/archived/TASK_023_api_routes_migration.yaml +86 -0
  1223. package/core/dev/pipeline/archived/TASK_024_feature_id_system.yaml +51 -0
  1224. package/core/dev/pipeline/archived/TASK_025_executable_acceptance.yaml +66 -0
  1225. package/core/dev/pipeline/archived/TASK_026_spec_versioning.yaml +56 -0
  1226. package/core/dev/pipeline/archived/TASK_027_drift_detection.yaml +60 -0
  1227. package/core/dev/pipeline/archived/TASK_028_tauri_pty_manager.yaml +60 -0
  1228. package/core/dev/pipeline/archived/TASK_029_tauri_pty_commands.yaml +62 -0
  1229. package/core/dev/pipeline/archived/TASK_030_frontend_session_adapter.yaml +54 -0
  1230. package/core/dev/pipeline/archived/TASK_031_terminal_tauri_events.yaml +56 -0
  1231. package/core/dev/pipeline/archived/TASK_041_scheduler_data_models.yaml +54 -0
  1232. package/core/dev/pipeline/archived/TASK_042_path_lock_manager.yaml +53 -0
  1233. package/core/dev/pipeline/archived/TASK_043_process_manager.yaml +57 -0
  1234. package/core/dev/pipeline/archived/TASK_044_git_integration.yaml +85 -0
  1235. package/core/dev/pipeline/archived/TASK_045_task_queue_manager.yaml +54 -0
  1236. package/core/dev/pipeline/archived/TASK_046_scheduler_dispatcher.yaml +60 -0
  1237. package/core/dev/pipeline/archived/TASK_047_tauri_scheduler_commands.yaml +55 -0
  1238. package/core/dev/pipeline/archived/TASK_048_realtime_status_push.yaml +92 -0
  1239. package/core/dev/pipeline/archived/TASK_049_frontend_scheduler_integration.yaml +58 -0
  1240. package/core/dev/pipeline/archived/TASK_050_error_recovery.yaml +56 -0
  1241. package/core/dev/pipeline/archived/TASK_051_optimization_proposals_backend.yaml +53 -0
  1242. package/core/dev/pipeline/archived/TASK_052_optimization_proposals_ui.yaml +58 -0
  1243. package/core/dev/pipeline/archived/TASK_053_optimization_proposals_diff_view.yaml +54 -0
  1244. package/core/dev/pipeline/archived/TASK_054_multi_project_scheduler_state.yaml +76 -0
  1245. package/core/dev/pipeline/archived/TASK_055_scheduler_api_project_scope.yaml +64 -0
  1246. package/core/dev/pipeline/archived/TASK_056_frontend_scheduler_project_scope.yaml +62 -0
  1247. package/core/dev/pipeline/archived/TASK_057_scheduler_module_structure.yaml +54 -0
  1248. package/core/dev/pipeline/archived/TASK_058_scheduler_models_extract.yaml +51 -0
  1249. package/core/dev/pipeline/archived/TASK_059_scheduler_core_extract.yaml +51 -0
  1250. package/core/dev/pipeline/archived/TASK_060_scheduler_commands_extract.yaml +51 -0
  1251. package/core/dev/pipeline/archived/TASK_061_scheduler_specialized_commands.yaml +53 -0
  1252. package/core/dev/pipeline/archived/TASK_062_process_manager_module_structure.yaml +56 -0
  1253. package/core/dev/pipeline/archived/TASK_063_process_manager_core_extract.yaml +56 -0
  1254. package/core/dev/pipeline/archived/TASK_064_recovery_module_structure.yaml +57 -0
  1255. package/core/dev/pipeline/archived/TASK_065_recovery_core_extract.yaml +56 -0
  1256. package/core/dev/pipeline/archived/TASK_066_self_enhancement_scheduler_models.yaml +60 -0
  1257. package/core/dev/pipeline/archived/TASK_067_agents_section_quick_actions.yaml +53 -0
  1258. package/core/dev/pipeline/archived/TASK_067_self_enhancement_scheduler_core.yaml +65 -0
  1259. package/core/dev/pipeline/archived/TASK_068_agent_workflow_rule_enhancement.yaml +46 -0
  1260. package/core/dev/pipeline/archived/TASK_068_self_enhancement_tauri_commands.yaml +61 -0
  1261. package/core/dev/pipeline/archived/TASK_069_agent_wizard_responsive.yaml +46 -0
  1262. package/core/dev/pipeline/archived/TASK_069_self_enhancement_ui.yaml +62 -0
  1263. package/core/dev/pipeline/archived/TASK_070_preview_panel_redesign.yaml +62 -0
  1264. package/core/dev/pipeline/archived/TASK_070_self_enhancement_integration.yaml +57 -0
  1265. package/core/dev/pipeline/archived/TASK_071_preview_panel_qa.yaml +98 -0
  1266. package/core/dev/pipeline/archived/TASK_071_single_agent_config.yaml +57 -0
  1267. package/core/dev/pipeline/archived/TASK_072_preview_modal_esc_fix.yaml +71 -0
  1268. package/core/dev/pipeline/archived/TASK_072_product_review_module.yaml +57 -0
  1269. package/core/dev/pipeline/archived/TASK_073_agents_page_component_refactor.yaml +68 -0
  1270. package/core/dev/pipeline/archived/TASK_073_rename_single_agent.yaml +64 -0
  1271. package/core/dev/pipeline/archived/TASK_074_preview_mode_ui_diagnosis.yaml +71 -0
  1272. package/core/dev/pipeline/archived/TASK_074_single_agent_ui_toggle.yaml +56 -0
  1273. package/core/dev/pipeline/archived/TASK_075_agents_page_component_refactor.yaml +69 -0
  1274. package/core/dev/pipeline/archived/TASK_075_single_agent_integration_test.yaml +75 -0
  1275. package/core/dev/pipeline/archived/TASK_076_preview_mode_ui_diagnosis.yaml +70 -0
  1276. package/core/dev/pipeline/archived/TASK_076_single_agent_continuous_mode.yaml +65 -0
  1277. package/core/dev/pipeline/archived/TASK_077_failure_directory_structure.yaml +51 -0
  1278. package/core/dev/pipeline/archived/TASK_077_single_agent_typescript_types.yaml +59 -0
  1279. package/core/dev/pipeline/archived/TASK_078_fail_001_agent_no_output.yaml +53 -0
  1280. package/core/dev/pipeline/archived/TASK_078_single_agent_ui_simplify.yaml +74 -0
  1281. package/core/dev/pipeline/archived/TASK_079_dashboard_rwd_responsive.yaml +63 -0
  1282. package/core/dev/pipeline/archived/TASK_079_fail_002_json_bom.yaml +56 -0
  1283. package/core/dev/pipeline/archived/TASK_080_fail_003_queue_pollution.yaml +56 -0
  1284. package/core/dev/pipeline/archived/TASK_080_http_server_module.yaml +55 -0
  1285. package/core/dev/pipeline/archived/TASK_081_failure_logging_tools.yaml +57 -0
  1286. package/core/dev/pipeline/archived/TASK_081_http_api_routes.yaml +62 -0
  1287. package/core/dev/pipeline/archived/TASK_082_frontend_api_adapter.yaml +51 -0
  1288. package/core/dev/pipeline/archived/TASK_082_integrate_failure_to_workflow.yaml +60 -0
  1289. package/core/dev/pipeline/archived/TASK_083_cleanup_nextjs_api.yaml +48 -0
  1290. package/core/dev/pipeline/archived/TASK_083_task_queue_cleanup.yaml +67 -0
  1291. package/core/dev/pipeline/archived/TASK_084_rust_dead_code_cleanup.yaml +66 -0
  1292. package/core/dev/pipeline/archived/TASK_084_scheduler_sync_logic_fix.yaml +68 -0
  1293. package/core/dev/pipeline/archived/TASK_085_http_api_web_mode_fixes.yaml +46 -0
  1294. package/core/dev/pipeline/archived/TASK_085_memory_consolidation_0204_0205.yaml +98 -0
  1295. package/core/dev/pipeline/archived/TASK_086_collapsible_preview_panel.yaml +161 -0
  1296. package/core/dev/pipeline/archived/TASK_086_web_mode_feature_parity.yaml +62 -0
  1297. package/core/dev/pipeline/archived/TASK_087_manual_memory_archival.yaml +163 -0
  1298. package/core/dev/pipeline/archived/TASK_087_mobile_http_control.yaml +68 -0
  1299. package/core/dev/pipeline/archived/TASK_088_fail_003_to_ppl_002_rule.yaml +63 -0
  1300. package/core/dev/pipeline/archived/TASK_088_mobile_http_qa.yaml +121 -0
  1301. package/core/dev/pipeline/archived/TASK_089_agent_collaboration_topic.yaml +60 -0
  1302. package/core/dev/pipeline/archived/TASK_089_web_mode_monitor_fix.yaml +58 -0
  1303. package/core/dev/pipeline/archived/TASK_090_cli_detection_fix.yaml +61 -0
  1304. package/core/dev/pipeline/archived/TASK_090_ui_ux_lessons_topic.yaml +63 -0
  1305. package/core/dev/pipeline/archived/TASK_091_expandable_card_design.yaml +56 -0
  1306. package/core/dev/pipeline/archived/TASK_091_move_agent_section_components.yaml +65 -0
  1307. package/core/dev/pipeline/archived/TASK_092_expandable_card_component.yaml +60 -0
  1308. package/core/dev/pipeline/archived/TASK_092_refactor_agent_create_wizard.yaml +84 -0
  1309. package/core/dev/pipeline/archived/TASK_093_taskcard_detail_integration.yaml +57 -0
  1310. package/core/dev/pipeline/archived/TASK_093_update_barrel_exports_test.yaml +84 -0
  1311. package/core/dev/pipeline/archived/TASK_094_ai_agent_generation_enhancement.yaml +115 -0
  1312. package/core/dev/pipeline/archived/TASK_094_proposalcard_detail_integration.yaml +55 -0
  1313. package/core/dev/pipeline/archived/TASK_095_preview_fixes.yaml +129 -0
  1314. package/core/dev/pipeline/archived/TASK_095_single_agent_task_selector.yaml +60 -0
  1315. package/core/dev/pipeline/archived/TASK_096_agents_page_ux_improvements.yaml +163 -0
  1316. package/core/dev/pipeline/archived/TASK_096_single_agent_status_sync.yaml +60 -0
  1317. package/core/dev/pipeline/archived/TASK_097_local_claude_error_logging.yaml +59 -0
  1318. package/core/dev/pipeline/archived/TASK_097_single_agent_monitor_events.yaml +59 -0
  1319. package/core/dev/pipeline/archived/TASK_098_product_manager_team_phase2.yaml +60 -0
  1320. package/core/dev/pipeline/archived/TASK_098_single_agent_refactor_test.yaml +58 -0
  1321. package/core/dev/pipeline/archived/TASK_099_backend_team_creation.yaml +258 -0
  1322. package/core/dev/pipeline/archived/TASK_100_frontend_team_creation.yaml +61 -0
  1323. package/core/dev/pipeline/archived/TASK_101_orchestrator_system.yaml +403 -0
  1324. package/core/dev/pipeline/archived/TASK_102_designer_sre_teams.yaml +90 -0
  1325. package/core/dev/pipeline/archived/TASK_103_integration_testing.yaml +86 -0
  1326. package/core/dev/pipeline/archived/TASK_128_integration_testing.yaml +83 -0
  1327. package/core/dev/pipeline/archived/TASK_134_cortex_v2_phase3_file_watcher.yaml +65 -0
  1328. package/core/dev/pipeline/archived/TASK_134_team_http_api.yaml +96 -0
  1329. package/core/dev/pipeline/archived/TASK_135_cortex_v2_phase3_git_monitor.yaml +63 -0
  1330. package/core/dev/pipeline/archived/TASK_136_cortex_v2_phase3_change_analyzer.yaml +69 -0
  1331. package/core/dev/pipeline/archived/TASK_137_cortex_v2_phase3_cache_invalidator.yaml +67 -0
  1332. package/core/dev/pipeline/archived/TASK_137_team_orchestrator_integration.yaml +93 -0
  1333. package/core/dev/pipeline/archived/TASK_138_cortex_v2_phase3_distributed_cache.yaml +68 -0
  1334. package/core/dev/pipeline/archived/TASK_138_matrix_folder_structure.yaml +61 -0
  1335. package/core/dev/pipeline/archived/TASK_139_cortex_v2_phase3_cache_prewarming.yaml +65 -0
  1336. package/core/dev/pipeline/archived/TASK_146_unified_documentation.yaml +141 -0
  1337. package/core/dev/pipeline/archived/TASK_147_pipeline_execution_rules.yaml +76 -0
  1338. package/core/dev/pipeline/archived/TASK_148_update_claude_md.yaml +77 -0
  1339. package/core/dev/pipeline/archived/TASK_149_fix_nextjs_mdx_serialization.yaml +63 -0
  1340. package/core/dev/pipeline/archived/TASK_149_update_team_docs.yaml +77 -0
  1341. package/core/dev/pipeline/archived/TASK_150_pipeline_workflow_guide.yaml +82 -0
  1342. package/core/dev/pipeline/archived/TASK_164_fix_smart_context_switch.yaml +49 -0
  1343. package/core/dev/pipeline/archived/TASK_165_ctags_symbol_index.yaml +53 -0
  1344. package/core/dev/pipeline/archived/TASK_166_symbol_search_mcp_tool.yaml +48 -0
  1345. package/core/dev/pipeline/archived/TASK_167_llm_module_summary.yaml +55 -0
  1346. package/core/dev/pipeline/archived/TASK_168_map_reduce_context.yaml +51 -0
  1347. package/core/dev/pipeline/archived/TASK_169_v2_integration_testing.yaml +52 -0
  1348. package/core/dev/pipeline/backups/archive_2026-02-04T22-07-22/TASK_QUEUE_backup.json +95 -0
  1349. package/core/dev/pipeline/docs/multi-agent-coordination.md +446 -0
  1350. package/core/dev/pipeline/docs/overview.md +406 -0
  1351. package/core/dev/pipeline/docs/task-management.md +412 -0
  1352. package/core/dev/pipeline/docs/workflows.md +32 -0
  1353. package/core/dev/pipeline/orchestrator/schemas/task_queue_schema.json +58 -0
  1354. package/core/dev/pipeline/orchestrator/schemas/task_schema.json +139 -0
  1355. package/core/dev/pipeline/plans/PLAN_task_cli_refactor.md +281 -0
  1356. package/core/dev/pipeline/tasks/.gitkeep +1 -0
  1357. package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -0
  1358. package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +128 -0
  1359. package/core/dev/plans/FORMATION_RETIREMENT_PLAN.md +313 -0
  1360. package/core/dev/plans/KUZU_REPLACEMENT_PLAN.md +126 -0
  1361. package/core/dev/schemas/PRODUCT_SPEC.example.yaml +382 -0
  1362. package/core/dev/schemas/PRODUCT_SPEC.schema.json +800 -0
  1363. package/core/dev/schemas/README.md +517 -0
  1364. package/core/dev/schemas/TASK.schema.json +226 -0
  1365. package/core/dev/schemas/dataflows_ground_truth.yaml +173 -0
  1366. package/core/dev/schemas/dataflows_spec.yaml +160 -0
  1367. package/core/dev/schemas/extends_ground_truth.yaml +66 -0
  1368. package/core/dev/schemas/extends_spec.yaml +121 -0
  1369. package/core/dev/schemas/implements_ground_truth.yaml +77 -0
  1370. package/core/dev/schemas/implements_spec.yaml +140 -0
  1371. package/core/dev/scripts/CRASH_DIAGNOSIS.md +47 -0
  1372. package/core/dev/scripts/README.md +142 -0
  1373. package/core/dev/scripts/RUN_ONCE.bat +40 -0
  1374. package/core/dev/scripts/START_DEV_AGENT.bat +63 -0
  1375. package/core/dev/scripts/START_MULTI_AGENT.bat +80 -0
  1376. package/core/dev/scripts/check-alaya-report-isolation.js +255 -0
  1377. package/core/dev/scripts/check-file-size.js +268 -0
  1378. package/core/dev/scripts/ci-feedback.js +251 -0
  1379. package/core/dev/scripts/clean-verify-targets.js +132 -0
  1380. package/core/dev/scripts/cleanup-queue.js +184 -0
  1381. package/core/dev/scripts/cleanup_queue.js +86 -0
  1382. package/core/dev/scripts/dev_agent_scheduler.ps1 +536 -0
  1383. package/core/dev/scripts/dispatch-stats.js +384 -0
  1384. package/core/dev/scripts/dispatch-stats.test.js +162 -0
  1385. package/core/dev/scripts/gc-agent-worktrees.js +461 -0
  1386. package/core/dev/scripts/gc-agent-worktrees.test.js +48 -0
  1387. package/core/dev/scripts/hooks/README.md +148 -0
  1388. package/core/dev/scripts/init_git.ps1 +396 -0
  1389. package/core/dev/scripts/init_git.sh +337 -0
  1390. package/core/dev/scripts/init_project.ps1 +851 -0
  1391. package/core/dev/scripts/init_project.sh +477 -0
  1392. package/core/dev/scripts/js-yaml-resolution.test.js +48 -0
  1393. package/core/dev/scripts/kenaz-session.js +354 -0
  1394. package/core/dev/scripts/kenaz-session.test.js +234 -0
  1395. package/core/dev/scripts/legacy/README.md +39 -0
  1396. package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -0
  1397. package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -0
  1398. package/core/dev/scripts/matrix/CLI_README.md +214 -0
  1399. package/core/dev/scripts/matrix/README.md +310 -0
  1400. package/core/dev/scripts/matrix/package-lock.json +4266 -0
  1401. package/core/dev/scripts/matrix/package.json +55 -0
  1402. package/core/dev/scripts/matrix/src/abbreviations.ts +295 -0
  1403. package/core/dev/scripts/matrix/src/cli/commands/convert.ts +211 -0
  1404. package/core/dev/scripts/matrix/src/cli/commands/create.ts +95 -0
  1405. package/core/dev/scripts/matrix/src/cli/commands/delete.ts +102 -0
  1406. package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -0
  1407. package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -0
  1408. package/core/dev/scripts/matrix/src/cli/commands/query.ts +261 -0
  1409. package/core/dev/scripts/matrix/src/cli/commands/read.ts +77 -0
  1410. package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -0
  1411. package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -0
  1412. package/core/dev/scripts/matrix/src/cli/commands/update.ts +147 -0
  1413. package/core/dev/scripts/matrix/src/cli/commands/validate.ts +275 -0
  1414. package/core/dev/scripts/matrix/src/cli/index.ts +350 -0
  1415. package/core/dev/scripts/matrix/src/cli/utils/file-handler.ts +72 -0
  1416. package/core/dev/scripts/matrix/src/cli/utils/logger.ts +65 -0
  1417. package/core/dev/scripts/matrix/src/index.ts +62 -0
  1418. package/core/dev/scripts/matrix/src/indexer/tag-indexer.ts +165 -0
  1419. package/core/dev/scripts/matrix/src/parser.ts +281 -0
  1420. package/core/dev/scripts/matrix/src/serializer.ts +151 -0
  1421. package/core/dev/scripts/matrix/src/validator.ts +301 -0
  1422. package/core/dev/scripts/matrix/tests/abbreviations.test.ts +122 -0
  1423. package/core/dev/scripts/matrix/tests/cli.test.ts +177 -0
  1424. package/core/dev/scripts/matrix/tests/convert.test.ts +143 -0
  1425. package/core/dev/scripts/matrix/tests/parser.test.ts +203 -0
  1426. package/core/dev/scripts/matrix/tests/query.test.ts +85 -0
  1427. package/core/dev/scripts/matrix/tests/serializer.test.ts +197 -0
  1428. package/core/dev/scripts/matrix/tests/validate.test.ts +141 -0
  1429. package/core/dev/scripts/matrix/tests/validator.test.ts +167 -0
  1430. package/core/dev/scripts/matrix/tsconfig.json +20 -0
  1431. package/core/dev/scripts/matrix/types/index.d.ts +111 -0
  1432. package/core/dev/scripts/matrix/types/index.d.ts.map +1 -0
  1433. package/core/dev/scripts/matrix/types/index.js +8 -0
  1434. package/core/dev/scripts/matrix/types/index.js.map +1 -0
  1435. package/core/dev/scripts/matrix/types/index.ts +124 -0
  1436. package/core/dev/scripts/memory-guard-eval.js +330 -0
  1437. package/core/dev/scripts/memory-usage-report.js +152 -0
  1438. package/core/dev/scripts/migrate-task-dependencies.js +198 -0
  1439. package/core/dev/scripts/multi_agent_scheduler.ps1 +2021 -0
  1440. package/core/dev/scripts/multi_agent_scheduler.sh +1344 -0
  1441. package/core/dev/scripts/probe/ansuz-scaffold-ablation.js +579 -0
  1442. package/core/dev/scripts/probe/ansuz-scaffold-ablation.test.js +244 -0
  1443. package/core/dev/scripts/probe/hippocampus-concurrency.js +247 -0
  1444. package/core/dev/scripts/probe/hook-latency-probe.js +315 -0
  1445. package/core/dev/scripts/probe/hook-latency-probe.test.js +105 -0
  1446. package/core/dev/scripts/probe/prediction-signal-auc.js +483 -0
  1447. package/core/dev/scripts/probe/prediction-signal-auc.test.js +200 -0
  1448. package/core/dev/scripts/reset_tasks.js +99 -0
  1449. package/core/dev/scripts/run-rust-tests.js +207 -0
  1450. package/core/dev/scripts/run-rust-tests.test.js +154 -0
  1451. package/core/dev/scripts/run_once.sh +47 -0
  1452. package/core/dev/scripts/rust-verify-worktree.js +591 -0
  1453. package/core/dev/scripts/rust-verify-worktree.test.js +574 -0
  1454. package/core/dev/scripts/seh_probe.rs +26 -0
  1455. package/core/dev/scripts/setup_mcp.ps1 +192 -0
  1456. package/core/dev/scripts/setup_mcp.sh +159 -0
  1457. package/core/dev/scripts/setup_rag.ps1 +304 -0
  1458. package/core/dev/scripts/setup_rag.sh +230 -0
  1459. package/core/dev/scripts/start_dev_agent.sh +137 -0
  1460. package/core/dev/scripts/start_multi_agent.sh +41 -0
  1461. package/core/dev/scripts/sync_task_queue.ps1 +505 -0
  1462. package/core/dev/scripts/sync_task_queue.sh +392 -0
  1463. package/core/dev/scripts/task1961/RUNBOOK.md +300 -0
  1464. package/core/dev/scripts/task1961/fixtures/never-listens.server.js +54 -0
  1465. package/core/dev/scripts/task1961/fixtures/slow-serve.server.js +134 -0
  1466. package/core/dev/scripts/task1961/run-fixture.js +659 -0
  1467. package/core/dev/scripts/task1961/selftest.js +247 -0
  1468. package/core/dev/scripts/test-pty-ws-tunnel.js +229 -0
  1469. package/core/dev/scripts/validate_pipeline.js +197 -0
  1470. package/core/dev/scripts/verify-isbar-capture.js +100 -0
  1471. package/core/dev/scripts/verify-target-dir.js +69 -0
  1472. package/core/dev/scripts/verify-task-1839.js +141 -0
  1473. package/core/dev/specs/agent-connect/PROTOCOL.md +177 -0
  1474. package/core/dev/specs/agent-connect/SDK_GUIDE.md +915 -0
  1475. package/core/dev/specs/agent-connect/examples/minimal-site/README.md +137 -0
  1476. package/core/dev/specs/agent-connect/examples/minimal-site/package.json +12 -0
  1477. package/core/dev/specs/agent-connect/examples/minimal-site/public/index.html +116 -0
  1478. package/core/dev/specs/agent-connect/examples/minimal-site/public/install-redirect.js +73 -0
  1479. package/core/dev/specs/agent-connect/examples/minimal-site/server.js +516 -0
  1480. package/core/dev/specs/agent-connect/examples/minimal-site/test/smoke.test.mjs +263 -0
  1481. package/core/dev/specs/agent-connect/history/DEFECT_REPORT_kenaz-agent-hub_2026-09-09_v0.4-empty-prompt-no-first-turn.md +77 -0
  1482. package/core/dev/specs/agent-connect/history/DEFECT_REPORT_kenaz-agent-hub_2026-09-09_v0.4-prompt-mojibake.md +90 -0
  1483. package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-07.md +61 -0
  1484. package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-08_relay-v1-conformance.md +76 -0
  1485. package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-09_v0.4-session-channel.md +87 -0
  1486. package/core/dev/specs/agent-connect/history/FLOWPIX_REPLY_agent-hub_2026-09-08_relay-v1-answers.md +68 -0
  1487. package/core/dev/specs/agent-connect/history/FLOWPIX_bridge_CONTRACT.md +642 -0
  1488. package/core/dev/specs/agent-connect/history/FLOWPIX_spikes_relay_spike_relay.mjs +244 -0
  1489. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-07.md +77 -0
  1490. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_direction-change-v0.3.md +44 -0
  1491. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_relay-v1-answers-ack.md +37 -0
  1492. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_relay-v1-conformance.md +56 -0
  1493. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_annotations-install-coldstart.md +44 -0
  1494. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_empty-prompt-first-turn.md +56 -0
  1495. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_prompt-mojibake-fixed.md +48 -0
  1496. package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_session-channel.md +57 -0
  1497. package/core/dev/specs/agent-connect/history/PROTOCOL_v0.3_snapshot.md +379 -0
  1498. package/core/dev/specs/agent-connect/history/README.md +47 -0
  1499. package/core/dev/specs/peer/CODEX_INTERACTIVE_PUSH.md +65 -0
  1500. package/core/dev/specs/peer/CODEX_PEER_PULL_SPIKE.md +54 -0
  1501. package/core/dev/specs/peer/MODS_SPIKE.md +133 -0
  1502. package/core/dev/specs/peer/PER_PROJECT_ADDRESSING.md +143 -0
  1503. package/core/dev/specs/peer/SWARM_PROTOCOLS.md +256 -0
  1504. package/core/dev/specs/peer/codex-hook-shim-peer.test.js +101 -0
  1505. package/core/dev/specs/peer/codex-push-launcher.test.js +142 -0
  1506. package/core/dev/specs/peer/setup-codex-hooks.test.js +22 -0
  1507. package/core/dev/specs/peer-ansuz/DELIVERY_SPIKE.md +397 -0
  1508. package/core/dev/specs/peer-ansuz/DESIGN.md +109 -0
  1509. package/core/dev/specs/peer-ansuz/spike/claude_poke.js +129 -0
  1510. package/core/dev/specs/peer-ansuz/spike/claude_session_start_hook.js +37 -0
  1511. package/core/dev/specs/peer-ansuz/spike/claude_settings_hook.json +14 -0
  1512. package/core/dev/specs/peer-ansuz/spike/codex_appserver_client.js +105 -0
  1513. package/core/dev/specs/peer-ansuz/spike/codex_appserver_query.js +29 -0
  1514. package/core/dev/specs/peer-ansuz/spike/extract_timeline.js +26 -0
  1515. package/core/dev/specs/synapse/SYMBOL_IDENTITY.md +305 -0
  1516. package/core/dev/spikes/A4-cypher-varlen.md +176 -0
  1517. package/core/dev/spikes/bloat-rounds.sh +25 -0
  1518. package/core/dev/spikes/mods/clean-claude.js +9 -0
  1519. package/core/dev/spikes/mods/drive-probe.js +29 -0
  1520. package/core/dev/spikes/mods/e2e-peer.js +71 -0
  1521. package/core/dev/spikes/mods/evidence/e2e-peer-control.json +17 -0
  1522. package/core/dev/spikes/mods/evidence/e2e-peer-mod.json +17 -0
  1523. package/core/dev/spikes/mods/evidence/probe-006a6e65-d807-4bc1-b648-fd7d1edfe0b4.jsonl +4 -0
  1524. package/core/dev/spikes/mods/evidence/probe-cebdf1f7-f9c4-47a8-83cd-7c0786177404.jsonl +12 -0
  1525. package/core/dev/spikes/mods/evidence/probe-eb21147d-10ce-4c32-93ed-37ca37d88d5a.jsonl +11 -0
  1526. package/core/dev/spikes/mods/evidence/stream-cebdf1f7.jsonl +18 -0
  1527. package/core/dev/spikes/mods/kenaz-mod-probe/.claude-plugin/plugin.json +1 -0
  1528. package/core/dev/spikes/mods/kenaz-mod-probe/hooks/hooks.json +1 -0
  1529. package/core/dev/spikes/mods/kenaz-mod-probe/hooks/register.ts +65 -0
  1530. package/core/dev/spikes/mods/kenaz-mod-probe/tsconfig.json +3 -0
  1531. package/core/k-cli/README.md +423 -0
  1532. package/core/k-cli/package.json +21 -0
  1533. package/core/k-cli/templates/k-cli.md +91 -0
  1534. package/core/k-cli/test/TEST_SPEC.md +134 -0
  1535. package/core/k-cli/test/run.sh +469 -0
  1536. package/core/plugins/kenaz/.claude-plugin/plugin.json +1 -0
  1537. package/core/plugins/kenaz/.mcp.json +5 -0
  1538. package/core/plugins/kenaz/INVENTORY.md +48 -0
  1539. package/core/plugins/kenaz/README.md +55 -0
  1540. package/core/plugins/kenaz/commands/Amelia.md +5 -0
  1541. package/core/plugins/kenaz/commands/Ansuz.md +38 -0
  1542. package/core/plugins/kenaz/commands/architect.md +24 -0
  1543. package/core/plugins/kenaz/commands/incident.md +15 -0
  1544. package/core/plugins/kenaz/commands/kenaz.md +12 -0
  1545. package/core/plugins/kenaz/commands/review.md +14 -0
  1546. package/core/plugins/kenaz/commands/security.md +15 -0
  1547. package/core/plugins/kenaz/commands/spec.md +22 -0
  1548. package/core/plugins/kenaz/commands/strategy.md +141 -0
  1549. package/core/plugins/kenaz/commands//347/224/242/345/223/201.md +15 -0
  1550. package/core/plugins/kenaz/commands//350/250/255/350/250/210.md +14 -0
  1551. package/core/plugins/kenaz/commands//350/251/225/344/274/260.md +16 -0
  1552. package/core/plugins/kenaz/guidance.md +5 -0
  1553. package/core/plugins/kenaz/hooks/core-resolve.js +230 -0
  1554. package/core/plugins/kenaz/hooks/hooks.json +31 -0
  1555. package/core/plugins/kenaz/hooks/kenaz-hook.js +112 -0
  1556. package/core/plugins/kenaz/hooks/mode.js +39 -0
  1557. package/core/plugins/kenaz/scripts/kenaz-mode.js +46 -0
  1558. package/core/plugins/kenaz/scripts/kenaz.test.js +123 -0
  1559. package/core/plugins/kenaz/scripts/sync.js +45 -0
  1560. package/core/plugins/kenaz/scripts/ygg.js +157 -0
  1561. package/core/plugins/kenaz/skills/adapt/SKILL.md +199 -0
  1562. package/core/plugins/kenaz/skills/animate/SKILL.md +190 -0
  1563. package/core/plugins/kenaz/skills/architect_agent/SKILL.md +408 -0
  1564. package/core/plugins/kenaz/skills/assessment_agent/SKILL.md +289 -0
  1565. package/core/plugins/kenaz/skills/audit/SKILL.md +129 -0
  1566. package/core/plugins/kenaz/skills/backend_agent/SKILL.md +407 -0
  1567. package/core/plugins/kenaz/skills/backend_engineer/SKILL.md +788 -0
  1568. package/core/plugins/kenaz/skills/bolder/SKILL.md +132 -0
  1569. package/core/plugins/kenaz/skills/clarify/SKILL.md +180 -0
  1570. package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +305 -0
  1571. package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +85 -0
  1572. package/core/plugins/kenaz/skills/colorize/SKILL.md +158 -0
  1573. package/core/plugins/kenaz/skills/critique/SKILL.md +118 -0
  1574. package/core/plugins/kenaz/skills/delight/SKILL.md +317 -0
  1575. package/core/plugins/kenaz/skills/designer/SKILL.md +845 -0
  1576. package/core/plugins/kenaz/skills/dev_agent/SKILL.md +503 -0
  1577. package/core/plugins/kenaz/skills/developer/SKILL.md +792 -0
  1578. package/core/plugins/kenaz/skills/distill/SKILL.md +137 -0
  1579. package/core/plugins/kenaz/skills/extract/SKILL.md +95 -0
  1580. package/core/plugins/kenaz/skills/feature_agent/SKILL.md +303 -0
  1581. package/core/plugins/kenaz/skills/frontend-design/SKILL.md +127 -0
  1582. package/core/plugins/kenaz/skills/frontend-design/reference/color-and-contrast.md +132 -0
  1583. package/core/plugins/kenaz/skills/frontend-design/reference/interaction-design.md +123 -0
  1584. package/core/plugins/kenaz/skills/frontend-design/reference/motion-design.md +99 -0
  1585. package/core/plugins/kenaz/skills/frontend-design/reference/responsive-design.md +114 -0
  1586. package/core/plugins/kenaz/skills/frontend-design/reference/spatial-design.md +100 -0
  1587. package/core/plugins/kenaz/skills/frontend-design/reference/typography.md +131 -0
  1588. package/core/plugins/kenaz/skills/frontend-design/reference/ux-writing.md +107 -0
  1589. package/core/plugins/kenaz/skills/frontend_agent/SKILL.md +327 -0
  1590. package/core/plugins/kenaz/skills/harden/SKILL.md +358 -0
  1591. package/core/plugins/kenaz/skills/incident_response/SKILL.md +315 -0
  1592. package/core/plugins/kenaz/skills/normalize/SKILL.md +67 -0
  1593. package/core/plugins/kenaz/skills/onboard/SKILL.md +243 -0
  1594. package/core/plugins/kenaz/skills/optimize/SKILL.md +269 -0
  1595. package/core/plugins/kenaz/skills/polish/SKILL.md +202 -0
  1596. package/core/plugins/kenaz/skills/product_manager/SKILL.md +1091 -0
  1597. package/core/plugins/kenaz/skills/qa_engineer/SKILL.md +1269 -0
  1598. package/core/plugins/kenaz/skills/quieter/SKILL.md +118 -0
  1599. package/core/plugins/kenaz/skills/security_agent/SKILL.md +312 -0
  1600. package/core/plugins/kenaz/skills/spec_optimizer/SKILL.md +299 -0
  1601. package/core/plugins/kenaz/skills/sre_agent/SKILL.md +1347 -0
  1602. package/core/plugins/kenaz/skills/ui-ux-pro-max/SKILL.md +377 -0
  1603. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -0
  1604. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -0
  1605. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -0
  1606. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -0
  1607. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +97 -0
  1608. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  1609. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  1610. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  1611. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  1612. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  1613. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  1614. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  1615. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  1616. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  1617. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  1618. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  1619. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  1620. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  1621. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  1622. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -0
  1623. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +58 -0
  1624. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  1625. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  1626. package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  1627. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -0
  1628. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  1629. package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -0
  1630. package/core/plugins/kenaz-peer/.claude-plugin/plugin.json +1 -0
  1631. package/core/plugins/kenaz-peer/hooks/hooks.json +1 -0
  1632. package/core/plugins/kenaz-peer/hooks/register.test.ts +62 -0
  1633. package/core/plugins/kenaz-peer/hooks/register.ts +146 -0
  1634. package/core/plugins/kenaz-peer/tsconfig.json +3 -0
  1635. package/lib/cli.js +251 -0
  1636. package/lib/cli.test.js +59 -0
  1637. package/lib/dashboard.js +80 -0
  1638. package/lib/dashboard.test.js +59 -0
  1639. package/package.json +28 -4
  1640. package/vendor/install-kenaz-cli.js +268 -0
  1641. package/README.md +0 -3
@@ -0,0 +1,3729 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * task-api.js — Thalamus-integrated task management
4
+ * 統一任務查詢 API v3.5
5
+ *
6
+ * v3.5 改進:
7
+ * - 新增 verification 欄位(三層驗證規格):level_1 commands, level_2 API probes, level_3 Playwright
8
+ * - task-api.js create 支援 --verification JSON 參數
9
+ * - task-api.js list 含 verification 的 task 顯示 [V] 標記
10
+ *
11
+ * v3.5 改進:
12
+ * - 支援可配置 tasksDir(多專案支援)
13
+ * - 自動偵測 core project vs sub-project(.kenaz/ 存在與否)
14
+ * - CLI 新增 --project-path 參數
15
+ * - Legacy pipeline fallback 支援 projectPath
16
+ * - 無參數時 100% backward compatible
17
+ *
18
+ * v3.3 改進:
19
+ * - 新增 rigor 欄位(嚴謹度):high 需要 team 執行
20
+ *
21
+ * v3.2 改進:
22
+ * - 新增 hash 欄位(UUID v4)用於 ID 衝突時的唯一識別
23
+ * - agent_type 改名為 role(保持向後相容)
24
+ * - 新增 tags 欄位(陣列)
25
+ * - 新增 actual_hours 和 branch 欄位
26
+ * - 簡化人員追蹤(移除 assigned_to, collaborators, metadata)
27
+ *
28
+ * v3.1 改進:
29
+ * - 使用 js-yaml 替代自訂 YAML 解析器(修復 round-trip 資料損壞 Bug)
30
+ * - moveTask 原子性操作(先複製後刪除)
31
+ * - 狀態機驗證(禁止不合理的狀態轉換)
32
+ *
33
+ * 目錄結構:
34
+ * Core project: .collaboration/tasks/{status}/
35
+ * Sub-project: .kenaz/tasks/{status}/
36
+ *
37
+ * .collaboration/tasks/ (or .kenaz/tasks/)
38
+ * ├── backlog/ # status: backlog (原 pending)
39
+ * ├── active/ # status: active (原 in_progress)
40
+ * ├── done/ # status: done (原 completed)
41
+ * ├── cancel/ # status: cancel (原 cancelled)
42
+ * └── blocked/ # status: blocked
43
+ */
44
+
45
+ const fs = require('fs').promises;
46
+ const path = require('path');
47
+ const { execSync } = require('child_process');
48
+ const { randomUUID } = require('crypto');
49
+ const fsSync = require('fs');
50
+
51
+ // ─────────────────────────────────────────
52
+ // M1: wx Claim Marker helpers (TASK_1758)
53
+ // Spec: dev/MULTI_ANSUZ_SPEC.md §2.2
54
+ // ─────────────────────────────────────────
55
+
56
+ function _claimContext(projectRoot) {
57
+ const locksDir = require('path').join(projectRoot, '.kenaz', 'locks');
58
+ const streamFile = require('path').join(projectRoot, '.kenaz', 'specialist', 'current_stream.json');
59
+ // TASK_2265: env-first (CLAUDE_CODE_SESSION_ID = own conversation id, mirrors
60
+ // dispatch.js _getOwnConversationId), then the per-conversation binding
61
+ // (hmap/scratch via _session-identity.js), current_stream.json singleton last.
62
+ const envConv = process.env.CLAUDE_CODE_SESSION_ID || null;
63
+ let convId = envConv;
64
+ let streamId = null;
65
+ let source = null;
66
+ if (envConv) {
67
+ try {
68
+ const ident = require(require('path').join(projectRoot, '.claude', 'hooks', '_session-identity.js'));
69
+ const b = ident.resolveOwnBinding(envConv, require('path').join(projectRoot, '.kenaz', 'specialist', 'sessions'));
70
+ if (b && b.bootSid) { streamId = b.bootSid; source = b.source; }
71
+ } catch { /* no helper in this project: fall through to the singleton */ }
72
+ }
73
+ if (!streamId) {
74
+ try {
75
+ const parsed = JSON.parse(require('fs').readFileSync(streamFile, 'utf8'));
76
+ // A singleton that names a DIFFERENT conversation belongs to another live session:
77
+ // never adopt it as our owner (worker-skip instead of a wrong-owner marker).
78
+ const foreign = envConv && parsed && parsed.conversation_id && parsed.conversation_id !== envConv;
79
+ if (parsed && parsed.session_id && !foreign) { streamId = parsed.session_id; source = 'current_stream'; }
80
+ if (!convId && parsed && parsed.conversation_id) convId = parsed.conversation_id;
81
+ } catch { /* no identity = worker context */ }
82
+ }
83
+ return { locksDir, streamId, convId, source };
84
+ }
85
+
86
+ function _claimMarkerPath(locksDir, taskId) {
87
+ return require('path').join(locksDir, 'claim_' + taskId + '.json');
88
+ }
89
+
90
+ function _isPidAlive(pid) {
91
+ if (!pid || pid <= 0) return false;
92
+ try { process.kill(pid, 0); return true; }
93
+ catch (e) { if (e.code === 'EPERM') return true; return false; }
94
+ }
95
+
96
+ function acquireClaimMarker(taskId, projectRoot, opts = {}) {
97
+ const { locksDir, streamId, convId } = _claimContext(projectRoot);
98
+ if (!streamId) return { claimed: true, workerSkip: true };
99
+ const fsS = require('fs');
100
+ const p = require('path');
101
+ fsS.mkdirSync(locksDir, { recursive: true });
102
+ const markerPath = _claimMarkerPath(locksDir, taskId);
103
+ // M1.5 (TASK_1761): owner_pid records process.pid (CLI pid) for re-entrancy detection.
104
+ // Liveness is judged via owner session LEASE (isSessionLive), not owner_pid alone.
105
+ // marker owner_pid is informational for re-entrancy; session lease is authoritative for liveness.
106
+ const content = JSON.stringify({
107
+ owner_session: streamId,
108
+ owner_conversation_id: convId || null,
109
+ owner_pid: process.pid,
110
+ claimed_at: new Date().toISOString(),
111
+ dispatch_path: opts.dispatchPath || null,
112
+ }, null, 2);
113
+ try {
114
+ fsS.writeFileSync(markerPath, content, { flag: 'wx', encoding: 'utf8' });
115
+ return { claimed: true };
116
+ } catch (err) {
117
+ if (err.code !== 'EEXIST') throw err;
118
+ let existing = null;
119
+ try { existing = JSON.parse(fsS.readFileSync(markerPath, 'utf8')); } catch { /* corrupted */ }
120
+ if (!existing) {
121
+ fsS.writeFileSync(markerPath, content, { encoding: 'utf8' });
122
+ return { claimed: true };
123
+ }
124
+ if (existing && existing.owner_session === streamId && existing.owner_pid === process.pid) {
125
+ // Re-entrant acquire by the same session+anchor (claimTask -> moveTask double path): refresh and succeed.
126
+ // Requires pid match too: concurrent CLIs share the same stream identity, so session alone is not proof of re-entrancy.
127
+ fsS.writeFileSync(markerPath, content, { encoding: 'utf8' });
128
+ return { claimed: true, reentrant: true };
129
+ }
130
+ // M1.5 (TASK_1761): owned/orphan judgment now uses owner SESSION lease liveness, not just owner_pid.
131
+ // Load the owner's lease from the leases directory and apply isSessionLive.
132
+ // Fallback to _isPidAlive(owner_pid) when no lease file exists.
133
+ const ownerSession = existing.owner_session;
134
+ const ownerPid = existing.owner_pid;
135
+ let ownerIsLive = false;
136
+ try {
137
+ const slPath = require('path').join(projectRoot, '.specialist', 'tools', 'stream-lease.js');
138
+ const sl = require(slPath);
139
+ const ownerLease = sl.readLease(ownerSession);
140
+ ownerIsLive = ownerLease ? sl.isSessionLive(ownerLease) : _isPidAlive(ownerPid);
141
+ } catch (_e) { ownerIsLive = _isPidAlive(ownerPid); }
142
+ if (ownerIsLive) {
143
+ return { claimed: false, reason: 'owned', owner: existing };
144
+ }
145
+ // Dead owner = ORPHAN
146
+ console.warn([
147
+ '⚠️ ORPHAN CLAIM DETECTED: ' + taskId,
148
+ ' Previous owner session: ' + existing.owner_session + ' (pid=' + ownerPid + ') — DEAD',
149
+ ' Claimed at: ' + existing.claimed_at,
150
+ ' The process is gone but the claim marker was not released.',
151
+ ' This likely indicates a crash. Manual arbitration required.',
152
+ ' To override: re-run with --force-claim (prints this warning and proceeds).',
153
+ ].join(String.fromCharCode(10)));
154
+ if (!opts.forceClaim) {
155
+ return { claimed: false, reason: 'orphan', owner: existing };
156
+ }
157
+ console.warn('⚠️ --force-claim: overriding orphan marker for ' + taskId);
158
+ fsS.writeFileSync(markerPath, content, { encoding: 'utf8' });
159
+ return { claimed: true, forceOverride: true };
160
+ }
161
+ }
162
+
163
+ function releaseClaimMarker(taskId, projectRoot) {
164
+ // No identity check: the marker guards a card while it is active, and a card leaving
165
+ // active is done with it whoever moved it. Worker contexts (verify-and-land,
166
+ // subagents) complete most cards and have no stream id; skipping them leaked a
167
+ // marker per completed card (Huginn 2026-10-05/07).
168
+ const { locksDir } = _claimContext(projectRoot);
169
+ const markerPath = _claimMarkerPath(locksDir, taskId);
170
+ try { require('fs').unlinkSync(markerPath); } catch { /* already gone */ }
171
+ }
172
+
173
+ // ─────────────────────────────────────────
174
+ // TASK_1861: Dispatch-path-agnostic health snapshot capture
175
+ // _before at activation (move/claim/update→active), _after + observe-only verdict
176
+ // at completion (complete AND bare `move X done`). Fires a detached child so the
177
+ // short-lived CLI never blocks, fails, or slows on Dashboard state. Every capture
178
+ // attempt is audited in .kenaz/health_snapshots/snapshot_events.jsonl (never silent).
179
+ // Reuses .formation/tools/post-complete/health.js (measureHealthScoreSync etc.) —
180
+ // no third implementation. Does NOT touch guardian_counter.json (TASK_1720 periodic
181
+ // counter semantics unchanged; verdict stays observe-only, no git action).
182
+ // ─────────────────────────────────────────
183
+
184
+ function _appendSnapshotEvent(projectRoot, taskId, phase, action, reason) {
185
+ try {
186
+ const dir = path.join(projectRoot, '.kenaz', 'health_snapshots');
187
+ fsSync.mkdirSync(dir, { recursive: true });
188
+ const line = JSON.stringify({
189
+ task_id: taskId,
190
+ ts: new Date().toISOString(),
191
+ snapshot_type: phase,
192
+ action,
193
+ ...(reason ? { reason } : {}),
194
+ });
195
+ fsSync.appendFileSync(path.join(dir, 'snapshot_events.jsonl'), line + '\n', 'utf8');
196
+ } catch { /* audit is best-effort — must never block task ops */ }
197
+ }
198
+
199
+ function scheduleHealthSnapshotCapture(taskId, phase, projectRoot) {
200
+ try {
201
+ if (!taskId || !projectRoot) return;
202
+ const healthJs = path.join(__dirname, '..', '..', '.formation', 'tools', 'post-complete', 'health.js');
203
+ if (!fsSync.existsSync(healthJs)) {
204
+ _appendSnapshotEvent(projectRoot, taskId, phase, 'skipped', 'health_module_missing');
205
+ return;
206
+ }
207
+ const cfg = { taskId, phase, projectRoot, healthJs };
208
+ // ASCII-only inline script (Windows argv codepage safety). Child owns the full
209
+ // outcome audit: written | skipped(reason). measureHealthScoreSync already
210
+ // handles Dashboard-down gracefully (returns null within its own timeout).
211
+ const script = [
212
+ `const CFG = ${JSON.stringify(cfg)};`,
213
+ "const fs = require('fs');",
214
+ "const path = require('path');",
215
+ "function logEvent(action, reason) {",
216
+ " try {",
217
+ " const dir = path.join(CFG.projectRoot, '.kenaz', 'health_snapshots');",
218
+ " fs.mkdirSync(dir, { recursive: true });",
219
+ " const line = JSON.stringify({ task_id: CFG.taskId, ts: new Date().toISOString(), snapshot_type: CFG.phase, action, ...(reason ? { reason } : {}) });",
220
+ " fs.appendFileSync(path.join(dir, 'snapshot_events.jsonl'), line + '\\n', 'utf8');",
221
+ " } catch (_e) { /* best-effort */ }",
222
+ "}",
223
+ "try {",
224
+ " const snapFile = path.join(CFG.projectRoot, '.kenaz', 'health_snapshots', CFG.taskId + '_' + CFG.phase + '.json');",
225
+ " if (fs.existsSync(snapFile)) { logEvent('skipped', 'snapshot_already_exists'); process.exit(0); }",
226
+ " const h = require(CFG.healthJs);",
227
+ " const health = h.measureHealthScoreSync(CFG.projectRoot);",
228
+ " if (!health || typeof health.score !== 'number') { logEvent('skipped', 'dashboard_unreachable'); process.exit(0); }",
229
+ // TASK_1891: capture the written payload (delta_from_previous) for
230
+ // observability threading into computeHealthVerdict below -- see
231
+ // health.js's computeHealthVerdict doc comment for why that field
232
+ // never gates the verdict itself.
233
+ " const afterPayload = h.saveTaskHealthSnapshot(CFG.taskId, CFG.phase, health, CFG.projectRoot);",
234
+ " logEvent('written');",
235
+ " if (CFG.phase === 'after') {",
236
+ " const pre = h.readPreTaskHealthSnapshot(CFG.taskId, CFG.projectRoot);",
237
+ " if (pre && typeof pre.score === 'number') {",
238
+ " const loopCfg = h.readLoopConfig(CFG.projectRoot);",
239
+ " const threshold = typeof loopCfg.revert_threshold === 'number' ? loopCfg.revert_threshold : h.DEFAULT_REVERT_THRESHOLD;",
240
+ // TASK_1890/1891: same computeHealthVerdict as post-complete.js's
241
+ // auto/manual arms -- no third re-implementation of the delta/trust-gate
242
+ // logic. No git access here (no isScopeUnchanged callback), so guard 3
243
+ // (stale snapshot + zero scope diff) is a safe no-op for this call
244
+ // site, same as it always was before TASK_1890. TASK_1891: pass the
245
+ // full `pre` object (not pre.score) so ordering + symmetric trust can
246
+ // be checked -- this is one of the two call sites that actually runs
247
+ // in production (readLoopConfig's TASK_1885 note).
248
+ " const vr = h.computeHealthVerdict(pre, health, threshold, { projectPath: CFG.projectRoot, trendDeltaFromPrevious: afterPayload ? afterPayload.delta_from_previous : null });",
249
+ " const reason = vr.verdict === 'REVERT'",
250
+ " ? vr.reason + ' - observe-only capture at task completion, no git action taken'",
251
+ " : vr.reason;",
252
+ " h.writeVerdictToTask(CFG.taskId, vr.verdict, vr.delta, reason, CFG.projectRoot);",
253
+ " }",
254
+ " }",
255
+ "} catch (e) { logEvent('skipped', 'error: ' + String(e && e.message || e)); }",
256
+ ].join('\n');
257
+ const { spawn } = require('child_process');
258
+ const child = spawn(process.execPath, ['-e', script], {
259
+ detached: true,
260
+ stdio: 'ignore',
261
+ windowsHide: true,
262
+ });
263
+ child.unref();
264
+ } catch (e) {
265
+ _appendSnapshotEvent(projectRoot, taskId, phase, 'skipped', 'spawn_failed: ' + String(e && e.message || e));
266
+ }
267
+ }
268
+ const yaml = (() => {
269
+ try { return require('js-yaml'); } catch {
270
+ // Fallback: resolve .vendor/js-yaml.cjs relative to this script's real location.
271
+ // When task-api.js is copied to a temp directory, '../../' relative path breaks.
272
+ // Try multiple strategies:
273
+ const candidates = [
274
+ path.resolve(__dirname, '../../.vendor/js-yaml.cjs'), // normal: .collaboration/core/ → project root
275
+ path.resolve(__dirname, '.vendor/js-yaml.cjs'), // temp dir mirror
276
+ ];
277
+ // Also try KENAZ_SOURCE_DIR if set
278
+ if (process.env.KENAZ_SOURCE_DIR) {
279
+ candidates.unshift(path.join(process.env.KENAZ_SOURCE_DIR, '.vendor', 'js-yaml.cjs'));
280
+ }
281
+ for (const c of candidates) {
282
+ try { return require(c); } catch { /* next */ }
283
+ }
284
+ throw new Error('js-yaml not found. Install it or ensure .vendor/js-yaml.cjs exists.');
285
+ }
286
+ })();
287
+
288
+ // Shared project identity resolver (TASK_1701): pin-based session->project routing.
289
+ // Lazy-loaded so task-api.js can still run without the specialist tools tree.
290
+ let _projectResolver = null;
291
+ function getProjectResolver() {
292
+ if (_projectResolver) return _projectResolver;
293
+ try {
294
+ _projectResolver = require('../../.specialist/tools/lib/project-resolver');
295
+ } catch (_) { _projectResolver = null; }
296
+ return _projectResolver;
297
+ }
298
+
299
+ // Yggdrasil task-sync (lazy loaded, graceful degradation)
300
+ let _taskSync = null;
301
+ let _taskSyncLoaded = false;
302
+ function getTaskSync() {
303
+ if (_taskSyncLoaded) return _taskSync;
304
+ _taskSyncLoaded = true;
305
+ try {
306
+ const mod = require('../../.formation/tools/task-sync');
307
+ _taskSync = mod;
308
+ } catch {
309
+ _taskSync = null;
310
+ }
311
+ return _taskSync;
312
+ }
313
+
314
+ // TASK_2142 R2: Task Pool status push (lazy loaded, graceful degradation --
315
+ // a sub-project checkout without .formation/ must not break moveTask).
316
+ // Overridable via setPoolStatusPush() so tests can inject a fake/spy
317
+ // implementation instead of require()-caching a module-level singleton.
318
+ let _poolStatusPush = null;
319
+ let _poolStatusPushLoaded = false;
320
+ function getPoolStatusPush() {
321
+ if (_poolStatusPushLoaded) return _poolStatusPush;
322
+ _poolStatusPushLoaded = true;
323
+ try {
324
+ _poolStatusPush = require('../../.formation/tools/pool-status-push');
325
+ } catch {
326
+ _poolStatusPush = { pushPoolStatus: async () => ({ pushed: false, reason: 'module-unavailable' }) };
327
+ }
328
+ return _poolStatusPush;
329
+ }
330
+ /** Test seam: force a specific module (or reset with null to re-require on next call). */
331
+ function setPoolStatusPush(mod) {
332
+ _poolStatusPush = mod;
333
+ _poolStatusPushLoaded = mod !== null;
334
+ }
335
+
336
+ // 配置
337
+ const CONFIG = {
338
+ TASKS_DIR: path.join(__dirname, '../tasks'),
339
+ API_VERSION: '3.5.0',
340
+ // 有效的 role 值
341
+ VALID_ROLES: ['backend', 'designer', 'developer', 'frontend', 'pm', 'qa', 'sre', 'team_developer', 'team_strategy'],
342
+ // 常用 tags
343
+ COMMON_TAGS: ['doc', 'code', 'plan', 'fix', 'refactor', 'test', 'design', 'review'],
344
+ // 有效的 rigor 值(嚴謹度)
345
+ VALID_RIGOR: ['high', 'medium', 'low'],
346
+ // 狀態目錄映射(list 預設掃描範圍,不含 archived)
347
+ STATUS_DIRS: ['backlog', 'active', 'done', 'blocked', 'cancel'],
348
+ // 所有有效的狀態目錄(含 archived,用於 moveTask 驗證)
349
+ ALL_STATUS_DIRS: ['backlog', 'active', 'done', 'blocked', 'cancel', 'archived', 'pool'],
350
+ // 舊狀態 → 新狀態映射(向後相容)
351
+ STATUS_MAP: {
352
+ 'pending': 'backlog',
353
+ 'in_progress': 'active',
354
+ 'completed': 'done',
355
+ 'cancelled': 'cancel',
356
+ 'blocked': 'blocked',
357
+ 'split': 'backlog',
358
+ 'archived': 'archived'
359
+ },
360
+ // 新狀態 → 舊狀態映射(輸出相容)
361
+ REVERSE_STATUS_MAP: {
362
+ 'backlog': 'pending',
363
+ 'active': 'in_progress',
364
+ 'done': 'completed',
365
+ 'cancel': 'cancelled',
366
+ 'blocked': 'blocked',
367
+ 'archived': 'completed'
368
+ },
369
+ // 狀態機:允許的狀態轉換
370
+ ALLOWED_TRANSITIONS: {
371
+ 'backlog': ['active', 'cancel', 'blocked'],
372
+ 'active': ['done', 'blocked', 'cancel', 'backlog'],
373
+ 'blocked': ['active', 'backlog', 'cancel'],
374
+ 'done': ['backlog', 'archived'], // 重新開啟 or 封存
375
+ 'cancel': ['backlog'], // 重新開啟
376
+ 'archived': ['backlog'] // 重新開啟
377
+ }
378
+ };
379
+
380
+ // Default number of recent entries to load for large dirs (done/cancel) in CLI fast path
381
+ const RECENT_DEFAULT = 20;
382
+
383
+ /**
384
+ * 使用 js-yaml 解析 YAML 內容
385
+ * json: true → 允許 duplicated keys(last-wins,與 JSON.parse 行為一致)
386
+ * 避免因歷史資料汙染(assigned_to/metadata 混入 outcome 欄位)導致靜默丟失任務
387
+ */
388
+ function parseYAML(content, filePath) {
389
+ try {
390
+ const result = yaml.load(content, { json: true });
391
+ return result ?? {};
392
+ } catch (error) {
393
+ const location = filePath ? ` (file: ${filePath})` : '';
394
+ console.error(`YAML 解析錯誤${location}:`, error.message);
395
+ return {};
396
+ }
397
+ }
398
+
399
+ /**
400
+ * 使用 js-yaml 將物件轉換為 YAML 格式
401
+ */
402
+ function toYAML(obj) {
403
+ // 過濾掉內部屬性
404
+ const cleanObj = {};
405
+ for (const [key, value] of Object.entries(obj)) {
406
+ if (!key.startsWith('_')) {
407
+ cleanObj[key] = value;
408
+ }
409
+ }
410
+ return yaml.dump(cleanObj, {
411
+ indent: 2,
412
+ lineWidth: -1, // 不自動換行
413
+ noRefs: true, // 不使用引用
414
+ sortKeys: false // 保持原有順序
415
+ });
416
+ }
417
+
418
+ /**
419
+ * TASK_1771: Coerce a value into a TOKEN array for LIST fields
420
+ * (dependencies, tags, scope_paths). These hold comma-separable short tokens
421
+ * (e.g. "TASK_9001,TASK_9002" or "code,fix").
422
+ * - array => map(String).map(trim).filter(Boolean)
423
+ * - string => split(",").map(trim).filter(Boolean)
424
+ * - null/other => []
425
+ * Root-cause safety net: MCP/args-file/programmatic callers may pass a raw
426
+ * string where an array is expected; without coercion a poisoned task crashes
427
+ * every consumer that calls .join/.some/.filter on the field, aborting the
428
+ * whole-queue dispatch scan.
429
+ */
430
+ function toTokenArray(v) {
431
+ if (Array.isArray(v)) {
432
+ return v.map((x) => String(x).trim()).filter(Boolean);
433
+ }
434
+ if (typeof v === "string") {
435
+ return v.split(",").map((s) => s.trim()).filter(Boolean);
436
+ }
437
+ return [];
438
+ }
439
+
440
+ /**
441
+ * TASK_1771: Coerce a value into a FREE-TEXT array for prose fields
442
+ * (requirements, acceptance_criteria, technical_notes). These may contain
443
+ * commas / newlines / YAML block scalars, so we MUST NOT comma-split.
444
+ * - array => filter(x => x != null)
445
+ * - string => trimmed non-empty ? [v] : [] (single element, preserved)
446
+ * - null/other => []
447
+ */
448
+ function toTextArray(v) {
449
+ if (Array.isArray(v)) {
450
+ return v.filter((x) => x != null);
451
+ }
452
+ if (typeof v === "string") {
453
+ return v.trim() ? [v] : [];
454
+ }
455
+ return [];
456
+ }
457
+
458
+ /**
459
+ * TASK_1778: Count how many LIVE status dirs (backlog/active/done/blocked/cancel)
460
+ * exist under a tasks root. Worktree git snapshots contain ONLY archived/ because
461
+ * .gitignore ignores .kenaz/tasks/ (the archived files pre-date the ignore rule),
462
+ * so a pin/cwd resolution landing on such a snapshot makes every list/scan
463
+ * silently report 0 tasks while the real files sit intact in the main repo.
464
+ */
465
+ function countLiveStatusDirs(tasksDir) {
466
+ const fsS = require('fs');
467
+ let n = 0;
468
+ for (const d of CONFIG.STATUS_DIRS) {
469
+ try { if (fsS.statSync(path.join(tasksDir, d)).isDirectory()) n++; } catch { /* missing */ }
470
+ }
471
+ return n;
472
+ }
473
+
474
+ /**
475
+ * Resolve the tasksDir for a given project path.
476
+ * - Sub-project (has .kenaz/): {projectPath}/.kenaz/tasks/
477
+ * - Core project: .collaboration/tasks/ (default)
478
+ * - Explicit tasksDir: use as-is
479
+ */
480
+ function resolveTasksDir(options = {}) {
481
+ // Explicit tasksDir always wins
482
+ if (options.tasksDir) {
483
+ return options.tasksDir;
484
+ }
485
+
486
+ // If projectPath is provided, auto-detect
487
+ if (options.projectPath) {
488
+ const fsSync = require('fs');
489
+ const collabTasksDir = path.join(options.projectPath, '.collaboration', 'tasks');
490
+ const kenazTasksDir = path.join(options.projectPath, '.kenaz', 'tasks');
491
+ const hasCollab = fsSync.existsSync(collabTasksDir);
492
+ const hasKenaz = fsSync.existsSync(kenazTasksDir);
493
+
494
+ // All projects use .kenaz/tasks/ when available (unified path, no core vs sub-project distinction)
495
+ if (hasKenaz) return kenazTasksDir;
496
+ if (hasCollab) return collabTasksDir;
497
+
498
+ // Fallback: assume sub-project pattern
499
+ return kenazTasksDir;
500
+ }
501
+
502
+ // Default: consult pin file first (TASK_1701), then cwd, then CONFIG default.
503
+ // This ensures a session pinned to a sub-project gets that project tasks,
504
+ // without requiring --project-path on every invocation.
505
+ const fsSync = require('fs');
506
+ const resolver = getProjectResolver();
507
+ if (resolver) {
508
+ const resolved = resolver.resolveProjectPath({ argv: process.argv.slice(2) });
509
+ if (resolved.source !== 'base') {
510
+ // Pin or cwd detected a specific project -- use its tasks dir
511
+ const pinKenaz = path.join(resolved.projectPath, '.kenaz', 'tasks');
512
+ const pinCollab = path.join(resolved.projectPath, '.collaboration', 'tasks');
513
+ const candidate = fsSync.existsSync(pinKenaz) ? pinKenaz
514
+ : (fsSync.existsSync(pinCollab) ? pinCollab : null);
515
+ if (candidate) {
516
+ // TASK_1778: a tasks root with ZERO live status dirs (backlog/active/done/
517
+ // blocked/cancel) is degenerate -- typically a .formation/worktrees/wt-*
518
+ // git snapshot that only carries archived/. Following it makes list/scan
519
+ // silently report 0 tasks. A PIN pointing at such a root is ignored
520
+ // (loudly) and resolution falls through to cwd; a cwd-sourced degenerate
521
+ // root is kept (behavior-preserving) but announced on stderr.
522
+ const viable = countLiveStatusDirs(candidate) > 0;
523
+ const isPin = resolved.source === 'pin' || resolved.source === 'pin-heuristic';
524
+ if (viable || !isPin) {
525
+ if (isPin && path.resolve(resolved.projectPath) !== path.resolve(process.cwd())) {
526
+ process.stderr.write('[task-api] tasksDir resolved via session pin -> ' + candidate + String.fromCharCode(10));
527
+ }
528
+ if (!viable) {
529
+ process.stderr.write('[task-api] ⚠️ tasksDir has no live status dirs (' + CONFIG.STATUS_DIRS.join('/') + '): ' + candidate +
530
+ ' -- list/scan will see 0 tasks. Worktree snapshot? Use --project-path to target the real repo.' + String.fromCharCode(10));
531
+ }
532
+ return candidate;
533
+ }
534
+ process.stderr.write('[task-api] ⚠️ ignoring pinned project ' + resolved.projectPath + ': tasks root ' + candidate +
535
+ ' has no live status dirs (' + CONFIG.STATUS_DIRS.join('/') + ') -- falling back to cwd resolution.' + String.fromCharCode(10));
536
+ }
537
+ }
538
+ }
539
+ const cwdKenaz = path.join(process.cwd(), '.kenaz', 'tasks');
540
+ if (fsSync.existsSync(cwdKenaz)) {
541
+ if (countLiveStatusDirs(cwdKenaz) === 0) {
542
+ process.stderr.write('[task-api] ⚠️ tasksDir has no live status dirs (' + CONFIG.STATUS_DIRS.join('/') + '): ' + cwdKenaz +
543
+ ' -- list/scan will see 0 tasks. If cwd is a worktree snapshot, pass --project-path <main repo>.' + String.fromCharCode(10));
544
+ }
545
+ return cwdKenaz;
546
+ }
547
+
548
+ return CONFIG.TASKS_DIR;
549
+ }
550
+
551
+ /**
552
+ * TASK_1778: When list scans 0 tasks, explain WHERE it looked instead of a
553
+ * silent '0 total'. Emits stderr ONLY when there is evidence of misresolution:
554
+ * non-empty sibling dirs (archived/archive/...) under the resolved root, or a
555
+ * non-empty .kenaz/tasks under cwd that resolution ignored. A genuinely fresh
556
+ * empty project stays quiet. Diagnostics must never break list.
557
+ */
558
+ function emitEmptyListDiagnostics(tasksDir) {
559
+ try {
560
+ const fsS = require('fs');
561
+ const abs = path.resolve(tasksDir);
562
+ const evidence = [];
563
+ let entries = [];
564
+ try { entries = fsS.readdirSync(abs, { withFileTypes: true }); } catch { /* missing root */ }
565
+ for (const e of entries) {
566
+ if (!e.isDirectory() || CONFIG.STATUS_DIRS.includes(e.name)) continue;
567
+ let n = 0;
568
+ try { n = fsS.readdirSync(path.join(abs, e.name)).filter((x) => x.startsWith('TASK_')).length; } catch { n = 0; }
569
+ if (n > 0) evidence.push(' sibling ' + e.name + '/ holds ' + n + ' task files -- this root looks like a snapshot (worktree?) missing live status dirs');
570
+ }
571
+ const cwdKenaz = path.resolve(process.cwd(), '.kenaz', 'tasks');
572
+ if (cwdKenaz !== abs && fsS.existsSync(cwdKenaz)) {
573
+ let live = 0;
574
+ for (const d of CONFIG.STATUS_DIRS) {
575
+ try { live += fsS.readdirSync(path.join(cwdKenaz, d)).filter((x) => x.startsWith('TASK_')).length; } catch { /* missing */ }
576
+ }
577
+ if (live > 0) evidence.push(' cwd has a non-empty tasks root: ' + cwdKenaz + ' (' + live + ' task files) -- resolution ignored it; use --project-path ' + process.cwd());
578
+ }
579
+ if (evidence.length > 0) {
580
+ process.stderr.write(['⚠️ [task-api] list found 0 tasks -- resolved tasksDir: ' + abs].concat(evidence).join(String.fromCharCode(10)) + String.fromCharCode(10));
581
+ }
582
+ } catch { /* never break list */ }
583
+ }
584
+
585
+ class TaskAPI {
586
+ /**
587
+ * @param {Object} config
588
+ * @param {string} [config.tasksDir] - Explicit tasks directory path
589
+ * @param {string} [config.projectPath] - Project root path (auto-detect tasksDir)
590
+ */
591
+ constructor(config = {}) {
592
+ this.tasksDir = resolveTasksDir(config);
593
+ this.projectPath = config.projectPath || null;
594
+ }
595
+
596
+ /**
597
+ * 取得當前 Git 用戶資訊
598
+ */
599
+ _getGitUser() {
600
+ const cwd = this.projectPath || path.resolve(__dirname, '../..');
601
+ try {
602
+ const name = execSync('git config user.name', { encoding: 'utf8', cwd, windowsHide: true }).trim();
603
+ const email = execSync('git config user.email', { encoding: 'utf8', cwd, windowsHide: true }).trim();
604
+ const gitUser = email.split('@')[0];
605
+ return { name, email, git_user: gitUser };
606
+ } catch (error) {
607
+ return { name: 'Unknown', email: '', git_user: 'unknown' };
608
+ }
609
+ }
610
+
611
+ /**
612
+ * 取得狀態對應的目錄路徑
613
+ */
614
+ _getStatusDir(status) {
615
+ // 轉換舊狀態名稱
616
+ const normalizedStatus = CONFIG.STATUS_MAP[status] || status;
617
+ return path.join(this.tasksDir, normalizedStatus);
618
+ }
619
+
620
+ /**
621
+ * 確保所有狀態目錄存在
622
+ */
623
+ async _ensureStatusDirs() {
624
+ for (const dir of CONFIG.STATUS_DIRS) {
625
+ await fs.mkdir(path.join(this.tasksDir, dir), { recursive: true });
626
+ }
627
+ }
628
+
629
+ /**
630
+ * 從任務檔案路徑推斷 status 目錄名(TASK_1843 / TASK_1846 共用 helper):
631
+ * 往上走祖先目錄鏈,找出第一個屬於 ALL_STATUS_DIRS 的目錄名。
632
+ * 只看直接父目錄會漏掉目錄型卡片
633
+ * (active/TASK_XXX/TASK_XXX.yaml → 父目錄是 'TASK_XXX',不是 status dir)。
634
+ * 上界:this.tasksDir(到達即停,不往上猜);最多走 3 層,涵蓋
635
+ * 扁平檔(1 層)與目錄型(2 層)版面,並留一層餘裕。
636
+ * 找不到時回傳 null — 呼叫端自行決定 fallback 策略。
637
+ */
638
+ _resolveStatusDirName(filePath) {
639
+ let probeDir = path.dirname(filePath);
640
+ for (let depth = 0; depth < 3; depth++) {
641
+ const base = path.basename(probeDir);
642
+ if (CONFIG.ALL_STATUS_DIRS.includes(base)) {
643
+ return base;
644
+ }
645
+ // 到達 tasksDir 上界或檔案系統根目錄 → 停止(status dir 一定在 tasksDir 之下)
646
+ if (this.tasksDir && path.resolve(probeDir) === path.resolve(this.tasksDir)) break;
647
+ const parent = path.dirname(probeDir);
648
+ if (parent === probeDir) break;
649
+ probeDir = parent;
650
+ }
651
+ return null;
652
+ }
653
+
654
+ /**
655
+ * TASK_1864: 寫回驗證(write-back verification)。
656
+ * 背景:三個已修復的靜默變異缺陷(TASK_1832 / TASK_1843+1846 / TASK_1851)
657
+ * 機制互不相同,唯一共性是「操作回報成功,但落盤狀態 != 呼叫者認知」。
658
+ * 本 helper 讓該類別的下一個形狀大聲失敗:寫檔後立刻讀回、解析、
659
+ * 與意圖物件全欄位 diff,不一致即 throw(不是 warn — 降級成警告等於沒做)。
660
+ *
661
+ * 比對規則:
662
+ * - 忽略底線開頭內部鍵(_file_path/_status_dir/...,toYAML 本來就過濾)
663
+ * - 忽略 metadata.updated_at(每次寫入必變)
664
+ * - 其餘欄位全比對,不用白名單 — 白名單會讓下一個未預期形狀再度靜默
665
+ * - Date 與 ISO 字串視為等值(YAML timestamp round-trip 可能互轉)
666
+ *
667
+ * 唯一實作:createTask / updateTask / moveTask 三個寫入點共用本方法,
668
+ * 禁止複製第二份(lock 協定三份實作各自漂移的前例:TASK_1839)。
669
+ */
670
+ async _verifyPersisted(filePath, intendedTask) {
671
+ let content;
672
+ try {
673
+ content = await fs.readFile(filePath, 'utf8');
674
+ } catch (err) {
675
+ throw new Error(`PERSISTENCE VERIFY FAILED (${filePath}): 寫入後無法讀回 — ${err.message}`);
676
+ }
677
+ const persisted = parseYAML(content, filePath);
678
+
679
+ // 意圖基準:與 toYAML 相同的底線鍵過濾(內部鍵不落盤)
680
+ const intended = {};
681
+ for (const [key, value] of Object.entries(intendedTask)) {
682
+ if (!key.startsWith('_')) intended[key] = value;
683
+ }
684
+
685
+ const IGNORED_PATHS = new Set(['metadata.updated_at']);
686
+ // YAML round-trip 正規化:Date <-> ISO 字串視為同值
687
+ const norm = (v) => (v instanceof Date ? v.toISOString() : v);
688
+ const show = (v) => {
689
+ if (v === undefined) return '<missing>';
690
+ let s;
691
+ try { s = JSON.stringify(norm(v)); } catch { s = String(v); }
692
+ return s && s.length > 120 ? s.slice(0, 120) + '…' : s;
693
+ };
694
+
695
+ const diffs = [];
696
+ const walk = (a, b, prefix) => {
697
+ if (IGNORED_PATHS.has(prefix)) return;
698
+ const na = norm(a);
699
+ const nb = norm(b);
700
+ if (na === nb) return;
701
+ const aIsObj = na !== null && typeof na === 'object';
702
+ const bIsObj = nb !== null && typeof nb === 'object';
703
+ if (aIsObj && bIsObj && Array.isArray(na) === Array.isArray(nb)) {
704
+ const keys = new Set([...Object.keys(na), ...Object.keys(nb)]);
705
+ for (const k of keys) {
706
+ walk(na[k], nb[k], prefix ? `${prefix}.${k}` : k);
707
+ }
708
+ return;
709
+ }
710
+ diffs.push(`${prefix || '<root>'}: intended=${show(a)} persisted=${show(b)}`);
711
+ };
712
+ walk(intended, persisted, '');
713
+
714
+ if (diffs.length > 0) {
715
+ throw new Error(
716
+ `PERSISTENCE VERIFY FAILED (${filePath}): 落盤內容與意圖不一致(${diffs.length} 處)— ` +
717
+ diffs.slice(0, 10).join('; ') +
718
+ (diffs.length > 10 ? `; …+${diffs.length - 10} more` : '')
719
+ );
720
+ }
721
+ }
722
+
723
+ /**
724
+ * 讀取 YAML 任務檔案
725
+ * Returns null when the file is unreadable or its YAML is invalid AND
726
+ * the parser produced an empty object — caller should filter nulls so
727
+ * one corrupted file does not break the entire list/scan operation.
728
+ */
729
+ async _loadTaskFile(filePath) {
730
+ let content;
731
+ try {
732
+ content = await fs.readFile(filePath, 'utf8');
733
+ } catch (err) {
734
+ console.error(`Task file unreadable (${filePath}): ${err.message}`);
735
+ return null;
736
+ }
737
+ const task = parseYAML(content, filePath);
738
+
739
+ // parseYAML returns {} on failure (after logging the error). Empty object →
740
+ // unrecoverable parse failure, skip silently (the parser already logged).
741
+ if (!task || Object.keys(task).length === 0) {
742
+ return null;
743
+ }
744
+
745
+ // Schema fallback: some legacy archived YAMLs have a valid body but no top-level
746
+ // `id` field. Recover the id from the filename pattern `TASK_<num>...` so the
747
+ // task is still discoverable instead of being silently dropped.
748
+ if (!task.id) {
749
+ const baseName = path.basename(filePath);
750
+ const match = baseName.match(/^(TASK_\d+)/);
751
+ if (match) {
752
+ task.id = match[1];
753
+ } else {
754
+ console.error(`Task file has no id field and filename does not match TASK_<num>: ${filePath}`);
755
+ return null;
756
+ }
757
+ }
758
+
759
+ // 從檔案路徑推斷狀態(TASK_1843):祖先鏈解析抽至 _resolveStatusDirName
760
+ // (TASK_1846,與 updateTask 共用)。直接父目錄的舊寫法會把目錄型卡片的
761
+ // _status_dir 毒化成 'TASK_XXX',並讓內嵌的陳舊 status 原樣流出
762
+ // (get 與 list 對同一張卡給出不同狀態)。
763
+ task._file_path = filePath;
764
+ const statusDirName = this._resolveStatusDirName(filePath);
765
+
766
+ if (statusDirName) {
767
+ // 目錄是狀態的唯一真相來源(使用舊格式輸出),同時覆寫內嵌 status 與 _status_dir
768
+ task._status_dir = statusDirName;
769
+ task.status = CONFIG.REVERSE_STATUS_MAP[statusDirName] || statusDirName;
770
+ } else {
771
+ // 找不到任何 status 祖先目錄:_loadTaskFile 收到了不在 status 樹下的路徑。
772
+ // 選擇:保留內嵌 status 值、但同時設 _status_unresolved = true。
773
+ // 為什麼不回 null status:既有呼叫端(list 過濾、post-complete 等)以
774
+ // truthy status 判斷卡片有效性,強制 null 會讓這類卡片被靜默丟棄;
775
+ // 保留內嵌值 + 顯式旗標讓呼叫端能區分「目錄推斷(可信)」vs
776
+ // 「僅內嵌值(不可信,需自行查證)」,而非沉默地信任內嵌值。
777
+ task._status_dir = null;
778
+ task._status_unresolved = true;
779
+ }
780
+
781
+ // 向後相容:agent_type → role
782
+ if (task.agent_type && !task.role) {
783
+ task.role = task.agent_type;
784
+ }
785
+
786
+ // 向後相容:字串 tags(逗號分隔)→ 陣列。歷史 bug:kenaz_task_create 曾把
787
+ // 逗號字串原樣寫入 YAML,下游 (t.tags || []).join 會 crash 並截斷 list 輸出。
788
+ if (typeof task.tags === 'string') {
789
+ task.tags = task.tags.split(',').map((t) => t.trim()).filter(Boolean);
790
+ }
791
+
792
+ return task;
793
+ }
794
+
795
+ /**
796
+ * 掃描指定目錄下的所有 YAML 任務
797
+ * 支援兩種格式:
798
+ * - 檔案: TASK_XXX_username.yaml
799
+ * - 資料夾: TASK_XXX/ (內含 .yaml 檔案)
800
+ */
801
+ async _scanDir(dirPath) {
802
+ const tasks = [];
803
+ const seenIds = new Set();
804
+
805
+ try {
806
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
807
+
808
+ for (const entry of entries) {
809
+ if (!entry.name.startsWith('TASK_')) continue;
810
+
811
+ if (entry.isFile() &&
812
+ (entry.name.endsWith('.yaml') || entry.name.endsWith('.yml'))) {
813
+ // Flat file: TASK_XXX_username.yaml
814
+ try {
815
+ const filePath = path.join(dirPath, entry.name);
816
+ const task = await this._loadTaskFile(filePath);
817
+ // _loadTaskFile returns null for unreadable / corrupted / id-less files
818
+ if (!task) continue;
819
+ if (seenIds.has(task.id)) continue;
820
+ seenIds.add(task.id);
821
+ tasks.push(task);
822
+ } catch (error) {
823
+ console.error(`警告: 無法載入任務 ${entry.name}:`, error.message);
824
+ }
825
+ } else if (entry.isDirectory()) {
826
+ // Directory task: TASK_XXX/ — find .yaml files inside
827
+ try {
828
+ const subEntries = await fs.readdir(path.join(dirPath, entry.name), { withFileTypes: true });
829
+ for (const sub of subEntries) {
830
+ if (sub.isFile() && (sub.name.endsWith('.yaml') || sub.name.endsWith('.yml'))) {
831
+ try {
832
+ const filePath = path.join(dirPath, entry.name, sub.name);
833
+ const task = await this._loadTaskFile(filePath);
834
+ if (!task) continue;
835
+ // Ensure id from directory name if missing (defensive — _loadTaskFile
836
+ // already requires id, but the directory-based layout could legitimately
837
+ // carry id only in dirname)
838
+ if (!task.id) {
839
+ const match = entry.name.match(/^(TASK_\d+)/);
840
+ if (match) task.id = match[1];
841
+ }
842
+ tasks.push(task);
843
+ } catch (error) {
844
+ console.error(`警告: 無法載入任務 ${entry.name}/${sub.name}:`, error.message);
845
+ }
846
+ }
847
+ }
848
+ } catch (error) {
849
+ console.error(`警告: 無法掃描資料夾 ${entry.name}:`, error.message);
850
+ }
851
+ }
852
+ }
853
+ } catch (error) {
854
+ if (error.code !== 'ENOENT') {
855
+ throw error;
856
+ }
857
+ }
858
+
859
+ return tasks;
860
+ }
861
+
862
+ /**
863
+ * 掃描所有狀態目錄
864
+ */
865
+ async _scanAllDirs(includeArchived = false) {
866
+ const allTasks = [];
867
+ const seenIds = new Set();
868
+ const dirsToScan = includeArchived ? [...CONFIG.STATUS_DIRS, 'archived'] : CONFIG.STATUS_DIRS;
869
+
870
+ // 1. 當前專案任務(.kenaz/tasks/ 或 .collaboration/tasks/)
871
+ for (const dir of dirsToScan) {
872
+ const dirPath = path.join(this.tasksDir, dir);
873
+ const tasks = await this._scanDir(dirPath);
874
+ for (const t of tasks) {
875
+ t._source = 'new';
876
+ seenIds.add(t.id);
877
+ allTasks.push(t);
878
+ }
879
+ }
880
+
881
+ // 2. 舊系統(dev/pipeline/)— 只有 Core 專案才掃描
882
+ // 子專案(.kenaz/tasks/)不再載入 legacy pipeline
883
+ const isSubProject = this.tasksDir.includes('.kenaz');
884
+ if (!isSubProject) {
885
+ const legacyTasks = await this._scanLegacyPipeline();
886
+ for (const t of legacyTasks) {
887
+ if (!seenIds.has(t.id)) {
888
+ t._source = 'old';
889
+ seenIds.add(t.id);
890
+ allTasks.push(t);
891
+ }
892
+ }
893
+ }
894
+
895
+ // 3. 專案隔離:不再跨專案載入任務
896
+ // 主專案只讀主專案任務,子專案只讀子專案任務
897
+
898
+ return allTasks;
899
+ }
900
+
901
+ /**
902
+ * Resolve project root from tasksDir.
903
+ * Core: .collaboration/tasks/ -> go up 2 levels
904
+ * Sub-project: .kenaz/tasks/ -> go up 2 levels
905
+ */
906
+ _resolveProjectRoot() {
907
+ if (this.projectPath) return this.projectPath;
908
+ return path.resolve(this.tasksDir, '../..');
909
+ }
910
+
911
+ _getAnsuzThoughtStackPath() {
912
+ return path.join(this._resolveProjectRoot(), '.kenaz', 'specialist', 'ansuz_thoughts_stack.json');
913
+ }
914
+
915
+ _rewriteJsonFileAtomic(filePath, data) {
916
+ const fsSync = require('fs');
917
+ const tempPath = `${filePath}.tmp`;
918
+ fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
919
+ fsSync.writeFileSync(tempPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
920
+ fsSync.renameSync(tempPath, filePath);
921
+ }
922
+
923
+ _autoPopThoughtsForCompletedTask(taskId, completedAt) {
924
+ const fsSync = require('fs');
925
+ const stackPath = this._getAnsuzThoughtStackPath();
926
+
927
+ if (!fsSync.existsSync(stackPath)) {
928
+ return {
929
+ event: 'thought_stack.auto_pop',
930
+ task_id: taskId,
931
+ removed_ids: [],
932
+ removed_count: 0,
933
+ completed_at: completedAt
934
+ };
935
+ }
936
+
937
+ const raw = fsSync.readFileSync(stackPath, 'utf8');
938
+ const parsed = JSON.parse(raw);
939
+ const originalStack = Array.isArray(parsed?.stack) ? parsed.stack : [];
940
+ const nowMs = Date.now();
941
+ const liveStack = originalStack.filter((thought) => {
942
+ if (!thought || typeof thought !== 'object') return false;
943
+ if (typeof thought.expires_at !== 'string' || thought.expires_at.trim() === '') {
944
+ return true;
945
+ }
946
+ const expiresAtMs = Date.parse(thought.expires_at);
947
+ return Number.isNaN(expiresAtMs) || expiresAtMs > nowMs;
948
+ });
949
+
950
+ const removedThoughts = liveStack.filter((thought) => thought.related_task_id === taskId);
951
+ const remainingStack = liveStack.filter((thought) => thought.related_task_id !== taskId);
952
+ const removedIds = removedThoughts
953
+ .map((thought) => thought.id)
954
+ .filter((id) => typeof id === 'string' && id.trim() !== '');
955
+ const shouldPersist =
956
+ liveStack.length !== originalStack.length ||
957
+ remainingStack.length !== originalStack.length;
958
+
959
+ if (shouldPersist) {
960
+ this._rewriteJsonFileAtomic(stackPath, {
961
+ ...parsed,
962
+ stack: remainingStack
963
+ });
964
+ }
965
+
966
+ if (removedIds.length > 0) {
967
+ console.log(`◈ Auto-popped ${removedIds.length} thoughts linked to ${taskId}`);
968
+ }
969
+
970
+ return {
971
+ event: 'thought_stack.auto_pop',
972
+ task_id: taskId,
973
+ removed_ids: removedIds,
974
+ removed_count: removedIds.length,
975
+ completed_at: completedAt
976
+ };
977
+ }
978
+
979
+ /**
980
+ * 掃描舊系統 dev/pipeline/(TASK_QUEUE.json + completed/ YAML)
981
+ */
982
+ async _scanLegacyPipeline() {
983
+ const projectRoot = this._resolveProjectRoot();
984
+ const pipelineDir = path.join(projectRoot, 'dev', 'pipeline');
985
+ const tasks = [];
986
+
987
+ // 2a. 從 TASK_QUEUE.json 讀取活躍(非 completed)任務
988
+ // completed 任務視為 archived,不在 Dashboard 顯示
989
+ const queueFile = path.join(pipelineDir, 'TASK_QUEUE.json');
990
+ try {
991
+ const content = await fs.readFile(queueFile, 'utf8');
992
+ // 移除 BOM
993
+ const cleanContent = content.charCodeAt(0) === 0xFEFF ? content.slice(1) : content;
994
+ const queue = JSON.parse(cleanContent);
995
+ if (Array.isArray(queue.tasks)) {
996
+ for (const t of queue.tasks) {
997
+ if (t.id && t.status !== 'completed') {
998
+ t._file_path = queueFile;
999
+ t._source = 'old';
1000
+ tasks.push(t);
1001
+ }
1002
+ }
1003
+ }
1004
+ } catch (e) {
1005
+ // TASK_QUEUE.json 不存在或解析失敗,靜默跳過
1006
+ }
1007
+
1008
+ // 2b. 不載入 completed/ 目錄 — 視為 archived
1009
+ // (舊系統的 completed/ ≈ 新系統的 archived/)
1010
+
1011
+ return tasks;
1012
+ }
1013
+
1014
+ /**
1015
+ * 找出最大的任務編號
1016
+ */
1017
+ async _findMaxTaskNumber() {
1018
+ const tasks = await this._scanAllDirs();
1019
+ let maxNum = 0;
1020
+
1021
+ for (const task of tasks) {
1022
+ const id = task.id || '';
1023
+ const match = id.match(/^TASK_(\d+)/);
1024
+ if (match) {
1025
+ const num = parseInt(match[1], 10);
1026
+ if (num > maxNum) {
1027
+ maxNum = num;
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ return maxNum;
1033
+ }
1034
+
1035
+ /**
1036
+ * 生成新的任務 ID
1037
+ * Uses high-water mark file (.counter) to prevent ID regression when
1038
+ * directories are temporarily empty or inaccessible (e.g., stale cwd
1039
+ * after worktree cleanup).
1040
+ *
1041
+ * Shared mode (Yggdrasil relay enabled + shared_task_ids !== false):
1042
+ * - Consumes IDs from a lease cache {tasksDir}/.id_lease.json
1043
+ * - Refills from relay POST /api/tasks/allocate-ids when exhausted
1044
+ * - Falls back to local counter on relay failure (id_alloc: local_fallback)
1045
+ * - Keeps .counter monotonic so solo fallback never collides backwards
1046
+ *
1047
+ * Solo mode: existing behavior byte-for-byte unchanged.
1048
+ */
1049
+ async generateTaskID() {
1050
+ const yggConfig = this._loadYggdrasilConfigSync();
1051
+ const isShared = yggConfig &&
1052
+ yggConfig.enabled !== false &&
1053
+ yggConfig.relay_url &&
1054
+ yggConfig.relay_url.trim() !== '' &&
1055
+ yggConfig.shared_task_ids !== false;
1056
+
1057
+ if (!isShared) {
1058
+ // Solo path — byte-for-byte unchanged
1059
+ const scanMax = await this._findMaxTaskNumber();
1060
+ const counterMax = this._readCounterFile();
1061
+ const maxNum = Math.max(scanMax, counterMax);
1062
+ const nextId = `TASK_${maxNum + 1}`;
1063
+ this._writeCounterFile(maxNum + 1);
1064
+ return nextId;
1065
+ }
1066
+
1067
+ // ---- Shared mode ----
1068
+ // Try to consume from lease cache first
1069
+ const lease = this._readLease();
1070
+ if (lease && lease.next <= lease.end) {
1071
+ const id = lease.next;
1072
+ this._writeLease({ ...lease, next: id + 1 });
1073
+ this._writeCounterFile(id);
1074
+ this._lastIdAlloc = 'relay';
1075
+ return `TASK_${id}`;
1076
+ }
1077
+
1078
+ // Lease exhausted — refill from relay
1079
+ const scanMax = await this._findMaxTaskNumber();
1080
+ const counterMax = this._readCounterFile();
1081
+ const localMax = Math.max(scanMax, counterMax);
1082
+
1083
+ let allocated = null;
1084
+ try {
1085
+ allocated = await this._allocateFromRelay(yggConfig, localMax);
1086
+ } catch {
1087
+ // relay unreachable — fall through to local fallback
1088
+ }
1089
+
1090
+ if (allocated && typeof allocated.start === 'number' && typeof allocated.end === 'number') {
1091
+ const start = allocated.start;
1092
+ const end = allocated.end;
1093
+ const firstId = start;
1094
+ this._writeLease({
1095
+ start,
1096
+ end,
1097
+ next: firstId + 1,
1098
+ leased_at: new Date().toISOString()
1099
+ });
1100
+ this._writeCounterFile(firstId);
1101
+ this._lastIdAlloc = 'relay';
1102
+ return `TASK_${firstId}`;
1103
+ }
1104
+
1105
+ // Relay unreachable + lease exhausted — local fallback
1106
+ const maxNum = localMax;
1107
+ const nextId = `TASK_${maxNum + 1}`;
1108
+ this._writeCounterFile(maxNum + 1);
1109
+ this._lastIdAlloc = 'local_fallback';
1110
+ return nextId;
1111
+ }
1112
+
1113
+ // ---- Yggdrasil shared-ID helpers ----
1114
+
1115
+ /**
1116
+ * Load .kenaz/yggdrasil.yaml synchronously.
1117
+ * Returns null when file absent, parse fails, or enabled:false.
1118
+ */
1119
+ _loadYggdrasilConfigSync() {
1120
+ const fsSync = require('fs');
1121
+ const projectRoot = this._resolveProjectRoot();
1122
+ const yamlPath = path.join(projectRoot, '.kenaz', 'yggdrasil.yaml');
1123
+ // TASK_2244: per-machine keys live in the gitignored yggdrasil.local.yaml (overlay).
1124
+ const localPath = path.join(projectRoot, '.kenaz', 'yggdrasil.local.yaml');
1125
+ if (!fsSync.existsSync(yamlPath) && !fsSync.existsSync(localPath)) return null;
1126
+ try {
1127
+ let config = fsSync.existsSync(yamlPath) ? yaml.load(fsSync.readFileSync(yamlPath, 'utf8')) : null;
1128
+ config = require('../../.formation/tools/yggdrasil-local.js').overlayMachineLocal(projectRoot, config, (t) => yaml.load(t));
1129
+ if (!config || config.enabled === false) return null;
1130
+ return config;
1131
+ } catch {
1132
+ return null;
1133
+ }
1134
+ }
1135
+
1136
+ /**
1137
+ * Read relay publishable_key.
1138
+ * Priority: YGGDRASIL_PUBLISHABLE_KEY env var -> ~/.kenaz/credentials.yaml
1139
+ */
1140
+ _readPublishableKey() {
1141
+ if (process.env.YGGDRASIL_PUBLISHABLE_KEY) {
1142
+ return process.env.YGGDRASIL_PUBLISHABLE_KEY;
1143
+ }
1144
+ const fsSync = require('fs');
1145
+ const os = require('os');
1146
+ try {
1147
+ const credPath = path.join(os.homedir(), '.kenaz', 'credentials.yaml');
1148
+ if (fsSync.existsSync(credPath)) {
1149
+ const raw = fsSync.readFileSync(credPath, 'utf8');
1150
+ const m = raw.match(/^publishable_key:\s*"?([^"]+)"?/m);
1151
+ if (m) return m[1].trim();
1152
+ }
1153
+ } catch { /* credentials unavailable */ }
1154
+ return null;
1155
+ }
1156
+
1157
+ /** Lease cache file path */
1158
+ _leaseCachePath() {
1159
+ return path.join(this.tasksDir, '.id_lease.json');
1160
+ }
1161
+
1162
+ /** Read lease cache. Returns null when absent or corrupt. */
1163
+ _readLease() {
1164
+ const fsSync = require('fs');
1165
+ try {
1166
+ const raw = fsSync.readFileSync(this._leaseCachePath(), 'utf8');
1167
+ const obj = JSON.parse(raw);
1168
+ if (typeof obj.start === 'number' && typeof obj.end === 'number' && typeof obj.next === 'number') {
1169
+ return obj;
1170
+ }
1171
+ } catch { /* absent or corrupt */ }
1172
+ return null;
1173
+ }
1174
+
1175
+ /** Write lease cache atomically. */
1176
+ _writeLease(obj) {
1177
+ const fsSync = require('fs');
1178
+ const filePath = this._leaseCachePath();
1179
+ const tempPath = `${filePath}.tmp`;
1180
+ try {
1181
+ fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
1182
+ fsSync.writeFileSync(tempPath, JSON.stringify(obj, null, 2), 'utf8');
1183
+ fsSync.renameSync(tempPath, filePath);
1184
+ } catch (e) {
1185
+ process.stderr.write(`[task-api] lease write failed: ${e.message}
1186
+ `);
1187
+ }
1188
+ }
1189
+
1190
+ /**
1191
+ * Call relay POST /api/tasks/allocate-ids with 3s total timeout.
1192
+ * Derives HTTP base from relay_url (wss:// -> https://, ws:// -> http://).
1193
+ * Uses Node core http/https -- no external dependencies.
1194
+ *
1195
+ * @param {object} yggConfig - loaded yggdrasil config
1196
+ * @param {number} localMax - Lamport local_max for sequence merge
1197
+ * @returns {Promise<{start:number, end:number}>}
1198
+ */
1199
+ _allocateFromRelay(yggConfig, localMax) {
1200
+ return new Promise((resolve, reject) => {
1201
+ const relayUrl = yggConfig.relay_url;
1202
+ const httpBase = relayUrl
1203
+ .replace(/^wss:\/\//, 'https://')
1204
+ .replace(/^ws:\/\//, 'http://');
1205
+
1206
+ const publishableKey = this._readPublishableKey();
1207
+
1208
+ // Derive project name (yggdrasil_project override -> folder basename)
1209
+ const projectRoot = this._resolveProjectRoot();
1210
+ const project = (yggConfig.yggdrasil_project && String(yggConfig.yggdrasil_project).trim()) ||
1211
+ path.basename(projectRoot) || 'default';
1212
+
1213
+ const body = JSON.stringify({
1214
+ project,
1215
+ count: 20,
1216
+ local_max: localMax
1217
+ });
1218
+
1219
+ let url;
1220
+ try {
1221
+ url = new URL('/api/tasks/allocate-ids', httpBase);
1222
+ } catch {
1223
+ reject(new Error(`invalid relay_url: ${httpBase}`));
1224
+ return;
1225
+ }
1226
+
1227
+ const isHttps = url.protocol === 'https:';
1228
+ const transport = isHttps ? require('https') : require('http');
1229
+
1230
+ const options = {
1231
+ hostname: url.hostname,
1232
+ port: url.port || (isHttps ? 443 : 80),
1233
+ path: url.pathname,
1234
+ method: 'POST',
1235
+ headers: {
1236
+ 'Content-Type': 'application/json',
1237
+ 'Content-Length': Buffer.byteLength(body),
1238
+ ...(publishableKey ? { Authorization: `Bearer ${publishableKey}` } : {})
1239
+ }
1240
+ };
1241
+
1242
+ const timeoutMs = 3000;
1243
+ let settled = false;
1244
+
1245
+ const req = transport.request(options, (res) => {
1246
+ let data = '';
1247
+ res.on('data', (chunk) => { data += chunk; });
1248
+ res.on('end', () => {
1249
+ if (settled) return;
1250
+ settled = true;
1251
+ try {
1252
+ const parsed = JSON.parse(data);
1253
+ if (typeof parsed.start === 'number' && typeof parsed.end === 'number') {
1254
+ resolve(parsed);
1255
+ } else {
1256
+ reject(new Error(`relay allocate-ids bad response: ${data}`));
1257
+ }
1258
+ } catch (e) {
1259
+ reject(new Error(`relay allocate-ids parse error: ${e.message}`));
1260
+ }
1261
+ });
1262
+ });
1263
+
1264
+ req.on('error', (e) => {
1265
+ if (settled) return;
1266
+ settled = true;
1267
+ reject(e);
1268
+ });
1269
+
1270
+ const timer = setTimeout(() => {
1271
+ if (settled) return;
1272
+ settled = true;
1273
+ req.destroy();
1274
+ reject(new Error('relay allocate-ids timeout'));
1275
+ }, timeoutMs);
1276
+
1277
+ req.on('close', () => clearTimeout(timer));
1278
+ req.write(body);
1279
+ req.end();
1280
+ });
1281
+ }
1282
+
1283
+ _counterFilePath() {
1284
+ return path.join(this.tasksDir, '.counter');
1285
+ }
1286
+
1287
+ _readCounterFile() {
1288
+ const fsSync = require('fs');
1289
+ try {
1290
+ const raw = fsSync.readFileSync(this._counterFilePath(), 'utf8').trim();
1291
+ const num = parseInt(raw, 10);
1292
+ return Number.isFinite(num) ? num : 0;
1293
+ } catch {
1294
+ return 0;
1295
+ }
1296
+ }
1297
+
1298
+ _writeCounterFile(num) {
1299
+ const fsSync = require('fs');
1300
+ try {
1301
+ const filePath = this._counterFilePath();
1302
+ const dir = path.dirname(filePath);
1303
+ fsSync.mkdirSync(dir, { recursive: true });
1304
+ fsSync.writeFileSync(filePath, String(num), 'utf8');
1305
+ } catch (e) {
1306
+ // best-effort — scan still works as fallback
1307
+ process.stderr.write(`[task-api] counter write failed: ${e.message}\n`);
1308
+ }
1309
+ }
1310
+
1311
+ /**
1312
+ * 驗證任務 ID 格式
1313
+ */
1314
+ validateTaskID(id) {
1315
+ if (!id || typeof id !== 'string') return false;
1316
+ const pattern = /^TASK_(\d+|[0-9A-Z]{10})$/;
1317
+ return pattern.test(id);
1318
+ }
1319
+
1320
+ /**
1321
+ * 驗證狀態轉換是否合法
1322
+ */
1323
+ validateTransition(fromStatus, toStatus) {
1324
+ const from = CONFIG.STATUS_MAP[fromStatus] || fromStatus;
1325
+ const to = CONFIG.STATUS_MAP[toStatus] || toStatus;
1326
+
1327
+ const allowed = CONFIG.ALLOWED_TRANSITIONS[from];
1328
+ if (!allowed) {
1329
+ return { valid: false, reason: `無效的來源狀態: ${fromStatus}` };
1330
+ }
1331
+
1332
+ if (!allowed.includes(to)) {
1333
+ return {
1334
+ valid: false,
1335
+ reason: `不允許的狀態轉換: ${fromStatus} → ${toStatus}。允許的目標: ${allowed.join(', ')}`
1336
+ };
1337
+ }
1338
+
1339
+ return { valid: true };
1340
+ }
1341
+
1342
+ /**
1343
+ * 驗證任務內容品質(品質閘門)
1344
+ * 確保任務有足夠的 description、requirements、acceptance_criteria、technical_notes
1345
+ * @param {Object} taskData - 任務資料
1346
+ * @returns {{ valid: boolean, warnings: string[] }}
1347
+ */
1348
+ validateTaskQuality(taskData) {
1349
+ const warnings = [];
1350
+
1351
+ if (!taskData.description || taskData.description.trim() === '') {
1352
+ warnings.push('description 為空');
1353
+ }
1354
+
1355
+ const reqCount = (taskData.requirements || []).length;
1356
+ if (reqCount < 3) {
1357
+ warnings.push(`requirements 僅 ${reqCount} 條(最少 3 條)`);
1358
+ }
1359
+
1360
+ const acCount = (taskData.acceptance_criteria || []).length;
1361
+ if (acCount < 3) {
1362
+ warnings.push(`acceptance_criteria 僅 ${acCount} 條(最少 3 條)`);
1363
+ }
1364
+
1365
+ const tnCount = (taskData.technical_notes || []).length;
1366
+ if (tnCount < 2) {
1367
+ warnings.push(`technical_notes 僅 ${tnCount} 條(最少 2 條)`);
1368
+ }
1369
+
1370
+ return {
1371
+ valid: warnings.length === 0,
1372
+ warnings
1373
+ };
1374
+ }
1375
+
1376
+ // ============================================================================
1377
+ // 核心 API
1378
+ // ============================================================================
1379
+
1380
+ /**
1381
+ * 取得所有任務
1382
+ * @param {Object} options - 可選參數
1383
+ * @param {string} options.format - 輸出格式:'table'(預設)或 'json'
1384
+ */
1385
+ async getAllTasks(options = {}) {
1386
+ const allTasks = await this._scanAllDirs(options.includeArchived || false);
1387
+
1388
+ // archived count: readdir-only (no YAML parse) when not loading archived tasks.
1389
+ // Mirrors route.ts countTaskDirs + Rust manager.rs:917 — total reflects
1390
+ // full project history, but tasks[] stays board-sized so dashboard polling
1391
+ // doesn't parse 1000+ historical files every 5s.
1392
+ let archivedCount = 0;
1393
+ if (!options.includeArchived) {
1394
+ try {
1395
+ const archivedDir = path.join(this.tasksDir, 'archived');
1396
+ const entries = await fs.readdir(archivedDir, { withFileTypes: true });
1397
+ archivedCount = entries.filter(e =>
1398
+ e.isFile() && /^TASK_.*\.ya?ml$/.test(e.name)
1399
+ ).length;
1400
+ } catch { /* archived dir absent → count 0 */ }
1401
+ }
1402
+
1403
+ // 計算統計(使用舊狀態名稱)
1404
+ const statistics = {
1405
+ total: allTasks.length + archivedCount,
1406
+ pending: allTasks.filter(t => t.status === 'pending').length,
1407
+ in_progress: allTasks.filter(t => t.status === 'in_progress').length,
1408
+ completed: allTasks.filter(t => t.status === 'completed').length + archivedCount,
1409
+ blocked: allTasks.filter(t => t.status === 'blocked').length,
1410
+ cancelled: allTasks.filter(t => t.status === 'cancelled').length
1411
+ };
1412
+
1413
+ return {
1414
+ version: CONFIG.API_VERSION,
1415
+ tasks: allTasks,
1416
+ statistics,
1417
+ metadata: {
1418
+ last_updated: new Date().toISOString(),
1419
+ directories: options.includeArchived ? [...CONFIG.STATUS_DIRS, 'archived'] : [...CONFIG.STATUS_DIRS, 'archived (count-only)'],
1420
+ format: options.format || 'table'
1421
+ }
1422
+ };
1423
+ }
1424
+
1425
+ /**
1426
+ * 取得單個任務
1427
+ */
1428
+ async getTask(taskId) {
1429
+ if (!this.validateTaskID(taskId)) {
1430
+ throw new Error(`無效的任務 ID: ${taskId}`);
1431
+ }
1432
+
1433
+ // 在所有目錄中搜尋(含 pool/blocked/cancel/archived)
1434
+ for (const dir of CONFIG.ALL_STATUS_DIRS) {
1435
+ const dirPath = path.join(this.tasksDir, dir);
1436
+
1437
+ try {
1438
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
1439
+
1440
+ for (const entry of entries) {
1441
+ // Match exact task ID: TASK_1_ or TASK_1.yaml, not TASK_10_
1442
+ if (entry.isFile() &&
1443
+ (entry.name.startsWith(taskId + '_') || entry.name.startsWith(taskId + '.')) &&
1444
+ (entry.name.endsWith('.yaml') || entry.name.endsWith('.yml'))) {
1445
+ const filePath = path.join(dirPath, entry.name);
1446
+ const task = await this._loadTaskFile(filePath);
1447
+ // _loadTaskFile returns null for corrupted/unparseable YAML (TASK_1699).
1448
+ // Guard: skip this file (warn) rather than crashing on null.id access.
1449
+ if (!task) {
1450
+ console.error('Warning: skipping corrupted task file (YAML parse failed): ' + filePath);
1451
+ continue;
1452
+ }
1453
+ if (!task.id) task.id = taskId;
1454
+ if (this.projectPath) {
1455
+ task._project_path = this.projectPath;
1456
+ }
1457
+ return task;
1458
+ }
1459
+ // Directory task: TASK_XXX/ (exact match)
1460
+ if (entry.isDirectory() && entry.name === taskId) {
1461
+ const subEntries = await fs.readdir(path.join(dirPath, entry.name));
1462
+ for (const sub of subEntries) {
1463
+ if (sub.endsWith('.yaml') || sub.endsWith('.yml')) {
1464
+ const filePath = path.join(dirPath, entry.name, sub);
1465
+ const task = await this._loadTaskFile(filePath);
1466
+ // _loadTaskFile returns null for corrupted YAML -- warn and skip (TASK_1699)
1467
+ if (!task) {
1468
+ console.error('Warning: skipping corrupted task file (YAML parse failed): ' + filePath);
1469
+ continue;
1470
+ }
1471
+ if (!task.id) task.id = taskId;
1472
+ if (this.projectPath) {
1473
+ task._project_path = this.projectPath;
1474
+ }
1475
+ return task;
1476
+ }
1477
+ }
1478
+ }
1479
+ }
1480
+ } catch (error) {
1481
+ if (error.code !== 'ENOENT') {
1482
+ throw error;
1483
+ }
1484
+ }
1485
+ }
1486
+
1487
+ return null;
1488
+ }
1489
+
1490
+ /**
1491
+ * 建立新任務
1492
+ */
1493
+ _assertTaskWritable(task, taskId) {
1494
+ const missing = [];
1495
+ if (!task || !task.id) missing.push('id');
1496
+ if (!task || !task.title) missing.push('title');
1497
+ if (!task || !task.priority) missing.push('priority');
1498
+ if (missing.length > 0) {
1499
+ throw new Error(`Task file for ${taskId || (task && task.id) || 'unknown'} is incomplete: missing [${missing.join(', ')}]. Refusing to rewrite a corrupted YAML.`);
1500
+ }
1501
+ }
1502
+
1503
+ /**
1504
+ * TASK_1110: Detect parallel scope overlap between a new task and existing
1505
+ * backlog/active tasks. Returns an array of overlapping task descriptors.
1506
+ *
1507
+ * A "parallel conflict" candidate is any existing task in backlog/ or
1508
+ * active/ that shares one or more normalized file paths with the new task.
1509
+ * Paths are extracted from both scope_paths and requirements/description
1510
+ * (any token that looks like a relative file path with an extension).
1511
+ */
1512
+ /**
1513
+ * TASK_1110 / TASK_1758: Detect parallel scope overlap between a new task and existing
1514
+ * backlog/active tasks.
1515
+ *
1516
+ * FIX (TASK_1758): comparison is ONLY on the actual scope_paths field values of each task,
1517
+ * never on text mined from description/requirements prose (which caused "e.g." false positives
1518
+ * observed on 2026-07-20 when TASK_1755/1756 creation reported sharing "e.g" with TASK_1752).
1519
+ *
1520
+ * Paths are normalized (backslash → forward-slash, lowercase on win32) before comparison.
1521
+ */
1522
+ async _detectParallelScopeOverlap(newTask) {
1523
+ const normalizePath = (p) => {
1524
+ if (!p) return '';
1525
+ let s = String(p).split(String.fromCharCode(92)).join('/').trim();
1526
+ if (process.platform === 'win32') s = s.toLowerCase();
1527
+ return s;
1528
+ };
1529
+
1530
+ const extractScopePaths = (task) => {
1531
+ const bag = new Set();
1532
+ for (const sp of (task.scope_paths || [])) {
1533
+ const n = normalizePath(sp);
1534
+ if (n) bag.add(n);
1535
+ }
1536
+ return bag;
1537
+ };
1538
+
1539
+ const newPaths = extractScopePaths(newTask);
1540
+ if (newPaths.size === 0) return [];
1541
+
1542
+ const targets = ['backlog', 'active'];
1543
+ const overlaps = [];
1544
+ for (const dir of targets) {
1545
+ const dirPath = require('path').join(this.tasksDir, dir);
1546
+ let entries;
1547
+ try {
1548
+ entries = await fs.readdir(dirPath, { withFileTypes: true });
1549
+ } catch (e) {
1550
+ if (e.code === 'ENOENT') continue;
1551
+ throw e;
1552
+ }
1553
+ for (const entry of entries) {
1554
+ if (!entry.isFile()) continue;
1555
+ if (!(entry.name.endsWith('.yaml') || entry.name.endsWith('.yml'))) continue;
1556
+ let other;
1557
+ try {
1558
+ other = await this._loadTaskFile(require('path').join(dirPath, entry.name));
1559
+ } catch { continue; }
1560
+ if (!other || !other.id || other.id === newTask.id) continue;
1561
+ const otherPaths = extractScopePaths(other);
1562
+ const shared = [];
1563
+ for (const p of newPaths) {
1564
+ if (otherPaths.has(p)) shared.push(p);
1565
+ }
1566
+ if (shared.length > 0) {
1567
+ overlaps.push({ task_id: other.id, status: dir, shared });
1568
+ }
1569
+ }
1570
+ }
1571
+ return overlaps;
1572
+ }
1573
+
1574
+ _requiresAcceptanceCriteria(taskData = {}) {
1575
+ const codeExtensions = new Set(['.js', '.ts', '.tsx', '.jsx', '.rs', '.py', '.go', '.rb', '.java']);
1576
+ const scopePaths = taskData.scope_paths || taskData.scope || [];
1577
+ return scopePaths.some(scopePath => codeExtensions.has(path.extname(String(scopePath)).toLowerCase()));
1578
+ }
1579
+
1580
+ async createTask(taskData, options = {}) {
1581
+ await this._ensureStatusDirs();
1582
+
1583
+ const acCount = (taskData.acceptance_criteria || []).length;
1584
+ const requiresAcceptanceCriteria = this._requiresAcceptanceCriteria(taskData);
1585
+ if (requiresAcceptanceCriteria && acCount === 0) {
1586
+ if (!options.force) {
1587
+ throw new Error('acceptance_criteria required for code-touching task');
1588
+ }
1589
+ console.warn('WARNING: overriding acceptance_criteria required for code-touching task via --force');
1590
+ }
1591
+
1592
+ // 品質閘門:檢查任務內容是否足夠豐富
1593
+ if (!options.force) {
1594
+ const quality = this.validateTaskQuality(taskData);
1595
+ if (!quality.valid) {
1596
+ const msg = [
1597
+ '❌ 任務品質不足,無法建立:',
1598
+ ...quality.warnings.map(w => ` - ${w}`),
1599
+ '',
1600
+ '提示:補齊欄位後重試,或使用 --force 強制建立'
1601
+ ].join('\n');
1602
+ throw new Error(msg);
1603
+ }
1604
+ }
1605
+
1606
+ const gitUser = this._getGitUser();
1607
+ const now = new Date().toISOString();
1608
+ const taskId = taskData.id || await this.generateTaskID();
1609
+ // Stamp id_alloc when relay fallback was used (set by generateTaskID)
1610
+ const idAllocStamp = (!taskData.id && this._lastIdAlloc === 'local_fallback')
1611
+ ? 'local_fallback' : undefined;
1612
+
1613
+ // 建立任務物件(v3.2 格式)
1614
+ const task = {
1615
+ id: taskId,
1616
+ hash: randomUUID(), // UUID v4 唯一識別
1617
+ title: taskData.title || '未命名任務',
1618
+ description: taskData.description || '',
1619
+ priority: taskData.priority || 'P2',
1620
+ status: 'pending', // 新任務預設為 pending(顯示用)
1621
+
1622
+ // 任務分類(v3.2)
1623
+ role: (() => {
1624
+ const r = taskData.role || taskData.agent_type || 'developer';
1625
+ if (r === 'developer' && !taskData.role && !taskData.agent_type) {
1626
+ console.warn('⚠️ role 未指定,自動 fallback 為 developer。建議根據 scope_paths 指定:frontend/backend/sre/qa/designer');
1627
+ }
1628
+ return r;
1629
+ })(), // 向後相容 agent_type,但警告未指定 role
1630
+ tags: toTokenArray(taskData.tags),
1631
+ complexity: taskData.complexity || 'medium',
1632
+ rigor: taskData.rigor || 'medium', // 嚴謹度:high 需要 team
1633
+ // TASK_2288: ?? not || — 0 hours is a real value
1634
+ estimated_hours: taskData.estimated_hours ?? null,
1635
+ actual_hours: taskData.actual_hours ?? null,
1636
+ branch: taskData.branch || null,
1637
+
1638
+ // 依賴關係
1639
+ dependencies: toTokenArray(taskData.dependencies),
1640
+
1641
+ // 範圍
1642
+ scope_paths: toTokenArray(taskData.scope_paths != null ? taskData.scope_paths : taskData.scope),
1643
+
1644
+ // 需求和驗收標準
1645
+ requirements: toTextArray(taskData.requirements),
1646
+ acceptance_criteria: toTextArray(taskData.acceptance_criteria),
1647
+
1648
+ // 技術筆記
1649
+ technical_notes: toTextArray(taskData.technical_notes),
1650
+
1651
+ // 人員追蹤(v3.2 簡化版)
1652
+ created_by: {
1653
+ name: gitUser.name,
1654
+ git_user: gitUser.git_user,
1655
+ timestamp: now
1656
+ },
1657
+ executed_by: null
1658
+ };
1659
+
1660
+ // 添加可選欄位
1661
+ if (taskData.source) task.source = taskData.source;
1662
+ if (taskData.feature_ids) task.feature_ids = taskData.feature_ids;
1663
+ if (taskData.related_tasks) task.related_tasks = taskData.related_tasks;
1664
+ if (taskData.keywords) task.keywords = taskData.keywords;
1665
+ if (taskData.notes) task.notes = taskData.notes;
1666
+
1667
+ // verification 欄位(optional):三層驗證規格
1668
+ // { level_1: string[], level_2: [{endpoint, method, expect}], level_3: {scenario, generates} }
1669
+ if (taskData.verification) task.verification = taskData.verification;
1670
+ if (idAllocStamp) task.id_alloc = idAllocStamp;
1671
+
1672
+ // TASK_1110: parallel scope overlap warning (non-blocking).
1673
+ // When two tasks declare overlapping scope_paths / file references in
1674
+ // requirements, running them in parallel via Formation dispatch produces
1675
+ // a parallel-conflict at merge time (e.g. TASK_1101 + TASK_1102 both
1676
+ // touching identity.yaml → merge fail). Warn the creator so they can
1677
+ // serialize or narrow scope before dispatch. This is advisory only; a
1678
+ // true fix requires worktree rebase-before-merge (tracked separately).
1679
+ try {
1680
+ const overlaps = await this._detectParallelScopeOverlap(task);
1681
+ if (overlaps.length > 0) {
1682
+ console.warn('⚠️ parallel scope overlap detected — running these tasks concurrently may cause merge conflict:');
1683
+ for (const o of overlaps) {
1684
+ console.warn(` - ${o.task_id} (${o.status}): shares ${o.shared.slice(0, 3).join(', ')}${o.shared.length > 3 ? ` +${o.shared.length - 3} more` : ''}`);
1685
+ }
1686
+ console.warn(' Consider serializing dispatch or narrowing scope_paths. (warning only, task still created)');
1687
+ }
1688
+ } catch (e) {
1689
+ // Never block task creation on overlap detection failure.
1690
+ console.warn(` (scope-overlap check skipped: ${e.message})`);
1691
+ }
1692
+
1693
+ // 寫入 backlog 目錄(檔名含 title slug + git 短名避免多 agent 撞名)
1694
+ const slug = taskData.title
1695
+ ? '_' + taskData.title.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, '_').replace(/^_|_$/g, '').slice(0, 40)
1696
+ : '';
1697
+ const fileName = `${taskId}${slug}_${gitUser.git_user}.yaml`;
1698
+ const filePath = path.join(this.tasksDir, 'backlog', fileName);
1699
+
1700
+ const yamlContent = toYAML(task);
1701
+ await fs.writeFile(filePath, yamlContent);
1702
+ await this._verifyPersisted(filePath, task); // TASK_1864: 落盤 != 意圖 → throw
1703
+
1704
+ task._file_path = filePath;
1705
+ task._status_dir = 'backlog';
1706
+
1707
+ return task;
1708
+ }
1709
+
1710
+ /**
1711
+ * 更新任務
1712
+ */
1713
+ async updateTask(taskId, updates) {
1714
+ const task = await this.getTask(taskId);
1715
+ if (!task) {
1716
+ throw new Error(`任務不存在: ${taskId}`);
1717
+ }
1718
+
1719
+ // TASK_1771: coerce array-typed fields so a raw-string update can never
1720
+ // poison a task (mirrors createTask normalization). Only touch keys the
1721
+ // caller actually provided.
1722
+ if (updates && typeof updates === "object") {
1723
+ updates = { ...updates };
1724
+ for (const k of ["dependencies", "tags", "scope_paths"]) {
1725
+ if (k in updates) updates[k] = toTokenArray(updates[k]);
1726
+ }
1727
+ for (const k of ["requirements", "acceptance_criteria", "technical_notes"]) {
1728
+ if (k in updates) updates[k] = toTextArray(updates[k]);
1729
+ }
1730
+ }
1731
+
1732
+ const gitUser = this._getGitUser();
1733
+
1734
+ // 合併更新
1735
+ const updatedTask = {
1736
+ ...task,
1737
+ ...updates,
1738
+ metadata: {
1739
+ ...task.metadata,
1740
+ updated_at: new Date().toISOString(),
1741
+ updated_by: gitUser.name
1742
+ }
1743
+ };
1744
+
1745
+ const filePath = task._file_path;
1746
+
1747
+ // 檢查是否需要移動目錄
1748
+ const oldStatus = task.status;
1749
+ const newStatus = updates.status || oldStatus;
1750
+
1751
+ if (newStatus !== oldStatus) {
1752
+ // 狀態變更,需要移動檔案 -- moveTask 需要 _file_path/_status_dir 才能定位
1753
+ // 舊檔案並判斷來源狀態,因此這條路徑上不能先移除內部屬性 (TASK_2108)
1754
+ return await this.moveTask(taskId, newStatus, updatedTask);
1755
+ }
1756
+
1757
+ // 僅更新內容:移除內部屬性後再落盤
1758
+ delete updatedTask._file_path;
1759
+ delete updatedTask._status_dir;
1760
+ const yamlContent = toYAML(updatedTask);
1761
+ await fs.writeFile(filePath, yamlContent);
1762
+ await this._verifyPersisted(filePath, updatedTask); // TASK_1864: 落盤 != 意圖 → throw
1763
+
1764
+ updatedTask._file_path = filePath;
1765
+ // TASK_1846:與 _loadTaskFile 共用祖先鏈解析。舊寫法 basename(dirname()) 對
1766
+ // 目錄型卡片(active/TASK_XXX/*.yaml)會把回傳值的 _status_dir 毒化成 'TASK_XXX'。
1767
+ // 垃圾值只污染回傳值,不落 YAML(toYAML 過濾底線鍵 + 寫檔前已 delete)。
1768
+ updatedTask._status_dir = this._resolveStatusDirName(filePath);
1769
+
1770
+ return updatedTask;
1771
+ }
1772
+
1773
+ /**
1774
+ * 移動任務到新狀態目錄(原子操作)
1775
+ */
1776
+ async moveTask(taskId, newStatus, taskData = null, options = {}) {
1777
+ await this._ensureStatusDirs();
1778
+ // TASK_2264: skipHealthAfter = the caller (completeTask) knows post-complete will measure and
1779
+ // save the 'after' snapshot itself, so scheduling a second detached measurement is pure waste.
1780
+ const { skipValidation = false, forceClaim = false, skipHealthAfter = false } = options;
1781
+
1782
+ // 取得任務資料
1783
+ let task = taskData;
1784
+ if (!task) {
1785
+ task = await this.getTask(taskId);
1786
+ if (!task) {
1787
+ throw new Error(`任務不存在: ${taskId}`);
1788
+ }
1789
+ }
1790
+
1791
+ this._assertTaskWritable(task, taskId);
1792
+ const oldFilePath = task._file_path;
1793
+ const oldStatus = task._status_dir;
1794
+
1795
+ // 轉換狀態名稱
1796
+ const targetDir = CONFIG.STATUS_MAP[newStatus] || newStatus;
1797
+ if (!CONFIG.ALL_STATUS_DIRS.includes(targetDir)) {
1798
+ throw new Error(`無效的狀態: ${newStatus}`);
1799
+ }
1800
+
1801
+ // 確保目標目錄存在
1802
+ await fs.mkdir(path.join(this.tasksDir, targetDir), { recursive: true });
1803
+
1804
+ // 狀態機驗證(可跳過)
1805
+ if (!skipValidation && oldStatus && oldStatus !== targetDir) {
1806
+ const validation = this.validateTransition(oldStatus, targetDir);
1807
+ if (!validation.valid) {
1808
+ throw new Error(validation.reason);
1809
+ }
1810
+ }
1811
+
1812
+ // M1 (TASK_1758): acquire claim marker when transitioning TO active (move active path)
1813
+ // claimTask already handles this; this covers direct moveTask(..., 'active') callers.
1814
+ if (targetDir === 'active') {
1815
+ const _prClaim = this._resolveProjectRoot();
1816
+ const claimRes = acquireClaimMarker(taskId, _prClaim, { dispatchPath: null, forceClaim });
1817
+ if (!claimRes.claimed) {
1818
+ if (claimRes.reason === 'owned') {
1819
+ const o = claimRes.owner;
1820
+ throw new Error(
1821
+ 'Task ' + taskId + ' is already claimed by session ' + o.owner_session +
1822
+ ' (pid=' + o.owner_pid + ', at ' + o.claimed_at + '). Use --force-claim to override.'
1823
+ );
1824
+ }
1825
+ throw new Error(
1826
+ 'Task ' + taskId + ' has an ORPHAN claim marker (dead pid). Resolve manually or use --force-claim.'
1827
+ );
1828
+ }
1829
+ }
1830
+
1831
+ // 更新狀態(使用舊格式)
1832
+ const updatedTask = {
1833
+ ...task,
1834
+ status: CONFIG.REVERSE_STATUS_MAP[targetDir] || targetDir
1835
+ };
1836
+
1837
+ // 移除內部屬性
1838
+ delete updatedTask._file_path;
1839
+ delete updatedTask._status_dir;
1840
+
1841
+ // 計算新檔案路徑
1842
+ // TASK_2260: preserve the card's layout under its status dir. Flat card:
1843
+ // <status>/TASK_X_user.yaml. Directory card: <status>/TASK_X/TASK_X.yaml (+ siblings).
1844
+ // Third callsite of the TASK_1843/1846 family -- uses _resolveStatusDirName's result
1845
+ // (task._status_dir) rather than assuming the file's parent is the status dir.
1846
+ const fileName = path.basename(oldFilePath);
1847
+ const relInStatus = oldStatus ? path.relative(path.join(this.tasksDir, oldStatus), oldFilePath) : fileName;
1848
+ const cardDirName = (path.dirname(relInStatus) !== '.' && !relInStatus.startsWith('..'))
1849
+ ? path.dirname(relInStatus).split(path.sep)[0] : null;
1850
+ const oldCardDir = cardDirName ? path.join(this.tasksDir, oldStatus, cardDirName) : null;
1851
+ const newCardDir = cardDirName ? path.join(this.tasksDir, targetDir, cardDirName) : null;
1852
+ const newFilePath = cardDirName
1853
+ ? path.join(this.tasksDir, targetDir, relInStatus)
1854
+ : path.join(this.tasksDir, targetDir, fileName);
1855
+ const cardDirIsNew = !!newCardDir && newCardDir !== oldCardDir && !fsSync.existsSync(newCardDir);
1856
+
1857
+ // 原子操作:先寫入新位置,確認成功後再刪除舊檔案
1858
+ const yamlContent = toYAML(updatedTask);
1859
+
1860
+ try {
1861
+ // Step 1: 寫入新位置(目錄型卡片:先把整個卡片目錄複製過去,附件才不會被孤立,再覆寫 yaml)
1862
+ if (oldCardDir && newCardDir !== oldCardDir) {
1863
+ await fs.cp(oldCardDir, newCardDir, { recursive: true });
1864
+ }
1865
+ await fs.writeFile(newFilePath, yamlContent);
1866
+
1867
+ // Step 2: 驗證新檔案寫入成功
1868
+ await fs.access(newFilePath);
1869
+
1870
+ // Step 2.5 (TASK_1864): 寫回驗證 — 必須在 Step 3 unlink 之前,
1871
+ // 失敗時舊檔仍在,狀態可回復;throw 會走下方 catch 清掉 newFilePath
1872
+ await this._verifyPersisted(newFilePath, updatedTask);
1873
+
1874
+ // Step 3: 刪除舊檔案(僅當路徑不同時)
1875
+ if (oldFilePath && oldFilePath !== newFilePath) {
1876
+ try {
1877
+ if (oldCardDir && newCardDir !== oldCardDir) await fs.rm(oldCardDir, { recursive: true, force: true });
1878
+ else await fs.unlink(oldFilePath);
1879
+ } catch (error) {
1880
+ if (error.code !== 'ENOENT') {
1881
+ // 刪除失敗但新檔案已存在,記錄警告但不拋出錯誤
1882
+ console.error(`警告: 刪除舊檔案失敗 ${oldFilePath}:`, error.message);
1883
+ }
1884
+ }
1885
+ }
1886
+
1887
+ // Step 4: 清除其他目錄的殘留副本(防止 git 操作還原舊檔案)
1888
+ for (const dir of CONFIG.ALL_STATUS_DIRS) {
1889
+ if (dir === targetDir) continue; // 跳過目標目錄
1890
+ // TASK_2260: sweep both the flat copy and a directory-layout copy (dir/TASK_X/)
1891
+ const residualPath = path.join(this.tasksDir, dir, fileName);
1892
+ try {
1893
+ if (cardDirName) {
1894
+ const residualDir = path.join(this.tasksDir, dir, cardDirName);
1895
+ if (fsSync.existsSync(residualDir)) {
1896
+ await fs.rm(residualDir, { recursive: true, force: true });
1897
+ console.error(`清除殘留: ${dir}/${cardDirName}/`);
1898
+ }
1899
+ }
1900
+ await fs.unlink(residualPath);
1901
+ console.error(`清除殘留: ${dir}/${fileName}`);
1902
+ } catch (error) {
1903
+ if (error.code !== 'ENOENT') {
1904
+ console.error(`警告: 清除殘留失敗 ${residualPath}:`, error.message);
1905
+ }
1906
+ // ENOENT = 檔案不存在,正常情況,靜默忽略
1907
+ }
1908
+ }
1909
+ } catch (error) {
1910
+ // 寫入失敗,嘗試清理
1911
+ try {
1912
+ if (cardDirIsNew) await fs.rm(newCardDir, { recursive: true, force: true });
1913
+ else await fs.unlink(newFilePath);
1914
+ } catch { /* 忽略清理錯誤 */ }
1915
+ throw new Error(`移動任務失敗: ${error.message}`);
1916
+ }
1917
+
1918
+ updatedTask._file_path = newFilePath;
1919
+ updatedTask._status_dir = targetDir;
1920
+
1921
+ // M1 (TASK_1758): release claim marker when task reaches a terminal state
1922
+ const _projectRootForClaim = this._resolveProjectRoot();
1923
+ if (['done', 'blocked', 'cancel'].includes(targetDir)) {
1924
+ releaseClaimMarker(taskId, _projectRootForClaim);
1925
+ }
1926
+
1927
+ // TASK_1861: dispatch-path-agnostic health capture. moveTask is the single choke
1928
+ // point every activation/completion path funnels through (claim → moveTask,
1929
+ // update --status → moveTask, complete → moveTask, bare move). Fire-and-forget
1930
+ // detached child; idempotent only against snapshots that ALREADY exist when the child
1931
+ // starts (it can race a concurrent writer). TASK_2264: completeTask therefore passes
1932
+ // skipHealthAfter when its own in-process postComplete will measure 'after', so that
1933
+ // path is measured exactly once; paths with no post-complete (bare move, update --status,
1934
+ // complete --skip-post-complete) keep this capture as their only one.
1935
+ if (targetDir === 'active') {
1936
+ scheduleHealthSnapshotCapture(taskId, 'before', _projectRootForClaim);
1937
+ } else if (targetDir === 'done' && !skipHealthAfter) {
1938
+ scheduleHealthSnapshotCapture(taskId, 'after', _projectRootForClaim);
1939
+ }
1940
+
1941
+ // Yggdrasil: sync status change to kenaz_tasks
1942
+ const sync = getTaskSync();
1943
+ if (sync) {
1944
+ sync.syncTaskStatus({
1945
+ taskId,
1946
+ status: CONFIG.REVERSE_STATUS_MAP[targetDir] || targetDir,
1947
+ projectPath: this.projectPath,
1948
+ }).catch(() => {});
1949
+ }
1950
+
1951
+ // TASK_2142 R2: push status to the Task Pool relay row (only when this
1952
+ // card originated from the pool -- see pool-status-push.js's
1953
+ // poolOriginOf(); silent no-op otherwise). Fire-and-forget, best-effort:
1954
+ // never awaited into a throw path, and pushPoolStatus() itself never
1955
+ // rejects, so this can never fail moveTask().
1956
+ getPoolStatusPush().pushPoolStatus({
1957
+ targetDir,
1958
+ task: updatedTask,
1959
+ fullYaml: yamlContent,
1960
+ projectPath: this.projectPath,
1961
+ }).catch(() => {});
1962
+
1963
+ return updatedTask;
1964
+ }
1965
+
1966
+ /**
1967
+ * 刪除任務
1968
+ */
1969
+ async deleteTask(taskId) {
1970
+ const task = await this.getTask(taskId);
1971
+ if (!task) {
1972
+ return false;
1973
+ }
1974
+
1975
+ await fs.unlink(task._file_path);
1976
+ return true;
1977
+ }
1978
+
1979
+ /**
1980
+ * 認領任務(移動到 active)
1981
+ */
1982
+ /**
1983
+ * 認領任務(移動到 active)
1984
+ * M1 (TASK_1758): acquires wx claim marker before moving.
1985
+ * Worker context (no current_stream.json): skip marker, behave as before.
1986
+ */
1987
+ async claimTask(taskId, assignee = null, opts = {}) {
1988
+ const currentUser = this._getGitUser();
1989
+ const now = new Date().toISOString();
1990
+
1991
+ const task = await this.getTask(taskId);
1992
+ if (!task) {
1993
+ throw new Error('任務不存在: ' + taskId);
1994
+ }
1995
+
1996
+ // M1: acquire claim marker (wx atomic — no TOCTOU)
1997
+ const projectRoot = this._resolveProjectRoot();
1998
+ const claimResult = acquireClaimMarker(taskId, projectRoot, {
1999
+ dispatchPath: task._file_path,
2000
+ forceClaim: opts.forceClaim,
2001
+ });
2002
+ if (!claimResult.claimed) {
2003
+ if (claimResult.reason === 'owned') {
2004
+ const o = claimResult.owner;
2005
+ throw new Error(
2006
+ 'Task ' + taskId + ' is already claimed by session ' + o.owner_session +
2007
+ ' (pid=' + o.owner_pid + ', at ' + o.claimed_at + '). ' +
2008
+ 'Use --force-claim to override an orphan (dead-pid) marker.'
2009
+ );
2010
+ }
2011
+ // orphan — warning already printed; refuse
2012
+ throw new Error(
2013
+ 'Task ' + taskId + ' has an ORPHAN claim marker (dead pid). ' +
2014
+ 'Resolve manually or use --force-claim to override.'
2015
+ );
2016
+ }
2017
+
2018
+ // M3 (TASK_1760): theme advisory — non-blocking one-liner when another live stream's
2019
+ // declared theme matches this task's scope_paths better than own stream's theme.
2020
+ try {
2021
+ const _slPath2 = require('path').join(projectRoot, '.specialist', 'tools', 'stream-lease.js');
2022
+ if (require('fs').existsSync(_slPath2)) {
2023
+ const _sl2 = require(_slPath2);
2024
+ const _allLeases2 = _sl2.readAllLeases();
2025
+ const _ownLease2 = _sl2.readLease(_claimContext(projectRoot).streamId);
2026
+ const _ownTheme2 = _ownLease2 ? (_ownLease2.theme || '') : '';
2027
+ const _taskScope = Array.isArray(task.scope_paths) ? task.scope_paths.join(' ') : '';
2028
+ if (_taskScope) {
2029
+ const _candidates = _allLeases2.filter(function(l) {
2030
+ return l.session_id !== (_ownLease2 && _ownLease2.session_id) &&
2031
+ _sl2.isSessionLive(l) && l.theme && l.theme.length > 0;
2032
+ });
2033
+ const _ownScore = _ownTheme2 ? (_taskScope.toLowerCase().includes(_ownTheme2.toLowerCase()) ? 1 : 0) : 0;
2034
+ for (const _cl of _candidates) {
2035
+ const _score = _taskScope.toLowerCase().includes(_cl.theme.toLowerCase()) ? 1 : 0;
2036
+ if (_score > _ownScore) {
2037
+ console.warn('[theme-advisory] ' + (_cl.label || _cl.session_id.slice(0,8)) +
2038
+ ' (theme=' + _cl.theme + ') may be a better fit for ' + taskId +
2039
+ '; proceeding with claim anyway.');
2040
+ break;
2041
+ }
2042
+ }
2043
+ }
2044
+ }
2045
+ } catch (e) { /* advisory best-effort — never block claim */ }
2046
+ // 更新分配資訊
2047
+ const updatedTask = {
2048
+ ...task,
2049
+ assigned_to: {
2050
+ name: assignee || currentUser.name,
2051
+ git_user: assignee || currentUser.git_user,
2052
+ timestamp: now
2053
+ },
2054
+ metadata: {
2055
+ ...task.metadata,
2056
+ claimed_at: now,
2057
+ claimed_by: currentUser.name
2058
+ }
2059
+ };
2060
+
2061
+ const result = await this.moveTask(taskId, 'active', updatedTask);
2062
+
2063
+ // Yggdrasil: sync claimed task to kenaz_tasks
2064
+ const sync = getTaskSync();
2065
+ if (sync) {
2066
+ sync.syncTaskStatus({
2067
+ taskId,
2068
+ status: 'active',
2069
+ claimedBy: currentUser.name,
2070
+ priority: task.priority,
2071
+ scopePaths: task.scope_paths,
2072
+ title: task.title,
2073
+ dependencies: task.dependencies,
2074
+ projectPath: this.projectPath,
2075
+ }).catch(() => {});
2076
+ }
2077
+
2078
+ return result;
2079
+ }
2080
+
2081
+ /**
2082
+ * 完成任務(移動到 done)
2083
+ * @param {string} taskId - 任務 ID
2084
+ * @param {Object} options - 可選參數
2085
+ * @param {number} options.actual_hours - 實際花費時間(小時)
2086
+ * @param {boolean} options.skipPostComplete - 跳過 post-complete 流程(預設 false)
2087
+ */
2088
+ async completeTask(taskId, options = {}) {
2089
+ const gitUser = this._getGitUser();
2090
+ const now = new Date().toISOString();
2091
+
2092
+ const task = await this.getTask(taskId);
2093
+ if (!task) {
2094
+ throw new Error(`任務不存在: ${taskId}`);
2095
+ }
2096
+
2097
+ // TASK_2233: backlog → done is not an allowed transition; say what to do instead
2098
+ if (task.status === 'backlog' || task.status === 'pending' || task._status_dir === 'backlog') {
2099
+ throw new Error(`無法從 backlog 直接完成 ${taskId}: move it to active first (node task-api.js move ${taskId} active), then complete.`);
2100
+ }
2101
+
2102
+ const updatedTask = {
2103
+ ...task,
2104
+ executed_by: {
2105
+ name: gitUser.name,
2106
+ git_user: gitUser.git_user,
2107
+ timestamp: now
2108
+ }
2109
+ };
2110
+
2111
+ // 設置實際工時(如果提供)
2112
+ if (options.actual_hours !== undefined) {
2113
+ updatedTask.actual_hours = options.actual_hours;
2114
+ }
2115
+
2116
+ // TASK_2264: decide up front whether post-complete (below) will measure 'after' in-process.
2117
+ // TASK_2288 single-winner rule: whenever a post-complete run is expected for this completion,
2118
+ // post-complete's own measurement is the ONE 'after' capture (it runs after merge and feeds the
2119
+ // verdict), so moveTask must not also spawn one. Expected = in-process (below) OR the caller
2120
+ // (headless.js / scheduler, signalled by FORMATION_HEADLESS / FORMATION_SCHEDULER) runs it itself.
2121
+ // moveTask's detached capture stays the sole one only when nobody measures: --skip-post-complete.
2122
+ const postCompleteRuns = !options.skipPostComplete;
2123
+ const result = await this.moveTask(taskId, 'done', updatedTask, { skipHealthAfter: postCompleteRuns });
2124
+
2125
+ try {
2126
+ this._autoPopThoughtsForCompletedTask(taskId, now);
2127
+ } catch (error) {
2128
+ console.error(`Warning: thought stack cleanup failed for ${taskId}: ${error.message}`);
2129
+ }
2130
+
2131
+ // Yggdrasil: sync completed task to kenaz_tasks
2132
+ const sync = getTaskSync();
2133
+ if (sync) {
2134
+ sync.syncTaskStatus({
2135
+ taskId,
2136
+ status: 'done',
2137
+ claimedBy: null,
2138
+ projectPath: this.projectPath,
2139
+ }).catch(() => {});
2140
+ }
2141
+
2142
+ // 自動執行 post-complete(除非明確跳過)
2143
+ // - FORMATION_SCHEDULER: 由 scheduler 統一管理,避免重複執行
2144
+ // - FORMATION_HEADLESS: 由 headless.js onDone 統一執行,避免並行 agent git 衝突
2145
+ if (postCompleteRuns) {
2146
+ try {
2147
+ const { postComplete } = require('../../.formation/tools/post-complete.js');
2148
+ console.log(`\n🔗 自動執行 post-complete for ${taskId}...\n`);
2149
+ await postComplete({
2150
+ taskId,
2151
+ keep: options.keep || 10,
2152
+ skipArchive: options.skipArchive || false,
2153
+ skipGit: options.skipGit || false,
2154
+ dryRun: options.dryRun || false,
2155
+ message: options.message,
2156
+ projectPath: this.projectPath,
2157
+ });
2158
+ } catch (error) {
2159
+ console.error(`⚠️ post-complete 失敗: ${error.message}`);
2160
+ console.error(' 任務已完成,但 post-complete 需要手動執行');
2161
+ }
2162
+ }
2163
+
2164
+ return result;
2165
+ }
2166
+
2167
+ /**
2168
+ * 查詢任務
2169
+ */
2170
+ async queryTasks(query = {}) {
2171
+ const queryDir = query.status ? (CONFIG.STATUS_MAP[query.status] || query.status) : null;
2172
+ const includeArchived = query.includeArchived || queryDir === 'archived';
2173
+ const allData = await this.getAllTasks({ includeArchived });
2174
+ let tasks = allData.tasks;
2175
+
2176
+ if (query.status) {
2177
+ // Allow both old status names ('completed') and new dir names ('done', 'active', etc.)
2178
+ const targetDir = CONFIG.STATUS_MAP[query.status] || query.status;
2179
+ tasks = tasks.filter(t => t.status === query.status || t._status_dir === targetDir);
2180
+ }
2181
+
2182
+ // Exclude done tasks where merge_landed=false (quarantine unverified completions)
2183
+ // Pass includeUnmerged:true to opt-in to the full set
2184
+ if (!query.includeUnmerged) {
2185
+ tasks = tasks.filter(t => t._status_dir !== 'done' || t.merge_landed !== false);
2186
+ }
2187
+
2188
+ if (query.priority) {
2189
+ tasks = tasks.filter(t => t.priority === query.priority);
2190
+ }
2191
+
2192
+ // 支持 role(新)和 agent_type(舊,向後相容)
2193
+ if (query.role || query.agent_type) {
2194
+ const targetRole = query.role || query.agent_type;
2195
+ tasks = tasks.filter(t => (t.role || t.agent_type) === targetRole);
2196
+ }
2197
+
2198
+ // 支持 rigor 過濾
2199
+ if (query.rigor) {
2200
+ tasks = tasks.filter(t => t.rigor === query.rigor);
2201
+ }
2202
+
2203
+ // 支持 tags 過濾(任一匹配)
2204
+ if (query.tags && query.tags.length > 0) {
2205
+ const targetTags = Array.isArray(query.tags) ? query.tags : [query.tags];
2206
+ tasks = tasks.filter(t => {
2207
+ const taskTags = t.tags || [];
2208
+ return targetTags.some(tag => taskTags.includes(tag));
2209
+ });
2210
+ }
2211
+
2212
+ // 文字搜尋
2213
+ if (query.search) {
2214
+ const searchLower = query.search.toLowerCase();
2215
+ tasks = tasks.filter(t => {
2216
+ const title = (t.title || '').toLowerCase();
2217
+ const desc = (t.description || '').toLowerCase();
2218
+ const id = (t.id || '').toLowerCase();
2219
+ const tags = (t.tags || []).join(' ').toLowerCase();
2220
+ return title.includes(searchLower) || desc.includes(searchLower) || id.includes(searchLower) || tags.includes(searchLower);
2221
+ });
2222
+ }
2223
+
2224
+ return tasks;
2225
+ }
2226
+
2227
+ /**
2228
+ * TASK_895: Thalamus Fire → Task Priority Auto-Boost
2229
+ *
2230
+ * Cross-reference fired symbols with backlog task scope_paths.
2231
+ * When a symbol fires 3+ times and its file_path matches a task's scope_path
2232
+ * (prefix match), boost that task's priority by one level.
2233
+ *
2234
+ * @param {Array<{symbolId: string, fireCount: number}>} firedSymbols
2235
+ * @returns {{ boosted: Array<{taskId: string, from: string, to: string}>, skipped: number }}
2236
+ */
2237
+ async boostByFiredSymbols(firedSymbols) {
2238
+ const PRIORITY_ORDER = ['P3', 'P2', 'P1', 'P0'];
2239
+ const results = { boosted: [], skipped: 0 };
2240
+
2241
+ if (!firedSymbols || firedSymbols.length === 0) return results;
2242
+
2243
+ // Filter to symbols with fire_count >= 2 (lowered from 3 — 2 fires in quick
2244
+ // succession indicates sustained attention, worth boosting related tasks)
2245
+ const eligible = firedSymbols.filter(s => s.fireCount >= 2);
2246
+ if (eligible.length === 0) return results;
2247
+
2248
+ // Extract file_paths from symbol_ids (format: "file::name" → "file")
2249
+ const firedFilePaths = eligible.map(s => {
2250
+ const parts = s.symbolId.split('::');
2251
+ return parts.length > 1 ? parts[0] : s.symbolId;
2252
+ });
2253
+
2254
+ // List backlog tasks
2255
+ const backlogTasks = await this.queryTasks({ status: 'pending' });
2256
+
2257
+ for (const task of backlogTasks) {
2258
+ // Skip tasks already boosted by thalamus
2259
+ const tags = task.tags || [];
2260
+ if (tags.includes('thalamus-boosted')) {
2261
+ results.skipped++;
2262
+ continue;
2263
+ }
2264
+
2265
+ const scopePaths = task.scope_paths || [];
2266
+ if (scopePaths.length === 0) continue;
2267
+
2268
+ // Check if any fired file_path matches any task scope_path (prefix match)
2269
+ const hasMatch = firedFilePaths.some(filePath =>
2270
+ scopePaths.some(scopePath => {
2271
+ // Normalize separators for cross-platform matching
2272
+ const normFile = filePath.replace(/\\/g, '/');
2273
+ const normScope = scopePath.replace(/\\/g, '/');
2274
+ return normFile.startsWith(normScope) || normScope.startsWith(normFile);
2275
+ })
2276
+ );
2277
+
2278
+ if (!hasMatch) continue;
2279
+
2280
+ // Compute boosted priority
2281
+ const currentPriority = task.priority || 'P2';
2282
+ const currentIndex = PRIORITY_ORDER.indexOf(currentPriority);
2283
+ if (currentIndex < 0 || currentIndex === PRIORITY_ORDER.length - 1) {
2284
+ // Already P0 or unknown priority — skip
2285
+ results.skipped++;
2286
+ continue;
2287
+ }
2288
+
2289
+ const newPriority = PRIORITY_ORDER[currentIndex + 1]; // P3→P2, P2→P1, P1→P0
2290
+
2291
+ // Update task: boost priority + add thalamus-boosted tag
2292
+ const updatedTags = [...tags, 'thalamus-boosted'];
2293
+ await this.updateTask(task.id, {
2294
+ priority: newPriority,
2295
+ tags: updatedTags,
2296
+ });
2297
+
2298
+ results.boosted.push({
2299
+ taskId: task.id,
2300
+ from: currentPriority,
2301
+ to: newPriority,
2302
+ });
2303
+ }
2304
+
2305
+ return results;
2306
+ }
2307
+
2308
+ /**
2309
+ * Reload: 確保目錄結構存在 + 遷移舊系統任務到新系統
2310
+ *
2311
+ * 1. 建立所有 status 目錄
2312
+ * 2. 掃描舊系統 dev/pipeline/ 的任務
2313
+ * 3. 將尚未存在於新系統的任務遷移過來
2314
+ *
2315
+ * @returns {{ created_dirs: string[], migrated: number, skipped: number, errors: string[] }}
2316
+ */
2317
+ async reload() {
2318
+ const stats = { created_dirs: [], migrated: 0, skipped: 0, errors: [] };
2319
+ const fsSync = require('fs');
2320
+
2321
+ // 1. 確保所有目錄存在
2322
+ for (const dir of CONFIG.ALL_STATUS_DIRS) {
2323
+ const dirPath = path.join(this.tasksDir, dir);
2324
+ if (!fsSync.existsSync(dirPath)) {
2325
+ await fs.mkdir(dirPath, { recursive: true });
2326
+ stats.created_dirs.push(dir);
2327
+ }
2328
+ }
2329
+
2330
+ // 2. 收集新系統中已有的 task ID
2331
+ const existingIds = new Set();
2332
+ for (const dir of CONFIG.ALL_STATUS_DIRS) {
2333
+ const dirPath = path.join(this.tasksDir, dir);
2334
+ try {
2335
+ const entries = await fs.readdir(dirPath);
2336
+ for (const entry of entries) {
2337
+ if (entry.startsWith('TASK_') && entry.endsWith('.yaml')) {
2338
+ const id = entry.split('_').slice(0, 2).join('_');
2339
+ existingIds.add(id);
2340
+ }
2341
+ }
2342
+ } catch { /* dir doesn't exist yet */ }
2343
+ }
2344
+
2345
+ // 3. 掃描舊系統
2346
+ const projectRoot = this._resolveProjectRoot();
2347
+ const pipelineDir = path.join(projectRoot, 'dev', 'pipeline');
2348
+
2349
+ // 3a. dev/pipeline/tasks/*.yaml → backlog/
2350
+ const legacyTasksDir = path.join(pipelineDir, 'tasks');
2351
+ if (fsSync.existsSync(legacyTasksDir)) {
2352
+ try {
2353
+ const files = await fs.readdir(legacyTasksDir);
2354
+ for (const file of files) {
2355
+ if (!file.startsWith('TASK_') || !file.endsWith('.yaml')) continue;
2356
+ const id = file.split('_').slice(0, 2).join('_');
2357
+ if (existingIds.has(id)) {
2358
+ stats.skipped++;
2359
+ continue;
2360
+ }
2361
+ try {
2362
+ const src = path.join(legacyTasksDir, file);
2363
+ const dst = path.join(this.tasksDir, 'backlog', file);
2364
+ await fs.copyFile(src, dst);
2365
+ existingIds.add(id);
2366
+ stats.migrated++;
2367
+ } catch (e) {
2368
+ stats.errors.push(`${file}: ${e.message}`);
2369
+ }
2370
+ }
2371
+ } catch { /* ignore */ }
2372
+ }
2373
+
2374
+ // 3b. dev/pipeline/completed/*.yaml → done/
2375
+ const legacyCompletedDir = path.join(pipelineDir, 'completed');
2376
+ if (fsSync.existsSync(legacyCompletedDir)) {
2377
+ try {
2378
+ const files = await fs.readdir(legacyCompletedDir);
2379
+ for (const file of files) {
2380
+ if (!file.startsWith('TASK_') || !file.endsWith('.yaml')) continue;
2381
+ const id = file.split('_').slice(0, 2).join('_');
2382
+ if (existingIds.has(id)) {
2383
+ stats.skipped++;
2384
+ continue;
2385
+ }
2386
+ try {
2387
+ const src = path.join(legacyCompletedDir, file);
2388
+ const dst = path.join(this.tasksDir, 'done', file);
2389
+ await fs.copyFile(src, dst);
2390
+ existingIds.add(id);
2391
+ stats.migrated++;
2392
+ } catch (e) {
2393
+ stats.errors.push(`${file}: ${e.message}`);
2394
+ }
2395
+ }
2396
+ } catch { /* ignore */ }
2397
+ }
2398
+
2399
+ // 3c. TASK_QUEUE.json — 將 JSON 條目轉為 YAML 檔案
2400
+ const queueFile = path.join(pipelineDir, 'TASK_QUEUE.json');
2401
+ if (fsSync.existsSync(queueFile)) {
2402
+ try {
2403
+ const content = await fs.readFile(queueFile, 'utf8');
2404
+ const cleanContent = content.charCodeAt(0) === 0xFEFF ? content.slice(1) : content;
2405
+ const queue = JSON.parse(cleanContent);
2406
+ if (Array.isArray(queue.tasks)) {
2407
+ for (const task of queue.tasks) {
2408
+ if (!task.id || existingIds.has(task.id)) {
2409
+ stats.skipped++;
2410
+ continue;
2411
+ }
2412
+ try {
2413
+ const status = (task.status || '').toLowerCase();
2414
+ const isCompleted = status === 'completed' || status === 'done';
2415
+ const targetDir = isCompleted ? 'done' : 'backlog';
2416
+ const gitUser = task.assigned_agent || 'system';
2417
+ const fileName = `${task.id}_${gitUser}.yaml`;
2418
+ const dst = path.join(this.tasksDir, targetDir, fileName);
2419
+ const yamlContent = toYAML(task);
2420
+ await fs.writeFile(dst, yamlContent);
2421
+ existingIds.add(task.id);
2422
+ stats.migrated++;
2423
+ } catch (e) {
2424
+ stats.errors.push(`${task.id}: ${e.message}`);
2425
+ }
2426
+ }
2427
+ }
2428
+ } catch { /* ignore */ }
2429
+ }
2430
+
2431
+ // 4. 子專案 .collaboration/tasks/ → .kenaz/tasks/ 遷移
2432
+ // 當 tasksDir 是 .kenaz/tasks/ 時,檢查同專案是否有 .collaboration/tasks/
2433
+ const collabTasksDir = path.join(projectRoot, '.collaboration', 'tasks');
2434
+ if (this.tasksDir.includes('.kenaz') && fsSync.existsSync(collabTasksDir) && collabTasksDir !== this.tasksDir) {
2435
+ // 狀態目錄映射:.collaboration/ dir name → .kenaz/ dir name(相同)
2436
+ for (const statusDir of CONFIG.ALL_STATUS_DIRS) {
2437
+ const srcDir = path.join(collabTasksDir, statusDir);
2438
+ if (!fsSync.existsSync(srcDir)) continue;
2439
+ try {
2440
+ const files = await fs.readdir(srcDir);
2441
+ for (const file of files) {
2442
+ if (!file.startsWith('TASK_') || !file.endsWith('.yaml')) continue;
2443
+ const id = file.split('_').slice(0, 2).join('_');
2444
+ if (existingIds.has(id)) {
2445
+ stats.skipped++;
2446
+ continue;
2447
+ }
2448
+ try {
2449
+ const src = path.join(srcDir, file);
2450
+ const dst = path.join(this.tasksDir, statusDir, file);
2451
+ await fs.copyFile(src, dst);
2452
+ existingIds.add(id);
2453
+ stats.migrated++;
2454
+ } catch (e) {
2455
+ stats.errors.push(`${file}: ${e.message}`);
2456
+ }
2457
+ }
2458
+ } catch { /* ignore */ }
2459
+ }
2460
+ }
2461
+
2462
+ return stats;
2463
+ }
2464
+
2465
+ /**
2466
+ * 取得統計資訊
2467
+ */
2468
+ async getStatistics(options = {}) {
2469
+ const allData = await this.getAllTasks({ includeArchived: options.includeArchived || false });
2470
+ const tasks = allData.tasks;
2471
+
2472
+ // 按狀態統計
2473
+ const byStatus = {};
2474
+ for (const task of tasks) {
2475
+ const status = task.status || 'unknown';
2476
+ byStatus[status] = (byStatus[status] || 0) + 1;
2477
+ }
2478
+
2479
+ // 按優先級統計
2480
+ const byPriority = {};
2481
+ for (const task of tasks) {
2482
+ const priority = task.priority || 'unknown';
2483
+ byPriority[priority] = (byPriority[priority] || 0) + 1;
2484
+ }
2485
+
2486
+ // 按 role 統計(向後相容 agent_type)
2487
+ const byRole = {};
2488
+ for (const task of tasks) {
2489
+ const role = task.role || task.agent_type || 'unknown';
2490
+ byRole[role] = (byRole[role] || 0) + 1;
2491
+ }
2492
+
2493
+ // 按 tags 統計
2494
+ const byTags = {};
2495
+ for (const task of tasks) {
2496
+ const tags = task.tags || [];
2497
+ for (const tag of tags) {
2498
+ byTags[tag] = (byTags[tag] || 0) + 1;
2499
+ }
2500
+ }
2501
+
2502
+ return {
2503
+ total: tasks.length,
2504
+ byStatus,
2505
+ byPriority,
2506
+ byRole,
2507
+ byTags,
2508
+ timestamp: new Date().toISOString()
2509
+ };
2510
+ }
2511
+ }
2512
+
2513
+ // ============================================================================
2514
+ // TASK_1992/TASK_2104 — Alaya process-lessons echo (best-effort, create-only)
2515
+ // ============================================================================
2516
+ // Mirrors prompt.js's appendAlayaLessons: same query, same alaya_recall
2517
+ // shape, and (TASK_2104) the same TASK_2101 cosine-floor + stack-match
2518
+ // gate — loaded from the dependency-free .formation/tools/alaya-gate.js
2519
+ // module (require() resolved relative to __dirname; GEN-008) instead of
2520
+ // the hand-copied query builder that used to live here with no gate at all.
2521
+ // Never affects create's exit code: any failure is swallowed silently.
2522
+ // Opt-out: KENAZ_NO_ALAYA_ECHO=1.
2523
+
2524
+ // Best-effort: null (not a throw) when unavailable — e.g. a sub-project
2525
+ // with no .formation/ checkout — so the caller prints nothing rather than
2526
+ // falling back to ungated results.
2527
+ function loadAlayaGate() {
2528
+ try {
2529
+ return require('../../.formation/tools/alaya-gate');
2530
+ } catch {
2531
+ return null;
2532
+ }
2533
+ }
2534
+
2535
+ // fetchImpl/gateImpl: injectable test seams; default to the real fetch and loadAlayaGate().
2536
+ async function echoAlayaLessons(taskData, fetchImpl, gateImpl) {
2537
+ if (process.env.KENAZ_NO_ALAYA_ECHO === '1') return;
2538
+
2539
+ const doFetch = fetchImpl || (typeof fetch !== 'undefined' ? fetch : null);
2540
+ if (!doFetch) return;
2541
+
2542
+ const gate = gateImpl !== undefined ? gateImpl : loadAlayaGate();
2543
+ if (!gate) return; // shared gate unavailable — never print ungated results
2544
+
2545
+ const query = gate.buildTaskContextQuery(taskData);
2546
+ if (!query) return;
2547
+
2548
+ const taskStack = gate.deriveTaskStack((taskData && taskData.scope_paths) || []);
2549
+
2550
+ const controller = new AbortController();
2551
+ const timeoutId = setTimeout(() => controller.abort(), 3000);
2552
+ try {
2553
+ const payload = JSON.stringify({
2554
+ jsonrpc: '2.0',
2555
+ id: 1,
2556
+ method: 'tools/call',
2557
+ params: {
2558
+ name: 'alaya_recall',
2559
+ arguments: {
2560
+ query, mode: 'hybrid', seed_types: ['failure_seed', 'lesson_seed'],
2561
+ limit: gate.ALAYA_CANDIDATE_POOL, min_cosine: gate.ALAYA_MIN_COSINE_DEFAULT,
2562
+ },
2563
+ },
2564
+ });
2565
+ const res = await doFetch('http://127.0.0.1:18080/api/mcp', {
2566
+ method: 'POST',
2567
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json, text/event-stream' },
2568
+ body: payload,
2569
+ signal: controller.signal,
2570
+ });
2571
+ if (!res || !res.ok) return;
2572
+ const result = await res.json();
2573
+ if (!result || result.error) return;
2574
+
2575
+ const content = result.result && result.result.content;
2576
+ if (!content || !Array.isArray(content)) return;
2577
+ const textPart = content.find(c => c.type === 'text');
2578
+ if (!textPart || !textPart.text) return;
2579
+
2580
+ let recallData;
2581
+ try { recallData = JSON.parse(textPart.text); } catch { return; }
2582
+ if (!recallData) return;
2583
+
2584
+ let bijas = recallData.bijas || [];
2585
+ if (bijas.length === 0) return;
2586
+
2587
+ bijas = gate.dedupBijas(bijas);
2588
+ // TASK_2104: same relevance gate as prompt.js's appendAlayaLessons —
2589
+ // the server already applied min_cosine, but re-checking (+ the
2590
+ // stack-match half, which the server does not apply) keeps this
2591
+ // correct against a stub/older server and drops stack mismatches.
2592
+ bijas = bijas.filter(b => gate.passesAlayaRelevanceGate(b, taskStack, gate.ALAYA_MIN_COSINE_DEFAULT)).slice(0, 3);
2593
+ if (bijas.length === 0) return;
2594
+
2595
+ console.log(' Alaya lessons for this card:');
2596
+ for (const b of bijas) {
2597
+ const preview = (b.content || '').replace(/\s+/g, ' ').trim().slice(0, 160);
2598
+ console.log(` - [${b.seed_type || 'seed'}/${b.origin || 'unknown'}] ${preview}`);
2599
+ }
2600
+ } catch {
2601
+ // Best-effort: MCP down, timeout, network error, malformed response — silent.
2602
+ } finally {
2603
+ clearTimeout(timeoutId);
2604
+ }
2605
+ }
2606
+
2607
+ // TASK_2257: estimated_hours is a finite number >= 0 or null. '' (flag present, empty value)
2608
+ // CLEARS the field (null) -- same "clearable" contract as --description/--notes. Anything
2609
+ // non-numeric or negative exits 1 instead of persisting NaN / a nonsense value.
2610
+ function parseEstimatedHours(raw, source) {
2611
+ if (raw === null || raw === undefined) return undefined;
2612
+ const text = String(raw).trim();
2613
+ if (text === '') return null;
2614
+ const n = Number(text);
2615
+ if (!Number.isFinite(n) || n < 0) {
2616
+ console.error(`❌ ${source}: estimated hours must be a number >= 0 (got "${text}"); pass an empty value to clear.`);
2617
+ process.exit(1);
2618
+ }
2619
+ return n;
2620
+ }
2621
+
2622
+ // ============================================================================
2623
+ // CLI 入口
2624
+ // ============================================================================
2625
+
2626
+ if (require.main === module) {
2627
+ const args = process.argv.slice(2);
2628
+
2629
+ // Extract --project-path before creating API instance
2630
+ // Supports both: --project-path PATH and --project-path=PATH
2631
+ let cliProjectPath = null;
2632
+ const projectPathIndex = args.indexOf('--project-path');
2633
+ if (projectPathIndex >= 0 && args[projectPathIndex + 1]) {
2634
+ cliProjectPath = args[projectPathIndex + 1];
2635
+ args.splice(projectPathIndex, 2);
2636
+ } else {
2637
+ const eqIndex = args.findIndex(a => a.startsWith('--project-path='));
2638
+ if (eqIndex >= 0) {
2639
+ cliProjectPath = args[eqIndex].split('=').slice(1).join('=');
2640
+ args.splice(eqIndex, 1);
2641
+ }
2642
+ }
2643
+
2644
+ // TASK_2221: Extract --tasks-dir the same way. "Explicit tasksDir always
2645
+ // wins" in resolveTasksDir() -- this lets a caller that already resolved
2646
+ // the directory itself (e.g. inbox.rs's mother self-heal, which checks
2647
+ // orphans against a tasks_dir BEFORE calling create) force this process
2648
+ // to write into that exact same directory instead of letting this
2649
+ // process independently re-derive one from --project-path, which is a
2650
+ // SEPARATE resolution that can land somewhere else (see inbox.rs's
2651
+ // resolve_tasks_dir doc comment for the 2026-10-02T16:18Z incident this
2652
+ // closes).
2653
+ let cliTasksDir = null;
2654
+ const tasksDirIndex = args.indexOf('--tasks-dir');
2655
+ if (tasksDirIndex >= 0 && args[tasksDirIndex + 1]) {
2656
+ cliTasksDir = args[tasksDirIndex + 1];
2657
+ args.splice(tasksDirIndex, 2);
2658
+ } else {
2659
+ const eqIndex = args.findIndex(a => a.startsWith('--tasks-dir='));
2660
+ if (eqIndex >= 0) {
2661
+ cliTasksDir = args[eqIndex].split('=').slice(1).join('=');
2662
+ args.splice(eqIndex, 1);
2663
+ }
2664
+ }
2665
+
2666
+ const apiConfig = {};
2667
+ if (cliProjectPath) apiConfig.projectPath = cliProjectPath;
2668
+ if (cliTasksDir) apiConfig.tasksDir = cliTasksDir;
2669
+ const api = new TaskAPI(apiConfig);
2670
+
2671
+ (async () => {
2672
+ try {
2673
+ if (args.includes('--help') || args.includes('-h') || args.length === 0) {
2674
+ console.log(`
2675
+ 任務 API v3.5
2676
+
2677
+ 用法:
2678
+ node task-api.js [--project-path PATH] <command> [options]
2679
+
2680
+ 全域選項:
2681
+ --project-path PATH 指定專案根目錄(自動偵測 .kenaz/tasks 或 .collaboration/tasks)
2682
+ --tasks-dir PATH 直接指定 tasks 目錄(優先於 --project-path 的自動偵測;TASK_2221)
2683
+
2684
+ Commands:
2685
+ node task-api.js list [--status STATUS] [--priority P] [--role ROLE] [--rigor RIGOR] [--tags TAG] [--format json|table] [--include-unmerged] [--no-cancel]
2686
+ node task-api.js get TASK_ID
2687
+ node task-api.js create --title TITLE [options]
2688
+ node task-api.js update TASK_ID [--title T] [--description D] [--priority P] [--role R] [--technical-notes N]...
2689
+ 陣列欄位(tags/dependencies/scope-paths/requirements/acceptance-criteria/technical-notes 等)
2690
+ 預設為整組取代(會回顯被移除的既有項目);加 --append 改為追加到既有項目之後(與既有重複的項目會略過並回顯)
2691
+ node task-api.js claim TASK_ID
2692
+ node task-api.js complete TASK_ID [--actual-hours HOURS]
2693
+ node task-api.js move TASK_ID STATUS [--force]
2694
+ node task-api.js delete TASK_ID (僅限 backlog 狀態)
2695
+ node task-api.js search KEYWORD
2696
+ node task-api.js stats [--format json|table]
2697
+ node task-api.js reload
2698
+ node task-api.js archive [--keep N] [--dry-run]
2699
+ node task-api.js reset-all [--from active,done] [--dry-run]
2700
+ node task-api.js migrate-merge-landed (one-shot: annotate unmerged tasks in done/)
2701
+
2702
+ 角色 (role):
2703
+ designer, developer, frontend, pm, sre, team_developer, team_strategy
2704
+
2705
+ 標籤 (tags):
2706
+ doc, code, plan, fix, refactor, test, design, review
2707
+
2708
+ 嚴謹度 (rigor):
2709
+ high - 需要 team 執行(多 Agent 協作)
2710
+ medium - 可選 team 或單一 Agent
2711
+ low - 單一 Agent 即可
2712
+
2713
+ 狀態:
2714
+ backlog (pending) - 待處理
2715
+ active (in_progress) - 進行中
2716
+ done (completed) - 已完成
2717
+ blocked - 阻塞中
2718
+ cancel (cancelled) - 已取消
2719
+
2720
+ 狀態轉換規則:
2721
+ backlog → active, cancel, blocked
2722
+ active → done, blocked, cancel, backlog
2723
+ blocked → active, backlog, cancel
2724
+ done → backlog(重新開啟)
2725
+ cancel → backlog(重新開啟)
2726
+
2727
+ Create 選項:
2728
+ --title TEXT 標題(--args-file 未提供 title 時必填;若兩者皆有,CLI 旗標優先,並會印出警告)
2729
+ --description TEXT 描述
2730
+ --priority P0|P1|P2|P3 優先級(預設 P2)
2731
+ --role ROLE 角色(預設 developer)
2732
+ --rigor high|medium|low 嚴謹度(預設 medium)
2733
+ --complexity simple|medium|complex 複雜度(預設 medium)
2734
+ --tags TAG1,TAG2 標籤(逗號分隔)
2735
+ --dependencies TASK_X,TASK_Y 依賴任務(逗號分隔)
2736
+ --scope-paths PATH1,PATH2 修改範圍(逗號分隔)
2737
+ --requirements "R1" --requirements "R2" 需求(每條重複旗標,最少 3 條)
2738
+ --acceptance-criteria "A1" --acceptance-criteria "A2" 驗收標準(每條重複旗標,最少 3 條)
2739
+ --technical-notes "T1" --technical-notes "T2" 技術備註(每條重複旗標,最少 2 條)
2740
+ --args-file PATH UTF-8 JSON 物件;支援 camelCase / snake_case,CLI 旗標優先
2741
+ requirements / acceptance_criteria / technical_notes 使用 JSON 陣列
2742
+ 一般品質門檻:requirements >= 3,acceptance_criteria >= 3,technical_notes >= 2
2743
+ --estimated-hours N 預估工時(小時)
2744
+ --branch NAME Git 分支
2745
+ --notes TEXT 備註
2746
+ --feature-ids F1,F2 Feature ID(逗號分隔)
2747
+ --keywords K1,K2 關鍵字(逗號分隔)
2748
+ --source NAME 來源標記(寫入 task.source;--origin 為同義旗標,TASK_2147 producer 用)
2749
+ --verification JSON 三層驗證規格(JSON 字串)
2750
+ 格式: {"level_1":["cmd1","cmd2"],"level_2":[{"endpoint":"/health","method":"GET","expect":200}],"level_3":{"scenario":"描述","generates":"playwright/spec.ts"}}
2751
+ level_1: commands array(CLI 驗證指令)
2752
+ level_2: API probe specs({endpoint, method, expect})
2753
+ level_3: Playwright scenario({scenario, generates})
2754
+
2755
+ 多專案範例:
2756
+ node task-api.js --project-path /path/to/subproject list
2757
+ node task-api.js --project-path /path/to/subproject get TASK_1
2758
+ node task-api.js --project-path /path/to/subproject create --title "New feature"
2759
+
2760
+ 範例:
2761
+ node task-api.js list
2762
+ node task-api.js list --status pending --role developer --rigor high --tags code
2763
+ node task-api.js list --tags doc --format json
2764
+ node task-api.js get TASK_171
2765
+ node task-api.js create --title "實作登入功能" --priority P1 --role frontend --rigor high --tags code,doc
2766
+ node task-api.js create --title "系統文檔" --priority P1 --scope-paths ".system/" --dependencies TASK_192,TASK_193 --estimated-hours 0.5
2767
+ node task-api.js claim TASK_171
2768
+ node task-api.js complete TASK_171 --actual-hours 3.5
2769
+ node task-api.js move TASK_171 active
2770
+ node task-api.js search "登入"
2771
+ node task-api.js stats
2772
+ node task-api.js stats --format json
2773
+ `);
2774
+ process.exit(0);
2775
+ }
2776
+
2777
+ const command = args[0];
2778
+
2779
+ if (command === 'worktrees') {
2780
+ // TASK_2297: list worktrees with reclaim eligibility; --reclaim removes eligible ones (manual path).
2781
+ const wr = require(path.join(__dirname, '..', '..', '.formation', 'tools', 'worktree-reclaim.js'));
2782
+ const wtRepo = path.resolve(cliProjectPath || process.cwd());
2783
+ if (args.includes('--reclaim')) {
2784
+ const r = wr.reclaimAllEligible(wtRepo);
2785
+ console.log(`reclaimed ${r.reclaimed.length} worktree(s)`);
2786
+ } else {
2787
+ console.log(wr.formatReport(wr.evaluateWorktrees(wtRepo)));
2788
+ }
2789
+ } else if (command === 'list') {
2790
+ const query = {};
2791
+ const statusIndex = args.indexOf('--status');
2792
+ const priorityIndex = args.indexOf('--priority');
2793
+ const roleIndex = args.indexOf('--role');
2794
+ const rigorIndex = args.indexOf('--rigor');
2795
+ const tagsIndex = args.indexOf('--tags');
2796
+ const formatIndex = args.indexOf('--format');
2797
+
2798
+ if (statusIndex >= 0) query.status = args[statusIndex + 1];
2799
+ if (priorityIndex >= 0) query.priority = args[priorityIndex + 1];
2800
+ if (roleIndex >= 0) query.role = args[roleIndex + 1];
2801
+ if (rigorIndex >= 0) query.rigor = args[rigorIndex + 1];
2802
+ if (tagsIndex >= 0) query.tags = args[tagsIndex + 1].split(',').map(t => t.trim());
2803
+
2804
+ const format = formatIndex >= 0 ? args[formatIndex + 1] : 'table';
2805
+ const showAll = args.includes('--all');
2806
+ const includeArchived = args.includes('--include-archived');
2807
+ const includeUnmerged = args.includes('--include-unmerged');
2808
+ // --no-cancel: omit the per-task list under cancel/ (count still shown in summary).
2809
+ // Cancel rows are mostly historical noise — Phase 0 boot uses this to save context tokens.
2810
+ const skipCancel = args.includes('--no-cancel');
2811
+ const hasStatusFilter = !!query.status;
2812
+
2813
+ // Parse --limit=N or --limit N
2814
+ let recentLimit = RECENT_DEFAULT;
2815
+ for (let i = 0; i < args.length; i++) {
2816
+ if (args[i].startsWith('--limit=')) {
2817
+ recentLimit = parseInt(args[i].split('=')[1], 10) || RECENT_DEFAULT;
2818
+ break;
2819
+ } else if (args[i] === '--limit' && i + 1 < args.length) {
2820
+ recentLimit = parseInt(args[i + 1], 10) || RECENT_DEFAULT;
2821
+ break;
2822
+ }
2823
+ }
2824
+
2825
+ const LARGE_DIRS = includeArchived ? ['done', 'cancel', 'archived'] : ['done', 'cancel'];
2826
+ const SMALL_DIRS = ['active', 'backlog', 'blocked'];
2827
+ // Fast path: no --all, no --status, no other filters — large dirs only load recent N entries
2828
+ const useFastPath = !showAll && !hasStatusFilter &&
2829
+ !query.priority && !query.role && !query.rigor && !query.tags;
2830
+
2831
+ if (useFastPath) {
2832
+ // --- FAST PATH ---
2833
+ // Count files per dir with readdir only (no YAML parsing for stats)
2834
+ const dirCounts = {};
2835
+ for (const dir of [...SMALL_DIRS, ...LARGE_DIRS]) {
2836
+ const dirPath = path.join(api.tasksDir, dir);
2837
+ try {
2838
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
2839
+ dirCounts[dir] = entries.filter(e => e.name.startsWith('TASK_')).length;
2840
+ } catch (e) {
2841
+ dirCounts[dir] = 0;
2842
+ }
2843
+ }
2844
+ const totalCount = Object.values(dirCounts).reduce((a, b) => a + b, 0);
2845
+ if (totalCount === 0) emitEmptyListDiagnostics(api.tasksDir); // TASK_1778
2846
+
2847
+ // Load tasks: full _scanDir for small dirs, fast path for large dirs
2848
+ const grouped = {};
2849
+
2850
+ for (const dir of SMALL_DIRS) {
2851
+ grouped[dir] = await api._scanDir(path.join(api.tasksDir, dir));
2852
+ }
2853
+
2854
+ for (const dir of LARGE_DIRS) {
2855
+ const dirPath = path.join(api.tasksDir, dir);
2856
+ try {
2857
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
2858
+ const taskEntries = entries.filter(e => e.name.startsWith('TASK_'));
2859
+ // Sort by TASK_NNNN number descending (most recent first)
2860
+ taskEntries.sort((a, b) => {
2861
+ const na = parseInt((a.name.match(/^TASK_(\d+)/) || [])[1] || '0', 10);
2862
+ const nb = parseInt((b.name.match(/^TASK_(\d+)/) || [])[1] || '0', 10);
2863
+ return nb - na;
2864
+ });
2865
+ const limited = taskEntries.slice(0, recentLimit);
2866
+ const tasks = [];
2867
+ for (const entry of limited) {
2868
+ try {
2869
+ let task = null;
2870
+ if (entry.isFile() && (entry.name.endsWith('.yaml') || entry.name.endsWith('.yml'))) {
2871
+ task = await api._loadTaskFile(path.join(dirPath, entry.name));
2872
+ } else if (entry.isDirectory()) {
2873
+ const subEntries = await fs.readdir(path.join(dirPath, entry.name), { withFileTypes: true });
2874
+ for (const sub of subEntries) {
2875
+ if (sub.isFile() && (sub.name.endsWith('.yaml') || sub.name.endsWith('.yml'))) {
2876
+ task = await api._loadTaskFile(path.join(dirPath, entry.name, sub.name));
2877
+ if (task) break;
2878
+ }
2879
+ }
2880
+ }
2881
+ if (task) tasks.push(task);
2882
+ } catch (e) {
2883
+ // skip corrupted entries
2884
+ }
2885
+ }
2886
+ // Filter unmerged done tasks unless --include-unmerged
2887
+ grouped[dir] = (!includeUnmerged && dir === 'done')
2888
+ ? tasks.filter(t => t.merge_landed !== false)
2889
+ : tasks;
2890
+ } catch (e) {
2891
+ grouped[dir] = [];
2892
+ }
2893
+ }
2894
+
2895
+ if (format === 'json') {
2896
+ const allTasks = [...SMALL_DIRS, ...LARGE_DIRS].flatMap(d => grouped[d] || []);
2897
+ console.log(JSON.stringify({
2898
+ tasks: allTasks,
2899
+ statistics: { total: totalCount, byDir: dirCounts, recent_limit: recentLimit }
2900
+ }, null, 2));
2901
+ } else {
2902
+ // Table output (fast path)
2903
+ const statusOrder = ['active', 'backlog', 'blocked', 'done', 'cancel', ...(includeArchived ? ['archived'] : [])];
2904
+ const statusLabels = {
2905
+ 'active': 'active', 'backlog': 'backlog', 'blocked': 'blocked',
2906
+ 'done': 'done', 'cancel': 'cancel', 'archived': 'archived'
2907
+ };
2908
+
2909
+ const statusSummary = statusOrder
2910
+ .map(s => `${statusLabels[s]}: ${dirCounts[s] || 0}`)
2911
+ .join(' | ');
2912
+ console.log(`任務統計 (${totalCount} total)`);
2913
+ console.log(` ${statusSummary}`);
2914
+
2915
+ const formatTask = (t) => {
2916
+ const role = t.role || t.agent_type || '-';
2917
+ const rigor = t.rigor || '-';
2918
+ const tags = (t.tags || []).join(',') || '-';
2919
+ const verif = t.verification ? ' [V]' : '';
2920
+ const unmerged = t.merge_landed === false ? ' [UNMERGED]' : '';
2921
+ return ` [${t.id}] ${t.title} ${t.priority} ${role} rigor:${rigor} ${tags}${verif}${unmerged}`;
2922
+ };
2923
+
2924
+ for (const status of statusOrder) {
2925
+ const group = grouped[status] || [];
2926
+ const totalInDir = dirCounts[status] || 0;
2927
+ if (totalInDir === 0 && !SMALL_DIRS.includes(status)) continue;
2928
+
2929
+ // --no-cancel: count already shown in summary line; skip the section body
2930
+ if (skipCancel && status === 'cancel') continue;
2931
+
2932
+ console.log(`\n--- ${statusLabels[status]} (${totalInDir}) ---`);
2933
+
2934
+ if (totalInDir === 0) {
2935
+ console.log(' (none)');
2936
+ continue;
2937
+ }
2938
+
2939
+ // Large dirs: show loaded tasks; if fewer than total, add "N more" hint
2940
+ // N = totalInDir - loaded (computed from readdir count, not group.length)
2941
+ if (LARGE_DIRS.includes(status) && group.length < totalInDir) {
2942
+ group.forEach(t => console.log(formatTask(t)));
2943
+ const remaining = totalInDir - group.length;
2944
+ console.log(` ... (${remaining} more, use --status=${status} or --all to show all)`);
2945
+ } else {
2946
+ group.forEach(t => console.log(formatTask(t)));
2947
+ }
2948
+ }
2949
+ }
2950
+
2951
+ } else {
2952
+ // --- SLOW PATH (--all, --status=XXX, or has other query filters) ---
2953
+ const showArchived = includeArchived || query.status === 'archived';
2954
+ const tasks = await api.queryTasks({ ...query, includeArchived: showArchived, includeUnmerged });
2955
+ const stats = await api.getStatistics({ includeArchived: showArchived });
2956
+ if (stats.total === 0) emitEmptyListDiagnostics(api.tasksDir); // TASK_1778
2957
+
2958
+ if (format === 'json') {
2959
+ console.log(JSON.stringify({ tasks, statistics: { ...stats, recent_limit: null } }, null, 2));
2960
+ } else {
2961
+ // 表格格式輸出(預設)— 按狀態分組
2962
+ const DONE_PREVIEW = 5;
2963
+ const CANCEL_PREVIEW = 3;
2964
+
2965
+ const statusOrder = ['active', 'backlog', 'blocked', 'done', 'cancel', ...(showArchived ? ['archived'] : [])];
2966
+ const statusLabels = {
2967
+ 'active': 'active',
2968
+ 'backlog': 'backlog',
2969
+ 'blocked': 'blocked',
2970
+ 'done': 'done',
2971
+ 'cancel': 'cancel',
2972
+ 'archived': 'archived'
2973
+ };
2974
+ // 用目錄名統計(從全局 stats 轉換,不受篩選影響)
2975
+ const byDir = {};
2976
+ for (const [oldStatus, count] of Object.entries(stats.byStatus)) {
2977
+ const dir = CONFIG.STATUS_MAP[oldStatus] || oldStatus;
2978
+ byDir[dir] = (byDir[dir] || 0) + count;
2979
+ }
2980
+ const statusSummary = statusOrder
2981
+ .map(s => `${statusLabels[s]}: ${byDir[s] || 0}`)
2982
+ .join(' | ');
2983
+ console.log(`任務統計 (${stats.total} total)`);
2984
+ console.log(` ${statusSummary}`);
2985
+
2986
+ // 按狀態分組
2987
+ const grouped = {};
2988
+ for (const t of tasks) {
2989
+ const dir = CONFIG.STATUS_MAP[t.status] || t.status;
2990
+ if (!grouped[dir]) grouped[dir] = [];
2991
+ grouped[dir].push(t);
2992
+ }
2993
+
2994
+ // 格式化單筆任務
2995
+ const formatTask = (t) => {
2996
+ const role = t.role || t.agent_type || '-';
2997
+ const rigor = t.rigor || '-';
2998
+ const tags = (t.tags || []).join(',') || '-';
2999
+ const verif = t.verification ? ' [V]' : '';
3000
+ const unmerged = t.merge_landed === false ? ' [UNMERGED]' : '';
3001
+ return ` [${t.id}] ${t.title} ${t.priority} ${role} rigor:${rigor} ${tags}${verif}${unmerged}`;
3002
+ };
3003
+
3004
+ // 顯示順序:active → backlog → blocked → done → cancel → archived
3005
+ for (const status of statusOrder) {
3006
+ const group = grouped[status] || [];
3007
+ if (group.length === 0 && !['active', 'backlog', 'blocked'].includes(status)) continue;
3008
+
3009
+ // --no-cancel: count already shown in summary line; skip the section body
3010
+ if (skipCancel && status === 'cancel') continue;
3011
+
3012
+ console.log(`\n--- ${statusLabels[status]} (${group.length}) ---`);
3013
+
3014
+ if (group.length === 0) {
3015
+ console.log(' (none)');
3016
+ continue;
3017
+ }
3018
+
3019
+ const isLargeGroup = ['done', 'cancel', ...(showArchived ? ['archived'] : [])].includes(status);
3020
+ const limit = status === 'cancel' ? CANCEL_PREVIEW : DONE_PREVIEW;
3021
+
3022
+ if (isLargeGroup && !showAll && !hasStatusFilter && group.length > limit) {
3023
+ // 只顯示最近 N 筆
3024
+ const recent = group.slice(-limit);
3025
+ recent.forEach(t => console.log(formatTask(t)));
3026
+ const remaining = group.length - limit;
3027
+ console.log(` ... (${remaining} more, use --status=${status} or --all to show all)`);
3028
+ } else {
3029
+ group.forEach(t => console.log(formatTask(t)));
3030
+ }
3031
+ }
3032
+ }
3033
+ }
3034
+
3035
+ } else if (command === 'get') {
3036
+ const taskId = args[1];
3037
+ if (!taskId) {
3038
+ console.error('錯誤: 缺少任務 ID');
3039
+ process.exit(1);
3040
+ }
3041
+ const task = await api.getTask(taskId);
3042
+ if (task) {
3043
+ console.log(JSON.stringify(task, null, 2));
3044
+ } else {
3045
+ console.error(`任務不存在: ${taskId}`);
3046
+ process.exit(1);
3047
+ }
3048
+
3049
+ } else if (command === 'create') {
3050
+ // TASK_1599: Windows CreateProcess caps command line at 32767 UTF-16 chars.
3051
+ // Detailed task cards (long description + many requirements) easily exceed this
3052
+ // limit when passed as argv. --args-file <path> provides a JSON file transport
3053
+ // that bypasses the limit entirely. Node reads the UTF-8 file directly --
3054
+ // no codepage sensitivity (known Windows mojibake issue for emoji/CJK on argv).
3055
+
3056
+ const getArg = (flag) => {
3057
+ for (let i = 0; i < args.length; i++) {
3058
+ if (args[i] === flag && args[i + 1]) return args[i + 1];
3059
+ if (args[i].startsWith(flag + '=')) return args[i].slice(flag.length + 1);
3060
+ }
3061
+ return null;
3062
+ };
3063
+ const getCsv = (flag) => {
3064
+ const val = getArg(flag);
3065
+ return val ? val.split(',').map(s => s.trim()).filter(Boolean) : [];
3066
+ };
3067
+ const getMulti = (flag) => {
3068
+ const results = [];
3069
+ for (let i = 0; i < args.length; i++) {
3070
+ if (args[i] === flag && args[i + 1] && !args[i + 1].startsWith('--')) {
3071
+ results.push(args[i + 1]);
3072
+ } else if (args[i].startsWith(flag + '=')) {
3073
+ results.push(args[i].slice(flag.length + 1));
3074
+ }
3075
+ }
3076
+ return results;
3077
+ };
3078
+
3079
+ // --args-file: load task fields from a UTF-8 JSON file (bypasses argv 32K limit).
3080
+ // Explicit CLI flags override args-file values. Supports camelCase and snake_case keys.
3081
+ let fileData = {};
3082
+ const argsFilePath = getArg('--args-file');
3083
+ if (argsFilePath) {
3084
+ const fsSync = require('fs');
3085
+ let raw;
3086
+ try { raw = fsSync.readFileSync(argsFilePath, 'utf8'); }
3087
+ catch (e) { console.error('❌ --args-file: cannot read file "' + argsFilePath + '": ' + e.message); process.exit(1); }
3088
+ try { fileData = JSON.parse(raw); }
3089
+ catch (e) { console.error('❌ --args-file: invalid JSON in "' + argsFilePath + '": ' + e.message); process.exit(1); }
3090
+ if (!fileData || typeof fileData !== 'object' || Array.isArray(fileData)) {
3091
+ console.error('❌ --args-file: expected a JSON object, got ' + (Array.isArray(fileData) ? 'array' : typeof fileData));
3092
+ process.exit(1);
3093
+ }
3094
+ }
3095
+
3096
+ const fromFile = (camel, snake) => {
3097
+ if (camel in fileData) return fileData[camel];
3098
+ if (snake && snake in fileData) return fileData[snake];
3099
+ return undefined;
3100
+ };
3101
+
3102
+ // TASK_2031: --title is documented as required only when --args-file supplies
3103
+ // no title. Warn loudly when both are present and differ (CLI still wins, per
3104
+ // documented precedence), and when neither is present (falls back to placeholder).
3105
+ const cliTitle = getArg('--title');
3106
+ const fileTitle = fromFile('title');
3107
+ if (cliTitle && fileTitle && cliTitle !== fileTitle) {
3108
+ console.error('⚠️ --title "' + cliTitle + '" overrides --args-file title "' + fileTitle + '" -- CLI flag wins.');
3109
+ } else if (!cliTitle && !fileTitle) {
3110
+ console.error('⚠️ no --title and no title in --args-file -- falling back to placeholder "未命名任务".');
3111
+ }
3112
+
3113
+ const taskData = {
3114
+ title: cliTitle || fileTitle || '未命名任务',
3115
+ description: getArg('--description') || fromFile('description') || '',
3116
+ priority: getArg('--priority') || fromFile('priority') || 'P2',
3117
+ role: getArg('--role') || fromFile('role') || 'developer',
3118
+ rigor: getArg('--rigor') || fromFile('rigor') || 'medium',
3119
+ complexity: getArg('--complexity') || fromFile('complexity') || 'medium',
3120
+ tags: (() => { const cli = getCsv('--tags'); return cli.length > 0 ? cli : (fromFile('tags') || []); })(),
3121
+ branch: getArg('--branch') || fromFile('branch') || null,
3122
+ dependencies: (() => {
3123
+ const cli = [...getCsv('--dependencies'), ...getCsv('--deps')];
3124
+ return cli.length > 0 ? cli : (fromFile('dependencies') || []);
3125
+ })(),
3126
+ scope_paths: (() => {
3127
+ const cli = getMulti('--scope-paths').flatMap(v => v.split(',').map(s => s.trim()).filter(Boolean));
3128
+ if (cli.length > 0) return cli;
3129
+ const fv = fromFile('scopePaths', 'scope_paths');
3130
+ return Array.isArray(fv) ? fv : (fv ? String(fv).split(',').map(s => s.trim()).filter(Boolean) : []);
3131
+ })(),
3132
+ estimated_hours: (() => {
3133
+ const cli = getArg('--estimated-hours');
3134
+ if (cli !== null) return parseEstimatedHours(cli, '--estimated-hours');
3135
+ const fv = fromFile('estimatedHours', 'estimated_hours');
3136
+ return fv != null ? parseEstimatedHours(fv, '--args-file estimatedHours') : null;
3137
+ })(),
3138
+ notes: getArg('--notes') || fromFile('notes') || '',
3139
+ feature_ids: (() => { const cli = getCsv('--feature-ids'); return cli.length > 0 ? cli : (fromFile('featureIds', 'feature_ids') || []); })(),
3140
+ keywords: (() => { const cli = getCsv('--keywords'); return cli.length > 0 ? cli : (fromFile('keywords') || []); })(),
3141
+ // TASK_2147: origin tag for tasks created by a non-interactive producer
3142
+ // (e.g. k-cli task submit on behalf of a sub-project process). --origin
3143
+ // is the documented flag name; it maps onto the pre-existing `source`
3144
+ // field that createTask() already promotes onto the task when truthy.
3145
+ source: getArg('--source') || getArg('--origin') || fromFile('source') || fromFile('origin') || null,
3146
+ };
3147
+
3148
+ const reqs = getMulti('--requirements');
3149
+ if (reqs.length > 0) { taskData.requirements = reqs; }
3150
+ else { const fv = fromFile('requirements'); if (Array.isArray(fv) && fv.length > 0) taskData.requirements = fv; }
3151
+
3152
+ const ac = getMulti('--acceptance-criteria');
3153
+ if (ac.length > 0) { taskData.acceptance_criteria = ac; }
3154
+ else { const fv = fromFile('acceptanceCriteria', 'acceptance_criteria'); if (Array.isArray(fv) && fv.length > 0) taskData.acceptance_criteria = fv; }
3155
+
3156
+ const tn = getMulti('--technical-notes');
3157
+ if (tn.length > 0) { taskData.technical_notes = tn; }
3158
+ else { const fv = fromFile('technicalNotes', 'technical_notes'); if (Array.isArray(fv) && fv.length > 0) taskData.technical_notes = fv; }
3159
+
3160
+ const verificationStr = getArg('--verification');
3161
+ if (verificationStr) {
3162
+ try { taskData.verification = JSON.parse(verificationStr); }
3163
+ catch (e) { console.warn('⚠️ --verification JSON 解析失敗,已忽略: ' + e.message); }
3164
+ } else if (fileData.verification) {
3165
+ taskData.verification = fileData.verification;
3166
+ }
3167
+
3168
+ const force = args.includes('--force') || fileData.force === true;
3169
+ const task = await api.createTask(taskData, { force });
3170
+ console.log(`✅ 任務已建立: ${task.id}`);
3171
+ console.log(`Hash: ${task.hash}`);
3172
+ console.log(`標題: ${task.title}`);
3173
+ console.log(`優先級: ${task.priority}`);
3174
+ console.log(`角色: ${task.role}`);
3175
+ console.log(`嚴謹度: ${task.rigor}`);
3176
+ console.log(`複雜度: ${task.complexity}`);
3177
+ if (task.tags.length > 0) console.log(`標籤: ${task.tags.join(', ')}`);
3178
+ if (task.dependencies && task.dependencies.length > 0) console.log(`依賴: ${task.dependencies.join(', ')}`);
3179
+ if (task.scope_paths && task.scope_paths.length > 0) console.log(`範圍: ${task.scope_paths.join(', ')}`);
3180
+ if (task.estimated_hours != null) console.log(`預估工時: ${task.estimated_hours}h`);
3181
+ if (task.branch) console.log(`分支: ${task.branch}`);
3182
+
3183
+ await echoAlayaLessons(taskData);
3184
+
3185
+ } else if (command === 'claim') {
3186
+ const taskId = args[1];
3187
+ if (!taskId) {
3188
+ console.error('錯誤: 缺少任務 ID');
3189
+ process.exit(1);
3190
+ }
3191
+ const forceClaim = args.includes('--force-claim');
3192
+ const task = await api.claimTask(taskId, null, { forceClaim });
3193
+ console.log(`✅ 任務已認領: ${task.id}`);
3194
+
3195
+ } else if (command === 'complete') {
3196
+ const taskId = args[1];
3197
+ if (!taskId) {
3198
+ console.error('錯誤: 缺少任務 ID');
3199
+ process.exit(1);
3200
+ }
3201
+ const actualHoursIndex = args.indexOf('--actual-hours');
3202
+ const options = {};
3203
+ if (actualHoursIndex >= 0) {
3204
+ // TASK_2288: same validation as --estimated-hours (NaN / negative / missing value exit 1
3205
+ // before any write). A missing value is an error here, not a 'clear'.
3206
+ const rawHours = args[actualHoursIndex + 1];
3207
+ const parsedHours = (rawHours === undefined || String(rawHours).startsWith('--'))
3208
+ ? null : parseEstimatedHours(rawHours, '--actual-hours');
3209
+ if (parsedHours === null) {
3210
+ console.error('❌ --actual-hours: requires a number >= 0');
3211
+ process.exit(1);
3212
+ }
3213
+ options.actual_hours = parsedHours;
3214
+ }
3215
+ if (args.includes('--skip-post-complete')) {
3216
+ options.skipPostComplete = true;
3217
+ }
3218
+ const task = await api.completeTask(taskId, options);
3219
+ console.log(`✅ 任務已完成: ${task.id}`);
3220
+ if (task.actual_hours != null) console.log(`實際工時: ${task.actual_hours} 小時`);
3221
+
3222
+ } else if (command === 'move') {
3223
+ const taskId = args[1];
3224
+ const newStatus = args[2];
3225
+ const force = args.includes('--force');
3226
+
3227
+ if (!taskId || !newStatus) {
3228
+ console.error('錯誤: 缺少任務 ID 或目標狀態');
3229
+ process.exit(1);
3230
+ }
3231
+
3232
+ const forceClaim = args.includes('--force-claim');
3233
+ const task = await api.moveTask(taskId, newStatus, null, { skipValidation: force, forceClaim });
3234
+ console.log(`✅ 任務已移動: ${task.id} → ${newStatus}`);
3235
+
3236
+ } else if (command === 'delete') {
3237
+ const taskId = args[1];
3238
+ if (!taskId) {
3239
+ console.error('錯誤: 缺少任務 ID');
3240
+ process.exit(1);
3241
+ }
3242
+ const task = await api.getTask(taskId);
3243
+ if (!task) {
3244
+ console.error(`任務不存在: ${taskId}`);
3245
+ process.exit(1);
3246
+ }
3247
+ if (task.status !== 'backlog' && task.status !== 'pending') {
3248
+ console.error(`❌ 僅允許刪除 backlog 狀態的任務。目前狀態: ${task.status}`);
3249
+ process.exit(1);
3250
+ }
3251
+ const deleted = await api.deleteTask(taskId);
3252
+ if (deleted) {
3253
+ console.log(`✅ 任務已刪除: ${taskId}`);
3254
+ } else {
3255
+ console.error(`❌ 刪除失敗: ${taskId}`);
3256
+ process.exit(1);
3257
+ }
3258
+
3259
+ } else if (command === 'search') {
3260
+ const keyword = args[1];
3261
+ if (!keyword) {
3262
+ console.error('錯誤: 缺少搜尋關鍵字');
3263
+ process.exit(1);
3264
+ }
3265
+ const tasks = await api.queryTasks({ search: keyword });
3266
+ console.log(`找到 ${tasks.length} 個符合 "${keyword}" 的任務:\n`);
3267
+ tasks.forEach(t => {
3268
+ console.log(` [${t.id}] ${t.title} (${t.status}, ${t.priority})`);
3269
+ });
3270
+
3271
+ } else if (command === 'stats') {
3272
+ const formatIndex = args.indexOf('--format');
3273
+ const format = formatIndex >= 0 ? args[formatIndex + 1] : 'table';
3274
+
3275
+ const stats = await api.getStatistics();
3276
+
3277
+ if (format === 'json') {
3278
+ // JSON 格式輸出
3279
+ console.log(JSON.stringify(stats, null, 2));
3280
+ } else {
3281
+ // 表格格式輸出(預設)
3282
+ console.log('任務統計:\n');
3283
+ console.log('按狀態:');
3284
+ for (const [status, count] of Object.entries(stats.byStatus)) {
3285
+ console.log(` ${status}: ${count}`);
3286
+ }
3287
+ console.log('\n按優先級:');
3288
+ for (const [priority, count] of Object.entries(stats.byPriority)) {
3289
+ console.log(` ${priority}: ${count}`);
3290
+ }
3291
+ console.log('\n按角色:');
3292
+ for (const [role, count] of Object.entries(stats.byRole)) {
3293
+ console.log(` ${role}: ${count}`);
3294
+ }
3295
+ if (Object.keys(stats.byTags).length > 0) {
3296
+ console.log('\n按標籤:');
3297
+ for (const [tag, count] of Object.entries(stats.byTags)) {
3298
+ console.log(` ${tag}: ${count}`);
3299
+ }
3300
+ }
3301
+ console.log(`\n總計: ${stats.total} 個任務`);
3302
+ }
3303
+
3304
+ } else if (command === 'reload') {
3305
+ const stats = await api.reload();
3306
+ console.log(`✅ Reload 完成`);
3307
+ console.log(` 建立目錄: ${stats.created_dirs.length > 0 ? stats.created_dirs.join(', ') : '(已存在)'}`);
3308
+ console.log(` 遷移任務: ${stats.migrated}`);
3309
+ console.log(` 跳過(已存在): ${stats.skipped}`);
3310
+ if (stats.errors.length > 0) {
3311
+ console.log(` 錯誤: ${stats.errors.length}`);
3312
+ stats.errors.forEach(e => console.log(` - ${e}`));
3313
+ }
3314
+
3315
+ } else if (command === 'archive') {
3316
+ // 封存 done 中最舊的任務,保留最新 N 個
3317
+ const keepIndex = args.indexOf('--keep');
3318
+ const keep = keepIndex >= 0 ? parseInt(args[keepIndex + 1], 10) : 10;
3319
+ const dryRun = args.includes('--dry-run');
3320
+
3321
+ const doneDir = path.join(api.tasksDir, 'done');
3322
+ const archivedDir = path.join(api.tasksDir, 'archived');
3323
+
3324
+ if (!dryRun) {
3325
+ await fs.mkdir(archivedDir, { recursive: true });
3326
+ }
3327
+
3328
+ let files = [];
3329
+ try {
3330
+ const entries = await fs.readdir(doneDir);
3331
+ files = entries
3332
+ .filter(f => f.startsWith('TASK_') && f.endsWith('.yaml'))
3333
+ .sort((a, b) => {
3334
+ const numA = parseInt(a.match(/TASK_(\d+)/)?.[1] || '0', 10);
3335
+ const numB = parseInt(b.match(/TASK_(\d+)/)?.[1] || '0', 10);
3336
+ return numA - numB;
3337
+ });
3338
+ } catch (e) {
3339
+ if (e.code !== 'ENOENT') throw e;
3340
+ }
3341
+
3342
+ if (files.length <= keep) {
3343
+ console.log(`✅ Done 任務數 ${files.length}/${keep} — 不需要封存`);
3344
+ } else {
3345
+ const toArchive = files.slice(0, files.length - keep);
3346
+ console.log(`📦 封存 ${toArchive.length} 個任務(保留最新 ${keep} 個):\n`);
3347
+
3348
+ for (const file of toArchive) {
3349
+ const src = path.join(doneDir, file);
3350
+ const dst = path.join(archivedDir, file);
3351
+ if (dryRun) {
3352
+ console.log(` [DRY RUN] ${file} → archived/`);
3353
+ } else {
3354
+ await fs.copyFile(src, dst);
3355
+ await fs.unlink(src);
3356
+ console.log(` ✅ ${file} → archived/`);
3357
+ }
3358
+ }
3359
+ }
3360
+
3361
+ } else if (command === 'reset-all') {
3362
+ // 將 active/ 和 done/ 的任務全部重置回 backlog/
3363
+ const fromIndex = args.indexOf('--from');
3364
+ const fromStatuses = fromIndex >= 0
3365
+ ? args[fromIndex + 1].split(',').map(s => s.trim())
3366
+ : ['active', 'done'];
3367
+ const dryRun = args.includes('--dry-run');
3368
+
3369
+ const statusDirMap = {
3370
+ active: 'active',
3371
+ done: 'done',
3372
+ blocked: 'blocked',
3373
+ cancel: 'cancel',
3374
+ };
3375
+
3376
+ let totalMoved = 0;
3377
+ for (const status of fromStatuses) {
3378
+ const dirName = statusDirMap[status];
3379
+ if (!dirName) {
3380
+ console.error(`⚠️ 未知狀態: ${status}(跳過)`);
3381
+ continue;
3382
+ }
3383
+ const dir = path.join(api.tasksDir, dirName);
3384
+ let files = [];
3385
+ try {
3386
+ const entries = await fs.readdir(dir);
3387
+ files = entries.filter(f => f.startsWith('TASK_') && f.endsWith('.yaml'));
3388
+ } catch (e) {
3389
+ if (e.code !== 'ENOENT') throw e;
3390
+ }
3391
+
3392
+ if (files.length === 0) continue;
3393
+
3394
+ console.log(`\n📂 ${status}/ → backlog/ (${files.length} 個任務):`);
3395
+ for (const file of files) {
3396
+ const taskId = file.split('_').slice(0, 2).join('_');
3397
+ if (dryRun) {
3398
+ console.log(` [DRY RUN] ${taskId}`);
3399
+ } else {
3400
+ try {
3401
+ await api.moveTask(taskId, 'backlog', null, { skipValidation: true });
3402
+ console.log(` ✅ ${taskId}`);
3403
+ } catch (e) {
3404
+ console.error(` ❌ ${taskId}: ${e.message}`);
3405
+ }
3406
+ }
3407
+ totalMoved++;
3408
+ }
3409
+ }
3410
+
3411
+ if (totalMoved === 0) {
3412
+ console.log('✅ 沒有需要重置的任務');
3413
+ } else {
3414
+ console.log(`\n${dryRun ? '[DRY RUN] ' : ''}共重置 ${totalMoved} 個任務 → backlog/`);
3415
+ }
3416
+
3417
+ } else if (command === 'update') {
3418
+ const taskId = args[1];
3419
+ if (!taskId) {
3420
+ console.error('❌ 用法: task-api.js update TASK_ID [--field value]...');
3421
+ process.exit(1);
3422
+ }
3423
+
3424
+ // TASK_1599: Windows CreateProcess caps command line at 32767 UTF-16 chars.
3425
+ // Detailed task cards easily exceed this limit when passed as argv.
3426
+ // --args-file <path> provides a JSON file transport bypassing the 32K limit.
3427
+ // Node reads UTF-8 file directly -- no codepage sensitivity for emoji/CJK.
3428
+
3429
+ // TASK_1832: the whole update path used truthiness to decide "was this flag
3430
+ // passed?", which collapses "--flag omitted" and "--flag ''" into the same
3431
+ // falsy answer. The consequence was that NO field could ever be cleared:
3432
+ // `update --dependencies ""` reported success and silently changed nothing.
3433
+ // The three readers below now report absence as null and an explicitly empty
3434
+ // value as an empty string/array, so the gates at "Build updates object" can
3435
+ // tell the two apart. Note the create-path helper of the same shape (~line
3436
+ // 2616) deliberately keeps its own semantics (returns [] for absent) -- this
3437
+ // one is named getCsvOrNull so the two can no longer be confused.
3438
+ const getArg = (flag) => {
3439
+ for (let i = 0; i < args.length; i++) {
3440
+ if (args[i] === flag && args[i + 1] !== undefined) return args[i + 1];
3441
+ if (args[i].startsWith(flag + '=')) return args[i].slice(flag.length + 1);
3442
+ }
3443
+ return null;
3444
+ };
3445
+ // null = flag absent | [] = flag present with an empty value (clear the field)
3446
+ // TASK_1869: collect EVERY occurrence of the flag, not just the first.
3447
+ // `--scope-paths a --scope-paths b` used to keep only [a] and drop b with
3448
+ // zero warning, while `--requirements r1 --requirements r2` (getMulti)
3449
+ // kept both -- two incompatible repeated-flag semantics in one command.
3450
+ // Repeated flags now concatenate, matching getMulti and the create path;
3451
+ // commas inside a single flag value still expand as before.
3452
+ // TASK_1869 follow-on: field -> flag-like tokens that were NOT taken as values.
3453
+ // Declared above getCsvOrNull on purpose -- the CLI entry runs at module scope,
3454
+ // so a const declared below its consumer sits in the temporal dead zone.
3455
+ const csvFlagLikeSkipped = {};
3456
+ const getCsvOrNull = (flag) => {
3457
+ let seen = false;
3458
+ const results = [];
3459
+ for (let i = 0; i < args.length; i++) {
3460
+ let val = null;
3461
+ if (args[i] === flag && args[i + 1] !== undefined) {
3462
+ // TASK_1869 follow-on: a flag-like next token is NOT this flag's value.
3463
+ // getMulti has always guarded this; getCsvOrNull (via getArg) never did,
3464
+ // so `--scope-paths --append` persisted the literal string "--append" as a
3465
+ // scope path AND consumed the --append flag, silently turning an append
3466
+ // into a replace. Measured 2026-08-13. Skip it, and record it so the
3467
+ // skip is not itself silent -- the whole point of this card.
3468
+ if (args[i + 1].startsWith('--')) {
3469
+ if (!csvFlagLikeSkipped[flag]) csvFlagLikeSkipped[flag] = [];
3470
+ csvFlagLikeSkipped[flag].push(args[i + 1]);
3471
+ continue;
3472
+ }
3473
+ val = args[i + 1];
3474
+ } else if (args[i].startsWith(flag + '=')) {
3475
+ val = args[i].slice(flag.length + 1);
3476
+ }
3477
+ if (val !== null) {
3478
+ seen = true;
3479
+ results.push(...val.split(',').map(s => s.trim()).filter(Boolean));
3480
+ }
3481
+ }
3482
+ return seen ? results : null;
3483
+ };
3484
+ const getMulti = (flag) => {
3485
+ const results = [];
3486
+ let seen = false;
3487
+ for (let i = 0; i < args.length; i++) {
3488
+ if (args[i] === flag && args[i + 1] !== undefined && !args[i + 1].startsWith('--')) {
3489
+ seen = true;
3490
+ if (args[i + 1] !== '') results.push(args[i + 1]);
3491
+ } else if (args[i].startsWith(flag + '=')) {
3492
+ seen = true;
3493
+ const v = args[i].slice(flag.length + 1);
3494
+ if (v !== '') results.push(v);
3495
+ }
3496
+ }
3497
+ if (results.length > 0) return results;
3498
+ return seen ? [] : null;
3499
+ };
3500
+ // --args-file: load update fields from a UTF-8 JSON file (bypasses argv 32K limit).
3501
+ // CLI flags override args-file values. Supports camelCase and snake_case keys.
3502
+ let updateFileData = {};
3503
+ const updateArgsFilePath = getArg('--args-file');
3504
+ if (updateArgsFilePath) {
3505
+ const fsSync = require('fs');
3506
+ let raw;
3507
+ try { raw = fsSync.readFileSync(updateArgsFilePath, 'utf8'); }
3508
+ catch (e) { console.error('❌ --args-file: cannot read ' + updateArgsFilePath + ': ' + e.message); process.exit(1); }
3509
+ try { updateFileData = JSON.parse(raw); }
3510
+ catch (e) { console.error('❌ --args-file: invalid JSON in ' + updateArgsFilePath + ': ' + e.message); process.exit(1); }
3511
+ if (!updateFileData || typeof updateFileData !== 'object' || Array.isArray(updateFileData)) {
3512
+ console.error('❌ --args-file: expected a JSON object'); process.exit(1);
3513
+ }
3514
+ }
3515
+
3516
+ const fromUpdateFile = (camel, snake) => {
3517
+ if (camel in updateFileData) return updateFileData[camel];
3518
+ if (snake && snake in updateFileData) return updateFileData[snake];
3519
+ return undefined;
3520
+ };
3521
+
3522
+ // Build updates object -- args-file base values first, CLI flags override
3523
+ const updates = {};
3524
+ if (updateArgsFilePath) {
3525
+ if (fromUpdateFile('title') != null) updates.title = fromUpdateFile('title');
3526
+ if (fromUpdateFile('description') != null) updates.description = fromUpdateFile('description');
3527
+ if (fromUpdateFile('priority') != null) updates.priority = fromUpdateFile('priority');
3528
+ if (fromUpdateFile('role') != null) updates.role = fromUpdateFile('role');
3529
+ if (fromUpdateFile('rigor') != null) updates.rigor = fromUpdateFile('rigor');
3530
+ if (fromUpdateFile('complexity') != null) updates.complexity = fromUpdateFile('complexity');
3531
+ const fT = fromUpdateFile('tags'); if (fT != null) updates.tags = fT;
3532
+ const fD = fromUpdateFile('dependencies'); if (fD != null) updates.dependencies = fD;
3533
+ const fSc = fromUpdateFile('scopePaths', 'scope_paths'); if (fSc != null) updates.scope_paths = fSc;
3534
+ const fH = fromUpdateFile('estimatedHours', 'estimated_hours'); if (fH != null) updates.estimated_hours = parseEstimatedHours(fH, '--args-file estimatedHours');
3535
+ if (fromUpdateFile('notes') != null) updates.notes = fromUpdateFile('notes');
3536
+ const fFt = fromUpdateFile('featureIds', 'feature_ids'); if (Array.isArray(fFt)) updates.feature_ids = fFt;
3537
+ const fKw = fromUpdateFile('keywords'); if (Array.isArray(fKw)) updates.keywords = fKw;
3538
+ const fRq = fromUpdateFile('requirements'); if (fRq != null) updates.requirements = fRq;
3539
+ const fAc = fromUpdateFile('acceptanceCriteria', 'acceptance_criteria'); if (fAc != null) updates.acceptance_criteria = fAc;
3540
+ const fTn = fromUpdateFile('technicalNotes', 'technical_notes'); if (fTn != null) updates.technical_notes = fTn;
3541
+ const fMl = fromUpdateFile('mergeLanded', 'merge_landed'); if (fMl != null) updates.merge_landed = Boolean(fMl);
3542
+ }
3543
+ // CLI flags (higher priority -- overwrite file values)
3544
+ // TASK_1832: identity fields cannot be blanked -- an empty title/priority/role
3545
+ // would corrupt the card. Reject loudly rather than ignoring silently; a silent
3546
+ // no-op is the exact defect this task exists to remove.
3547
+ for (const flag of ['--title', '--priority', '--role', '--rigor', '--complexity']) {
3548
+ if (getArg(flag) === '') {
3549
+ console.error(`❌ ${flag} cannot be set to an empty value (this field has no "cleared" state).`);
3550
+ process.exit(1);
3551
+ }
3552
+ }
3553
+ const title = getArg('--title'); if (title) updates.title = title;
3554
+ // Clearable text fields: '' is a legitimate value, so gate on presence (!== null).
3555
+ const desc = getArg('--description'); if (desc !== null) updates.description = desc;
3556
+ const priority = getArg('--priority'); if (priority) updates.priority = priority;
3557
+ const role = getArg('--role'); if (role) updates.role = role;
3558
+ const rigor = getArg('--rigor'); if (rigor) updates.rigor = rigor;
3559
+ const complexity = getArg('--complexity'); if (complexity) updates.complexity = complexity;
3560
+ const tags = getCsvOrNull('--tags'); if (tags !== null) updates.tags = tags;
3561
+ const deps = getCsvOrNull('--dependencies') ?? getCsvOrNull('--deps'); if (deps !== null) updates.dependencies = deps;
3562
+ const scopePaths = getCsvOrNull('--scope-paths'); if (scopePaths !== null) updates.scope_paths = scopePaths;
3563
+ const predictedSymbols = getCsvOrNull('--predicted-symbols'); if (predictedSymbols !== null) updates.predicted_symbols = predictedSymbols;
3564
+ const hours = getArg('--estimated-hours'); if (hours !== null) updates.estimated_hours = parseEstimatedHours(hours, '--estimated-hours');
3565
+ const notes = getArg('--notes'); if (notes !== null) updates.notes = notes;
3566
+ const featureIds = getCsvOrNull('--feature-ids'); if (featureIds !== null) updates.feature_ids = featureIds;
3567
+ const keywords = getCsvOrNull('--keywords'); if (keywords !== null) updates.keywords = keywords;
3568
+ const reqs = getMulti('--requirements'); if (reqs) updates.requirements = reqs;
3569
+ const ac = getMulti('--acceptance-criteria'); if (ac) updates.acceptance_criteria = ac;
3570
+ const tn = getMulti('--technical-notes'); if (tn) updates.technical_notes = tn;
3571
+ const mergeLandedArg = getArg('--merge-landed');
3572
+ if (mergeLandedArg !== null) updates.merge_landed = (mergeLandedArg === 'true');
3573
+
3574
+ if (Object.keys(updates).length === 0) {
3575
+ console.error('❌ 未指定任何更新欄位');
3576
+ process.exit(1);
3577
+ }
3578
+
3579
+ // TASK_1851: every array field on the update path REPLACES the whole array
3580
+ // (updateTask spreads `...updates` over `...task`). Passing a single
3581
+ // --technical-notes onto a card that already had N entries silently dropped
3582
+ // the other N -- the CLI echoed only the new value and reported ✅ success.
3583
+ // Replace semantics are kept (passing N values at once still stores exactly
3584
+ // N, unchanged), but the loss is no longer invisible:
3585
+ // --append : this invocation appends to existing entries instead
3586
+ // (default) : replace, but removed existing entries are echoed after
3587
+ // the update so truncation is observable from the CLI.
3588
+ // Covers ALL array fields, not just technical_notes.
3589
+ const ARRAY_FIELDS = ['tags', 'dependencies', 'scope_paths', 'predicted_symbols',
3590
+ 'feature_ids', 'keywords', 'requirements', 'acceptance_criteria', 'technical_notes'];
3591
+ const appendMode = args.includes('--append');
3592
+ const touchedArrayFields = ARRAY_FIELDS.filter(k => k in updates);
3593
+ if (appendMode && touchedArrayFields.length === 0) {
3594
+ console.error('❌ --append 只作用於陣列欄位,本次未指定任何陣列欄位');
3595
+ process.exit(1);
3596
+ }
3597
+ const replacedItems = {}; // field -> existing entries removed by the replace
3598
+ const appendSkipped = {}; // TASK_1867: field -> appended entries skipped as duplicates
3599
+ if (touchedArrayFields.length > 0) {
3600
+ const before = await api.getTask(taskId); // null → updateTask 會丟出任務不存在
3601
+ if (before) {
3602
+ for (const k of touchedArrayFields) {
3603
+ const oldArr = Array.isArray(before[k]) ? before[k]
3604
+ : (before[k] == null || before[k] === '' ? [] : [before[k]]);
3605
+ if (appendMode) {
3606
+ // TASK_1867: appending a value identical to an existing entry used to
3607
+ // write the duplicate silently (✅ 已更新, no warning) — downstream the
3608
+ // dashboard rendered duplicate React keys. Skip exact duplicates and
3609
+ // echo them, mirroring the TASK_1851 replace-path echo.
3610
+ const seen = new Set(oldArr.map(v => String(v)));
3611
+ const merged = [...oldArr];
3612
+ for (const v of updates[k]) {
3613
+ if (seen.has(String(v))) {
3614
+ if (!appendSkipped[k]) appendSkipped[k] = [];
3615
+ appendSkipped[k].push(v);
3616
+ } else {
3617
+ seen.add(String(v));
3618
+ merged.push(v);
3619
+ }
3620
+ }
3621
+ updates[k] = merged;
3622
+ } else {
3623
+ const kept = new Set(updates[k].map(v => String(v)));
3624
+ const removed = oldArr.filter(v => !kept.has(String(v)));
3625
+ if (removed.length > 0) replacedItems[k] = removed;
3626
+ }
3627
+ }
3628
+ }
3629
+ }
3630
+
3631
+ const updated = await api.updateTask(taskId, updates);
3632
+ console.log(`✅ 已更新: ${updated.id}`);
3633
+ for (const [key, val] of Object.entries(updates)) {
3634
+ const display = Array.isArray(val) ? val.join(', ') : val;
3635
+ const displayStr = String(display).length > 80 ? String(display).substring(0, 77) + '...' : display;
3636
+ console.log(` ${key}: ${displayStr}`);
3637
+ }
3638
+ // TASK_1851: make the replace visible -- echo what was removed.
3639
+ for (const [key, removed] of Object.entries(replacedItems)) {
3640
+ console.log(` ⚠️ ${key}: 取代模式移除了 ${removed.length} 條既有項目(追加請用 --append):`);
3641
+ for (const item of removed) {
3642
+ const s = String(item);
3643
+ console.log(` - ${s.length > 80 ? s.substring(0, 77) + '...' : s}`);
3644
+ }
3645
+ }
3646
+ // TASK_1867: make the append-path dedupe visible -- echo what was skipped.
3647
+ for (const [key, skipped] of Object.entries(appendSkipped)) {
3648
+ console.log(` ⚠️ ${key}: 追加模式略過 ${skipped.length} 條與既有重複的項目:`);
3649
+ for (const item of skipped) {
3650
+ const s = String(item);
3651
+ console.log(` - ${s.length > 80 ? s.substring(0, 77) + '...' : s}`);
3652
+ }
3653
+ }
3654
+ // TASK_1869 follow-on: a flag-like token was never taken as a value. Say so --
3655
+ // the field is left untouched, which without this line looks like success.
3656
+ for (const [flag, tokens] of Object.entries(csvFlagLikeSkipped)) {
3657
+ console.log(` ⚠️ ${flag}: 忽略了 ${tokens.length} 個看起來是 flag 的值(該欄位未被修改): ${tokens.join(', ')}`);
3658
+ }
3659
+
3660
+ } else if (command === 'migrate-merge-landed') {
3661
+ // One-shot migration: annotate done/ and archived/ tasks that have evidence of
3662
+ // merge failure with merge_landed=false. Safe to re-run (already-annotated skipped).
3663
+ let annotated = 0, alreadySet = 0, skipped = 0;
3664
+ for (const dirName of ['done', 'archived']) {
3665
+ const dirPath = path.join(api.tasksDir, dirName);
3666
+ const dirTasks = await api._scanDir(dirPath);
3667
+ for (const task of dirTasks) {
3668
+ if (task.merge_landed !== undefined) { alreadySet++; continue; }
3669
+ const vmlFailed = task.vitals?.verify_merge_landed === false;
3670
+ const outcomeFailed = task.outcome?.success === false;
3671
+ if (vmlFailed || outcomeFailed) {
3672
+ try {
3673
+ await api.updateTask(task.id, { merge_landed: false });
3674
+ console.log(` [${task.id}] (${dirName}) merge_landed=false`);
3675
+ annotated++;
3676
+ } catch (e) {
3677
+ console.log(` [${task.id}] update failed: ${e.message}`);
3678
+ }
3679
+ } else {
3680
+ skipped++;
3681
+ }
3682
+ }
3683
+ }
3684
+ console.log(`migrate-merge-landed: ${annotated} annotated, ${alreadySet} already set, ${skipped} skipped (no failure evidence)`);
3685
+
3686
+ } else if (command === 'boost-by-fires') {
3687
+ // TASK_895: Thalamus fire → task priority auto-boost
3688
+ // Usage: node task-api.js boost-by-fires --symbols '[{"symbolId":"file::fn","fireCount":5}]'
3689
+ const symbolsArg = args.find((a, i) => args[i - 1] === '--symbols') || '[]';
3690
+ let firedSymbols;
3691
+ try {
3692
+ firedSymbols = JSON.parse(symbolsArg);
3693
+ } catch {
3694
+ console.error('❌ Invalid --symbols JSON');
3695
+ process.exit(1);
3696
+ }
3697
+ const result = await api.boostByFiredSymbols(firedSymbols);
3698
+ console.log(JSON.stringify(result));
3699
+
3700
+ } else {
3701
+ console.error(`未知命令: ${command}`);
3702
+ console.error('使用 --help 查看用法');
3703
+ process.exit(1);
3704
+ }
3705
+ } catch (error) {
3706
+ console.error(`❌ 錯誤: ${error.message}`);
3707
+ process.exit(1);
3708
+ }
3709
+ })();
3710
+ }
3711
+
3712
+ // ============================================================================
3713
+ // 導出
3714
+ // ============================================================================
3715
+
3716
+ module.exports = {
3717
+ TaskAPI,
3718
+ CONFIG,
3719
+ parseYAML,
3720
+ toYAML,
3721
+ resolveTasksDir,
3722
+ // TASK_1992/TASK_2104: exported for direct unit-testing with a mocked
3723
+ // fetch (and an injectable alaya-gate module) — no live MCP dependency.
3724
+ loadAlayaGate,
3725
+ echoAlayaLessons,
3726
+ // TASK_2142 R2: test seam for injecting a fake pool-status-push module.
3727
+ setPoolStatusPush,
3728
+ };
3729
+ // thalamus cascade test 1775057515