wendkeep 0.47.0 → 0.49.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 +28 -0
- package/README.md +2 -0
- package/bin/wendkeep.mjs +3 -0
- package/hooks/harness-doctor.mjs +38 -2
- package/hooks/linked-notes.mjs +763 -692
- package/package.json +1 -1
- package/src/doctor.mjs +21 -1
- package/src/note.mjs +17 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,34 @@ 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.49.0] — 2026-07-23
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`wendkeep doctor` agora surfaça os órfãos do grafo com o comando de reparo.** Nova seção
|
|
12
|
+
`[links]`: conta notas derivadas sem sessão-fonte (→ `note relink`), artefatos de change
|
|
13
|
+
sem backlink (→ `change backlink`) e o estado das cores do grafo (→ `theme sync`); quando
|
|
14
|
+
tudo conectado, diz `grafo conectado`. Reusa as funções de reparo em dry-run — zero lógica
|
|
15
|
+
nova de detecção. Antes o dono do vault só descobria os órfãos olhando o grafo no Obsidian
|
|
16
|
+
e não sabia qual comando rodava.
|
|
17
|
+
- **Sessão não mente "inativa" com atividade recente.** Nova seção `[sessão]`: quando o
|
|
18
|
+
control marca `inactive` mas a nota da sessão foi escrita há pouco, o doctor sinaliza
|
|
19
|
+
possível workflow/subagente em segundo plano — o control só reflete o lifecycle da
|
|
20
|
+
sessão-mãe, não a atividade de background. Capability `vault-doctor` (DIAG-1..4).
|
|
21
|
+
|
|
22
|
+
## [0.48.0] — 2026-07-23
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`wendkeep note relink [--apply]` — backfill de proveniência das notas derivadas
|
|
27
|
+
órfãs.** BUG/APR legadas (criadas por versão antiga, sem `source:` de sessão) ficam ilhas
|
|
28
|
+
no grafo — num vault real: 13 de 15 BUG e 3 de 8 APR sem nenhum link de entrada nem de
|
|
29
|
+
saída. A origem não está registrada no órfão, mas os irmãos não-órfãos do mesmo tipo
|
|
30
|
+
carregam a sessão-fonte real. O comando liga cada órfão herdando a sessão **modal** (mais
|
|
31
|
+
comum) dos irmãos do mesmo tipo e mês, injetando `source:` + `related:` no frontmatter.
|
|
32
|
+
Dry-run por default; `--apply` escreve; idempotente; pula e reporta o órfão sem nenhum
|
|
33
|
+
irmão-fonte pra inferir (nunca chuta). Documentado em `derived-notes` (DRV-9).
|
|
34
|
+
|
|
7
35
|
## [0.47.0] — 2026-07-23
|
|
8
36
|
|
|
9
37
|
### Added
|
package/README.md
CHANGED
|
@@ -184,6 +184,8 @@ wendkeep note new --type learning "a regex without /g only ever returns the firs
|
|
|
184
184
|
|
|
185
185
|
It prints the created path, numbers from the current max (recursive scan), files it in the month folder for today (`--date YYYY-MM-DD` to override), and links the active session in `source:` so the graph stays connected. Agents get this rule injected at SessionStart — they call the command instead of guessing a filename.
|
|
186
186
|
|
|
187
|
+
**Reconnecting legacy notes.** Derived notes created by older versions carry no `source:` session and sit as islands in the graph. `wendkeep note relink` backfills them: each orphan inherits the modal source session of its type/month cohort (the session its non-orphan siblings already point to). Dry-run by default; `--apply` writes; notes with no sibling to infer from are skipped and reported.
|
|
188
|
+
|
|
187
189
|
**Migrating an existing vault.** Notes created before `0.41.0` have date-prefixed names (`2026-07-16-bug-<slug>.md`) and may sit in legacy `DIA N` subfolders. One command per tree renumbers them chronologically, moves them up into the month folder, and rewrites every wikilink across the vault:
|
|
188
190
|
|
|
189
191
|
```bash
|
package/bin/wendkeep.mjs
CHANGED
|
@@ -77,6 +77,9 @@ Usage:
|
|
|
77
77
|
wendkeep renumber-learnings Same for 06-Aprendizados/06-Learnings with APR-<NNNN>-<slug>.
|
|
78
78
|
wendkeep note new --type bug|learning "<título>" Create a numbered derived note (BUG-/APR-NNNN)
|
|
79
79
|
in the month folder and print its vault path. --date YYYY-MM-DD · --vault P.
|
|
80
|
+
wendkeep note relink [--apply] Backfill orphan derived notes (BUG/APR without a source session),
|
|
81
|
+
linking each to the modal source session of its type/month cohort. Dry-run
|
|
82
|
+
by default; --apply writes; skips notes with no sibling to infer from.
|
|
80
83
|
wendkeep lesson add "t" "l" Record a project-local lesson (injected at SessionStart).
|
|
81
84
|
wendkeep validate-memory [path] Validate .brain/CORE.md against the compaction
|
|
82
85
|
protocol (cap 25, 3 sections, no secrets/PII). Uses
|
package/hooks/harness-doctor.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// hooks/harness-doctor.mjs — integrity checks for the a2 harness state (Wave B).
|
|
2
2
|
// Pure-ish (fs reads only). `wendkeep doctor` reports errors (exit 1) + warnings.
|
|
3
|
-
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { activeChange, parseTasks } from './change-core.mjs';
|
|
5
|
+
import { activeChange, parseTasks, backfillArtifactLinks } from './change-core.mjs';
|
|
6
|
+
import { relinkDerivedNotes } from './linked-notes.mjs';
|
|
6
7
|
import { buildEffectiveRequirementPackage, checkSpecsState, evaluateVerdict, tasksHashOf, validateSpecImpact } from './spec-core.mjs';
|
|
7
8
|
import { getLocale } from './locale.mjs';
|
|
9
|
+
import { readControl } from './obsidian-common.mjs';
|
|
8
10
|
|
|
9
11
|
export function checkHarness(vaultBase, projectRoot) {
|
|
10
12
|
const loc = getLocale(vaultBase);
|
|
@@ -63,3 +65,37 @@ export function checkHarness(vaultBase, projectRoot) {
|
|
|
63
65
|
|
|
64
66
|
return { errors, warnings };
|
|
65
67
|
}
|
|
68
|
+
|
|
69
|
+
// --- diagnóstico de links do grafo (read-only, reusa os reparos em dry-run) -----
|
|
70
|
+
// Surfaça os órfãos que o doctor não enxergava: notas derivadas sem sessão-fonte,
|
|
71
|
+
// artefatos de change sem backlink, e o estado das cores do grafo. Cada não-zero tem um
|
|
72
|
+
// comando de reparo (note relink / change backlink / theme sync).
|
|
73
|
+
export function checkVaultLinks(vaultBase) {
|
|
74
|
+
let derivedOrphans = 0;
|
|
75
|
+
try { derivedOrphans = relinkDerivedNotes(vaultBase, {}).linked.length; } catch { /* sem notas derivadas */ }
|
|
76
|
+
let artifactOrphans = 0;
|
|
77
|
+
try { artifactOrphans = backfillArtifactLinks(vaultBase, {}).changed.length; } catch { /* sem changes */ }
|
|
78
|
+
let graphColors = null; // true=com grupos · false=vazio/ausente de cores · null=sem graph.json
|
|
79
|
+
try {
|
|
80
|
+
const g = JSON.parse(readFileSync(join(vaultBase, '.obsidian', 'graph.json'), 'utf8'));
|
|
81
|
+
graphColors = Array.isArray(g.colorGroups) && g.colorGroups.length > 0;
|
|
82
|
+
} catch { graphColors = null; }
|
|
83
|
+
return { derivedOrphans, artifactOrphans, graphColors };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const unquoteControl = (v) => String(v ?? '').replace(/^"(.*)"$/, '$1').trim();
|
|
87
|
+
|
|
88
|
+
// O control marca `inactive` quando a sessão-mãe encerra, mesmo com um workflow/subagente
|
|
89
|
+
// ainda vivo em background. Se a nota da sessão foi escrita há pouco apesar do `inactive`,
|
|
90
|
+
// sinaliza a atividade recente — o doctor deixa de dizer "inativa" quando não está.
|
|
91
|
+
export function checkSessionActivity(vaultBase, { now = Date.now(), windowMs = 5 * 60000 } = {}) {
|
|
92
|
+
const control = readControl(vaultBase);
|
|
93
|
+
const active = unquoteControl(control.status) === 'active';
|
|
94
|
+
const sessionRel = unquoteControl(active ? control.session_file : (control.last_session_file || control.session_file));
|
|
95
|
+
let ageMs = null;
|
|
96
|
+
if (sessionRel) {
|
|
97
|
+
try { ageMs = now - statSync(join(vaultBase, sessionRel)).mtimeMs; } catch { ageMs = null; }
|
|
98
|
+
}
|
|
99
|
+
const backgroundSuspected = !active && sessionRel !== '' && ageMs !== null && ageMs >= 0 && ageMs < windowMs;
|
|
100
|
+
return { lastSession: sessionRel, active, ageMs, backgroundSuspected };
|
|
101
|
+
}
|