wendkeep 0.38.1 → 0.38.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ All notable changes to **wendkeep** are documented here. Format based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project follows
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.38.2] — 2026-07-12
8
+
9
+ ### Fixed
10
+
11
+ - Observabilidade de sessão: o `Effort` do Claude Code passa a derivar da **presença** de blocos `thinking` (a `signature` persiste mesmo quando o Claude Code redige o texto do pensamento), não da estimativa por caracteres — que dava `0` e marcava a sessão como `unknown` mesmo com o extended thinking ativo (visto em 42/43 chamadas do transcript principal). Rótulo binário `thinking`/`none`, desacoplado da contagem de reasoning tokens. Subagents com mesmo modelo/estado deixam de se dividir em linhas `unknown` + `thinking ~Nk` e agrupam corretamente. Reasoning do Claude vira estimativa-piso do texto sobrevivente (não determina mais o effort). Caminho Codex inalterado.
12
+
7
13
  ## [0.38.1] — 2026-07-12
8
14
 
9
15
  ### Fixed
@@ -40,6 +46,12 @@ All notable changes to **wendkeep** are documented here. Format based on
40
46
  - Migração remove os headings legados sem perder reaberturas ou iterações mal posicionadas.
41
47
  - Totais combinados de tokens/custo são atualizados também no `SubagentStop` e persistidos em campos compatíveis com os dashboards existentes.
42
48
 
49
+ ## [0.36.0] — 2026-07-11
50
+
51
+ _Publicada no npm sem changelog dedicado (bump de versão não foi commitado à época);
52
+ registrada retroativamente para paridade npm ↔ GitHub. As mudanças reais desta faixa
53
+ estão consolidadas entre 0.35.0 e 0.38.1._
54
+
43
55
  ## [0.35.0] — 2026-07-11
44
56
 
45
57
  ### Fixed
@@ -11,7 +11,7 @@ const usd = (n) => `$${(Number(n) || 0).toFixed(4)}`;
11
11
  const round4 = (n) => Math.round((Number(n) || 0) * 10000) / 10000;
12
12
  const effort = (value) => {
13
13
  const normalized = String(value || '').trim().toLowerCase();
14
- return ['none', 'low', 'medium', 'high', 'xhigh'].includes(normalized) ? normalized : (normalized || 'unknown');
14
+ return ['none', 'low', 'medium', 'high', 'xhigh', 'thinking'].includes(normalized) ? normalized : (normalized || 'unknown');
15
15
  };
16
16
  const usageTotal = (u = {}) => Number(u.total || 0) || (Number(u.input || 0) + Number(u.cached || 0) + Number(u.cacheWrite || 0) + Number(u.output || 0));
17
17
 
@@ -454,6 +454,7 @@ function parseClaudeLines(lines, result) {
454
454
  const seenThinking = new Set();
455
455
  let thinkingChars = 0;
456
456
  const thinkingCharsByModel = new Map();
457
+ let sawThinking = false;
457
458
  let latestPrompt = '';
458
459
 
459
460
  for (const line of lines) {
@@ -483,12 +484,18 @@ function parseClaudeLines(lines, result) {
483
484
  result.toolCalls += 1;
484
485
  addUnique(result.tools, block.name || 'tool_use');
485
486
  }
486
- if (block?.type === 'thinking' && block.thinking) {
487
- const thinkKey = `${msg.id || ''}:${block.thinking.slice(0, 60)}`;
488
- if (!seenThinking.has(thinkKey)) {
489
- seenThinking.add(thinkKey);
490
- thinkingChars += block.thinking.length;
491
- thinkingCharsByModel.set(model, (thinkingCharsByModel.get(model) || 0) + block.thinking.length);
487
+ if (block?.type === 'thinking') {
488
+ // Presença = extended thinking ATIVO. A `signature` persiste mesmo quando o Claude
489
+ // Code redige o texto (`thinking: ''`) — é o único sinal confiável do effort.
490
+ sawThinking = true;
491
+ // O texto só sobrevive à redação às vezes; quando sobrevive, estima reasoning tokens.
492
+ if (block.thinking) {
493
+ const thinkKey = `${msg.id || ''}:${block.thinking.slice(0, 60)}`;
494
+ if (!seenThinking.has(thinkKey)) {
495
+ seenThinking.add(thinkKey);
496
+ thinkingChars += block.thinking.length;
497
+ thinkingCharsByModel.set(model, (thinkingCharsByModel.get(model) || 0) + block.thinking.length);
498
+ }
492
499
  }
493
500
  }
494
501
  }
@@ -510,12 +517,16 @@ function parseClaudeLines(lines, result) {
510
517
  result.model = model;
511
518
  }
512
519
 
513
- // Thinking estimado: ~3,5 chars por token. Distribuído no total como informação à parte
514
- // (já contido em output_tokens não somar de novo).
520
+ // Effort observável no Claude: presença de blocos thinking (signature), não o texto o
521
+ // nível low/medium/high não é gravado no transcript. Rótulo binário: thinking/none.
522
+ result.pensamento = sawThinking ? 'thinking' : 'none';
523
+
524
+ // Reasoning: estimativa-piso ~3,5 chars/token dos textos que escaparam da redação (quase
525
+ // sempre 0 no thread principal). Já contido em output_tokens — nunca somado de novo. NÃO
526
+ // determina o effort (desacoplado do pensamento acima).
515
527
  const thinkingTokens = Math.round(thinkingChars / 3.5);
516
528
  if (thinkingTokens > 0) {
517
529
  result.totals.reasoning = thinkingTokens;
518
- result.pensamento = `thinking ~${formatTokensShort(thinkingTokens)}`;
519
530
  for (const [model, chars] of thinkingCharsByModel) {
520
531
  const entry = result.byModel.get(`anthropic:${model}`);
521
532
  if (entry) entry.usage.reasoning = Math.round(chars / 3.5);
@@ -525,12 +536,6 @@ function parseClaudeLines(lines, result) {
525
536
  return result;
526
537
  }
527
538
 
528
- function formatTokensShort(n) {
529
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
530
- if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
531
- return String(n);
532
- }
533
-
534
539
  function detectTranscriptFormat(lines) {
535
540
  for (const line of lines.slice(0, 20)) {
536
541
  const event = parseJsonLine(line);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.38.1",
3
+ "version": "0.38.2",
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": {
@@ -20,7 +20,9 @@
20
20
  },
21
21
  "scripts": {
22
22
  "check": "node --check bin/wendkeep.mjs && node --check src/init.mjs && node --check src/doctor.mjs",
23
- "test": "node --test"
23
+ "test": "node --test",
24
+ "release": "node scripts/release.mjs",
25
+ "release:dry": "node scripts/release.mjs --dry-run"
24
26
  },
25
27
  "keywords": [
26
28
  "claude-code",
@@ -42,5 +44,8 @@
42
44
  "homepage": "https://github.com/rogersialves/wendkeep#readme",
43
45
  "bugs": {
44
46
  "url": "https://github.com/rogersialves/wendkeep/issues"
47
+ },
48
+ "devDependencies": {
49
+ "wendkeep": "^0.38.1"
45
50
  }
46
51
  }
@@ -0,0 +1,38 @@
1
+ // Pure helper: extract a single version's release notes from a Keep-a-Changelog
2
+ // file. Reused by scripts/release.mjs and .github/workflows/release.yml so the
3
+ // GitHub Release body always matches the committed CHANGELOG.
4
+
5
+ const HEADER_RE = /^##\s*\[([^\]]+)\]\s*[—–-]\s*(.+?)\s*$/;
6
+
7
+ /**
8
+ * @param {string} changelogText Full CHANGELOG.md contents.
9
+ * @param {string} version Version to extract (with or without leading "v").
10
+ * @returns {{ version: string, date: string, notes: string }}
11
+ * @throws if the version has no section.
12
+ */
13
+ export function extractReleaseNotes(changelogText, version) {
14
+ const target = String(version).replace(/^v/i, '').trim();
15
+ const lines = String(changelogText).split(/\r?\n/);
16
+
17
+ let start = -1;
18
+ let date = '';
19
+ for (let i = 0; i < lines.length; i++) {
20
+ const m = lines[i].match(HEADER_RE);
21
+ if (m && m[1].trim() === target) {
22
+ start = i;
23
+ date = m[2].trim();
24
+ break;
25
+ }
26
+ }
27
+ if (start === -1) {
28
+ throw new Error(`CHANGELOG: versão ${version} não encontrada`);
29
+ }
30
+
31
+ const body = [];
32
+ for (let j = start + 1; j < lines.length; j++) {
33
+ if (HEADER_RE.test(lines[j])) break;
34
+ body.push(lines[j]);
35
+ }
36
+
37
+ return { version: target, date, notes: body.join('\n').trim() };
38
+ }