wendkeep 0.35.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -0
- package/README.pt-BR.md +1 -0
- package/bin/wendkeep.mjs +3 -0
- package/hooks/pricing.json +5 -2
- package/hooks/subagent-usage.mjs +40 -4
- package/hooks/token-usage.mjs +16 -2
- package/package.json +1 -1
- package/src/cost.mjs +27 -2
- package/src/rebuild-costs.mjs +40 -0
- package/src/stats.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -870,3 +870,9 @@ Initial release — the capture engine, extracted from a system in daily product
|
|
|
870
870
|
[0.2.7]: https://www.npmjs.com/package/wendkeep/v/0.2.7
|
|
871
871
|
[0.2.0]: https://www.npmjs.com/package/wendkeep/v/0.2.0
|
|
872
872
|
[0.1.0]: https://www.npmjs.com/package/wendkeep/v/0.1.0
|
|
873
|
+
# 0.36.0 — 2026-07-11
|
|
874
|
+
|
|
875
|
+
- Custos de subagents agora são atribuídos ao modelo que realmente os executou por meio de um ledger por sessão; campos legados continuam compatíveis.
|
|
876
|
+
- Novo `wendkeep cost rebuild`, dry-run por padrão, reconstrói sessões antigas via `SESSION_REGISTRY` e, com `--apply`, grava um manifesto auditável em `.brain/COST_REBUILD.json`.
|
|
877
|
+
- Totais combinados de tokens/custo passam a ser persistidos explicitamente nas notas.
|
|
878
|
+
- Preços API-equivalentes de GPT-5.6 Sol, Terra e Luna adicionados à tabela editável e ao fallback interno.
|
package/README.md
CHANGED
|
@@ -118,6 +118,7 @@ No re‑copying, no snapshot to re‑sync — the package is the single source o
|
|
|
118
118
|
| `wendkeep sensors <sub>` | `list` / `add <id> "<command>"` — view/edit `wendkeep.sensors.json` (JSON Schema shipped). |
|
|
119
119
|
| `wendkeep cost [--since d]` | Aggregate AI-coding spend across the vault's sessions — total, by model, by day (`--json`). |
|
|
120
120
|
| `wendkeep import [opts]` | **Retroactive memory** — backfill past **Claude + Codex** sessions into the vault (deduped by `session_id`). `--source all\|claude\|codex` / `--from <dir>` / `--codex-from <dir>` / `--since d` / `--limit n` / `--dry-run` / `--json`. |
|
|
121
|
+
| `wendkeep cost rebuild [opts]` | Recalcula custos históricos do transcript principal e subagents usando `SESSION_REGISTRY`. Dry-run por padrão; `--apply` atualiza notas e grava `.brain/COST_REBUILD.json`. Aceita `--session`, `--limit` e `--json`. |
|
|
121
122
|
| `wendkeep lesson add "t" "l"` | Record a project-local lesson (injected at the next SessionStart). |
|
|
122
123
|
| `wendkeep sync-defs` | Copy `.brain/agents\|skills` into `.codex/agents`, `.claude/skills`, `.agents/skills`; `--check` detects drift. |
|
|
123
124
|
| `wendkeep validate-memory [path]` | Validate `.brain/CORE.md` (cap 25, 3 sections, no secrets/PII). |
|
package/README.pt-BR.md
CHANGED
|
@@ -115,6 +115,7 @@ Sem recopiar, sem snapshot pra re‑sincronizar — o pacote é a única fonte d
|
|
|
115
115
|
| `wendkeep spec <sub>` | Specs vivos: `list` / `show <capability>`. |
|
|
116
116
|
| `wendkeep sensors <sub>` | `list` / `add <id> "<comando>"` — vê/edita `wendkeep.sensors.json` (JSON Schema incluso). |
|
|
117
117
|
| `wendkeep cost [opts]` | Agrega o gasto de IA nas sessões do cofre — total, por modelo, por dia · `--top [N]` · `--trend [day\|week\|month]` (+ projeção) · `--write` (gera `00-Custo.md`) · `--json`. |
|
|
118
|
+
| `wendkeep cost rebuild [opts]` | Reconstrói custos históricos do transcript principal e subagents via `SESSION_REGISTRY`. Dry-run por padrão; `--apply` grava notas e `.brain/COST_REBUILD.json`. |
|
|
118
119
|
| `wendkeep stats [--vault P]` | Uma linha compartilhável: sessões · prompts · gasto · período · modelos (`--json`). |
|
|
119
120
|
| `wendkeep import [opts]` | **Memória retroativa** — importa sessões passadas de **Claude + Codex** pro cofre (dedup por `session_id`). `--source all\|claude\|codex` / `--from <dir>` / `--codex-from <dir>` / `--stamp-ids` / `--since d` / `--limit n` / `--dry-run` / `--json`. |
|
|
120
121
|
| `wendkeep dashboard [--force]` | (Re)gera os Bases filtrados por pasta + o MOC `00-Dashboard`. |
|
package/bin/wendkeep.mjs
CHANGED
|
@@ -51,6 +51,9 @@ Usage:
|
|
|
51
51
|
wendkeep cost [opts] Aggregate AI-coding spend across the vault's sessions.
|
|
52
52
|
--since <date> · --top [N] (priciest) · --trend [day|week|month]
|
|
53
53
|
(+ run-rate projection) · --write (generate 00-Custo.md) · --json.
|
|
54
|
+
wendkeep cost rebuild Recalculate historical parent + subagent costs from SESSION_REGISTRY.
|
|
55
|
+
Dry-run by default · --apply writes notes + .brain/COST_REBUILD.json
|
|
56
|
+
· --session <id|file> · --limit N · --json.
|
|
54
57
|
wendkeep stats [--vault P] One shareable line: sessions · prompts · spend · span · models (--json).
|
|
55
58
|
wendkeep import [opts] Backfill: import this project's past Claude + Codex sessions into
|
|
56
59
|
the vault (deduped by session_id). --source all|claude|codex (default
|
package/hooks/pricing.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_nota": "Preços API por milhão de tokens. cachedInput = cache read. Cache write aplica multiplicador no código: 5m = 1.25x input, 1h = 2x input. Editar aqui quando o provedor mudar preços (sem mexer no .mjs). Se o arquivo sumir ou ficar inválido, o hook usa a tabela embutida em token-usage.mjs.",
|
|
3
|
-
"_fonte": "
|
|
4
|
-
"models": {
|
|
3
|
+
"_fonte": "OpenAI https://openai.com/index/gpt-5-6/ e https://help.openai.com/en/articles/20001325-a-preview-of-gpt-56-sol-terra-and-luna; Anthropic https://www.anthropic.com/pricing — conferido 2026-07-11",
|
|
4
|
+
"models": {
|
|
5
|
+
"gpt-5.6-sol": { "label": "GPT-5.6 Sol API", "provider": "openai", "input": 5, "cachedInput": 0.5, "output": 30 },
|
|
6
|
+
"gpt-5.6-terra": { "label": "GPT-5.6 Terra API", "provider": "openai", "input": 2.5, "cachedInput": 0.25, "output": 15 },
|
|
7
|
+
"gpt-5.6-luna": { "label": "GPT-5.6 Luna API", "provider": "openai", "input": 1, "cachedInput": 0.1, "output": 6 },
|
|
5
8
|
"gpt-5.5": {
|
|
6
9
|
"label": "GPT-5.5 API",
|
|
7
10
|
"provider": "openai",
|
package/hooks/subagent-usage.mjs
CHANGED
|
@@ -91,6 +91,7 @@ export function collectSubagentUsage(sessionDir) {
|
|
|
91
91
|
let count = 0;
|
|
92
92
|
let calls = 0;
|
|
93
93
|
let cost = 0;
|
|
94
|
+
const modelMap = new Map();
|
|
94
95
|
|
|
95
96
|
for (const f of files) {
|
|
96
97
|
const summary = summarizeTokenUsage(parseTokenUsageFromTranscript(f));
|
|
@@ -112,8 +113,18 @@ export function collectSubagentUsage(sessionDir) {
|
|
|
112
113
|
calls: summary.calls,
|
|
113
114
|
tokens,
|
|
114
115
|
cost: round4(summary.costs.model),
|
|
116
|
+
modelRows: summary.modelRows,
|
|
115
117
|
});
|
|
116
118
|
|
|
119
|
+
for (const row of summary.modelRows || []) {
|
|
120
|
+
const key = `${row.provider || '?'}\u0000${row.model || '?'}`;
|
|
121
|
+
const current = modelMap.get(key) || { provider: row.provider || '?', model: row.model || '?', calls: 0, tokens: 0, cost: 0 };
|
|
122
|
+
current.calls += row.calls || 0;
|
|
123
|
+
current.tokens += tokensTotal(row.usage);
|
|
124
|
+
current.cost += row.costs?.model || 0;
|
|
125
|
+
modelMap.set(key, current);
|
|
126
|
+
}
|
|
127
|
+
|
|
117
128
|
count += 1;
|
|
118
129
|
calls += summary.calls;
|
|
119
130
|
cost += summary.costs.model;
|
|
@@ -151,7 +162,9 @@ export function collectSubagentUsage(sessionDir) {
|
|
|
151
162
|
return {
|
|
152
163
|
subagents,
|
|
153
164
|
workflows,
|
|
154
|
-
aggregate: { count, calls, tokens: tokensTotal(usageAgg), cost: round4(cost), wasted, usage: usageAgg, tools: [...allTools]
|
|
165
|
+
aggregate: { count, calls, tokens: tokensTotal(usageAgg), cost: round4(cost), wasted, usage: usageAgg, tools: [...allTools],
|
|
166
|
+
modelRows: [...modelMap.values()].map((r) => ({ ...r, cost: round4(r.cost), source: 'subagent' })),
|
|
167
|
+
},
|
|
155
168
|
};
|
|
156
169
|
}
|
|
157
170
|
|
|
@@ -173,19 +186,22 @@ export function renderSubagentSection(c) {
|
|
|
173
186
|
.map((s) => `| ${s.id} | ${s.agentType || '-'} | ${s.workflow || '-'} | ${s.model} | ${s.tools} | ${fmt(s.tokens)} | ${usd(s.cost)} |`)
|
|
174
187
|
.join('\n');
|
|
175
188
|
const wasteLine = a.wasted ? `\n- **Desperdiçado (runs killed/failed):** ${usd(a.wasted)}` : '';
|
|
189
|
+
const combinedLine = c.combined ? `\n- **Sessão completa (main + subagents):** ${fmt(c.combined.tokens)} tokens · ${usd(c.combined.cost)}` : '';
|
|
190
|
+
const modelRows = c.combined?.models?.map((m) => `| ${m.model} | ${m.source} | ${fmt(m.tokens)} | ${usd(m.cost)} |`).join('\n') || '';
|
|
191
|
+
const models = modelRows ? `\n\n### Por modelo (sessão completa)\n\n| Modelo | Origem | Tokens | Custo |\n|---|---|---:|---:|\n${modelRows}` : '';
|
|
176
192
|
return `## Subagents & Workflows
|
|
177
193
|
|
|
178
|
-
> Custo de subagents/workflows desta sessão
|
|
194
|
+
> Custo de subagents/workflows desta sessão, seguido do total combinado e da atribuição por modelo.
|
|
179
195
|
|
|
180
196
|
- **Subagents:** ${a.count} · ${a.calls} chamadas · ${fmt(a.tokens)} tokens · ${usd(a.cost)}
|
|
181
197
|
- **Workflows:** ${wf}
|
|
182
|
-
- **Tools (subagents):** ${tools}${wasteLine}
|
|
198
|
+
- **Tools (subagents):** ${tools}${wasteLine}${combinedLine}
|
|
183
199
|
|
|
184
200
|
### Por subagent (${a.count})
|
|
185
201
|
|
|
186
202
|
| Agent | Tipo | Workflow | Modelo | Tools | Tokens | Custo |
|
|
187
203
|
|---|---|---|---|---:|---:|---:|
|
|
188
|
-
${rows}`;
|
|
204
|
+
${rows}${models}`;
|
|
189
205
|
}
|
|
190
206
|
|
|
191
207
|
function setFrontmatterField(content, key, value) {
|
|
@@ -228,6 +244,26 @@ export function upsertSubagentUsage(sessionPath, transcriptPath) {
|
|
|
228
244
|
content = setFrontmatterField(content, 'subagents_tools', `"${(a.tools || []).join(', ')}"`);
|
|
229
245
|
content = setFrontmatterField(content, 'subagents_wasted_usd', a.wasted || 0);
|
|
230
246
|
content = setFrontmatterField(content, 'tokens_total_incl_subagents', frontmatterNumber(content, 'tokens_total') + a.tokens);
|
|
247
|
+
content = setFrontmatterField(content, 'custo_total_incl_subagents_usd', round4(frontmatterNumber(content, 'custo_modelo_usd') + a.cost));
|
|
248
|
+
let mainRows = [];
|
|
249
|
+
try {
|
|
250
|
+
const main = summarizeTokenUsage(parseTokenUsageFromTranscript(transcriptPath));
|
|
251
|
+
mainRows = (main.modelRows || []).map((r) => ({
|
|
252
|
+
provider: r.provider || '?', model: r.model || '?', source: 'main', calls: r.calls || 0,
|
|
253
|
+
tokens: tokensTotal(r.usage), cost: round4(r.costs?.model || 0),
|
|
254
|
+
}));
|
|
255
|
+
} catch { /* preserve legacy aggregate fallback */ }
|
|
256
|
+
if (!mainRows.length) {
|
|
257
|
+
const mainModel = (content.match(/^custo_modelo_label:\s*["']?([^"'\r\n]+)["']?\s*$/m) || [])[1] || '?';
|
|
258
|
+
mainRows = [{ model: mainModel, source: 'main', cost: round4(frontmatterNumber(content, 'custo_modelo_usd')), tokens: frontmatterNumber(content, 'tokens_total') }];
|
|
259
|
+
}
|
|
260
|
+
const ledger = [...mainRows, ...(a.modelRows || [])];
|
|
261
|
+
collected.combined = {
|
|
262
|
+
tokens: frontmatterNumber(content, 'tokens_total') + a.tokens,
|
|
263
|
+
cost: round4(frontmatterNumber(content, 'custo_modelo_usd') + a.cost),
|
|
264
|
+
models: ledger,
|
|
265
|
+
};
|
|
266
|
+
content = setFrontmatterField(content, 'custo_por_modelo_json', `'${JSON.stringify(ledger).replaceAll("'", "''")}'`);
|
|
231
267
|
content = upsertSection(content, '## Subagents & Workflows', renderSubagentSection(collected));
|
|
232
268
|
writeFileSync(sessionPath, content, 'utf8');
|
|
233
269
|
return true;
|
package/hooks/token-usage.mjs
CHANGED
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
// Cache write: 5m = 1.25x input, 1h = 2x input (multiplicadores em calculateCost).
|
|
13
13
|
// Tabela editável em pricing.json (mesma pasta); esta é o fallback embutido
|
|
14
14
|
// usado quando o JSON some ou fica inválido — o hook nunca deve quebrar por isso.
|
|
15
|
-
const DEFAULT_PRICE_REFERENCE = {
|
|
15
|
+
const DEFAULT_PRICE_REFERENCE = {
|
|
16
|
+
'gpt-5.6-sol': { label: 'GPT-5.6 Sol API', provider: 'openai', input: 5, cachedInput: 0.5, output: 30 },
|
|
17
|
+
'gpt-5.6-terra': { label: 'GPT-5.6 Terra API', provider: 'openai', input: 2.5, cachedInput: 0.25, output: 15 },
|
|
18
|
+
'gpt-5.6-luna': { label: 'GPT-5.6 Luna API', provider: 'openai', input: 1, cachedInput: 0.1, output: 6 },
|
|
16
19
|
'gpt-5.5': {
|
|
17
20
|
label: 'GPT-5.5 API',
|
|
18
21
|
provider: 'openai',
|
|
@@ -82,7 +85,18 @@ export function loadPriceReference(file = PRICING_FILE) {
|
|
|
82
85
|
|
|
83
86
|
const PRICE_REFERENCE = loadPriceReference();
|
|
84
87
|
|
|
85
|
-
const MODEL_ALIASES = {
|
|
88
|
+
const MODEL_ALIASES = {
|
|
89
|
+
'gpt-5.6': 'gpt-5.6-sol',
|
|
90
|
+
'gpt-5.6-sol': 'gpt-5.6-sol',
|
|
91
|
+
'gpt-5-6-sol': 'gpt-5.6-sol',
|
|
92
|
+
'openai/gpt-5.6': 'gpt-5.6-sol',
|
|
93
|
+
'openai/gpt-5.6-sol': 'gpt-5.6-sol',
|
|
94
|
+
'gpt-5.6-terra': 'gpt-5.6-terra',
|
|
95
|
+
'gpt-5-6-terra': 'gpt-5.6-terra',
|
|
96
|
+
'openai/gpt-5.6-terra': 'gpt-5.6-terra',
|
|
97
|
+
'gpt-5.6-luna': 'gpt-5.6-luna',
|
|
98
|
+
'gpt-5-6-luna': 'gpt-5.6-luna',
|
|
99
|
+
'openai/gpt-5.6-luna': 'gpt-5.6-luna',
|
|
86
100
|
'gpt-5.5': 'gpt-5.5',
|
|
87
101
|
'gpt-5_5': 'gpt-5.5',
|
|
88
102
|
'openai/gpt-5.5': 'gpt-5.5',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "A persistent-memory harness for AI coding agents on your Obsidian vault: turn-by-turn session capture plus a native, zero-dependency spec→change→verify→archive loop (sensor-gated, independent verdict, mutation discrimination). Local-first, agent-agnostic (Claude Code, Codex, Cursor…).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cost.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
6
6
|
import { getLocale } from '../hooks/locale.mjs';
|
|
7
|
+
import { rebuildSessionCosts } from './rebuild-costs.mjs';
|
|
7
8
|
|
|
8
9
|
const round4 = (n) => Math.round((Number(n) || 0) * 10000) / 10000;
|
|
9
10
|
const usd = (n) => `$${(Number(n) || 0).toFixed(4)}`;
|
|
@@ -20,6 +21,11 @@ function fmValue(content, key) {
|
|
|
20
21
|
// Parse the cost-relevant frontmatter of one note; null if it is not a session note.
|
|
21
22
|
export function parseSessionCost(content) {
|
|
22
23
|
if (!/^type:\s*session\s*$/m.test(content)) return null;
|
|
24
|
+
let ledger = [];
|
|
25
|
+
try {
|
|
26
|
+
const raw = fmValue(content, 'custo_por_modelo_json').replaceAll("''", "'");
|
|
27
|
+
if (raw) ledger = JSON.parse(raw);
|
|
28
|
+
} catch { /* legacy/malformed note: use safe fallback below */ }
|
|
23
29
|
return {
|
|
24
30
|
date: (fmValue(content, 'date') || '').slice(0, 10),
|
|
25
31
|
model: fmValue(content, 'custo_modelo_label') || fmValue(content, 'modelo') || '?',
|
|
@@ -29,6 +35,7 @@ export function parseSessionCost(content) {
|
|
|
29
35
|
tokens: Number(fmValue(content, 'tokens_total')) || 0,
|
|
30
36
|
subTokens: Number(fmValue(content, 'subagents_tokens_total')) || 0,
|
|
31
37
|
prompts: Number(fmValue(content, 'prompts')) || 0,
|
|
38
|
+
ledger,
|
|
32
39
|
};
|
|
33
40
|
}
|
|
34
41
|
|
|
@@ -46,8 +53,16 @@ export function aggregateCosts(entries) {
|
|
|
46
53
|
const d = e.date || '?';
|
|
47
54
|
(byDay[d] = byDay[d] || { cost: 0, count: 0 }).cost += e.mainCost + e.subCost;
|
|
48
55
|
byDay[d].count += 1;
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
const rows = e.ledger?.length ? e.ledger : [
|
|
57
|
+
{ model: e.model, cost: e.mainCost },
|
|
58
|
+
...(e.subCost ? [{ model: 'subagents (legado, modelo desconhecido)', cost: e.subCost }] : []),
|
|
59
|
+
];
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
for (const row of rows) {
|
|
62
|
+
const model = row.model || '?';
|
|
63
|
+
(byModel[model] = byModel[model] || { cost: 0, count: 0 }).cost += Number(row.cost) || 0;
|
|
64
|
+
if (!seen.has(model)) { byModel[model].count += 1; seen.add(model); }
|
|
65
|
+
}
|
|
51
66
|
}
|
|
52
67
|
const total = main + sub;
|
|
53
68
|
return {
|
|
@@ -186,6 +201,16 @@ export function runCost(argv) {
|
|
|
186
201
|
if (!vaultRaw) { process.stderr.write('wendkeep cost: no vault (--vault or OBSIDIAN_VAULT_PATH).\n'); process.exit(2); }
|
|
187
202
|
const vaultBase = isAbsolute(vaultRaw) ? vaultRaw : resolve(process.cwd(), vaultRaw);
|
|
188
203
|
if (!existsSync(vaultBase)) { process.stderr.write(`wendkeep cost: vault not found: ${vaultBase}\n`); process.exit(2); }
|
|
204
|
+
if (argv[0] === 'rebuild') {
|
|
205
|
+
const report = rebuildSessionCosts(vaultBase, {
|
|
206
|
+
apply: argv.includes('--apply'),
|
|
207
|
+
session: opt(argv, '--session') || '',
|
|
208
|
+
limit: Number(opt(argv, '--limit')) || 0,
|
|
209
|
+
});
|
|
210
|
+
if (argv.includes('--json')) process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
211
|
+
else process.stdout.write(`cost rebuild (${report.mode}): ${report.scanned} lidas · ${report.changed} alteradas · ${report.unchanged} iguais · ${report.missing.length} sem fonte · ${report.errors.length} erros\n${report.mode === 'apply' ? 'Relatório: .brain/COST_REBUILD.json\n' : 'Nenhum arquivo foi alterado; use --apply para gravar.\n'}`);
|
|
212
|
+
process.exit(report.ok ? 0 : 1);
|
|
213
|
+
}
|
|
189
214
|
const agg = collectVaultCost(vaultBase, { since: opt(argv, '--since') });
|
|
190
215
|
|
|
191
216
|
if (argv.includes('--json')) { process.stdout.write(`${JSON.stringify(agg, null, 2)}\n`); process.exit(0); }
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Deterministic cost reconstruction for historical sessions.
|
|
2
|
+
// Registry is authoritative: session_file <-> transcript_path. Dry-run restores every note.
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { readSessionRegistry } from '../hooks/obsidian-common.mjs';
|
|
6
|
+
import { updateSessionUsage } from '../hooks/token-usage.mjs';
|
|
7
|
+
import { upsertSubagentUsage } from '../hooks/subagent-usage.mjs';
|
|
8
|
+
|
|
9
|
+
export function rebuildSessionCosts(vaultBase, { apply = false, session = '', limit = 0 } = {}) {
|
|
10
|
+
const registry = readSessionRegistry(vaultBase);
|
|
11
|
+
const report = { version: 1, generatedAt: new Date().toISOString(), mode: apply ? 'apply' : 'dry-run', scanned: 0, changed: 0, unchanged: 0, missing: [], errors: [], sessions: [] };
|
|
12
|
+
const entries = Object.entries(registry.sessions || {}).map(([sessionId, value]) => ({ sessionId, ...value }))
|
|
13
|
+
.filter((e) => e.session_file && e.transcript_path)
|
|
14
|
+
.filter((e) => !session || e.sessionId === session || e.session_file === session);
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
if (limit && report.scanned >= limit) break;
|
|
17
|
+
report.scanned += 1;
|
|
18
|
+
const note = join(vaultBase, entry.session_file);
|
|
19
|
+
if (!existsSync(note) || !existsSync(entry.transcript_path)) {
|
|
20
|
+
report.missing.push({ sessionId: entry.sessionId, session: entry.session_file, note: existsSync(note), transcript: existsSync(entry.transcript_path) });
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const before = readFileSync(note, 'utf8');
|
|
24
|
+
try {
|
|
25
|
+
updateSessionUsage({ vaultBase, sessionRel: entry.session_file, sessionPath: note, transcriptPath: entry.transcript_path });
|
|
26
|
+
upsertSubagentUsage(note, entry.transcript_path);
|
|
27
|
+
const after = readFileSync(note, 'utf8');
|
|
28
|
+
const changed = before !== after;
|
|
29
|
+
if (changed) report.changed += 1; else report.unchanged += 1;
|
|
30
|
+
report.sessions.push({ sessionId: entry.sessionId, session: entry.session_file, transcript: entry.transcript_path, changed });
|
|
31
|
+
if (!apply && changed) writeFileSync(note, before, 'utf8');
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (!apply) writeFileSync(note, before, 'utf8');
|
|
34
|
+
report.errors.push({ sessionId: entry.sessionId, session: entry.session_file, error: error.message });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
report.ok = report.errors.length === 0;
|
|
38
|
+
if (apply) writeFileSync(join(vaultBase, '.brain', 'COST_REBUILD.json'), `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
39
|
+
return report;
|
|
40
|
+
}
|
package/src/stats.mjs
CHANGED
|
@@ -14,7 +14,7 @@ export function statsFrom(agg) {
|
|
|
14
14
|
sessions: agg.count,
|
|
15
15
|
prompts: agg.prompts || 0,
|
|
16
16
|
cost: agg.total,
|
|
17
|
-
models: (agg.byModel || []).length,
|
|
17
|
+
models: (agg.byModel || []).filter((m) => m.model !== 'subagents (legado, modelo desconhecido)').length,
|
|
18
18
|
firstDay: days[0] || '',
|
|
19
19
|
lastDay: days[days.length - 1] || '',
|
|
20
20
|
spanDays: days.length,
|