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
@@ -11,6 +11,7 @@ process likely crashed) as ``orphaned``.
11
11
  from __future__ import annotations
12
12
 
13
13
  import json
14
+ import logging
14
15
  import time
15
16
  from pathlib import Path
16
17
 
@@ -19,9 +20,12 @@ from rich.console import Console
19
20
  from rich.table import Table
20
21
 
21
22
  from devcouncil.utils.redaction import redact_text
23
+ from devcouncil.utils.json_persist import read_json
24
+ from devcouncil.telemetry.stages import log_stage, log_step
22
25
 
23
26
  app = typer.Typer(help="List and inspect coding-agent run manifests.")
24
27
  console = Console()
28
+ logger = logging.getLogger(__name__)
25
29
 
26
30
  # A run still marked ``running`` whose manifest has not been touched for longer
27
31
  # than this is treated as orphaned (the executor process likely died). Used as a
@@ -49,14 +53,14 @@ def _orphan_after_seconds(project_root: Path) -> int:
49
53
  ttl = int(load_config(project_root).execution.lease_ttl_seconds)
50
54
  if ttl > 0:
51
55
  return ttl
52
- except Exception:
53
- pass
56
+ except Exception as e:
57
+ logger.debug("Failed to load lease TTL from config, using default orphan threshold: %s", e)
54
58
  return _DEFAULT_ORPHAN_AFTER_SECONDS
55
59
 
56
60
 
57
61
  def _load_manifest(manifest_path: Path) -> dict | None:
58
62
  try:
59
- data = json.loads(manifest_path.read_text(encoding="utf-8"))
63
+ data = read_json(manifest_path)
60
64
  except (OSError, json.JSONDecodeError):
61
65
  return None
62
66
  return data if isinstance(data, dict) else None
@@ -142,41 +146,50 @@ def list_runs(
142
146
  ) -> None:
143
147
  """List recorded coding-agent runs, newest first."""
144
148
  root = project_root.expanduser().resolve()
145
- orphan_after = _orphan_after_seconds(root)
146
- runs = _collect_runs(root, orphan_after=orphan_after)
147
- if status:
148
- runs = [run for run in runs if run.get("status") == status]
149
- if limit > 0:
150
- runs = runs[:limit]
151
-
152
- if json_output:
153
- console.print_json(data={"runs": runs, "count": len(runs)})
154
- return
155
-
156
- if not runs:
157
- console.print("[dim]No agent runs found under .devcouncil/runs/.[/dim]")
158
- return
159
-
160
- table = Table(title="Agent runs")
161
- table.add_column("Run ID", overflow="fold")
162
- table.add_column("Task")
163
- table.add_column("Agent")
164
- table.add_column("Profile")
165
- table.add_column("Status")
166
- table.add_column("Started")
167
- for run in runs:
168
- status_text = str(run.get("status") or "?")
169
- if run.get("orphaned"):
170
- status_text = f"[red]{status_text} (orphaned)[/red]"
171
- table.add_row(
172
- str(run.get("run_id") or ""),
173
- str(run.get("task_id") or ""),
174
- str(run.get("agent") or ""),
175
- str(run.get("profile") or ""),
176
- status_text,
177
- str(run.get("started_at") or ""),
178
- )
179
- console.print(table)
149
+ from devcouncil.telemetry.logging_setup import set_log_dir
150
+ set_log_dir(root)
151
+ logger.info("dev runs list: status=%s limit=%d", status, limit)
152
+ with log_stage("runs", project_root=root, subcommand="list"):
153
+ log_step("runs/1: collecting run manifests", project_root=root, trace=True)
154
+ orphan_after = _orphan_after_seconds(root)
155
+ runs = _collect_runs(root, orphan_after=orphan_after)
156
+ if status:
157
+ runs = [run for run in runs if run.get("status") == status]
158
+ total = len(runs)
159
+ if limit > 0:
160
+ runs = runs[:limit]
161
+
162
+ if json_output:
163
+ console.print_json(data={"runs": runs, "count": len(runs), "total": total})
164
+ log_step("runs/complete", project_root=root, count=len(runs), trace=True)
165
+ return
166
+
167
+ if not runs:
168
+ console.print("[dim]No agent runs found under .devcouncil/runs/.[/dim]")
169
+ log_step("runs/complete", project_root=root, count=0, trace=True)
170
+ return
171
+
172
+ table = Table(title="Agent runs")
173
+ table.add_column("Run ID", overflow="fold")
174
+ table.add_column("Task")
175
+ table.add_column("Agent")
176
+ table.add_column("Profile")
177
+ table.add_column("Status")
178
+ table.add_column("Started")
179
+ for run in runs:
180
+ status_text = str(run.get("status") or "?")
181
+ if run.get("orphaned"):
182
+ status_text = f"[red]{status_text} (orphaned)[/red]"
183
+ table.add_row(
184
+ str(run.get("run_id") or ""),
185
+ str(run.get("task_id") or ""),
186
+ str(run.get("agent") or ""),
187
+ str(run.get("profile") or ""),
188
+ status_text,
189
+ str(run.get("started_at") or ""),
190
+ )
191
+ console.print(table)
192
+ log_step("runs/complete", project_root=root, count=len(runs), trace=True)
180
193
 
181
194
 
182
195
  @app.command("show")
@@ -187,37 +200,254 @@ def show_run(
187
200
  ) -> None:
188
201
  """Show the full manifest for a run plus a redacted transcript tail."""
189
202
  root = project_root.expanduser().resolve()
190
- run_dir = _runs_dir(root) / run_id
191
- manifest_path = run_dir / "agent-run.json"
192
- manifest = _load_manifest(manifest_path)
193
- if manifest is None:
203
+ from devcouncil.telemetry.logging_setup import set_log_dir
204
+ set_log_dir(root)
205
+ logger.info("dev runs show: run_id=%s", run_id)
206
+ with log_stage("runs", project_root=root, subcommand="show", run_id=run_id):
207
+ log_step("runs/1: loading run manifest", project_root=root, run_id=run_id, trace=True)
208
+ run_dir = _runs_dir(root) / run_id
209
+ manifest_path = run_dir / "agent-run.json"
210
+ manifest = _load_manifest(manifest_path)
211
+ if manifest is None:
212
+ if json_output:
213
+ console.print_json(data={"ok": False, "error": f"Run {run_id} not found.", "run_id": run_id})
214
+ else:
215
+ console.print(f"[red]Run {run_id} not found under .devcouncil/runs/.[/red]")
216
+ raise typer.Exit(code=1)
217
+
218
+ orphan_after = _orphan_after_seconds(root)
219
+ orphaned = _is_orphaned(manifest, manifest_path, orphan_after=orphan_after, now=time.time())
220
+ transcript_path = _find_transcript(run_dir, manifest)
221
+ transcript_tail = _transcript_tail(transcript_path) if transcript_path else ""
222
+
194
223
  if json_output:
195
- console.print_json(data={"ok": False, "error": f"Run {run_id} not found.", "run_id": run_id})
224
+ console.print_json(data={
225
+ "ok": True,
226
+ "run_id": run_id,
227
+ "manifest": manifest,
228
+ "orphaned": orphaned,
229
+ "transcript_path": str(transcript_path) if transcript_path else None,
230
+ "transcript_tail": transcript_tail,
231
+ })
232
+ log_step("runs/complete", project_root=root, run_id=run_id, trace=True)
233
+ return
234
+
235
+ console.print_json(data=manifest)
236
+ if orphaned:
237
+ console.print("[red]This run is orphaned: still marked running but its manifest is stale.[/red]")
238
+ if transcript_path:
239
+ console.print(f"\n[bold]Transcript tail[/bold] [dim]({transcript_path})[/dim]:")
240
+ console.print(transcript_tail or "[dim](empty)[/dim]")
196
241
  else:
197
- console.print(f"[red]Run {run_id} not found under .devcouncil/runs/.[/red]")
242
+ console.print("\n[dim]No transcript file found for this run.[/dim]")
243
+ log_step("runs/complete", project_root=root, run_id=run_id, trace=True)
244
+
245
+
246
+ # --- Shepherd-style reversible-trace commands -------------------------------------
247
+ # A run is an object a supervisor (human or meta-agent) can operate on: inspect the
248
+ # full timeline (`timeline`), see exactly what it changed (`diff`), reverse its
249
+ # workspace effects (`revert`), or ask for a keep/revert/repair verdict (`supervise`).
250
+
251
+
252
+ def _resolve_root(project_root: Path) -> Path:
253
+ root = project_root.expanduser().resolve()
254
+ from devcouncil.telemetry.logging_setup import set_log_dir
255
+
256
+ set_log_dir(root)
257
+ return root
258
+
259
+
260
+ def _load_timeline_or_exit(root: Path, reference: str):
261
+ from devcouncil.execution.run_trace import load_timeline
262
+
263
+ try:
264
+ return load_timeline(root, reference)
265
+ except ValueError as exc:
266
+ console.print(f"[red]{exc}[/red]")
198
267
  raise typer.Exit(code=1)
199
268
 
200
- orphan_after = _orphan_after_seconds(root)
201
- orphaned = _is_orphaned(manifest, manifest_path, orphan_after=orphan_after, now=time.time())
202
- transcript_path = _find_transcript(run_dir, manifest)
203
- transcript_tail = _transcript_tail(transcript_path) if transcript_path else ""
204
-
205
- if json_output:
206
- console.print_json(data={
207
- "ok": True,
208
- "run_id": run_id,
209
- "manifest": manifest,
210
- "orphaned": orphaned,
211
- "transcript_path": str(transcript_path) if transcript_path else None,
212
- "transcript_tail": transcript_tail,
213
- })
214
- return
215
-
216
- console.print_json(data=manifest)
217
- if orphaned:
218
- console.print("[red]This run is orphaned: still marked running but its manifest is stale.[/red]")
219
- if transcript_path:
220
- console.print(f"\n[bold]Transcript tail[/bold] [dim]({transcript_path})[/dim]:")
221
- console.print(transcript_tail or "[dim](empty)[/dim]")
222
- else:
223
- console.print("\n[dim]No transcript file found for this run.[/dim]")
269
+
270
+ def _supervisor_router(root: Path):
271
+ """Best-effort ModelRouter for `dev runs supervise`; None degrades to heuristics.
272
+
273
+ The ``run_supervisor`` role falls back to a capable existing role when the project
274
+ config doesn't define a dedicated one (same pattern as `dev skills` / `dev wiki`).
275
+ """
276
+ try:
277
+ from devcouncil.app.config import get_api_key, load_config
278
+ from devcouncil.llm.provider import create_provider, validate_model_provider
279
+ from devcouncil.llm.router import ModelRouter
280
+
281
+ config = load_config(root)
282
+ validate_model_provider(config.models.provider)
283
+ api_key = get_api_key(config.models.provider, root)
284
+ provider = create_provider(
285
+ config.models.provider, api_key, project_root=root, provider_prefs=config.provider
286
+ )
287
+ role_config = {name: role.model_dump() for name, role in config.models.roles.items()}
288
+ if not role_config:
289
+ return None
290
+ capable = (
291
+ role_config.get("arbiter")
292
+ or role_config.get("planner_a")
293
+ or next(iter(role_config.values()))
294
+ )
295
+ role_config.setdefault("run_supervisor", dict(capable))
296
+ return ModelRouter(provider, role_config, project_root=root)
297
+ except Exception as exc:
298
+ logger.warning("Model-backed supervision unavailable; using heuristics: %s", exc)
299
+ return None
300
+
301
+
302
+ @app.command("timeline")
303
+ def timeline(
304
+ reference: str = typer.Argument(..., help="A run id or task id to inspect."),
305
+ json_output: bool = typer.Option(False, "--json", help="Emit machine-readable JSON."),
306
+ limit: int = typer.Option(40, "--limit", help="Maximum number of trace events to show."),
307
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
308
+ ) -> None:
309
+ """Show a run's full reversible trace: events, checkpoints, and diff stat."""
310
+ root = _resolve_root(project_root)
311
+ logger.info("dev runs timeline: ref=%s", reference)
312
+ with log_stage("runs", project_root=root, subcommand="timeline"):
313
+ tl = _load_timeline_or_exit(root, reference)
314
+
315
+ if json_output:
316
+ data = tl.model_dump(mode="json")
317
+ if limit > 0:
318
+ data["events"] = data["events"][-limit:]
319
+ console.print_json(data=data)
320
+ return
321
+
322
+ title = tl.run_id or tl.task_id
323
+ console.print(f"[bold]Run[/bold] {title} [dim]task={tl.task_id or '?'} status={tl.status or '?'}[/dim]")
324
+ if tl.checkpoints:
325
+ table = Table(title="Checkpoints")
326
+ table.add_column("Stage")
327
+ table.add_column("Ref", overflow="fold")
328
+ table.add_column("Snapshot")
329
+ table.add_column("Patch", overflow="fold")
330
+ for cp in tl.checkpoints:
331
+ table.add_row(cp.stage, cp.ref, cp.sha[:12], cp.patch_path or "-")
332
+ console.print(table)
333
+ else:
334
+ console.print("[dim]No git checkpoints recorded for this run.[/dim]")
335
+
336
+ events = tl.events[-limit:] if limit > 0 else tl.events
337
+ if events:
338
+ table = Table(title=f"Trace events (last {len(events)})")
339
+ table.add_column("Time", overflow="fold")
340
+ table.add_column("Type")
341
+ table.add_column("Summary", overflow="fold")
342
+ for event in events:
343
+ table.add_row(event.timestamp, event.type, redact_text(event.summary or ""))
344
+ console.print(table)
345
+ else:
346
+ console.print("[dim]No trace events recorded for this run.[/dim]")
347
+
348
+ if tl.diff_stat:
349
+ console.print("\n[bold]Workspace changes[/bold]:")
350
+ console.print(tl.diff_stat)
351
+ console.print(
352
+ f"\nReversible: {'[green]yes[/green]' if tl.reversible else '[yellow]no[/yellow]'}"
353
+ + (f" — `dev runs revert {reference}` restores the pre-run state." if tl.reversible else "")
354
+ )
355
+ log_step("runs/complete", project_root=root, trace=True)
356
+
357
+
358
+ @app.command("diff")
359
+ def diff(
360
+ reference: str = typer.Argument(..., help="A run id or task id."),
361
+ stat: bool = typer.Option(False, "--stat", help="Show only the diff stat."),
362
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
363
+ ) -> None:
364
+ """Show the workspace changes a run produced (from its git checkpoints)."""
365
+ root = _resolve_root(project_root)
366
+ logger.info("dev runs diff: ref=%s stat=%s", reference, stat)
367
+ tl = _load_timeline_or_exit(root, reference)
368
+ from devcouncil.execution.run_trace import diff_run
369
+
370
+ output = diff_run(root, tl.task_id, stat_only=stat)
371
+ if not output:
372
+ console.print("[dim]No recorded diff for this run (no before/after checkpoints or patch).[/dim]")
373
+ raise typer.Exit(code=1)
374
+ console.print(output)
375
+
376
+
377
+ @app.command("revert")
378
+ def revert(
379
+ reference: str = typer.Argument(..., help="A run id or task id to revert."),
380
+ yes: bool = typer.Option(False, "--yes", "-y", help="Skip the confirmation prompt."),
381
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
382
+ ) -> None:
383
+ """Reverse a run's workspace effects using its git checkpoints (recorded in the trace)."""
384
+ root = _resolve_root(project_root)
385
+ logger.info("dev runs revert: ref=%s", reference)
386
+ with log_stage("runs", project_root=root, subcommand="revert"):
387
+ tl = _load_timeline_or_exit(root, reference)
388
+ if not tl.reversible:
389
+ console.print(
390
+ f"[red]Run {reference} has no before/after checkpoints; nothing to revert.[/red]"
391
+ )
392
+ raise typer.Exit(code=1)
393
+ if tl.diff_stat:
394
+ console.print("[bold]This will reverse:[/bold]")
395
+ console.print(tl.diff_stat)
396
+ if not yes and not typer.confirm(f"Revert run {reference} (task {tl.task_id})?"):
397
+ raise typer.Exit(code=1)
398
+
399
+ from devcouncil.execution.run_trace import revert_run
400
+
401
+ result = revert_run(root, reference)
402
+ if "failed" in result.message.lower() or result.message.startswith("No checkpoint"):
403
+ console.print(f"[red]{result.message}[/red] Try `dev rollback {tl.task_id}` for manual guidance.")
404
+ raise typer.Exit(code=1)
405
+ console.print(f"[green]Reverted run {reference}.[/green] {result.message}")
406
+ log_step("runs/complete", project_root=root, trace=True)
407
+
408
+
409
+ @app.command("supervise")
410
+ def supervise(
411
+ reference: str = typer.Argument(..., help="A run id or task id to review."),
412
+ llm: bool = typer.Option(True, "--llm/--no-llm", help="Use the run_supervisor model role (falls back to deterministic heuristics)."),
413
+ apply: bool = typer.Option(False, "--apply", help="If the verdict is 'revert', revert the run immediately."),
414
+ json_output: bool = typer.Option(False, "--json", help="Emit machine-readable JSON."),
415
+ project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root containing .devcouncil/."),
416
+ ) -> None:
417
+ """Ask the supervisor meta-agent for a keep/revert/repair verdict on a run."""
418
+ import asyncio
419
+
420
+ root = _resolve_root(project_root)
421
+ logger.info("dev runs supervise: ref=%s llm=%s apply=%s", reference, llm, apply)
422
+ with log_stage("runs", project_root=root, subcommand="supervise"):
423
+ tl = _load_timeline_or_exit(root, reference)
424
+ router = _supervisor_router(root) if llm else None
425
+
426
+ from devcouncil.execution.run_trace import supervise_run
427
+
428
+ verdict = asyncio.run(supervise_run(root, tl, router))
429
+
430
+ if json_output:
431
+ console.print_json(data=verdict.model_dump(mode="json"))
432
+ else:
433
+ color = {"keep": "green", "repair": "yellow", "revert": "red"}[verdict.verdict]
434
+ console.print(
435
+ f"Verdict: [{color}]{verdict.verdict}[/{color}] "
436
+ f"[dim](confidence {verdict.confidence:.2f}, {verdict.source})[/dim]"
437
+ )
438
+ if verdict.rationale:
439
+ console.print(verdict.rationale)
440
+ for finding in verdict.findings:
441
+ console.print(f" - {redact_text(finding)}")
442
+
443
+ if verdict.verdict == "revert":
444
+ if not tl.reversible:
445
+ console.print("[yellow]Verdict is 'revert' but the run has no checkpoints to revert with.[/yellow]")
446
+ elif apply:
447
+ from devcouncil.execution.run_trace import revert_run
448
+
449
+ result = revert_run(root, reference)
450
+ console.print(f"[green]Applied revert.[/green] {result.message}")
451
+ else:
452
+ console.print(f"Run [bold]dev runs revert {reference}[/bold] to apply it.")
453
+ log_step("runs/complete", project_root=root, verdict=verdict.verdict, trace=True)
@@ -1,32 +1,53 @@
1
+ import logging
1
2
  from pathlib import Path
2
3
 
3
4
  import typer
4
5
  from rich.console import Console
5
6
 
6
- from devcouncil.repo.ci_scaffold import WORKFLOW_RELPATH, detect_stacks, scaffold_ci
7
+ from devcouncil.repo.ci_scaffold import WORKFLOW_RELPATH, EVIDENCE_WORKFLOW_RELPATH, detect_stacks, scaffold_ci, scaffold_evidence_ci
8
+ from devcouncil.telemetry.stages import log_stage, log_step
7
9
 
8
10
  console = Console()
11
+ logger = logging.getLogger(__name__)
9
12
 
10
13
 
11
14
  def scaffold_ci_command(
12
15
  project_root: Path = typer.Option(Path("."), "--project-root", help="Repository root to scaffold CI into."),
13
16
  force: bool = typer.Option(False, "--force", help="Overwrite an existing devcouncil.yml workflow."),
17
+ evidence: bool = typer.Option(False, "--evidence", help="Also write .github/workflows/devcouncil-evidence.yml."),
14
18
  ):
15
19
  """Write a starter GitHub Actions workflow derived from the configured commands."""
16
20
  root = project_root.expanduser().resolve()
21
+ from devcouncil.telemetry.logging_setup import set_log_dir
22
+ set_log_dir(root)
23
+ logger.info("dev scaffold-ci: force=%s evidence=%s", force, evidence)
17
24
  if not (root / ".devcouncil").exists():
18
25
  console.print("[red]DevCouncil is not initialized here. Run 'dev setup' first.[/red]")
19
26
  raise typer.Exit(code=1)
20
27
 
21
- target = scaffold_ci(root, force=force)
22
- if target is None:
23
- console.print(
24
- f"[yellow]{WORKFLOW_RELPATH.as_posix()} already exists. "
25
- f"Re-run with --force to overwrite.[/yellow]"
26
- )
27
- return
28
+ with log_stage("scaffold", project_root=root, force=force):
29
+ log_step("scaffold/1: writing CI workflow", project_root=root, trace=True)
30
+ target = scaffold_ci(root, force=force)
31
+ if target is None:
32
+ console.print(
33
+ f"[yellow]{WORKFLOW_RELPATH.as_posix()} already exists. "
34
+ f"Re-run with --force to overwrite.[/yellow]"
35
+ )
36
+ log_step("scaffold/complete", project_root=root, skipped=True, trace=True)
37
+ else:
38
+ stacks = detect_stacks(root)
39
+ detected = ", ".join(sorted(stacks)) if stacks else "none auto-detected"
40
+ console.print(f"[green]Wrote {target.relative_to(root).as_posix()} (stacks: {detected}).[/green]")
41
+ console.print("[dim]Review the setup/install steps and commands before relying on it.[/dim]")
42
+ log_step("scaffold/complete", project_root=root, trace=True)
28
43
 
29
- stacks = detect_stacks(root)
30
- detected = ", ".join(sorted(stacks)) if stacks else "none auto-detected"
31
- console.print(f"[green]Wrote {target.relative_to(root).as_posix()} (stacks: {detected}).[/green]")
32
- console.print("[dim]Review the setup/install steps and commands before relying on it.[/dim]")
44
+ if evidence:
45
+ log_step("scaffold/2: writing evidence workflow", project_root=root, trace=True)
46
+ evidence_target = scaffold_evidence_ci(root, force=force)
47
+ if evidence_target is None:
48
+ console.print(
49
+ f"[yellow]{EVIDENCE_WORKFLOW_RELPATH.as_posix()} already exists. "
50
+ f"Re-run with --force to overwrite.[/yellow]"
51
+ )
52
+ else:
53
+ console.print(f"[green]Wrote {evidence_target.relative_to(root).as_posix()} (verify → evidence artifacts → optional GitHub Check/comment).[/green]")
@@ -1,13 +1,16 @@
1
- import json
1
+ from devcouncil.utils.json_persist import dump_json
2
+ import logging
2
3
  import typer
3
4
  from pathlib import Path
4
5
  from rich.console import Console
5
6
 
6
7
  from devcouncil.cli.commands.init import initialize_project
7
8
  from devcouncil.indexing.semantic_index import SemanticIndex
9
+ from devcouncil.telemetry.stages import log_stage, log_step
8
10
 
9
11
  app = typer.Typer(help="Semantic snapshots and diffs.")
10
12
  console = Console()
13
+ logger = logging.getLogger(__name__)
11
14
 
12
15
 
13
16
  @app.command("snapshot")
@@ -18,16 +21,22 @@ def snapshot(
18
21
  json_format: bool = typer.Option(False, "--json"),
19
22
  ):
20
23
  root = project_root.expanduser().resolve()
24
+ from devcouncil.telemetry.logging_setup import set_log_dir
25
+ set_log_dir(root)
26
+ logger.info("dev semantic snapshot: task=%s stage=%s", task_id, stage)
21
27
  initialize_project(root, quiet=True)
22
28
  if stage not in {"before", "after"}:
23
29
  console.print("[red]--stage must be before or after[/red]")
24
30
  raise typer.Exit(code=2)
25
- path = SemanticIndex(root).create_snapshot(task_id, stage)
26
- payload = {"task_id": task_id, "stage": stage, "path": str(path)}
27
- if json_format:
28
- typer.echo(json.dumps(payload, indent=2))
29
- else:
30
- console.print(f"[green]Wrote semantic snapshot:[/green] {path}")
31
+ with log_stage("semantic", project_root=root, subcommand="snapshot", task_id=task_id):
32
+ log_step("semantic/1: creating snapshot", project_root=root, task_id=task_id, trace=True)
33
+ path = SemanticIndex(root).create_snapshot(task_id, stage)
34
+ payload = {"task_id": task_id, "stage": stage, "path": str(path)}
35
+ if json_format:
36
+ typer.echo(dump_json(payload, indent=2))
37
+ else:
38
+ console.print(f"[green]Wrote semantic snapshot:[/green] {path}")
39
+ log_step("semantic/complete", project_root=root, task_id=task_id, trace=True)
31
40
 
32
41
 
33
42
  @app.command("diff")
@@ -37,11 +46,17 @@ def semantic_diff(
37
46
  json_format: bool = typer.Option(False, "--json"),
38
47
  ):
39
48
  root = project_root.expanduser().resolve()
49
+ from devcouncil.telemetry.logging_setup import set_log_dir
50
+ set_log_dir(root)
51
+ logger.info("dev semantic diff: task=%s", task_id)
40
52
  initialize_project(root, quiet=True)
41
- result = SemanticIndex(root).diff(task_id)
42
- if json_format:
43
- typer.echo(json.dumps(result, indent=2))
44
- else:
45
- console.print(f"[cyan]Summary:[/cyan] {result['summary']}")
46
- for item in result["classifications"]:
47
- console.print(f" - {item['type']}: {item.get('path', '')}")
53
+ with log_stage("semantic", project_root=root, subcommand="diff", task_id=task_id):
54
+ log_step("semantic/1: computing semantic diff", project_root=root, task_id=task_id, trace=True)
55
+ result = SemanticIndex(root).diff(task_id)
56
+ if json_format:
57
+ typer.echo(dump_json(result, indent=2))
58
+ else:
59
+ console.print(f"[cyan]Summary:[/cyan] {result['summary']}")
60
+ for item in result["classifications"]:
61
+ console.print(f" - {item['type']}: {item.get('path', '')}")
62
+ log_step("semantic/complete", project_root=root, task_id=task_id, trace=True)