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.
Files changed (125) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +643 -0
  3. package/bin/devcouncil.js +62 -0
  4. package/package.json +47 -0
  5. package/pyproject.toml +31 -0
  6. package/src/devcouncil/__init__.py +0 -0
  7. package/src/devcouncil/__main__.py +4 -0
  8. package/src/devcouncil/app/__init__.py +28 -0
  9. package/src/devcouncil/app/config.py +131 -0
  10. package/src/devcouncil/app/errors.py +23 -0
  11. package/src/devcouncil/app/events.py +44 -0
  12. package/src/devcouncil/app/orchestrator.py +92 -0
  13. package/src/devcouncil/app/run_context.py +39 -0
  14. package/src/devcouncil/app/state_machine.py +108 -0
  15. package/src/devcouncil/artifacts/__init__.py +1 -0
  16. package/src/devcouncil/artifacts/coverage.py +96 -0
  17. package/src/devcouncil/artifacts/graph.py +143 -0
  18. package/src/devcouncil/artifacts/migrations.py +20 -0
  19. package/src/devcouncil/artifacts/schemas.py +23 -0
  20. package/src/devcouncil/artifacts/serializer.py +21 -0
  21. package/src/devcouncil/artifacts/validators.py +27 -0
  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 +48 -0
  25. package/src/devcouncil/cli/commands/baseline.py +32 -0
  26. package/src/devcouncil/cli/commands/config.py +54 -0
  27. package/src/devcouncil/cli/commands/doctor.py +96 -0
  28. package/src/devcouncil/cli/commands/hook.py +61 -0
  29. package/src/devcouncil/cli/commands/init.py +142 -0
  30. package/src/devcouncil/cli/commands/integrate.py +420 -0
  31. package/src/devcouncil/cli/commands/map.py +38 -0
  32. package/src/devcouncil/cli/commands/mcp_server.py +18 -0
  33. package/src/devcouncil/cli/commands/plan.py +276 -0
  34. package/src/devcouncil/cli/commands/prompt.py +47 -0
  35. package/src/devcouncil/cli/commands/repair.py +69 -0
  36. package/src/devcouncil/cli/commands/report.py +71 -0
  37. package/src/devcouncil/cli/commands/reset_demo_state.py +28 -0
  38. package/src/devcouncil/cli/commands/rollback.py +58 -0
  39. package/src/devcouncil/cli/commands/run.py +224 -0
  40. package/src/devcouncil/cli/commands/setup.py +82 -0
  41. package/src/devcouncil/cli/commands/show.py +57 -0
  42. package/src/devcouncil/cli/commands/status.py +105 -0
  43. package/src/devcouncil/cli/commands/tasks.py +41 -0
  44. package/src/devcouncil/cli/commands/trace.py +43 -0
  45. package/src/devcouncil/cli/commands/verify.py +163 -0
  46. package/src/devcouncil/cli/commands/version.py +20 -0
  47. package/src/devcouncil/cli/main.py +70 -0
  48. package/src/devcouncil/council/__init__.py +0 -0
  49. package/src/devcouncil/council/prompts/__init__.py +0 -0
  50. package/src/devcouncil/council/prompts/arbiter.md +19 -0
  51. package/src/devcouncil/council/prompts/critic_a.md +10 -0
  52. package/src/devcouncil/council/prompts/critic_b.md +10 -0
  53. package/src/devcouncil/council/prompts/implementation_reviewer.md +16 -0
  54. package/src/devcouncil/council/prompts/planner_a.md +16 -0
  55. package/src/devcouncil/council/prompts/planner_b.md +16 -0
  56. package/src/devcouncil/council/prompts/rebuttal.md +10 -0
  57. package/src/devcouncil/council/prompts/spec_writer.md +12 -0
  58. package/src/devcouncil/domain/__init__.py +0 -0
  59. package/src/devcouncil/domain/assumption.py +17 -0
  60. package/src/devcouncil/domain/critique.py +32 -0
  61. package/src/devcouncil/domain/evidence.py +27 -0
  62. package/src/devcouncil/domain/gap.py +26 -0
  63. package/src/devcouncil/domain/requirement.py +22 -0
  64. package/src/devcouncil/domain/task.py +26 -0
  65. package/src/devcouncil/execution/__init__.py +1 -0
  66. package/src/devcouncil/execution/context_builder.py +60 -0
  67. package/src/devcouncil/execution/executor.py +15 -0
  68. package/src/devcouncil/execution/hook_policy.py +144 -0
  69. package/src/devcouncil/execution/patch.py +28 -0
  70. package/src/devcouncil/execution/paths.py +14 -0
  71. package/src/devcouncil/execution/permissions.py +92 -0
  72. package/src/devcouncil/execution/prompt_builder.py +59 -0
  73. package/src/devcouncil/execution/task_runner.py +166 -0
  74. package/src/devcouncil/executors/__init__.py +1 -0
  75. package/src/devcouncil/executors/mini_swe.py +73 -0
  76. package/src/devcouncil/executors/native/__init__.py +0 -0
  77. package/src/devcouncil/executors/native/agent.py +107 -0
  78. package/src/devcouncil/executors/openhands.py +71 -0
  79. package/src/devcouncil/gating/__init__.py +1 -0
  80. package/src/devcouncil/gating/checks/__init__.py +0 -0
  81. package/src/devcouncil/gating/checks/clean_git.py +45 -0
  82. package/src/devcouncil/gating/checks/planned_files_check.py +32 -0
  83. package/src/devcouncil/gating/checks/requirement_coverage.py +26 -0
  84. package/src/devcouncil/gating/checks/secret_scan_check.py +34 -0
  85. package/src/devcouncil/gating/policy.py +190 -0
  86. package/src/devcouncil/indexing/__init__.py +1 -0
  87. package/src/devcouncil/indexing/graph_index.py +48 -0
  88. package/src/devcouncil/indexing/repo_mapper.py +204 -0
  89. package/src/devcouncil/indexing/symbol_index.py +0 -0
  90. package/src/devcouncil/integrations/code_review_graph.py +163 -0
  91. package/src/devcouncil/integrations/github.py +39 -0
  92. package/src/devcouncil/integrations/gitnexus.py +27 -0
  93. package/src/devcouncil/integrations/graphify.py +34 -0
  94. package/src/devcouncil/integrations/mcp/__init__.py +0 -0
  95. package/src/devcouncil/integrations/mcp/server.py +146 -0
  96. package/src/devcouncil/llm/__init__.py +1 -0
  97. package/src/devcouncil/llm/cache.py +38 -0
  98. package/src/devcouncil/llm/provider.py +125 -0
  99. package/src/devcouncil/llm/router.py +125 -0
  100. package/src/devcouncil/planning/__init__.py +1 -0
  101. package/src/devcouncil/planning/arbiter_service.py +57 -0
  102. package/src/devcouncil/planning/critique_service.py +66 -0
  103. package/src/devcouncil/planning/plan_service.py +46 -0
  104. package/src/devcouncil/planning/repair_service.py +39 -0
  105. package/src/devcouncil/planning/spec_service.py +44 -0
  106. package/src/devcouncil/repo/__init__.py +0 -0
  107. package/src/devcouncil/reporting/__init__.py +0 -0
  108. package/src/devcouncil/reporting/github_check.py +32 -0
  109. package/src/devcouncil/reporting/json_report.py +17 -0
  110. package/src/devcouncil/reporting/markdown_report.py +46 -0
  111. package/src/devcouncil/reporting/report_builder.py +14 -0
  112. package/src/devcouncil/storage/__init__.py +0 -0
  113. package/src/devcouncil/storage/db.py +66 -0
  114. package/src/devcouncil/storage/models.py +83 -0
  115. package/src/devcouncil/storage/repositories.py +346 -0
  116. package/src/devcouncil/telemetry/__init__.py +0 -0
  117. package/src/devcouncil/telemetry/cost.py +34 -0
  118. package/src/devcouncil/telemetry/traces.py +91 -0
  119. package/src/devcouncil/telemetry/tracker.py +49 -0
  120. package/src/devcouncil/utils/__init__.py +1 -0
  121. package/src/devcouncil/utils/redaction.py +141 -0
  122. package/src/devcouncil/verification/__init__.py +1 -0
  123. package/src/devcouncil/verification/implementation_reviewer.py +55 -0
  124. package/src/devcouncil/verification/verifier.py +513 -0
  125. 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,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
+ )