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
@@ -1,5 +1,4 @@
1
1
  import ast
2
- import json
3
2
  import logging
4
3
  import re
5
4
  from pathlib import Path
@@ -8,6 +7,7 @@ from typing import List
8
7
  from devcouncil.domain.requirement import Requirement
9
8
  from devcouncil.domain.task import Task
10
9
  from devcouncil.integrations.code_review_graph import CodeReviewGraphAdapter
10
+ from devcouncil.utils.json_persist import read_json
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
@@ -22,6 +22,24 @@ MAX_SYMBOLS_PER_FILE = 40
22
22
  # lowest-priority ones are dropped (with a marker) if the whole prompt would exceed this.
23
23
  MAX_PROMPT_CHARS = 60_000
24
24
 
25
+ # Appended to the instructions when the task classifies as HARD (see
26
+ # devcouncil.verification.difficulty). Tells the executor up front that the
27
+ # anti-laziness gates will block, so it does not discover strict mode via a
28
+ # failed verify + repair loop. Kept compact — it rides in the never-dropped core.
29
+ _HARD_TASK_RIGOR_SECTION = """
30
+ ## Rigor (this task is classified HARD — verification is strict)
31
+ - No stubs, placeholders, or TODO/FIXME markers in added code: the verifier scans the
32
+ diff and BLOCKS on them. Intentional scaffolding requires the task description to
33
+ mention "scaffolding" AND the line to carry `devcouncil: allow-stub` (declared stubs
34
+ are surfaced for human review even when suppressed).
35
+ - Every acceptance criterion needs a passing behavioral check; changed lines must be
36
+ exercised by the tests you run (diff coverage is enforced).
37
+ - Do not claim completion without running the expected tests and seeing them pass.
38
+ - Never delete, skip, or weaken a test to get to green.
39
+ - If part of the task is genuinely infeasible, say so explicitly and state what is
40
+ missing instead of stubbing around it.
41
+ """
42
+
25
43
  # Rough chars-per-token for English/code; deliberately conservative so the derived
26
44
  # budget under-fills the window rather than over-fills it.
27
45
  _CHARS_PER_TOKEN = 4
@@ -36,13 +54,15 @@ _MIN_PROMPT_CHARS = 8_000
36
54
  def _local_context_window_budget(project_root: Path, cfg=None) -> int | None:
37
55
  """Char budget derived from a constrained local context window, or ``None``.
38
56
 
39
- When the run targets the local Ollama provider with an explicit ``OLLAMA_NUM_CTX``,
40
- the server silently truncates anything past that window — so a char-only budget that
41
- ignores it lets the carefully-assembled prompt get cut off mid-stream. Returns a char
42
- budget that fits the window (minus completion headroom) so :meth:`build_task_prompt`
43
- can cap itself. Returns ``None`` for cloud providers / unset windows, leaving the
44
- default behavior (and the large cloud CLIs' big windows) untouched. Best-effort:
45
- any error degrades to ``None``.
57
+ When the run targets the local Ollama provider, the server silently truncates
58
+ anything past its context window — so a char-only budget that ignores it lets the
59
+ carefully-assembled prompt get cut off mid-stream. Returns a char budget that fits
60
+ the window the provider will actually request (``OLLAMA_NUM_CTX``, else the
61
+ provider's raised ``DEFAULT_NUM_CTX``), minus completion headroom, so
62
+ :meth:`build_task_prompt` can cap itself. Returns ``None`` for cloud providers and
63
+ for an explicit ``OLLAMA_NUM_CTX=0`` opt-out (server-default window, unknowable
64
+ here), leaving the default behavior untouched. Best-effort: any error degrades
65
+ to ``None``.
46
66
 
47
67
  ``cfg`` may be a pre-loaded config (loaded once per task by ``build_task_prompt``); when
48
68
  ``None`` it is loaded here so other callers keep working."""
@@ -61,8 +81,8 @@ def _local_context_window_budget(project_root: Path, cfg=None) -> int | None:
61
81
 
62
82
  num_ctx = OllamaProvider._resolve_num_ctx()
63
83
  if not num_ctx:
64
- # No explicit window: Ollama uses a small default, but DevCouncil cannot know it.
65
- # `dev doctor` already warns to set OLLAMA_NUM_CTX; don't guess a cap here.
84
+ # Explicit OLLAMA_NUM_CTX=0 opt-out: the server-default window applies and
85
+ # DevCouncil cannot know it; don't guess a cap here.
66
86
  return None
67
87
  usable_tokens = num_ctx - _RESERVED_COMPLETION_TOKENS
68
88
  if usable_tokens <= 0:
@@ -81,6 +101,7 @@ _LANG_BY_EXT = {
81
101
  class PromptBuilder:
82
102
  def __init__(self, project_root: Path = Path(".")):
83
103
  self.project_root = project_root
104
+ self._file_text_cache: dict[str, str] = {}
84
105
 
85
106
  @staticmethod
86
107
  def _lang_for(path: str) -> str:
@@ -101,7 +122,7 @@ class PromptBuilder:
101
122
  ``path``) so that if the file's content differs between the two reads, the outline
102
123
  is recomputed from the current text rather than served stale — and using the text
103
124
  directly (rather than its hash) means there is no collision risk."""
104
- cache = getattr(self, "_outline_cache", None)
125
+ cache: dict[tuple[str, str], List[str]] | None = getattr(self, "_outline_cache", None)
105
126
  key = (path, text)
106
127
  if cache is not None and key in cache:
107
128
  return cache[key]
@@ -231,6 +252,7 @@ class PromptBuilder:
231
252
  new files are shown as headers only."""
232
253
  from devcouncil.utils.redaction import redact_string
233
254
 
255
+ self._file_text_cache.clear()
234
256
  blocks: List[str] = []
235
257
  budget = MAX_FILE_CONTEXT_CHARS
236
258
  omitted = 0
@@ -248,6 +270,7 @@ class PromptBuilder:
248
270
  except Exception:
249
271
  blocks.append(f"### `{pf.path}` [{label}] — [error reading file]\n")
250
272
  continue
273
+ self._file_text_cache[pf.path.replace("\\", "/")] = raw
251
274
  content = redact_string(raw)
252
275
  cap = min(MAX_PER_FILE_CHARS, budget)
253
276
  truncated = len(content) > cap
@@ -287,7 +310,7 @@ class PromptBuilder:
287
310
  if not map_path.exists():
288
311
  return None
289
312
  try:
290
- data = json.loads(map_path.read_text(encoding="utf-8"))
313
+ data = read_json(map_path)
291
314
  return data if isinstance(data, dict) else None
292
315
  except Exception:
293
316
  return None
@@ -440,6 +463,185 @@ class PromptBuilder:
440
463
  )
441
464
  return design_block, knowledge_block
442
465
 
466
+ # Impact block bounds: this is a short, always-on summary — keep it tight so it can
467
+ # never crowd out the detailed file bodies it complements.
468
+ _IMPACT_MAX_FILES = 8
469
+ _IMPACT_MAX_DEPS = 5
470
+ _IMPACT_MAX_NEIGHBORS = 4
471
+
472
+ def _impact_section(self, task: Task, data: dict | None) -> str:
473
+ """A short "changing X touches Y" block sourced purely from ``repo_map.json``.
474
+
475
+ Unlike the optional code-review-graph structural section (which only appears when
476
+ that CLI is installed and enabled), this is ALWAYS emitted whenever the repo map
477
+ carries dependents or subsystem neighbors — so an agent always sees the blast
478
+ radius of its edits (who imports each changed file, and which neighboring
479
+ subsystems the change reaches) even on the common keyless path. Complements the
480
+ detailed ``_dependents_section``/``_call_sites_section`` with a one-line-per-file
481
+ orientation. When a code graph exists, also appends symbol-level inbound blast
482
+ (depth 1) for planned modify paths. Bounded and best-effort; never raises."""
483
+ if not data:
484
+ return ""
485
+ from devcouncil.indexing.subsystem_map import (
486
+ area_for_path,
487
+ dependents_of,
488
+ neighbors_for_area,
489
+ )
490
+
491
+ lines: List[str] = []
492
+ for pf in task.planned_files[: self._IMPACT_MAX_FILES]:
493
+ path = pf.path.replace("\\", "/")
494
+ is_new = pf.allowed_change == "create"
495
+ importers = [] if is_new else dependents_of(path, data)
496
+ area = area_for_path(path, data)
497
+ neighbors = neighbors_for_area(area, data)
498
+ if not importers and not neighbors and not area and not is_new:
499
+ continue
500
+ parts: List[str] = []
501
+ if importers:
502
+ shown = importers[: self._IMPACT_MAX_DEPS]
503
+ more = f" (+{len(importers) - len(shown)})" if len(importers) > len(shown) else ""
504
+ parts.append(
505
+ f"imported by {len(importers)} file(s): "
506
+ + ", ".join(f"`{p}`" for p in shown) + more
507
+ )
508
+ elif is_new:
509
+ parts.append("new file — plan its importer before finishing")
510
+ if area:
511
+ neighbor_txt = ""
512
+ if neighbors:
513
+ shown_n = neighbors[: self._IMPACT_MAX_NEIGHBORS]
514
+ neighbor_txt = "; neighbors: " + ", ".join(f"`{n}`" for n in shown_n)
515
+ parts.append(f"subsystem `{area}`{neighbor_txt}")
516
+ if parts:
517
+ lines.append(f"- `{path}` → " + "; ".join(parts))
518
+
519
+ graph_lines = self._graph_impact_lines(task)
520
+ if not lines and not graph_lines:
521
+ return ""
522
+ body = "\n".join(lines) if lines else ""
523
+ if graph_lines:
524
+ extra = "\n".join(graph_lines)
525
+ body = f"{body}\n{extra}" if body else extra
526
+ return (
527
+ "\n## Impact (changing X touches Y)\n"
528
+ "_From the repo map — keep these call sites and neighboring subsystems working, "
529
+ "or update them in scope._\n"
530
+ + body
531
+ + "\n"
532
+ )
533
+
534
+ def _graph_impact_lines(self, task: Task) -> List[str]:
535
+ """Optional symbol-level inbound blast (depth 1) from the code graph."""
536
+ try:
537
+ from devcouncil.indexing.graph.build import load_code_graph
538
+ from devcouncil.indexing.graph.intel import diff_impact
539
+
540
+ graph = load_code_graph(self.project_root)
541
+ if graph is None:
542
+ return []
543
+ paths = [
544
+ pf.path.replace("\\", "/")
545
+ for pf in task.planned_files[: self._IMPACT_MAX_FILES]
546
+ if pf.allowed_change != "create"
547
+ ]
548
+ if not paths:
549
+ return []
550
+ result = diff_impact(
551
+ self.project_root, graph, paths=paths, use_diff=False, max_depth=1
552
+ )
553
+ out: List[str] = []
554
+ for item in result.get("paths") or []:
555
+ layers = (item.get("blast") or {}).get("layers") or []
556
+ depth1 = next((L for L in layers if L.get("depth") == 1), None)
557
+ nodes = (depth1 or {}).get("nodes") or []
558
+ if not nodes:
559
+ continue
560
+ shown = nodes[: self._IMPACT_MAX_DEPS]
561
+ more = f" (+{len(nodes) - len(shown)})" if len(nodes) > len(shown) else ""
562
+ out.append(
563
+ f"- `{item['path']}` symbol callers (depth 1): "
564
+ + ", ".join(f"`{n}`" for n in shown)
565
+ + more
566
+ )
567
+ return out
568
+ except Exception:
569
+ logger.debug("graph impact lines skipped", exc_info=True)
570
+ return []
571
+
572
+ def _liveness_debt_section(self, task: Task, data: dict | None) -> str:
573
+ """Surface map unwired/dead-symbol candidates that overlap the task's subsystems."""
574
+ if not data:
575
+ return ""
576
+ try:
577
+ from devcouncil.indexing.subsystem_map import (
578
+ area_for_path,
579
+ areas_touched,
580
+ dead_symbol_candidates_of,
581
+ unreachable_of,
582
+ unwired_candidates_of,
583
+ )
584
+
585
+ planned = [pf.path.replace("\\", "/") for pf in task.planned_files]
586
+ task_areas = set(areas_touched(planned, data))
587
+ if not task_areas:
588
+ return ""
589
+
590
+ unwired_hits: list[str] = []
591
+ for cand in unwired_candidates_of(data)[:40]:
592
+ area = area_for_path(cand, data)
593
+ if area and area in task_areas:
594
+ unwired_hits.append(cand)
595
+ if len(unwired_hits) >= 6:
596
+ break
597
+
598
+ unreachable_hits: list[str] = []
599
+ roots = data.get("entry_roots") or []
600
+ unreachable_unreliable = (
601
+ data.get("liveness_unreachable_unreliable") is True
602
+ or not isinstance(roots, list)
603
+ or not roots
604
+ )
605
+ if not unreachable_unreliable:
606
+ for cand in unreachable_of(data)[:40]:
607
+ area = area_for_path(cand, data)
608
+ if area and area in task_areas:
609
+ unreachable_hits.append(cand)
610
+ if len(unreachable_hits) >= 6:
611
+ break
612
+
613
+ dead_hits: list[str] = []
614
+ for cand in dead_symbol_candidates_of(data)[:40]:
615
+ path = cand.split(":", 1)[0]
616
+ area = area_for_path(path, data)
617
+ if area and area in task_areas:
618
+ dead_hits.append(cand)
619
+ if len(dead_hits) >= 6:
620
+ break
621
+
622
+ if not unwired_hits and not unreachable_hits and not dead_hits:
623
+ return ""
624
+ lines = [
625
+ "\n## Nearby liveness debt (from repo map)",
626
+ "_Existing unwired / unreachable files and unused symbols near your "
627
+ "subsystems — do not add to this debt; wire what you create._",
628
+ "_Verify same-task island rule: a new file imported only by other files "
629
+ "added in this task still needs a pre-existing non-test caller._",
630
+ "_Prefer `dev graph dead --confidence extracted` + greps; treat inferred "
631
+ "as unconfirmed. If entry_roots are empty / liveness_unreachable_unreliable, "
632
+ "ignore unreachable_files and mass inferred dead._",
633
+ ]
634
+ for p in unwired_hits:
635
+ lines.append(f"- unwired: `{p}`")
636
+ for p in unreachable_hits:
637
+ lines.append(f"- unreachable: `{p}`")
638
+ for p in dead_hits:
639
+ lines.append(f"- dead symbol: `{p}`")
640
+ return "\n".join(lines) + "\n"
641
+ except Exception:
642
+ logger.debug("liveness debt section skipped", exc_info=True)
643
+ return ""
644
+
443
645
  def _dependents_section(self, task: Task, data: dict | None) -> str:
444
646
  """List, per planned file the agent will change, the files that import it — the
445
647
  blast radius. Sourced from repo_map.json's precomputed reverse-import index, so
@@ -447,6 +649,9 @@ class PromptBuilder:
447
649
  dependents = (data or {}).get("dependents") or {}
448
650
  if not isinstance(dependents, dict) or not dependents:
449
651
  return ""
652
+ totals = (data or {}).get("dependents_total") or {}
653
+ if not isinstance(totals, dict):
654
+ totals = {}
450
655
  lines: List[str] = []
451
656
  for pf in task.planned_files:
452
657
  # New files have no dependents yet; only existing code carries blast radius.
@@ -455,18 +660,22 @@ class PromptBuilder:
455
660
  # Normalize the planned path to posix before the lookup — the map keys are
456
661
  # always posix, so a backslash planned path on Windows would otherwise miss
457
662
  # and silently drop the whole blast-radius entry (see _repo_map_section).
458
- importers = dependents.get(pf.path.replace("\\", "/")) or []
663
+ key = pf.path.replace("\\", "/")
664
+ importers = dependents.get(key) or []
459
665
  if not importers:
460
666
  continue
461
667
  shown = importers[:8]
462
- more = f" (+{len(importers) - len(shown)} more)" if len(importers) > len(shown) else ""
668
+ full_n = int(totals.get(key) or len(importers))
669
+ hidden = max(0, full_n - len(shown))
670
+ more = f" (+{hidden} more; {full_n} total)" if hidden else ""
463
671
  lines.append(f"- `{pf.path}` is imported by: " + ", ".join(f"`{p}`" for p in shown) + more)
464
672
  if not lines:
465
673
  return ""
466
674
  return (
467
675
  "\n## Dependents (blast radius)\n"
468
676
  "_These files import the files you're changing — keep their call sites working, "
469
- "or update them in scope._\n"
677
+ "or update them in scope. Counts may exceed the listed sample when the map "
678
+ "truncated dependents._\n"
470
679
  + "\n".join(lines)
471
680
  + "\n"
472
681
  )
@@ -515,10 +724,13 @@ class PromptBuilder:
515
724
  if not importers:
516
725
  continue
517
726
  src_path = self.project_root / pf.path
518
- try:
519
- src_text = src_path.read_text(encoding="utf-8", errors="replace")
520
- except Exception:
521
- continue
727
+ key = pf.path.replace("\\", "/")
728
+ src_text = self._file_text_cache.get(key)
729
+ if src_text is None:
730
+ try:
731
+ src_text = src_path.read_text(encoding="utf-8", errors="replace")
732
+ except Exception:
733
+ continue
522
734
  symbols = self._exported_symbol_names(pf.path, src_text)
523
735
  if not symbols:
524
736
  continue
@@ -526,10 +738,13 @@ class PromptBuilder:
526
738
  for importer in importers[: self._CALL_SITES_MAX_DEP_FILES]:
527
739
  if emitted >= self._CALL_SITES_MAX_TOTAL:
528
740
  break
529
- try:
530
- dep_text = (self.project_root / importer).read_text(encoding="utf-8", errors="replace")
531
- except Exception:
532
- continue
741
+ importer_key = importer.replace("\\", "/")
742
+ dep_text = self._file_text_cache.get(importer_key)
743
+ if dep_text is None:
744
+ try:
745
+ dep_text = (self.project_root / importer).read_text(encoding="utf-8", errors="replace")
746
+ except Exception:
747
+ continue
533
748
  hits = 0
534
749
  for lineno, raw in enumerate(dep_text.splitlines(), start=1):
535
750
  if hits >= self._CALL_SITES_MAX_LINES_PER_FILE or emitted >= self._CALL_SITES_MAX_TOTAL:
@@ -630,7 +845,7 @@ class PromptBuilder:
630
845
  max_chars = MAX_PROMPT_CHARS
631
846
  # Per-task outline cache so a planned file's symbol outline is computed once even
632
847
  # though both the planned-files section and the call-sites section consume it.
633
- self._outline_cache: dict[str, List[str]] = {}
848
+ self._outline_cache: dict[tuple[str, str], List[str]] = {}
634
849
  # Load the project config once and share it with the helpers that need it, instead
635
850
  # of each independently re-reading + re-parsing it. Best-effort: if it fails the
636
851
  # helpers fall back to loading it themselves (and degrade the same way).
@@ -705,10 +920,28 @@ class PromptBuilder:
705
920
  acceptance criteria require. Do NOT remove, rename, or alter the signature of an
706
921
  existing public symbol the task did not ask you to touch — verification flags an
707
922
  unrequested public-API change as scope drift and blocks it.
708
- 5. Run the allowed commands to verify your work.
709
- 6. Provide evidence of passing tests.
923
+ 5. Wire every created file and every new public symbol into its intended caller
924
+ (import, register, or call it) before claiming done. A file nothing imports, or a
925
+ public function nothing calls, fails verification. If the caller is outside the
926
+ current planned files, append it with `dev scope update <task_id> --lease-token
927
+ <token> --planned-file <caller>` (modify-op only), then edit.
928
+ 6. Run the allowed commands to verify your work.
929
+ 7. Provide evidence of passing tests.
710
930
  """
711
931
 
932
+ # Hard-task rigor: when the task classifies as hard, verification runs in
933
+ # strict mode (stub/effort gates block, diff coverage enforced). Saying so
934
+ # up front is cheaper than a repair loop after the fact.
935
+ try:
936
+ rigor_enabled = True if cfg is None else bool(cfg.verification.rigor.enabled)
937
+ if rigor_enabled:
938
+ from devcouncil.verification.difficulty import estimate_difficulty
939
+
940
+ if estimate_difficulty(task, requirements) == "hard":
941
+ instructions += _HARD_TASK_RIGOR_SECTION
942
+ except Exception:
943
+ logger.debug("hard-task rigor section skipped", exc_info=True)
944
+
712
945
  # --- Optional context: fitted within the remaining budget, dropped lowest-
713
946
  # priority first. Priority: file contents (1) > structural (2) ~ dependents (2)
714
947
  # > skills (3); display order keeps the original reading sequence. ---
@@ -738,39 +971,51 @@ class PromptBuilder:
738
971
  "text": f"\n{self._NO_MAP_NOTE}",
739
972
  })
740
973
 
974
+ # Always-on impact block ("changing X touches Y") from the repo map — present
975
+ # even when the code-review-graph CLI is absent (the common case). High priority
976
+ # (1) and short, so it survives all but the tightest budgets; ordered right after
977
+ # structural context so the agent sees the blast radius before the file bodies.
978
+ impact_text = self._impact_section(task, repo_map_data)
979
+ if impact_text:
980
+ segments.append({"order": 2, "priority": 1, "name": "impact", "text": impact_text})
981
+
982
+ liveness_text = self._liveness_debt_section(task, repo_map_data)
983
+ if liveness_text:
984
+ segments.append({"order": 2, "priority": 2, "name": "liveness debt", "text": liveness_text})
985
+
741
986
  files_text = self._planned_files_section(task)
742
987
  if files_text:
743
- segments.append({"order": 2, "priority": 1, "name": "file contents", "text": files_text})
988
+ segments.append({"order": 3, "priority": 1, "name": "file contents", "text": files_text})
744
989
 
745
990
  dependents_section = self._dependents_section(task, repo_map_data)
746
991
  if dependents_section:
747
992
  prefix = f"\n{self._STALE_MAP_NOTE}" if (repo_map_stale and not struct_has_stale_note) else ""
748
- segments.append({"order": 3, "priority": 2, "name": "dependents", "text": prefix + dependents_section})
993
+ segments.append({"order": 4, "priority": 2, "name": "dependents", "text": prefix + dependents_section})
749
994
 
750
995
  skills_text = self._skills_section(task)
751
996
  if skills_text:
752
- segments.append({"order": 4, "priority": 3, "name": "engineering skills", "text": skills_text})
997
+ segments.append({"order": 5, "priority": 3, "name": "engineering skills", "text": skills_text})
753
998
 
754
999
  # Design system (a hard constraint for UI work) and OKF project knowledge. The
755
1000
  # design system rides just above skills; OKF knowledge alongside them. Both are
756
1001
  # bounded by config char budgets in `_knowledge_sections`.
757
1002
  design_text, knowledge_text = self._knowledge_sections(task, cfg=cfg)
758
1003
  if design_text:
759
- segments.append({"order": 4, "priority": 2, "name": "design system", "text": design_text})
1004
+ segments.append({"order": 5, "priority": 2, "name": "design system", "text": design_text})
760
1005
  if knowledge_text:
761
- segments.append({"order": 4, "priority": 3, "name": "project knowledge", "text": knowledge_text})
1006
+ segments.append({"order": 5, "priority": 3, "name": "project knowledge", "text": knowledge_text})
762
1007
 
763
1008
  # Lowest priority (4): the budget drops call sites first. It only adds value once
764
1009
  # the file bodies + dependents are present anyway.
765
1010
  call_sites_text = self._call_sites_section(task, repo_map_data)
766
1011
  if call_sites_text:
767
- segments.append({"order": 5, "priority": 4, "name": "call sites", "text": call_sites_text})
1012
+ segments.append({"order": 6, "priority": 4, "name": "call sites", "text": call_sites_text})
768
1013
 
769
1014
  # Lowest priority (5): dependency risks are opt-in, advisory context — the
770
1015
  # budget drops them first so they never displace structural/file context.
771
1016
  dependency_risks_text = self._dependency_risks_section(repo_map_data)
772
1017
  if dependency_risks_text:
773
- segments.append({"order": 6, "priority": 5, "name": "dependency risks", "text": dependency_risks_text})
1018
+ segments.append({"order": 7, "priority": 5, "name": "dependency risks", "text": dependency_risks_text})
774
1019
 
775
1020
  # The core + instructions are never dropped; if they alone exceed the budget the
776
1021
  # model's server will truncate them, so warn loudly instead of failing silently.