devcouncil 0.3.1 → 0.4.1

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 (366) hide show
  1. package/README.md +92 -33
  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 +56 -40
  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 +964 -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 +281 -0
  219. package/src/devcouncil/integrations/mcp/handlers/graph.py +34 -0
  220. package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
  221. package/src/devcouncil/integrations/mcp/handlers/knowledge.py +28 -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 +87 -0
  229. package/src/devcouncil/integrations/mcp/handlers/read.py +103 -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 +69 -0
  233. package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
  234. package/src/devcouncil/integrations/mcp/handlers/status.py +199 -0
  235. package/src/devcouncil/integrations/mcp/handlers/task.py +88 -0
  236. package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +922 -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 +53 -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 +325 -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/summary.py +21 -3
  255. package/src/devcouncil/live/tasks.py +12 -3
  256. package/src/devcouncil/live/transcripts.py +69 -2
  257. package/src/devcouncil/llm/cache.py +5 -6
  258. package/src/devcouncil/llm/model_defaults.yaml +10 -10
  259. package/src/devcouncil/llm/provider.py +647 -73
  260. package/src/devcouncil/llm/router.py +271 -46
  261. package/src/devcouncil/llm/semantic_bridge.py +614 -0
  262. package/src/devcouncil/optimization/gepa_agent.py +6 -4
  263. package/src/devcouncil/optimization/skillopt.py +9 -5
  264. package/src/devcouncil/planning/arbiter_service.py +12 -3
  265. package/src/devcouncil/planning/correction_manifest.py +107 -10
  266. package/src/devcouncil/planning/plan_difficulty.py +69 -0
  267. package/src/devcouncil/planning/plan_service.py +5 -2
  268. package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
  269. package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
  270. package/src/devcouncil/planning/question_conversion.py +56 -0
  271. package/src/devcouncil/planning/spec_service.py +9 -3
  272. package/src/devcouncil/repo/ci_scaffold.py +197 -1
  273. package/src/devcouncil/repo/gitignore.py +1 -2
  274. package/src/devcouncil/reporting/evidence_export.py +124 -0
  275. package/src/devcouncil/reporting/evidence_html.py +210 -0
  276. package/src/devcouncil/reporting/json_report.py +16 -12
  277. package/src/devcouncil/reporting/markdown_report.py +38 -9
  278. package/src/devcouncil/reporting/mcp_resources.py +142 -0
  279. package/src/devcouncil/reporting/report_builder.py +40 -4
  280. package/src/devcouncil/reporting/task_provenance.py +42 -0
  281. package/src/devcouncil/reporting/verdict.py +75 -0
  282. package/src/devcouncil/skills/library/README.md +1 -0
  283. package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
  284. package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
  285. package/src/devcouncil/skills/library/devcouncil.md +93 -0
  286. package/src/devcouncil/skills/registry.py +43 -12
  287. package/src/devcouncil/storage/db.py +57 -11
  288. package/src/devcouncil/storage/models.py +6 -0
  289. package/src/devcouncil/storage/native.py +5 -3
  290. package/src/devcouncil/storage/repositories.py +50 -18
  291. package/src/devcouncil/telemetry/context.py +28 -0
  292. package/src/devcouncil/telemetry/cost.py +4 -5
  293. package/src/devcouncil/telemetry/logging_setup.py +78 -11
  294. package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
  295. package/src/devcouncil/telemetry/stages.py +27 -2
  296. package/src/devcouncil/telemetry/tracker.py +50 -13
  297. package/src/devcouncil/ui/dashboard.py +120 -8
  298. package/src/devcouncil/utils/fsio.py +58 -0
  299. package/src/devcouncil/utils/git_snapshot.py +112 -0
  300. package/src/devcouncil/utils/json_persist.py +53 -0
  301. package/src/devcouncil/utils/proc.py +89 -0
  302. package/src/devcouncil/verification/acceptance_compiler.py +36 -13
  303. package/src/devcouncil/verification/ad_hoc_check.py +95 -3
  304. package/src/devcouncil/verification/checks/__init__.py +41 -0
  305. package/src/devcouncil/verification/checks/acceptance.py +39 -0
  306. package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
  307. package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
  308. package/src/devcouncil/verification/checks/command_evidence.py +148 -0
  309. package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
  310. package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
  311. package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
  312. package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
  313. package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
  314. package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
  315. package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
  316. package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
  317. package/src/devcouncil/verification/checks/planned_files.py +98 -0
  318. package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
  319. package/src/devcouncil/verification/checks/stale_map.py +80 -0
  320. package/src/devcouncil/verification/checks/stub_scan.py +71 -0
  321. package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
  322. package/src/devcouncil/verification/checks/wiring.py +216 -0
  323. package/src/devcouncil/verification/claims/__init__.py +23 -0
  324. package/src/devcouncil/verification/claims/checks.py +395 -0
  325. package/src/devcouncil/verification/claims/mapper.py +168 -0
  326. package/src/devcouncil/verification/claims/models.py +39 -0
  327. package/src/devcouncil/verification/claims/transcript.py +92 -0
  328. package/src/devcouncil/verification/claims/verdict.py +88 -0
  329. package/src/devcouncil/verification/command_evidence.py +170 -0
  330. package/src/devcouncil/verification/command_malformation.py +147 -0
  331. package/src/devcouncil/verification/command_runner.py +164 -0
  332. package/src/devcouncil/verification/coverage_measurement.py +292 -0
  333. package/src/devcouncil/verification/diff_coverage.py +151 -0
  334. package/src/devcouncil/verification/difficulty.py +296 -0
  335. package/src/devcouncil/verification/effort_heuristics.py +178 -0
  336. package/src/devcouncil/verification/gap_ids.py +63 -0
  337. package/src/devcouncil/verification/gate_cache.py +194 -0
  338. package/src/devcouncil/verification/gate_selector.py +344 -0
  339. package/src/devcouncil/verification/git_diff_fallback.py +272 -0
  340. package/src/devcouncil/verification/implementation_reviewer.py +13 -0
  341. package/src/devcouncil/verification/incremental_check.py +241 -0
  342. package/src/devcouncil/verification/next_actions.py +60 -1
  343. package/src/devcouncil/verification/rigor_analytics.py +130 -0
  344. package/src/devcouncil/verification/sandbox.py +38 -11
  345. package/src/devcouncil/verification/stub_detector.py +369 -0
  346. package/src/devcouncil/verification/test_resolver.py +67 -1
  347. package/src/devcouncil/verification/verifier.py +137 -1666
  348. package/src/devcouncil/verification/verify_orchestration.py +610 -0
  349. package/src/devcouncil/verification/verify_setup.py +176 -0
  350. package/src/devcouncil/verification/wiki_refresh.py +208 -0
  351. package/src/semantic_layer/__init__.py +58 -0
  352. package/src/semantic_layer/benchmark.py +75 -0
  353. package/src/semantic_layer/cache.py +290 -0
  354. package/src/semantic_layer/compressor.py +137 -0
  355. package/src/semantic_layer/config.py +75 -0
  356. package/src/semantic_layer/embeddings.py +69 -0
  357. package/src/semantic_layer/llm_backends.py +99 -0
  358. package/src/semantic_layer/pipeline.py +111 -0
  359. package/src/semantic_layer/router.py +128 -0
  360. package/src/semantic_layer/tuner.py +72 -0
  361. package/uv.lock +973 -9
  362. package/src/devcouncil/artifacts/migrations.py +0 -20
  363. package/src/devcouncil/artifacts/schemas.py +0 -23
  364. package/src/devcouncil/artifacts/serializer.py +0 -21
  365. package/src/devcouncil/integrations/gitnexus.py +0 -70
  366. package/src/devcouncil/integrations/graphify.py +0 -34
@@ -1,5 +1,7 @@
1
- import json
1
+ from devcouncil.utils.json_persist import dump_json
2
+ import logging
2
3
  from pathlib import Path
4
+ from typing import Literal, cast, Any
3
5
 
4
6
  import typer
5
7
  from rich.console import Console
@@ -7,14 +9,45 @@ from rich.table import Table
7
9
  from devcouncil.cli.commands.init import initialize_project
8
10
  from devcouncil.storage.db import get_db
9
11
  from devcouncil.storage.repositories import TaskRepository
12
+ from devcouncil.storage.native import TaskLeaseRepository
13
+ from devcouncil.telemetry.stages import log_stage, log_step
14
+ from devcouncil.telemetry.traces import TraceLogger
10
15
 
11
16
  app = typer.Typer()
12
17
  console = Console()
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def _lease_public_view(lease, *, expired: bool) -> dict[str, Any]:
22
+ return {
23
+ "owner": lease.owner,
24
+ "agent": lease.agent,
25
+ "expires_at": lease.expires_at,
26
+ "expired": expired,
27
+ }
28
+
29
+
30
+ def _active_leases_by_task(session) -> dict[str, tuple[Any, bool]]:
31
+ return {
32
+ lease.task_id: (lease, expired)
33
+ for lease, expired in TaskLeaseRepository(session).list_leases(active_only=True)
34
+ }
35
+
36
+
37
+ def _task_row_payload(task, leases_by_task: dict[str, tuple[Any, bool]]) -> Any:
38
+ payload = task.model_dump()
39
+ lease_pair = leases_by_task.get(task.id)
40
+ payload["lease"] = _lease_public_view(lease_pair[0], expired=lease_pair[1]) if lease_pair else None
41
+ return payload
42
+
13
43
 
14
44
  @app.callback(invoke_without_command=True)
15
45
  def tasks(
16
46
  ctx: typer.Context,
17
47
  json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
48
+ status: str | None = typer.Option(None, "--status", help="Filter tasks by status."),
49
+ limit: int = typer.Option(100, "--limit", min=1, max=500, help="Max tasks to return (JSON pagination)."),
50
+ offset: int = typer.Option(0, "--offset", min=0, help="Skip this many tasks before returning results."),
18
51
  project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
19
52
  ):
20
53
  """
@@ -24,32 +57,228 @@ def tasks(
24
57
  return
25
58
 
26
59
  root = project_root.expanduser().resolve()
60
+ from devcouncil.telemetry.logging_setup import set_log_dir
61
+ set_log_dir(root)
62
+ logger.info("dev tasks: json=%s", json_format)
27
63
  initialize_project(root, quiet=True)
28
64
  db = get_db(root)
29
65
  if not db:
30
66
  console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
31
67
  raise typer.Exit(code=1)
32
68
 
33
- with db.get_session() as session:
34
- task_repo = TaskRepository(session)
35
- tasks_list = task_repo.get_all()
69
+ with log_stage("tasks", project_root=root):
70
+ log_step("tasks/1: loading task graph", project_root=root, trace=True)
71
+ with db.get_session() as session:
72
+ task_repo = TaskRepository(session)
73
+ tasks_list = task_repo.get_all()
74
+ if status:
75
+ tasks_list = [t for t in tasks_list if t.status == status]
76
+ total = len(tasks_list)
77
+ window = tasks_list[offset:offset + limit]
78
+ leases_by_task = _active_leases_by_task(session)
79
+
80
+ if json_format:
81
+ typer.echo(dump_json({
82
+ "tasks": [_task_row_payload(task, leases_by_task) for task in window],
83
+ "total": total,
84
+ "offset": offset,
85
+ "limit": limit,
86
+ "returned": len(window),
87
+ }, indent=2))
88
+ log_step("tasks/complete", project_root=root, count=len(window), trace=True)
89
+ return
90
+
91
+ tasks_list = window
92
+
93
+ if not tasks_list:
94
+ console.print("No tasks found. Run 'dev plan' to generate tasks.")
95
+ log_step("tasks/complete", project_root=root, count=0, trace=True)
96
+ return
97
+
98
+ table = Table(title="DevCouncil Tasks")
99
+ table.add_column("Task ID", style="cyan", no_wrap=True)
100
+ table.add_column("Title", style="white")
101
+ table.add_column("Status", style="magenta")
102
+ table.add_column("Priority", style="yellow")
103
+ table.add_column("Lease", style="blue")
104
+ table.add_column("Linked Reqs", style="green")
105
+
106
+ for t in tasks_list:
107
+ reqs = ", ".join(t.requirement_ids)
108
+ priority = t.priority or "-"
109
+ lease_pair = leases_by_task.get(t.id)
110
+ lease_label = lease_pair[0].owner if lease_pair else "—"
111
+ table.add_row(t.id, t.title, t.status, priority, lease_label, reqs)
112
+
113
+ console.print(table)
114
+ log_step("tasks/complete", project_root=root, count=len(tasks_list), trace=True)
115
+
36
116
 
117
+ @app.command("cancel")
118
+ def cancel_task(
119
+ task_id: str = typer.Argument(..., help="Task ID to cancel, e.g. TASK-001."),
120
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
121
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
122
+ ):
123
+ """Cancel a task that is not already done or cancelled."""
124
+ root = project_root.expanduser().resolve()
125
+ from devcouncil.telemetry.logging_setup import set_log_dir
126
+ set_log_dir(root)
127
+ initialize_project(root, quiet=True)
128
+ db = get_db(root)
129
+ if not db:
130
+ message = "DevCouncil state is unavailable in this directory."
37
131
  if json_format:
38
- typer.echo(json.dumps({"tasks": [task.model_dump() for task in tasks_list]}, indent=2))
39
- return
40
-
41
- if not tasks_list:
42
- console.print("No tasks found. Run 'dev plan' to generate tasks.")
43
- return
132
+ typer.echo(dump_json({"ok": False, "error": message}, indent=2))
133
+ else:
134
+ console.print(f"[red]{message}[/red]")
135
+ raise typer.Exit(code=1)
136
+
137
+ with log_stage("tasks", project_root=root, subcommand="cancel"):
138
+ with db.get_session() as session:
139
+ task_repo = TaskRepository(session)
140
+ task = task_repo.get_by_id(task_id)
141
+ if task is None:
142
+ message = f"Task {task_id} not found."
143
+ if json_format:
144
+ typer.echo(dump_json({"ok": False, "error": message}, indent=2))
145
+ else:
146
+ console.print(f"[red]{message}[/red]")
147
+ raise typer.Exit(code=1)
148
+ if task.status in {"done", "cancelled"}:
149
+ message = f"Task {task_id} cannot be cancelled (status={task.status})."
150
+ if json_format:
151
+ typer.echo(dump_json({"ok": False, "error": message, "task_id": task_id, "status": task.status}, indent=2))
152
+ else:
153
+ console.print(f"[red]{message}[/red]")
154
+ raise typer.Exit(code=2)
155
+ previous_status = task.status
156
+ task.status = "cancelled"
157
+ task_repo.save(task)
158
+ TraceLogger(root).log_event(
159
+ "task_cancelled",
160
+ {"task_id": task_id, "previous_status": previous_status},
161
+ task_id=task_id,
162
+ summary=f"Cancelled {task_id} (was {previous_status})",
163
+ )
164
+ payload = {"ok": True, "task_id": task_id, "status": "cancelled", "previous_status": previous_status}
165
+ if json_format:
166
+ typer.echo(dump_json(payload, indent=2))
167
+ else:
168
+ console.print(f"[green]Cancelled {task_id}[/green] (was {previous_status})")
44
169
 
45
- table = Table(title="DevCouncil Tasks")
46
- table.add_column("Task ID", style="cyan", no_wrap=True)
47
- table.add_column("Title", style="white")
48
- table.add_column("Status", style="magenta")
49
- table.add_column("Linked Reqs", style="green")
50
170
 
51
- for t in tasks_list:
52
- reqs = ", ".join(t.requirement_ids)
53
- table.add_row(t.id, t.title, t.status, reqs)
171
+ VALID_PRIORITIES = frozenset({"high", "medium", "low"})
54
172
 
55
- console.print(table)
173
+
174
+ @app.command("reprioritize")
175
+ def reprioritize_task(
176
+ task_id: str = typer.Argument(..., help="Task ID to reprioritize, e.g. TASK-001."),
177
+ priority: str = typer.Option(..., "--priority", help="New priority: high, medium, or low."),
178
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
179
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
180
+ ):
181
+ """Set or update a task's optional priority hint."""
182
+ normalized = priority.strip().lower()
183
+ if normalized not in VALID_PRIORITIES:
184
+ message = f"--priority must be one of: {', '.join(sorted(VALID_PRIORITIES))}."
185
+ if json_format:
186
+ typer.echo(dump_json({"ok": False, "error": message}, indent=2))
187
+ else:
188
+ console.print(f"[red]{message}[/red]")
189
+ raise typer.Exit(code=2)
190
+
191
+ root = project_root.expanduser().resolve()
192
+ from devcouncil.telemetry.logging_setup import set_log_dir
193
+ set_log_dir(root)
194
+ initialize_project(root, quiet=True)
195
+ db = get_db(root)
196
+ if not db:
197
+ message = "DevCouncil state is unavailable in this directory."
198
+ if json_format:
199
+ typer.echo(dump_json({"ok": False, "error": message}, indent=2))
200
+ else:
201
+ console.print(f"[red]{message}[/red]")
202
+ raise typer.Exit(code=1)
203
+
204
+ with log_stage("tasks", project_root=root, subcommand="reprioritize"):
205
+ with db.get_session() as session:
206
+ task_repo = TaskRepository(session)
207
+ task = task_repo.get_by_id(task_id)
208
+ if task is None:
209
+ message = f"Task {task_id} not found."
210
+ if json_format:
211
+ typer.echo(dump_json({"ok": False, "error": message}, indent=2))
212
+ else:
213
+ console.print(f"[red]{message}[/red]")
214
+ raise typer.Exit(code=1)
215
+ previous = task.priority
216
+ task.priority = cast(Literal["high", "medium", "low"], normalized)
217
+ task_repo.save(task)
218
+ payload = {
219
+ "ok": True,
220
+ "task_id": task_id,
221
+ "priority": normalized,
222
+ "previous_priority": previous,
223
+ }
224
+ if json_format:
225
+ typer.echo(dump_json(payload, indent=2))
226
+ else:
227
+ prev_label = previous or "(none)"
228
+ console.print(f"[green]Set {task_id} priority[/green] to {normalized} (was {prev_label})")
229
+
230
+
231
+ @app.command("edit")
232
+ def edit_task(
233
+ task_id: str = typer.Argument(..., help="Task ID to edit, e.g. TASK-001."),
234
+ title: str | None = typer.Option(None, "--title", help="New task title."),
235
+ description: str | None = typer.Option(None, "--description", help="New task description."),
236
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
237
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
238
+ ):
239
+ """Edit a task's title and/or description."""
240
+ if title is None and description is None:
241
+ message = "Provide at least one of --title or --description."
242
+ if json_format:
243
+ typer.echo(dump_json({"ok": False, "error": message}, indent=2))
244
+ else:
245
+ console.print(f"[red]{message}[/red]")
246
+ raise typer.Exit(code=2)
247
+
248
+ root = project_root.expanduser().resolve()
249
+ from devcouncil.telemetry.logging_setup import set_log_dir
250
+ set_log_dir(root)
251
+ initialize_project(root, quiet=True)
252
+ db = get_db(root)
253
+ if not db:
254
+ message = "DevCouncil state is unavailable in this directory."
255
+ if json_format:
256
+ typer.echo(dump_json({"ok": False, "error": message}, indent=2))
257
+ else:
258
+ console.print(f"[red]{message}[/red]")
259
+ raise typer.Exit(code=1)
260
+
261
+ with log_stage("tasks", project_root=root, subcommand="edit"):
262
+ with db.get_session() as session:
263
+ task_repo = TaskRepository(session)
264
+ task = task_repo.get_by_id(task_id)
265
+ if task is None:
266
+ message = f"Task {task_id} not found."
267
+ if json_format:
268
+ typer.echo(dump_json({"ok": False, "error": message}, indent=2))
269
+ else:
270
+ console.print(f"[red]{message}[/red]")
271
+ raise typer.Exit(code=1)
272
+ changes: dict[str, tuple[str, str]] = {}
273
+ if title is not None:
274
+ changes["title"] = (task.title, title)
275
+ task.title = title
276
+ if description is not None:
277
+ changes["description"] = (task.description, description)
278
+ task.description = description
279
+ task_repo.save(task)
280
+ payload = {"ok": True, "task_id": task_id, "changes": {k: {"from": v[0], "to": v[1]} for k, v in changes.items()}}
281
+ if json_format:
282
+ typer.echo(dump_json(payload, indent=2))
283
+ else:
284
+ console.print(f"[green]Updated {task_id}[/green]: {', '.join(changes)}")
@@ -1,4 +1,9 @@
1
- import json
1
+ """DELIBERATELY UNINSTRUMENTED: this command's subject *is* the trace/log
2
+ stream. Emitting log records or trace events from here mutates what the user
3
+ asked to inspect (``log_step`` appends to the very traces.jsonl being tailed)
4
+ and pollutes the machine-readable JSONL/--json output consumers parse. An
5
+ introspection command must not modify what it introspects."""
6
+
2
7
  import time
3
8
  from pathlib import Path
4
9
  from typing import Optional
@@ -7,6 +12,7 @@ import typer
7
12
  from rich.console import Console
8
13
 
9
14
  from devcouncil.telemetry.traces import read_trace_events, read_trace_events_since
15
+ from devcouncil.utils.json_persist import dump_json
10
16
 
11
17
  app = typer.Typer(help="Inspect DevCouncil trace events.")
12
18
  console = Console()
@@ -37,15 +43,15 @@ def tail(
37
43
  agent gets only the events appended after the cursor plus a ``next_cursor`` to
38
44
  pass back on the next poll, so each poll is O(new) rather than O(all).
39
45
  """
40
- project_root = project_root.expanduser().resolve()
46
+ root = project_root.expanduser().resolve()
41
47
 
42
48
  # Incremental cursor mode: any of --since / --json / explicit --no-follow.
43
49
  incremental = since is not None or json_summary
44
50
  if incremental:
45
- events, next_cursor = read_trace_events_since(project_root, since)
51
+ events, next_cursor = read_trace_events_since(root, since)
46
52
  if json_summary:
47
53
  typer.echo(
48
- json.dumps(
54
+ dump_json(
49
55
  {
50
56
  "events": [event.model_dump(by_alias=True) for event in events],
51
57
  "next_cursor": next_cursor,
@@ -59,7 +65,7 @@ def tail(
59
65
  else:
60
66
  console.print(
61
67
  f"{event.timestamp} {event.type} "
62
- f"{event.task_id or '-'} {event.summary or json.dumps(event.details)}"
68
+ f"{event.task_id or '-'} {event.summary or dump_json(event.details)}"
63
69
  )
64
70
  console.print(f"[dim]next_cursor: {next_cursor}[/dim]")
65
71
  return
@@ -67,7 +73,7 @@ def tail(
67
73
  printed = 0
68
74
 
69
75
  def emit_new(start_index: int) -> int:
70
- events = list(read_trace_events(project_root))
76
+ events = list(read_trace_events(root))
71
77
  selected = events[start_index:]
72
78
  for event in selected:
73
79
  if jsonl:
@@ -75,11 +81,11 @@ def tail(
75
81
  else:
76
82
  console.print(
77
83
  f"{event.timestamp} {event.type} "
78
- f"{event.task_id or '-'} {event.summary or json.dumps(event.details)}"
84
+ f"{event.task_id or '-'} {event.summary or dump_json(event.details)}"
79
85
  )
80
86
  return len(events)
81
87
 
82
- events = list(read_trace_events(project_root))
88
+ events = list(read_trace_events(root))
83
89
  start = max(0, len(events) - limit)
84
90
  printed = emit_new(start)
85
91
 
@@ -1,6 +1,6 @@
1
1
  import typer
2
2
  import asyncio
3
- import json
3
+ from devcouncil.utils.json_persist import dump_json
4
4
  import logging
5
5
  from rich.console import Console
6
6
  from rich.table import Table
@@ -19,6 +19,7 @@ from devcouncil.app.config import load_config, get_api_key
19
19
  from devcouncil.app.state_machine import ProjectPhase
20
20
  from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
21
21
  from devcouncil.telemetry.traces import TraceLogger
22
+ from devcouncil.telemetry.stages import log_stage, log_step
22
23
 
23
24
  console = Console()
24
25
  logger = logging.getLogger(__name__)
@@ -64,11 +65,24 @@ def verify(
64
65
  db = get_db(root)
65
66
  if not db:
66
67
  if json_format:
67
- typer.echo(json.dumps({"ok": False, "error": "DevCouncil state is unavailable in this directory."}, indent=2))
68
+ typer.echo(dump_json({"ok": False, "error": "DevCouncil state is unavailable in this directory."}, indent=2))
68
69
  else:
69
70
  console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
70
71
  return
71
72
 
73
+ with log_stage("verify", project_root=root, task_id=task_id or "ALL", sandbox=sandbox):
74
+ _run_verify_body(
75
+ root, task_id, sandbox, json_format, db,
76
+ )
77
+
78
+
79
+ def _run_verify_body(
80
+ root: Path,
81
+ task_id: Optional[str],
82
+ sandbox: str,
83
+ json_format: bool,
84
+ db,
85
+ ) -> None:
72
86
  with db.get_session() as session:
73
87
  task_repo = TaskRepository(session)
74
88
  req_repo = RequirementRepository(session)
@@ -80,7 +94,7 @@ def verify(
80
94
  if not tasks:
81
95
  missing = f"Task {task_id} not found." if task_id else "No tasks found to verify."
82
96
  if json_format:
83
- typer.echo(json.dumps({"ok": False, "error": missing}, indent=2))
97
+ typer.echo(dump_json({"ok": False, "error": missing}, indent=2))
84
98
  else:
85
99
  console.print(f"[red]{missing}[/red]")
86
100
  return
@@ -96,8 +110,9 @@ def verify(
96
110
  provider = create_provider(config.models.provider, api_key, project_root=root, provider_prefs=config.provider)
97
111
  role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
98
112
  router = ModelRouter(provider, role_config, project_root=root)
99
- except Exception:
100
- pass
113
+ except Exception as e:
114
+ # Verification still runs, but silently without the LLM review layer.
115
+ logger.warning("Failed to build model router, verifying without LLM review: %s", e)
101
116
 
102
117
  from devcouncil.verification.sandbox import get_sandbox
103
118
 
@@ -111,13 +126,19 @@ def verify(
111
126
  per_task_gaps: dict[str, list] = {}
112
127
 
113
128
  for task in tasks:
129
+ log_step(
130
+ f"verify task {task.id}",
131
+ project_root=root,
132
+ task_id=task.id,
133
+ sandbox=sandbox,
134
+ )
114
135
  if sandbox != "local":
115
136
  commands = task.expected_tests or task.allowed_commands
116
137
  sandbox_result = get_sandbox(sandbox, root).run(task, commands, reqs)
117
138
  if sandbox_result.status == "unsupported":
118
139
  message = f"Sandbox {sandbox} is unavailable."
119
140
  if json_format:
120
- typer.echo(json.dumps({"ok": False, "error": message, "sandbox": sandbox}, indent=2))
141
+ typer.echo(dump_json({"ok": False, "error": message, "sandbox": sandbox}, indent=2))
121
142
  else:
122
143
  console.print(f"[red]{message}[/red]")
123
144
  return
@@ -134,7 +155,7 @@ def verify(
134
155
  "gaps": [],
135
156
  })
136
157
  if json_format:
137
- typer.echo(json.dumps({
158
+ typer.echo(dump_json({
138
159
  "ok": False,
139
160
  "task_id": task.id,
140
161
  "sandbox": sandbox,
@@ -231,6 +252,10 @@ def verify(
231
252
  "diff_empty": outcome.diff_empty if outcome else False,
232
253
  "coverage_measured": outcome.coverage_measured if outcome else False,
233
254
  "coverage_skipped_reason": outcome.coverage_skipped_reason if outcome else None,
255
+ # Anti-laziness rigor: the task's difficulty classification and which
256
+ # escalations (blocking stub/effort gates, enforced coverage) applied.
257
+ "difficulty": outcome.difficulty if outcome else None,
258
+ "rigor_applied": list(outcome.rigor_applied) if outcome else [],
234
259
  })
235
260
 
236
261
  # Cross-task acceptance reconciliation (only meaningful across the full set).
@@ -276,7 +301,7 @@ def verify(
276
301
  )
277
302
 
278
303
  if json_format:
279
- typer.echo(json.dumps({
304
+ typer.echo(dump_json({
280
305
  "ok": blocked_tasks == 0,
281
306
  "verified_tasks": len(tasks),
282
307
  "blocked_tasks": blocked_tasks,
@@ -1,9 +1,13 @@
1
+ import logging
1
2
  import typer
2
3
  from rich.console import Console
3
4
  import importlib.metadata
4
5
 
6
+ from devcouncil.telemetry.stages import log_stage, log_step
7
+
5
8
  app = typer.Typer()
6
9
  console = Console()
10
+ logger = logging.getLogger(__name__)
7
11
 
8
12
  @app.callback(invoke_without_command=True)
9
13
  def version(ctx: typer.Context):
@@ -12,9 +16,13 @@ def version(ctx: typer.Context):
12
16
  """
13
17
  if ctx.invoked_subcommand is not None:
14
18
  return
15
-
16
- try:
17
- ver = importlib.metadata.version("devcouncil")
18
- console.print(f"DevCouncil version: [bold cyan]{ver}[/bold cyan]")
19
- except importlib.metadata.PackageNotFoundError:
20
- console.print("DevCouncil version: [yellow]unknown (editable/uninstalled)[/yellow]")
19
+
20
+ logger.info("dev version")
21
+ with log_stage("version"):
22
+ log_step("version/1: reading package metadata", trace=True)
23
+ try:
24
+ ver = importlib.metadata.version("devcouncil")
25
+ console.print(f"DevCouncil version: [bold cyan]{ver}[/bold cyan]")
26
+ except importlib.metadata.PackageNotFoundError:
27
+ console.print("DevCouncil version: [yellow]unknown (editable/uninstalled)[/yellow]")
28
+ log_step("version/complete", trace=True)