devcouncil 0.2.0 → 0.2.2

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 (205) hide show
  1. package/README.md +144 -308
  2. package/bin/devcouncil.js +130 -32
  3. package/package.json +22 -19
  4. package/pyproject.toml +0 -66
  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 -320
  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 -163
  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/assets/__init__.py +0 -1
  23. package/src/devcouncil/assets/devcouncil-logo.svg +0 -60
  24. package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
  25. package/src/devcouncil/cli/__init__.py +0 -0
  26. package/src/devcouncil/cli/commands/__init__.py +0 -0
  27. package/src/devcouncil/cli/commands/agents.py +0 -292
  28. package/src/devcouncil/cli/commands/artifacts.py +0 -54
  29. package/src/devcouncil/cli/commands/ast.py +0 -22
  30. package/src/devcouncil/cli/commands/baseline.py +0 -35
  31. package/src/devcouncil/cli/commands/check.py +0 -209
  32. package/src/devcouncil/cli/commands/config.py +0 -115
  33. package/src/devcouncil/cli/commands/cost.py +0 -57
  34. package/src/devcouncil/cli/commands/dashboard.py +0 -31
  35. package/src/devcouncil/cli/commands/doctor.py +0 -340
  36. package/src/devcouncil/cli/commands/evidence.py +0 -48
  37. package/src/devcouncil/cli/commands/go.py +0 -656
  38. package/src/devcouncil/cli/commands/handoff.py +0 -69
  39. package/src/devcouncil/cli/commands/hook.py +0 -237
  40. package/src/devcouncil/cli/commands/init.py +0 -289
  41. package/src/devcouncil/cli/commands/integrate.py +0 -1515
  42. package/src/devcouncil/cli/commands/lsp.py +0 -20
  43. package/src/devcouncil/cli/commands/map.py +0 -112
  44. package/src/devcouncil/cli/commands/mcp_server.py +0 -18
  45. package/src/devcouncil/cli/commands/plan.py +0 -488
  46. package/src/devcouncil/cli/commands/prompt.py +0 -61
  47. package/src/devcouncil/cli/commands/repair.py +0 -89
  48. package/src/devcouncil/cli/commands/report.py +0 -137
  49. package/src/devcouncil/cli/commands/reset_demo_state.py +0 -33
  50. package/src/devcouncil/cli/commands/rollback.py +0 -59
  51. package/src/devcouncil/cli/commands/run.py +0 -289
  52. package/src/devcouncil/cli/commands/runs.py +0 -223
  53. package/src/devcouncil/cli/commands/scaffold.py +0 -32
  54. package/src/devcouncil/cli/commands/semantic.py +0 -47
  55. package/src/devcouncil/cli/commands/setup.py +0 -362
  56. package/src/devcouncil/cli/commands/shell.py +0 -73
  57. package/src/devcouncil/cli/commands/show.py +0 -76
  58. package/src/devcouncil/cli/commands/skills.py +0 -88
  59. package/src/devcouncil/cli/commands/status.py +0 -141
  60. package/src/devcouncil/cli/commands/tasks.py +0 -55
  61. package/src/devcouncil/cli/commands/trace.py +0 -88
  62. package/src/devcouncil/cli/commands/verify.py +0 -328
  63. package/src/devcouncil/cli/commands/version.py +0 -20
  64. package/src/devcouncil/cli/commands/watch.py +0 -574
  65. package/src/devcouncil/cli/commands/watch_fs.py +0 -40
  66. package/src/devcouncil/cli/main.py +0 -137
  67. package/src/devcouncil/council/__init__.py +0 -0
  68. package/src/devcouncil/council/prompts/__init__.py +0 -0
  69. package/src/devcouncil/council/prompts/arbiter.md +0 -19
  70. package/src/devcouncil/council/prompts/critic_a.md +0 -10
  71. package/src/devcouncil/council/prompts/critic_b.md +0 -10
  72. package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
  73. package/src/devcouncil/council/prompts/planner_a.md +0 -16
  74. package/src/devcouncil/council/prompts/planner_b.md +0 -16
  75. package/src/devcouncil/council/prompts/rebuttal.md +0 -10
  76. package/src/devcouncil/council/prompts/spec_writer.md +0 -12
  77. package/src/devcouncil/domain/__init__.py +0 -0
  78. package/src/devcouncil/domain/assumption.py +0 -17
  79. package/src/devcouncil/domain/critique.py +0 -32
  80. package/src/devcouncil/domain/evidence.py +0 -47
  81. package/src/devcouncil/domain/gap.py +0 -52
  82. package/src/devcouncil/domain/requirement.py +0 -22
  83. package/src/devcouncil/domain/task.py +0 -55
  84. package/src/devcouncil/execution/__init__.py +0 -1
  85. package/src/devcouncil/execution/checkpoints.py +0 -246
  86. package/src/devcouncil/execution/context_builder.py +0 -60
  87. package/src/devcouncil/execution/executor.py +0 -15
  88. package/src/devcouncil/execution/fs_watcher.py +0 -180
  89. package/src/devcouncil/execution/handoff.py +0 -102
  90. package/src/devcouncil/execution/hook_policy.py +0 -253
  91. package/src/devcouncil/execution/patch.py +0 -77
  92. package/src/devcouncil/execution/paths.py +0 -14
  93. package/src/devcouncil/execution/permissions.py +0 -85
  94. package/src/devcouncil/execution/policy_engine.py +0 -343
  95. package/src/devcouncil/execution/prompt_builder.py +0 -671
  96. package/src/devcouncil/execution/shell_session.py +0 -225
  97. package/src/devcouncil/execution/task_runner.py +0 -170
  98. package/src/devcouncil/executors/__init__.py +0 -1
  99. package/src/devcouncil/executors/agent_registry.py +0 -575
  100. package/src/devcouncil/executors/coding_cli.py +0 -736
  101. package/src/devcouncil/executors/mini_swe.py +0 -73
  102. package/src/devcouncil/executors/native/__init__.py +0 -0
  103. package/src/devcouncil/executors/native/agent.py +0 -208
  104. package/src/devcouncil/executors/openhands.py +0 -71
  105. package/src/devcouncil/gating/__init__.py +0 -1
  106. package/src/devcouncil/gating/checks/__init__.py +0 -0
  107. package/src/devcouncil/gating/checks/clean_git.py +0 -52
  108. package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
  109. package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
  110. package/src/devcouncil/gating/checks/secret_scan_check.py +0 -53
  111. package/src/devcouncil/gating/policy.py +0 -338
  112. package/src/devcouncil/hardware.py +0 -184
  113. package/src/devcouncil/indexing/__init__.py +0 -1
  114. package/src/devcouncil/indexing/ast_matcher.py +0 -168
  115. package/src/devcouncil/indexing/graph_index.py +0 -48
  116. package/src/devcouncil/indexing/lsp.py +0 -161
  117. package/src/devcouncil/indexing/repo_mapper.py +0 -1455
  118. package/src/devcouncil/indexing/semantic_index.py +0 -205
  119. package/src/devcouncil/integrations/actions.py +0 -146
  120. package/src/devcouncil/integrations/check.py +0 -423
  121. package/src/devcouncil/integrations/code_review_graph.py +0 -163
  122. package/src/devcouncil/integrations/github.py +0 -39
  123. package/src/devcouncil/integrations/github_intent.py +0 -142
  124. package/src/devcouncil/integrations/gitnexus.py +0 -62
  125. package/src/devcouncil/integrations/graphify.py +0 -34
  126. package/src/devcouncil/integrations/mcp/__init__.py +0 -0
  127. package/src/devcouncil/integrations/mcp/server.py +0 -2122
  128. package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +0 -24
  129. package/src/devcouncil/integrations/pr_comments.py +0 -62
  130. package/src/devcouncil/live/__init__.py +0 -2
  131. package/src/devcouncil/live/cards.py +0 -349
  132. package/src/devcouncil/live/models.py +0 -63
  133. package/src/devcouncil/live/repair_prompt.py +0 -83
  134. package/src/devcouncil/live/reviewer.py +0 -70
  135. package/src/devcouncil/live/signals.py +0 -135
  136. package/src/devcouncil/live/summary.py +0 -34
  137. package/src/devcouncil/live/tasks.py +0 -18
  138. package/src/devcouncil/live/transcripts.py +0 -141
  139. package/src/devcouncil/llm/__init__.py +0 -1
  140. package/src/devcouncil/llm/cache.py +0 -42
  141. package/src/devcouncil/llm/model_defaults.yaml +0 -44
  142. package/src/devcouncil/llm/provider.py +0 -627
  143. package/src/devcouncil/llm/router.py +0 -310
  144. package/src/devcouncil/optimization/__init__.py +0 -1
  145. package/src/devcouncil/optimization/gepa_agent.py +0 -318
  146. package/src/devcouncil/planning/__init__.py +0 -1
  147. package/src/devcouncil/planning/arbiter_service.py +0 -57
  148. package/src/devcouncil/planning/correction_manifest.py +0 -303
  149. package/src/devcouncil/planning/critique_service.py +0 -71
  150. package/src/devcouncil/planning/plan_service.py +0 -60
  151. package/src/devcouncil/planning/prompt_enhancer_service.py +0 -167
  152. package/src/devcouncil/planning/repair_service.py +0 -39
  153. package/src/devcouncil/planning/spec_service.py +0 -70
  154. package/src/devcouncil/repo/__init__.py +0 -0
  155. package/src/devcouncil/repo/ci_scaffold.py +0 -157
  156. package/src/devcouncil/repo/gitignore.py +0 -123
  157. package/src/devcouncil/repo/sca.py +0 -374
  158. package/src/devcouncil/reporting/__init__.py +0 -0
  159. package/src/devcouncil/reporting/github_check.py +0 -32
  160. package/src/devcouncil/reporting/json_report.py +0 -30
  161. package/src/devcouncil/reporting/markdown_report.py +0 -83
  162. package/src/devcouncil/reporting/report_builder.py +0 -14
  163. package/src/devcouncil/skills/__init__.py +0 -19
  164. package/src/devcouncil/skills/library/README.md +0 -46
  165. package/src/devcouncil/skills/library/ai-training.md +0 -50
  166. package/src/devcouncil/skills/library/android.md +0 -50
  167. package/src/devcouncil/skills/library/backend.md +0 -52
  168. package/src/devcouncil/skills/library/core-engineering.md +0 -95
  169. package/src/devcouncil/skills/library/data-engineering.md +0 -47
  170. package/src/devcouncil/skills/library/desktop.md +0 -46
  171. package/src/devcouncil/skills/library/devops.md +0 -48
  172. package/src/devcouncil/skills/library/game-dev.md +0 -46
  173. package/src/devcouncil/skills/library/ios.md +0 -48
  174. package/src/devcouncil/skills/library/mobile-cross-platform.md +0 -46
  175. package/src/devcouncil/skills/library/security.md +0 -48
  176. package/src/devcouncil/skills/library/systems.md +0 -48
  177. package/src/devcouncil/skills/library/web.md +0 -47
  178. package/src/devcouncil/skills/library/windows.md +0 -47
  179. package/src/devcouncil/skills/registry.py +0 -330
  180. package/src/devcouncil/storage/__init__.py +0 -0
  181. package/src/devcouncil/storage/db.py +0 -147
  182. package/src/devcouncil/storage/models.py +0 -204
  183. package/src/devcouncil/storage/native.py +0 -557
  184. package/src/devcouncil/storage/repositories.py +0 -485
  185. package/src/devcouncil/telemetry/__init__.py +0 -0
  186. package/src/devcouncil/telemetry/cost.py +0 -140
  187. package/src/devcouncil/telemetry/model_pricing.yaml +0 -48
  188. package/src/devcouncil/telemetry/pricing.py +0 -28
  189. package/src/devcouncil/telemetry/traces.py +0 -146
  190. package/src/devcouncil/telemetry/tracker.py +0 -52
  191. package/src/devcouncil/ui/__init__.py +0 -1
  192. package/src/devcouncil/ui/dashboard.py +0 -423
  193. package/src/devcouncil/utils/__init__.py +0 -1
  194. package/src/devcouncil/utils/redaction.py +0 -147
  195. package/src/devcouncil/utils/subprocess_env.py +0 -69
  196. package/src/devcouncil/verification/__init__.py +0 -1
  197. package/src/devcouncil/verification/acceptance_compiler.py +0 -125
  198. package/src/devcouncil/verification/ad_hoc_check.py +0 -129
  199. package/src/devcouncil/verification/diff_coverage.py +0 -353
  200. package/src/devcouncil/verification/implementation_reviewer.py +0 -55
  201. package/src/devcouncil/verification/next_actions.py +0 -189
  202. package/src/devcouncil/verification/sandbox.py +0 -178
  203. package/src/devcouncil/verification/test_resolver.py +0 -91
  204. package/src/devcouncil/verification/verifier.py +0 -1548
  205. package/uv.lock +0 -1226
@@ -1,135 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import hashlib
4
- import json
5
- from datetime import datetime, timezone
6
- from pathlib import Path
7
- from typing import Any
8
-
9
- from pydantic import BaseModel, Field
10
-
11
-
12
- class ReviewSignal(BaseModel):
13
- client: str = "generic"
14
- payload: dict[str, Any] = Field(default_factory=dict)
15
- transcript_path: str | None = None
16
- session_id: str | None = None
17
- task_id: str | None = None
18
- created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
19
- review_command: str | None = None
20
- path: str | None = None
21
-
22
-
23
- def signal_dir(project_root: Path) -> Path:
24
- return project_root / ".devcouncil" / "live" / "signals"
25
-
26
-
27
- def processed_signal_dir(project_root: Path) -> Path:
28
- return signal_dir(project_root) / "processed"
29
-
30
-
31
- def write_signal(project_root: Path, client: str, payload: dict[str, Any]) -> Path:
32
- directory = signal_dir(project_root)
33
- directory.mkdir(parents=True, exist_ok=True)
34
- transcript_path = extract_transcript_path(payload)
35
- session_id = _string_value(payload, "session_id", "sessionId", "session", "cwd")
36
- task_id = extract_task_id(payload)
37
- signal = ReviewSignal(
38
- client=client.lower(),
39
- payload=payload,
40
- transcript_path=transcript_path,
41
- session_id=session_id,
42
- task_id=task_id,
43
- review_command=_review_command(client.lower(), transcript_path, task_id),
44
- )
45
- key = transcript_path or session_id or json.dumps(payload, sort_keys=True, default=str)
46
-
47
- digest = hashlib.sha256(key.encode("utf-8", errors="replace")).hexdigest()[:12]
48
- path = directory / f"{client.lower()}-{digest}.json"
49
- signal.path = str(path)
50
- path.write_text(signal.model_dump_json(indent=2) + "\n", encoding="utf-8")
51
- return path
52
-
53
-
54
- def load_signals(project_root: Path) -> list[ReviewSignal]:
55
- directory = signal_dir(project_root)
56
- if not directory.exists():
57
- return []
58
- signals: list[ReviewSignal] = []
59
- for path in sorted(directory.glob("*.json"), key=lambda item: item.stat().st_mtime, reverse=True):
60
- try:
61
- raw = json.loads(path.read_text(encoding="utf-8"))
62
- signal = ReviewSignal.model_validate(raw)
63
- signal.path = str(path)
64
- signals.append(signal)
65
- except Exception:
66
- continue
67
- return signals
68
-
69
-
70
- def mark_processed(signal: ReviewSignal, project_root: Path) -> Path | None:
71
- if not signal.path:
72
- return None
73
- source = Path(signal.path)
74
- if not source.exists():
75
- return None
76
- target_dir = processed_signal_dir(project_root)
77
- target_dir.mkdir(parents=True, exist_ok=True)
78
- target = target_dir / source.name
79
- if target.exists():
80
- target.unlink()
81
- source.replace(target)
82
- return target
83
-
84
-
85
- def extract_transcript_path(payload: dict[str, Any]) -> str | None:
86
- direct = _string_value(
87
- payload,
88
- "transcript_path",
89
- "transcriptPath",
90
- "transcript",
91
- "conversation_path",
92
- "conversationPath",
93
- "file",
94
- "path",
95
- )
96
- if direct:
97
- return direct
98
- for key in ("session", "message", "hook_event", "event"):
99
- nested = payload.get(key)
100
- if isinstance(nested, dict):
101
- value = extract_transcript_path(nested)
102
- if value:
103
- return value
104
- return None
105
-
106
-
107
- def extract_task_id(payload: dict[str, Any]) -> str | None:
108
- direct = _string_value(payload, "task_id", "taskId", "task", "active_task", "activeTask")
109
- if direct:
110
- return direct
111
- for key in ("session", "message", "hook_event", "event", "metadata"):
112
- nested = payload.get(key)
113
- if isinstance(nested, dict):
114
- value = extract_task_id(nested)
115
- if value:
116
- return value
117
- return None
118
-
119
-
120
- def _string_value(payload: dict[str, Any], *keys: str) -> str | None:
121
- for key in keys:
122
- value = payload.get(key)
123
- if isinstance(value, str) and value.strip():
124
- return value
125
- return None
126
-
127
-
128
- def _review_command(client: str, transcript_path: str | None, task_id: str | None = None) -> str:
129
- if transcript_path:
130
- command = f"dev watch review --client {client} --transcript {transcript_path}"
131
- else:
132
- command = f"dev watch pending --client {client}"
133
- if task_id:
134
- command += f" --task-id {task_id}"
135
- return command
@@ -1,34 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pathlib import Path
4
-
5
- from devcouncil.live.cards import load_cards, unresolved_blocking_cards
6
- from devcouncil.live.signals import load_signals
7
- from devcouncil.live.tasks import active_task_id
8
-
9
-
10
- def live_review_summary(project_root: Path, task_id: str | None = None) -> dict:
11
- cards = load_cards(project_root)
12
- signals = load_signals(project_root)
13
- active_id = active_task_id(project_root)
14
- scoped_task_id = task_id or active_id
15
- blockers = unresolved_blocking_cards(project_root, task_id=scoped_task_id)
16
- pending_signal_items = [signal.model_dump() for signal in signals]
17
- return {
18
- "active_task_id": active_id,
19
- "scope_task_id": scoped_task_id,
20
- "pending_signals": len(signals),
21
- "pending_signal_items": pending_signal_items[:10],
22
- "cards": {
23
- "total": len(cards),
24
- "open": len([card for card in cards if card.status == "open"]),
25
- "resolved": len([card for card in cards if card.status == "resolved"]),
26
- "ignored": len([card for card in cards if card.status == "ignored"]),
27
- "critical_open": len([
28
- card for card in cards
29
- if card.status == "open" and card.verdict == "Critical Issues"
30
- ]),
31
- },
32
- "blocking_cards": [card.model_dump() for card in blockers],
33
- "recent_cards": [card.model_dump() for card in cards[:5]],
34
- }
@@ -1,18 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pathlib import Path
4
-
5
- from devcouncil.storage.db import get_db
6
- from devcouncil.storage.repositories import TaskRepository
7
-
8
-
9
- def active_task_id(project_root: Path) -> str | None:
10
- """Return the single running DevCouncil task ID, if one is unambiguous."""
11
- db = get_db(project_root)
12
- if not db:
13
- return None
14
- with db.get_session() as session:
15
- running = [task for task in TaskRepository(session).get_all() if task.status == "running"]
16
- if len(running) != 1:
17
- return None
18
- return running[0].id
@@ -1,141 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from pathlib import Path
5
- from typing import Any, Iterable, Literal
6
-
7
- from devcouncil.live.models import AgentSession, AgentTurn, session_id_from_path
8
-
9
- RoleName = Literal["user", "assistant", "system", "tool", "unknown"]
10
- KNOWN_ROLES: set[RoleName] = {"user", "assistant", "system", "tool"}
11
-
12
-
13
- CLAUDE_TRANSCRIPT_ROOT = Path.home() / ".claude" / "projects"
14
-
15
-
16
- def discover_sessions(project_root: Path, client: str = "claude") -> list[AgentSession]:
17
- """Find local coding-agent transcripts DevCouncil can review."""
18
- client = client.lower()
19
- if client == "claude":
20
- candidates = _claude_transcript_candidates(project_root)
21
- else:
22
- candidates = sorted((project_root / ".devcouncil" / "live" / client).glob("*.jsonl"))
23
-
24
- sessions: list[AgentSession] = []
25
- for path in candidates:
26
- if not path.exists() or not path.is_file():
27
- continue
28
- stat = path.stat()
29
- sessions.append(AgentSession(
30
- id=session_id_from_path(path),
31
- client=client,
32
- transcript_path=str(path),
33
- updated_at=str(stat.st_mtime),
34
- turns=sum(1 for _ in _safe_lines(path)),
35
- ))
36
- return sorted(sessions, key=lambda item: item.updated_at or "", reverse=True)
37
-
38
-
39
- def load_turns(path: Path, client: str = "generic") -> list[AgentTurn]:
40
- """Parse a transcript into normalized turns.
41
-
42
- Supports Claude Code JSONL plus generic JSONL records with role/content fields.
43
- """
44
- turns: list[AgentTurn] = []
45
- session_id = session_id_from_path(path)
46
- for index, raw in enumerate(_read_jsonl(path)):
47
- turn = _turn_from_record(raw, session_id=session_id, turn_index=index, client=client)
48
- if turn and turn.content.strip():
49
- turns.append(turn)
50
- return turns
51
-
52
-
53
- def latest_assistant_turn(path: Path, client: str = "generic") -> AgentTurn | None:
54
- for turn in reversed(load_turns(path, client=client)):
55
- if turn.role == "assistant":
56
- return turn
57
- return None
58
-
59
-
60
- def _claude_transcript_candidates(project_root: Path) -> list[Path]:
61
- local_runtime = project_root / ".devcouncil" / "live" / "claude"
62
- candidates = list(local_runtime.glob("*.jsonl"))
63
- if CLAUDE_TRANSCRIPT_ROOT.exists():
64
- candidates.extend(CLAUDE_TRANSCRIPT_ROOT.rglob("*.jsonl"))
65
- return sorted(set(candidates), key=lambda path: path.stat().st_mtime if path.exists() else 0, reverse=True)
66
-
67
-
68
- def _safe_lines(path: Path) -> Iterable[str]:
69
- try:
70
- return path.read_text(encoding="utf-8", errors="replace").splitlines()
71
- except OSError:
72
- return []
73
-
74
-
75
- def _read_jsonl(path: Path) -> Iterable[dict[str, Any]]:
76
- for line in _safe_lines(path):
77
- if not line.strip():
78
- continue
79
- try:
80
- value = json.loads(line)
81
- except json.JSONDecodeError:
82
- continue
83
- if isinstance(value, dict):
84
- yield value
85
-
86
-
87
- def _turn_from_record(raw: dict[str, Any], session_id: str, turn_index: int, client: str) -> AgentTurn | None:
88
- role = _role(raw)
89
- content = _content(raw)
90
- if not content:
91
- return None
92
- turn_id = str(raw.get("uuid") or raw.get("id") or raw.get("message_id") or f"turn-{turn_index}")
93
- return AgentTurn(
94
- session_id=str(raw.get("sessionId") or raw.get("session_id") or session_id),
95
- turn_id=turn_id,
96
- source=client,
97
- role=role,
98
- content=content,
99
- timestamp=raw.get("timestamp") or raw.get("created_at"),
100
- raw=raw,
101
- )
102
-
103
-
104
- def _role(raw: dict[str, Any]) -> RoleName:
105
- role = raw.get("role")
106
- if isinstance(role, str):
107
- return role if role in KNOWN_ROLES else "unknown"
108
- message = raw.get("message")
109
- if isinstance(message, dict):
110
- nested = message.get("role")
111
- if isinstance(nested, str):
112
- return nested if nested in KNOWN_ROLES else "unknown"
113
- record_type = raw.get("type")
114
- if record_type in KNOWN_ROLES:
115
- return record_type
116
- return "unknown"
117
-
118
-
119
- def _content(raw: dict[str, Any]) -> str:
120
- direct = raw.get("content") or raw.get("text")
121
- if isinstance(direct, str):
122
- return direct
123
- message = raw.get("message")
124
- if isinstance(message, dict):
125
- nested = message.get("content")
126
- if isinstance(nested, str):
127
- return nested
128
- if isinstance(nested, list):
129
- return "\n".join(_content_block_text(block) for block in nested).strip()
130
- if isinstance(direct, list):
131
- return "\n".join(_content_block_text(block) for block in direct).strip()
132
- return ""
133
-
134
-
135
- def _content_block_text(block: Any) -> str:
136
- if isinstance(block, str):
137
- return block
138
- if isinstance(block, dict):
139
- value = block.get("text") or block.get("content")
140
- return value if isinstance(value, str) else ""
141
- return ""
@@ -1 +0,0 @@
1
-
@@ -1,42 +0,0 @@
1
- import json
2
- import hashlib
3
- from pathlib import Path
4
- from typing import Optional
5
- from devcouncil.llm.provider import LLMResponse
6
-
7
- class LLMCache:
8
- def __init__(self, project_root: Path):
9
- self.cache_dir = project_root / ".devcouncil" / "cache" / "llm"
10
- self.cache_dir.mkdir(parents=True, exist_ok=True)
11
-
12
- def _get_key(self, model: str, messages: list, temp: float, json_mode: bool, provider_fingerprint: str = "") -> str:
13
- data = {
14
- "model": model,
15
- "messages": messages,
16
- "temp": temp,
17
- "json_mode": json_mode,
18
- # Provider-specific knobs that change the output for an identical prompt
19
- # (e.g. Ollama's num_ctx / base_url). Empty for providers without such knobs,
20
- # so their cache keys are unchanged.
21
- "provider": provider_fingerprint,
22
- }
23
- s = json.dumps(data, sort_keys=True)
24
- return hashlib.sha256(s.encode("utf-8")).hexdigest()
25
-
26
- def get(self, model: str, messages: list, temp: float, json_mode: bool, provider_fingerprint: str = "") -> Optional[LLMResponse]:
27
- key = self._get_key(model, messages, temp, json_mode, provider_fingerprint)
28
- cache_file = self.cache_dir / f"{key}.json"
29
- if cache_file.exists():
30
- try:
31
- with open(cache_file, "r") as f:
32
- data = json.load(f)
33
- return LLMResponse(**data)
34
- except Exception:
35
- pass
36
- return None
37
-
38
- def set(self, model: str, messages: list, temp: float, json_mode: bool, response: LLMResponse, provider_fingerprint: str = ""):
39
- key = self._get_key(model, messages, temp, json_mode, provider_fingerprint)
40
- cache_file = self.cache_dir / f"{key}.json"
41
- with open(cache_file, "w") as f:
42
- json.dump(response.model_dump(), f)
@@ -1,44 +0,0 @@
1
- openrouter:
2
- spec_writer: anthropic/claude-sonnet-4.6
3
- prompt_enhancer: anthropic/claude-sonnet-4.6
4
- planner_a: anthropic/claude-sonnet-4.6
5
- planner_b: google/gemini-2.5-pro
6
- critic_a: openai/gpt-5.5
7
- critic_b: anthropic/claude-opus-4.8
8
- arbiter: openai/gpt-5.5
9
- native_agent: anthropic/claude-sonnet-4.6
10
- implementation_reviewer: openai/gpt-5.5
11
- live_reviewer: openai/gpt-5.5
12
- vertexai:
13
- spec_writer: google/gemini-2.5-flash
14
- prompt_enhancer: google/gemini-2.5-flash
15
- planner_a: google/gemini-2.5-flash
16
- planner_b: google/gemini-2.5-flash
17
- critic_a: google/gemini-2.5-flash
18
- critic_b: google/gemini-2.5-flash
19
- arbiter: google/gemini-2.5-flash
20
- native_agent: google/gemini-2.5-flash
21
- implementation_reviewer: google/gemini-2.5-flash
22
- live_reviewer: google/gemini-2.5-flash
23
- doubleword:
24
- spec_writer: deepseek/deepseek-v4
25
- prompt_enhancer: deepseek/deepseek-v4
26
- planner_a: deepseek/deepseek-v4
27
- planner_b: deepseek/deepseek-v4
28
- critic_a: deepseek/deepseek-v4
29
- critic_b: deepseek/deepseek-v4
30
- arbiter: deepseek/deepseek-v4
31
- native_agent: deepseek/deepseek-v4
32
- implementation_reviewer: deepseek/deepseek-v4
33
- live_reviewer: deepseek/deepseek-v4
34
- ollama:
35
- spec_writer: qwen2.5-coder:7b
36
- prompt_enhancer: qwen2.5-coder:7b
37
- planner_a: qwen2.5-coder:7b
38
- planner_b: qwen2.5-coder:7b
39
- critic_a: qwen2.5-coder:7b
40
- critic_b: qwen2.5-coder:7b
41
- arbiter: qwen2.5-coder:7b
42
- native_agent: qwen2.5-coder:7b
43
- implementation_reviewer: qwen2.5-coder:7b
44
- live_reviewer: qwen2.5-coder:7b