devcouncil 0.1.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.
- package/LICENSE +201 -0
- package/README.md +643 -0
- package/bin/devcouncil.js +62 -0
- package/package.json +47 -0
- package/pyproject.toml +31 -0
- package/src/devcouncil/__init__.py +0 -0
- package/src/devcouncil/__main__.py +4 -0
- package/src/devcouncil/app/__init__.py +28 -0
- package/src/devcouncil/app/config.py +131 -0
- package/src/devcouncil/app/errors.py +23 -0
- package/src/devcouncil/app/events.py +44 -0
- package/src/devcouncil/app/orchestrator.py +92 -0
- package/src/devcouncil/app/run_context.py +39 -0
- package/src/devcouncil/app/state_machine.py +108 -0
- package/src/devcouncil/artifacts/__init__.py +1 -0
- package/src/devcouncil/artifacts/coverage.py +96 -0
- package/src/devcouncil/artifacts/graph.py +143 -0
- package/src/devcouncil/artifacts/migrations.py +20 -0
- package/src/devcouncil/artifacts/schemas.py +23 -0
- package/src/devcouncil/artifacts/serializer.py +21 -0
- package/src/devcouncil/artifacts/validators.py +27 -0
- package/src/devcouncil/cli/__init__.py +0 -0
- package/src/devcouncil/cli/commands/__init__.py +0 -0
- package/src/devcouncil/cli/commands/artifacts.py +48 -0
- package/src/devcouncil/cli/commands/baseline.py +32 -0
- package/src/devcouncil/cli/commands/config.py +54 -0
- package/src/devcouncil/cli/commands/doctor.py +96 -0
- package/src/devcouncil/cli/commands/hook.py +61 -0
- package/src/devcouncil/cli/commands/init.py +142 -0
- package/src/devcouncil/cli/commands/integrate.py +420 -0
- package/src/devcouncil/cli/commands/map.py +38 -0
- package/src/devcouncil/cli/commands/mcp_server.py +18 -0
- package/src/devcouncil/cli/commands/plan.py +276 -0
- package/src/devcouncil/cli/commands/prompt.py +47 -0
- package/src/devcouncil/cli/commands/repair.py +69 -0
- package/src/devcouncil/cli/commands/report.py +71 -0
- package/src/devcouncil/cli/commands/reset_demo_state.py +28 -0
- package/src/devcouncil/cli/commands/rollback.py +58 -0
- package/src/devcouncil/cli/commands/run.py +224 -0
- package/src/devcouncil/cli/commands/setup.py +82 -0
- package/src/devcouncil/cli/commands/show.py +57 -0
- package/src/devcouncil/cli/commands/status.py +105 -0
- package/src/devcouncil/cli/commands/tasks.py +41 -0
- package/src/devcouncil/cli/commands/trace.py +43 -0
- package/src/devcouncil/cli/commands/verify.py +163 -0
- package/src/devcouncil/cli/commands/version.py +20 -0
- package/src/devcouncil/cli/main.py +70 -0
- package/src/devcouncil/council/__init__.py +0 -0
- package/src/devcouncil/council/prompts/__init__.py +0 -0
- package/src/devcouncil/council/prompts/arbiter.md +19 -0
- package/src/devcouncil/council/prompts/critic_a.md +10 -0
- package/src/devcouncil/council/prompts/critic_b.md +10 -0
- package/src/devcouncil/council/prompts/implementation_reviewer.md +16 -0
- package/src/devcouncil/council/prompts/planner_a.md +16 -0
- package/src/devcouncil/council/prompts/planner_b.md +16 -0
- package/src/devcouncil/council/prompts/rebuttal.md +10 -0
- package/src/devcouncil/council/prompts/spec_writer.md +12 -0
- package/src/devcouncil/domain/__init__.py +0 -0
- package/src/devcouncil/domain/assumption.py +17 -0
- package/src/devcouncil/domain/critique.py +32 -0
- package/src/devcouncil/domain/evidence.py +27 -0
- package/src/devcouncil/domain/gap.py +26 -0
- package/src/devcouncil/domain/requirement.py +22 -0
- package/src/devcouncil/domain/task.py +26 -0
- package/src/devcouncil/execution/__init__.py +1 -0
- package/src/devcouncil/execution/context_builder.py +60 -0
- package/src/devcouncil/execution/executor.py +15 -0
- package/src/devcouncil/execution/hook_policy.py +144 -0
- package/src/devcouncil/execution/patch.py +28 -0
- package/src/devcouncil/execution/paths.py +14 -0
- package/src/devcouncil/execution/permissions.py +92 -0
- package/src/devcouncil/execution/prompt_builder.py +59 -0
- package/src/devcouncil/execution/task_runner.py +166 -0
- package/src/devcouncil/executors/__init__.py +1 -0
- package/src/devcouncil/executors/mini_swe.py +73 -0
- package/src/devcouncil/executors/native/__init__.py +0 -0
- package/src/devcouncil/executors/native/agent.py +107 -0
- package/src/devcouncil/executors/openhands.py +71 -0
- package/src/devcouncil/gating/__init__.py +1 -0
- package/src/devcouncil/gating/checks/__init__.py +0 -0
- package/src/devcouncil/gating/checks/clean_git.py +45 -0
- package/src/devcouncil/gating/checks/planned_files_check.py +32 -0
- package/src/devcouncil/gating/checks/requirement_coverage.py +26 -0
- package/src/devcouncil/gating/checks/secret_scan_check.py +34 -0
- package/src/devcouncil/gating/policy.py +190 -0
- package/src/devcouncil/indexing/__init__.py +1 -0
- package/src/devcouncil/indexing/graph_index.py +48 -0
- package/src/devcouncil/indexing/repo_mapper.py +204 -0
- package/src/devcouncil/indexing/symbol_index.py +0 -0
- package/src/devcouncil/integrations/code_review_graph.py +163 -0
- package/src/devcouncil/integrations/github.py +39 -0
- package/src/devcouncil/integrations/gitnexus.py +27 -0
- package/src/devcouncil/integrations/graphify.py +34 -0
- package/src/devcouncil/integrations/mcp/__init__.py +0 -0
- package/src/devcouncil/integrations/mcp/server.py +146 -0
- package/src/devcouncil/llm/__init__.py +1 -0
- package/src/devcouncil/llm/cache.py +38 -0
- package/src/devcouncil/llm/provider.py +125 -0
- package/src/devcouncil/llm/router.py +125 -0
- package/src/devcouncil/planning/__init__.py +1 -0
- package/src/devcouncil/planning/arbiter_service.py +57 -0
- package/src/devcouncil/planning/critique_service.py +66 -0
- package/src/devcouncil/planning/plan_service.py +46 -0
- package/src/devcouncil/planning/repair_service.py +39 -0
- package/src/devcouncil/planning/spec_service.py +44 -0
- package/src/devcouncil/repo/__init__.py +0 -0
- package/src/devcouncil/reporting/__init__.py +0 -0
- package/src/devcouncil/reporting/github_check.py +32 -0
- package/src/devcouncil/reporting/json_report.py +17 -0
- package/src/devcouncil/reporting/markdown_report.py +46 -0
- package/src/devcouncil/reporting/report_builder.py +14 -0
- package/src/devcouncil/storage/__init__.py +0 -0
- package/src/devcouncil/storage/db.py +66 -0
- package/src/devcouncil/storage/models.py +83 -0
- package/src/devcouncil/storage/repositories.py +346 -0
- package/src/devcouncil/telemetry/__init__.py +0 -0
- package/src/devcouncil/telemetry/cost.py +34 -0
- package/src/devcouncil/telemetry/traces.py +91 -0
- package/src/devcouncil/telemetry/tracker.py +49 -0
- package/src/devcouncil/utils/__init__.py +1 -0
- package/src/devcouncil/utils/redaction.py +141 -0
- package/src/devcouncil/verification/__init__.py +1 -0
- package/src/devcouncil/verification/implementation_reviewer.py +55 -0
- package/src/devcouncil/verification/verifier.py +513 -0
- package/uv.lock +1085 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Proactive secret and PII redaction for LLM-bound prompts.
|
|
2
|
+
|
|
3
|
+
Two entry points:
|
|
4
|
+
- redact_string(text) -- regex-based redaction of known secret patterns
|
|
5
|
+
- redact_text(text, extra_patterns) -- enhanced version with custom patterns and typed labels
|
|
6
|
+
- redact_env_vars(text) -- redact values in environment variable assignments
|
|
7
|
+
- redact_dict(data) -- recursively redact all string values in a dict
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from typing import Dict, List, Optional, Pattern
|
|
12
|
+
|
|
13
|
+
# Common patterns for sensitive data — each with a human-readable label
|
|
14
|
+
SECRET_PATTERNS: Dict[str, Pattern] = {
|
|
15
|
+
"aws_access_key": re.compile(r"(?i)\b(AKIA[0-9A-Z]{16})\b"),
|
|
16
|
+
"aws_secret_key": re.compile(r"(?i)(?:aws_secret_access_key|aws_secret|secret_key)\s*[=:]\s*([0-9a-zA-Z/+]{40})"),
|
|
17
|
+
"github_token": re.compile(r"(?i)\b(gh[pusr]_[A-Za-z0-9_]{36})\b"),
|
|
18
|
+
"slack_token": re.compile(r"(?i)\b(xox[baprs]-[0-9]{12}-[0-9]{12}-[0-9]{12}-[a-z0-9]{32})\b"),
|
|
19
|
+
"jwt": re.compile(r"(?i)\b(ey[a-zA-Z0-9_-]{10,}\.ey[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,})\b"),
|
|
20
|
+
"generic_api_key": re.compile(r"(?i)(api[_-]?key|secret|token|password)[\"'\s]*[:=][\"'\s]*([a-zA-Z0-9_\-\.]{16,})"),
|
|
21
|
+
"private_key": re.compile(r"(?s)-----BEGIN [A-Z]+ PRIVATE KEY-----.*?-----END [A-Z]+ PRIVATE KEY-----"),
|
|
22
|
+
"bearer": re.compile(r"(?i)\b(Bearer\s+)([a-zA-Z0-9_\-\.]{16,})\b"),
|
|
23
|
+
"database_url": re.compile(r"(?i)((?:postgresql|mysql|mongodb|redis)://[^:]+:)([^@]+)(@.+)"),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# For backward compatibility
|
|
27
|
+
def redact_string(text: str) -> str:
|
|
28
|
+
"""Redact known sensitive patterns from a string (legacy API)."""
|
|
29
|
+
return redact_text(text)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def redact_text(text: str, extra_patterns: Optional[List[str]] = None) -> str:
|
|
33
|
+
"""Redact known sensitive patterns from a string.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
text: The input text to redact.
|
|
37
|
+
extra_patterns: Optional list of regex patterns to additionally redact,
|
|
38
|
+
labeled as [REDACTED:custom_N].
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Text with all sensitive patterns replaced with [REDACTED:type] labels.
|
|
42
|
+
"""
|
|
43
|
+
if not isinstance(text, str):
|
|
44
|
+
return text
|
|
45
|
+
|
|
46
|
+
redacted_text = text
|
|
47
|
+
|
|
48
|
+
for key_type, pattern in SECRET_PATTERNS.items():
|
|
49
|
+
if key_type == "generic_api_key":
|
|
50
|
+
# For the generic pattern, replace the value part (group 2)
|
|
51
|
+
def _make_generic_replacer(kt: str):
|
|
52
|
+
def replacer(match):
|
|
53
|
+
prefix = match.group(1)
|
|
54
|
+
separator = match.group(0)[len(match.group(1)):-len(match.group(2))]
|
|
55
|
+
return f"{prefix}{separator}[REDACTED:{kt}]"
|
|
56
|
+
return replacer
|
|
57
|
+
redacted_text = pattern.sub(_make_generic_replacer(key_type), redacted_text)
|
|
58
|
+
elif key_type == "bearer":
|
|
59
|
+
# Preserve "Bearer " prefix, redact the token
|
|
60
|
+
def _bearer_replacer(match):
|
|
61
|
+
return f"{match.group(1)}[REDACTED:bearer]"
|
|
62
|
+
redacted_text = pattern.sub(_bearer_replacer, redacted_text)
|
|
63
|
+
elif key_type == "database_url":
|
|
64
|
+
# Preserve protocol and host, redact password
|
|
65
|
+
def _db_url_replacer(match):
|
|
66
|
+
return f"{match.group(1)}[REDACTED:database_url]{match.group(3)}"
|
|
67
|
+
redacted_text = pattern.sub(_db_url_replacer, redacted_text)
|
|
68
|
+
else:
|
|
69
|
+
redacted_text = pattern.sub(f"[REDACTED:{key_type}]", redacted_text)
|
|
70
|
+
|
|
71
|
+
# Apply custom extra patterns
|
|
72
|
+
if extra_patterns:
|
|
73
|
+
for i, pat_str in enumerate(extra_patterns):
|
|
74
|
+
try:
|
|
75
|
+
pat = re.compile(pat_str)
|
|
76
|
+
redacted_text = pat.sub(f"[REDACTED:custom_{i}]", redacted_text)
|
|
77
|
+
except re.error:
|
|
78
|
+
pass # Skip invalid patterns
|
|
79
|
+
|
|
80
|
+
return redacted_text
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def redact_env_vars(text: str) -> str:
|
|
84
|
+
"""Redact values in environment variable assignments.
|
|
85
|
+
|
|
86
|
+
Handles patterns like:
|
|
87
|
+
export KEY=value
|
|
88
|
+
KEY='value'
|
|
89
|
+
KEY="value"
|
|
90
|
+
"""
|
|
91
|
+
if not isinstance(text, str):
|
|
92
|
+
return text
|
|
93
|
+
|
|
94
|
+
# Match: optional export, VAR_NAME = value (with optional quotes)
|
|
95
|
+
env_pattern = re.compile(
|
|
96
|
+
r"(?m)^(\s*(?:export\s+)?)" # optional export
|
|
97
|
+
r"([A-Z_][A-Z0-9_]*)" # variable name
|
|
98
|
+
r"(\s*=\s*)" # equals sign
|
|
99
|
+
r"(?:'([^']*)'|\"([^\"]*)\"|(\S+))" # value (quoted or unquoted)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
sensitive_keys = {
|
|
103
|
+
"api_key", "secret", "token", "password", "passwd", "credential",
|
|
104
|
+
"private_key", "access_key", "secret_key", "auth",
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
def _env_replacer(match):
|
|
108
|
+
prefix = match.group(1)
|
|
109
|
+
var_name = match.group(2)
|
|
110
|
+
eq_sign = match.group(3)
|
|
111
|
+
|
|
112
|
+
# Check if the variable name contains a sensitive keyword
|
|
113
|
+
var_lower = var_name.lower()
|
|
114
|
+
is_sensitive = any(kw in var_lower for kw in sensitive_keys)
|
|
115
|
+
|
|
116
|
+
if is_sensitive:
|
|
117
|
+
return f"{prefix}{var_name}{eq_sign}[REDACTED]"
|
|
118
|
+
|
|
119
|
+
return match.group(0)
|
|
120
|
+
|
|
121
|
+
return env_pattern.sub(_env_replacer, text)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def redact_dict(data: dict) -> dict:
|
|
125
|
+
"""Recursively redact sensitive patterns from a dictionary (e.g. JSON response)."""
|
|
126
|
+
result = {}
|
|
127
|
+
for k, v in data.items():
|
|
128
|
+
if isinstance(v, str):
|
|
129
|
+
result[k] = redact_text(v)
|
|
130
|
+
elif isinstance(v, dict):
|
|
131
|
+
result[k] = redact_dict(v)
|
|
132
|
+
elif isinstance(v, list):
|
|
133
|
+
result[k] = [
|
|
134
|
+
redact_dict(item) if isinstance(item, dict)
|
|
135
|
+
else redact_text(item) if isinstance(item, str)
|
|
136
|
+
else item
|
|
137
|
+
for item in v
|
|
138
|
+
]
|
|
139
|
+
else:
|
|
140
|
+
result[k] = v
|
|
141
|
+
return result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import List
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from devcouncil.domain.task import Task
|
|
5
|
+
from devcouncil.domain.requirement import Requirement
|
|
6
|
+
from devcouncil.domain.gap import Gap
|
|
7
|
+
from devcouncil.llm.router import ModelRouter
|
|
8
|
+
from devcouncil.utils.redaction import redact_string
|
|
9
|
+
|
|
10
|
+
class ReviewOutput(BaseModel):
|
|
11
|
+
is_satisfactory: bool
|
|
12
|
+
findings: List[Gap]
|
|
13
|
+
|
|
14
|
+
class ImplementationReviewer:
|
|
15
|
+
"""Uses LLM to review code changes against task requirements."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, router: ModelRouter):
|
|
18
|
+
self.router = router
|
|
19
|
+
|
|
20
|
+
async def review_changes(
|
|
21
|
+
self,
|
|
22
|
+
task: Task,
|
|
23
|
+
requirements: List[Requirement],
|
|
24
|
+
diff: str
|
|
25
|
+
) -> ReviewOutput:
|
|
26
|
+
linked_reqs = [r for r in requirements if r.id in task.requirement_ids]
|
|
27
|
+
if not linked_reqs:
|
|
28
|
+
linked_reqs = requirements
|
|
29
|
+
requirements_json = json.dumps([r.model_dump() for r in linked_reqs], indent=2)
|
|
30
|
+
redacted_diff = redact_string(diff)
|
|
31
|
+
prompt = f"""
|
|
32
|
+
You are an expert software reviewer. Review the following code changes against the task requirements.
|
|
33
|
+
Task: {task.title}
|
|
34
|
+
Description: {task.description}
|
|
35
|
+
|
|
36
|
+
Requirements:
|
|
37
|
+
{requirements_json}
|
|
38
|
+
|
|
39
|
+
Code Diff:
|
|
40
|
+
{redacted_diff}
|
|
41
|
+
|
|
42
|
+
Your task is to identify if the implementation is complete, correct, and follows best practices.
|
|
43
|
+
- Identify missing edge cases.
|
|
44
|
+
- Identify architectural drift.
|
|
45
|
+
- Identify security risks not caught by static scans.
|
|
46
|
+
|
|
47
|
+
Return a JSON object with 'is_satisfactory' and a list of 'findings' (as Gap objects).
|
|
48
|
+
"""
|
|
49
|
+
messages = [{"role": "user", "content": prompt}]
|
|
50
|
+
|
|
51
|
+
return await self.router.complete_structured(
|
|
52
|
+
role="implementation_reviewer",
|
|
53
|
+
messages=messages,
|
|
54
|
+
schema=ReviewOutput
|
|
55
|
+
)
|