devcouncil 0.2.0 → 0.2.2
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 +144 -308
- package/bin/devcouncil.js +130 -32
- package/package.json +22 -19
- package/pyproject.toml +0 -66
- package/src/devcouncil/__init__.py +0 -0
- package/src/devcouncil/__main__.py +0 -4
- package/src/devcouncil/app/__init__.py +0 -28
- package/src/devcouncil/app/config.py +0 -320
- package/src/devcouncil/app/errors.py +0 -23
- package/src/devcouncil/app/events.py +0 -44
- package/src/devcouncil/app/orchestrator.py +0 -92
- package/src/devcouncil/app/project_status.py +0 -29
- package/src/devcouncil/app/run_context.py +0 -39
- package/src/devcouncil/app/state_machine.py +0 -108
- package/src/devcouncil/artifacts/__init__.py +0 -1
- package/src/devcouncil/artifacts/coverage.py +0 -96
- package/src/devcouncil/artifacts/graph.py +0 -163
- 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/artifacts/validators.py +0 -27
- package/src/devcouncil/assets/__init__.py +0 -1
- package/src/devcouncil/assets/devcouncil-logo.svg +0 -60
- package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
- package/src/devcouncil/cli/__init__.py +0 -0
- package/src/devcouncil/cli/commands/__init__.py +0 -0
- package/src/devcouncil/cli/commands/agents.py +0 -292
- package/src/devcouncil/cli/commands/artifacts.py +0 -54
- package/src/devcouncil/cli/commands/ast.py +0 -22
- package/src/devcouncil/cli/commands/baseline.py +0 -35
- package/src/devcouncil/cli/commands/check.py +0 -209
- package/src/devcouncil/cli/commands/config.py +0 -115
- package/src/devcouncil/cli/commands/cost.py +0 -57
- package/src/devcouncil/cli/commands/dashboard.py +0 -31
- package/src/devcouncil/cli/commands/doctor.py +0 -340
- package/src/devcouncil/cli/commands/evidence.py +0 -48
- package/src/devcouncil/cli/commands/go.py +0 -656
- package/src/devcouncil/cli/commands/handoff.py +0 -69
- package/src/devcouncil/cli/commands/hook.py +0 -237
- package/src/devcouncil/cli/commands/init.py +0 -289
- package/src/devcouncil/cli/commands/integrate.py +0 -1515
- package/src/devcouncil/cli/commands/lsp.py +0 -20
- package/src/devcouncil/cli/commands/map.py +0 -112
- package/src/devcouncil/cli/commands/mcp_server.py +0 -18
- package/src/devcouncil/cli/commands/plan.py +0 -488
- package/src/devcouncil/cli/commands/prompt.py +0 -61
- package/src/devcouncil/cli/commands/repair.py +0 -89
- package/src/devcouncil/cli/commands/report.py +0 -137
- package/src/devcouncil/cli/commands/reset_demo_state.py +0 -33
- package/src/devcouncil/cli/commands/rollback.py +0 -59
- package/src/devcouncil/cli/commands/run.py +0 -289
- package/src/devcouncil/cli/commands/runs.py +0 -223
- package/src/devcouncil/cli/commands/scaffold.py +0 -32
- package/src/devcouncil/cli/commands/semantic.py +0 -47
- package/src/devcouncil/cli/commands/setup.py +0 -362
- package/src/devcouncil/cli/commands/shell.py +0 -73
- package/src/devcouncil/cli/commands/show.py +0 -76
- package/src/devcouncil/cli/commands/skills.py +0 -88
- package/src/devcouncil/cli/commands/status.py +0 -141
- package/src/devcouncil/cli/commands/tasks.py +0 -55
- package/src/devcouncil/cli/commands/trace.py +0 -88
- package/src/devcouncil/cli/commands/verify.py +0 -328
- package/src/devcouncil/cli/commands/version.py +0 -20
- package/src/devcouncil/cli/commands/watch.py +0 -574
- package/src/devcouncil/cli/commands/watch_fs.py +0 -40
- package/src/devcouncil/cli/main.py +0 -137
- package/src/devcouncil/council/__init__.py +0 -0
- package/src/devcouncil/council/prompts/__init__.py +0 -0
- package/src/devcouncil/council/prompts/arbiter.md +0 -19
- package/src/devcouncil/council/prompts/critic_a.md +0 -10
- package/src/devcouncil/council/prompts/critic_b.md +0 -10
- package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
- package/src/devcouncil/council/prompts/planner_a.md +0 -16
- package/src/devcouncil/council/prompts/planner_b.md +0 -16
- package/src/devcouncil/council/prompts/rebuttal.md +0 -10
- package/src/devcouncil/council/prompts/spec_writer.md +0 -12
- package/src/devcouncil/domain/__init__.py +0 -0
- package/src/devcouncil/domain/assumption.py +0 -17
- package/src/devcouncil/domain/critique.py +0 -32
- package/src/devcouncil/domain/evidence.py +0 -47
- package/src/devcouncil/domain/gap.py +0 -52
- package/src/devcouncil/domain/requirement.py +0 -22
- package/src/devcouncil/domain/task.py +0 -55
- package/src/devcouncil/execution/__init__.py +0 -1
- package/src/devcouncil/execution/checkpoints.py +0 -246
- package/src/devcouncil/execution/context_builder.py +0 -60
- package/src/devcouncil/execution/executor.py +0 -15
- package/src/devcouncil/execution/fs_watcher.py +0 -180
- package/src/devcouncil/execution/handoff.py +0 -102
- package/src/devcouncil/execution/hook_policy.py +0 -253
- package/src/devcouncil/execution/patch.py +0 -77
- package/src/devcouncil/execution/paths.py +0 -14
- package/src/devcouncil/execution/permissions.py +0 -85
- package/src/devcouncil/execution/policy_engine.py +0 -343
- package/src/devcouncil/execution/prompt_builder.py +0 -671
- package/src/devcouncil/execution/shell_session.py +0 -225
- package/src/devcouncil/execution/task_runner.py +0 -170
- package/src/devcouncil/executors/__init__.py +0 -1
- package/src/devcouncil/executors/agent_registry.py +0 -575
- package/src/devcouncil/executors/coding_cli.py +0 -736
- package/src/devcouncil/executors/mini_swe.py +0 -73
- package/src/devcouncil/executors/native/__init__.py +0 -0
- package/src/devcouncil/executors/native/agent.py +0 -208
- package/src/devcouncil/executors/openhands.py +0 -71
- package/src/devcouncil/gating/__init__.py +0 -1
- package/src/devcouncil/gating/checks/__init__.py +0 -0
- package/src/devcouncil/gating/checks/clean_git.py +0 -52
- package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
- package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
- package/src/devcouncil/gating/checks/secret_scan_check.py +0 -53
- package/src/devcouncil/gating/policy.py +0 -338
- package/src/devcouncil/hardware.py +0 -184
- package/src/devcouncil/indexing/__init__.py +0 -1
- package/src/devcouncil/indexing/ast_matcher.py +0 -168
- package/src/devcouncil/indexing/graph_index.py +0 -48
- package/src/devcouncil/indexing/lsp.py +0 -161
- package/src/devcouncil/indexing/repo_mapper.py +0 -1455
- package/src/devcouncil/indexing/semantic_index.py +0 -205
- package/src/devcouncil/integrations/actions.py +0 -146
- package/src/devcouncil/integrations/check.py +0 -423
- package/src/devcouncil/integrations/code_review_graph.py +0 -163
- package/src/devcouncil/integrations/github.py +0 -39
- package/src/devcouncil/integrations/github_intent.py +0 -142
- package/src/devcouncil/integrations/gitnexus.py +0 -62
- package/src/devcouncil/integrations/graphify.py +0 -34
- package/src/devcouncil/integrations/mcp/__init__.py +0 -0
- package/src/devcouncil/integrations/mcp/server.py +0 -2122
- package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +0 -24
- package/src/devcouncil/integrations/pr_comments.py +0 -62
- package/src/devcouncil/live/__init__.py +0 -2
- package/src/devcouncil/live/cards.py +0 -349
- package/src/devcouncil/live/models.py +0 -63
- package/src/devcouncil/live/repair_prompt.py +0 -83
- package/src/devcouncil/live/reviewer.py +0 -70
- package/src/devcouncil/live/signals.py +0 -135
- package/src/devcouncil/live/summary.py +0 -34
- package/src/devcouncil/live/tasks.py +0 -18
- package/src/devcouncil/live/transcripts.py +0 -141
- package/src/devcouncil/llm/__init__.py +0 -1
- package/src/devcouncil/llm/cache.py +0 -42
- package/src/devcouncil/llm/model_defaults.yaml +0 -44
- package/src/devcouncil/llm/provider.py +0 -627
- package/src/devcouncil/llm/router.py +0 -310
- package/src/devcouncil/optimization/__init__.py +0 -1
- package/src/devcouncil/optimization/gepa_agent.py +0 -318
- package/src/devcouncil/planning/__init__.py +0 -1
- package/src/devcouncil/planning/arbiter_service.py +0 -57
- package/src/devcouncil/planning/correction_manifest.py +0 -303
- package/src/devcouncil/planning/critique_service.py +0 -71
- package/src/devcouncil/planning/plan_service.py +0 -60
- package/src/devcouncil/planning/prompt_enhancer_service.py +0 -167
- package/src/devcouncil/planning/repair_service.py +0 -39
- package/src/devcouncil/planning/spec_service.py +0 -70
- package/src/devcouncil/repo/__init__.py +0 -0
- package/src/devcouncil/repo/ci_scaffold.py +0 -157
- package/src/devcouncil/repo/gitignore.py +0 -123
- package/src/devcouncil/repo/sca.py +0 -374
- package/src/devcouncil/reporting/__init__.py +0 -0
- package/src/devcouncil/reporting/github_check.py +0 -32
- package/src/devcouncil/reporting/json_report.py +0 -30
- package/src/devcouncil/reporting/markdown_report.py +0 -83
- package/src/devcouncil/reporting/report_builder.py +0 -14
- package/src/devcouncil/skills/__init__.py +0 -19
- package/src/devcouncil/skills/library/README.md +0 -46
- package/src/devcouncil/skills/library/ai-training.md +0 -50
- package/src/devcouncil/skills/library/android.md +0 -50
- package/src/devcouncil/skills/library/backend.md +0 -52
- package/src/devcouncil/skills/library/core-engineering.md +0 -95
- package/src/devcouncil/skills/library/data-engineering.md +0 -47
- package/src/devcouncil/skills/library/desktop.md +0 -46
- package/src/devcouncil/skills/library/devops.md +0 -48
- package/src/devcouncil/skills/library/game-dev.md +0 -46
- package/src/devcouncil/skills/library/ios.md +0 -48
- package/src/devcouncil/skills/library/mobile-cross-platform.md +0 -46
- package/src/devcouncil/skills/library/security.md +0 -48
- package/src/devcouncil/skills/library/systems.md +0 -48
- package/src/devcouncil/skills/library/web.md +0 -47
- package/src/devcouncil/skills/library/windows.md +0 -47
- package/src/devcouncil/skills/registry.py +0 -330
- package/src/devcouncil/storage/__init__.py +0 -0
- package/src/devcouncil/storage/db.py +0 -147
- package/src/devcouncil/storage/models.py +0 -204
- package/src/devcouncil/storage/native.py +0 -557
- package/src/devcouncil/storage/repositories.py +0 -485
- package/src/devcouncil/telemetry/__init__.py +0 -0
- package/src/devcouncil/telemetry/cost.py +0 -140
- package/src/devcouncil/telemetry/model_pricing.yaml +0 -48
- package/src/devcouncil/telemetry/pricing.py +0 -28
- package/src/devcouncil/telemetry/traces.py +0 -146
- package/src/devcouncil/telemetry/tracker.py +0 -52
- package/src/devcouncil/ui/__init__.py +0 -1
- package/src/devcouncil/ui/dashboard.py +0 -423
- package/src/devcouncil/utils/__init__.py +0 -1
- package/src/devcouncil/utils/redaction.py +0 -147
- package/src/devcouncil/utils/subprocess_env.py +0 -69
- package/src/devcouncil/verification/__init__.py +0 -1
- package/src/devcouncil/verification/acceptance_compiler.py +0 -125
- package/src/devcouncil/verification/ad_hoc_check.py +0 -129
- package/src/devcouncil/verification/diff_coverage.py +0 -353
- package/src/devcouncil/verification/implementation_reviewer.py +0 -55
- package/src/devcouncil/verification/next_actions.py +0 -189
- package/src/devcouncil/verification/sandbox.py +0 -178
- package/src/devcouncil/verification/test_resolver.py +0 -91
- package/src/devcouncil/verification/verifier.py +0 -1548
- package/uv.lock +0 -1226
|
@@ -1,1455 +0,0 @@
|
|
|
1
|
-
import ast
|
|
2
|
-
import hashlib
|
|
3
|
-
import json
|
|
4
|
-
import logging
|
|
5
|
-
import os
|
|
6
|
-
import re
|
|
7
|
-
import subprocess
|
|
8
|
-
import sys
|
|
9
|
-
from collections import Counter, defaultdict
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from typing import Dict, List, Set, Tuple
|
|
12
|
-
|
|
13
|
-
from pydantic import BaseModel, Field
|
|
14
|
-
|
|
15
|
-
from devcouncil.indexing.lsp import LspInspector
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
18
|
-
|
|
19
|
-
# File extensions treated as primary source for subsystem inference.
|
|
20
|
-
_CODE_EXTENSIONS = {".py", ".ts", ".tsx", ".js", ".jsx", ".go", ".rs", ".java", ".kt", ".rb", ".cs", ".cpp", ".c"}
|
|
21
|
-
# Top-level directories grouped as their own area rather than folded into a source root.
|
|
22
|
-
_AUX_AREA_ROOTS = {"tests", "test", "docs", "doc", "scripts", "examples", "example", "benchmarks"}
|
|
23
|
-
# Filenames that signal an entry point, used to break ties when no import data exists.
|
|
24
|
-
_ENTRY_NAME_HINTS = ("__init__", "__main__", "main", "index", "app", "cli", "server", "mod", "lib")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class RepoFileEntry(BaseModel):
|
|
28
|
-
path: str
|
|
29
|
-
area: str
|
|
30
|
-
kind: str
|
|
31
|
-
language: str | None = None
|
|
32
|
-
summary: str
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class RepoSubsystem(BaseModel):
|
|
36
|
-
area: str
|
|
37
|
-
summary: str
|
|
38
|
-
entry_points: List[str]
|
|
39
|
-
critical_files: List[str]
|
|
40
|
-
neighbors: List[str] = Field(default_factory=list)
|
|
41
|
-
handoff_paths: List[str] = Field(default_factory=list)
|
|
42
|
-
role_files: Dict[str, List[str]] = Field(default_factory=dict)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class RepoMap(BaseModel):
|
|
46
|
-
languages: List[str]
|
|
47
|
-
frameworks: List[str]
|
|
48
|
-
package_managers: List[str]
|
|
49
|
-
test_commands: List[str]
|
|
50
|
-
important_files: List[str]
|
|
51
|
-
candidate_files: List[Dict[str, str]]
|
|
52
|
-
files: List[RepoFileEntry] = Field(default_factory=list)
|
|
53
|
-
subsystems: List[RepoSubsystem] = Field(default_factory=list)
|
|
54
|
-
# file -> the files that import it (reverse import edges, capped per file). Lets a
|
|
55
|
-
# prompt show the blast radius of changing a file without re-parsing the repo.
|
|
56
|
-
dependents: Dict[str, List[str]] = Field(default_factory=dict)
|
|
57
|
-
# Freshness fingerprints captured at generation: the git HEAD the map was built from
|
|
58
|
-
# and a hash of the tracked file set. Consumers compare against the current repo to
|
|
59
|
-
# detect a stale map before trusting its structure.
|
|
60
|
-
generated_head: str = ""
|
|
61
|
-
indexed_hash: str = ""
|
|
62
|
-
lsp: Dict[str, object] = Field(default_factory=dict)
|
|
63
|
-
# Optional dependency-vulnerability findings. Populated only when `dev map` is
|
|
64
|
-
# run with SCA explicitly enabled (off by default so the map stays fast and
|
|
65
|
-
# offline-by-default); empty otherwise.
|
|
66
|
-
dependency_risks: List[Dict[str, str]] = Field(default_factory=list)
|
|
67
|
-
|
|
68
|
-
class RepoMapper:
|
|
69
|
-
def __init__(self, project_root: Path):
|
|
70
|
-
self.project_root = project_root
|
|
71
|
-
# Common source-root prefix of the repo's primary code (e.g. "src/pkg"),
|
|
72
|
-
# computed once per map_repo run. Drives generic, non-DevCouncil subsystem
|
|
73
|
-
# inference. None until computed.
|
|
74
|
-
self._source_root: str | None = None
|
|
75
|
-
# True when this is not the DevCouncil source tree, so generic inference is used
|
|
76
|
-
# for area bucketing. Set in map_repo.
|
|
77
|
-
self._use_generic: bool = False
|
|
78
|
-
# Import edges (importer -> imported), computed once per map_repo run and reused
|
|
79
|
-
# by subsystem inference, important-file ranking, and the dependents index.
|
|
80
|
-
self._edges: List[Tuple[str, str]] | None = None
|
|
81
|
-
|
|
82
|
-
_DEPENDENTS_MAX = 12 # cap dependents listed per file to bound repo_map.json size
|
|
83
|
-
|
|
84
|
-
_LANGUAGE_BY_EXTENSION = {
|
|
85
|
-
".py": "python",
|
|
86
|
-
".ts": "typescript",
|
|
87
|
-
".tsx": "typescript",
|
|
88
|
-
".js": "javascript",
|
|
89
|
-
".jsx": "javascript",
|
|
90
|
-
".go": "go",
|
|
91
|
-
".rs": "rust",
|
|
92
|
-
".java": "java",
|
|
93
|
-
".c": "c",
|
|
94
|
-
".cpp": "cpp",
|
|
95
|
-
".md": "markdown",
|
|
96
|
-
".yaml": "yaml",
|
|
97
|
-
".yml": "yaml",
|
|
98
|
-
".toml": "toml",
|
|
99
|
-
".json": "json",
|
|
100
|
-
".sh": "shell",
|
|
101
|
-
".ps1": "powershell",
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
_AREA_SUMMARIES = {
|
|
105
|
-
"src/devcouncil/cli": "CLI entrypoints and command registration",
|
|
106
|
-
"src/devcouncil/app": "Orchestration runtime and lifecycle state",
|
|
107
|
-
"src/devcouncil/artifacts": "Artifact graph, coverage, and serialization",
|
|
108
|
-
"src/devcouncil/council": "Council prompts and debate scaffolding",
|
|
109
|
-
"src/devcouncil/domain": "Domain entities for requirements, tasks, and evidence",
|
|
110
|
-
"src/devcouncil/execution": "Execution plumbing, prompts, permissions, and task runs",
|
|
111
|
-
"src/devcouncil/executors": "Executor adapters and CLI agent registry",
|
|
112
|
-
"src/devcouncil/gating": "Blocking policies and guardrails",
|
|
113
|
-
"src/devcouncil/indexing": "Repo mapping, AST matching, semantic snapshots, and language-server detection",
|
|
114
|
-
"src/devcouncil/integrations": "External integrations and graph adapters",
|
|
115
|
-
"src/devcouncil/live": "Live review cards, signals, summaries, and transcripts",
|
|
116
|
-
"src/devcouncil/llm": "Model provider routing, defaults, and caching",
|
|
117
|
-
"src/devcouncil/planning": "Planning, critique, repair, and spec services",
|
|
118
|
-
"src/devcouncil/repo": "Repository helpers and filesystem utilities",
|
|
119
|
-
"src/devcouncil/reporting": "JSON and markdown report builders",
|
|
120
|
-
"src/devcouncil/storage": "SQLite persistence and repository layer",
|
|
121
|
-
"src/devcouncil/telemetry": "Trace logging, pricing, and telemetry tracking",
|
|
122
|
-
"src/devcouncil/ui": "Dashboard and lightweight UI helpers",
|
|
123
|
-
"src/devcouncil/utils": "Shared utilities and redaction helpers",
|
|
124
|
-
"src/devcouncil/verification": "Verification gates and implementation review",
|
|
125
|
-
"docs": "Repository documentation",
|
|
126
|
-
"tests": "Automated tests",
|
|
127
|
-
"scripts": "Maintenance and smoke-test scripts",
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
_SUBSYSTEM_INDEX: Dict[str, Tuple[str, List[str]]] = {
|
|
131
|
-
"src/devcouncil/council": (
|
|
132
|
-
"Prompt-driven council workflows and debate templates.",
|
|
133
|
-
[
|
|
134
|
-
"src/devcouncil/council/prompts/spec_writer.md",
|
|
135
|
-
"src/devcouncil/council/prompts/planner_a.md",
|
|
136
|
-
"src/devcouncil/council/prompts/implementation_reviewer.md",
|
|
137
|
-
],
|
|
138
|
-
),
|
|
139
|
-
"src/devcouncil/domain": (
|
|
140
|
-
"Shared domain entities for tasks, requirements, evidence, and critique.",
|
|
141
|
-
[
|
|
142
|
-
"src/devcouncil/domain/task.py",
|
|
143
|
-
"src/devcouncil/domain/requirement.py",
|
|
144
|
-
"src/devcouncil/domain/gap.py",
|
|
145
|
-
"src/devcouncil/domain/evidence.py",
|
|
146
|
-
],
|
|
147
|
-
),
|
|
148
|
-
"src/devcouncil/execution": (
|
|
149
|
-
"Execution planning and task orchestration runtime, including prompt and permission handling.",
|
|
150
|
-
[
|
|
151
|
-
"src/devcouncil/execution/task_runner.py",
|
|
152
|
-
"src/devcouncil/execution/prompt_builder.py",
|
|
153
|
-
"src/devcouncil/execution/permissions.py",
|
|
154
|
-
"src/devcouncil/execution/paths.py",
|
|
155
|
-
],
|
|
156
|
-
),
|
|
157
|
-
"src/devcouncil/executors": (
|
|
158
|
-
"Adapter layer that converts tasks into CLI/API side effects.",
|
|
159
|
-
[
|
|
160
|
-
"src/devcouncil/executors/agent_registry.py",
|
|
161
|
-
"src/devcouncil/executors/coding_cli.py",
|
|
162
|
-
"src/devcouncil/executors/mini_swe.py",
|
|
163
|
-
"src/devcouncil/executors/openhands.py",
|
|
164
|
-
],
|
|
165
|
-
),
|
|
166
|
-
"src/devcouncil/indexing": (
|
|
167
|
-
"Repo mapping, AST matching, semantic snapshots, and language-server detection (no live LSP client).",
|
|
168
|
-
[
|
|
169
|
-
"src/devcouncil/indexing/repo_mapper.py",
|
|
170
|
-
"src/devcouncil/indexing/ast_matcher.py",
|
|
171
|
-
"src/devcouncil/indexing/semantic_index.py",
|
|
172
|
-
"src/devcouncil/indexing/lsp.py",
|
|
173
|
-
],
|
|
174
|
-
),
|
|
175
|
-
"src/devcouncil/integrations": (
|
|
176
|
-
"External system integrations and MCP/Graph adapters.",
|
|
177
|
-
[
|
|
178
|
-
"src/devcouncil/integrations/gitnexus.py",
|
|
179
|
-
"src/devcouncil/integrations/graphify.py",
|
|
180
|
-
"src/devcouncil/integrations/mcp/server.py",
|
|
181
|
-
"src/devcouncil/integrations/github.py",
|
|
182
|
-
],
|
|
183
|
-
),
|
|
184
|
-
"src/devcouncil/verification": (
|
|
185
|
-
"Verification gates, evidence checks, and implementation review.",
|
|
186
|
-
[
|
|
187
|
-
"src/devcouncil/verification/verifier.py",
|
|
188
|
-
"src/devcouncil/verification/patch_reviewer.py",
|
|
189
|
-
"src/devcouncil/verification/review_agent.py",
|
|
190
|
-
],
|
|
191
|
-
),
|
|
192
|
-
"src/devcouncil/live": (
|
|
193
|
-
"Live review cards, signals, summaries, and repair guidance.",
|
|
194
|
-
[
|
|
195
|
-
"src/devcouncil/live/reviewer.py",
|
|
196
|
-
"src/devcouncil/live/cards.py",
|
|
197
|
-
"src/devcouncil/live/signals.py",
|
|
198
|
-
],
|
|
199
|
-
),
|
|
200
|
-
"src/devcouncil/llm": (
|
|
201
|
-
"Model routing, provider registry, and LLM response caching.",
|
|
202
|
-
[
|
|
203
|
-
"src/devcouncil/llm/router.py",
|
|
204
|
-
"src/devcouncil/llm/provider.py",
|
|
205
|
-
"src/devcouncil/llm/cache.py",
|
|
206
|
-
],
|
|
207
|
-
),
|
|
208
|
-
"src/devcouncil/planning": (
|
|
209
|
-
"Task planning, spec generation, critique, and arbitration services.",
|
|
210
|
-
[
|
|
211
|
-
"src/devcouncil/planning/plan_service.py",
|
|
212
|
-
"src/devcouncil/planning/spec_service.py",
|
|
213
|
-
"src/devcouncil/planning/critique_service.py",
|
|
214
|
-
"src/devcouncil/planning/repair_service.py",
|
|
215
|
-
],
|
|
216
|
-
),
|
|
217
|
-
"src/devcouncil/repo": (
|
|
218
|
-
"Repository helper helpers for workspace interactions.",
|
|
219
|
-
[
|
|
220
|
-
"src/devcouncil/repo/__init__.py",
|
|
221
|
-
],
|
|
222
|
-
),
|
|
223
|
-
"src/devcouncil/reporting": (
|
|
224
|
-
"Report generation and check-writing utilities.",
|
|
225
|
-
[
|
|
226
|
-
"src/devcouncil/reporting/report_builder.py",
|
|
227
|
-
"src/devcouncil/reporting/json_report.py",
|
|
228
|
-
"src/devcouncil/reporting/markdown_report.py",
|
|
229
|
-
],
|
|
230
|
-
),
|
|
231
|
-
"src/devcouncil/gating": (
|
|
232
|
-
"Policy gates and blocking criteria.",
|
|
233
|
-
[
|
|
234
|
-
"src/devcouncil/gating/gate.py",
|
|
235
|
-
"src/devcouncil/gating/policy.py",
|
|
236
|
-
"src/devcouncil/gating/rules.py",
|
|
237
|
-
],
|
|
238
|
-
),
|
|
239
|
-
"src/devcouncil/storage": (
|
|
240
|
-
"Persistence layer for run state, artifacts, and graph-backed history.",
|
|
241
|
-
[
|
|
242
|
-
"src/devcouncil/storage/repositories.py",
|
|
243
|
-
"src/devcouncil/storage/db.py",
|
|
244
|
-
"src/devcouncil/storage/models.py",
|
|
245
|
-
],
|
|
246
|
-
),
|
|
247
|
-
"src/devcouncil/cli": (
|
|
248
|
-
"User command surface and command wiring.",
|
|
249
|
-
[
|
|
250
|
-
"src/devcouncil/cli/main.py",
|
|
251
|
-
"src/devcouncil/cli/commands/map.py",
|
|
252
|
-
"src/devcouncil/cli/commands/plan.py",
|
|
253
|
-
"src/devcouncil/cli/commands/run.py",
|
|
254
|
-
],
|
|
255
|
-
),
|
|
256
|
-
"src/devcouncil/app": (
|
|
257
|
-
"Orchestrator and state machine controlling project lifecycle.",
|
|
258
|
-
[
|
|
259
|
-
"src/devcouncil/app/orchestrator.py",
|
|
260
|
-
"src/devcouncil/app/state_machine.py",
|
|
261
|
-
"src/devcouncil/app/run_context.py",
|
|
262
|
-
],
|
|
263
|
-
),
|
|
264
|
-
"src/devcouncil/artifacts": (
|
|
265
|
-
"Artifact graph primitives and evidence linking.",
|
|
266
|
-
[
|
|
267
|
-
"src/devcouncil/artifacts/graph.py",
|
|
268
|
-
"src/devcouncil/artifacts/exports.py",
|
|
269
|
-
"src/devcouncil/artifacts/types.py",
|
|
270
|
-
],
|
|
271
|
-
),
|
|
272
|
-
"src/devcouncil/telemetry": (
|
|
273
|
-
"Telemetry ingestion, tracing, cost, and pricing.",
|
|
274
|
-
[
|
|
275
|
-
"src/devcouncil/telemetry/traces.py",
|
|
276
|
-
"src/devcouncil/telemetry/tracker.py",
|
|
277
|
-
"src/devcouncil/telemetry/cost.py",
|
|
278
|
-
],
|
|
279
|
-
),
|
|
280
|
-
"src/devcouncil/ui": (
|
|
281
|
-
"Dashboard rendering and lightweight user interface glue.",
|
|
282
|
-
[
|
|
283
|
-
"src/devcouncil/ui/dashboard.py",
|
|
284
|
-
],
|
|
285
|
-
),
|
|
286
|
-
"src/devcouncil/utils": (
|
|
287
|
-
"Shared utility helpers, redaction, and support functions.",
|
|
288
|
-
[
|
|
289
|
-
"src/devcouncil/utils/redaction.py",
|
|
290
|
-
],
|
|
291
|
-
),
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
_SUBSYSTEM_CRITICAL_MAX = 6
|
|
295
|
-
|
|
296
|
-
_SUBSYSTEM_NEIGHBORS: Dict[str, List[str]] = {
|
|
297
|
-
"src/devcouncil/council": [
|
|
298
|
-
"src/devcouncil/planning",
|
|
299
|
-
"src/devcouncil/verification",
|
|
300
|
-
],
|
|
301
|
-
"src/devcouncil/domain": [
|
|
302
|
-
"src/devcouncil/execution",
|
|
303
|
-
"src/devcouncil/executors",
|
|
304
|
-
"src/devcouncil/storage",
|
|
305
|
-
"src/devcouncil/verification",
|
|
306
|
-
"src/devcouncil/planning",
|
|
307
|
-
"src/devcouncil/gating",
|
|
308
|
-
],
|
|
309
|
-
"src/devcouncil/execution": [
|
|
310
|
-
"src/devcouncil/executors",
|
|
311
|
-
"src/devcouncil/gating",
|
|
312
|
-
"src/devcouncil/verification",
|
|
313
|
-
"src/devcouncil/storage",
|
|
314
|
-
],
|
|
315
|
-
"src/devcouncil/executors": [
|
|
316
|
-
"src/devcouncil/execution",
|
|
317
|
-
"src/devcouncil/app",
|
|
318
|
-
"src/devcouncil/storage",
|
|
319
|
-
],
|
|
320
|
-
"src/devcouncil/verification": [
|
|
321
|
-
"src/devcouncil/storage",
|
|
322
|
-
"src/devcouncil/gating",
|
|
323
|
-
"src/devcouncil/app",
|
|
324
|
-
],
|
|
325
|
-
"src/devcouncil/gating": [
|
|
326
|
-
"src/devcouncil/execution",
|
|
327
|
-
"src/devcouncil/verification",
|
|
328
|
-
"src/devcouncil/storage",
|
|
329
|
-
],
|
|
330
|
-
"src/devcouncil/storage": [
|
|
331
|
-
"src/devcouncil/app",
|
|
332
|
-
"src/devcouncil/artifacts",
|
|
333
|
-
"src/devcouncil/verification",
|
|
334
|
-
],
|
|
335
|
-
"src/devcouncil/cli": [
|
|
336
|
-
"src/devcouncil/app",
|
|
337
|
-
"src/devcouncil/storage",
|
|
338
|
-
"src/devcouncil/indexing",
|
|
339
|
-
],
|
|
340
|
-
"src/devcouncil/app": [
|
|
341
|
-
"src/devcouncil/cli",
|
|
342
|
-
"src/devcouncil/execution",
|
|
343
|
-
"src/devcouncil/storage",
|
|
344
|
-
"src/devcouncil/verification",
|
|
345
|
-
],
|
|
346
|
-
"src/devcouncil/artifacts": [
|
|
347
|
-
"src/devcouncil/storage",
|
|
348
|
-
"src/devcouncil/verification",
|
|
349
|
-
],
|
|
350
|
-
"src/devcouncil/indexing": [
|
|
351
|
-
"src/devcouncil/llm",
|
|
352
|
-
"src/devcouncil/execution",
|
|
353
|
-
"src/devcouncil/cli",
|
|
354
|
-
],
|
|
355
|
-
"src/devcouncil/integrations": [
|
|
356
|
-
"src/devcouncil/cli",
|
|
357
|
-
"src/devcouncil/live",
|
|
358
|
-
"src/devcouncil/reporting",
|
|
359
|
-
"src/devcouncil/telemetry",
|
|
360
|
-
],
|
|
361
|
-
"src/devcouncil/live": [
|
|
362
|
-
"src/devcouncil/verification",
|
|
363
|
-
"src/devcouncil/telemetry",
|
|
364
|
-
"src/devcouncil/reporting",
|
|
365
|
-
"src/devcouncil/cli",
|
|
366
|
-
],
|
|
367
|
-
"src/devcouncil/llm": [
|
|
368
|
-
"src/devcouncil/planning",
|
|
369
|
-
"src/devcouncil/execution",
|
|
370
|
-
"src/devcouncil/verification",
|
|
371
|
-
"src/devcouncil/app",
|
|
372
|
-
],
|
|
373
|
-
"src/devcouncil/planning": [
|
|
374
|
-
"src/devcouncil/domain",
|
|
375
|
-
"src/devcouncil/llm",
|
|
376
|
-
"src/devcouncil/cli",
|
|
377
|
-
"src/devcouncil/execution",
|
|
378
|
-
],
|
|
379
|
-
"src/devcouncil/repo": [
|
|
380
|
-
"src/devcouncil/cli",
|
|
381
|
-
],
|
|
382
|
-
"src/devcouncil/reporting": [
|
|
383
|
-
"src/devcouncil/telemetry",
|
|
384
|
-
"src/devcouncil/integrations",
|
|
385
|
-
"src/devcouncil/cli",
|
|
386
|
-
"src/devcouncil/live",
|
|
387
|
-
],
|
|
388
|
-
"src/devcouncil/telemetry": [
|
|
389
|
-
"src/devcouncil/cli",
|
|
390
|
-
"src/devcouncil/app",
|
|
391
|
-
"src/devcouncil/execution",
|
|
392
|
-
"src/devcouncil/verification",
|
|
393
|
-
"src/devcouncil/llm",
|
|
394
|
-
],
|
|
395
|
-
"src/devcouncil/ui": [
|
|
396
|
-
"src/devcouncil/telemetry",
|
|
397
|
-
],
|
|
398
|
-
"src/devcouncil/utils": [
|
|
399
|
-
"src/devcouncil/execution",
|
|
400
|
-
"src/devcouncil/cli",
|
|
401
|
-
"src/devcouncil/verification",
|
|
402
|
-
"src/devcouncil/executors",
|
|
403
|
-
"src/devcouncil/llm",
|
|
404
|
-
],
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
_SUBSYSTEM_HANDOFFS: Dict[str, List[str]] = {
|
|
408
|
-
"src/devcouncil/council": [
|
|
409
|
-
"planning/arbiter_service.py -> planning/plan_service.py",
|
|
410
|
-
"planning/spec_service.py -> planning/plan_service.py",
|
|
411
|
-
],
|
|
412
|
-
"src/devcouncil/domain": [
|
|
413
|
-
"domain/task.py -> execution/task_runner.py",
|
|
414
|
-
"domain/evidence.py -> artifacts/graph.py",
|
|
415
|
-
"domain/requirement.py -> verification/verifier.py",
|
|
416
|
-
],
|
|
417
|
-
"src/devcouncil/execution": [
|
|
418
|
-
"execution/task_runner.py -> executors/*",
|
|
419
|
-
"execution/task_runner.py -> verification/verifier.py",
|
|
420
|
-
"execution/task_runner.py -> storage/repositories.py",
|
|
421
|
-
],
|
|
422
|
-
"src/devcouncil/executors": [
|
|
423
|
-
"executors/* -> execution/task_runner.py",
|
|
424
|
-
"executors/* -> storage/repositories.py",
|
|
425
|
-
],
|
|
426
|
-
"src/devcouncil/verification": [
|
|
427
|
-
"verification/verifier.py -> storage/repositories.py",
|
|
428
|
-
"verification/verifier.py -> gating/policy.py",
|
|
429
|
-
"verification/verifier.py -> artifacts/graph.py",
|
|
430
|
-
],
|
|
431
|
-
"src/devcouncil/gating": [
|
|
432
|
-
"gating/policy.py -> execution/permissions.py",
|
|
433
|
-
"gating/policy.py -> verification/verifier.py",
|
|
434
|
-
],
|
|
435
|
-
"src/devcouncil/storage": [
|
|
436
|
-
"storage/repositories.py -> app/state_machine.py",
|
|
437
|
-
"storage/repositories.py -> artifacts/graph.py",
|
|
438
|
-
],
|
|
439
|
-
"src/devcouncil/indexing": [
|
|
440
|
-
"indexing/repo_mapper.py -> cli/commands/map.py",
|
|
441
|
-
"indexing/lsp.py -> execution/task_runner.py",
|
|
442
|
-
],
|
|
443
|
-
"src/devcouncil/integrations": [
|
|
444
|
-
"integrations/mcp/server.py -> live/reviewer.py",
|
|
445
|
-
"integrations/code_review_graph.py -> live/cards.py",
|
|
446
|
-
"integrations/gitnexus.py -> reporting/report_builder.py",
|
|
447
|
-
],
|
|
448
|
-
"src/devcouncil/live": [
|
|
449
|
-
"live/summary.py -> live/cards.py",
|
|
450
|
-
"live/reviewer.py -> live/models.py",
|
|
451
|
-
"live/tasks.py -> live/signals.py",
|
|
452
|
-
],
|
|
453
|
-
"src/devcouncil/llm": [
|
|
454
|
-
"llm/router.py -> telemetry/tracker.py",
|
|
455
|
-
"llm/router.py -> telemetry/traces.py",
|
|
456
|
-
"llm/provider.py -> llm/router.py",
|
|
457
|
-
],
|
|
458
|
-
"src/devcouncil/planning": [
|
|
459
|
-
"planning/plan_service.py -> execution/task_runner.py",
|
|
460
|
-
"planning/repair_service.py -> verification/implementation_reviewer.py",
|
|
461
|
-
"planning/arbiter_service.py -> verification/verifier.py",
|
|
462
|
-
],
|
|
463
|
-
"src/devcouncil/repo": [
|
|
464
|
-
"repo/__init__.py -> cli/commands/map.py",
|
|
465
|
-
],
|
|
466
|
-
"src/devcouncil/reporting": [
|
|
467
|
-
"reporting/report_builder.py -> reporting/markdown_report.py",
|
|
468
|
-
"reporting/report_builder.py -> reporting/json_report.py",
|
|
469
|
-
"reporting/github_check.py -> integrations/pr_comments.py",
|
|
470
|
-
],
|
|
471
|
-
"src/devcouncil/telemetry": [
|
|
472
|
-
"telemetry/traces.py -> live/summary.py",
|
|
473
|
-
"telemetry/tracker.py -> reporting/markdown_report.py",
|
|
474
|
-
],
|
|
475
|
-
"src/devcouncil/ui": [
|
|
476
|
-
"ui/dashboard.py -> live/summary.py",
|
|
477
|
-
],
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
_SUBSYSTEM_ROLE_FILES: Dict[str, List[Tuple[str, List[str]]]] = {
|
|
481
|
-
"src/devcouncil/council": [
|
|
482
|
-
("prompts", ["council/prompts/spec_writer.md", "council/prompts/rebuttal.md", "council/prompts/implementation_reviewer.md"]),
|
|
483
|
-
("planners", ["council/prompts/planner_a.md", "council/prompts/planner_b.md"]),
|
|
484
|
-
("critics", ["council/prompts/critic_a.md", "council/prompts/critic_b.md"]),
|
|
485
|
-
("arbitration", ["council/prompts/arbiter.md"]),
|
|
486
|
-
],
|
|
487
|
-
"src/devcouncil/domain": [
|
|
488
|
-
("tasks", ["domain/task.py"]),
|
|
489
|
-
("requirements", ["domain/requirement.py"]),
|
|
490
|
-
("evidence", ["domain/evidence.py"]),
|
|
491
|
-
("gaps", ["domain/gap.py"]),
|
|
492
|
-
("critiques", ["domain/critique.py"]),
|
|
493
|
-
("assumptions", ["domain/assumption.py"]),
|
|
494
|
-
],
|
|
495
|
-
"src/devcouncil/indexing": [
|
|
496
|
-
("mapping", ["indexing/repo_mapper.py"]),
|
|
497
|
-
("ast", ["indexing/ast_matcher.py"]),
|
|
498
|
-
("semantic", ["indexing/semantic_index.py"]),
|
|
499
|
-
# Detection-only LSP helper (no live client); see lsp.py docstring.
|
|
500
|
-
("lsp", ["indexing/lsp.py"]),
|
|
501
|
-
# GraphIndex is consumed by integrations/gitnexus.py — kept, not dead.
|
|
502
|
-
("graph", ["indexing/graph_index.py"]),
|
|
503
|
-
],
|
|
504
|
-
"src/devcouncil/integrations": [
|
|
505
|
-
("vcs", ["integrations/github.py"]),
|
|
506
|
-
("graphify", ["integrations/graphify.py"]),
|
|
507
|
-
("code_review", ["integrations/code_review_graph.py"]),
|
|
508
|
-
("comments", ["integrations/pr_comments.py"]),
|
|
509
|
-
("mcp", ["integrations/mcp/server.py"]),
|
|
510
|
-
("third_party", ["integrations/gitnexus.py"]),
|
|
511
|
-
],
|
|
512
|
-
"src/devcouncil/cli": [
|
|
513
|
-
("entrypoints", ["cli/main.py"]),
|
|
514
|
-
("commands", ["cli/commands/map.py", "cli/commands/plan.py", "cli/commands/run.py", "cli/commands/verify.py"]),
|
|
515
|
-
("setup", ["cli/commands/init.py", "cli/commands/setup.py", "cli/commands/integrate.py"]),
|
|
516
|
-
("lifecycle", ["cli/commands/status.py", "cli/commands/show.py", "cli/commands/watch.py"]),
|
|
517
|
-
("maintenance", ["cli/commands/doctor.py", "cli/commands/version.py", "cli/commands/config.py"]),
|
|
518
|
-
],
|
|
519
|
-
"src/devcouncil/app": [
|
|
520
|
-
("orchestration", ["app/orchestrator.py"]),
|
|
521
|
-
("state", ["app/state_machine.py", "app/run_context.py"]),
|
|
522
|
-
("events", ["app/events.py"]),
|
|
523
|
-
("configuration", ["app/config.py", "app/errors.py", "app/project_status.py"]),
|
|
524
|
-
],
|
|
525
|
-
"src/devcouncil/artifacts": [
|
|
526
|
-
("graph", ["artifacts/graph.py"]),
|
|
527
|
-
("coverage", ["artifacts/coverage.py", "artifacts/serializer.py"]),
|
|
528
|
-
("schema", ["artifacts/schemas.py", "artifacts/migrations.py"]),
|
|
529
|
-
("validation", ["artifacts/validators.py"]),
|
|
530
|
-
],
|
|
531
|
-
"src/devcouncil/executors": [
|
|
532
|
-
("registry", ["executors/agent_registry.py"]),
|
|
533
|
-
("adapters", ["executors/coding_cli.py", "executors/openhands.py", "executors/mini_swe.py"]),
|
|
534
|
-
("native", ["executors/native/agent.py"]),
|
|
535
|
-
],
|
|
536
|
-
"src/devcouncil/execution": [
|
|
537
|
-
("runtime", ["execution/task_runner.py", "execution/context_builder.py"]),
|
|
538
|
-
("prompting", ["execution/prompt_builder.py"]),
|
|
539
|
-
("permissions", ["execution/permissions.py"]),
|
|
540
|
-
("patching", ["execution/patch.py", "execution/executor.py"]),
|
|
541
|
-
("paths", ["execution/paths.py"]),
|
|
542
|
-
],
|
|
543
|
-
"src/devcouncil/gating": [
|
|
544
|
-
("policy", ["gating/policy.py"]),
|
|
545
|
-
("checks", ["gating/checks/clean_git.py", "gating/checks/planned_files_check.py"]),
|
|
546
|
-
("coverage", ["gating/checks/requirement_coverage.py", "gating/checks/secret_scan_check.py"]),
|
|
547
|
-
],
|
|
548
|
-
"src/devcouncil/live": [
|
|
549
|
-
("cards", ["live/cards.py"]),
|
|
550
|
-
("review", ["live/reviewer.py"]),
|
|
551
|
-
("signals", ["live/signals.py"]),
|
|
552
|
-
("sessions", ["live/tasks.py", "live/transcripts.py"]),
|
|
553
|
-
("summaries", ["live/summary.py"]),
|
|
554
|
-
("models", ["live/models.py"]),
|
|
555
|
-
],
|
|
556
|
-
"src/devcouncil/llm": [
|
|
557
|
-
("routing", ["llm/router.py"]),
|
|
558
|
-
("providers", ["llm/provider.py"]),
|
|
559
|
-
("cache", ["llm/cache.py"]),
|
|
560
|
-
("defaults", ["llm/model_defaults.yaml"]),
|
|
561
|
-
],
|
|
562
|
-
"src/devcouncil/planning": [
|
|
563
|
-
("plan", ["planning/plan_service.py", "planning/prompt_enhancer_service.py"]),
|
|
564
|
-
("spec", ["planning/spec_service.py"]),
|
|
565
|
-
("critique", ["planning/critique_service.py"]),
|
|
566
|
-
("repair", ["planning/repair_service.py"]),
|
|
567
|
-
("arbiter", ["planning/arbiter_service.py"]),
|
|
568
|
-
],
|
|
569
|
-
"src/devcouncil/repo": [
|
|
570
|
-
("api", ["repo/__init__.py"]),
|
|
571
|
-
],
|
|
572
|
-
"src/devcouncil/reporting": [
|
|
573
|
-
("builder", ["reporting/report_builder.py"]),
|
|
574
|
-
("markdown", ["reporting/markdown_report.py"]),
|
|
575
|
-
("json", ["reporting/json_report.py"]),
|
|
576
|
-
("checks", ["reporting/github_check.py"]),
|
|
577
|
-
],
|
|
578
|
-
"src/devcouncil/verification": [
|
|
579
|
-
("gates", ["verification/verifier.py", "verification/implementation_reviewer.py"]),
|
|
580
|
-
("implementation_reviewer", ["verification/implementation_reviewer.py"]),
|
|
581
|
-
("policy", ["verification/verifier.py"]),
|
|
582
|
-
],
|
|
583
|
-
"src/devcouncil/telemetry": [
|
|
584
|
-
("traces", ["telemetry/traces.py"]),
|
|
585
|
-
("tracker", ["telemetry/tracker.py"]),
|
|
586
|
-
("cost", ["telemetry/cost.py"]),
|
|
587
|
-
("pricing", ["telemetry/pricing.py", "telemetry/model_pricing.yaml"]),
|
|
588
|
-
],
|
|
589
|
-
"src/devcouncil/storage": [
|
|
590
|
-
("repositories", ["storage/repositories.py"]),
|
|
591
|
-
("schema", ["storage/models.py"]),
|
|
592
|
-
("database", ["storage/db.py"]),
|
|
593
|
-
],
|
|
594
|
-
"src/devcouncil/ui": [
|
|
595
|
-
("dashboard", ["ui/dashboard.py"]),
|
|
596
|
-
],
|
|
597
|
-
"src/devcouncil/utils": [
|
|
598
|
-
("redaction", ["utils/redaction.py"]),
|
|
599
|
-
],
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
_COMMAND_SUMMARIES = {
|
|
603
|
-
"agents": "CLI agent registry and integration commands",
|
|
604
|
-
"artifacts": "Artifact graph inspection commands",
|
|
605
|
-
"ast": "AST matching and symbol discovery commands",
|
|
606
|
-
"baseline": "Capture or inspect a baseline snapshot",
|
|
607
|
-
"config": "Inspect or mutate project configuration",
|
|
608
|
-
"dashboard": "Dashboard launch command",
|
|
609
|
-
"doctor": "Preflight and environment diagnostics",
|
|
610
|
-
"go": "End-to-end task execution alias",
|
|
611
|
-
"hook": "Hook configuration commands",
|
|
612
|
-
"init": "Project initialization and integration bootstrap",
|
|
613
|
-
"integrate": "Coding CLI and MCP integration setup",
|
|
614
|
-
"lsp": "LSP inspection commands",
|
|
615
|
-
"map": "Repository mapping command",
|
|
616
|
-
"mcp_server": "MCP server command",
|
|
617
|
-
"plan": "Planning workflow command",
|
|
618
|
-
"prompt": "Prompt generation for agent handoff",
|
|
619
|
-
"repair": "Repair prompt generation",
|
|
620
|
-
"report": "Task and project reporting commands",
|
|
621
|
-
"reset_demo_state": "Reset demo state and sample data",
|
|
622
|
-
"rollback": "Rollback workflow command",
|
|
623
|
-
"run": "Execute an approved task",
|
|
624
|
-
"setup": "Interactive project setup command",
|
|
625
|
-
"show": "Show current project state",
|
|
626
|
-
"status": "Compact workflow status command",
|
|
627
|
-
"tasks": "Task graph and task listing commands",
|
|
628
|
-
"trace": "Trace inspection commands",
|
|
629
|
-
"verify": "Verification workflow command",
|
|
630
|
-
"version": "Version display command",
|
|
631
|
-
"watch": "Live review and transcript monitoring",
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
_DOC_SUMMARIES = {
|
|
635
|
-
"AGENTS.md": "Workspace guide for coding agents",
|
|
636
|
-
"CLAUDE.md": "Workspace guide for Claude-based agents",
|
|
637
|
-
"README.md": "Project overview and usage entrypoint",
|
|
638
|
-
"architecture.md": "Top-level architecture overview",
|
|
639
|
-
"cli-reference.md": "CLI command reference",
|
|
640
|
-
"quickstart.md": "First-run installation and workflow",
|
|
641
|
-
"workflow.md": "Manual sidecar workflow guide",
|
|
642
|
-
"security.md": "Security and privacy model",
|
|
643
|
-
"project-status.md": "Subsystem maturity snapshot",
|
|
644
|
-
"roadmap.md": "Planned work and roadmap",
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
def _language_for_file(self, path: str) -> str | None:
|
|
648
|
-
suffix = Path(path).suffix.lower()
|
|
649
|
-
return self._LANGUAGE_BY_EXTENSION.get(suffix)
|
|
650
|
-
|
|
651
|
-
def _kind_for_file(self, path: str) -> str:
|
|
652
|
-
normalized = path.replace("\\", "/")
|
|
653
|
-
suffix = Path(normalized).suffix.lower()
|
|
654
|
-
name = Path(normalized).name
|
|
655
|
-
if normalized.startswith("tests/") or name.startswith("test_"):
|
|
656
|
-
return "test"
|
|
657
|
-
if normalized.startswith("docs/") or suffix == ".md":
|
|
658
|
-
return "doc"
|
|
659
|
-
if suffix in {".yaml", ".yml", ".toml", ".json", ".ini"}:
|
|
660
|
-
return "config"
|
|
661
|
-
if suffix in {".sh", ".ps1", ".bat"}:
|
|
662
|
-
return "script"
|
|
663
|
-
if suffix in {".sqlite", ".db"}:
|
|
664
|
-
return "database"
|
|
665
|
-
if suffix in {".py", ".ts", ".tsx", ".js", ".jsx", ".go", ".rs", ".java", ".c", ".cpp"}:
|
|
666
|
-
return "module" if name != "__init__.py" else "package"
|
|
667
|
-
return "file"
|
|
668
|
-
|
|
669
|
-
def _summary_for_file(self, path: str) -> str:
|
|
670
|
-
normalized = path.replace("\\", "/")
|
|
671
|
-
name = Path(normalized).name
|
|
672
|
-
parts = normalized.split("/")
|
|
673
|
-
if normalized == "README.md":
|
|
674
|
-
return self._DOC_SUMMARIES["README.md"]
|
|
675
|
-
if normalized.startswith("docs/"):
|
|
676
|
-
stem = Path(name).stem.replace("-", " ")
|
|
677
|
-
return self._DOC_SUMMARIES.get(name, f"Documentation: {stem}")
|
|
678
|
-
if normalized.startswith("tests/"):
|
|
679
|
-
remainder = normalized.removeprefix("tests/")
|
|
680
|
-
if remainder.startswith("unit/"):
|
|
681
|
-
return f"Unit tests for {Path(remainder).stem.replace('test_', '').replace('_', ' ').strip() or 'the package'}"
|
|
682
|
-
return f"Tests for {Path(remainder).stem.replace('test_', '').replace('_', ' ').strip() or 'the package'}"
|
|
683
|
-
if normalized.startswith("src/devcouncil/cli/commands/"):
|
|
684
|
-
stem = Path(name).stem
|
|
685
|
-
return self._COMMAND_SUMMARIES.get(stem, f"CLI command module: {stem}")
|
|
686
|
-
if normalized == "src/devcouncil/cli/main.py":
|
|
687
|
-
return "Typer root command composition"
|
|
688
|
-
if normalized == "src/devcouncil/app/orchestrator.py":
|
|
689
|
-
return "Orchestration coordinator and run lifecycle"
|
|
690
|
-
if normalized == "src/devcouncil/app/state_machine.py":
|
|
691
|
-
return "Allowed project phase transitions"
|
|
692
|
-
if normalized == "src/devcouncil/artifacts/graph.py":
|
|
693
|
-
return "Artifact graph and coverage queries"
|
|
694
|
-
if normalized == "src/devcouncil/indexing/repo_mapper.py":
|
|
695
|
-
return "Repository mapping and file classification"
|
|
696
|
-
if normalized == "src/devcouncil/storage/repositories.py":
|
|
697
|
-
return "Persistence repositories for state and artifacts"
|
|
698
|
-
if normalized == "src/devcouncil/storage/models.py":
|
|
699
|
-
return "SQLModel database schema"
|
|
700
|
-
if normalized == "src/devcouncil/verification/verifier.py":
|
|
701
|
-
return "Verification gates and evidence checks"
|
|
702
|
-
if normalized == "src/devcouncil/planning/plan_service.py":
|
|
703
|
-
return "Plan generation service"
|
|
704
|
-
if normalized == "src/devcouncil/planning/spec_service.py":
|
|
705
|
-
return "Spec generation service"
|
|
706
|
-
if normalized == "src/devcouncil/planning/critique_service.py":
|
|
707
|
-
return "Plan critique service"
|
|
708
|
-
if normalized == "src/devcouncil/planning/repair_service.py":
|
|
709
|
-
return "Repair workflow service"
|
|
710
|
-
if normalized == "src/devcouncil/planning/arbiter_service.py":
|
|
711
|
-
return "Plan arbitration service"
|
|
712
|
-
if normalized == "src/devcouncil/execution/task_runner.py":
|
|
713
|
-
return "Task execution runner"
|
|
714
|
-
if normalized == "src/devcouncil/execution/prompt_builder.py":
|
|
715
|
-
return "Prompt assembly for executors"
|
|
716
|
-
if normalized == "src/devcouncil/execution/permissions.py":
|
|
717
|
-
return "Execution permission policy"
|
|
718
|
-
if normalized == "src/devcouncil/executors/agent_registry.py":
|
|
719
|
-
return "Built-in and configured CLI agent registry"
|
|
720
|
-
if normalized == "src/devcouncil/llm/router.py":
|
|
721
|
-
return "LLM provider routing"
|
|
722
|
-
if normalized == "src/devcouncil/telemetry/traces.py":
|
|
723
|
-
return "Trace logging and event persistence"
|
|
724
|
-
if normalized == "src/devcouncil/live/reviewer.py":
|
|
725
|
-
return "Live review service"
|
|
726
|
-
if normalized == "src/devcouncil/integrations/gitnexus.py":
|
|
727
|
-
return "GitNexus integration shim"
|
|
728
|
-
if normalized == "src/devcouncil/integrations/graphify.py":
|
|
729
|
-
return "Graphify integration shim"
|
|
730
|
-
if normalized.startswith("src/devcouncil/"):
|
|
731
|
-
area = "/".join(parts[:3]) if len(parts) >= 3 else "src/devcouncil"
|
|
732
|
-
return self._AREA_SUMMARIES.get(area, f"{area} subsystem")
|
|
733
|
-
if normalized.startswith("scripts/"):
|
|
734
|
-
return f"Utility script: {name}"
|
|
735
|
-
if name in self._DOC_SUMMARIES:
|
|
736
|
-
return self._DOC_SUMMARIES[name]
|
|
737
|
-
return Path(name).stem.replace("_", " ")
|
|
738
|
-
|
|
739
|
-
def _area_for_file(self, path: str) -> str:
|
|
740
|
-
normalized = path.replace("\\", "/")
|
|
741
|
-
if normalized.startswith("src/devcouncil/"):
|
|
742
|
-
parts = normalized.split("/")
|
|
743
|
-
if len(parts) >= 5 and parts[2] == "cli" and parts[3] == "commands":
|
|
744
|
-
return "src/devcouncil/cli/commands"
|
|
745
|
-
if len(parts) >= 4:
|
|
746
|
-
return "/".join(parts[:3])
|
|
747
|
-
return "src/devcouncil"
|
|
748
|
-
if normalized.startswith("tests/"):
|
|
749
|
-
return "tests"
|
|
750
|
-
if normalized.startswith("docs/"):
|
|
751
|
-
return "docs"
|
|
752
|
-
if normalized.startswith("scripts/"):
|
|
753
|
-
return "scripts"
|
|
754
|
-
# Foreign repos: derive the area from the directory tree. Gated on _use_generic
|
|
755
|
-
# so DevCouncil's own map keeps its existing "root" bucketing.
|
|
756
|
-
if self._use_generic:
|
|
757
|
-
return self._generic_area_for_file(normalized, self._source_root or "")
|
|
758
|
-
return "root"
|
|
759
|
-
|
|
760
|
-
def _build_subsystem_index(self, files: List[str]) -> List[RepoSubsystem]:
|
|
761
|
-
# The hardcoded index is authoritative for DevCouncil's own tree (preserves
|
|
762
|
-
# its curated summaries/role buckets). For any other repo it matches nothing,
|
|
763
|
-
# so fall back to generic, import-graph-driven inference.
|
|
764
|
-
hardcoded = self._build_hardcoded_subsystems(files)
|
|
765
|
-
if hardcoded:
|
|
766
|
-
return hardcoded
|
|
767
|
-
return self._build_generic_subsystems(files)
|
|
768
|
-
|
|
769
|
-
def _build_hardcoded_subsystems(self, files: List[str]) -> List[RepoSubsystem]:
|
|
770
|
-
file_set = set(files)
|
|
771
|
-
subsystems: List[RepoSubsystem] = []
|
|
772
|
-
for area, (summary, entry_points) in self._SUBSYSTEM_INDEX.items():
|
|
773
|
-
available_entry_points = [path for path in entry_points if path in file_set]
|
|
774
|
-
if not available_entry_points:
|
|
775
|
-
continue
|
|
776
|
-
area_files = sorted(path for path in files if path.startswith(f"{area}/"))
|
|
777
|
-
ranked_files = [path for path in available_entry_points if path in file_set]
|
|
778
|
-
for path in area_files:
|
|
779
|
-
if path in available_entry_points:
|
|
780
|
-
continue
|
|
781
|
-
if len(ranked_files) >= self._SUBSYSTEM_CRITICAL_MAX:
|
|
782
|
-
break
|
|
783
|
-
ranked_files.append(path)
|
|
784
|
-
critical_files = ranked_files[: self._SUBSYSTEM_CRITICAL_MAX]
|
|
785
|
-
neighbors = [n for n in self._SUBSYSTEM_NEIGHBORS.get(area, []) if any(f.startswith(f"{n}/") for f in files)]
|
|
786
|
-
handoff_paths = self._SUBSYSTEM_HANDOFFS.get(area, [])
|
|
787
|
-
role_files = self._build_role_files(area, area_files)
|
|
788
|
-
subsystems.append(
|
|
789
|
-
RepoSubsystem(
|
|
790
|
-
area=area,
|
|
791
|
-
summary=summary,
|
|
792
|
-
entry_points=available_entry_points,
|
|
793
|
-
critical_files=critical_files,
|
|
794
|
-
neighbors=neighbors,
|
|
795
|
-
handoff_paths=handoff_paths,
|
|
796
|
-
role_files=role_files,
|
|
797
|
-
)
|
|
798
|
-
)
|
|
799
|
-
return subsystems
|
|
800
|
-
|
|
801
|
-
def _build_role_files(self, area: str, area_files: List[str]) -> Dict[str, List[str]]:
|
|
802
|
-
role_specs = self._SUBSYSTEM_ROLE_FILES.get(area)
|
|
803
|
-
if not role_specs:
|
|
804
|
-
return {}
|
|
805
|
-
|
|
806
|
-
by_role: Dict[str, List[str]] = {}
|
|
807
|
-
used = set()
|
|
808
|
-
for role, tokens in role_specs:
|
|
809
|
-
matches = [path for path in area_files if any(token in path for token in tokens)]
|
|
810
|
-
if not matches:
|
|
811
|
-
continue
|
|
812
|
-
selected = matches[:4]
|
|
813
|
-
by_role[role] = selected
|
|
814
|
-
used.update(selected)
|
|
815
|
-
|
|
816
|
-
if not by_role:
|
|
817
|
-
return {}
|
|
818
|
-
|
|
819
|
-
leftovers = [path for path in area_files if path not in used][:4]
|
|
820
|
-
if leftovers:
|
|
821
|
-
by_role.setdefault("other", leftovers)
|
|
822
|
-
|
|
823
|
-
return by_role
|
|
824
|
-
|
|
825
|
-
# ------------------------------------------------------------------
|
|
826
|
-
# Generic (non-DevCouncil) subsystem inference
|
|
827
|
-
# ------------------------------------------------------------------
|
|
828
|
-
|
|
829
|
-
def _code_files(self, files: List[str]) -> List[str]:
|
|
830
|
-
return [f for f in files if Path(f).suffix.lower() in _CODE_EXTENSIONS]
|
|
831
|
-
|
|
832
|
-
def _primary_code_files(self, files: List[str]) -> List[str]:
|
|
833
|
-
"""Code files excluding tests/docs/scripts — the ones that define the repo's
|
|
834
|
-
real structure and so determine the source root."""
|
|
835
|
-
primary: List[str] = []
|
|
836
|
-
for f in self._code_files(files):
|
|
837
|
-
top = f.replace("\\", "/").split("/")[0]
|
|
838
|
-
name = Path(f).name
|
|
839
|
-
if top in _AUX_AREA_ROOTS or name.startswith("test_") or name.endswith("_test.go"):
|
|
840
|
-
continue
|
|
841
|
-
primary.append(f)
|
|
842
|
-
return primary
|
|
843
|
-
|
|
844
|
-
def detect_source_root(self, files: List[str]) -> str:
|
|
845
|
-
"""Longest common directory prefix shared by the primary source files
|
|
846
|
-
(e.g. ``src/mypkg``). Empty when the code spans unrelated top-level dirs."""
|
|
847
|
-
dirs = [Path(f).parent.as_posix() for f in self._primary_code_files(files)]
|
|
848
|
-
dirs = [d for d in dirs if d not in ("", ".")]
|
|
849
|
-
if not dirs:
|
|
850
|
-
return ""
|
|
851
|
-
split = [d.split("/") for d in dirs]
|
|
852
|
-
common = split[0]
|
|
853
|
-
for parts in split[1:]:
|
|
854
|
-
limit = min(len(common), len(parts))
|
|
855
|
-
i = 0
|
|
856
|
-
while i < limit and common[i] == parts[i]:
|
|
857
|
-
i += 1
|
|
858
|
-
common = common[:i]
|
|
859
|
-
if not common:
|
|
860
|
-
break
|
|
861
|
-
return "/".join(common)
|
|
862
|
-
|
|
863
|
-
def _generic_area_for_file(self, path: str, source_root: str) -> str:
|
|
864
|
-
normalized = path.replace("\\", "/")
|
|
865
|
-
parts = normalized.split("/")
|
|
866
|
-
if parts[0] in _AUX_AREA_ROOTS:
|
|
867
|
-
return parts[0]
|
|
868
|
-
if source_root and (normalized == source_root or normalized.startswith(f"{source_root}/")):
|
|
869
|
-
rest = normalized[len(source_root):].lstrip("/").split("/")
|
|
870
|
-
if len(rest) >= 2:
|
|
871
|
-
return f"{source_root}/{rest[0]}"
|
|
872
|
-
return source_root or "root"
|
|
873
|
-
if len(parts) >= 2:
|
|
874
|
-
return parts[0]
|
|
875
|
-
return "root"
|
|
876
|
-
|
|
877
|
-
def _module_suffix_index(self, py_files: List[str]) -> Dict[str, str]:
|
|
878
|
-
"""Map every dotted suffix of each module's path to its file, so an import
|
|
879
|
-
statement's module string resolves to a repo file. Ambiguous suffixes (shared
|
|
880
|
-
by two files) are dropped to avoid mislinking. Packages (``__init__.py``) are
|
|
881
|
-
also indexed under their package dotted path."""
|
|
882
|
-
index: Dict[str, str] = {}
|
|
883
|
-
ambiguous: Set[str] = set()
|
|
884
|
-
|
|
885
|
-
def _register(dotted: str, file: str) -> None:
|
|
886
|
-
comps = [c for c in dotted.split(".") if c]
|
|
887
|
-
for i in range(len(comps)):
|
|
888
|
-
suffix = ".".join(comps[i:])
|
|
889
|
-
if not suffix:
|
|
890
|
-
continue
|
|
891
|
-
if suffix in index and index[suffix] != file:
|
|
892
|
-
ambiguous.add(suffix)
|
|
893
|
-
else:
|
|
894
|
-
index[suffix] = file
|
|
895
|
-
|
|
896
|
-
for f in py_files:
|
|
897
|
-
module_path = f[:-3] if f.endswith(".py") else f
|
|
898
|
-
if module_path.endswith("/__init__"):
|
|
899
|
-
# Package import resolves to the __init__ file under the dir's name.
|
|
900
|
-
_register(module_path[: -len("/__init__")].replace("/", "."), f)
|
|
901
|
-
else:
|
|
902
|
-
_register(module_path.replace("/", "."), f)
|
|
903
|
-
for suffix in ambiguous:
|
|
904
|
-
index.pop(suffix, None)
|
|
905
|
-
return index
|
|
906
|
-
|
|
907
|
-
def _resolve_module(self, module: str, index: Dict[str, str]) -> str | None:
|
|
908
|
-
comps = [c for c in module.split(".") if c]
|
|
909
|
-
# An absolute import of a stdlib module is never a repo file — don't let a
|
|
910
|
-
# repo file whose stem happens to equal a stdlib name (e.g. a local json.py)
|
|
911
|
-
# create a false edge for `import json`.
|
|
912
|
-
if comps and comps[0] in sys.stdlib_module_names:
|
|
913
|
-
return None
|
|
914
|
-
while comps:
|
|
915
|
-
candidate = ".".join(comps)
|
|
916
|
-
if candidate in index:
|
|
917
|
-
return index[candidate]
|
|
918
|
-
comps = comps[:-1] # `from pkg.mod import name` -> try pkg.mod, then pkg
|
|
919
|
-
return None
|
|
920
|
-
|
|
921
|
-
def _python_import_edges(self, files: List[str]) -> List[Tuple[str, str]]:
|
|
922
|
-
"""Resolve Python import statements into (importer, imported) file edges."""
|
|
923
|
-
py_files = [f for f in self._code_files(files) if f.endswith(".py")]
|
|
924
|
-
if not py_files:
|
|
925
|
-
return []
|
|
926
|
-
index = self._module_suffix_index(py_files)
|
|
927
|
-
edges: List[Tuple[str, str]] = []
|
|
928
|
-
seen: Set[Tuple[str, str]] = set() # dedupe so in-degree isn't inflated by repeats
|
|
929
|
-
for rel in py_files:
|
|
930
|
-
try:
|
|
931
|
-
source = (self.project_root / rel).read_text(encoding="utf-8", errors="replace")
|
|
932
|
-
tree = ast.parse(source)
|
|
933
|
-
except (OSError, SyntaxError, ValueError):
|
|
934
|
-
continue
|
|
935
|
-
pkg_parts = rel[:-3].replace("/", ".").split(".") # importer's dotted path
|
|
936
|
-
for node in ast.walk(tree):
|
|
937
|
-
modules: List[str] = []
|
|
938
|
-
if isinstance(node, ast.Import):
|
|
939
|
-
modules = [alias.name for alias in node.names]
|
|
940
|
-
elif isinstance(node, ast.ImportFrom):
|
|
941
|
-
if node.level:
|
|
942
|
-
base = pkg_parts[: -node.level] if node.level <= len(pkg_parts) else []
|
|
943
|
-
base_mod = ".".join(base + ([node.module] if node.module else []))
|
|
944
|
-
else:
|
|
945
|
-
base_mod = node.module or ""
|
|
946
|
-
if base_mod:
|
|
947
|
-
modules.append(base_mod)
|
|
948
|
-
# `from pkg import sub` / `from . import sub` may import a SUBMODULE
|
|
949
|
-
# file, not just a symbol — resolve each name as a candidate module so
|
|
950
|
-
# those edges aren't silently dropped.
|
|
951
|
-
for alias in node.names:
|
|
952
|
-
if alias.name and alias.name != "*":
|
|
953
|
-
modules.append(f"{base_mod}.{alias.name}" if base_mod else alias.name)
|
|
954
|
-
for module in modules:
|
|
955
|
-
target = self._resolve_module(module, index)
|
|
956
|
-
if target and target != rel and (rel, target) not in seen:
|
|
957
|
-
seen.add((rel, target))
|
|
958
|
-
edges.append((rel, target))
|
|
959
|
-
return edges
|
|
960
|
-
|
|
961
|
-
# Module specifiers in import/require statements: import ... from "x"; require("x");
|
|
962
|
-
# export ... from "x"; dynamic import("x"). Best-effort; only relative specs resolve.
|
|
963
|
-
_JS_IMPORT_RE = re.compile(
|
|
964
|
-
r"""(?:import|export)\s[^'"]*?from\s*['"](?P<spec>[^'"]+)['"]"""
|
|
965
|
-
r"""|(?:require|import)\s*\(\s*['"](?P<spec2>[^'"]+)['"]\s*\)"""
|
|
966
|
-
)
|
|
967
|
-
_JS_BARE_IMPORT_RE = re.compile(r"""^\s*import\s*['"](?P<spec>[^'"]+)['"]""")
|
|
968
|
-
_JS_RESOLVE_EXTS = (".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs")
|
|
969
|
-
_GO_IMPORT_BLOCK_RE = re.compile(r"import\s*\((?P<body>[^)]*)\)", re.DOTALL)
|
|
970
|
-
_GO_IMPORT_SINGLE_RE = re.compile(r"""^\s*import\s+(?:[A-Za-z_.]\w*\s+)?['"](?P<spec>[^'"]+)['"]""")
|
|
971
|
-
_GO_IMPORT_SPEC_RE = re.compile(r"""['"](?P<spec>[^'"]+)['"]""")
|
|
972
|
-
_GO_MODULE_RE = re.compile(r"^\s*module\s+(?P<mod>\S+)", re.MULTILINE)
|
|
973
|
-
|
|
974
|
-
def _resolve_js_spec(self, importer: str, spec: str, file_set: Set[str]) -> str | None:
|
|
975
|
-
"""Resolve a relative TS/JS import specifier (``./x`` / ``../y``) to a repo file.
|
|
976
|
-
Bare specifiers (node_modules packages) are intentionally not resolved."""
|
|
977
|
-
if not spec.startswith("."):
|
|
978
|
-
return None
|
|
979
|
-
base = Path(importer).parent
|
|
980
|
-
try:
|
|
981
|
-
target = (base / spec).as_posix()
|
|
982
|
-
except Exception:
|
|
983
|
-
return None
|
|
984
|
-
# Normalize away any ".." segments without touching the filesystem.
|
|
985
|
-
parts: List[str] = []
|
|
986
|
-
for comp in target.split("/"):
|
|
987
|
-
if comp in ("", "."):
|
|
988
|
-
continue
|
|
989
|
-
if comp == "..":
|
|
990
|
-
if parts:
|
|
991
|
-
parts.pop()
|
|
992
|
-
continue
|
|
993
|
-
parts.append(comp)
|
|
994
|
-
norm = "/".join(parts)
|
|
995
|
-
if not norm:
|
|
996
|
-
return None
|
|
997
|
-
candidates = [norm]
|
|
998
|
-
candidates += [f"{norm}{ext}" for ext in self._JS_RESOLVE_EXTS]
|
|
999
|
-
candidates += [f"{norm}/index{ext}" for ext in self._JS_RESOLVE_EXTS]
|
|
1000
|
-
for cand in candidates:
|
|
1001
|
-
if cand in file_set:
|
|
1002
|
-
return cand
|
|
1003
|
-
return None
|
|
1004
|
-
|
|
1005
|
-
def _js_import_edges(self, files: List[str], file_set: Set[str]) -> List[Tuple[str, str]]:
|
|
1006
|
-
"""Resolve TS/JS relative import/require/export-from edges to repo files."""
|
|
1007
|
-
js_files = [f for f in files if Path(f).suffix.lower() in {".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"}]
|
|
1008
|
-
edges: List[Tuple[str, str]] = []
|
|
1009
|
-
seen: Set[Tuple[str, str]] = set()
|
|
1010
|
-
for rel in js_files:
|
|
1011
|
-
try:
|
|
1012
|
-
source = (self.project_root / rel).read_text(encoding="utf-8", errors="replace")
|
|
1013
|
-
except OSError:
|
|
1014
|
-
continue
|
|
1015
|
-
specs: List[str] = []
|
|
1016
|
-
for m in self._JS_IMPORT_RE.finditer(source):
|
|
1017
|
-
spec = m.group("spec") or m.group("spec2")
|
|
1018
|
-
if spec:
|
|
1019
|
-
specs.append(spec)
|
|
1020
|
-
for line in source.splitlines():
|
|
1021
|
-
bare = self._JS_BARE_IMPORT_RE.match(line)
|
|
1022
|
-
if bare:
|
|
1023
|
-
specs.append(bare.group("spec"))
|
|
1024
|
-
for spec in specs:
|
|
1025
|
-
target = self._resolve_js_spec(rel, spec, file_set)
|
|
1026
|
-
if target and target != rel and (rel, target) not in seen:
|
|
1027
|
-
seen.add((rel, target))
|
|
1028
|
-
edges.append((rel, target))
|
|
1029
|
-
return edges
|
|
1030
|
-
|
|
1031
|
-
def _go_module_prefix(self, file_set: Set[str]) -> str | None:
|
|
1032
|
-
"""The module path declared in go.mod, used to map import paths back to repo dirs."""
|
|
1033
|
-
for candidate in (p for p in file_set if p == "go.mod" or p.endswith("/go.mod")):
|
|
1034
|
-
try:
|
|
1035
|
-
text = (self.project_root / candidate).read_text(encoding="utf-8", errors="replace")
|
|
1036
|
-
except OSError:
|
|
1037
|
-
continue
|
|
1038
|
-
m = self._GO_MODULE_RE.search(text)
|
|
1039
|
-
if m:
|
|
1040
|
-
return m.group("mod").strip()
|
|
1041
|
-
return None
|
|
1042
|
-
|
|
1043
|
-
def _go_import_edges(self, files: List[str], file_set: Set[str]) -> List[Tuple[str, str]]:
|
|
1044
|
-
"""Resolve Go import paths (under the module prefix) to package directories, then
|
|
1045
|
-
to the .go files in those directories. Edges target every file in the package."""
|
|
1046
|
-
module = self._go_module_prefix(file_set)
|
|
1047
|
-
if not module:
|
|
1048
|
-
return []
|
|
1049
|
-
go_files = [f for f in files if f.endswith(".go")]
|
|
1050
|
-
if not go_files:
|
|
1051
|
-
return []
|
|
1052
|
-
# package dir -> .go files in it (excluding _test.go, which aren't imported).
|
|
1053
|
-
pkg_files: Dict[str, List[str]] = defaultdict(list)
|
|
1054
|
-
for f in go_files:
|
|
1055
|
-
if f.endswith("_test.go"):
|
|
1056
|
-
continue
|
|
1057
|
-
pkg_files[Path(f).parent.as_posix()].append(f)
|
|
1058
|
-
edges: List[Tuple[str, str]] = []
|
|
1059
|
-
seen: Set[Tuple[str, str]] = set()
|
|
1060
|
-
for rel in go_files:
|
|
1061
|
-
try:
|
|
1062
|
-
source = (self.project_root / rel).read_text(encoding="utf-8", errors="replace")
|
|
1063
|
-
except OSError:
|
|
1064
|
-
continue
|
|
1065
|
-
specs: List[str] = []
|
|
1066
|
-
for block in self._GO_IMPORT_BLOCK_RE.finditer(source):
|
|
1067
|
-
for sm in self._GO_IMPORT_SPEC_RE.finditer(block.group("body")):
|
|
1068
|
-
specs.append(sm.group("spec"))
|
|
1069
|
-
for line in source.splitlines():
|
|
1070
|
-
single = self._GO_IMPORT_SINGLE_RE.match(line)
|
|
1071
|
-
if single:
|
|
1072
|
-
specs.append(single.group("spec"))
|
|
1073
|
-
for spec in specs:
|
|
1074
|
-
if spec != module and not spec.startswith(module + "/"):
|
|
1075
|
-
continue # external/stdlib package
|
|
1076
|
-
rel_pkg = spec[len(module):].lstrip("/")
|
|
1077
|
-
target_dir = rel_pkg if rel_pkg else "."
|
|
1078
|
-
for target in pkg_files.get(target_dir, []):
|
|
1079
|
-
if target != rel and (rel, target) not in seen:
|
|
1080
|
-
seen.add((rel, target))
|
|
1081
|
-
edges.append((rel, target))
|
|
1082
|
-
return edges
|
|
1083
|
-
|
|
1084
|
-
def _all_import_edges(self, files: List[str]) -> List[Tuple[str, str]]:
|
|
1085
|
-
"""All cross-file import edges across supported languages, feeding the same
|
|
1086
|
-
dependents reverse index. Best-effort; never raises."""
|
|
1087
|
-
file_set = set(files)
|
|
1088
|
-
edges = list(self._python_import_edges(files))
|
|
1089
|
-
try:
|
|
1090
|
-
edges.extend(self._js_import_edges(files, file_set))
|
|
1091
|
-
except Exception:
|
|
1092
|
-
logger.debug("JS/TS import-edge resolution failed", exc_info=True)
|
|
1093
|
-
try:
|
|
1094
|
-
edges.extend(self._go_import_edges(files, file_set))
|
|
1095
|
-
except Exception:
|
|
1096
|
-
logger.debug("Go import-edge resolution failed", exc_info=True)
|
|
1097
|
-
return edges
|
|
1098
|
-
|
|
1099
|
-
def _rank_area_files(self, area_files: List[str], in_degree: Counter) -> List[str]:
|
|
1100
|
-
def sort_key(path: str) -> Tuple[int, int, str]:
|
|
1101
|
-
name = Path(path).stem.lower()
|
|
1102
|
-
entry_rank = next((i for i, hint in enumerate(_ENTRY_NAME_HINTS) if name == hint), len(_ENTRY_NAME_HINTS))
|
|
1103
|
-
# Most-imported first, then entry-named, then alphabetical for stability.
|
|
1104
|
-
return (-in_degree.get(path, 0), entry_rank, path)
|
|
1105
|
-
|
|
1106
|
-
return sorted(area_files, key=sort_key)
|
|
1107
|
-
|
|
1108
|
-
def _build_generic_subsystems(self, files: List[str]) -> List[RepoSubsystem]:
|
|
1109
|
-
source_root = self._source_root if self._source_root is not None else self.detect_source_root(files)
|
|
1110
|
-
code_files = self._code_files(files)
|
|
1111
|
-
if not code_files:
|
|
1112
|
-
return []
|
|
1113
|
-
|
|
1114
|
-
by_area: Dict[str, List[str]] = defaultdict(list)
|
|
1115
|
-
area_of: Dict[str, str] = {}
|
|
1116
|
-
for f in code_files:
|
|
1117
|
-
area = self._generic_area_for_file(f, source_root)
|
|
1118
|
-
by_area[area].append(f)
|
|
1119
|
-
area_of[f] = area
|
|
1120
|
-
|
|
1121
|
-
edges = self._edges if self._edges is not None else self._python_import_edges(files)
|
|
1122
|
-
in_degree: Counter = Counter(target for _, target in edges)
|
|
1123
|
-
area_neighbors: Dict[str, Set[str]] = defaultdict(set)
|
|
1124
|
-
area_handoffs: Dict[str, List[str]] = defaultdict(list)
|
|
1125
|
-
for importer, imported in edges:
|
|
1126
|
-
a, b = area_of.get(importer), area_of.get(imported)
|
|
1127
|
-
if a and b and a != b:
|
|
1128
|
-
area_neighbors[a].add(b)
|
|
1129
|
-
if len(area_handoffs[a]) < 3:
|
|
1130
|
-
area_handoffs[a].append(f"{importer} -> {imported}")
|
|
1131
|
-
|
|
1132
|
-
subsystems: List[RepoSubsystem] = []
|
|
1133
|
-
for area in sorted(by_area):
|
|
1134
|
-
area_files = by_area[area]
|
|
1135
|
-
# Skip trivial single-file aux areas (e.g. a lone script) to reduce noise,
|
|
1136
|
-
# but keep every real source subsystem.
|
|
1137
|
-
if len(area_files) < 2 and area.split("/")[0] in _AUX_AREA_ROOTS:
|
|
1138
|
-
continue
|
|
1139
|
-
ranked = self._rank_area_files(area_files, in_degree)
|
|
1140
|
-
critical_files = ranked[: self._SUBSYSTEM_CRITICAL_MAX]
|
|
1141
|
-
entry_points = [p for p in critical_files if in_degree.get(p, 0) > 0][:3] or critical_files[:1]
|
|
1142
|
-
stems = ", ".join(Path(p).stem for p in critical_files[:3])
|
|
1143
|
-
summary = f"{Path(area).name or area}: {stems}" if stems else f"{area} ({len(area_files)} files)"
|
|
1144
|
-
subsystems.append(
|
|
1145
|
-
RepoSubsystem(
|
|
1146
|
-
area=area,
|
|
1147
|
-
summary=summary,
|
|
1148
|
-
entry_points=entry_points,
|
|
1149
|
-
critical_files=critical_files,
|
|
1150
|
-
neighbors=sorted(area_neighbors.get(area, set()))[:6],
|
|
1151
|
-
handoff_paths=area_handoffs.get(area, []),
|
|
1152
|
-
role_files={},
|
|
1153
|
-
)
|
|
1154
|
-
)
|
|
1155
|
-
return subsystems
|
|
1156
|
-
|
|
1157
|
-
def generic_important_files(self, files: List[str]) -> List[str]:
|
|
1158
|
-
"""The most-depended-on source files across the repo (highest import in-degree),
|
|
1159
|
-
used to seed 'important surfaces' on repos without a curated index."""
|
|
1160
|
-
edges = self._edges if self._edges is not None else self._python_import_edges(files)
|
|
1161
|
-
if not edges:
|
|
1162
|
-
return []
|
|
1163
|
-
in_degree = Counter(target for _, target in edges)
|
|
1164
|
-
ranked = [path for path, _ in in_degree.most_common()]
|
|
1165
|
-
return ranked[:8]
|
|
1166
|
-
|
|
1167
|
-
def build_dependents(self, edges: List[Tuple[str, str]]) -> Dict[str, List[str]]:
|
|
1168
|
-
"""Reverse the import edges into a file -> dependents map (who imports each file),
|
|
1169
|
-
capped per file. This is the blast radius an agent needs before changing a file."""
|
|
1170
|
-
reverse: Dict[str, Set[str]] = defaultdict(set)
|
|
1171
|
-
for importer, imported in edges:
|
|
1172
|
-
reverse[imported].add(importer)
|
|
1173
|
-
return {
|
|
1174
|
-
path: sorted(importers)[: self._DEPENDENTS_MAX]
|
|
1175
|
-
for path, importers in sorted(reverse.items())
|
|
1176
|
-
if importers
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
def describe_file(self, path: str) -> RepoFileEntry:
|
|
1180
|
-
return RepoFileEntry(
|
|
1181
|
-
path=path,
|
|
1182
|
-
area=self._area_for_file(path),
|
|
1183
|
-
kind=self._kind_for_file(path),
|
|
1184
|
-
language=self._language_for_file(path),
|
|
1185
|
-
summary=self._summary_for_file(path),
|
|
1186
|
-
)
|
|
1187
|
-
|
|
1188
|
-
def _is_runtime_or_generated_file(self, path: str) -> bool:
|
|
1189
|
-
normalized = path.replace("\\", "/")
|
|
1190
|
-
parts = set(normalized.split("/"))
|
|
1191
|
-
name = Path(normalized).name
|
|
1192
|
-
if "__pycache__" in parts or normalized.endswith(".pyc"):
|
|
1193
|
-
return True
|
|
1194
|
-
if parts.intersection({".git", ".devcouncil", ".pytest_cache", ".ruff_cache", ".mypy_cache", ".venv"}):
|
|
1195
|
-
return True
|
|
1196
|
-
if normalized.startswith("dist/") or normalized.startswith("build/"):
|
|
1197
|
-
return True
|
|
1198
|
-
if name.startswith(("tmp", "temp", ".tmp", "debug")) or name.endswith("~"):
|
|
1199
|
-
return True
|
|
1200
|
-
return False
|
|
1201
|
-
|
|
1202
|
-
def _git_head(self) -> str:
|
|
1203
|
-
try:
|
|
1204
|
-
return subprocess.check_output(
|
|
1205
|
-
["git", "rev-parse", "HEAD"], cwd=self.project_root, stderr=subprocess.DEVNULL
|
|
1206
|
-
).decode("utf-8", errors="replace").strip()
|
|
1207
|
-
except Exception:
|
|
1208
|
-
return ""
|
|
1209
|
-
|
|
1210
|
-
def _files_fingerprint(self, files: List[str]) -> str:
|
|
1211
|
-
return hashlib.sha1("\n".join(sorted(files)).encode("utf-8")).hexdigest()
|
|
1212
|
-
|
|
1213
|
-
def map_is_stale(self, repo_map: Dict[str, object]) -> bool:
|
|
1214
|
-
"""True when the stored map no longer matches the repo's current git HEAD or
|
|
1215
|
-
tracked file set — i.e. commits or file add/removes happened since ``dev map``
|
|
1216
|
-
last ran. Returns False for maps written before fingerprinting (no false alarms)."""
|
|
1217
|
-
stored_head = str(repo_map.get("generated_head") or "")
|
|
1218
|
-
stored_hash = str(repo_map.get("indexed_hash") or "")
|
|
1219
|
-
if not stored_head and not stored_hash:
|
|
1220
|
-
return False
|
|
1221
|
-
try:
|
|
1222
|
-
files = self.get_git_files()
|
|
1223
|
-
except Exception:
|
|
1224
|
-
return False
|
|
1225
|
-
return self._git_head() != stored_head or self._files_fingerprint(files) != stored_hash
|
|
1226
|
-
|
|
1227
|
-
def get_git_files(self) -> List[str]:
|
|
1228
|
-
try:
|
|
1229
|
-
output = subprocess.check_output(
|
|
1230
|
-
["git", "ls-files", "--cached", "--others", "--exclude-standard"],
|
|
1231
|
-
cwd=self.project_root,
|
|
1232
|
-
stderr=subprocess.DEVNULL
|
|
1233
|
-
).decode().splitlines()
|
|
1234
|
-
return [path for path in output if not self._is_runtime_or_generated_file(path)]
|
|
1235
|
-
except Exception:
|
|
1236
|
-
# Fallback to os.walk if not a git repo or git missing
|
|
1237
|
-
files = []
|
|
1238
|
-
for root, _, filenames in os.walk(self.project_root):
|
|
1239
|
-
for f in filenames:
|
|
1240
|
-
rel_path = os.path.relpath(os.path.join(root, f), self.project_root)
|
|
1241
|
-
if not rel_path.startswith(".") and not self._is_runtime_or_generated_file(rel_path):
|
|
1242
|
-
files.append(rel_path)
|
|
1243
|
-
return files
|
|
1244
|
-
|
|
1245
|
-
def detect_languages(self, files: List[str]) -> List[str]:
|
|
1246
|
-
exts = {os.path.splitext(f)[1] for f in files}
|
|
1247
|
-
lang_map = {
|
|
1248
|
-
".py": "python",
|
|
1249
|
-
".ts": "typescript",
|
|
1250
|
-
".tsx": "typescript",
|
|
1251
|
-
".js": "javascript",
|
|
1252
|
-
".jsx": "javascript",
|
|
1253
|
-
".go": "go",
|
|
1254
|
-
".rs": "rust",
|
|
1255
|
-
".java": "java",
|
|
1256
|
-
".c": "c",
|
|
1257
|
-
".cpp": "cpp",
|
|
1258
|
-
}
|
|
1259
|
-
return sorted(list({lang_map[ext] for ext in exts if ext in lang_map}))
|
|
1260
|
-
|
|
1261
|
-
def detect_frameworks(self, files: List[str]) -> List[str]:
|
|
1262
|
-
frameworks = []
|
|
1263
|
-
file_set = set(files)
|
|
1264
|
-
if "package.json" in file_set:
|
|
1265
|
-
content = (self.project_root / "package.json").read_text()
|
|
1266
|
-
if "next" in content:
|
|
1267
|
-
frameworks.append("nextjs")
|
|
1268
|
-
if "react" in content:
|
|
1269
|
-
frameworks.append("react")
|
|
1270
|
-
if "vue" in content:
|
|
1271
|
-
frameworks.append("vue")
|
|
1272
|
-
if "express" in content:
|
|
1273
|
-
frameworks.append("express")
|
|
1274
|
-
|
|
1275
|
-
if "requirements.txt" in file_set or "pyproject.toml" in file_set:
|
|
1276
|
-
try:
|
|
1277
|
-
content = ""
|
|
1278
|
-
if "requirements.txt" in file_set:
|
|
1279
|
-
content += (self.project_root / "requirements.txt").read_text()
|
|
1280
|
-
if "pyproject.toml" in file_set:
|
|
1281
|
-
content += (self.project_root / "pyproject.toml").read_text()
|
|
1282
|
-
|
|
1283
|
-
if "fastapi" in content.lower():
|
|
1284
|
-
frameworks.append("fastapi")
|
|
1285
|
-
if "flask" in content.lower():
|
|
1286
|
-
frameworks.append("flask")
|
|
1287
|
-
if "django" in content.lower():
|
|
1288
|
-
frameworks.append("django")
|
|
1289
|
-
except Exception as e:
|
|
1290
|
-
logger.debug("Failed to read Python config files: %s", e)
|
|
1291
|
-
return frameworks
|
|
1292
|
-
|
|
1293
|
-
def detect_package_managers(self, files: List[str]) -> List[str]:
|
|
1294
|
-
managers = []
|
|
1295
|
-
file_set = set(files)
|
|
1296
|
-
if "package-lock.json" in file_set:
|
|
1297
|
-
managers.append("npm")
|
|
1298
|
-
elif "package.json" in file_set:
|
|
1299
|
-
managers.append("npm")
|
|
1300
|
-
if "yarn.lock" in file_set:
|
|
1301
|
-
managers.append("yarn")
|
|
1302
|
-
if "pnpm-lock.yaml" in file_set:
|
|
1303
|
-
managers.append("pnpm")
|
|
1304
|
-
if "requirements.txt" in file_set:
|
|
1305
|
-
managers.append("pip")
|
|
1306
|
-
if "uv.lock" in file_set:
|
|
1307
|
-
managers.append("uv")
|
|
1308
|
-
if "go.sum" in file_set:
|
|
1309
|
-
managers.append("go mod")
|
|
1310
|
-
return managers
|
|
1311
|
-
|
|
1312
|
-
def detect_test_commands(self, files: List[str]) -> List[str]:
|
|
1313
|
-
"""Detect test, lint, and typecheck commands from project config."""
|
|
1314
|
-
commands: List[str] = []
|
|
1315
|
-
file_set = set(files)
|
|
1316
|
-
|
|
1317
|
-
# Node.js projects: read scripts from package.json
|
|
1318
|
-
if "package.json" in file_set:
|
|
1319
|
-
try:
|
|
1320
|
-
pkg = json.loads((self.project_root / "package.json").read_text())
|
|
1321
|
-
scripts = pkg.get("scripts", {})
|
|
1322
|
-
pm = "pnpm" if "pnpm-lock.yaml" in file_set else (
|
|
1323
|
-
"yarn" if "yarn.lock" in file_set else "npm"
|
|
1324
|
-
)
|
|
1325
|
-
for key in ["test", "lint", "typecheck", "check", "type-check"]:
|
|
1326
|
-
if key in scripts:
|
|
1327
|
-
if pm == "npm" and key != "test":
|
|
1328
|
-
commands.append(f"npm run {key}")
|
|
1329
|
-
else:
|
|
1330
|
-
commands.append(f"{pm} {key}")
|
|
1331
|
-
except Exception as e:
|
|
1332
|
-
logger.debug("Failed to parse package.json scripts: %s", e)
|
|
1333
|
-
|
|
1334
|
-
# Python projects
|
|
1335
|
-
if "pyproject.toml" in file_set or "setup.py" in file_set:
|
|
1336
|
-
if any(f.startswith("tests/") or f.startswith("test_") for f in files):
|
|
1337
|
-
commands.append("pytest")
|
|
1338
|
-
commands.append("ruff check .")
|
|
1339
|
-
commands.append("mypy .")
|
|
1340
|
-
|
|
1341
|
-
# Go projects
|
|
1342
|
-
if "go.mod" in file_set:
|
|
1343
|
-
commands.append("go test ./...")
|
|
1344
|
-
commands.append("go vet ./...")
|
|
1345
|
-
|
|
1346
|
-
# Rust projects
|
|
1347
|
-
if "Cargo.toml" in file_set:
|
|
1348
|
-
commands.append("cargo test")
|
|
1349
|
-
commands.append("cargo clippy")
|
|
1350
|
-
|
|
1351
|
-
return commands
|
|
1352
|
-
|
|
1353
|
-
def _ripgrep_search(self, goal: str, files: List[str]) -> List[Dict[str, str]]:
|
|
1354
|
-
"""Use ripgrep for goal-keyword search if available, else fall back to naive matching."""
|
|
1355
|
-
candidates: List[Dict[str, str]] = []
|
|
1356
|
-
try:
|
|
1357
|
-
# Try ripgrep first for better matching
|
|
1358
|
-
result = subprocess.run(
|
|
1359
|
-
["rg", "--files-with-matches", "--ignore-case", "--glob", "!.git", goal],
|
|
1360
|
-
capture_output=True, text=True, cwd=self.project_root, timeout=10,
|
|
1361
|
-
)
|
|
1362
|
-
if result.returncode == 0:
|
|
1363
|
-
for line in sorted(result.stdout.strip().splitlines())[:10]:
|
|
1364
|
-
candidates.append({"path": line.strip(), "reason": f"ripgrep match for '{goal}'"})
|
|
1365
|
-
return candidates
|
|
1366
|
-
except Exception:
|
|
1367
|
-
pass # Fall back to naive matching
|
|
1368
|
-
|
|
1369
|
-
# Naive keyword matching fallback
|
|
1370
|
-
goal_words = set(goal.lower().split())
|
|
1371
|
-
scored_candidates: list[tuple[int, str]] = []
|
|
1372
|
-
for f in files:
|
|
1373
|
-
f_lower = f.lower()
|
|
1374
|
-
score = sum(1 for word in goal_words if word in f_lower)
|
|
1375
|
-
if score > 0:
|
|
1376
|
-
scored_candidates.append((score, f))
|
|
1377
|
-
candidates = [
|
|
1378
|
-
{"path": path, "reason": f"Matches goal keywords (score: {score})"}
|
|
1379
|
-
for score, path in sorted(scored_candidates, key=lambda item: (item[0], item[1]), reverse=True)
|
|
1380
|
-
][:10]
|
|
1381
|
-
return candidates
|
|
1382
|
-
|
|
1383
|
-
def _scan_dependency_risks(self) -> List[Dict[str, str]]:
|
|
1384
|
-
"""Best-effort SCA scan; isolated so map_repo stays simple and never raises."""
|
|
1385
|
-
try:
|
|
1386
|
-
from devcouncil.repo.sca import scan_dependency_risks
|
|
1387
|
-
|
|
1388
|
-
return scan_dependency_risks(self.project_root)
|
|
1389
|
-
except Exception:
|
|
1390
|
-
logger.debug("Dependency-risk scan failed", exc_info=True)
|
|
1391
|
-
return []
|
|
1392
|
-
|
|
1393
|
-
def map_repo(self, goal: str = "", *, scan_dependencies: bool = False) -> RepoMap:
|
|
1394
|
-
"""Build the repo map.
|
|
1395
|
-
|
|
1396
|
-
``scan_dependencies`` is opt-in (default off) so the common ``dev map`` path
|
|
1397
|
-
stays fast and never shells out to a vulnerability auditor. When enabled, a
|
|
1398
|
-
best-effort SCA scan runs locally (only if an auditor is installed) and its
|
|
1399
|
-
findings are attached as ``dependency_risks``.
|
|
1400
|
-
"""
|
|
1401
|
-
files = self.get_git_files()
|
|
1402
|
-
# Decide DevCouncil-vs-generic and the source root BEFORE describing files, so
|
|
1403
|
-
# area bucketing and subsystem inference agree within a single run.
|
|
1404
|
-
self._use_generic = not any(path.startswith("src/devcouncil/") for path in files)
|
|
1405
|
-
self._source_root = self.detect_source_root(files)
|
|
1406
|
-
# Compute the import graph once; reused by subsystem inference, important-file
|
|
1407
|
-
# ranking, and the dependents (blast-radius) index. Spans Python, TS/JS, and Go
|
|
1408
|
-
# so non-Python repos get dependents/neighbors too.
|
|
1409
|
-
self._edges = self._all_import_edges(files)
|
|
1410
|
-
file_entries = [self.describe_file(path) for path in sorted(files)]
|
|
1411
|
-
file_set = set(files)
|
|
1412
|
-
|
|
1413
|
-
important_candidates = [
|
|
1414
|
-
"README.md",
|
|
1415
|
-
"AGENTS.md",
|
|
1416
|
-
"CLAUDE.md",
|
|
1417
|
-
"package.json",
|
|
1418
|
-
"pyproject.toml",
|
|
1419
|
-
"src/devcouncil/cli/main.py",
|
|
1420
|
-
"src/devcouncil/app/orchestrator.py",
|
|
1421
|
-
"src/devcouncil/app/state_machine.py",
|
|
1422
|
-
"src/devcouncil/artifacts/graph.py",
|
|
1423
|
-
"src/devcouncil/indexing/repo_mapper.py",
|
|
1424
|
-
"src/devcouncil/storage/repositories.py",
|
|
1425
|
-
"src/devcouncil/execution/task_runner.py",
|
|
1426
|
-
"src/devcouncil/verification/verifier.py",
|
|
1427
|
-
]
|
|
1428
|
-
important_files = [path for path in important_candidates if path in file_set]
|
|
1429
|
-
important_files.extend(sorted(path for path in files if path.startswith(".github/workflows/")))
|
|
1430
|
-
# On non-DevCouncil repos the curated candidates above mostly miss, so seed
|
|
1431
|
-
# important surfaces from the most-depended-on source files.
|
|
1432
|
-
if self._use_generic:
|
|
1433
|
-
for path in self.generic_important_files(files):
|
|
1434
|
-
if path not in important_files:
|
|
1435
|
-
important_files.append(path)
|
|
1436
|
-
|
|
1437
|
-
candidates: List[Dict[str, str]] = []
|
|
1438
|
-
if goal:
|
|
1439
|
-
candidates = self._ripgrep_search(goal, files)
|
|
1440
|
-
|
|
1441
|
-
return RepoMap(
|
|
1442
|
-
languages=self.detect_languages(files),
|
|
1443
|
-
frameworks=self.detect_frameworks(files),
|
|
1444
|
-
package_managers=self.detect_package_managers(files),
|
|
1445
|
-
test_commands=self.detect_test_commands(files),
|
|
1446
|
-
important_files=important_files,
|
|
1447
|
-
candidate_files=candidates,
|
|
1448
|
-
files=file_entries,
|
|
1449
|
-
subsystems=self._build_subsystem_index(files),
|
|
1450
|
-
dependents=self.build_dependents(self._edges or []),
|
|
1451
|
-
generated_head=self._git_head(),
|
|
1452
|
-
indexed_hash=self._files_fingerprint(files),
|
|
1453
|
-
lsp=LspInspector(self.project_root).summary(files),
|
|
1454
|
-
dependency_risks=self._scan_dependency_risks() if scan_dependencies else [],
|
|
1455
|
-
)
|