wendkeep 0.66.3 → 0.66.5
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 +34 -0
- package/README.en.md +13 -8
- package/README.md +13 -8
- package/docs/en/commands/costs-and-observability.md +21 -7
- package/docs/en/commands/maintenance-and-diagnostics.md +13 -1
- package/docs/en/commands/memory.md +31 -2
- package/docs/en/commands/sessions-and-import.md +15 -0
- package/docs/pt-BR/commands/costs-and-observability.md +21 -7
- package/docs/pt-BR/commands/maintenance-and-diagnostics.md +12 -1
- package/docs/pt-BR/commands/memory.md +32 -2
- package/docs/pt-BR/commands/sessions-and-import.md +15 -1
- package/hooks/codex-rollout-meta.mjs +112 -0
- package/hooks/codex-subagent-graph.mjs +903 -0
- package/hooks/harness-doctor.mjs +82 -1
- package/hooks/import-sessions.mjs +185 -50
- package/hooks/session-identity.mjs +40 -5
- package/hooks/session-observability-lifecycle.mjs +129 -0
- package/hooks/session-observability-state.mjs +241 -0
- package/hooks/session-observability-store.mjs +436 -0
- package/hooks/session-observability.mjs +647 -21
- package/hooks/session-stop.mjs +218 -5
- package/hooks/subagent-stop.mjs +266 -12
- package/hooks/subagent-usage.mjs +65 -0
- package/hooks/token-usage.mjs +81 -4
- package/hooks/vault-health.mjs +6 -0
- package/package.json +1 -1
- package/packages/cli/src/index.mjs +1 -0
- package/packages/vault/src/memory-store.mjs +5 -0
- package/src/cost.mjs +40 -6
- package/src/doctor.mjs +4 -1
- package/src/memory.mjs +603 -5
- package/src/rebuild-costs.mjs +220 -34
package/hooks/token-usage.mjs
CHANGED
|
@@ -510,16 +510,19 @@ function detectTranscriptFormat(lines) {
|
|
|
510
510
|
return 'codex';
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
-
export function
|
|
513
|
+
export function parseTokenUsageFromContent(content, { transcriptPath = '' } = {}) {
|
|
514
514
|
const result = emptyParseResult(transcriptPath);
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
const lines = readFileSync(transcriptPath, 'utf-8').split('\n').filter(Boolean);
|
|
515
|
+
const lines = String(content || '').split('\n').filter(Boolean);
|
|
518
516
|
return detectTranscriptFormat(lines) === 'claude'
|
|
519
517
|
? parseClaudeLines(lines, result)
|
|
520
518
|
: parseCodexLines(lines, result);
|
|
521
519
|
}
|
|
522
520
|
|
|
521
|
+
export function parseTokenUsageFromTranscript(transcriptPath) {
|
|
522
|
+
if (!transcriptPath || !existsSync(transcriptPath)) return emptyParseResult(transcriptPath);
|
|
523
|
+
return parseTokenUsageFromContent(readFileSync(transcriptPath, 'utf-8'), { transcriptPath });
|
|
524
|
+
}
|
|
525
|
+
|
|
523
526
|
function modelCost(usage, model) {
|
|
524
527
|
const normalized = normalizeModelName(model);
|
|
525
528
|
const price = PRICE_REFERENCE[normalized];
|
|
@@ -935,6 +938,80 @@ export function collectSessionUsage({ sessionContent, transcriptPath }) {
|
|
|
935
938
|
};
|
|
936
939
|
}
|
|
937
940
|
|
|
941
|
+
function normalizedTranscriptKey(value) {
|
|
942
|
+
return String(value || '').replace(/\.jsonl?$/i, '').trim().toLowerCase();
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Rebuild the main bucket from every validated top-level rollout. Existing history is used
|
|
946
|
+
// only to preserve stable timestamps; an entry that is neither a known root nor a proven
|
|
947
|
+
// descendant makes the reconstruction fail closed instead of silently deleting a legitimate
|
|
948
|
+
// reopening.
|
|
949
|
+
export function collectSessionUsageForRoots({
|
|
950
|
+
sessionContent,
|
|
951
|
+
rootPaths = [],
|
|
952
|
+
descendantIds = [],
|
|
953
|
+
} = {}) {
|
|
954
|
+
const fmMatch = String(sessionContent || '').match(/^---\n([\s\S]*?)\n---/);
|
|
955
|
+
if (!fmMatch) return null;
|
|
956
|
+
|
|
957
|
+
const roots = [...new Set(rootPaths.filter(Boolean))].sort((a, b) => String(a).localeCompare(String(b)));
|
|
958
|
+
const rootKeys = new Set(roots.map((path) => normalizedTranscriptKey(transcriptIdFromPath(path))));
|
|
959
|
+
const descendantKeys = new Set(descendantIds.map(normalizedTranscriptKey).filter(Boolean));
|
|
960
|
+
const existingEntries = parseUsageHistory(fmMatch[1]);
|
|
961
|
+
const unresolved = existingEntries.filter((entry) => {
|
|
962
|
+
const key = normalizedTranscriptKey(entry.transcript_id);
|
|
963
|
+
return key && !rootKeys.has(key) && !descendantKeys.has(key);
|
|
964
|
+
});
|
|
965
|
+
if (unresolved.length) {
|
|
966
|
+
return {
|
|
967
|
+
state: 'degraded',
|
|
968
|
+
diagnostics: [{ code: 'MAIN_TRANSCRIPT_UNRESOLVED', count: unresolved.length }],
|
|
969
|
+
entries: existingEntries,
|
|
970
|
+
content: sessionContent,
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
const entries = [];
|
|
975
|
+
const summaries = [];
|
|
976
|
+
for (const transcriptPath of roots) {
|
|
977
|
+
if (!existsSync(transcriptPath)) {
|
|
978
|
+
return {
|
|
979
|
+
state: 'degraded',
|
|
980
|
+
diagnostics: [{ code: 'MAIN_TRANSCRIPT_UNRESOLVED', count: 1 }],
|
|
981
|
+
entries: existingEntries,
|
|
982
|
+
content: sessionContent,
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
const summary = summarizeTokenUsage(parseTokenUsageFromTranscript(transcriptPath));
|
|
986
|
+
if (!summary.calls) {
|
|
987
|
+
return {
|
|
988
|
+
state: 'degraded',
|
|
989
|
+
diagnostics: [{ code: 'MAIN_TRANSCRIPT_UNRESOLVED', count: 1 }],
|
|
990
|
+
entries: existingEntries,
|
|
991
|
+
content: sessionContent,
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
const transcriptId = transcriptIdFromPath(transcriptPath);
|
|
995
|
+
const current = entryFromSummary(summary, transcriptId);
|
|
996
|
+
const previous = existingEntries.find((entry) => normalizedTranscriptKey(entry.transcript_id) === normalizedTranscriptKey(transcriptId));
|
|
997
|
+
if (previous && sameUsageData(previous, current)) current.atualizado_em = previous.atualizado_em;
|
|
998
|
+
entries.push(current);
|
|
999
|
+
summaries.push(summary);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const agg = aggregateEntries(entries);
|
|
1003
|
+
const content = upsertSessionFrontmatter(sessionContent, agg, entries);
|
|
1004
|
+
if (content === null) return null;
|
|
1005
|
+
return {
|
|
1006
|
+
state: 'complete',
|
|
1007
|
+
diagnostics: [],
|
|
1008
|
+
summaries,
|
|
1009
|
+
aggregate: agg,
|
|
1010
|
+
entries,
|
|
1011
|
+
content,
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
938
1015
|
export function updateSessionUsage({ vaultBase, sessionRel, sessionPath, transcriptPath, lockTimeoutMs }) {
|
|
939
1016
|
if (!sessionPath || !existsSync(sessionPath)) return null;
|
|
940
1017
|
let result = null;
|
package/hooks/vault-health.mjs
CHANGED
|
@@ -338,6 +338,12 @@ function checkMemoryAttempts(registry, {
|
|
|
338
338
|
const missing = eventIds.filter((eventId) => !ledgerEventIds.has(eventId) && !outboxEventIds.has(eventId));
|
|
339
339
|
if (missing.length) {
|
|
340
340
|
failures.push(`Attempt v2 perdeu ${missing.length} evento(s): ausentes do ledger e da outbox. Inspecione com: ${MEMORY_STATUS_COMMAND}.`);
|
|
341
|
+
} else if (
|
|
342
|
+
state === 'enqueued'
|
|
343
|
+
&& eventIds.every((eventId) => ledgerEventIds.has(eventId))
|
|
344
|
+
&& eventIds.every((eventId) => !outboxEventIds.has(eventId))
|
|
345
|
+
) {
|
|
346
|
+
warnings.push(`Attempt de memória v2 possui acknowledgement projetado pendente. Recupere com: wendkeep memory recover-attempt ${sessionId}.`);
|
|
341
347
|
} else {
|
|
342
348
|
warnings.push(`Attempt de memória v2 ${state} permanece recuperável: ${eventIds.length} evento(s) durável(is) no ledger e/ou outbox.`);
|
|
343
349
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.66.
|
|
3
|
+
"version": "0.66.5",
|
|
4
4
|
"description": "Vault-first persistent memory for AI coding agents, with an optional profile-aware governance runtime: OFF, FLOW, GUIDE, GOVERN, or ASSURE. Local-first and agent-agnostic (Claude Code, Codex, Cursor…).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -100,6 +100,7 @@ Usage:
|
|
|
100
100
|
behind the closing block. Dry-run by default · --apply · --json.
|
|
101
101
|
wendkeep lesson add "t" "l" Record a project-local lesson (injected at SessionStart).
|
|
102
102
|
wendkeep memory <sub> Shared memory v2: status | migrate [--apply] | repair |
|
|
103
|
+
recover-attempt <session> [--apply] |
|
|
103
104
|
reconcile <session> --by-session <session> --reason <text> [--apply] |
|
|
104
105
|
promote <candidate> [--event <event-id>] | reject <candidate>. --vault P.
|
|
105
106
|
Reconcile is dry-run by default; the original attempt remains audited.
|
|
@@ -313,6 +313,8 @@ export function readMemoryLedger(vaultBase) {
|
|
|
313
313
|
if (eventIds.has(parsed.event_id)) {
|
|
314
314
|
if (eventPayloads.get(parsed.event_id) !== payload) {
|
|
315
315
|
errors.push(ledgerError(lineNumber, `event_id collision: ${parsed.event_id}`, partial));
|
|
316
|
+
} else {
|
|
317
|
+
errors.push(ledgerError(lineNumber, `duplicate event_id: ${parsed.event_id}`, partial));
|
|
316
318
|
}
|
|
317
319
|
return;
|
|
318
320
|
}
|
|
@@ -911,15 +913,18 @@ function projectLocked(vaultBase, { faultAt } = {}) {
|
|
|
911
913
|
const projection = publishMemoryProjection(vaultBase, prepared);
|
|
912
914
|
injectFault(faultAt, 'after-projection');
|
|
913
915
|
|
|
916
|
+
const consumedEventIds = [];
|
|
914
917
|
for (const entry of outbox) {
|
|
915
918
|
unlinkVaultFile(vaultBase, entry.path, {
|
|
916
919
|
missingOk: false, label: 'evento consumido do outbox de memória',
|
|
917
920
|
});
|
|
921
|
+
consumedEventIds.push(entry.event.event_id);
|
|
918
922
|
}
|
|
919
923
|
return {
|
|
920
924
|
status: 'projected',
|
|
921
925
|
appended: newEvents.length,
|
|
922
926
|
consumed: outbox.length,
|
|
927
|
+
consumedEventIds,
|
|
923
928
|
pending: 0,
|
|
924
929
|
...projection,
|
|
925
930
|
};
|
package/src/cost.mjs
CHANGED
|
@@ -196,19 +196,53 @@ function opt(argv, name) {
|
|
|
196
196
|
return eq ? eq.slice(name.length + 1) : undefined;
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
const REBUILD_LIMIT_FLAGS = [
|
|
200
|
+
['--max-graph-nodes', 'maxGraphNodes'],
|
|
201
|
+
['--max-fallback-days', 'maxFallbackDays'],
|
|
202
|
+
['--max-fallback-candidates', 'maxFallbackCandidates'],
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
export function parseRebuildOptions(argv = []) {
|
|
206
|
+
const session = opt(argv, '--session') || '';
|
|
207
|
+
const overrides = {};
|
|
208
|
+
for (const [flag, key] of REBUILD_LIMIT_FLAGS) {
|
|
209
|
+
const present = argv.includes(flag) || argv.some((arg) => arg.startsWith(`${flag}=`));
|
|
210
|
+
if (!present) continue;
|
|
211
|
+
const value = Number(opt(argv, flag));
|
|
212
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
213
|
+
return { ok: false, exitCode: 2, code: 'INVALID_LIMIT_OVERRIDE' };
|
|
214
|
+
}
|
|
215
|
+
overrides[key] = value;
|
|
216
|
+
}
|
|
217
|
+
if (Object.keys(overrides).length > 0 && !session) {
|
|
218
|
+
return { ok: false, exitCode: 2, code: 'TARGET_REQUIRED_FOR_LIMIT_OVERRIDE' };
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
ok: true,
|
|
222
|
+
options: {
|
|
223
|
+
apply: argv.includes('--apply'),
|
|
224
|
+
session,
|
|
225
|
+
limit: Number(opt(argv, '--limit')) || 0,
|
|
226
|
+
limits: { ...overrides },
|
|
227
|
+
overrides: { ...overrides },
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
199
232
|
export function runCost(argv) {
|
|
200
233
|
const vaultRaw = opt(argv, '--vault') || process.env.OBSIDIAN_VAULT_PATH;
|
|
201
234
|
if (!vaultRaw) { process.stderr.write('wendkeep cost: no vault (--vault or OBSIDIAN_VAULT_PATH).\n'); process.exit(2); }
|
|
202
235
|
const vaultBase = isAbsolute(vaultRaw) ? vaultRaw : resolve(process.cwd(), vaultRaw);
|
|
203
236
|
if (!existsSync(vaultBase)) { process.stderr.write(`wendkeep cost: vault not found: ${vaultBase}\n`); process.exit(2); }
|
|
204
237
|
if (argv[0] === 'rebuild') {
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
238
|
+
const parsed = parseRebuildOptions(argv);
|
|
239
|
+
if (!parsed.ok) {
|
|
240
|
+
process.stderr.write(`wendkeep cost rebuild: ${parsed.code}\n`);
|
|
241
|
+
process.exit(parsed.exitCode);
|
|
242
|
+
}
|
|
243
|
+
const report = rebuildSessionCosts(vaultBase, parsed.options);
|
|
210
244
|
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.
|
|
245
|
+
else process.stdout.write(`cost rebuild (${report.mode}): ${report.scanned} lidas · ${report.changed} alteradas · ${report.unchanged} iguais · ${report.degraded} degradadas · ${report.stale} stale · ${report.missing} sem fonte · ${report.errors} erros\n${report.mode === 'apply' ? 'Relatório: .brain/COST_REBUILD.json\n' : 'Nenhum arquivo foi alterado; use --apply para gravar.\n'}`);
|
|
212
246
|
process.exit(report.ok ? 0 : 1);
|
|
213
247
|
}
|
|
214
248
|
const agg = collectVaultCost(vaultBase, { since: opt(argv, '--since') });
|
package/src/doctor.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
import { dirname, join, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
-
import { checkHarness, checkVaultLinks, checkSessionActivity, checkStackedFrontmatter, renderStackedFrontmatterLines, checkUnpricedModels, renderUnpricedModelLines, checkStaleDerivedSections, renderStaleDerivedSectionLines } from '../hooks/harness-doctor.mjs';
|
|
7
|
+
import { checkHarness, checkVaultLinks, checkSessionActivity, checkStackedFrontmatter, renderStackedFrontmatterLines, checkUnpricedModels, renderUnpricedModelLines, checkStaleDerivedSections, renderStaleDerivedSectionLines, checkSessionObservability, renderSessionObservabilityLines } from '../hooks/harness-doctor.mjs';
|
|
8
8
|
import { checkSyncDefs } from './sync-defs.mjs';
|
|
9
9
|
import { resolveProjectVault } from './project-vault.mjs';
|
|
10
10
|
|
|
@@ -79,6 +79,9 @@ export function runDoctor(argv) {
|
|
|
79
79
|
// 3d. Seções derivadas do corpo que ficaram para trás do Encerramento (notas pré-0.53.0).
|
|
80
80
|
process.stdout.write(`\n${renderStaleDerivedSectionLines(checkStaleDerivedSections(vaultBase)).join('\n')}\n`);
|
|
81
81
|
|
|
82
|
+
// 3e. Observabilidade materializada: schema vigente não basta sem frontier + manifest frescos.
|
|
83
|
+
process.stdout.write(`\n${renderSessionObservabilityLines(checkSessionObservability(vaultBase)).join('\n')}\n`);
|
|
84
|
+
|
|
82
85
|
// 4. Sessão: não mente "inativa" quando há atividade recente (workflow/subagente em background).
|
|
83
86
|
const act = checkSessionActivity(vaultBase);
|
|
84
87
|
if (act.lastSession) {
|