devcouncil 0.2.0 → 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.
- package/README.md +191 -253
- package/bin/devcouncil.js +130 -32
- package/package.json +6 -17
- package/pyproject.toml +0 -66
- package/src/devcouncil/__init__.py +0 -0
- package/src/devcouncil/__main__.py +0 -4
- package/src/devcouncil/app/__init__.py +0 -28
- package/src/devcouncil/app/config.py +0 -320
- package/src/devcouncil/app/errors.py +0 -23
- package/src/devcouncil/app/events.py +0 -44
- package/src/devcouncil/app/orchestrator.py +0 -92
- package/src/devcouncil/app/project_status.py +0 -29
- package/src/devcouncil/app/run_context.py +0 -39
- package/src/devcouncil/app/state_machine.py +0 -108
- package/src/devcouncil/artifacts/__init__.py +0 -1
- package/src/devcouncil/artifacts/coverage.py +0 -96
- package/src/devcouncil/artifacts/graph.py +0 -163
- package/src/devcouncil/artifacts/migrations.py +0 -20
- package/src/devcouncil/artifacts/schemas.py +0 -23
- package/src/devcouncil/artifacts/serializer.py +0 -21
- package/src/devcouncil/artifacts/validators.py +0 -27
- package/src/devcouncil/assets/__init__.py +0 -1
- package/src/devcouncil/assets/devcouncil-logo.svg +0 -60
- package/src/devcouncil/assets/devcouncil_logo_premium.png +0 -0
- package/src/devcouncil/cli/__init__.py +0 -0
- package/src/devcouncil/cli/commands/__init__.py +0 -0
- package/src/devcouncil/cli/commands/agents.py +0 -292
- package/src/devcouncil/cli/commands/artifacts.py +0 -54
- package/src/devcouncil/cli/commands/ast.py +0 -22
- package/src/devcouncil/cli/commands/baseline.py +0 -35
- package/src/devcouncil/cli/commands/check.py +0 -209
- package/src/devcouncil/cli/commands/config.py +0 -115
- package/src/devcouncil/cli/commands/cost.py +0 -57
- package/src/devcouncil/cli/commands/dashboard.py +0 -31
- package/src/devcouncil/cli/commands/doctor.py +0 -340
- package/src/devcouncil/cli/commands/evidence.py +0 -48
- package/src/devcouncil/cli/commands/go.py +0 -656
- package/src/devcouncil/cli/commands/handoff.py +0 -69
- package/src/devcouncil/cli/commands/hook.py +0 -237
- package/src/devcouncil/cli/commands/init.py +0 -289
- package/src/devcouncil/cli/commands/integrate.py +0 -1515
- package/src/devcouncil/cli/commands/lsp.py +0 -20
- package/src/devcouncil/cli/commands/map.py +0 -112
- package/src/devcouncil/cli/commands/mcp_server.py +0 -18
- package/src/devcouncil/cli/commands/plan.py +0 -488
- package/src/devcouncil/cli/commands/prompt.py +0 -61
- package/src/devcouncil/cli/commands/repair.py +0 -89
- package/src/devcouncil/cli/commands/report.py +0 -137
- package/src/devcouncil/cli/commands/reset_demo_state.py +0 -33
- package/src/devcouncil/cli/commands/rollback.py +0 -59
- package/src/devcouncil/cli/commands/run.py +0 -289
- package/src/devcouncil/cli/commands/runs.py +0 -223
- package/src/devcouncil/cli/commands/scaffold.py +0 -32
- package/src/devcouncil/cli/commands/semantic.py +0 -47
- package/src/devcouncil/cli/commands/setup.py +0 -362
- package/src/devcouncil/cli/commands/shell.py +0 -73
- package/src/devcouncil/cli/commands/show.py +0 -76
- package/src/devcouncil/cli/commands/skills.py +0 -88
- package/src/devcouncil/cli/commands/status.py +0 -141
- package/src/devcouncil/cli/commands/tasks.py +0 -55
- package/src/devcouncil/cli/commands/trace.py +0 -88
- package/src/devcouncil/cli/commands/verify.py +0 -328
- package/src/devcouncil/cli/commands/version.py +0 -20
- package/src/devcouncil/cli/commands/watch.py +0 -574
- package/src/devcouncil/cli/commands/watch_fs.py +0 -40
- package/src/devcouncil/cli/main.py +0 -137
- package/src/devcouncil/council/__init__.py +0 -0
- package/src/devcouncil/council/prompts/__init__.py +0 -0
- package/src/devcouncil/council/prompts/arbiter.md +0 -19
- package/src/devcouncil/council/prompts/critic_a.md +0 -10
- package/src/devcouncil/council/prompts/critic_b.md +0 -10
- package/src/devcouncil/council/prompts/implementation_reviewer.md +0 -16
- package/src/devcouncil/council/prompts/planner_a.md +0 -16
- package/src/devcouncil/council/prompts/planner_b.md +0 -16
- package/src/devcouncil/council/prompts/rebuttal.md +0 -10
- package/src/devcouncil/council/prompts/spec_writer.md +0 -12
- package/src/devcouncil/domain/__init__.py +0 -0
- package/src/devcouncil/domain/assumption.py +0 -17
- package/src/devcouncil/domain/critique.py +0 -32
- package/src/devcouncil/domain/evidence.py +0 -47
- package/src/devcouncil/domain/gap.py +0 -52
- package/src/devcouncil/domain/requirement.py +0 -22
- package/src/devcouncil/domain/task.py +0 -55
- package/src/devcouncil/execution/__init__.py +0 -1
- package/src/devcouncil/execution/checkpoints.py +0 -246
- package/src/devcouncil/execution/context_builder.py +0 -60
- package/src/devcouncil/execution/executor.py +0 -15
- package/src/devcouncil/execution/fs_watcher.py +0 -180
- package/src/devcouncil/execution/handoff.py +0 -102
- package/src/devcouncil/execution/hook_policy.py +0 -253
- package/src/devcouncil/execution/patch.py +0 -77
- package/src/devcouncil/execution/paths.py +0 -14
- package/src/devcouncil/execution/permissions.py +0 -85
- package/src/devcouncil/execution/policy_engine.py +0 -343
- package/src/devcouncil/execution/prompt_builder.py +0 -671
- package/src/devcouncil/execution/shell_session.py +0 -225
- package/src/devcouncil/execution/task_runner.py +0 -170
- package/src/devcouncil/executors/__init__.py +0 -1
- package/src/devcouncil/executors/agent_registry.py +0 -575
- package/src/devcouncil/executors/coding_cli.py +0 -736
- package/src/devcouncil/executors/mini_swe.py +0 -73
- package/src/devcouncil/executors/native/__init__.py +0 -0
- package/src/devcouncil/executors/native/agent.py +0 -208
- package/src/devcouncil/executors/openhands.py +0 -71
- package/src/devcouncil/gating/__init__.py +0 -1
- package/src/devcouncil/gating/checks/__init__.py +0 -0
- package/src/devcouncil/gating/checks/clean_git.py +0 -52
- package/src/devcouncil/gating/checks/planned_files_check.py +0 -32
- package/src/devcouncil/gating/checks/requirement_coverage.py +0 -26
- package/src/devcouncil/gating/checks/secret_scan_check.py +0 -53
- package/src/devcouncil/gating/policy.py +0 -338
- package/src/devcouncil/hardware.py +0 -184
- package/src/devcouncil/indexing/__init__.py +0 -1
- package/src/devcouncil/indexing/ast_matcher.py +0 -168
- package/src/devcouncil/indexing/graph_index.py +0 -48
- package/src/devcouncil/indexing/lsp.py +0 -161
- package/src/devcouncil/indexing/repo_mapper.py +0 -1455
- package/src/devcouncil/indexing/semantic_index.py +0 -205
- package/src/devcouncil/integrations/actions.py +0 -146
- package/src/devcouncil/integrations/check.py +0 -423
- package/src/devcouncil/integrations/code_review_graph.py +0 -163
- package/src/devcouncil/integrations/github.py +0 -39
- package/src/devcouncil/integrations/github_intent.py +0 -142
- package/src/devcouncil/integrations/gitnexus.py +0 -62
- package/src/devcouncil/integrations/graphify.py +0 -34
- package/src/devcouncil/integrations/mcp/__init__.py +0 -0
- package/src/devcouncil/integrations/mcp/server.py +0 -2122
- package/src/devcouncil/integrations/opencode_devcouncil_plugin.mjs +0 -24
- package/src/devcouncil/integrations/pr_comments.py +0 -62
- package/src/devcouncil/live/__init__.py +0 -2
- package/src/devcouncil/live/cards.py +0 -349
- package/src/devcouncil/live/models.py +0 -63
- package/src/devcouncil/live/repair_prompt.py +0 -83
- package/src/devcouncil/live/reviewer.py +0 -70
- package/src/devcouncil/live/signals.py +0 -135
- package/src/devcouncil/live/summary.py +0 -34
- package/src/devcouncil/live/tasks.py +0 -18
- package/src/devcouncil/live/transcripts.py +0 -141
- package/src/devcouncil/llm/__init__.py +0 -1
- package/src/devcouncil/llm/cache.py +0 -42
- package/src/devcouncil/llm/model_defaults.yaml +0 -44
- package/src/devcouncil/llm/provider.py +0 -627
- package/src/devcouncil/llm/router.py +0 -310
- package/src/devcouncil/optimization/__init__.py +0 -1
- package/src/devcouncil/optimization/gepa_agent.py +0 -318
- package/src/devcouncil/planning/__init__.py +0 -1
- package/src/devcouncil/planning/arbiter_service.py +0 -57
- package/src/devcouncil/planning/correction_manifest.py +0 -303
- package/src/devcouncil/planning/critique_service.py +0 -71
- package/src/devcouncil/planning/plan_service.py +0 -60
- package/src/devcouncil/planning/prompt_enhancer_service.py +0 -167
- package/src/devcouncil/planning/repair_service.py +0 -39
- package/src/devcouncil/planning/spec_service.py +0 -70
- package/src/devcouncil/repo/__init__.py +0 -0
- package/src/devcouncil/repo/ci_scaffold.py +0 -157
- package/src/devcouncil/repo/gitignore.py +0 -123
- package/src/devcouncil/repo/sca.py +0 -374
- package/src/devcouncil/reporting/__init__.py +0 -0
- package/src/devcouncil/reporting/github_check.py +0 -32
- package/src/devcouncil/reporting/json_report.py +0 -30
- package/src/devcouncil/reporting/markdown_report.py +0 -83
- package/src/devcouncil/reporting/report_builder.py +0 -14
- package/src/devcouncil/skills/__init__.py +0 -19
- package/src/devcouncil/skills/library/README.md +0 -46
- package/src/devcouncil/skills/library/ai-training.md +0 -50
- package/src/devcouncil/skills/library/android.md +0 -50
- package/src/devcouncil/skills/library/backend.md +0 -52
- package/src/devcouncil/skills/library/core-engineering.md +0 -95
- package/src/devcouncil/skills/library/data-engineering.md +0 -47
- package/src/devcouncil/skills/library/desktop.md +0 -46
- package/src/devcouncil/skills/library/devops.md +0 -48
- package/src/devcouncil/skills/library/game-dev.md +0 -46
- package/src/devcouncil/skills/library/ios.md +0 -48
- package/src/devcouncil/skills/library/mobile-cross-platform.md +0 -46
- package/src/devcouncil/skills/library/security.md +0 -48
- package/src/devcouncil/skills/library/systems.md +0 -48
- package/src/devcouncil/skills/library/web.md +0 -47
- package/src/devcouncil/skills/library/windows.md +0 -47
- package/src/devcouncil/skills/registry.py +0 -330
- package/src/devcouncil/storage/__init__.py +0 -0
- package/src/devcouncil/storage/db.py +0 -147
- package/src/devcouncil/storage/models.py +0 -204
- package/src/devcouncil/storage/native.py +0 -557
- package/src/devcouncil/storage/repositories.py +0 -485
- package/src/devcouncil/telemetry/__init__.py +0 -0
- package/src/devcouncil/telemetry/cost.py +0 -140
- package/src/devcouncil/telemetry/model_pricing.yaml +0 -48
- package/src/devcouncil/telemetry/pricing.py +0 -28
- package/src/devcouncil/telemetry/traces.py +0 -146
- package/src/devcouncil/telemetry/tracker.py +0 -52
- package/src/devcouncil/ui/__init__.py +0 -1
- package/src/devcouncil/ui/dashboard.py +0 -423
- package/src/devcouncil/utils/__init__.py +0 -1
- package/src/devcouncil/utils/redaction.py +0 -147
- package/src/devcouncil/utils/subprocess_env.py +0 -69
- package/src/devcouncil/verification/__init__.py +0 -1
- package/src/devcouncil/verification/acceptance_compiler.py +0 -125
- package/src/devcouncil/verification/ad_hoc_check.py +0 -129
- package/src/devcouncil/verification/diff_coverage.py +0 -353
- package/src/devcouncil/verification/implementation_reviewer.py +0 -55
- package/src/devcouncil/verification/next_actions.py +0 -189
- package/src/devcouncil/verification/sandbox.py +0 -178
- package/src/devcouncil/verification/test_resolver.py +0 -91
- package/src/devcouncil/verification/verifier.py +0 -1548
- package/uv.lock +0 -1226
|
@@ -1,485 +0,0 @@
|
|
|
1
|
-
from sqlmodel import Session, col, delete, select
|
|
2
|
-
from typing import List, Optional, Any
|
|
3
|
-
import json
|
|
4
|
-
from devcouncil.storage.models import (
|
|
5
|
-
RequirementModel,
|
|
6
|
-
AssumptionModel,
|
|
7
|
-
TaskModel,
|
|
8
|
-
GapModel,
|
|
9
|
-
EvidenceModel,
|
|
10
|
-
CritiqueFindingModel,
|
|
11
|
-
ProjectStateModel,
|
|
12
|
-
)
|
|
13
|
-
from devcouncil.domain.requirement import Requirement, AcceptanceCriterion
|
|
14
|
-
from devcouncil.domain.assumption import Assumption
|
|
15
|
-
from devcouncil.domain.task import Task, PlannedFile
|
|
16
|
-
from devcouncil.domain.gap import Gap
|
|
17
|
-
from devcouncil.domain.evidence import CommandResult, DiffEvidence, DiffCoverageEvidence, TestEvidence
|
|
18
|
-
from devcouncil.domain.critique import CritiqueFinding
|
|
19
|
-
from devcouncil.artifacts.graph import ArtifactGraph
|
|
20
|
-
|
|
21
|
-
class RequirementRepository:
|
|
22
|
-
def __init__(self, session: Session):
|
|
23
|
-
self.session = session
|
|
24
|
-
|
|
25
|
-
def get_all(self) -> List[Requirement]:
|
|
26
|
-
statement = select(RequirementModel)
|
|
27
|
-
models = self.session.exec(statement).all()
|
|
28
|
-
results = []
|
|
29
|
-
for m in models:
|
|
30
|
-
ac_list = [AcceptanceCriterion.model_validate(ac) for ac in json.loads(m.acceptance_criteria_json)]
|
|
31
|
-
results.append(Requirement.model_validate({
|
|
32
|
-
"id": m.id,
|
|
33
|
-
"title": m.title,
|
|
34
|
-
"description": m.description,
|
|
35
|
-
"priority": m.priority,
|
|
36
|
-
"source": m.source,
|
|
37
|
-
"acceptance_criteria": ac_list,
|
|
38
|
-
}))
|
|
39
|
-
return results
|
|
40
|
-
|
|
41
|
-
def save(self, req: Requirement):
|
|
42
|
-
model = RequirementModel(
|
|
43
|
-
id=req.id,
|
|
44
|
-
title=req.title,
|
|
45
|
-
description=req.description,
|
|
46
|
-
priority=req.priority,
|
|
47
|
-
source=req.source,
|
|
48
|
-
acceptance_criteria_json=json.dumps([ac.model_dump() for ac in req.acceptance_criteria])
|
|
49
|
-
)
|
|
50
|
-
self.session.merge(model)
|
|
51
|
-
self.session.commit()
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class AssumptionRepository:
|
|
55
|
-
def __init__(self, session: Session):
|
|
56
|
-
self.session = session
|
|
57
|
-
|
|
58
|
-
def get_all(self) -> List[Assumption]:
|
|
59
|
-
statement = select(AssumptionModel)
|
|
60
|
-
models = self.session.exec(statement).all()
|
|
61
|
-
return [
|
|
62
|
-
Assumption.model_validate({
|
|
63
|
-
"id": m.id,
|
|
64
|
-
"statement": m.statement,
|
|
65
|
-
"confidence": m.confidence,
|
|
66
|
-
"impact": m.impact,
|
|
67
|
-
"reversible": m.reversible,
|
|
68
|
-
"requires_user_confirmation": m.requires_user_confirmation,
|
|
69
|
-
"linked_requirement_ids": json.loads(m.linked_requirement_ids_json),
|
|
70
|
-
"status": m.status,
|
|
71
|
-
})
|
|
72
|
-
for m in models
|
|
73
|
-
]
|
|
74
|
-
|
|
75
|
-
def save(self, assumption: Assumption):
|
|
76
|
-
model = AssumptionModel(
|
|
77
|
-
id=assumption.id,
|
|
78
|
-
statement=assumption.statement,
|
|
79
|
-
confidence=assumption.confidence,
|
|
80
|
-
impact=assumption.impact,
|
|
81
|
-
reversible=assumption.reversible,
|
|
82
|
-
requires_user_confirmation=assumption.requires_user_confirmation,
|
|
83
|
-
linked_requirement_ids_json=json.dumps(assumption.linked_requirement_ids),
|
|
84
|
-
status=assumption.status,
|
|
85
|
-
)
|
|
86
|
-
self.session.merge(model)
|
|
87
|
-
self.session.commit()
|
|
88
|
-
|
|
89
|
-
class TaskRepository:
|
|
90
|
-
def __init__(self, session: Session):
|
|
91
|
-
self.session = session
|
|
92
|
-
|
|
93
|
-
def get_by_id(self, task_id: str) -> Optional[Task]:
|
|
94
|
-
statement = select(TaskModel).where(TaskModel.id == task_id)
|
|
95
|
-
m = self.session.exec(statement).first()
|
|
96
|
-
if not m:
|
|
97
|
-
return None
|
|
98
|
-
|
|
99
|
-
pf_list = [PlannedFile.model_validate(pf) for pf in json.loads(m.planned_files_json)]
|
|
100
|
-
return Task.model_validate({
|
|
101
|
-
"id": m.id,
|
|
102
|
-
"title": m.title,
|
|
103
|
-
"description": m.description,
|
|
104
|
-
"requirement_ids": json.loads(m.requirement_ids_json),
|
|
105
|
-
"acceptance_criterion_ids": json.loads(m.acceptance_criterion_ids_json),
|
|
106
|
-
"planned_files": pf_list,
|
|
107
|
-
"expected_tests": json.loads(m.expected_tests_json),
|
|
108
|
-
"allowed_commands": json.loads(m.allowed_commands_json),
|
|
109
|
-
"forbidden_changes": json.loads(m.forbidden_changes_json),
|
|
110
|
-
"status": m.status,
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
def get_all(self) -> List[Task]:
|
|
114
|
-
statement = select(TaskModel)
|
|
115
|
-
models = self.session.exec(statement).all()
|
|
116
|
-
results = []
|
|
117
|
-
for m in models:
|
|
118
|
-
pf_list = [PlannedFile.model_validate(pf) for pf in json.loads(m.planned_files_json)]
|
|
119
|
-
results.append(Task.model_validate({
|
|
120
|
-
"id": m.id,
|
|
121
|
-
"title": m.title,
|
|
122
|
-
"description": m.description,
|
|
123
|
-
"requirement_ids": json.loads(m.requirement_ids_json),
|
|
124
|
-
"acceptance_criterion_ids": json.loads(m.acceptance_criterion_ids_json),
|
|
125
|
-
"planned_files": pf_list,
|
|
126
|
-
"expected_tests": json.loads(m.expected_tests_json),
|
|
127
|
-
"allowed_commands": json.loads(m.allowed_commands_json),
|
|
128
|
-
"forbidden_changes": json.loads(m.forbidden_changes_json),
|
|
129
|
-
"status": m.status,
|
|
130
|
-
}))
|
|
131
|
-
return results
|
|
132
|
-
|
|
133
|
-
def save(self, task: Task):
|
|
134
|
-
model = TaskModel(
|
|
135
|
-
id=task.id,
|
|
136
|
-
title=task.title,
|
|
137
|
-
description=task.description,
|
|
138
|
-
requirement_ids_json=json.dumps(task.requirement_ids),
|
|
139
|
-
acceptance_criterion_ids_json=json.dumps(task.acceptance_criterion_ids),
|
|
140
|
-
planned_files_json=json.dumps([pf.model_dump() for pf in task.planned_files]),
|
|
141
|
-
expected_tests_json=json.dumps(task.expected_tests),
|
|
142
|
-
allowed_commands_json=json.dumps(task.allowed_commands),
|
|
143
|
-
forbidden_changes_json=json.dumps(task.forbidden_changes),
|
|
144
|
-
status=task.status
|
|
145
|
-
)
|
|
146
|
-
self.session.merge(model)
|
|
147
|
-
self.session.commit()
|
|
148
|
-
|
|
149
|
-
class GapRepository:
|
|
150
|
-
def __init__(self, session: Session):
|
|
151
|
-
self.session = session
|
|
152
|
-
|
|
153
|
-
def get_all(self) -> List[Gap]:
|
|
154
|
-
statement = select(GapModel)
|
|
155
|
-
models = self.session.exec(statement).all()
|
|
156
|
-
results = []
|
|
157
|
-
for m in models:
|
|
158
|
-
results.append(Gap.model_validate({
|
|
159
|
-
"id": m.id,
|
|
160
|
-
"severity": m.severity,
|
|
161
|
-
"gap_type": m.gap_type,
|
|
162
|
-
"requirement_id": m.requirement_id,
|
|
163
|
-
"task_id": m.task_id,
|
|
164
|
-
"description": m.description,
|
|
165
|
-
"evidence": json.loads(m.evidence_json),
|
|
166
|
-
"recommended_fix": m.recommended_fix,
|
|
167
|
-
"blocking": m.blocking,
|
|
168
|
-
"file": m.file,
|
|
169
|
-
"line": m.line,
|
|
170
|
-
"suggested_command": m.suggested_command,
|
|
171
|
-
"acceptance_criterion_id": m.acceptance_criterion_id,
|
|
172
|
-
}))
|
|
173
|
-
return results
|
|
174
|
-
|
|
175
|
-
def save(self, gap: Gap):
|
|
176
|
-
model = GapModel(
|
|
177
|
-
id=gap.id,
|
|
178
|
-
severity=gap.severity,
|
|
179
|
-
gap_type=gap.gap_type,
|
|
180
|
-
requirement_id=gap.requirement_id,
|
|
181
|
-
task_id=gap.task_id,
|
|
182
|
-
description=gap.description,
|
|
183
|
-
evidence_json=json.dumps(gap.evidence),
|
|
184
|
-
recommended_fix=gap.recommended_fix,
|
|
185
|
-
blocking=gap.blocking,
|
|
186
|
-
file=gap.file,
|
|
187
|
-
line=gap.line,
|
|
188
|
-
suggested_command=gap.suggested_command,
|
|
189
|
-
acceptance_criterion_id=gap.acceptance_criterion_id,
|
|
190
|
-
)
|
|
191
|
-
self.session.merge(model)
|
|
192
|
-
self.session.commit()
|
|
193
|
-
|
|
194
|
-
def delete_for_task(self, task_id: str):
|
|
195
|
-
self.session.exec(delete(GapModel).where(col(GapModel.task_id) == task_id))
|
|
196
|
-
self.session.commit()
|
|
197
|
-
|
|
198
|
-
def delete_plan_gaps(self):
|
|
199
|
-
self.session.exec(delete(GapModel).where(col(GapModel.id).like("GAP-PLAN-%")))
|
|
200
|
-
self.session.commit()
|
|
201
|
-
|
|
202
|
-
class EvidenceRepository:
|
|
203
|
-
def __init__(self, session: Session):
|
|
204
|
-
self.session = session
|
|
205
|
-
|
|
206
|
-
def save_command_result(self, task_id: str, result: CommandResult):
|
|
207
|
-
model = EvidenceModel(
|
|
208
|
-
type="command",
|
|
209
|
-
task_id=task_id,
|
|
210
|
-
data_json=result.model_dump_json()
|
|
211
|
-
)
|
|
212
|
-
self.session.add(model)
|
|
213
|
-
self.session.commit()
|
|
214
|
-
|
|
215
|
-
def save_diff_evidence(self, ev: DiffEvidence):
|
|
216
|
-
model = EvidenceModel(
|
|
217
|
-
type="diff",
|
|
218
|
-
task_id=ev.task_id,
|
|
219
|
-
data_json=ev.model_dump_json()
|
|
220
|
-
)
|
|
221
|
-
self.session.add(model)
|
|
222
|
-
self.session.commit()
|
|
223
|
-
|
|
224
|
-
def save_test_evidence(self, ev: TestEvidence, task_id: Optional[str] = None):
|
|
225
|
-
model = EvidenceModel(
|
|
226
|
-
type="test",
|
|
227
|
-
task_id=task_id,
|
|
228
|
-
requirement_id=ev.requirement_id,
|
|
229
|
-
acceptance_criterion_id=ev.acceptance_criterion_id,
|
|
230
|
-
data_json=ev.model_dump_json()
|
|
231
|
-
)
|
|
232
|
-
self.session.add(model)
|
|
233
|
-
self.session.commit()
|
|
234
|
-
|
|
235
|
-
def save_diff_coverage_evidence(self, ev: DiffCoverageEvidence):
|
|
236
|
-
model = EvidenceModel(
|
|
237
|
-
type="diff_coverage",
|
|
238
|
-
task_id=ev.task_id,
|
|
239
|
-
data_json=ev.model_dump_json()
|
|
240
|
-
)
|
|
241
|
-
self.session.add(model)
|
|
242
|
-
self.session.commit()
|
|
243
|
-
|
|
244
|
-
def get_command_results_for_task(self, task_id: str) -> List[CommandResult]:
|
|
245
|
-
statement = select(EvidenceModel).where(EvidenceModel.task_id == task_id).where(
|
|
246
|
-
EvidenceModel.type == "command"
|
|
247
|
-
)
|
|
248
|
-
models = self.session.exec(statement).all()
|
|
249
|
-
return [CommandResult.model_validate(json.loads(m.data_json)) for m in models]
|
|
250
|
-
|
|
251
|
-
def get_all(self) -> List[Any]:
|
|
252
|
-
statement = select(EvidenceModel)
|
|
253
|
-
models = self.session.exec(statement).all()
|
|
254
|
-
results: List[Any] = []
|
|
255
|
-
for m in models:
|
|
256
|
-
data = json.loads(m.data_json)
|
|
257
|
-
if m.type == "command":
|
|
258
|
-
results.append(CommandResult.model_validate(data))
|
|
259
|
-
elif m.type == "diff":
|
|
260
|
-
results.append(DiffEvidence.model_validate(data))
|
|
261
|
-
elif m.type == "diff_coverage":
|
|
262
|
-
results.append(DiffCoverageEvidence.model_validate(data))
|
|
263
|
-
elif m.type == "test":
|
|
264
|
-
results.append(TestEvidence.model_validate(data))
|
|
265
|
-
return results
|
|
266
|
-
|
|
267
|
-
def delete_for_task(self, task_id: str):
|
|
268
|
-
self.session.exec(delete(EvidenceModel).where(col(EvidenceModel.task_id) == task_id))
|
|
269
|
-
self.session.commit()
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
class CritiqueFindingRepository:
|
|
273
|
-
def __init__(self, session: Session):
|
|
274
|
-
self.session = session
|
|
275
|
-
|
|
276
|
-
def get_all(self) -> List[CritiqueFinding]:
|
|
277
|
-
statement = select(CritiqueFindingModel)
|
|
278
|
-
models = self.session.exec(statement).all()
|
|
279
|
-
return [
|
|
280
|
-
CritiqueFinding.model_validate({
|
|
281
|
-
"id": m.id,
|
|
282
|
-
"source_agent": m.source_agent,
|
|
283
|
-
"target_plan_id": m.target_plan_id,
|
|
284
|
-
"severity": m.severity,
|
|
285
|
-
"finding_type": m.finding_type,
|
|
286
|
-
"claim": m.claim,
|
|
287
|
-
"linked_requirement_id": m.linked_requirement_id,
|
|
288
|
-
"suggested_requirement": m.suggested_requirement,
|
|
289
|
-
"suggested_task": m.suggested_task,
|
|
290
|
-
"falsifiable_check": m.falsifiable_check,
|
|
291
|
-
"status": m.status,
|
|
292
|
-
})
|
|
293
|
-
for m in models
|
|
294
|
-
]
|
|
295
|
-
|
|
296
|
-
def save(self, finding: CritiqueFinding):
|
|
297
|
-
model = CritiqueFindingModel(
|
|
298
|
-
id=finding.id,
|
|
299
|
-
source_agent=finding.source_agent,
|
|
300
|
-
target_plan_id=finding.target_plan_id,
|
|
301
|
-
severity=finding.severity,
|
|
302
|
-
finding_type=finding.finding_type,
|
|
303
|
-
claim=finding.claim,
|
|
304
|
-
linked_requirement_id=finding.linked_requirement_id,
|
|
305
|
-
suggested_requirement=finding.suggested_requirement,
|
|
306
|
-
suggested_task=finding.suggested_task,
|
|
307
|
-
falsifiable_check=finding.falsifiable_check,
|
|
308
|
-
status=finding.status,
|
|
309
|
-
)
|
|
310
|
-
self.session.merge(model)
|
|
311
|
-
self.session.commit()
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
class PlanningStateRepository:
|
|
315
|
-
"""Manage the persisted active plan graph."""
|
|
316
|
-
|
|
317
|
-
def __init__(self, session: Session):
|
|
318
|
-
self.session = session
|
|
319
|
-
|
|
320
|
-
def clear_active_plan(self):
|
|
321
|
-
for model in (
|
|
322
|
-
EvidenceModel,
|
|
323
|
-
GapModel,
|
|
324
|
-
CritiqueFindingModel,
|
|
325
|
-
TaskModel,
|
|
326
|
-
AssumptionModel,
|
|
327
|
-
RequirementModel,
|
|
328
|
-
):
|
|
329
|
-
self.session.exec(delete(model))
|
|
330
|
-
|
|
331
|
-
def replace_active_plan(
|
|
332
|
-
self,
|
|
333
|
-
requirements: list[Requirement],
|
|
334
|
-
assumptions: list[Assumption],
|
|
335
|
-
tasks: list[Task],
|
|
336
|
-
findings: list[CritiqueFinding],
|
|
337
|
-
):
|
|
338
|
-
self.clear_active_plan()
|
|
339
|
-
for req in requirements:
|
|
340
|
-
self.session.merge(RequirementModel(
|
|
341
|
-
id=req.id,
|
|
342
|
-
title=req.title,
|
|
343
|
-
description=req.description,
|
|
344
|
-
priority=req.priority,
|
|
345
|
-
source=req.source,
|
|
346
|
-
acceptance_criteria_json=json.dumps([ac.model_dump() for ac in req.acceptance_criteria]),
|
|
347
|
-
))
|
|
348
|
-
|
|
349
|
-
for assumption in assumptions:
|
|
350
|
-
self.session.merge(AssumptionModel(
|
|
351
|
-
id=assumption.id,
|
|
352
|
-
statement=assumption.statement,
|
|
353
|
-
confidence=assumption.confidence,
|
|
354
|
-
impact=assumption.impact,
|
|
355
|
-
reversible=assumption.reversible,
|
|
356
|
-
requires_user_confirmation=assumption.requires_user_confirmation,
|
|
357
|
-
linked_requirement_ids_json=json.dumps(assumption.linked_requirement_ids),
|
|
358
|
-
status=assumption.status,
|
|
359
|
-
))
|
|
360
|
-
|
|
361
|
-
for task in tasks:
|
|
362
|
-
self.session.merge(TaskModel(
|
|
363
|
-
id=task.id,
|
|
364
|
-
title=task.title,
|
|
365
|
-
description=task.description,
|
|
366
|
-
requirement_ids_json=json.dumps(task.requirement_ids),
|
|
367
|
-
acceptance_criterion_ids_json=json.dumps(task.acceptance_criterion_ids),
|
|
368
|
-
planned_files_json=json.dumps([pf.model_dump() for pf in task.planned_files]),
|
|
369
|
-
expected_tests_json=json.dumps(task.expected_tests),
|
|
370
|
-
allowed_commands_json=json.dumps(task.allowed_commands),
|
|
371
|
-
forbidden_changes_json=json.dumps(task.forbidden_changes),
|
|
372
|
-
status=task.status,
|
|
373
|
-
))
|
|
374
|
-
|
|
375
|
-
for finding in findings:
|
|
376
|
-
self.session.merge(CritiqueFindingModel(
|
|
377
|
-
id=finding.id,
|
|
378
|
-
source_agent=finding.source_agent,
|
|
379
|
-
target_plan_id=finding.target_plan_id,
|
|
380
|
-
severity=finding.severity,
|
|
381
|
-
finding_type=finding.finding_type,
|
|
382
|
-
claim=finding.claim,
|
|
383
|
-
linked_requirement_id=finding.linked_requirement_id,
|
|
384
|
-
suggested_requirement=finding.suggested_requirement,
|
|
385
|
-
suggested_task=finding.suggested_task,
|
|
386
|
-
falsifiable_check=finding.falsifiable_check,
|
|
387
|
-
status=finding.status,
|
|
388
|
-
))
|
|
389
|
-
|
|
390
|
-
class ArtifactGraphRepository:
|
|
391
|
-
def __init__(self, session: Session):
|
|
392
|
-
self.session = session
|
|
393
|
-
self.req_repo = RequirementRepository(session)
|
|
394
|
-
self.assumption_repo = AssumptionRepository(session)
|
|
395
|
-
self.task_repo = TaskRepository(session)
|
|
396
|
-
self.gap_repo = GapRepository(session)
|
|
397
|
-
self.evidence_repo = EvidenceRepository(session)
|
|
398
|
-
self.finding_repo = CritiqueFindingRepository(session)
|
|
399
|
-
|
|
400
|
-
def load_graph(self) -> ArtifactGraph:
|
|
401
|
-
graph = ArtifactGraph()
|
|
402
|
-
for req in self.req_repo.get_all():
|
|
403
|
-
graph.add_requirement(req)
|
|
404
|
-
for assumption in self.assumption_repo.get_all():
|
|
405
|
-
graph.add_assumption(assumption)
|
|
406
|
-
for task in self.task_repo.get_all():
|
|
407
|
-
graph.add_task(task)
|
|
408
|
-
for finding in self.finding_repo.get_all():
|
|
409
|
-
graph.add_finding(finding)
|
|
410
|
-
for gap in self.gap_repo.get_all():
|
|
411
|
-
graph.add_gap(gap)
|
|
412
|
-
for ev in self.evidence_repo.get_all():
|
|
413
|
-
if isinstance(ev, CommandResult):
|
|
414
|
-
graph.add_command_result(ev)
|
|
415
|
-
elif isinstance(ev, DiffCoverageEvidence):
|
|
416
|
-
# Must precede DiffEvidence: distinct type, and the diff↔coverage proof
|
|
417
|
-
# was silently dropped on reload before this branch existed.
|
|
418
|
-
graph.add_diff_coverage_evidence(ev)
|
|
419
|
-
elif isinstance(ev, DiffEvidence):
|
|
420
|
-
graph.add_diff_evidence(ev)
|
|
421
|
-
elif isinstance(ev, TestEvidence):
|
|
422
|
-
graph.add_test_evidence(ev)
|
|
423
|
-
return graph
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
class StateRepository:
|
|
427
|
-
def __init__(self, session: Session):
|
|
428
|
-
self.session = session
|
|
429
|
-
|
|
430
|
-
def get_state(self) -> Optional[ProjectStateModel]:
|
|
431
|
-
statement = select(ProjectStateModel).where(ProjectStateModel.id == "singleton")
|
|
432
|
-
return self.session.exec(statement).first()
|
|
433
|
-
|
|
434
|
-
def save_state(self, state: ProjectStateModel | str, history: Optional[List[str]] = None):
|
|
435
|
-
if isinstance(state, ProjectStateModel):
|
|
436
|
-
state.id = "singleton"
|
|
437
|
-
else:
|
|
438
|
-
state = ProjectStateModel(
|
|
439
|
-
id="singleton",
|
|
440
|
-
current_phase=state,
|
|
441
|
-
history_json=json.dumps(history or []),
|
|
442
|
-
)
|
|
443
|
-
self.session.merge(state)
|
|
444
|
-
self.session.commit()
|
|
445
|
-
|
|
446
|
-
def record_phase(self, phase: str):
|
|
447
|
-
current = self.get_state()
|
|
448
|
-
history = []
|
|
449
|
-
prev = None
|
|
450
|
-
if current:
|
|
451
|
-
history = json.loads(current.history_json)
|
|
452
|
-
prev = current.current_phase
|
|
453
|
-
# Keep the persisted history a LEGAL transition sequence. A repair re-run records
|
|
454
|
-
# TASK_EXECUTING straight after a terminal TASK_BLOCKED/TASK_VERIFIED, which is not
|
|
455
|
-
# a direct transition; bridge it through the intermediate phase (TASK_READY) so the
|
|
456
|
-
# state machine's invariant holds on reload. Fail-soft: an unbridgeable jump is
|
|
457
|
-
# still recorded verbatim rather than lost.
|
|
458
|
-
for step in self._bridge_phases(prev, phase):
|
|
459
|
-
if not history or history[-1] != step:
|
|
460
|
-
history.append(step)
|
|
461
|
-
self.save_state(phase, history)
|
|
462
|
-
|
|
463
|
-
@staticmethod
|
|
464
|
-
def _bridge_phases(prev: Optional[str], target: str) -> List[str]:
|
|
465
|
-
"""Return the phases to append so prev -> ... -> target is a legal path.
|
|
466
|
-
|
|
467
|
-
Inserts a single intermediate phase when prev cannot transition directly to
|
|
468
|
-
target but a one-hop bridge exists (the repair loop's
|
|
469
|
-
TASK_BLOCKED/TASK_VERIFIED -> TASK_READY -> TASK_EXECUTING). Returns ``[target]``
|
|
470
|
-
when prev is unknown, equal, already-legal, or no one-hop bridge exists."""
|
|
471
|
-
if prev is None or prev == target:
|
|
472
|
-
return [target]
|
|
473
|
-
try:
|
|
474
|
-
from devcouncil.app.state_machine import TRANSITIONS, ProjectPhase
|
|
475
|
-
|
|
476
|
-
prev_phase = ProjectPhase(prev)
|
|
477
|
-
target_phase = ProjectPhase(target)
|
|
478
|
-
except (ImportError, ValueError):
|
|
479
|
-
return [target]
|
|
480
|
-
if target_phase in TRANSITIONS.get(prev_phase, set()):
|
|
481
|
-
return [target]
|
|
482
|
-
for mid in TRANSITIONS.get(prev_phase, set()):
|
|
483
|
-
if target_phase in TRANSITIONS.get(mid, set()):
|
|
484
|
-
return [mid.value, target]
|
|
485
|
-
return [target]
|
|
File without changes
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import logging
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Any, Dict, List
|
|
5
|
-
|
|
6
|
-
from devcouncil.telemetry.pricing import pricing_for_model
|
|
7
|
-
|
|
8
|
-
logger = logging.getLogger(__name__)
|
|
9
|
-
|
|
10
|
-
UNATTRIBUTED = "(unattributed)"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class CostEstimator:
|
|
14
|
-
"""Estimates LLM usage cost based on provider pricing."""
|
|
15
|
-
|
|
16
|
-
# Conservative default for unknown models
|
|
17
|
-
DEFAULT_PRICING = {"prompt_per_1k": 0.005, "completion_per_1k": 0.015}
|
|
18
|
-
|
|
19
|
-
# Local providers run on-device and incur no per-token cost. Ollama model ids
|
|
20
|
-
# are open-ended (e.g. ``qwen2.5-coder:7b`` or an ``ollama/<name>`` form), so
|
|
21
|
-
# match the conventional prefixes rather than relying on the open-ended yaml.
|
|
22
|
-
LOCAL_MODEL_PREFIXES = ("ollama/", "ollama:")
|
|
23
|
-
|
|
24
|
-
@classmethod
|
|
25
|
-
def _is_local_model(cls, model: str) -> bool:
|
|
26
|
-
return model.startswith(cls.LOCAL_MODEL_PREFIXES)
|
|
27
|
-
|
|
28
|
-
@classmethod
|
|
29
|
-
def estimate_cost(cls, model: str, usage: Dict[str, int]) -> float:
|
|
30
|
-
# Local/Ollama models are free regardless of yaml coverage; short-circuit
|
|
31
|
-
# before the conservative DEFAULT_PRICING fallback would bill them.
|
|
32
|
-
if cls._is_local_model(model):
|
|
33
|
-
return 0.0
|
|
34
|
-
prices = pricing_for_model(model, cls.DEFAULT_PRICING)
|
|
35
|
-
if prices == cls.DEFAULT_PRICING:
|
|
36
|
-
logger.debug("Unknown model for cost estimation: %s — using default pricing", model)
|
|
37
|
-
|
|
38
|
-
prompt_tokens = usage.get("prompt_tokens", 0)
|
|
39
|
-
completion_tokens = usage.get("completion_tokens", 0)
|
|
40
|
-
|
|
41
|
-
cost = ((prompt_tokens / 1000.0) * prices["prompt_per_1k"]) + (
|
|
42
|
-
(completion_tokens / 1000.0) * prices["completion_per_1k"]
|
|
43
|
-
)
|
|
44
|
-
return cost
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _model_calls_file(project_root: Path) -> Path:
|
|
48
|
-
return project_root / ".devcouncil" / "logs" / "model_calls.jsonl"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def read_cost_records(project_root: Path) -> List[Dict[str, Any]]:
|
|
52
|
-
"""Read the model-call ledger and attach an estimated cost to each record.
|
|
53
|
-
|
|
54
|
-
Never raises: malformed lines are skipped. Records missing ``task_id`` /
|
|
55
|
-
``run_id`` (older entries written before per-task attribution) keep those as
|
|
56
|
-
``None`` so callers can bucket them under ``(unattributed)``.
|
|
57
|
-
"""
|
|
58
|
-
log_file = _model_calls_file(project_root)
|
|
59
|
-
records: List[Dict[str, Any]] = []
|
|
60
|
-
if not log_file.exists():
|
|
61
|
-
return records
|
|
62
|
-
try:
|
|
63
|
-
lines = log_file.read_text(encoding="utf-8").splitlines()
|
|
64
|
-
except Exception as exc:
|
|
65
|
-
logger.debug("Failed to read model_calls ledger: %s", exc)
|
|
66
|
-
return records
|
|
67
|
-
|
|
68
|
-
for line in lines:
|
|
69
|
-
if not line.strip():
|
|
70
|
-
continue
|
|
71
|
-
try:
|
|
72
|
-
entry = json.loads(line)
|
|
73
|
-
except Exception as exc:
|
|
74
|
-
logger.debug("Skipping invalid model_calls line: %s", exc)
|
|
75
|
-
continue
|
|
76
|
-
model = ""
|
|
77
|
-
response = entry.get("response")
|
|
78
|
-
if isinstance(response, dict):
|
|
79
|
-
model = str(response.get("model", "") or "")
|
|
80
|
-
raw_usage = entry.get("usage")
|
|
81
|
-
usage: Dict[str, Any] = raw_usage if isinstance(raw_usage, dict) else {}
|
|
82
|
-
# Local providers (ollama) are always free, regardless of the open-ended model
|
|
83
|
-
# tag Ollama echoes back (e.g. ``mistral:latest``) — trust the recorded provider
|
|
84
|
-
# over fragile model-id prefix matching.
|
|
85
|
-
provider = str(entry.get("provider") or "")
|
|
86
|
-
try:
|
|
87
|
-
cost = 0.0 if provider == "ollama" else CostEstimator.estimate_cost(model, usage)
|
|
88
|
-
except Exception:
|
|
89
|
-
cost = 0.0
|
|
90
|
-
records.append(
|
|
91
|
-
{
|
|
92
|
-
"task_id": entry.get("task_id"),
|
|
93
|
-
"run_id": entry.get("run_id"),
|
|
94
|
-
"timestamp": entry.get("timestamp"),
|
|
95
|
-
"model": model,
|
|
96
|
-
"usage": usage,
|
|
97
|
-
"cost": cost,
|
|
98
|
-
}
|
|
99
|
-
)
|
|
100
|
-
return records
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def _group(records: List[Dict[str, Any]], key: str) -> Dict[str, Dict[str, Any]]:
|
|
104
|
-
groups: Dict[str, Dict[str, Any]] = {}
|
|
105
|
-
for record in records:
|
|
106
|
-
raw = record.get(key)
|
|
107
|
-
bucket = str(raw) if isinstance(raw, str) and raw else UNATTRIBUTED
|
|
108
|
-
group = groups.setdefault(
|
|
109
|
-
bucket,
|
|
110
|
-
{"cost": 0.0, "calls": 0, "prompt_tokens": 0, "completion_tokens": 0},
|
|
111
|
-
)
|
|
112
|
-
raw_usage = record.get("usage")
|
|
113
|
-
usage: Dict[str, Any] = raw_usage if isinstance(raw_usage, dict) else {}
|
|
114
|
-
group["cost"] += float(record.get("cost", 0.0) or 0.0)
|
|
115
|
-
group["calls"] += 1
|
|
116
|
-
group["prompt_tokens"] += int(usage.get("prompt_tokens", 0) or 0)
|
|
117
|
-
group["completion_tokens"] += int(usage.get("completion_tokens", 0) or 0)
|
|
118
|
-
return groups
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def group_cost(project_root: Path) -> Dict[str, Any]:
|
|
122
|
-
"""Aggregate model-call cost grouped by ``task_id`` and ``run_id``.
|
|
123
|
-
|
|
124
|
-
Returns a JSON-friendly summary: a grand total plus per-task and per-run
|
|
125
|
-
breakdowns. Unattributed records (older entries, or calls made without a
|
|
126
|
-
task/run context) are bucketed under ``(unattributed)``. Never raises.
|
|
127
|
-
"""
|
|
128
|
-
records = read_cost_records(project_root)
|
|
129
|
-
total_cost = sum(float(record.get("cost", 0.0) or 0.0) for record in records)
|
|
130
|
-
return {
|
|
131
|
-
"total_cost": total_cost,
|
|
132
|
-
"total_calls": len(records),
|
|
133
|
-
"by_task": _group(records, "task_id"),
|
|
134
|
-
"by_run": _group(records, "run_id"),
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def cost_by_task(project_root: Path) -> Dict[str, Dict[str, Any]]:
|
|
139
|
-
"""Convenience accessor for the per-task cost breakdown (used by ``dev status``)."""
|
|
140
|
-
return group_cost(project_root)["by_task"]
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
anthropic/claude-3-opus:
|
|
2
|
-
prompt_per_1k: 0.015
|
|
3
|
-
completion_per_1k: 0.075
|
|
4
|
-
anthropic/claude-3.5-sonnet:
|
|
5
|
-
prompt_per_1k: 0.003
|
|
6
|
-
completion_per_1k: 0.015
|
|
7
|
-
anthropic/claude-sonnet-4:
|
|
8
|
-
prompt_per_1k: 0.003
|
|
9
|
-
completion_per_1k: 0.015
|
|
10
|
-
google/gemini-pro-1.5:
|
|
11
|
-
prompt_per_1k: 0.00125
|
|
12
|
-
completion_per_1k: 0.005
|
|
13
|
-
google/gemini-2.5-pro:
|
|
14
|
-
prompt_per_1k: 0.00125
|
|
15
|
-
completion_per_1k: 0.01
|
|
16
|
-
openai/gpt-4o:
|
|
17
|
-
prompt_per_1k: 0.005
|
|
18
|
-
completion_per_1k: 0.015
|
|
19
|
-
openai/o3-mini:
|
|
20
|
-
prompt_per_1k: 0.0011
|
|
21
|
-
completion_per_1k: 0.0044
|
|
22
|
-
anthropic/claude-sonnet-4.6:
|
|
23
|
-
prompt_per_1k: 0.003
|
|
24
|
-
completion_per_1k: 0.015
|
|
25
|
-
anthropic/claude-opus-4.8:
|
|
26
|
-
prompt_per_1k: 0.005
|
|
27
|
-
completion_per_1k: 0.025
|
|
28
|
-
openai/gpt-5.5:
|
|
29
|
-
prompt_per_1k: 0.005
|
|
30
|
-
completion_per_1k: 0.03
|
|
31
|
-
google/gemini-2.5-flash:
|
|
32
|
-
prompt_per_1k: 0.0003
|
|
33
|
-
completion_per_1k: 0.0025
|
|
34
|
-
# Local Ollama models run on-device and are free. Bare tags (no ``ollama/``
|
|
35
|
-
# prefix) are listed here so the cost reporter shows $0; the cost.py prefix
|
|
36
|
-
# guard covers any other ``ollama/`` or ``ollama:`` model id.
|
|
37
|
-
qwen2.5-coder:7b:
|
|
38
|
-
prompt_per_1k: 0.0
|
|
39
|
-
completion_per_1k: 0.0
|
|
40
|
-
qwen2.5-coder:14b:
|
|
41
|
-
prompt_per_1k: 0.0
|
|
42
|
-
completion_per_1k: 0.0
|
|
43
|
-
qwen2.5-coder:32b:
|
|
44
|
-
prompt_per_1k: 0.0
|
|
45
|
-
completion_per_1k: 0.0
|
|
46
|
-
llama3.1:
|
|
47
|
-
prompt_per_1k: 0.0
|
|
48
|
-
completion_per_1k: 0.0
|