devcouncil 0.1.1 → 0.2.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.
Files changed (146) hide show
  1. package/README.md +218 -96
  2. package/bin/devcouncil.js +130 -32
  3. package/package.json +7 -11
  4. package/pyproject.toml +0 -34
  5. package/src/devcouncil/__init__.py +0 -0
  6. package/src/devcouncil/__main__.py +0 -4
  7. package/src/devcouncil/app/__init__.py +0 -28
  8. package/src/devcouncil/app/config.py +0 -158
  9. package/src/devcouncil/app/errors.py +0 -23
  10. package/src/devcouncil/app/events.py +0 -44
  11. package/src/devcouncil/app/orchestrator.py +0 -92
  12. package/src/devcouncil/app/project_status.py +0 -29
  13. package/src/devcouncil/app/run_context.py +0 -39
  14. package/src/devcouncil/app/state_machine.py +0 -108
  15. package/src/devcouncil/artifacts/__init__.py +0 -1
  16. package/src/devcouncil/artifacts/coverage.py +0 -96
  17. package/src/devcouncil/artifacts/graph.py +0 -143
  18. package/src/devcouncil/artifacts/migrations.py +0 -20
  19. package/src/devcouncil/artifacts/schemas.py +0 -23
  20. package/src/devcouncil/artifacts/serializer.py +0 -21
  21. package/src/devcouncil/artifacts/validators.py +0 -27
  22. package/src/devcouncil/cli/__init__.py +0 -0
  23. package/src/devcouncil/cli/commands/__init__.py +0 -0
  24. package/src/devcouncil/cli/commands/artifacts.py +0 -51
  25. package/src/devcouncil/cli/commands/ast.py +0 -22
  26. package/src/devcouncil/cli/commands/baseline.py +0 -35
  27. package/src/devcouncil/cli/commands/config.py +0 -76
  28. package/src/devcouncil/cli/commands/dashboard.py +0 -26
  29. package/src/devcouncil/cli/commands/doctor.py +0 -140
  30. package/src/devcouncil/cli/commands/go.py +0 -237
  31. package/src/devcouncil/cli/commands/hook.py +0 -128
  32. package/src/devcouncil/cli/commands/init.py +0 -153
  33. package/src/devcouncil/cli/commands/integrate.py +0 -726
  34. package/src/devcouncil/cli/commands/lsp.py +0 -20
  35. package/src/devcouncil/cli/commands/map.py +0 -42
  36. package/src/devcouncil/cli/commands/mcp_server.py +0 -18
  37. package/src/devcouncil/cli/commands/plan.py +0 -327
  38. package/src/devcouncil/cli/commands/prompt.py +0 -50
  39. package/src/devcouncil/cli/commands/repair.py +0 -72
  40. package/src/devcouncil/cli/commands/report.py +0 -129
  41. package/src/devcouncil/cli/commands/reset_demo_state.py +0 -31
  42. package/src/devcouncil/cli/commands/rollback.py +0 -60
  43. package/src/devcouncil/cli/commands/run.py +0 -269
  44. package/src/devcouncil/cli/commands/setup.py +0 -223
  45. package/src/devcouncil/cli/commands/show.py +0 -76
  46. package/src/devcouncil/cli/commands/status.py +0 -117
  47. package/src/devcouncil/cli/commands/tasks.py +0 -55
  48. package/src/devcouncil/cli/commands/trace.py +0 -44
  49. package/src/devcouncil/cli/commands/verify.py +0 -193
  50. package/src/devcouncil/cli/commands/version.py +0 -20
  51. package/src/devcouncil/cli/commands/watch.py +0 -574
  52. package/src/devcouncil/cli/main.py +0 -88
  53. package/src/devcouncil/council/__init__.py +0 -0
  54. package/src/devcouncil/council/prompts/__init__.py +0 -0
  55. package/src/devcouncil/council/prompts/arbiter.md +0 -19
  56. package/src/devcouncil/council/prompts/critic_a.md +0 -10
  57. package/src/devcouncil/council/prompts/critic_b.md +0 -10
  58. package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
  59. package/src/devcouncil/council/prompts/planner_a.md +0 -16
  60. package/src/devcouncil/council/prompts/planner_b.md +0 -16
  61. package/src/devcouncil/council/prompts/rebuttal.md +0 -10
  62. package/src/devcouncil/council/prompts/spec_writer.md +0 -12
  63. package/src/devcouncil/domain/__init__.py +0 -0
  64. package/src/devcouncil/domain/assumption.py +0 -17
  65. package/src/devcouncil/domain/critique.py +0 -32
  66. package/src/devcouncil/domain/evidence.py +0 -27
  67. package/src/devcouncil/domain/gap.py +0 -26
  68. package/src/devcouncil/domain/requirement.py +0 -22
  69. package/src/devcouncil/domain/task.py +0 -26
  70. package/src/devcouncil/execution/__init__.py +0 -1
  71. package/src/devcouncil/execution/context_builder.py +0 -60
  72. package/src/devcouncil/execution/executor.py +0 -15
  73. package/src/devcouncil/execution/hook_policy.py +0 -165
  74. package/src/devcouncil/execution/patch.py +0 -28
  75. package/src/devcouncil/execution/paths.py +0 -14
  76. package/src/devcouncil/execution/permissions.py +0 -92
  77. package/src/devcouncil/execution/prompt_builder.py +0 -59
  78. package/src/devcouncil/execution/task_runner.py +0 -166
  79. package/src/devcouncil/executors/__init__.py +0 -1
  80. package/src/devcouncil/executors/coding_cli.py +0 -112
  81. package/src/devcouncil/executors/mini_swe.py +0 -73
  82. package/src/devcouncil/executors/native/__init__.py +0 -0
  83. package/src/devcouncil/executors/native/agent.py +0 -107
  84. package/src/devcouncil/executors/openhands.py +0 -71
  85. package/src/devcouncil/gating/__init__.py +0 -1
  86. package/src/devcouncil/gating/checks/__init__.py +0 -0
  87. package/src/devcouncil/gating/checks/clean_git.py +0 -50
  88. package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
  89. package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
  90. package/src/devcouncil/gating/checks/secret_scan_check.py +0 -34
  91. package/src/devcouncil/gating/policy.py +0 -190
  92. package/src/devcouncil/indexing/__init__.py +0 -1
  93. package/src/devcouncil/indexing/ast_matcher.py +0 -168
  94. package/src/devcouncil/indexing/graph_index.py +0 -48
  95. package/src/devcouncil/indexing/lsp.py +0 -120
  96. package/src/devcouncil/indexing/repo_mapper.py +0 -208
  97. package/src/devcouncil/indexing/symbol_index.py +0 -0
  98. package/src/devcouncil/integrations/code_review_graph.py +0 -163
  99. package/src/devcouncil/integrations/github.py +0 -39
  100. package/src/devcouncil/integrations/gitnexus.py +0 -27
  101. package/src/devcouncil/integrations/graphify.py +0 -34
  102. package/src/devcouncil/integrations/mcp/__init__.py +0 -0
  103. package/src/devcouncil/integrations/mcp/server.py +0 -599
  104. package/src/devcouncil/integrations/pr_comments.py +0 -62
  105. package/src/devcouncil/live/__init__.py +0 -2
  106. package/src/devcouncil/live/cards.py +0 -207
  107. package/src/devcouncil/live/models.py +0 -63
  108. package/src/devcouncil/live/repair_prompt.py +0 -83
  109. package/src/devcouncil/live/reviewer.py +0 -70
  110. package/src/devcouncil/live/signals.py +0 -135
  111. package/src/devcouncil/live/summary.py +0 -34
  112. package/src/devcouncil/live/tasks.py +0 -18
  113. package/src/devcouncil/live/transcripts.py +0 -138
  114. package/src/devcouncil/llm/__init__.py +0 -1
  115. package/src/devcouncil/llm/cache.py +0 -38
  116. package/src/devcouncil/llm/provider.py +0 -146
  117. package/src/devcouncil/llm/router.py +0 -125
  118. package/src/devcouncil/planning/__init__.py +0 -1
  119. package/src/devcouncil/planning/arbiter_service.py +0 -57
  120. package/src/devcouncil/planning/critique_service.py +0 -66
  121. package/src/devcouncil/planning/plan_service.py +0 -46
  122. package/src/devcouncil/planning/prompt_enhancer_service.py +0 -86
  123. package/src/devcouncil/planning/repair_service.py +0 -39
  124. package/src/devcouncil/planning/spec_service.py +0 -44
  125. package/src/devcouncil/repo/__init__.py +0 -0
  126. package/src/devcouncil/reporting/__init__.py +0 -0
  127. package/src/devcouncil/reporting/github_check.py +0 -32
  128. package/src/devcouncil/reporting/json_report.py +0 -20
  129. package/src/devcouncil/reporting/markdown_report.py +0 -68
  130. package/src/devcouncil/reporting/report_builder.py +0 -14
  131. package/src/devcouncil/storage/__init__.py +0 -0
  132. package/src/devcouncil/storage/db.py +0 -66
  133. package/src/devcouncil/storage/models.py +0 -83
  134. package/src/devcouncil/storage/repositories.py +0 -423
  135. package/src/devcouncil/telemetry/__init__.py +0 -0
  136. package/src/devcouncil/telemetry/cost.py +0 -34
  137. package/src/devcouncil/telemetry/traces.py +0 -91
  138. package/src/devcouncil/telemetry/tracker.py +0 -49
  139. package/src/devcouncil/ui/__init__.py +0 -1
  140. package/src/devcouncil/ui/dashboard.py +0 -122
  141. package/src/devcouncil/utils/__init__.py +0 -1
  142. package/src/devcouncil/utils/redaction.py +0 -141
  143. package/src/devcouncil/verification/__init__.py +0 -1
  144. package/src/devcouncil/verification/implementation_reviewer.py +0 -55
  145. package/src/devcouncil/verification/verifier.py +0 -530
  146. package/uv.lock +0 -1085
@@ -1,122 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
5
- from pathlib import Path
6
- from urllib.parse import urlparse
7
-
8
- from devcouncil.app.project_status import compute_phase
9
- from devcouncil.storage.db import get_db
10
- from devcouncil.storage.repositories import ArtifactGraphRepository, StateRepository, TaskRepository
11
- from devcouncil.telemetry.traces import read_trace_events
12
-
13
-
14
- def dashboard_payload(project_root: Path) -> dict:
15
- db = get_db(project_root)
16
- if not db:
17
- return {"initialized": False, "phase": "UNINITIALIZED", "tasks": [], "coverage": {}, "events": []}
18
- with db.get_session() as session:
19
- graph = ArtifactGraphRepository(session).load_graph()
20
- state = StateRepository(session).get_state()
21
- phase = compute_phase(graph, state.current_phase if state else None)
22
- tasks = [task.model_dump() for task in TaskRepository(session).get_all()]
23
- return {
24
- "initialized": True,
25
- "phase": phase,
26
- "coverage": graph.coverage_summary(),
27
- "tasks": tasks,
28
- "events": [event.model_dump(by_alias=True) for event in list(read_trace_events(project_root))[-50:]],
29
- }
30
-
31
-
32
- def dashboard_html() -> str:
33
- return """<!doctype html>
34
- <html lang="en">
35
- <head>
36
- <meta charset="utf-8">
37
- <meta name="viewport" content="width=device-width, initial-scale=1">
38
- <title>DevCouncil Dashboard</title>
39
- <style>
40
- body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f7f7f5; color: #202124; }
41
- header { padding: 20px 28px; border-bottom: 1px solid #d9d9d4; background: #ffffff; display: flex; align-items: center; justify-content: space-between; }
42
- main { padding: 24px 28px; display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
43
- section { background: #ffffff; border: 1px solid #d9d9d4; border-radius: 8px; padding: 16px; }
44
- h1 { font-size: 20px; margin: 0; }
45
- h2 { font-size: 15px; margin: 0 0 12px; }
46
- .phase { font-weight: 700; }
47
- table { width: 100%; border-collapse: collapse; font-size: 13px; }
48
- th, td { text-align: left; border-bottom: 1px solid #ededeb; padding: 8px; vertical-align: top; }
49
- pre { margin: 0; white-space: pre-wrap; font-size: 12px; }
50
- @media (max-width: 800px) { main { grid-template-columns: 1fr; padding: 16px; } header { padding: 16px; } }
51
- </style>
52
- </head>
53
- <body>
54
- <header><h1>DevCouncil Dashboard</h1><div>Phase: <span id="phase" class="phase">loading</span></div></header>
55
- <main>
56
- <section><h2>Coverage</h2><pre id="coverage">{}</pre></section>
57
- <section><h2>Tasks</h2><table><thead><tr><th>ID</th><th>Status</th><th>Title</th></tr></thead><tbody id="tasks"></tbody></table></section>
58
- <section style="grid-column: 1 / -1;"><h2>Recent Trace Events</h2><pre id="events"></pre></section>
59
- </main>
60
- <script>
61
- function setText(cell, value) {
62
- cell.textContent = value == null ? '' : String(value);
63
- return cell;
64
- }
65
- async function refresh() {
66
- const res = await fetch('/api/status');
67
- const data = await res.json();
68
- document.getElementById('phase').textContent = data.phase;
69
- document.getElementById('coverage').textContent = JSON.stringify(data.coverage, null, 2);
70
- const body = document.getElementById('tasks');
71
- body.replaceChildren(...(data.tasks || []).map(t => {
72
- const row = document.createElement('tr');
73
- row.appendChild(setText(document.createElement('td'), t.id));
74
- row.appendChild(setText(document.createElement('td'), t.status));
75
- row.appendChild(setText(document.createElement('td'), t.title));
76
- return row;
77
- }));
78
- document.getElementById('events').textContent = JSON.stringify(data.events || [], null, 2);
79
- }
80
- refresh();
81
- setInterval(refresh, 2000);
82
- </script>
83
- </body>
84
- </html>"""
85
-
86
-
87
- def run_dashboard(project_root: Path, host: str = "127.0.0.1", port: int = 8765) -> None:
88
- class DashboardServer(ThreadingHTTPServer):
89
- allow_reuse_address = True
90
- daemon_threads = True
91
-
92
- class Handler(BaseHTTPRequestHandler):
93
- def do_GET(self): # noqa: N802
94
- parsed = urlparse(self.path)
95
- if parsed.path == "/api/status":
96
- body = json.dumps(dashboard_payload(project_root)).encode("utf-8")
97
- self.send_response(200)
98
- self.send_header("Content-Type", "application/json")
99
- self.send_header("Content-Length", str(len(body)))
100
- self.end_headers()
101
- self.wfile.write(body)
102
- return
103
- if parsed.path.startswith("/api/"):
104
- body = json.dumps({"error": "Not found"}).encode("utf-8")
105
- self.send_response(404)
106
- self.send_header("Content-Type", "application/json")
107
- self.send_header("Content-Length", str(len(body)))
108
- self.end_headers()
109
- self.wfile.write(body)
110
- return
111
- body = dashboard_html().encode("utf-8")
112
- self.send_response(200)
113
- self.send_header("Content-Type", "text/html; charset=utf-8")
114
- self.send_header("Content-Length", str(len(body)))
115
- self.end_headers()
116
- self.wfile.write(body)
117
-
118
- def log_message(self, format, *args): # noqa: A002
119
- return
120
-
121
- server = DashboardServer((host, port), Handler)
122
- server.serve_forever()
@@ -1 +0,0 @@
1
- """Utilities package: redaction, paths, hashing, subprocess helpers."""
@@ -1,141 +0,0 @@
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
@@ -1,55 +0,0 @@
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
- )