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.
- package/README.md +92 -33
- package/package.json +6 -2
- package/packages/codeintel-grammars/hatch_build.py +43 -0
- package/packages/codeintel-grammars/pyproject.toml +16 -0
- package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
- package/pyproject.toml +99 -4
- package/src/devcouncil/app/config.py +512 -20
- package/src/devcouncil/app/events.py +4 -23
- package/src/devcouncil/app/orchestrator.py +5 -0
- package/src/devcouncil/app/run_context.py +3 -3
- package/src/devcouncil/assets/__init__.py +4 -1
- package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
- package/src/devcouncil/campaign/__init__.py +71 -0
- package/src/devcouncil/campaign/bloom.py +137 -0
- package/src/devcouncil/campaign/dashboard.py +123 -0
- package/src/devcouncil/campaign/mailbox.py +305 -0
- package/src/devcouncil/campaign/notify.py +91 -0
- package/src/devcouncil/campaign/orchestrator.py +592 -0
- package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
- package/src/devcouncil/campaign/prompts/director.md +21 -0
- package/src/devcouncil/campaign/prompts/protocol.md +46 -0
- package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
- package/src/devcouncil/campaign/prompts/worker.md +24 -0
- package/src/devcouncil/campaign/roles.py +202 -0
- package/src/devcouncil/campaign/watcher.py +153 -0
- package/src/devcouncil/cli/commands/agents.py +24 -17
- package/src/devcouncil/cli/commands/artifacts.py +36 -27
- package/src/devcouncil/cli/commands/ast.py +12 -3
- package/src/devcouncil/cli/commands/baseline.py +21 -12
- package/src/devcouncil/cli/commands/boot.py +218 -0
- package/src/devcouncil/cli/commands/campaign.py +302 -0
- package/src/devcouncil/cli/commands/check.py +225 -12
- package/src/devcouncil/cli/commands/config.py +221 -74
- package/src/devcouncil/cli/commands/cost.py +137 -28
- package/src/devcouncil/cli/commands/dashboard.py +12 -4
- package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
- package/src/devcouncil/cli/commands/design.py +27 -17
- package/src/devcouncil/cli/commands/doctor.py +790 -8
- package/src/devcouncil/cli/commands/evidence.py +41 -20
- package/src/devcouncil/cli/commands/export.py +73 -0
- package/src/devcouncil/cli/commands/gaps.py +175 -0
- package/src/devcouncil/cli/commands/gated_write.py +76 -0
- package/src/devcouncil/cli/commands/go.py +220 -68
- package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
- package/src/devcouncil/cli/commands/handoff.py +45 -34
- package/src/devcouncil/cli/commands/hook.py +630 -85
- package/src/devcouncil/cli/commands/init.py +89 -30
- package/src/devcouncil/cli/commands/integrate.py +296 -1385
- package/src/devcouncil/cli/commands/lease.py +120 -0
- package/src/devcouncil/cli/commands/logs.py +12 -5
- package/src/devcouncil/cli/commands/lsp.py +40 -5
- package/src/devcouncil/cli/commands/map.py +317 -74
- package/src/devcouncil/cli/commands/mcp_server.py +12 -2
- package/src/devcouncil/cli/commands/okf.py +44 -6
- package/src/devcouncil/cli/commands/plan.py +184 -69
- package/src/devcouncil/cli/commands/prompt.py +26 -17
- package/src/devcouncil/cli/commands/provenance.py +79 -0
- package/src/devcouncil/cli/commands/repair.py +60 -49
- package/src/devcouncil/cli/commands/report.py +148 -40
- package/src/devcouncil/cli/commands/requirements.py +104 -0
- package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
- package/src/devcouncil/cli/commands/rollback.py +46 -35
- package/src/devcouncil/cli/commands/run.py +173 -8
- package/src/devcouncil/cli/commands/runs.py +298 -68
- package/src/devcouncil/cli/commands/scaffold.py +33 -12
- package/src/devcouncil/cli/commands/semantic.py +29 -14
- package/src/devcouncil/cli/commands/setup.py +103 -93
- package/src/devcouncil/cli/commands/shell.py +51 -42
- package/src/devcouncil/cli/commands/show.py +56 -42
- package/src/devcouncil/cli/commands/skills.py +29 -20
- package/src/devcouncil/cli/commands/status.py +80 -67
- package/src/devcouncil/cli/commands/task_gate.py +295 -0
- package/src/devcouncil/cli/commands/tasks.py +248 -19
- package/src/devcouncil/cli/commands/trace.py +14 -8
- package/src/devcouncil/cli/commands/verify.py +33 -8
- package/src/devcouncil/cli/commands/version.py +14 -6
- package/src/devcouncil/cli/commands/watch.py +56 -40
- package/src/devcouncil/cli/commands/watch_fs.py +30 -19
- package/src/devcouncil/cli/commands/wiki.py +278 -0
- package/src/devcouncil/cli/main.py +58 -1
- package/src/devcouncil/codeintel/__init__.py +16 -0
- package/src/devcouncil/codeintel/build_control.py +429 -0
- package/src/devcouncil/codeintel/build_worker.py +78 -0
- package/src/devcouncil/codeintel/debug/__init__.py +17 -0
- package/src/devcouncil/codeintel/debug/broker.py +114 -0
- package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
- package/src/devcouncil/codeintel/debug/consent.py +36 -0
- package/src/devcouncil/codeintel/debug/discovery.py +132 -0
- package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
- package/src/devcouncil/codeintel/debug/protocol.py +259 -0
- package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
- package/src/devcouncil/codeintel/debug/session.py +238 -0
- package/src/devcouncil/codeintel/debug/tracing.py +201 -0
- package/src/devcouncil/codeintel/languages/__init__.py +17 -0
- package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
- package/src/devcouncil/codeintel/languages/registry.py +149 -0
- package/src/devcouncil/codeintel/languages/workers.py +245 -0
- package/src/devcouncil/codeintel/query/__init__.py +5 -0
- package/src/devcouncil/codeintel/query/engine.py +289 -0
- package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
- package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
- package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
- package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
- package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
- package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
- package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
- package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
- package/src/devcouncil/codeintel/service.py +104 -0
- package/src/devcouncil/codeintel/store/__init__.py +15 -0
- package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
- package/src/devcouncil/codeintel/sync/__init__.py +19 -0
- package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
- package/src/devcouncil/codeintel/sync/incremental.py +484 -0
- package/src/devcouncil/codeintel/sync/lease.py +96 -0
- package/src/devcouncil/codeintel/sync/scope.py +98 -0
- package/src/devcouncil/council/__init__.py +4 -0
- package/src/devcouncil/council/prompts/__init__.py +4 -0
- package/src/devcouncil/domain/checkpoint_refs.py +17 -0
- package/src/devcouncil/domain/evidence.py +1 -0
- package/src/devcouncil/domain/gap.py +10 -0
- package/src/devcouncil/domain/requirement.py +5 -1
- package/src/devcouncil/domain/task.py +43 -2
- package/src/devcouncil/execution/checkpoints.py +25 -31
- package/src/devcouncil/execution/context_builder.py +15 -44
- package/src/devcouncil/execution/fs_watcher.py +64 -0
- package/src/devcouncil/execution/gated_write.py +203 -0
- package/src/devcouncil/execution/handoff.py +2 -1
- package/src/devcouncil/execution/hook_policy.py +19 -5
- package/src/devcouncil/execution/lease_ops.py +177 -0
- package/src/devcouncil/execution/lease_validation.py +71 -0
- package/src/devcouncil/execution/patch.py +3 -0
- package/src/devcouncil/execution/permissions.py +1 -0
- package/src/devcouncil/execution/policy_engine.py +205 -10
- package/src/devcouncil/execution/prompt_builder.py +278 -33
- package/src/devcouncil/execution/run_trace.py +356 -0
- package/src/devcouncil/execution/shell_session.py +46 -5
- package/src/devcouncil/execution/stop_gate.py +746 -0
- package/src/devcouncil/execution/stop_gate_history.py +113 -0
- package/src/devcouncil/execution/stop_gate_state.py +54 -0
- package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
- package/src/devcouncil/execution/task_gate_ops.py +590 -0
- package/src/devcouncil/execution/task_runner.py +19 -0
- package/src/devcouncil/executors/advisor_tool.py +315 -0
- package/src/devcouncil/executors/agent_registry.py +125 -17
- package/src/devcouncil/executors/claude_sdk.py +376 -0
- package/src/devcouncil/executors/coding_cli.py +724 -25
- package/src/devcouncil/executors/mini_swe.py +50 -8
- package/src/devcouncil/executors/native/agent.py +224 -19
- package/src/devcouncil/executors/openhands.py +50 -8
- package/src/devcouncil/executors/transient_retry.py +99 -0
- package/src/devcouncil/gating/checks/clean_git.py +5 -2
- package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
- package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
- package/src/devcouncil/gating/policy.py +46 -2
- package/src/devcouncil/indexing/ast_matcher.py +41 -4
- package/src/devcouncil/indexing/graph/__init__.py +78 -0
- package/src/devcouncil/indexing/graph/api_routes.py +522 -0
- package/src/devcouncil/indexing/graph/build.py +862 -0
- package/src/devcouncil/indexing/graph/cache.py +329 -0
- package/src/devcouncil/indexing/graph/communities.py +28 -0
- package/src/devcouncil/indexing/graph/cypher.py +107 -0
- package/src/devcouncil/indexing/graph/embeddings.py +194 -0
- package/src/devcouncil/indexing/graph/export.py +381 -0
- package/src/devcouncil/indexing/graph/export_links.py +81 -0
- package/src/devcouncil/indexing/graph/extract_python.py +307 -0
- package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
- package/src/devcouncil/indexing/graph/intel.py +668 -0
- package/src/devcouncil/indexing/graph/liveness.py +992 -0
- package/src/devcouncil/indexing/graph/okf_export.py +65 -0
- package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
- package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
- package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
- package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
- package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
- package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
- package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
- package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
- package/src/devcouncil/indexing/graph/query.py +302 -0
- package/src/devcouncil/indexing/graph/resolve.py +1020 -0
- package/src/devcouncil/indexing/graph/schema.py +103 -0
- package/src/devcouncil/indexing/graph_index.py +20 -29
- package/src/devcouncil/indexing/lsp.py +57 -25
- package/src/devcouncil/indexing/lsp_client.py +577 -0
- package/src/devcouncil/indexing/map_artifacts.py +355 -0
- package/src/devcouncil/indexing/map_refresh.py +141 -0
- package/src/devcouncil/indexing/repo_mapper.py +1509 -138
- package/src/devcouncil/indexing/semantic_index.py +12 -6
- package/src/devcouncil/indexing/subsystem_map.py +163 -0
- package/src/devcouncil/indexing/ts_imports.py +343 -0
- package/src/devcouncil/indexing/viz.py +964 -0
- package/src/devcouncil/indexing/walk.py +52 -0
- package/src/devcouncil/indexing/wiring.py +1776 -0
- package/src/devcouncil/integrations/actions.py +27 -4
- package/src/devcouncil/integrations/check.py +211 -16
- package/src/devcouncil/integrations/claude_assets.py +209 -12
- package/src/devcouncil/integrations/clients/__init__.py +1 -0
- package/src/devcouncil/integrations/clients/aider.py +52 -0
- package/src/devcouncil/integrations/clients/antigravity.py +87 -0
- package/src/devcouncil/integrations/clients/claude.py +339 -0
- package/src/devcouncil/integrations/clients/codex.py +39 -0
- package/src/devcouncil/integrations/clients/common.py +332 -0
- package/src/devcouncil/integrations/clients/cursor.py +164 -0
- package/src/devcouncil/integrations/clients/gemini.py +49 -0
- package/src/devcouncil/integrations/clients/grok.py +105 -0
- package/src/devcouncil/integrations/clients/hooks.py +500 -0
- package/src/devcouncil/integrations/clients/opencode.py +96 -0
- package/src/devcouncil/integrations/clients/warp.py +75 -0
- package/src/devcouncil/integrations/code_review_graph.py +2 -2
- package/src/devcouncil/integrations/github.py +73 -7
- package/src/devcouncil/integrations/integration_cli.py +197 -0
- package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
- package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
- package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
- package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
- package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
- package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
- package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/git.py +281 -0
- package/src/devcouncil/integrations/mcp/handlers/graph.py +34 -0
- package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/knowledge.py +28 -0
- package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
- package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
- package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
- package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
- package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
- package/src/devcouncil/integrations/mcp/handlers/provenance.py +87 -0
- package/src/devcouncil/integrations/mcp/handlers/read.py +103 -0
- package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/runs.py +69 -0
- package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
- package/src/devcouncil/integrations/mcp/handlers/status.py +199 -0
- package/src/devcouncil/integrations/mcp/handlers/task.py +88 -0
- package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +922 -0
- package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
- package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
- package/src/devcouncil/integrations/mcp/handlers/wiki.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
- package/src/devcouncil/integrations/mcp/server.py +265 -2391
- package/src/devcouncil/integrations/mcp/util.py +325 -0
- package/src/devcouncil/integrations/setup.py +152 -0
- package/src/devcouncil/knowledge/fetch.py +4 -0
- package/src/devcouncil/knowledge/knowledge_select.py +38 -0
- package/src/devcouncil/knowledge/okf.py +2 -1
- package/src/devcouncil/knowledge/resource_discovery.py +40 -0
- package/src/devcouncil/knowledge/wiki.py +643 -0
- package/src/devcouncil/knowledge/wiki_read.py +87 -0
- package/src/devcouncil/live/cards.py +7 -7
- package/src/devcouncil/live/models.py +4 -1
- package/src/devcouncil/live/reviewer.py +90 -11
- package/src/devcouncil/live/signals.py +4 -2
- package/src/devcouncil/live/summary.py +21 -3
- package/src/devcouncil/live/tasks.py +12 -3
- package/src/devcouncil/live/transcripts.py +69 -2
- package/src/devcouncil/llm/cache.py +5 -6
- package/src/devcouncil/llm/model_defaults.yaml +10 -10
- package/src/devcouncil/llm/provider.py +647 -73
- package/src/devcouncil/llm/router.py +271 -46
- package/src/devcouncil/llm/semantic_bridge.py +614 -0
- package/src/devcouncil/optimization/gepa_agent.py +6 -4
- package/src/devcouncil/optimization/skillopt.py +9 -5
- package/src/devcouncil/planning/arbiter_service.py +12 -3
- package/src/devcouncil/planning/correction_manifest.py +107 -10
- package/src/devcouncil/planning/plan_difficulty.py +69 -0
- package/src/devcouncil/planning/plan_service.py +5 -2
- package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
- package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
- package/src/devcouncil/planning/question_conversion.py +56 -0
- package/src/devcouncil/planning/spec_service.py +9 -3
- package/src/devcouncil/repo/ci_scaffold.py +197 -1
- package/src/devcouncil/repo/gitignore.py +1 -2
- package/src/devcouncil/reporting/evidence_export.py +124 -0
- package/src/devcouncil/reporting/evidence_html.py +210 -0
- package/src/devcouncil/reporting/json_report.py +16 -12
- package/src/devcouncil/reporting/markdown_report.py +38 -9
- package/src/devcouncil/reporting/mcp_resources.py +142 -0
- package/src/devcouncil/reporting/report_builder.py +40 -4
- package/src/devcouncil/reporting/task_provenance.py +42 -0
- package/src/devcouncil/reporting/verdict.py +75 -0
- package/src/devcouncil/skills/library/README.md +1 -0
- package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
- package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
- package/src/devcouncil/skills/library/devcouncil.md +93 -0
- package/src/devcouncil/skills/registry.py +43 -12
- package/src/devcouncil/storage/db.py +57 -11
- package/src/devcouncil/storage/models.py +6 -0
- package/src/devcouncil/storage/native.py +5 -3
- package/src/devcouncil/storage/repositories.py +50 -18
- package/src/devcouncil/telemetry/context.py +28 -0
- package/src/devcouncil/telemetry/cost.py +4 -5
- package/src/devcouncil/telemetry/logging_setup.py +78 -11
- package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
- package/src/devcouncil/telemetry/stages.py +27 -2
- package/src/devcouncil/telemetry/tracker.py +50 -13
- package/src/devcouncil/ui/dashboard.py +120 -8
- package/src/devcouncil/utils/fsio.py +58 -0
- package/src/devcouncil/utils/git_snapshot.py +112 -0
- package/src/devcouncil/utils/json_persist.py +53 -0
- package/src/devcouncil/utils/proc.py +89 -0
- package/src/devcouncil/verification/acceptance_compiler.py +36 -13
- package/src/devcouncil/verification/ad_hoc_check.py +95 -3
- package/src/devcouncil/verification/checks/__init__.py +41 -0
- package/src/devcouncil/verification/checks/acceptance.py +39 -0
- package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
- package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
- package/src/devcouncil/verification/checks/command_evidence.py +148 -0
- package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
- package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
- package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
- package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
- package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
- package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
- package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
- package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
- package/src/devcouncil/verification/checks/planned_files.py +98 -0
- package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
- package/src/devcouncil/verification/checks/stale_map.py +80 -0
- package/src/devcouncil/verification/checks/stub_scan.py +71 -0
- package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
- package/src/devcouncil/verification/checks/wiring.py +216 -0
- package/src/devcouncil/verification/claims/__init__.py +23 -0
- package/src/devcouncil/verification/claims/checks.py +395 -0
- package/src/devcouncil/verification/claims/mapper.py +168 -0
- package/src/devcouncil/verification/claims/models.py +39 -0
- package/src/devcouncil/verification/claims/transcript.py +92 -0
- package/src/devcouncil/verification/claims/verdict.py +88 -0
- package/src/devcouncil/verification/command_evidence.py +170 -0
- package/src/devcouncil/verification/command_malformation.py +147 -0
- package/src/devcouncil/verification/command_runner.py +164 -0
- package/src/devcouncil/verification/coverage_measurement.py +292 -0
- package/src/devcouncil/verification/diff_coverage.py +151 -0
- package/src/devcouncil/verification/difficulty.py +296 -0
- package/src/devcouncil/verification/effort_heuristics.py +178 -0
- package/src/devcouncil/verification/gap_ids.py +63 -0
- package/src/devcouncil/verification/gate_cache.py +194 -0
- package/src/devcouncil/verification/gate_selector.py +344 -0
- package/src/devcouncil/verification/git_diff_fallback.py +272 -0
- package/src/devcouncil/verification/implementation_reviewer.py +13 -0
- package/src/devcouncil/verification/incremental_check.py +241 -0
- package/src/devcouncil/verification/next_actions.py +60 -1
- package/src/devcouncil/verification/rigor_analytics.py +130 -0
- package/src/devcouncil/verification/sandbox.py +38 -11
- package/src/devcouncil/verification/stub_detector.py +369 -0
- package/src/devcouncil/verification/test_resolver.py +67 -1
- package/src/devcouncil/verification/verifier.py +137 -1666
- package/src/devcouncil/verification/verify_orchestration.py +610 -0
- package/src/devcouncil/verification/verify_setup.py +176 -0
- package/src/devcouncil/verification/wiki_refresh.py +208 -0
- package/src/semantic_layer/__init__.py +58 -0
- package/src/semantic_layer/benchmark.py +75 -0
- package/src/semantic_layer/cache.py +290 -0
- package/src/semantic_layer/compressor.py +137 -0
- package/src/semantic_layer/config.py +75 -0
- package/src/semantic_layer/embeddings.py +69 -0
- package/src/semantic_layer/llm_backends.py +99 -0
- package/src/semantic_layer/pipeline.py +111 -0
- package/src/semantic_layer/router.py +128 -0
- package/src/semantic_layer/tuner.py +72 -0
- package/uv.lock +973 -9
- package/src/devcouncil/artifacts/migrations.py +0 -20
- package/src/devcouncil/artifacts/schemas.py +0 -23
- package/src/devcouncil/artifacts/serializer.py +0 -21
- package/src/devcouncil/integrations/gitnexus.py +0 -70
- 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
|
-
|
|
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
|
+
]
|