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
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Generic Tree-sitter extraction used by the broad language matrix."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from devcouncil.codeintel.languages.registry import detect_language
|
|
9
|
+
from devcouncil.codeintel.languages.workers import process_tree_sitter
|
|
10
|
+
from devcouncil.indexing.graph.extract_python import (
|
|
11
|
+
ExtractedCall,
|
|
12
|
+
ExtractedImport,
|
|
13
|
+
ExtractedSymbol,
|
|
14
|
+
FileExtraction,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_QUOTED = re.compile(r"['\"]([^'\"]+)['\"]")
|
|
18
|
+
_CALL = re.compile(r"(?<![\w$])(?:(?P<receiver>[A-Za-z_$][\w$.:]*)\s*[.>:]+\s*)?(?P<name>[A-Za-z_$][\w$]*)\s*\(")
|
|
19
|
+
_CALL_KEYWORDS = {"if", "for", "while", "switch", "catch", "return", "sizeof", "typeof", "match", "when"}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def extract_generic(path: str, source: str) -> FileExtraction:
|
|
23
|
+
spec = detect_language(path)
|
|
24
|
+
if spec is None:
|
|
25
|
+
return FileExtraction(path=path, language="")
|
|
26
|
+
output = FileExtraction(path=path, language=spec.grammar)
|
|
27
|
+
result = process_tree_sitter(spec.grammar, source)
|
|
28
|
+
if result is not None:
|
|
29
|
+
_append_result(output, source, result)
|
|
30
|
+
for language, region, line_offset in _embedded_regions(spec.name, source):
|
|
31
|
+
embedded = process_tree_sitter(language, region)
|
|
32
|
+
if embedded is not None:
|
|
33
|
+
_append_result(output, region, embedded, line_offset=line_offset)
|
|
34
|
+
return output
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _append_result(
|
|
38
|
+
output: FileExtraction,
|
|
39
|
+
source: str,
|
|
40
|
+
result: dict[str, Any],
|
|
41
|
+
*,
|
|
42
|
+
line_offset: int = 0,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""Merge one container or embedded Tree-sitter result into a file extraction."""
|
|
45
|
+
|
|
46
|
+
exported_names = {
|
|
47
|
+
str(item.get("name", "")).strip()
|
|
48
|
+
for item in result.get("exports", [])
|
|
49
|
+
if str(item.get("name", "")).strip()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Seen-sets instead of list scans: large generated files can carry
|
|
53
|
+
# thousands of symbols/calls, and per-append linear scans go quadratic.
|
|
54
|
+
seen_symbols = {(existing.qualname, existing.line) for existing in output.symbols}
|
|
55
|
+
seen_calls = {
|
|
56
|
+
(existing.name, existing.line, existing.receiver) for existing in output.calls
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def visit(item: dict[str, Any], prefix: str = "") -> None:
|
|
60
|
+
name = str(item.get("name", "") or "").strip()
|
|
61
|
+
kind = _kind(str(item.get("kind", "") or ""))
|
|
62
|
+
if name and kind:
|
|
63
|
+
qualname = f"{prefix}.{name}" if prefix else name
|
|
64
|
+
symbol = ExtractedSymbol(
|
|
65
|
+
kind=kind,
|
|
66
|
+
name=name,
|
|
67
|
+
qualname=qualname,
|
|
68
|
+
line=int(item.get("start_line", 0)) + line_offset + 1,
|
|
69
|
+
end_line=int(item.get("end_line", 0)) + line_offset + 1,
|
|
70
|
+
decorators=[str(value) for value in item.get("decorators", [])],
|
|
71
|
+
exported=name in exported_names or not name.startswith("_"),
|
|
72
|
+
)
|
|
73
|
+
if (symbol.qualname, symbol.line) not in seen_symbols:
|
|
74
|
+
seen_symbols.add((symbol.qualname, symbol.line))
|
|
75
|
+
output.symbols.append(symbol)
|
|
76
|
+
child_prefix = qualname if kind in {"class", "interface", "struct", "trait", "enum"} else prefix
|
|
77
|
+
else:
|
|
78
|
+
child_prefix = prefix
|
|
79
|
+
for child in item.get("children", []) or []:
|
|
80
|
+
visit(child, child_prefix)
|
|
81
|
+
|
|
82
|
+
for item in result.get("structure", []) or []:
|
|
83
|
+
visit(item)
|
|
84
|
+
|
|
85
|
+
for item in result.get("imports", []) or []:
|
|
86
|
+
raw_source = str(item.get("source", "") or "")
|
|
87
|
+
match = _QUOTED.search(raw_source)
|
|
88
|
+
module = match.group(1) if match else raw_source.strip()
|
|
89
|
+
if not module:
|
|
90
|
+
continue
|
|
91
|
+
if module not in output.imports:
|
|
92
|
+
output.imports.append(module)
|
|
93
|
+
names = [str(value) for value in item.get("items", []) or []]
|
|
94
|
+
alias = str(item.get("alias", "") or "")
|
|
95
|
+
output.import_details.append(ExtractedImport(
|
|
96
|
+
module=module,
|
|
97
|
+
names=names,
|
|
98
|
+
alias_map={alias: module} if alias else {},
|
|
99
|
+
))
|
|
100
|
+
output.all_exports = sorted(set(output.all_exports) | exported_names)
|
|
101
|
+
|
|
102
|
+
# ``qualname_hint`` is a CALLEE hint (``receiver.name`` / bare name) — the
|
|
103
|
+
# same convention as every dedicated extractor. resolve_calls resolves it
|
|
104
|
+
# against the same-file symbol index; the enclosing caller must never go
|
|
105
|
+
# here or bare in-function calls all bind back to their own caller.
|
|
106
|
+
if "calls" in result:
|
|
107
|
+
for item in result.get("calls", []) or []:
|
|
108
|
+
name = str(item.get("name", "") or "")
|
|
109
|
+
if not name or name in _CALL_KEYWORDS:
|
|
110
|
+
continue
|
|
111
|
+
line_no = int(item.get("line", 0)) + line_offset
|
|
112
|
+
receiver = str(item.get("receiver", "") or "")
|
|
113
|
+
call = ExtractedCall(
|
|
114
|
+
name=name,
|
|
115
|
+
line=line_no,
|
|
116
|
+
receiver=receiver,
|
|
117
|
+
qualname_hint=f"{receiver}.{name}" if receiver else name,
|
|
118
|
+
)
|
|
119
|
+
key = (call.name, call.line, call.receiver)
|
|
120
|
+
if key not in seen_calls:
|
|
121
|
+
seen_calls.add(key)
|
|
122
|
+
output.calls.append(call)
|
|
123
|
+
output.references.append(name)
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
# Compatibility for older companion packs that do not return call rows.
|
|
127
|
+
for line_no, line in enumerate(source.splitlines(), start=line_offset + 1):
|
|
128
|
+
for match in _CALL.finditer(line):
|
|
129
|
+
name = match.group("name")
|
|
130
|
+
if name in _CALL_KEYWORDS:
|
|
131
|
+
continue
|
|
132
|
+
receiver = match.group("receiver") or ""
|
|
133
|
+
output.calls.append(ExtractedCall(
|
|
134
|
+
name=name,
|
|
135
|
+
line=line_no,
|
|
136
|
+
receiver=receiver,
|
|
137
|
+
qualname_hint=f"{receiver}.{name}" if receiver else name,
|
|
138
|
+
))
|
|
139
|
+
output.references.append(name)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _embedded_regions(container: str, source: str) -> list[tuple[str, str, int]]:
|
|
143
|
+
regions: list[tuple[str, str, int]] = []
|
|
144
|
+
if container in {"Svelte", "Vue", "Astro", "Liquid"}:
|
|
145
|
+
for match in re.finditer(
|
|
146
|
+
r"<script\b(?P<attrs>[^>]*)>(?P<body>.*?)</script\s*>",
|
|
147
|
+
source,
|
|
148
|
+
re.IGNORECASE | re.DOTALL,
|
|
149
|
+
):
|
|
150
|
+
attrs = match.group("attrs")
|
|
151
|
+
language = (
|
|
152
|
+
"typescript"
|
|
153
|
+
if re.search(r"\blang\s*=\s*['\"](?:ts|typescript)['\"]", attrs, re.I)
|
|
154
|
+
else "javascript"
|
|
155
|
+
)
|
|
156
|
+
regions.append((
|
|
157
|
+
language,
|
|
158
|
+
match.group("body"),
|
|
159
|
+
source[:match.start("body")].count("\n"),
|
|
160
|
+
))
|
|
161
|
+
for match in re.finditer(
|
|
162
|
+
r"<style\b[^>]*>(?P<body>.*?)</style\s*>",
|
|
163
|
+
source,
|
|
164
|
+
re.IGNORECASE | re.DOTALL,
|
|
165
|
+
):
|
|
166
|
+
regions.append((
|
|
167
|
+
"css",
|
|
168
|
+
match.group("body"),
|
|
169
|
+
source[:match.start("body")].count("\n"),
|
|
170
|
+
))
|
|
171
|
+
if container in {"Svelte", "Vue", "Astro"}:
|
|
172
|
+
for match in re.finditer(
|
|
173
|
+
r"<template\b[^>]*>(?P<body>.*?)</template\s*>",
|
|
174
|
+
source,
|
|
175
|
+
re.IGNORECASE | re.DOTALL,
|
|
176
|
+
):
|
|
177
|
+
regions.append((
|
|
178
|
+
"html",
|
|
179
|
+
match.group("body"),
|
|
180
|
+
source[:match.start("body")].count("\n"),
|
|
181
|
+
))
|
|
182
|
+
if container == "Astro":
|
|
183
|
+
frontmatter = re.match(r"^---\s*\n(?P<body>.*?)\n---(?:\s*\n|$)", source, re.DOTALL)
|
|
184
|
+
if frontmatter is not None:
|
|
185
|
+
regions.append((
|
|
186
|
+
"typescript",
|
|
187
|
+
frontmatter.group("body"),
|
|
188
|
+
source[:frontmatter.start("body")].count("\n"),
|
|
189
|
+
))
|
|
190
|
+
regions.append((
|
|
191
|
+
"html",
|
|
192
|
+
source[frontmatter.end():],
|
|
193
|
+
source[:frontmatter.end()].count("\n"),
|
|
194
|
+
))
|
|
195
|
+
else:
|
|
196
|
+
regions.append(("html", source, 0))
|
|
197
|
+
if container == "Svelte":
|
|
198
|
+
regions.append(("html", source, 0))
|
|
199
|
+
if container == "Liquid":
|
|
200
|
+
for match in re.finditer(
|
|
201
|
+
r"{%\s*javascript\s*%}(?P<body>.*?){%\s*endjavascript\s*%}",
|
|
202
|
+
source,
|
|
203
|
+
re.IGNORECASE | re.DOTALL,
|
|
204
|
+
):
|
|
205
|
+
regions.append((
|
|
206
|
+
"javascript",
|
|
207
|
+
match.group("body"),
|
|
208
|
+
source[:match.start("body")].count("\n"),
|
|
209
|
+
))
|
|
210
|
+
regions.append(("html", source, 0))
|
|
211
|
+
return regions
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _kind(raw: str) -> str:
|
|
215
|
+
normalized = raw.lower().replace("_", "").replace(" ", "")
|
|
216
|
+
if "method" in normalized or "constructor" in normalized:
|
|
217
|
+
return "method"
|
|
218
|
+
if "function" in normalized or "procedure" in normalized or normalized in {"func", "subroutine"}:
|
|
219
|
+
return "function"
|
|
220
|
+
if "interface" in normalized or "protocol" in normalized:
|
|
221
|
+
return "interface"
|
|
222
|
+
if "struct" in normalized or "record" in normalized:
|
|
223
|
+
return "struct"
|
|
224
|
+
if "trait" in normalized:
|
|
225
|
+
return "trait"
|
|
226
|
+
if "enum" in normalized:
|
|
227
|
+
return "enum"
|
|
228
|
+
if "class" in normalized or "object" in normalized or "module" in normalized:
|
|
229
|
+
return "class"
|
|
230
|
+
if "type" in normalized or "alias" in normalized:
|
|
231
|
+
return "type"
|
|
232
|
+
if "property" in normalized or "field" in normalized:
|
|
233
|
+
return "property"
|
|
234
|
+
if "variable" in normalized or "constant" in normalized:
|
|
235
|
+
return "variable"
|
|
236
|
+
return ""
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Explicit language/grammar manifest for the code-intelligence release."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class LanguageSpec:
|
|
12
|
+
name: str
|
|
13
|
+
grammar: str
|
|
14
|
+
extensions: tuple[str, ...]
|
|
15
|
+
embedded: tuple[str, ...] = ()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _spec(name: str, grammar: str, extensions: str, *, embedded: tuple[str, ...] = ()) -> LanguageSpec:
|
|
19
|
+
return LanguageSpec(name, grammar, tuple(extensions.split()), embedded)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
LANGUAGE_SPECS: tuple[LanguageSpec, ...] = (
|
|
23
|
+
_spec("TypeScript", "typescript", ".ts .mts .cts"),
|
|
24
|
+
_spec("TSX", "tsx", ".tsx"),
|
|
25
|
+
_spec("JavaScript", "javascript", ".js .jsx .mjs .cjs"),
|
|
26
|
+
_spec("ArkTS", "typescript", ".ets"),
|
|
27
|
+
_spec("Python", "python", ".py .pyi"),
|
|
28
|
+
_spec("Go", "go", ".go"),
|
|
29
|
+
_spec("Rust", "rust", ".rs"),
|
|
30
|
+
_spec("Java", "java", ".java"),
|
|
31
|
+
_spec("C#", "csharp", ".cs"),
|
|
32
|
+
_spec("VB.NET", "vb", ".vb"),
|
|
33
|
+
_spec("PHP", "php", ".php .phtml"),
|
|
34
|
+
_spec("Ruby", "ruby", ".rb .rake"),
|
|
35
|
+
_spec("C", "c", ".c .h"),
|
|
36
|
+
_spec("C++", "cpp", ".cc .cpp .cxx .hh .hpp .hxx"),
|
|
37
|
+
_spec("Objective-C", "objc", ".m .mm"),
|
|
38
|
+
# Metal is a C++14-derived language; the language pack ships no separate
|
|
39
|
+
# Metal grammar, so use its maintained C++ parser plus Metal augmenters.
|
|
40
|
+
_spec("Metal", "cpp", ".metal"),
|
|
41
|
+
_spec("CUDA", "cuda", ".cu .cuh"),
|
|
42
|
+
_spec("Swift", "swift", ".swift"),
|
|
43
|
+
_spec("Kotlin", "kotlin", ".kt .kts"),
|
|
44
|
+
_spec("Scala", "scala", ".scala .sc"),
|
|
45
|
+
_spec("Dart", "dart", ".dart"),
|
|
46
|
+
_spec("Svelte", "svelte", ".svelte", embedded=("typescript", "javascript", "css", "html")),
|
|
47
|
+
_spec("Vue", "vue", ".vue", embedded=("typescript", "javascript", "css", "html")),
|
|
48
|
+
_spec("Astro", "astro", ".astro", embedded=("typescript", "javascript", "css", "html")),
|
|
49
|
+
_spec("Liquid", "liquid", ".liquid", embedded=("html", "javascript", "css")),
|
|
50
|
+
_spec("Pascal/Delphi", "pascal", ".pas .pp .dpr"),
|
|
51
|
+
_spec("Lua", "lua", ".lua"),
|
|
52
|
+
_spec("Luau", "luau", ".luau"),
|
|
53
|
+
_spec("R", "r", ".r .R"),
|
|
54
|
+
_spec("CFML", "cfml", ".cfm .cfc"),
|
|
55
|
+
_spec("COBOL", "cobol", ".cob .cbl .cpy"),
|
|
56
|
+
_spec("Erlang", "erlang", ".erl .hrl"),
|
|
57
|
+
_spec("Solidity", "solidity", ".sol"),
|
|
58
|
+
_spec("Terraform/OpenTofu", "hcl", ".tf .tfvars .hcl"),
|
|
59
|
+
_spec("Nix", "nix", ".nix"),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
_BY_EXTENSION = {
|
|
63
|
+
extension.lower(): spec
|
|
64
|
+
for spec in LANGUAGE_SPECS
|
|
65
|
+
for extension in spec.extensions
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def supported_languages() -> list[str]:
|
|
70
|
+
return [spec.name for spec in LANGUAGE_SPECS]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def detect_language(path: str | Path) -> LanguageSpec | None:
|
|
74
|
+
return _BY_EXTENSION.get(Path(path).suffix.lower())
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def grammar_status() -> dict[str, Any]:
|
|
78
|
+
"""Report local grammar availability without downloading anything."""
|
|
79
|
+
|
|
80
|
+
companion: dict[str, Any] = {"installed": False}
|
|
81
|
+
try:
|
|
82
|
+
try:
|
|
83
|
+
import devcouncil_codeintel_grammars as grammar_assets
|
|
84
|
+
|
|
85
|
+
companion = {"installed": True, **grammar_assets.activate()}
|
|
86
|
+
except ImportError:
|
|
87
|
+
pass
|
|
88
|
+
import tree_sitter_language_pack as pack
|
|
89
|
+
|
|
90
|
+
available = set(pack.available_languages())
|
|
91
|
+
pack_version = str(getattr(pack, "__version__", "unknown"))
|
|
92
|
+
cache_dir = str(pack.cache_dir())
|
|
93
|
+
error = ""
|
|
94
|
+
except Exception as exc:
|
|
95
|
+
available = set()
|
|
96
|
+
pack_version = "unavailable"
|
|
97
|
+
cache_dir = ""
|
|
98
|
+
error = f"{type(exc).__name__}: {exc}"
|
|
99
|
+
rows = []
|
|
100
|
+
for spec in LANGUAGE_SPECS:
|
|
101
|
+
required = (spec.grammar, *spec.embedded)
|
|
102
|
+
missing = sorted(set(required) - available)
|
|
103
|
+
rows.append({
|
|
104
|
+
"language": spec.name,
|
|
105
|
+
"grammar": spec.grammar,
|
|
106
|
+
"required_grammars": list(required),
|
|
107
|
+
"missing_grammars": missing,
|
|
108
|
+
"available": not missing,
|
|
109
|
+
"extensions": list(spec.extensions),
|
|
110
|
+
"embedded": list(spec.embedded),
|
|
111
|
+
})
|
|
112
|
+
missing_required = sorted({
|
|
113
|
+
grammar
|
|
114
|
+
for spec in LANGUAGE_SPECS
|
|
115
|
+
for grammar in (spec.grammar, *spec.embedded)
|
|
116
|
+
if grammar not in available
|
|
117
|
+
})
|
|
118
|
+
companion_installed = bool(companion.get("installed"))
|
|
119
|
+
companion_ok = bool(companion.get("ok"))
|
|
120
|
+
if missing_required and not companion_installed:
|
|
121
|
+
action = (
|
|
122
|
+
"Install the platform-matched devcouncil-codeintel-grammars wheel; "
|
|
123
|
+
"runtime grammar downloads are disabled."
|
|
124
|
+
)
|
|
125
|
+
elif missing_required and not companion_ok:
|
|
126
|
+
action = (
|
|
127
|
+
"Reinstall the platform-matched devcouncil-codeintel-grammars wheel; "
|
|
128
|
+
"its manifest or checksums are invalid."
|
|
129
|
+
)
|
|
130
|
+
elif missing_required:
|
|
131
|
+
action = (
|
|
132
|
+
"The installed companion wheel is incomplete for this platform; "
|
|
133
|
+
"install a wheel containing every required grammar."
|
|
134
|
+
)
|
|
135
|
+
else:
|
|
136
|
+
action = ""
|
|
137
|
+
return {
|
|
138
|
+
"ok": all(row["available"] for row in rows),
|
|
139
|
+
"pack_version": pack_version,
|
|
140
|
+
"cache_dir": cache_dir,
|
|
141
|
+
"available_count": sum(bool(row["available"]) for row in rows),
|
|
142
|
+
"required_count": len(rows),
|
|
143
|
+
"languages": rows,
|
|
144
|
+
"missing_grammars": missing_required,
|
|
145
|
+
"action": action,
|
|
146
|
+
"error": error,
|
|
147
|
+
"downloaded_at_runtime": False,
|
|
148
|
+
"companion": companion,
|
|
149
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""Crash-isolated native Tree-sitter worker pool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import atexit
|
|
6
|
+
import multiprocessing
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
import threading
|
|
10
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
_ACTIVATION_LOCK = threading.Lock()
|
|
15
|
+
_ACTIVATION_ATTEMPTED = False
|
|
16
|
+
_ACTIVATION_STATUS: dict[str, Any] = {"installed": False, "activated": False}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _activate_companion_once() -> dict[str, Any]:
|
|
20
|
+
"""Activate packaged assets once per process without fetching grammars."""
|
|
21
|
+
|
|
22
|
+
global _ACTIVATION_ATTEMPTED, _ACTIVATION_STATUS
|
|
23
|
+
with _ACTIVATION_LOCK:
|
|
24
|
+
if _ACTIVATION_ATTEMPTED:
|
|
25
|
+
return _ACTIVATION_STATUS
|
|
26
|
+
_ACTIVATION_ATTEMPTED = True
|
|
27
|
+
try:
|
|
28
|
+
import devcouncil_codeintel_grammars as grammar_assets
|
|
29
|
+
except ImportError:
|
|
30
|
+
return _ACTIVATION_STATUS
|
|
31
|
+
try:
|
|
32
|
+
_ACTIVATION_STATUS = {"installed": True, **grammar_assets.activate()}
|
|
33
|
+
except Exception as exc:
|
|
34
|
+
_ACTIVATION_STATUS = {
|
|
35
|
+
"installed": True,
|
|
36
|
+
"activated": False,
|
|
37
|
+
"ok": False,
|
|
38
|
+
"error": f"{type(exc).__name__}: {exc}",
|
|
39
|
+
}
|
|
40
|
+
return _ACTIVATION_STATUS
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _structure_row(item: Any) -> dict[str, Any]:
|
|
44
|
+
span = getattr(item, "span", None)
|
|
45
|
+
return {
|
|
46
|
+
"name": str(getattr(item, "name", "") or ""),
|
|
47
|
+
"kind": str(getattr(item, "kind", "") or ""),
|
|
48
|
+
"start_line": int(getattr(span, "start_line", 0)) if span is not None else 0,
|
|
49
|
+
"end_line": int(getattr(span, "end_line", 0)) if span is not None else 0,
|
|
50
|
+
"decorators": [str(value) for value in getattr(item, "decorators", []) or []],
|
|
51
|
+
"children": [_structure_row(child) for child in getattr(item, "children", []) or []],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _declared_name(node: Any, raw: bytes) -> str:
|
|
56
|
+
"""Resolve a definition's identifier through C-family declarator nesting."""
|
|
57
|
+
current = node
|
|
58
|
+
for _ in range(8):
|
|
59
|
+
candidate = None
|
|
60
|
+
for field in ("name", "declarator"):
|
|
61
|
+
try:
|
|
62
|
+
candidate = current.child_by_field_name(field)
|
|
63
|
+
except (AttributeError, TypeError):
|
|
64
|
+
candidate = None
|
|
65
|
+
if candidate is not None:
|
|
66
|
+
break
|
|
67
|
+
if candidate is None:
|
|
68
|
+
return ""
|
|
69
|
+
if str(getattr(candidate, "type", "")).endswith("identifier"):
|
|
70
|
+
return raw[candidate.start_byte:candidate.end_byte].decode("utf-8", errors="replace")
|
|
71
|
+
current = candidate
|
|
72
|
+
return ""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _fill_missing_structure_names(tree: Any, raw: bytes, rows: list[dict[str, Any]]) -> None:
|
|
76
|
+
"""The language pack's structure pass yields ``name=None`` for C-family
|
|
77
|
+
definitions — the identifier hides inside the declarator chain — and
|
|
78
|
+
downstream extraction drops nameless rows, losing every C/C++/ObjC symbol.
|
|
79
|
+
Recover names from the parse tree, matched by start line."""
|
|
80
|
+
pending: dict[int, list[dict[str, Any]]] = {}
|
|
81
|
+
|
|
82
|
+
def collect(items: list[dict[str, Any]]) -> None:
|
|
83
|
+
for row in items:
|
|
84
|
+
if not row["name"]:
|
|
85
|
+
pending.setdefault(int(row["start_line"]), []).append(row)
|
|
86
|
+
collect(row["children"])
|
|
87
|
+
|
|
88
|
+
collect(rows)
|
|
89
|
+
if not pending:
|
|
90
|
+
return
|
|
91
|
+
stack = [tree.root_node]
|
|
92
|
+
while stack:
|
|
93
|
+
node = stack.pop()
|
|
94
|
+
stack.extend(reversed(list(getattr(node, "children", ()) or ())))
|
|
95
|
+
point = getattr(node, "start_point", None)
|
|
96
|
+
waiting = pending.get(int(point[0])) if point is not None else None
|
|
97
|
+
if not waiting:
|
|
98
|
+
continue
|
|
99
|
+
name = _declared_name(node, raw)
|
|
100
|
+
if not name:
|
|
101
|
+
continue
|
|
102
|
+
row = waiting.pop(0)
|
|
103
|
+
row["name"] = name
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _call_rows(parser: Any, source: str, tree: Any | None = None) -> list[dict[str, Any]]:
|
|
107
|
+
raw = source.encode("utf-8")
|
|
108
|
+
if tree is None:
|
|
109
|
+
tree = parser.parse(raw)
|
|
110
|
+
rows: list[dict[str, Any]] = []
|
|
111
|
+
stack = [tree.root_node]
|
|
112
|
+
while stack:
|
|
113
|
+
node = stack.pop()
|
|
114
|
+
stack.extend(reversed(list(getattr(node, "children", ()) or ())))
|
|
115
|
+
node_type = str(getattr(node, "type", ""))
|
|
116
|
+
if "call" not in node_type or any(
|
|
117
|
+
value in node_type for value in ("callable", "calling_convention")
|
|
118
|
+
):
|
|
119
|
+
continue
|
|
120
|
+
callee = None
|
|
121
|
+
for field in ("function", "name", "method"):
|
|
122
|
+
try:
|
|
123
|
+
callee = node.child_by_field_name(field)
|
|
124
|
+
except (AttributeError, TypeError):
|
|
125
|
+
callee = None
|
|
126
|
+
if callee is not None:
|
|
127
|
+
break
|
|
128
|
+
if callee is None:
|
|
129
|
+
named = list(getattr(node, "named_children", ()) or ())
|
|
130
|
+
callee = named[0] if named else None
|
|
131
|
+
if callee is None:
|
|
132
|
+
continue
|
|
133
|
+
text = raw[callee.start_byte:callee.end_byte].decode("utf-8", errors="replace")
|
|
134
|
+
identifiers = re.findall(r"[A-Za-z_$][\w$]*", text)
|
|
135
|
+
if not identifiers:
|
|
136
|
+
continue
|
|
137
|
+
name = identifiers[-1]
|
|
138
|
+
receiver = ".".join(identifiers[:-1])
|
|
139
|
+
rows.append({
|
|
140
|
+
"name": name,
|
|
141
|
+
"receiver": receiver,
|
|
142
|
+
"line": int(node.start_point[0]) + 1,
|
|
143
|
+
})
|
|
144
|
+
return rows
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _native_process(language: str, source: str) -> dict[str, Any] | None:
|
|
148
|
+
"""Execute inside a child so a faulty native grammar cannot kill the server."""
|
|
149
|
+
import tree_sitter_language_pack as pack
|
|
150
|
+
|
|
151
|
+
if language not in set(pack.available_languages()):
|
|
152
|
+
return None
|
|
153
|
+
result = pack.process(
|
|
154
|
+
source,
|
|
155
|
+
pack.ProcessConfig(
|
|
156
|
+
language=language,
|
|
157
|
+
structure=True,
|
|
158
|
+
imports=True,
|
|
159
|
+
exports=True,
|
|
160
|
+
symbols=True,
|
|
161
|
+
diagnostics=True,
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
parser = pack.get_parser(language)
|
|
165
|
+
raw = source.encode("utf-8")
|
|
166
|
+
tree = parser.parse(raw)
|
|
167
|
+
structure = [_structure_row(item) for item in getattr(result, "structure", []) or []]
|
|
168
|
+
_fill_missing_structure_names(tree, raw, structure)
|
|
169
|
+
return {
|
|
170
|
+
"structure": structure,
|
|
171
|
+
"imports": [
|
|
172
|
+
{
|
|
173
|
+
"source": str(getattr(item, "source", "") or ""),
|
|
174
|
+
"items": [str(value) for value in getattr(item, "items", []) or []],
|
|
175
|
+
"alias": str(getattr(item, "alias", "") or ""),
|
|
176
|
+
}
|
|
177
|
+
for item in getattr(result, "imports", []) or []
|
|
178
|
+
],
|
|
179
|
+
"exports": [
|
|
180
|
+
{"name": str(getattr(item, "name", "") or "")}
|
|
181
|
+
for item in getattr(result, "exports", []) or []
|
|
182
|
+
],
|
|
183
|
+
"calls": _call_rows(parser, source, tree),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class ParserWorkerPool:
|
|
188
|
+
"""Small spawn-based pool that is discarded after a crash or timeout."""
|
|
189
|
+
|
|
190
|
+
def __init__(self, *, max_workers: int | None = None, timeout: float = 30.0):
|
|
191
|
+
cpu_bound = max(1, (os.cpu_count() or 2) // 2)
|
|
192
|
+
self.max_workers = max(1, min(4, max_workers or cpu_bound))
|
|
193
|
+
self.timeout = max(1.0, timeout)
|
|
194
|
+
self._pool: ProcessPoolExecutor | None = None
|
|
195
|
+
self._lock = threading.Lock()
|
|
196
|
+
|
|
197
|
+
def process(self, language: str, source: str) -> dict[str, Any] | None:
|
|
198
|
+
try:
|
|
199
|
+
pool = self._executor()
|
|
200
|
+
return pool.submit(_native_process, language, source).result(timeout=self.timeout)
|
|
201
|
+
except Exception:
|
|
202
|
+
self.restart()
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
def _executor(self) -> ProcessPoolExecutor:
|
|
206
|
+
with self._lock:
|
|
207
|
+
if self._pool is None:
|
|
208
|
+
_activate_companion_once()
|
|
209
|
+
self._pool = ProcessPoolExecutor(
|
|
210
|
+
max_workers=self.max_workers,
|
|
211
|
+
mp_context=multiprocessing.get_context("spawn"),
|
|
212
|
+
initializer=_activate_companion_once,
|
|
213
|
+
)
|
|
214
|
+
return self._pool
|
|
215
|
+
|
|
216
|
+
def restart(self) -> None:
|
|
217
|
+
with self._lock:
|
|
218
|
+
pool, self._pool = self._pool, None
|
|
219
|
+
if pool is not None:
|
|
220
|
+
pool.shutdown(wait=False, cancel_futures=True)
|
|
221
|
+
|
|
222
|
+
def close(self) -> None:
|
|
223
|
+
with self._lock:
|
|
224
|
+
pool, self._pool = self._pool, None
|
|
225
|
+
if pool is not None:
|
|
226
|
+
pool.shutdown(wait=True, cancel_futures=True)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
_POOL = ParserWorkerPool()
|
|
230
|
+
atexit.register(_POOL.close)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def process_tree_sitter(language: str, source: str) -> dict[str, Any] | None:
|
|
234
|
+
return _POOL.process(language, source)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def parser_worker_status() -> dict[str, object]:
|
|
238
|
+
return {
|
|
239
|
+
"start_method": "spawn",
|
|
240
|
+
"max_workers": _POOL.max_workers,
|
|
241
|
+
"timeout_seconds": _POOL.timeout,
|
|
242
|
+
"pid": os.getpid(),
|
|
243
|
+
"module": str(Path(__file__).resolve()),
|
|
244
|
+
"grammar_companion": dict(_ACTIVATION_STATUS),
|
|
245
|
+
}
|