devcouncil 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +62 -543
- package/package.json +1 -1
- package/pyproject.toml +29 -26
- package/src/devcouncil/__main__.py +4 -4
- package/src/devcouncil/app/__init__.py +28 -28
- package/src/devcouncil/app/config.py +135 -108
- package/src/devcouncil/app/errors.py +23 -23
- package/src/devcouncil/app/events.py +44 -44
- package/src/devcouncil/app/orchestrator.py +67 -67
- package/src/devcouncil/app/project_status.py +29 -0
- package/src/devcouncil/app/run_context.py +39 -39
- package/src/devcouncil/app/state_machine.py +108 -108
- package/src/devcouncil/artifacts/__init__.py +1 -1
- package/src/devcouncil/artifacts/coverage.py +96 -96
- package/src/devcouncil/artifacts/graph.py +143 -143
- package/src/devcouncil/artifacts/migrations.py +20 -20
- package/src/devcouncil/artifacts/schemas.py +23 -23
- package/src/devcouncil/artifacts/serializer.py +21 -21
- package/src/devcouncil/artifacts/validators.py +27 -27
- package/src/devcouncil/cli/commands/artifacts.py +51 -48
- package/src/devcouncil/cli/commands/ast.py +22 -0
- package/src/devcouncil/cli/commands/baseline.py +35 -32
- package/src/devcouncil/cli/commands/config.py +76 -54
- package/src/devcouncil/cli/commands/dashboard.py +26 -0
- package/src/devcouncil/cli/commands/doctor.py +86 -42
- package/src/devcouncil/cli/commands/go.py +237 -0
- package/src/devcouncil/cli/commands/hook.py +96 -29
- package/src/devcouncil/cli/commands/init.py +67 -56
- package/src/devcouncil/cli/commands/integrate.py +320 -14
- package/src/devcouncil/cli/commands/lsp.py +20 -0
- package/src/devcouncil/cli/commands/map.py +25 -21
- package/src/devcouncil/cli/commands/plan.py +257 -206
- package/src/devcouncil/cli/commands/prompt.py +36 -33
- package/src/devcouncil/cli/commands/repair.py +72 -69
- package/src/devcouncil/cli/commands/report.py +112 -54
- package/src/devcouncil/cli/commands/reset_demo_state.py +31 -28
- package/src/devcouncil/cli/commands/rollback.py +49 -47
- package/src/devcouncil/cli/commands/run.py +252 -207
- package/src/devcouncil/cli/commands/setup.py +159 -18
- package/src/devcouncil/cli/commands/show.py +76 -57
- package/src/devcouncil/cli/commands/status.py +117 -105
- package/src/devcouncil/cli/commands/tasks.py +55 -41
- package/src/devcouncil/cli/commands/trace.py +2 -1
- package/src/devcouncil/cli/commands/verify.py +158 -128
- package/src/devcouncil/cli/commands/version.py +20 -20
- package/src/devcouncil/cli/commands/watch.py +574 -0
- package/src/devcouncil/cli/main.py +42 -24
- package/src/devcouncil/council/prompts/arbiter.md +19 -19
- package/src/devcouncil/council/prompts/critic_a.md +10 -10
- package/src/devcouncil/council/prompts/critic_b.md +10 -10
- package/src/devcouncil/council/prompts/implementation_reviewer.md +16 -16
- package/src/devcouncil/council/prompts/planner_a.md +16 -16
- package/src/devcouncil/council/prompts/planner_b.md +16 -16
- package/src/devcouncil/council/prompts/rebuttal.md +10 -10
- package/src/devcouncil/council/prompts/spec_writer.md +12 -12
- package/src/devcouncil/domain/assumption.py +17 -17
- package/src/devcouncil/domain/critique.py +32 -32
- package/src/devcouncil/domain/evidence.py +27 -27
- package/src/devcouncil/domain/gap.py +26 -26
- package/src/devcouncil/domain/requirement.py +22 -22
- package/src/devcouncil/domain/task.py +26 -26
- package/src/devcouncil/execution/__init__.py +1 -1
- package/src/devcouncil/execution/context_builder.py +54 -54
- package/src/devcouncil/execution/executor.py +15 -15
- package/src/devcouncil/execution/hook_policy.py +24 -3
- package/src/devcouncil/execution/patch.py +28 -28
- package/src/devcouncil/execution/permissions.py +44 -44
- package/src/devcouncil/execution/prompt_builder.py +23 -23
- package/src/devcouncil/execution/task_runner.py +63 -63
- package/src/devcouncil/executors/__init__.py +1 -1
- package/src/devcouncil/executors/coding_cli.py +112 -0
- package/src/devcouncil/executors/mini_swe.py +63 -63
- package/src/devcouncil/executors/native/agent.py +81 -81
- package/src/devcouncil/executors/openhands.py +56 -56
- package/src/devcouncil/gating/__init__.py +1 -1
- package/src/devcouncil/gating/checks/clean_git.py +50 -45
- package/src/devcouncil/gating/checks/planned_files_check.py +32 -32
- package/src/devcouncil/gating/checks/requirement_coverage.py +26 -26
- package/src/devcouncil/gating/checks/secret_scan_check.py +34 -34
- package/src/devcouncil/gating/policy.py +157 -157
- package/src/devcouncil/indexing/__init__.py +1 -1
- package/src/devcouncil/indexing/ast_matcher.py +168 -0
- package/src/devcouncil/indexing/graph_index.py +48 -48
- package/src/devcouncil/indexing/lsp.py +120 -0
- package/src/devcouncil/indexing/repo_mapper.py +208 -204
- package/src/devcouncil/integrations/github.py +35 -35
- package/src/devcouncil/integrations/gitnexus.py +27 -27
- package/src/devcouncil/integrations/graphify.py +34 -34
- package/src/devcouncil/integrations/mcp/server.py +549 -96
- package/src/devcouncil/integrations/pr_comments.py +62 -0
- package/src/devcouncil/live/__init__.py +2 -0
- package/src/devcouncil/live/cards.py +207 -0
- package/src/devcouncil/live/models.py +63 -0
- package/src/devcouncil/live/repair_prompt.py +83 -0
- package/src/devcouncil/live/reviewer.py +70 -0
- package/src/devcouncil/live/signals.py +135 -0
- package/src/devcouncil/live/summary.py +34 -0
- package/src/devcouncil/live/tasks.py +18 -0
- package/src/devcouncil/live/transcripts.py +138 -0
- package/src/devcouncil/llm/__init__.py +1 -1
- package/src/devcouncil/llm/cache.py +38 -38
- package/src/devcouncil/llm/provider.py +146 -125
- package/src/devcouncil/llm/router.py +111 -111
- package/src/devcouncil/planning/__init__.py +1 -1
- package/src/devcouncil/planning/arbiter_service.py +57 -57
- package/src/devcouncil/planning/critique_service.py +66 -66
- package/src/devcouncil/planning/plan_service.py +46 -46
- package/src/devcouncil/planning/prompt_enhancer_service.py +86 -0
- package/src/devcouncil/planning/repair_service.py +39 -39
- package/src/devcouncil/planning/spec_service.py +44 -44
- package/src/devcouncil/reporting/github_check.py +32 -32
- package/src/devcouncil/reporting/json_report.py +20 -17
- package/src/devcouncil/reporting/markdown_report.py +68 -46
- package/src/devcouncil/reporting/report_builder.py +14 -14
- package/src/devcouncil/storage/db.py +66 -66
- package/src/devcouncil/storage/models.py +83 -83
- package/src/devcouncil/storage/repositories.py +299 -222
- package/src/devcouncil/telemetry/cost.py +34 -34
- package/src/devcouncil/telemetry/tracker.py +49 -49
- package/src/devcouncil/ui/__init__.py +1 -0
- package/src/devcouncil/ui/dashboard.py +122 -0
- package/src/devcouncil/utils/__init__.py +1 -1
- package/src/devcouncil/utils/redaction.py +141 -141
- package/src/devcouncil/verification/__init__.py +1 -1
- package/src/devcouncil/verification/implementation_reviewer.py +55 -55
- package/src/devcouncil/verification/verifier.py +319 -302
- package/uv.lock +1 -1
package/pyproject.toml
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "devcouncil"
|
|
3
|
-
version = "0.1.
|
|
4
|
-
description = "Gated orchestrator for AI-assisted software development"
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.12"
|
|
7
|
-
dependencies = [
|
|
8
|
-
"typer>=0.12.3",
|
|
9
|
-
"rich>=13.7.1",
|
|
10
|
-
"pydantic>=2.7.1",
|
|
11
|
-
"pyyaml>=6.0.1",
|
|
1
|
+
[project]
|
|
2
|
+
name = "devcouncil"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Gated orchestrator for AI-assisted software development"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"typer>=0.12.3",
|
|
9
|
+
"rich>=13.7.1",
|
|
10
|
+
"pydantic>=2.7.1",
|
|
11
|
+
"pyyaml>=6.0.1",
|
|
12
12
|
"sqlmodel>=0.0.19",
|
|
13
13
|
"httpx>=0.27.0",
|
|
14
14
|
"gitpython>=3.1.43",
|
|
15
15
|
"mcp>=1.27.0",
|
|
16
16
|
]
|
|
17
|
-
|
|
18
|
-
[project.scripts]
|
|
19
|
-
dev = "devcouncil.cli.main:app"
|
|
20
|
-
devcouncil = "devcouncil.cli.main:app"
|
|
21
|
-
|
|
22
|
-
[dependency-groups]
|
|
23
|
-
dev = [
|
|
24
|
-
"pytest>=8.2.0",
|
|
25
|
-
"ruff>=0.4.4",
|
|
26
|
-
"mypy>=1.10.0",
|
|
27
|
-
]
|
|
28
|
-
|
|
29
|
-
[
|
|
30
|
-
|
|
31
|
-
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
dev = "devcouncil.cli.main:app"
|
|
20
|
+
devcouncil = "devcouncil.cli.main:app"
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"pytest>=8.2.0",
|
|
25
|
+
"ruff>=0.4.4",
|
|
26
|
+
"mypy>=1.10.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
testpaths = ["tests"]
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["hatchling"]
|
|
34
|
+
build-backend = "hatchling.build"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from devcouncil.cli.main import app
|
|
2
|
-
|
|
3
|
-
if __name__ == "__main__":
|
|
4
|
-
app()
|
|
1
|
+
from devcouncil.cli.main import app
|
|
2
|
+
|
|
3
|
+
if __name__ == "__main__":
|
|
4
|
+
app()
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
from devcouncil.app.state_machine import StateMachine, ProjectPhase
|
|
2
|
-
from devcouncil.app.run_context import RunContext
|
|
3
|
-
from devcouncil.app.orchestrator import Orchestrator
|
|
4
|
-
from devcouncil.app.events import EventBus, bus, EventTypes
|
|
5
|
-
from devcouncil.app.errors import (
|
|
6
|
-
DevCouncilError,
|
|
7
|
-
GatingError,
|
|
8
|
-
ConfigurationError,
|
|
9
|
-
OrchestrationError,
|
|
10
|
-
ExecutionError,
|
|
11
|
-
VerificationError,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
__all__ = [
|
|
15
|
-
"StateMachine",
|
|
16
|
-
"ProjectPhase",
|
|
17
|
-
"RunContext",
|
|
18
|
-
"Orchestrator",
|
|
19
|
-
"EventBus",
|
|
20
|
-
"bus",
|
|
21
|
-
"EventTypes",
|
|
22
|
-
"DevCouncilError",
|
|
23
|
-
"GatingError",
|
|
24
|
-
"ConfigurationError",
|
|
25
|
-
"OrchestrationError",
|
|
26
|
-
"ExecutionError",
|
|
27
|
-
"VerificationError",
|
|
28
|
-
]
|
|
1
|
+
from devcouncil.app.state_machine import StateMachine, ProjectPhase
|
|
2
|
+
from devcouncil.app.run_context import RunContext
|
|
3
|
+
from devcouncil.app.orchestrator import Orchestrator
|
|
4
|
+
from devcouncil.app.events import EventBus, bus, EventTypes
|
|
5
|
+
from devcouncil.app.errors import (
|
|
6
|
+
DevCouncilError,
|
|
7
|
+
GatingError,
|
|
8
|
+
ConfigurationError,
|
|
9
|
+
OrchestrationError,
|
|
10
|
+
ExecutionError,
|
|
11
|
+
VerificationError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"StateMachine",
|
|
16
|
+
"ProjectPhase",
|
|
17
|
+
"RunContext",
|
|
18
|
+
"Orchestrator",
|
|
19
|
+
"EventBus",
|
|
20
|
+
"bus",
|
|
21
|
+
"EventTypes",
|
|
22
|
+
"DevCouncilError",
|
|
23
|
+
"GatingError",
|
|
24
|
+
"ConfigurationError",
|
|
25
|
+
"OrchestrationError",
|
|
26
|
+
"ExecutionError",
|
|
27
|
+
"VerificationError",
|
|
28
|
+
]
|
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
"""Typed configuration loader with Pydantic validation.
|
|
2
|
-
|
|
3
|
-
Replaces scattered yaml.safe_load() calls with a single validated config service.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
import os
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Dict, List
|
|
11
|
-
|
|
1
|
+
"""Typed configuration loader with Pydantic validation.
|
|
2
|
+
|
|
3
|
+
Replaces scattered yaml.safe_load() calls with a single validated config service.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, List
|
|
11
|
+
|
|
12
12
|
from pydantic import BaseModel, Field
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ModelRoleConfig(BaseModel):
|
|
16
|
-
model: str
|
|
17
|
-
temperature: float = 0.0
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class ModelsConfig(BaseModel):
|
|
21
|
-
provider: str = "openrouter"
|
|
22
|
-
roles: Dict[str, ModelRoleConfig] = Field(default_factory=dict)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class ProjectConfig(BaseModel):
|
|
26
|
-
name: str = "devcouncil-project"
|
|
27
|
-
root: str = "."
|
|
28
|
-
default_branch: str = "main"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class CommandsConfig(BaseModel):
|
|
32
|
-
test: List[str] = Field(default_factory=list)
|
|
33
|
-
lint: List[str] = Field(default_factory=list)
|
|
34
|
-
typecheck: List[str] = Field(default_factory=list)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class GatesConfig(BaseModel):
|
|
38
|
-
require_clean_git_before_task: bool = True
|
|
39
|
-
block_orphan_diffs: bool = True
|
|
40
|
-
block_missing_tests_for_high_requirements: bool = True
|
|
41
|
-
block_dependency_changes_without_approval: bool = True
|
|
42
|
-
block_schema_change_without_migration: bool = True
|
|
43
|
-
block_failed_commands: bool = True
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class ExecutionConfig(BaseModel):
|
|
47
|
-
default_executor: str = "manual"
|
|
48
|
-
max_repair_attempts: int = 3
|
|
49
|
-
checkpoint_before_each_task: bool = True
|
|
50
|
-
command_timeout: int = 300
|
|
51
|
-
|
|
52
|
-
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ModelRoleConfig(BaseModel):
|
|
16
|
+
model: str
|
|
17
|
+
temperature: float = 0.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ModelsConfig(BaseModel):
|
|
21
|
+
provider: str = "openrouter"
|
|
22
|
+
roles: Dict[str, ModelRoleConfig] = Field(default_factory=dict)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ProjectConfig(BaseModel):
|
|
26
|
+
name: str = "devcouncil-project"
|
|
27
|
+
root: str = "."
|
|
28
|
+
default_branch: str = "main"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CommandsConfig(BaseModel):
|
|
32
|
+
test: List[str] = Field(default_factory=list)
|
|
33
|
+
lint: List[str] = Field(default_factory=list)
|
|
34
|
+
typecheck: List[str] = Field(default_factory=list)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GatesConfig(BaseModel):
|
|
38
|
+
require_clean_git_before_task: bool = True
|
|
39
|
+
block_orphan_diffs: bool = True
|
|
40
|
+
block_missing_tests_for_high_requirements: bool = True
|
|
41
|
+
block_dependency_changes_without_approval: bool = True
|
|
42
|
+
block_schema_change_without_migration: bool = True
|
|
43
|
+
block_failed_commands: bool = True
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ExecutionConfig(BaseModel):
|
|
47
|
+
default_executor: str = "manual"
|
|
48
|
+
max_repair_attempts: int = 3
|
|
49
|
+
checkpoint_before_each_task: bool = True
|
|
50
|
+
command_timeout: int = 300
|
|
51
|
+
|
|
52
|
+
|
|
53
53
|
class PrivacyConfig(BaseModel):
|
|
54
|
-
redact_env_vars: bool = True
|
|
55
|
-
redact_secrets_in_logs: bool = True
|
|
54
|
+
redact_env_vars: bool = True
|
|
55
|
+
redact_secrets_in_logs: bool = True
|
|
56
56
|
store_prompts_locally: bool = True
|
|
57
57
|
|
|
58
58
|
|
|
@@ -68,64 +68,91 @@ class CodeReviewGraphIntegrationConfig(BaseModel):
|
|
|
68
68
|
optional: bool = True
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
class LiveReviewIntegrationConfig(BaseModel):
|
|
72
|
+
enabled: bool = True
|
|
73
|
+
cards_path: str = ".devcouncil/live/cards"
|
|
74
|
+
signals_path: str = ".devcouncil/live/signals"
|
|
75
|
+
default_client: str = "claude"
|
|
76
|
+
|
|
77
|
+
|
|
71
78
|
class IntegrationsConfig(BaseModel):
|
|
72
79
|
agent_flow: AgentFlowIntegrationConfig = Field(default_factory=AgentFlowIntegrationConfig)
|
|
73
80
|
code_review_graph: CodeReviewGraphIntegrationConfig = Field(default_factory=CodeReviewGraphIntegrationConfig)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
live_review: LiveReviewIntegrationConfig = Field(default_factory=LiveReviewIntegrationConfig)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ProviderConfig(BaseModel):
|
|
85
|
+
sort: str = "price"
|
|
86
|
+
allow_fallbacks: bool = True
|
|
87
|
+
require_parameters: bool = True
|
|
88
|
+
data_collection: str = "deny"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class DevCouncilConfig(BaseModel):
|
|
92
|
+
"""Top-level validated configuration."""
|
|
93
|
+
|
|
94
|
+
project: ProjectConfig = Field(default_factory=ProjectConfig)
|
|
95
|
+
models: ModelsConfig = Field(default_factory=ModelsConfig)
|
|
96
|
+
provider: ProviderConfig = Field(default_factory=ProviderConfig)
|
|
97
|
+
commands: CommandsConfig = Field(default_factory=CommandsConfig)
|
|
98
|
+
gates: GatesConfig = Field(default_factory=GatesConfig)
|
|
91
99
|
execution: ExecutionConfig = Field(default_factory=ExecutionConfig)
|
|
92
100
|
privacy: PrivacyConfig = Field(default_factory=PrivacyConfig)
|
|
93
101
|
integrations: IntegrationsConfig = Field(default_factory=IntegrationsConfig)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def load_config(project_root: Path = Path(".")) -> DevCouncilConfig:
|
|
97
|
-
"""Load and validate .devcouncil/config.yaml.
|
|
98
|
-
|
|
99
|
-
Returns DevCouncilConfig with defaults for any missing fields.
|
|
100
|
-
Raises FileNotFoundError if config doesn't exist.
|
|
101
|
-
"""
|
|
102
|
-
import yaml
|
|
103
|
-
|
|
104
|
-
config_path = project_root / ".devcouncil" / "config.yaml"
|
|
105
|
-
if not config_path.exists():
|
|
106
|
-
raise FileNotFoundError(f"Config not found at {config_path}. Run 'dev init' first.")
|
|
107
|
-
|
|
108
|
-
with open(config_path) as f:
|
|
109
|
-
raw = yaml.safe_load(f) or {}
|
|
110
|
-
|
|
111
|
-
return DevCouncilConfig.model_validate(raw)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
131
|
-
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def load_config(project_root: Path = Path(".")) -> DevCouncilConfig:
|
|
105
|
+
"""Load and validate .devcouncil/config.yaml.
|
|
106
|
+
|
|
107
|
+
Returns DevCouncilConfig with defaults for any missing fields.
|
|
108
|
+
Raises FileNotFoundError if config doesn't exist.
|
|
109
|
+
"""
|
|
110
|
+
import yaml
|
|
111
|
+
|
|
112
|
+
config_path = project_root / ".devcouncil" / "config.yaml"
|
|
113
|
+
if not config_path.exists():
|
|
114
|
+
raise FileNotFoundError(f"Config not found at {config_path}. Run 'dev init' first.")
|
|
115
|
+
|
|
116
|
+
with open(config_path) as f:
|
|
117
|
+
raw = yaml.safe_load(f) or {}
|
|
118
|
+
|
|
119
|
+
return DevCouncilConfig.model_validate(raw)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def provider_api_key_env_var(provider: str = "openrouter") -> str:
|
|
123
|
+
env_map = {
|
|
124
|
+
"openrouter": "OPENROUTER_API_KEY",
|
|
125
|
+
"openai": "OPENAI_API_KEY",
|
|
126
|
+
"anthropic": "ANTHROPIC_API_KEY",
|
|
127
|
+
}
|
|
128
|
+
return env_map.get(provider, f"{provider.upper()}_API_KEY")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def load_local_secrets(project_root: Path = Path(".")) -> Dict[str, str]:
|
|
132
|
+
secrets_path = project_root / ".devcouncil" / "secrets.env"
|
|
133
|
+
if not secrets_path.exists():
|
|
134
|
+
return {}
|
|
135
|
+
|
|
136
|
+
secrets: Dict[str, str] = {}
|
|
137
|
+
for line in secrets_path.read_text(encoding="utf-8").splitlines():
|
|
138
|
+
stripped = line.strip()
|
|
139
|
+
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
140
|
+
continue
|
|
141
|
+
key, value = stripped.split("=", 1)
|
|
142
|
+
secrets[key.strip()] = value.strip().strip('"').strip("'")
|
|
143
|
+
return secrets
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_api_key(provider: str = "openrouter", project_root: Path = Path(".")) -> str:
|
|
147
|
+
"""Retrieve the API key for the configured provider from environment.
|
|
148
|
+
|
|
149
|
+
Raises ValueError if not found.
|
|
150
|
+
"""
|
|
151
|
+
env_var = provider_api_key_env_var(provider)
|
|
152
|
+
key = os.environ.get(env_var) or load_local_secrets(project_root).get(env_var)
|
|
153
|
+
if not key:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
f"API key not found. Set {env_var} in your environment or run 'dev setup'. "
|
|
156
|
+
f"Provider: {provider}"
|
|
157
|
+
)
|
|
158
|
+
return key
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
class DevCouncilError(Exception):
|
|
2
|
-
"""Base exception for all DevCouncil errors."""
|
|
3
|
-
pass
|
|
4
|
-
|
|
5
|
-
class GatingError(DevCouncilError):
|
|
6
|
-
"""Raised when a task or plan fails a mandatory gate."""
|
|
7
|
-
pass
|
|
8
|
-
|
|
9
|
-
class ConfigurationError(DevCouncilError):
|
|
10
|
-
"""Raised when the project configuration is invalid or missing."""
|
|
11
|
-
pass
|
|
12
|
-
|
|
13
|
-
class OrchestrationError(DevCouncilError):
|
|
14
|
-
"""Raised when the orchestrator encounters an unexpected state."""
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
class ExecutionError(DevCouncilError):
|
|
18
|
-
"""Raised when an external or internal executor fails to run a task."""
|
|
19
|
-
pass
|
|
20
|
-
|
|
21
|
-
class VerificationError(DevCouncilError):
|
|
22
|
-
"""Raised when the verifier fails to run its checks properly."""
|
|
23
|
-
pass
|
|
1
|
+
class DevCouncilError(Exception):
|
|
2
|
+
"""Base exception for all DevCouncil errors."""
|
|
3
|
+
pass
|
|
4
|
+
|
|
5
|
+
class GatingError(DevCouncilError):
|
|
6
|
+
"""Raised when a task or plan fails a mandatory gate."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
class ConfigurationError(DevCouncilError):
|
|
10
|
+
"""Raised when the project configuration is invalid or missing."""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class OrchestrationError(DevCouncilError):
|
|
14
|
+
"""Raised when the orchestrator encounters an unexpected state."""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
class ExecutionError(DevCouncilError):
|
|
18
|
+
"""Raised when an external or internal executor fails to run a task."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
class VerificationError(DevCouncilError):
|
|
22
|
+
"""Raised when the verifier fails to run its checks properly."""
|
|
23
|
+
pass
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
from typing import Any, Callable, Dict, List
|
|
2
|
-
import inspect
|
|
3
|
-
import logging
|
|
4
|
-
|
|
5
|
-
logger = logging.getLogger(__name__)
|
|
6
|
-
|
|
7
|
-
class EventBus:
|
|
8
|
-
"""Simple asynchronous event bus for DevCouncil orchestration."""
|
|
9
|
-
def __init__(self):
|
|
10
|
-
self._listeners: Dict[str, List[Callable]] = {}
|
|
11
|
-
|
|
12
|
-
def subscribe(self, event_type: str, callback: Callable):
|
|
13
|
-
if event_type not in self._listeners:
|
|
14
|
-
self._listeners[event_type] = []
|
|
15
|
-
self._listeners[event_type].append(callback)
|
|
16
|
-
|
|
17
|
-
async def emit(self, event_type: str, payload: Any = None):
|
|
18
|
-
"""Emit an event asynchronously to all registered listeners.
|
|
19
|
-
|
|
20
|
-
Supports both sync and async callbacks.
|
|
21
|
-
"""
|
|
22
|
-
logger.debug(f"Event emitted: {event_type}")
|
|
23
|
-
if event_type in self._listeners:
|
|
24
|
-
for callback in self._listeners[event_type]:
|
|
25
|
-
try:
|
|
26
|
-
if inspect.iscoroutinefunction(callback):
|
|
27
|
-
await callback(payload)
|
|
28
|
-
else:
|
|
29
|
-
callback(payload)
|
|
30
|
-
except Exception as e:
|
|
31
|
-
logger.error(f"Error in event listener for {event_type}: {e}")
|
|
32
|
-
|
|
33
|
-
# Global event bus instance
|
|
34
|
-
bus = EventBus()
|
|
35
|
-
|
|
36
|
-
# Standard Event Types
|
|
37
|
-
class EventTypes:
|
|
38
|
-
PLANNING_STARTED = "planning_started"
|
|
39
|
-
PLANNING_COMPLETED = "planning_completed"
|
|
40
|
-
TASK_EXECUTING = "task_executing"
|
|
41
|
-
TASK_VERIFIED = "task_verified"
|
|
42
|
-
TASK_BLOCKED = "task_blocked"
|
|
43
|
-
GATE_FAILED = "gate_failed"
|
|
44
|
-
MODEL_CALLED = "model_called"
|
|
1
|
+
from typing import Any, Callable, Dict, List
|
|
2
|
+
import inspect
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
class EventBus:
|
|
8
|
+
"""Simple asynchronous event bus for DevCouncil orchestration."""
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._listeners: Dict[str, List[Callable]] = {}
|
|
11
|
+
|
|
12
|
+
def subscribe(self, event_type: str, callback: Callable):
|
|
13
|
+
if event_type not in self._listeners:
|
|
14
|
+
self._listeners[event_type] = []
|
|
15
|
+
self._listeners[event_type].append(callback)
|
|
16
|
+
|
|
17
|
+
async def emit(self, event_type: str, payload: Any = None):
|
|
18
|
+
"""Emit an event asynchronously to all registered listeners.
|
|
19
|
+
|
|
20
|
+
Supports both sync and async callbacks.
|
|
21
|
+
"""
|
|
22
|
+
logger.debug(f"Event emitted: {event_type}")
|
|
23
|
+
if event_type in self._listeners:
|
|
24
|
+
for callback in self._listeners[event_type]:
|
|
25
|
+
try:
|
|
26
|
+
if inspect.iscoroutinefunction(callback):
|
|
27
|
+
await callback(payload)
|
|
28
|
+
else:
|
|
29
|
+
callback(payload)
|
|
30
|
+
except Exception as e:
|
|
31
|
+
logger.error(f"Error in event listener for {event_type}: {e}")
|
|
32
|
+
|
|
33
|
+
# Global event bus instance
|
|
34
|
+
bus = EventBus()
|
|
35
|
+
|
|
36
|
+
# Standard Event Types
|
|
37
|
+
class EventTypes:
|
|
38
|
+
PLANNING_STARTED = "planning_started"
|
|
39
|
+
PLANNING_COMPLETED = "planning_completed"
|
|
40
|
+
TASK_EXECUTING = "task_executing"
|
|
41
|
+
TASK_VERIFIED = "task_verified"
|
|
42
|
+
TASK_BLOCKED = "task_blocked"
|
|
43
|
+
GATE_FAILED = "gate_failed"
|
|
44
|
+
MODEL_CALLED = "model_called"
|