@rryando/arcs 3.0.0

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 (370) hide show
  1. package/README.md +448 -0
  2. package/dist/cli/arcs-orchestrate-caveman.d.ts +16 -0
  3. package/dist/cli/arcs-orchestrate-caveman.d.ts.map +1 -0
  4. package/dist/cli/arcs-orchestrate-caveman.js +109 -0
  5. package/dist/cli/arcs-orchestrate-caveman.js.map +1 -0
  6. package/dist/cli/arcs-orchestrate.d.ts +2 -0
  7. package/dist/cli/arcs-orchestrate.d.ts.map +1 -0
  8. package/dist/cli/arcs-orchestrate.js +431 -0
  9. package/dist/cli/arcs-orchestrate.js.map +1 -0
  10. package/dist/cli/arg-parser.d.ts +20 -0
  11. package/dist/cli/arg-parser.d.ts.map +1 -0
  12. package/dist/cli/arg-parser.js +216 -0
  13. package/dist/cli/arg-parser.js.map +1 -0
  14. package/dist/cli/brief-renderer.d.ts +32 -0
  15. package/dist/cli/brief-renderer.d.ts.map +1 -0
  16. package/dist/cli/brief-renderer.js +67 -0
  17. package/dist/cli/brief-renderer.js.map +1 -0
  18. package/dist/cli/bundle-installer.d.ts +77 -0
  19. package/dist/cli/bundle-installer.d.ts.map +1 -0
  20. package/dist/cli/bundle-installer.js +328 -0
  21. package/dist/cli/bundle-installer.js.map +1 -0
  22. package/dist/cli/command-registry.d.ts +52 -0
  23. package/dist/cli/command-registry.d.ts.map +1 -0
  24. package/dist/cli/command-registry.js +68 -0
  25. package/dist/cli/command-registry.js.map +1 -0
  26. package/dist/cli/commands/batch.d.ts +2 -0
  27. package/dist/cli/commands/batch.d.ts.map +1 -0
  28. package/dist/cli/commands/batch.js +307 -0
  29. package/dist/cli/commands/batch.js.map +1 -0
  30. package/dist/cli/commands/brief.d.ts +2 -0
  31. package/dist/cli/commands/brief.d.ts.map +1 -0
  32. package/dist/cli/commands/brief.js +146 -0
  33. package/dist/cli/commands/brief.js.map +1 -0
  34. package/dist/cli/commands/bundle.d.ts +2 -0
  35. package/dist/cli/commands/bundle.d.ts.map +1 -0
  36. package/dist/cli/commands/bundle.js +90 -0
  37. package/dist/cli/commands/bundle.js.map +1 -0
  38. package/dist/cli/commands/dependency.d.ts +2 -0
  39. package/dist/cli/commands/dependency.d.ts.map +1 -0
  40. package/dist/cli/commands/dependency.js +260 -0
  41. package/dist/cli/commands/dependency.js.map +1 -0
  42. package/dist/cli/commands/diagnostics.d.ts +2 -0
  43. package/dist/cli/commands/diagnostics.d.ts.map +1 -0
  44. package/dist/cli/commands/diagnostics.js +91 -0
  45. package/dist/cli/commands/diagnostics.js.map +1 -0
  46. package/dist/cli/commands/diagram.d.ts +2 -0
  47. package/dist/cli/commands/diagram.d.ts.map +1 -0
  48. package/dist/cli/commands/diagram.js +305 -0
  49. package/dist/cli/commands/diagram.js.map +1 -0
  50. package/dist/cli/commands/done.d.ts +2 -0
  51. package/dist/cli/commands/done.d.ts.map +1 -0
  52. package/dist/cli/commands/done.js +140 -0
  53. package/dist/cli/commands/done.js.map +1 -0
  54. package/dist/cli/commands/graph.d.ts +2 -0
  55. package/dist/cli/commands/graph.d.ts.map +1 -0
  56. package/dist/cli/commands/graph.js +105 -0
  57. package/dist/cli/commands/graph.js.map +1 -0
  58. package/dist/cli/commands/index.d.ts +21 -0
  59. package/dist/cli/commands/index.d.ts.map +1 -0
  60. package/dist/cli/commands/index.js +22 -0
  61. package/dist/cli/commands/index.js.map +1 -0
  62. package/dist/cli/commands/knowledge-search.d.ts +2 -0
  63. package/dist/cli/commands/knowledge-search.d.ts.map +1 -0
  64. package/dist/cli/commands/knowledge-search.js +43 -0
  65. package/dist/cli/commands/knowledge-search.js.map +1 -0
  66. package/dist/cli/commands/knowledge.d.ts +2 -0
  67. package/dist/cli/commands/knowledge.d.ts.map +1 -0
  68. package/dist/cli/commands/knowledge.js +416 -0
  69. package/dist/cli/commands/knowledge.js.map +1 -0
  70. package/dist/cli/commands/loop.d.ts +2 -0
  71. package/dist/cli/commands/loop.d.ts.map +1 -0
  72. package/dist/cli/commands/loop.js +192 -0
  73. package/dist/cli/commands/loop.js.map +1 -0
  74. package/dist/cli/commands/maintenance.d.ts +2 -0
  75. package/dist/cli/commands/maintenance.d.ts.map +1 -0
  76. package/dist/cli/commands/maintenance.js +231 -0
  77. package/dist/cli/commands/maintenance.js.map +1 -0
  78. package/dist/cli/commands/next.d.ts +2 -0
  79. package/dist/cli/commands/next.d.ts.map +1 -0
  80. package/dist/cli/commands/next.js +116 -0
  81. package/dist/cli/commands/next.js.map +1 -0
  82. package/dist/cli/commands/plan.d.ts +2 -0
  83. package/dist/cli/commands/plan.d.ts.map +1 -0
  84. package/dist/cli/commands/plan.js +340 -0
  85. package/dist/cli/commands/plan.js.map +1 -0
  86. package/dist/cli/commands/project-updates.d.ts +2 -0
  87. package/dist/cli/commands/project-updates.d.ts.map +1 -0
  88. package/dist/cli/commands/project-updates.js +243 -0
  89. package/dist/cli/commands/project-updates.js.map +1 -0
  90. package/dist/cli/commands/project.d.ts +2 -0
  91. package/dist/cli/commands/project.d.ts.map +1 -0
  92. package/dist/cli/commands/project.js +362 -0
  93. package/dist/cli/commands/project.js.map +1 -0
  94. package/dist/cli/commands/remember.d.ts +2 -0
  95. package/dist/cli/commands/remember.d.ts.map +1 -0
  96. package/dist/cli/commands/remember.js +70 -0
  97. package/dist/cli/commands/remember.js.map +1 -0
  98. package/dist/cli/commands/status.d.ts +2 -0
  99. package/dist/cli/commands/status.d.ts.map +1 -0
  100. package/dist/cli/commands/status.js +104 -0
  101. package/dist/cli/commands/status.js.map +1 -0
  102. package/dist/cli/commands/task.d.ts +6 -0
  103. package/dist/cli/commands/task.d.ts.map +1 -0
  104. package/dist/cli/commands/task.js +361 -0
  105. package/dist/cli/commands/task.js.map +1 -0
  106. package/dist/cli/commands/utility.d.ts +9 -0
  107. package/dist/cli/commands/utility.d.ts.map +1 -0
  108. package/dist/cli/commands/utility.js +302 -0
  109. package/dist/cli/commands/utility.js.map +1 -0
  110. package/dist/cli/config.d.ts +42 -0
  111. package/dist/cli/config.d.ts.map +1 -0
  112. package/dist/cli/config.js +134 -0
  113. package/dist/cli/config.js.map +1 -0
  114. package/dist/cli/dag-commands.d.ts +7 -0
  115. package/dist/cli/dag-commands.d.ts.map +1 -0
  116. package/dist/cli/dag-commands.js +152 -0
  117. package/dist/cli/dag-commands.js.map +1 -0
  118. package/dist/cli/diagram-renderer.d.ts +26 -0
  119. package/dist/cli/diagram-renderer.d.ts.map +1 -0
  120. package/dist/cli/diagram-renderer.js +118 -0
  121. package/dist/cli/diagram-renderer.js.map +1 -0
  122. package/dist/cli/help-generator.d.ts +21 -0
  123. package/dist/cli/help-generator.d.ts.map +1 -0
  124. package/dist/cli/help-generator.js +100 -0
  125. package/dist/cli/help-generator.js.map +1 -0
  126. package/dist/cli/index.d.ts +8 -0
  127. package/dist/cli/index.d.ts.map +1 -0
  128. package/dist/cli/index.js +95 -0
  129. package/dist/cli/index.js.map +1 -0
  130. package/dist/cli/instructions.d.ts +53 -0
  131. package/dist/cli/instructions.d.ts.map +1 -0
  132. package/dist/cli/instructions.js +219 -0
  133. package/dist/cli/instructions.js.map +1 -0
  134. package/dist/cli/lean-output.d.ts +15 -0
  135. package/dist/cli/lean-output.d.ts.map +1 -0
  136. package/dist/cli/lean-output.js +66 -0
  137. package/dist/cli/lean-output.js.map +1 -0
  138. package/dist/cli/md-renderer.d.ts +6 -0
  139. package/dist/cli/md-renderer.d.ts.map +1 -0
  140. package/dist/cli/md-renderer.js +396 -0
  141. package/dist/cli/md-renderer.js.map +1 -0
  142. package/dist/cli/output-envelope.d.ts +15 -0
  143. package/dist/cli/output-envelope.d.ts.map +1 -0
  144. package/dist/cli/output-envelope.js +61 -0
  145. package/dist/cli/output-envelope.js.map +1 -0
  146. package/dist/cli/setup.d.ts +11 -0
  147. package/dist/cli/setup.d.ts.map +1 -0
  148. package/dist/cli/setup.js +415 -0
  149. package/dist/cli/setup.js.map +1 -0
  150. package/dist/cli/status-dashboard.d.ts +5 -0
  151. package/dist/cli/status-dashboard.d.ts.map +1 -0
  152. package/dist/cli/status-dashboard.js +248 -0
  153. package/dist/cli/status-dashboard.js.map +1 -0
  154. package/dist/index.d.ts +3 -0
  155. package/dist/index.d.ts.map +1 -0
  156. package/dist/index.js +25 -0
  157. package/dist/index.js.map +1 -0
  158. package/dist/retrieval/bm25.d.ts +23 -0
  159. package/dist/retrieval/bm25.d.ts.map +1 -0
  160. package/dist/retrieval/bm25.js +92 -0
  161. package/dist/retrieval/bm25.js.map +1 -0
  162. package/dist/retrieval/cross-project-search.d.ts +30 -0
  163. package/dist/retrieval/cross-project-search.d.ts.map +1 -0
  164. package/dist/retrieval/cross-project-search.js +109 -0
  165. package/dist/retrieval/cross-project-search.js.map +1 -0
  166. package/dist/retrieval/graph-builder.d.ts +6 -0
  167. package/dist/retrieval/graph-builder.d.ts.map +1 -0
  168. package/dist/retrieval/graph-builder.js +196 -0
  169. package/dist/retrieval/graph-builder.js.map +1 -0
  170. package/dist/retrieval/graph-cache.d.ts +15 -0
  171. package/dist/retrieval/graph-cache.d.ts.map +1 -0
  172. package/dist/retrieval/graph-cache.js +66 -0
  173. package/dist/retrieval/graph-cache.js.map +1 -0
  174. package/dist/retrieval/graph-invalidate.d.ts +18 -0
  175. package/dist/retrieval/graph-invalidate.d.ts.map +1 -0
  176. package/dist/retrieval/graph-invalidate.js +23 -0
  177. package/dist/retrieval/graph-invalidate.js.map +1 -0
  178. package/dist/retrieval/graph-retrieval.d.ts +24 -0
  179. package/dist/retrieval/graph-retrieval.d.ts.map +1 -0
  180. package/dist/retrieval/graph-retrieval.js +165 -0
  181. package/dist/retrieval/graph-retrieval.js.map +1 -0
  182. package/dist/retrieval/graph-traverse.d.ts +3 -0
  183. package/dist/retrieval/graph-traverse.d.ts.map +1 -0
  184. package/dist/retrieval/graph-traverse.js +53 -0
  185. package/dist/retrieval/graph-traverse.js.map +1 -0
  186. package/dist/retrieval/graph-types.d.ts +37 -0
  187. package/dist/retrieval/graph-types.d.ts.map +1 -0
  188. package/dist/retrieval/graph-types.js +12 -0
  189. package/dist/retrieval/graph-types.js.map +1 -0
  190. package/dist/retrieval/index-builder.d.ts +21 -0
  191. package/dist/retrieval/index-builder.d.ts.map +1 -0
  192. package/dist/retrieval/index-builder.js +94 -0
  193. package/dist/retrieval/index-builder.js.map +1 -0
  194. package/dist/retrieval/knowledge-selection.d.ts +7 -0
  195. package/dist/retrieval/knowledge-selection.d.ts.map +1 -0
  196. package/dist/retrieval/knowledge-selection.js +80 -0
  197. package/dist/retrieval/knowledge-selection.js.map +1 -0
  198. package/dist/retrieval/task-scoped.d.ts +23 -0
  199. package/dist/retrieval/task-scoped.d.ts.map +1 -0
  200. package/dist/retrieval/task-scoped.js +51 -0
  201. package/dist/retrieval/task-scoped.js.map +1 -0
  202. package/dist/utils/content-assembly.d.ts +44 -0
  203. package/dist/utils/content-assembly.d.ts.map +1 -0
  204. package/dist/utils/content-assembly.js +101 -0
  205. package/dist/utils/content-assembly.js.map +1 -0
  206. package/dist/utils/dag.d.ts +28 -0
  207. package/dist/utils/dag.d.ts.map +1 -0
  208. package/dist/utils/dag.js +68 -0
  209. package/dist/utils/dag.js.map +1 -0
  210. package/dist/utils/diagram-generator.d.ts +17 -0
  211. package/dist/utils/diagram-generator.d.ts.map +1 -0
  212. package/dist/utils/diagram-generator.js +38 -0
  213. package/dist/utils/diagram-generator.js.map +1 -0
  214. package/dist/utils/errors.d.ts +40 -0
  215. package/dist/utils/errors.d.ts.map +1 -0
  216. package/dist/utils/errors.js +83 -0
  217. package/dist/utils/errors.js.map +1 -0
  218. package/dist/utils/file-lock.d.ts +13 -0
  219. package/dist/utils/file-lock.d.ts.map +1 -0
  220. package/dist/utils/file-lock.js +80 -0
  221. package/dist/utils/file-lock.js.map +1 -0
  222. package/dist/utils/git.d.ts +14 -0
  223. package/dist/utils/git.d.ts.map +1 -0
  224. package/dist/utils/git.js +56 -0
  225. package/dist/utils/git.js.map +1 -0
  226. package/dist/utils/graphify-knowledge.d.ts +6 -0
  227. package/dist/utils/graphify-knowledge.d.ts.map +1 -0
  228. package/dist/utils/graphify-knowledge.js +60 -0
  229. package/dist/utils/graphify-knowledge.js.map +1 -0
  230. package/dist/utils/graphify.d.ts +49 -0
  231. package/dist/utils/graphify.d.ts.map +1 -0
  232. package/dist/utils/graphify.js +306 -0
  233. package/dist/utils/graphify.js.map +1 -0
  234. package/dist/utils/json-schemas.d.ts +723 -0
  235. package/dist/utils/json-schemas.d.ts.map +1 -0
  236. package/dist/utils/json-schemas.js +147 -0
  237. package/dist/utils/json-schemas.js.map +1 -0
  238. package/dist/utils/json.d.ts +15 -0
  239. package/dist/utils/json.d.ts.map +1 -0
  240. package/dist/utils/json.js +39 -0
  241. package/dist/utils/json.js.map +1 -0
  242. package/dist/utils/knowledge-store.d.ts +49 -0
  243. package/dist/utils/knowledge-store.d.ts.map +1 -0
  244. package/dist/utils/knowledge-store.js +191 -0
  245. package/dist/utils/knowledge-store.js.map +1 -0
  246. package/dist/utils/loop-state.d.ts +51 -0
  247. package/dist/utils/loop-state.d.ts.map +1 -0
  248. package/dist/utils/loop-state.js +123 -0
  249. package/dist/utils/loop-state.js.map +1 -0
  250. package/dist/utils/paths.d.ts +16 -0
  251. package/dist/utils/paths.d.ts.map +1 -0
  252. package/dist/utils/paths.js +66 -0
  253. package/dist/utils/paths.js.map +1 -0
  254. package/dist/utils/plan-store.d.ts +46 -0
  255. package/dist/utils/plan-store.d.ts.map +1 -0
  256. package/dist/utils/plan-store.js +184 -0
  257. package/dist/utils/plan-store.js.map +1 -0
  258. package/dist/utils/project-documents.d.ts +20 -0
  259. package/dist/utils/project-documents.d.ts.map +1 -0
  260. package/dist/utils/project-documents.js +10 -0
  261. package/dist/utils/project-documents.js.map +1 -0
  262. package/dist/utils/project-memory.d.ts +16 -0
  263. package/dist/utils/project-memory.d.ts.map +1 -0
  264. package/dist/utils/project-memory.js +16 -0
  265. package/dist/utils/project-memory.js.map +1 -0
  266. package/dist/utils/project-resolver.d.ts +28 -0
  267. package/dist/utils/project-resolver.d.ts.map +1 -0
  268. package/dist/utils/project-resolver.js +119 -0
  269. package/dist/utils/project-resolver.js.map +1 -0
  270. package/dist/utils/quick-scan.d.ts +13 -0
  271. package/dist/utils/quick-scan.d.ts.map +1 -0
  272. package/dist/utils/quick-scan.js +156 -0
  273. package/dist/utils/quick-scan.js.map +1 -0
  274. package/dist/utils/schemas.d.ts +13 -0
  275. package/dist/utils/schemas.d.ts.map +1 -0
  276. package/dist/utils/schemas.js +6 -0
  277. package/dist/utils/schemas.js.map +1 -0
  278. package/dist/utils/slug.d.ts +10 -0
  279. package/dist/utils/slug.d.ts.map +1 -0
  280. package/dist/utils/slug.js +18 -0
  281. package/dist/utils/slug.js.map +1 -0
  282. package/dist/utils/stdin.d.ts +6 -0
  283. package/dist/utils/stdin.d.ts.map +1 -0
  284. package/dist/utils/stdin.js +25 -0
  285. package/dist/utils/stdin.js.map +1 -0
  286. package/dist/utils/storage-utils.d.ts +48 -0
  287. package/dist/utils/storage-utils.d.ts.map +1 -0
  288. package/dist/utils/storage-utils.js +175 -0
  289. package/dist/utils/storage-utils.js.map +1 -0
  290. package/dist/utils/task-store.d.ts +49 -0
  291. package/dist/utils/task-store.d.ts.map +1 -0
  292. package/dist/utils/task-store.js +178 -0
  293. package/dist/utils/task-store.js.map +1 -0
  294. package/dist/utils/template.d.ts +15 -0
  295. package/dist/utils/template.d.ts.map +1 -0
  296. package/dist/utils/template.js +27 -0
  297. package/dist/utils/template.js.map +1 -0
  298. package/dist/utils/tool-response.d.ts +37 -0
  299. package/dist/utils/tool-response.d.ts.map +1 -0
  300. package/dist/utils/tool-response.js +44 -0
  301. package/dist/utils/tool-response.js.map +1 -0
  302. package/dist/utils/workflow-policy.d.ts +50 -0
  303. package/dist/utils/workflow-policy.d.ts.map +1 -0
  304. package/dist/utils/workflow-policy.js +111 -0
  305. package/dist/utils/workflow-policy.js.map +1 -0
  306. package/dist/utils/workspace-match.d.ts +35 -0
  307. package/dist/utils/workspace-match.d.ts.map +1 -0
  308. package/dist/utils/workspace-match.js +58 -0
  309. package/dist/utils/workspace-match.js.map +1 -0
  310. package/opencode/arcs/.opencode/plugins/arcs.js +348 -0
  311. package/opencode/arcs/bundle-runtime.json +68 -0
  312. package/opencode/arcs/manifest.json +279 -0
  313. package/opencode/arcs/prompts/arcs-docs.txt +124 -0
  314. package/opencode/arcs/prompts/arcs-orchestrate-caveman.txt +529 -0
  315. package/opencode/arcs/prompts/arcs-orchestrate.txt +437 -0
  316. package/opencode/arcs/prompts/code-reviewer.txt +55 -0
  317. package/opencode/arcs/prompts/devil-advocate.txt +121 -0
  318. package/opencode/arcs/prompts/docs-researcher.txt +54 -0
  319. package/opencode/arcs/prompts/oncall-ops.txt +54 -0
  320. package/opencode/arcs/prompts/qa-analyst.txt +54 -0
  321. package/opencode/arcs/prompts/software-engineer.txt +48 -0
  322. package/opencode/arcs/prompts/system-architect.txt +60 -0
  323. package/opencode/arcs/prompts/tech-architect.txt +54 -0
  324. package/opencode/arcs/skills/brainstorming/SKILL.md +144 -0
  325. package/opencode/arcs/skills/brainstorming/scripts/frame-template.html +214 -0
  326. package/opencode/arcs/skills/brainstorming/scripts/helper.js +88 -0
  327. package/opencode/arcs/skills/brainstorming/scripts/server.js +338 -0
  328. package/opencode/arcs/skills/brainstorming/scripts/start-server.sh +137 -0
  329. package/opencode/arcs/skills/brainstorming/scripts/stop-server.sh +31 -0
  330. package/opencode/arcs/skills/brainstorming/spec-document-reviewer-prompt.md +50 -0
  331. package/opencode/arcs/skills/brainstorming/visual-companion.md +277 -0
  332. package/opencode/arcs/skills/caveman-commit/SKILL.md +51 -0
  333. package/opencode/arcs/skills/code-agent/SKILL.md +58 -0
  334. package/opencode/arcs/skills/deep-pr-review/SKILL.md +191 -0
  335. package/opencode/arcs/skills/deep-pr-review/graphify-diff.md +102 -0
  336. package/opencode/arcs/skills/deep-pr-review/review-template.md +69 -0
  337. package/opencode/arcs/skills/executing-plans/SKILL.md +91 -0
  338. package/opencode/arcs/skills/init-project/SKILL.md +150 -0
  339. package/opencode/arcs/skills/quick-dev/SKILL.md +47 -0
  340. package/opencode/arcs/skills/requesting-code-review/SKILL.md +47 -0
  341. package/opencode/arcs/skills/requesting-code-review/code-reviewer.md +179 -0
  342. package/opencode/arcs/skills/subagent-driven-development/SKILL.md +162 -0
  343. package/opencode/arcs/skills/subagent-driven-development/code-quality-reviewer-prompt.md +53 -0
  344. package/opencode/arcs/skills/subagent-driven-development/implementer-prompt.md +148 -0
  345. package/opencode/arcs/skills/subagent-driven-development/spec-reviewer-prompt.md +85 -0
  346. package/opencode/arcs/skills/systematic-debugging/SKILL.md +174 -0
  347. package/opencode/arcs/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  348. package/opencode/arcs/skills/systematic-debugging/condition-based-waiting.md +115 -0
  349. package/opencode/arcs/skills/systematic-debugging/defense-in-depth.md +122 -0
  350. package/opencode/arcs/skills/systematic-debugging/find-polluter.sh +63 -0
  351. package/opencode/arcs/skills/systematic-debugging/phases-reference.md +168 -0
  352. package/opencode/arcs/skills/systematic-debugging/root-cause-tracing.md +169 -0
  353. package/opencode/arcs/skills/test-driven-development/SKILL.md +72 -0
  354. package/opencode/arcs/skills/test-driven-development/tdd-rationalizations-and-examples.md +157 -0
  355. package/opencode/arcs/skills/test-driven-development/testing-anti-patterns.md +299 -0
  356. package/opencode/arcs/skills/to-diagram/SKILL.md +235 -0
  357. package/opencode/arcs/skills/to-diagram/scripts/manage-diagram.mjs +963 -0
  358. package/opencode/arcs/skills/writing-plans/SKILL.md +179 -0
  359. package/opencode/arcs/skills/writing-plans/plan-document-reviewer-prompt.md +52 -0
  360. package/package.json +60 -0
  361. package/scripts/arcs-cli.mjs +45 -0
  362. package/skills/explore-dag.md +57 -0
  363. package/skills/init-project.md +105 -0
  364. package/skills/orchestrate.md +94 -0
  365. package/skills/update-docs.md +72 -0
  366. package/templates/dependency.md.tmpl +9 -0
  367. package/templates/knowledge.md.tmpl +15 -0
  368. package/templates/project-meta.json.tmpl +13 -0
  369. package/templates/project.md.tmpl +11 -0
  370. package/templates/task.md.tmpl +9 -0
@@ -0,0 +1,963 @@
1
+ #!/usr/bin/env node
2
+ // manage-diagram.mjs — Deterministic diagram management for .diagram.mmd files
3
+ // No external dependencies. Node ESM CLI.
4
+
5
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
+
7
+ // --- Safe file read ---
8
+
9
+ function safeReadFile(filePath) {
10
+ if (!existsSync(filePath)) {
11
+ process.stderr.write(`Error: File not found: ${filePath}\n`);
12
+ process.exit(1);
13
+ }
14
+ return readFileSync(filePath, "utf-8");
15
+ }
16
+
17
+ // --- Pure helpers ---
18
+
19
+ function parseNodes(graphLines) {
20
+ const nodes = [];
21
+ const seen = new Set();
22
+ // Match node declarations: ID[label]:::status or ID[label] (no status)
23
+ const nodeRe = /\b(T\d{3,})\[([^\]]+)\](?::::(\w+))?/g;
24
+ for (const line of graphLines) {
25
+ for (const match of line.matchAll(nodeRe)) {
26
+ const [, id, label, status] = match;
27
+ if (!seen.has(id)) {
28
+ nodes.push({ id, label, status: status || "backlog" });
29
+ seen.add(id);
30
+ }
31
+ }
32
+ }
33
+ return nodes;
34
+ }
35
+
36
+ function parseEdges(graphLines) {
37
+ const edges = [];
38
+ const edgeRe = /\b(T\d{3,})(?:\[[^\]]*\](?::::\w+)?)?\s*-->\s*(T\d{3,})/g;
39
+ for (const line of graphLines) {
40
+ for (const match of line.matchAll(edgeRe)) {
41
+ edges.push({ from: match[1], to: match[2] });
42
+ }
43
+ }
44
+ return edges;
45
+ }
46
+
47
+ function parseMetadataBlocks(lines) {
48
+ const blocks = {};
49
+ let current = null;
50
+
51
+ for (const line of lines) {
52
+ const trimmed = line.trim();
53
+ if (!trimmed.startsWith("%%")) continue;
54
+
55
+ const content = trimmed.slice(2).trim();
56
+ const fieldMatch = content.match(/^(\S+?):\s*(.*)$/);
57
+ if (!fieldMatch) continue;
58
+
59
+ const [, key, value] = fieldMatch;
60
+
61
+ if (key === "node") {
62
+ current = value.trim();
63
+ blocks[current] = { node: current };
64
+ } else if (current) {
65
+ blocks[current][key] = value.trim();
66
+ }
67
+ }
68
+
69
+ return blocks;
70
+ }
71
+
72
+ function parsePlanLevelComments(lines) {
73
+ const result = {
74
+ planId: null,
75
+ statusComment: null,
76
+ readyComment: null,
77
+ blockedComment: null,
78
+ nextActionComment: null,
79
+ };
80
+
81
+ for (const line of lines) {
82
+ const trimmed = line.trim();
83
+ if (!trimmed.startsWith("%%")) continue;
84
+ const content = trimmed.slice(2).trim();
85
+
86
+ // Stop at first node metadata block
87
+ if (content.startsWith("node:")) break;
88
+
89
+ if (content.startsWith("plan:")) {
90
+ result.planId = content.slice(5).trim();
91
+ } else if (content.startsWith("status:")) {
92
+ result.statusComment = content.slice(7).trim();
93
+ } else if (content.startsWith("ready:")) {
94
+ result.readyComment = content.slice(6).trim();
95
+ } else if (content.startsWith("blocked:")) {
96
+ result.blockedComment = content.slice(8).trim();
97
+ } else if (content.startsWith("next-action:")) {
98
+ result.nextActionComment = content.slice(12).trim();
99
+ }
100
+ }
101
+
102
+ return result;
103
+ }
104
+
105
+ function splitSections(content) {
106
+ const lines = content.split("\n");
107
+ const flowchartIdx = lines.findIndex((l) => /^\s*flowchart\s+TD/i.test(l));
108
+
109
+ if (flowchartIdx === -1) {
110
+ return { headerLines: lines, graphLines: [], flowchartIdx: -1 };
111
+ }
112
+
113
+ return {
114
+ headerLines: lines.slice(0, flowchartIdx),
115
+ graphLines: lines.slice(flowchartIdx),
116
+ flowchartIdx,
117
+ };
118
+ }
119
+
120
+ function computeReady(nodes, edges) {
121
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
122
+ const ready = [];
123
+
124
+ for (const node of nodes) {
125
+ if (node.status !== "backlog") continue;
126
+ const incomingDeps = edges.filter((e) => e.to === node.id).map((e) => e.from);
127
+ const allDepsDone = incomingDeps.every((depId) => {
128
+ const dep = nodeMap.get(depId);
129
+ return dep && dep.status === "done";
130
+ });
131
+ if (allDepsDone) ready.push(node.id);
132
+ }
133
+
134
+ return ready;
135
+ }
136
+
137
+ function computeBlocked(nodes, edges) {
138
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
139
+ const blocked = [];
140
+
141
+ for (const node of nodes) {
142
+ // Explicitly blocked status always counts
143
+ if (node.status === "blocked") {
144
+ blocked.push(node.id);
145
+ continue;
146
+ }
147
+ // Implicitly blocked: backlog node with at least one undone predecessor
148
+ if (node.status !== "backlog") continue;
149
+ const incomingDeps = edges.filter((e) => e.to === node.id).map((e) => e.from);
150
+ if (incomingDeps.length === 0) continue; // No deps = ready, not blocked
151
+ const hasUndone = incomingDeps.some((depId) => {
152
+ const dep = nodeMap.get(depId);
153
+ return dep && dep.status !== "done";
154
+ });
155
+ if (hasUndone) {
156
+ blocked.push(node.id);
157
+ }
158
+ }
159
+
160
+ return blocked;
161
+ }
162
+
163
+ function parseReadyNodeIds(readyComment) {
164
+ if (!readyComment) return [];
165
+ const ids = [];
166
+ for (const match of readyComment.matchAll(/T\d{3,}/g)) {
167
+ ids.push(match[0]);
168
+ }
169
+ return ids.sort();
170
+ }
171
+
172
+ function parseStatusComment(statusComment) {
173
+ if (!statusComment) return {};
174
+ const result = {};
175
+ for (const match of statusComment.matchAll(/(T\d{3,})=(\w+)/g)) {
176
+ result[match[1]] = match[2];
177
+ }
178
+ return result;
179
+ }
180
+
181
+ // --- Commands ---
182
+
183
+ function requirePlanHeader(content) {
184
+ const lines = content.split("\n");
185
+ let found = false;
186
+ for (const line of lines) {
187
+ const trimmed = line.trim();
188
+ if (trimmed.startsWith("%%")) {
189
+ const c = trimmed.slice(2).trim();
190
+ if (c.startsWith("plan:")) {
191
+ const val = c.slice(5).trim();
192
+ if (val) {
193
+ found = true;
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ }
199
+ if (!found) {
200
+ process.stderr.write("Error: Diagram must have a non-empty plan header.\n");
201
+ process.exit(1);
202
+ }
203
+ }
204
+
205
+ function inspect(filePath) {
206
+ const content = safeReadFile(filePath);
207
+ requirePlanHeader(content);
208
+ const { headerLines, graphLines } = splitSections(content);
209
+
210
+ if (graphLines.length === 0) {
211
+ // Check for stateDiagram
212
+ if (content.includes("stateDiagram-v2")) {
213
+ process.stderr.write("Error: stateDiagram-v2 is not supported by this tool.\n");
214
+ process.exit(1);
215
+ }
216
+ process.stderr.write("Error: No flowchart TD found in file.\n");
217
+ process.exit(1);
218
+ }
219
+
220
+ const nodes = parseNodes(graphLines);
221
+ const edges = parseEdges(graphLines);
222
+ const metadataBlocks = parseMetadataBlocks(headerLines);
223
+ const planLevel = parsePlanLevelComments(headerLines);
224
+
225
+ const result = {
226
+ planId: planLevel.planId,
227
+ nodes,
228
+ edges,
229
+ metadataBlocks,
230
+ statusComment: planLevel.statusComment,
231
+ readyComment: planLevel.readyComment,
232
+ blockedComment: planLevel.blockedComment,
233
+ nextActionComment: planLevel.nextActionComment,
234
+ };
235
+
236
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
237
+ }
238
+
239
+ function ready(filePath) {
240
+ const content = safeReadFile(filePath);
241
+ requirePlanHeader(content);
242
+ const { graphLines } = splitSections(content);
243
+
244
+ if (graphLines.length === 0) {
245
+ if (content.includes("stateDiagram-v2")) {
246
+ process.stderr.write("Error: stateDiagram-v2 is not supported by this tool.\n");
247
+ process.exit(1);
248
+ }
249
+ process.stderr.write("Error: No flowchart TD found in file.\n");
250
+ process.exit(1);
251
+ }
252
+
253
+ const nodes = parseNodes(graphLines);
254
+ const edges = parseEdges(graphLines);
255
+ const readyNodes = computeReady(nodes, edges);
256
+
257
+ process.stdout.write(`${JSON.stringify(readyNodes, null, 2)}\n`);
258
+ }
259
+
260
+ function validate(filePath, metadataPath) {
261
+ const content = safeReadFile(filePath);
262
+
263
+ // If --metadata provided, do drift validation
264
+ if (metadataPath) {
265
+ return validateWithMetadata(content, metadataPath);
266
+ }
267
+ const errors = [];
268
+
269
+ // Check for unsupported diagram type
270
+ if (content.includes("stateDiagram-v2")) {
271
+ process.stdout.write(
272
+ `${JSON.stringify(
273
+ {
274
+ ok: false,
275
+ errors: ["Unsupported diagram type: stateDiagram-v2. Only flowchart TD is supported."],
276
+ },
277
+ null,
278
+ 2,
279
+ )}\n`,
280
+ );
281
+ process.exit(1);
282
+ }
283
+
284
+ const { headerLines, graphLines } = splitSections(content);
285
+
286
+ if (graphLines.length === 0) {
287
+ process.stdout.write(
288
+ `${JSON.stringify({ ok: false, errors: ["No flowchart TD found in file."] }, null, 2)}\n`,
289
+ );
290
+ process.exit(1);
291
+ }
292
+
293
+ const nodes = parseNodes(graphLines);
294
+ const edges = parseEdges(graphLines);
295
+ const metadataBlocks = parseMetadataBlocks(headerLines);
296
+ const planLevel = parsePlanLevelComments(headerLines);
297
+
298
+ // Duplicate node IDs in graph
299
+ const nodeIds = [];
300
+ const nodeRe = /\b(T\d{3,})\[([^\]]+)\](?::::(\w+))?/g;
301
+ for (const line of graphLines) {
302
+ for (const match of line.matchAll(nodeRe)) {
303
+ nodeIds.push(match[1]);
304
+ }
305
+ }
306
+ const idCounts = {};
307
+ for (const id of nodeIds) {
308
+ idCounts[id] = (idCounts[id] || 0) + 1;
309
+ }
310
+ for (const [id, count] of Object.entries(idCounts)) {
311
+ if (count > 1) errors.push(`Duplicate node ID in graph: ${id}`);
312
+ }
313
+
314
+ // Duplicate metadata blocks
315
+ const metaNodeIds = [];
316
+ for (const line of headerLines) {
317
+ const trimmed = line.trim();
318
+ if (trimmed.startsWith("%%")) {
319
+ const content = trimmed.slice(2).trim();
320
+ const m = content.match(/^node:\s*(.+)$/);
321
+ if (m) metaNodeIds.push(m[1].trim());
322
+ }
323
+ }
324
+ const metaCounts = {};
325
+ for (const id of metaNodeIds) {
326
+ metaCounts[id] = (metaCounts[id] || 0) + 1;
327
+ }
328
+ for (const [id, count] of Object.entries(metaCounts)) {
329
+ if (count > 1) errors.push(`Duplicate metadata block for node: ${id}`);
330
+ }
331
+
332
+ // Edges referencing missing nodes
333
+ const nodeIdSet = new Set(nodes.map((n) => n.id));
334
+ for (const edge of edges) {
335
+ if (!nodeIdSet.has(edge.from)) errors.push(`Edge references missing node: ${edge.from}`);
336
+ if (!nodeIdSet.has(edge.to)) errors.push(`Edge references missing node: ${edge.to}`);
337
+ }
338
+
339
+ // Missing metadata block for graph nodes
340
+ for (const node of nodes) {
341
+ if (!metadataBlocks[node.id]) {
342
+ errors.push(`Missing metadata block for node: ${node.id}`);
343
+ }
344
+ }
345
+
346
+ // Missing required metadata fields (verify is optional per SKILL.md)
347
+ const requiredFields = ["node", "title", "status", "skill", "scope", "acceptance"];
348
+ for (const node of nodes) {
349
+ const block = metadataBlocks[node.id];
350
+ if (block) {
351
+ for (const field of requiredFields) {
352
+ if (!block[field]) {
353
+ errors.push(`Missing required field '${field}' in metadata block for ${node.id}`);
354
+ }
355
+ }
356
+ }
357
+ }
358
+
359
+ // Metadata blocks not ordered by node ID
360
+ if (metaNodeIds.length > 1) {
361
+ const uniqueMetaIds = [...new Set(metaNodeIds)];
362
+ const sorted = [...uniqueMetaIds].sort();
363
+ if (JSON.stringify(uniqueMetaIds) !== JSON.stringify(sorted)) {
364
+ errors.push("Metadata blocks are not ordered by node ID");
365
+ }
366
+ }
367
+
368
+ // Graph node status differs from metadata block status
369
+ for (const node of nodes) {
370
+ const block = metadataBlocks[node.id];
371
+ if (block?.status && block.status !== node.status) {
372
+ errors.push(
373
+ `Status mismatch for ${node.id}: graph says '${node.status}', metadata says '${block.status}'`,
374
+ );
375
+ }
376
+ }
377
+
378
+ // Status comment differs from graph node statuses
379
+ if (planLevel.statusComment) {
380
+ const commentStatuses = parseStatusComment(planLevel.statusComment);
381
+ for (const node of nodes) {
382
+ if (commentStatuses[node.id] && commentStatuses[node.id] !== node.status) {
383
+ errors.push(
384
+ `Status comment mismatch for ${node.id}: comment says '${commentStatuses[node.id]}', graph says '${node.status}'`,
385
+ );
386
+ }
387
+ }
388
+ }
389
+
390
+ // Ready comment differs from computed ready nodes
391
+ if (planLevel.readyComment) {
392
+ const commentReady = parseReadyNodeIds(planLevel.readyComment);
393
+ // Validate that all ready IDs actually exist in the graph
394
+ for (const id of commentReady) {
395
+ if (!nodeIdSet.has(id)) {
396
+ errors.push(`Ready comment references node '${id}' not found in graph`);
397
+ }
398
+ }
399
+ const computedReady = computeReady(nodes, edges).sort();
400
+ if (JSON.stringify(commentReady) !== JSON.stringify(computedReady)) {
401
+ errors.push(
402
+ `Ready comment mismatch: comment says [${commentReady.join(", ")}], computed is [${computedReady.join(", ")}]`,
403
+ );
404
+ }
405
+ }
406
+
407
+ const ok = errors.length === 0;
408
+ process.stdout.write(`${JSON.stringify({ ok, errors }, null, 2)}\n`);
409
+ if (!ok) process.exit(1);
410
+ }
411
+
412
+ function validateWithMetadata(content, metadataPath) {
413
+ const metaRaw = safeReadFile(metadataPath);
414
+ let meta;
415
+ try {
416
+ meta = JSON.parse(metaRaw);
417
+ } catch (e) {
418
+ process.stderr.write(`Error: Failed to parse metadata JSON: ${e.message}\n`);
419
+ process.exit(1);
420
+ }
421
+
422
+ const errors = [];
423
+ const { headerLines, graphLines } = splitSections(content);
424
+
425
+ if (graphLines.length === 0) {
426
+ process.stdout.write(
427
+ `${JSON.stringify({ ok: false, errors: ["No flowchart TD found in file."] }, null, 2)}\n`,
428
+ );
429
+ process.exit(1);
430
+ }
431
+
432
+ const nodes = parseNodes(graphLines);
433
+ const edges = parseEdges(graphLines);
434
+ const planLevel = parsePlanLevelComments(headerLines);
435
+ const metadataBlocks = parseMetadataBlocks(headerLines);
436
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
437
+ const metaTaskIds = new Set((meta.tasks || []).map((t) => t.id));
438
+ const diagramNodeIds = new Set(nodes.map((n) => n.id));
439
+
440
+ // 1. Class status mismatch
441
+ for (const task of meta.tasks || []) {
442
+ const node = nodeMap.get(task.id);
443
+ if (node && node.status !== task.status) {
444
+ errors.push(
445
+ `Class status mismatch for ${task.id}: diagram says '${node.status}', metadata says '${task.status}'`,
446
+ );
447
+ }
448
+ }
449
+
450
+ // 2. Phantom node
451
+ for (const node of nodes) {
452
+ if (!metaTaskIds.has(node.id)) {
453
+ errors.push(`Phantom node in diagram: ${node.id}`);
454
+ }
455
+ }
456
+
457
+ // 3. Missing node
458
+ for (const task of meta.tasks || []) {
459
+ if (!diagramNodeIds.has(task.id)) {
460
+ errors.push(`Missing node from diagram: ${task.id}`);
461
+ }
462
+ }
463
+
464
+ // 4. Topology mismatch
465
+ const metaEdges = new Set();
466
+ for (const task of meta.tasks || []) {
467
+ for (const dep of (task.dependencies || []).sort()) {
468
+ metaEdges.add(`${dep}->${task.id}`);
469
+ }
470
+ }
471
+ const diagramEdges = new Set(edges.map((e) => `${e.from}->${e.to}`));
472
+ const allEdgeKeys = new Set([...metaEdges, ...diagramEdges]);
473
+ let topoMismatch = false;
474
+ for (const key of allEdgeKeys) {
475
+ if (!metaEdges.has(key) || !diagramEdges.has(key)) {
476
+ topoMismatch = true;
477
+ break;
478
+ }
479
+ }
480
+ if (topoMismatch) {
481
+ errors.push("Topology mismatch: diagram edges differ from metadata dependencies");
482
+ }
483
+
484
+ // 5. Stale plan-level comments
485
+ if (planLevel.statusComment) {
486
+ const commentStatuses = parseStatusComment(planLevel.statusComment);
487
+ for (const task of meta.tasks || []) {
488
+ if (commentStatuses[task.id] && commentStatuses[task.id] !== task.status) {
489
+ errors.push(
490
+ `Stale plan-level comments: status comment says ${task.id}=${commentStatuses[task.id]}, metadata says ${task.status}`,
491
+ );
492
+ break;
493
+ }
494
+ }
495
+ }
496
+
497
+ // 6. Incomplete metadata in node blocks (verify is optional per SKILL.md)
498
+ const requiredFields = ["node", "title", "status", "skill", "scope", "acceptance"];
499
+ for (const task of meta.tasks || []) {
500
+ const block = metadataBlocks[task.id];
501
+ if (!block) {
502
+ errors.push(`Incomplete metadata: missing node block for ${task.id}`);
503
+ } else {
504
+ for (const field of requiredFields) {
505
+ if (!block[field]) {
506
+ errors.push(`Incomplete metadata: missing '${field}' in node block for ${task.id}`);
507
+ break;
508
+ }
509
+ }
510
+ }
511
+ }
512
+
513
+ const ok = errors.length === 0;
514
+ process.stdout.write(`${JSON.stringify({ ok, errors }, null, 2)}\n`);
515
+ if (!ok) process.exit(1);
516
+ }
517
+
518
+ // --- Metadata schema validation ---
519
+
520
+ const VALID_STATUSES = ["done", "inProgress", "blocked", "backlog"];
521
+ const REQUIRED_TASK_FIELDS = ["title", "status", "skill", "scope", "acceptance"];
522
+
523
+ function validateMetadataSchema(meta) {
524
+ if (!meta.planId || typeof meta.planId !== "string" || !meta.planId.trim()) {
525
+ process.stderr.write("Error: Metadata requires a non-empty 'planId' string.\n");
526
+ process.exit(1);
527
+ }
528
+ if (!Array.isArray(meta.tasks)) {
529
+ process.stderr.write("Error: Metadata requires a 'tasks' array.\n");
530
+ process.exit(1);
531
+ }
532
+ for (let i = 0; i < meta.tasks.length; i++) {
533
+ const task = meta.tasks[i];
534
+ for (const field of REQUIRED_TASK_FIELDS) {
535
+ if (!task[field] || typeof task[field] !== "string" || !task[field].trim()) {
536
+ process.stderr.write(
537
+ `Error: Task ${task.id || `[${i}]`} missing required field '${field}'.\n`,
538
+ );
539
+ process.exit(1);
540
+ }
541
+ }
542
+ if (!VALID_STATUSES.includes(task.status)) {
543
+ process.stderr.write(
544
+ `Error: Task ${task.id || `[${i}]`} has invalid status '${task.status}'. Must be one of: ${VALID_STATUSES.join(", ")}\n`,
545
+ );
546
+ process.exit(1);
547
+ }
548
+ }
549
+ }
550
+
551
+ // --- Regenerate command ---
552
+
553
+ function regenerate(filePath, metadataPath) {
554
+ const metaRaw = safeReadFile(metadataPath);
555
+ let meta;
556
+ try {
557
+ meta = JSON.parse(metaRaw);
558
+ } catch (e) {
559
+ process.stderr.write(`Error: Failed to parse metadata JSON: ${e.message}\n`);
560
+ process.exit(1);
561
+ }
562
+
563
+ validateMetadataSchema(meta);
564
+
565
+ // Determine existing IDs from previous diagram (if exists)
566
+ const existingIds = new Set();
567
+ if (existsSync(filePath)) {
568
+ const prev = readFileSync(filePath, "utf-8");
569
+ const { graphLines } = splitSections(prev);
570
+ const prevNodes = parseNodes(graphLines);
571
+ for (const n of prevNodes) existingIds.add(n.id);
572
+ }
573
+
574
+ // Assign IDs: preserve existing task IDs, assign new sequential ones for tasks without IDs
575
+ const allUsedIds = new Set(existingIds);
576
+ const tasks = meta.tasks.map((t) => {
577
+ if (t.id) {
578
+ allUsedIds.add(t.id);
579
+ return { ...t };
580
+ }
581
+ return { ...t, id: null }; // will assign below
582
+ });
583
+
584
+ // Assign IDs to tasks without one
585
+ let nextNum = 1;
586
+ for (const task of tasks) {
587
+ if (!task.id) {
588
+ while (allUsedIds.has(`T${String(nextNum).padStart(3, "0")}`)) nextNum++;
589
+ task.id = `T${String(nextNum).padStart(3, "0")}`;
590
+ allUsedIds.add(task.id);
591
+ nextNum++;
592
+ }
593
+ }
594
+
595
+ // Validate dependencies reference existing task IDs
596
+ const taskIdSet = new Set(tasks.map((t) => t.id));
597
+ for (const task of tasks) {
598
+ for (const dep of task.dependencies || []) {
599
+ if (!taskIdSet.has(dep)) {
600
+ process.stderr.write(`Error: Task dependency references missing task: ${dep}\n`);
601
+ process.exit(1);
602
+ }
603
+ }
604
+ }
605
+
606
+ // Sort tasks by ID for determinism
607
+ tasks.sort((a, b) => a.id.localeCompare(b.id));
608
+
609
+ // Build plan-level comments
610
+ const statusStr = tasks.map((t) => `${t.id}=${t.status}`).join(", ");
611
+
612
+ // Build edges (sorted) for ready/blocked computation
613
+ const edges = [];
614
+ for (const task of tasks) {
615
+ for (const dep of (task.dependencies || []).sort()) {
616
+ edges.push({ from: dep, to: task.id });
617
+ }
618
+ }
619
+ edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
620
+
621
+ const nodesForCompute = tasks.map((t) => ({ id: t.id, label: t.title, status: t.status }));
622
+ const readyNodes = computeReady(nodesForCompute, edges);
623
+ const blockedNodes = computeBlocked(nodesForCompute, edges);
624
+
625
+ const readyStr = readyNodes.length > 0 ? readyNodes.join(", ") : "none";
626
+ const blockedStr = blockedNodes.length > 0 ? blockedNodes.join(", ") : "none";
627
+ const nextAction = readyNodes.length > 0 ? `Start ${readyNodes[0]}` : "No ready tasks";
628
+
629
+ // Build output
630
+ const lines = [];
631
+ lines.push(`%% plan: ${meta.planId}`);
632
+ lines.push(`%% status: ${statusStr}`);
633
+ lines.push(`%% ready: ${readyStr}`);
634
+ lines.push(`%% blocked: ${blockedStr}`);
635
+ lines.push(`%% next-action: ${nextAction}`);
636
+ lines.push("");
637
+
638
+ // Per-node metadata
639
+ for (const task of tasks) {
640
+ lines.push(`%% node: ${task.id}`);
641
+ lines.push(`%% title: ${task.title}`);
642
+ lines.push(`%% status: ${task.status}`);
643
+ lines.push(`%% skill: ${task.skill}`);
644
+ lines.push(`%% scope: ${task.scope}`);
645
+ if (task.files) lines.push(`%% files: ${task.files}`);
646
+ lines.push(`%% acceptance: ${task.acceptance}`);
647
+ if (task.verify) lines.push(`%% verify: ${task.verify}`);
648
+ const deps = (task.dependencies || []).sort();
649
+ if (deps.length > 0) lines.push(`%% blocked-by: ${deps.join(", ")}`);
650
+ if (task.delegate) lines.push(`%% delegate: ${task.delegate}`);
651
+ lines.push("");
652
+ }
653
+
654
+ // Flowchart
655
+ lines.push("flowchart TD");
656
+ lines.push(" classDef done fill:#22c55e,color:#fff");
657
+ lines.push(" classDef inProgress fill:#f59e0b,color:#fff");
658
+ lines.push(" classDef blocked fill:#ef4444,color:#fff");
659
+ lines.push(" classDef backlog fill:#94a3b8,color:#fff");
660
+ lines.push("");
661
+
662
+ // Node declarations + edges
663
+ // Declare all nodes first, then edges with bare IDs
664
+ for (const task of tasks) {
665
+ lines.push(` ${task.id}[${task.title}]:::${task.status}`);
666
+ }
667
+
668
+ if (edges.length > 0) {
669
+ lines.push("");
670
+ for (const edge of edges) {
671
+ lines.push(` ${edge.from} --> ${edge.to}`);
672
+ }
673
+ }
674
+
675
+ lines.push("");
676
+
677
+ const output = lines.join("\n");
678
+ writeFileSync(filePath, output);
679
+
680
+ process.stdout.write(
681
+ `${JSON.stringify(
682
+ { regenerated: true, planId: meta.planId, nodeCount: tasks.length, readyNodes },
683
+ null,
684
+ 2,
685
+ )}\n`,
686
+ );
687
+ }
688
+
689
+ function status(filePath, nodeId, newStatus) {
690
+ const validStatuses = ["done", "inProgress", "blocked", "backlog"];
691
+ if (!validStatuses.includes(newStatus)) {
692
+ process.stderr.write(
693
+ `Error: Invalid status '${newStatus}'. Must be one of: ${validStatuses.join(", ")}\n`,
694
+ );
695
+ process.exit(1);
696
+ }
697
+
698
+ const content = safeReadFile(filePath);
699
+
700
+ if (content.includes("stateDiagram-v2")) {
701
+ process.stderr.write("Error: stateDiagram-v2 is not supported by this tool.\n");
702
+ process.exit(1);
703
+ }
704
+
705
+ const { headerLines, graphLines } = splitSections(content);
706
+
707
+ if (graphLines.length === 0) {
708
+ process.stderr.write("Error: No flowchart TD found in file.\n");
709
+ process.exit(1);
710
+ }
711
+
712
+ // Capture pre-update topology
713
+ const preNodes = parseNodes(graphLines);
714
+ const preEdges = parseEdges(graphLines);
715
+ const preNodeSet = new Set(preNodes.map((n) => n.id));
716
+ const preEdgeSet = new Set(preEdges.map((e) => `${e.from}->${e.to}`));
717
+
718
+ if (!preNodeSet.has(nodeId)) {
719
+ process.stderr.write(`Error: Node '${nodeId}' not found in graph.\n`);
720
+ process.exit(1);
721
+ }
722
+
723
+ // Update graph lines — change :::status for the target node
724
+ const updatedGraphLines = graphLines.map((line) => {
725
+ // Match node declaration: nodeId[label]:::status — anchored to avoid matching inside labels
726
+ // Uses (?<![\\w]) negative lookbehind instead of \b to prevent matching T001 inside another node's label
727
+ const re = new RegExp(`(?<![\\w])(${nodeId}\\[[^\\]]+\\]):::\\w+`, "g");
728
+ return line.replace(re, `$1:::${newStatus}`);
729
+ });
730
+
731
+ // Verify the graph was actually updated (node must have :::class suffix)
732
+ const graphChanged = graphLines.some((line, i) => line !== updatedGraphLines[i]);
733
+ if (!graphChanged) {
734
+ process.stderr.write(
735
+ `Error: Node '${nodeId}' has no :::class suffix in graph declaration. Cannot update status.\n`,
736
+ );
737
+ process.exit(1);
738
+ }
739
+
740
+ // Update metadata block status
741
+ const updatedHeaderLines = [];
742
+ let inTargetBlock = false;
743
+ for (const line of headerLines) {
744
+ const trimmed = line.trim();
745
+ if (trimmed.startsWith("%%")) {
746
+ const c = trimmed.slice(2).trim();
747
+ const nodeMatch = c.match(/^node:\s*(.+)$/);
748
+ if (nodeMatch) {
749
+ inTargetBlock = nodeMatch[1].trim() === nodeId;
750
+ }
751
+ if (inTargetBlock && c.startsWith("status:")) {
752
+ updatedHeaderLines.push(`%% status: ${newStatus}`);
753
+ continue;
754
+ }
755
+ } else {
756
+ inTargetBlock = false;
757
+ }
758
+ updatedHeaderLines.push(line);
759
+ }
760
+
761
+ // Recompute plan-level comments
762
+ const updatedNodes = parseNodes(updatedGraphLines);
763
+ const updatedEdges = parseEdges(updatedGraphLines);
764
+ const readyNodes = computeReady(updatedNodes, updatedEdges);
765
+ const blockedNodes = computeBlocked(updatedNodes, updatedEdges);
766
+
767
+ // Build new status comment
768
+ const statusStr = updatedNodes.map((n) => `${n.id}=${n.status}`).join(", ");
769
+ const readyStr = readyNodes.length > 0 ? readyNodes.join(", ") : "none";
770
+ const blockedStr = blockedNodes.length > 0 ? blockedNodes.join(", ") : "none";
771
+ const nextAction = readyNodes.length > 0 ? `Start ${readyNodes[0]}` : "No ready tasks";
772
+
773
+ // Replace plan-level comments in header
774
+ const result = [];
775
+ let replacedStatus = false,
776
+ replacedReady = false,
777
+ replacedBlocked = false,
778
+ replacedNext = false;
779
+
780
+ const firstNodeIdx = updatedHeaderLines.findIndex((l) => l.trim().startsWith("%% node:"));
781
+
782
+ for (let i = 0; i < updatedHeaderLines.length; i++) {
783
+ const line = updatedHeaderLines[i];
784
+ const trimmed = line.trim();
785
+ if (trimmed.startsWith("%%")) {
786
+ const c = trimmed.slice(2).trim();
787
+ // Plan-level comments come before first node block
788
+ if (c.startsWith("status:") && !replacedStatus) {
789
+ if (i < firstNodeIdx || firstNodeIdx === -1) {
790
+ result.push(`%% status: ${statusStr}`);
791
+ replacedStatus = true;
792
+ continue;
793
+ }
794
+ }
795
+ if (c.startsWith("ready:") && !replacedReady) {
796
+ result.push(`%% ready: ${readyStr}`);
797
+ replacedReady = true;
798
+ continue;
799
+ }
800
+ if (c.startsWith("blocked:") && !replacedBlocked) {
801
+ // Plan-level blocked
802
+ if (i < firstNodeIdx || firstNodeIdx === -1) {
803
+ result.push(`%% blocked: ${blockedStr}`);
804
+ replacedBlocked = true;
805
+ continue;
806
+ }
807
+ }
808
+ if (c.startsWith("next-action:") && !replacedNext) {
809
+ result.push(`%% next-action: ${nextAction}`);
810
+ replacedNext = true;
811
+ continue;
812
+ }
813
+ }
814
+ result.push(line);
815
+ }
816
+
817
+ const finalContent = [...result, ...updatedGraphLines].join("\n");
818
+
819
+ // Verify topology unchanged
820
+ const postNodes = parseNodes(updatedGraphLines);
821
+ const postEdges = parseEdges(updatedGraphLines);
822
+ const postNodeSet = new Set(postNodes.map((n) => n.id));
823
+ const postEdgeSet = new Set(postEdges.map((e) => `${e.from}->${e.to}`));
824
+
825
+ const nodeSetEqual =
826
+ preNodeSet.size === postNodeSet.size && [...preNodeSet].every((id) => postNodeSet.has(id));
827
+ const edgeSetEqual =
828
+ preEdgeSet.size === postEdgeSet.size && [...preEdgeSet].every((e) => postEdgeSet.has(e));
829
+
830
+ if (!nodeSetEqual || !edgeSetEqual) {
831
+ process.stderr.write("Error: Topology changed during status update. Aborting.\n");
832
+ process.exit(1);
833
+ }
834
+
835
+ writeFileSync(filePath, finalContent);
836
+
837
+ const summary = { updated: true, nodeId, status: newStatus, ready: readyNodes };
838
+ process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
839
+ }
840
+
841
+ function sortMetadata(filePath) {
842
+ const content = safeReadFile(filePath);
843
+
844
+ if (content.includes("stateDiagram-v2")) {
845
+ process.stderr.write("Error: stateDiagram-v2 is not supported by this tool.\n");
846
+ process.exit(1);
847
+ }
848
+
849
+ const lines = content.split("\n");
850
+ const flowchartIdx = lines.findIndex((l) => /^\s*flowchart\s+TD/i.test(l));
851
+
852
+ if (flowchartIdx === -1) {
853
+ process.stderr.write("Error: No flowchart TD found in file.\n");
854
+ process.exit(1);
855
+ }
856
+
857
+ const headerLines = lines.slice(0, flowchartIdx);
858
+ const graphLines = lines.slice(flowchartIdx);
859
+
860
+ // Separate plan-level comments from node metadata blocks
861
+ const planComments = [];
862
+ const nodeBlocks = []; // array of { id, lines }
863
+ let currentBlock = null;
864
+
865
+ for (const line of headerLines) {
866
+ const trimmed = line.trim();
867
+ if (trimmed.startsWith("%%")) {
868
+ const c = trimmed.slice(2).trim();
869
+ const nodeMatch = c.match(/^node:\s*(.+)$/);
870
+ if (nodeMatch) {
871
+ if (currentBlock) nodeBlocks.push(currentBlock);
872
+ currentBlock = { id: nodeMatch[1].trim(), lines: [line] };
873
+ continue;
874
+ }
875
+ if (currentBlock) {
876
+ currentBlock.lines.push(line);
877
+ continue;
878
+ }
879
+ } else if (trimmed === "" && currentBlock) {
880
+ // Empty line after a node block — keep as separator
881
+ currentBlock.lines.push(line);
882
+ nodeBlocks.push(currentBlock);
883
+ currentBlock = null;
884
+ continue;
885
+ } else if (currentBlock) {
886
+ // Non-comment line ends the block
887
+ nodeBlocks.push(currentBlock);
888
+ currentBlock = null;
889
+ }
890
+
891
+ if (!currentBlock) {
892
+ planComments.push(line);
893
+ }
894
+ }
895
+ if (currentBlock) nodeBlocks.push(currentBlock);
896
+
897
+ // Sort node blocks by ID
898
+ nodeBlocks.sort((a, b) => a.id.localeCompare(b.id));
899
+
900
+ // Reassemble
901
+ const sortedHeader = [...planComments, ...nodeBlocks.flatMap((block) => block.lines)];
902
+
903
+ // Ensure blank line between header and flowchart
904
+ const lastHeaderLine = sortedHeader[sortedHeader.length - 1];
905
+ if (lastHeaderLine && lastHeaderLine.trim() !== "") {
906
+ sortedHeader.push("");
907
+ }
908
+
909
+ const finalContent = [...sortedHeader, ...graphLines].join("\n");
910
+ writeFileSync(filePath, finalContent);
911
+
912
+ const sortedIds = nodeBlocks.map((b) => b.id);
913
+ process.stdout.write(`${JSON.stringify({ sorted: true, order: sortedIds }, null, 2)}\n`);
914
+ }
915
+
916
+ // --- CLI ---
917
+
918
+ const [, , command, filePath, ...args] = process.argv;
919
+
920
+ if (!command || !filePath) {
921
+ process.stderr.write("Usage: manage-diagram.mjs <command> <file> [args...]\n");
922
+ process.stderr.write("Commands: inspect, ready, validate, status, sort-metadata, regenerate\n");
923
+ process.exit(1);
924
+ }
925
+
926
+ switch (command) {
927
+ case "inspect":
928
+ inspect(filePath);
929
+ break;
930
+ case "ready":
931
+ ready(filePath);
932
+ break;
933
+ case "validate": {
934
+ const metaIdx = args.indexOf("--metadata");
935
+ const metaPath = metaIdx !== -1 ? args[metaIdx + 1] : null;
936
+ validate(filePath, metaPath);
937
+ break;
938
+ }
939
+ case "status":
940
+ if (args.length < 2) {
941
+ process.stderr.write("Usage: manage-diagram.mjs status <file> <nodeId> <newStatus>\n");
942
+ process.exit(1);
943
+ }
944
+ status(filePath, args[0], args[1]);
945
+ break;
946
+ case "sort-metadata":
947
+ sortMetadata(filePath);
948
+ break;
949
+ case "regenerate": {
950
+ const metaIdx = args.indexOf("--metadata");
951
+ if (metaIdx === -1 || !args[metaIdx + 1]) {
952
+ process.stderr.write(
953
+ "Usage: manage-diagram.mjs regenerate <file> --metadata <metadata.json>\n",
954
+ );
955
+ process.exit(1);
956
+ }
957
+ regenerate(filePath, args[metaIdx + 1]);
958
+ break;
959
+ }
960
+ default:
961
+ process.stderr.write(`Unknown command: ${command}\n`);
962
+ process.exit(1);
963
+ }