opencode-telos 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (568) hide show
  1. package/README.md +927 -0
  2. package/dist/code-intelligence/analyzer.d.ts +9 -0
  3. package/dist/code-intelligence/analyzer.d.ts.map +1 -0
  4. package/dist/code-intelligence/analyzer.js +321 -0
  5. package/dist/code-intelligence/analyzer.js.map +1 -0
  6. package/dist/code-intelligence/ast/cache.d.ts +16 -0
  7. package/dist/code-intelligence/ast/cache.d.ts.map +1 -0
  8. package/dist/code-intelligence/ast/cache.js +44 -0
  9. package/dist/code-intelligence/ast/cache.js.map +1 -0
  10. package/dist/code-intelligence/ast/common.d.ts +8 -0
  11. package/dist/code-intelligence/ast/common.d.ts.map +1 -0
  12. package/dist/code-intelligence/ast/common.js +42 -0
  13. package/dist/code-intelligence/ast/common.js.map +1 -0
  14. package/dist/code-intelligence/ast/component.d.ts +10 -0
  15. package/dist/code-intelligence/ast/component.d.ts.map +1 -0
  16. package/dist/code-intelligence/ast/component.js +38 -0
  17. package/dist/code-intelligence/ast/component.js.map +1 -0
  18. package/dist/code-intelligence/ast/fallback.d.ts +9 -0
  19. package/dist/code-intelligence/ast/fallback.d.ts.map +1 -0
  20. package/dist/code-intelligence/ast/fallback.js +38 -0
  21. package/dist/code-intelligence/ast/fallback.js.map +1 -0
  22. package/dist/code-intelligence/ast/index.d.ts +5 -0
  23. package/dist/code-intelligence/ast/index.d.ts.map +1 -0
  24. package/dist/code-intelligence/ast/index.js +5 -0
  25. package/dist/code-intelligence/ast/index.js.map +1 -0
  26. package/dist/code-intelligence/ast/ir.d.ts +72 -0
  27. package/dist/code-intelligence/ast/ir.d.ts.map +1 -0
  28. package/dist/code-intelligence/ast/ir.js +2 -0
  29. package/dist/code-intelligence/ast/ir.js.map +1 -0
  30. package/dist/code-intelligence/ast/metrics.d.ts +25 -0
  31. package/dist/code-intelligence/ast/metrics.d.ts.map +1 -0
  32. package/dist/code-intelligence/ast/metrics.js +38 -0
  33. package/dist/code-intelligence/ast/metrics.js.map +1 -0
  34. package/dist/code-intelligence/ast/registry.d.ts +5 -0
  35. package/dist/code-intelligence/ast/registry.d.ts.map +1 -0
  36. package/dist/code-intelligence/ast/registry.js +22 -0
  37. package/dist/code-intelligence/ast/registry.js.map +1 -0
  38. package/dist/code-intelligence/ast/tree-sitter.d.ts +8 -0
  39. package/dist/code-intelligence/ast/tree-sitter.d.ts.map +1 -0
  40. package/dist/code-intelligence/ast/tree-sitter.js +247 -0
  41. package/dist/code-intelligence/ast/tree-sitter.js.map +1 -0
  42. package/dist/code-intelligence/ast/typescript.d.ts +8 -0
  43. package/dist/code-intelligence/ast/typescript.d.ts.map +1 -0
  44. package/dist/code-intelligence/ast/typescript.js +147 -0
  45. package/dist/code-intelligence/ast/typescript.js.map +1 -0
  46. package/dist/index.d.ts +10 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +18 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/mcp/server.d.ts +27 -0
  51. package/dist/mcp/server.d.ts.map +1 -0
  52. package/dist/mcp/server.js +67 -0
  53. package/dist/mcp/server.js.map +1 -0
  54. package/dist/opencode/hooks.d.ts +9 -0
  55. package/dist/opencode/hooks.d.ts.map +1 -0
  56. package/dist/opencode/hooks.js +583 -0
  57. package/dist/opencode/hooks.js.map +1 -0
  58. package/dist/opencode/router/categories.d.ts +31 -0
  59. package/dist/opencode/router/categories.d.ts.map +1 -0
  60. package/dist/opencode/router/categories.js +165 -0
  61. package/dist/opencode/router/categories.js.map +1 -0
  62. package/dist/opencode/router/embeddings.d.ts +41 -0
  63. package/dist/opencode/router/embeddings.d.ts.map +1 -0
  64. package/dist/opencode/router/embeddings.js +136 -0
  65. package/dist/opencode/router/embeddings.js.map +1 -0
  66. package/dist/opencode/router/graph-state-snapshot.d.ts +35 -0
  67. package/dist/opencode/router/graph-state-snapshot.d.ts.map +1 -0
  68. package/dist/opencode/router/graph-state-snapshot.js +129 -0
  69. package/dist/opencode/router/graph-state-snapshot.js.map +1 -0
  70. package/dist/opencode/router/index.d.ts +21 -0
  71. package/dist/opencode/router/index.d.ts.map +1 -0
  72. package/dist/opencode/router/index.js +23 -0
  73. package/dist/opencode/router/index.js.map +1 -0
  74. package/dist/opencode/router/intent-classifier.d.ts +35 -0
  75. package/dist/opencode/router/intent-classifier.d.ts.map +1 -0
  76. package/dist/opencode/router/intent-classifier.js +108 -0
  77. package/dist/opencode/router/intent-classifier.js.map +1 -0
  78. package/dist/opencode/router/semantic-nudge.d.ts +42 -0
  79. package/dist/opencode/router/semantic-nudge.d.ts.map +1 -0
  80. package/dist/opencode/router/semantic-nudge.js +73 -0
  81. package/dist/opencode/router/semantic-nudge.js.map +1 -0
  82. package/dist/opencode/router/state-gate.d.ts +25 -0
  83. package/dist/opencode/router/state-gate.d.ts.map +1 -0
  84. package/dist/opencode/router/state-gate.js +178 -0
  85. package/dist/opencode/router/state-gate.js.map +1 -0
  86. package/dist/opencode/router/tool-embeddings.d.ts +36 -0
  87. package/dist/opencode/router/tool-embeddings.d.ts.map +1 -0
  88. package/dist/opencode/router/tool-embeddings.js +140 -0
  89. package/dist/opencode/router/tool-embeddings.js.map +1 -0
  90. package/dist/opencode/router/tool-registry.d.ts +45 -0
  91. package/dist/opencode/router/tool-registry.d.ts.map +1 -0
  92. package/dist/opencode/router/tool-registry.js +103 -0
  93. package/dist/opencode/router/tool-registry.js.map +1 -0
  94. package/dist/opencode/router/tool-taxonomy.d.ts +51 -0
  95. package/dist/opencode/router/tool-taxonomy.d.ts.map +1 -0
  96. package/dist/opencode/router/tool-taxonomy.js +250 -0
  97. package/dist/opencode/router/tool-taxonomy.js.map +1 -0
  98. package/dist/opencode/router/tools-composite.d.ts +21 -0
  99. package/dist/opencode/router/tools-composite.d.ts.map +1 -0
  100. package/dist/opencode/router/tools-composite.js +684 -0
  101. package/dist/opencode/router/tools-composite.js.map +1 -0
  102. package/dist/opencode/shell-hooks.d.ts +7 -0
  103. package/dist/opencode/shell-hooks.d.ts.map +1 -0
  104. package/dist/opencode/shell-hooks.js +96 -0
  105. package/dist/opencode/shell-hooks.js.map +1 -0
  106. package/dist/opencode/system-prompt.d.ts +10 -0
  107. package/dist/opencode/system-prompt.d.ts.map +1 -0
  108. package/dist/opencode/system-prompt.js +600 -0
  109. package/dist/opencode/system-prompt.js.map +1 -0
  110. package/dist/opencode/tools.d.ts +8 -0
  111. package/dist/opencode/tools.d.ts.map +1 -0
  112. package/dist/opencode/tools.js +3731 -0
  113. package/dist/opencode/tools.js.map +1 -0
  114. package/dist/opencode/workflows/chains.d.ts +52 -0
  115. package/dist/opencode/workflows/chains.d.ts.map +1 -0
  116. package/dist/opencode/workflows/chains.js +170 -0
  117. package/dist/opencode/workflows/chains.js.map +1 -0
  118. package/dist/opencode/workflows/executor.d.ts +52 -0
  119. package/dist/opencode/workflows/executor.d.ts.map +1 -0
  120. package/dist/opencode/workflows/executor.js +130 -0
  121. package/dist/opencode/workflows/executor.js.map +1 -0
  122. package/dist/opencode/workflows/index.d.ts +14 -0
  123. package/dist/opencode/workflows/index.d.ts.map +1 -0
  124. package/dist/opencode/workflows/index.js +15 -0
  125. package/dist/opencode/workflows/index.js.map +1 -0
  126. package/dist/opencode/workflows/tools-workflow.d.ts +24 -0
  127. package/dist/opencode/workflows/tools-workflow.d.ts.map +1 -0
  128. package/dist/opencode/workflows/tools-workflow.js +85 -0
  129. package/dist/opencode/workflows/tools-workflow.js.map +1 -0
  130. package/dist/opencode/workflows/types.d.ts +32 -0
  131. package/dist/opencode/workflows/types.d.ts.map +1 -0
  132. package/dist/opencode/workflows/types.js +13 -0
  133. package/dist/opencode/workflows/types.js.map +1 -0
  134. package/dist/sdd/analysis/compliance.d.ts +30 -0
  135. package/dist/sdd/analysis/compliance.d.ts.map +1 -0
  136. package/dist/sdd/analysis/compliance.js +251 -0
  137. package/dist/sdd/analysis/compliance.js.map +1 -0
  138. package/dist/sdd/analysis/scalability.d.ts +15 -0
  139. package/dist/sdd/analysis/scalability.d.ts.map +1 -0
  140. package/dist/sdd/analysis/scalability.js +72 -0
  141. package/dist/sdd/analysis/scalability.js.map +1 -0
  142. package/dist/sdd/analysis/security.d.ts +25 -0
  143. package/dist/sdd/analysis/security.d.ts.map +1 -0
  144. package/dist/sdd/analysis/security.js +94 -0
  145. package/dist/sdd/analysis/security.js.map +1 -0
  146. package/dist/sdd/brownfield/scanner.d.ts +31 -0
  147. package/dist/sdd/brownfield/scanner.d.ts.map +1 -0
  148. package/dist/sdd/brownfield/scanner.js +228 -0
  149. package/dist/sdd/brownfield/scanner.js.map +1 -0
  150. package/dist/sdd/cache/manager.d.ts +237 -0
  151. package/dist/sdd/cache/manager.d.ts.map +1 -0
  152. package/dist/sdd/cache/manager.js +770 -0
  153. package/dist/sdd/cache/manager.js.map +1 -0
  154. package/dist/sdd/cache/snapshot-store.d.ts +39 -0
  155. package/dist/sdd/cache/snapshot-store.d.ts.map +1 -0
  156. package/dist/sdd/cache/snapshot-store.js +156 -0
  157. package/dist/sdd/cache/snapshot-store.js.map +1 -0
  158. package/dist/sdd/changes/manager.d.ts +58 -0
  159. package/dist/sdd/changes/manager.d.ts.map +1 -0
  160. package/dist/sdd/changes/manager.js +235 -0
  161. package/dist/sdd/changes/manager.js.map +1 -0
  162. package/dist/sdd/cicd/generators.d.ts +16 -0
  163. package/dist/sdd/cicd/generators.d.ts.map +1 -0
  164. package/dist/sdd/cicd/generators.js +1042 -0
  165. package/dist/sdd/cicd/generators.js.map +1 -0
  166. package/dist/sdd/code-quality/complexity.d.ts +38 -0
  167. package/dist/sdd/code-quality/complexity.d.ts.map +1 -0
  168. package/dist/sdd/code-quality/complexity.js +161 -0
  169. package/dist/sdd/code-quality/complexity.js.map +1 -0
  170. package/dist/sdd/code-quality/conventions.d.ts +33 -0
  171. package/dist/sdd/code-quality/conventions.d.ts.map +1 -0
  172. package/dist/sdd/code-quality/conventions.js +193 -0
  173. package/dist/sdd/code-quality/conventions.js.map +1 -0
  174. package/dist/sdd/code-quality/dependencies.d.ts +27 -0
  175. package/dist/sdd/code-quality/dependencies.d.ts.map +1 -0
  176. package/dist/sdd/code-quality/dependencies.js +142 -0
  177. package/dist/sdd/code-quality/dependencies.js.map +1 -0
  178. package/dist/sdd/code-quality/import-analyzer.d.ts +49 -0
  179. package/dist/sdd/code-quality/import-analyzer.d.ts.map +1 -0
  180. package/dist/sdd/code-quality/import-analyzer.js +213 -0
  181. package/dist/sdd/code-quality/import-analyzer.js.map +1 -0
  182. package/dist/sdd/code-quality/metrics.d.ts +35 -0
  183. package/dist/sdd/code-quality/metrics.d.ts.map +1 -0
  184. package/dist/sdd/code-quality/metrics.js +172 -0
  185. package/dist/sdd/code-quality/metrics.js.map +1 -0
  186. package/dist/sdd/code-quality/smells.d.ts +38 -0
  187. package/dist/sdd/code-quality/smells.d.ts.map +1 -0
  188. package/dist/sdd/code-quality/smells.js +319 -0
  189. package/dist/sdd/code-quality/smells.js.map +1 -0
  190. package/dist/sdd/code-quality/symbol-parser.d.ts +28 -0
  191. package/dist/sdd/code-quality/symbol-parser.d.ts.map +1 -0
  192. package/dist/sdd/code-quality/symbol-parser.js +230 -0
  193. package/dist/sdd/code-quality/symbol-parser.js.map +1 -0
  194. package/dist/sdd/code-quality/usage-tracker.d.ts +59 -0
  195. package/dist/sdd/code-quality/usage-tracker.d.ts.map +1 -0
  196. package/dist/sdd/code-quality/usage-tracker.js +402 -0
  197. package/dist/sdd/code-quality/usage-tracker.js.map +1 -0
  198. package/dist/sdd/code-quality/utils.d.ts +6 -0
  199. package/dist/sdd/code-quality/utils.d.ts.map +1 -0
  200. package/dist/sdd/code-quality/utils.js +14 -0
  201. package/dist/sdd/code-quality/utils.js.map +1 -0
  202. package/dist/sdd/codegen/generator.d.ts +42 -0
  203. package/dist/sdd/codegen/generator.d.ts.map +1 -0
  204. package/dist/sdd/codegen/generator.js +1023 -0
  205. package/dist/sdd/codegen/generator.js.map +1 -0
  206. package/dist/sdd/constitution/validator.d.ts +37 -0
  207. package/dist/sdd/constitution/validator.d.ts.map +1 -0
  208. package/dist/sdd/constitution/validator.js +192 -0
  209. package/dist/sdd/constitution/validator.js.map +1 -0
  210. package/dist/sdd/cost/estimator.d.ts +31 -0
  211. package/dist/sdd/cost/estimator.d.ts.map +1 -0
  212. package/dist/sdd/cost/estimator.js +92 -0
  213. package/dist/sdd/cost/estimator.js.map +1 -0
  214. package/dist/sdd/coverage/tracker.d.ts +47 -0
  215. package/dist/sdd/coverage/tracker.d.ts.map +1 -0
  216. package/dist/sdd/coverage/tracker.js +251 -0
  217. package/dist/sdd/coverage/tracker.js.map +1 -0
  218. package/dist/sdd/disaster/recovery.d.ts +11 -0
  219. package/dist/sdd/disaster/recovery.d.ts.map +1 -0
  220. package/dist/sdd/disaster/recovery.js +55 -0
  221. package/dist/sdd/disaster/recovery.js.map +1 -0
  222. package/dist/sdd/discovery/adaptive.d.ts +14 -0
  223. package/dist/sdd/discovery/adaptive.d.ts.map +1 -0
  224. package/dist/sdd/discovery/adaptive.js +145 -0
  225. package/dist/sdd/discovery/adaptive.js.map +1 -0
  226. package/dist/sdd/discovery/briefing-analyzer.d.ts +66 -0
  227. package/dist/sdd/discovery/briefing-analyzer.d.ts.map +1 -0
  228. package/dist/sdd/discovery/briefing-analyzer.js +672 -0
  229. package/dist/sdd/discovery/briefing-analyzer.js.map +1 -0
  230. package/dist/sdd/discovery/briefing.d.ts +109 -0
  231. package/dist/sdd/discovery/briefing.d.ts.map +1 -0
  232. package/dist/sdd/discovery/briefing.js +617 -0
  233. package/dist/sdd/discovery/briefing.js.map +1 -0
  234. package/dist/sdd/discovery/graph-builder.d.ts +10 -0
  235. package/dist/sdd/discovery/graph-builder.d.ts.map +1 -0
  236. package/dist/sdd/discovery/graph-builder.js +510 -0
  237. package/dist/sdd/discovery/graph-builder.js.map +1 -0
  238. package/dist/sdd/documentation/generator.d.ts +7 -0
  239. package/dist/sdd/documentation/generator.d.ts.map +1 -0
  240. package/dist/sdd/documentation/generator.js +119 -0
  241. package/dist/sdd/documentation/generator.js.map +1 -0
  242. package/dist/sdd/domain/types.d.ts +460 -0
  243. package/dist/sdd/domain/types.d.ts.map +1 -0
  244. package/dist/sdd/domain/types.js +25 -0
  245. package/dist/sdd/domain/types.js.map +1 -0
  246. package/dist/sdd/drift/detector.d.ts +28 -0
  247. package/dist/sdd/drift/detector.d.ts.map +1 -0
  248. package/dist/sdd/drift/detector.js +379 -0
  249. package/dist/sdd/drift/detector.js.map +1 -0
  250. package/dist/sdd/drift/exclusion.d.ts +54 -0
  251. package/dist/sdd/drift/exclusion.d.ts.map +1 -0
  252. package/dist/sdd/drift/exclusion.js +179 -0
  253. package/dist/sdd/drift/exclusion.js.map +1 -0
  254. package/dist/sdd/drift/signals.d.ts +52 -0
  255. package/dist/sdd/drift/signals.d.ts.map +1 -0
  256. package/dist/sdd/drift/signals.js +470 -0
  257. package/dist/sdd/drift/signals.js.map +1 -0
  258. package/dist/sdd/enforcement/interceptor.d.ts +47 -0
  259. package/dist/sdd/enforcement/interceptor.d.ts.map +1 -0
  260. package/dist/sdd/enforcement/interceptor.js +365 -0
  261. package/dist/sdd/enforcement/interceptor.js.map +1 -0
  262. package/dist/sdd/enforcement/workflow-tracker.d.ts +66 -0
  263. package/dist/sdd/enforcement/workflow-tracker.d.ts.map +1 -0
  264. package/dist/sdd/enforcement/workflow-tracker.js +229 -0
  265. package/dist/sdd/enforcement/workflow-tracker.js.map +1 -0
  266. package/dist/sdd/graph/engine.d.ts +44 -0
  267. package/dist/sdd/graph/engine.d.ts.map +1 -0
  268. package/dist/sdd/graph/engine.js +203 -0
  269. package/dist/sdd/graph/engine.js.map +1 -0
  270. package/dist/sdd/graph/index.d.ts +160 -0
  271. package/dist/sdd/graph/index.d.ts.map +1 -0
  272. package/dist/sdd/graph/index.js +624 -0
  273. package/dist/sdd/graph/index.js.map +1 -0
  274. package/dist/sdd/graph/integrity-guard.d.ts +129 -0
  275. package/dist/sdd/graph/integrity-guard.d.ts.map +1 -0
  276. package/dist/sdd/graph/integrity-guard.js +263 -0
  277. package/dist/sdd/graph/integrity-guard.js.map +1 -0
  278. package/dist/sdd/graph/integrity.d.ts +79 -0
  279. package/dist/sdd/graph/integrity.d.ts.map +1 -0
  280. package/dist/sdd/graph/integrity.js +462 -0
  281. package/dist/sdd/graph/integrity.js.map +1 -0
  282. package/dist/sdd/graph/pruner.d.ts +27 -0
  283. package/dist/sdd/graph/pruner.d.ts.map +1 -0
  284. package/dist/sdd/graph/pruner.js +203 -0
  285. package/dist/sdd/graph/pruner.js.map +1 -0
  286. package/dist/sdd/graph/traverse.d.ts +43 -0
  287. package/dist/sdd/graph/traverse.d.ts.map +1 -0
  288. package/dist/sdd/graph/traverse.js +265 -0
  289. package/dist/sdd/graph/traverse.js.map +1 -0
  290. package/dist/sdd/incidents/manager.d.ts +10 -0
  291. package/dist/sdd/incidents/manager.d.ts.map +1 -0
  292. package/dist/sdd/incidents/manager.js +63 -0
  293. package/dist/sdd/incidents/manager.js.map +1 -0
  294. package/dist/sdd/knowledge/transfer.d.ts +24 -0
  295. package/dist/sdd/knowledge/transfer.d.ts.map +1 -0
  296. package/dist/sdd/knowledge/transfer.js +94 -0
  297. package/dist/sdd/knowledge/transfer.js.map +1 -0
  298. package/dist/sdd/migrations/fixes.d.ts +2 -0
  299. package/dist/sdd/migrations/fixes.d.ts.map +1 -0
  300. package/dist/sdd/migrations/fixes.js +159 -0
  301. package/dist/sdd/migrations/fixes.js.map +1 -0
  302. package/dist/sdd/migrations/index.d.ts +2 -0
  303. package/dist/sdd/migrations/index.d.ts.map +1 -0
  304. package/dist/sdd/migrations/index.js +5 -0
  305. package/dist/sdd/migrations/index.js.map +1 -0
  306. package/dist/sdd/migrations/migration-runner.d.ts +41 -0
  307. package/dist/sdd/migrations/migration-runner.d.ts.map +1 -0
  308. package/dist/sdd/migrations/migration-runner.js +91 -0
  309. package/dist/sdd/migrations/migration-runner.js.map +1 -0
  310. package/dist/sdd/monitoring/setup.d.ts +40 -0
  311. package/dist/sdd/monitoring/setup.d.ts.map +1 -0
  312. package/dist/sdd/monitoring/setup.js +177 -0
  313. package/dist/sdd/monitoring/setup.js.map +1 -0
  314. package/dist/sdd/monitoring/telemetry.d.ts +18 -0
  315. package/dist/sdd/monitoring/telemetry.d.ts.map +1 -0
  316. package/dist/sdd/monitoring/telemetry.js +37 -0
  317. package/dist/sdd/monitoring/telemetry.js.map +1 -0
  318. package/dist/sdd/patterns/anti-patterns.d.ts +21 -0
  319. package/dist/sdd/patterns/anti-patterns.d.ts.map +1 -0
  320. package/dist/sdd/patterns/anti-patterns.js +286 -0
  321. package/dist/sdd/patterns/anti-patterns.js.map +1 -0
  322. package/dist/sdd/patterns/ast-clones.d.ts +23 -0
  323. package/dist/sdd/patterns/ast-clones.d.ts.map +1 -0
  324. package/dist/sdd/patterns/ast-clones.js +171 -0
  325. package/dist/sdd/patterns/ast-clones.js.map +1 -0
  326. package/dist/sdd/patterns/config-drift.d.ts +16 -0
  327. package/dist/sdd/patterns/config-drift.d.ts.map +1 -0
  328. package/dist/sdd/patterns/config-drift.js +322 -0
  329. package/dist/sdd/patterns/config-drift.js.map +1 -0
  330. package/dist/sdd/patterns/contradictions.d.ts +38 -0
  331. package/dist/sdd/patterns/contradictions.d.ts.map +1 -0
  332. package/dist/sdd/patterns/contradictions.js +237 -0
  333. package/dist/sdd/patterns/contradictions.js.map +1 -0
  334. package/dist/sdd/patterns/learner.d.ts +49 -0
  335. package/dist/sdd/patterns/learner.d.ts.map +1 -0
  336. package/dist/sdd/patterns/learner.js +226 -0
  337. package/dist/sdd/patterns/learner.js.map +1 -0
  338. package/dist/sdd/permissions/access.d.ts +69 -0
  339. package/dist/sdd/permissions/access.d.ts.map +1 -0
  340. package/dist/sdd/permissions/access.js +333 -0
  341. package/dist/sdd/permissions/access.js.map +1 -0
  342. package/dist/sdd/persistence/repository.d.ts +60 -0
  343. package/dist/sdd/persistence/repository.d.ts.map +1 -0
  344. package/dist/sdd/persistence/repository.js +96 -0
  345. package/dist/sdd/persistence/repository.js.map +1 -0
  346. package/dist/sdd/persistence/sqlite.d.ts +122 -0
  347. package/dist/sdd/persistence/sqlite.d.ts.map +1 -0
  348. package/dist/sdd/persistence/sqlite.js +602 -0
  349. package/dist/sdd/persistence/sqlite.js.map +1 -0
  350. package/dist/sdd/persistence/yaml.d.ts +41 -0
  351. package/dist/sdd/persistence/yaml.d.ts.map +1 -0
  352. package/dist/sdd/persistence/yaml.js +382 -0
  353. package/dist/sdd/persistence/yaml.js.map +1 -0
  354. package/dist/sdd/promises/classifier.d.ts +48 -0
  355. package/dist/sdd/promises/classifier.d.ts.map +1 -0
  356. package/dist/sdd/promises/classifier.js +215 -0
  357. package/dist/sdd/promises/classifier.js.map +1 -0
  358. package/dist/sdd/promises/tracker.d.ts +20 -0
  359. package/dist/sdd/promises/tracker.d.ts.map +1 -0
  360. package/dist/sdd/promises/tracker.js +204 -0
  361. package/dist/sdd/promises/tracker.js.map +1 -0
  362. package/dist/sdd/quality/scorer.d.ts +44 -0
  363. package/dist/sdd/quality/scorer.d.ts.map +1 -0
  364. package/dist/sdd/quality/scorer.js +216 -0
  365. package/dist/sdd/quality/scorer.js.map +1 -0
  366. package/dist/sdd/rollback/manager.d.ts +45 -0
  367. package/dist/sdd/rollback/manager.d.ts.map +1 -0
  368. package/dist/sdd/rollback/manager.js +266 -0
  369. package/dist/sdd/rollback/manager.js.map +1 -0
  370. package/dist/sdd/session/handoff.d.ts +52 -0
  371. package/dist/sdd/session/handoff.d.ts.map +1 -0
  372. package/dist/sdd/session/handoff.js +264 -0
  373. package/dist/sdd/session/handoff.js.map +1 -0
  374. package/dist/sdd/sla/tracker.d.ts +11 -0
  375. package/dist/sdd/sla/tracker.d.ts.map +1 -0
  376. package/dist/sdd/sla/tracker.js +52 -0
  377. package/dist/sdd/sla/tracker.js.map +1 -0
  378. package/dist/sdd/sync/git-sync.d.ts +61 -0
  379. package/dist/sdd/sync/git-sync.d.ts.map +1 -0
  380. package/dist/sdd/sync/git-sync.js +297 -0
  381. package/dist/sdd/sync/git-sync.js.map +1 -0
  382. package/dist/sdd/toggle/state.d.ts +8 -0
  383. package/dist/sdd/toggle/state.d.ts.map +1 -0
  384. package/dist/sdd/toggle/state.js +32 -0
  385. package/dist/sdd/toggle/state.js.map +1 -0
  386. package/dist/sdd/transactions/manager.d.ts +12 -0
  387. package/dist/sdd/transactions/manager.d.ts.map +1 -0
  388. package/dist/sdd/transactions/manager.js +80 -0
  389. package/dist/sdd/transactions/manager.js.map +1 -0
  390. package/dist/sdd/validation/coverage-index.d.ts +117 -0
  391. package/dist/sdd/validation/coverage-index.d.ts.map +1 -0
  392. package/dist/sdd/validation/coverage-index.js +251 -0
  393. package/dist/sdd/validation/coverage-index.js.map +1 -0
  394. package/dist/sdd/validation/executable.d.ts +26 -0
  395. package/dist/sdd/validation/executable.d.ts.map +1 -0
  396. package/dist/sdd/validation/executable.js +122 -0
  397. package/dist/sdd/validation/executable.js.map +1 -0
  398. package/dist/sdd/validation/smart-validator.d.ts +53 -0
  399. package/dist/sdd/validation/smart-validator.d.ts.map +1 -0
  400. package/dist/sdd/validation/smart-validator.js +347 -0
  401. package/dist/sdd/validation/smart-validator.js.map +1 -0
  402. package/dist/sdd/validation/validator.d.ts +39 -0
  403. package/dist/sdd/validation/validator.d.ts.map +1 -0
  404. package/dist/sdd/validation/validator.js +512 -0
  405. package/dist/sdd/validation/validator.js.map +1 -0
  406. package/dist/sdd/workflow/exporter.d.ts +36 -0
  407. package/dist/sdd/workflow/exporter.d.ts.map +1 -0
  408. package/dist/sdd/workflow/exporter.js +112 -0
  409. package/dist/sdd/workflow/exporter.js.map +1 -0
  410. package/dist/sdd/workflows/ab-testing.d.ts +18 -0
  411. package/dist/sdd/workflows/ab-testing.d.ts.map +1 -0
  412. package/dist/sdd/workflows/ab-testing.js +67 -0
  413. package/dist/sdd/workflows/ab-testing.js.map +1 -0
  414. package/dist/sdd/workflows/bug-fix.d.ts +13 -0
  415. package/dist/sdd/workflows/bug-fix.d.ts.map +1 -0
  416. package/dist/sdd/workflows/bug-fix.js +65 -0
  417. package/dist/sdd/workflows/bug-fix.js.map +1 -0
  418. package/dist/sdd/workflows/data-migration.d.ts +14 -0
  419. package/dist/sdd/workflows/data-migration.d.ts.map +1 -0
  420. package/dist/sdd/workflows/data-migration.js +68 -0
  421. package/dist/sdd/workflows/data-migration.js.map +1 -0
  422. package/dist/sdd/workflows/deprecation.d.ts +15 -0
  423. package/dist/sdd/workflows/deprecation.d.ts.map +1 -0
  424. package/dist/sdd/workflows/deprecation.js +68 -0
  425. package/dist/sdd/workflows/deprecation.js.map +1 -0
  426. package/dist/sdd/workflows/feature-flags.d.ts +14 -0
  427. package/dist/sdd/workflows/feature-flags.d.ts.map +1 -0
  428. package/dist/sdd/workflows/feature-flags.js +73 -0
  429. package/dist/sdd/workflows/feature-flags.js.map +1 -0
  430. package/dist/sdd/workflows/hotfix.d.ts +13 -0
  431. package/dist/sdd/workflows/hotfix.d.ts.map +1 -0
  432. package/dist/sdd/workflows/hotfix.js +67 -0
  433. package/dist/sdd/workflows/hotfix.js.map +1 -0
  434. package/dist/sdd/workflows/multi-tenancy.d.ts +13 -0
  435. package/dist/sdd/workflows/multi-tenancy.d.ts.map +1 -0
  436. package/dist/sdd/workflows/multi-tenancy.js +68 -0
  437. package/dist/sdd/workflows/multi-tenancy.js.map +1 -0
  438. package/dist/sdd/workflows/onboarding.d.ts +7 -0
  439. package/dist/sdd/workflows/onboarding.d.ts.map +1 -0
  440. package/dist/sdd/workflows/onboarding.js +43 -0
  441. package/dist/sdd/workflows/onboarding.js.map +1 -0
  442. package/dist/sdd/workflows/refactoring.d.ts +14 -0
  443. package/dist/sdd/workflows/refactoring.d.ts.map +1 -0
  444. package/dist/sdd/workflows/refactoring.js +73 -0
  445. package/dist/sdd/workflows/refactoring.js.map +1 -0
  446. package/dist/server/events.d.ts +64 -0
  447. package/dist/server/events.d.ts.map +1 -0
  448. package/dist/server/events.js +149 -0
  449. package/dist/server/events.js.map +1 -0
  450. package/dist/server/server.d.ts +33 -0
  451. package/dist/server/server.d.ts.map +1 -0
  452. package/dist/server/server.js +1019 -0
  453. package/dist/server/server.js.map +1 -0
  454. package/marketplace.json +39 -0
  455. package/package.json +65 -0
  456. package/src/code-intelligence/analyzer.ts +350 -0
  457. package/src/code-intelligence/ast/cache.ts +61 -0
  458. package/src/code-intelligence/ast/common.ts +62 -0
  459. package/src/code-intelligence/ast/component.ts +39 -0
  460. package/src/code-intelligence/ast/fallback.ts +44 -0
  461. package/src/code-intelligence/ast/index.ts +4 -0
  462. package/src/code-intelligence/ast/ir.ts +91 -0
  463. package/src/code-intelligence/ast/metrics.ts +65 -0
  464. package/src/code-intelligence/ast/registry.ts +28 -0
  465. package/src/code-intelligence/ast/tree-sitter.ts +226 -0
  466. package/src/code-intelligence/ast/typescript.ts +140 -0
  467. package/src/index.ts +22 -0
  468. package/src/mcp/server.ts +77 -0
  469. package/src/opencode/hooks.ts +728 -0
  470. package/src/opencode/router/categories.ts +192 -0
  471. package/src/opencode/router/embeddings.ts +149 -0
  472. package/src/opencode/router/graph-state-snapshot.ts +163 -0
  473. package/src/opencode/router/index.ts +44 -0
  474. package/src/opencode/router/intent-classifier.ts +140 -0
  475. package/src/opencode/router/semantic-nudge.ts +89 -0
  476. package/src/opencode/router/state-gate.ts +192 -0
  477. package/src/opencode/router/tool-embeddings.ts +162 -0
  478. package/src/opencode/router/tool-registry.ts +135 -0
  479. package/src/opencode/router/tool-taxonomy.ts +284 -0
  480. package/src/opencode/router/tools-composite.ts +685 -0
  481. package/src/opencode/shell-hooks.ts +115 -0
  482. package/src/opencode/system-prompt.ts +617 -0
  483. package/src/opencode/tools.ts +4102 -0
  484. package/src/opencode/workflows/chains.ts +205 -0
  485. package/src/opencode/workflows/executor.ts +172 -0
  486. package/src/opencode/workflows/index.ts +21 -0
  487. package/src/opencode/workflows/tools-workflow.ts +91 -0
  488. package/src/opencode/workflows/types.ts +42 -0
  489. package/src/sdd/analysis/compliance.ts +279 -0
  490. package/src/sdd/analysis/scalability.ts +97 -0
  491. package/src/sdd/analysis/security.ts +135 -0
  492. package/src/sdd/brownfield/scanner.ts +276 -0
  493. package/src/sdd/cache/manager.ts +899 -0
  494. package/src/sdd/cache/snapshot-store.ts +185 -0
  495. package/src/sdd/changes/manager.ts +326 -0
  496. package/src/sdd/cicd/generators.ts +1095 -0
  497. package/src/sdd/code-quality/complexity.ts +235 -0
  498. package/src/sdd/code-quality/conventions.ts +220 -0
  499. package/src/sdd/code-quality/dependencies.ts +203 -0
  500. package/src/sdd/code-quality/import-analyzer.ts +294 -0
  501. package/src/sdd/code-quality/metrics.ts +232 -0
  502. package/src/sdd/code-quality/smells.ts +415 -0
  503. package/src/sdd/code-quality/symbol-parser.ts +335 -0
  504. package/src/sdd/code-quality/usage-tracker.ts +527 -0
  505. package/src/sdd/code-quality/utils.ts +13 -0
  506. package/src/sdd/codegen/generator.ts +1181 -0
  507. package/src/sdd/constitution/validator.ts +253 -0
  508. package/src/sdd/cost/estimator.ts +147 -0
  509. package/src/sdd/coverage/tracker.ts +342 -0
  510. package/src/sdd/disaster/recovery.ts +79 -0
  511. package/src/sdd/discovery/adaptive.ts +167 -0
  512. package/src/sdd/discovery/briefing-analyzer.ts +802 -0
  513. package/src/sdd/discovery/briefing.ts +770 -0
  514. package/src/sdd/discovery/graph-builder.ts +598 -0
  515. package/src/sdd/documentation/generator.ts +158 -0
  516. package/src/sdd/domain/types.ts +658 -0
  517. package/src/sdd/drift/detector.ts +465 -0
  518. package/src/sdd/drift/exclusion.ts +223 -0
  519. package/src/sdd/drift/signals.ts +577 -0
  520. package/src/sdd/enforcement/interceptor.ts +444 -0
  521. package/src/sdd/enforcement/workflow-tracker.ts +263 -0
  522. package/src/sdd/graph/engine.ts +277 -0
  523. package/src/sdd/graph/index.ts +700 -0
  524. package/src/sdd/graph/integrity-guard.ts +348 -0
  525. package/src/sdd/graph/integrity.ts +628 -0
  526. package/src/sdd/graph/pruner.ts +221 -0
  527. package/src/sdd/graph/traverse.ts +331 -0
  528. package/src/sdd/incidents/manager.ts +87 -0
  529. package/src/sdd/knowledge/transfer.ts +140 -0
  530. package/src/sdd/migrations/fixes.ts +154 -0
  531. package/src/sdd/migrations/index.ts +14 -0
  532. package/src/sdd/migrations/migration-runner.ts +118 -0
  533. package/src/sdd/monitoring/setup.ts +237 -0
  534. package/src/sdd/monitoring/telemetry.ts +44 -0
  535. package/src/sdd/patterns/anti-patterns.ts +333 -0
  536. package/src/sdd/patterns/ast-clones.ts +214 -0
  537. package/src/sdd/patterns/config-drift.ts +346 -0
  538. package/src/sdd/patterns/contradictions.ts +323 -0
  539. package/src/sdd/patterns/learner.ts +266 -0
  540. package/src/sdd/permissions/access.ts +478 -0
  541. package/src/sdd/persistence/repository.ts +166 -0
  542. package/src/sdd/persistence/sqlite.ts +703 -0
  543. package/src/sdd/persistence/yaml.ts +409 -0
  544. package/src/sdd/promises/classifier.ts +253 -0
  545. package/src/sdd/promises/tracker.ts +258 -0
  546. package/src/sdd/quality/scorer.ts +263 -0
  547. package/src/sdd/rollback/manager.ts +361 -0
  548. package/src/sdd/session/handoff.ts +354 -0
  549. package/src/sdd/sla/tracker.ts +68 -0
  550. package/src/sdd/sync/git-sync.ts +401 -0
  551. package/src/sdd/toggle/state.ts +38 -0
  552. package/src/sdd/transactions/manager.ts +93 -0
  553. package/src/sdd/validation/coverage-index.ts +339 -0
  554. package/src/sdd/validation/executable.ts +127 -0
  555. package/src/sdd/validation/smart-validator.ts +470 -0
  556. package/src/sdd/validation/validator.ts +641 -0
  557. package/src/sdd/workflow/exporter.ts +169 -0
  558. package/src/sdd/workflows/ab-testing.ts +90 -0
  559. package/src/sdd/workflows/bug-fix.ts +81 -0
  560. package/src/sdd/workflows/data-migration.ts +86 -0
  561. package/src/sdd/workflows/deprecation.ts +87 -0
  562. package/src/sdd/workflows/feature-flags.ts +91 -0
  563. package/src/sdd/workflows/hotfix.ts +83 -0
  564. package/src/sdd/workflows/multi-tenancy.ts +84 -0
  565. package/src/sdd/workflows/onboarding.ts +82 -0
  566. package/src/sdd/workflows/refactoring.ts +90 -0
  567. package/src/server/events.ts +184 -0
  568. package/src/server/server.ts +1088 -0
@@ -0,0 +1,4102 @@
1
+ import { tool, type ToolDefinition } from "@opencode-ai/plugin"
2
+ import { createRepository, loadSddConfig, type GraphRepository } from "../sdd/persistence/repository.js"
3
+ import { getNeighbors } from "../sdd/graph/engine.js"
4
+ import {
5
+ createGraph,
6
+ addNode,
7
+ getNode,
8
+ addRelationship,
9
+ removeRelationship,
10
+ getRelationships,
11
+ updateNode,
12
+ removeNode,
13
+ getNodeIndexed,
14
+ getNodesByTypeIndexed,
15
+ getNodesByStatusIndexed,
16
+ getOutgoingIndexed,
17
+ getIncomingIndexed,
18
+ searchNodesIndexed,
19
+ getGraphStatsIndexed,
20
+ } from "../sdd/graph/engine.js"
21
+ import { GraphIndices } from "../sdd/graph/index.js"
22
+ import { bfsOutgoing, bfsBoth, bfsIncoming, computeImpact, findPath, getSubgraph } from "../sdd/graph/traverse.js"
23
+ import { analyzeBriefing, generateDiscoveryQuestions, updateGraphFromAnswers, formatDiscoverySummary } from "../sdd/discovery/briefing.js"
24
+ import { createChange, classifyApprovalLevel, approveChange, completeChange, getPendingChanges, failChange, getChangeHistory, formatImpactReport } from "../sdd/changes/manager.js"
25
+ import { validateGraph, formatValidationResult } from "../sdd/validation/validator.js"
26
+ import { detectDrift, formatDriftReport } from "../sdd/drift/detector.js"
27
+ import { buildSddContextPack } from "./system-prompt.js"
28
+ import { generateProject, writeGeneratedFiles, detectTechStack } from "../sdd/codegen/generator.js"
29
+ import { enforceSddFirst, classifyChangeRequest, buildEnforcementPrompt, getSddEnforcementRules } from "../sdd/enforcement/interceptor.js"
30
+ import { isSddEnabled, setToggleState, getToggleState } from "../sdd/toggle/state.js"
31
+ import { validateAgainstConstitution, formatConstitutionResult } from "../sdd/constitution/validator.js"
32
+ import { extractPromises, getPromiseReport, verifyPromise, markPromiseViolated, formatPromiseReport } from "../sdd/promises/tracker.js"
33
+ import { findUnverifiablePromises } from "../sdd/promises/classifier.js"
34
+ import { calculateQualityScore, formatQualityReport } from "../sdd/quality/scorer.js"
35
+ import { generateHandoff, formatHandoffPack, saveSessionLog, computeGraphHealth } from "../sdd/session/handoff.js"
36
+ import { detectAntiPatterns, formatAntiPatterns } from "../sdd/patterns/anti-patterns.js"
37
+ import { detectAstClones, formatCloneReport } from "../sdd/patterns/ast-clones.js"
38
+ import { detectContradictions, formatContradictionReport } from "../sdd/patterns/contradictions.js"
39
+ import { calculateCoverage, formatCoverageReport } from "../sdd/coverage/tracker.js"
40
+ import { detectConfigDrift, formatConfigDriftReport } from "../sdd/patterns/config-drift.js"
41
+ import { exportWorkflow, formatWorkflowExport } from "../sdd/workflow/exporter.js"
42
+ import { generateShellHooks, formatShellHookResult } from "./shell-hooks.js"
43
+ import { scanExistingProject, formatBrownfieldAnalysis } from "../sdd/brownfield/scanner.js"
44
+ import { generateCicd, writeCicdFiles, formatCicdResults } from "../sdd/cicd/generators.js"
45
+ import { getSyncStatus, pullLatest, pushChanges, detectConflicts, mergeGraphs, acquireLock, releaseLock, formatSyncStatus, resolveConflict } from "../sdd/sync/git-sync.js"
46
+ import { createSnapshot, executeRollback, loadRollbackHistory, formatRollbackResult, formatRollbackHistory } from "../sdd/rollback/manager.js"
47
+ import { loadPermissions, checkPermission, checkChangeApproval, setRole, getUserRoleWithAuth, addAuditEntry, getAuditLog, formatPermissionCheck, formatAuditLog, detectRemote, formatRemoteStatus, savePermissions, getRequiredApprovals, getUserRole, fetchRemoteUser } from "../sdd/permissions/access.js"
48
+ import { analyzeCodebase } from "../code-intelligence/analyzer.js"
49
+ import { createMcpServer } from "../mcp/server.js"
50
+ import { SddDashboardServer } from "../server/server.js"
51
+ import { readJson } from "../sdd/persistence/yaml.js"
52
+ import type { KnowledgeGraph, AnyNode, NodeType, ConstitutionNode } from "../sdd/domain/types.js"
53
+ import { getCacheManager } from "../sdd/cache/manager.js"
54
+ import { markEnforced, markApproved, markValidated, markCompleted, resetWorkflowState } from "../sdd/enforcement/workflow-tracker.js"
55
+ import { validateSmart, type SmartValidationOptions } from "../sdd/validation/smart-validator.js"
56
+ import { validateExecutableProject, saveExecutableValidation, loadExecutableValidation, isExecutableValidationCurrent } from "../sdd/validation/executable.js"
57
+ import { getTelemetrySummary, recordFeedback, recordTelemetry } from "../sdd/monitoring/telemetry.js"
58
+ import { ValidationIndex } from "../sdd/validation/coverage-index.js"
59
+ import { detectAllSignals, formatDriftSignals } from "../sdd/drift/signals.js"
60
+ import { TransactionManager } from "../sdd/transactions/manager.js"
61
+ import {
62
+ createGraphMutationTool,
63
+ createGraphQueryTool,
64
+ createTraverseTool,
65
+ createPermissionsTool,
66
+ createSnapshotTool,
67
+ createSyncTool,
68
+ createGraphAdminTool,
69
+ createCodeQualityTool,
70
+ createEnterpriseTool,
71
+ createDriftWhitelistTool,
72
+ } from "./router/tools-composite.js"
73
+ import { createWorkflowTools } from "./workflows/tools-workflow.js"
74
+
75
+ // ── Validation Coverage Index (singleton per session) ──────────────
76
+ const validationIndex = new ValidationIndex()
77
+
78
+ function getRepo(directory: string): GraphRepository {
79
+ return createRepository(directory)
80
+ }
81
+
82
+ function loadOrEmpty(directory: string): KnowledgeGraph {
83
+ const repo = getRepo(directory)
84
+ if (repo.isInitialized()) return repo.loadGraph()
85
+ return createGraph("pending")
86
+ }
87
+
88
+ /**
89
+ * Get cached tool response if available.
90
+ * Returns null if not cached or cache is stale.
91
+ */
92
+ function getCachedToolResponse(directory: string, toolName: string, args: Record<string, unknown>): string | null {
93
+ try {
94
+ const cacheMgr = getCacheManager(directory)
95
+ const repo = getRepo(directory)
96
+ if (!repo.isInitialized()) return null
97
+ const graph = repo.loadGraph()
98
+ return cacheMgr.getToolResponse(toolName, args, graph.metadata.updated_at)
99
+ } catch {
100
+ return null
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Cache a tool response.
106
+ */
107
+ function setCachedToolResponse(directory: string, toolName: string, args: Record<string, unknown>, response: string): void {
108
+ try {
109
+ const cacheMgr = getCacheManager(directory)
110
+ const repo = getRepo(directory)
111
+ if (!repo.isInitialized()) return
112
+ const graph = repo.loadGraph()
113
+ cacheMgr.setToolResponse(toolName, args, response, graph.metadata.updated_at)
114
+ } catch {}
115
+ }
116
+
117
+ /**
118
+ * Invalidate cache for specific node types after a mutation.
119
+ */
120
+ export function invalidateCacheForMutation(directory: string, nodeTypes: string[], relTypes: string[] = []): void {
121
+ try {
122
+ const cacheMgr = getCacheManager(directory)
123
+ cacheMgr.invalidatePartial(nodeTypes, relTypes)
124
+ } catch {}
125
+ }
126
+
127
+ // Re-export for composite tools
128
+ export const invalidateCache = invalidateCacheForMutation
129
+
130
+
131
+ // ── Pagination helpers ───────────────────────────────────────────────
132
+ const DEFAULT_PAGE_SIZE = 30
133
+ const MAX_PAGE_SIZE = 100
134
+
135
+ function paginate<T>(items: T[], pageSize: number = DEFAULT_PAGE_SIZE): { page: T[]; total: number; hasMore: boolean } {
136
+ const total = items.length
137
+ const size = Math.min(pageSize, MAX_PAGE_SIZE)
138
+ const hasMore = total > size
139
+ return { page: items.slice(0, size), total, hasMore }
140
+ }
141
+
142
+ function truncateList(items: string[], maxItems: number = 20): string[] {
143
+ if (items.length <= maxItems) return items
144
+ return [...items.slice(0, maxItems), `... and ${items.length - maxItems} more`]
145
+ }
146
+
147
+ function formatTruncated(items: string[], label: string, maxItems: number = 20): string {
148
+ if (items.length <= maxItems) return items.join("\n")
149
+ return [...items.slice(0, maxItems), `\n... and ${items.length - maxItems} more ${label}`].join("\n")
150
+ }
151
+
152
+ export function createSddTools(): Record<string, ToolDefinition> {
153
+ return {
154
+ "sdd.initialize": tool({
155
+ description:
156
+ "Initialize the SDD Knowledge Graph for a project. Detects if SDD is already initialized. " +
157
+ "Creates the .sdd/ directory structure and initial project node.",
158
+ args: {
159
+ project_name: tool.schema.string().describe("Name of the project"),
160
+ description: tool.schema.string().optional().describe("Brief project description"),
161
+ },
162
+ async execute(args, ctx) {
163
+ const repo = getRepo(ctx.directory)
164
+ if (repo.isInitialized()) {
165
+ const graph = repo.loadGraph()
166
+ return `SDD already initialized for project "${graph.project_id}". Graph has ${graph.nodes.length} nodes and ${graph.relationships.length} relationships.`
167
+ }
168
+
169
+ const projectId = args.project_name
170
+ .toLowerCase()
171
+ .replace(/[^a-z0-9]+/g, "-")
172
+ .replace(/^-|-$/g, "")
173
+ repo.createProject(projectId, args.project_name, args.description)
174
+ return `SDD initialized for project "${args.project_name}" (${projectId}).\n\nThe Knowledge Graph is ready. You can now use sdd.query_graph, sdd.add_node, and other SDD tools to build the specification.`
175
+ },
176
+ }),
177
+
178
+ "sdd.inspect": tool({
179
+ description:
180
+ "Inspect the current state of the SDD Knowledge Graph. Shows stats, node counts by type, " +
181
+ "and status distribution. Use this to understand the current project state.",
182
+ args: {},
183
+ async execute(_args, ctx) {
184
+ const repo = getRepo(ctx.directory)
185
+ if (!repo.isInitialized()) {
186
+ return "SDD is not initialized for this project. Use sdd.initialize first."
187
+ }
188
+ const indices = repo.getIndices()
189
+ const stats = getGraphStatsIndexed(indices)
190
+
191
+ // Sort by count descending, show top 15 types
192
+ const sortedTypes = Object.entries(stats.by_type)
193
+ .sort(([, a], [, b]) => b - a)
194
+ .slice(0, 15)
195
+
196
+ const lines = [
197
+ `## SDD Project: ${repo.loadGraph().project_id}`,
198
+ `**Graph Version:** ${repo.loadGraph().version}`,
199
+ `**SDD Version:** ${repo.loadGraph().metadata.sdd_version}`,
200
+ `**Created:** ${repo.loadGraph().metadata.created_at}`,
201
+ `**Updated:** ${repo.loadGraph().metadata.updated_at}`,
202
+ "",
203
+ "### Node Summary",
204
+ ]
205
+ for (const [type, count] of sortedTypes) {
206
+ lines.push(`- ${type}: ${count}`)
207
+ }
208
+ if (Object.keys(stats.by_type).length > 15) {
209
+ lines.push(`- ... and ${Object.keys(stats.by_type).length - 15} more types`)
210
+ }
211
+ lines.push(`\n**Total:** ${stats.total_nodes} nodes, ${stats.total_relationships} relationships`)
212
+
213
+ return lines.join("\n")
214
+ },
215
+ }),
216
+
217
+ "sdd.query_graph": tool({
218
+ description:
219
+ "Query the SDD Knowledge Graph. Search for nodes by text, type, or status. " +
220
+ "Get details about specific nodes and their relationships.",
221
+ args: {
222
+ query: tool.schema.string().describe("Search query or node ID"),
223
+ type: tool.schema.string().optional().describe("Filter by node type"),
224
+ },
225
+ async execute(args, ctx) {
226
+ const repo = getRepo(ctx.directory)
227
+ if (!repo.isInitialized()) return "SDD not initialized."
228
+ const indices = repo.getIndices()
229
+
230
+ // Try exact ID match first — O(1) with index
231
+ const exact = getNodeIndexed(indices, args.query)
232
+ if (exact) {
233
+ const outgoing = getOutgoingIndexed(indices, exact.id)
234
+ const incoming = getIncomingIndexed(indices, exact.id)
235
+
236
+ const lines = [
237
+ `## Node: ${exact.id}`,
238
+ `**Type:** ${exact.type}`,
239
+ `**Name:** ${exact.name}`,
240
+ `**Status:** ${exact.status}`,
241
+ `**Version:** ${exact.version}`,
242
+ ]
243
+ if (exact.description) lines.push(`**Description:** ${exact.description}`)
244
+
245
+ if (outgoing.length > 0) {
246
+ lines.push("\n### Outgoing Relationships")
247
+ for (const r of truncateList(outgoing.map(r => `- ${r.type} → ${r.to} (${indices.byId.get(r.to)?.name || "?"})`))) {
248
+ lines.push(r)
249
+ }
250
+ }
251
+
252
+ if (incoming.length > 0) {
253
+ lines.push("\n### Incoming Relationships")
254
+ for (const r of truncateList(incoming.map(r => `- ${r.type} ← ${r.from} (${indices.byId.get(r.from)?.name || "?"})`))) {
255
+ lines.push(r)
256
+ }
257
+ }
258
+
259
+ return lines.join("\n")
260
+ }
261
+
262
+ // Search by text — O(k) with inverted index instead of O(n×m)
263
+ // For multi-word queries, use AND search (all tokens must match) for precision
264
+ let results = searchNodesIndexed(indices, args.query, args.type as any)
265
+
266
+ // If query has multiple words and OR search returned many results,
267
+ // try AND search for more precise matches
268
+ const queryWords = args.query.split(/\s+/).filter(w => w.length > 1)
269
+ if (queryWords.length > 1 && results.length > 20) {
270
+ const andResults = indices.searchIndex.searchAnd(new Set(queryWords.map(w => w.toLowerCase())))
271
+ if (andResults.length > 0 && andResults.length < results.length) {
272
+ results = andResults
273
+ .map(id => indices.byId.get(id))
274
+ .filter((n): n is AnyNode => n !== undefined && (!args.type || n.type === args.type))
275
+ }
276
+ }
277
+
278
+ if (results.length === 0) return `No nodes found matching "${args.query}".`
279
+
280
+ const lines = [`## Search Results for "${args.query}" (${results.length} found)\n`]
281
+ for (const node of results.slice(0, 20)) {
282
+ lines.push(`- **${node.id}** (${node.type}): ${node.name} [${node.status}]`)
283
+ }
284
+ if (results.length > 20) lines.push(`\n... and ${results.length - 20} more`)
285
+
286
+ return lines.join("\n")
287
+ },
288
+ }),
289
+
290
+ "sdd.add_node": tool({
291
+ description:
292
+ "Add a new node to the SDD Knowledge Graph. Supports all node types: " +
293
+ "feature, requirement, business_rule, entity, architecture_component, task, test, etc.",
294
+ args: {
295
+ type: tool.schema.string().describe("Node type (feature, requirement, entity, etc.)"),
296
+ name: tool.schema.string().describe("Node name"),
297
+ description: tool.schema.string().optional().describe("Node description"),
298
+ parent_id: tool.schema.string().optional().describe("Parent node ID to create containment relationship"),
299
+ metadata_json: tool.schema.string().optional().describe("Additional metadata as JSON string"),
300
+ },
301
+ async execute(args, ctx) {
302
+ const repo = getRepo(ctx.directory)
303
+ if (!repo.isInitialized()) return "SDD not initialized. Run sdd.initialize first."
304
+ const graph = repo.loadGraph()
305
+
306
+ // ── Pre-mutation validation: check for duplicate nodes ──
307
+ const existingByName = graph.nodes.find(
308
+ (n) => n.type === args.type && n.name.toLowerCase() === args.name.toLowerCase()
309
+ )
310
+ if (existingByName) {
311
+ return [
312
+ `⚠️ Node with type "${args.type}" and name "${args.name}" already exists:`,
313
+ `- **ID:** ${existingByName.id}`,
314
+ `- **Status:** ${existingByName.status}`,
315
+ "",
316
+ "Use `sdd.update_node` to modify the existing node, or choose a different name.",
317
+ ].join("\n")
318
+ }
319
+
320
+ const nodeId = `${graph.project_id}-${args.type.toUpperCase().slice(0, 4)}-${String(graph.nodes.filter((n) => n.type === args.type).length + 1).padStart(3, "0")}`
321
+
322
+ let metadata: Record<string, unknown> = {}
323
+ if (args.metadata_json) {
324
+ try {
325
+ metadata = JSON.parse(args.metadata_json)
326
+ } catch {
327
+ return "Error: Invalid JSON in metadata_json"
328
+ }
329
+ }
330
+
331
+ // Auto-populate required metadata fields based on node type
332
+ const nodeType = args.type as NodeType
333
+ if (nodeType === "business_rule" && !metadata.rule_text) {
334
+ metadata.rule_text = args.description || args.name || ""
335
+ }
336
+ if (nodeType === "constraint" && !metadata.rule_text) {
337
+ metadata.rule_text = args.description || args.name || ""
338
+ }
339
+ if (nodeType === "constraint" && !metadata.constraint_type) {
340
+ metadata.constraint_type = "business"
341
+ }
342
+ if (nodeType === "entity" && !metadata.fields) {
343
+ metadata.fields = []
344
+ }
345
+ if (nodeType === "endpoint" && !metadata.method) {
346
+ metadata.method = "GET"
347
+ metadata.path = "/"
348
+ }
349
+ if (nodeType === "database" && !metadata.engine) {
350
+ metadata.engine = "postgresql"
351
+ }
352
+ if (nodeType === "architecture_component" && !metadata.layer) {
353
+ metadata.layer = "backend"
354
+ }
355
+
356
+ const node: AnyNode = {
357
+ id: nodeId,
358
+ type: args.type as NodeType,
359
+ name: args.name,
360
+ description: args.description,
361
+ status: "DRAFT",
362
+ version: 1,
363
+ metadata,
364
+ created_at: new Date().toISOString(),
365
+ updated_at: new Date().toISOString(),
366
+ } as AnyNode
367
+
368
+ addNode(graph, node)
369
+
370
+ if (args.parent_id) {
371
+ try {
372
+ addRelationship(graph, args.parent_id, nodeId, "contains")
373
+ } catch (e) {
374
+ // Parent might not exist, just log
375
+ }
376
+ }
377
+
378
+ repo.saveGraph(graph)
379
+ invalidateCacheForMutation(ctx.directory, [args.type], ["contains"])
380
+ return `Node created: **${nodeId}** (${args.type}): ${args.name}`
381
+ },
382
+ }),
383
+
384
+ "sdd.add_relationship": tool({
385
+ description:
386
+ "Add a relationship between two nodes in the SDD Knowledge Graph. Prevents cycles and self-loops.",
387
+ args: {
388
+ from_id: tool.schema.string().describe("Source node ID"),
389
+ to_id: tool.schema.string().describe("Target node ID"),
390
+ type: tool.schema.string().describe("Relationship type (contains, depends_on, implements, etc.)"),
391
+ },
392
+ async execute(args, ctx) {
393
+ const repo = getRepo(ctx.directory)
394
+ if (!repo.isInitialized()) return "SDD not initialized."
395
+ const graph = repo.loadGraph()
396
+
397
+ // ── Pre-mutation: warn about orphan creation ──
398
+ const fromNode = graph.nodes.find(n => n.id === args.from_id)
399
+ const toNode = graph.nodes.find(n => n.id === args.to_id)
400
+ if (fromNode && toNode) {
401
+ const fromRels = graph.relationships.filter(r => r.from === args.from_id || r.to === args.from_id)
402
+ const toRels = graph.relationships.filter(r => r.from === args.to_id || r.to === args.to_id)
403
+ const warnings: string[] = []
404
+ if (fromRels.length === 0) {
405
+ warnings.push(`Source "${fromNode.name}" currently has no relationships — this will be its first connection.`)
406
+ }
407
+ if (toRels.length === 0) {
408
+ warnings.push(`Target "${toNode.name}" currently has no relationships — this will be its first connection.`)
409
+ }
410
+ }
411
+
412
+ try {
413
+ addRelationship(graph, args.from_id, args.to_id, args.type as any)
414
+ repo.saveGraph(graph)
415
+ invalidateCacheForMutation(ctx.directory, [], [args.type])
416
+ return `Relationship created: ${args.from_id} --[${args.type}]--> ${args.to_id}`
417
+ } catch (e) {
418
+ return `Error: ${e instanceof Error ? e.message : String(e)}`
419
+ }
420
+ },
421
+ }),
422
+
423
+ "sdd.analyze_impact": tool({
424
+ description:
425
+ "Perform impact analysis for a change request. Traverses the Knowledge Graph " +
426
+ "to find all affected nodes, features, entities, APIs, and tests.",
427
+ args: {
428
+ node_id: tool.schema.string().describe("Node ID to analyze impact for"),
429
+ depth: tool.schema.number().optional().describe("Maximum traversal depth (default: 3)"),
430
+ },
431
+ async execute(args, ctx) {
432
+ const repo = getRepo(ctx.directory)
433
+ if (!repo.isInitialized()) return "SDD not initialized."
434
+ const graph = repo.loadGraph()
435
+ const indices = repo.getIndices()
436
+
437
+ const node = getNodeIndexed(indices, args.node_id)
438
+ if (!node) return `Node ${args.node_id} not found.`
439
+
440
+ const depth = args.depth || 3
441
+ const impact = computeImpact(graph, args.node_id, depth)
442
+ const { computeImpactActions } = await import("../sdd/graph/traverse.js")
443
+ const actions = computeImpactActions(graph, args.node_id, depth)
444
+
445
+ const lines = [
446
+ `## Impact Analysis: ${args.node_id}`,
447
+ `**Node:** ${node.name} (${node.type})`,
448
+ `**Depth:** ${depth}`,
449
+ "",
450
+ "### Direct Impact",
451
+ ]
452
+
453
+ if (impact.direct.length === 0) {
454
+ lines.push("- No direct impact")
455
+ } else {
456
+ for (const n of impact.direct.slice(0, 15)) {
457
+ lines.push(`- **${n.id}** (${n.type}): ${n.name}`)
458
+ }
459
+ if (impact.direct.length > 15) lines.push(`- ... and ${impact.direct.length - 15} more`)
460
+ }
461
+
462
+ lines.push("\n### Indirect Impact")
463
+ if (impact.indirect.length === 0) {
464
+ lines.push("- No indirect impact")
465
+ } else {
466
+ for (const n of impact.indirect.slice(0, 15)) {
467
+ lines.push(`- **${n.id}** (${n.type}): ${n.name}`)
468
+ }
469
+ if (impact.indirect.length > 15) lines.push(`- ... and ${impact.indirect.length - 15} more`)
470
+ }
471
+
472
+ // Concrete actions
473
+ if (actions.length > 0) {
474
+ lines.push("\n### 🎯 Recommended Actions")
475
+ const highPriority = actions.filter(a => a.priority === "high")
476
+ const medPriority = actions.filter(a => a.priority === "medium")
477
+
478
+ if (highPriority.length > 0) {
479
+ lines.push("\n**High Priority:**")
480
+ for (const a of highPriority.slice(0, 10)) {
481
+ const files = a.target_files.length > 0 ? ` → ${a.target_files.join(", ")}` : ""
482
+ lines.push(`- ${a.description}${files}`)
483
+ }
484
+ }
485
+ if (medPriority.length > 0) {
486
+ lines.push("\n**Medium Priority:**")
487
+ for (const a of medPriority.slice(0, 10)) {
488
+ lines.push(`- ${a.description}`)
489
+ }
490
+ }
491
+ }
492
+
493
+ return lines.join("\n")
494
+ },
495
+ }),
496
+
497
+ "sdd.create_change": tool({
498
+ description:
499
+ "Create a Change node in the SDD Knowledge Graph. Represents a modification " +
500
+ "to the system specification. Automatically performs impact analysis.",
501
+ args: {
502
+ title: tool.schema.string().describe("Change title"),
503
+ reason: tool.schema.string().describe("Reason for the change"),
504
+ affected_node_ids: tool.schema.string().optional().describe("Comma-separated list of affected node IDs"),
505
+ new_nodes_json: tool.schema.string().optional().describe("JSON array of new nodes to create"),
506
+ modified_nodes_json: tool.schema.string().optional().describe('JSON array of {id, updates} for modified nodes'),
507
+ },
508
+ async execute(args, ctx) {
509
+ const repo = getRepo(ctx.directory)
510
+ if (!repo.isInitialized()) return "SDD not initialized."
511
+ const graph = repo.loadGraph()
512
+
513
+ const affectedIds = args.affected_node_ids
514
+ ? args.affected_node_ids.split(",").map((s) => s.trim())
515
+ : []
516
+
517
+ let newNodes: Partial<AnyNode>[] = []
518
+ if (args.new_nodes_json) {
519
+ try { newNodes = JSON.parse(args.new_nodes_json) } catch { return "Invalid JSON in new_nodes_json" }
520
+ }
521
+
522
+ let modifiedNodes: Array<{ id: string; updates: Partial<AnyNode> }> = []
523
+ if (args.modified_nodes_json) {
524
+ try { modifiedNodes = JSON.parse(args.modified_nodes_json) } catch { return "Invalid JSON in modified_nodes_json" }
525
+ }
526
+
527
+ const change = createChange(graph, {
528
+ title: args.title,
529
+ reason: args.reason,
530
+ affected_node_ids: affectedIds,
531
+ new_nodes: newNodes,
532
+ modified_nodes: modifiedNodes,
533
+ removed_node_ids: [],
534
+ affected_files: [],
535
+ affected_tests: [],
536
+ implementation_tasks: [],
537
+ })
538
+
539
+ repo.saveGraph(graph)
540
+ invalidateCacheForMutation(ctx.directory, ["change"], ["created_by"])
541
+
542
+ // Create a transaction to track this change lifecycle
543
+ const txManager = new TransactionManager(ctx.directory)
544
+ const tx = txManager.createTransaction(change.id)
545
+
546
+ const approvalLevel = classifyApprovalLevel(
547
+ {
548
+ title: args.title,
549
+ reason: args.reason,
550
+ affected_node_ids: affectedIds,
551
+ new_nodes: newNodes,
552
+ modified_nodes: modifiedNodes,
553
+ removed_node_ids: [],
554
+ affected_files: [],
555
+ affected_tests: [],
556
+ implementation_tasks: [],
557
+ },
558
+ graph,
559
+ )
560
+
561
+ return [
562
+ `Change created: **${change.id}**: ${args.title}`,
563
+ `**Approval Level:** ${approvalLevel}`,
564
+ `**Status:** ${change.status}`,
565
+ `**Transaction:** ${tx.id}`,
566
+ "",
567
+ approvalLevel === "APPROVAL"
568
+ ? "⚠️ This change requires explicit approval before implementation."
569
+ : approvalLevel === "REVIEW"
570
+ ? "This change should be reviewed before implementation."
571
+ : "This change can proceed automatically.",
572
+ ].join("\n")
573
+ },
574
+ }),
575
+
576
+ "sdd.discover": tool({
577
+ description:
578
+ "Analyze a user briefing and return structured discovery questions for missing information. " +
579
+ "Use this when the user provides a project description or feature request that needs elaboration. " +
580
+ "For each question returned, you MUST use the `question` tool to ask the user. " +
581
+ "Pass each question's `question`, `header`, and `options` directly to the `question` tool. " +
582
+ "If file references (@) are found, read those files first to extract additional context.",
583
+ args: {
584
+ briefing: tool.schema.string().describe("User briefing or feature description to analyze"),
585
+ },
586
+ async execute(args, _ctx) {
587
+ const analysis = analyzeBriefing(args.briefing)
588
+ let questions = generateDiscoveryQuestions(analysis)
589
+
590
+ // Adaptive discovery: filter out questions already answered by graph
591
+ try {
592
+ const repo = getRepo(_ctx.directory)
593
+ if (repo.isInitialized()) {
594
+ const graph = repo.loadGraph()
595
+ const { filterAlreadyAnswered, generateGapQuestions } = await import("../sdd/discovery/adaptive.js")
596
+ const filteredQuestions = filterAlreadyAnswered(questions, graph)
597
+ const gapQuestions = generateGapQuestions(graph)
598
+ questions = [...filteredQuestions, ...gapQuestions]
599
+ }
600
+ } catch {}
601
+
602
+ // Build the prompt for the agent
603
+ const lines: string[] = []
604
+ lines.push("## Briefing Analysis\n")
605
+ lines.push(formatDiscoverySummary(analysis))
606
+
607
+ if (analysis.file_references.length > 0) {
608
+ lines.push("")
609
+ lines.push("### Arquivos Referenciados")
610
+ lines.push("Leia os seguintes arquivos para extrair contexto adicional:")
611
+ for (const ref of analysis.file_references) {
612
+ lines.push(`- \`${ref.path}\``)
613
+ }
614
+ }
615
+
616
+ if (questions.length === 0) {
617
+ lines.push("")
618
+ lines.push("### Briefing suficiente para prosseguir.")
619
+ lines.push("Não há perguntas pendentes. Você pode prosseguir com a especificação.")
620
+ } else {
621
+ lines.push("")
622
+ lines.push("### Perguntas para o usuário")
623
+ lines.push("")
624
+ lines.push("Para CADA pergunta abaixo, use a ferramenta `question` com os dados exatos fornecidos.")
625
+ lines.push("NÃO gere as perguntas como texto livre — use sempre a ferramenta `question`.")
626
+ lines.push("")
627
+ for (let i = 0; i < questions.length; i++) {
628
+ const q = questions[i]
629
+ lines.push(`#### ${i + 1}. ${q.header}`)
630
+ lines.push(`- **question:** ${q.question}`)
631
+ lines.push(`- **header:** ${q.header}`)
632
+ lines.push(`- **options:**`)
633
+ for (const opt of q.options) {
634
+ lines.push(` - label: "${opt.label}" — ${opt.description}`)
635
+ }
636
+ if (q.multiple) lines.push(`- **multiple:** true`)
637
+ lines.push("")
638
+ }
639
+
640
+ lines.push("### Formato de chamada")
641
+ lines.push("```")
642
+ lines.push("question(questions=[{")
643
+ lines.push(' question: "...",')
644
+ lines.push(' header: "...",')
645
+ lines.push(" options: [{ label: \"...\", description: \"...\" }]")
646
+ lines.push("}])")
647
+ lines.push("```")
648
+ }
649
+
650
+ lines.push("")
651
+ lines.push("### Após coletar as respostas")
652
+ lines.push("Chame `sdd.update_from_answers` com um JSON mapeando cada pergunta para a resposta do usuário.")
653
+ lines.push(`\n\`\`\`json\n${JSON.stringify(Object.fromEntries(questions.map(q => [q.question, ""])), null, 2)}\n\`\`\``)
654
+
655
+ return lines.join("\n")
656
+ },
657
+ }),
658
+
659
+ "sdd.update_from_answers": tool({
660
+ description:
661
+ "Update the Knowledge Graph based on user answers to discovery questions. " +
662
+ "This processes answers and creates/updates relevant nodes.",
663
+ args: {
664
+ answers_json: tool.schema
665
+ .string()
666
+ .describe('JSON object mapping questions to answers, e.g. {"How will users login?": "Google OAuth"}'),
667
+ },
668
+ async execute(args, ctx) {
669
+ const repo = getRepo(ctx.directory)
670
+ if (!repo.isInitialized()) return "SDD not initialized."
671
+ const graph = repo.loadGraph()
672
+
673
+ let answers: Record<string, string>
674
+ try {
675
+ answers = JSON.parse(args.answers_json)
676
+ } catch {
677
+ return "Invalid JSON in answers_json"
678
+ }
679
+
680
+ updateGraphFromAnswers(graph, answers)
681
+ repo.saveGraph(graph)
682
+ invalidateCacheForMutation(ctx.directory, ["feature", "requirement", "entity", "endpoint", "business_rule"], ["satisfies", "depends_on", "implements"])
683
+
684
+ return `Graph updated with ${Object.keys(answers).length} answers. Use sdd.query_graph to inspect changes.`
685
+ },
686
+ }),
687
+
688
+ "sdd.validate": tool({
689
+ description:
690
+ "Validate the SDD Knowledge Graph for structural integrity, semantic correctness, " +
691
+ "and completeness. Uses smart validation to only check affected subsystems when possible.",
692
+ args: {},
693
+ async execute(_args, ctx) {
694
+ const startedAt = Date.now()
695
+ const repo = getRepo(ctx.directory)
696
+ if (!repo.isInitialized()) return "SDD not initialized."
697
+ const graph = repo.loadGraph()
698
+
699
+ // Check analysis cache
700
+ const cacheMgr = getCacheManager(ctx.directory)
701
+ const cached = cacheMgr.getAnalysisResult("validate", graph.metadata.updated_at ? new Date(graph.metadata.updated_at).getTime() : 0, graph.nodes.length)
702
+ if (cached) {
703
+ recordTelemetry(ctx.directory, {
704
+ name: "graph_validation",
705
+ duration_ms: Date.now() - startedAt,
706
+ tokens_estimate: Math.ceil(String(cached).length / 4),
707
+ cache_hit: true,
708
+ })
709
+ return cached as string
710
+ }
711
+
712
+ // Use smart validation with the coverage index for incremental validation
713
+ const smartOptions: SmartValidationOptions = {
714
+ coverageIndex: validationIndex,
715
+ // A user-requested validation is always a full authoritative check.
716
+ // Incremental validation is reserved for callers that provide a
717
+ // concrete dirty-node set.
718
+ validateAll: true,
719
+ policy: loadSddConfig(ctx.directory).validation,
720
+ }
721
+
722
+ const smartResult = validateSmart(graph, new Set(), smartOptions)
723
+ const formatted = formatValidationResult(smartResult)
724
+
725
+ if (smartResult.valid) {
726
+ markValidated()
727
+ }
728
+
729
+ // Cache the result
730
+ cacheMgr.setAnalysisResult("validate", formatted, graph.metadata.updated_at ? new Date(graph.metadata.updated_at).getTime() : 0, graph.nodes.length)
731
+ recordTelemetry(ctx.directory, {
732
+ name: "graph_validation",
733
+ duration_ms: Date.now() - startedAt,
734
+ tokens_estimate: Math.ceil(formatted.length / 4),
735
+ cache_hit: false,
736
+ metadata: { valid: smartResult.valid, errors: smartResult.errors.length, warnings: smartResult.warnings.length },
737
+ })
738
+
739
+ return formatted
740
+ },
741
+ }),
742
+
743
+ "sdd.detect_drift": tool({
744
+ description:
745
+ "Detect SDD drift - find discrepancies between the specification and the codebase.",
746
+ args: {},
747
+ async execute(_args, ctx) {
748
+ const repo = getRepo(ctx.directory)
749
+ if (!repo.isInitialized()) return "SDD not initialized."
750
+ const graph = repo.loadGraph()
751
+
752
+ // Check analysis cache
753
+ const cacheMgr = getCacheManager(ctx.directory)
754
+ const cached = cacheMgr.getAnalysisResult("drift", graph.metadata.updated_at ? new Date(graph.metadata.updated_at).getTime() : 0, graph.nodes.length)
755
+ if (cached) return cached as string
756
+
757
+ const result = detectDrift(graph, ctx.directory)
758
+ const formatted = formatDriftReport(result)
759
+
760
+ // Cache the result
761
+ cacheMgr.setAnalysisResult("drift", formatted, graph.metadata.updated_at ? new Date(graph.metadata.updated_at).getTime() : 0, graph.nodes.length)
762
+
763
+ return formatted
764
+ },
765
+ }),
766
+
767
+ "sdd.check_migrations": tool({
768
+ description:
769
+ "Check if SDD data needs migration. Analyzes graph.yaml vs graph.db sync, " +
770
+ "missing fields, structural issues, and drift whitelist integrity.",
771
+ args: {
772
+ auto_fix: tool.schema.boolean().optional().describe("If true, automatically run pending migrations (default: false)").default(false),
773
+ },
774
+ async execute(args, ctx) {
775
+ const { hasPendingMigrations, runMigrations, getMigrations } = await import("../sdd/migrations/index.js")
776
+ const { existsSync, statSync } = await import("fs")
777
+ const { join } = await import("path")
778
+
779
+ const lines: string[] = []
780
+ const issues: string[] = []
781
+
782
+ // 1. Check pending migrations
783
+ const pending = hasPendingMigrations(ctx.directory)
784
+ const allMigrations = getMigrations()
785
+ lines.push(`## Migration Status`)
786
+ lines.push(`- Pending migrations: ${pending ? "YES" : "No"}`)
787
+ lines.push(`- Total registered: ${allMigrations.length}`)
788
+
789
+ // 2. Check graph.yaml vs graph.db sync
790
+ const yamlPath = join(ctx.directory, ".sdd", "graph.yaml")
791
+ const dbPath = join(ctx.directory, ".sdd", "graph.db")
792
+
793
+ if (existsSync(yamlPath) && existsSync(dbPath)) {
794
+ const yamlStat = statSync(yamlPath)
795
+ const dbStat = statSync(dbPath)
796
+
797
+ if (dbStat.mtimeMs < yamlStat.mtimeMs) {
798
+ issues.push("graph.db is older than graph.yaml — needs sync")
799
+ lines.push(`- ⚠️ graph.db stale (YAML is newer)`)
800
+ } else {
801
+ lines.push(`- ✅ graph.db is up to date`)
802
+ }
803
+ } else if (existsSync(yamlPath)) {
804
+ lines.push(`- ℹ️ Only graph.yaml exists (no SQLite)`)
805
+ }
806
+
807
+ // 3. Check for missing priority fields
808
+ try {
809
+ const repo = createRepository(ctx.directory)
810
+ if (repo.isInitialized()) {
811
+ const graph = repo.loadGraph()
812
+ let missingPriority = 0
813
+ let missingFields = 0
814
+
815
+ for (const node of graph.nodes) {
816
+ const meta = node.metadata as Record<string, unknown>
817
+ if (!meta.priority && (node.type === "test" || node.type === "requirement")) {
818
+ missingPriority++
819
+ }
820
+ if (!node.type || !node.name || !node.status) {
821
+ missingFields++
822
+ }
823
+ }
824
+
825
+ if (missingPriority > 0) {
826
+ issues.push(`${missingPriority} nodes missing priority field`)
827
+ lines.push(`- ⚠️ ${missingPriority} nodes without priority`)
828
+ } else {
829
+ lines.push(`- ✅ All nodes have priority`)
830
+ }
831
+
832
+ if (missingFields > 0) {
833
+ issues.push(`${missingFields} nodes with missing required fields`)
834
+ lines.push(`- ⚠️ ${missingFields} nodes with missing fields`)
835
+ } else {
836
+ lines.push(`- ✅ All nodes have required fields`)
837
+ }
838
+ }
839
+ } catch {
840
+ lines.push(`- ℹ️ Could not analyze graph structure`)
841
+ }
842
+
843
+ // 4. Check drift whitelist
844
+ const whitelistPath = join(ctx.directory, ".sdd", "drift-whitelist.json")
845
+ if (existsSync(whitelistPath)) {
846
+ try {
847
+ const whitelist = JSON.parse(require("fs").readFileSync(whitelistPath, "utf-8"))
848
+ if (!whitelist.entries || !Array.isArray(whitelist.entries)) {
849
+ issues.push("drift-whitelist.json is corrupted")
850
+ lines.push(`- ⚠️ Whitelist corrupted`)
851
+ } else {
852
+ lines.push(`- ✅ Whitelist valid (${whitelist.entries.length} entries)`)
853
+ }
854
+ } catch {
855
+ issues.push("drift-whitelist.json parse error")
856
+ lines.push(`- ⚠️ Whitelist parse error`)
857
+ }
858
+ }
859
+
860
+ lines.push("")
861
+
862
+ // Summary
863
+ if (issues.length === 0) {
864
+ lines.push("✅ **No migrations needed.**")
865
+ } else {
866
+ lines.push(`⚠️ **${issues.length} issue(s) detected:**`)
867
+ for (const issue of issues) {
868
+ lines.push(`- ${issue}`)
869
+ }
870
+ lines.push("")
871
+
872
+ if (args.auto_fix) {
873
+ lines.push("🔄 Running migrations...")
874
+ const results = runMigrations(ctx.directory)
875
+ const successful = results.filter(r => r.success)
876
+ lines.push(`✅ ${successful.length} migration(s) completed.`)
877
+ } else {
878
+ lines.push("Run with `auto_fix: true` to apply fixes, or call `sdd.run_migrations` to execute.")
879
+ }
880
+ }
881
+
882
+ return lines.join("\n")
883
+ },
884
+ }),
885
+
886
+ "sdd.run_migrations": tool({
887
+ description:
888
+ "Execute all pending SDD migrations to fix data issues.",
889
+ args: {},
890
+ async execute(_args, ctx) {
891
+ const { runMigrations, hasPendingMigrations } = await import("../sdd/migrations/index.js")
892
+
893
+ if (!hasPendingMigrations(ctx.directory)) {
894
+ return "✅ No pending migrations."
895
+ }
896
+
897
+ const results = runMigrations(ctx.directory)
898
+ const lines: string[] = []
899
+
900
+ for (const result of results) {
901
+ const icon = result.success ? "✅" : "❌"
902
+ lines.push(`${icon} ${result.message}`)
903
+ if (result.files_modified?.length) {
904
+ lines.push(` Modified: ${result.files_modified.join(", ")}`)
905
+ }
906
+ }
907
+
908
+ return lines.join("\n") || "✅ All migrations completed."
909
+ },
910
+ }),
911
+
912
+ "sdd.get_context": tool({
913
+ description:
914
+ "Build a context pack for a specific node. Returns all relevant nodes, " +
915
+ "relationships, and files for focused work.",
916
+ args: {
917
+ node_id: tool.schema.string().describe("Node ID to build context for"),
918
+ },
919
+ async execute(args, ctx) {
920
+ const repo = getRepo(ctx.directory)
921
+ if (!repo.isInitialized()) return "SDD not initialized."
922
+ const graph = repo.loadGraph()
923
+ const indices = repo.getIndices()
924
+
925
+ // Build focused subgraph: target node + depth-1 neighbors
926
+ const target = indices.byId.get(args.node_id)
927
+ if (!target) return `Node ${args.node_id} not found.`
928
+
929
+ // Use getNeighbors for richer neighbor data with direction info
930
+ const neighbors = getNeighbors(graph, args.node_id, "both")
931
+ const neighborIds = new Set(neighbors.map(n => n.id))
932
+ const subgraphNodeIds = new Set([args.node_id, ...neighborIds])
933
+
934
+ const subgraphNodes = [...subgraphNodeIds]
935
+ .map(id => indices.byId.get(id))
936
+ .filter((n): n is AnyNode => n !== undefined)
937
+
938
+ const subgraphRels = graph.relationships.filter(
939
+ r => subgraphNodeIds.has(r.from) && subgraphNodeIds.has(r.to)
940
+ )
941
+
942
+ // Also include cross-boundary relationships for context
943
+ const boundaryRels = graph.relationships.filter(
944
+ r => (subgraphNodeIds.has(r.from) && !subgraphNodeIds.has(r.to)) ||
945
+ (!subgraphNodeIds.has(r.from) && subgraphNodeIds.has(r.to))
946
+ ).slice(0, 10)
947
+
948
+ const lines = [
949
+ `## Context Pack: ${target.id}`,
950
+ `**Type:** ${target.type}`,
951
+ `**Name:** ${target.name}`,
952
+ `**Status:** ${target.status}`,
953
+ ]
954
+ if (target.description) lines.push(`**Description:** ${target.description}`)
955
+
956
+ if (target.metadata && Object.keys(target.metadata).length > 0) {
957
+ const metaStr = JSON.stringify(target.metadata, null, 2)
958
+ if (metaStr.length < 500) {
959
+ lines.push(`\n### Metadata\n\`\`\`\n${metaStr}\n\`\`\``)
960
+ } else {
961
+ lines.push(`\n### Metadata (truncated)\n\`\`\`\n${metaStr.slice(0, 500)}...\n\`\`\``)
962
+ }
963
+ }
964
+
965
+ if (subgraphRels.length > 0) {
966
+ lines.push(`\n### Internal Relationships (${subgraphRels.length})`)
967
+ for (const r of subgraphRels.slice(0, 20)) {
968
+ const from = indices.byId.get(r.from)
969
+ const to = indices.byId.get(r.to)
970
+ lines.push(`- ${from?.name || r.from} →[${r.type}]→ ${to?.name || r.to}`)
971
+ }
972
+ if (subgraphRels.length > 20) lines.push(`- ... and ${subgraphRels.length - 20} more`)
973
+ }
974
+
975
+ if (boundaryRels.length > 0) {
976
+ lines.push(`\n### External Connections (${boundaryRels.length})`)
977
+ for (const r of boundaryRels) {
978
+ const from = indices.byId.get(r.from)
979
+ const to = indices.byId.get(r.to)
980
+ const isOut = subgraphNodeIds.has(r.from)
981
+ if (isOut) {
982
+ lines.push(`- ${from?.name || r.from} →[${r.type}]→ ${to?.name || r.to}`)
983
+ } else {
984
+ lines.push(`- ${from?.name || r.from} →[${r.type}]← ${to?.name || r.to}`)
985
+ }
986
+ }
987
+ }
988
+
989
+ // Add the standard context pack
990
+ lines.push("")
991
+ lines.push(buildSddContextPack(graph, args.node_id))
992
+
993
+ return lines.join("\n")
994
+ },
995
+ }),
996
+
997
+ "sdd.approve_change": tool({
998
+ description: "Approve a pending change in the SDD graph.",
999
+ args: {
1000
+ change_id: tool.schema.string().describe("Change node ID (e.g., CHG-001)"),
1001
+ },
1002
+ async execute(args, ctx) {
1003
+ const repo = getRepo(ctx.directory)
1004
+ if (!repo.isInitialized()) return "SDD not initialized."
1005
+ const graph = repo.loadGraph()
1006
+
1007
+ // ENFORCEMENT: prevent self-approval
1008
+ const changeNode = graph.nodes.find(
1009
+ (n) => n.type === "change" && n.id === args.change_id,
1010
+ )
1011
+ if (changeNode) {
1012
+ const creator = changeNode.created_by || (changeNode.metadata as Record<string, unknown>)?.created_by as string
1013
+ const approver = process.env.USER || process.env.USERNAME || "current"
1014
+ if (creator && creator === approver) {
1015
+ addAuditEntry(
1016
+ ctx.directory,
1017
+ approver,
1018
+ "sdd.approve_change",
1019
+ args.change_id,
1020
+ "denied",
1021
+ "Self-approval prevented",
1022
+ )
1023
+ return [
1024
+ `## SDD BLOCKED: Self-approval prevented`,
1025
+ "",
1026
+ `Change ${args.change_id} was created by **${creator}**.`,
1027
+ `You cannot approve your own Change.`,
1028
+ "",
1029
+ "A different user or an admin must approve this Change.",
1030
+ "Use `sdd.check_change_approval` to verify approval requirements.",
1031
+ ].join("\n")
1032
+ }
1033
+ }
1034
+
1035
+ try {
1036
+ approveChange(graph, args.change_id)
1037
+ repo.saveGraph(graph)
1038
+ invalidateCacheForMutation(ctx.directory, ["change"], ["approved_by"])
1039
+ markApproved()
1040
+
1041
+ // Advance transaction to SPEC_UPDATED
1042
+ try {
1043
+ const txManager = new TransactionManager(ctx.directory)
1044
+ const txs = txManager.getTransactionsForChange(args.change_id)
1045
+ if (txs.length > 0) {
1046
+ txManager.advanceStatus(txs[0].id, "SPEC_UPDATED")
1047
+ }
1048
+ } catch {}
1049
+
1050
+ return `Change ${args.change_id} approved.`
1051
+ } catch (e) {
1052
+ return `Error: ${e instanceof Error ? e.message : String(e)}`
1053
+ }
1054
+ },
1055
+ }),
1056
+
1057
+ "sdd.complete_change": tool({
1058
+ description: "Mark a change as completed in the SDD graph. Checks pending promises on affected nodes before completing.",
1059
+ args: {
1060
+ change_id: tool.schema.string().describe("Change node ID"),
1061
+ force: tool.schema.boolean().optional().describe("Force completion even with pending promises"),
1062
+ },
1063
+ async execute(args, ctx) {
1064
+ const repo = getRepo(ctx.directory)
1065
+ if (!repo.isInitialized()) return "SDD not initialized."
1066
+ const graph = repo.loadGraph()
1067
+ try {
1068
+ if (!args.force) {
1069
+ const execution = loadExecutableValidation(ctx.directory, args.change_id)
1070
+ if (!execution?.verified || !execution.passed || !isExecutableValidationCurrent(ctx.directory, execution)) {
1071
+ return `## Change ${args.change_id} Completion BLOCKED\n\nRun sdd.verify_implementation successfully after the last code change. Use force=true only for an explicit override.`
1072
+ }
1073
+ }
1074
+ const { completeChangeWithPromiseCheck } = await import("../sdd/changes/manager.js")
1075
+ const result = completeChangeWithPromiseCheck(graph, args.change_id, args.force)
1076
+
1077
+ if (!result.completed) {
1078
+ const lines = [
1079
+ `## Change ${args.change_id} Completion BLOCKED`,
1080
+ "",
1081
+ `**Reason:** ${result.result.reason}`,
1082
+ "",
1083
+ "### Pending Promises",
1084
+ ]
1085
+ for (const p of result.result.pending_promises) {
1086
+ lines.push(`- **${p.id}** (${p.source_node_id}): ${p.description}`)
1087
+ }
1088
+ lines.push("")
1089
+ lines.push("Use `sdd.promises` to verify these promises before completing the change.")
1090
+ lines.push("Or use `force=true` to override (not recommended).")
1091
+ repo.saveGraph(graph)
1092
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
1093
+ return lines.join("\n")
1094
+ }
1095
+
1096
+ repo.saveGraph(graph)
1097
+ invalidateCacheForMutation(ctx.directory, ["change"], ["completed"])
1098
+ markCompleted()
1099
+
1100
+ // Advance transaction to COMPLETED
1101
+ try {
1102
+ const txManager = new TransactionManager(ctx.directory)
1103
+ const txs = txManager.getTransactionsForChange(args.change_id)
1104
+ if (txs.length > 0) {
1105
+ txManager.advanceStatus(txs[0].id, "COMPLETED")
1106
+ }
1107
+ } catch {}
1108
+
1109
+ return `Change ${args.change_id} completed.`
1110
+ } catch (e) {
1111
+ return `Error: ${e instanceof Error ? e.message : String(e)}`
1112
+ }
1113
+ },
1114
+ }),
1115
+
1116
+ "sdd.verify_implementation": tool({
1117
+ description: "Run declared formatter, lint, typecheck, tests and git diff checks after implementation. A successful report is required before completing a Change.",
1118
+ args: {
1119
+ change_id: tool.schema.string().describe("Approved Change node ID being verified"),
1120
+ },
1121
+ async execute(args, ctx) {
1122
+ const startedAt = Date.now()
1123
+ const result = validateExecutableProject(ctx.directory)
1124
+ saveExecutableValidation(ctx.directory, args.change_id, result)
1125
+ recordTelemetry(ctx.directory, {
1126
+ name: "executable_verification",
1127
+ duration_ms: Date.now() - startedAt,
1128
+ metadata: { change_id: args.change_id, passed: result.passed, verified: result.verified },
1129
+ })
1130
+ const lines = [`## Executable Verification: ${result.passed && result.verified ? "PASSED" : "BLOCKED"}`]
1131
+ for (const check of result.checks) lines.push(`- ${check.status.toUpperCase()}: ${check.name}${check.output ? ` — ${check.output.slice(0, 300)}` : ""}`)
1132
+ if (!result.verified) lines.push("No executable verification script was available; configure project scripts before completing the Change.")
1133
+ return lines.join("\n")
1134
+ },
1135
+ }),
1136
+
1137
+ "sdd.record_feedback": tool({
1138
+ description: "Record a local human correction for an extracted fact, classification, or test-requirement link.",
1139
+ args: {
1140
+ category: tool.schema.string().describe("discovery, classification, coverage, drift, or generation"),
1141
+ expected: tool.schema.string().describe("Correct result"),
1142
+ actual: tool.schema.string().describe("Observed incorrect result"),
1143
+ node_id: tool.schema.string().optional().describe("Related graph node"),
1144
+ },
1145
+ async execute(args, ctx) {
1146
+ recordFeedback(ctx.directory, { ...args })
1147
+ return "Feedback recorded locally. Future evaluation reports can use this correction category."
1148
+ },
1149
+ }),
1150
+
1151
+ "sdd.telemetry": tool({
1152
+ description: "Show local performance, estimated token, and cache telemetry. No data is sent externally.",
1153
+ args: {},
1154
+ async execute(_args, ctx) {
1155
+ const summary = getTelemetrySummary(ctx.directory)
1156
+ return [
1157
+ "## Local Telemetry",
1158
+ `- Events: ${summary.events}`,
1159
+ `- Total duration: ${summary.total_duration_ms}ms`,
1160
+ `- Estimated context tokens: ${summary.estimated_tokens}`,
1161
+ `- Cache hit rate: ${(summary.cache_hit_rate * 100).toFixed(1)}%`,
1162
+ ].join("\n")
1163
+ },
1164
+ }),
1165
+
1166
+ "sdd.fail_change": tool({
1167
+ description: "Mark a change as FAILED with a reason. Use when a change cannot be completed.",
1168
+ args: {
1169
+ change_id: tool.schema.string().describe("Change node ID"),
1170
+ reason: tool.schema.string().describe("Reason for failure"),
1171
+ },
1172
+ async execute(args, ctx) {
1173
+ const repo = getRepo(ctx.directory)
1174
+ if (!repo.isInitialized()) return "SDD not initialized."
1175
+ const graph = repo.loadGraph()
1176
+
1177
+ try {
1178
+ failChange(graph, args.change_id, args.reason)
1179
+ repo.saveGraph(graph)
1180
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
1181
+ addAuditEntry(ctx.directory, process.env.USER || "system", "sdd.fail_change", args.change_id, "denied", args.reason)
1182
+ return `Change ${args.change_id} marked as FAILED.\nReason: ${args.reason}`
1183
+ } catch (e) {
1184
+ return `Error: ${e instanceof Error ? e.message : String(e)}`
1185
+ }
1186
+ },
1187
+ }),
1188
+
1189
+ "sdd.change_history": tool({
1190
+ description: "View the full history of all changes in the SDD graph, ordered by creation date.",
1191
+ args: {
1192
+ limit: tool.schema.number().optional().describe("Max changes to show (default: 20)"),
1193
+ },
1194
+ async execute(args, ctx) {
1195
+ const repo = getRepo(ctx.directory)
1196
+ if (!repo.isInitialized()) return "SDD not initialized."
1197
+ const graph = repo.loadGraph()
1198
+ const history = getChangeHistory(graph)
1199
+
1200
+ if (history.length === 0) return "No changes found in history."
1201
+
1202
+ const limit = args.limit || 20
1203
+ const lines = [`## Change History (${history.length} total)\n`]
1204
+ for (const change of history.slice(-limit).reverse()) {
1205
+ const statusIcon = change.status === "COMPLETED" ? "✅" : change.status === "FAILED" ? "❌" : change.status === "APPROVED" ? "🟢" : "⏳"
1206
+ lines.push(`- ${statusIcon} **${change.id}:** ${change.name} [${change.status}]`)
1207
+ lines.push(` Approval: ${change.metadata.approval_level} | Created: ${change.created_at}`)
1208
+ if (change.metadata.affected_nodes.length > 0) {
1209
+ lines.push(` Affected: ${change.metadata.affected_nodes.length} node(s)`)
1210
+ }
1211
+ }
1212
+ if (history.length > limit) {
1213
+ lines.push(`\n... and ${history.length - limit} older changes`)
1214
+ }
1215
+ return lines.join("\n")
1216
+ },
1217
+ }),
1218
+
1219
+ "sdd.impact_report": tool({
1220
+ description: "Generate a detailed impact report for a specific change, showing affected nodes, files, and new/modified/removed items.",
1221
+ args: {
1222
+ change_id: tool.schema.string().describe("Change node ID"),
1223
+ },
1224
+ async execute(args, ctx) {
1225
+ const repo = getRepo(ctx.directory)
1226
+ if (!repo.isInitialized()) return "SDD not initialized."
1227
+ const graph = repo.loadGraph()
1228
+ return formatImpactReport(graph, args.change_id)
1229
+ },
1230
+ }),
1231
+
1232
+ "sdd.pending_changes": tool({
1233
+ description: "List all pending changes in the SDD graph. Detects stale changes older than 7 days.",
1234
+ args: {},
1235
+ async execute(_args, ctx) {
1236
+ const repo = getRepo(ctx.directory)
1237
+ if (!repo.isInitialized()) return "SDD not initialized."
1238
+ const graph = repo.loadGraph()
1239
+ const pending = getPendingChanges(graph)
1240
+
1241
+ if (pending.length === 0) return "No pending changes."
1242
+
1243
+ const lines = [`## Pending Changes (${pending.length})\n`]
1244
+ const changeLines = pending.map(change =>
1245
+ `- **${change.id}:** ${change.name} [${change.status}] (approval: ${change.metadata.approval_level})`
1246
+ )
1247
+ lines.push(...truncateList(changeLines, 15))
1248
+
1249
+ // Detect stale changes
1250
+ const now = Date.now()
1251
+ const staleThreshold = 7 * 24 * 60 * 60 * 1000
1252
+ const staleChanges = pending.filter(
1253
+ (c) => now - new Date(c.created_at).getTime() > staleThreshold
1254
+ )
1255
+
1256
+ if (staleChanges.length > 0) {
1257
+ lines.push("")
1258
+ lines.push(`### ⚠️ Stale Changes (${staleChanges.length})`)
1259
+ lines.push("These changes have been pending for over 7 days:")
1260
+ for (const c of staleChanges.slice(0, 5)) {
1261
+ const age = Math.floor((now - new Date(c.created_at).getTime()) / (24 * 60 * 60 * 1000))
1262
+ lines.push(`- **${c.id}**: ${c.name} (${age} days old)`)
1263
+ }
1264
+ lines.push("")
1265
+ lines.push("Consider completing or archiving stale changes to keep the graph clean.")
1266
+ }
1267
+
1268
+ return lines.join("\n")
1269
+ },
1270
+ }),
1271
+
1272
+ "sdd.list_nodes": tool({
1273
+ description: "List all nodes of a specific type in the SDD graph.",
1274
+ args: {
1275
+ type: tool.schema.string().describe("Node type to list"),
1276
+ },
1277
+ async execute(args, ctx) {
1278
+ const repo = getRepo(ctx.directory)
1279
+ if (!repo.isInitialized()) return "SDD not initialized."
1280
+ const indices = repo.getIndices()
1281
+ const nodes = getNodesByTypeIndexed(indices, args.type as NodeType)
1282
+
1283
+ if (nodes.length === 0) return `No nodes of type "${args.type}" found.`
1284
+
1285
+ const { page, total, hasMore } = paginate(nodes)
1286
+ const lines = [`## ${args.type} Nodes (${total}${hasMore ? ", showing " + page.length : ""})\n`]
1287
+ for (const node of page) {
1288
+ lines.push(`- **${node.id}:** ${node.name} [${node.status}]`)
1289
+ }
1290
+ if (hasMore) lines.push(`\nUse pagination or filter to see more.`)
1291
+ return lines.join("\n")
1292
+ },
1293
+ }),
1294
+
1295
+ "sdd.find_path": tool({
1296
+ description: "Find the relationship path between two nodes in the graph.",
1297
+ args: {
1298
+ from_id: tool.schema.string().describe("Source node ID"),
1299
+ to_id: tool.schema.string().describe("Target node ID"),
1300
+ },
1301
+ async execute(args, ctx) {
1302
+ const repo = getRepo(ctx.directory)
1303
+ if (!repo.isInitialized()) return "SDD not initialized."
1304
+ const graph = repo.loadGraph()
1305
+ const path = findPath(graph, args.from_id, args.to_id)
1306
+
1307
+ if (!path) return `No path found between ${args.from_id} and ${args.to_id}.`
1308
+
1309
+ const lines = [`## Path: ${args.from_id} → ${args.to_id}\n`]
1310
+ for (let i = 0; i < path.length; i++) {
1311
+ const node = path[i]
1312
+ const arrow = i < path.length - 1 ? " →" : ""
1313
+ lines.push(`${i + 1}. **${node.id}** (${node.type}): ${node.name}${arrow}`)
1314
+ }
1315
+ return lines.join("\n")
1316
+ },
1317
+ }),
1318
+
1319
+ "sdd.generate_code": tool({
1320
+ description:
1321
+ "Generate project code from the SDD Knowledge Graph. Creates actual project files " +
1322
+ "(backend routes, models, services, controllers, frontend components, database schema, tests) " +
1323
+ "based on the specification in the graph. " +
1324
+ "If the detected tech stack is not in the built-in template set, returns a specification prompt " +
1325
+ "that you should use to generate the code yourself using your knowledge of the specified technologies.",
1326
+ args: {
1327
+ target_dir: tool.schema.string().optional().describe("Target directory for generated code (defaults to project dir)"),
1328
+ force: tool.schema.boolean().optional().describe("Force regeneration even if validation fails"),
1329
+ overwrite_existing: tool.schema.boolean().optional().describe("Replace conflicting generated files after review; creates backups"),
1330
+ },
1331
+ async execute(args, ctx) {
1332
+ const repo = getRepo(ctx.directory)
1333
+ if (!repo.isInitialized()) return "SDD not initialized. Run sdd.initialize first."
1334
+ const graph = repo.loadGraph()
1335
+
1336
+ // ENFORCEMENT: require approved Change before generating code
1337
+ const { getWorkflowState } = await import("../sdd/enforcement/workflow-tracker.js")
1338
+ const wfState = getWorkflowState()
1339
+ if (!wfState.enforced) {
1340
+ return [
1341
+ "## SDD BLOCKED: No workflow active",
1342
+ "",
1343
+ "Cannot generate code without an active SDD workflow.",
1344
+ "",
1345
+ "1. Run sdd.enforce to classify the change and create a Change node",
1346
+ "2. Update the specification",
1347
+ "3. Run sdd.approve_change",
1348
+ "4. THEN run sdd.generate_code",
1349
+ ].join("\n")
1350
+ }
1351
+
1352
+ // ENFORCEMENT: verify approved Change exists in graph
1353
+ const approvedChanges = graph.nodes.filter(
1354
+ (n) => n.type === "change" && n.status === "APPROVED",
1355
+ )
1356
+ if (approvedChanges.length === 0 && !args.force) {
1357
+ return [
1358
+ "## SDD BLOCKED: No approved Change node",
1359
+ "",
1360
+ "Cannot generate code without an approved Change node in the graph.",
1361
+ "",
1362
+ "1. Run sdd.enforce to create a Change node",
1363
+ "2. Run sdd.approve_change to approve it",
1364
+ "3. THEN run sdd.generate_code",
1365
+ "",
1366
+ "Or use force=true to override (not recommended).",
1367
+ ].join("\n")
1368
+ }
1369
+
1370
+ // Validate before generating
1371
+ const validation = validateGraph(graph)
1372
+ if (!validation.valid && !args.force) {
1373
+ return [
1374
+ "Cannot generate code: SDD validation failed.",
1375
+ "",
1376
+ formatValidationResult(validation),
1377
+ "",
1378
+ "Fix the specification first, or use force=true to override.",
1379
+ ].join("\n")
1380
+ }
1381
+
1382
+ // Detect stack
1383
+ const stack = detectTechStack(graph)
1384
+ const stackDesc = [stack.frontend, stack.backend, stack.database].filter(Boolean).join(" + ") || "unknown"
1385
+
1386
+ // Generate
1387
+ const targetDir = args.target_dir || ctx.directory
1388
+ const plan = generateProject(graph, stack)
1389
+
1390
+ // If no files were generated (spec prompt mode), tell the agent to generate code itself
1391
+ if (plan.files.length === 0) {
1392
+ return [
1393
+ `## Code Generation: Custom Stack`,
1394
+ `**Detected stack:** ${stackDesc}`,
1395
+ `**Built-in templates:** Not available for this stack`,
1396
+ "",
1397
+ "### Specification",
1398
+ plan.summary,
1399
+ "",
1400
+ "### Action Required",
1401
+ "The detected tech stack does not have built-in code templates.",
1402
+ "Use the specification above to generate the project code using your knowledge.",
1403
+ "Create the files directly — follow the conventions of the specified technologies.",
1404
+ ].join("\n")
1405
+ }
1406
+
1407
+ // Write files (template mode)
1408
+ const result = writeGeneratedFiles(targetDir, plan, {
1409
+ overwrite: args.overwrite_existing === true,
1410
+ backup: true,
1411
+ })
1412
+
1413
+ const lines = [
1414
+ plan.summary,
1415
+ "",
1416
+ `### Write Results`,
1417
+ `- Files written: ${result.written}`,
1418
+ `- Files created: ${result.created}`,
1419
+ `- Unchanged files: ${result.unchanged}`,
1420
+ ]
1421
+
1422
+ if (result.conflicts.length > 0) {
1423
+ lines.push(`- Conflicts (not overwritten): ${result.conflicts.length}`)
1424
+ for (const conflict of result.conflicts) lines.push(` - ${conflict}`)
1425
+ lines.push("Review the diff and call again with overwrite_existing=true only for an approved replacement.")
1426
+ }
1427
+ if (result.backups.length > 0) lines.push(`- Backups created: ${result.backups.length}`)
1428
+
1429
+ if (result.errors.length > 0) {
1430
+ lines.push(`- Errors: ${result.errors.length}`)
1431
+ for (const err of result.errors) {
1432
+ lines.push(` - ${err}`)
1433
+ }
1434
+ }
1435
+
1436
+ // Update graph with file nodes
1437
+ const now = new Date().toISOString()
1438
+ for (const file of plan.files) {
1439
+ const fileId = `${graph.project_id}-FILE-${file.path.replace(/[^a-zA-Z0-9]/g, "-").slice(0, 30)}`
1440
+ try {
1441
+ addNode(graph, {
1442
+ id: fileId,
1443
+ type: "file",
1444
+ name: file.path,
1445
+ description: file.description,
1446
+ status: "IMPLEMENTED",
1447
+ version: 1,
1448
+ metadata: { path: file.path, language: "typescript" },
1449
+ created_at: now,
1450
+ updated_at: now,
1451
+ })
1452
+ } catch {
1453
+ // Node might already exist
1454
+ }
1455
+ }
1456
+
1457
+ repo.saveGraph(graph)
1458
+ invalidateCacheForMutation(ctx.directory, ["file"], ["implements"])
1459
+
1460
+ return lines.join("\n")
1461
+ },
1462
+ }),
1463
+
1464
+ "sdd.enforce": tool({
1465
+ description:
1466
+ "Enforce SDD-first workflow. Classifies a user request, creates a Change node, " +
1467
+ "validates the SDD, and determines if implementation can proceed. " +
1468
+ "MUST be called before any code modification.",
1469
+ args: {
1470
+ request_description: tool.schema.string().describe("What the user wants to do"),
1471
+ affected_entities: tool.schema.string().optional().describe("Comma-separated entity names affected"),
1472
+ affected_files: tool.schema.string().optional().describe("Comma-separated file paths affected"),
1473
+ },
1474
+ async execute(args, ctx) {
1475
+ const repo = getRepo(ctx.directory)
1476
+ if (!repo.isInitialized()) {
1477
+ return [
1478
+ "## SDD Enforcement: BLOCKED",
1479
+ "",
1480
+ "SDD is not initialized for this project.",
1481
+ "You MUST run sdd.initialize first before making any changes.",
1482
+ "",
1483
+ "Ask the user: 'Should I initialize the SDD for this project?'",
1484
+ ].join("\n")
1485
+ }
1486
+
1487
+ const graph = repo.loadGraph()
1488
+ const request = classifyChangeRequest(args.request_description)
1489
+
1490
+ const affectedEntities = args.affected_entities
1491
+ ? args.affected_entities.split(",").map((s) => s.trim())
1492
+ : []
1493
+
1494
+ const affectedFiles = args.affected_files
1495
+ ? args.affected_files.split(",").map((s) => s.trim())
1496
+ : []
1497
+
1498
+ // Smart batch: for AUTO-level changes, execute full cycle in one call
1499
+ const { enforceSmartBatch } = await import("../sdd/enforcement/interceptor.js")
1500
+ const result = enforceSmartBatch(graph, request, affectedEntities, affectedFiles)
1501
+
1502
+ repo.saveGraph(graph)
1503
+ invalidateCacheForMutation(ctx.directory, ["change"], ["created_by"])
1504
+
1505
+ if (result.auto_completed) {
1506
+ if (result.change_id) markEnforced(result.change_id)
1507
+ return [
1508
+ `## SDD Enforcement: AUTO-COMPLETED ✅`,
1509
+ `**Request Type:** ${request.type}`,
1510
+ `**Description:** ${request.description}`,
1511
+ `**Change ID:** ${result.change_id}`,
1512
+ `**Validation:** PASSED`,
1513
+ ``,
1514
+ `This is a low-risk change. The SDD cycle has been completed automatically.`,
1515
+ `You may proceed with implementation.`,
1516
+ ].join("\n")
1517
+ }
1518
+
1519
+ if (result.allowed && result.change_id) {
1520
+ markEnforced(result.change_id)
1521
+ }
1522
+
1523
+ return buildEnforcementPrompt(request, result)
1524
+ },
1525
+ }),
1526
+
1527
+ "sdd.enforce_rules": tool({
1528
+ description:
1529
+ "Returns the SDD enforcement rules. Use this to understand the mandatory workflow.",
1530
+ args: {},
1531
+ async execute() {
1532
+ return getSddEnforcementRules()
1533
+ },
1534
+ }),
1535
+
1536
+ "sdd.full_cycle": tool({
1537
+ description:
1538
+ "Execute a complete SDD change cycle: analyze request → update spec → validate → " +
1539
+ "generate/implement code → validate implementation → sync graph. " +
1540
+ "This is the main tool for processing user change requests.",
1541
+ args: {
1542
+ request: tool.schema.string().describe("What the user wants to change"),
1543
+ auto_approve: tool.schema.boolean().optional().describe("Skip approval for non-architecture changes"),
1544
+ },
1545
+ async execute(args, ctx) {
1546
+ const repo = getRepo(ctx.directory)
1547
+ if (!repo.isInitialized()) {
1548
+ return "SDD not initialized. Run sdd.initialize first."
1549
+ }
1550
+
1551
+ const graph = repo.loadGraph()
1552
+ const lines: string[] = []
1553
+ const request = classifyChangeRequest(args.request)
1554
+
1555
+ lines.push(`## Full Cycle: ${request.type}`)
1556
+ lines.push(`**Request:** ${args.request}\n`)
1557
+
1558
+ // Step 1: Enforce SDD-first
1559
+ lines.push("### Step 1: SDD Enforcement")
1560
+ const enforcement = enforceSddFirst(graph, request)
1561
+
1562
+ if (!enforcement.allowed) {
1563
+ lines.push(`❌ **BLOCKED:** ${enforcement.reason}`)
1564
+ if (enforcement.blocking_reasons) {
1565
+ for (const r of enforcement.blocking_reasons) lines.push(`- ${r}`)
1566
+ }
1567
+ repo.saveGraph(graph)
1568
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
1569
+ return lines.join("\n")
1570
+ }
1571
+
1572
+ lines.push(`✅ **APPROVED:** Change ${enforcement.change_id}`)
1573
+ lines.push(enforcement.impact_summary || "")
1574
+
1575
+ // Step 2: Validate SDD
1576
+ lines.push("\n### Step 2: SDD Validation")
1577
+ const validation = validateGraph(graph)
1578
+ if (validation.valid) {
1579
+ lines.push("✅ SDD validation passed")
1580
+ } else {
1581
+ lines.push(`❌ SDD validation failed: ${validation.errors.length} errors`)
1582
+ for (const e of validation.errors) lines.push(`- [${e.code}] ${e.message}`)
1583
+ repo.saveGraph(graph)
1584
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
1585
+ return lines.join("\n")
1586
+ }
1587
+
1588
+ // Step 3: Generate code plan
1589
+ lines.push("\n### Step 3: Code Generation Plan")
1590
+ const plan = generateProject(graph)
1591
+ lines.push(`Generated plan: ${plan.files.length} files to create/update`)
1592
+
1593
+ // Step 4: Write code
1594
+ lines.push("\n### Step 4: Implementation")
1595
+ const writeResult = writeGeneratedFiles(ctx.directory, plan)
1596
+ lines.push(`Files written: ${writeResult.written}`)
1597
+ if (writeResult.errors.length > 0) {
1598
+ lines.push(`Errors: ${writeResult.errors.length}`)
1599
+ for (const err of writeResult.errors) lines.push(` - ${err}`)
1600
+ }
1601
+
1602
+ // Step 5: Validate implementation
1603
+ lines.push("\nStep 5: Post-Implementation Validation")
1604
+ const postValidation = validateGraph(graph)
1605
+ if (postValidation.valid) {
1606
+ lines.push("✅ Post-implementation SDD validation passed")
1607
+ } else {
1608
+ lines.push(`⚠️ Post-implementation warnings: ${postValidation.warnings.length}`)
1609
+ }
1610
+
1611
+ // Step 5b: execute the project's declared verification pipeline.
1612
+ lines.push("\n### Step 5b: Executable Verification")
1613
+ const execution = validateExecutableProject(ctx.directory)
1614
+ if (enforcement.change_id) saveExecutableValidation(ctx.directory, enforcement.change_id, execution)
1615
+ lines.push(execution.passed && execution.verified
1616
+ ? "✅ Formatter/lint/typecheck/tests passed"
1617
+ : "❌ Executable verification is incomplete or failed; Change will not be completed")
1618
+
1619
+ // Step 6: Complete change (with promise check)
1620
+ if (enforcement.change_id) {
1621
+ lines.push("\n### Step 6: Change Completion")
1622
+ try {
1623
+ if (!execution.passed || !execution.verified) {
1624
+ lines.push("⚠️ Completion blocked: executable verification did not pass")
1625
+ repo.saveGraph(graph)
1626
+ return lines.join("\n")
1627
+ }
1628
+ const { completeChangeWithPromiseCheck } = await import("../sdd/changes/manager.js")
1629
+ const completionResult = completeChangeWithPromiseCheck(graph, enforcement.change_id)
1630
+ if (completionResult.completed) {
1631
+ lines.push(`✅ Change ${enforcement.change_id} completed`)
1632
+ } else {
1633
+ lines.push(`⚠️ Change ${enforcement.change_id} completion BLOCKED`)
1634
+ lines.push(`**Reason:** ${completionResult.result.reason}`)
1635
+ if (completionResult.result.pending_promises.length > 0) {
1636
+ lines.push("")
1637
+ lines.push("### Pending Promises")
1638
+ for (const p of completionResult.result.pending_promises) {
1639
+ lines.push(`- **${p.id}**: ${p.description}`)
1640
+ }
1641
+ }
1642
+ }
1643
+ } catch (e) {
1644
+ lines.push(`⚠️ Could not complete change: ${e}`)
1645
+ }
1646
+ }
1647
+
1648
+ repo.saveGraph(graph)
1649
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
1650
+
1651
+ lines.push("\n### Summary")
1652
+ lines.push(`- Change: ${enforcement.change_id}`)
1653
+ lines.push(`- Files generated: ${writeResult.written}`)
1654
+ lines.push(`- SDD valid: ${postValidation.valid}`)
1655
+ lines.push(`- Status: COMPLETED`)
1656
+
1657
+ return lines.join("\n")
1658
+ },
1659
+ }),
1660
+
1661
+ "sdd.toggle": tool({
1662
+ description:
1663
+ "Enable or disable SDD enforcement. When disabled, code changes can be made freely " +
1664
+ "without going through the SDD workflow. When enabled, all changes must follow the " +
1665
+ "SDD-first process (spec before code).",
1666
+ args: {
1667
+ enabled: tool.schema.boolean().optional().describe("true to enable, false to disable. Omit to toggle."),
1668
+ },
1669
+ async execute(args, ctx) {
1670
+ const current = isSddEnabled(ctx.directory)
1671
+ const newState = args.enabled !== undefined ? args.enabled : !current
1672
+ const state = setToggleState(ctx.directory, newState)
1673
+
1674
+ if (!state.enabled) {
1675
+ resetWorkflowState()
1676
+ }
1677
+
1678
+ const status = state.enabled ? "🟢 ENABLED" : "🔴 DISABLED"
1679
+ const lines = [
1680
+ `## SDD Enforcement: ${status}`,
1681
+ "",
1682
+ state.enabled
1683
+ ? "All code changes must now go through the SDD workflow (spec before code)."
1684
+ : "SDD enforcement is off. You can make code changes freely.",
1685
+ "",
1686
+ `Changed at: ${state.changed_at}`,
1687
+ "",
1688
+ "Commands: `/sdd on`, `/sdd off`, `/sdd status`",
1689
+ ]
1690
+
1691
+ return lines.join("\n")
1692
+ },
1693
+ }),
1694
+
1695
+ "sdd.constitution": tool({
1696
+ description:
1697
+ "View or update the project constitution. The constitution defines high-level principles " +
1698
+ "that govern ALL specifications. Every principle is checked during validation.",
1699
+ args: {
1700
+ action: tool.schema.string().describe("Action: 'view', 'add', 'remove'"),
1701
+ rule: tool.schema.string().optional().describe("Principle rule text (for add). E.g., 'Never use var, always use const'"),
1702
+ severity: tool.schema.string().optional().describe("Severity: 'must', 'should', or 'may' (default: must)"),
1703
+ scope: tool.schema.string().optional().describe("Scope regex to limit which nodes are checked (optional)"),
1704
+ principle_id: tool.schema.string().optional().describe("Principle ID to remove (for remove)"),
1705
+ },
1706
+ async execute(args, ctx) {
1707
+ const repo = getRepo(ctx.directory)
1708
+ if (!repo.isInitialized()) return "SDD not initialized. Run sdd.initialize first."
1709
+ const graph = repo.loadGraph()
1710
+
1711
+ if (args.action === "view") {
1712
+ const result = validateAgainstConstitution(graph)
1713
+ return formatConstitutionResult(result)
1714
+ }
1715
+
1716
+ if (args.action === "add") {
1717
+ if (!args.rule) return "Error: 'rule' is required for add action."
1718
+
1719
+ const existing = graph.nodes.find((n) => n.type === "constitution") as ConstitutionNode | undefined
1720
+ const now = new Date().toISOString()
1721
+
1722
+ if (existing) {
1723
+ const principles = existing.metadata.principles || []
1724
+ const newId = `P-${String(principles.length + 1).padStart(3, "0")}`
1725
+ principles.push({
1726
+ id: newId,
1727
+ rule: args.rule,
1728
+ severity: (args.severity as "must" | "should" | "may") || "must",
1729
+ scope: args.scope,
1730
+ })
1731
+ existing.metadata.principles = principles
1732
+ existing.updated_at = now
1733
+ } else {
1734
+ const nodeId = `${graph.project_id}-CONSTITUTION`
1735
+ addNode(graph, {
1736
+ id: nodeId,
1737
+ type: "constitution",
1738
+ name: "Project Constitution",
1739
+ description: "High-level principles governing the project specification",
1740
+ status: "APPROVED",
1741
+ version: 1,
1742
+ metadata: {
1743
+ principles: [{
1744
+ id: "P-001",
1745
+ rule: args.rule,
1746
+ severity: (args.severity as "must" | "should" | "may") || "must",
1747
+ scope: args.scope,
1748
+ }],
1749
+ },
1750
+ created_at: now,
1751
+ updated_at: now,
1752
+ } as ConstitutionNode)
1753
+ }
1754
+
1755
+ repo.saveGraph(graph)
1756
+ invalidateCacheForMutation(ctx.directory, ["constitution"], ["validates"])
1757
+ return `Principle added: "${args.rule}" [${args.severity || "must"}]`
1758
+ }
1759
+
1760
+ if (args.action === "remove") {
1761
+ if (!args.principle_id) return "Error: 'principle_id' is required for remove action."
1762
+
1763
+ const existing = graph.nodes.find((n) => n.type === "constitution") as ConstitutionNode | undefined
1764
+ if (!existing) return "No constitution found."
1765
+
1766
+ const principles = existing.metadata.principles || []
1767
+ const idx = principles.findIndex((p) => p.id === args.principle_id)
1768
+ if (idx === -1) return `Principle ${args.principle_id} not found.`
1769
+
1770
+ const removed = principles.splice(idx, 1)[0]
1771
+ existing.updated_at = new Date().toISOString()
1772
+ repo.saveGraph(graph)
1773
+ invalidateCacheForMutation(ctx.directory, ["constitution"], ["validates"])
1774
+ return `Principle removed: "${removed.rule}"`
1775
+ }
1776
+
1777
+ return "Invalid action. Use 'view', 'add', or 'remove'."
1778
+ },
1779
+ }),
1780
+
1781
+ "sdd.promises": tool({
1782
+ description:
1783
+ "View and manage specification promises. Promises are verifiable assertions extracted " +
1784
+ "from requirements (acceptance_criteria) and business rules. Each promise tracks whether " +
1785
+ "it has been fulfilled, violated, or is still pending.",
1786
+ args: {
1787
+ action: tool.schema.string().describe("Action: 'report', 'list', 'verify', 'violate', 'unverifiable'"),
1788
+ promise_id: tool.schema.string().optional().describe("Promise ID (for verify/violate)"),
1789
+ evidence: tool.schema.string().optional().describe("Evidence of fulfillment (for verify)"),
1790
+ },
1791
+ async execute(args, ctx) {
1792
+ const repo = getRepo(ctx.directory)
1793
+ if (!repo.isInitialized()) return "SDD not initialized."
1794
+ const graph = repo.loadGraph()
1795
+
1796
+ if (args.action === "report") {
1797
+ const report = getPromiseReport(graph)
1798
+ return formatPromiseReport(report)
1799
+ }
1800
+
1801
+ if (args.action === "list") {
1802
+ const promises = extractPromises(graph)
1803
+ if (promises.length === 0) return "No promises found. Add requirements with acceptance_criteria or business rules."
1804
+
1805
+ const lines = [`## All Promises (${promises.length})\n`]
1806
+ for (const p of promises) {
1807
+ const status = p.status === "fulfilled" ? "✅" : p.status === "violated" ? "❌" : "⏳"
1808
+ lines.push(`- ${status} **${p.id}** (${p.source_node_id}): ${p.description}`)
1809
+ }
1810
+ return lines.join("\n")
1811
+ }
1812
+
1813
+ if (args.action === "verify") {
1814
+ if (!args.promise_id) return "Error: 'promise_id' is required for verify."
1815
+ if (!args.evidence) return "Error: 'evidence' is required for verify."
1816
+
1817
+ const result = verifyPromise(graph, args.promise_id, args.evidence)
1818
+ if (!result) return `Promise ${args.promise_id} not found.`
1819
+ repo.saveGraph(graph)
1820
+ invalidateCacheForMutation(ctx.directory, ["requirement", "business_rule"], ["promises"])
1821
+ return `Promise ${args.promise_id} marked as FULFILLED.\nEvidence: ${args.evidence}`
1822
+ }
1823
+
1824
+ if (args.action === "violate") {
1825
+ if (!args.promise_id) return "Error: 'promise_id' is required for violate."
1826
+
1827
+ const result = markPromiseViolated(graph, args.promise_id)
1828
+ if (!result) return `Promise ${args.promise_id} not found.`
1829
+ repo.saveGraph(graph)
1830
+ invalidateCacheForMutation(ctx.directory, ["requirement", "business_rule"], ["promises"])
1831
+ return `Promise ${args.promise_id} marked as VIOLATED.`
1832
+ }
1833
+
1834
+ if (args.action === "unverifiable") {
1835
+ const allPromises = extractPromises(graph)
1836
+ const pendingPromises = allPromises.filter(p => p.status === "pending")
1837
+ const unverifiable = findUnverifiablePromises(pendingPromises, graph)
1838
+
1839
+ if (unverifiable.length === 0) {
1840
+ return "All pending promises are verifiable. No infrastructure gaps detected."
1841
+ }
1842
+
1843
+ const lines = [`## Unverifiable Promises (${unverifiable.length})\n`]
1844
+ lines.push("These promises cannot be verified because the required infrastructure is missing from the graph:\n")
1845
+ for (const item of unverifiable) {
1846
+ lines.push(`- **${item.id}**: ${item.description}`)
1847
+ lines.push(` **Missing:** ${item.reason}`)
1848
+ }
1849
+ lines.push("")
1850
+ lines.push("Add the missing infrastructure nodes (architecture components, files, etc.) to make these promises verifiable.")
1851
+ return lines.join("\n")
1852
+ }
1853
+
1854
+ return "Invalid action. Use 'report', 'list', 'verify', 'violate', or 'unverifiable'."
1855
+ },
1856
+ }),
1857
+
1858
+ "sdd.quality": tool({
1859
+ description:
1860
+ "Calculate and display the project quality score (0-100%) with trend analysis. " +
1861
+ "Factors: validation, drift, promises, completeness, constitution compliance, test coverage.",
1862
+ args: {},
1863
+ async execute(_args, ctx) {
1864
+ const repo = getRepo(ctx.directory)
1865
+ if (!repo.isInitialized()) return "SDD not initialized."
1866
+ const graph = repo.loadGraph()
1867
+
1868
+ // Check analysis cache
1869
+ const cacheMgr = getCacheManager(ctx.directory)
1870
+ const cached = cacheMgr.getAnalysisResult("quality", graph.metadata.updated_at ? new Date(graph.metadata.updated_at).getTime() : 0, graph.nodes.length)
1871
+ if (cached) return cached as string
1872
+
1873
+ const report = calculateQualityScore(graph, ctx.directory)
1874
+ const formatted = formatQualityReport(report)
1875
+
1876
+ // Cache the result
1877
+ cacheMgr.setAnalysisResult("quality", formatted, graph.metadata.updated_at ? new Date(graph.metadata.updated_at).getTime() : 0, graph.nodes.length)
1878
+
1879
+ return formatted
1880
+ },
1881
+ }),
1882
+
1883
+ "sdd.session_handoff": tool({
1884
+ description:
1885
+ "Generate a session handoff pack. Shows the current SDD state including active changes, " +
1886
+ "pending promises, quality score, recent decisions, and blocked items.",
1887
+ args: {},
1888
+ async execute(_args, ctx) {
1889
+ const repo = getRepo(ctx.directory)
1890
+ if (!repo.isInitialized()) return "SDD not initialized."
1891
+ const graph = repo.loadGraph()
1892
+ const handoff = generateHandoff(graph, ctx.directory)
1893
+ saveSessionLog(ctx.directory, "session_handoff")
1894
+ return formatHandoffPack(handoff)
1895
+ },
1896
+ }),
1897
+
1898
+ "sdd.anti_patterns": tool({
1899
+ description:
1900
+ "Detect anti-patterns in the spec graph: god nodes, circular dependencies, " +
1901
+ "speculation, skipped steps, missing tests, empty nodes.",
1902
+ args: {},
1903
+ async execute(_args, ctx) {
1904
+ const repo = getRepo(ctx.directory)
1905
+ if (!repo.isInitialized()) return "SDD not initialized."
1906
+ const graph = repo.loadGraph()
1907
+ const result = detectAntiPatterns(graph)
1908
+ const formatted = formatAntiPatterns(result)
1909
+ // Truncate if too long
1910
+ if (formatted.length > 3000) {
1911
+ return formatted.slice(0, 3000) + "\n\n... (truncated, " + formatted.length + " chars total)"
1912
+ }
1913
+ return formatted
1914
+ },
1915
+ }),
1916
+
1917
+ "sdd.clone_detection": tool({
1918
+ description:
1919
+ "Detect code clones (duplicate code blocks) across the project source files. " +
1920
+ "Returns groups of similar code blocks with risk assessment.",
1921
+ args: {},
1922
+ async execute(_args, ctx) {
1923
+ const report = detectAstClones(ctx.directory)
1924
+ return formatCloneReport(report)
1925
+ },
1926
+ }),
1927
+
1928
+ "sdd.contradictions": tool({
1929
+ description:
1930
+ "Detect contradictions in the spec graph: conflicting requirements, " +
1931
+ "contradictory business rules, clashing designs, cross-type conflicts.",
1932
+ args: {},
1933
+ async execute(_args, ctx) {
1934
+ const repo = getRepo(ctx.directory)
1935
+ if (!repo.isInitialized()) return "SDD not initialized."
1936
+ const graph = repo.loadGraph()
1937
+ const report = detectContradictions(graph)
1938
+ const formatted = formatContradictionReport(report)
1939
+ // Truncate if too long
1940
+ if (formatted.length > 3000) {
1941
+ return formatted.slice(0, 3000) + "\n\n... (truncated, " + formatted.length + " chars total)"
1942
+ }
1943
+ return formatted
1944
+ },
1945
+ }),
1946
+
1947
+ "sdd.coverage": tool({
1948
+ description:
1949
+ "Calculate test coverage for requirements. Shows which requirements " +
1950
+ "are fully covered, partially covered, or have no tests at all.",
1951
+ args: {},
1952
+ async execute(_args, ctx) {
1953
+ const repo = getRepo(ctx.directory)
1954
+ if (!repo.isInitialized()) return "SDD not initialized."
1955
+ const graph = repo.loadGraph()
1956
+ const report = calculateCoverage(graph)
1957
+ return formatCoverageReport(report)
1958
+ },
1959
+ }),
1960
+
1961
+ "sdd.config_drift": tool({
1962
+ description:
1963
+ "Detect configuration drift: version mismatches, deprecated settings, " +
1964
+ "missing configs, lock file inconsistencies.",
1965
+ args: {},
1966
+ async execute(_args, ctx) {
1967
+ const report = detectConfigDrift(ctx.directory)
1968
+ return formatConfigDriftReport(report)
1969
+ },
1970
+ }),
1971
+
1972
+ "sdd.workflow_export": tool({
1973
+ description:
1974
+ "Export the current SDD workflow state as a structured report. " +
1975
+ "Includes summary, changes, decisions, blockers, and recommendations.",
1976
+ args: {},
1977
+ async execute(_args, ctx) {
1978
+ const repo = getRepo(ctx.directory)
1979
+ if (!repo.isInitialized()) return "SDD not initialized."
1980
+ const graph = repo.loadGraph()
1981
+ const exportData = exportWorkflow(graph)
1982
+ return formatWorkflowExport(exportData)
1983
+ },
1984
+ }),
1985
+
1986
+ "sdd.install_hooks": tool({
1987
+ description:
1988
+ "Install Git shell hooks for SDD integration (pre-commit, post-checkout, post-merge).",
1989
+ args: {
1990
+ hooks: tool.schema.array(tool.schema.string()).describe("Hooks to install: pre-commit, post-checkout, post-merge"),
1991
+ },
1992
+ async execute(args, ctx) {
1993
+ const hooks = (args.hooks as string[]) || ["pre-commit"]
1994
+ const created = generateShellHooks({ projectDir: ctx.directory, hooks })
1995
+ return formatShellHookResult(created)
1996
+ },
1997
+ }),
1998
+
1999
+ "sdd.brownfield_scan": tool({
2000
+ description:
2001
+ "Scan an existing (brownfield) project to understand its structure, " +
2002
+ "entry points, config files, test files, and frameworks.",
2003
+ args: {},
2004
+ async execute(_args, ctx) {
2005
+ const analysis = scanExistingProject(ctx.directory)
2006
+ return formatBrownfieldAnalysis(analysis)
2007
+ },
2008
+ }),
2009
+
2010
+ "sdd.generate_cicd": tool({
2011
+ description:
2012
+ "Generate CI/CD configuration files for the project. Supports GitHub Actions, GitLab CI, Jenkins, and Docker.",
2013
+ args: {
2014
+ platform: tool.schema.enum(["github", "gitlab", "jenkins", "docker", "all"]).describe("CI/CD platform"),
2015
+ },
2016
+ async execute(args, ctx) {
2017
+ const repo = getRepo(ctx.directory)
2018
+ if (!repo.isInitialized()) return "SDD not initialized."
2019
+ const graph = repo.loadGraph()
2020
+
2021
+ const config = {
2022
+ platform: (args.platform as "github" | "gitlab" | "jenkins" | "docker" | "all") || "all",
2023
+ projectDir: ctx.directory,
2024
+ graph,
2025
+ }
2026
+
2027
+ const results = generateCicd(config)
2028
+ writeCicdFiles(results)
2029
+ return formatCicdResults(results)
2030
+ },
2031
+ }),
2032
+
2033
+ "sdd.sync_status": tool({
2034
+ description: "Check the sync status with remote repository (branch, ahead/behind, dirty state).",
2035
+ args: {},
2036
+ async execute(_args, ctx) {
2037
+ const status = getSyncStatus(ctx.directory)
2038
+ return formatSyncStatus(status)
2039
+ },
2040
+ }),
2041
+
2042
+ "sdd.sync_pull": tool({
2043
+ description: "Pull latest changes from remote repository and sync SDD graph.",
2044
+ args: {},
2045
+ async execute(_args, ctx) {
2046
+ const acquired = acquireLock(ctx.directory, "current")
2047
+ if (!acquired) return "Error: Another sync operation is in progress. Wait or clear the lock."
2048
+
2049
+ try {
2050
+ const result = pullLatest(ctx.directory)
2051
+ return result.details
2052
+ } finally {
2053
+ releaseLock(ctx.directory)
2054
+ }
2055
+ },
2056
+ }),
2057
+
2058
+ "sdd.sync_push": tool({
2059
+ description: "Push SDD changes to remote repository.",
2060
+ args: {
2061
+ message: tool.schema.string().describe("Commit message"),
2062
+ },
2063
+ async execute(args, ctx) {
2064
+ const acquired = acquireLock(ctx.directory, "current")
2065
+ if (!acquired) return "Error: Another sync operation is in progress."
2066
+
2067
+ try {
2068
+ const result = pushChanges(ctx.directory, args.message || "SDD updates")
2069
+ return result.details
2070
+ } finally {
2071
+ releaseLock(ctx.directory)
2072
+ }
2073
+ },
2074
+ }),
2075
+
2076
+ "sdd.create_snapshot": tool({
2077
+ description: "Create a snapshot of the current SDD state before making changes.",
2078
+ args: {
2079
+ change_id: tool.schema.string().describe("Change ID to snapshot"),
2080
+ },
2081
+ async execute(args, ctx) {
2082
+ const repo = getRepo(ctx.directory)
2083
+ if (!repo.isInitialized()) return "SDD not initialized."
2084
+ const graph = repo.loadGraph()
2085
+
2086
+ const snapshot = createSnapshot(graph, args.change_id, ctx.directory)
2087
+ return `Snapshot created: ${snapshot.id} for change ${args.change_id}`
2088
+ },
2089
+ }),
2090
+
2091
+ "sdd.rollback": tool({
2092
+ description: "Rollback a change using git revert, snapshot restore, or backup restore (tries all methods).",
2093
+ args: {
2094
+ change_id: tool.schema.string().describe("Change ID to rollback"),
2095
+ },
2096
+ async execute(args, ctx) {
2097
+ const repo = getRepo(ctx.directory)
2098
+ if (!repo.isInitialized()) return "SDD not initialized."
2099
+ const graph = repo.loadGraph()
2100
+
2101
+ const result = executeRollback(graph, args.change_id, ctx.directory)
2102
+ if (result.success) {
2103
+ repo.saveGraph(graph)
2104
+ invalidateCacheForMutation(ctx.directory, ["change"], ["rollback"])
2105
+ }
2106
+ return formatRollbackResult(result)
2107
+ },
2108
+ }),
2109
+
2110
+ "sdd.rollback_history": tool({
2111
+ description: "View the history of rollback operations.",
2112
+ args: {},
2113
+ async execute(_args, ctx) {
2114
+ const history = loadRollbackHistory(ctx.directory)
2115
+ return formatRollbackHistory(history)
2116
+ },
2117
+ }),
2118
+
2119
+ "sdd.set_role": tool({
2120
+ description: "Set a user's role (admin, architect, developer, viewer).",
2121
+ args: {
2122
+ user: tool.schema.string().describe("Username"),
2123
+ role: tool.schema.enum(["admin", "architect", "developer", "viewer"]).describe("Role to assign"),
2124
+ },
2125
+ async execute(args, ctx) {
2126
+ setRole(ctx.directory, args.user, args.role as "admin" | "architect" | "developer" | "viewer")
2127
+ return `Role set: ${args.user} → ${args.role}`
2128
+ },
2129
+ }),
2130
+
2131
+ "sdd.check_permission": tool({
2132
+ description: "Check if a user has a specific permission.",
2133
+ args: {
2134
+ user: tool.schema.string().describe("Username"),
2135
+ permission: tool.schema.string().describe("Permission to check"),
2136
+ },
2137
+ async execute(args, ctx) {
2138
+ const role = getUserRoleWithAuth(ctx.directory, args.user)
2139
+ const allowed = checkPermission(role, args.permission as any, ctx.directory)
2140
+ addAuditEntry(ctx.directory, args.user, "check_permission", args.permission, allowed ? "allowed" : "denied")
2141
+ return formatPermissionCheck(args.user, role, args.permission as any, allowed)
2142
+ },
2143
+ }),
2144
+
2145
+ "sdd.audit_log": tool({
2146
+ description: "View the audit log of permission checks and actions.",
2147
+ args: {
2148
+ user: tool.schema.string().optional().describe("Filter by user"),
2149
+ limit: tool.schema.number().optional().describe("Limit entries"),
2150
+ },
2151
+ async execute(args, ctx) {
2152
+ const entries = getAuditLog(ctx.directory, {
2153
+ user: args.user,
2154
+ limit: args.limit || 20,
2155
+ })
2156
+ return formatAuditLog(entries)
2157
+ },
2158
+ }),
2159
+
2160
+ "sdd.remote_status": tool({
2161
+ description: "Check the status of remote GitHub/GitLab integration for authentication.",
2162
+ args: {},
2163
+ async execute(_args, ctx) {
2164
+ const remote = detectRemote(ctx.directory)
2165
+ return formatRemoteStatus(remote)
2166
+ },
2167
+ }),
2168
+
2169
+ "sdd.bug_fix": tool({
2170
+ description: "Workflow completo de bug fix com aprovação automática.",
2171
+ args: {
2172
+ description: tool.schema.string().describe("Descrição do bug"),
2173
+ files: tool.schema.array(tool.schema.string()).describe("Arquivos afetados"),
2174
+ severity: tool.schema.enum(["critical", "high", "medium", "low"]).describe("Severidade do bug"),
2175
+ },
2176
+ async execute(args, ctx) {
2177
+ const graph = loadOrEmpty(ctx.directory)
2178
+ const { createBugFixChange, getBugFixInstructions } = await import("../sdd/workflows/bug-fix.js")
2179
+ const { change, bugFix } = createBugFixChange(graph, {
2180
+ id: `bugfix-${Date.now()}`,
2181
+ description: args.description,
2182
+ files: args.files,
2183
+ severity: args.severity as any,
2184
+ })
2185
+
2186
+ const repo = getRepo(ctx.directory)
2187
+ if (repo.isInitialized()) {
2188
+ const graph = repo.loadGraph()
2189
+ graph.nodes.push(change, bugFix)
2190
+ repo.saveGraph(graph)
2191
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
2192
+ }
2193
+
2194
+ return getBugFixInstructions(bugFix)
2195
+ },
2196
+ }),
2197
+
2198
+ "sdd.hotfix": tool({
2199
+ description: "Documentar hotfix retroativamente (post-hoc).",
2200
+ args: {
2201
+ description: tool.schema.string().describe("Descrição do incidente"),
2202
+ files: tool.schema.array(tool.schema.string()).describe("Arquivos modificados"),
2203
+ urgency: tool.schema.enum(["critical", "high", "medium"]).describe("Urgência"),
2204
+ },
2205
+ async execute(args, ctx) {
2206
+ const graph = loadOrEmpty(ctx.directory)
2207
+ const { createHotfixChange, getHotfixInstructions } = await import("../sdd/workflows/hotfix.js")
2208
+ const { change, hotfix } = createHotfixChange(graph, {
2209
+ id: `hotfix-${Date.now()}`,
2210
+ description: args.description,
2211
+ files: args.files,
2212
+ urgency: args.urgency as any,
2213
+ })
2214
+
2215
+ hotfix.metadata.fix_applied = true
2216
+ hotfix.metadata.post_hoc_documented = true
2217
+ change.status = "COMPLETED"
2218
+
2219
+ const repo = getRepo(ctx.directory)
2220
+ if (repo.isInitialized()) {
2221
+ const graph = repo.loadGraph()
2222
+ graph.nodes.push(change, hotfix)
2223
+ repo.saveGraph(graph)
2224
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
2225
+ }
2226
+
2227
+ return getHotfixInstructions(hotfix)
2228
+ },
2229
+ }),
2230
+
2231
+ "sdd.refactoring": tool({
2232
+ description: "Workflow de refactoring com verificação de dependências.",
2233
+ args: {
2234
+ target: tool.schema.string().describe("Módulo alvo"),
2235
+ description: tool.schema.string().describe("Descrição do refactoring"),
2236
+ type: tool.schema.enum(["extract", "rename", "move", "simplify", "restructure"]).describe("Tipo de refactoring"),
2237
+ files: tool.schema.array(tool.schema.string()).describe("Arquivos envolvidos"),
2238
+ },
2239
+ async execute(args, ctx) {
2240
+ const graph = loadOrEmpty(ctx.directory)
2241
+ const { createRefactoringChange, getRefactoringInstructions } = await import("../sdd/workflows/refactoring.js")
2242
+ const { change, refactoring } = createRefactoringChange(graph, {
2243
+ id: `refactor-${Date.now()}`,
2244
+ target_module: args.target,
2245
+ description: args.description,
2246
+ refactoring_type: args.type as any,
2247
+ files: args.files,
2248
+ })
2249
+
2250
+ const repo = getRepo(ctx.directory)
2251
+ if (repo.isInitialized()) {
2252
+ const graph = repo.loadGraph()
2253
+ graph.nodes.push(change, refactoring)
2254
+ repo.saveGraph(graph)
2255
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
2256
+ }
2257
+
2258
+ return getRefactoringInstructions(refactoring)
2259
+ },
2260
+ }),
2261
+
2262
+ "sdd.deprecate": tool({
2263
+ description: "Deprecar feature com plano de migração.",
2264
+ args: {
2265
+ target: tool.schema.string().describe("Feature a deprecar"),
2266
+ removal_date: tool.schema.string().describe("Data de remoção (YYYY-MM-DD)"),
2267
+ migration_guide: tool.schema.string().optional().describe("Guia de migração"),
2268
+ endpoints: tool.schema.array(tool.schema.string()).describe("Endpoints afetados"),
2269
+ },
2270
+ async execute(args, ctx) {
2271
+ const graph = loadOrEmpty(ctx.directory)
2272
+ const { createDeprecationChange, getDeprecationInstructions } = await import("../sdd/workflows/deprecation.js")
2273
+ const { change, deprecation } = createDeprecationChange(graph, {
2274
+ id: `deprecate-${Date.now()}`,
2275
+ target_feature: args.target,
2276
+ description: `Deprecating ${args.target}`,
2277
+ removal_date: args.removal_date,
2278
+ migration_guide: args.migration_guide,
2279
+ affected_endpoints: args.endpoints,
2280
+ })
2281
+
2282
+ const repo = getRepo(ctx.directory)
2283
+ if (repo.isInitialized()) {
2284
+ const graph = repo.loadGraph()
2285
+ graph.nodes.push(change, deprecation)
2286
+ repo.saveGraph(graph)
2287
+ invalidateCacheForMutation(ctx.directory, ["change", "feature"], ["deprecated"])
2288
+ }
2289
+
2290
+ return getDeprecationInstructions(deprecation)
2291
+ },
2292
+ }),
2293
+
2294
+ "sdd.create_migration": tool({
2295
+ description: "Criar migração de dados com rollback.",
2296
+ args: {
2297
+ source: tool.schema.string().describe("Schema de origem"),
2298
+ target: tool.schema.string().describe("Schema alvo"),
2299
+ description: tool.schema.string().describe("Descrição da migração"),
2300
+ transformations: tool.schema.array(tool.schema.string()).optional().describe("Transformações de dados"),
2301
+ },
2302
+ async execute(args, ctx) {
2303
+ const graph = loadOrEmpty(ctx.directory)
2304
+ const { createMigrationChange, getMigrationInstructions } = await import("../sdd/workflows/data-migration.js")
2305
+ const { change, migration } = createMigrationChange(graph, {
2306
+ id: `migration-${Date.now()}`,
2307
+ source_schema: args.source,
2308
+ target_schema: args.target,
2309
+ description: args.description,
2310
+ data_transformations: args.transformations,
2311
+ })
2312
+
2313
+ const repo = getRepo(ctx.directory)
2314
+ if (repo.isInitialized()) {
2315
+ const graph = repo.loadGraph()
2316
+ graph.nodes.push(change, migration)
2317
+ repo.saveGraph(graph)
2318
+ invalidateCacheForMutation(ctx.directory, ["change", "entity"], [])
2319
+ }
2320
+
2321
+ return getMigrationInstructions(migration)
2322
+ },
2323
+ }),
2324
+
2325
+ "sdd.create_experiment": tool({
2326
+ description: "Criar experimento A/B.",
2327
+ args: {
2328
+ hypothesis: tool.schema.string().describe("Hipótese do experimento"),
2329
+ variants: tool.schema.array(tool.schema.object({
2330
+ name: tool.schema.string(),
2331
+ description: tool.schema.string(),
2332
+ traffic_percentage: tool.schema.number(),
2333
+ })).describe("Variantes do experimento"),
2334
+ metric: tool.schema.string().describe("Métrica primária"),
2335
+ duration: tool.schema.number().describe("Duração em dias"),
2336
+ },
2337
+ async execute(args, ctx) {
2338
+ const graph = loadOrEmpty(ctx.directory)
2339
+ const { createExperimentChange, getExperimentInstructions } = await import("../sdd/workflows/ab-testing.js")
2340
+ const { change, experiment } = createExperimentChange(graph, {
2341
+ id: `experiment-${Date.now()}`,
2342
+ hypothesis: args.hypothesis,
2343
+ variants: args.variants as any,
2344
+ primary_metric: args.metric,
2345
+ duration_days: args.duration,
2346
+ })
2347
+
2348
+ const repo = getRepo(ctx.directory)
2349
+ if (repo.isInitialized()) {
2350
+ const graph = repo.loadGraph()
2351
+ graph.nodes.push(change, experiment)
2352
+ repo.saveGraph(graph)
2353
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
2354
+ }
2355
+
2356
+ return getExperimentInstructions(experiment)
2357
+ },
2358
+ }),
2359
+
2360
+ "sdd.create_flag": tool({
2361
+ description: "Criar feature flag.",
2362
+ args: {
2363
+ name: tool.schema.string().describe("Nome da flag"),
2364
+ description: tool.schema.string().describe("Descrição da flag"),
2365
+ rollout: tool.schema.number().describe("Porcentagem de rollout (0-100)"),
2366
+ audience: tool.schema.string().optional().describe("Público-alvo"),
2367
+ },
2368
+ async execute(args, ctx) {
2369
+ const graph = loadOrEmpty(ctx.directory)
2370
+ const { createFeatureFlagChange, getFeatureFlagInstructions } = await import("../sdd/workflows/feature-flags.js")
2371
+ const { change, featureFlag } = createFeatureFlagChange(graph, {
2372
+ id: `flag-${Date.now()}`,
2373
+ flag_name: args.name,
2374
+ description: args.description,
2375
+ rollout_percentage: args.rollout,
2376
+ target_audience: args.audience,
2377
+ })
2378
+
2379
+ const repo = getRepo(ctx.directory)
2380
+ if (repo.isInitialized()) {
2381
+ const graph = repo.loadGraph()
2382
+ graph.nodes.push(change, featureFlag)
2383
+ repo.saveGraph(graph)
2384
+ invalidateCacheForMutation(ctx.directory, ["change", "feature"], [])
2385
+ }
2386
+
2387
+ return getFeatureFlagInstructions(featureFlag)
2388
+ },
2389
+ }),
2390
+
2391
+ "sdd.create_tenant": tool({
2392
+ description: "Adicionar multi-tenancy ao sistema.",
2393
+ args: {
2394
+ name: tool.schema.string().describe("Nome do tenant"),
2395
+ type: tool.schema.enum(["shared_database", "dedicated_database", "shared_schema"]).describe("Tipo de tenant"),
2396
+ isolation: tool.schema.enum(["row", "schema", "database"]).describe("Nível de isolamento"),
2397
+ },
2398
+ async execute(args, ctx) {
2399
+ const graph = loadOrEmpty(ctx.directory)
2400
+ const { createTenantChange, getTenantInstructions } = await import("../sdd/workflows/multi-tenancy.js")
2401
+ const { change, tenant } = createTenantChange(graph, {
2402
+ id: `tenant-${Date.now()}`,
2403
+ tenant_name: args.name,
2404
+ tenant_type: args.type as any,
2405
+ isolation_level: args.isolation as any,
2406
+ })
2407
+
2408
+ const repo = getRepo(ctx.directory)
2409
+ if (repo.isInitialized()) {
2410
+ const graph = repo.loadGraph()
2411
+ graph.nodes.push(change, tenant)
2412
+ repo.saveGraph(graph)
2413
+ invalidateCacheForMutation(ctx.directory, ["change", "entity"], [])
2414
+ }
2415
+
2416
+ return getTenantInstructions(tenant)
2417
+ },
2418
+ }),
2419
+
2420
+ "sdd.onboard_developer": tool({
2421
+ description: "Gerar guia de onboarding para novo desenvolvedor.",
2422
+ args: {
2423
+ developer_name: tool.schema.string().optional().describe("Nome do desenvolvedor"),
2424
+ },
2425
+ async execute(args, ctx) {
2426
+ const graph = loadOrEmpty(ctx.directory)
2427
+ const { generateOnboardingGuide } = await import("../sdd/workflows/onboarding.js")
2428
+ return generateOnboardingGuide(graph, {
2429
+ id: `onboarding-${Date.now()}`,
2430
+ developer_name: args.developer_name,
2431
+ })
2432
+ },
2433
+ }),
2434
+
2435
+ "sdd.security_audit": tool({
2436
+ description: "Realizar auditoria de segurança do projeto.",
2437
+ args: {},
2438
+ async execute(_args, ctx) {
2439
+ const graph = loadOrEmpty(ctx.directory)
2440
+ const { performSecurityAudit, formatSecurityAudit } = await import("../sdd/analysis/security.js")
2441
+ const result = performSecurityAudit(graph)
2442
+ return formatSecurityAudit(result)
2443
+ },
2444
+ }),
2445
+
2446
+ "sdd.analyze_scalability": tool({
2447
+ description: "Analisar escalabilidade do projeto.",
2448
+ args: {},
2449
+ async execute(_args, ctx) {
2450
+ const graph = loadOrEmpty(ctx.directory)
2451
+ const { analyzeScalability, formatScalabilityAnalysis } = await import("../sdd/analysis/scalability.js")
2452
+ const result = analyzeScalability(graph)
2453
+ return formatScalabilityAnalysis(result)
2454
+ },
2455
+ }),
2456
+
2457
+ "sdd.check_compliance": tool({
2458
+ description: "Verificar compliance com padrão regulatory.",
2459
+ args: {
2460
+ standard: tool.schema.enum(["GDPR", "LGPD", "HIPAA", "SOC2", "PCI_DSS", "ISO27001"]).describe("Padrão de compliance"),
2461
+ },
2462
+ async execute(args, ctx) {
2463
+ const graph = loadOrEmpty(ctx.directory)
2464
+ const { checkCompliance, formatComplianceCheck } = await import("../sdd/analysis/compliance.js")
2465
+ const result = checkCompliance(graph, args.standard)
2466
+ return formatComplianceCheck(result)
2467
+ },
2468
+ }),
2469
+
2470
+ "sdd.setup_monitoring": tool({
2471
+ description: "Configurar monitoramento do projeto.",
2472
+ args: {},
2473
+ async execute(_args, ctx) {
2474
+ const graph = loadOrEmpty(ctx.directory)
2475
+ const { setupMonitoring, formatMonitoringSetup } = await import("../sdd/monitoring/setup.js")
2476
+ const result = setupMonitoring(graph)
2477
+ return formatMonitoringSetup(result)
2478
+ },
2479
+ }),
2480
+
2481
+ "sdd.generate_dashboard": tool({
2482
+ description: "Gerar configuração de dashboard de monitoramento.",
2483
+ args: {
2484
+ type: tool.schema.enum(["overview", "api", "database", "security"]).describe("Tipo de dashboard"),
2485
+ },
2486
+ async execute(args, ctx) {
2487
+ const graph = loadOrEmpty(ctx.directory)
2488
+ const { generateDashboardConfig, formatDashboardConfig } = await import("../sdd/monitoring/setup.js")
2489
+ const config = generateDashboardConfig(graph, args.type as any)
2490
+ return formatDashboardConfig(config)
2491
+ },
2492
+ }),
2493
+
2494
+ "sdd.report_incident": tool({
2495
+ description: "Reportar incidente.",
2496
+ args: {
2497
+ title: tool.schema.string().describe("Título do incidente"),
2498
+ severity: tool.schema.enum(["SEV1", "SEV2", "SEV3", "SEV4"]).describe("Severidade"),
2499
+ impact: tool.schema.string().describe("Impacto do incidente"),
2500
+ },
2501
+ async execute(args, ctx) {
2502
+ const graph = loadOrEmpty(ctx.directory)
2503
+ const { createIncident, getIncidentInstructions } = await import("../sdd/incidents/manager.js")
2504
+ const incident = createIncident(graph, {
2505
+ id: `incident-${Date.now()}`,
2506
+ title: args.title,
2507
+ severity: args.severity as any,
2508
+ impact: args.impact,
2509
+ })
2510
+
2511
+ const repo = getRepo(ctx.directory)
2512
+ if (repo.isInitialized()) {
2513
+ const graph = repo.loadGraph()
2514
+ graph.nodes.push(incident)
2515
+ repo.saveGraph(graph)
2516
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
2517
+ }
2518
+
2519
+ return getIncidentInstructions(incident)
2520
+ },
2521
+ }),
2522
+
2523
+ "sdd.create_sla": tool({
2524
+ description: "Criar SLA (Acordo de Nível de Serviço).",
2525
+ args: {
2526
+ name: tool.schema.string().describe("Nome do SLA"),
2527
+ metric: tool.schema.string().describe("Métrica a ser monitorada"),
2528
+ target: tool.schema.number().describe("Meta desejada"),
2529
+ period: tool.schema.string().describe("Período de medição"),
2530
+ },
2531
+ async execute(args, ctx) {
2532
+ const graph = loadOrEmpty(ctx.directory)
2533
+ const { createSLA, getSLAInstructions } = await import("../sdd/sla/tracker.js")
2534
+ const sla = createSLA(graph, {
2535
+ id: `sla-${Date.now()}`,
2536
+ name: args.name,
2537
+ metric: args.metric,
2538
+ target: args.target,
2539
+ period: args.period,
2540
+ })
2541
+
2542
+ const repo = getRepo(ctx.directory)
2543
+ if (repo.isInitialized()) {
2544
+ const graph = repo.loadGraph()
2545
+ graph.nodes.push(sla)
2546
+ repo.saveGraph(graph)
2547
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
2548
+ }
2549
+
2550
+ return getSLAInstructions(sla)
2551
+ },
2552
+ }),
2553
+
2554
+ "sdd.estimate_cost": tool({
2555
+ description: "Estimar custos do projeto.",
2556
+ args: {},
2557
+ async execute(_args, ctx) {
2558
+ const graph = loadOrEmpty(ctx.directory)
2559
+ const { estimateCost, formatCostEstimate } = await import("../sdd/cost/estimator.js")
2560
+ const result = estimateCost(graph)
2561
+ return formatCostEstimate(result)
2562
+ },
2563
+ }),
2564
+
2565
+ "sdd.generate_docs": tool({
2566
+ description: "Gerar documentação do projeto.",
2567
+ args: {
2568
+ type: tool.schema.enum(["api", "user_guide", "developer_guide", "architecture"]).describe("Tipo de documentação"),
2569
+ language: tool.schema.string().optional().describe("Idioma da documentação"),
2570
+ },
2571
+ async execute(args, ctx) {
2572
+ const graph = loadOrEmpty(ctx.directory)
2573
+ const { generateDocumentation } = await import("../sdd/documentation/generator.js")
2574
+ return generateDocumentation(graph, {
2575
+ type: args.type as any,
2576
+ language: args.language,
2577
+ })
2578
+ },
2579
+ }),
2580
+
2581
+ "sdd.knowledge_transfer": tool({
2582
+ description: "Gerar documento de transferência de conhecimento.",
2583
+ args: {},
2584
+ async execute(_args, ctx) {
2585
+ const graph = loadOrEmpty(ctx.directory)
2586
+ const { generateKnowledgeTransfer, formatKnowledgeTransfer } = await import("../sdd/knowledge/transfer.js")
2587
+ const data = generateKnowledgeTransfer(graph)
2588
+ return formatKnowledgeTransfer(data)
2589
+ },
2590
+ }),
2591
+
2592
+ "sdd.disaster_recovery_plan": tool({
2593
+ description: "Criar plano de disaster recovery.",
2594
+ args: {},
2595
+ async execute(_args, ctx) {
2596
+ const graph = loadOrEmpty(ctx.directory)
2597
+ const { generateDisasterRecoveryPlan, formatDisasterRecoveryPlan } = await import("../sdd/disaster/recovery.js")
2598
+ const plan = generateDisasterRecoveryPlan(graph)
2599
+ return formatDisasterRecoveryPlan(plan)
2600
+ },
2601
+ }),
2602
+
2603
+ "sdd.analyze_complexity": tool({
2604
+ description: "Analisar complexidade ciclomática e cognitiva do código.",
2605
+ args: {
2606
+ file: tool.schema.string().describe("Caminho do arquivo para analisar"),
2607
+ },
2608
+ async execute(args, ctx) {
2609
+ const { readFileSync } = await import("fs")
2610
+ const { join } = await import("path")
2611
+ const filePath = join(ctx.directory, args.file)
2612
+ const code = readFileSync(filePath, "utf-8")
2613
+ const { analyzeComplexity, formatComplexityReport } = await import("../sdd/code-quality/complexity.js")
2614
+ const report = analyzeComplexity(code, args.file)
2615
+ return formatComplexityReport(report)
2616
+ },
2617
+ }),
2618
+
2619
+ "sdd.code_metrics": tool({
2620
+ description: "Calcular métricas de código (linhas, aninhamento, parâmetros).",
2621
+ args: {
2622
+ file: tool.schema.string().describe("Caminho do arquivo para analisar"),
2623
+ },
2624
+ async execute(args, ctx) {
2625
+ const { readFileSync } = await import("fs")
2626
+ const { join } = await import("path")
2627
+ const filePath = join(ctx.directory, args.file)
2628
+ const code = readFileSync(filePath, "utf-8")
2629
+ const { analyzeMetrics, formatMetricsReport } = await import("../sdd/code-quality/metrics.js")
2630
+ const report = analyzeMetrics(code, args.file)
2631
+ return formatMetricsReport(report)
2632
+ },
2633
+ }),
2634
+
2635
+ "sdd.detect_smells": tool({
2636
+ description: "Detectar code smells (God Class, Feature Envy, Switch Statements, etc).",
2637
+ args: {
2638
+ file: tool.schema.string().describe("Caminho do arquivo para analisar"),
2639
+ },
2640
+ async execute(args, ctx) {
2641
+ const { readFileSync } = await import("fs")
2642
+ const { join } = await import("path")
2643
+ const filePath = join(ctx.directory, args.file)
2644
+ const code = readFileSync(filePath, "utf-8")
2645
+ const { detectCodeSmells, formatCodeSmellReport } = await import("../sdd/code-quality/smells.js")
2646
+ const report = detectCodeSmells(code, args.file)
2647
+ return formatCodeSmellReport(report)
2648
+ },
2649
+ }),
2650
+
2651
+ "sdd.analyze_dependencies": tool({
2652
+ description: "Analisar dependências, detectar ciclos e medir acoplamento.",
2653
+ args: {},
2654
+ async execute(_args, ctx) {
2655
+ const graph = loadOrEmpty(ctx.directory)
2656
+ const { analyzeDependencies, formatDependencyReport } = await import("../sdd/code-quality/dependencies.js")
2657
+ const report = analyzeDependencies(graph)
2658
+ return formatDependencyReport(report)
2659
+ },
2660
+ }),
2661
+
2662
+ "sdd.verify_usage": tool({
2663
+ description: "Verificar se código é realmente usado/importado no projeto.",
2664
+ args: {},
2665
+ async execute(_args, ctx) {
2666
+ const graph = loadOrEmpty(ctx.directory)
2667
+ const { trackUsage, formatUsageReport } = await import("../sdd/code-quality/usage-tracker.js")
2668
+ const { readdirSync, readFileSync, statSync } = await import("fs")
2669
+ const { join } = await import("path")
2670
+
2671
+ const sourceFiles = new Map<string, string>()
2672
+ const sourceExtensions = [".ts", ".tsx", ".js", ".jsx"]
2673
+
2674
+ function scanDir(dir: string, relativePath: string = ""): void {
2675
+ let entries: string[]
2676
+ try {
2677
+ entries = readdirSync(dir)
2678
+ } catch {
2679
+ return
2680
+ }
2681
+
2682
+ for (const entry of entries) {
2683
+ if (["node_modules", ".sdd", "dist", "build", ".git", ".opencode"].includes(entry)) continue
2684
+
2685
+ const fullPath = join(dir, entry)
2686
+ const relPath = relativePath ? `${relativePath}/${entry}` : entry
2687
+
2688
+ try {
2689
+ const stat = statSync(fullPath)
2690
+ if (stat.isDirectory()) {
2691
+ scanDir(fullPath, relPath)
2692
+ } else if (sourceExtensions.some(ext => entry.endsWith(ext))) {
2693
+ try {
2694
+ const content = readFileSync(fullPath, "utf-8")
2695
+ sourceFiles.set(relPath, content)
2696
+ } catch {}
2697
+ }
2698
+ } catch {}
2699
+ }
2700
+ }
2701
+
2702
+ scanDir(ctx.directory)
2703
+ const report = trackUsage(graph, sourceFiles)
2704
+ return formatUsageReport(report)
2705
+ },
2706
+ }),
2707
+
2708
+ "sdd.find_dead_code": tool({
2709
+ description: "🔍 READ-ONLY: Detectar código não utilizado (imports não usados, funções mortas). NÃO deleta arquivos.",
2710
+ args: {
2711
+ file: tool.schema.string().describe("Caminho do arquivo para analisar"),
2712
+ },
2713
+ async execute(args, ctx) {
2714
+ const { readFileSync } = await import("fs")
2715
+ const { join } = await import("path")
2716
+ const filePath = join(ctx.directory, args.file)
2717
+ const code = readFileSync(filePath, "utf-8")
2718
+ const { analyzeImports, formatImportAnalysis } = await import("../sdd/code-quality/import-analyzer.js")
2719
+ const analysis = analyzeImports(code, args.file)
2720
+ return formatImportAnalysis(analysis)
2721
+ },
2722
+ }),
2723
+
2724
+ "sdd.remove_dead_code": tool({
2725
+ description: "Remover código morto com aprovação SDD (CRIA ChangeNode, NÃO deleta diretamente).",
2726
+ args: {
2727
+ file: tool.schema.string().describe("Caminho do arquivo para limpar"),
2728
+ dry_run: tool.schema.boolean().optional().describe("Apenas simular (não deletar)"),
2729
+ },
2730
+ async execute(args, ctx) {
2731
+ const { readFileSync, existsSync } = await import("fs")
2732
+ const { join } = await import("path")
2733
+ const filePath = join(ctx.directory, args.file)
2734
+
2735
+ if (!existsSync(filePath)) {
2736
+ return `❌ Arquivo não encontrado: ${args.file}`
2737
+ }
2738
+
2739
+ const code = readFileSync(filePath, "utf-8")
2740
+ const { analyzeImports } = await import("../sdd/code-quality/import-analyzer.js")
2741
+ const analysis = analyzeImports(code, args.file)
2742
+
2743
+ const unusedImports = analysis.issues.filter(i => i.type === 'unused_import')
2744
+
2745
+ if (unusedImports.length === 0) {
2746
+ return `✅ Nenhum import não utilizado encontrado em ${args.file}`
2747
+ }
2748
+
2749
+ if (args.dry_run) {
2750
+ return [
2751
+ `🔍 **Dry Run** - Nenhuma alteração será feita`,
2752
+ "",
2753
+ `**Arquivo:** ${args.file}`,
2754
+ `**Imports não utilizados:** ${unusedImports.length}`,
2755
+ "",
2756
+ "### Imports para remover:",
2757
+ ...unusedImports.map(i => `- Linha ${i.line}: ${i.message}`),
2758
+ "",
2759
+ "Execute sem dry_run para criar o ChangeNode e solicitar aprovação.",
2760
+ ].join("\n")
2761
+ }
2762
+
2763
+ const graph = loadOrEmpty(ctx.directory)
2764
+
2765
+ const changeNodeId = `change-remove-dead-${Date.now()}`
2766
+ const affectedNodes: string[] = []
2767
+
2768
+ const fileNodeId = graph.nodes.find(n =>
2769
+ n.type === "file" && (n.metadata as any).path === args.file
2770
+ )?.id
2771
+
2772
+ if (fileNodeId) {
2773
+ affectedNodes.push(fileNodeId)
2774
+ }
2775
+
2776
+ const changeNode = {
2777
+ id: changeNodeId,
2778
+ type: "change" as const,
2779
+ name: `Remover código morto de ${args.file}`,
2780
+ status: "PROPOSED" as const,
2781
+ version: 1,
2782
+ created_at: new Date().toISOString(),
2783
+ updated_at: new Date().toISOString(),
2784
+ metadata: {
2785
+ title: `Remover código morto de ${args.file}`,
2786
+ reason: `${unusedImports.length} imports não utilizados detectados`,
2787
+ approval_level: "REVIEW" as const,
2788
+ affected_nodes: affectedNodes,
2789
+ affected_relationships: [],
2790
+ new_nodes: [],
2791
+ removed_nodes: [],
2792
+ modified_nodes: [],
2793
+ affected_files: [args.file],
2794
+ affected_tests: [],
2795
+ implementation_tasks: [],
2796
+ origin: "dead_code_detection",
2797
+ },
2798
+ }
2799
+
2800
+ graph.nodes.push(changeNode)
2801
+
2802
+ const repo = createRepository(ctx.directory)
2803
+ if (repo.isInitialized()) {
2804
+ repo.saveGraph(graph)
2805
+ invalidateCacheForMutation(ctx.directory, ["change"], [])
2806
+ }
2807
+
2808
+ return [
2809
+ `📋 **ChangeNode criado para remoção de código morto**`,
2810
+ "",
2811
+ `**ID:** ${changeNodeId}`,
2812
+ `**Arquivo:** ${args.file}`,
2813
+ `**Imports para remover:** ${unusedImports.length}`,
2814
+ "",
2815
+ "### Próximos passos:",
2816
+ "1. Use `sdd.approve_change` para aprovar a remoção",
2817
+ "2. Após aprovação, o hook permitirá a edição do arquivo",
2818
+ "3. Remova manualmente os imports listados acima",
2819
+ "",
2820
+ "⚠️ **NENHUMA alteração foi feita ainda.** Aguarde aprovação.",
2821
+ ].join("\n")
2822
+ },
2823
+ }),
2824
+
2825
+ "sdd.parse_symbols": tool({
2826
+ description: "Extrair símbolos (funções, classes, interfaces) do código fonte.",
2827
+ args: {
2828
+ file: tool.schema.string().describe("Caminho do arquivo para analisar"),
2829
+ },
2830
+ async execute(args, ctx) {
2831
+ const { readFileSync } = await import("fs")
2832
+ const { join } = await import("path")
2833
+ const filePath = join(ctx.directory, args.file)
2834
+ const code = readFileSync(filePath, "utf-8")
2835
+ const { parseSymbols, formatSymbolParseResult } = await import("../sdd/code-quality/symbol-parser.js")
2836
+ const result = parseSymbols(code, args.file)
2837
+ return formatSymbolParseResult(result)
2838
+ },
2839
+ }),
2840
+
2841
+ "sdd.plan_implementation": tool({
2842
+ description: "Planejar implementação conectando código a spec nodes (feature/entity/component).",
2843
+ args: {
2844
+ feature_id: tool.schema.string().describe("ID da feature ou entity no grafo"),
2845
+ files: tool.schema.string().describe("Lista de arquivos separados por vírgula"),
2846
+ },
2847
+ async execute(args, ctx) {
2848
+ const repo2 = getRepo(ctx.directory)
2849
+ const graph = loadOrEmpty(ctx.directory)
2850
+ const indices = repo2.isInitialized() ? repo2.getIndices() : null
2851
+ const { parseSymbols, convertToSymbolNodes } = await import("../sdd/code-quality/symbol-parser.js")
2852
+ const { readFileSync } = await import("fs")
2853
+ const { join } = await import("path")
2854
+
2855
+ const fileList = args.files.split(",").map(f => f.trim())
2856
+ const featureNode = indices ? indices.byId.get(args.feature_id) : graph.nodes.find(n => n.id === args.feature_id)
2857
+
2858
+ if (!featureNode) {
2859
+ return `❌ Feature/Entity não encontrada: ${args.feature_id}\n\nCrie o nó primeiro com sdd.discover ou sdd.update_from_answers.`
2860
+ }
2861
+
2862
+ const newNodes: string[] = []
2863
+ const newRelationships: Array<{id: string, from: string, to: string, type: string, metadata: Record<string, unknown>}> = []
2864
+
2865
+ for (const file of fileList) {
2866
+ const fullPath = join(ctx.directory, file)
2867
+ let code: string
2868
+ try {
2869
+ code = readFileSync(fullPath, "utf-8")
2870
+ } catch {
2871
+ continue
2872
+ }
2873
+
2874
+ const fileNodeId = `file-${file.replace(/[^a-zA-Z0-9]/g, "-")}`
2875
+ const fileNode = {
2876
+ id: fileNodeId,
2877
+ type: "file" as const,
2878
+ name: file,
2879
+ status: "DRAFT" as const,
2880
+ version: 1,
2881
+ created_at: new Date().toISOString(),
2882
+ updated_at: new Date().toISOString(),
2883
+ metadata: { path: file },
2884
+ }
2885
+
2886
+ graph.nodes.push(fileNode)
2887
+ newNodes.push(fileNodeId)
2888
+
2889
+ newRelationships.push({
2890
+ id: `rel-${fileNodeId}-${args.feature_id}`,
2891
+ from: args.feature_id,
2892
+ to: fileNodeId,
2893
+ type: "implements" as const,
2894
+ metadata: {},
2895
+ })
2896
+
2897
+ const symbolResult = parseSymbols(code, file)
2898
+ const symbolNodes = convertToSymbolNodes(symbolResult)
2899
+
2900
+ for (const symNode of symbolNodes) {
2901
+ graph.nodes.push(symNode)
2902
+ newNodes.push(symNode.id)
2903
+
2904
+ newRelationships.push({
2905
+ id: `rel-${fileNodeId}-${symNode.id}`,
2906
+ from: fileNodeId,
2907
+ to: symNode.id,
2908
+ type: "contains" as const,
2909
+ metadata: {},
2910
+ })
2911
+
2912
+ newRelationships.push({
2913
+ id: `rel-${args.feature_id}-${symNode.id}`,
2914
+ from: args.feature_id,
2915
+ to: symNode.id,
2916
+ type: "defines" as const,
2917
+ metadata: {},
2918
+ })
2919
+ }
2920
+ }
2921
+
2922
+ for (const rel of newRelationships) {
2923
+ graph.relationships.push(rel as any)
2924
+ }
2925
+
2926
+ const repo = createRepository(ctx.directory)
2927
+ if (repo.isInitialized()) {
2928
+ repo.saveGraph(graph)
2929
+ invalidateCacheForMutation(ctx.directory, ["file", "symbol"], ["implements", "contains", "defines"])
2930
+ }
2931
+
2932
+ return [
2933
+ `✅ Implementação planejada para: ${featureNode.name}`,
2934
+ "",
2935
+ `**Arquivos criados no grafo:** ${fileList.length}`,
2936
+ `**Símbolos extraídos:** ${newNodes.length - fileList.length}`,
2937
+ `**Relacionamentos criados:** ${newRelationships.length}`,
2938
+ "",
2939
+ "### Próximos passos",
2940
+ "1. Use sdd.verify_usage para garantir que o código é usado",
2941
+ "2. Use sdd.find_dead_code para detectar imports não utilizados",
2942
+ "3. Use sdd.analyze_dependencies para verificar acoplamento",
2943
+ ].join("\n")
2944
+ },
2945
+ }),
2946
+
2947
+ "sdd.update_node": tool({
2948
+ description: "Update an existing node in the SDD Knowledge Graph. Updates fields and increments version.",
2949
+ args: {
2950
+ node_id: tool.schema.string().describe("Node ID to update"),
2951
+ name: tool.schema.string().optional().describe("New name"),
2952
+ description: tool.schema.string().optional().describe("New description"),
2953
+ status: tool.schema.string().optional().describe("New status (DRAFT, APPROVED, IMPLEMENTING, IMPLEMENTED, BLOCKED)"),
2954
+ metadata_json: tool.schema.string().optional().describe("Additional metadata as JSON string"),
2955
+ },
2956
+ async execute(args, ctx) {
2957
+ const repo = getRepo(ctx.directory)
2958
+ if (!repo.isInitialized()) return "SDD not initialized."
2959
+ const graph = repo.loadGraph()
2960
+
2961
+ const updates: Partial<AnyNode> = {}
2962
+ if (args.name) updates.name = args.name
2963
+ if (args.description !== undefined) updates.description = args.description
2964
+ if (args.status) updates.status = args.status as AnyNode["status"]
2965
+ if (args.metadata_json) {
2966
+ try {
2967
+ updates.metadata = { ...getNode(graph, args.node_id)?.metadata, ...JSON.parse(args.metadata_json) }
2968
+ } catch {
2969
+ return "Error: Invalid JSON in metadata_json"
2970
+ }
2971
+ }
2972
+
2973
+ try {
2974
+ const updated = updateNode(graph, args.node_id, updates)
2975
+ repo.saveGraph(graph)
2976
+ invalidateCacheForMutation(ctx.directory, [updated.type], [])
2977
+ return `Node updated: **${updated.id}** (${updated.type}): ${updated.name} [v${updated.version}]`
2978
+ } catch (e) {
2979
+ return `Error: ${e instanceof Error ? e.message : String(e)}`
2980
+ }
2981
+ },
2982
+ }),
2983
+
2984
+ "sdd.remove_node": tool({
2985
+ description: "Remove a node and all its relationships from the SDD Knowledge Graph.",
2986
+ args: {
2987
+ node_id: tool.schema.string().describe("Node ID to remove"),
2988
+ },
2989
+ async execute(args, ctx) {
2990
+ const repo = getRepo(ctx.directory)
2991
+ if (!repo.isInitialized()) return "SDD not initialized."
2992
+ const graph = repo.loadGraph()
2993
+
2994
+ const node = getNode(graph, args.node_id)
2995
+ if (!node) return `Node ${args.node_id} not found.`
2996
+ const relCount = getRelationships(graph, args.node_id).length
2997
+
2998
+ try {
2999
+ removeNode(graph, args.node_id)
3000
+ repo.saveGraph(graph)
3001
+ invalidateCacheForMutation(ctx.directory, [node.type], [])
3002
+ return `Node removed: **${args.node_id}** (${node.type}): ${node.name}\nRemoved ${relCount} associated relationship(s).`
3003
+ } catch (e) {
3004
+ return `Error: ${e instanceof Error ? e.message : String(e)}`
3005
+ }
3006
+ },
3007
+ }),
3008
+
3009
+ "sdd.remove_relationship": tool({
3010
+ description: "Remove a specific relationship between two nodes.",
3011
+ args: {
3012
+ from_id: tool.schema.string().describe("Source node ID"),
3013
+ to_id: tool.schema.string().describe("Target node ID"),
3014
+ type: tool.schema.string().describe("Relationship type"),
3015
+ },
3016
+ async execute(args, ctx) {
3017
+ const repo = getRepo(ctx.directory)
3018
+ if (!repo.isInitialized()) return "SDD not initialized."
3019
+ const graph = repo.loadGraph()
3020
+
3021
+ removeRelationship(graph, args.from_id, args.to_id, args.type as any)
3022
+ repo.saveGraph(graph)
3023
+ invalidateCacheForMutation(ctx.directory, [], [args.type])
3024
+ return `Relationship removed: ${args.from_id} --[${args.type}]--> ${args.to_id}`
3025
+ },
3026
+ }),
3027
+
3028
+ "sdd.traverse_outgoing": tool({
3029
+ description: "Traverse the graph following outgoing edges from a node (BFS). Returns reachable nodes and distances.",
3030
+ args: {
3031
+ node_id: tool.schema.string().describe("Starting node ID"),
3032
+ max_depth: tool.schema.number().optional().describe("Maximum traversal depth (default: 5)"),
3033
+ },
3034
+ async execute(args, ctx) {
3035
+ const repo = getRepo(ctx.directory)
3036
+ if (!repo.isInitialized()) return "SDD not initialized."
3037
+ const graph = repo.loadGraph()
3038
+ const indices = repo.getIndices()
3039
+
3040
+ const node = getNodeIndexed(indices, args.node_id)
3041
+ if (!node) return `Node ${args.node_id} not found.`
3042
+
3043
+ const depth = args.max_depth || 5
3044
+ const result = bfsOutgoing(graph, args.node_id, { max_depth: depth, include_start: true })
3045
+
3046
+ const lines = [
3047
+ `## Outgoing Traverse: ${args.node_id}`,
3048
+ `**Node:** ${node.name} (${node.type})`,
3049
+ `**Max Depth:** ${depth}`,
3050
+ `**Reachable Nodes:** ${result.nodes.length}`,
3051
+ "",
3052
+ "### Nodes by Distance",
3053
+ ]
3054
+
3055
+ const maxShow = 30
3056
+ let shown = 0
3057
+ for (const [id, dist] of result.distances) {
3058
+ if (shown >= maxShow) {
3059
+ lines.push(`- ... and ${result.distances.size - maxShow} more`)
3060
+ break
3061
+ }
3062
+ const n = indices.byId.get(id)
3063
+ if (n) { lines.push(`- **${id}** (depth ${dist}): ${n.name} (${n.type})`); shown++ }
3064
+ }
3065
+
3066
+ return lines.join("\n")
3067
+ },
3068
+ }),
3069
+
3070
+ "sdd.traverse_incoming": tool({
3071
+ description: "Traverse the graph following incoming edges to a node (BFS). Finds what depends on this node.",
3072
+ args: {
3073
+ node_id: tool.schema.string().describe("Starting node ID"),
3074
+ max_depth: tool.schema.number().optional().describe("Maximum traversal depth (default: 5)"),
3075
+ },
3076
+ async execute(args, ctx) {
3077
+ const repo = getRepo(ctx.directory)
3078
+ if (!repo.isInitialized()) return "SDD not initialized."
3079
+ const graph = repo.loadGraph()
3080
+ const indices = repo.getIndices()
3081
+
3082
+ const node = getNodeIndexed(indices, args.node_id)
3083
+ if (!node) return `Node ${args.node_id} not found.`
3084
+
3085
+ const depth = args.max_depth || 5
3086
+ const result = bfsIncoming(graph, args.node_id, { max_depth: depth, include_start: true })
3087
+
3088
+ const lines = [
3089
+ `## Incoming Traverse: ${args.node_id}`,
3090
+ `**Node:** ${node.name} (${node.type})`,
3091
+ `**Max Depth:** ${depth}`,
3092
+ `**Dependents Found:** ${result.nodes.length}`,
3093
+ "",
3094
+ "### Nodes by Distance",
3095
+ ]
3096
+
3097
+ const maxShow = 30
3098
+ let shown = 0
3099
+ for (const [id, dist] of result.distances) {
3100
+ if (shown >= maxShow) {
3101
+ lines.push(`- ... and ${result.distances.size - maxShow} more`)
3102
+ break
3103
+ }
3104
+ const n = indices.byId.get(id)
3105
+ if (n) { lines.push(`- **${id}** (depth ${dist}): ${n.name} (${n.type})`); shown++ }
3106
+ }
3107
+
3108
+ return lines.join("\n")
3109
+ },
3110
+ }),
3111
+
3112
+ "sdd.traverse_both": tool({
3113
+ description: "Traverse the graph in both directions from a node (BFS). Returns the full neighborhood.",
3114
+ args: {
3115
+ node_id: tool.schema.string().describe("Starting node ID"),
3116
+ max_depth: tool.schema.number().optional().describe("Maximum traversal depth (default: 3)"),
3117
+ },
3118
+ async execute(args, ctx) {
3119
+ const repo = getRepo(ctx.directory)
3120
+ if (!repo.isInitialized()) return "SDD not initialized."
3121
+ const graph = repo.loadGraph()
3122
+ const indices = repo.getIndices()
3123
+
3124
+ const node = getNodeIndexed(indices, args.node_id)
3125
+ if (!node) return `Node ${args.node_id} not found.`
3126
+
3127
+ const depth = args.max_depth || 3
3128
+ const result = bfsBoth(graph, args.node_id, { max_depth: depth, include_start: true })
3129
+
3130
+ const lines = [
3131
+ `## Bidirectional Traverse: ${args.node_id}`,
3132
+ `**Node:** ${node.name} (${node.type})`,
3133
+ `**Max Depth:** ${depth}`,
3134
+ `**Connected Nodes:** ${result.nodes.length}`,
3135
+ "",
3136
+ "### Nodes by Distance",
3137
+ ]
3138
+
3139
+ const maxShow = 30
3140
+ let shown = 0
3141
+ for (const [id, dist] of result.distances) {
3142
+ if (shown >= maxShow) {
3143
+ lines.push(`- ... and ${result.distances.size - maxShow} more`)
3144
+ break
3145
+ }
3146
+ const n = indices.byId.get(id)
3147
+ if (n) { lines.push(`- **${id}** (depth ${dist}): ${n.name} (${n.type})`); shown++ }
3148
+ }
3149
+
3150
+ return lines.join("\n")
3151
+ },
3152
+ }),
3153
+
3154
+ "sdd.get_subgraph": tool({
3155
+ description: "Extract a subgraph containing only the specified nodes and their internal relationships.",
3156
+ args: {
3157
+ node_ids: tool.schema.string().describe("Comma-separated list of node IDs"),
3158
+ },
3159
+ async execute(args, ctx) {
3160
+ const repo = getRepo(ctx.directory)
3161
+ if (!repo.isInitialized()) return "SDD not initialized."
3162
+ const graph = repo.loadGraph()
3163
+
3164
+ const ids = args.node_ids.split(",").map(s => s.trim())
3165
+ const subgraph = getSubgraph(graph, ids)
3166
+
3167
+ const lines = [
3168
+ `## Subgraph`,
3169
+ `**Nodes:** ${subgraph.nodes.length}`,
3170
+ `**Relationships:** ${subgraph.relationships.length}`,
3171
+ "",
3172
+ "### Nodes",
3173
+ ]
3174
+
3175
+ const nodePage = paginate(subgraph.nodes)
3176
+ for (const n of nodePage.page) {
3177
+ lines.push(`- **${n.id}** (${n.type}): ${n.name} [${n.status}]`)
3178
+ }
3179
+ if (nodePage.hasMore) lines.push(`\n... and ${nodePage.total - nodePage.page.length} more nodes`)
3180
+
3181
+ if (subgraph.relationships.length > 0) {
3182
+ lines.push("\n### Relationships")
3183
+ const relPage = paginate(subgraph.relationships)
3184
+ for (const r of relPage.page) {
3185
+ lines.push(`- ${r.from} --[${r.type}]--> ${r.to}`)
3186
+ }
3187
+ if (relPage.hasMore) lines.push(`\n... and ${relPage.total - relPage.page.length} more relationships`)
3188
+ }
3189
+
3190
+ return lines.join("\n")
3191
+ },
3192
+ }),
3193
+
3194
+ "sdd.count_nodes": tool({
3195
+ description: "Count nodes by type in the SDD Knowledge Graph.",
3196
+ args: {},
3197
+ async execute(_args, ctx) {
3198
+ const repo = getRepo(ctx.directory)
3199
+ if (!repo.isInitialized()) return "SDD not initialized."
3200
+ const indices = repo.getIndices()
3201
+
3202
+ const lines = ["## Node Counts by Type\n"]
3203
+ const sorted = [...indices.byType.entries()]
3204
+ .sort(([, a], [, b]) => b.length - a.length)
3205
+ for (const [type, nodes] of sorted) {
3206
+ lines.push(`- **${type}:** ${nodes.length}`)
3207
+ }
3208
+ lines.push(`\n**Total:** ${indices.totalNodes} nodes`)
3209
+ return lines.join("\n")
3210
+ },
3211
+ }),
3212
+
3213
+ "sdd.get_nodes_by_status": tool({
3214
+ description: "List all nodes with a specific status.",
3215
+ args: {
3216
+ status: tool.schema.enum(["DRAFT", "PROPOSED", "APPROVED", "IMPLEMENTING", "IMPLEMENTED", "VERIFIED", "DEPRECATED", "CONFLICT", "DRIFTED", "BLOCKED", "todo", "ready", "in_progress", "blocked"]).describe("Status to filter by"),
3217
+ },
3218
+ async execute(args, ctx) {
3219
+ const repo = getRepo(ctx.directory)
3220
+ if (!repo.isInitialized()) return "SDD not initialized."
3221
+ const indices = repo.getIndices()
3222
+
3223
+ const nodes = getNodesByStatusIndexed(indices, args.status)
3224
+ if (nodes.length === 0) return `No nodes with status "${args.status}".`
3225
+
3226
+ const { page, total, hasMore } = paginate(nodes)
3227
+ const lines = [`## Nodes with status "${args.status}" (${total}${hasMore ? ", showing " + page.length : ""})\n`]
3228
+ for (const node of page) {
3229
+ lines.push(`- **${node.id}** (${node.type}): ${node.name}`)
3230
+ }
3231
+ if (hasMore) lines.push(`\nUse pagination or filter to see more.`)
3232
+ return lines.join("\n")
3233
+ },
3234
+ }),
3235
+
3236
+ "sdd.detect_sync_conflicts": tool({
3237
+ description: "Detect conflicts between local and remote SDD graphs. Requires a remote graph file path.",
3238
+ args: {
3239
+ remote_graph_path: tool.schema.string().describe("Path to the remote graph YAML file"),
3240
+ },
3241
+ async execute(args, ctx) {
3242
+ const repo = getRepo(ctx.directory)
3243
+ if (!repo.isInitialized()) return "SDD not initialized."
3244
+ const graph = repo.loadGraph()
3245
+
3246
+ const conflicts = detectConflicts(graph, args.remote_graph_path)
3247
+
3248
+ if (conflicts.length === 0) return "No sync conflicts detected."
3249
+
3250
+ const lines = [
3251
+ `## Sync Conflicts (${conflicts.length})`,
3252
+ "",
3253
+ ]
3254
+
3255
+ for (const c of conflicts) {
3256
+ lines.push(`- **Node:** ${c.node_id}`)
3257
+ lines.push(` **Field:** ${c.field}`)
3258
+ lines.push(` **Local:** ${JSON.stringify(c.local_value)}`)
3259
+ lines.push(` **Remote:** ${JSON.stringify(c.remote_value)}`)
3260
+ lines.push("")
3261
+ }
3262
+
3263
+ lines.push("Use `sdd.merge_graphs` to resolve conflicts.")
3264
+ return lines.join("\n")
3265
+ },
3266
+ }),
3267
+
3268
+ "sdd.merge_graphs": tool({
3269
+ description: "Merge local and remote graphs using a merge strategy.",
3270
+ args: {
3271
+ remote_graph_path: tool.schema.string().describe("Path to the remote graph YAML file"),
3272
+ auto_resolve: tool.schema.boolean().optional().describe("Auto-resolve using field priorities (default: false)"),
3273
+ },
3274
+ async execute(args, ctx) {
3275
+ const repo = getRepo(ctx.directory)
3276
+ if (!repo.isInitialized()) return "SDD not initialized."
3277
+ const graph = repo.loadGraph()
3278
+
3279
+ try {
3280
+ const remoteGraph = readJson<KnowledgeGraph>(args.remote_graph_path)
3281
+
3282
+ // Detect conflicts first using resolveConflict for detailed reporting
3283
+ const conflicts = detectConflicts(graph, args.remote_graph_path)
3284
+ const resolvedConflicts = conflicts.map(c => resolveConflict(c, "local"))
3285
+
3286
+ const strategy = {
3287
+ auto_resolve: args.auto_resolve ?? false,
3288
+ field_priorities: {} as Record<string, "local" | "remote">,
3289
+ }
3290
+
3291
+ const merged = mergeGraphs(graph, remoteGraph, strategy)
3292
+
3293
+ repo.saveGraph(merged)
3294
+ invalidateCacheForMutation(ctx.directory, [...new Set(merged.nodes.map(n => n.type))], [...new Set(merged.relationships.map(r => r.type))])
3295
+
3296
+ const nodeCount = merged.nodes.length - graph.nodes.length
3297
+ const lines = [
3298
+ `## Graph Merge Complete`,
3299
+ `- **Local nodes:** ${graph.nodes.length}`,
3300
+ `- **Remote nodes:** ${remoteGraph.nodes.length}`,
3301
+ `- **Merged nodes:** ${merged.nodes.length}`,
3302
+ `- **New nodes added:** ${nodeCount}`,
3303
+ ]
3304
+
3305
+ if (resolvedConflicts.length > 0) {
3306
+ lines.push("")
3307
+ lines.push(`### Conflicts Resolved (${resolvedConflicts.length})`)
3308
+ for (const c of resolvedConflicts.slice(0, 10)) {
3309
+ lines.push(`- **${c.node_id}** (${c.field}): resolved as ${c.resolution}`)
3310
+ }
3311
+ if (resolvedConflicts.length > 10) {
3312
+ lines.push(`- ... and ${resolvedConflicts.length - 10} more`)
3313
+ }
3314
+ }
3315
+
3316
+ return lines.join("\n")
3317
+ } catch (e) {
3318
+ return `Error merging graphs: ${e instanceof Error ? e.message : String(e)}`
3319
+ }
3320
+ },
3321
+ }),
3322
+
3323
+ "sdd.load_permissions_config": tool({
3324
+ description: "View the current permissions configuration (roles, approval requirements).",
3325
+ args: {},
3326
+ async execute(_args, ctx) {
3327
+ const config = loadPermissions(ctx.directory)
3328
+ const lines = [
3329
+ "## Permissions Configuration",
3330
+ "",
3331
+ "### Roles",
3332
+ ]
3333
+
3334
+ for (const role of config.roles) {
3335
+ lines.push(`\n**${role.role}** (max approvals: ${role.max_approvals}):`)
3336
+ for (const perm of role.permissions) {
3337
+ lines.push(` - ${perm}`)
3338
+ }
3339
+ }
3340
+
3341
+ lines.push("\n### Approval Requirements")
3342
+ for (const [type, count] of Object.entries(config.approval_requirements)) {
3343
+ lines.push(`- **${type}:** ${count} approval(s)`)
3344
+ }
3345
+
3346
+ return lines.join("\n")
3347
+ },
3348
+ }),
3349
+
3350
+ "sdd.check_change_approval": tool({
3351
+ description: "Check if a user has the required approval permission for a specific change type.",
3352
+ args: {
3353
+ change_type: tool.schema.string().describe("Change type (feature, requirement, architecture_component, constraint, decision, constitution)"),
3354
+ user: tool.schema.string().describe("Username to check"),
3355
+ },
3356
+ async execute(args, ctx) {
3357
+ const role = getUserRoleWithAuth(ctx.directory, args.user)
3358
+ const approved = checkChangeApproval(args.change_type, role, ctx.directory)
3359
+ const required = getRequiredApprovals(args.change_type, ctx.directory)
3360
+
3361
+ return [
3362
+ `## Change Approval Check`,
3363
+ `**User:** ${args.user} (${role})`,
3364
+ `**Change Type:** ${args.change_type}`,
3365
+ `**Required Approvals:** ${required}`,
3366
+ `**Has Permission:** ${approved ? "✅ YES" : "❌ NO"}`,
3367
+ ].join("\n")
3368
+ },
3369
+ }),
3370
+
3371
+ "sdd.save_permissions_config": tool({
3372
+ description: "Save a custom permissions configuration.",
3373
+ args: {
3374
+ config_json: tool.schema.string().describe("PermissionConfig as JSON (roles, approval_requirements)"),
3375
+ },
3376
+ async execute(args, ctx) {
3377
+ try {
3378
+ const config = JSON.parse(args.config_json)
3379
+ savePermissions(ctx.directory, config)
3380
+ return "✅ Permissions configuration saved."
3381
+ } catch (e) {
3382
+ return `Error: ${e instanceof Error ? e.message : String(e)}`
3383
+ }
3384
+ },
3385
+ }),
3386
+
3387
+ "sdd.get_user_role": tool({
3388
+ description: "Get a user's role, checking local config and remote (GitHub/GitLab) permissions.",
3389
+ args: {
3390
+ user: tool.schema.string().describe("Username"),
3391
+ },
3392
+ async execute(args, ctx) {
3393
+ const role = getUserRoleWithAuth(ctx.directory, args.user)
3394
+ const localRole = getUserRole(ctx.directory, args.user)
3395
+ const remoteUser = fetchRemoteUser(ctx.directory, args.user)
3396
+
3397
+ const lines = [
3398
+ `## User Role: ${args.user}`,
3399
+ `**Effective Role:** ${role}`,
3400
+ `**Local Role:** ${localRole}`,
3401
+ ]
3402
+
3403
+ if (remoteUser) {
3404
+ lines.push(`**Remote Provider:** ${remoteUser.login}`)
3405
+ lines.push(`**Remote Permissions:** admin=${remoteUser.permissions.admin}, push=${remoteUser.permissions.push}, pull=${remoteUser.permissions.pull}`)
3406
+ }
3407
+
3408
+ return lines.join("\n")
3409
+ },
3410
+ }),
3411
+
3412
+ "sdd.analyze_codebase": tool({
3413
+ description: "Analyze the project codebase and create FileNode/SymbolNode entries in the Knowledge Graph.",
3414
+ args: {},
3415
+ async execute(_args, ctx) {
3416
+ const repo = getRepo(ctx.directory)
3417
+ if (!repo.isInitialized()) return "SDD not initialized."
3418
+
3419
+ const graph = repo.loadGraph()
3420
+ const result = analyzeCodebase(graph, ctx.directory)
3421
+ repo.saveGraph(graph)
3422
+ invalidateCacheForMutation(ctx.directory, ["file", "symbol"], ["contains"])
3423
+
3424
+ return [
3425
+ `## Codebase Analysis Complete`,
3426
+ `**Files Analyzed:** ${result.files_analyzed}`,
3427
+ `**Symbols Found:** ${result.symbols_found}`,
3428
+ "",
3429
+ "FileNode and SymbolNode entries have been added to the Knowledge Graph.",
3430
+ "Use `sdd.query_graph` to inspect the new nodes.",
3431
+ ].join("\n")
3432
+ },
3433
+ }),
3434
+
3435
+ "sdd.start_dashboard": tool({
3436
+ description: "Start the SDD Knowledge Graph dashboard server with a 3D visualization UI.",
3437
+ args: {},
3438
+ async execute(_args, ctx) {
3439
+ const dashboard = new SddDashboardServer(ctx.directory)
3440
+ try {
3441
+ const port = await dashboard.start()
3442
+ return [
3443
+ `## SDD Dashboard Started`,
3444
+ `**URL:** http://127.0.0.1:${port}`,
3445
+ "",
3446
+ "Open the URL in a browser to view the Knowledge Graph visualization.",
3447
+ "The dashboard auto-refreshes every 5 seconds.",
3448
+ ].join("\n")
3449
+ } catch (e) {
3450
+ return `Error starting dashboard: ${e instanceof Error ? e.message : String(e)}`
3451
+ }
3452
+ },
3453
+ }),
3454
+
3455
+ "sdd.mcp_server_info": tool({
3456
+ description: "Get MCP server configuration and available tools for the SDD project.",
3457
+ args: {},
3458
+ async execute(_args, ctx) {
3459
+ const mcp = createMcpServer(ctx.directory)
3460
+ const tools = mcp.getTools()
3461
+
3462
+ const lines = [
3463
+ `## SDD MCP Server`,
3464
+ `**Name:** ${mcp.config.name}`,
3465
+ `**Version:** ${mcp.config.version}`,
3466
+ `**Description:** ${mcp.config.description}`,
3467
+ "",
3468
+ `### Available MCP Tools (${tools.length})`,
3469
+ ]
3470
+
3471
+ for (const t of tools) {
3472
+ lines.push(`- **${t.name}:** ${t.description}`)
3473
+ }
3474
+
3475
+ return lines.join("\n")
3476
+ },
3477
+ }),
3478
+
3479
+ "sdd.handle_mcp_tool": tool({
3480
+ description: "Execute an MCP tool call against the SDD MCP server.",
3481
+ args: {
3482
+ tool_name: tool.schema.string().describe("MCP tool name (sdd_get_quality, sdd_get_drift, sdd_get_validation, sdd_get_handoff)"),
3483
+ },
3484
+ async execute(args, ctx) {
3485
+ const mcp = createMcpServer(ctx.directory)
3486
+ const result = await mcp.handleToolCall(args.tool_name, {})
3487
+ if ("error" in result) return `Error: ${result.error}`
3488
+ const content = (result as { content: Array<{ type: string; text: string }> }).content
3489
+ return content?.map(c => c.text).join("\n") || "No output"
3490
+ },
3491
+ }),
3492
+
3493
+ "sdd.toggle_status": tool({
3494
+ description: "Check the current SDD toggle status.",
3495
+ args: {},
3496
+ async execute(_args, ctx) {
3497
+ const state = getToggleState(ctx.directory)
3498
+ const status = state.enabled ? "🟢 ON" : "🔴 OFF"
3499
+ return [
3500
+ `## SDD Toggle Status`,
3501
+ `**Status:** ${status}`,
3502
+ `**Last Changed:** ${state.changed_at}`,
3503
+ "",
3504
+ "Commands: `/sdd on`, `/sdd off`, `/sdd status`",
3505
+ ].join("\n")
3506
+ },
3507
+ }),
3508
+
3509
+ "sdd.list_snapshots": tool({
3510
+ description: "List all SDD snapshots available for rollback.",
3511
+ args: {},
3512
+ async execute(_args, ctx) {
3513
+ const repo = getRepo(ctx.directory)
3514
+ if (!repo.isInitialized()) return "SDD not initialized."
3515
+
3516
+ const snapshots = repo.listSnapshots()
3517
+ if (snapshots.length === 0) return "No snapshots found."
3518
+
3519
+ const lines = [`## Snapshots (${snapshots.length})\n`]
3520
+ for (const snap of snapshots) {
3521
+ lines.push(`- ${snap}`)
3522
+ }
3523
+ return lines.join("\n")
3524
+ },
3525
+ }),
3526
+
3527
+ "sdd.build_graph": tool({
3528
+ description:
3529
+ "Build a complete SDD Knowledge Graph from a project briefing. " +
3530
+ "Analyzes the briefing text and automatically creates ALL necessary nodes: " +
3531
+ "features, entities, endpoints, business rules, architecture components, " +
3532
+ "decisions, and requirements. Then connects them with relationships. " +
3533
+ "This is the PRIMARY tool for bootstrapping a project specification. " +
3534
+ "Use this INSTEAD of generating markdown documentation files. " +
3535
+ "PROVIDE analysis_json WHEN POSSIBLE: The agent should analyze the briefing using its own intelligence "
3536
+ + "and pass a structured JSON with features, entities, endpoints, businessRules, architectureComponents, "
3537
+ + "decisions, requirements, relationships, domains, and techStack. This produces FAR richer graphs "
3538
+ + "than regex-based extraction. Without analysis_json, falls back to regex (lower quality).",
3539
+ args: {
3540
+ briefing: tool.schema.string().describe("Complete project briefing text to analyze and build graph from"),
3541
+ analysis_json: tool.schema.string().optional().describe(
3542
+ 'Structured analysis JSON from LLM. Schema: { features: [{name, description, priority, phase?}], ' +
3543
+ 'entities: [{name, description, fields: [{name, type, required?, unique?}]}], ' +
3544
+ 'endpoints: [{method, path, description, relatedEntity?}], ' +
3545
+ 'businessRules: [{name, description, relatedFeature?}], ' +
3546
+ 'architectureComponents: [{name, layer, technology, description}], ' +
3547
+ 'decisions: [{title, context, decision, consequences}], ' +
3548
+ 'requirements: [{name, description, type, priority, acceptanceCriteria: [string]}], ' +
3549
+ 'relationships: [{from, to, type}], domains: [string], techStack: {layer: technology} }. '
3550
+ + 'The agent MUST analyze the briefing deeply and provide this for maximum graph quality.'
3551
+ ),
3552
+ force: tool.schema.boolean().optional().describe("Force rebuild even if graph already has nodes"),
3553
+ },
3554
+ async execute(args, ctx) {
3555
+ const repo = getRepo(ctx.directory)
3556
+ if (!repo.isInitialized()) {
3557
+ // Auto-initialize if not done yet
3558
+ const projectId = "project"
3559
+ repo.createProject(projectId, "Project", "Auto-initialized from briefing")
3560
+ }
3561
+
3562
+ const graph = repo.loadGraph()
3563
+
3564
+ // Check if graph already has significant content
3565
+ if (!args.force && graph.nodes.length > 20) {
3566
+ const indices = repo.getIndices()
3567
+ const hasFeatures = indices.byType.has("feature")
3568
+ const hasEntities = indices.byType.has("entity")
3569
+ if (hasFeatures && hasEntities) {
3570
+ return [
3571
+ "## Graph Already Populated",
3572
+ `Graph has ${graph.nodes.length} nodes with features and entities.`,
3573
+ "",
3574
+ "Use `force=true` to rebuild from scratch, or use individual tools to add/update nodes.",
3575
+ "Use `sdd.inspect` to see current graph state.",
3576
+ ].join("\n")
3577
+ }
3578
+ }
3579
+
3580
+ // Import analysis and graph builder
3581
+ const { analyzeBriefingDeep, formatDeepAnalysis } = await import("../sdd/discovery/briefing-analyzer.js")
3582
+ const { buildGraphFromAnalysis } = await import("../sdd/discovery/graph-builder.js")
3583
+
3584
+ // Use LLM-provided analysis if available, otherwise fall back to regex
3585
+ let analysis
3586
+ let analysisSource: string = "unknown"
3587
+ if (args.analysis_json) {
3588
+ try {
3589
+ analysis = JSON.parse(args.analysis_json)
3590
+ // Validate required fields
3591
+ if (!analysis.features || !Array.isArray(analysis.features)) throw new Error("Missing or invalid 'features' array")
3592
+ if (!analysis.entities || !Array.isArray(analysis.entities)) throw new Error("Missing or invalid 'entities' array")
3593
+ if (!analysis.endpoints || !Array.isArray(analysis.endpoints)) throw new Error("Missing or invalid 'endpoints' array")
3594
+ if (!analysis.businessRules || !Array.isArray(analysis.businessRules)) throw new Error("Missing or invalid 'businessRules' array")
3595
+ if (!analysis.architectureComponents || !Array.isArray(analysis.architectureComponents)) throw new Error("Missing or invalid 'architectureComponents' array")
3596
+ if (!analysis.decisions || !Array.isArray(analysis.decisions)) throw new Error("Missing or invalid 'decisions' array")
3597
+ if (!analysis.requirements || !Array.isArray(analysis.requirements)) throw new Error("Missing or invalid 'requirements' array")
3598
+ // Ensure optional fields have defaults
3599
+ if (!analysis.relationships) analysis.relationships = []
3600
+ if (!analysis.domains) analysis.domains = []
3601
+ if (!analysis.techStack) analysis.techStack = {}
3602
+ analysisSource = "LLM (intelligent extraction)"
3603
+ } catch (e) {
3604
+ const errMsg = e instanceof Error ? e.message : String(e)
3605
+ return "Error parsing analysis_json: " + errMsg + "\n\nFalling back to regex-based analysis."
3606
+ }
3607
+ }
3608
+
3609
+ if (!analysis) {
3610
+ analysis = analyzeBriefingDeep(args.briefing)
3611
+ analysisSource = "regex (automatic extraction — lower quality)"
3612
+ }
3613
+
3614
+ const buildResult = buildGraphFromAnalysis(graph, analysis)
3615
+
3616
+ // Save the graph
3617
+ repo.saveGraph(graph)
3618
+ invalidateCacheForMutation(ctx.directory, [...new Set(graph.nodes.map(n => n.type))], [...new Set(graph.relationships.map(r => r.type))])
3619
+
3620
+ // Build comprehensive response
3621
+ const lines = [
3622
+ buildResult.summary,
3623
+ "",
3624
+ `**Analysis source:** ${analysisSource}`,
3625
+ "",
3626
+ "### Analysis Preview",
3627
+ formatDeepAnalysis(analysis),
3628
+ "",
3629
+ "### What happened",
3630
+ "All specification data has been stored in the Knowledge Graph (.sdd/).",
3631
+ "Do NOT create markdown files for this specification.",
3632
+ "Use sdd.query_graph, sdd.inspect, and sdd.get_context to read the specification.",
3633
+ ]
3634
+
3635
+ return lines.join("\n")
3636
+ },
3637
+ }),
3638
+
3639
+ // ── NEW TOOLS: Drift Signals, Graph Pruning, Conventions ────────
3640
+
3641
+ "sdd.drift_signals": tool({
3642
+ description:
3643
+ "Detect advanced drift signals: mutant duplicates (near-identical files), " +
3644
+ "architecture violations, pattern fragmentation, and temporal volatility.",
3645
+ args: {},
3646
+ async execute(_args, ctx) {
3647
+ const repo = getRepo(ctx.directory)
3648
+ if (!repo.isInitialized()) return "SDD not initialized."
3649
+ const graph = repo.loadGraph()
3650
+ const signals = detectAllSignals(graph, ctx.directory)
3651
+ return formatDriftSignals(signals)
3652
+ },
3653
+ }),
3654
+
3655
+ "sdd.graph_health": tool({
3656
+ description:
3657
+ "Analyze graph health using indices: orphan nodes, disconnected components, " +
3658
+ "god nodes, type distribution, and relationship density.",
3659
+ args: {},
3660
+ async execute(_args, ctx) {
3661
+ const repo = getRepo(ctx.directory)
3662
+ if (!repo.isInitialized()) return "SDD not initialized."
3663
+ const graph = repo.loadGraph()
3664
+ const indices: GraphIndices = repo.getIndices()
3665
+
3666
+ const lines = ["## Graph Health Report\n"]
3667
+
3668
+ // Type distribution
3669
+ lines.push("### Type Distribution")
3670
+ const sortedTypes = [...indices.byType.entries()]
3671
+ .sort(([, a], [, b]) => b.length - a.length)
3672
+ for (const [type, nodes] of sortedTypes.slice(0, 10)) {
3673
+ lines.push(`- **${type}:** ${nodes.length}`)
3674
+ }
3675
+ if (sortedTypes.length > 10) lines.push(`- ... and ${sortedTypes.length - 10} more types`)
3676
+ lines.push("")
3677
+
3678
+ // Orphan nodes (no relationships)
3679
+ const orphanCount = graph.nodes.filter(
3680
+ n => !graph.relationships.some(r => r.from === n.id || r.to === n.id)
3681
+ ).length
3682
+ lines.push(`### Orphan Nodes: ${orphanCount}`)
3683
+
3684
+ // God nodes (high connectivity)
3685
+ const relCounts = new Map<string, number>()
3686
+ for (const rel of graph.relationships) {
3687
+ relCounts.set(rel.from, (relCounts.get(rel.from) || 0) + 1)
3688
+ relCounts.set(rel.to, (relCounts.get(rel.to) || 0) + 1)
3689
+ }
3690
+ const godThreshold = Math.max(20, graph.nodes.length * 0.15)
3691
+ const godNodes = [...relCounts.entries()]
3692
+ .filter(([, count]) => count > godThreshold)
3693
+ .map(([id, count]) => ({ id, count }))
3694
+ .sort((a, b) => b.count - a.count)
3695
+ .slice(0, 5)
3696
+
3697
+ if (godNodes.length > 0) {
3698
+ lines.push("")
3699
+ lines.push("### God Nodes (high connectivity)")
3700
+ for (const gn of godNodes) {
3701
+ const node = indices.byId.get(gn.id)
3702
+ lines.push(`- **${node?.name || gn.id}** (${node?.type || "?"}): ${gn.count} relationships`)
3703
+ }
3704
+ }
3705
+
3706
+ // Relationship density
3707
+ const avgRels = graph.nodes.length > 0
3708
+ ? (graph.relationships.length / graph.nodes.length).toFixed(2)
3709
+ : "0"
3710
+ lines.push("")
3711
+ lines.push(`### Relationship Density: ${avgRels} rels/node`)
3712
+ lines.push(`**Total:** ${graph.nodes.length} nodes, ${graph.relationships.length} relationships`)
3713
+
3714
+ return lines.join("\n")
3715
+ },
3716
+ }),
3717
+
3718
+ "sdd.graph_health_detail": tool({
3719
+ description:
3720
+ "Detailed graph health analysis using computeGraphHealth: stale changes, cycles, " +
3721
+ "god nodes, orphan changes, and draft endpoints. Provides actionable health indicators.",
3722
+ args: {},
3723
+ async execute(_args, ctx) {
3724
+ const repo = getRepo(ctx.directory)
3725
+ if (!repo.isInitialized()) return "SDD not initialized."
3726
+ const graph = repo.loadGraph()
3727
+ const health = computeGraphHealth(graph)
3728
+
3729
+ const lines = ["## Detailed Graph Health Report\n"]
3730
+
3731
+ // Stale changes
3732
+ lines.push(`### Stale Changes: ${health.staleChanges}`)
3733
+ if (health.staleChangeIds.length > 0) {
3734
+ for (const id of health.staleChangeIds.slice(0, 10)) {
3735
+ lines.push(`- ${id}`)
3736
+ }
3737
+ if (health.staleChangeIds.length > 10) lines.push(`- ... and ${health.staleChangeIds.length - 10} more`)
3738
+ } else {
3739
+ lines.push("No stale changes.")
3740
+ }
3741
+ lines.push("")
3742
+
3743
+ // Cycles
3744
+ lines.push(`### Cycles Detected: ${health.cyclesDetected}`)
3745
+ if (health.cyclesDetected > 0) {
3746
+ lines.push("⚠️ Circular dependencies detected in the graph.")
3747
+ } else {
3748
+ lines.push("No cycles detected.")
3749
+ }
3750
+ lines.push("")
3751
+
3752
+ // God nodes
3753
+ lines.push(`### God Nodes: ${health.godNodes.length}`)
3754
+ if (health.godNodes.length > 0) {
3755
+ for (const gn of health.godNodes.slice(0, 5)) {
3756
+ lines.push(`- **${gn.name}** (${gn.id}): ${gn.relCount} relationships`)
3757
+ }
3758
+ if (health.godNodes.length > 5) lines.push(`- ... and ${health.godNodes.length - 5} more`)
3759
+ } else {
3760
+ lines.push("No god nodes detected.")
3761
+ }
3762
+ lines.push("")
3763
+
3764
+ // Orphan changes
3765
+ lines.push(`### Orphan Changes: ${health.orphanChanges}`)
3766
+ if (health.orphanChanges > 0) {
3767
+ lines.push("⚠️ Some changes are not connected to any feature or entity.")
3768
+ }
3769
+ lines.push("")
3770
+
3771
+ // Draft endpoints
3772
+ lines.push(`### Draft Endpoints: ${health.draftEndpoints}`)
3773
+ if (health.draftEndpoints > 0) {
3774
+ lines.push("⚠️ Some endpoints are still in DRAFT status.")
3775
+ }
3776
+
3777
+ return lines.join("\n")
3778
+ },
3779
+ }),
3780
+
3781
+ "sdd.graph_prune": tool({
3782
+ description:
3783
+ "Prune obsolete nodes from the SDD graph: dead file references, orphaned symbols, " +
3784
+ "old completed changes, and duplicate relationships.",
3785
+ args: {},
3786
+ async execute(_args, ctx) {
3787
+ const repo = getRepo(ctx.directory)
3788
+ if (!repo.isInitialized()) return "SDD not initialized."
3789
+ const graph = repo.loadGraph()
3790
+ const { pruneGraph, formatPruneReport } = await import("../sdd/graph/pruner.js")
3791
+ const report = pruneGraph(graph, ctx.directory)
3792
+ repo.saveGraph(graph)
3793
+ invalidateCacheForMutation(ctx.directory, [...new Set(graph.nodes.map(n => n.type))], [...new Set(graph.relationships.map(r => r.type))])
3794
+ return formatPruneReport(report)
3795
+ },
3796
+ }),
3797
+
3798
+ "sdd.migrate_storage": tool({
3799
+ description:
3800
+ "Migrate SDD storage between YAML and SQLite backends. " +
3801
+ "SQLite is recommended for projects with 1000+ nodes.",
3802
+ args: {
3803
+ target: tool.schema.enum(["yaml", "sqlite"]).describe("Target storage backend"),
3804
+ },
3805
+ async execute(args, ctx) {
3806
+ const repo = getRepo(ctx.directory)
3807
+ if (!repo.isInitialized()) return "SDD not initialized."
3808
+
3809
+ const currentType = repo.getStorageType()
3810
+ if (currentType === args.target) {
3811
+ return `Already using ${args.target} storage.`
3812
+ }
3813
+
3814
+ const graph = repo.loadGraph()
3815
+ const newRepo = repo.migrateTo(args.target as "yaml" | "sqlite", ctx.directory)
3816
+
3817
+ return [
3818
+ `## Storage Migration Complete`,
3819
+ `- **From:** ${currentType}`,
3820
+ `- **To:** ${args.target}`,
3821
+ `- **Nodes:** ${graph.nodes.length}`,
3822
+ `- **Relationships:** ${graph.relationships.length}`,
3823
+ ].join("\n")
3824
+ },
3825
+ }),
3826
+
3827
+ "sdd.detect_conventions": tool({
3828
+ description:
3829
+ "Detect project coding conventions (naming, imports, async patterns, structure) " +
3830
+ "by analyzing existing source files.",
3831
+ args: {},
3832
+ async execute(_args, ctx) {
3833
+ const { detectConventions, formatConventions } = await import("../sdd/code-quality/conventions.js")
3834
+ const conventions = detectConventions(ctx.directory)
3835
+ return formatConventions(conventions)
3836
+ },
3837
+ }),
3838
+
3839
+ "sdd.learn_patterns": tool({
3840
+ description:
3841
+ "Analyze the SDD graph to learn project patterns: common node defaults, " +
3842
+ "relationship patterns, naming conventions, and approval history.",
3843
+ args: {
3844
+ action: tool.schema.enum(["learn", "show", "suggest"]).describe("learn: analyze graph, show: display patterns, suggest: get suggestions for new node"),
3845
+ node_type: tool.schema.string().optional().describe("Node type for suggestions (with action=suggest)"),
3846
+ },
3847
+ async execute(args, ctx) {
3848
+ const repo = getRepo(ctx.directory)
3849
+ if (!repo.isInitialized()) return "SDD not initialized."
3850
+ const graph = repo.loadGraph()
3851
+ const { learnPatterns, formatPatterns, loadPatterns, savePatterns, getSuggestedDefaults, getSuggestedRelationships, getSuggestedNaming } = await import("../sdd/patterns/learner.js")
3852
+
3853
+ if (args.action === "learn") {
3854
+ const patterns = learnPatterns(graph)
3855
+ savePatterns(ctx.directory, patterns)
3856
+ return formatPatterns(patterns)
3857
+ }
3858
+
3859
+ if (args.action === "show") {
3860
+ const patterns = loadPatterns(ctx.directory)
3861
+ if (!patterns) return "No patterns learned yet. Run with action=learn first."
3862
+ return formatPatterns(patterns)
3863
+ }
3864
+
3865
+ if (args.action === "suggest") {
3866
+ if (!args.node_type) return "node_type is required for suggest action."
3867
+ const patterns = loadPatterns(ctx.directory)
3868
+ if (!patterns) return "No patterns learned yet. Run with action=learn first."
3869
+ const defaults = getSuggestedDefaults(patterns, args.node_type as any)
3870
+ const rels = getSuggestedRelationships(patterns, args.node_type as any)
3871
+ const naming = getSuggestedNaming(patterns, args.node_type as any)
3872
+ const lines = [
3873
+ `## Suggestions for ${args.node_type}`,
3874
+ "",
3875
+ "### Default Metadata",
3876
+ Object.keys(defaults).length > 0 ? JSON.stringify(defaults, null, 2) : "No defaults learned.",
3877
+ "",
3878
+ "### Suggested Relationships",
3879
+ ]
3880
+ for (const r of rels) {
3881
+ lines.push(`- →[${r.relationship_type}]→ ${r.to_type}`)
3882
+ }
3883
+ if (naming) {
3884
+ lines.push("")
3885
+ lines.push(`### Naming Convention: ${naming}`)
3886
+ }
3887
+ return lines.join("\n")
3888
+ }
3889
+
3890
+ return "Invalid action. Use 'learn', 'show', or 'suggest'."
3891
+ },
3892
+ }),
3893
+
3894
+ "sdd.cache_stats": tool({
3895
+ description:
3896
+ "Show cache statistics: hit rates, sizes, invalidation count. " +
3897
+ "Useful for debugging cache performance.",
3898
+ args: {},
3899
+ async execute(_args, ctx) {
3900
+ const cacheMgr = getCacheManager(ctx.directory)
3901
+ const stats = cacheMgr.getStats()
3902
+ const lines = [
3903
+ "## Cache Statistics",
3904
+ "",
3905
+ "### Tool Response Cache",
3906
+ `- Size: ${stats.toolCacheSize} entries`,
3907
+ `- Hit rate: ${stats.hitRate}`,
3908
+ `- Hits: ${stats.toolHits} | Misses: ${stats.toolMisses}`,
3909
+ "",
3910
+ "### Analysis Cache",
3911
+ `- Size: ${stats.analysisCacheSize} entries`,
3912
+ `- Hit rate: ${stats.analysisHitRate}`,
3913
+ `- Hits: ${stats.analysisHits} | Misses: ${stats.analysisMisses}`,
3914
+ "",
3915
+ `### Invalidation Count: ${stats.invalidations}`,
3916
+ "",
3917
+ "### Repository Info",
3918
+ ]
3919
+
3920
+ const repo = getRepo(ctx.directory)
3921
+ if (repo.isInitialized()) {
3922
+ lines.push(`- Storage type: ${repo.getStorageType()}`)
3923
+ lines.push(`- Node count: ${repo.getNodeCount()}`)
3924
+ lines.push(`- Relationship count: ${repo.getRelationshipCount()}`)
3925
+ } else {
3926
+ lines.push("- SDD not initialized")
3927
+ }
3928
+
3929
+ return lines.join("\n")
3930
+ },
3931
+ }),
3932
+
3933
+ // ── Drift Whitelist Tools ───────────────────────────────────────
3934
+
3935
+ "sdd.whitelist_drift": tool({
3936
+ description:
3937
+ "Add a file or pattern to the drift whitelist. Whitelisted files will NOT be reported as drift. " +
3938
+ "Use this to suppress false positives for legitimate architectural decisions.",
3939
+ args: {
3940
+ file_path: tool.schema.string().describe("File path or pattern (e.g., 'src/validators/*') to whitelist"),
3941
+ reason: tool.schema.string().describe("Why this file is whitelisted (e.g., 'Unified schema.sql pattern')"),
3942
+ },
3943
+ async execute(args, ctx) {
3944
+ const { addToDriftWhitelist, loadDriftWhitelist } = await import("../sdd/drift/exclusion.js")
3945
+ addToDriftWhitelist(ctx.directory, args.file_path, args.reason)
3946
+ const whitelist = loadDriftWhitelist(ctx.directory)
3947
+ return [
3948
+ `✅ File whitelisted: \`${args.file_path}\``,
3949
+ `**Reason:** ${args.reason}`,
3950
+ `**Total whitelisted:** ${whitelist.entries.length} files/patterns`,
3951
+ ].join("\n")
3952
+ },
3953
+ }),
3954
+
3955
+ "sdd.unwhitelist_drift": tool({
3956
+ description: "Remove a file from the drift whitelist.",
3957
+ args: {
3958
+ file_path: tool.schema.string().describe("File path to remove from whitelist"),
3959
+ },
3960
+ async execute(args, ctx) {
3961
+ const { removeFromDriftWhitelist } = await import("../sdd/drift/exclusion.js")
3962
+ removeFromDriftWhitelist(ctx.directory, args.file_path)
3963
+ return `✅ Removed from whitelist: \`${args.file_path}\``
3964
+ },
3965
+ }),
3966
+
3967
+ "sdd.list_whitelist": tool({
3968
+ description: "List all files/patterns in the drift whitelist.",
3969
+ args: {},
3970
+ async execute(_args, ctx) {
3971
+ const { loadDriftWhitelist } = await import("../sdd/drift/exclusion.js")
3972
+ const whitelist = loadDriftWhitelist(ctx.directory)
3973
+
3974
+ if (whitelist.entries.length === 0) {
3975
+ return "Whitelist is empty. Use `sdd.whitelist_drift` to add entries."
3976
+ }
3977
+
3978
+ const lines = [`## Drift Whitelist (${whitelist.entries.length})\n`]
3979
+ for (const entry of whitelist.entries) {
3980
+ lines.push(`- **${entry.file_path}** — ${entry.reason}`)
3981
+ if (entry.added_by) lines.push(` Added by: ${entry.added_by}`)
3982
+ lines.push(` Added at: ${entry.added_at}`)
3983
+ lines.push("")
3984
+ }
3985
+ return lines.join("\n")
3986
+ },
3987
+ }),
3988
+
3989
+ // ── Auto-Link Tests ─────────────────────────────────────────────
3990
+
3991
+ "sdd.auto_link_tests": tool({
3992
+ description:
3993
+ "Automatically link orphan tests to requirements based on name matching, " +
3994
+ "import analysis, and file relationships. Creates tested_by relationships in the graph.",
3995
+ args: {
3996
+ dry_run: tool.schema.boolean().optional().describe("Preview links without creating them (default: false)"),
3997
+ },
3998
+ async execute(args, ctx) {
3999
+ const repo = getRepo(ctx.directory)
4000
+ if (!repo.isInitialized()) return "SDD not initialized."
4001
+ const graph = repo.loadGraph()
4002
+
4003
+ const { calculateCoverage } = await import("../sdd/coverage/tracker.js")
4004
+ const coverage = calculateCoverage(graph)
4005
+
4006
+ // Filter orphan tests that have inferred requirements
4007
+ const linkableOrphans = coverage.orphan_tests.filter(t => t.inferred_requirement_id)
4008
+
4009
+ if (linkableOrphans.length === 0) {
4010
+ return [
4011
+ "## Auto-Link Tests",
4012
+ "",
4013
+ `**Orphan tests:** ${coverage.orphan_tests.length}`,
4014
+ `**Linkable (inferred):** 0`,
4015
+ "",
4016
+ "No orphan tests could be automatically linked to requirements.",
4017
+ "Try running `sdd.analyze_codebase` first to create FileNode/SymbolNode entries.",
4018
+ ].join("\n")
4019
+ }
4020
+
4021
+ if (args.dry_run) {
4022
+ const lines = [
4023
+ "## Auto-Link Tests (Dry Run)",
4024
+ "",
4025
+ `**Orphan tests:** ${coverage.orphan_tests.length}`,
4026
+ `**Linkable (inferred):** ${linkableOrphans.length}`,
4027
+ "",
4028
+ "### Would create these relationships:\n",
4029
+ ]
4030
+ for (const orphan of linkableOrphans) {
4031
+ lines.push(`- ${orphan.test_id} --[tested_by]--> ${orphan.inferred_requirement_id}`)
4032
+ lines.push(` Method: ${orphan.inferred_by}`)
4033
+ }
4034
+ lines.push("")
4035
+ lines.push("Run without `dry_run=true` to create the relationships.")
4036
+ return lines.join("\n")
4037
+ }
4038
+
4039
+ // Create the relationships
4040
+ let linked = 0
4041
+ const errors: string[] = []
4042
+
4043
+ for (const orphan of linkableOrphans) {
4044
+ const relId = `rel-${orphan.test_id}-tested-by-${orphan.inferred_requirement_id}`
4045
+ const exists = graph.relationships.some(
4046
+ r => r.from === orphan.inferred_requirement_id && r.to === orphan.test_id && r.type === "tested_by"
4047
+ )
4048
+ if (exists) continue
4049
+
4050
+ try {
4051
+ graph.relationships.push({
4052
+ id: relId,
4053
+ from: orphan.inferred_requirement_id!,
4054
+ to: orphan.test_id,
4055
+ type: "tested_by",
4056
+ metadata: { inferred: true, method: orphan.inferred_by },
4057
+ })
4058
+ linked++
4059
+ } catch (e) {
4060
+ errors.push(`Failed to link ${orphan.test_id}: ${e}`)
4061
+ }
4062
+ }
4063
+
4064
+ if (linked > 0) {
4065
+ repo.saveGraph(graph)
4066
+ invalidateCacheForMutation(ctx.directory, ["requirement", "test"], ["tested_by"])
4067
+ }
4068
+
4069
+ const lines = [
4070
+ "## Auto-Link Tests Complete",
4071
+ "",
4072
+ `**Links created:** ${linked}`,
4073
+ `**Already linked:** ${linkableOrphans.length - linked}`,
4074
+ `**Remaining orphans:** ${coverage.orphan_tests.length - linkableOrphans.length}`,
4075
+ ]
4076
+
4077
+ if (errors.length > 0) {
4078
+ lines.push("")
4079
+ lines.push(`**Errors:** ${errors.length}`)
4080
+ for (const err of errors) lines.push(`- ${err}`)
4081
+ }
4082
+
4083
+ return lines.join("\n")
4084
+ },
4085
+ }),
4086
+
4087
+ // ── Composite Tools (Item 5: Redução de Tools) ─────────────────
4088
+ "sdd.graph_mutation": createGraphMutationTool(),
4089
+ "sdd.graph_query": createGraphQueryTool(),
4090
+ "sdd.traverse": createTraverseTool(),
4091
+ "sdd.permissions": createPermissionsTool(),
4092
+ "sdd.snapshot": createSnapshotTool(),
4093
+ "sdd.sync": createSyncTool(),
4094
+ "sdd.graph_admin": createGraphAdminTool(),
4095
+ "sdd.code_quality": createCodeQualityTool(),
4096
+ "sdd.enterprise": createEnterpriseTool(),
4097
+ "sdd.drift_whitelist": createDriftWhitelistTool(),
4098
+
4099
+ // ── Workflow Chains (Item 4: Orquestração) ──────────────────────
4100
+ ...createWorkflowTools(),
4101
+ }
4102
+ }