kostria 0.1.0__tar.gz

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 (73) hide show
  1. kostria-0.1.0/ARCHITECTURE.md +274 -0
  2. kostria-0.1.0/CHANGELOG.md +80 -0
  3. kostria-0.1.0/LICENSE +21 -0
  4. kostria-0.1.0/MANIFEST.in +16 -0
  5. kostria-0.1.0/PKG-INFO +198 -0
  6. kostria-0.1.0/README.md +167 -0
  7. kostria-0.1.0/kostria/__init__.py +3 -0
  8. kostria-0.1.0/kostria/analysis.py +312 -0
  9. kostria-0.1.0/kostria/attribution.py +359 -0
  10. kostria-0.1.0/kostria/badge.py +56 -0
  11. kostria-0.1.0/kostria/cache.py +112 -0
  12. kostria-0.1.0/kostria/cli.py +616 -0
  13. kostria-0.1.0/kostria/collectors/__init__.py +62 -0
  14. kostria-0.1.0/kostria/collectors/claude_code.py +233 -0
  15. kostria-0.1.0/kostria/collectors/codex.py +82 -0
  16. kostria-0.1.0/kostria/collectors/opencode.py +88 -0
  17. kostria-0.1.0/kostria/compare.py +89 -0
  18. kostria-0.1.0/kostria/config.py +159 -0
  19. kostria-0.1.0/kostria/doctor.py +209 -0
  20. kostria-0.1.0/kostria/landing/index.html +648 -0
  21. kostria-0.1.0/kostria/landing/vendor/ScrollTrigger.min.js +11 -0
  22. kostria-0.1.0/kostria/landing/vendor/fonts/Inter.woff2 +0 -0
  23. kostria-0.1.0/kostria/landing/vendor/fonts/SourceSerif4.woff2 +0 -0
  24. kostria-0.1.0/kostria/landing/vendor/gsap.min.js +11 -0
  25. kostria-0.1.0/kostria/landing/vendor/three.core.js +60007 -0
  26. kostria-0.1.0/kostria/landing/vendor/three.module.js +19610 -0
  27. kostria-0.1.0/kostria/markdown.py +146 -0
  28. kostria-0.1.0/kostria/period.py +103 -0
  29. kostria-0.1.0/kostria/pricing.py +275 -0
  30. kostria-0.1.0/kostria/render.py +678 -0
  31. kostria-0.1.0/kostria/report.py +307 -0
  32. kostria-0.1.0/kostria/sync.py +226 -0
  33. kostria-0.1.0/kostria/telemetry.py +101 -0
  34. kostria-0.1.0/kostria/watch.py +73 -0
  35. kostria-0.1.0/kostria/web/dashboard.html +1496 -0
  36. kostria-0.1.0/kostria/web/vendor/fonts/Inter.woff2 +0 -0
  37. kostria-0.1.0/kostria/web/vendor/fonts/SourceSerif4.woff2 +0 -0
  38. kostria-0.1.0/kostria/web/vendor/gsap.min.js +11 -0
  39. kostria-0.1.0/kostria.egg-info/PKG-INFO +198 -0
  40. kostria-0.1.0/kostria.egg-info/SOURCES.txt +71 -0
  41. kostria-0.1.0/kostria.egg-info/dependency_links.txt +1 -0
  42. kostria-0.1.0/kostria.egg-info/entry_points.txt +2 -0
  43. kostria-0.1.0/kostria.egg-info/requires.txt +4 -0
  44. kostria-0.1.0/kostria.egg-info/top_level.txt +1 -0
  45. kostria-0.1.0/pyproject.toml +77 -0
  46. kostria-0.1.0/setup.cfg +4 -0
  47. kostria-0.1.0/tests/test_analysis.py +178 -0
  48. kostria-0.1.0/tests/test_attribution.py +255 -0
  49. kostria-0.1.0/tests/test_badge.py +56 -0
  50. kostria-0.1.0/tests/test_cache.py +136 -0
  51. kostria-0.1.0/tests/test_claude_code_collector.py +114 -0
  52. kostria-0.1.0/tests/test_cli.py +172 -0
  53. kostria-0.1.0/tests/test_codex_collector.py +107 -0
  54. kostria-0.1.0/tests/test_compare.py +89 -0
  55. kostria-0.1.0/tests/test_config.py +127 -0
  56. kostria-0.1.0/tests/test_contrast.py +130 -0
  57. kostria-0.1.0/tests/test_doctor.py +97 -0
  58. kostria-0.1.0/tests/test_e2e.py +260 -0
  59. kostria-0.1.0/tests/test_init.py +121 -0
  60. kostria-0.1.0/tests/test_markdown.py +75 -0
  61. kostria-0.1.0/tests/test_opencode_collector.py +102 -0
  62. kostria-0.1.0/tests/test_period.py +93 -0
  63. kostria-0.1.0/tests/test_pricing.py +212 -0
  64. kostria-0.1.0/tests/test_pricing_openai.py +81 -0
  65. kostria-0.1.0/tests/test_pricing_values.py +130 -0
  66. kostria-0.1.0/tests/test_project_budgets.py +257 -0
  67. kostria-0.1.0/tests/test_render.py +151 -0
  68. kostria-0.1.0/tests/test_report.py +252 -0
  69. kostria-0.1.0/tests/test_robustness.py +141 -0
  70. kostria-0.1.0/tests/test_serve_cache.py +77 -0
  71. kostria-0.1.0/tests/test_signals.py +84 -0
  72. kostria-0.1.0/tests/test_sync_cli.py +325 -0
  73. kostria-0.1.0/tests/test_watch.py +108 -0
@@ -0,0 +1,274 @@
1
+ # Arquitetura do kostria
2
+
3
+ Documento para quem for mexer no código. Baseado no que o código faz de
4
+ fato; se algo aqui divergir da implementação, a implementação manda.
5
+
6
+ ## 1. Visão geral
7
+
8
+ O `kostria` é um pacote Python de auditoria **local** de custo de IA. Ele lê
9
+ os registros que as ferramentas já gravam na máquina (Claude Code, OpenCode,
10
+ Codex CLI), calcula ou aceita o custo de cada sessão e atribui esse gasto a
11
+ projeto, conta, modelo e — quando há histórico git — a área/feature.
12
+
13
+ Existe porque o total sozinho não decide nada: o valor do produto está na
14
+ **distribuição** (qual projeto, qual conta, com que evidência). Tudo roda
15
+ offline, sem dependência externa e sem rede (`pyproject.toml` declara
16
+ `dependencies = []`).
17
+
18
+ ## 2. Fluxo de dados
19
+
20
+ A unidade de troca entre camadas é a `Session` (`collectors/__init__.py`).
21
+ A sessão é o menor recorte em que as ferramentas concordam: Claude Code
22
+ registra por mensagem; OpenCode e Codex agregam por sessão.
23
+
24
+ ```
25
+ logs locais Session[] dict (relatório)
26
+ (~/.claude, sqlite, + meta por agregado em
27
+ ~/.codex/...) coletor várias dimensões
28
+ | | |
29
+ v v v
30
+ +-----------+ +-------------+ +-------------+ +----------+
31
+ | coletores |-->| atribuição |-->| report.build|-->| render |
32
+ | collect() | | split_ | | + analysis | | html/md |
33
+ | | | session_cost| | + features | | csv/json |
34
+ +-----------+ +-------------+ +-------------+ +----------+
35
+ ^ ^
36
+ | |
37
+ cache.py (só cli.py (scan,
38
+ claude_code hoje) report, serve,
39
+ doctor, ...)
40
+ ```
41
+
42
+ Passos concretos:
43
+
44
+ 1. **Coleta** — `collect_all()` chama cada coletor. Falha de um não derruba
45
+ os outros; o erro vira entrada em `meta[nome]`.
46
+ 2. **Período** (opcional) — `period.filter_sessions` recorta por
47
+ `started_at` e pela fração de edições dentro da janela (sessão longa
48
+ que atravessa o mês é rateada e o relatório declara a aproximação).
49
+ 3. **Atribuição** — para cada sessão, `split_session_cost` rateia custo e
50
+ tokens entre projetos e devolve o método usado (`edited` / `read` /
51
+ `cwd`).
52
+ 4. **Agregação** — `report.build` monta um único dict: totais, buckets
53
+ (`by_project`, `by_tool`, `by_vendor`, `by_account`, `by_model`,
54
+ `by_provenance`, `by_attribution`), sinais de `analysis.py`,
55
+ opcionalmente custo por feature via `features_for_repo` e status de
56
+ orçamento. Comparação entre períodos (`compare.compare_reports`) é
57
+ anexada depois pela CLI quando `--compare` está ativo.
58
+ 5. **Render** — a CLI escolhe o formato. O dict é a fonte única para
59
+ terminal, HTML (`render.py`), Markdown, CSV, JSON e o dashboard
60
+ (`serve` + `web/dashboard.html`).
61
+
62
+ Separar montagem (`report`) de apresentação (`render` / `markdown` / CSV)
63
+ existe de propósito: CLI, dashboard e testes consomem a mesma estrutura.
64
+
65
+ ## 3. Por que a atribuição segue arquivos, não o `cwd`
66
+
67
+ Documentado no próprio `attribution.py`:
68
+
69
+ > O diretório da sessão mente. O agente edita vários repositórios a partir
70
+ > de um cwd só (working dirs adicionais, worktrees, pastas temporárias).
71
+ > Medir por cwd joga o custo no projeto errado. A atribuição segue os
72
+ > ARQUIVOS efetivamente editados.
73
+
74
+ Cascata de evidência em `split_session_cost`, do mais forte ao mais fraco
75
+ (`ATTRIBUTION`):
76
+
77
+ | método | evidência |
78
+ |----------|------------------------------------------------|
79
+ | `edited` | paths em `session.edits` (escrita = entrega) |
80
+ | `read` | paths em `session.reads` (leitura = contexto) |
81
+ | `cwd` | diretório declarado, sem arquivo tocado |
82
+
83
+ O rateio é uniforme entre os projetos encontrados nos paths (custo/N,
84
+ tokens//N). O Claude Code popula `edits` e `reads` a partir das tool calls
85
+ (`Edit`/`Write`/… e `Read`/`Grep`/`Glob`/…). O comentário no coletor é
86
+ explícito: sem leituras, toda sessão de investigação cairia no diretório
87
+ declarado — que mente.
88
+
89
+ `ProjectResolver` resolve path → nome de projeto com, nesta ordem:
90
+ índice de worktrees (`git worktree list` + convenção `<repo>-wt`),
91
+ projeto codificado em scratch de sessão (`-home-user-code-app`),
92
+ `git rev-parse --git-common-dir`, primeiro nível do workspace, ou basename.
93
+ Worktree não é outro projeto; scratch/tmp/UUID viram rótulos genéricos.
94
+
95
+ Dentro de um repo, a unidade fina de feature é o **commit** (não o PR):
96
+ `features_for_repo` liga cada edição ao primeiro commit posterior que tocou
97
+ o arquivo e agrega por escopo convencional (`feat(escopo): …`) e por ticket
98
+ no subject/body.
99
+
100
+ ## 4. Proveniência: nenhum número sem classificação
101
+
102
+ Regra em `pricing.py`:
103
+
104
+ > Nenhum número sai daqui sem `provenance`. Preço que não pode ser
105
+ > verificado devolve custo `None`, nunca uma estimativa silenciosa.
106
+
107
+ Camadas em `PROVENANCE`:
108
+
109
+ | valor | significado |
110
+ |-------------------|----------------------------------------------------------|
111
+ | `measured` | tokens do log + tabela de preço pública |
112
+ | `self_reported` | custo calculado pela própria ferramenta |
113
+ | `tokens_only` | tokens medidos, sem tabela de preço verificada |
114
+ | `assumed` | preço de um modelo **assumido pelo operador**, não no log|
115
+ | `uninstrumented` | a ferramenta não registra consumo (legenda; uso futuro) |
116
+
117
+ Como cada coletor preenche isso hoje:
118
+
119
+ - **Claude Code** — cada sessão com tokens sai com `provenance="measured"`.
120
+ Por evento, `anthropic_usage_cost` precifica se o modelo está na tabela;
121
+ modelo desconhecido incrementa `meta["unpriced"]` e não soma USD (os
122
+ tokens do evento ainda entram no total da sessão).
123
+ - **OpenCode** — lê o custo do SQLite → `self_reported` (vários
124
+ fornecedores; não reaplicamos tabela própria).
125
+ - **Codex** — `openai_usage_cost`. Sem breakdown input/output, ou modelo
126
+ desconhecido → `Cost(None, tokens, "tokens_only")`. Com
127
+ `--assume-codex-model`, se precificar, a sessão vira `assumed`.
128
+
129
+ No relatório, `priced_cost` soma só `measured` + `self_reported`. A
130
+ **cobertura** é sobre tokens (`priced_tokens / total_tokens`), não sobre
131
+ custo: custo não apurado entra como 0 e faria cobertura por USD parecer
132
+ sempre 100%. O doctor ainda exige que `tokens_only` tenha custo ~0.
133
+
134
+ ## 5. Cache incremental (`cache.py`)
135
+
136
+ Varrer o histórico inteiro a cada execução é desperdício: sessão
137
+ encerrada não muda. O cache guarda o resultado por **arquivo de origem**,
138
+ com chave `mtime_ns:size`. Fica em `~/.cache/kostria/` (ou `$XDG_CACHE_HOME`).
139
+ Apagar a pasta é sempre seguro. `VERSION` no módulo invalida o formato
140
+ inteiro quando o cálculo muda. Gravação é atômica (`.tmp` + `os.replace`).
141
+
142
+ Hoje só o coletor do Claude Code usa `SessionCache` (é quem varre a árvore
143
+ de `*.jsonl` sob cada config dir). Dedupe por id de mensagem continua
144
+ valendo mesmo em hit de cache (retomada/compactação copia mensagens
145
+ entre arquivos).
146
+
147
+ ### A armadilha mutation-on-read
148
+
149
+ `_from_dict` precisa extrair `edits`/`reads` do dict serializado. A versão
150
+ bugada fazia `d.pop("edits")` **no dicionário ainda referenciado por
151
+ `self._data`**. Consequência, documentada no próprio código:
152
+
153
+ > Ler o cache destruía o cache, e a atribuição por arquivo morria em
154
+ > silêncio a partir da terceira execução (o custo total continuava certo).
155
+
156
+ A correção é copiar antes de mutar: `d = dict(d)` e só então `pop`.
157
+
158
+ ### Por que há teste de regressão específico
159
+
160
+ `tests/test_cache.py`, classe `TestCacheSurvivesReadWriteCycles`:
161
+
162
+ - `test_edits_survive_three_cycles` — put → save → get/save × 3; as
163
+ edições têm que continuar lá.
164
+ - `test_read_does_not_mutate_internal_state` — após `get`, a chave
165
+ `"edits"` ainda existe em `c._data[...]["sessions"][0]`.
166
+
167
+ O bug era invisível para qualquer asserção de total: o USD batia, a
168
+ distribuição por projeto não. Por isso o teste ataca o estado interno e o
169
+ ciclo de regravação, não só o round-trip feliz.
170
+
171
+ ## 6. `doctor.py`
172
+
173
+ Nasceu de um incidente real (descrito no docstring do módulo): um bug de
174
+ cache apagou as edições de todas as sessões e derrubou a atribuição por
175
+ arquivo, enquanto o **custo total continuou correto**. Conferir o total
176
+ não prova distribuição.
177
+
178
+ A verificação compara **rotas independentes** e invariantes, por exemplo:
179
+
180
+ - coleta sem cache × com cache (total e `by_project`);
181
+ - contagem de edições preservada;
182
+ - soma de `by_project` / `by_account` / `by_attribution` fecha com o total;
183
+ - toda chave de proveniência tem legenda; `tokens_only` não inventa USD;
184
+ - o relatório serializa como JSON estrito (`allow_nan=False`);
185
+ - modo anônimo não vaza e-mail nem `/home/...`;
186
+ - avisos (não críticos): fração com evidência de arquivo, cobertura de
187
+ preço, custo só-cwd, dedupe ativa.
188
+
189
+ ### Disco em movimento
190
+
191
+ `_log_fingerprint` captura `(mtime, size)` de todo jsonl do Claude Code
192
+ antes e depois das coletas. Se os logs mudaram durante o doctor — típico
193
+ quando a sessão que roda o doctor está gravando no próprio projeto
194
+ auditado — divergência entre fresco e cache é **inconclusive**, não
195
+ corrupção. Nesses casos o check é marcado `skipped` em vez de falha, para
196
+ não gritar lobo.
197
+
198
+ ## 7. Convenções do projeto
199
+
200
+ - **Sem dependência de terceiros** — só stdlib. Python `>=3.10`
201
+ (`tomllib` para config exige 3.11+; abaixo disso a config é ignorada com
202
+ aviso).
203
+ - **Sem rede na apuração** — lê disco local. `serve` escuta só em
204
+ `127.0.0.1` de propósito (relatório tem dado sensível).
205
+ - **Config** — `./.kostria.toml` ou `~/.config/kostria/config.toml`; projeto
206
+ sobrepõe usuário; CLI tem a última palavra (`config.apply` só preenche
207
+ buracos).
208
+ - **Testes** — `tests/`, `unittest`.
209
+ - **Rodar a suite:**
210
+
211
+ ```bash
212
+ python3 -m unittest discover -s tests
213
+ ```
214
+
215
+ - **Tom do código** — comentários explicam *por que*, não o óbvio; falha
216
+ de dado de terceiro não pode derrubar a apuração inteira (`_int`
217
+ tolerante, coletor que devolve `available: False`, etc.).
218
+ - **JSON estrito** — nunca emitir `NaN`/`Infinity` (já quebrou o dashboard
219
+ em silêncio; ver `compare._delta` e `cli._json_dump`).
220
+
221
+ ## 8. Como adicionar um coletor
222
+
223
+ Referência: `collectors/__init__.py` e os três coletores existentes.
224
+
225
+ 1. Crie `kostria/collectors/<ferramenta>.py` com:
226
+
227
+ ```python
228
+ def collect(... ) -> tuple[list[Session], dict]:
229
+ ...
230
+ ```
231
+
232
+ - Devolve `(lista_de_Session, meta)`.
233
+ - Se a ferramenta não estiver instalada / sem dados: `([], {"available": False})`
234
+ (ou equivalente), sem levantar.
235
+ - Exceção não tratada ainda é engolida por `collect_all` e vira
236
+ `meta[nome] = {"error": ...}`, mas o ideal é falhar de forma controlada.
237
+
238
+ 2. Preencha `Session` com o que for confiável:
239
+
240
+ - `tool`, `account`, `vendor`, `tokens`, `provenance` (obrigatório no
241
+ sentido de contrato — ver `PROVENANCE`);
242
+ - `cost` como `float` ou `None` (nunca chute silencioso);
243
+ - `edits` / `reads` como `list[Edit]` se a ferramenta expuser paths —
244
+ isso alimenta atribuição forte; se não expuser, deixe vazio e a
245
+ sessão cairá em `cwd` (como OpenCode/Codex hoje);
246
+ - `models`, `token_mix`, `started_at`, `source`, `cwd` conforme existir.
247
+
248
+ 3. Registre em `collect_all`:
249
+
250
+ ```python
251
+ from . import claude_code, codex, opencode, minha_ferramenta
252
+ ...
253
+ for name, mod in (..., ("minha_ferramenta", minha_ferramenta)):
254
+ ```
255
+
256
+ Passe kwargs específicos só quando o coletor os aceitar (padrão atual:
257
+ `use_cache` só no Claude Code, `assume_model` só no Codex).
258
+
259
+ 4. Precificação:
260
+
261
+ - Se houver tabela pública estável, coloque em `pricing.py` e devolva
262
+ `Cost` com `measured` ou `tokens_only`.
263
+ - Se a ferramenta já calcula o custo, use `self_reported` e documente
264
+ no docstring do coletor (caso OpenCode).
265
+ - Se o operador puder forçar premissa, use `assumed` e exponha flag na
266
+ CLI/config (caso Codex).
267
+
268
+ 5. Testes em `tests/` com fixtures mínimas (arquivo/sqlite temporário),
269
+ cobrindo: ausência da ferramenta, parse tolerante, proveniência
270
+ correta, e — se houver cache — ciclo get/save sem mutar estado.
271
+
272
+ Não é necessário implementar cache no coletor novo; só vale a pena se a
273
+ varredura for cara e o artefato for imutável por `mtime`/tamanho como no
274
+ Claude Code.
@@ -0,0 +1,80 @@
1
+ # Changelog
2
+
3
+ Tudo o que mudou no Kostria, agrupado por sessão de trabalho e na ordem em que foi construído.
4
+
5
+ ## [2026-07-25]
6
+
7
+ ### Núcleo do produto
8
+
9
+ - Coletores multi-conta para Claude Code, OpenCode (SQLite) e Codex.
10
+ - Custo por feature: arquivo → commit → área/item do board.
11
+ - Atribuição pelos arquivos editados, não pelo diretório de trabalho atual.
12
+ - Worktree resolvido para o repositório principal via `git-common-dir`.
13
+ - Sinais iniciais: reprocessamento, retrabalho, eficiência por modelo e projeção.
14
+ - CLI `scan`, `report` e `serve` com relatório HTML.
15
+ - Exportação CSV para o financeiro (152 linhas por dimensão).
16
+ - README, landing e testes iniciais; `pyproject` com descoberta explícita de pacotes para evitar falha no `pip install -e`.
17
+ - Recuperação do projeto de origem a partir do scratch de sessão do Claude Code, decodificando o caminho codificado em `/tmp/claude-<uid>/<cwd-codificado>/<sessão>/` e casando contra os projetos existentes.
18
+ - `--repo` repetível e descoberta automática de repositórios git entre os projetos que mais gastaram.
19
+ - Arquivo de configuração `.kostria.toml` para guardar padrões de workspace, repo, budget, período e modelo assumido; chave desconhecida ou tipo errado viram aviso no stderr, e TOML quebrado não derruba o comando.
20
+ - `--format md` para resumo colável em e-mail ou ticket.
21
+ - `kostria init` detecta pastas de trabalho plausíveis e já escreve o `.kostria.toml` inicial.
22
+ - CSV mais completo incluindo as dimensões `by_attribution` e `project_budgets`.
23
+ - Atribuição em cascata de evidência: escreveu > leu > diretório. Read, NotebookRead, Grep e Glob passam a trazer o caminho como evidência secundária.
24
+ - Sinais novos: dias fora do padrão, sessões mais caras e perfil semanal.
25
+ - `kostria watch` para vigia ao vivo no terminal.
26
+ - Selo SVG local (`kostria badge`) sem depender de serviço externo.
27
+
28
+ ### Recorte temporal e comparação
29
+
30
+ - `--since`/`--until` aceitam AAAA-MM-DD, `30d`, `2w`, `6m`, `1y`, `hoje` e `ontem`.
31
+ - Sessão que atravessa a fronteira do período entra rateada pela fração de edições dentro da janela, sem mutar a sessão original.
32
+ - `--budget` compara a projeção mensal com o limite e alerta se estoura.
33
+ - `--compare` mede a janela imediatamente anterior, de mesma duração, e mostra as maiores variações por projeto, conta, modelo e fornecedor.
34
+ - Corrigido: a duração da janela passou a contar os dois extremos (por exemplo, 08 a 14 vira 7 dias, não 6).
35
+
36
+ ### Integridade e cache
37
+
38
+ - Cache incremental da coleta: validação por mtime e tamanho, passando de 3,6s para 0,6s; `--no-cache` para ignorar.
39
+ - **Corrigido**: "ler o cache destruía o cache". `_from_dict` fazia `d.pop('edits')` sobre o dicionário guardado em `self._data`, então a leitura apagava a chave e o `save()` seguinte regravava o cache sem as edições. A partir do terceiro ciclo sobravam 17 edições de 1264 e toda a atribuição por arquivo colapsava no cwd. A correção copia o dict antes do `pop`; caches corrompidos são invalidados pela `VERSION` 4.
40
+ - `kostria doctor` para verificação de integridade dos números, comparando rotas independentes (cache × leitura direta, soma das partes, proveniência, custo negativo, custo não atribuído, deduplicação etc.).
41
+ - Tabela de preços da OpenAI completa a lacuna do Codex ($413,20): input, cached input e output separados; `--assume-codex-model` permite declarar o modelo ausente com proveniência `assumed`.
42
+ - Tratamento de dados malformados: JSONL corrompido, arquivo binário, token não numérico, valor negativo, timestamp inválido, período invertido, caminho com acento/quebra de linha/aspas e SQLite corrompido. Um único valor estranho deixava de derrubar a coleta inteira.
43
+ - **Corrigido**: `float('inf')` na variação percentual era serializado como `Infinity`, que não é JSON válido, e o dashboard ficava vazio sem logar erro no servidor. Sem base anterior, `pct` passou a ser `None` e o consumidor usa o campo `new`.
44
+ - **Corrigido**: modo anônimo vazava e-mail real e caminho absoluto da casa em `collectors.config_dirs` e em `signals`. A limpeza agora é recursiva e cega ao formato, varrendo o payload por padrões de e-mail e caminho absoluto.
45
+ - **Corrigido**: `kostria doctor` falhava intermitentemente quando o próprio Claude Code gravava logs durante a leitura. Agora ele tira uma "fotografia" (mtime + tamanho) dos logs antes do ciclo e marca as checagens vulneráveis como "pulado" quando o disco está em movimento.
46
+ - Serialização JSON com `allow_nan=False` para falhar alto em vez de emitir payload inválido.
47
+ - Servidor passou a atender em paralelo (`ThreadingHTTPServer`) e a reaproveitar a apuração com lock; falhas do processo viram HTTP 500 em vez de derrubar o servidor.
48
+
49
+ ### UX e UI do painel
50
+
51
+ - Dashboard interativo local servido por `kostria serve`.
52
+ - Dashboard reescrito com direção visual própria: régua proporcional de atribuição no topo, tipografia monoespaçada para números, paleta fria (teal para valor medido, âmbar para lacuna declarada), navegação lateral com deep-link por hash e acessibilidade.
53
+ - Relatório estático com a mesma identidade visual do dashboard; regras de impressão para não cortar blocos no PDF.
54
+ - Landing com a tese como demonstração; depois com seção de objeções e comparação honesta.
55
+ - Dashboard com busca global, atalhos de teclado, cópia de valor em dólar com um clique, exportação client-side (JSON/CSV), skeleton de carregamento, estados vazios específicos, tooltips de ajuda, microcopy revisado e indicador de atualidade relativa.
56
+ - **Corrigido**: h1 da landing quebrava em 4 linhas em tela larga; ajustado para 3 linhas.
57
+ - **Corrigido**: vazamento horizontal no painel em tela estreita (item flex não encolhia e a página ganhava largura extra).
58
+ - **Corrigido**: cinza de apoio estava abaixo do mínimo de contraste WCAG AA; trocado por `#5a6c7a`.
59
+ - **Corrigido**: entidades HTML escapavam como literais na tela (`Sess&otilde;es`); texto acentuado vai direto em UTF-8.
60
+ - Corrigido: mockups de terminal perdiam quebra de linha por falta de `white-space: pre-wrap`.
61
+
62
+ ### Orçamento por projeto e previsão de fim de mês
63
+
64
+ - `--project-budget NOME=VALOR` (repetível) e tabela `[project_budgets]` no `.kostria.toml` para alertar quando um projeto específico estoura, não só o total da organização.
65
+ - `end_of_month_forecast()` responde quanto já foi gasto desde o dia 1 do mês corrente mais a média recente extrapolada pelos dias que faltam no calendário (não numa janela móvel genérica).
66
+ - Exposto em `scan`, no relatório HTML, em `--format md` e no dashboard.
67
+
68
+ ### Testes e qualidade
69
+
70
+ - **Bug de teste revelado por mutação**: os testes de preço anteriores importavam constantes do módulo e calculavam `expected = 3.0 * CACHE_READ`, o que fazia a expectativa mudar junto com o bug. Foram travados em literais calculados à mão, e as 5 mutações passaram a falhar.
71
+ - Testes adversariais para dados corrompidos e bordas de calendário (dia 1, último dia do mês, fevereiro bissexto e não-bissexto).
72
+ - Testes de contraste travando onze pares cor/fundo com cálculo de razão, não com impressão visual.
73
+ - Teste de ponta a ponta que roda a CLI de verdade sobre um `HOME` sintético, em vez de substituir o coletor por um duplo.
74
+ - Total final: 287 testes.
75
+
76
+ ### Outras correções
77
+
78
+ - Corrigido: `--anonymize` antes do subcomando era ignorado silenciosamente por causa do `argparse` com `parents=`; as flags comuns passaram a ser lidas do `argv` inteiro e reaplicadas.
79
+ - Corrigido: `main()` devolvia o relatório como código de saída depois que `doctor` foi ligado; apenas `doctor` devolve código de erro.
80
+ - Corrigido: `json.load(open(cand))` sem fechar o arquivo em `_account_of`, achado pelo `ResourceWarning` nos testes.
kostria-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pedro Henrique Quadro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,16 @@
1
+ # Inclui os assets web embarcados no pacote (dashboard, landing e vendor
2
+ # vendorizado) tanto na sdist quanto na wheel. Sao necessarios para que
3
+ # `kostria serve` funcione quando instalado via pip.
4
+ recursive-include kostria/web *.html
5
+ recursive-include kostria/web/vendor *.js
6
+ recursive-include kostria/web/vendor/fonts *
7
+ recursive-include kostria/landing *.html
8
+ recursive-include kostria/landing/vendor *.js
9
+ recursive-include kostria/landing/vendor *.mjs
10
+ recursive-include kostria/landing/vendor/fonts *
11
+
12
+ # Metadados e docs da raiz.
13
+ include README.md
14
+ include CHANGELOG.md
15
+ include ARCHITECTURE.md
16
+ include LICENSE
kostria-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: kostria
3
+ Version: 0.1.0
4
+ Summary: Auditoria local de custo de IA: atribui gasto a projeto, feature e conta
5
+ Author-email: Pedro Henrique Quadro <184245414+PedroHenrique0713@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/PedroHenrique0713/kostria
8
+ Project-URL: Repository, https://github.com/PedroHenrique0713/kostria
9
+ Project-URL: Issues, https://github.com/PedroHenrique0713/kostria/issues
10
+ Project-URL: Changelog, https://github.com/PedroHenrique0713/kostria/blob/main/CHANGELOG.md
11
+ Keywords: ai,llm,cost,audit,attribution,claude-code,opencode,codex,token-usage,developer-tools,observability
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: POSIX
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Build Tools
22
+ Classifier: Topic :: System :: Monitoring
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Provides-Extra: dev
28
+ Requires-Dist: build>=1.0; extra == "dev"
29
+ Requires-Dist: twine>=5.0; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # Kostria
33
+
34
+ <p align="center">
35
+ <img src="https://kostria.hypermind.space/static/brand/og-default.png" alt="Kostria" width="480">
36
+ </p>
37
+
38
+ <p align="center">
39
+ <strong>Pasta nao e projeto.</strong> O Kostria segue o arquivo que a IA
40
+ editou de verdade, resolve o repositorio certo e extrai dali feature e
41
+ ticket do Jira — em 30 segundos, sem proxy, sem SDK, sem mudar codigo.
42
+ </p>
43
+
44
+ <p align="center">
45
+ <a href="https://pypi.org/project/kostria/"><img src="https://img.shields.io/pypi/v/kostria" alt="PyPI"></a>
46
+ <a href="LICENSE"><img src="https://img.shields.io/pypi/l/kostria" alt="MIT"></a>
47
+ <a href="#"><img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="zero dependencies"></a>
48
+ <a href="#"><img src="https://img.shields.io/badge/python-3.10+-blue" alt="Python 3.10+"></a>
49
+ </p>
50
+
51
+ ---
52
+
53
+ ## O que e
54
+
55
+ Ferramentas de IA (Claude Code, OpenCode, Codex CLI) custam caro e crescem
56
+ rapido. O painel do fornecedor mostra custo por conta — nao por projeto, nao
57
+ por feature, nao por ticket. Planilha juntada na mao e lenda com erro.
58
+
59
+ O **Kostria** le os logs que essas ferramentas ja deixam no disco, segue o
60
+ arquivo editado ate o commit que o fechou, e extrai dali o escopo da feature e
61
+ o ticket. O board do time agrega isso pra todo mundo, sem a sessao bruta sair
62
+ do disco de ninguem.
63
+
64
+ ```
65
+ $ pip install kostria
66
+ $ kostria scan
67
+
68
+ custo total: $2,480.00 tokens: 1.2B sessoes: 89
69
+
70
+ by feature:
71
+ checkout-pix (PROJ-118) $942.40 38%
72
+ onboarding (PROJ-204) $719.20 29%
73
+ busca (PROJ-091) $496.00 20%
74
+ ```
75
+
76
+ **Por que atribuir por cwd mente:** 65% das edicoes do Claude Code acontecem
77
+ em repos que NAO sao o diretorio de trabalho. Worktree, monorepo, sessao de
78
+ terminal — o `cwd` joga o custo no projeto errado. O Kostria segue o arquivo.
79
+
80
+ ---
81
+
82
+ ## Quickstart
83
+
84
+ ```bash
85
+ pip install kostria
86
+ kostria init # detecta suas pastas de trabalho
87
+ kostria scan # 30 segundos ate o primeiro resultado
88
+ kostria serve # dashboard em http://127.0.0.1:8787
89
+ ```
90
+
91
+ Zero dependencias. So Python 3.10+ e stdlib.
92
+
93
+ ---
94
+
95
+ ## O que o Kostria responde
96
+
97
+ - **Quanto cada projeto consumiu?** Por arquivo editado, nao por diretorio.
98
+ - **Quanto cada feature custou?** Arquivo → commit → escopo + ticket.
99
+ - **Qual modelo e mais eficiente?** Custo por edicao entregue, nao por token.
100
+ - **Quanto da assinatura foi usado?** Separa consumo real do fixo (Claude
101
+ Pro/Max).
102
+ - **Tem conta ociosa?** Sinaliza contas sem atividade nos ultimos 14 dias.
103
+ - **O orcamento vai estourar?** Projecao mensal + previsao de fechamento do mes.
104
+
105
+ ---
106
+
107
+ ## Suporta
108
+
109
+ | Ferramenta | Coletor | Preco |
110
+ |---|---|---|
111
+ | Claude Code | `~/.claude*/projects/**/*.jsonl` | Tabela Anthropic publica (medido) |
112
+ | OpenCode | `~/.local/share/opencode/opencode.db` | Auto-reportado (confiavel) |
113
+ | Codex CLI | `~/.codex/sessions/rollout-*.jsonl` | Tokens (sem tabela OpenAI) |
114
+
115
+ ---
116
+
117
+ ## Planos
118
+
119
+ | Plano | Preco | Inclui |
120
+ |---|---|---|
121
+ | **Local** | Gratis | Motor de atribuicao completo, painel local, retencao 90d |
122
+ | **Pro** | $9/mes | Integracao Jira/Linear/GitHub Issues, sync cross-machine |
123
+ | **Team** | $14/assento/mes | Board agregado do time, budget alerts, org-scoped sync |
124
+
125
+ O motor e open source (MIT). O board do time e opcional e hospedado.
126
+
127
+ ---
128
+
129
+ ## Comandos
130
+
131
+ ```bash
132
+ kostria scan # resumo no terminal
133
+ kostria scan --since 30d --compare # contra mes anterior
134
+ kostria scan --budget 3000 # alerta de orcamento
135
+ kostria scan --anonymize # mascara nomes (compartilhavel)
136
+
137
+ kostria report # HTML (abre no navegador)
138
+ kostria report --format json -o dados.json
139
+ kostria report --format csv -o custos.csv
140
+
141
+ kostria serve # dashboard local em 127.0.0.1:8787
142
+ kostria watch # vigia ao vivo no terminal
143
+ kostria doctor # 11 checagens de integridade
144
+ kostria badge -o badge.svg # selo shields.io local
145
+
146
+ kostria sync # envia agregado pro board do time
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Privacidade
152
+
153
+ - Roda 100% local. Nada sai da maquina sem voce pedir.
154
+ - O `kostria sync` envia so o agregado: projeto, dia, custo, tokens. Nunca
155
+ caminho de arquivo, nunca conteudo de sessao, nunca e-mail.
156
+ - `--anonymize` troca nomes por hash estavel para relatorios compartilhaveis.
157
+
158
+ ---
159
+
160
+ ## Por que o Kostria e diferente
161
+
162
+ Nao e um proxy (Helicone). Nao e tracing de qualidade (Langfuse). Nao e FinOps
163
+ de cloud (Vantage). E o unico que le os logs que ja estao no disco, segue o
164
+ arquivo ate o commit e extrai dali feature e ticket — sem SDK, sem proxy, sem
165
+ mudar uma linha de codigo.
166
+
167
+ [Mapa completo de concorrentes →](COMPETITORS.md)
168
+
169
+ ---
170
+
171
+ ## Docs
172
+
173
+ - [Estrategia de lancamento](GTM.md)
174
+ - [Mapa de concorrentes](COMPETITORS.md)
175
+ - [One-pager / pitch](PITCH.md)
176
+ - [Arquitetura](ARCHITECTURE.md)
177
+ - [Guia de contribuicao](CONTRIBUTING.md)
178
+ - [Saas blueprint](SAAS-BLUEPRINT.md)
179
+ - [Checklist de lancamento](LAUNCH.md)
180
+
181
+ ---
182
+
183
+ ## Testes
184
+
185
+ ```bash
186
+ python3 -m unittest discover -s tests # CLI: 388 testes
187
+ cd saas && python3 -m pytest -q # SaaS: 123 testes
188
+ ```
189
+
190
+ O SaaS nao exige Postgres: a suite injeta um pool em memoria.
191
+
192
+ ---
193
+
194
+ ## Licenca
195
+
196
+ MIT. O CLI e livre. O board hospedado e opcional.
197
+
198
+ Feito em Vicosa, MG por [Pedro Henrique](https://github.com/PedroHenrique0713).