@luanpdd/kit-mcp 1.26.0 → 1.28.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 (326) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -914
  3. package/gates/agent-no-recursive-dispatch.md +45 -11
  4. package/kit/COMANDOS.md +138 -138
  5. package/kit/README.md +76 -76
  6. package/kit/agents/advisor-researcher.md +106 -106
  7. package/kit/agents/assumptions-analyzer.md +107 -107
  8. package/kit/agents/audit-log-implementer.md +1 -1
  9. package/kit/agents/auditor-consistencia-isolamento.md +1 -1
  10. package/kit/agents/b2b-saas-architect.md +1 -1
  11. package/kit/agents/cascading-failures-auditor.md +1 -1
  12. package/kit/agents/codebase-mapper.md +768 -768
  13. package/kit/agents/crm-pipeline-implementer.md +1 -1
  14. package/kit/agents/debugger.md +813 -813
  15. package/kit/agents/detector-tenant-quente.md +1 -1
  16. package/kit/agents/evolution-go-integrator.md +1 -1
  17. package/kit/agents/example-reviewer.md +21 -21
  18. package/kit/agents/executor.md +564 -564
  19. package/kit/agents/integration-checker.md +200 -200
  20. package/kit/agents/invite-flow-implementer.md +1 -1
  21. package/kit/agents/legacy-characterizer.md +1 -1
  22. package/kit/agents/lgpd-compliance-auditor.md +1 -1
  23. package/kit/agents/multi-tenant-isolation-auditor.md +1 -1
  24. package/kit/agents/multi-tenant-rls-writer.md +1 -1
  25. package/kit/agents/nyquist-auditor.md +178 -178
  26. package/kit/agents/observability-coverage-auditor.md +1 -1
  27. package/kit/agents/org-onboarding-implementer.md +1 -1
  28. package/kit/agents/payload-capture-instrumenter.md +1 -1
  29. package/kit/agents/phase-researcher.md +696 -696
  30. package/kit/agents/plan-checker.md +272 -272
  31. package/kit/agents/planner.md +922 -922
  32. package/kit/agents/project-researcher.md +652 -652
  33. package/kit/agents/refactor-safety-auditor.md +1 -1
  34. package/kit/agents/release-pipeline-auditor.md +11 -0
  35. package/kit/agents/research-synthesizer.md +245 -245
  36. package/kit/agents/roadmapper.md +677 -677
  37. package/kit/agents/seam-finder.md +1 -1
  38. package/kit/agents/shotgun-surgery-detector.md +1 -1
  39. package/kit/agents/supabase-architect.md +14 -0
  40. package/kit/agents/supabase-branching-architect.md +562 -0
  41. package/kit/agents/supabase-cicd-pipeline-implementer.md +777 -0
  42. package/kit/agents/supabase-column-privileges-writer.md +1 -1
  43. package/kit/agents/supabase-migration-writer.md +13 -1
  44. package/kit/agents/supabase-rbac-implementer.md +1 -1
  45. package/kit/agents/supabase-rls-hardener.md +1 -1
  46. package/kit/agents/supabase-rls-writer.md +1 -1
  47. package/kit/agents/supabase-roles-implementer.md +1 -1
  48. package/kit/agents/super-admin-implementer.md +1 -1
  49. package/kit/agents/ui-auditor.md +437 -437
  50. package/kit/agents/ui-checker.md +302 -302
  51. package/kit/agents/ui-researcher.md +355 -355
  52. package/kit/agents/user-profiler.md +175 -175
  53. package/kit/agents/validador-evolucao-schema.md +1 -1
  54. package/kit/agents/verifier.md +728 -728
  55. package/kit/commands/adicionar-backlog.md +75 -75
  56. package/kit/commands/adicionar-fase.md +42 -42
  57. package/kit/commands/adicionar-tarefa.md +45 -45
  58. package/kit/commands/adicionar-testes.md +41 -41
  59. package/kit/commands/ajuda.md +21 -21
  60. package/kit/commands/atualizar.md +37 -37
  61. package/kit/commands/auditar-cascading.md +1 -1
  62. package/kit/commands/auditar-marco.md +179 -179
  63. package/kit/commands/auditar-observabilidade-cobertura.md +1 -1
  64. package/kit/commands/auditar-refactor.md +1 -1
  65. package/kit/commands/auditar-release.md +1 -1
  66. package/kit/commands/auditar-uat.md +23 -23
  67. package/kit/commands/autonomo.md +40 -40
  68. package/kit/commands/branch-pr.md +24 -24
  69. package/kit/commands/burn-rate-status.md +1 -1
  70. package/kit/commands/capturar-payloads.md +1 -1
  71. package/kit/commands/caracterizar.md +1 -1
  72. package/kit/commands/concluir-marco.md +247 -247
  73. package/kit/commands/configuracoes.md +36 -36
  74. package/kit/commands/dados-distribuidos.md +1 -1
  75. package/kit/commands/definir-perfil.md +10 -10
  76. package/kit/commands/depurar.md +190 -190
  77. package/kit/commands/detectar-duplicacao.md +1 -1
  78. package/kit/commands/discutir-fase.md +131 -131
  79. package/kit/commands/encontrar-seams.md +1 -1
  80. package/kit/commands/entrar-discord.md +17 -17
  81. package/kit/commands/estatisticas.md +18 -18
  82. package/kit/commands/example-greeting.md +33 -33
  83. package/kit/commands/executar-fase.md +58 -58
  84. package/kit/commands/expresso.md +56 -56
  85. package/kit/commands/fase-ui.md +34 -34
  86. package/kit/commands/fazer.md +57 -57
  87. package/kit/commands/fio.md +125 -125
  88. package/kit/commands/fluxos-trabalho.md +64 -64
  89. package/kit/commands/forense.md +176 -176
  90. package/kit/commands/gerenciador.md +38 -38
  91. package/kit/commands/inserir-fase.md +31 -31
  92. package/kit/commands/legacy.md +1 -1
  93. package/kit/commands/limpeza.md +17 -17
  94. package/kit/commands/listar-hipoteses-fase.md +45 -45
  95. package/kit/commands/listar-workspaces.md +18 -18
  96. package/kit/commands/load-shedding.md +1 -1
  97. package/kit/commands/mapear-codebase.md +70 -70
  98. package/kit/commands/multi-tenant.md +1 -1
  99. package/kit/commands/nota.md +33 -33
  100. package/kit/commands/novo-marco.md +43 -43
  101. package/kit/commands/novo-projeto.md +41 -41
  102. package/kit/commands/novo-workspace.md +43 -43
  103. package/kit/commands/pausar-trabalho.md +37 -37
  104. package/kit/commands/perfil-usuario.md +45 -45
  105. package/kit/commands/pesquisar-fase.md +195 -195
  106. package/kit/commands/planejar-fase.md +67 -67
  107. package/kit/commands/planejar-lacunas.md +33 -33
  108. package/kit/commands/plantar-ideia.md +25 -25
  109. package/kit/commands/progresso.md +24 -24
  110. package/kit/commands/proximo.md +30 -30
  111. package/kit/commands/publicar.md +490 -490
  112. package/kit/commands/rapido.md +35 -35
  113. package/kit/commands/reaplicar-patches.md +124 -124
  114. package/kit/commands/refactor-seguro.md +1 -1
  115. package/kit/commands/relatorio-sessao.md +19 -19
  116. package/kit/commands/remover-fase.md +31 -31
  117. package/kit/commands/remover-workspace.md +26 -26
  118. package/kit/commands/resumo-marco.md +50 -50
  119. package/kit/commands/retomar-trabalho.md +40 -40
  120. package/kit/commands/revisar-backlog.md +60 -60
  121. package/kit/commands/revisar-ui.md +32 -32
  122. package/kit/commands/revisar.md +37 -37
  123. package/kit/commands/saude.md +21 -21
  124. package/kit/commands/setup-notion.md +93 -93
  125. package/kit/commands/storytelling.md +1 -1
  126. package/kit/commands/supabase.md +1 -1
  127. package/kit/commands/sync-main.md +68 -68
  128. package/kit/commands/validar-fase.md +35 -35
  129. package/kit/commands/verificar-tarefas.md +44 -44
  130. package/kit/commands/verificar-trabalho.md +64 -64
  131. package/kit/file-manifest.json +93 -86
  132. package/kit/framework/bin/lib/commands.cjs +959 -959
  133. package/kit/framework/bin/lib/config.cjs +442 -442
  134. package/kit/framework/bin/lib/core.cjs +1230 -1230
  135. package/kit/framework/bin/lib/frontmatter.cjs +336 -336
  136. package/kit/framework/bin/lib/init.cjs +1442 -1442
  137. package/kit/framework/bin/lib/milestone.cjs +252 -252
  138. package/kit/framework/bin/lib/model-profiles.cjs +68 -68
  139. package/kit/framework/bin/lib/phase.cjs +888 -888
  140. package/kit/framework/bin/lib/profile-output.cjs +952 -952
  141. package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
  142. package/kit/framework/bin/lib/roadmap.cjs +329 -329
  143. package/kit/framework/bin/lib/security.cjs +382 -382
  144. package/kit/framework/bin/lib/state.cjs +1031 -1031
  145. package/kit/framework/bin/lib/template.cjs +222 -222
  146. package/kit/framework/bin/lib/uat.cjs +282 -282
  147. package/kit/framework/bin/lib/verify.cjs +888 -888
  148. package/kit/framework/bin/lib/workstream.cjs +491 -491
  149. package/kit/framework/bin/tools.cjs +918 -918
  150. package/kit/framework/commands/workstreams.md +63 -63
  151. package/kit/framework/references/checkpoints.md +778 -778
  152. package/kit/framework/references/continuation-format.md +249 -249
  153. package/kit/framework/references/decimal-phase-calculation.md +64 -64
  154. package/kit/framework/references/git-integration.md +295 -295
  155. package/kit/framework/references/git-planning-commit.md +38 -38
  156. package/kit/framework/references/model-profile-resolution.md +36 -36
  157. package/kit/framework/references/model-profiles.md +139 -139
  158. package/kit/framework/references/phase-argument-parsing.md +61 -61
  159. package/kit/framework/references/planning-config.md +202 -202
  160. package/kit/framework/references/questioning.md +162 -162
  161. package/kit/framework/references/tdd.md +263 -263
  162. package/kit/framework/references/ui-brand.md +160 -160
  163. package/kit/framework/references/user-profiling.md +657 -657
  164. package/kit/framework/references/verification-patterns.md +612 -612
  165. package/kit/framework/references/workstream-flag.md +58 -58
  166. package/kit/framework/templates/DEBUG.md +164 -164
  167. package/kit/framework/templates/UAT.md +265 -265
  168. package/kit/framework/templates/UI-SPEC.md +100 -100
  169. package/kit/framework/templates/VALIDATION.md +76 -76
  170. package/kit/framework/templates/claude-md.md +122 -122
  171. package/kit/framework/templates/codebase/architecture.md +185 -185
  172. package/kit/framework/templates/codebase/concerns.md +205 -205
  173. package/kit/framework/templates/codebase/conventions.md +204 -204
  174. package/kit/framework/templates/codebase/integrations.md +192 -192
  175. package/kit/framework/templates/codebase/stack.md +158 -158
  176. package/kit/framework/templates/codebase/structure.md +199 -199
  177. package/kit/framework/templates/codebase/testing.md +301 -301
  178. package/kit/framework/templates/config.json +44 -44
  179. package/kit/framework/templates/context.md +352 -352
  180. package/kit/framework/templates/continue-here.md +78 -78
  181. package/kit/framework/templates/copilot-instructions.md +7 -7
  182. package/kit/framework/templates/debug-subagent-prompt.md +91 -91
  183. package/kit/framework/templates/dev-preferences.md +20 -20
  184. package/kit/framework/templates/discovery.md +146 -146
  185. package/kit/framework/templates/discussion-log.md +63 -63
  186. package/kit/framework/templates/milestone-archive.md +123 -123
  187. package/kit/framework/templates/milestone.md +115 -115
  188. package/kit/framework/templates/phase-prompt.md +610 -610
  189. package/kit/framework/templates/planner-subagent-prompt.md +117 -117
  190. package/kit/framework/templates/project.md +186 -186
  191. package/kit/framework/templates/requirements.md +231 -231
  192. package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
  193. package/kit/framework/templates/research-project/FEATURES.md +147 -147
  194. package/kit/framework/templates/research-project/PITFALLS.md +200 -200
  195. package/kit/framework/templates/research-project/STACK.md +120 -120
  196. package/kit/framework/templates/research-project/SUMMARY.md +170 -170
  197. package/kit/framework/templates/research.md +419 -419
  198. package/kit/framework/templates/retrospective.md +54 -54
  199. package/kit/framework/templates/roadmap.md +202 -202
  200. package/kit/framework/templates/state.md +176 -176
  201. package/kit/framework/templates/summary-complex.md +59 -59
  202. package/kit/framework/templates/summary-minimal.md +41 -41
  203. package/kit/framework/templates/summary-standard.md +48 -48
  204. package/kit/framework/templates/summary.md +209 -209
  205. package/kit/framework/templates/user-profile.md +146 -146
  206. package/kit/framework/templates/user-setup.md +256 -256
  207. package/kit/framework/templates/verification-report.md +258 -258
  208. package/kit/framework/workflows/add-phase.md +112 -112
  209. package/kit/framework/workflows/add-tests.md +351 -351
  210. package/kit/framework/workflows/add-todo.md +158 -158
  211. package/kit/framework/workflows/audit-milestone.md +340 -340
  212. package/kit/framework/workflows/audit-uat.md +109 -109
  213. package/kit/framework/workflows/autonomous.md +891 -891
  214. package/kit/framework/workflows/check-todos.md +177 -177
  215. package/kit/framework/workflows/cleanup.md +152 -152
  216. package/kit/framework/workflows/complete-milestone.md +696 -696
  217. package/kit/framework/workflows/diagnose-issues.md +231 -231
  218. package/kit/framework/workflows/discovery-phase.md +289 -289
  219. package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
  220. package/kit/framework/workflows/discuss-phase.md +784 -784
  221. package/kit/framework/workflows/do.md +104 -104
  222. package/kit/framework/workflows/execute-phase.md +838 -838
  223. package/kit/framework/workflows/execute-plan.md +510 -510
  224. package/kit/framework/workflows/fast.md +102 -102
  225. package/kit/framework/workflows/forensics.md +265 -265
  226. package/kit/framework/workflows/health.md +181 -181
  227. package/kit/framework/workflows/help.md +619 -619
  228. package/kit/framework/workflows/insert-phase.md +130 -130
  229. package/kit/framework/workflows/list-phase-assumptions.md +178 -178
  230. package/kit/framework/workflows/list-workspaces.md +56 -56
  231. package/kit/framework/workflows/manager.md +362 -362
  232. package/kit/framework/workflows/map-codebase.md +377 -377
  233. package/kit/framework/workflows/milestone-summary.md +223 -223
  234. package/kit/framework/workflows/new-milestone.md +486 -486
  235. package/kit/framework/workflows/new-project.md +1159 -1159
  236. package/kit/framework/workflows/new-workspace.md +237 -237
  237. package/kit/framework/workflows/next.md +97 -97
  238. package/kit/framework/workflows/node-repair.md +92 -92
  239. package/kit/framework/workflows/note.md +156 -156
  240. package/kit/framework/workflows/pause-work.md +176 -176
  241. package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
  242. package/kit/framework/workflows/plan-phase.md +765 -765
  243. package/kit/framework/workflows/plant-seed.md +169 -169
  244. package/kit/framework/workflows/pr-branch.md +129 -129
  245. package/kit/framework/workflows/profile-user.md +450 -450
  246. package/kit/framework/workflows/progress.md +507 -507
  247. package/kit/framework/workflows/quick.md +757 -757
  248. package/kit/framework/workflows/remove-phase.md +155 -155
  249. package/kit/framework/workflows/remove-workspace.md +90 -90
  250. package/kit/framework/workflows/research-phase.md +82 -82
  251. package/kit/framework/workflows/resume-project.md +326 -326
  252. package/kit/framework/workflows/review.md +228 -228
  253. package/kit/framework/workflows/session-report.md +146 -146
  254. package/kit/framework/workflows/settings.md +283 -283
  255. package/kit/framework/workflows/ship.md +228 -228
  256. package/kit/framework/workflows/stats.md +60 -60
  257. package/kit/framework/workflows/transition.md +671 -671
  258. package/kit/framework/workflows/ui-phase.md +302 -302
  259. package/kit/framework/workflows/ui-review.md +165 -165
  260. package/kit/framework/workflows/update.md +323 -323
  261. package/kit/framework/workflows/validate-phase.md +174 -174
  262. package/kit/framework/workflows/verify-phase.md +252 -252
  263. package/kit/framework/workflows/verify-work.md +637 -637
  264. package/kit/hooks/check-update.js +118 -118
  265. package/kit/hooks/context-monitor.js +163 -163
  266. package/kit/hooks/prompt-guard.js +103 -103
  267. package/kit/hooks/statusline.js +125 -125
  268. package/kit/hooks/workflow-guard.js +101 -101
  269. package/kit/settings.json +45 -45
  270. package/kit/skills/_shared-supabase/glossary.md +10 -0
  271. package/kit/skills/ai-prompt-characterization/SKILL.md +1 -1
  272. package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +1 -1
  273. package/kit/skills/audit-log-multi-tenant/SKILL.md +1 -1
  274. package/kit/skills/b2b-saas-architecture/SKILL.md +1 -1
  275. package/kit/skills/consistencia-leitura-replica/SKILL.md +1 -1
  276. package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +1 -1
  277. package/kit/skills/escolha-modelo-consistencia/SKILL.md +1 -1
  278. package/kit/skills/evolucao-schema-compativel/SKILL.md +1 -1
  279. package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +1 -1
  280. package/kit/skills/example-skill/SKILL.md +42 -42
  281. package/kit/skills/legacy-api-only-applications/SKILL.md +1 -1
  282. package/kit/skills/legacy-characterization-tests/SKILL.md +1 -1
  283. package/kit/skills/legacy-effect-analysis/SKILL.md +1 -1
  284. package/kit/skills/legacy-extract-class/SKILL.md +1 -1
  285. package/kit/skills/legacy-programming-by-difference/SKILL.md +1 -1
  286. package/kit/skills/legacy-seams-and-test-harness/SKILL.md +1 -1
  287. package/kit/skills/legacy-shotgun-surgery/SKILL.md +1 -1
  288. package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +1 -1
  289. package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +1 -1
  290. package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +1 -1
  291. package/kit/skills/member-invite-flow/SKILL.md +1 -1
  292. package/kit/skills/member-management-react-shadcn/SKILL.md +1 -1
  293. package/kit/skills/multi-tenant-performance-scaling/SKILL.md +1 -1
  294. package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +1 -1
  295. package/kit/skills/org-onboarding-flow/SKILL.md +1 -1
  296. package/kit/skills/org-switcher-react-pattern/SKILL.md +1 -1
  297. package/kit/skills/permission-gate-react-pattern/SKILL.md +1 -1
  298. package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +1 -1
  299. package/kit/skills/pre-refactor-characterization/SKILL.md +1 -1
  300. package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +1 -1
  301. package/kit/skills/streams-eventos-cdc/SKILL.md +1 -1
  302. package/kit/skills/supabase-branching-workflow/SKILL.md +544 -0
  303. package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -0
  304. package/kit/skills/supabase-column-level-security/SKILL.md +1 -1
  305. package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -0
  306. package/kit/skills/supabase-custom-claims-rbac/SKILL.md +1 -1
  307. package/kit/skills/supabase-migration-repair/SKILL.md +823 -0
  308. package/kit/skills/supabase-migrations/SKILL.md +1 -1
  309. package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -0
  310. package/kit/skills/supabase-postgres-roles/SKILL.md +1 -1
  311. package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +1 -1
  312. package/kit/skills/supabase-rls-policies/SKILL.md +1 -1
  313. package/kit/skills/super-admin-platform-pattern/SKILL.md +1 -1
  314. package/kit/skills/tenant-quente-mitigacao/SKILL.md +1 -1
  315. package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +1 -1
  316. package/package.json +63 -63
  317. package/src/cli/index.js +345 -6
  318. package/src/cli/render.js +7 -0
  319. package/src/core/kit.js +216 -216
  320. package/src/core/logger.js +170 -0
  321. package/src/core/notify.js +60 -0
  322. package/src/core/reflect.js +247 -247
  323. package/src/core/reverse-sync.js +372 -372
  324. package/src/core/sync.js +418 -418
  325. package/src/core/watch.js +121 -121
  326. package/src/mcp-server/index.js +65 -2
@@ -1,491 +1,491 @@
1
- /**
2
- * Workstream — CRUD operations for workstream namespacing
3
- *
4
- * Workstreams enable parallel milestones by scoping ROADMAP.md, STATE.md,
5
- * REQUIREMENTS.md, and phases/ into .planning/workstreams/{name}/ directories.
6
- *
7
- * When no workstreams/ directory exists, framework operates in "flat mode" with
8
- * everything at .planning/ — backward compatible with pre-workstream installs.
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
- const { output, error, planningPaths, planningRoot, toPosixPath, getMilestoneInfo, generateSlugInternal, setActiveWorkstream, getActiveWorkstream, filterPlanFiles, filterSummaryFiles, readSubdirectories } = require('./core.cjs');
14
- const { stateExtractField } = require('./state.cjs');
15
-
16
- // ─── Migration ──────────────────────────────────────────────────────────────
17
-
18
- /**
19
- * Migrate flat .planning/ layout to workstream mode.
20
- * Moves per-workstream files (ROADMAP.md, STATE.md, REQUIREMENTS.md, phases/)
21
- * into .planning/workstreams/{name}/. Shared files (PROJECT.md, config.json,
22
- * milestones/, research/, codebase/, todos/) stay in place.
23
- */
24
- function migrateToWorkstreams(cwd, workstreamName) {
25
- if (!workstreamName || /[/\\]/.test(workstreamName) || workstreamName === '.' || workstreamName === '..') {
26
- throw new Error('Invalid workstream name for migration');
27
- }
28
-
29
- const baseDir = planningRoot(cwd);
30
- const wsDir = path.join(baseDir, 'workstreams', workstreamName);
31
-
32
- if (fs.existsSync(path.join(baseDir, 'workstreams'))) {
33
- throw new Error('Already in workstream mode — .planning/workstreams/ exists');
34
- }
35
-
36
- const toMove = [
37
- { name: 'ROADMAP.md', type: 'file' },
38
- { name: 'STATE.md', type: 'file' },
39
- { name: 'REQUIREMENTS.md', type: 'file' },
40
- { name: 'phases', type: 'dir' },
41
- ];
42
-
43
- fs.mkdirSync(wsDir, { recursive: true });
44
-
45
- const filesMoved = [];
46
- try {
47
- for (const item of toMove) {
48
- const src = path.join(baseDir, item.name);
49
- if (fs.existsSync(src)) {
50
- const dest = path.join(wsDir, item.name);
51
- fs.renameSync(src, dest);
52
- filesMoved.push(item.name);
53
- }
54
- }
55
- } catch (err) {
56
- for (const name of filesMoved) {
57
- try { fs.renameSync(path.join(wsDir, name), path.join(baseDir, name)); } catch {}
58
- }
59
- try { fs.rmSync(wsDir, { recursive: true }); } catch {}
60
- try { fs.rmdirSync(path.join(baseDir, 'workstreams')); } catch {}
61
- throw err;
62
- }
63
-
64
- return { migrated: true, workstream: workstreamName, files_moved: filesMoved };
65
- }
66
-
67
- // ─── CRUD Commands ──────────────────────────────────────────────────────────
68
-
69
- function cmdWorkstreamCreate(cwd, name, options, raw) {
70
- if (!name) {
71
- error('workstream name required. Usage: workstream create <name>');
72
- }
73
-
74
- const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
75
- if (!slug) {
76
- error('Invalid workstream name — must contain at least one alphanumeric character');
77
- }
78
-
79
- const baseDir = planningRoot(cwd);
80
- if (!fs.existsSync(baseDir)) {
81
- error('.planning/ directory not found — run /new-project first');
82
- }
83
-
84
- const wsRoot = path.join(baseDir, 'workstreams');
85
- const wsDir = path.join(wsRoot, slug);
86
-
87
- if (fs.existsSync(wsDir) && fs.existsSync(path.join(wsDir, 'STATE.md'))) {
88
- output({ created: false, error: 'already_exists', workstream: slug, path: toPosixPath(path.relative(cwd, wsDir)) }, raw);
89
- return;
90
- }
91
-
92
- const isFlatMode = !fs.existsSync(wsRoot);
93
- let migration = null;
94
- if (isFlatMode && options.migrate !== false) {
95
- const hasExistingWork = fs.existsSync(path.join(baseDir, 'ROADMAP.md')) ||
96
- fs.existsSync(path.join(baseDir, 'STATE.md')) ||
97
- fs.existsSync(path.join(baseDir, 'phases'));
98
-
99
- if (hasExistingWork) {
100
- const migrateName = options.migrateName || null;
101
- let existingWsName;
102
- if (migrateName) {
103
- existingWsName = migrateName;
104
- } else {
105
- try {
106
- const milestone = getMilestoneInfo(cwd);
107
- existingWsName = generateSlugInternal(milestone.name) || 'default';
108
- } catch {
109
- existingWsName = 'default';
110
- }
111
- }
112
-
113
- try {
114
- migration = migrateToWorkstreams(cwd, existingWsName);
115
- } catch (e) {
116
- output({ created: false, error: 'migration_failed', message: e.message }, raw);
117
- return;
118
- }
119
- } else {
120
- fs.mkdirSync(wsRoot, { recursive: true });
121
- }
122
- }
123
-
124
- fs.mkdirSync(wsDir, { recursive: true });
125
- fs.mkdirSync(path.join(wsDir, 'phases'), { recursive: true });
126
-
127
- const today = new Date().toISOString().split('T')[0];
128
- const stateContent = [
129
- '---',
130
- `workstream: ${slug}`,
131
- `created: ${today}`,
132
- '---',
133
- '',
134
- '# Project State',
135
- '',
136
- '## Current Position',
137
- '**Status:** Not started',
138
- '**Current Phase:** None',
139
- `**Last Activity:** ${today}`,
140
- '**Last Activity Description:** Workstream created',
141
- '',
142
- '## Progress',
143
- '**Phases Complete:** 0',
144
- '**Current Plan:** N/A',
145
- '',
146
- '## Session Continuity',
147
- '**Stopped At:** N/A',
148
- '**Resume File:** None',
149
- '',
150
- ].join('\n');
151
-
152
- const statePath = path.join(wsDir, 'STATE.md');
153
- if (!fs.existsSync(statePath)) {
154
- fs.writeFileSync(statePath, stateContent, 'utf-8');
155
- }
156
-
157
- setActiveWorkstream(cwd, slug);
158
-
159
- const relPath = toPosixPath(path.relative(cwd, wsDir));
160
- output({
161
- created: true,
162
- workstream: slug,
163
- path: relPath,
164
- state_path: relPath + '/STATE.md',
165
- phases_path: relPath + '/phases',
166
- migration: migration || null,
167
- active: true,
168
- }, raw);
169
- }
170
-
171
- function cmdWorkstreamList(cwd, raw) {
172
- const wsRoot = path.join(planningRoot(cwd), 'workstreams');
173
-
174
- if (!fs.existsSync(wsRoot)) {
175
- output({ mode: 'flat', workstreams: [], message: 'No workstreams — operating in flat mode' }, raw);
176
- return;
177
- }
178
-
179
- const entries = fs.readdirSync(wsRoot, { withFileTypes: true });
180
- const workstreams = [];
181
-
182
- for (const entry of entries) {
183
- if (!entry.isDirectory()) continue;
184
-
185
- const wsDir = path.join(wsRoot, entry.name);
186
- const phasesDir = path.join(wsDir, 'phases');
187
-
188
- const phaseDirs = readSubdirectories(phasesDir);
189
- const phaseCount = phaseDirs.length;
190
- let completedCount = 0;
191
- for (const d of phaseDirs) {
192
- try {
193
- const phaseFiles = fs.readdirSync(path.join(phasesDir, d));
194
- const plans = filterPlanFiles(phaseFiles);
195
- const summaries = filterSummaryFiles(phaseFiles);
196
- if (plans.length > 0 && summaries.length >= plans.length) completedCount++;
197
- } catch {}
198
- }
199
-
200
- let status = 'unknown', currentPhase = null;
201
- try {
202
- const stateContent = fs.readFileSync(path.join(wsDir, 'STATE.md'), 'utf-8');
203
- status = stateExtractField(stateContent, 'Status') || 'unknown';
204
- currentPhase = stateExtractField(stateContent, 'Current Phase');
205
- } catch {}
206
-
207
- workstreams.push({
208
- name: entry.name,
209
- path: toPosixPath(path.relative(cwd, wsDir)),
210
- has_roadmap: fs.existsSync(path.join(wsDir, 'ROADMAP.md')),
211
- has_state: fs.existsSync(path.join(wsDir, 'STATE.md')),
212
- status,
213
- current_phase: currentPhase,
214
- phase_count: phaseCount,
215
- completed_phases: completedCount,
216
- });
217
- }
218
-
219
- output({ mode: 'workstream', workstreams, count: workstreams.length }, raw);
220
- }
221
-
222
- function cmdWorkstreamStatus(cwd, name, raw) {
223
- if (!name) error('workstream name required. Usage: workstream status <name>');
224
- if (/[/\\]/.test(name) || name === '.' || name === '..') error('Invalid workstream name');
225
-
226
- const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
227
- if (!fs.existsSync(wsDir)) {
228
- output({ found: false, workstream: name }, raw);
229
- return;
230
- }
231
-
232
- const p = planningPaths(cwd, name);
233
- const relPath = toPosixPath(path.relative(cwd, wsDir));
234
-
235
- const files = {
236
- roadmap: fs.existsSync(p.roadmap),
237
- state: fs.existsSync(p.state),
238
- requirements: fs.existsSync(p.requirements),
239
- };
240
-
241
- const phases = [];
242
- for (const dir of readSubdirectories(p.phases).sort()) {
243
- try {
244
- const phaseFiles = fs.readdirSync(path.join(p.phases, dir));
245
- const plans = filterPlanFiles(phaseFiles);
246
- const summaries = filterSummaryFiles(phaseFiles);
247
- phases.push({
248
- directory: dir,
249
- status: summaries.length >= plans.length && plans.length > 0 ? 'complete' :
250
- plans.length > 0 ? 'in_progress' : 'pending',
251
- plan_count: plans.length,
252
- summary_count: summaries.length,
253
- });
254
- } catch {}
255
- }
256
-
257
- let stateInfo = {};
258
- try {
259
- const stateContent = fs.readFileSync(p.state, 'utf-8');
260
- stateInfo = {
261
- status: stateExtractField(stateContent, 'Status') || 'unknown',
262
- current_phase: stateExtractField(stateContent, 'Current Phase'),
263
- last_activity: stateExtractField(stateContent, 'Last Activity'),
264
- };
265
- } catch {}
266
-
267
- output({
268
- found: true,
269
- workstream: name,
270
- path: relPath,
271
- files,
272
- phases,
273
- phase_count: phases.length,
274
- completed_phases: phases.filter(ph => ph.status === 'complete').length,
275
- ...stateInfo,
276
- }, raw);
277
- }
278
-
279
- function cmdWorkstreamComplete(cwd, name, options, raw) {
280
- if (!name) error('workstream name required. Usage: workstream complete <name>');
281
- if (/[/\\]/.test(name) || name === '.' || name === '..') error('Invalid workstream name');
282
-
283
- const root = planningRoot(cwd);
284
- const wsRoot = path.join(root, 'workstreams');
285
- const wsDir = path.join(wsRoot, name);
286
-
287
- if (!fs.existsSync(wsDir)) {
288
- output({ completed: false, error: 'not_found', workstream: name }, raw);
289
- return;
290
- }
291
-
292
- const active = getActiveWorkstream(cwd);
293
- if (active === name) setActiveWorkstream(cwd, null);
294
-
295
- const archiveDir = path.join(root, 'milestones');
296
- const today = new Date().toISOString().split('T')[0];
297
- let archivePath = path.join(archiveDir, `ws-${name}-${today}`);
298
- let suffix = 1;
299
- while (fs.existsSync(archivePath)) {
300
- archivePath = path.join(archiveDir, `ws-${name}-${today}-${suffix++}`);
301
- }
302
-
303
- fs.mkdirSync(archivePath, { recursive: true });
304
-
305
- const filesMoved = [];
306
- try {
307
- const entries = fs.readdirSync(wsDir, { withFileTypes: true });
308
- for (const entry of entries) {
309
- fs.renameSync(path.join(wsDir, entry.name), path.join(archivePath, entry.name));
310
- filesMoved.push(entry.name);
311
- }
312
- } catch (err) {
313
- for (const fname of filesMoved) {
314
- try { fs.renameSync(path.join(archivePath, fname), path.join(wsDir, fname)); } catch {}
315
- }
316
- try { fs.rmSync(archivePath, { recursive: true }); } catch {}
317
- if (active === name) setActiveWorkstream(cwd, name);
318
- output({ completed: false, error: 'archive_failed', message: err.message, workstream: name }, raw);
319
- return;
320
- }
321
-
322
- try { fs.rmdirSync(wsDir); } catch {}
323
-
324
- let remainingWs = 0;
325
- try {
326
- remainingWs = fs.readdirSync(wsRoot, { withFileTypes: true }).filter(e => e.isDirectory()).length;
327
- if (remainingWs === 0) fs.rmdirSync(wsRoot);
328
- } catch {}
329
-
330
- output({
331
- completed: true,
332
- workstream: name,
333
- archived_to: toPosixPath(path.relative(cwd, archivePath)),
334
- remaining_workstreams: remainingWs,
335
- reverted_to_flat: remainingWs === 0,
336
- }, raw);
337
- }
338
-
339
- // ─── Active Workstream Commands ──────────────────────────────────────────────
340
-
341
- function cmdWorkstreamSet(cwd, name, raw) {
342
- if (!name) {
343
- setActiveWorkstream(cwd, null);
344
- output({ active: null, cleared: true }, raw);
345
- return;
346
- }
347
-
348
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
349
- output({ active: null, error: 'invalid_name', message: 'Workstream name must be alphanumeric, hyphens, and underscores only' }, raw);
350
- return;
351
- }
352
-
353
- const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
354
- if (!fs.existsSync(wsDir)) {
355
- output({ active: null, error: 'not_found', workstream: name }, raw);
356
- return;
357
- }
358
-
359
- setActiveWorkstream(cwd, name);
360
- output({ active: name, set: true }, raw, name);
361
- }
362
-
363
- function cmdWorkstreamGet(cwd, raw) {
364
- const active = getActiveWorkstream(cwd);
365
- const wsRoot = path.join(planningRoot(cwd), 'workstreams');
366
- output({ active, mode: fs.existsSync(wsRoot) ? 'workstream' : 'flat' }, raw, active || 'none');
367
- }
368
-
369
- function cmdWorkstreamProgress(cwd, raw) {
370
- const root = planningRoot(cwd);
371
- const wsRoot = path.join(root, 'workstreams');
372
-
373
- if (!fs.existsSync(wsRoot)) {
374
- output({ mode: 'flat', workstreams: [], message: 'No workstreams — operating in flat mode' }, raw);
375
- return;
376
- }
377
-
378
- const active = getActiveWorkstream(cwd);
379
- const entries = fs.readdirSync(wsRoot, { withFileTypes: true });
380
- const workstreams = [];
381
-
382
- for (const entry of entries) {
383
- if (!entry.isDirectory()) continue;
384
-
385
- const wsDir = path.join(wsRoot, entry.name);
386
- const phasesDir = path.join(wsDir, 'phases');
387
-
388
- const phaseDirsProgress = readSubdirectories(phasesDir);
389
- const phaseCount = phaseDirsProgress.length;
390
- let completedCount = 0, totalPlans = 0, completedPlans = 0;
391
- for (const d of phaseDirsProgress) {
392
- try {
393
- const phaseFiles = fs.readdirSync(path.join(phasesDir, d));
394
- const plans = filterPlanFiles(phaseFiles);
395
- const summaries = filterSummaryFiles(phaseFiles);
396
- totalPlans += plans.length;
397
- completedPlans += Math.min(summaries.length, plans.length);
398
- if (plans.length > 0 && summaries.length >= plans.length) completedCount++;
399
- } catch {}
400
- }
401
-
402
- let roadmapPhaseCount = phaseCount;
403
- try {
404
- const roadmapContent = fs.readFileSync(path.join(wsDir, 'ROADMAP.md'), 'utf-8');
405
- const phaseMatches = roadmapContent.match(/^###?\s+Phase\s+\d/gm);
406
- if (phaseMatches) roadmapPhaseCount = phaseMatches.length;
407
- } catch {}
408
-
409
- let status = 'unknown', currentPhase = null;
410
- try {
411
- const stateContent = fs.readFileSync(path.join(wsDir, 'STATE.md'), 'utf-8');
412
- status = stateExtractField(stateContent, 'Status') || 'unknown';
413
- currentPhase = stateExtractField(stateContent, 'Current Phase');
414
- } catch {}
415
-
416
- workstreams.push({
417
- name: entry.name,
418
- active: entry.name === active,
419
- status,
420
- current_phase: currentPhase,
421
- phases: `${completedCount}/${roadmapPhaseCount}`,
422
- plans: `${completedPlans}/${totalPlans}`,
423
- progress_percent: roadmapPhaseCount > 0 ? Math.round((completedCount / roadmapPhaseCount) * 100) : 0,
424
- });
425
- }
426
-
427
- output({ mode: 'workstream', active, workstreams, count: workstreams.length }, raw);
428
- }
429
-
430
- // ─── Collision Detection ────────────────────────────────────────────────────
431
-
432
- /**
433
- * Return other workstreams that are NOT complete.
434
- * Used to detect whether the milestone has active parallel work
435
- * when a workstream finishes its last phase.
436
- */
437
- function getOtherActiveWorkstreams(cwd, excludeWs) {
438
- const wsRoot = path.join(planningRoot(cwd), 'workstreams');
439
- if (!fs.existsSync(wsRoot)) return [];
440
-
441
- const entries = fs.readdirSync(wsRoot, { withFileTypes: true });
442
- const others = [];
443
-
444
- for (const entry of entries) {
445
- if (!entry.isDirectory() || entry.name === excludeWs) continue;
446
-
447
- const wsDir = path.join(wsRoot, entry.name);
448
- const statePath = path.join(wsDir, 'STATE.md');
449
-
450
- let status = 'unknown', currentPhase = null;
451
- try {
452
- const content = fs.readFileSync(statePath, 'utf-8');
453
- status = stateExtractField(content, 'Status') || 'unknown';
454
- currentPhase = stateExtractField(content, 'Current Phase');
455
- } catch {}
456
-
457
- if (status.toLowerCase().includes('milestone complete') ||
458
- status.toLowerCase().includes('archived')) {
459
- continue;
460
- }
461
-
462
- const phasesDir = path.join(wsDir, 'phases');
463
- const phaseDirsOther = readSubdirectories(phasesDir);
464
- const phaseCount = phaseDirsOther.length;
465
- let completedCount = 0;
466
- for (const d of phaseDirsOther) {
467
- try {
468
- const phaseFiles = fs.readdirSync(path.join(phasesDir, d));
469
- const plans = filterPlanFiles(phaseFiles);
470
- const summaries = filterSummaryFiles(phaseFiles);
471
- if (plans.length > 0 && summaries.length >= plans.length) completedCount++;
472
- } catch {}
473
- }
474
-
475
- others.push({ name: entry.name, status, current_phase: currentPhase, phases: `${completedCount}/${phaseCount}` });
476
- }
477
-
478
- return others;
479
- }
480
-
481
- module.exports = {
482
- migrateToWorkstreams,
483
- cmdWorkstreamCreate,
484
- cmdWorkstreamList,
485
- cmdWorkstreamStatus,
486
- cmdWorkstreamComplete,
487
- cmdWorkstreamSet,
488
- cmdWorkstreamGet,
489
- cmdWorkstreamProgress,
490
- getOtherActiveWorkstreams,
491
- };
1
+ /**
2
+ * Workstream — CRUD operations for workstream namespacing
3
+ *
4
+ * Workstreams enable parallel milestones by scoping ROADMAP.md, STATE.md,
5
+ * REQUIREMENTS.md, and phases/ into .planning/workstreams/{name}/ directories.
6
+ *
7
+ * When no workstreams/ directory exists, framework operates in "flat mode" with
8
+ * everything at .planning/ — backward compatible with pre-workstream installs.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { output, error, planningPaths, planningRoot, toPosixPath, getMilestoneInfo, generateSlugInternal, setActiveWorkstream, getActiveWorkstream, filterPlanFiles, filterSummaryFiles, readSubdirectories } = require('./core.cjs');
14
+ const { stateExtractField } = require('./state.cjs');
15
+
16
+ // ─── Migration ──────────────────────────────────────────────────────────────
17
+
18
+ /**
19
+ * Migrate flat .planning/ layout to workstream mode.
20
+ * Moves per-workstream files (ROADMAP.md, STATE.md, REQUIREMENTS.md, phases/)
21
+ * into .planning/workstreams/{name}/. Shared files (PROJECT.md, config.json,
22
+ * milestones/, research/, codebase/, todos/) stay in place.
23
+ */
24
+ function migrateToWorkstreams(cwd, workstreamName) {
25
+ if (!workstreamName || /[/\\]/.test(workstreamName) || workstreamName === '.' || workstreamName === '..') {
26
+ throw new Error('Invalid workstream name for migration');
27
+ }
28
+
29
+ const baseDir = planningRoot(cwd);
30
+ const wsDir = path.join(baseDir, 'workstreams', workstreamName);
31
+
32
+ if (fs.existsSync(path.join(baseDir, 'workstreams'))) {
33
+ throw new Error('Already in workstream mode — .planning/workstreams/ exists');
34
+ }
35
+
36
+ const toMove = [
37
+ { name: 'ROADMAP.md', type: 'file' },
38
+ { name: 'STATE.md', type: 'file' },
39
+ { name: 'REQUIREMENTS.md', type: 'file' },
40
+ { name: 'phases', type: 'dir' },
41
+ ];
42
+
43
+ fs.mkdirSync(wsDir, { recursive: true });
44
+
45
+ const filesMoved = [];
46
+ try {
47
+ for (const item of toMove) {
48
+ const src = path.join(baseDir, item.name);
49
+ if (fs.existsSync(src)) {
50
+ const dest = path.join(wsDir, item.name);
51
+ fs.renameSync(src, dest);
52
+ filesMoved.push(item.name);
53
+ }
54
+ }
55
+ } catch (err) {
56
+ for (const name of filesMoved) {
57
+ try { fs.renameSync(path.join(wsDir, name), path.join(baseDir, name)); } catch {}
58
+ }
59
+ try { fs.rmSync(wsDir, { recursive: true }); } catch {}
60
+ try { fs.rmdirSync(path.join(baseDir, 'workstreams')); } catch {}
61
+ throw err;
62
+ }
63
+
64
+ return { migrated: true, workstream: workstreamName, files_moved: filesMoved };
65
+ }
66
+
67
+ // ─── CRUD Commands ──────────────────────────────────────────────────────────
68
+
69
+ function cmdWorkstreamCreate(cwd, name, options, raw) {
70
+ if (!name) {
71
+ error('workstream name required. Usage: workstream create <name>');
72
+ }
73
+
74
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
75
+ if (!slug) {
76
+ error('Invalid workstream name — must contain at least one alphanumeric character');
77
+ }
78
+
79
+ const baseDir = planningRoot(cwd);
80
+ if (!fs.existsSync(baseDir)) {
81
+ error('.planning/ directory not found — run /new-project first');
82
+ }
83
+
84
+ const wsRoot = path.join(baseDir, 'workstreams');
85
+ const wsDir = path.join(wsRoot, slug);
86
+
87
+ if (fs.existsSync(wsDir) && fs.existsSync(path.join(wsDir, 'STATE.md'))) {
88
+ output({ created: false, error: 'already_exists', workstream: slug, path: toPosixPath(path.relative(cwd, wsDir)) }, raw);
89
+ return;
90
+ }
91
+
92
+ const isFlatMode = !fs.existsSync(wsRoot);
93
+ let migration = null;
94
+ if (isFlatMode && options.migrate !== false) {
95
+ const hasExistingWork = fs.existsSync(path.join(baseDir, 'ROADMAP.md')) ||
96
+ fs.existsSync(path.join(baseDir, 'STATE.md')) ||
97
+ fs.existsSync(path.join(baseDir, 'phases'));
98
+
99
+ if (hasExistingWork) {
100
+ const migrateName = options.migrateName || null;
101
+ let existingWsName;
102
+ if (migrateName) {
103
+ existingWsName = migrateName;
104
+ } else {
105
+ try {
106
+ const milestone = getMilestoneInfo(cwd);
107
+ existingWsName = generateSlugInternal(milestone.name) || 'default';
108
+ } catch {
109
+ existingWsName = 'default';
110
+ }
111
+ }
112
+
113
+ try {
114
+ migration = migrateToWorkstreams(cwd, existingWsName);
115
+ } catch (e) {
116
+ output({ created: false, error: 'migration_failed', message: e.message }, raw);
117
+ return;
118
+ }
119
+ } else {
120
+ fs.mkdirSync(wsRoot, { recursive: true });
121
+ }
122
+ }
123
+
124
+ fs.mkdirSync(wsDir, { recursive: true });
125
+ fs.mkdirSync(path.join(wsDir, 'phases'), { recursive: true });
126
+
127
+ const today = new Date().toISOString().split('T')[0];
128
+ const stateContent = [
129
+ '---',
130
+ `workstream: ${slug}`,
131
+ `created: ${today}`,
132
+ '---',
133
+ '',
134
+ '# Project State',
135
+ '',
136
+ '## Current Position',
137
+ '**Status:** Not started',
138
+ '**Current Phase:** None',
139
+ `**Last Activity:** ${today}`,
140
+ '**Last Activity Description:** Workstream created',
141
+ '',
142
+ '## Progress',
143
+ '**Phases Complete:** 0',
144
+ '**Current Plan:** N/A',
145
+ '',
146
+ '## Session Continuity',
147
+ '**Stopped At:** N/A',
148
+ '**Resume File:** None',
149
+ '',
150
+ ].join('\n');
151
+
152
+ const statePath = path.join(wsDir, 'STATE.md');
153
+ if (!fs.existsSync(statePath)) {
154
+ fs.writeFileSync(statePath, stateContent, 'utf-8');
155
+ }
156
+
157
+ setActiveWorkstream(cwd, slug);
158
+
159
+ const relPath = toPosixPath(path.relative(cwd, wsDir));
160
+ output({
161
+ created: true,
162
+ workstream: slug,
163
+ path: relPath,
164
+ state_path: relPath + '/STATE.md',
165
+ phases_path: relPath + '/phases',
166
+ migration: migration || null,
167
+ active: true,
168
+ }, raw);
169
+ }
170
+
171
+ function cmdWorkstreamList(cwd, raw) {
172
+ const wsRoot = path.join(planningRoot(cwd), 'workstreams');
173
+
174
+ if (!fs.existsSync(wsRoot)) {
175
+ output({ mode: 'flat', workstreams: [], message: 'No workstreams — operating in flat mode' }, raw);
176
+ return;
177
+ }
178
+
179
+ const entries = fs.readdirSync(wsRoot, { withFileTypes: true });
180
+ const workstreams = [];
181
+
182
+ for (const entry of entries) {
183
+ if (!entry.isDirectory()) continue;
184
+
185
+ const wsDir = path.join(wsRoot, entry.name);
186
+ const phasesDir = path.join(wsDir, 'phases');
187
+
188
+ const phaseDirs = readSubdirectories(phasesDir);
189
+ const phaseCount = phaseDirs.length;
190
+ let completedCount = 0;
191
+ for (const d of phaseDirs) {
192
+ try {
193
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, d));
194
+ const plans = filterPlanFiles(phaseFiles);
195
+ const summaries = filterSummaryFiles(phaseFiles);
196
+ if (plans.length > 0 && summaries.length >= plans.length) completedCount++;
197
+ } catch {}
198
+ }
199
+
200
+ let status = 'unknown', currentPhase = null;
201
+ try {
202
+ const stateContent = fs.readFileSync(path.join(wsDir, 'STATE.md'), 'utf-8');
203
+ status = stateExtractField(stateContent, 'Status') || 'unknown';
204
+ currentPhase = stateExtractField(stateContent, 'Current Phase');
205
+ } catch {}
206
+
207
+ workstreams.push({
208
+ name: entry.name,
209
+ path: toPosixPath(path.relative(cwd, wsDir)),
210
+ has_roadmap: fs.existsSync(path.join(wsDir, 'ROADMAP.md')),
211
+ has_state: fs.existsSync(path.join(wsDir, 'STATE.md')),
212
+ status,
213
+ current_phase: currentPhase,
214
+ phase_count: phaseCount,
215
+ completed_phases: completedCount,
216
+ });
217
+ }
218
+
219
+ output({ mode: 'workstream', workstreams, count: workstreams.length }, raw);
220
+ }
221
+
222
+ function cmdWorkstreamStatus(cwd, name, raw) {
223
+ if (!name) error('workstream name required. Usage: workstream status <name>');
224
+ if (/[/\\]/.test(name) || name === '.' || name === '..') error('Invalid workstream name');
225
+
226
+ const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
227
+ if (!fs.existsSync(wsDir)) {
228
+ output({ found: false, workstream: name }, raw);
229
+ return;
230
+ }
231
+
232
+ const p = planningPaths(cwd, name);
233
+ const relPath = toPosixPath(path.relative(cwd, wsDir));
234
+
235
+ const files = {
236
+ roadmap: fs.existsSync(p.roadmap),
237
+ state: fs.existsSync(p.state),
238
+ requirements: fs.existsSync(p.requirements),
239
+ };
240
+
241
+ const phases = [];
242
+ for (const dir of readSubdirectories(p.phases).sort()) {
243
+ try {
244
+ const phaseFiles = fs.readdirSync(path.join(p.phases, dir));
245
+ const plans = filterPlanFiles(phaseFiles);
246
+ const summaries = filterSummaryFiles(phaseFiles);
247
+ phases.push({
248
+ directory: dir,
249
+ status: summaries.length >= plans.length && plans.length > 0 ? 'complete' :
250
+ plans.length > 0 ? 'in_progress' : 'pending',
251
+ plan_count: plans.length,
252
+ summary_count: summaries.length,
253
+ });
254
+ } catch {}
255
+ }
256
+
257
+ let stateInfo = {};
258
+ try {
259
+ const stateContent = fs.readFileSync(p.state, 'utf-8');
260
+ stateInfo = {
261
+ status: stateExtractField(stateContent, 'Status') || 'unknown',
262
+ current_phase: stateExtractField(stateContent, 'Current Phase'),
263
+ last_activity: stateExtractField(stateContent, 'Last Activity'),
264
+ };
265
+ } catch {}
266
+
267
+ output({
268
+ found: true,
269
+ workstream: name,
270
+ path: relPath,
271
+ files,
272
+ phases,
273
+ phase_count: phases.length,
274
+ completed_phases: phases.filter(ph => ph.status === 'complete').length,
275
+ ...stateInfo,
276
+ }, raw);
277
+ }
278
+
279
+ function cmdWorkstreamComplete(cwd, name, options, raw) {
280
+ if (!name) error('workstream name required. Usage: workstream complete <name>');
281
+ if (/[/\\]/.test(name) || name === '.' || name === '..') error('Invalid workstream name');
282
+
283
+ const root = planningRoot(cwd);
284
+ const wsRoot = path.join(root, 'workstreams');
285
+ const wsDir = path.join(wsRoot, name);
286
+
287
+ if (!fs.existsSync(wsDir)) {
288
+ output({ completed: false, error: 'not_found', workstream: name }, raw);
289
+ return;
290
+ }
291
+
292
+ const active = getActiveWorkstream(cwd);
293
+ if (active === name) setActiveWorkstream(cwd, null);
294
+
295
+ const archiveDir = path.join(root, 'milestones');
296
+ const today = new Date().toISOString().split('T')[0];
297
+ let archivePath = path.join(archiveDir, `ws-${name}-${today}`);
298
+ let suffix = 1;
299
+ while (fs.existsSync(archivePath)) {
300
+ archivePath = path.join(archiveDir, `ws-${name}-${today}-${suffix++}`);
301
+ }
302
+
303
+ fs.mkdirSync(archivePath, { recursive: true });
304
+
305
+ const filesMoved = [];
306
+ try {
307
+ const entries = fs.readdirSync(wsDir, { withFileTypes: true });
308
+ for (const entry of entries) {
309
+ fs.renameSync(path.join(wsDir, entry.name), path.join(archivePath, entry.name));
310
+ filesMoved.push(entry.name);
311
+ }
312
+ } catch (err) {
313
+ for (const fname of filesMoved) {
314
+ try { fs.renameSync(path.join(archivePath, fname), path.join(wsDir, fname)); } catch {}
315
+ }
316
+ try { fs.rmSync(archivePath, { recursive: true }); } catch {}
317
+ if (active === name) setActiveWorkstream(cwd, name);
318
+ output({ completed: false, error: 'archive_failed', message: err.message, workstream: name }, raw);
319
+ return;
320
+ }
321
+
322
+ try { fs.rmdirSync(wsDir); } catch {}
323
+
324
+ let remainingWs = 0;
325
+ try {
326
+ remainingWs = fs.readdirSync(wsRoot, { withFileTypes: true }).filter(e => e.isDirectory()).length;
327
+ if (remainingWs === 0) fs.rmdirSync(wsRoot);
328
+ } catch {}
329
+
330
+ output({
331
+ completed: true,
332
+ workstream: name,
333
+ archived_to: toPosixPath(path.relative(cwd, archivePath)),
334
+ remaining_workstreams: remainingWs,
335
+ reverted_to_flat: remainingWs === 0,
336
+ }, raw);
337
+ }
338
+
339
+ // ─── Active Workstream Commands ──────────────────────────────────────────────
340
+
341
+ function cmdWorkstreamSet(cwd, name, raw) {
342
+ if (!name) {
343
+ setActiveWorkstream(cwd, null);
344
+ output({ active: null, cleared: true }, raw);
345
+ return;
346
+ }
347
+
348
+ if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
349
+ output({ active: null, error: 'invalid_name', message: 'Workstream name must be alphanumeric, hyphens, and underscores only' }, raw);
350
+ return;
351
+ }
352
+
353
+ const wsDir = path.join(planningRoot(cwd), 'workstreams', name);
354
+ if (!fs.existsSync(wsDir)) {
355
+ output({ active: null, error: 'not_found', workstream: name }, raw);
356
+ return;
357
+ }
358
+
359
+ setActiveWorkstream(cwd, name);
360
+ output({ active: name, set: true }, raw, name);
361
+ }
362
+
363
+ function cmdWorkstreamGet(cwd, raw) {
364
+ const active = getActiveWorkstream(cwd);
365
+ const wsRoot = path.join(planningRoot(cwd), 'workstreams');
366
+ output({ active, mode: fs.existsSync(wsRoot) ? 'workstream' : 'flat' }, raw, active || 'none');
367
+ }
368
+
369
+ function cmdWorkstreamProgress(cwd, raw) {
370
+ const root = planningRoot(cwd);
371
+ const wsRoot = path.join(root, 'workstreams');
372
+
373
+ if (!fs.existsSync(wsRoot)) {
374
+ output({ mode: 'flat', workstreams: [], message: 'No workstreams — operating in flat mode' }, raw);
375
+ return;
376
+ }
377
+
378
+ const active = getActiveWorkstream(cwd);
379
+ const entries = fs.readdirSync(wsRoot, { withFileTypes: true });
380
+ const workstreams = [];
381
+
382
+ for (const entry of entries) {
383
+ if (!entry.isDirectory()) continue;
384
+
385
+ const wsDir = path.join(wsRoot, entry.name);
386
+ const phasesDir = path.join(wsDir, 'phases');
387
+
388
+ const phaseDirsProgress = readSubdirectories(phasesDir);
389
+ const phaseCount = phaseDirsProgress.length;
390
+ let completedCount = 0, totalPlans = 0, completedPlans = 0;
391
+ for (const d of phaseDirsProgress) {
392
+ try {
393
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, d));
394
+ const plans = filterPlanFiles(phaseFiles);
395
+ const summaries = filterSummaryFiles(phaseFiles);
396
+ totalPlans += plans.length;
397
+ completedPlans += Math.min(summaries.length, plans.length);
398
+ if (plans.length > 0 && summaries.length >= plans.length) completedCount++;
399
+ } catch {}
400
+ }
401
+
402
+ let roadmapPhaseCount = phaseCount;
403
+ try {
404
+ const roadmapContent = fs.readFileSync(path.join(wsDir, 'ROADMAP.md'), 'utf-8');
405
+ const phaseMatches = roadmapContent.match(/^###?\s+Phase\s+\d/gm);
406
+ if (phaseMatches) roadmapPhaseCount = phaseMatches.length;
407
+ } catch {}
408
+
409
+ let status = 'unknown', currentPhase = null;
410
+ try {
411
+ const stateContent = fs.readFileSync(path.join(wsDir, 'STATE.md'), 'utf-8');
412
+ status = stateExtractField(stateContent, 'Status') || 'unknown';
413
+ currentPhase = stateExtractField(stateContent, 'Current Phase');
414
+ } catch {}
415
+
416
+ workstreams.push({
417
+ name: entry.name,
418
+ active: entry.name === active,
419
+ status,
420
+ current_phase: currentPhase,
421
+ phases: `${completedCount}/${roadmapPhaseCount}`,
422
+ plans: `${completedPlans}/${totalPlans}`,
423
+ progress_percent: roadmapPhaseCount > 0 ? Math.round((completedCount / roadmapPhaseCount) * 100) : 0,
424
+ });
425
+ }
426
+
427
+ output({ mode: 'workstream', active, workstreams, count: workstreams.length }, raw);
428
+ }
429
+
430
+ // ─── Collision Detection ────────────────────────────────────────────────────
431
+
432
+ /**
433
+ * Return other workstreams that are NOT complete.
434
+ * Used to detect whether the milestone has active parallel work
435
+ * when a workstream finishes its last phase.
436
+ */
437
+ function getOtherActiveWorkstreams(cwd, excludeWs) {
438
+ const wsRoot = path.join(planningRoot(cwd), 'workstreams');
439
+ if (!fs.existsSync(wsRoot)) return [];
440
+
441
+ const entries = fs.readdirSync(wsRoot, { withFileTypes: true });
442
+ const others = [];
443
+
444
+ for (const entry of entries) {
445
+ if (!entry.isDirectory() || entry.name === excludeWs) continue;
446
+
447
+ const wsDir = path.join(wsRoot, entry.name);
448
+ const statePath = path.join(wsDir, 'STATE.md');
449
+
450
+ let status = 'unknown', currentPhase = null;
451
+ try {
452
+ const content = fs.readFileSync(statePath, 'utf-8');
453
+ status = stateExtractField(content, 'Status') || 'unknown';
454
+ currentPhase = stateExtractField(content, 'Current Phase');
455
+ } catch {}
456
+
457
+ if (status.toLowerCase().includes('milestone complete') ||
458
+ status.toLowerCase().includes('archived')) {
459
+ continue;
460
+ }
461
+
462
+ const phasesDir = path.join(wsDir, 'phases');
463
+ const phaseDirsOther = readSubdirectories(phasesDir);
464
+ const phaseCount = phaseDirsOther.length;
465
+ let completedCount = 0;
466
+ for (const d of phaseDirsOther) {
467
+ try {
468
+ const phaseFiles = fs.readdirSync(path.join(phasesDir, d));
469
+ const plans = filterPlanFiles(phaseFiles);
470
+ const summaries = filterSummaryFiles(phaseFiles);
471
+ if (plans.length > 0 && summaries.length >= plans.length) completedCount++;
472
+ } catch {}
473
+ }
474
+
475
+ others.push({ name: entry.name, status, current_phase: currentPhase, phases: `${completedCount}/${phaseCount}` });
476
+ }
477
+
478
+ return others;
479
+ }
480
+
481
+ module.exports = {
482
+ migrateToWorkstreams,
483
+ cmdWorkstreamCreate,
484
+ cmdWorkstreamList,
485
+ cmdWorkstreamStatus,
486
+ cmdWorkstreamComplete,
487
+ cmdWorkstreamSet,
488
+ cmdWorkstreamGet,
489
+ cmdWorkstreamProgress,
490
+ getOtherActiveWorkstreams,
491
+ };