devcouncil 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (366) hide show
  1. package/README.md +92 -33
  2. package/package.json +6 -2
  3. package/packages/codeintel-grammars/hatch_build.py +43 -0
  4. package/packages/codeintel-grammars/pyproject.toml +16 -0
  5. package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
  6. package/pyproject.toml +99 -4
  7. package/src/devcouncil/app/config.py +512 -20
  8. package/src/devcouncil/app/events.py +4 -23
  9. package/src/devcouncil/app/orchestrator.py +5 -0
  10. package/src/devcouncil/app/run_context.py +3 -3
  11. package/src/devcouncil/assets/__init__.py +4 -1
  12. package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
  13. package/src/devcouncil/campaign/__init__.py +71 -0
  14. package/src/devcouncil/campaign/bloom.py +137 -0
  15. package/src/devcouncil/campaign/dashboard.py +123 -0
  16. package/src/devcouncil/campaign/mailbox.py +305 -0
  17. package/src/devcouncil/campaign/notify.py +91 -0
  18. package/src/devcouncil/campaign/orchestrator.py +592 -0
  19. package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
  20. package/src/devcouncil/campaign/prompts/director.md +21 -0
  21. package/src/devcouncil/campaign/prompts/protocol.md +46 -0
  22. package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
  23. package/src/devcouncil/campaign/prompts/worker.md +24 -0
  24. package/src/devcouncil/campaign/roles.py +202 -0
  25. package/src/devcouncil/campaign/watcher.py +153 -0
  26. package/src/devcouncil/cli/commands/agents.py +24 -17
  27. package/src/devcouncil/cli/commands/artifacts.py +36 -27
  28. package/src/devcouncil/cli/commands/ast.py +12 -3
  29. package/src/devcouncil/cli/commands/baseline.py +21 -12
  30. package/src/devcouncil/cli/commands/boot.py +218 -0
  31. package/src/devcouncil/cli/commands/campaign.py +302 -0
  32. package/src/devcouncil/cli/commands/check.py +225 -12
  33. package/src/devcouncil/cli/commands/config.py +221 -74
  34. package/src/devcouncil/cli/commands/cost.py +137 -28
  35. package/src/devcouncil/cli/commands/dashboard.py +12 -4
  36. package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
  37. package/src/devcouncil/cli/commands/design.py +27 -17
  38. package/src/devcouncil/cli/commands/doctor.py +790 -8
  39. package/src/devcouncil/cli/commands/evidence.py +41 -20
  40. package/src/devcouncil/cli/commands/export.py +73 -0
  41. package/src/devcouncil/cli/commands/gaps.py +175 -0
  42. package/src/devcouncil/cli/commands/gated_write.py +76 -0
  43. package/src/devcouncil/cli/commands/go.py +220 -68
  44. package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
  45. package/src/devcouncil/cli/commands/handoff.py +45 -34
  46. package/src/devcouncil/cli/commands/hook.py +630 -85
  47. package/src/devcouncil/cli/commands/init.py +89 -30
  48. package/src/devcouncil/cli/commands/integrate.py +296 -1385
  49. package/src/devcouncil/cli/commands/lease.py +120 -0
  50. package/src/devcouncil/cli/commands/logs.py +12 -5
  51. package/src/devcouncil/cli/commands/lsp.py +40 -5
  52. package/src/devcouncil/cli/commands/map.py +317 -74
  53. package/src/devcouncil/cli/commands/mcp_server.py +12 -2
  54. package/src/devcouncil/cli/commands/okf.py +44 -6
  55. package/src/devcouncil/cli/commands/plan.py +184 -69
  56. package/src/devcouncil/cli/commands/prompt.py +26 -17
  57. package/src/devcouncil/cli/commands/provenance.py +79 -0
  58. package/src/devcouncil/cli/commands/repair.py +60 -49
  59. package/src/devcouncil/cli/commands/report.py +148 -40
  60. package/src/devcouncil/cli/commands/requirements.py +104 -0
  61. package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
  62. package/src/devcouncil/cli/commands/rollback.py +46 -35
  63. package/src/devcouncil/cli/commands/run.py +173 -8
  64. package/src/devcouncil/cli/commands/runs.py +298 -68
  65. package/src/devcouncil/cli/commands/scaffold.py +33 -12
  66. package/src/devcouncil/cli/commands/semantic.py +29 -14
  67. package/src/devcouncil/cli/commands/setup.py +103 -93
  68. package/src/devcouncil/cli/commands/shell.py +51 -42
  69. package/src/devcouncil/cli/commands/show.py +56 -42
  70. package/src/devcouncil/cli/commands/skills.py +29 -20
  71. package/src/devcouncil/cli/commands/status.py +80 -67
  72. package/src/devcouncil/cli/commands/task_gate.py +295 -0
  73. package/src/devcouncil/cli/commands/tasks.py +248 -19
  74. package/src/devcouncil/cli/commands/trace.py +14 -8
  75. package/src/devcouncil/cli/commands/verify.py +33 -8
  76. package/src/devcouncil/cli/commands/version.py +14 -6
  77. package/src/devcouncil/cli/commands/watch.py +56 -40
  78. package/src/devcouncil/cli/commands/watch_fs.py +30 -19
  79. package/src/devcouncil/cli/commands/wiki.py +278 -0
  80. package/src/devcouncil/cli/main.py +58 -1
  81. package/src/devcouncil/codeintel/__init__.py +16 -0
  82. package/src/devcouncil/codeintel/build_control.py +429 -0
  83. package/src/devcouncil/codeintel/build_worker.py +78 -0
  84. package/src/devcouncil/codeintel/debug/__init__.py +17 -0
  85. package/src/devcouncil/codeintel/debug/broker.py +114 -0
  86. package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
  87. package/src/devcouncil/codeintel/debug/consent.py +36 -0
  88. package/src/devcouncil/codeintel/debug/discovery.py +132 -0
  89. package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
  90. package/src/devcouncil/codeintel/debug/protocol.py +259 -0
  91. package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
  92. package/src/devcouncil/codeintel/debug/session.py +238 -0
  93. package/src/devcouncil/codeintel/debug/tracing.py +201 -0
  94. package/src/devcouncil/codeintel/languages/__init__.py +17 -0
  95. package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
  96. package/src/devcouncil/codeintel/languages/registry.py +149 -0
  97. package/src/devcouncil/codeintel/languages/workers.py +245 -0
  98. package/src/devcouncil/codeintel/query/__init__.py +5 -0
  99. package/src/devcouncil/codeintel/query/engine.py +289 -0
  100. package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
  101. package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
  102. package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
  103. package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
  104. package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
  105. package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
  106. package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
  107. package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
  108. package/src/devcouncil/codeintel/service.py +104 -0
  109. package/src/devcouncil/codeintel/store/__init__.py +15 -0
  110. package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
  111. package/src/devcouncil/codeintel/sync/__init__.py +19 -0
  112. package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
  113. package/src/devcouncil/codeintel/sync/incremental.py +484 -0
  114. package/src/devcouncil/codeintel/sync/lease.py +96 -0
  115. package/src/devcouncil/codeintel/sync/scope.py +98 -0
  116. package/src/devcouncil/council/__init__.py +4 -0
  117. package/src/devcouncil/council/prompts/__init__.py +4 -0
  118. package/src/devcouncil/domain/checkpoint_refs.py +17 -0
  119. package/src/devcouncil/domain/evidence.py +1 -0
  120. package/src/devcouncil/domain/gap.py +10 -0
  121. package/src/devcouncil/domain/requirement.py +5 -1
  122. package/src/devcouncil/domain/task.py +43 -2
  123. package/src/devcouncil/execution/checkpoints.py +25 -31
  124. package/src/devcouncil/execution/context_builder.py +15 -44
  125. package/src/devcouncil/execution/fs_watcher.py +64 -0
  126. package/src/devcouncil/execution/gated_write.py +203 -0
  127. package/src/devcouncil/execution/handoff.py +2 -1
  128. package/src/devcouncil/execution/hook_policy.py +19 -5
  129. package/src/devcouncil/execution/lease_ops.py +177 -0
  130. package/src/devcouncil/execution/lease_validation.py +71 -0
  131. package/src/devcouncil/execution/patch.py +3 -0
  132. package/src/devcouncil/execution/permissions.py +1 -0
  133. package/src/devcouncil/execution/policy_engine.py +205 -10
  134. package/src/devcouncil/execution/prompt_builder.py +278 -33
  135. package/src/devcouncil/execution/run_trace.py +356 -0
  136. package/src/devcouncil/execution/shell_session.py +46 -5
  137. package/src/devcouncil/execution/stop_gate.py +746 -0
  138. package/src/devcouncil/execution/stop_gate_history.py +113 -0
  139. package/src/devcouncil/execution/stop_gate_state.py +54 -0
  140. package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
  141. package/src/devcouncil/execution/task_gate_ops.py +590 -0
  142. package/src/devcouncil/execution/task_runner.py +19 -0
  143. package/src/devcouncil/executors/advisor_tool.py +315 -0
  144. package/src/devcouncil/executors/agent_registry.py +125 -17
  145. package/src/devcouncil/executors/claude_sdk.py +376 -0
  146. package/src/devcouncil/executors/coding_cli.py +724 -25
  147. package/src/devcouncil/executors/mini_swe.py +50 -8
  148. package/src/devcouncil/executors/native/agent.py +224 -19
  149. package/src/devcouncil/executors/openhands.py +50 -8
  150. package/src/devcouncil/executors/transient_retry.py +99 -0
  151. package/src/devcouncil/gating/checks/clean_git.py +5 -2
  152. package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
  153. package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
  154. package/src/devcouncil/gating/policy.py +46 -2
  155. package/src/devcouncil/indexing/ast_matcher.py +41 -4
  156. package/src/devcouncil/indexing/graph/__init__.py +78 -0
  157. package/src/devcouncil/indexing/graph/api_routes.py +522 -0
  158. package/src/devcouncil/indexing/graph/build.py +862 -0
  159. package/src/devcouncil/indexing/graph/cache.py +329 -0
  160. package/src/devcouncil/indexing/graph/communities.py +28 -0
  161. package/src/devcouncil/indexing/graph/cypher.py +107 -0
  162. package/src/devcouncil/indexing/graph/embeddings.py +194 -0
  163. package/src/devcouncil/indexing/graph/export.py +381 -0
  164. package/src/devcouncil/indexing/graph/export_links.py +81 -0
  165. package/src/devcouncil/indexing/graph/extract_python.py +307 -0
  166. package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
  167. package/src/devcouncil/indexing/graph/intel.py +668 -0
  168. package/src/devcouncil/indexing/graph/liveness.py +992 -0
  169. package/src/devcouncil/indexing/graph/okf_export.py +65 -0
  170. package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
  171. package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
  172. package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
  173. package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
  174. package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
  175. package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
  176. package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
  177. package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
  178. package/src/devcouncil/indexing/graph/query.py +302 -0
  179. package/src/devcouncil/indexing/graph/resolve.py +1020 -0
  180. package/src/devcouncil/indexing/graph/schema.py +103 -0
  181. package/src/devcouncil/indexing/graph_index.py +20 -29
  182. package/src/devcouncil/indexing/lsp.py +57 -25
  183. package/src/devcouncil/indexing/lsp_client.py +577 -0
  184. package/src/devcouncil/indexing/map_artifacts.py +355 -0
  185. package/src/devcouncil/indexing/map_refresh.py +141 -0
  186. package/src/devcouncil/indexing/repo_mapper.py +1509 -138
  187. package/src/devcouncil/indexing/semantic_index.py +12 -6
  188. package/src/devcouncil/indexing/subsystem_map.py +163 -0
  189. package/src/devcouncil/indexing/ts_imports.py +343 -0
  190. package/src/devcouncil/indexing/viz.py +964 -0
  191. package/src/devcouncil/indexing/walk.py +52 -0
  192. package/src/devcouncil/indexing/wiring.py +1776 -0
  193. package/src/devcouncil/integrations/actions.py +27 -4
  194. package/src/devcouncil/integrations/check.py +211 -16
  195. package/src/devcouncil/integrations/claude_assets.py +209 -12
  196. package/src/devcouncil/integrations/clients/__init__.py +1 -0
  197. package/src/devcouncil/integrations/clients/aider.py +52 -0
  198. package/src/devcouncil/integrations/clients/antigravity.py +87 -0
  199. package/src/devcouncil/integrations/clients/claude.py +339 -0
  200. package/src/devcouncil/integrations/clients/codex.py +39 -0
  201. package/src/devcouncil/integrations/clients/common.py +332 -0
  202. package/src/devcouncil/integrations/clients/cursor.py +164 -0
  203. package/src/devcouncil/integrations/clients/gemini.py +49 -0
  204. package/src/devcouncil/integrations/clients/grok.py +105 -0
  205. package/src/devcouncil/integrations/clients/hooks.py +500 -0
  206. package/src/devcouncil/integrations/clients/opencode.py +96 -0
  207. package/src/devcouncil/integrations/clients/warp.py +75 -0
  208. package/src/devcouncil/integrations/code_review_graph.py +2 -2
  209. package/src/devcouncil/integrations/github.py +73 -7
  210. package/src/devcouncil/integrations/integration_cli.py +197 -0
  211. package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
  212. package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
  213. package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
  214. package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
  215. package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
  216. package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
  217. package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
  218. package/src/devcouncil/integrations/mcp/handlers/git.py +281 -0
  219. package/src/devcouncil/integrations/mcp/handlers/graph.py +34 -0
  220. package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
  221. package/src/devcouncil/integrations/mcp/handlers/knowledge.py +28 -0
  222. package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
  223. package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
  224. package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
  225. package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
  226. package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
  227. package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
  228. package/src/devcouncil/integrations/mcp/handlers/provenance.py +87 -0
  229. package/src/devcouncil/integrations/mcp/handlers/read.py +103 -0
  230. package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
  231. package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
  232. package/src/devcouncil/integrations/mcp/handlers/runs.py +69 -0
  233. package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
  234. package/src/devcouncil/integrations/mcp/handlers/status.py +199 -0
  235. package/src/devcouncil/integrations/mcp/handlers/task.py +88 -0
  236. package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +922 -0
  237. package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
  238. package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
  239. package/src/devcouncil/integrations/mcp/handlers/wiki.py +53 -0
  240. package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
  241. package/src/devcouncil/integrations/mcp/server.py +265 -2391
  242. package/src/devcouncil/integrations/mcp/util.py +325 -0
  243. package/src/devcouncil/integrations/setup.py +152 -0
  244. package/src/devcouncil/knowledge/fetch.py +4 -0
  245. package/src/devcouncil/knowledge/knowledge_select.py +38 -0
  246. package/src/devcouncil/knowledge/okf.py +2 -1
  247. package/src/devcouncil/knowledge/resource_discovery.py +40 -0
  248. package/src/devcouncil/knowledge/wiki.py +643 -0
  249. package/src/devcouncil/knowledge/wiki_read.py +87 -0
  250. package/src/devcouncil/live/cards.py +7 -7
  251. package/src/devcouncil/live/models.py +4 -1
  252. package/src/devcouncil/live/reviewer.py +90 -11
  253. package/src/devcouncil/live/signals.py +4 -2
  254. package/src/devcouncil/live/summary.py +21 -3
  255. package/src/devcouncil/live/tasks.py +12 -3
  256. package/src/devcouncil/live/transcripts.py +69 -2
  257. package/src/devcouncil/llm/cache.py +5 -6
  258. package/src/devcouncil/llm/model_defaults.yaml +10 -10
  259. package/src/devcouncil/llm/provider.py +647 -73
  260. package/src/devcouncil/llm/router.py +271 -46
  261. package/src/devcouncil/llm/semantic_bridge.py +614 -0
  262. package/src/devcouncil/optimization/gepa_agent.py +6 -4
  263. package/src/devcouncil/optimization/skillopt.py +9 -5
  264. package/src/devcouncil/planning/arbiter_service.py +12 -3
  265. package/src/devcouncil/planning/correction_manifest.py +107 -10
  266. package/src/devcouncil/planning/plan_difficulty.py +69 -0
  267. package/src/devcouncil/planning/plan_service.py +5 -2
  268. package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
  269. package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
  270. package/src/devcouncil/planning/question_conversion.py +56 -0
  271. package/src/devcouncil/planning/spec_service.py +9 -3
  272. package/src/devcouncil/repo/ci_scaffold.py +197 -1
  273. package/src/devcouncil/repo/gitignore.py +1 -2
  274. package/src/devcouncil/reporting/evidence_export.py +124 -0
  275. package/src/devcouncil/reporting/evidence_html.py +210 -0
  276. package/src/devcouncil/reporting/json_report.py +16 -12
  277. package/src/devcouncil/reporting/markdown_report.py +38 -9
  278. package/src/devcouncil/reporting/mcp_resources.py +142 -0
  279. package/src/devcouncil/reporting/report_builder.py +40 -4
  280. package/src/devcouncil/reporting/task_provenance.py +42 -0
  281. package/src/devcouncil/reporting/verdict.py +75 -0
  282. package/src/devcouncil/skills/library/README.md +1 -0
  283. package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
  284. package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
  285. package/src/devcouncil/skills/library/devcouncil.md +93 -0
  286. package/src/devcouncil/skills/registry.py +43 -12
  287. package/src/devcouncil/storage/db.py +57 -11
  288. package/src/devcouncil/storage/models.py +6 -0
  289. package/src/devcouncil/storage/native.py +5 -3
  290. package/src/devcouncil/storage/repositories.py +50 -18
  291. package/src/devcouncil/telemetry/context.py +28 -0
  292. package/src/devcouncil/telemetry/cost.py +4 -5
  293. package/src/devcouncil/telemetry/logging_setup.py +78 -11
  294. package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
  295. package/src/devcouncil/telemetry/stages.py +27 -2
  296. package/src/devcouncil/telemetry/tracker.py +50 -13
  297. package/src/devcouncil/ui/dashboard.py +120 -8
  298. package/src/devcouncil/utils/fsio.py +58 -0
  299. package/src/devcouncil/utils/git_snapshot.py +112 -0
  300. package/src/devcouncil/utils/json_persist.py +53 -0
  301. package/src/devcouncil/utils/proc.py +89 -0
  302. package/src/devcouncil/verification/acceptance_compiler.py +36 -13
  303. package/src/devcouncil/verification/ad_hoc_check.py +95 -3
  304. package/src/devcouncil/verification/checks/__init__.py +41 -0
  305. package/src/devcouncil/verification/checks/acceptance.py +39 -0
  306. package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
  307. package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
  308. package/src/devcouncil/verification/checks/command_evidence.py +148 -0
  309. package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
  310. package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
  311. package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
  312. package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
  313. package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
  314. package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
  315. package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
  316. package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
  317. package/src/devcouncil/verification/checks/planned_files.py +98 -0
  318. package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
  319. package/src/devcouncil/verification/checks/stale_map.py +80 -0
  320. package/src/devcouncil/verification/checks/stub_scan.py +71 -0
  321. package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
  322. package/src/devcouncil/verification/checks/wiring.py +216 -0
  323. package/src/devcouncil/verification/claims/__init__.py +23 -0
  324. package/src/devcouncil/verification/claims/checks.py +395 -0
  325. package/src/devcouncil/verification/claims/mapper.py +168 -0
  326. package/src/devcouncil/verification/claims/models.py +39 -0
  327. package/src/devcouncil/verification/claims/transcript.py +92 -0
  328. package/src/devcouncil/verification/claims/verdict.py +88 -0
  329. package/src/devcouncil/verification/command_evidence.py +170 -0
  330. package/src/devcouncil/verification/command_malformation.py +147 -0
  331. package/src/devcouncil/verification/command_runner.py +164 -0
  332. package/src/devcouncil/verification/coverage_measurement.py +292 -0
  333. package/src/devcouncil/verification/diff_coverage.py +151 -0
  334. package/src/devcouncil/verification/difficulty.py +296 -0
  335. package/src/devcouncil/verification/effort_heuristics.py +178 -0
  336. package/src/devcouncil/verification/gap_ids.py +63 -0
  337. package/src/devcouncil/verification/gate_cache.py +194 -0
  338. package/src/devcouncil/verification/gate_selector.py +344 -0
  339. package/src/devcouncil/verification/git_diff_fallback.py +272 -0
  340. package/src/devcouncil/verification/implementation_reviewer.py +13 -0
  341. package/src/devcouncil/verification/incremental_check.py +241 -0
  342. package/src/devcouncil/verification/next_actions.py +60 -1
  343. package/src/devcouncil/verification/rigor_analytics.py +130 -0
  344. package/src/devcouncil/verification/sandbox.py +38 -11
  345. package/src/devcouncil/verification/stub_detector.py +369 -0
  346. package/src/devcouncil/verification/test_resolver.py +67 -1
  347. package/src/devcouncil/verification/verifier.py +137 -1666
  348. package/src/devcouncil/verification/verify_orchestration.py +610 -0
  349. package/src/devcouncil/verification/verify_setup.py +176 -0
  350. package/src/devcouncil/verification/wiki_refresh.py +208 -0
  351. package/src/semantic_layer/__init__.py +58 -0
  352. package/src/semantic_layer/benchmark.py +75 -0
  353. package/src/semantic_layer/cache.py +290 -0
  354. package/src/semantic_layer/compressor.py +137 -0
  355. package/src/semantic_layer/config.py +75 -0
  356. package/src/semantic_layer/embeddings.py +69 -0
  357. package/src/semantic_layer/llm_backends.py +99 -0
  358. package/src/semantic_layer/pipeline.py +111 -0
  359. package/src/semantic_layer/router.py +128 -0
  360. package/src/semantic_layer/tuner.py +72 -0
  361. package/uv.lock +973 -9
  362. package/src/devcouncil/artifacts/migrations.py +0 -20
  363. package/src/devcouncil/artifacts/schemas.py +0 -23
  364. package/src/devcouncil/artifacts/serializer.py +0 -21
  365. package/src/devcouncil/integrations/gitnexus.py +0 -70
  366. package/src/devcouncil/integrations/graphify.py +0 -34
@@ -32,11 +32,13 @@ from devcouncil.cli.commands import ( # noqa: E402 - imports follow stdio recon
32
32
  artifacts,
33
33
  agents,
34
34
  baseline,
35
+ boot,
35
36
  check,
36
37
  config,
37
38
  cost,
38
39
  ast,
39
40
  dashboard,
41
+ debug_cmd,
40
42
  design,
41
43
  doctor,
42
44
  go,
@@ -46,6 +48,7 @@ from devcouncil.cli.commands import ( # noqa: E402 - imports follow stdio recon
46
48
  logs,
47
49
  lsp,
48
50
  map,
51
+ graph_cmd,
49
52
  mcp_server,
50
53
  okf,
51
54
  plan,
@@ -57,19 +60,28 @@ from devcouncil.cli.commands import ( # noqa: E402 - imports follow stdio recon
57
60
  run,
58
61
  runs,
59
62
  setup,
63
+ campaign,
60
64
  show,
61
65
  status,
66
+ gaps,
67
+ requirements,
68
+ export,
62
69
  tasks,
63
70
  trace,
64
71
  verify,
65
72
  version,
66
73
  watch,
74
+ wiki,
67
75
  shell,
68
76
  semantic,
69
77
  evidence,
70
78
  handoff,
71
79
  skills,
72
80
  scaffold,
81
+ provenance,
82
+ lease,
83
+ gated_write,
84
+ task_gate,
73
85
  )
74
86
  from devcouncil.cli.commands.watch_fs import watch_fs # noqa: E402 - imports follow stdio reconfiguration
75
87
 
@@ -101,19 +113,29 @@ app.add_typer(setup.app, name="setup")
101
113
  app.add_typer(lsp.app, name="lsp")
102
114
  app.add_typer(ast.app, name="ast")
103
115
  app.add_typer(dashboard.app, name="dashboard")
116
+ app.add_typer(debug_cmd.app, name="debug")
104
117
  app.add_typer(watch.app, name="watch")
105
118
  app.add_typer(semantic.app, name="semantic")
106
119
  app.add_typer(evidence.app, name="evidence")
107
120
  app.add_typer(skills.app, name="skills")
108
121
  app.add_typer(okf.app, name="okf")
109
122
  app.add_typer(design.app, name="design")
123
+ app.add_typer(wiki.app, name="wiki")
124
+ app.add_typer(campaign.app, name="campaign")
125
+ app.add_typer(provenance.resource_app, name="resource")
126
+ app.add_typer(lease.lease_app, name="lease")
127
+ app.add_typer(task_gate.scope_app, name="scope")
110
128
  watch.app.command("fs")(watch_fs)
111
129
 
112
130
  # Direct command registrations (those defined as def cmd())
113
131
  app.command(name="baseline")(baseline.baseline)
132
+ app.command(name="boot")(boot.boot)
114
133
  app.command(name="e2e")(go.go)
115
134
  app.command(name="go")(go.go)
116
135
  app.command(name="map")(map.map_repo)
136
+ app.add_typer(graph_cmd.app, name="graph")
137
+ app.add_typer(graph_cmd.corpus_app, name="corpus")
138
+ app.command(name="graph-context")(map.graph_context_cmd)
117
139
  app.command(name="scaffold-ci")(scaffold.scaffold_ci_command)
118
140
  app.command(name="plan")(plan.plan)
119
141
  app.command(name="approve")(plan.approve)
@@ -130,6 +152,23 @@ app.command(name="verify")(verify.verify)
130
152
  app.command(name="check")(check.check)
131
153
  app.command(name="repair")(repair.repair)
132
154
  app.command(name="status")(status.status)
155
+ app.command(name="gaps")(gaps.gaps)
156
+ app.command(name="provenance")(provenance.provenance)
157
+ app.command(name="checkout")(lease.checkout)
158
+ app.command(name="release")(lease.release)
159
+ app.command(name="write")(gated_write.write)
160
+ app.command(name="apply-patch")(gated_write.apply_patch)
161
+ app.command(name="next-task")(task_gate.next_task)
162
+ app.command(name="policy-check")(task_gate.policy_check)
163
+ app.command(name="record-command")(task_gate.record_command)
164
+ app.command(name="run-cmd")(task_gate.run_cmd)
165
+ app.command(name="attach-committed-range")(task_gate.attach_committed_range)
166
+ app.command(name="verify-leased")(task_gate.verify_leased)
167
+ app.command(name="evidence-append")(task_gate.evidence_append)
168
+ app.command(name="evidence-list")(task_gate.evidence_list)
169
+ app.command(name="handoff-leased")(task_gate.handoff_leased)
170
+ app.command(name="requirements")(requirements.requirements)
171
+ app.command(name="export")(export.export_state)
133
172
  app.command(name="optimize")(agents.optimize_agent)
134
173
 
135
174
  @app.callback()
@@ -168,5 +207,23 @@ def main(
168
207
  configure_logging(verbosity=verbose, quiet=quiet, log_level=log_level)
169
208
  return
170
209
 
210
+ def run_cli() -> None:
211
+ """Console-script entry: run the CLI with EPIPE-safe stdout.
212
+
213
+ ``dev map | head`` (or any consumer closing the pipe early) must exit with
214
+ the conventional SIGPIPE status instead of a BrokenPipeError traceback.
215
+ """
216
+ import os
217
+
218
+ try:
219
+ app()
220
+ except BrokenPipeError:
221
+ try:
222
+ os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno())
223
+ except OSError:
224
+ pass
225
+ sys.exit(128 + 13)
226
+
227
+
171
228
  if __name__ == "__main__":
172
- app()
229
+ run_cli()
@@ -0,0 +1,16 @@
1
+ """Transactional, project-scoped code intelligence services.
2
+
3
+ The legacy :mod:`devcouncil.indexing.graph` package remains the public
4
+ compatibility surface. New code should obtain a :class:`CodeIntelService` for a
5
+ canonical project root and query its committed SQLite generation.
6
+ """
7
+
8
+ from devcouncil.codeintel.service import CodeIntelService, get_codeintel_service
9
+ from devcouncil.codeintel.store import CodeIntelStore, StoreStatus
10
+
11
+ __all__ = [
12
+ "CodeIntelService",
13
+ "CodeIntelStore",
14
+ "StoreStatus",
15
+ "get_codeintel_service",
16
+ ]
@@ -0,0 +1,429 @@
1
+ """Single-writer graph build sessions and supervised full-build isolation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import queue
8
+ import signal
9
+ import subprocess
10
+ import sys
11
+ import threading
12
+ import time
13
+ import uuid
14
+ from collections import deque
15
+ from contextlib import contextmanager
16
+ from dataclasses import asdict, dataclass
17
+ from pathlib import Path
18
+ from typing import TYPE_CHECKING, Iterator
19
+
20
+ from devcouncil.codeintel.service import get_codeintel_service
21
+ from devcouncil.indexing.graph.schema import CodeGraph
22
+ from devcouncil.utils.json_persist import read_json, write_json
23
+
24
+ # WriterLease is imported lazily inside graph_build_session to avoid
25
+ # sync.__init__ → incremental → build_control circular import at module load.
26
+ if TYPE_CHECKING:
27
+ from devcouncil.codeintel.sync.lease import WriterLease
28
+
29
+ STATUS_REL = Path(".devcouncil") / "codeintel" / "build_status.json"
30
+
31
+
32
+ class GraphBuildBusy(RuntimeError):
33
+ """Another thread or process owns the graph writer session."""
34
+
35
+
36
+ class GraphBuildTimeout(TimeoutError):
37
+ """A supervised graph build exceeded its progress or total deadline."""
38
+
39
+
40
+ class GraphBuildFailed(RuntimeError):
41
+ """A supervised graph worker exited without committing a graph."""
42
+
43
+
44
+ @dataclass
45
+ class BuildStatus:
46
+ build_id: str = ""
47
+ state: str = "idle"
48
+ mode: str = ""
49
+ pid: int | None = None
50
+ phase: str = ""
51
+ completed: int = 0
52
+ total: int = 0
53
+ started_at: float | None = None
54
+ last_progress_at: float | None = None
55
+ generation_before: int | None = None
56
+ generation_after: int | None = None
57
+ degraded_reason: str = ""
58
+ compatibility_export: str = "unknown"
59
+ stall_timeout_seconds: float = 90.0
60
+ total_timeout_seconds: float = 900.0
61
+
62
+
63
+ @dataclass
64
+ class IsolatedBuildResult:
65
+ graph: CodeGraph
66
+ status: BuildStatus
67
+
68
+
69
+ _LOCKS: dict[Path, threading.RLock] = {}
70
+ _LOCKS_GUARD = threading.Lock()
71
+ _LOCAL = threading.local()
72
+
73
+
74
+ def status_path(root: Path) -> Path:
75
+ return root.expanduser().resolve() / STATUS_REL
76
+
77
+
78
+ def read_build_status(root: Path) -> BuildStatus:
79
+ try:
80
+ raw = read_json(status_path(root))
81
+ status = BuildStatus(**{
82
+ key: raw[key] for key in BuildStatus.__dataclass_fields__ if key in raw
83
+ })
84
+ if status.state == "building":
85
+ if status.pid is not None and not _pid_alive(status.pid):
86
+ status.state = "stale"
87
+ status.degraded_reason = "recorded graph worker is no longer running"
88
+ elif (
89
+ status.last_progress_at is not None
90
+ and time.time() - status.last_progress_at > status.stall_timeout_seconds
91
+ ):
92
+ status.state = "stalled"
93
+ status.degraded_reason = (
94
+ f"no recorded graph progress for {status.stall_timeout_seconds:.1f}s"
95
+ )
96
+ return status
97
+ except Exception:
98
+ return BuildStatus()
99
+
100
+
101
+ def _pid_alive(pid: int) -> bool:
102
+ try:
103
+ os.kill(pid, 0)
104
+ except ProcessLookupError:
105
+ return False
106
+ except (PermissionError, OSError):
107
+ return True
108
+ return True
109
+
110
+
111
+ def _write_status(root: Path, status: BuildStatus) -> None:
112
+ path = status_path(root)
113
+ path.parent.mkdir(parents=True, exist_ok=True)
114
+ write_json(path, asdict(status))
115
+
116
+
117
+ def record_inline_build_status(
118
+ root: Path,
119
+ *,
120
+ state: str,
121
+ mode: str,
122
+ phase: str,
123
+ generation_before: int | None,
124
+ generation_after: int | None,
125
+ reason: str = "",
126
+ compatibility_export: str = "healthy",
127
+ ) -> BuildStatus:
128
+ now = time.time()
129
+ status = BuildStatus(
130
+ build_id=uuid.uuid4().hex,
131
+ state=state,
132
+ mode=mode,
133
+ pid=os.getpid(),
134
+ phase=phase,
135
+ completed=1,
136
+ total=1,
137
+ started_at=now,
138
+ last_progress_at=now,
139
+ generation_before=generation_before,
140
+ generation_after=generation_after,
141
+ degraded_reason=reason,
142
+ compatibility_export=compatibility_export,
143
+ stall_timeout_seconds=90.0,
144
+ total_timeout_seconds=900.0,
145
+ )
146
+ _write_status(root.expanduser().resolve(), status)
147
+ return status
148
+
149
+
150
+ def _root_lock(root: Path) -> threading.RLock:
151
+ with _LOCKS_GUARD:
152
+ return _LOCKS.setdefault(root, threading.RLock())
153
+
154
+
155
+ def _lease_timeouts(root: Path) -> tuple[float, float]:
156
+ """Return ``(build_timeout, sync_timeout)`` seconds for writer-lease waits."""
157
+ build_timeout = 30.0
158
+ sync_timeout = 5.0
159
+ try:
160
+ from devcouncil.app.config import load_config
161
+
162
+ cfg = load_config(root).code_intelligence
163
+ build_timeout = float(
164
+ getattr(cfg, "writer_lease_timeout_seconds", build_timeout) or build_timeout
165
+ )
166
+ sync_timeout = float(
167
+ getattr(cfg, "writer_lease_sync_timeout_seconds", sync_timeout) or sync_timeout
168
+ )
169
+ except Exception:
170
+ pass
171
+ return max(0.1, build_timeout), max(0.1, sync_timeout)
172
+
173
+
174
+ @contextmanager
175
+ def graph_build_session(
176
+ root: Path,
177
+ *,
178
+ lease: WriterLease | None = None,
179
+ timeout: float | None = None,
180
+ ) -> Iterator[None]:
181
+ """Hold the process-local and cross-process writer locks at outermost depth.
182
+
183
+ ``timeout`` bounds how long a contending writer waits for ``writer.lock``
184
+ (exponential backoff). Defaults to ``code_intelligence.writer_lease_timeout_seconds``.
185
+ """
186
+ from devcouncil.codeintel.sync.lease import WriterLease
187
+
188
+ root = root.expanduser().resolve()
189
+ lock = _root_lock(root)
190
+ with lock:
191
+ depths = getattr(_LOCAL, "depths", None)
192
+ if depths is None:
193
+ depths = {}
194
+ _LOCAL.depths = depths
195
+ leases = getattr(_LOCAL, "leases", None)
196
+ if leases is None:
197
+ leases = {}
198
+ _LOCAL.leases = leases
199
+ depth = int(depths.get(root, 0))
200
+ owned_lease = None
201
+ if depth == 0:
202
+ owned_lease = lease or WriterLease(
203
+ root / ".devcouncil" / "codeintel" / "writer.lock"
204
+ )
205
+ wait = timeout if timeout is not None else _lease_timeouts(root)[0]
206
+ if not owned_lease.acquire_with_retry(timeout=wait):
207
+ raise GraphBuildBusy("another process owns the code-intelligence writer lease")
208
+ leases[root] = owned_lease
209
+ depths[root] = depth + 1
210
+ try:
211
+ yield
212
+ finally:
213
+ remaining = int(depths.get(root, 1)) - 1
214
+ if remaining:
215
+ depths[root] = remaining
216
+ else:
217
+ depths.pop(root, None)
218
+ held = leases.pop(root, owned_lease)
219
+ if held is not None:
220
+ held.release()
221
+
222
+
223
+ @contextmanager
224
+ def yield_writer_lease_for_child(root: Path) -> Iterator[None]:
225
+ """Temporarily release this process's writer lease so a supervised child can own it.
226
+
227
+ Isolated graph workers must hold ``writer.lock`` themselves. When the parent already
228
+ owns the lease (nested ``graph_build_session``), release it for the child lifetime and
229
+ re-acquire afterward with bounded backoff so a concurrent watcher cannot permanently
230
+ starve the parent. If the parent does not own the lease, this is a no-op.
231
+ """
232
+ root = root.expanduser().resolve()
233
+ leases = getattr(_LOCAL, "leases", {}) or {}
234
+ held = leases.get(root)
235
+ if held is None:
236
+ yield
237
+ return
238
+ held.release()
239
+ try:
240
+ yield
241
+ finally:
242
+ # Another watcher may have claimed the lock in the yield window; wait out a
243
+ # full build-lease budget before failing the otherwise-successful child commit.
244
+ wait = _lease_timeouts(root)[0]
245
+ if not held.acquire_with_retry(timeout=wait):
246
+ raise GraphBuildBusy(
247
+ "could not re-acquire the code-intelligence writer lease after isolated build"
248
+ )
249
+
250
+
251
+ def _terminate_worker(process: subprocess.Popen[str]) -> None:
252
+ if process.poll() is not None:
253
+ return
254
+ try:
255
+ if os.name == "nt":
256
+ process.terminate()
257
+ else:
258
+ os.killpg(process.pid, signal.SIGTERM)
259
+ process.wait(timeout=5.0)
260
+ except (OSError, subprocess.TimeoutExpired):
261
+ try:
262
+ if os.name == "nt":
263
+ process.kill()
264
+ else:
265
+ os.killpg(process.pid, signal.SIGKILL)
266
+ except OSError:
267
+ pass
268
+
269
+
270
+ def run_isolated_full_build(
271
+ root: Path,
272
+ *,
273
+ changed_paths: set[str] | None = None,
274
+ liveness: bool = True,
275
+ ) -> IsolatedBuildResult:
276
+ """Build and persist a full graph in a heartbeat-supervised child process."""
277
+ from devcouncil.app.config import IndexingConfig, load_config
278
+
279
+ root = root.expanduser().resolve()
280
+ try:
281
+ config = load_config(root).indexing
282
+ except FileNotFoundError:
283
+ config = IndexingConfig()
284
+ before = get_codeintel_service(root).store.current_generation()
285
+ build_id = uuid.uuid4().hex
286
+ command = [
287
+ sys.executable,
288
+ "-m",
289
+ "devcouncil.codeintel.build_worker",
290
+ "--root",
291
+ str(root),
292
+ "--build-id",
293
+ build_id,
294
+ ]
295
+ if not liveness:
296
+ command.append("--no-liveness")
297
+ for path in sorted(changed_paths or set()):
298
+ command.extend(("--changed-path", path))
299
+ creationflags = 0
300
+ if os.name == "nt":
301
+ creationflags = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
302
+ process = subprocess.Popen(
303
+ command,
304
+ cwd=root,
305
+ stdout=subprocess.PIPE,
306
+ stderr=subprocess.PIPE,
307
+ text=True,
308
+ bufsize=1,
309
+ start_new_session=os.name != "nt",
310
+ creationflags=creationflags,
311
+ )
312
+ now = time.time()
313
+ status = BuildStatus(
314
+ build_id=build_id,
315
+ state="building",
316
+ mode="full",
317
+ pid=process.pid,
318
+ phase="starting",
319
+ started_at=now,
320
+ last_progress_at=now,
321
+ generation_before=before,
322
+ stall_timeout_seconds=float(config.build_stall_timeout_seconds),
323
+ total_timeout_seconds=float(config.build_total_timeout_seconds),
324
+ )
325
+ _write_status(root, status)
326
+ messages: queue.Queue[str] = queue.Queue()
327
+ stderr_lines: deque[str] = deque(maxlen=200)
328
+
329
+ def _read_stdout() -> None:
330
+ assert process.stdout is not None
331
+ for line in process.stdout:
332
+ messages.put(line)
333
+
334
+ reader = threading.Thread(target=_read_stdout, name="devcouncil-graph-worker-output", daemon=True)
335
+ reader.start()
336
+
337
+ def _read_stderr() -> None:
338
+ assert process.stderr is not None
339
+ for line in process.stderr:
340
+ stderr_lines.append(line)
341
+
342
+ error_reader = threading.Thread(
343
+ target=_read_stderr,
344
+ name="devcouncil-graph-worker-errors",
345
+ daemon=True,
346
+ )
347
+ error_reader.start()
348
+ started = time.monotonic()
349
+ last_progress = started
350
+ stall = float(config.build_stall_timeout_seconds)
351
+ total = float(config.build_total_timeout_seconds)
352
+ final_payload: dict[str, object] = {}
353
+ after: int | None = None
354
+ # Child must own writer.lock; release any parent-held lease for the supervision window.
355
+ with yield_writer_lease_for_child(root):
356
+ try:
357
+ while True:
358
+ try:
359
+ line = messages.get(timeout=0.2)
360
+ except queue.Empty:
361
+ line = ""
362
+ if line:
363
+ try:
364
+ payload = json.loads(line)
365
+ except json.JSONDecodeError:
366
+ payload = {}
367
+ if payload:
368
+ last_progress = time.monotonic()
369
+ status.phase = str(payload.get("phase") or status.phase)
370
+ status.completed = int(payload.get("completed") or 0)
371
+ status.total = int(payload.get("total") or 0)
372
+ status.last_progress_at = time.time()
373
+ status.compatibility_export = str(
374
+ payload.get("compatibility_export") or status.compatibility_export
375
+ )
376
+ if payload.get("state") in {"complete", "degraded"}:
377
+ final_payload = payload
378
+ _write_status(root, status)
379
+ elapsed = time.monotonic() - started
380
+ idle = time.monotonic() - last_progress
381
+ if elapsed > total or idle > stall:
382
+ reason = (
383
+ f"graph build exceeded {total:.1f}s total timeout"
384
+ if elapsed > total
385
+ else f"graph build made no progress for {stall:.1f}s"
386
+ )
387
+ _terminate_worker(process)
388
+ status.state = "timed_out"
389
+ status.degraded_reason = reason
390
+ status.generation_after = get_codeintel_service(root).store.current_generation()
391
+ _write_status(root, status)
392
+ raise GraphBuildTimeout(reason)
393
+ if process.poll() is not None:
394
+ reader.join(timeout=0.2)
395
+ if not reader.is_alive() and messages.empty():
396
+ break
397
+ reader.join(timeout=1.0)
398
+ error_reader.join(timeout=1.0)
399
+ after = get_codeintel_service(root).store.current_generation()
400
+ if process.returncode != 0 or after is None or after == before:
401
+ stderr = "".join(stderr_lines).strip()
402
+ reason = stderr[-4000:] or f"graph worker exited with code {process.returncode}"
403
+ status.state = "failed"
404
+ status.degraded_reason = reason
405
+ status.generation_after = after
406
+ _write_status(root, status)
407
+ raise GraphBuildFailed(reason)
408
+ status.state = str(final_payload.get("state") or "complete")
409
+ status.phase = "complete"
410
+ status.completed = status.total or 1
411
+ status.total = status.total or 1
412
+ status.generation_after = after
413
+ status.compatibility_export = str(
414
+ final_payload.get("compatibility_export") or "healthy"
415
+ )
416
+ status.degraded_reason = str(final_payload.get("reason") or "")
417
+ _write_status(root, status)
418
+ finally:
419
+ if process.poll() is None:
420
+ _terminate_worker(process)
421
+ # Reload under the re-acquired lease so a concurrent watcher that wrote during
422
+ # the yield window cannot leave the parent holding a stale in-memory graph.
423
+ after = get_codeintel_service(root).store.current_generation()
424
+ status.generation_after = after
425
+ graph = get_codeintel_service(root).load()
426
+ if graph is None:
427
+ raise GraphBuildFailed("graph worker committed a generation that could not be loaded")
428
+ _write_status(root, status)
429
+ return IsolatedBuildResult(graph=graph, status=status)
@@ -0,0 +1,78 @@
1
+ """Internal subprocess entry point for supervised full graph builds.
2
+
3
+ devcouncil: allow-unwired — launched with ``python -m`` by build_control.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import json
10
+ from pathlib import Path
11
+
12
+
13
+ def _emit(**payload: object) -> None:
14
+ print(json.dumps(payload, separators=(",", ":")), flush=True)
15
+
16
+
17
+ def main() -> int:
18
+ parser = argparse.ArgumentParser(add_help=False)
19
+ parser.add_argument("--root", required=True)
20
+ parser.add_argument("--build-id", required=True)
21
+ parser.add_argument("--changed-path", action="append", default=[])
22
+ parser.add_argument("--no-liveness", action="store_true")
23
+ args = parser.parse_args()
24
+ root = Path(args.root).expanduser().resolve()
25
+
26
+ def progress(phase: str, completed: int, total: int) -> None:
27
+ _emit(
28
+ build_id=args.build_id,
29
+ state="building",
30
+ phase=phase,
31
+ completed=completed,
32
+ total=total,
33
+ )
34
+
35
+ from devcouncil.codeintel.build_control import graph_build_session
36
+ from devcouncil.indexing.graph.build import (
37
+ CompatibilityGraphTooLarge,
38
+ build_code_graph,
39
+ write_code_graph,
40
+ )
41
+
42
+ # Acquire the cross-process writer lease in the child so an orphaned worker
43
+ # (parent crash) still serializes against watchers/MCP writers.
44
+ with graph_build_session(root):
45
+ graph = build_code_graph(
46
+ root,
47
+ changed_paths=set(args.changed_path),
48
+ liveness=not args.no_liveness,
49
+ progress=progress,
50
+ )
51
+ graph.meta.update({
52
+ "incremental": False,
53
+ "changed_paths": sorted(set(args.changed_path)),
54
+ "affected_paths": sorted(set(args.changed_path)),
55
+ "affected_fraction": 1.0,
56
+ "resolution_scope": "full",
57
+ })
58
+ compatibility_export = "healthy"
59
+ reason = ""
60
+ try:
61
+ write_code_graph(root, graph, _lease_held=True)
62
+ except CompatibilityGraphTooLarge as exc:
63
+ compatibility_export = "degraded"
64
+ reason = str(exc)
65
+ _emit(
66
+ build_id=args.build_id,
67
+ state="degraded" if reason else "complete",
68
+ phase="complete",
69
+ completed=1,
70
+ total=1,
71
+ compatibility_export=compatibility_export,
72
+ reason=reason,
73
+ )
74
+ return 0
75
+
76
+
77
+ if __name__ == "__main__":
78
+ raise SystemExit(main())
@@ -0,0 +1,17 @@
1
+ """Debugger control and fingerprint-scoped runtime evidence."""
2
+
3
+ from devcouncil.codeintel.debug.consent import require_debug_consent
4
+ from devcouncil.codeintel.debug.discovery import AdapterInfo, discover_adapters
5
+ from devcouncil.codeintel.debug.protocol import DAPClient, DAPError
6
+ from devcouncil.codeintel.debug.session import DebugSession, DebugSessionManager, get_debug_manager
7
+
8
+ __all__ = [
9
+ "AdapterInfo",
10
+ "DAPClient",
11
+ "DAPError",
12
+ "DebugSession",
13
+ "DebugSessionManager",
14
+ "discover_adapters",
15
+ "get_debug_manager",
16
+ "require_debug_consent",
17
+ ]