devcouncil 0.3.1 → 0.4.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 (365) hide show
  1. package/README.md +46 -30
  2. package/package.json +6 -2
  3. package/packages/codeintel-grammars/hatch_build.py +43 -0
  4. package/packages/codeintel-grammars/pyproject.toml +16 -0
  5. package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
  6. package/pyproject.toml +99 -4
  7. package/src/devcouncil/app/config.py +512 -20
  8. package/src/devcouncil/app/events.py +4 -23
  9. package/src/devcouncil/app/orchestrator.py +5 -0
  10. package/src/devcouncil/app/run_context.py +3 -3
  11. package/src/devcouncil/assets/__init__.py +4 -1
  12. package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
  13. package/src/devcouncil/campaign/__init__.py +71 -0
  14. package/src/devcouncil/campaign/bloom.py +137 -0
  15. package/src/devcouncil/campaign/dashboard.py +123 -0
  16. package/src/devcouncil/campaign/mailbox.py +305 -0
  17. package/src/devcouncil/campaign/notify.py +91 -0
  18. package/src/devcouncil/campaign/orchestrator.py +592 -0
  19. package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
  20. package/src/devcouncil/campaign/prompts/director.md +21 -0
  21. package/src/devcouncil/campaign/prompts/protocol.md +46 -0
  22. package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
  23. package/src/devcouncil/campaign/prompts/worker.md +24 -0
  24. package/src/devcouncil/campaign/roles.py +202 -0
  25. package/src/devcouncil/campaign/watcher.py +153 -0
  26. package/src/devcouncil/cli/commands/agents.py +24 -17
  27. package/src/devcouncil/cli/commands/artifacts.py +36 -27
  28. package/src/devcouncil/cli/commands/ast.py +12 -3
  29. package/src/devcouncil/cli/commands/baseline.py +21 -12
  30. package/src/devcouncil/cli/commands/boot.py +218 -0
  31. package/src/devcouncil/cli/commands/campaign.py +302 -0
  32. package/src/devcouncil/cli/commands/check.py +225 -12
  33. package/src/devcouncil/cli/commands/config.py +221 -74
  34. package/src/devcouncil/cli/commands/cost.py +137 -28
  35. package/src/devcouncil/cli/commands/dashboard.py +12 -4
  36. package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
  37. package/src/devcouncil/cli/commands/design.py +27 -17
  38. package/src/devcouncil/cli/commands/doctor.py +790 -8
  39. package/src/devcouncil/cli/commands/evidence.py +41 -20
  40. package/src/devcouncil/cli/commands/export.py +73 -0
  41. package/src/devcouncil/cli/commands/gaps.py +175 -0
  42. package/src/devcouncil/cli/commands/gated_write.py +76 -0
  43. package/src/devcouncil/cli/commands/go.py +220 -68
  44. package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
  45. package/src/devcouncil/cli/commands/handoff.py +45 -34
  46. package/src/devcouncil/cli/commands/hook.py +630 -85
  47. package/src/devcouncil/cli/commands/init.py +89 -30
  48. package/src/devcouncil/cli/commands/integrate.py +296 -1385
  49. package/src/devcouncil/cli/commands/lease.py +120 -0
  50. package/src/devcouncil/cli/commands/logs.py +12 -5
  51. package/src/devcouncil/cli/commands/lsp.py +40 -5
  52. package/src/devcouncil/cli/commands/map.py +317 -74
  53. package/src/devcouncil/cli/commands/mcp_server.py +12 -2
  54. package/src/devcouncil/cli/commands/okf.py +44 -6
  55. package/src/devcouncil/cli/commands/plan.py +184 -69
  56. package/src/devcouncil/cli/commands/prompt.py +26 -17
  57. package/src/devcouncil/cli/commands/provenance.py +79 -0
  58. package/src/devcouncil/cli/commands/repair.py +60 -49
  59. package/src/devcouncil/cli/commands/report.py +148 -40
  60. package/src/devcouncil/cli/commands/requirements.py +104 -0
  61. package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
  62. package/src/devcouncil/cli/commands/rollback.py +46 -35
  63. package/src/devcouncil/cli/commands/run.py +173 -8
  64. package/src/devcouncil/cli/commands/runs.py +298 -68
  65. package/src/devcouncil/cli/commands/scaffold.py +33 -12
  66. package/src/devcouncil/cli/commands/semantic.py +29 -14
  67. package/src/devcouncil/cli/commands/setup.py +103 -93
  68. package/src/devcouncil/cli/commands/shell.py +51 -42
  69. package/src/devcouncil/cli/commands/show.py +56 -42
  70. package/src/devcouncil/cli/commands/skills.py +29 -20
  71. package/src/devcouncil/cli/commands/status.py +80 -67
  72. package/src/devcouncil/cli/commands/task_gate.py +295 -0
  73. package/src/devcouncil/cli/commands/tasks.py +248 -19
  74. package/src/devcouncil/cli/commands/trace.py +14 -8
  75. package/src/devcouncil/cli/commands/verify.py +33 -8
  76. package/src/devcouncil/cli/commands/version.py +14 -6
  77. package/src/devcouncil/cli/commands/watch.py +49 -30
  78. package/src/devcouncil/cli/commands/watch_fs.py +30 -19
  79. package/src/devcouncil/cli/commands/wiki.py +278 -0
  80. package/src/devcouncil/cli/main.py +58 -1
  81. package/src/devcouncil/codeintel/__init__.py +16 -0
  82. package/src/devcouncil/codeintel/build_control.py +429 -0
  83. package/src/devcouncil/codeintel/build_worker.py +78 -0
  84. package/src/devcouncil/codeintel/debug/__init__.py +17 -0
  85. package/src/devcouncil/codeintel/debug/broker.py +114 -0
  86. package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
  87. package/src/devcouncil/codeintel/debug/consent.py +36 -0
  88. package/src/devcouncil/codeintel/debug/discovery.py +132 -0
  89. package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
  90. package/src/devcouncil/codeintel/debug/protocol.py +259 -0
  91. package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
  92. package/src/devcouncil/codeintel/debug/session.py +238 -0
  93. package/src/devcouncil/codeintel/debug/tracing.py +201 -0
  94. package/src/devcouncil/codeintel/languages/__init__.py +17 -0
  95. package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
  96. package/src/devcouncil/codeintel/languages/registry.py +149 -0
  97. package/src/devcouncil/codeintel/languages/workers.py +245 -0
  98. package/src/devcouncil/codeintel/query/__init__.py +5 -0
  99. package/src/devcouncil/codeintel/query/engine.py +289 -0
  100. package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
  101. package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
  102. package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
  103. package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
  104. package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
  105. package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
  106. package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
  107. package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
  108. package/src/devcouncil/codeintel/service.py +104 -0
  109. package/src/devcouncil/codeintel/store/__init__.py +15 -0
  110. package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
  111. package/src/devcouncil/codeintel/sync/__init__.py +19 -0
  112. package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
  113. package/src/devcouncil/codeintel/sync/incremental.py +484 -0
  114. package/src/devcouncil/codeintel/sync/lease.py +96 -0
  115. package/src/devcouncil/codeintel/sync/scope.py +98 -0
  116. package/src/devcouncil/council/__init__.py +4 -0
  117. package/src/devcouncil/council/prompts/__init__.py +4 -0
  118. package/src/devcouncil/domain/checkpoint_refs.py +17 -0
  119. package/src/devcouncil/domain/evidence.py +1 -0
  120. package/src/devcouncil/domain/gap.py +10 -0
  121. package/src/devcouncil/domain/requirement.py +5 -1
  122. package/src/devcouncil/domain/task.py +43 -2
  123. package/src/devcouncil/execution/checkpoints.py +25 -31
  124. package/src/devcouncil/execution/context_builder.py +15 -44
  125. package/src/devcouncil/execution/fs_watcher.py +64 -0
  126. package/src/devcouncil/execution/gated_write.py +203 -0
  127. package/src/devcouncil/execution/handoff.py +2 -1
  128. package/src/devcouncil/execution/hook_policy.py +19 -5
  129. package/src/devcouncil/execution/lease_ops.py +177 -0
  130. package/src/devcouncil/execution/lease_validation.py +71 -0
  131. package/src/devcouncil/execution/patch.py +3 -0
  132. package/src/devcouncil/execution/permissions.py +1 -0
  133. package/src/devcouncil/execution/policy_engine.py +205 -10
  134. package/src/devcouncil/execution/prompt_builder.py +278 -33
  135. package/src/devcouncil/execution/run_trace.py +356 -0
  136. package/src/devcouncil/execution/shell_session.py +46 -5
  137. package/src/devcouncil/execution/stop_gate.py +746 -0
  138. package/src/devcouncil/execution/stop_gate_history.py +113 -0
  139. package/src/devcouncil/execution/stop_gate_state.py +54 -0
  140. package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
  141. package/src/devcouncil/execution/task_gate_ops.py +590 -0
  142. package/src/devcouncil/execution/task_runner.py +19 -0
  143. package/src/devcouncil/executors/advisor_tool.py +315 -0
  144. package/src/devcouncil/executors/agent_registry.py +125 -17
  145. package/src/devcouncil/executors/claude_sdk.py +376 -0
  146. package/src/devcouncil/executors/coding_cli.py +724 -25
  147. package/src/devcouncil/executors/mini_swe.py +50 -8
  148. package/src/devcouncil/executors/native/agent.py +224 -19
  149. package/src/devcouncil/executors/openhands.py +50 -8
  150. package/src/devcouncil/executors/transient_retry.py +99 -0
  151. package/src/devcouncil/gating/checks/clean_git.py +5 -2
  152. package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
  153. package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
  154. package/src/devcouncil/gating/policy.py +46 -2
  155. package/src/devcouncil/indexing/ast_matcher.py +41 -4
  156. package/src/devcouncil/indexing/graph/__init__.py +78 -0
  157. package/src/devcouncil/indexing/graph/api_routes.py +522 -0
  158. package/src/devcouncil/indexing/graph/build.py +862 -0
  159. package/src/devcouncil/indexing/graph/cache.py +329 -0
  160. package/src/devcouncil/indexing/graph/communities.py +28 -0
  161. package/src/devcouncil/indexing/graph/cypher.py +107 -0
  162. package/src/devcouncil/indexing/graph/embeddings.py +194 -0
  163. package/src/devcouncil/indexing/graph/export.py +381 -0
  164. package/src/devcouncil/indexing/graph/export_links.py +81 -0
  165. package/src/devcouncil/indexing/graph/extract_python.py +307 -0
  166. package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
  167. package/src/devcouncil/indexing/graph/intel.py +668 -0
  168. package/src/devcouncil/indexing/graph/liveness.py +992 -0
  169. package/src/devcouncil/indexing/graph/okf_export.py +65 -0
  170. package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
  171. package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
  172. package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
  173. package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
  174. package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
  175. package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
  176. package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
  177. package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
  178. package/src/devcouncil/indexing/graph/query.py +302 -0
  179. package/src/devcouncil/indexing/graph/resolve.py +1020 -0
  180. package/src/devcouncil/indexing/graph/schema.py +103 -0
  181. package/src/devcouncil/indexing/graph_index.py +20 -29
  182. package/src/devcouncil/indexing/lsp.py +57 -25
  183. package/src/devcouncil/indexing/lsp_client.py +577 -0
  184. package/src/devcouncil/indexing/map_artifacts.py +355 -0
  185. package/src/devcouncil/indexing/map_refresh.py +141 -0
  186. package/src/devcouncil/indexing/repo_mapper.py +1509 -138
  187. package/src/devcouncil/indexing/semantic_index.py +12 -6
  188. package/src/devcouncil/indexing/subsystem_map.py +163 -0
  189. package/src/devcouncil/indexing/ts_imports.py +343 -0
  190. package/src/devcouncil/indexing/viz.py +960 -0
  191. package/src/devcouncil/indexing/walk.py +52 -0
  192. package/src/devcouncil/indexing/wiring.py +1776 -0
  193. package/src/devcouncil/integrations/actions.py +27 -4
  194. package/src/devcouncil/integrations/check.py +211 -16
  195. package/src/devcouncil/integrations/claude_assets.py +209 -12
  196. package/src/devcouncil/integrations/clients/__init__.py +1 -0
  197. package/src/devcouncil/integrations/clients/aider.py +52 -0
  198. package/src/devcouncil/integrations/clients/antigravity.py +87 -0
  199. package/src/devcouncil/integrations/clients/claude.py +339 -0
  200. package/src/devcouncil/integrations/clients/codex.py +39 -0
  201. package/src/devcouncil/integrations/clients/common.py +332 -0
  202. package/src/devcouncil/integrations/clients/cursor.py +164 -0
  203. package/src/devcouncil/integrations/clients/gemini.py +49 -0
  204. package/src/devcouncil/integrations/clients/grok.py +105 -0
  205. package/src/devcouncil/integrations/clients/hooks.py +500 -0
  206. package/src/devcouncil/integrations/clients/opencode.py +96 -0
  207. package/src/devcouncil/integrations/clients/warp.py +75 -0
  208. package/src/devcouncil/integrations/code_review_graph.py +2 -2
  209. package/src/devcouncil/integrations/github.py +73 -7
  210. package/src/devcouncil/integrations/integration_cli.py +197 -0
  211. package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
  212. package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
  213. package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
  214. package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
  215. package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
  216. package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
  217. package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
  218. package/src/devcouncil/integrations/mcp/handlers/git.py +99 -0
  219. package/src/devcouncil/integrations/mcp/handlers/graph.py +36 -0
  220. package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
  221. package/src/devcouncil/integrations/mcp/handlers/knowledge.py +30 -0
  222. package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
  223. package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
  224. package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
  225. package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
  226. package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
  227. package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
  228. package/src/devcouncil/integrations/mcp/handlers/provenance.py +101 -0
  229. package/src/devcouncil/integrations/mcp/handlers/read.py +74 -0
  230. package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
  231. package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
  232. package/src/devcouncil/integrations/mcp/handlers/runs.py +84 -0
  233. package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
  234. package/src/devcouncil/integrations/mcp/handlers/status.py +114 -0
  235. package/src/devcouncil/integrations/mcp/handlers/task.py +100 -0
  236. package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +904 -0
  237. package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
  238. package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
  239. package/src/devcouncil/integrations/mcp/handlers/wiki.py +60 -0
  240. package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
  241. package/src/devcouncil/integrations/mcp/server.py +265 -2391
  242. package/src/devcouncil/integrations/mcp/util.py +303 -0
  243. package/src/devcouncil/integrations/setup.py +152 -0
  244. package/src/devcouncil/knowledge/fetch.py +4 -0
  245. package/src/devcouncil/knowledge/knowledge_select.py +38 -0
  246. package/src/devcouncil/knowledge/okf.py +2 -1
  247. package/src/devcouncil/knowledge/resource_discovery.py +40 -0
  248. package/src/devcouncil/knowledge/wiki.py +643 -0
  249. package/src/devcouncil/knowledge/wiki_read.py +87 -0
  250. package/src/devcouncil/live/cards.py +7 -7
  251. package/src/devcouncil/live/models.py +4 -1
  252. package/src/devcouncil/live/reviewer.py +90 -11
  253. package/src/devcouncil/live/signals.py +4 -2
  254. package/src/devcouncil/live/tasks.py +12 -3
  255. package/src/devcouncil/live/transcripts.py +69 -2
  256. package/src/devcouncil/llm/cache.py +5 -6
  257. package/src/devcouncil/llm/model_defaults.yaml +10 -10
  258. package/src/devcouncil/llm/provider.py +647 -73
  259. package/src/devcouncil/llm/router.py +271 -46
  260. package/src/devcouncil/llm/semantic_bridge.py +614 -0
  261. package/src/devcouncil/optimization/gepa_agent.py +6 -4
  262. package/src/devcouncil/optimization/skillopt.py +9 -5
  263. package/src/devcouncil/planning/arbiter_service.py +12 -3
  264. package/src/devcouncil/planning/correction_manifest.py +107 -10
  265. package/src/devcouncil/planning/plan_difficulty.py +69 -0
  266. package/src/devcouncil/planning/plan_service.py +5 -2
  267. package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
  268. package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
  269. package/src/devcouncil/planning/question_conversion.py +56 -0
  270. package/src/devcouncil/planning/spec_service.py +9 -3
  271. package/src/devcouncil/repo/ci_scaffold.py +197 -1
  272. package/src/devcouncil/repo/gitignore.py +1 -2
  273. package/src/devcouncil/reporting/evidence_export.py +124 -0
  274. package/src/devcouncil/reporting/evidence_html.py +210 -0
  275. package/src/devcouncil/reporting/json_report.py +16 -12
  276. package/src/devcouncil/reporting/markdown_report.py +38 -9
  277. package/src/devcouncil/reporting/mcp_resources.py +142 -0
  278. package/src/devcouncil/reporting/report_builder.py +40 -4
  279. package/src/devcouncil/reporting/task_provenance.py +42 -0
  280. package/src/devcouncil/reporting/verdict.py +75 -0
  281. package/src/devcouncil/skills/library/README.md +1 -0
  282. package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
  283. package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
  284. package/src/devcouncil/skills/library/devcouncil.md +93 -0
  285. package/src/devcouncil/skills/registry.py +43 -12
  286. package/src/devcouncil/storage/db.py +57 -11
  287. package/src/devcouncil/storage/models.py +6 -0
  288. package/src/devcouncil/storage/native.py +5 -3
  289. package/src/devcouncil/storage/repositories.py +50 -18
  290. package/src/devcouncil/telemetry/context.py +28 -0
  291. package/src/devcouncil/telemetry/cost.py +4 -5
  292. package/src/devcouncil/telemetry/logging_setup.py +78 -11
  293. package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
  294. package/src/devcouncil/telemetry/stages.py +27 -2
  295. package/src/devcouncil/telemetry/tracker.py +50 -13
  296. package/src/devcouncil/ui/dashboard.py +120 -8
  297. package/src/devcouncil/utils/fsio.py +58 -0
  298. package/src/devcouncil/utils/git_snapshot.py +112 -0
  299. package/src/devcouncil/utils/json_persist.py +53 -0
  300. package/src/devcouncil/utils/proc.py +89 -0
  301. package/src/devcouncil/verification/acceptance_compiler.py +36 -13
  302. package/src/devcouncil/verification/ad_hoc_check.py +95 -3
  303. package/src/devcouncil/verification/checks/__init__.py +41 -0
  304. package/src/devcouncil/verification/checks/acceptance.py +39 -0
  305. package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
  306. package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
  307. package/src/devcouncil/verification/checks/command_evidence.py +148 -0
  308. package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
  309. package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
  310. package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
  311. package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
  312. package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
  313. package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
  314. package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
  315. package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
  316. package/src/devcouncil/verification/checks/planned_files.py +98 -0
  317. package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
  318. package/src/devcouncil/verification/checks/stale_map.py +80 -0
  319. package/src/devcouncil/verification/checks/stub_scan.py +71 -0
  320. package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
  321. package/src/devcouncil/verification/checks/wiring.py +216 -0
  322. package/src/devcouncil/verification/claims/__init__.py +23 -0
  323. package/src/devcouncil/verification/claims/checks.py +395 -0
  324. package/src/devcouncil/verification/claims/mapper.py +168 -0
  325. package/src/devcouncil/verification/claims/models.py +39 -0
  326. package/src/devcouncil/verification/claims/transcript.py +92 -0
  327. package/src/devcouncil/verification/claims/verdict.py +88 -0
  328. package/src/devcouncil/verification/command_evidence.py +170 -0
  329. package/src/devcouncil/verification/command_malformation.py +147 -0
  330. package/src/devcouncil/verification/command_runner.py +164 -0
  331. package/src/devcouncil/verification/coverage_measurement.py +292 -0
  332. package/src/devcouncil/verification/diff_coverage.py +151 -0
  333. package/src/devcouncil/verification/difficulty.py +296 -0
  334. package/src/devcouncil/verification/effort_heuristics.py +178 -0
  335. package/src/devcouncil/verification/gap_ids.py +63 -0
  336. package/src/devcouncil/verification/gate_cache.py +194 -0
  337. package/src/devcouncil/verification/gate_selector.py +344 -0
  338. package/src/devcouncil/verification/git_diff_fallback.py +272 -0
  339. package/src/devcouncil/verification/implementation_reviewer.py +13 -0
  340. package/src/devcouncil/verification/incremental_check.py +241 -0
  341. package/src/devcouncil/verification/next_actions.py +60 -1
  342. package/src/devcouncil/verification/rigor_analytics.py +130 -0
  343. package/src/devcouncil/verification/sandbox.py +38 -11
  344. package/src/devcouncil/verification/stub_detector.py +369 -0
  345. package/src/devcouncil/verification/test_resolver.py +67 -1
  346. package/src/devcouncil/verification/verifier.py +137 -1666
  347. package/src/devcouncil/verification/verify_orchestration.py +610 -0
  348. package/src/devcouncil/verification/verify_setup.py +176 -0
  349. package/src/devcouncil/verification/wiki_refresh.py +208 -0
  350. package/src/semantic_layer/__init__.py +58 -0
  351. package/src/semantic_layer/benchmark.py +75 -0
  352. package/src/semantic_layer/cache.py +290 -0
  353. package/src/semantic_layer/compressor.py +137 -0
  354. package/src/semantic_layer/config.py +75 -0
  355. package/src/semantic_layer/embeddings.py +69 -0
  356. package/src/semantic_layer/llm_backends.py +99 -0
  357. package/src/semantic_layer/pipeline.py +111 -0
  358. package/src/semantic_layer/router.py +128 -0
  359. package/src/semantic_layer/tuner.py +72 -0
  360. package/uv.lock +973 -9
  361. package/src/devcouncil/artifacts/migrations.py +0 -20
  362. package/src/devcouncil/artifacts/schemas.py +0 -23
  363. package/src/devcouncil/artifacts/serializer.py +0 -21
  364. package/src/devcouncil/integrations/gitnexus.py +0 -70
  365. package/src/devcouncil/integrations/graphify.py +0 -34
@@ -1,4 +1,5 @@
1
- import json
1
+ from devcouncil.utils.json_persist import dump_json
2
+ import logging
2
3
  from pathlib import Path
3
4
 
4
5
  import typer
@@ -6,9 +7,11 @@ from rich.console import Console
6
7
  from rich.table import Table
7
8
 
8
9
  from devcouncil.telemetry.cost import group_cost
10
+ from devcouncil.telemetry.stages import log_stage, log_step
9
11
 
10
12
  app = typer.Typer(help="Inspect DevCouncil model-call cost, grouped by task and run.")
11
13
  console = Console()
14
+ logger = logging.getLogger(__name__)
12
15
 
13
16
 
14
17
  @app.command("show")
@@ -23,35 +26,141 @@ def show(
23
26
  grouped under ``(unattributed)``.
24
27
  """
25
28
  root = project_root.expanduser().resolve()
26
- summary = group_cost(root)
29
+ from devcouncil.telemetry.logging_setup import set_log_dir
30
+ set_log_dir(root)
31
+ logger.info("dev cost show: json=%s", json_format)
27
32
 
28
- if json_format:
29
- typer.echo(json.dumps(summary, indent=2))
30
- return
33
+ with log_stage("cost", project_root=root):
34
+ log_step("cost/1: aggregating model-call ledger", project_root=root, trace=True)
35
+ summary = group_cost(root)
31
36
 
32
- console.print(
33
- f"[bold]Total Cost:[/bold] ${summary['total_cost']:.4f} "
34
- f"across {summary['total_calls']} model call(s)"
35
- )
36
-
37
- def _render(title: str, groups: dict) -> None:
38
- if not groups:
37
+ if json_format:
38
+ typer.echo(dump_json(summary, indent=2))
39
+ log_step("cost/complete", project_root=root, trace=True)
39
40
  return
40
- table = Table(title=title)
41
- table.add_column("Group", style="cyan")
42
- table.add_column("Cost ($)", justify="right")
43
- table.add_column("Calls", justify="right")
44
- table.add_column("Prompt", justify="right")
45
- table.add_column("Completion", justify="right")
46
- for name, stats in sorted(groups.items(), key=lambda kv: kv[1]["cost"], reverse=True):
47
- table.add_row(
48
- name,
49
- f"{stats['cost']:.4f}",
50
- str(stats["calls"]),
51
- str(stats["prompt_tokens"]),
52
- str(stats["completion_tokens"]),
41
+
42
+ console.print(
43
+ f"[bold]Total Cost:[/bold] ${summary['total_cost']:.4f} "
44
+ f"across {summary['total_calls']} model call(s)"
45
+ )
46
+
47
+ def _render(title: str, groups: dict) -> None:
48
+ if not groups:
49
+ return
50
+ table = Table(title=title)
51
+ table.add_column("Group", style="cyan")
52
+ table.add_column("Cost ($)", justify="right")
53
+ table.add_column("Calls", justify="right")
54
+ table.add_column("Prompt", justify="right")
55
+ table.add_column("Completion", justify="right")
56
+ for name, stats in sorted(groups.items(), key=lambda kv: kv[1]["cost"], reverse=True):
57
+ table.add_row(
58
+ name,
59
+ f"{stats['cost']:.4f}",
60
+ str(stats["calls"]),
61
+ str(stats["prompt_tokens"]),
62
+ str(stats["completion_tokens"]),
63
+ )
64
+ console.print(table)
65
+
66
+ _render("Cost by Task", summary["by_task"])
67
+ _render("Cost by Run", summary["by_run"])
68
+ log_step("cost/complete", project_root=root, trace=True)
69
+
70
+
71
+ @app.command("budget")
72
+ def budget(
73
+ set_value: float = typer.Option(
74
+ None,
75
+ "--set",
76
+ help="Set the advisory spend budget in USD (telemetry.cost_budget_usd in config.yaml).",
77
+ ),
78
+ clear: bool = typer.Option(False, "--clear", help="Remove the configured budget."),
79
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
80
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
81
+ ):
82
+ """Show or configure the advisory model-spend budget.
83
+
84
+ Reports the configured budget (``telemetry.cost_budget_usd``), spend-to-date from
85
+ the local ``model_calls.jsonl`` ledger, and the remainder. WARN-ONLY: when
86
+ cumulative spend crosses the budget the telemetry tracker logs a warning, but no
87
+ run is ever blocked.
88
+ """
89
+ root = project_root.expanduser().resolve()
90
+ from devcouncil.telemetry.logging_setup import set_log_dir
91
+ set_log_dir(root)
92
+ logger.info("dev cost budget: set=%s clear=%s json=%s", set_value, clear, json_format)
93
+
94
+ if set_value is not None and clear:
95
+ console.print("[red]Use either --set or --clear, not both.[/red]")
96
+ raise typer.Exit(code=2)
97
+
98
+ with log_stage("cost", project_root=root, subcommand="budget"):
99
+ if set_value is not None or clear:
100
+ import yaml
101
+
102
+ config_path = root / ".devcouncil" / "config.yaml"
103
+ if not config_path.exists():
104
+ console.print(f"[red]Config not found at {config_path}. Run 'dev init' first.[/red]")
105
+ raise typer.Exit(code=1)
106
+ if set_value is not None and set_value <= 0:
107
+ console.print("[red]--set expects a positive USD amount (e.g. --set 5.00).[/red]")
108
+ raise typer.Exit(code=2)
109
+
110
+ log_step("cost/1: updating telemetry.cost_budget_usd", project_root=root, trace=True)
111
+ with open(config_path, encoding="utf-8") as f:
112
+ raw_config = yaml.safe_load(f) or {}
113
+ if set_value is not None:
114
+ raw_config.setdefault("telemetry", {})["cost_budget_usd"] = float(set_value)
115
+ else:
116
+ telemetry_section = raw_config.get("telemetry")
117
+ if isinstance(telemetry_section, dict):
118
+ telemetry_section.pop("cost_budget_usd", None)
119
+ if not telemetry_section:
120
+ raw_config.pop("telemetry", None)
121
+ with open(config_path, "w", encoding="utf-8") as f:
122
+ yaml.dump(raw_config, f, default_flow_style=False)
123
+ if set_value is not None:
124
+ console.print(f"[green]Set telemetry.cost_budget_usd = {float(set_value):.2f}[/green]")
125
+ else:
126
+ console.print("[green]Cleared telemetry.cost_budget_usd.[/green]")
127
+
128
+ log_step("cost/2: aggregating model-call ledger", project_root=root, trace=True)
129
+ try:
130
+ from devcouncil.app.config import load_config
131
+
132
+ budget_usd = load_config(root).telemetry.cost_budget_usd
133
+ except Exception:
134
+ budget_usd = None
135
+ spend = float(group_cost(root)["total_cost"])
136
+ remaining = None if budget_usd is None else budget_usd - spend
137
+
138
+ if json_format:
139
+ typer.echo(
140
+ dump_json(
141
+ {
142
+ "budget_usd": budget_usd,
143
+ "spend_usd": spend,
144
+ "remaining_usd": remaining,
145
+ "over_budget": bool(budget_usd is not None and spend > budget_usd),
146
+ },
147
+ indent=2,
148
+ )
53
149
  )
54
- console.print(table)
150
+ log_step("cost/complete", project_root=root, trace=True)
151
+ return
55
152
 
56
- _render("Cost by Task", summary["by_task"])
57
- _render("Cost by Run", summary["by_run"])
153
+ if budget_usd is None:
154
+ console.print("[bold]Budget:[/bold] not configured (set one with 'dev cost budget --set 5.00')")
155
+ console.print(f"[bold]Spend to date:[/bold] ${spend:.4f}")
156
+ else:
157
+ console.print(f"[bold]Budget:[/bold] ${budget_usd:.2f}")
158
+ console.print(f"[bold]Spend to date:[/bold] ${spend:.4f}")
159
+ if remaining is not None and remaining < 0:
160
+ console.print(
161
+ f"[bold]Remaining:[/bold] [red]-${abs(remaining):.4f} "
162
+ "(over budget — warn-only, runs are never blocked)[/red]"
163
+ )
164
+ else:
165
+ console.print(f"[bold]Remaining:[/bold] ${remaining:.4f}")
166
+ log_step("cost/complete", project_root=root, trace=True)
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  from pathlib import Path
2
3
  import webbrowser
3
4
 
@@ -6,9 +7,11 @@ from rich.console import Console
6
7
 
7
8
  from devcouncil.cli.commands.init import initialize_project
8
9
  from devcouncil.ui.dashboard import run_dashboard
10
+ from devcouncil.telemetry.stages import log_stage, log_step
9
11
 
10
12
  app = typer.Typer(help="Serve the live DevCouncil dashboard.")
11
13
  console = Console()
14
+ logger = logging.getLogger(__name__)
12
15
 
13
16
 
14
17
  @app.callback(invoke_without_command=True)
@@ -23,9 +26,14 @@ def dashboard(
23
26
  if ctx.invoked_subcommand is not None:
24
27
  return
25
28
  root = project_root.expanduser().resolve()
29
+ from devcouncil.telemetry.logging_setup import set_log_dir
30
+ set_log_dir(root)
31
+ logger.info("dev dashboard: host=%s port=%d", host, port)
26
32
  initialize_project(root, quiet=True)
27
33
  url = f"http://{host}:{port}"
28
- console.print(f"Serving DevCouncil dashboard at {url}")
29
- if open_browser:
30
- webbrowser.open(url)
31
- run_dashboard(root, host=host, port=port)
34
+ with log_stage("dashboard", project_root=root, host=host, port=port):
35
+ log_step("dashboard/1: starting server", project_root=root, trace=True)
36
+ console.print(f"Serving DevCouncil dashboard at {url}")
37
+ if open_browser:
38
+ webbrowser.open(url)
39
+ run_dashboard(root, host=host, port=port)
@@ -0,0 +1,249 @@
1
+ """``dev debug`` — persistent DAP control and runtime traces."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import List, Optional
8
+
9
+ import typer
10
+ from rich.console import Console
11
+
12
+ from devcouncil.codeintel.debug.broker_client import DebugBrokerClient
13
+ from devcouncil.codeintel.debug.consent import require_debug_consent, set_debug_consent
14
+ from devcouncil.codeintel.debug.discovery import adapter_by_id, discover_adapters
15
+
16
+ app = typer.Typer(name="debug", help="Control DAP adapters and capture runtime evidence.", add_completion=False)
17
+ console = Console()
18
+ status = Console(stderr=True)
19
+
20
+
21
+ def _root(path: Path) -> Path:
22
+ return path.expanduser().resolve()
23
+
24
+
25
+ def _require_consent(root: Path) -> None:
26
+ try:
27
+ require_debug_consent(root)
28
+ except PermissionError as exc:
29
+ status.print(f"[yellow]{exc}[/yellow]")
30
+ raise typer.Exit(code=2) from None
31
+
32
+
33
+ def _config(value: str) -> dict:
34
+ parsed = json.loads(value or "{}")
35
+ if not isinstance(parsed, dict):
36
+ raise typer.BadParameter("--config-json must contain a JSON object")
37
+ return parsed
38
+
39
+
40
+ def _adapter_command(adapter_id: str, command: Optional[List[str]]) -> list[str]:
41
+ if command:
42
+ return list(command)
43
+ adapter = adapter_by_id(adapter_id)
44
+ if adapter is None:
45
+ raise typer.BadParameter(f"Adapter {adapter_id!r} was not discovered; use repeatable --adapter-command")
46
+ return list(adapter.command)
47
+
48
+
49
+ def _initial_breakpoints(values: Optional[List[str]]) -> dict[str, list[int]]:
50
+ result: dict[str, list[int]] = {}
51
+ for value in values or []:
52
+ source, separator, raw_line = value.rpartition(":")
53
+ if not separator or not source:
54
+ raise typer.BadParameter("--breakpoint must use PATH:LINE")
55
+ try:
56
+ line = int(raw_line)
57
+ except ValueError:
58
+ raise typer.BadParameter("--breakpoint line must be an integer") from None
59
+ result.setdefault(str(Path(source).expanduser().resolve()), []).append(line)
60
+ return result
61
+
62
+
63
+ @app.command("discover")
64
+ def discover(
65
+ project_root: Path = typer.Option(Path("."), "--project-root"),
66
+ consent: bool = typer.Option(False, "--consent", help="Persist the one-time debugger opt-in."),
67
+ json_output: bool = typer.Option(False, "--json"),
68
+ ) -> None:
69
+ root = _root(project_root)
70
+ if consent:
71
+ set_debug_consent(root, True)
72
+ _require_consent(root)
73
+ rows = [adapter.as_dict() for adapter in discover_adapters()]
74
+ if json_output:
75
+ typer.echo(json.dumps({"consent": True, "adapters": rows}, indent=2))
76
+ return
77
+ for row in rows:
78
+ requests = "/".join(row["requests"])
79
+ version = row["version"] or "unknown version"
80
+ console.print(f"{row['id']}: {row['path']} {version} requests={requests} sha256={row['executable_hash']}")
81
+ if not rows:
82
+ console.print("No supported DAP adapters discovered.")
83
+
84
+
85
+ def _start(
86
+ request: str,
87
+ adapter: str,
88
+ adapter_command: Optional[List[str]],
89
+ config_json: str,
90
+ breakpoints: Optional[List[str]],
91
+ project_root: Path,
92
+ ) -> None:
93
+ root = _root(project_root)
94
+ _require_consent(root)
95
+ client = DebugBrokerClient(root)
96
+ client.ensure_started()
97
+ result = client.call("start", {
98
+ "adapter_command": _adapter_command(adapter, adapter_command),
99
+ "request": request,
100
+ "arguments": _config(config_json),
101
+ "initial_breakpoints": _initial_breakpoints(breakpoints),
102
+ })
103
+ typer.echo(json.dumps(result, indent=2))
104
+
105
+
106
+ @app.command("start")
107
+ def start(
108
+ adapter: str = typer.Option("debugpy", "--adapter"),
109
+ adapter_command: Optional[List[str]] = typer.Option(None, "--adapter-command"),
110
+ config_json: str = typer.Option("{}", "--config-json"),
111
+ breakpoint: Optional[List[str]] = typer.Option(None, "--breakpoint", help="Set PATH:LINE before launch."),
112
+ project_root: Path = typer.Option(Path("."), "--project-root"),
113
+ ) -> None:
114
+ """Launch a debuggee through a persistent local DAP broker."""
115
+ _start("launch", adapter, adapter_command, config_json, breakpoint, project_root)
116
+
117
+
118
+ @app.command("attach")
119
+ def attach(
120
+ adapter: str = typer.Option("debugpy", "--adapter"),
121
+ adapter_command: Optional[List[str]] = typer.Option(None, "--adapter-command"),
122
+ config_json: str = typer.Option("{}", "--config-json"),
123
+ breakpoint: Optional[List[str]] = typer.Option(None, "--breakpoint", help="Set PATH:LINE before attach."),
124
+ project_root: Path = typer.Option(Path("."), "--project-root"),
125
+ ) -> None:
126
+ """Attach to a debuggee through a persistent local DAP broker."""
127
+ _start("attach", adapter, adapter_command, config_json, breakpoint, project_root)
128
+
129
+
130
+ @app.command("break")
131
+ def set_breakpoint(
132
+ session_id: str = typer.Argument(...),
133
+ source: Path = typer.Argument(...),
134
+ lines: List[int] = typer.Argument(...),
135
+ project_root: Path = typer.Option(Path("."), "--project-root"),
136
+ ) -> None:
137
+ _require_consent(_root(project_root))
138
+ result = DebugBrokerClient(_root(project_root)).call("breakpoints", {
139
+ "session_id": session_id, "source": str(source), "lines": lines,
140
+ })
141
+ typer.echo(json.dumps(result, indent=2))
142
+
143
+
144
+ def _control(action: str, session_id: str, thread_id: int | None, project_root: Path) -> None:
145
+ _require_consent(_root(project_root))
146
+ result = DebugBrokerClient(_root(project_root)).call("control", {
147
+ "session_id": session_id, "debug_action": action, "thread_id": thread_id,
148
+ })
149
+ typer.echo(json.dumps(result, indent=2))
150
+
151
+
152
+ @app.command("continue")
153
+ def continue_(session_id: str, thread_id: Optional[int] = typer.Option(None, "--thread"), project_root: Path = typer.Option(Path("."), "--project-root")) -> None:
154
+ _control("continue", session_id, thread_id, project_root)
155
+
156
+
157
+ @app.command("pause")
158
+ def pause(session_id: str, thread_id: Optional[int] = typer.Option(None, "--thread"), project_root: Path = typer.Option(Path("."), "--project-root")) -> None:
159
+ _control("pause", session_id, thread_id, project_root)
160
+
161
+
162
+ @app.command("step")
163
+ def step(session_id: str, kind: str = typer.Option("next", "--kind", help="next|stepIn|stepOut"), thread_id: int = typer.Option(..., "--thread"), project_root: Path = typer.Option(Path("."), "--project-root")) -> None:
164
+ _control(kind, session_id, thread_id, project_root)
165
+
166
+
167
+ def _inspect(operation: str, session_id: str, arguments: dict, project_root: Path) -> None:
168
+ _require_consent(_root(project_root))
169
+ result = DebugBrokerClient(_root(project_root)).call("inspect", {
170
+ "session_id": session_id, "operation": operation, "arguments": arguments,
171
+ })
172
+ typer.echo(json.dumps(result, indent=2))
173
+
174
+
175
+ @app.command("threads")
176
+ def threads(session_id: str, project_root: Path = typer.Option(Path("."), "--project-root")) -> None:
177
+ _inspect("threads", session_id, {}, project_root)
178
+
179
+
180
+ @app.command("stack")
181
+ def stack(session_id: str, thread_id: int = typer.Option(..., "--thread"), project_root: Path = typer.Option(Path("."), "--project-root")) -> None:
182
+ _inspect("stackTrace", session_id, {"threadId": thread_id}, project_root)
183
+
184
+
185
+ @app.command("scopes")
186
+ def scopes(session_id: str, frame_id: int = typer.Option(..., "--frame"), project_root: Path = typer.Option(Path("."), "--project-root")) -> None:
187
+ _inspect("scopes", session_id, {"frameId": frame_id}, project_root)
188
+
189
+
190
+ @app.command("variables")
191
+ def variables(session_id: str, reference: int = typer.Option(..., "--reference"), project_root: Path = typer.Option(Path("."), "--project-root")) -> None:
192
+ _inspect("variables", session_id, {"variablesReference": reference}, project_root)
193
+
194
+
195
+ @app.command("evaluate")
196
+ def evaluate(
197
+ session_id: str,
198
+ expression: str,
199
+ frame_id: Optional[int] = typer.Option(None, "--frame"),
200
+ allow_side_effects: bool = typer.Option(False, "--allow-side-effects"),
201
+ project_root: Path = typer.Option(Path("."), "--project-root"),
202
+ ) -> None:
203
+ if not allow_side_effects:
204
+ raise typer.BadParameter("evaluate can execute code; pass --allow-side-effects explicitly")
205
+ _require_consent(_root(project_root))
206
+ result = DebugBrokerClient(_root(project_root)).call("evaluate", {
207
+ "session_id": session_id,
208
+ "expression": expression,
209
+ "frame_id": frame_id,
210
+ "allow_side_effects": True,
211
+ })
212
+ typer.echo(json.dumps(result, indent=2))
213
+
214
+
215
+ @app.command("trace")
216
+ def trace(
217
+ script: Optional[Path] = typer.Option(None, "--python-script"),
218
+ node_script: Optional[Path] = typer.Option(None, "--node-script"),
219
+ args: Optional[List[str]] = typer.Option(None, "--arg"),
220
+ import_path: Optional[Path] = typer.Option(None, "--import"),
221
+ project_root: Path = typer.Option(Path("."), "--project-root"),
222
+ ) -> None:
223
+ from devcouncil.codeintel.debug.tracing import NodeCpuProfileProvider, PythonTraceProvider, import_runtime_trace
224
+
225
+ root = _root(project_root)
226
+ _require_consent(root)
227
+ if script is not None:
228
+ result = PythonTraceProvider(root).run(script, args or [])
229
+ elif node_script is not None:
230
+ result = NodeCpuProfileProvider(root).run(node_script, args or [])
231
+ elif import_path is not None:
232
+ result = import_runtime_trace(root, import_path)
233
+ else:
234
+ raise typer.BadParameter("provide --python-script, --node-script, or --import")
235
+ typer.echo(json.dumps(result, indent=2))
236
+
237
+
238
+ @app.command("stop")
239
+ def stop(
240
+ session_id: str,
241
+ keep_debuggee: bool = typer.Option(False, "--keep-debuggee"),
242
+ project_root: Path = typer.Option(Path("."), "--project-root"),
243
+ ) -> None:
244
+ root = _root(project_root)
245
+ _require_consent(root)
246
+ result = DebugBrokerClient(root).call("stop", {
247
+ "session_id": session_id, "terminate_debuggee": not keep_debuggee,
248
+ })
249
+ typer.echo(json.dumps(result, indent=2))
@@ -21,9 +21,12 @@ from devcouncil.knowledge.design_conformance import (
21
21
  STYLE_EXTENSIONS,
22
22
  scan_files,
23
23
  )
24
+ from devcouncil.telemetry.stages import log_stage, log_step
25
+ import logging
24
26
 
25
27
  app = typer.Typer(help="Lint, export, and inspect a design.md design system.")
26
28
  console = Console()
29
+ logger = logging.getLogger(__name__)
27
30
 
28
31
  # Where a project's design system is looked for, in order.
29
32
  _DEFAULT_PATHS = (
@@ -73,23 +76,30 @@ def lint(
73
76
  ):
74
77
  """Validate a design system: broken token refs, contrast, ordering, orphans."""
75
78
  root = project_root.expanduser().resolve()
76
- target = _resolve_path(path, root)
77
- if target is None:
78
- console.print("[red]No design.md found.[/red] Looked for: " + ", ".join(_DEFAULT_PATHS))
79
- raise typer.Exit(code=1)
80
-
81
- findings = lint_design(parse_design_md(target))
82
- if not findings:
83
- console.print(f"[green] {target} passed all design.md lint rules.[/green]")
84
- return
85
-
86
- errors = [f for f in findings if f.severity == "error"]
87
- color = {"error": "red", "warning": "yellow", "info": "cyan"}
88
- console.print(f"[bold]{len(findings)} finding(s) in {target}:[/bold]")
89
- for f in findings:
90
- console.print(f" [{color.get(f.severity, 'white')}]{f.format()}[/{color.get(f.severity, 'white')}]")
91
- if errors:
92
- raise typer.Exit(code=1)
79
+ from devcouncil.telemetry.logging_setup import set_log_dir
80
+ set_log_dir(root)
81
+ logger.info("dev design lint")
82
+ with log_stage("design", project_root=root, subcommand="lint"):
83
+ log_step("design/1: linting design system", project_root=root, trace=True)
84
+ target = _resolve_path(path, root)
85
+ if target is None:
86
+ console.print("[red]No design.md found.[/red] Looked for: " + ", ".join(_DEFAULT_PATHS))
87
+ raise typer.Exit(code=1)
88
+
89
+ findings = lint_design(parse_design_md(target))
90
+ if not findings:
91
+ console.print(f"[green]{target} passed all design.md lint rules.[/green]")
92
+ log_step("design/complete", project_root=root, trace=True)
93
+ return
94
+
95
+ errors = [f for f in findings if f.severity == "error"]
96
+ color = {"error": "red", "warning": "yellow", "info": "cyan"}
97
+ console.print(f"[bold]{len(findings)} finding(s) in {target}:[/bold]")
98
+ for f in findings:
99
+ console.print(f" [{color.get(f.severity, 'white')}]{f.format()}[/{color.get(f.severity, 'white')}]")
100
+ log_step("design/complete", project_root=root, trace=True)
101
+ if errors:
102
+ raise typer.Exit(code=1)
93
103
 
94
104
 
95
105
  @app.command("export")