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
@@ -0,0 +1,79 @@
1
+ """`dev provenance` and `dev resource` — audit trail and MCP corpus reads."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from pathlib import Path
7
+
8
+ import typer
9
+
10
+ from devcouncil.reporting.mcp_resources import list_mcp_resource_uris, read_mcp_resource
11
+ from devcouncil.reporting.task_provenance import task_provenance_payload
12
+ from devcouncil.telemetry.logging_setup import set_log_dir
13
+ from devcouncil.telemetry.stages import log_stage, log_step
14
+ from devcouncil.utils.json_persist import dump_json
15
+
16
+ resource_app = typer.Typer(help="Read MCP corpus resources through the CLI service layer.")
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def provenance(
21
+ task_id: str = typer.Argument(..., help="Task ID to inspect."),
22
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
23
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
24
+ ) -> None:
25
+ """Show file changes, verification runs, and diff-coverage evidence for a task."""
26
+ root = project_root.expanduser().resolve()
27
+ set_log_dir(root)
28
+ logger.info("dev provenance: task=%s json=%s", task_id, json_format)
29
+
30
+ with log_stage("provenance", project_root=root, task_id=task_id):
31
+ payload = task_provenance_payload(root, task_id)
32
+ if json_format:
33
+ typer.echo(dump_json(payload, indent=2))
34
+ log_step("provenance/complete", project_root=root, task_id=task_id, trace=True)
35
+ if not payload.get("ok", True):
36
+ raise typer.Exit(code=1)
37
+ return
38
+
39
+ if not payload.get("ok", True):
40
+ typer.echo(payload.get("error", "provenance unavailable"))
41
+ raise typer.Exit(code=1)
42
+
43
+ typer.echo(
44
+ f"Task {task_id}: {len(payload.get('file_changes', []))} file change(s), "
45
+ f"{len(payload.get('verification_runs', []))} verification run(s), "
46
+ f"{len(payload.get('diff_coverage', []))} diff-coverage record(s)."
47
+ )
48
+ log_step("provenance/complete", project_root=root, task_id=task_id, trace=True)
49
+
50
+
51
+ @resource_app.command("list")
52
+ def resource_list(
53
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
54
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
55
+ ) -> None:
56
+ """List browsable MCP corpus resource URIs."""
57
+ root = project_root.expanduser().resolve()
58
+ set_log_dir(root)
59
+ resources = list_mcp_resource_uris(root)
60
+ if json_format:
61
+ typer.echo(dump_json({"resources": resources}, indent=2))
62
+ return
63
+ for item in resources:
64
+ typer.echo(f"{item['uri']} ({item['mimeType']}) {item['name']}")
65
+
66
+
67
+ @resource_app.command("read")
68
+ def resource_read(
69
+ uri: str = typer.Argument(..., help="Resource URI, e.g. devcouncil://tasks"),
70
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
71
+ ) -> None:
72
+ """Read one MCP corpus resource (stdout body)."""
73
+ root = project_root.expanduser().resolve()
74
+ set_log_dir(root)
75
+ try:
76
+ typer.echo(read_mcp_resource(root, uri), nl=False)
77
+ except ValueError as exc:
78
+ typer.echo(str(exc))
79
+ raise typer.Exit(code=1) from exc
@@ -8,6 +8,7 @@ from devcouncil.llm.provider import create_provider, validate_model_provider
8
8
  from devcouncil.llm.router import ModelRouter
9
9
  from devcouncil.app.config import load_config, get_api_key
10
10
  from devcouncil.cli.commands.init import initialize_project
11
+ from devcouncil.telemetry.stages import log_stage, log_step
11
12
  import asyncio
12
13
  import logging
13
14
  from pathlib import Path
@@ -26,63 +27,73 @@ async def run_repair_flow(project_root: Path = Path(".")):
26
27
  console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
27
28
  return
28
29
 
29
- with db.get_session() as session:
30
- gap_repo = GapRepository(session)
31
- task_repo = TaskRepository(session)
30
+ with log_stage("repair", project_root=root):
31
+ log_step("repair/1: loading blocking gaps", project_root=root)
32
+ with db.get_session() as session:
33
+ gap_repo = GapRepository(session)
34
+ task_repo = TaskRepository(session)
32
35
 
33
- all_gaps = gap_repo.get_all()
34
- blocking_gaps = [g for g in all_gaps if g.blocking]
36
+ all_gaps = gap_repo.get_all()
37
+ blocking_gaps = [g for g in all_gaps if g.blocking]
35
38
 
36
- if not blocking_gaps:
37
- logger.info("dev repair: no blocking gaps; nothing to repair")
38
- console.print("[green]No blocking gaps found. Nothing to repair![/green]")
39
- return
39
+ if not blocking_gaps:
40
+ logger.info("dev repair: no blocking gaps; nothing to repair")
41
+ console.print("[green]No blocking gaps found. Nothing to repair![/green]")
42
+ return
40
43
 
41
- logger.info("dev repair: %d blocking gap(s) across %d task(s)", len(blocking_gaps), len({g.task_id for g in blocking_gaps if g.task_id}))
42
- console.print(f"Found [bold]{len(blocking_gaps)}[/bold] blocking gaps. Orchestrating repair plan...")
44
+ logger.info("dev repair: %d blocking gap(s) across %d task(s)", len(blocking_gaps), len({g.task_id for g in blocking_gaps if g.task_id}))
45
+ console.print(f"Found [bold]{len(blocking_gaps)}[/bold] blocking gaps. Orchestrating repair plan...")
43
46
 
44
- # Load router when credentials are available. Correction manifests have a
45
- # deterministic fallback path, so missing model credentials must not block
46
- # repair artifact generation.
47
- repair_service = None
48
- config = None
49
- try:
50
- config = load_config(root)
51
- validate_model_provider(config.models.provider)
52
- api_key = get_api_key(config.models.provider, root)
53
- except (FileNotFoundError, ValueError) as e:
54
- logger.warning("dev repair: no LLM provider (%s); using deterministic manifest fallback", e)
55
- console.print(f"[yellow]{e}[/yellow]")
56
- else:
57
- provider = create_provider(config.models.provider, api_key, project_root=root, provider_prefs=config.provider)
58
- role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
59
- router = ModelRouter(provider, role_config, project_root=root)
60
- repair_service = RepairService(router)
61
- context_builder = ContextBuilder(root)
62
-
63
- # Build minimal context for repair.
64
- project_context = context_builder.get_structure_summary()
47
+ # Load router when credentials are available. Correction manifests have a
48
+ # deterministic fallback path, so missing model credentials must not block
49
+ # repair artifact generation.
50
+ repair_service = None
51
+ config = None
52
+ try:
53
+ config = load_config(root)
54
+ validate_model_provider(config.models.provider)
55
+ api_key = get_api_key(config.models.provider, root)
56
+ except (FileNotFoundError, ValueError) as e:
57
+ logger.warning("dev repair: no LLM provider (%s); using deterministic manifest fallback", e)
58
+ console.print(f"[yellow]{e}[/yellow]")
59
+ else:
60
+ provider = create_provider(config.models.provider, api_key, project_root=root, provider_prefs=config.provider)
61
+ role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
62
+ router = ModelRouter(provider, role_config, project_root=root)
63
+ repair_service = RepairService(router)
64
+ context_builder = ContextBuilder(root)
65
65
 
66
- from devcouncil.planning.correction_manifest import write_correction_manifest
66
+ # Build minimal context for repair.
67
+ project_context = context_builder.get_structure_summary()
67
68
 
68
- task_ids = {gap.task_id for gap in blocking_gaps if gap.task_id}
69
- for scoped_task_id in task_ids:
70
- if scoped_task_id:
71
- path = write_correction_manifest(root, scoped_task_id, repair_service=repair_service, config=config)
72
- if path:
73
- console.print(f" - Wrote correction manifest [dim]{path}[/dim]")
69
+ from devcouncil.planning.correction_manifest import write_correction_manifest
74
70
 
75
- repair_count = 0
76
- if repair_service is not None:
77
- repair_output = await repair_service.generate_repair_plan(blocking_gaps, str(project_context))
78
- for task in repair_output.suggested_tasks:
79
- task.id = f"REPAIR-{task.id}"
80
- task_repo.save(task)
81
- repair_count += 1
82
- console.print(f" - Created intelligent repair task [bold]{task.id}[/bold]: {task.title}")
71
+ task_ids = {gap.task_id for gap in blocking_gaps if gap.task_id}
72
+ log_step("repair/2: writing correction manifests", project_root=root, task_count=len(task_ids))
73
+ for scoped_task_id in task_ids:
74
+ if scoped_task_id:
75
+ path = write_correction_manifest(root, scoped_task_id, repair_service=repair_service, config=config)
76
+ if path:
77
+ console.print(f" - Wrote correction manifest [dim]{path}[/dim]")
83
78
 
84
- logger.info("dev repair complete: generated %d repair task(s)", repair_count)
85
- console.print(f"\n[green]Successfully generated {repair_count} repair tasks.[/green]")
79
+ repair_count = 0
80
+ if repair_service is not None:
81
+ log_step("repair/3: generating LLM repair plan", project_root=root)
82
+ repair_output = await repair_service.generate_repair_plan(blocking_gaps, str(project_context))
83
+ for task in repair_output.suggested_tasks:
84
+ task.id = f"REPAIR-{task.id}"
85
+ task_repo.save(task)
86
+ repair_count += 1
87
+ console.print(f" - Created intelligent repair task [bold]{task.id}[/bold]: {task.title}")
88
+
89
+ log_step(
90
+ "repair/complete",
91
+ project_root=root,
92
+ repair_tasks=repair_count,
93
+ trace=True,
94
+ )
95
+ logger.info("dev repair complete: generated %d repair task(s)", repair_count)
96
+ console.print(f"\n[green]Successfully generated {repair_count} repair tasks.[/green]")
86
97
 
87
98
  @app.callback(invoke_without_command=True)
88
99
  def repair(
@@ -1,3 +1,6 @@
1
+ from devcouncil.utils.json_persist import dump_json
2
+ from dataclasses import asdict
3
+
1
4
  import typer
2
5
  from rich.console import Console
3
6
  from rich.markdown import Markdown
@@ -8,15 +11,18 @@ from devcouncil.integrations.github import GitHubIntegration
8
11
  from devcouncil.integrations.pr_comments import GitHubPRCommenter, GitLabMRCommenter, build_pr_comment_body
9
12
  from devcouncil.artifacts.graph import ArtifactGraph
10
13
  from devcouncil.telemetry.traces import TraceLogger
14
+ from devcouncil.telemetry.stages import log_stage, log_step
11
15
  from devcouncil.cli.commands.init import initialize_project
12
16
  from devcouncil.live.summary import live_review_summary
13
17
  import asyncio
18
+ import logging
14
19
  import os
15
- import subprocess
16
20
  from pathlib import Path
21
+ from typing import Optional
17
22
 
18
23
  app = typer.Typer()
19
24
  console = Console()
25
+ logger = logging.getLogger(__name__)
20
26
 
21
27
  async def run_github_report(graph: ArtifactGraph, project_root: Path):
22
28
  token = os.environ.get("GITHUB_TOKEN")
@@ -27,8 +33,11 @@ async def run_github_report(graph: ArtifactGraph, project_root: Path):
27
33
  return
28
34
 
29
35
  try:
30
- # Detect current SHA
31
- sha = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=project_root).decode().strip()
36
+ sha = os.environ.get("GITHUB_SHA", "").strip()
37
+ if not sha:
38
+ from devcouncil.utils.proc import git_output
39
+
40
+ sha = git_output(["rev-parse", "HEAD"], cwd=project_root).strip()
32
41
  integration = GitHubIntegration(token, repo, sha)
33
42
  await integration.report_verification(graph)
34
43
  console.print(f"[green]Successfully reported to GitHub PR Checks for {repo} at {sha[:7]}[/green]")
@@ -78,6 +87,17 @@ def report(
78
87
  github: bool = typer.Option(False, "--github", help="Post report to GitHub PR Checks"),
79
88
  github_pr_comment: bool = typer.Option(False, "--github-pr-comment", help="Post report as a GitHub PR comment"),
80
89
  gitlab_pr_comment: bool = typer.Option(False, "--gitlab-pr-comment", help="Post report as a GitLab merge request comment"),
90
+ evidence_json: Optional[Path] = typer.Option(
91
+ None,
92
+ "--evidence-json",
93
+ help="Write the requirement→task→diff→evidence graph as reviewer-readable JSON to this path "
94
+ "(e.g. for a CI/PR artifact).",
95
+ ),
96
+ evidence_html: Optional[Path] = typer.Option(
97
+ None,
98
+ "--evidence-html",
99
+ help="Write a self-contained HTML evidence report (AC table + task/diff links) to this path.",
100
+ ),
81
101
  fail_on_blocking: bool = typer.Option(
82
102
  False,
83
103
  "--fail-on-blocking",
@@ -91,47 +111,135 @@ def report(
91
111
  if ctx.invoked_subcommand is not None:
92
112
  return
93
113
 
114
+ # Direct Python calls (e.g. from `dev go`) bypass Typer, leaving OptionInfo
115
+ # defaults in place of real values. Normalize them so `is not None` and
116
+ # truthiness checks behave as they do under CLI invocation.
117
+ if not isinstance(evidence_json, (Path, type(None))):
118
+ evidence_json = None
119
+ if not isinstance(evidence_html, (Path, type(None))):
120
+ evidence_html = None
121
+ if not isinstance(fail_on_blocking, bool):
122
+ fail_on_blocking = False
123
+
94
124
  root = project_root.expanduser().resolve()
125
+ from devcouncil.telemetry.logging_setup import set_log_dir
126
+ set_log_dir(root)
127
+ logger.info("dev report: json=%s planning_only=%s", json_format, planning_only)
95
128
  initialize_project(root, quiet=True)
96
129
  db = get_db(root)
97
130
  if not db:
98
131
  console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
99
132
  raise typer.Exit(code=1)
100
133
 
101
- with db.get_session() as session:
102
- graph_repo = ArtifactGraphRepository(session)
103
- graph = graph_repo.load_graph()
104
- live_review = live_review_summary(root)
105
- TraceLogger(root).log_event(
106
- "report_generated",
107
- {
108
- "json": json_format,
109
- "github": github,
110
- "github_pr_comment": github_pr_comment,
111
- "gitlab_pr_comment": gitlab_pr_comment,
112
- "planning_only": planning_only,
113
- },
114
- summary="Generated DevCouncil report",
115
- )
116
-
117
- if github:
118
- asyncio.run(run_github_report(graph, root))
119
- return
120
-
121
- if github_pr_comment:
122
- asyncio.run(run_github_pr_comment(graph, live_review=live_review))
123
- return
124
-
125
- if gitlab_pr_comment:
126
- asyncio.run(run_gitlab_mr_comment(graph, live_review=live_review))
127
- return
128
-
129
- if json_format:
130
- output = ReportBuilder.build_json(graph, live_review=live_review)
131
- typer.echo(output)
132
- else:
133
- output = ReportBuilder.build_markdown(graph, live_review=live_review)
134
- console.print(Markdown(output))
135
-
136
- if fail_on_blocking and graph.blocking_gaps():
137
- raise typer.Exit(code=1)
134
+ with log_stage("report", project_root=root, planning_only=planning_only):
135
+ log_step("report/1: loading artifact graph", project_root=root)
136
+ with db.get_session() as session:
137
+ graph_repo = ArtifactGraphRepository(session)
138
+ graph = graph_repo.load_graph()
139
+ live_review = live_review_summary(root)
140
+ TraceLogger(root).log_event(
141
+ "report_generated",
142
+ {
143
+ "json": json_format,
144
+ "github": github,
145
+ "github_pr_comment": github_pr_comment,
146
+ "gitlab_pr_comment": gitlab_pr_comment,
147
+ "evidence_json": evidence_json is not None,
148
+ "evidence_html": evidence_html is not None,
149
+ "planning_only": planning_only,
150
+ },
151
+ summary="Generated DevCouncil report",
152
+ )
153
+
154
+ if github:
155
+ log_step("report/2: posting to GitHub checks", project_root=root)
156
+ asyncio.run(run_github_report(graph, root))
157
+ if fail_on_blocking and graph.blocking_gaps():
158
+ raise typer.Exit(code=1)
159
+ return
160
+
161
+ if github_pr_comment:
162
+ log_step("report/2: posting GitHub PR comment", project_root=root)
163
+ asyncio.run(run_github_pr_comment(graph, live_review=live_review))
164
+ if fail_on_blocking and graph.blocking_gaps():
165
+ raise typer.Exit(code=1)
166
+ return
167
+
168
+ if gitlab_pr_comment:
169
+ log_step("report/2: posting GitLab MR comment", project_root=root)
170
+ asyncio.run(run_gitlab_mr_comment(graph, live_review=live_review))
171
+ if fail_on_blocking and graph.blocking_gaps():
172
+ raise typer.Exit(code=1)
173
+ return
174
+
175
+ if evidence_json is not None:
176
+ log_step("report/2: writing evidence-export JSON", project_root=root)
177
+ output_path = evidence_json.expanduser()
178
+ try:
179
+ output_path.parent.mkdir(parents=True, exist_ok=True)
180
+ output_path.write_text(
181
+ ReportBuilder.build_evidence_export(graph, live_review=live_review) + "\n",
182
+ encoding="utf-8",
183
+ )
184
+ except OSError as exc:
185
+ console.print(f"[red]Failed to write evidence export to {output_path}: {exc}[/red]")
186
+ raise typer.Exit(code=1) from exc
187
+ console.print(f"[green]Wrote evidence export to {output_path}[/green]")
188
+ if fail_on_blocking and graph.blocking_gaps():
189
+ raise typer.Exit(code=1)
190
+ return
191
+
192
+ if evidence_html is not None:
193
+ log_step("report/2: writing evidence-export HTML", project_root=root)
194
+ output_path = evidence_html.expanduser()
195
+ try:
196
+ output_path.parent.mkdir(parents=True, exist_ok=True)
197
+ output_path.write_text(
198
+ ReportBuilder.build_evidence_html(graph, live_review=live_review) + "\n",
199
+ encoding="utf-8",
200
+ )
201
+ except OSError as exc:
202
+ console.print(f"[red]Failed to write evidence HTML to {output_path}: {exc}[/red]")
203
+ raise typer.Exit(code=1) from exc
204
+ console.print(f"[green]Wrote evidence HTML to {output_path}[/green]")
205
+ if fail_on_blocking and graph.blocking_gaps():
206
+ raise typer.Exit(code=1)
207
+ return
208
+
209
+ log_step(
210
+ "report/2: rendering output",
211
+ project_root=root,
212
+ json_format=json_format,
213
+ trace=True,
214
+ )
215
+ if json_format:
216
+ output = ReportBuilder.build_json(graph, live_review=live_review)
217
+ typer.echo(output)
218
+ else:
219
+ output = ReportBuilder.build_markdown(graph, live_review=live_review)
220
+ console.print(Markdown(output))
221
+
222
+ if fail_on_blocking and graph.blocking_gaps():
223
+ raise typer.Exit(code=1)
224
+
225
+
226
+ @app.command("rigor")
227
+ def rigor_report(
228
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
229
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
230
+ ):
231
+ """Aggregate rigor-gap patterns across persisted verification runs.
232
+
233
+ Surfaces recurring stub/effort failures and repair-loop stats so teams can
234
+ tune ``verification.rigor`` thresholds from evidence rather than guesses.
235
+ """
236
+ from devcouncil.verification.rigor_analytics import build_rigor_report
237
+
238
+ root = project_root.expanduser().resolve()
239
+ initialize_project(root, quiet=True)
240
+ report = build_rigor_report(root)
241
+
242
+ if json_format:
243
+ typer.echo(dump_json(asdict(report), indent=2))
244
+ else:
245
+ console.print(Markdown(report.to_markdown()))
@@ -0,0 +1,104 @@
1
+ from devcouncil.utils.json_persist import dump_json
2
+ import logging
3
+ from pathlib import Path
4
+
5
+ import typer
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+ from devcouncil.cli.commands.init import initialize_project
10
+ from devcouncil.domain.task import Task
11
+ from devcouncil.storage.db import get_db
12
+ from devcouncil.storage.repositories import RequirementRepository, TaskRepository
13
+ from devcouncil.telemetry.stages import log_stage, log_step
14
+
15
+ console = Console()
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def _derive_requirement_status(req_id: str, tasks: list[Task]) -> str:
20
+ linked = [task for task in tasks if req_id in task.requirement_ids]
21
+ if not linked:
22
+ return "unmapped"
23
+ statuses = {task.status for task in linked}
24
+ if "blocked" in statuses:
25
+ return "blocked"
26
+ if statuses <= {"verified", "done"}:
27
+ return "verified"
28
+ if "running" in statuses:
29
+ return "in_progress"
30
+ return "planned"
31
+
32
+
33
+ def _requirements_payload(project_root: Path) -> dict:
34
+ initialize_project(project_root, quiet=True)
35
+ db = get_db(project_root)
36
+ if not db:
37
+ return {"initialized": False, "requirements": []}
38
+
39
+ with db.get_session() as session:
40
+ requirements = RequirementRepository(session).get_all()
41
+ tasks = TaskRepository(session).get_all()
42
+ rows = []
43
+ for req in requirements:
44
+ linked_count = sum(1 for task in tasks if req.id in task.requirement_ids)
45
+ rows.append(
46
+ {
47
+ "id": req.id,
48
+ "title": req.title,
49
+ "priority": req.priority,
50
+ "status": _derive_requirement_status(req.id, tasks),
51
+ "linked_task_count": linked_count,
52
+ "requirement": req.model_dump(),
53
+ }
54
+ )
55
+ rows.sort(key=lambda row: (row["priority"], row["id"]))
56
+ return {
57
+ "initialized": True,
58
+ "requirements": rows,
59
+ "total_count": len(rows),
60
+ }
61
+
62
+
63
+ def requirements(
64
+ json_format: bool = typer.Option(False, "--json", help="Output machine-readable JSON."),
65
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
66
+ ):
67
+ """List requirements with priority, status, and linked task counts."""
68
+ root = project_root.expanduser().resolve()
69
+ from devcouncil.telemetry.logging_setup import set_log_dir
70
+
71
+ set_log_dir(root)
72
+ logger.info("dev requirements: json=%s", json_format)
73
+
74
+ with log_stage("requirements", project_root=root):
75
+ log_step("requirements/1: loading requirements", project_root=root, trace=True)
76
+ payload = _requirements_payload(root)
77
+
78
+ if json_format:
79
+ typer.echo(dump_json(payload, indent=2))
80
+ log_step("requirements/complete", project_root=root, trace=True)
81
+ return
82
+
83
+ if not payload["initialized"]:
84
+ console.print("[yellow]DevCouncil state is not available in this directory.[/yellow]")
85
+ log_step("requirements/complete", project_root=root, trace=True)
86
+ return
87
+
88
+ table = Table(title="DevCouncil Requirements")
89
+ table.add_column("ID", style="cyan", no_wrap=True)
90
+ table.add_column("Priority")
91
+ table.add_column("Status", style="magenta")
92
+ table.add_column("Linked Tasks", justify="right")
93
+
94
+ for row in payload["requirements"]:
95
+ table.add_row(
96
+ row["id"],
97
+ row["priority"],
98
+ row["status"],
99
+ str(row["linked_task_count"]),
100
+ )
101
+
102
+ console.print(table)
103
+ console.print(f"\n[bold]{payload['total_count']}[/bold] requirement(s)")
104
+ log_step("requirements/complete", project_root=root, trace=True)
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import typer
2
3
  from rich.console import Console
3
4
  from sqlmodel import delete
@@ -6,8 +7,10 @@ from pathlib import Path
6
7
  from devcouncil.storage.db import get_db
7
8
  from devcouncil.storage.models import EvidenceModel, GapModel, RequirementModel, TaskModel
8
9
  from devcouncil.cli.commands.init import initialize_project
10
+ from devcouncil.telemetry.stages import log_stage, log_step
9
11
 
10
12
  console = Console()
13
+ logger = logging.getLogger(__name__)
11
14
 
12
15
 
13
16
  def reset_demo_state(
@@ -20,14 +23,20 @@ def reset_demo_state(
20
23
  raise typer.Exit(code=1)
21
24
 
22
25
  root = project_root.expanduser().resolve()
26
+ from devcouncil.telemetry.logging_setup import set_log_dir
27
+ set_log_dir(root)
28
+ logger.info("dev reset-demo-state")
23
29
  initialize_project(root, quiet=True)
24
30
  db = get_db(root)
25
31
  if not db:
26
32
  console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
27
33
  raise typer.Exit(code=1)
28
34
 
29
- with db.get_session() as session:
30
- for model in (EvidenceModel, GapModel, TaskModel, RequirementModel):
31
- session.exec(delete(model))
35
+ with log_stage("reset_demo_state", project_root=root):
36
+ log_step("reset_demo_state/1: clearing planning artifacts", project_root=root, trace=True)
37
+ with db.get_session() as session:
38
+ for model in (EvidenceModel, GapModel, TaskModel, RequirementModel):
39
+ session.exec(delete(model))
32
40
 
33
- console.print("[green]Cleared requirements, tasks, gaps, and evidence from local state.[/green]")
41
+ console.print("[green]Cleared requirements, tasks, gaps, and evidence from local state.[/green]")
42
+ log_step("reset_demo_state/complete", project_root=root, trace=True)