devcouncil 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +201 -6
  2. package/package.json +9 -2
  3. package/pyproject.toml +34 -2
  4. package/src/devcouncil/app/config.py +348 -12
  5. package/src/devcouncil/app/orchestrator.py +10 -6
  6. package/src/devcouncil/app/state_machine.py +4 -0
  7. package/src/devcouncil/artifacts/graph.py +32 -5
  8. package/src/devcouncil/assets/__init__.py +1 -0
  9. package/src/devcouncil/assets/devcouncil-logo.svg +60 -0
  10. package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
  11. package/src/devcouncil/cli/commands/agents.py +292 -0
  12. package/src/devcouncil/cli/commands/artifacts.py +6 -3
  13. package/src/devcouncil/cli/commands/check.py +220 -0
  14. package/src/devcouncil/cli/commands/config.py +43 -4
  15. package/src/devcouncil/cli/commands/cost.py +57 -0
  16. package/src/devcouncil/cli/commands/dashboard.py +6 -1
  17. package/src/devcouncil/cli/commands/design.py +186 -0
  18. package/src/devcouncil/cli/commands/doctor.py +379 -22
  19. package/src/devcouncil/cli/commands/evidence.py +48 -0
  20. package/src/devcouncil/cli/commands/go.py +532 -33
  21. package/src/devcouncil/cli/commands/handoff.py +69 -0
  22. package/src/devcouncil/cli/commands/hook.py +296 -15
  23. package/src/devcouncil/cli/commands/init.py +161 -20
  24. package/src/devcouncil/cli/commands/integrate.py +1371 -124
  25. package/src/devcouncil/cli/commands/logs.py +106 -0
  26. package/src/devcouncil/cli/commands/map.py +80 -10
  27. package/src/devcouncil/cli/commands/okf.py +245 -0
  28. package/src/devcouncil/cli/commands/plan.py +256 -55
  29. package/src/devcouncil/cli/commands/prompt.py +18 -7
  30. package/src/devcouncil/cli/commands/repair.py +50 -24
  31. package/src/devcouncil/cli/commands/report.py +8 -0
  32. package/src/devcouncil/cli/commands/reset_demo_state.py +4 -2
  33. package/src/devcouncil/cli/commands/rollback.py +27 -28
  34. package/src/devcouncil/cli/commands/run.py +195 -54
  35. package/src/devcouncil/cli/commands/runs.py +223 -0
  36. package/src/devcouncil/cli/commands/scaffold.py +32 -0
  37. package/src/devcouncil/cli/commands/semantic.py +47 -0
  38. package/src/devcouncil/cli/commands/setup.py +145 -6
  39. package/src/devcouncil/cli/commands/shell.py +73 -0
  40. package/src/devcouncil/cli/commands/skills.py +267 -0
  41. package/src/devcouncil/cli/commands/status.py +30 -15
  42. package/src/devcouncil/cli/commands/trace.py +47 -3
  43. package/src/devcouncil/cli/commands/verify.py +144 -3
  44. package/src/devcouncil/cli/commands/watch.py +32 -12
  45. package/src/devcouncil/cli/commands/watch_fs.py +40 -0
  46. package/src/devcouncil/cli/main.py +91 -7
  47. package/src/devcouncil/domain/evidence.py +29 -2
  48. package/src/devcouncil/domain/gap.py +27 -1
  49. package/src/devcouncil/domain/task.py +31 -2
  50. package/src/devcouncil/execution/checkpoints.py +256 -0
  51. package/src/devcouncil/execution/context_builder.py +1 -1
  52. package/src/devcouncil/execution/fs_watcher.py +205 -0
  53. package/src/devcouncil/execution/handoff.py +102 -0
  54. package/src/devcouncil/execution/hook_policy.py +162 -74
  55. package/src/devcouncil/execution/patch.py +65 -10
  56. package/src/devcouncil/execution/permissions.py +24 -24
  57. package/src/devcouncil/execution/policy_engine.py +350 -0
  58. package/src/devcouncil/execution/prompt_builder.py +751 -23
  59. package/src/devcouncil/execution/shell_session.py +231 -0
  60. package/src/devcouncil/execution/task_runner.py +24 -9
  61. package/src/devcouncil/executors/agent_registry.py +596 -0
  62. package/src/devcouncil/executors/coding_cli.py +791 -39
  63. package/src/devcouncil/executors/mini_swe.py +6 -0
  64. package/src/devcouncil/executors/native/agent.py +135 -19
  65. package/src/devcouncil/executors/openhands.py +6 -0
  66. package/src/devcouncil/gating/checks/clean_git.py +3 -1
  67. package/src/devcouncil/gating/checks/secret_scan_check.py +47 -21
  68. package/src/devcouncil/gating/policy.py +190 -11
  69. package/src/devcouncil/hardware.py +184 -0
  70. package/src/devcouncil/indexing/ast_matcher.py +17 -7
  71. package/src/devcouncil/indexing/lsp.py +45 -4
  72. package/src/devcouncil/indexing/repo_mapper.py +1284 -15
  73. package/src/devcouncil/indexing/semantic_index.py +221 -0
  74. package/src/devcouncil/integrations/actions.py +166 -0
  75. package/src/devcouncil/integrations/check.py +426 -0
  76. package/src/devcouncil/integrations/claude_assets.py +444 -0
  77. package/src/devcouncil/integrations/code_review_graph.py +13 -2
  78. package/src/devcouncil/integrations/github_intent.py +149 -0
  79. package/src/devcouncil/integrations/gitnexus.py +45 -2
  80. package/src/devcouncil/integrations/mcp/server.py +1944 -32
  81. package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +24 -0
  82. package/src/devcouncil/integrations/pr_comments.py +9 -0
  83. package/src/devcouncil/knowledge/__init__.py +23 -0
  84. package/src/devcouncil/knowledge/design.py +374 -0
  85. package/src/devcouncil/knowledge/design_conformance.py +317 -0
  86. package/src/devcouncil/knowledge/fetch.py +223 -0
  87. package/src/devcouncil/knowledge/frontmatter.py +51 -0
  88. package/src/devcouncil/knowledge/okf.py +202 -0
  89. package/src/devcouncil/knowledge/skill_bridge.py +96 -0
  90. package/src/devcouncil/knowledge/sources.py +239 -0
  91. package/src/devcouncil/live/cards.py +181 -25
  92. package/src/devcouncil/live/repair_prompt.py +29 -6
  93. package/src/devcouncil/live/reviewer.py +72 -13
  94. package/src/devcouncil/live/signals.py +2 -2
  95. package/src/devcouncil/live/summary.py +18 -8
  96. package/src/devcouncil/live/transcripts.py +47 -11
  97. package/src/devcouncil/llm/cache.py +20 -8
  98. package/src/devcouncil/llm/model_defaults.yaml +44 -0
  99. package/src/devcouncil/llm/provider.py +617 -49
  100. package/src/devcouncil/llm/router.py +337 -53
  101. package/src/devcouncil/optimization/__init__.py +1 -0
  102. package/src/devcouncil/optimization/gepa_agent.py +318 -0
  103. package/src/devcouncil/optimization/skillopt.py +673 -0
  104. package/src/devcouncil/planning/arbiter_service.py +10 -2
  105. package/src/devcouncil/planning/correction_manifest.py +346 -0
  106. package/src/devcouncil/planning/critique_service.py +16 -4
  107. package/src/devcouncil/planning/plan_service.py +86 -6
  108. package/src/devcouncil/planning/prompt_enhancer_service.py +206 -1
  109. package/src/devcouncil/planning/repair_service.py +8 -2
  110. package/src/devcouncil/planning/spec_service.py +37 -3
  111. package/src/devcouncil/repo/ci_scaffold.py +165 -0
  112. package/src/devcouncil/repo/gitignore.py +123 -0
  113. package/src/devcouncil/repo/sca.py +384 -0
  114. package/src/devcouncil/reporting/json_report.py +22 -1
  115. package/src/devcouncil/reporting/markdown_report.py +29 -1
  116. package/src/devcouncil/reporting/okf_bundle_writer.py +364 -0
  117. package/src/devcouncil/reporting/okf_html.py +323 -0
  118. package/src/devcouncil/reporting/report_builder.py +18 -1
  119. package/src/devcouncil/skills/__init__.py +19 -0
  120. package/src/devcouncil/skills/library/README.md +46 -0
  121. package/src/devcouncil/skills/library/ai-training.md +50 -0
  122. package/src/devcouncil/skills/library/android.md +50 -0
  123. package/src/devcouncil/skills/library/backend.md +52 -0
  124. package/src/devcouncil/skills/library/core-engineering.md +95 -0
  125. package/src/devcouncil/skills/library/data-engineering.md +47 -0
  126. package/src/devcouncil/skills/library/desktop.md +46 -0
  127. package/src/devcouncil/skills/library/devops.md +48 -0
  128. package/src/devcouncil/skills/library/game-dev.md +46 -0
  129. package/src/devcouncil/skills/library/ios.md +48 -0
  130. package/src/devcouncil/skills/library/mobile-cross-platform.md +46 -0
  131. package/src/devcouncil/skills/library/security.md +48 -0
  132. package/src/devcouncil/skills/library/systems.md +48 -0
  133. package/src/devcouncil/skills/library/web.md +47 -0
  134. package/src/devcouncil/skills/library/windows.md +47 -0
  135. package/src/devcouncil/skills/registry.py +408 -0
  136. package/src/devcouncil/storage/db.py +140 -3
  137. package/src/devcouncil/storage/models.py +125 -0
  138. package/src/devcouncil/storage/native.py +559 -0
  139. package/src/devcouncil/storage/repositories.py +157 -78
  140. package/src/devcouncil/telemetry/cost.py +123 -17
  141. package/src/devcouncil/telemetry/logging_setup.py +244 -0
  142. package/src/devcouncil/telemetry/model_pricing.yaml +48 -0
  143. package/src/devcouncil/telemetry/pricing.py +28 -0
  144. package/src/devcouncil/telemetry/stages.py +141 -0
  145. package/src/devcouncil/telemetry/traces.py +62 -7
  146. package/src/devcouncil/telemetry/tracker.py +24 -10
  147. package/src/devcouncil/ui/dashboard.py +393 -28
  148. package/src/devcouncil/utils/redaction.py +9 -3
  149. package/src/devcouncil/utils/subprocess_env.py +69 -0
  150. package/src/devcouncil/verification/acceptance_compiler.py +253 -0
  151. package/src/devcouncil/verification/ad_hoc_check.py +135 -0
  152. package/src/devcouncil/verification/diff_coverage.py +353 -0
  153. package/src/devcouncil/verification/implementation_reviewer.py +11 -2
  154. package/src/devcouncil/verification/next_actions.py +189 -0
  155. package/src/devcouncil/verification/sandbox.py +181 -0
  156. package/src/devcouncil/verification/test_resolver.py +91 -0
  157. package/src/devcouncil/verification/verifier.py +1549 -143
  158. package/uv.lock +205 -64
  159. package/src/devcouncil/indexing/symbol_index.py +0 -0
@@ -1,4 +1,5 @@
1
1
  import copy
2
+ from typing import Any
2
3
  import typer
3
4
  import yaml
4
5
  from rich.console import Console
@@ -6,10 +7,42 @@ from pathlib import Path
6
7
  from devcouncil.storage.db import Database
7
8
  from devcouncil.integrations.gitnexus import GitNexusIntegration
8
9
  from devcouncil.integrations.graphify import GraphifyIntegration
10
+ from devcouncil.llm.provider import build_role_model_config, validate_model_provider
11
+ from devcouncil.repo.gitignore import ensure_gitignore
9
12
 
10
13
  app = typer.Typer()
11
14
  console = Console()
12
15
 
16
+ # Per-stack default verification commands. A fresh project gets ONLY the commands
17
+ # for the stack(s) actually detected in the repo, so the verifier never inherits a
18
+ # cross-stack gate (e.g. `npm test`/`eslint`/`tsc` on a Python repo) that it would run
19
+ # as a blocking fallback and fail for tooling/stack reasons instead of a real defect.
20
+ _STACK_COMMAND_DEFAULTS: dict[str, dict[str, list[str]]] = {
21
+ "python": {"test": ["pytest"], "lint": ["ruff check ."], "typecheck": ["mypy ."]},
22
+ "node": {"test": ["npm test"], "lint": ["eslint ."], "typecheck": ["tsc --noEmit"]},
23
+ }
24
+
25
+
26
+ def _stack_aware_commands(project_root: Path) -> dict[str, list[str]]:
27
+ """Default test/lint/typecheck commands scoped to the repo's detected stack(s).
28
+
29
+ Returns empty lists when no stack is detected — empty is safe (no speculative
30
+ fallback gates) and far better than guessing wrong-stack tools."""
31
+ from devcouncil.repo.ci_scaffold import detect_stacks
32
+
33
+ commands: dict[str, list[str]] = {"test": [], "lint": [], "typecheck": []}
34
+ try:
35
+ stacks = detect_stacks(project_root)
36
+ except Exception:
37
+ return commands
38
+ for stack in sorted(stacks):
39
+ for key, cmds in _STACK_COMMAND_DEFAULTS.get(stack, {}).items():
40
+ for command in cmds:
41
+ if command not in commands[key]:
42
+ commands[key].append(command)
43
+ return commands
44
+
45
+
13
46
  DEFAULT_CONFIG = {
14
47
  "project": {
15
48
  "name": "devcouncil-project",
@@ -18,23 +51,12 @@ DEFAULT_CONFIG = {
18
51
  },
19
52
  "models": {
20
53
  "provider": "openrouter",
21
- "roles": {
22
- "spec_writer": {"model": "anthropic/claude-3.5-sonnet"},
23
- "prompt_enhancer": {"model": "anthropic/claude-3.5-sonnet"},
24
- "planner_a": {"model": "anthropic/claude-3.5-sonnet"},
25
- "planner_b": {"model": "google/gemini-pro-1.5"},
26
- "critic_a": {"model": "openai/gpt-4o"},
27
- "critic_b": {"model": "anthropic/claude-3-opus"},
28
- "arbiter": {"model": "openai/gpt-4o"},
29
- "native_agent": {"model": "anthropic/claude-3.5-sonnet"},
30
- "implementation_reviewer": {"model": "openai/gpt-4o"},
31
- "live_reviewer": {"model": "openai/gpt-4o"},
32
- }
54
+ "roles": build_role_model_config("openrouter"),
33
55
  },
34
56
  "commands": {
35
- "test": ["pytest", "npm test"],
36
- "lint": ["flake8", "eslint"],
37
- "typecheck": ["mypy", "tsc"]
57
+ "test": [],
58
+ "lint": [],
59
+ "typecheck": [],
38
60
  },
39
61
  "gates": {
40
62
  "require_clean_git_before_task": True,
@@ -45,9 +67,12 @@ DEFAULT_CONFIG = {
45
67
  "block_failed_commands": True
46
68
  },
47
69
  "execution": {
48
- "default_executor": "native",
70
+ "default_executor": "manual",
49
71
  "max_repair_attempts": 3,
50
- "checkpoint_before_each_task": True
72
+ "checkpoint_before_each_task": True,
73
+ "stream_cli_output": False,
74
+ "cursor_resume_mode": "off",
75
+ "coding_cli_probe_order": [],
51
76
  },
52
77
  "privacy": {
53
78
  "redact_env_vars": True,
@@ -71,15 +96,90 @@ DEFAULT_CONFIG = {
71
96
  "signals_path": ".devcouncil/live/signals",
72
97
  "default_client": "claude",
73
98
  },
99
+ "cli_agents": {
100
+ "enabled": True,
101
+ "profiles": {
102
+ "default": {
103
+ "description": "Balanced local execution with DevCouncil verification.",
104
+ },
105
+ "yolo": {
106
+ "description": "Faster local execution; DevCouncil still verifies the final diff.",
107
+ "timeout_seconds": 3600,
108
+ "prompt_preamble": "Profile: yolo. Move efficiently within the task scope.",
109
+ },
110
+ "prod": {
111
+ "description": "Restrictive execution for high-risk repositories.",
112
+ "timeout_seconds": 1800,
113
+ "prompt_preamble": "Profile: prod. Keep edits minimal and explicitly within task scope.",
114
+ "require_explicit_confirmation": True,
115
+ },
116
+ },
117
+ "agents": {},
118
+ },
74
119
  }
75
120
  }
76
121
 
77
122
 
123
+ def parse_role_model_overrides(values: list[str] | None) -> dict[str, str]:
124
+ overrides: dict[str, str] = {}
125
+ for value in values or []:
126
+ if "=" not in value:
127
+ raise ValueError(f"Invalid --role-model value '{value}'. Use ROLE=MODEL.")
128
+ role, model = value.split("=", 1)
129
+ role = role.strip()
130
+ model = model.strip()
131
+ if not role or not model:
132
+ raise ValueError(f"Invalid --role-model value '{value}'. Use ROLE=MODEL.")
133
+ overrides[role] = model
134
+ return overrides
135
+
136
+
137
+ def _generate_initial_map(project_root: Path, quiet: bool) -> None:
138
+ """Best-effort repo map + agent guide generation on fresh init.
139
+
140
+ Imported lazily to avoid a circular import with the map command, and wrapped
141
+ so a mapping failure never blocks initialization.
142
+ """
143
+ try:
144
+ from devcouncil.cli.commands.map import generate_map_artifacts
145
+
146
+ generate_map_artifacts(project_root, project_root / ".devcouncil" / "repo_map.json")
147
+ if not quiet:
148
+ console.print("[green]Generated .devcouncil/repo_map.json and agent guides (AGENTS.md, CLAUDE.md).[/green]")
149
+ except Exception as exc: # mapping is best-effort, never fatal
150
+ if not quiet:
151
+ console.print(f"[yellow]Skipped repo map generation: {exc}. Run 'dev map' later.[/yellow]")
152
+
153
+
154
+ def _scaffold_initial_skills(project_root: Path, quiet: bool) -> None:
155
+ """Best-effort scaffolding of applicable engineering skills into .claude/skills/.
156
+
157
+ Always writes the core-engineering skill; adds domain skills (android, ios, web,
158
+ ...) whose file triggers match the repository. Never fatal.
159
+ """
160
+ try:
161
+ from devcouncil.skills.registry import scaffold_skills, select_skills
162
+
163
+ selected = select_skills(project_root=project_root)
164
+ written = scaffold_skills(project_root, selected)
165
+ if written and not quiet:
166
+ names = ", ".join(sorted(skill.name for skill in selected))
167
+ console.print(f"[green]Scaffolded {len(written)} skill(s) into .claude/skills/ ({names}).[/green]")
168
+ except Exception as exc: # skill scaffolding is best-effort, never fatal
169
+ if not quiet:
170
+ console.print(f"[yellow]Skipped skill scaffolding: {exc}. Run 'dev skills scaffold' later.[/yellow]")
171
+
172
+
78
173
  def initialize_project(
79
174
  project_root: Path = Path("."),
80
175
  project_name: str | None = None,
176
+ model_provider: str = "openrouter",
177
+ model: str | None = None,
178
+ role_models: dict[str, str] | None = None,
81
179
  with_gitnexus: bool = False,
82
180
  with_graphify: bool = False,
181
+ with_map: bool = True,
182
+ with_skills: bool = True,
83
183
  quiet: bool = False,
84
184
  ) -> bool:
85
185
  """Initialize DevCouncil project state.
@@ -90,7 +190,10 @@ def initialize_project(
90
190
  dev_dir = project_root / ".devcouncil"
91
191
  created = False
92
192
 
93
- if not dev_dir.exists():
193
+ # Detect "already initialized" by the config file, NOT the .devcouncil/ directory:
194
+ # logging creates .devcouncil/logs/ on every CLI startup, so the directory exists
195
+ # before init runs. Keying on config.yaml ensures a first init still writes config.
196
+ if not (dev_dir / "config.yaml").exists():
94
197
  if not quiet:
95
198
  console.print("Initializing DevCouncil...")
96
199
  dev_dir.mkdir(exist_ok=True)
@@ -100,11 +203,20 @@ def initialize_project(
100
203
  (dev_dir / "logs").mkdir(exist_ok=True)
101
204
 
102
205
  config_path = dev_dir / "config.yaml"
103
- config = copy.deepcopy(DEFAULT_CONFIG)
206
+ config: dict[str, Any] = copy.deepcopy(DEFAULT_CONFIG)
207
+ # Scope default verification commands to the repo's actual stack(s).
208
+ config["commands"] = _stack_aware_commands(project_root)
104
209
  if project_name:
105
210
  config["project"]["name"] = project_name
106
211
  else:
107
212
  config["project"]["name"] = project_root.name
213
+ provider = validate_model_provider(model_provider)
214
+ config["models"]["provider"] = provider
215
+ config["models"]["roles"] = build_role_model_config(
216
+ provider,
217
+ model=model,
218
+ role_models=role_models,
219
+ )
108
220
 
109
221
  with open(config_path, "w") as f:
110
222
  yaml.dump(config, f, default_flow_style=False)
@@ -115,6 +227,11 @@ def initialize_project(
115
227
  console.print(f"[green]Successfully initialized DevCouncil in {dev_dir}[/green]")
116
228
  created = True
117
229
 
230
+ if with_map:
231
+ _generate_initial_map(project_root, quiet)
232
+ if with_skills:
233
+ _scaffold_initial_skills(project_root, quiet)
234
+
118
235
  if with_gitnexus:
119
236
  nexus = GitNexusIntegration(project_root)
120
237
  nexus.initialize()
@@ -123,6 +240,7 @@ def initialize_project(
123
240
  graphify = GraphifyIntegration(project_root)
124
241
  graphify.initialize()
125
242
 
243
+ ensure_gitignore(project_root)
126
244
  return created
127
245
 
128
246
 
@@ -130,8 +248,17 @@ def initialize_project(
130
248
  def init(
131
249
  ctx: typer.Context,
132
250
  project_name: str = typer.Option(None, "--name", "-n", help="Project name"),
251
+ provider: str = typer.Option("openrouter", "--provider", help="Model provider for generated config."),
252
+ model: str | None = typer.Option(None, "--model", "-m", help="Model id to use for every default role."),
253
+ role_model: list[str] | None = typer.Option(
254
+ None,
255
+ "--role-model",
256
+ help="Per-role model override in ROLE=MODEL form. Can be repeated.",
257
+ ),
133
258
  with_gitnexus: bool = typer.Option(False, "--gitnexus", help="Initialize GitNexus structural awareness"),
134
259
  with_graphify: bool = typer.Option(False, "--graphify", help="Initialize Graphify knowledge graph engine"),
260
+ skip_map: bool = typer.Option(False, "--skip-map", help="Skip generating repo_map.json and agent guides on init."),
261
+ skip_skills: bool = typer.Option(False, "--skip-skills", help="Skip scaffolding engineering skills into .claude/skills/ on init."),
135
262
  ):
136
263
  """
137
264
  Initialize DevCouncil in the current directory.
@@ -140,14 +267,28 @@ def init(
140
267
  return
141
268
 
142
269
  dev_dir = Path(".devcouncil")
143
- if dev_dir.exists() and not (with_gitnexus or with_graphify):
270
+ # "Already initialized" means a config.yaml exists not merely the .devcouncil/
271
+ # directory, which the logging setup pre-creates (.devcouncil/logs/) on startup.
272
+ if (dev_dir / "config.yaml").exists() and not (with_gitnexus or with_graphify):
144
273
  console.print("[yellow]DevCouncil is already initialized in this directory.[/yellow]")
145
274
  console.print("Use --gitnexus or --graphify to add upgrade paths.")
146
275
  raise typer.Exit()
147
276
 
277
+ try:
278
+ role_models = parse_role_model_overrides(role_model)
279
+ model_provider = validate_model_provider(provider)
280
+ except ValueError as e:
281
+ console.print(f"[red]{e}[/red]")
282
+ raise typer.Exit(code=2) from e
283
+
148
284
  initialize_project(
149
285
  Path("."),
150
286
  project_name=project_name,
287
+ model_provider=model_provider,
288
+ model=model,
289
+ role_models=role_models,
151
290
  with_gitnexus=with_gitnexus,
152
291
  with_graphify=with_graphify,
292
+ with_map=not skip_map,
293
+ with_skills=not skip_skills,
153
294
  )