@devtrack-solution/codesdd 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (433) hide show
  1. package/.sdd/skills/curated/api-clean-flask-langgraph/SKILL.md +2751 -0
  2. package/.sdd/skills/curated/devtrack-api/SKILL.md +137 -0
  3. package/.sdd/skills/curated/devtrack-api/agents/openai.yaml +4 -0
  4. package/.sdd/skills/curated/devtrack-api/references/application-presentation.md +381 -0
  5. package/.sdd/skills/curated/devtrack-api/references/architecture-governance.md +219 -0
  6. package/.sdd/skills/curated/devtrack-api/references/domain-modeling.md +359 -0
  7. package/.sdd/skills/curated/devtrack-api/references/implementation-checklist.md +127 -0
  8. package/.sdd/skills/curated/devtrack-api/references/imports-lint.md +207 -0
  9. package/.sdd/skills/curated/devtrack-api/references/testing-validation.md +167 -0
  10. package/.sdd/skills/curated/devtrack-api/references/typeorm-infrastructure.md +334 -0
  11. package/LICENSE +21 -0
  12. package/README.md +842 -0
  13. package/bin/codesdd.js +10 -0
  14. package/dist/cli/index.d.ts +3 -0
  15. package/dist/cli/index.js +560 -0
  16. package/dist/commands/change.d.ts +35 -0
  17. package/dist/commands/change.js +296 -0
  18. package/dist/commands/completion.d.ts +72 -0
  19. package/dist/commands/completion.js +258 -0
  20. package/dist/commands/config.d.ts +36 -0
  21. package/dist/commands/config.js +552 -0
  22. package/dist/commands/feedback.d.ts +9 -0
  23. package/dist/commands/feedback.js +184 -0
  24. package/dist/commands/schema.d.ts +6 -0
  25. package/dist/commands/schema.js +870 -0
  26. package/dist/commands/sdd/execution.d.ts +3 -0
  27. package/dist/commands/sdd/execution.js +409 -0
  28. package/dist/commands/sdd/shared.d.ts +9 -0
  29. package/dist/commands/sdd/shared.js +84 -0
  30. package/dist/commands/sdd/skills.d.ts +3 -0
  31. package/dist/commands/sdd/skills.js +154 -0
  32. package/dist/commands/sdd.d.ts +3 -0
  33. package/dist/commands/sdd.js +769 -0
  34. package/dist/commands/show.d.ts +14 -0
  35. package/dist/commands/show.js +133 -0
  36. package/dist/commands/spec.d.ts +15 -0
  37. package/dist/commands/spec.js +228 -0
  38. package/dist/commands/validate.d.ts +24 -0
  39. package/dist/commands/validate.js +295 -0
  40. package/dist/commands/workflow/index.d.ts +17 -0
  41. package/dist/commands/workflow/index.js +12 -0
  42. package/dist/commands/workflow/instructions.d.ts +29 -0
  43. package/dist/commands/workflow/instructions.js +383 -0
  44. package/dist/commands/workflow/new-change.d.ts +11 -0
  45. package/dist/commands/workflow/new-change.js +45 -0
  46. package/dist/commands/workflow/schemas.d.ts +10 -0
  47. package/dist/commands/workflow/schemas.js +34 -0
  48. package/dist/commands/workflow/shared.d.ts +57 -0
  49. package/dist/commands/workflow/shared.js +117 -0
  50. package/dist/commands/workflow/status.d.ts +14 -0
  51. package/dist/commands/workflow/status.js +76 -0
  52. package/dist/commands/workflow/templates.d.ts +16 -0
  53. package/dist/commands/workflow/templates.js +68 -0
  54. package/dist/core/archive.d.ts +16 -0
  55. package/dist/core/archive.js +487 -0
  56. package/dist/core/artifact-graph/graph.d.ts +56 -0
  57. package/dist/core/artifact-graph/graph.js +141 -0
  58. package/dist/core/artifact-graph/index.d.ts +7 -0
  59. package/dist/core/artifact-graph/index.js +13 -0
  60. package/dist/core/artifact-graph/instruction-loader.d.ts +143 -0
  61. package/dist/core/artifact-graph/instruction-loader.js +215 -0
  62. package/dist/core/artifact-graph/resolver.d.ts +81 -0
  63. package/dist/core/artifact-graph/resolver.js +258 -0
  64. package/dist/core/artifact-graph/schema.d.ts +13 -0
  65. package/dist/core/artifact-graph/schema.js +108 -0
  66. package/dist/core/artifact-graph/state.d.ts +12 -0
  67. package/dist/core/artifact-graph/state.js +54 -0
  68. package/dist/core/artifact-graph/types.d.ts +45 -0
  69. package/dist/core/artifact-graph/types.js +43 -0
  70. package/dist/core/available-tools.d.ts +16 -0
  71. package/dist/core/available-tools.js +30 -0
  72. package/dist/core/branding.d.ts +8 -0
  73. package/dist/core/branding.js +12 -0
  74. package/dist/core/cli/command-matrix.d.ts +23 -0
  75. package/dist/core/cli/command-matrix.js +123 -0
  76. package/dist/core/command-generation/adapters/amazon-q.d.ts +13 -0
  77. package/dist/core/command-generation/adapters/amazon-q.js +26 -0
  78. package/dist/core/command-generation/adapters/antigravity.d.ts +13 -0
  79. package/dist/core/command-generation/adapters/antigravity.js +26 -0
  80. package/dist/core/command-generation/adapters/auggie.d.ts +13 -0
  81. package/dist/core/command-generation/adapters/auggie.js +27 -0
  82. package/dist/core/command-generation/adapters/claude.d.ts +13 -0
  83. package/dist/core/command-generation/adapters/claude.js +50 -0
  84. package/dist/core/command-generation/adapters/cline.d.ts +14 -0
  85. package/dist/core/command-generation/adapters/cline.js +27 -0
  86. package/dist/core/command-generation/adapters/codebuddy.d.ts +13 -0
  87. package/dist/core/command-generation/adapters/codebuddy.js +28 -0
  88. package/dist/core/command-generation/adapters/codex.d.ts +16 -0
  89. package/dist/core/command-generation/adapters/codex.js +39 -0
  90. package/dist/core/command-generation/adapters/continue.d.ts +13 -0
  91. package/dist/core/command-generation/adapters/continue.js +28 -0
  92. package/dist/core/command-generation/adapters/costrict.d.ts +13 -0
  93. package/dist/core/command-generation/adapters/costrict.js +27 -0
  94. package/dist/core/command-generation/adapters/crush.d.ts +13 -0
  95. package/dist/core/command-generation/adapters/crush.js +30 -0
  96. package/dist/core/command-generation/adapters/cursor.d.ts +14 -0
  97. package/dist/core/command-generation/adapters/cursor.js +44 -0
  98. package/dist/core/command-generation/adapters/factory.d.ts +13 -0
  99. package/dist/core/command-generation/adapters/factory.js +27 -0
  100. package/dist/core/command-generation/adapters/gemini.d.ts +13 -0
  101. package/dist/core/command-generation/adapters/gemini.js +26 -0
  102. package/dist/core/command-generation/adapters/github-copilot.d.ts +13 -0
  103. package/dist/core/command-generation/adapters/github-copilot.js +26 -0
  104. package/dist/core/command-generation/adapters/iflow.d.ts +13 -0
  105. package/dist/core/command-generation/adapters/iflow.js +29 -0
  106. package/dist/core/command-generation/adapters/index.d.ts +29 -0
  107. package/dist/core/command-generation/adapters/index.js +29 -0
  108. package/dist/core/command-generation/adapters/kilocode.d.ts +14 -0
  109. package/dist/core/command-generation/adapters/kilocode.js +23 -0
  110. package/dist/core/command-generation/adapters/kiro.d.ts +13 -0
  111. package/dist/core/command-generation/adapters/kiro.js +26 -0
  112. package/dist/core/command-generation/adapters/opencode.d.ts +13 -0
  113. package/dist/core/command-generation/adapters/opencode.js +29 -0
  114. package/dist/core/command-generation/adapters/pi.d.ts +14 -0
  115. package/dist/core/command-generation/adapters/pi.js +41 -0
  116. package/dist/core/command-generation/adapters/qoder.d.ts +13 -0
  117. package/dist/core/command-generation/adapters/qoder.js +30 -0
  118. package/dist/core/command-generation/adapters/qwen.d.ts +13 -0
  119. package/dist/core/command-generation/adapters/qwen.js +26 -0
  120. package/dist/core/command-generation/adapters/roocode.d.ts +14 -0
  121. package/dist/core/command-generation/adapters/roocode.js +27 -0
  122. package/dist/core/command-generation/adapters/windsurf.d.ts +14 -0
  123. package/dist/core/command-generation/adapters/windsurf.js +51 -0
  124. package/dist/core/command-generation/generator.d.ts +21 -0
  125. package/dist/core/command-generation/generator.js +27 -0
  126. package/dist/core/command-generation/index.d.ts +22 -0
  127. package/dist/core/command-generation/index.js +24 -0
  128. package/dist/core/command-generation/registry.d.ts +36 -0
  129. package/dist/core/command-generation/registry.js +92 -0
  130. package/dist/core/command-generation/types.d.ts +56 -0
  131. package/dist/core/command-generation/types.js +8 -0
  132. package/dist/core/completions/command-registry.d.ts +7 -0
  133. package/dist/core/completions/command-registry.js +461 -0
  134. package/dist/core/completions/completion-provider.d.ts +60 -0
  135. package/dist/core/completions/completion-provider.js +102 -0
  136. package/dist/core/completions/factory.d.ts +64 -0
  137. package/dist/core/completions/factory.js +75 -0
  138. package/dist/core/completions/generators/bash-generator.d.ts +32 -0
  139. package/dist/core/completions/generators/bash-generator.js +174 -0
  140. package/dist/core/completions/generators/fish-generator.d.ts +32 -0
  141. package/dist/core/completions/generators/fish-generator.js +157 -0
  142. package/dist/core/completions/generators/powershell-generator.d.ts +33 -0
  143. package/dist/core/completions/generators/powershell-generator.js +207 -0
  144. package/dist/core/completions/generators/zsh-generator.d.ts +44 -0
  145. package/dist/core/completions/generators/zsh-generator.js +250 -0
  146. package/dist/core/completions/installers/bash-installer.d.ts +87 -0
  147. package/dist/core/completions/installers/bash-installer.js +318 -0
  148. package/dist/core/completions/installers/fish-installer.d.ts +43 -0
  149. package/dist/core/completions/installers/fish-installer.js +143 -0
  150. package/dist/core/completions/installers/powershell-installer.d.ts +88 -0
  151. package/dist/core/completions/installers/powershell-installer.js +327 -0
  152. package/dist/core/completions/installers/zsh-installer.d.ts +125 -0
  153. package/dist/core/completions/installers/zsh-installer.js +452 -0
  154. package/dist/core/completions/templates/bash-templates.d.ts +6 -0
  155. package/dist/core/completions/templates/bash-templates.js +24 -0
  156. package/dist/core/completions/templates/fish-templates.d.ts +7 -0
  157. package/dist/core/completions/templates/fish-templates.js +39 -0
  158. package/dist/core/completions/templates/powershell-templates.d.ts +6 -0
  159. package/dist/core/completions/templates/powershell-templates.js +25 -0
  160. package/dist/core/completions/templates/zsh-templates.d.ts +6 -0
  161. package/dist/core/completions/templates/zsh-templates.js +36 -0
  162. package/dist/core/completions/types.d.ts +79 -0
  163. package/dist/core/completions/types.js +2 -0
  164. package/dist/core/config-prompts.d.ts +9 -0
  165. package/dist/core/config-prompts.js +34 -0
  166. package/dist/core/config-schema.d.ts +86 -0
  167. package/dist/core/config-schema.js +213 -0
  168. package/dist/core/config.d.ts +17 -0
  169. package/dist/core/config.js +33 -0
  170. package/dist/core/converters/json-converter.d.ts +6 -0
  171. package/dist/core/converters/json-converter.js +51 -0
  172. package/dist/core/global-config.d.ts +44 -0
  173. package/dist/core/global-config.js +125 -0
  174. package/dist/core/index.d.ts +2 -0
  175. package/dist/core/index.js +3 -0
  176. package/dist/core/init.d.ts +36 -0
  177. package/dist/core/init.js +576 -0
  178. package/dist/core/legacy-cleanup.d.ts +162 -0
  179. package/dist/core/legacy-cleanup.js +512 -0
  180. package/dist/core/list.d.ts +9 -0
  181. package/dist/core/list.js +173 -0
  182. package/dist/core/migration.d.ts +23 -0
  183. package/dist/core/migration.js +108 -0
  184. package/dist/core/parsers/change-parser.d.ts +13 -0
  185. package/dist/core/parsers/change-parser.js +193 -0
  186. package/dist/core/parsers/markdown-parser.d.ts +22 -0
  187. package/dist/core/parsers/markdown-parser.js +187 -0
  188. package/dist/core/parsers/requirement-blocks.d.ts +37 -0
  189. package/dist/core/parsers/requirement-blocks.js +201 -0
  190. package/dist/core/profile-sync-drift.d.ts +38 -0
  191. package/dist/core/profile-sync-drift.js +201 -0
  192. package/dist/core/profiles.d.ts +26 -0
  193. package/dist/core/profiles.js +41 -0
  194. package/dist/core/project-config.d.ts +64 -0
  195. package/dist/core/project-config.js +223 -0
  196. package/dist/core/schemas/base.schema.d.ts +13 -0
  197. package/dist/core/schemas/base.schema.js +13 -0
  198. package/dist/core/schemas/change.schema.d.ts +73 -0
  199. package/dist/core/schemas/change.schema.js +31 -0
  200. package/dist/core/schemas/index.d.ts +4 -0
  201. package/dist/core/schemas/index.js +4 -0
  202. package/dist/core/schemas/spec.schema.d.ts +18 -0
  203. package/dist/core/schemas/spec.schema.js +15 -0
  204. package/dist/core/sdd/adr-policy.d.ts +7 -0
  205. package/dist/core/sdd/adr-policy.js +47 -0
  206. package/dist/core/sdd/adr.d.ts +4 -0
  207. package/dist/core/sdd/adr.js +27 -0
  208. package/dist/core/sdd/bootstrap.d.ts +28 -0
  209. package/dist/core/sdd/bootstrap.js +353 -0
  210. package/dist/core/sdd/check.d.ts +51 -0
  211. package/dist/core/sdd/check.js +831 -0
  212. package/dist/core/sdd/coordination/coordination-adapters.d.ts +73 -0
  213. package/dist/core/sdd/coordination/coordination-adapters.js +87 -0
  214. package/dist/core/sdd/coordination/index.d.ts +2 -0
  215. package/dist/core/sdd/coordination/index.js +2 -0
  216. package/dist/core/sdd/dedup.d.ts +23 -0
  217. package/dist/core/sdd/dedup.js +62 -0
  218. package/dist/core/sdd/default-bootstrap-files.d.ts +23 -0
  219. package/dist/core/sdd/default-bootstrap-files.js +385 -0
  220. package/dist/core/sdd/default-skills.d.ts +16 -0
  221. package/dist/core/sdd/default-skills.js +427 -0
  222. package/dist/core/sdd/diagnose.d.ts +25 -0
  223. package/dist/core/sdd/diagnose.js +1312 -0
  224. package/dist/core/sdd/docs-sync.d.ts +21 -0
  225. package/dist/core/sdd/docs-sync.js +231 -0
  226. package/dist/core/sdd/domain/helpers.d.ts +6 -0
  227. package/dist/core/sdd/domain/helpers.js +37 -0
  228. package/dist/core/sdd/domain/lifecycle-guardrails.d.ts +22 -0
  229. package/dist/core/sdd/domain/lifecycle-guardrails.js +31 -0
  230. package/dist/core/sdd/domain/lifecycle-hooks.d.ts +16 -0
  231. package/dist/core/sdd/domain/lifecycle-hooks.js +27 -0
  232. package/dist/core/sdd/domain/post-active-validation.d.ts +15 -0
  233. package/dist/core/sdd/domain/post-active-validation.js +71 -0
  234. package/dist/core/sdd/domain/traceability.d.ts +8 -0
  235. package/dist/core/sdd/domain/traceability.js +83 -0
  236. package/dist/core/sdd/domain/transition-engine.d.ts +49 -0
  237. package/dist/core/sdd/domain/transition-engine.js +120 -0
  238. package/dist/core/sdd/fingerprint.d.ts +23 -0
  239. package/dist/core/sdd/fingerprint.js +146 -0
  240. package/dist/core/sdd/import-openspec.d.ts +31 -0
  241. package/dist/core/sdd/import-openspec.js +232 -0
  242. package/dist/core/sdd/init.d.ts +36 -0
  243. package/dist/core/sdd/init.js +65 -0
  244. package/dist/core/sdd/json-schema.d.ts +6 -0
  245. package/dist/core/sdd/json-schema.js +59 -0
  246. package/dist/core/sdd/legacy-operations.d.ts +286 -0
  247. package/dist/core/sdd/legacy-operations.js +2175 -0
  248. package/dist/core/sdd/lenses.d.ts +14 -0
  249. package/dist/core/sdd/lenses.js +97 -0
  250. package/dist/core/sdd/merge-catalog.d.ts +9 -0
  251. package/dist/core/sdd/merge-catalog.js +70 -0
  252. package/dist/core/sdd/migrate-workspace.d.ts +36 -0
  253. package/dist/core/sdd/migrate-workspace.js +344 -0
  254. package/dist/core/sdd/migrate.d.ts +24 -0
  255. package/dist/core/sdd/migrate.js +385 -0
  256. package/dist/core/sdd/resolve-project-root.d.ts +15 -0
  257. package/dist/core/sdd/resolve-project-root.js +46 -0
  258. package/dist/core/sdd/root-resolver.d.ts +16 -0
  259. package/dist/core/sdd/root-resolver.js +62 -0
  260. package/dist/core/sdd/sanitize.d.ts +35 -0
  261. package/dist/core/sdd/sanitize.js +750 -0
  262. package/dist/core/sdd/services/approve.service.d.ts +20 -0
  263. package/dist/core/sdd/services/approve.service.js +82 -0
  264. package/dist/core/sdd/services/audit.service.d.ts +53 -0
  265. package/dist/core/sdd/services/audit.service.js +136 -0
  266. package/dist/core/sdd/services/breakdown.service.d.ts +35 -0
  267. package/dist/core/sdd/services/breakdown.service.js +185 -0
  268. package/dist/core/sdd/services/context.service.d.ts +346 -0
  269. package/dist/core/sdd/services/context.service.js +278 -0
  270. package/dist/core/sdd/services/debate.service.d.ts +16 -0
  271. package/dist/core/sdd/services/debate.service.js +73 -0
  272. package/dist/core/sdd/services/decide.service.d.ts +23 -0
  273. package/dist/core/sdd/services/decide.service.js +81 -0
  274. package/dist/core/sdd/services/dedup-apply.service.d.ts +39 -0
  275. package/dist/core/sdd/services/dedup-apply.service.js +259 -0
  276. package/dist/core/sdd/services/feature-lint.service.d.ts +29 -0
  277. package/dist/core/sdd/services/feature-lint.service.js +146 -0
  278. package/dist/core/sdd/services/finalize.service.d.ts +33 -0
  279. package/dist/core/sdd/services/finalize.service.js +707 -0
  280. package/dist/core/sdd/services/frontend-gap.service.d.ts +23 -0
  281. package/dist/core/sdd/services/frontend-gap.service.js +117 -0
  282. package/dist/core/sdd/services/frontend-impact.service.d.ts +19 -0
  283. package/dist/core/sdd/services/frontend-impact.service.js +46 -0
  284. package/dist/core/sdd/services/ingest-deposito.service.d.ts +32 -0
  285. package/dist/core/sdd/services/ingest-deposito.service.js +231 -0
  286. package/dist/core/sdd/services/insight.service.d.ts +21 -0
  287. package/dist/core/sdd/services/insight.service.js +81 -0
  288. package/dist/core/sdd/services/legacy-capability.service.d.ts +24 -0
  289. package/dist/core/sdd/services/legacy-capability.service.js +59 -0
  290. package/dist/core/sdd/services/mcp-runtime.service.d.ts +42 -0
  291. package/dist/core/sdd/services/mcp-runtime.service.js +144 -0
  292. package/dist/core/sdd/services/metrics.service.d.ts +49 -0
  293. package/dist/core/sdd/services/metrics.service.js +181 -0
  294. package/dist/core/sdd/services/next.service.d.ts +35 -0
  295. package/dist/core/sdd/services/next.service.js +54 -0
  296. package/dist/core/sdd/services/onboard.service.d.ts +9 -0
  297. package/dist/core/sdd/services/onboard.service.js +165 -0
  298. package/dist/core/sdd/services/rebuild.service.d.ts +31 -0
  299. package/dist/core/sdd/services/rebuild.service.js +482 -0
  300. package/dist/core/sdd/services/scan-naming.service.d.ts +43 -0
  301. package/dist/core/sdd/services/scan-naming.service.js +246 -0
  302. package/dist/core/sdd/services/skills-invoke.service.d.ts +24 -0
  303. package/dist/core/sdd/services/skills-invoke.service.js +63 -0
  304. package/dist/core/sdd/services/skills-sync.service.d.ts +15 -0
  305. package/dist/core/sdd/services/skills-sync.service.js +117 -0
  306. package/dist/core/sdd/services/start.service.d.ts +26 -0
  307. package/dist/core/sdd/services/start.service.js +237 -0
  308. package/dist/core/sdd/skills.d.ts +15 -0
  309. package/dist/core/sdd/skills.js +46 -0
  310. package/dist/core/sdd/state-lock.d.ts +19 -0
  311. package/dist/core/sdd/state-lock.js +144 -0
  312. package/dist/core/sdd/state.d.ts +155 -0
  313. package/dist/core/sdd/state.js +1000 -0
  314. package/dist/core/sdd/store/in-memory-adapter.d.ts +12 -0
  315. package/dist/core/sdd/store/in-memory-adapter.js +27 -0
  316. package/dist/core/sdd/store/index.d.ts +5 -0
  317. package/dist/core/sdd/store/index.js +5 -0
  318. package/dist/core/sdd/store/sdd-stores.d.ts +25 -0
  319. package/dist/core/sdd/store/sdd-stores.js +59 -0
  320. package/dist/core/sdd/store/state-store.d.ts +32 -0
  321. package/dist/core/sdd/store/state-store.js +2 -0
  322. package/dist/core/sdd/store/yaml-file-adapter.d.ts +12 -0
  323. package/dist/core/sdd/store/yaml-file-adapter.js +43 -0
  324. package/dist/core/sdd/structural-health.d.ts +557 -0
  325. package/dist/core/sdd/structural-health.js +187 -0
  326. package/dist/core/sdd/transaction.d.ts +14 -0
  327. package/dist/core/sdd/transaction.js +100 -0
  328. package/dist/core/sdd/types.d.ts +1570 -0
  329. package/dist/core/sdd/types.js +617 -0
  330. package/dist/core/sdd/views.d.ts +3 -0
  331. package/dist/core/sdd/views.js +560 -0
  332. package/dist/core/sdd/workspace-schemas.d.ts +620 -0
  333. package/dist/core/sdd/workspace-schemas.js +254 -0
  334. package/dist/core/sdd/write-manifest.d.ts +25 -0
  335. package/dist/core/sdd/write-manifest.js +353 -0
  336. package/dist/core/shared/index.d.ts +8 -0
  337. package/dist/core/shared/index.js +8 -0
  338. package/dist/core/shared/skill-generation.d.ts +49 -0
  339. package/dist/core/shared/skill-generation.js +106 -0
  340. package/dist/core/shared/tool-detection.d.ts +71 -0
  341. package/dist/core/shared/tool-detection.js +158 -0
  342. package/dist/core/specs-apply.d.ts +73 -0
  343. package/dist/core/specs-apply.js +385 -0
  344. package/dist/core/styles/palette.d.ts +7 -0
  345. package/dist/core/styles/palette.js +8 -0
  346. package/dist/core/templates/index.d.ts +8 -0
  347. package/dist/core/templates/index.js +9 -0
  348. package/dist/core/templates/skill-templates.d.ts +20 -0
  349. package/dist/core/templates/skill-templates.js +19 -0
  350. package/dist/core/templates/types.d.ts +19 -0
  351. package/dist/core/templates/types.js +5 -0
  352. package/dist/core/templates/workflows/apply-change.d.ts +10 -0
  353. package/dist/core/templates/workflows/apply-change.js +308 -0
  354. package/dist/core/templates/workflows/archive-change.d.ts +10 -0
  355. package/dist/core/templates/workflows/archive-change.js +277 -0
  356. package/dist/core/templates/workflows/bulk-archive-change.d.ts +10 -0
  357. package/dist/core/templates/workflows/bulk-archive-change.js +502 -0
  358. package/dist/core/templates/workflows/continue-change.d.ts +10 -0
  359. package/dist/core/templates/workflows/continue-change.js +232 -0
  360. package/dist/core/templates/workflows/explore.d.ts +10 -0
  361. package/dist/core/templates/workflows/explore.js +475 -0
  362. package/dist/core/templates/workflows/feedback.d.ts +9 -0
  363. package/dist/core/templates/workflows/feedback.js +108 -0
  364. package/dist/core/templates/workflows/ff-change.d.ts +10 -0
  365. package/dist/core/templates/workflows/ff-change.js +206 -0
  366. package/dist/core/templates/workflows/new-change.d.ts +10 -0
  367. package/dist/core/templates/workflows/new-change.js +151 -0
  368. package/dist/core/templates/workflows/onboard.d.ts +10 -0
  369. package/dist/core/templates/workflows/onboard.js +573 -0
  370. package/dist/core/templates/workflows/propose.d.ts +10 -0
  371. package/dist/core/templates/workflows/propose.js +224 -0
  372. package/dist/core/templates/workflows/sdd.d.ts +10 -0
  373. package/dist/core/templates/workflows/sdd.js +107 -0
  374. package/dist/core/templates/workflows/sync-specs.d.ts +10 -0
  375. package/dist/core/templates/workflows/sync-specs.js +286 -0
  376. package/dist/core/templates/workflows/verify-change.d.ts +10 -0
  377. package/dist/core/templates/workflows/verify-change.js +346 -0
  378. package/dist/core/update.d.ts +77 -0
  379. package/dist/core/update.js +538 -0
  380. package/dist/core/validation/constants.d.ts +34 -0
  381. package/dist/core/validation/constants.js +40 -0
  382. package/dist/core/validation/types.d.ts +18 -0
  383. package/dist/core/validation/types.js +2 -0
  384. package/dist/core/validation/validator.d.ts +33 -0
  385. package/dist/core/validation/validator.js +409 -0
  386. package/dist/core/view.d.ts +8 -0
  387. package/dist/core/view.js +170 -0
  388. package/dist/index.d.ts +3 -0
  389. package/dist/index.js +3 -0
  390. package/dist/prompts/searchable-multi-select.d.ts +28 -0
  391. package/dist/prompts/searchable-multi-select.js +159 -0
  392. package/dist/telemetry/config.d.ts +32 -0
  393. package/dist/telemetry/config.js +68 -0
  394. package/dist/telemetry/index.d.ts +44 -0
  395. package/dist/telemetry/index.js +207 -0
  396. package/dist/ui/ascii-patterns.d.ts +16 -0
  397. package/dist/ui/ascii-patterns.js +133 -0
  398. package/dist/ui/welcome-screen.d.ts +10 -0
  399. package/dist/ui/welcome-screen.js +146 -0
  400. package/dist/utils/change-metadata.d.ts +51 -0
  401. package/dist/utils/change-metadata.js +147 -0
  402. package/dist/utils/change-utils.d.ts +62 -0
  403. package/dist/utils/change-utils.js +121 -0
  404. package/dist/utils/command-references.d.ts +18 -0
  405. package/dist/utils/command-references.js +20 -0
  406. package/dist/utils/file-system.d.ts +36 -0
  407. package/dist/utils/file-system.js +281 -0
  408. package/dist/utils/index.d.ts +6 -0
  409. package/dist/utils/index.js +9 -0
  410. package/dist/utils/interactive.d.ts +18 -0
  411. package/dist/utils/interactive.js +21 -0
  412. package/dist/utils/item-discovery.d.ts +4 -0
  413. package/dist/utils/item-discovery.js +73 -0
  414. package/dist/utils/match.d.ts +3 -0
  415. package/dist/utils/match.js +22 -0
  416. package/dist/utils/openspec-compat.d.ts +2 -0
  417. package/dist/utils/openspec-compat.js +2 -0
  418. package/dist/utils/shell-detection.d.ts +20 -0
  419. package/dist/utils/shell-detection.js +41 -0
  420. package/dist/utils/task-progress.d.ts +8 -0
  421. package/dist/utils/task-progress.js +36 -0
  422. package/package.json +111 -0
  423. package/schemas/sdd/1-spec.schema.json +221 -0
  424. package/schemas/sdd/2-plan.schema.json +199 -0
  425. package/schemas/sdd/3-tasks.schema.json +102 -0
  426. package/schemas/sdd/4-changelog.schema.json +55 -0
  427. package/schemas/sdd/5-quality.schema.json +427 -0
  428. package/schemas/sdd/workspace-catalog.schema.json +1012 -0
  429. package/schemas/spec-driven/schema.yaml +153 -0
  430. package/schemas/spec-driven/templates/design.md +19 -0
  431. package/schemas/spec-driven/templates/proposal.md +23 -0
  432. package/schemas/spec-driven/templates/spec.md +8 -0
  433. package/schemas/spec-driven/templates/tasks.md +9 -0
@@ -0,0 +1,1312 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parse as parseYaml } from 'yaml';
4
+ import { loadProjectSddConfig, loadStateSnapshot, resolveSddPaths, } from './state.js';
5
+ import { StructuralDiagnosticReportSchema, StructuralFindingSchema, } from './structural-health.js';
6
+ import { evaluateWorkspaceTraceability } from './domain/traceability.js';
7
+ import { detectArchiveArchivedLifecycleCollisions } from './domain/post-active-validation.js';
8
+ import { parseWorkspaceYamlDocument, workspaceSchemaForFile, } from './workspace-schemas.js';
9
+ const WORKSPACE_FILE_ALLOWLIST = [
10
+ '1-spec.yaml',
11
+ '2-plan.yaml',
12
+ '3-tasks.yaml',
13
+ '4-changelog.yaml',
14
+ '5-quality.yaml',
15
+ ];
16
+ const WORKSPACE_FILE_ALLOWLIST_SET = new Set(WORKSPACE_FILE_ALLOWLIST);
17
+ const WORKSPACE_SCHEMA_FILE_SET = new Set(WORKSPACE_FILE_ALLOWLIST);
18
+ const FEATURE_WORKSPACE_PATTERN = /^FEAT-\d{4}$/;
19
+ export class SddDiagnosticOptionsError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = 'SddDiagnosticOptionsError';
23
+ }
24
+ }
25
+ function projectRelative(projectRoot, targetPath) {
26
+ const relative = path.relative(projectRoot, targetPath);
27
+ return relative.length > 0 ? relative : '.';
28
+ }
29
+ function isInsidePath(rootPath, candidatePath) {
30
+ const relative = path.relative(rootPath, candidatePath);
31
+ return relative === '' || (!!relative && !relative.startsWith('..') && !path.isAbsolute(relative));
32
+ }
33
+ async function pathExists(targetPath) {
34
+ try {
35
+ await fs.access(targetPath);
36
+ return true;
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ async function safeRealpath(targetPath) {
43
+ try {
44
+ return await fs.realpath(targetPath);
45
+ }
46
+ catch {
47
+ return path.resolve(targetPath);
48
+ }
49
+ }
50
+ function addFinding(findings, input) {
51
+ findings.push(StructuralFindingSchema.parse({
52
+ evidence: {},
53
+ ...input,
54
+ sanitizer: {
55
+ actions: [],
56
+ ...input.sanitizer,
57
+ },
58
+ }));
59
+ }
60
+ function duplicateIdFindings(findings, items, scope, filePath) {
61
+ const seen = new Set();
62
+ const duplicates = new Set();
63
+ for (const item of items) {
64
+ if (seen.has(item.id))
65
+ duplicates.add(item.id);
66
+ seen.add(item.id);
67
+ }
68
+ for (const id of duplicates) {
69
+ addFinding(findings, {
70
+ finding_id: `SH-DUPLICATE-ID-${id}`,
71
+ rule_id: 'SH-RULE-DUPLICATE-ID',
72
+ category: 'integrity',
73
+ severity: 'blocker',
74
+ summary: `Duplicate SDD identifier detected: ${id}.`,
75
+ location: filePath,
76
+ evidence: { id, scope },
77
+ sanitizer: {
78
+ disposition: 'manual',
79
+ reason: 'Duplicate IDs may represent conflicting planning history and cannot be safely merged.',
80
+ actions: ['inspect duplicate records', 'preserve one canonical record', 'record migration rationale'],
81
+ },
82
+ });
83
+ }
84
+ }
85
+ function zodIssuePath(pathSegments) {
86
+ return pathSegments.length > 0 ? pathSegments.join('.') : '<root>';
87
+ }
88
+ function normalizeFindingToken(input) {
89
+ return input.toUpperCase().replace(/[^A-Z0-9]+/g, '-').replace(/^-+|-+$/g, '');
90
+ }
91
+ async function listFeatureWorkspaceDirs(rootDir, lifecycleRoot) {
92
+ const entries = await fs.readdir(rootDir, { withFileTypes: true }).catch(() => []);
93
+ return entries
94
+ .filter((entry) => entry.isDirectory() && FEATURE_WORKSPACE_PATTERN.test(entry.name))
95
+ .map((entry) => ({
96
+ featureId: entry.name,
97
+ lifecycleRoot,
98
+ dirPath: path.join(rootDir, entry.name),
99
+ }));
100
+ }
101
+ async function hasLegacyMarkdown(workspaceDir) {
102
+ const entries = await fs.readdir(workspaceDir, { withFileTypes: true }).catch(() => []);
103
+ return entries.some((entry) => entry.isFile() && entry.name.endsWith('.md'));
104
+ }
105
+ async function collectWorkspaceSchemaFindings(findings, paths) {
106
+ const workspaces = [
107
+ ...(await listFeatureWorkspaceDirs(paths.plannedDir, 'planned')),
108
+ ...(await listFeatureWorkspaceDirs(paths.activeDir, 'active')),
109
+ ];
110
+ for (const workspace of workspaces) {
111
+ const entries = await fs.readdir(workspace.dirPath, { withFileTypes: true }).catch(() => []);
112
+ const legacyMarkdown = await hasLegacyMarkdown(workspace.dirPath);
113
+ for (const entry of entries) {
114
+ if (!entry.isFile() || !WORKSPACE_SCHEMA_FILE_SET.has(entry.name))
115
+ continue;
116
+ const schema = workspaceSchemaForFile(entry.name);
117
+ if (!schema)
118
+ continue;
119
+ const filePath = path.join(workspace.dirPath, entry.name);
120
+ const relativePath = projectRelative(paths.projectRoot, filePath);
121
+ let parsed;
122
+ try {
123
+ parsed = parseYaml(await fs.readFile(filePath, 'utf-8'));
124
+ }
125
+ catch (error) {
126
+ addFinding(findings, {
127
+ finding_id: `SH-WORKSPACE-SCHEMA-INVALID-${workspace.featureId}-${normalizeFindingToken(entry.name)}`,
128
+ rule_id: 'SH-RULE-WORKSPACE-SCHEMA-INVALID',
129
+ category: 'integrity',
130
+ severity: 'error',
131
+ summary: `${workspace.featureId} ${entry.name} is not valid YAML.`,
132
+ location: relativePath,
133
+ evidence: {
134
+ id: workspace.featureId,
135
+ lifecycle_root: workspace.lifecycleRoot,
136
+ file: entry.name,
137
+ parse_error: error.message,
138
+ has_legacy_markdown: legacyMarkdown,
139
+ },
140
+ sanitizer: {
141
+ disposition: 'manual',
142
+ reason: legacyMarkdown
143
+ ? 'Legacy markdown exists and should be migrated into schema-valid workspace YAML.'
144
+ : 'Workspace YAML cannot be repaired automatically without losing feature-specific intent.',
145
+ actions: legacyMarkdown
146
+ ? [`opensdd sdd migrate-workspace --feat ${workspace.featureId}`]
147
+ : [`edit ${relativePath} to match ${entry.name} schema`],
148
+ },
149
+ });
150
+ continue;
151
+ }
152
+ const result = schema.safeParse(parsed);
153
+ if (result.success)
154
+ continue;
155
+ addFinding(findings, {
156
+ finding_id: `SH-WORKSPACE-SCHEMA-INVALID-${workspace.featureId}-${normalizeFindingToken(entry.name)}`,
157
+ rule_id: 'SH-RULE-WORKSPACE-SCHEMA-INVALID',
158
+ category: 'integrity',
159
+ severity: 'error',
160
+ summary: `${workspace.featureId} ${entry.name} does not match the workspace schema.`,
161
+ location: relativePath,
162
+ evidence: {
163
+ id: workspace.featureId,
164
+ lifecycle_root: workspace.lifecycleRoot,
165
+ file: entry.name,
166
+ invalid_fields: result.error.issues.map((issue) => ({
167
+ path: zodIssuePath(issue.path),
168
+ message: issue.message,
169
+ })),
170
+ has_legacy_markdown: legacyMarkdown,
171
+ },
172
+ sanitizer: {
173
+ disposition: 'manual',
174
+ reason: legacyMarkdown
175
+ ? 'Legacy markdown exists and should be migrated into schema-valid workspace YAML.'
176
+ : 'Workspace YAML requires schema-aware manual editing.',
177
+ actions: legacyMarkdown
178
+ ? [`opensdd sdd migrate-workspace --feat ${workspace.featureId}`]
179
+ : [`edit ${relativePath} and rerun opensdd sdd diagnose`],
180
+ },
181
+ });
182
+ }
183
+ }
184
+ }
185
+ async function collectWorkspaceAllowlistFindings(findings, paths) {
186
+ const workspaces = [
187
+ ...(await listFeatureWorkspaceDirs(paths.plannedDir, 'planned')),
188
+ ...(await listFeatureWorkspaceDirs(paths.activeDir, 'active')),
189
+ ...(await listFeatureWorkspaceDirs(paths.archivedDir, 'archived')),
190
+ ];
191
+ for (const workspace of workspaces) {
192
+ const entries = await fs.readdir(workspace.dirPath, { withFileTypes: true }).catch(() => []);
193
+ for (const entry of entries) {
194
+ if (!entry.isFile() || WORKSPACE_FILE_ALLOWLIST_SET.has(entry.name))
195
+ continue;
196
+ const filePath = path.join(workspace.dirPath, entry.name);
197
+ const relativePath = projectRelative(paths.projectRoot, filePath);
198
+ addFinding(findings, {
199
+ finding_id: `SH-WORKSPACE-FILE-NOTALLOWLISTED-${workspace.featureId}-${normalizeFindingToken(entry.name)}`,
200
+ rule_id: 'SH-RULE-WORKSPACE-FILE-NOT-ALLOWLISTED',
201
+ category: 'integrity',
202
+ severity: 'warning',
203
+ summary: `${workspace.featureId} contains a workspace file outside the OpenSDD allowlist: ${entry.name}.`,
204
+ location: relativePath,
205
+ evidence: {
206
+ id: workspace.featureId,
207
+ lifecycle_root: workspace.lifecycleRoot,
208
+ file: entry.name,
209
+ allowed_files: WORKSPACE_FILE_ALLOWLIST,
210
+ legacy_markdown: entry.name.endsWith('.md'),
211
+ },
212
+ sanitizer: {
213
+ disposition: 'manual',
214
+ reason: entry.name.endsWith('.md')
215
+ ? 'Legacy markdown workspace files should be migrated or moved out of the lifecycle workspace.'
216
+ : 'Non-allowlisted files may contain user data and require confirmation before relocation.',
217
+ actions: [
218
+ entry.name.endsWith('.md')
219
+ ? `opensdd sdd migrate-workspace --feat ${workspace.featureId}`
220
+ : 'move the file to .sdd/backup after review',
221
+ 'remove the file only after confirming it is obsolete',
222
+ ],
223
+ },
224
+ });
225
+ }
226
+ }
227
+ }
228
+ async function collectLifecycleViolationFindings(findings, paths, backlogItems) {
229
+ const byId = new Map(backlogItems.map((item) => [item.id, item]));
230
+ const checks = [
231
+ { root: 'planned', dir: paths.plannedDir, expectedStatus: 'READY' },
232
+ { root: 'active', dir: paths.activeDir, expectedStatus: 'IN_PROGRESS' },
233
+ { root: 'archived', dir: paths.archivedDir, expectedStatus: 'DONE' },
234
+ ];
235
+ for (const check of checks) {
236
+ const workspaces = await listFeatureWorkspaceDirs(check.dir, check.root);
237
+ for (const workspace of workspaces) {
238
+ const item = byId.get(workspace.featureId);
239
+ if (!item || item.status === check.expectedStatus)
240
+ continue;
241
+ addFinding(findings, {
242
+ finding_id: `SH-LIFECYCLE-VIOLATION-${workspace.featureId}-${check.root.toUpperCase()}`,
243
+ rule_id: 'SH-RULE-LIFECYCLE-VIOLATION',
244
+ category: 'lifecycle',
245
+ severity: 'error',
246
+ summary: `${workspace.featureId} is in ${check.root}/ but backlog status is ${item.status}; expected ${check.expectedStatus}.`,
247
+ location: projectRelative(paths.projectRoot, workspace.dirPath),
248
+ evidence: {
249
+ id: workspace.featureId,
250
+ lifecycle_root: check.root,
251
+ actual_status: item.status,
252
+ expected_status: check.expectedStatus,
253
+ backlog_path: projectRelative(paths.projectRoot, paths.stateFiles.backlog),
254
+ },
255
+ sanitizer: {
256
+ disposition: 'manual',
257
+ reason: 'Backlog status should be corrected to match the real lifecycle directory after review.',
258
+ actions: [`set ${workspace.featureId}.status to ${check.expectedStatus} in backlog.yaml`],
259
+ },
260
+ });
261
+ }
262
+ }
263
+ }
264
+ function qualityEvidenceReferencesCoverage(evidenceLog, targets) {
265
+ if (evidenceLog.length === 0)
266
+ return false;
267
+ const evidenceText = evidenceLog
268
+ .map((entry) => `${entry.kind} ${entry.result} ${entry.artifact ?? ''}`)
269
+ .join('\n')
270
+ .toLowerCase();
271
+ return (evidenceText.includes('coverage') ||
272
+ evidenceText.includes(`${targets.unit}`) ||
273
+ evidenceText.includes(`${targets.integration}`));
274
+ }
275
+ async function collectQualityEvidenceFindings(findings, paths, backlogItems) {
276
+ const byId = new Map(backlogItems.map((item) => [item.id, item]));
277
+ const activeWorkspaces = await listFeatureWorkspaceDirs(paths.activeDir, 'active');
278
+ for (const workspace of activeWorkspaces) {
279
+ const item = byId.get(workspace.featureId);
280
+ if (!item || item.status !== 'IN_PROGRESS')
281
+ continue;
282
+ if (item.current_stage !== 'execucao' && item.current_stage !== 'consolidacao')
283
+ continue;
284
+ const qualityPath = path.join(workspace.dirPath, '5-quality.yaml');
285
+ const relativePath = projectRelative(paths.projectRoot, qualityPath);
286
+ if (!(await pathExists(qualityPath))) {
287
+ addFinding(findings, {
288
+ finding_id: `SH-QUALITY-EVIDENCE-INCOMPLETE-${workspace.featureId}`,
289
+ rule_id: 'SH-RULE-QUALITY-EVIDENCE-INCOMPLETE',
290
+ category: 'integrity',
291
+ severity: 'warning',
292
+ summary: `${workspace.featureId} is in ${item.current_stage} without 5-quality.yaml evidence.`,
293
+ location: relativePath,
294
+ evidence: {
295
+ id: workspace.featureId,
296
+ current_stage: item.current_stage,
297
+ missing_quality_file: true,
298
+ },
299
+ sanitizer: {
300
+ disposition: 'manual',
301
+ reason: 'Quality evidence is feature-specific and must be supplied by the implementation round.',
302
+ actions: [`create ${relativePath} and fill evidence_log[]`],
303
+ },
304
+ });
305
+ continue;
306
+ }
307
+ const qualityContent = await fs.readFile(qualityPath, 'utf-8').catch(() => '');
308
+ let qualityDocument;
309
+ try {
310
+ qualityDocument = parseWorkspaceYamlDocument('5-quality.yaml', qualityContent);
311
+ }
312
+ catch (error) {
313
+ const message = error instanceof Error ? error.message : String(error);
314
+ addFinding(findings, {
315
+ finding_id: `SH-QUALITY-EVIDENCE-INCOMPLETE-${workspace.featureId}`,
316
+ rule_id: 'SH-RULE-QUALITY-EVIDENCE-INCOMPLETE',
317
+ category: 'integrity',
318
+ severity: 'warning',
319
+ summary: `${workspace.featureId} quality evidence cannot be validated because 5-quality.yaml is invalid.`,
320
+ location: relativePath,
321
+ evidence: {
322
+ id: workspace.featureId,
323
+ current_stage: item.current_stage,
324
+ parser_gate_error: message,
325
+ },
326
+ sanitizer: {
327
+ disposition: 'manual',
328
+ reason: 'Fix the quality YAML so coverage evidence can be validated before finalize.',
329
+ actions: [`edit ${relativePath} and rerun opensdd sdd diagnose`],
330
+ },
331
+ });
332
+ continue;
333
+ }
334
+ const coverageReferenced = qualityEvidenceReferencesCoverage(qualityDocument.evidence_log, qualityDocument.coverage_targets);
335
+ if (qualityDocument.evidence_log.length > 0 && coverageReferenced)
336
+ continue;
337
+ addFinding(findings, {
338
+ finding_id: `SH-QUALITY-EVIDENCE-INCOMPLETE-${workspace.featureId}`,
339
+ rule_id: 'SH-RULE-QUALITY-EVIDENCE-INCOMPLETE',
340
+ category: 'integrity',
341
+ severity: 'warning',
342
+ summary: `${workspace.featureId} quality evidence is incomplete for execution/finalization.`,
343
+ location: relativePath,
344
+ evidence: {
345
+ id: workspace.featureId,
346
+ current_stage: item.current_stage,
347
+ evidence_count: qualityDocument.evidence_log.length,
348
+ coverage_targets: qualityDocument.coverage_targets,
349
+ coverage_referenced: coverageReferenced,
350
+ },
351
+ sanitizer: {
352
+ disposition: 'manual',
353
+ reason: 'Coverage evidence must be recorded before finalize or covered by a formal exception.',
354
+ actions: [`fill evidence_log[] in ${relativePath} with coverage validation results`],
355
+ },
356
+ });
357
+ }
358
+ }
359
+ async function collectTraceabilityFindings(findings, paths, backlogItems) {
360
+ const byId = new Map(backlogItems.map((item) => [item.id, item]));
361
+ const activeWorkspaces = await listFeatureWorkspaceDirs(paths.activeDir, 'active');
362
+ for (const workspace of activeWorkspaces) {
363
+ const item = byId.get(workspace.featureId);
364
+ if (!item || item.status !== 'IN_PROGRESS')
365
+ continue;
366
+ if (item.current_stage !== 'execucao' && item.current_stage !== 'consolidacao')
367
+ continue;
368
+ const specPath = path.join(workspace.dirPath, '1-spec.yaml');
369
+ const changelogPath = path.join(workspace.dirPath, '4-changelog.yaml');
370
+ const qualityPath = path.join(workspace.dirPath, '5-quality.yaml');
371
+ const relativePath = projectRelative(paths.projectRoot, qualityPath);
372
+ const [specContent, changelogContent, qualityContent] = await Promise.all([
373
+ fs.readFile(specPath, 'utf-8').catch(() => ''),
374
+ fs.readFile(changelogPath, 'utf-8').catch(() => ''),
375
+ fs.readFile(qualityPath, 'utf-8').catch(() => ''),
376
+ ]);
377
+ if (!specContent.trim() || !changelogContent.trim() || !qualityContent.trim()) {
378
+ addFinding(findings, {
379
+ finding_id: `SH-TRACEABILITY-INCOMPLETE-${workspace.featureId}`,
380
+ rule_id: 'SH-RULE-TRACEABILITY-INCOMPLETE',
381
+ category: 'integrity',
382
+ severity: 'warning',
383
+ summary: `${workspace.featureId} is missing traceability inputs required for finalize.`,
384
+ location: relativePath,
385
+ evidence: {
386
+ id: workspace.featureId,
387
+ spec_present: Boolean(specContent.trim()),
388
+ changelog_present: Boolean(changelogContent.trim()),
389
+ quality_present: Boolean(qualityContent.trim()),
390
+ },
391
+ sanitizer: {
392
+ disposition: 'manual',
393
+ reason: 'Requirement-to-code-to-test traceability must be curated per feature before finalize.',
394
+ actions: [`complete traceability in ${relativePath}`],
395
+ },
396
+ });
397
+ continue;
398
+ }
399
+ let spec;
400
+ let changelog;
401
+ let quality;
402
+ try {
403
+ spec = parseWorkspaceYamlDocument('1-spec.yaml', specContent);
404
+ changelog = parseWorkspaceYamlDocument('4-changelog.yaml', changelogContent);
405
+ quality = parseWorkspaceYamlDocument('5-quality.yaml', qualityContent);
406
+ }
407
+ catch (error) {
408
+ addFinding(findings, {
409
+ finding_id: `SH-TRACEABILITY-INCOMPLETE-${workspace.featureId}`,
410
+ rule_id: 'SH-RULE-TRACEABILITY-INCOMPLETE',
411
+ category: 'integrity',
412
+ severity: 'warning',
413
+ summary: `${workspace.featureId} traceability cannot be validated because workspace YAML is invalid.`,
414
+ location: relativePath,
415
+ evidence: {
416
+ id: workspace.featureId,
417
+ parser_gate_error: error instanceof Error ? error.message : String(error),
418
+ },
419
+ sanitizer: {
420
+ disposition: 'manual',
421
+ reason: 'Fix the workspace YAML before validating traceability closure.',
422
+ actions: [`edit ${relativePath} and rerun opensdd sdd diagnose`],
423
+ },
424
+ });
425
+ continue;
426
+ }
427
+ const traceability = evaluateWorkspaceTraceability(paths.projectRoot, spec, changelog, quality);
428
+ if (traceability.ok)
429
+ continue;
430
+ addFinding(findings, {
431
+ finding_id: `SH-TRACEABILITY-INCOMPLETE-${workspace.featureId}`,
432
+ rule_id: 'SH-RULE-TRACEABILITY-INCOMPLETE',
433
+ category: 'integrity',
434
+ severity: 'warning',
435
+ summary: `${workspace.featureId} traceability closure is incomplete for execution/finalization.`,
436
+ location: relativePath,
437
+ evidence: {
438
+ id: workspace.featureId,
439
+ reasons: traceability.reasons,
440
+ mapped_requirements: quality.traceability.requirements.length,
441
+ },
442
+ sanitizer: {
443
+ disposition: 'manual',
444
+ reason: 'Finalize requires requirement-to-code-to-test links, semantic coverage status, and changelog linkage.',
445
+ actions: [`update traceability.requirements[] in ${relativePath}`],
446
+ },
447
+ });
448
+ }
449
+ }
450
+ function isPrivacyComplianceBacklogItem(item) {
451
+ if (item.origin_ref === 'EPIC-0020' || item.origin_ref === 'EPIC-0021')
452
+ return true;
453
+ const refs = new Set(item.acceptance_refs || []);
454
+ return refs.has('EPIC-0020') || refs.has('EPIC-0021') || refs.has('DEB-0021') || refs.has('INS-0021');
455
+ }
456
+ async function collectPrivacyComplianceFindings(findings, paths, snapshot) {
457
+ const privacyItems = snapshot.backlog.items.filter((item) => item.status !== 'DONE' && item.status !== 'ARCHIVED' && isPrivacyComplianceBacklogItem(item));
458
+ if (privacyItems.length === 0)
459
+ return;
460
+ if (snapshot.sourceIndex.jurisdiction_profiles.length === 0) {
461
+ addFinding(findings, {
462
+ finding_id: 'SH-PRIVACY-JURISDICTION-PROFILES-MISSING',
463
+ rule_id: 'SH-RULE-PRIVACY-COMPLIANCE-BASELINE',
464
+ category: 'integrity',
465
+ severity: 'warning',
466
+ summary: 'Privacy compliance baseline missing jurisdiction profiles for active EPIC-0020/EPIC-0021 features.',
467
+ location: projectRelative(paths.projectRoot, paths.stateFiles.sourceIndex),
468
+ evidence: { required: 'source-index.jurisdiction_profiles[]', found: 0 },
469
+ sanitizer: {
470
+ disposition: 'manual',
471
+ reason: 'Jurisdiction profiles require domain modeling and traceable source references.',
472
+ actions: ['populate source-index.jurisdiction_profiles with canonical IDs and source refs'],
473
+ },
474
+ });
475
+ }
476
+ if (snapshot.sourceIndex.control_catalog.length === 0) {
477
+ addFinding(findings, {
478
+ finding_id: 'SH-PRIVACY-CONTROL-CATALOG-MISSING',
479
+ rule_id: 'SH-RULE-PRIVACY-COMPLIANCE-BASELINE',
480
+ category: 'integrity',
481
+ severity: 'warning',
482
+ summary: 'Privacy compliance baseline missing control catalog for active EPIC-0020/EPIC-0021 features.',
483
+ location: projectRelative(paths.projectRoot, paths.stateFiles.sourceIndex),
484
+ evidence: { required: 'source-index.control_catalog[]', found: 0 },
485
+ sanitizer: {
486
+ disposition: 'manual',
487
+ reason: 'Control catalog entries must be mapped to jurisdictions and evidence strategies.',
488
+ actions: ['populate source-index.control_catalog with category, severity, and source refs'],
489
+ },
490
+ });
491
+ }
492
+ if (snapshot.sourceIndex.version_events.length === 0) {
493
+ addFinding(findings, {
494
+ finding_id: 'SH-PRIVACY-SOURCE-VERSION-EVENTS-MISSING',
495
+ rule_id: 'SH-RULE-PRIVACY-COMPLIANCE-BASELINE',
496
+ category: 'integrity',
497
+ severity: 'warning',
498
+ summary: 'Privacy compliance baseline missing source version events for active EPIC-0020/EPIC-0021 features.',
499
+ location: projectRelative(paths.projectRoot, paths.stateFiles.sourceIndex),
500
+ evidence: { required: 'source-index.version_events[]', found: 0 },
501
+ sanitizer: {
502
+ disposition: 'manual',
503
+ reason: 'Versioned regulatory catalog requires auditable source revision events.',
504
+ actions: ['populate source-index.version_events via source ingest/update lifecycle'],
505
+ },
506
+ });
507
+ }
508
+ const activeRefs = new Set();
509
+ for (const item of privacyItems) {
510
+ activeRefs.add(item.id);
511
+ if (item.origin_ref)
512
+ activeRefs.add(item.origin_ref);
513
+ }
514
+ for (const source of snapshot.sourceIndex.sources) {
515
+ const relevant = source.used_by.some((ref) => activeRefs.has(ref));
516
+ if (!relevant)
517
+ continue;
518
+ const hasAuthority = source.authority !== 'unknown';
519
+ const hasClassification = source.source_classification !== 'other';
520
+ const hasVerificationDate = Boolean((source.last_verified_at || '').trim());
521
+ const hasVersion = typeof source.source_version === 'number' && source.source_version > 0;
522
+ const hasFingerprint = Boolean((source.source_fingerprint || '').trim());
523
+ if (hasAuthority && hasClassification && hasVerificationDate && hasVersion && hasFingerprint)
524
+ continue;
525
+ addFinding(findings, {
526
+ finding_id: `SH-PRIVACY-SOURCE-METADATA-${normalizeFindingToken(source.id)}`,
527
+ rule_id: 'SH-RULE-PRIVACY-SOURCE-METADATA',
528
+ category: 'integrity',
529
+ severity: 'warning',
530
+ summary: `Source ${source.id} linked to privacy features is missing provenance/version metadata.`,
531
+ location: projectRelative(paths.projectRoot, paths.stateFiles.sourceIndex),
532
+ evidence: {
533
+ source_id: source.id,
534
+ authority: source.authority,
535
+ source_classification: source.source_classification,
536
+ last_verified_at: source.last_verified_at || '',
537
+ source_version: source.source_version ?? null,
538
+ source_fingerprint: source.source_fingerprint || '',
539
+ },
540
+ sanitizer: {
541
+ disposition: 'manual',
542
+ reason: 'Regulatory traceability requires explicit provenance and verification metadata.',
543
+ actions: [
544
+ `set authority/source_classification/last_verified_at/source_version/source_fingerprint for ${source.id}`,
545
+ 'rerun opensdd sdd check --render and opensdd sdd diagnose',
546
+ ],
547
+ },
548
+ });
549
+ }
550
+ for (const item of privacyItems.filter((entry) => entry.status === 'IN_PROGRESS')) {
551
+ const specPath = path.join(paths.activeDir, item.id, '1-spec.yaml');
552
+ const planPath = path.join(paths.activeDir, item.id, '2-plan.yaml');
553
+ const qualityPath = path.join(paths.activeDir, item.id, '5-quality.yaml');
554
+ if (!(await pathExists(specPath)) || !(await pathExists(planPath)) || !(await pathExists(qualityPath))) {
555
+ addFinding(findings, {
556
+ finding_id: `SH-PRIVACY-WORKSPACE-FILES-${item.id}`,
557
+ rule_id: 'SH-RULE-PRIVACY-WORKSPACE-CHECKLIST',
558
+ category: 'integrity',
559
+ severity: 'warning',
560
+ summary: `${item.id} is missing mandatory privacy workspace files (1-spec.yaml, 2-plan.yaml, 5-quality.yaml).`,
561
+ location: projectRelative(paths.projectRoot, path.join(paths.activeDir, item.id)),
562
+ evidence: { feature_id: item.id },
563
+ sanitizer: {
564
+ disposition: 'manual',
565
+ reason: 'Privacy lifecycle gates require complete workspace artifacts.',
566
+ actions: [`restore missing YAML files under .sdd/active/${item.id}`],
567
+ },
568
+ });
569
+ continue;
570
+ }
571
+ try {
572
+ const spec = parseWorkspaceYamlDocument('1-spec.yaml', await fs.readFile(specPath, 'utf-8'));
573
+ const plan = parseWorkspaceYamlDocument('2-plan.yaml', await fs.readFile(planPath, 'utf-8'));
574
+ const quality = parseWorkspaceYamlDocument('5-quality.yaml', await fs.readFile(qualityPath, 'utf-8'));
575
+ const missingBlocks = [];
576
+ if ((spec.compliance_context?.source_refs || []).length === 0) {
577
+ missingBlocks.push('spec.compliance_context.source_refs');
578
+ }
579
+ if ((spec.compliance_context?.jurisdictions || []).length === 0) {
580
+ missingBlocks.push('spec.compliance_context.jurisdictions');
581
+ }
582
+ if ((plan.privacy_controls?.source_registry_refs || []).length === 0) {
583
+ missingBlocks.push('plan.privacy_controls.source_registry_refs');
584
+ }
585
+ if ((plan.privacy_controls?.jurisdiction_profiles || []).length === 0) {
586
+ missingBlocks.push('plan.privacy_controls.jurisdiction_profiles');
587
+ }
588
+ if ((plan.privacy_controls?.control_ids || []).length === 0) {
589
+ missingBlocks.push('plan.privacy_controls.control_ids');
590
+ }
591
+ if ((plan.skill_conformance?.selected_skills || []).length === 0) {
592
+ missingBlocks.push('plan.skill_conformance.selected_skills');
593
+ }
594
+ if (!((plan.skill_conformance?.architecture_tree_ascii || '').trim()) && item.execution_kind !== 'documentation') {
595
+ missingBlocks.push('plan.skill_conformance.architecture_tree_ascii');
596
+ }
597
+ if ((plan.skill_conformance?.detected_conflicts || []).length > 0 &&
598
+ !((plan.skill_conformance?.resolution_adr_ref || '').trim())) {
599
+ missingBlocks.push('plan.skill_conformance.resolution_adr_ref');
600
+ }
601
+ if (!quality.security_integrity) {
602
+ missingBlocks.push('quality.security_integrity');
603
+ }
604
+ if (missingBlocks.length > 0) {
605
+ addFinding(findings, {
606
+ finding_id: `SH-PRIVACY-WORKSPACE-CHECKLIST-${item.id}`,
607
+ rule_id: 'SH-RULE-PRIVACY-WORKSPACE-CHECKLIST',
608
+ category: 'integrity',
609
+ severity: 'warning',
610
+ summary: `${item.id} has incomplete privacy lifecycle checklist fields.`,
611
+ location: projectRelative(paths.projectRoot, path.join(paths.activeDir, item.id)),
612
+ evidence: { feature_id: item.id, missing_blocks: missingBlocks },
613
+ sanitizer: {
614
+ disposition: 'manual',
615
+ reason: 'Checklist completion requires feature-specific privacy data and evidence.',
616
+ actions: ['fill missing privacy blocks in 1-spec/2-plan/5-quality'],
617
+ },
618
+ });
619
+ }
620
+ }
621
+ catch (error) {
622
+ addFinding(findings, {
623
+ finding_id: `SH-PRIVACY-WORKSPACE-PARSE-${item.id}`,
624
+ rule_id: 'SH-RULE-PRIVACY-WORKSPACE-CHECKLIST',
625
+ category: 'integrity',
626
+ severity: 'warning',
627
+ summary: `${item.id} privacy checklist could not be evaluated due to invalid workspace YAML.`,
628
+ location: projectRelative(paths.projectRoot, path.join(paths.activeDir, item.id)),
629
+ evidence: { feature_id: item.id, error: error.message },
630
+ sanitizer: {
631
+ disposition: 'manual',
632
+ reason: 'Workspace YAML must be valid before privacy checklist gates can be applied.',
633
+ actions: ['fix workspace YAML schema errors and rerun diagnose'],
634
+ },
635
+ });
636
+ }
637
+ }
638
+ }
639
+ function collectDuplicateIdFindings(findings, snapshot, paths) {
640
+ duplicateIdFindings(findings, snapshot.discoveryIndex.records, 'discovery-index.records', projectRelative(paths.projectRoot, paths.stateFiles.discoveryIndex));
641
+ duplicateIdFindings(findings, snapshot.backlog.items, 'backlog.items', projectRelative(paths.projectRoot, paths.stateFiles.backlog));
642
+ duplicateIdFindings(findings, snapshot.techDebt.items, 'tech-debt.items', projectRelative(paths.projectRoot, paths.stateFiles.techDebt));
643
+ if (snapshot.frontendGaps) {
644
+ duplicateIdFindings(findings, snapshot.frontendGaps.items, 'frontend-gaps.items', projectRelative(paths.projectRoot, paths.stateFiles.frontendGaps));
645
+ }
646
+ }
647
+ function addBrokenReference(findings, ref, owner, relation, location) {
648
+ addFinding(findings, {
649
+ finding_id: `SH-BROKEN-REF-${owner}-${relation}-${ref}`.replace(/[^A-Z0-9-]/g, '-'),
650
+ rule_id: 'SH-RULE-BROKEN-REFERENCE',
651
+ category: 'references',
652
+ severity: 'error',
653
+ summary: `${owner} references missing ${relation}: ${ref}.`,
654
+ location,
655
+ evidence: { owner, relation, ref },
656
+ sanitizer: {
657
+ disposition: 'manual',
658
+ reason: 'Broken references require semantic review before removal or replacement.',
659
+ actions: ['verify referenced entity', 'update relation or create missing entity'],
660
+ },
661
+ });
662
+ }
663
+ function collectReferenceFindings(findings, snapshot, paths) {
664
+ const discoveryIds = new Set(snapshot.discoveryIndex.records.map((record) => record.id));
665
+ const backlogIds = new Set(snapshot.backlog.items.map((item) => item.id));
666
+ const techDebtIds = new Set(snapshot.techDebt.items.map((item) => item.id));
667
+ const frontendGapIds = new Set(snapshot.frontendGaps?.items.map((item) => item.id) ?? []);
668
+ const ecosystemIds = new Set([...discoveryIds, ...backlogIds, ...techDebtIds, ...frontendGapIds]);
669
+ for (const item of snapshot.backlog.items) {
670
+ const location = projectRelative(paths.projectRoot, paths.stateFiles.backlog);
671
+ if ((item.origin_type === 'epic' || item.origin_type === 'radar') && item.origin_ref && !discoveryIds.has(item.origin_ref)) {
672
+ addBrokenReference(findings, item.origin_ref, item.id, 'origin_ref', location);
673
+ }
674
+ if (item.origin_type === 'frontend_gap' && item.origin_ref && !frontendGapIds.has(item.origin_ref)) {
675
+ addBrokenReference(findings, item.origin_ref, item.id, 'origin_ref', location);
676
+ }
677
+ if (item.origin_type === 'tech_debt' && item.origin_ref && !techDebtIds.has(item.origin_ref)) {
678
+ addBrokenReference(findings, item.origin_ref, item.id, 'origin_ref', location);
679
+ }
680
+ for (const dep of item.blocked_by) {
681
+ if (!backlogIds.has(dep)) {
682
+ addBrokenReference(findings, dep, item.id, 'blocked_by', location);
683
+ }
684
+ }
685
+ for (const acceptanceRef of item.acceptance_refs) {
686
+ if (!ecosystemIds.has(acceptanceRef)) {
687
+ addBrokenReference(findings, acceptanceRef, item.id, 'acceptance_refs', location);
688
+ }
689
+ }
690
+ }
691
+ for (const record of snapshot.discoveryIndex.records) {
692
+ const location = projectRelative(paths.projectRoot, paths.stateFiles.discoveryIndex);
693
+ for (const relatedId of record.related_ids) {
694
+ if (!ecosystemIds.has(relatedId)) {
695
+ addBrokenReference(findings, relatedId, record.id, 'related_ids', location);
696
+ }
697
+ }
698
+ }
699
+ for (const gap of snapshot.frontendGaps?.items ?? []) {
700
+ if (gap.resolved_by_feature && !backlogIds.has(gap.resolved_by_feature)) {
701
+ addBrokenReference(findings, gap.resolved_by_feature, gap.id, 'resolved_by_feature', projectRelative(paths.projectRoot, paths.stateFiles.frontendGaps));
702
+ }
703
+ }
704
+ }
705
+ async function collectDirectoryFindings(findings, config, paths) {
706
+ const requiredDirectories = [
707
+ { semantic: 'memory', dir: paths.memoryRoot, severity: 'blocker' },
708
+ { semantic: 'state', dir: paths.stateDir, severity: 'blocker' },
709
+ { semantic: 'discovery', dir: paths.discoveryDir, severity: 'error' },
710
+ { semantic: 'planning', dir: paths.pendenciasDir, severity: 'error' },
711
+ { semantic: 'planned', dir: paths.plannedDir, severity: 'error' },
712
+ { semantic: 'active', dir: paths.activeDir, severity: 'error' },
713
+ { semantic: 'archived', dir: paths.archivedDir, severity: 'error' },
714
+ { semantic: 'core', dir: paths.coreDir, severity: 'error' },
715
+ ];
716
+ for (const item of requiredDirectories) {
717
+ if (!(await pathExists(item.dir))) {
718
+ addFinding(findings, {
719
+ finding_id: `SH-MISSING-DIR-${item.semantic.toUpperCase()}`,
720
+ rule_id: 'SH-RULE-MISSING-DIRECTORY',
721
+ category: 'lifecycle',
722
+ severity: item.severity,
723
+ summary: `Required SDD directory is missing: ${item.semantic}.`,
724
+ location: projectRelative(paths.projectRoot, item.dir),
725
+ evidence: { semantic: item.semantic, expected_path: projectRelative(paths.projectRoot, item.dir) },
726
+ sanitizer: {
727
+ disposition: 'safe',
728
+ reason: 'Missing canonical directories can be recreated without deleting user data.',
729
+ actions: [`mkdir ${projectRelative(paths.projectRoot, item.dir)}`],
730
+ },
731
+ });
732
+ }
733
+ }
734
+ if (config.layout !== 'en-US') {
735
+ addFinding(findings, {
736
+ finding_id: `SH-LANGUAGE-DEPENDENT-LAYOUT-${config.layout.toUpperCase().replace(/[^A-Z0-9]/g, '-')}`,
737
+ rule_id: 'SH-RULE-LANGUAGE-INVARIANT-LAYOUT',
738
+ category: 'naming',
739
+ severity: 'error',
740
+ summary: `SDD layout is language-dependent: ${config.layout}.`,
741
+ location: projectRelative(paths.projectRoot, paths.memoryRoot),
742
+ evidence: { layout: config.layout, language: config.language, folders: config.folders },
743
+ sanitizer: {
744
+ disposition: 'manual',
745
+ reason: 'Migrating localized structural folders requires preserving existing state and generated views.',
746
+ actions: ['plan migration to en-US structural tokens', 'preserve content before relocating folders'],
747
+ },
748
+ });
749
+ }
750
+ const translatedCandidates = ['pendencias', 'planejamento', 'execucao', 'arquivados', 'descoberta'];
751
+ for (const name of translatedCandidates) {
752
+ const candidate = path.join(paths.memoryRoot, name);
753
+ if (candidate === paths.pendenciasDir || candidate === paths.activeDir || candidate === paths.archivedDir || candidate === paths.discoveryDir) {
754
+ continue;
755
+ }
756
+ if (await pathExists(candidate)) {
757
+ addFinding(findings, {
758
+ finding_id: `SH-TRANSLATED-DIR-${name.toUpperCase().replace(/[^A-Z0-9]/g, '-')}`,
759
+ rule_id: 'SH-RULE-TRANSLATED-STRUCTURAL-DIRECTORY',
760
+ category: 'naming',
761
+ severity: 'error',
762
+ summary: `Translated or legacy structural directory found outside the active layout: ${name}.`,
763
+ location: projectRelative(paths.projectRoot, candidate),
764
+ evidence: { directory: name, active_layout: config.layout },
765
+ sanitizer: {
766
+ disposition: 'manual',
767
+ reason: 'Directory may contain historical planning data and must be reviewed before relocation.',
768
+ actions: ['inspect directory contents', 'move valid artifacts to canonical folders', 'record migration'],
769
+ },
770
+ });
771
+ }
772
+ }
773
+ }
774
+ async function collectStateFileFindings(findings, config, paths) {
775
+ const requiredStateFiles = [
776
+ paths.stateFiles.discoveryIndex,
777
+ paths.stateFiles.backlog,
778
+ paths.stateFiles.techDebt,
779
+ paths.stateFiles.finalizeQueue,
780
+ paths.stateFiles.skillCatalog,
781
+ paths.stateFiles.unblockEvents,
782
+ paths.stateFiles.transitionLog,
783
+ paths.stateFiles.architecture,
784
+ paths.stateFiles.serviceCatalog,
785
+ paths.stateFiles.techStack,
786
+ paths.stateFiles.integrationContracts,
787
+ paths.stateFiles.repoMap,
788
+ paths.stateFiles.sourceIndex,
789
+ paths.stateFiles.skillRouting,
790
+ ];
791
+ if (config.frontend.enabled) {
792
+ requiredStateFiles.push(paths.stateFiles.frontendGaps);
793
+ requiredStateFiles.push(paths.stateFiles.frontendMap);
794
+ requiredStateFiles.push(paths.stateFiles.frontendDecisions);
795
+ }
796
+ for (const filePath of requiredStateFiles) {
797
+ if (!(await pathExists(filePath))) {
798
+ addFinding(findings, {
799
+ finding_id: `SH-MISSING-STATE-${path.basename(filePath).toUpperCase().replace(/[^A-Z0-9]/g, '-')}`,
800
+ rule_id: 'SH-RULE-MISSING-STATE-FILE',
801
+ category: 'metadata',
802
+ severity: 'error',
803
+ summary: `Required SDD state file is missing: ${path.basename(filePath)}.`,
804
+ location: projectRelative(paths.projectRoot, filePath),
805
+ evidence: { state_file: projectRelative(paths.projectRoot, filePath) },
806
+ sanitizer: {
807
+ disposition: 'manual',
808
+ reason: 'State files need schema-aware defaults and may require project-specific reconstruction.',
809
+ actions: ['inspect backup or generated views', 'restore schema-valid state file'],
810
+ },
811
+ });
812
+ }
813
+ }
814
+ }
815
+ async function collectLifecyclePlacementFindings(findings, paths, backlogItems) {
816
+ const byId = new Map(backlogItems.map((item) => [item.id, item]));
817
+ const plannedEntries = await fs.readdir(paths.plannedDir).catch(() => []);
818
+ const activeEntries = await fs.readdir(paths.activeDir).catch(() => []);
819
+ const archivedEntries = await fs.readdir(paths.archivedDir).catch(() => []);
820
+ const plannedSet = new Set(plannedEntries);
821
+ const activeSet = new Set(activeEntries);
822
+ const archivedSet = new Set(archivedEntries);
823
+ const archiveCollisions = await detectArchiveArchivedLifecycleCollisions(paths);
824
+ for (const collision of archiveCollisions) {
825
+ addFinding(findings, {
826
+ finding_id: `SH-LIFECYCLE-ARCHIVE-CANONICALIZATION-${collision.feature_id}`,
827
+ rule_id: 'SH-RULE-LIFECYCLE-ARCHIVE-CANONICALIZATION',
828
+ category: 'lifecycle',
829
+ severity: 'error',
830
+ summary: collision.message,
831
+ location: collision.legacy_archive_path,
832
+ evidence: {
833
+ id: collision.feature_id,
834
+ legacy_archive_path: collision.legacy_archive_path,
835
+ canonical_archived_path: collision.canonical_archived_path,
836
+ expected_root: 'archived',
837
+ },
838
+ sanitizer: {
839
+ disposition: 'manual',
840
+ reason: 'Archive versus archived lifecycle duplicates require operator review before completed workspaces are consolidated.',
841
+ actions: ['compare archive and archived workspaces', 'move or merge evidence into .sdd/archived', 'remove the non-canonical archive copy after review'],
842
+ },
843
+ });
844
+ }
845
+ for (const item of byId.values()) {
846
+ const plannedPath = path.join(paths.plannedDir, item.id);
847
+ const activePath = path.join(paths.activeDir, item.id);
848
+ const archivedPath = path.join(paths.archivedDir, item.id);
849
+ const isPlanned = item.status === 'READY' || item.status === 'BLOCKED' || item.status === 'SYNC_REQUIRED';
850
+ if (isPlanned && !plannedSet.has(item.id)) {
851
+ addFinding(findings, {
852
+ finding_id: `SH-LIFECYCLE-MISSING-PLANNED-${item.id}`,
853
+ rule_id: 'SH-RULE-LIFECYCLE-PLACEMENT',
854
+ category: 'lifecycle',
855
+ severity: 'error',
856
+ summary: `${item.id} is ${item.status} but has no planned workspace.`,
857
+ location: projectRelative(paths.projectRoot, plannedPath),
858
+ evidence: { id: item.id, status: item.status, expected_root: 'planned' },
859
+ sanitizer: {
860
+ disposition: 'manual',
861
+ reason: 'Planned workspace reconstruction can be safely regenerated.',
862
+ actions: ['run sdd start to regenerate or manually recreate the folder'],
863
+ },
864
+ });
865
+ }
866
+ if (!isPlanned && plannedSet.has(item.id)) {
867
+ addFinding(findings, {
868
+ finding_id: `SH-LIFECYCLE-UNEXPECTED-PLANNED-${item.id}`,
869
+ rule_id: 'SH-RULE-LIFECYCLE-PLACEMENT',
870
+ category: 'lifecycle',
871
+ severity: 'error',
872
+ summary: `${item.id} is ${item.status} but still has a planned workspace.`,
873
+ location: projectRelative(paths.projectRoot, plannedPath),
874
+ evidence: { id: item.id, status: item.status, actual_root: 'planned' },
875
+ sanitizer: {
876
+ disposition: 'manual',
877
+ reason: 'Moving planned workspaces requires confirmation before archival or removal.',
878
+ actions: ['inspect workspace contents', 'archive or remove only after confirmation'],
879
+ },
880
+ });
881
+ }
882
+ if (item.status === 'IN_PROGRESS' && plannedSet.has(item.id)) {
883
+ addFinding(findings, {
884
+ finding_id: `SH-LIFECYCLE-ACTIVE-PLANNED-COLLISION-${item.id}`,
885
+ rule_id: 'SH-RULE-LIFECYCLE-PLACEMENT',
886
+ category: 'lifecycle',
887
+ severity: 'error',
888
+ summary: `${item.id} is IN_PROGRESS and also has a planned workspace.`,
889
+ location: projectRelative(paths.projectRoot, plannedPath),
890
+ evidence: { id: item.id, status: item.status, actual_root: 'planned' },
891
+ sanitizer: {
892
+ disposition: 'manual',
893
+ reason: 'Planned workspace may conflict with active workspace.',
894
+ actions: ['compare active and planned workspaces', 'remove redundant planned workspace'],
895
+ },
896
+ });
897
+ }
898
+ if (item.status === 'IN_PROGRESS' && !activeSet.has(item.id)) {
899
+ addFinding(findings, {
900
+ finding_id: `SH-LIFECYCLE-MISSING-ACTIVE-${item.id}`,
901
+ rule_id: 'SH-RULE-LIFECYCLE-PLACEMENT',
902
+ category: 'lifecycle',
903
+ severity: 'error',
904
+ summary: `${item.id} is IN_PROGRESS but has no active workspace.`,
905
+ location: projectRelative(paths.projectRoot, activePath),
906
+ evidence: { id: item.id, status: item.status, expected_root: 'active' },
907
+ sanitizer: {
908
+ disposition: 'manual',
909
+ reason: 'Active workspace reconstruction must preserve feature-specific spec, plan, tasks, and changelog.',
910
+ actions: ['run or repair sdd start for the feature', 'restore active workspace files'],
911
+ },
912
+ });
913
+ }
914
+ if (item.status !== 'IN_PROGRESS' && activeSet.has(item.id)) {
915
+ addFinding(findings, {
916
+ finding_id: `SH-LIFECYCLE-UNEXPECTED-ACTIVE-${item.id}`,
917
+ rule_id: 'SH-RULE-LIFECYCLE-PLACEMENT',
918
+ category: 'lifecycle',
919
+ severity: item.status === 'DONE' || item.status === 'ARCHIVED' ? 'warning' : 'error',
920
+ summary: `${item.id} is ${item.status} but still has an active workspace.`,
921
+ location: projectRelative(paths.projectRoot, activePath),
922
+ evidence: { id: item.id, status: item.status, actual_root: 'active' },
923
+ sanitizer: {
924
+ disposition: 'manual',
925
+ reason: 'Moving active workspaces can hide unfinished work if done blindly.',
926
+ actions: ['inspect workspace contents', 'archive or remove only after confirmation'],
927
+ },
928
+ });
929
+ }
930
+ if (item.status === 'IN_PROGRESS' && archivedSet.has(item.id)) {
931
+ addFinding(findings, {
932
+ finding_id: `SH-LIFECYCLE-ACTIVE-ARCHIVED-COLLISION-${item.id}`,
933
+ rule_id: 'SH-RULE-LIFECYCLE-PLACEMENT',
934
+ category: 'lifecycle',
935
+ severity: 'error',
936
+ summary: `${item.id} is IN_PROGRESS and also has an archived workspace.`,
937
+ location: projectRelative(paths.projectRoot, archivedPath),
938
+ evidence: { id: item.id, status: item.status, actual_root: 'archived' },
939
+ sanitizer: {
940
+ disposition: 'manual',
941
+ reason: 'Archived workspace may contain completed historical evidence.',
942
+ actions: ['compare active and archived workspaces', 'preserve historical evidence before relocating'],
943
+ },
944
+ });
945
+ }
946
+ }
947
+ for (const entry of activeEntries) {
948
+ if (!/^FEAT-\d{3,}$/.test(entry))
949
+ continue;
950
+ if (!byId.has(entry)) {
951
+ addFinding(findings, {
952
+ finding_id: `SH-ORPHAN-ACTIVE-${entry}`,
953
+ rule_id: 'SH-RULE-ORPHAN-WORKSPACE',
954
+ category: 'integrity',
955
+ severity: 'error',
956
+ summary: `Active workspace has no matching backlog item: ${entry}.`,
957
+ location: projectRelative(paths.projectRoot, path.join(paths.activeDir, entry)),
958
+ evidence: { workspace: entry, root: 'active' },
959
+ sanitizer: {
960
+ disposition: 'manual',
961
+ reason: 'Orphan workspaces may contain user-authored execution evidence.',
962
+ actions: ['inspect workspace', 'restore backlog entry or archive after review'],
963
+ },
964
+ });
965
+ }
966
+ }
967
+ for (const entry of plannedEntries) {
968
+ if (!/^FEAT-\d{3,}$/.test(entry))
969
+ continue;
970
+ if (!byId.has(entry)) {
971
+ addFinding(findings, {
972
+ finding_id: `SH-ORPHAN-PLANNED-${entry}`,
973
+ rule_id: 'SH-RULE-ORPHAN-WORKSPACE',
974
+ category: 'integrity',
975
+ severity: 'error',
976
+ summary: `Planned workspace has no matching backlog item: ${entry}.`,
977
+ location: projectRelative(paths.projectRoot, path.join(paths.plannedDir, entry)),
978
+ evidence: { workspace: entry, root: 'planned' },
979
+ sanitizer: {
980
+ disposition: 'manual',
981
+ reason: 'Orphan planned workspaces may contain spec details.',
982
+ actions: ['inspect workspace', 'restore backlog entry or remove after review'],
983
+ },
984
+ });
985
+ }
986
+ }
987
+ }
988
+ async function newestMtime(filePaths) {
989
+ let newest = 0;
990
+ for (const filePath of filePaths) {
991
+ const stat = await fs.stat(filePath).catch(() => null);
992
+ if (stat)
993
+ newest = Math.max(newest, stat.mtimeMs);
994
+ }
995
+ return newest;
996
+ }
997
+ async function collectGeneratedViewFindings(findings, config, paths) {
998
+ const generatedViews = [
999
+ path.join(paths.pendenciasDir, 'backlog-features.md'),
1000
+ path.join(paths.pendenciasDir, 'backlog-graph.md'),
1001
+ path.join(paths.pendenciasDir, 'discovery.md'),
1002
+ path.join(paths.pendenciasDir, 'progress.md'),
1003
+ path.join(paths.pendenciasDir, 'compliance-health.md'),
1004
+ path.join(paths.coreDir, 'index.md'),
1005
+ path.join(paths.coreDir, 'arquitetura.md'),
1006
+ path.join(paths.coreDir, 'servicos.md'),
1007
+ path.join(paths.coreDir, 'spec-tecnologica.md'),
1008
+ path.join(paths.coreDir, 'repo-map.md'),
1009
+ ];
1010
+ if (config.frontend.enabled) {
1011
+ generatedViews.push(path.join(paths.pendenciasDir, 'frontend-auditoria.md'));
1012
+ generatedViews.push(path.join(paths.coreDir, 'frontend-map.md'));
1013
+ generatedViews.push(path.join(paths.coreDir, 'frontend-sitemap.md'));
1014
+ generatedViews.push(path.join(paths.coreDir, 'frontend-decisions.md'));
1015
+ }
1016
+ const stateFiles = Object.values(paths.stateFiles);
1017
+ const latestStateMtime = await newestMtime(stateFiles);
1018
+ for (const viewPath of generatedViews) {
1019
+ const exists = await pathExists(viewPath);
1020
+ if (!exists) {
1021
+ addFinding(findings, {
1022
+ finding_id: `SH-MISSING-VIEW-${path.basename(viewPath).toUpperCase().replace(/[^A-Z0-9]/g, '-')}`,
1023
+ rule_id: 'SH-RULE-GENERATED-VIEW',
1024
+ category: 'render',
1025
+ severity: 'warning',
1026
+ summary: `Generated SDD view is missing: ${path.basename(viewPath)}.`,
1027
+ location: projectRelative(paths.projectRoot, viewPath),
1028
+ evidence: { view: projectRelative(paths.projectRoot, viewPath) },
1029
+ sanitizer: {
1030
+ disposition: 'safe',
1031
+ reason: 'Generated views can be recreated from canonical state.',
1032
+ actions: ['run opensdd sdd check --render'],
1033
+ },
1034
+ });
1035
+ continue;
1036
+ }
1037
+ const stat = await fs.stat(viewPath);
1038
+ if (latestStateMtime > stat.mtimeMs + 1000) {
1039
+ addFinding(findings, {
1040
+ finding_id: `SH-STALE-VIEW-${path.basename(viewPath).toUpperCase().replace(/[^A-Z0-9]/g, '-')}`,
1041
+ rule_id: 'SH-RULE-GENERATED-VIEW',
1042
+ category: 'render',
1043
+ severity: 'warning',
1044
+ summary: `Generated SDD view may be stale: ${path.basename(viewPath)}.`,
1045
+ location: projectRelative(paths.projectRoot, viewPath),
1046
+ evidence: {
1047
+ view_mtime: new Date(stat.mtimeMs).toISOString(),
1048
+ latest_state_mtime: new Date(latestStateMtime).toISOString(),
1049
+ },
1050
+ sanitizer: {
1051
+ disposition: 'safe',
1052
+ reason: 'Stale generated views can be refreshed from canonical state.',
1053
+ actions: ['run opensdd sdd check --render'],
1054
+ },
1055
+ });
1056
+ }
1057
+ }
1058
+ }
1059
+ async function collectBoundaryFindings(findings, paths) {
1060
+ if (!(await pathExists(paths.memoryRoot)))
1061
+ return;
1062
+ const memoryRootReal = await safeRealpath(paths.memoryRoot);
1063
+ const stack = [paths.memoryRoot];
1064
+ while (stack.length > 0) {
1065
+ const current = stack.pop();
1066
+ const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
1067
+ for (const entry of entries) {
1068
+ const candidate = path.join(current, entry.name);
1069
+ if (entry.isSymbolicLink()) {
1070
+ const realTarget = await safeRealpath(candidate);
1071
+ if (!isInsidePath(memoryRootReal, realTarget)) {
1072
+ addFinding(findings, {
1073
+ finding_id: `SH-BOUNDARY-SYMLINK-${projectRelative(paths.projectRoot, candidate).toUpperCase().replace(/[^A-Z0-9]/g, '-')}`,
1074
+ rule_id: 'SH-RULE-ROOT-BOUNDARY',
1075
+ category: 'boundary',
1076
+ severity: 'blocker',
1077
+ summary: 'SDD-owned symlink resolves outside the SDD root.',
1078
+ location: projectRelative(paths.projectRoot, candidate),
1079
+ evidence: {
1080
+ symlink: projectRelative(paths.projectRoot, candidate),
1081
+ real_target: realTarget,
1082
+ sdd_root: projectRelative(paths.projectRoot, paths.memoryRoot),
1083
+ },
1084
+ sanitizer: {
1085
+ disposition: 'blocked',
1086
+ reason: 'External symlink targets can escape the SDD root and require manual containment review.',
1087
+ actions: ['remove or replace symlink with in-root artifact after review'],
1088
+ },
1089
+ });
1090
+ }
1091
+ continue;
1092
+ }
1093
+ if (entry.isDirectory()) {
1094
+ stack.push(candidate);
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1099
+ function collectNavigationShapeFindings(findings, snapshot, paths) {
1100
+ const backlogByOrigin = new Map();
1101
+ for (const item of snapshot.backlog.items) {
1102
+ if (!item.origin_ref)
1103
+ continue;
1104
+ const existing = backlogByOrigin.get(item.origin_ref) ?? [];
1105
+ existing.push(item);
1106
+ backlogByOrigin.set(item.origin_ref, existing);
1107
+ }
1108
+ for (const record of snapshot.discoveryIndex.records) {
1109
+ if (record.type !== 'EPIC' && record.type !== 'RAD')
1110
+ continue;
1111
+ if (record.status === 'SPLIT') {
1112
+ const children = backlogByOrigin.get(record.id) ?? [];
1113
+ if (children.length === 0) {
1114
+ addFinding(findings, {
1115
+ finding_id: `SH-NAV-SPLIT-WITHOUT-FEATS-${record.id}`,
1116
+ rule_id: 'SH-RULE-NAVIGATION-INTEGRITY',
1117
+ category: 'references',
1118
+ severity: 'error',
1119
+ summary: `${record.id} is SPLIT but has no child FEATs in backlog.`,
1120
+ location: projectRelative(paths.projectRoot, paths.stateFiles.discoveryIndex),
1121
+ evidence: { id: record.id, status: record.status },
1122
+ sanitizer: {
1123
+ disposition: 'manual',
1124
+ reason: 'Missing child FEATs require recovery from EPIC intent or backlog history.',
1125
+ actions: ['inspect EPIC artifact', 'rerun or repair breakdown'],
1126
+ },
1127
+ });
1128
+ }
1129
+ }
1130
+ }
1131
+ }
1132
+ function summarizeFindings(findings) {
1133
+ const bySeverity = {};
1134
+ const byCategory = {};
1135
+ for (const finding of findings) {
1136
+ bySeverity[finding.severity] = (bySeverity[finding.severity] ?? 0) + 1;
1137
+ byCategory[finding.category] = (byCategory[finding.category] ?? 0) + 1;
1138
+ }
1139
+ return {
1140
+ total: findings.length,
1141
+ by_severity: bySeverity,
1142
+ by_category: byCategory,
1143
+ };
1144
+ }
1145
+ export function getDiagnosticExitCode(report) {
1146
+ const blockers = report.summary.by_severity.blocker ?? 0;
1147
+ const errors = report.summary.by_severity.error ?? 0;
1148
+ const warnings = report.summary.by_severity.warning ?? 0;
1149
+ if (blockers > 0)
1150
+ return 2;
1151
+ if (errors > 0)
1152
+ return 1;
1153
+ if (report.strict && warnings > 0)
1154
+ return 1;
1155
+ return 0;
1156
+ }
1157
+ export function getDiagnosticHealth(report) {
1158
+ if ((report.summary.by_severity.blocker ?? 0) > 0)
1159
+ return 'BLOCKED';
1160
+ if ((report.summary.by_severity.error ?? 0) > 0)
1161
+ return 'ERROR';
1162
+ if ((report.summary.by_severity.warning ?? 0) > 0)
1163
+ return 'WARNING';
1164
+ return 'HEALTHY';
1165
+ }
1166
+ export async function diagnoseSddRoot(projectRoot, options = {}) {
1167
+ const absoluteProjectRoot = path.resolve(projectRoot);
1168
+ const findings = [];
1169
+ let config;
1170
+ let paths;
1171
+ try {
1172
+ config = await loadProjectSddConfig(absoluteProjectRoot);
1173
+ paths = resolveSddPaths(absoluteProjectRoot, config);
1174
+ }
1175
+ catch (error) {
1176
+ const rootPath = path.join(absoluteProjectRoot, '.sdd');
1177
+ addFinding(findings, {
1178
+ finding_id: 'SH-INVALID-SDD-CONFIG',
1179
+ rule_id: 'SH-RULE-INVALID-METADATA',
1180
+ category: 'metadata',
1181
+ severity: 'blocker',
1182
+ summary: 'SDD runtime configuration is invalid.',
1183
+ location: '.sdd/config.yaml',
1184
+ evidence: { error: error.message },
1185
+ sanitizer: {
1186
+ disposition: 'manual',
1187
+ reason: 'Invalid configuration must be corrected before canonical paths can be resolved.',
1188
+ actions: ['fix .sdd/config.yaml', 'rerun diagnosis'],
1189
+ },
1190
+ });
1191
+ return StructuralDiagnosticReportSchema.parse({
1192
+ report_version: 1,
1193
+ generated_at: new Date().toISOString(),
1194
+ root_path: rootPath,
1195
+ strict: options.strict ?? false,
1196
+ findings,
1197
+ summary: summarizeFindings(findings),
1198
+ });
1199
+ }
1200
+ await collectDirectoryFindings(findings, config, paths);
1201
+ await collectStateFileFindings(findings, config, paths);
1202
+ await collectBoundaryFindings(findings, paths);
1203
+ await collectWorkspaceSchemaFindings(findings, paths);
1204
+ await collectWorkspaceAllowlistFindings(findings, paths);
1205
+ let snapshot = null;
1206
+ try {
1207
+ snapshot = await loadStateSnapshot(paths, config);
1208
+ }
1209
+ catch (error) {
1210
+ addFinding(findings, {
1211
+ finding_id: 'SH-INVALID-STATE-METADATA',
1212
+ rule_id: 'SH-RULE-INVALID-METADATA',
1213
+ category: 'metadata',
1214
+ severity: 'blocker',
1215
+ summary: 'One or more SDD state files do not match the canonical schema.',
1216
+ location: projectRelative(paths.projectRoot, paths.stateDir),
1217
+ evidence: { error: error.message },
1218
+ sanitizer: {
1219
+ disposition: 'manual',
1220
+ reason: 'Schema-invalid state may lose information if rewritten automatically.',
1221
+ actions: ['inspect invalid YAML/schema fields', 'restore schema-valid state'],
1222
+ },
1223
+ });
1224
+ }
1225
+ if (snapshot) {
1226
+ collectDuplicateIdFindings(findings, snapshot, paths);
1227
+ collectReferenceFindings(findings, snapshot, paths);
1228
+ collectNavigationShapeFindings(findings, snapshot, paths);
1229
+ await collectLifecyclePlacementFindings(findings, paths, snapshot.backlog.items);
1230
+ await collectLifecycleViolationFindings(findings, paths, snapshot.backlog.items);
1231
+ await collectQualityEvidenceFindings(findings, paths, snapshot.backlog.items);
1232
+ await collectTraceabilityFindings(findings, paths, snapshot.backlog.items);
1233
+ await collectPrivacyComplianceFindings(findings, paths, snapshot);
1234
+ await collectGeneratedViewFindings(findings, config, paths);
1235
+ }
1236
+ return StructuralDiagnosticReportSchema.parse({
1237
+ report_version: 1,
1238
+ generated_at: new Date().toISOString(),
1239
+ root_path: paths.memoryRoot,
1240
+ strict: options.strict ?? false,
1241
+ findings,
1242
+ summary: summarizeFindings(findings),
1243
+ });
1244
+ }
1245
+ export async function writeDiagnosticReport(projectRoot, outputPath, report) {
1246
+ const config = await loadProjectSddConfig(projectRoot);
1247
+ const paths = resolveSddPaths(path.resolve(projectRoot), config);
1248
+ const resolvedOutputPath = path.resolve(projectRoot, outputPath);
1249
+ const resolvedMemoryRoot = path.resolve(paths.memoryRoot);
1250
+ if (!isInsidePath(resolvedMemoryRoot, resolvedOutputPath)) {
1251
+ throw new SddDiagnosticOptionsError(`Diagnostic output must stay inside the active SDD root: ${projectRelative(path.resolve(projectRoot), resolvedOutputPath)}`);
1252
+ }
1253
+ await fs.mkdir(path.dirname(resolvedOutputPath), { recursive: true });
1254
+ await fs.writeFile(resolvedOutputPath, `${JSON.stringify(report, null, 2)}\n`, 'utf-8');
1255
+ return resolvedOutputPath;
1256
+ }
1257
+ export function formatDiagnosticText(report) {
1258
+ const severity = report.summary.by_severity;
1259
+ const lines = [
1260
+ `SDD diagnosis: ${report.root_path}`,
1261
+ `Health: ${getDiagnosticHealth(report)}`,
1262
+ `Findings: ${severity.blocker ?? 0} blockers, ${severity.error ?? 0} errors, ${severity.warning ?? 0} warnings, ${severity.info ?? 0} info`,
1263
+ ];
1264
+ if (report.findings.length > 0) {
1265
+ lines.push('');
1266
+ }
1267
+ for (const finding of report.findings) {
1268
+ lines.push(`${finding.severity.toUpperCase()} ${finding.category}/${finding.rule_id}`);
1269
+ lines.push(` id: ${finding.finding_id}`);
1270
+ lines.push(` message: ${finding.summary}`);
1271
+ if (finding.location) {
1272
+ lines.push(` path: ${finding.location}`);
1273
+ }
1274
+ if (finding.rule_id === 'SH-RULE-WORKSPACE-SCHEMA-INVALID') {
1275
+ const invalidFields = Array.isArray(finding.evidence.invalid_fields)
1276
+ ? finding.evidence.invalid_fields
1277
+ : [];
1278
+ if (invalidFields.length > 0) {
1279
+ lines.push(` invalid fields: ${invalidFields.map((field) => `${field.path ?? '<root>'} (${field.message ?? 'invalid'})`).join('; ')}`);
1280
+ }
1281
+ if (typeof finding.evidence.parse_error === 'string') {
1282
+ lines.push(` parse error: ${finding.evidence.parse_error}`);
1283
+ }
1284
+ }
1285
+ if (finding.rule_id === 'SH-RULE-WORKSPACE-FILE-NOT-ALLOWLISTED') {
1286
+ lines.push(` allowed: ${WORKSPACE_FILE_ALLOWLIST.join(', ')}`);
1287
+ }
1288
+ if (finding.rule_id === 'SH-RULE-LIFECYCLE-VIOLATION') {
1289
+ lines.push(` expected status: ${String(finding.evidence.expected_status)}; actual status: ${String(finding.evidence.actual_status)}`);
1290
+ }
1291
+ if (finding.rule_id === 'SH-RULE-QUALITY-EVIDENCE-INCOMPLETE') {
1292
+ lines.push(` evidence entries: ${String(finding.evidence.evidence_count ?? 0)}; coverage referenced: ${String(finding.evidence.coverage_referenced ?? false)}`);
1293
+ }
1294
+ lines.push(` fix: ${finding.sanitizer.disposition} - ${finding.sanitizer.reason}`);
1295
+ }
1296
+ return lines.join('\n');
1297
+ }
1298
+ export class SddDiagnoseCommand {
1299
+ async execute(projectRoot, options = {}) {
1300
+ const report = await diagnoseSddRoot(projectRoot, { strict: options.strict });
1301
+ const outputPath = options.output
1302
+ ? await writeDiagnosticReport(projectRoot, options.output, report)
1303
+ : undefined;
1304
+ return {
1305
+ report,
1306
+ exitCode: getDiagnosticExitCode(report),
1307
+ health: getDiagnosticHealth(report),
1308
+ outputPath,
1309
+ };
1310
+ }
1311
+ }
1312
+ //# sourceMappingURL=diagnose.js.map