devcouncil 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/README.md +46 -30
  2. package/package.json +6 -2
  3. package/packages/codeintel-grammars/hatch_build.py +43 -0
  4. package/packages/codeintel-grammars/pyproject.toml +16 -0
  5. package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
  6. package/pyproject.toml +99 -4
  7. package/src/devcouncil/app/config.py +512 -20
  8. package/src/devcouncil/app/events.py +4 -23
  9. package/src/devcouncil/app/orchestrator.py +5 -0
  10. package/src/devcouncil/app/run_context.py +3 -3
  11. package/src/devcouncil/assets/__init__.py +4 -1
  12. package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
  13. package/src/devcouncil/campaign/__init__.py +71 -0
  14. package/src/devcouncil/campaign/bloom.py +137 -0
  15. package/src/devcouncil/campaign/dashboard.py +123 -0
  16. package/src/devcouncil/campaign/mailbox.py +305 -0
  17. package/src/devcouncil/campaign/notify.py +91 -0
  18. package/src/devcouncil/campaign/orchestrator.py +592 -0
  19. package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
  20. package/src/devcouncil/campaign/prompts/director.md +21 -0
  21. package/src/devcouncil/campaign/prompts/protocol.md +46 -0
  22. package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
  23. package/src/devcouncil/campaign/prompts/worker.md +24 -0
  24. package/src/devcouncil/campaign/roles.py +202 -0
  25. package/src/devcouncil/campaign/watcher.py +153 -0
  26. package/src/devcouncil/cli/commands/agents.py +24 -17
  27. package/src/devcouncil/cli/commands/artifacts.py +36 -27
  28. package/src/devcouncil/cli/commands/ast.py +12 -3
  29. package/src/devcouncil/cli/commands/baseline.py +21 -12
  30. package/src/devcouncil/cli/commands/boot.py +218 -0
  31. package/src/devcouncil/cli/commands/campaign.py +302 -0
  32. package/src/devcouncil/cli/commands/check.py +225 -12
  33. package/src/devcouncil/cli/commands/config.py +221 -74
  34. package/src/devcouncil/cli/commands/cost.py +137 -28
  35. package/src/devcouncil/cli/commands/dashboard.py +12 -4
  36. package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
  37. package/src/devcouncil/cli/commands/design.py +27 -17
  38. package/src/devcouncil/cli/commands/doctor.py +790 -8
  39. package/src/devcouncil/cli/commands/evidence.py +41 -20
  40. package/src/devcouncil/cli/commands/export.py +73 -0
  41. package/src/devcouncil/cli/commands/gaps.py +175 -0
  42. package/src/devcouncil/cli/commands/gated_write.py +76 -0
  43. package/src/devcouncil/cli/commands/go.py +220 -68
  44. package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
  45. package/src/devcouncil/cli/commands/handoff.py +45 -34
  46. package/src/devcouncil/cli/commands/hook.py +630 -85
  47. package/src/devcouncil/cli/commands/init.py +89 -30
  48. package/src/devcouncil/cli/commands/integrate.py +296 -1385
  49. package/src/devcouncil/cli/commands/lease.py +120 -0
  50. package/src/devcouncil/cli/commands/logs.py +12 -5
  51. package/src/devcouncil/cli/commands/lsp.py +40 -5
  52. package/src/devcouncil/cli/commands/map.py +317 -74
  53. package/src/devcouncil/cli/commands/mcp_server.py +12 -2
  54. package/src/devcouncil/cli/commands/okf.py +44 -6
  55. package/src/devcouncil/cli/commands/plan.py +184 -69
  56. package/src/devcouncil/cli/commands/prompt.py +26 -17
  57. package/src/devcouncil/cli/commands/provenance.py +79 -0
  58. package/src/devcouncil/cli/commands/repair.py +60 -49
  59. package/src/devcouncil/cli/commands/report.py +148 -40
  60. package/src/devcouncil/cli/commands/requirements.py +104 -0
  61. package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
  62. package/src/devcouncil/cli/commands/rollback.py +46 -35
  63. package/src/devcouncil/cli/commands/run.py +173 -8
  64. package/src/devcouncil/cli/commands/runs.py +298 -68
  65. package/src/devcouncil/cli/commands/scaffold.py +33 -12
  66. package/src/devcouncil/cli/commands/semantic.py +29 -14
  67. package/src/devcouncil/cli/commands/setup.py +103 -93
  68. package/src/devcouncil/cli/commands/shell.py +51 -42
  69. package/src/devcouncil/cli/commands/show.py +56 -42
  70. package/src/devcouncil/cli/commands/skills.py +29 -20
  71. package/src/devcouncil/cli/commands/status.py +80 -67
  72. package/src/devcouncil/cli/commands/task_gate.py +295 -0
  73. package/src/devcouncil/cli/commands/tasks.py +248 -19
  74. package/src/devcouncil/cli/commands/trace.py +14 -8
  75. package/src/devcouncil/cli/commands/verify.py +33 -8
  76. package/src/devcouncil/cli/commands/version.py +14 -6
  77. package/src/devcouncil/cli/commands/watch.py +49 -30
  78. package/src/devcouncil/cli/commands/watch_fs.py +30 -19
  79. package/src/devcouncil/cli/commands/wiki.py +278 -0
  80. package/src/devcouncil/cli/main.py +58 -1
  81. package/src/devcouncil/codeintel/__init__.py +16 -0
  82. package/src/devcouncil/codeintel/build_control.py +429 -0
  83. package/src/devcouncil/codeintel/build_worker.py +78 -0
  84. package/src/devcouncil/codeintel/debug/__init__.py +17 -0
  85. package/src/devcouncil/codeintel/debug/broker.py +114 -0
  86. package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
  87. package/src/devcouncil/codeintel/debug/consent.py +36 -0
  88. package/src/devcouncil/codeintel/debug/discovery.py +132 -0
  89. package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
  90. package/src/devcouncil/codeintel/debug/protocol.py +259 -0
  91. package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
  92. package/src/devcouncil/codeintel/debug/session.py +238 -0
  93. package/src/devcouncil/codeintel/debug/tracing.py +201 -0
  94. package/src/devcouncil/codeintel/languages/__init__.py +17 -0
  95. package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
  96. package/src/devcouncil/codeintel/languages/registry.py +149 -0
  97. package/src/devcouncil/codeintel/languages/workers.py +245 -0
  98. package/src/devcouncil/codeintel/query/__init__.py +5 -0
  99. package/src/devcouncil/codeintel/query/engine.py +289 -0
  100. package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
  101. package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
  102. package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
  103. package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
  104. package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
  105. package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
  106. package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
  107. package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
  108. package/src/devcouncil/codeintel/service.py +104 -0
  109. package/src/devcouncil/codeintel/store/__init__.py +15 -0
  110. package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
  111. package/src/devcouncil/codeintel/sync/__init__.py +19 -0
  112. package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
  113. package/src/devcouncil/codeintel/sync/incremental.py +484 -0
  114. package/src/devcouncil/codeintel/sync/lease.py +96 -0
  115. package/src/devcouncil/codeintel/sync/scope.py +98 -0
  116. package/src/devcouncil/council/__init__.py +4 -0
  117. package/src/devcouncil/council/prompts/__init__.py +4 -0
  118. package/src/devcouncil/domain/checkpoint_refs.py +17 -0
  119. package/src/devcouncil/domain/evidence.py +1 -0
  120. package/src/devcouncil/domain/gap.py +10 -0
  121. package/src/devcouncil/domain/requirement.py +5 -1
  122. package/src/devcouncil/domain/task.py +43 -2
  123. package/src/devcouncil/execution/checkpoints.py +25 -31
  124. package/src/devcouncil/execution/context_builder.py +15 -44
  125. package/src/devcouncil/execution/fs_watcher.py +64 -0
  126. package/src/devcouncil/execution/gated_write.py +203 -0
  127. package/src/devcouncil/execution/handoff.py +2 -1
  128. package/src/devcouncil/execution/hook_policy.py +19 -5
  129. package/src/devcouncil/execution/lease_ops.py +177 -0
  130. package/src/devcouncil/execution/lease_validation.py +71 -0
  131. package/src/devcouncil/execution/patch.py +3 -0
  132. package/src/devcouncil/execution/permissions.py +1 -0
  133. package/src/devcouncil/execution/policy_engine.py +205 -10
  134. package/src/devcouncil/execution/prompt_builder.py +278 -33
  135. package/src/devcouncil/execution/run_trace.py +356 -0
  136. package/src/devcouncil/execution/shell_session.py +46 -5
  137. package/src/devcouncil/execution/stop_gate.py +746 -0
  138. package/src/devcouncil/execution/stop_gate_history.py +113 -0
  139. package/src/devcouncil/execution/stop_gate_state.py +54 -0
  140. package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
  141. package/src/devcouncil/execution/task_gate_ops.py +590 -0
  142. package/src/devcouncil/execution/task_runner.py +19 -0
  143. package/src/devcouncil/executors/advisor_tool.py +315 -0
  144. package/src/devcouncil/executors/agent_registry.py +125 -17
  145. package/src/devcouncil/executors/claude_sdk.py +376 -0
  146. package/src/devcouncil/executors/coding_cli.py +724 -25
  147. package/src/devcouncil/executors/mini_swe.py +50 -8
  148. package/src/devcouncil/executors/native/agent.py +224 -19
  149. package/src/devcouncil/executors/openhands.py +50 -8
  150. package/src/devcouncil/executors/transient_retry.py +99 -0
  151. package/src/devcouncil/gating/checks/clean_git.py +5 -2
  152. package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
  153. package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
  154. package/src/devcouncil/gating/policy.py +46 -2
  155. package/src/devcouncil/indexing/ast_matcher.py +41 -4
  156. package/src/devcouncil/indexing/graph/__init__.py +78 -0
  157. package/src/devcouncil/indexing/graph/api_routes.py +522 -0
  158. package/src/devcouncil/indexing/graph/build.py +862 -0
  159. package/src/devcouncil/indexing/graph/cache.py +329 -0
  160. package/src/devcouncil/indexing/graph/communities.py +28 -0
  161. package/src/devcouncil/indexing/graph/cypher.py +107 -0
  162. package/src/devcouncil/indexing/graph/embeddings.py +194 -0
  163. package/src/devcouncil/indexing/graph/export.py +381 -0
  164. package/src/devcouncil/indexing/graph/export_links.py +81 -0
  165. package/src/devcouncil/indexing/graph/extract_python.py +307 -0
  166. package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
  167. package/src/devcouncil/indexing/graph/intel.py +668 -0
  168. package/src/devcouncil/indexing/graph/liveness.py +992 -0
  169. package/src/devcouncil/indexing/graph/okf_export.py +65 -0
  170. package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
  171. package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
  172. package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
  173. package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
  174. package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
  175. package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
  176. package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
  177. package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
  178. package/src/devcouncil/indexing/graph/query.py +302 -0
  179. package/src/devcouncil/indexing/graph/resolve.py +1020 -0
  180. package/src/devcouncil/indexing/graph/schema.py +103 -0
  181. package/src/devcouncil/indexing/graph_index.py +20 -29
  182. package/src/devcouncil/indexing/lsp.py +57 -25
  183. package/src/devcouncil/indexing/lsp_client.py +577 -0
  184. package/src/devcouncil/indexing/map_artifacts.py +355 -0
  185. package/src/devcouncil/indexing/map_refresh.py +141 -0
  186. package/src/devcouncil/indexing/repo_mapper.py +1509 -138
  187. package/src/devcouncil/indexing/semantic_index.py +12 -6
  188. package/src/devcouncil/indexing/subsystem_map.py +163 -0
  189. package/src/devcouncil/indexing/ts_imports.py +343 -0
  190. package/src/devcouncil/indexing/viz.py +960 -0
  191. package/src/devcouncil/indexing/walk.py +52 -0
  192. package/src/devcouncil/indexing/wiring.py +1776 -0
  193. package/src/devcouncil/integrations/actions.py +27 -4
  194. package/src/devcouncil/integrations/check.py +211 -16
  195. package/src/devcouncil/integrations/claude_assets.py +209 -12
  196. package/src/devcouncil/integrations/clients/__init__.py +1 -0
  197. package/src/devcouncil/integrations/clients/aider.py +52 -0
  198. package/src/devcouncil/integrations/clients/antigravity.py +87 -0
  199. package/src/devcouncil/integrations/clients/claude.py +339 -0
  200. package/src/devcouncil/integrations/clients/codex.py +39 -0
  201. package/src/devcouncil/integrations/clients/common.py +332 -0
  202. package/src/devcouncil/integrations/clients/cursor.py +164 -0
  203. package/src/devcouncil/integrations/clients/gemini.py +49 -0
  204. package/src/devcouncil/integrations/clients/grok.py +105 -0
  205. package/src/devcouncil/integrations/clients/hooks.py +500 -0
  206. package/src/devcouncil/integrations/clients/opencode.py +96 -0
  207. package/src/devcouncil/integrations/clients/warp.py +75 -0
  208. package/src/devcouncil/integrations/code_review_graph.py +2 -2
  209. package/src/devcouncil/integrations/github.py +73 -7
  210. package/src/devcouncil/integrations/integration_cli.py +197 -0
  211. package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
  212. package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
  213. package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
  214. package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
  215. package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
  216. package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
  217. package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
  218. package/src/devcouncil/integrations/mcp/handlers/git.py +99 -0
  219. package/src/devcouncil/integrations/mcp/handlers/graph.py +36 -0
  220. package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
  221. package/src/devcouncil/integrations/mcp/handlers/knowledge.py +30 -0
  222. package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
  223. package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
  224. package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
  225. package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
  226. package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
  227. package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
  228. package/src/devcouncil/integrations/mcp/handlers/provenance.py +101 -0
  229. package/src/devcouncil/integrations/mcp/handlers/read.py +74 -0
  230. package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
  231. package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
  232. package/src/devcouncil/integrations/mcp/handlers/runs.py +84 -0
  233. package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
  234. package/src/devcouncil/integrations/mcp/handlers/status.py +114 -0
  235. package/src/devcouncil/integrations/mcp/handlers/task.py +100 -0
  236. package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +904 -0
  237. package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
  238. package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
  239. package/src/devcouncil/integrations/mcp/handlers/wiki.py +60 -0
  240. package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
  241. package/src/devcouncil/integrations/mcp/server.py +265 -2391
  242. package/src/devcouncil/integrations/mcp/util.py +303 -0
  243. package/src/devcouncil/integrations/setup.py +152 -0
  244. package/src/devcouncil/knowledge/fetch.py +4 -0
  245. package/src/devcouncil/knowledge/knowledge_select.py +38 -0
  246. package/src/devcouncil/knowledge/okf.py +2 -1
  247. package/src/devcouncil/knowledge/resource_discovery.py +40 -0
  248. package/src/devcouncil/knowledge/wiki.py +643 -0
  249. package/src/devcouncil/knowledge/wiki_read.py +87 -0
  250. package/src/devcouncil/live/cards.py +7 -7
  251. package/src/devcouncil/live/models.py +4 -1
  252. package/src/devcouncil/live/reviewer.py +90 -11
  253. package/src/devcouncil/live/signals.py +4 -2
  254. package/src/devcouncil/live/tasks.py +12 -3
  255. package/src/devcouncil/live/transcripts.py +69 -2
  256. package/src/devcouncil/llm/cache.py +5 -6
  257. package/src/devcouncil/llm/model_defaults.yaml +10 -10
  258. package/src/devcouncil/llm/provider.py +647 -73
  259. package/src/devcouncil/llm/router.py +271 -46
  260. package/src/devcouncil/llm/semantic_bridge.py +614 -0
  261. package/src/devcouncil/optimization/gepa_agent.py +6 -4
  262. package/src/devcouncil/optimization/skillopt.py +9 -5
  263. package/src/devcouncil/planning/arbiter_service.py +12 -3
  264. package/src/devcouncil/planning/correction_manifest.py +107 -10
  265. package/src/devcouncil/planning/plan_difficulty.py +69 -0
  266. package/src/devcouncil/planning/plan_service.py +5 -2
  267. package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
  268. package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
  269. package/src/devcouncil/planning/question_conversion.py +56 -0
  270. package/src/devcouncil/planning/spec_service.py +9 -3
  271. package/src/devcouncil/repo/ci_scaffold.py +197 -1
  272. package/src/devcouncil/repo/gitignore.py +1 -2
  273. package/src/devcouncil/reporting/evidence_export.py +124 -0
  274. package/src/devcouncil/reporting/evidence_html.py +210 -0
  275. package/src/devcouncil/reporting/json_report.py +16 -12
  276. package/src/devcouncil/reporting/markdown_report.py +38 -9
  277. package/src/devcouncil/reporting/mcp_resources.py +142 -0
  278. package/src/devcouncil/reporting/report_builder.py +40 -4
  279. package/src/devcouncil/reporting/task_provenance.py +42 -0
  280. package/src/devcouncil/reporting/verdict.py +75 -0
  281. package/src/devcouncil/skills/library/README.md +1 -0
  282. package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
  283. package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
  284. package/src/devcouncil/skills/library/devcouncil.md +93 -0
  285. package/src/devcouncil/skills/registry.py +43 -12
  286. package/src/devcouncil/storage/db.py +57 -11
  287. package/src/devcouncil/storage/models.py +6 -0
  288. package/src/devcouncil/storage/native.py +5 -3
  289. package/src/devcouncil/storage/repositories.py +50 -18
  290. package/src/devcouncil/telemetry/context.py +28 -0
  291. package/src/devcouncil/telemetry/cost.py +4 -5
  292. package/src/devcouncil/telemetry/logging_setup.py +78 -11
  293. package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
  294. package/src/devcouncil/telemetry/stages.py +27 -2
  295. package/src/devcouncil/telemetry/tracker.py +50 -13
  296. package/src/devcouncil/ui/dashboard.py +120 -8
  297. package/src/devcouncil/utils/fsio.py +58 -0
  298. package/src/devcouncil/utils/git_snapshot.py +112 -0
  299. package/src/devcouncil/utils/json_persist.py +53 -0
  300. package/src/devcouncil/utils/proc.py +89 -0
  301. package/src/devcouncil/verification/acceptance_compiler.py +36 -13
  302. package/src/devcouncil/verification/ad_hoc_check.py +95 -3
  303. package/src/devcouncil/verification/checks/__init__.py +41 -0
  304. package/src/devcouncil/verification/checks/acceptance.py +39 -0
  305. package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
  306. package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
  307. package/src/devcouncil/verification/checks/command_evidence.py +148 -0
  308. package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
  309. package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
  310. package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
  311. package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
  312. package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
  313. package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
  314. package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
  315. package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
  316. package/src/devcouncil/verification/checks/planned_files.py +98 -0
  317. package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
  318. package/src/devcouncil/verification/checks/stale_map.py +80 -0
  319. package/src/devcouncil/verification/checks/stub_scan.py +71 -0
  320. package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
  321. package/src/devcouncil/verification/checks/wiring.py +216 -0
  322. package/src/devcouncil/verification/claims/__init__.py +23 -0
  323. package/src/devcouncil/verification/claims/checks.py +395 -0
  324. package/src/devcouncil/verification/claims/mapper.py +168 -0
  325. package/src/devcouncil/verification/claims/models.py +39 -0
  326. package/src/devcouncil/verification/claims/transcript.py +92 -0
  327. package/src/devcouncil/verification/claims/verdict.py +88 -0
  328. package/src/devcouncil/verification/command_evidence.py +170 -0
  329. package/src/devcouncil/verification/command_malformation.py +147 -0
  330. package/src/devcouncil/verification/command_runner.py +164 -0
  331. package/src/devcouncil/verification/coverage_measurement.py +292 -0
  332. package/src/devcouncil/verification/diff_coverage.py +151 -0
  333. package/src/devcouncil/verification/difficulty.py +296 -0
  334. package/src/devcouncil/verification/effort_heuristics.py +178 -0
  335. package/src/devcouncil/verification/gap_ids.py +63 -0
  336. package/src/devcouncil/verification/gate_cache.py +194 -0
  337. package/src/devcouncil/verification/gate_selector.py +344 -0
  338. package/src/devcouncil/verification/git_diff_fallback.py +272 -0
  339. package/src/devcouncil/verification/implementation_reviewer.py +13 -0
  340. package/src/devcouncil/verification/incremental_check.py +241 -0
  341. package/src/devcouncil/verification/next_actions.py +60 -1
  342. package/src/devcouncil/verification/rigor_analytics.py +130 -0
  343. package/src/devcouncil/verification/sandbox.py +38 -11
  344. package/src/devcouncil/verification/stub_detector.py +369 -0
  345. package/src/devcouncil/verification/test_resolver.py +67 -1
  346. package/src/devcouncil/verification/verifier.py +137 -1666
  347. package/src/devcouncil/verification/verify_orchestration.py +610 -0
  348. package/src/devcouncil/verification/verify_setup.py +176 -0
  349. package/src/devcouncil/verification/wiki_refresh.py +208 -0
  350. package/src/semantic_layer/__init__.py +58 -0
  351. package/src/semantic_layer/benchmark.py +75 -0
  352. package/src/semantic_layer/cache.py +290 -0
  353. package/src/semantic_layer/compressor.py +137 -0
  354. package/src/semantic_layer/config.py +75 -0
  355. package/src/semantic_layer/embeddings.py +69 -0
  356. package/src/semantic_layer/llm_backends.py +99 -0
  357. package/src/semantic_layer/pipeline.py +111 -0
  358. package/src/semantic_layer/router.py +128 -0
  359. package/src/semantic_layer/tuner.py +72 -0
  360. package/uv.lock +973 -9
  361. package/src/devcouncil/artifacts/migrations.py +0 -20
  362. package/src/devcouncil/artifacts/schemas.py +0 -23
  363. package/src/devcouncil/artifacts/serializer.py +0 -21
  364. package/src/devcouncil/integrations/gitnexus.py +0 -70
  365. package/src/devcouncil/integrations/graphify.py +0 -34
@@ -0,0 +1,24 @@
1
+ # Reviewer — Reviewer (quality control)
2
+
3
+ You are a thinker, not a doer. You judge the work and you shape the strategy.
4
+
5
+ ## Duties
6
+
7
+ - **Quality-control** every task the Worker finish, through the DevCouncil
8
+ **Verifier** (`verify_task`). Report each as *verified* or *blocked* with the
9
+ blocking gaps named.
10
+ - Own the hard cognition — Bloom **Analyze / Evaluate / Create**: architecture,
11
+ root-cause analysis, evaluation, and strategy planning.
12
+ - **Aggregate** the verdicts and report up to the **Coordinator**.
13
+
14
+ ## Forbidden
15
+
16
+ - **Never manage the Worker** — you review their output, you do not command them.
17
+ - **Never report directly to the Director** — everything goes through the Coordinator.
18
+ - **Never implement** the fix yourself — name the gap; the Coordinator re-dispatches.
19
+ - **Never contact the operator.**
20
+
21
+ ## Style
22
+
23
+ Measured and analytical. "The gate holds on three counts; two gaps remain. Coordinator,
24
+ re-dispatch task T-004."
@@ -0,0 +1,24 @@
1
+ # Worker — Worker (worker)
2
+
3
+ You are the front line. You take one task and you finish it.
4
+
5
+ ## Duties
6
+
7
+ - Execute the **single** task the Coordinator assigned to you, through your coding
8
+ executor (`CodingCliExecutor` or the native agent). Stay strictly inside the
9
+ task's `planned_files`, `allowed_commands` and `forbidden_changes`.
10
+ - When done, **self-review** against the parent command's intent.
11
+ - Write a completion report (`queue`/mailbox `report_received`) and notify the
12
+ **Reviewer** — not the Coordinator — for quality control.
13
+ - Then check your own mailbox for the next order.
14
+
15
+ ## Forbidden
16
+
17
+ - **Never QC your own work** — that is the Reviewer's job.
18
+ - **Never touch another Worker's task or files.**
19
+ - **Never contact the operator.**
20
+ - **Never poll** — act only when nudged.
21
+
22
+ ## Style
23
+
24
+ Eager and brief. "Hah! The task is complete — Reviewer, I await your judgement."
@@ -0,0 +1,202 @@
1
+ """The Director command hierarchy — ranks, duties, and enforceable boundaries.
2
+
3
+ Each :class:`Role` couples a rank to the *actions it is permitted to take*. The
4
+ original encodes "who may do what / who may never do what" as prose in role
5
+ markdown; here that contract is also machine-checkable via
6
+ :func:`assert_allowed`, so the orchestrator can hard-enforce the chain of
7
+ command (only the Coordinator writes the dashboard, only the Reviewer performs QC, an
8
+ Worker never reviews its own work, and so on) rather than trusting a prompt.
9
+
10
+ The human-readable persona/duty text still lives in ``prompts/<rank>.md`` and is
11
+ surfaced by :func:`load_role_instructions` for injection into an agent's
12
+ context.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass, field
18
+ from enum import Enum
19
+ from pathlib import Path
20
+ from typing import Dict, FrozenSet, List
21
+
22
+ _PROMPTS_DIR = Path(__file__).parent / "prompts"
23
+
24
+
25
+ class Rank(str, Enum):
26
+ """A position in the command hierarchy."""
27
+
28
+ DIRECTOR = "director"
29
+ COORDINATOR = "coordinator"
30
+ WORKER = "worker"
31
+ REVIEWER = "reviewer"
32
+
33
+ def __str__(self) -> str: # nicer CLI/log output
34
+ return self.value
35
+
36
+
37
+ class Action(str, Enum):
38
+ """A discrete capability that a rank may or may not exercise."""
39
+
40
+ RELAY_ORDER = "relay_order" # accept the operator's order, hand to Coordinator
41
+ READ_DASHBOARD = "read_dashboard"
42
+ APPROVE_SKILL = "approve_skill"
43
+ DECOMPOSE = "decompose" # split an order into subtasks
44
+ ASSIGN = "assign" # dispatch a task to a worker
45
+ WRITE_DASHBOARD = "write_dashboard" # sole right of the Coordinator
46
+ ROUTE_QC = "route_qc" # send finished work to the Reviewer
47
+ ROLLUP = "rollup" # aggregate reports, decide done/blocked
48
+ NOTIFY = "notify" # push to the operator (ntfy / dashboard)
49
+ EXECUTE_TASK = "execute_task" # actually do the work
50
+ SELF_REVIEW = "self_review" # a worker's own sanity check
51
+ WRITE_REPORT = "write_report" # a worker reporting completion
52
+ QC_REVIEW = "qc_review" # quality control of another's work
53
+ DEEP_ANALYSIS = "deep_analysis" # architecture / root-cause (Bloom L4-6)
54
+ AGGREGATE_REPORTS = "aggregate_reports"
55
+ CONTACT_HUMAN = "contact_human" # only the Director/Coordinator may reach the operator
56
+
57
+
58
+ class ForbiddenActionError(RuntimeError):
59
+ """Raised when a rank attempts an action outside its remit."""
60
+
61
+ def __init__(self, rank: "Rank", action: "Action"):
62
+ self.rank = rank
63
+ self.action = action
64
+ super().__init__(
65
+ f"{rank} is forbidden from '{action.value}' — it violates the role hierarchy"
66
+ )
67
+
68
+
69
+ @dataclass(frozen=True)
70
+ class Role:
71
+ """A rank plus its persona and the actions it is permitted to take."""
72
+
73
+ rank: Rank
74
+ title: str
75
+ summary: str
76
+ allowed: FrozenSet[Action]
77
+ reports_to: Rank | None
78
+ # Human-facing bullet duties, mirrored in prompts/<rank>.md for quick display.
79
+ duties: List[str] = field(default_factory=list)
80
+
81
+ def may(self, action: Action) -> bool:
82
+ return action in self.allowed
83
+
84
+ @property
85
+ def forbidden(self) -> FrozenSet[Action]:
86
+ return frozenset(Action) - self.allowed
87
+
88
+
89
+ ROLES: Dict[Rank, Role] = {
90
+ Rank.DIRECTOR: Role(
91
+ rank=Rank.DIRECTOR,
92
+ title="Director ",
93
+ summary="Relays the operator's order to the Coordinator and steps back. Never works.",
94
+ allowed=frozenset(
95
+ {
96
+ Action.RELAY_ORDER,
97
+ Action.READ_DASHBOARD,
98
+ Action.APPROVE_SKILL,
99
+ Action.NOTIFY,
100
+ Action.CONTACT_HUMAN,
101
+ }
102
+ ),
103
+ reports_to=None,
104
+ duties=[
105
+ "Receive the operator's order and record it as a campaign command.",
106
+ "Hand the command to the Coordinator, then yield so the operator may issue more.",
107
+ "Read the dashboard; never write it, never execute a task, never bypass the Coordinator.",
108
+ ],
109
+ ),
110
+ Rank.COORDINATOR: Role(
111
+ rank=Rank.COORDINATOR,
112
+ title="Coordinator ",
113
+ summary="Decomposes the order, dispatches Worker in parallel, owns the dashboard.",
114
+ allowed=frozenset(
115
+ {
116
+ Action.DECOMPOSE,
117
+ Action.ASSIGN,
118
+ Action.WRITE_DASHBOARD,
119
+ Action.ROUTE_QC,
120
+ Action.ROLLUP,
121
+ Action.NOTIFY,
122
+ Action.READ_DASHBOARD,
123
+ Action.CONTACT_HUMAN,
124
+ }
125
+ ),
126
+ reports_to=Rank.DIRECTOR,
127
+ duties=[
128
+ "Decompose the command into subtasks and classify each by Bloom level.",
129
+ "Dispatch execution tasks to Worker in parallel; route analysis/QC to the Reviewer.",
130
+ "Own the dashboard and roll finished, verified work up to the Director.",
131
+ "Never do the work yourself — 'one Worker can do it all' is Coordinator laziness.",
132
+ ],
133
+ ),
134
+ Rank.WORKER: Role(
135
+ rank=Rank.WORKER,
136
+ title="Worker ",
137
+ summary="Runs exactly one task through a coding executor, then reports to the Reviewer.",
138
+ allowed=frozenset(
139
+ {
140
+ Action.EXECUTE_TASK,
141
+ Action.SELF_REVIEW,
142
+ Action.WRITE_REPORT,
143
+ }
144
+ ),
145
+ reports_to=Rank.REVIEWER,
146
+ duties=[
147
+ "Execute the single task assigned to you and nothing beyond its scope.",
148
+ "Self-review against the parent command, then write a completion report.",
149
+ "Notify the Reviewer for QC. Never QC your own work, never touch another's task, never poll.",
150
+ ],
151
+ ),
152
+ Rank.REVIEWER: Role(
153
+ rank=Rank.REVIEWER,
154
+ title="Reviewer ",
155
+ summary="A thinker, not a doer. Verifies finished work and handles Bloom L4-L6.",
156
+ allowed=frozenset(
157
+ {
158
+ Action.QC_REVIEW,
159
+ Action.DEEP_ANALYSIS,
160
+ Action.AGGREGATE_REPORTS,
161
+ Action.WRITE_REPORT,
162
+ }
163
+ ),
164
+ reports_to=Rank.COORDINATOR,
165
+ duties=[
166
+ "Quality-control Worker output through the DevCouncil Verifier.",
167
+ "Own architecture, root-cause and strategy work (Bloom Analyze/Evaluate/Create).",
168
+ "Aggregate verdicts and report up to the Coordinator. Never manage Worker, never implement.",
169
+ ],
170
+ ),
171
+ }
172
+
173
+
174
+ def get_role(rank: Rank | str) -> Role:
175
+ """Look up a :class:`Role` by rank (enum or string)."""
176
+ if isinstance(rank, str):
177
+ rank = Rank(rank)
178
+ return ROLES[rank]
179
+
180
+
181
+ def assert_allowed(rank: Rank | str, action: Action) -> None:
182
+ """Raise :class:`ForbiddenActionError` if ``rank`` may not perform ``action``."""
183
+ role = get_role(rank)
184
+ if not role.may(action):
185
+ raise ForbiddenActionError(role.rank, action)
186
+
187
+
188
+ def load_role_instructions(rank: Rank | str) -> str:
189
+ """Return the persona/duty markdown for ``rank`` (``prompts/<rank>.md``).
190
+
191
+ Falls back to a synthesised summary when the file is missing so the system is
192
+ resilient to a trimmed install.
193
+ """
194
+ if isinstance(rank, str):
195
+ rank = Rank(rank)
196
+ path = _PROMPTS_DIR / f"{rank.value}.md"
197
+ if path.exists():
198
+ return path.read_text(encoding="utf-8")
199
+ role = ROLES[rank]
200
+ lines = [f"# {role.title}", "", role.summary, ""]
201
+ lines += [f"- {duty}" for duty in role.duties]
202
+ return "\n".join(lines)
@@ -0,0 +1,153 @@
1
+ """Mailbox watcher — the "you have mail" nudge layer.
2
+
3
+ In the original, one ``inotifywait``/``fswatch`` daemon per agent blocks on a
4
+ kernel file-change event and, when the inbox changes, fires a content-free
5
+ ``inboxN`` keystroke into that agent's tmux pane. Message *content* never travels
6
+ over the wire — only the wake-up.
7
+
8
+ DevCouncil runs its agents in-process, so the "keystroke" becomes a Python
9
+ callback: ``nudge(agent, unread_count)``. We still key off real filesystem
10
+ events (via :mod:`watchdog`, already a DevCouncil dependency) so cross-process
11
+ writers — e.g. a leased MCP agent appending to a mailbox — also wake the
12
+ campaign. When watchdog is unavailable we fall back to a polling thread, and
13
+ :meth:`MailboxWatcher.poll_once` lets a single-threaded orchestrator drive the
14
+ same logic deterministically (and keeps unit tests free of timing races).
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import threading
20
+ from typing import Any, Callable, Dict, Iterable, List, Optional
21
+
22
+ from devcouncil.campaign.mailbox import Mailbox
23
+
24
+ NudgeCallback = Callable[[str, int], None]
25
+
26
+ try: # watchdog is a declared dependency; degrade gracefully if it is missing.
27
+ from watchdog.events import FileSystemEventHandler
28
+ from watchdog.observers import Observer
29
+
30
+ _HAS_WATCHDOG = True
31
+ except Exception: # pragma: no cover - exercised only without watchdog
32
+ FileSystemEventHandler = object # type: ignore[assignment,misc]
33
+ Observer = None # type: ignore[assignment]
34
+ _HAS_WATCHDOG = False
35
+
36
+
37
+ class MailboxWatcher:
38
+ """Watch a set of agent mailboxes and nudge on new unread mail.
39
+
40
+ Parameters
41
+ ----------
42
+ mailbox:
43
+ The shared :class:`~devcouncil.campaign.mailbox.Mailbox`.
44
+ agents:
45
+ Agent ids to watch.
46
+ nudge:
47
+ Called ``nudge(agent, unread_count)`` whenever an agent's unread count
48
+ rises. Never called with a count of zero.
49
+ poll_interval:
50
+ Seconds between scans on the polling fallback.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ mailbox: Mailbox,
56
+ agents: Iterable[str],
57
+ nudge: NudgeCallback,
58
+ poll_interval: float = 1.0,
59
+ ):
60
+ self.mailbox = mailbox
61
+ self.agents: List[str] = list(agents)
62
+ self.nudge = nudge
63
+ self.poll_interval = poll_interval
64
+ # Last unread count we nudged for, per agent — so a nudge fires on the
65
+ # *rising edge* only (0->N or N->N+1), never repeatedly for the same mail.
66
+ self._last: Dict[str, int] = {a: 0 for a in self.agents}
67
+ self._lock = threading.Lock()
68
+ self._observer: Any = None
69
+ self._stop = threading.Event()
70
+ self._poll_thread: Optional[threading.Thread] = None
71
+
72
+ # -- core logic ------------------------------------------------------------
73
+
74
+ def poll_once(self) -> List[str]:
75
+ """Scan all mailboxes once; nudge agents whose unread count rose.
76
+
77
+ Returns the list of agents nudged. Safe to call from any thread and from
78
+ tests; this is the single choke-point through which every nudge flows.
79
+ """
80
+ nudged: List[str] = []
81
+ with self._lock:
82
+ for agent in self.agents:
83
+ count = self.mailbox.count_unread(agent)
84
+ previous = self._last.get(agent, 0)
85
+ if count > previous and count > 0:
86
+ self._last[agent] = count
87
+ nudged.append(agent)
88
+ elif count < previous:
89
+ # Agent drained its inbox; re-arm so the next arrival nudges.
90
+ self._last[agent] = count
91
+ for agent in nudged:
92
+ self.nudge(agent, self.mailbox.count_unread(agent))
93
+ return nudged
94
+
95
+ def reset(self, agent: Optional[str] = None) -> None:
96
+ """Forget prior nudge state so the next scan re-nudges pending mail."""
97
+ with self._lock:
98
+ if agent is None:
99
+ self._last = {a: 0 for a in self.agents}
100
+ else:
101
+ self._last[agent] = 0
102
+
103
+ # -- background operation --------------------------------------------------
104
+
105
+ def start(self) -> None:
106
+ """Begin watching in the background (watchdog, or a polling thread)."""
107
+ self.mailbox.inbox_dir.mkdir(parents=True, exist_ok=True)
108
+ if _HAS_WATCHDOG and Observer is not None:
109
+ handler = _InboxEventHandler(self)
110
+ self._observer = Observer()
111
+ self._observer.schedule(handler, str(self.mailbox.inbox_dir), recursive=False)
112
+ self._observer.start()
113
+ else: # pragma: no cover - fallback path
114
+ self._poll_thread = threading.Thread(target=self._poll_loop, daemon=True)
115
+ self._poll_thread.start()
116
+ # Deliver anything already waiting at startup.
117
+ self.poll_once()
118
+
119
+ def _poll_loop(self) -> None: # pragma: no cover - timing loop
120
+ while not self._stop.wait(self.poll_interval):
121
+ self.poll_once()
122
+
123
+ def stop(self) -> None:
124
+ self._stop.set()
125
+ if self._observer is not None:
126
+ try:
127
+ self._observer.stop()
128
+ self._observer.join(timeout=2.0)
129
+ except Exception:
130
+ pass
131
+ self._observer = None
132
+ if self._poll_thread is not None: # pragma: no cover
133
+ self._poll_thread.join(timeout=2.0)
134
+ self._poll_thread = None
135
+
136
+ def __enter__(self) -> "MailboxWatcher":
137
+ self.start()
138
+ return self
139
+
140
+ def __exit__(self, *exc: object) -> None:
141
+ self.stop()
142
+
143
+
144
+ class _InboxEventHandler(FileSystemEventHandler):
145
+ """Translates filesystem change events into a debounced ``poll_once``."""
146
+
147
+ def __init__(self, watcher: MailboxWatcher):
148
+ self._watcher = watcher
149
+
150
+ def on_any_event(self, event: object) -> None: # pragma: no cover - needs fs events
151
+ # A YAML write shows up as create/modify/moved on the atomic replace; we
152
+ # do not care which — a single re-scan reconciles all mailboxes.
153
+ self._watcher.poll_once()
@@ -22,9 +22,12 @@ from devcouncil.executors.agent_registry import (
22
22
  resolve_cursor_agent_executable,
23
23
  )
24
24
  from devcouncil.optimization.gepa_agent import GepaUnavailableError, optimize_agent_profile
25
+ from devcouncil.telemetry.stages import log_stage, log_step
26
+ import logging
25
27
 
26
28
  app = typer.Typer(help="Manage DevCouncil CLI agents.")
27
29
  console = Console()
30
+ logger = logging.getLogger(__name__)
28
31
 
29
32
 
30
33
  @app.callback(invoke_without_command=True)
@@ -37,24 +40,28 @@ def list_agents(
37
40
  return
38
41
 
39
42
  root = _project_root(project_root)
40
- table = Table(title="DevCouncil Agents")
41
- table.add_column("Agent", style="cyan")
42
- table.add_column("Type")
43
- table.add_column("Command")
44
- table.add_column("Profile")
45
- table.add_column("MCP")
46
- table.add_column("Diff Review")
43
+ logger.info("dev agents list")
44
+ with log_stage("agents", project_root=root, subcommand="list"):
45
+ log_step("agents/1: loading agent registry", project_root=root, trace=True)
46
+ table = Table(title="DevCouncil Agents")
47
+ table.add_column("Agent", style="cyan")
48
+ table.add_column("Type")
49
+ table.add_column("Command")
50
+ table.add_column("Profile")
51
+ table.add_column("MCP")
52
+ table.add_column("Diff Review")
47
53
 
48
- for name, spec in sorted(load_cli_agent_specs(root).items()):
49
- table.add_row(
50
- name,
51
- "built-in" if spec.built_in else spec.kind,
52
- " ".join(spec.base_command()),
53
- spec.default_profile,
54
- "yes" if spec.supports_mcp else "no",
55
- "yes" if spec.supports_diff_review else "no",
56
- )
57
- console.print(table)
54
+ for name, spec in sorted(load_cli_agent_specs(root).items()):
55
+ table.add_row(
56
+ name,
57
+ "built-in" if spec.built_in else spec.kind,
58
+ " ".join(spec.base_command()),
59
+ spec.default_profile,
60
+ "yes" if spec.supports_mcp else "no",
61
+ "yes" if spec.supports_diff_review else "no",
62
+ )
63
+ console.print(table)
64
+ log_step("agents/complete", project_root=root, trace=True)
58
65
 
59
66
 
60
67
  @app.command("add")
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import typer
2
3
  from rich.console import Console
3
4
  from rich.table import Table
@@ -8,9 +9,11 @@ from devcouncil.artifacts.validators import ArtifactValidator
8
9
  from devcouncil.storage.db import get_db
9
10
  from devcouncil.storage.repositories import RequirementRepository, TaskRepository
10
11
  from devcouncil.cli.commands.init import initialize_project
12
+ from devcouncil.telemetry.stages import log_stage, log_step
11
13
 
12
14
  app = typer.Typer()
13
15
  console = Console()
16
+ logger = logging.getLogger(__name__)
14
17
 
15
18
 
16
19
  @app.command(name="validate")
@@ -19,36 +22,42 @@ def validate(
19
22
  ):
20
23
  """Validate requirements and tasks stored in the artifact graph."""
21
24
  root = project_root.expanduser().resolve()
25
+ from devcouncil.telemetry.logging_setup import set_log_dir
26
+ set_log_dir(root)
27
+ logger.info("dev artifacts validate")
22
28
  initialize_project(root, quiet=True)
23
29
  db = get_db(root)
24
30
  if not db:
25
31
  console.print("[red]DevCouncil state is unavailable in this directory.[/red]")
26
32
  raise typer.Exit(code=1)
27
33
 
28
- errors: list[str] = []
29
- with db.get_session() as session:
30
- req_repo = RequirementRepository(session)
31
- task_repo = TaskRepository(session)
32
-
33
- for req in req_repo.get_all():
34
- try:
35
- ArtifactValidator.validate_requirement(req)
36
- except GatingError as exc:
37
- errors.append(str(exc))
38
-
39
- for task in task_repo.get_all():
40
- try:
41
- ArtifactValidator.validate_task(task)
42
- except GatingError as exc:
43
- errors.append(str(exc))
44
-
45
- if not errors:
46
- console.print("[green]Artifacts are valid.[/green]")
47
- return
48
-
49
- table = Table(title="Artifact Validation Errors")
50
- table.add_column("Error", style="red")
51
- for error in errors:
52
- table.add_row(error)
53
- console.print(table)
54
- raise typer.Exit(code=1)
34
+ with log_stage("artifacts", project_root=root):
35
+ log_step("artifacts/1: validating requirements and tasks", project_root=root, trace=True)
36
+ errors: list[str] = []
37
+ with db.get_session() as session:
38
+ req_repo = RequirementRepository(session)
39
+ task_repo = TaskRepository(session)
40
+
41
+ for req in req_repo.get_all():
42
+ try:
43
+ ArtifactValidator.validate_requirement(req)
44
+ except GatingError as exc:
45
+ errors.append(str(exc))
46
+
47
+ for task in task_repo.get_all():
48
+ try:
49
+ ArtifactValidator.validate_task(task)
50
+ except GatingError as exc:
51
+ errors.append(str(exc))
52
+
53
+ if not errors:
54
+ console.print("[green]Artifacts are valid.[/green]")
55
+ log_step("artifacts/complete", project_root=root, trace=True)
56
+ return
57
+
58
+ table = Table(title="Artifact Validation Errors")
59
+ table.add_column("Error", style="red")
60
+ for error in errors:
61
+ table.add_row(error)
62
+ console.print(table)
63
+ raise typer.Exit(code=1)
@@ -1,11 +1,14 @@
1
- import json
1
+ from devcouncil.utils.json_persist import dump_json
2
+ import logging
2
3
  from pathlib import Path
3
4
 
4
5
  import typer
5
6
 
6
7
  from devcouncil.indexing.ast_matcher import AstMatcher
8
+ from devcouncil.telemetry.stages import log_stage, log_step
7
9
 
8
10
  app = typer.Typer(help="Run structural AST searches.")
11
+ logger = logging.getLogger(__name__)
9
12
 
10
13
 
11
14
  @app.command("match")
@@ -18,5 +21,11 @@ def match(
18
21
  ):
19
22
  """Find function/class/type symbols using tree-sitter when available and fallbacks otherwise."""
20
23
  root = project_root.expanduser().resolve()
21
- matches = AstMatcher(root).match(query=query, language=language, kind=kind, limit=max(1, limit))
22
- typer.echo(json.dumps({"matches": [item.model_dump() for item in matches]}, indent=2))
24
+ from devcouncil.telemetry.logging_setup import set_log_dir
25
+ set_log_dir(root)
26
+ logger.info("dev ast match: query=%r language=%s kind=%s", query, language, kind)
27
+ with log_stage("ast", project_root=root):
28
+ log_step("ast/1: running structural match", project_root=root, trace=True)
29
+ matches = AstMatcher(root).match(query=query, language=language, kind=kind, limit=max(1, limit))
30
+ typer.echo(dump_json({"matches": [item.model_dump() for item in matches]}, indent=2))
31
+ log_step("ast/complete", project_root=root, count=len(matches), trace=True)
@@ -1,4 +1,4 @@
1
- import json
1
+ import logging
2
2
  from pathlib import Path
3
3
 
4
4
  import typer
@@ -6,9 +6,12 @@ from rich.console import Console
6
6
 
7
7
  from devcouncil.cli.commands.init import initialize_project
8
8
  from devcouncil.storage.db import get_db
9
+ from devcouncil.telemetry.stages import log_stage, log_step
10
+ from devcouncil.utils.json_persist import write_json
9
11
  from devcouncil.verification.verifier import Verifier
10
12
 
11
13
  console = Console()
14
+ logger = logging.getLogger(__name__)
12
15
 
13
16
 
14
17
  def baseline(
@@ -17,19 +20,25 @@ def baseline(
17
20
  ):
18
21
  """Capture the current repo state as DevCouncil's verification baseline."""
19
22
  root = project_root.expanduser().resolve()
23
+ from devcouncil.telemetry.logging_setup import set_log_dir
24
+ set_log_dir(root)
25
+ logger.info("dev baseline: force=%s", force)
20
26
  initialize_project(root, quiet=True)
21
27
  if not get_db(root):
22
28
  raise typer.Exit(code=1)
23
29
 
24
- baseline_path = root / ".devcouncil" / "baseline.json"
25
- if baseline_path.exists() and not force:
26
- console.print("[yellow]Baseline already exists. Use --force to replace it.[/yellow]")
27
- raise typer.Exit(code=1)
30
+ with log_stage("baseline", project_root=root, force=force):
31
+ log_step("baseline/1: capturing verification snapshot", project_root=root, trace=True)
32
+ baseline_path = root / ".devcouncil" / "baseline.json"
33
+ if baseline_path.exists() and not force:
34
+ console.print("[yellow]Baseline already exists. Use --force to replace it.[/yellow]")
35
+ raise typer.Exit(code=1)
28
36
 
29
- changed_files = Verifier(root).get_changed_files()
30
- payload = {
31
- "changed_files": changed_files,
32
- "note": "Files present in this snapshot are excluded from future task-scoped verification diffs.",
33
- }
34
- baseline_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
35
- console.print(f"[green]Captured baseline with {len(changed_files)} changed file(s).[/green]")
37
+ changed_files = Verifier(root).get_changed_files()
38
+ payload = {
39
+ "changed_files": changed_files,
40
+ "note": "Files present in this snapshot are excluded from future task-scoped verification diffs.",
41
+ }
42
+ write_json(baseline_path, payload)
43
+ console.print(f"[green]Captured baseline with {len(changed_files)} changed file(s).[/green]")
44
+ log_step("baseline/complete", project_root=root, file_count=len(changed_files), trace=True)