wendkeep 0.63.0 → 0.65.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 +40 -0
- package/README.en.md +19 -5
- package/README.md +19 -5
- package/bin/wendkeep.mjs +2 -344
- package/docs/en/commands/getting-started.md +5 -0
- package/docs/pt-BR/commands/getting-started.md +5 -0
- package/package.json +2 -2
- package/packages/cli/package.json +2 -1
- package/packages/cli/src/index.mjs +349 -0
- package/packages/mcp/package.json +2 -1
- package/packages/mcp/src/config.mjs +33 -0
- package/packages/mcp/src/index.mjs +1 -0
- package/src/init.mjs +6 -7
- package/src/taxonomy.mjs +13 -19
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,46 @@ 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.65.0] — 2026-07-29
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **O workspace privado MCP passa a ter um kernel canônico de configuração.**
|
|
12
|
+
`packages/mcp/src/config.mjs` concentra a entrada do MCPVault, a seleção de servidores por
|
|
13
|
+
descritores e o merge imutável de `.mcp.json`, sem efeitos no import.
|
|
14
|
+
- **O tarball instalado prova a composição MCP fora do checkout.** O teste executa `init` em um
|
|
15
|
+
consumidor temporário, confirma a preservação de configuração existente e rejeita exports
|
|
16
|
+
públicos prematuros como `wendkeep/mcp` e `@wendkeep/mcp`.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Taxonomia e instalador agora delegam ao kernel MCP.** `src/taxonomy.mjs` fornece descritores
|
|
21
|
+
como dados e preserva a identidade dos exports históricos; `src/init.mjs` mantém a orquestração
|
|
22
|
+
do filesystem e o comportamento de reconciliação, inclusive `.mcp.json.new` para JSON inválido.
|
|
23
|
+
- **A publicação continua unificada e compatível.** Não há servidor MCP nativo, subpath público ou
|
|
24
|
+
pacote npm separado nesta fase; comandos, flags, transporte MCPVault e arquivos existentes
|
|
25
|
+
preservam o comportamento anterior.
|
|
26
|
+
|
|
27
|
+
## [0.64.0] — 2026-07-28
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **O workspace privado CLI passa a ter runtime canônico em `packages/cli/src/index.mjs`.** Help,
|
|
32
|
+
versão, seleção de Vault, apresentação de erros e dispatch lazy agora pertencem à fronteira
|
|
33
|
+
`@wendkeep/cli`, com import sem autoexecução.
|
|
34
|
+
- **O tarball instalado prova a CLI fora do checkout.** A distribuição inclui o runtime novo e
|
|
35
|
+
exercita os aliases `wendkeep` e `wk` em um consumidor temporário, mantendo o pacote raiz como
|
|
36
|
+
a única unidade npm.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **`bin/wendkeep.mjs` agora é uma fachada executável fina.** O arquivo preserva o shebang e
|
|
41
|
+
apenas invoca `runCli()`, enquanto comandos, flags, help antes do Vault, streams, mensagens e
|
|
42
|
+
códigos de saída mantêm o comportamento anterior.
|
|
43
|
+
- **A CLI programática permanece privada durante a migração.** Não há export raiz
|
|
44
|
+
`wendkeep/cli`; as superfícies públicas continuam sendo os binários, `wendkeep/harness` e
|
|
45
|
+
`wendkeep/vault`.
|
|
46
|
+
|
|
7
47
|
## [0.63.0] — 2026-07-28
|
|
8
48
|
|
|
9
49
|
### Added
|
package/README.en.md
CHANGED
|
@@ -16,13 +16,27 @@
|
|
|
16
16
|
**Persistent memory for AI coding agents, built on your Obsidian vault.** Every Claude Code **and Codex** session is captured turn by turn into local Markdown — `init` wires both (Codex asks you to approve its hooks once; `import` backfills past sessions either way) — with token/cost tracking and automatically extracted decisions, bugs, and learnings. That always-on plane is **Keep Core**. On top of it, **Wend Runtime** provides a native, zero-dependency lifecycle (spec → change → TDD → sensor-gated archive), selected through the `OFF`, `FLOW`, `GUIDE`, `GOVERN`, and `ASSURE` Operating Profiles. 100% local, open-core.
|
|
17
17
|
|
|
18
18
|
The runtime is being separated into six physical boundaries — `cli`, `harness`, `vault`, `mcp`,
|
|
19
|
-
`integrations`, and `pi` — without fragmenting installation. The private `
|
|
20
|
-
workspaces now canonically own
|
|
21
|
-
Project Memory v2 kernel,
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
`integrations`, and `pi` — without fragmenting installation. The private `cli`, `harness`,
|
|
20
|
+
`vault`, and `mcp` workspaces now canonically own the executable runtime, Operating Profiles/the
|
|
21
|
+
sensor engine, safe binding/the Shared Project Memory v2 kernel, and the MCP configuration kernel,
|
|
22
|
+
respectively. The root package exposes Harness and Vault through `wendkeep/harness` and
|
|
23
|
+
`wendkeep/vault`; CLI and MCP remain private surfaces, reached only through the binaries and the
|
|
24
|
+
configuration effects of `init`. Historical imports keep working through compatibility facades
|
|
25
|
+
and no session data needs migration;
|
|
24
26
|
see the [modular architecture](docs/en/architecture.md).
|
|
25
27
|
|
|
28
|
+
In the **0.65 MCP Configuration Kernel** phase, `packages/mcp/src/config.mjs` becomes the
|
|
29
|
+
canonical authority for the MCPVault entry, catalog-described server selection, and `.mcp.json`
|
|
30
|
+
merging. `src/taxonomy.mjs` supplies descriptors while `src/init.mjs` retains only filesystem
|
|
31
|
+
orchestration. Existing keys and servers remain preserved; invalid JSON stays byte-for-byte intact
|
|
32
|
+
and the reconciled proposal is written to `.mcp.json.new`. The workspace remains private, with no
|
|
33
|
+
public `wendkeep/mcp` subpath or separate npm package.
|
|
34
|
+
|
|
35
|
+
In the **0.64 CLI Runtime** phase, `packages/cli/src/index.mjs` owns help, version reporting, Vault
|
|
36
|
+
selection, error presentation, and lazy dispatch. `bin/wendkeep.mjs` is reduced to the shebang and
|
|
37
|
+
a `runCli()` invocation. The tarball remains a single artifact and proves both aliases in an
|
|
38
|
+
isolated consumer; there is no public `wendkeep/cli` subpath in this phase.
|
|
39
|
+
|
|
26
40
|
In the **0.63 Harness FLOW Store** phase, `packages/vault/src/locale.mjs` becomes the canonical
|
|
27
41
|
source for Vault locale and taxonomy, while `packages/harness/src/flow-store.mjs` becomes the
|
|
28
42
|
canonical durable FLOW store. The historical `hooks/locale.mjs` and
|
package/README.md
CHANGED
|
@@ -16,13 +16,27 @@
|
|
|
16
16
|
**Persistent memory for AI coding agents, built on your Obsidian vault.** Every Claude Code **and Codex** session is captured turn by turn into local Markdown — `init` wires both (Codex asks you to approve its hooks once; `import` backfills past sessions either way) — with token/cost tracking and automatically extracted decisions, bugs, and learnings. That always-on plane is **Keep Core**. On top of it, **Wend Runtime** provides a native, zero-dependency lifecycle (spec → change → TDD → sensor-gated archive), selected through the `OFF`, `FLOW`, `GUIDE`, `GOVERN`, and `ASSURE` Operating Profiles. 100% local, open-core.
|
|
17
17
|
|
|
18
18
|
The runtime is being separated into six physical boundaries — `cli`, `harness`, `vault`, `mcp`,
|
|
19
|
-
`integrations`, and `pi` — without fragmenting installation. The private `
|
|
20
|
-
workspaces now canonically own
|
|
21
|
-
Project Memory v2 kernel,
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
`integrations`, and `pi` — without fragmenting installation. The private `cli`, `harness`,
|
|
20
|
+
`vault`, and `mcp` workspaces now canonically own the executable runtime, Operating Profiles/the
|
|
21
|
+
sensor engine, safe binding/the Shared Project Memory v2 kernel, and the MCP configuration kernel,
|
|
22
|
+
respectively. The root package exposes Harness and Vault through `wendkeep/harness` and
|
|
23
|
+
`wendkeep/vault`; CLI and MCP remain private surfaces, reached only through the binaries and the
|
|
24
|
+
configuration effects of `init`. Historical imports keep working through compatibility facades
|
|
25
|
+
and no session data needs migration;
|
|
24
26
|
see the [modular architecture](docs/en/architecture.md).
|
|
25
27
|
|
|
28
|
+
In the **0.65 MCP Configuration Kernel** phase, `packages/mcp/src/config.mjs` becomes the
|
|
29
|
+
canonical authority for the MCPVault entry, catalog-described server selection, and `.mcp.json`
|
|
30
|
+
merging. `src/taxonomy.mjs` supplies descriptors while `src/init.mjs` retains only filesystem
|
|
31
|
+
orchestration. Existing keys and servers remain preserved; invalid JSON stays byte-for-byte intact
|
|
32
|
+
and the reconciled proposal is written to `.mcp.json.new`. The workspace remains private, with no
|
|
33
|
+
public `wendkeep/mcp` subpath or separate npm package.
|
|
34
|
+
|
|
35
|
+
In the **0.64 CLI Runtime** phase, `packages/cli/src/index.mjs` owns help, version reporting, Vault
|
|
36
|
+
selection, error presentation, and lazy dispatch. `bin/wendkeep.mjs` is reduced to the shebang and
|
|
37
|
+
a `runCli()` invocation. The tarball remains a single artifact and proves both aliases in an
|
|
38
|
+
isolated consumer; there is no public `wendkeep/cli` subpath in this phase.
|
|
39
|
+
|
|
26
40
|
In the **0.63 Harness FLOW Store** phase, `packages/vault/src/locale.mjs` becomes the canonical
|
|
27
41
|
source for Vault locale and taxonomy, while `packages/harness/src/flow-store.mjs` becomes the
|
|
28
42
|
canonical durable FLOW store. The historical `hooks/locale.mjs` and
|
package/bin/wendkeep.mjs
CHANGED
|
@@ -1,346 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// wendkeep init [--vault <path>] [--project <path>] [--no-mcp] [--yes] [--force]
|
|
4
|
-
// wendkeep hook <name> (invoked by the agent's settings.json; pipes stdin/stdout)
|
|
5
|
-
// wendkeep doctor [--vault <path>]
|
|
6
|
-
// wendkeep --version | --help
|
|
7
|
-
import { spawnSync } from 'node:child_process';
|
|
8
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
9
|
-
import { dirname, join } from 'node:path';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
|
-
import { RUNNABLE_HOOKS } from '../src/taxonomy.mjs';
|
|
2
|
+
import { runCli } from '../packages/cli/src/index.mjs';
|
|
12
3
|
|
|
13
|
-
|
|
14
|
-
const pkgRoot = join(here, '..');
|
|
15
|
-
const hooksDir = join(pkgRoot, 'hooks');
|
|
16
|
-
|
|
17
|
-
function version() {
|
|
18
|
-
try {
|
|
19
|
-
return JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8')).version;
|
|
20
|
-
} catch {
|
|
21
|
-
return '0.0.0';
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const HELP = `wendkeep ${version()} — keep durable AI sessions in an Obsidian vault, with optional governance.
|
|
26
|
-
|
|
27
|
-
Usage:
|
|
28
|
-
wendkeep init [options] Set up wendkeep in a project (cross-platform).
|
|
29
|
-
--vault <path> Obsidian vault folder (default: <project>/.<project-name>-vault).
|
|
30
|
-
--project <path> Project root to wire (default: current directory).
|
|
31
|
-
--profile <name> Operating profile: OFF, FLOW, GUIDE, GOVERN (default), or ASSURE.
|
|
32
|
-
--no-mcp Do not add the mcpvault MCP server to .mcp.json.
|
|
33
|
-
--companions <csv> Companion plugins/MCP to pin: context-mode,caveman,understand-anything
|
|
34
|
-
(default: none — opt in explicitly). dotcontext is legacy — the native a2 loop replaces it.
|
|
35
|
-
--no-companions Skip companion plugins/MCP entirely.
|
|
36
|
-
--no-colors Skip the Obsidian color system (.obsidian snippet + graph groups).
|
|
37
|
-
--dotcontext-mcp <v> dotcontext MCP placement: auto (default; skip project entry
|
|
38
|
-
if already global), project, or none.
|
|
39
|
-
--dotcontext-hooks <v> dotcontext hooks: full (default), light (no PostToolUse), none.
|
|
40
|
-
--yes, -y Non-interactive; accept defaults.
|
|
41
|
-
--force Overwrite existing wendkeep config blocks.
|
|
42
|
-
|
|
43
|
-
wendkeep hook <name> Run a session hook (used by settings.json). Reads the
|
|
44
|
-
agent's JSON on stdin. Names: ${RUNNABLE_HOOKS.join(', ')}.
|
|
45
|
-
|
|
46
|
-
wendkeep sync [--project P] Run init -> sync-defs -> doctor on the CURRENT project, in one
|
|
47
|
-
command — the three steps that repeat identically after every
|
|
48
|
-
package update. Stops at the first failing step. Install the
|
|
49
|
-
package first (npm i -D wendkeep@latest); a running process
|
|
50
|
-
cannot replace itself. · --vault P · --profile <name> · --yes.
|
|
51
|
-
|
|
52
|
-
wendkeep doctor [--vault P] Run a vault health check.
|
|
53
|
-
wendkeep change <sub> Change lifecycle: new [--simple] | use | bind <slug> --session <id> | continue | list | show |
|
|
54
|
-
status | done <id> | undone <id> | diff | archive [--force] | abandon | relink | backlink.
|
|
55
|
-
archive exige verdict (rode verify --deep); abandon descarta sem ADR.
|
|
56
|
-
backlink [--apply]: injeta o backlink pro proposta em design/tarefas/spec órfãos (open + _arquivo).
|
|
57
|
-
wendkeep theme sync Re-aplica o color system (snippet CSS + graph color groups) num vault
|
|
58
|
-
existente — recupera o grafo cinza sem re-init. --vault P.
|
|
59
|
-
wendkeep session <sub> Session registry: list | show <id> | use <id>.
|
|
60
|
-
wendkeep profile <sub> Operating profile: status | use <OFF|FLOW|GUIDE|GOVERN|ASSURE>.
|
|
61
|
-
--session <id> sets an audited session override; otherwise changes
|
|
62
|
-
the project default. The Vault/session/memory core is always active.
|
|
63
|
-
wendkeep flow <sub> Low-ceremony E -> V contract: start | status | show | finish | promote.
|
|
64
|
-
FLOW records scope, sensors and a receipt without creating a change.
|
|
65
|
-
wendkeep spec <sub> Specs: list | show | effective [--change] [--json] | migrate | rebase.
|
|
66
|
-
wendkeep sensors <sub> list | add <id> "<command>" [--severity --type --report].
|
|
67
|
-
wendkeep cost [opts] Aggregate AI-coding spend across the vault's sessions.
|
|
68
|
-
--since <date> · --top [N] (priciest) · --trend [day|week|month]
|
|
69
|
-
(+ run-rate projection) · --write (generate 00-Custo.md) · --json.
|
|
70
|
-
wendkeep cost rebuild Recalculate historical parent + subagent costs from SESSION_REGISTRY.
|
|
71
|
-
Dry-run by default · --apply writes notes + .brain/COST_REBUILD.json
|
|
72
|
-
· --session <id|file> · --limit N · --json.
|
|
73
|
-
wendkeep stats [--vault P] One shareable line: sessions · prompts · spend · span · models (--json).
|
|
74
|
-
wendkeep import [opts] Backfill: import this project's past Claude + Codex sessions into
|
|
75
|
-
the vault (deduped by session_id). --source all|claude|codex (default
|
|
76
|
-
all) · --stamp-ids (backfill session_id in existing notes) ·
|
|
77
|
-
--rescan-decisions (capture prose decisions from already-imported transcripts) ·
|
|
78
|
-
--from <dir> · --codex-from <dir> · --since <date> · --limit N ·
|
|
79
|
-
--dry-run · --json.
|
|
80
|
-
wendkeep verify [--deep] [--change s] Run a change's task sensors + record evidence (the gate);
|
|
81
|
-
--deep assembles the verification package for the wk-verify pass.
|
|
82
|
-
wendkeep dashboard [--force] (Re)generate the vault's folder-filtered Bases + 00-Dashboard MOC.
|
|
83
|
-
wendkeep renumber-decisions Renumber 04-Decisões to ADR-<NNNN>-<slug> in chronological order,
|
|
84
|
-
renaming files + rewriting every wikilink. Preview by default; --apply to
|
|
85
|
-
write. --vault P · --json.
|
|
86
|
-
wendkeep renumber-bugs Renumber 05-Bugs to BUG-<NNNN>-<slug> chronologically, moving notes
|
|
87
|
-
out of legacy "DIA N" subfolders into the month folder and rewriting
|
|
88
|
-
wikilinks. Preview by default; --apply · --vault P · --json.
|
|
89
|
-
wendkeep renumber-learnings Same for 06-Aprendizados/06-Learnings with APR-<NNNN>-<slug>.
|
|
90
|
-
wendkeep note new --type bug|learning "<título>" Create a numbered derived note (BUG-/APR-NNNN)
|
|
91
|
-
in the month folder and print its vault path. --date YYYY-MM-DD · --vault P.
|
|
92
|
-
wendkeep note relink [--apply] Backfill orphan derived notes (BUG/APR without a source session),
|
|
93
|
-
linking each to the modal source session of its type/month cohort. Dry-run
|
|
94
|
-
by default; --apply writes; skips notes with no sibling to infer from.
|
|
95
|
-
wendkeep note repair-frontmatter [--apply] Merge stacked frontmatter blocks in session notes
|
|
96
|
-
(damage from pre-lock concurrent writes) into a single block: base keys
|
|
97
|
-
from the original block, values from the newest. Dry-run by default;
|
|
98
|
-
--apply writes under the same lock as the hooks · --json.
|
|
99
|
-
wendkeep note repair-sections [--apply] Rebuild the derived sections (decisions/bugs/learnings)
|
|
100
|
-
in session notes from the linked derived notes — the body used to lag
|
|
101
|
-
behind the closing block. Dry-run by default · --apply · --json.
|
|
102
|
-
wendkeep lesson add "t" "l" Record a project-local lesson (injected at SessionStart).
|
|
103
|
-
wendkeep memory <sub> Shared memory v2: status | migrate [--apply] | repair |
|
|
104
|
-
reconcile <session> --by-session <session> --reason <text> [--apply] |
|
|
105
|
-
promote <candidate> | reject <candidate>. --vault P.
|
|
106
|
-
Reconcile is dry-run by default; the original attempt remains audited.
|
|
107
|
-
wendkeep validate-memory [path] Validate .brain/CORE.md against the compaction
|
|
108
|
-
protocol (cap 25, 3 sections, no secrets/PII).
|
|
109
|
-
--vault <path> validates the complete v2 bundle.
|
|
110
|
-
wendkeep sync-defs [opts] Copy versioned defs from the vault's .brain into the
|
|
111
|
-
project: .brain/agents/*.toml -> .codex/agents,
|
|
112
|
-
.brain/skills/<name> -> .claude/skills + .agents/skills. --vault P --project P.
|
|
113
|
-
--reseed re-semeia as skills wk-* com os seeds da versão instalada
|
|
114
|
-
(sobrescreve edições manuais nas wk-*) antes de copiar.
|
|
115
|
-
--check detecta drift sem modificar arquivos.
|
|
116
|
-
wendkeep --version Print version.
|
|
117
|
-
wendkeep --help Show this help.
|
|
118
|
-
`;
|
|
119
|
-
|
|
120
|
-
function runHook(name) {
|
|
121
|
-
if (!name) {
|
|
122
|
-
process.stderr.write('wendkeep hook: missing hook name\n');
|
|
123
|
-
process.exit(2);
|
|
124
|
-
}
|
|
125
|
-
if (!RUNNABLE_HOOKS.includes(name)) {
|
|
126
|
-
process.stderr.write(`wendkeep hook: unknown hook "${name}". Known: ${RUNNABLE_HOOKS.join(', ')}\n`);
|
|
127
|
-
process.exit(2);
|
|
128
|
-
}
|
|
129
|
-
const file = join(hooksDir, `${name}.mjs`);
|
|
130
|
-
if (!existsSync(file)) {
|
|
131
|
-
process.stderr.write(`wendkeep hook: hook file not found: ${file}\n`);
|
|
132
|
-
process.exit(2);
|
|
133
|
-
}
|
|
134
|
-
// Spawn exactly as the agent would run `node <hook>.mjs`: stdio inherited so the
|
|
135
|
-
// hook's stdin (agent JSON) and stdout (hookSpecificOutput) pass through untouched.
|
|
136
|
-
const r = spawnSync(process.execPath, [file], { stdio: 'inherit' });
|
|
137
|
-
process.exit(r.status ?? 0);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function optionValue(argv, name) {
|
|
141
|
-
const index = argv.indexOf(name);
|
|
142
|
-
if (index >= 0) return argv[index + 1] || '';
|
|
143
|
-
return argv.find((item) => item.startsWith(`${name}=`))?.slice(name.length + 1) || '';
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async function preferProjectVault(argv) {
|
|
147
|
-
// Existing command modules still consume OBSIDIAN_VAULT_PATH internally. Populate it
|
|
148
|
-
// only inside this CLI process from the provider-neutral project binding, overriding
|
|
149
|
-
// any inherited machine-global value. An explicit --vault remains authoritative.
|
|
150
|
-
if (optionValue(argv, '--vault')) return;
|
|
151
|
-
try {
|
|
152
|
-
const { resolveProjectVault } = await import('../src/project-vault.mjs');
|
|
153
|
-
// A sensor may itself invoke WendKeep. `verify`/`flow finish` already selected the
|
|
154
|
-
// authoritative Vault explicitly, so preserve that choice across the child process
|
|
155
|
-
// instead of letting the sensor cwd's project binding redirect it.
|
|
156
|
-
const sensorVault = process.env.WENDKEEP_SENSOR_VAULT;
|
|
157
|
-
if (sensorVault) {
|
|
158
|
-
const selected = resolveProjectVault({
|
|
159
|
-
startDir: optionValue(argv, '--project') || process.cwd(),
|
|
160
|
-
explicitVault: sensorVault,
|
|
161
|
-
});
|
|
162
|
-
process.env.OBSIDIAN_VAULT_PATH = selected.base;
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const resolved = resolveProjectVault({ startDir: optionValue(argv, '--project') || process.cwd() });
|
|
166
|
-
process.env.OBSIDIAN_VAULT_PATH = resolved.base;
|
|
167
|
-
} catch (error) {
|
|
168
|
-
// Backward-compatible manual CLI behavior: individual commands still explain
|
|
169
|
-
// --vault / legacy env only when no project binding exists. A configured but
|
|
170
|
-
// corrupt/missing/mismatched binding must abort before dispatch to another Vault.
|
|
171
|
-
if (error?.code !== 'WENDKEEP_VAULT_UNCONFIGURED') throw error;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async function main() {
|
|
176
|
-
const [cmd, ...rest] = process.argv.slice(2);
|
|
177
|
-
// Universal --help: any subcommand with --help/-h prints usage and never executes.
|
|
178
|
-
// Intercepted BEFORE vault resolution so it works anywhere — help must never depend
|
|
179
|
-
// on project state, and no command may treat --help as a runnable default.
|
|
180
|
-
if (cmd && (rest.includes('--help') || rest.includes('-h'))) {
|
|
181
|
-
if (cmd === 'flow') {
|
|
182
|
-
const { FLOW_HELP } = await import('../src/flow.mjs');
|
|
183
|
-
process.stdout.write(FLOW_HELP);
|
|
184
|
-
} else if (cmd === 'profile') {
|
|
185
|
-
const { PROFILE_HELP } = await import('../src/profile.mjs');
|
|
186
|
-
process.stdout.write(PROFILE_HELP);
|
|
187
|
-
} else {
|
|
188
|
-
process.stdout.write(HELP);
|
|
189
|
-
}
|
|
190
|
-
process.exit(0);
|
|
191
|
-
}
|
|
192
|
-
const validatesStandaloneCore = cmd === 'validate-memory'
|
|
193
|
-
&& !rest.includes('--vault')
|
|
194
|
-
&& !rest.some((item) => item.startsWith('--vault='));
|
|
195
|
-
if (cmd
|
|
196
|
-
&& !validatesStandaloneCore
|
|
197
|
-
// `sync` starts with `init` and resolves the freshly bound Vault itself. Pre-resolving
|
|
198
|
-
// here would prevent that repair step from reporting a corrupt binding as its own
|
|
199
|
-
// first-stage failure (and could never make it as far as the guarded init).
|
|
200
|
-
&& !['init', 'sync', 'hook', '--version', '-v', '--help', '-h', 'help'].includes(cmd)) {
|
|
201
|
-
await preferProjectVault(rest);
|
|
202
|
-
}
|
|
203
|
-
switch (cmd) {
|
|
204
|
-
case 'init': {
|
|
205
|
-
const { runInit } = await import('../src/init.mjs');
|
|
206
|
-
await runInit(rest);
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
case 'hook':
|
|
210
|
-
runHook(rest[0]);
|
|
211
|
-
break;
|
|
212
|
-
case 'doctor': {
|
|
213
|
-
const { runDoctor } = await import('../src/doctor.mjs');
|
|
214
|
-
process.exit(runDoctor(rest));
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
case 'sync': {
|
|
218
|
-
const { runSync } = await import('../src/sync.mjs');
|
|
219
|
-
process.exit(await runSync(rest));
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
case 'validate-memory': {
|
|
223
|
-
if (rest.includes('--vault') || rest.some((item) => item.startsWith('--vault='))) {
|
|
224
|
-
const { runValidateMemoryBundle } = await import('../src/memory.mjs');
|
|
225
|
-
runValidateMemoryBundle(rest);
|
|
226
|
-
} else {
|
|
227
|
-
const { runValidateMemory } = await import('../src/validate-core.mjs');
|
|
228
|
-
runValidateMemory(rest);
|
|
229
|
-
}
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
case 'memory': {
|
|
233
|
-
const { runMemory } = await import('../src/memory.mjs');
|
|
234
|
-
runMemory(rest);
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
case 'sync-defs': {
|
|
238
|
-
const { runSyncDefs } = await import('../src/sync-defs.mjs');
|
|
239
|
-
process.exit(runSyncDefs(rest));
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
case 'change': {
|
|
243
|
-
const { runChange } = await import('../src/change.mjs');
|
|
244
|
-
runChange(rest);
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
case 'session': {
|
|
248
|
-
const { runSession } = await import('../src/session.mjs');
|
|
249
|
-
runSession(rest);
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
case 'profile': {
|
|
253
|
-
const { runProfile } = await import('../src/profile.mjs');
|
|
254
|
-
process.exit(runProfile(rest));
|
|
255
|
-
break;
|
|
256
|
-
}
|
|
257
|
-
case 'flow': {
|
|
258
|
-
const { runFlow } = await import('../src/flow.mjs');
|
|
259
|
-
process.exit(await runFlow(rest));
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
case 'theme': {
|
|
263
|
-
const { runTheme } = await import('../src/theme.mjs');
|
|
264
|
-
runTheme(rest);
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
case 'verify': {
|
|
268
|
-
const { runVerify } = await import('../src/verify.mjs');
|
|
269
|
-
runVerify(rest);
|
|
270
|
-
break;
|
|
271
|
-
}
|
|
272
|
-
case 'lesson': {
|
|
273
|
-
const { runLesson } = await import('../src/lessons.mjs');
|
|
274
|
-
runLesson(rest);
|
|
275
|
-
break;
|
|
276
|
-
}
|
|
277
|
-
case 'spec': {
|
|
278
|
-
const { runSpec } = await import('../src/spec.mjs');
|
|
279
|
-
runSpec(rest);
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
case 'sensors': {
|
|
283
|
-
const { runSensors } = await import('../src/sensors.mjs');
|
|
284
|
-
runSensors(rest);
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
case 'cost': {
|
|
288
|
-
const { runCost } = await import('../src/cost.mjs');
|
|
289
|
-
runCost(rest);
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
case 'stats': {
|
|
293
|
-
const { runStats } = await import('../src/stats.mjs');
|
|
294
|
-
runStats(rest);
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
case 'import': {
|
|
298
|
-
const { runImportCli } = await import('../src/import.mjs');
|
|
299
|
-
runImportCli(rest);
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
case 'dashboard': {
|
|
303
|
-
const { runDashboard } = await import('../src/vault-views.mjs');
|
|
304
|
-
runDashboard(rest);
|
|
305
|
-
break;
|
|
306
|
-
}
|
|
307
|
-
case 'renumber-decisions': {
|
|
308
|
-
const { runRenumberDecisions } = await import('../src/renumber.mjs');
|
|
309
|
-
runRenumberDecisions(rest);
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
case 'renumber-bugs': {
|
|
313
|
-
const { runRenumberBugs } = await import('../src/renumber.mjs');
|
|
314
|
-
runRenumberBugs(rest);
|
|
315
|
-
break;
|
|
316
|
-
}
|
|
317
|
-
case 'renumber-learnings': {
|
|
318
|
-
const { runRenumberLearnings } = await import('../src/renumber.mjs');
|
|
319
|
-
runRenumberLearnings(rest);
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
case 'note': {
|
|
323
|
-
const { runNote } = await import('../src/note.mjs');
|
|
324
|
-
runNote(rest);
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
case '--version':
|
|
328
|
-
case '-v':
|
|
329
|
-
process.stdout.write(`${version()}\n`);
|
|
330
|
-
break;
|
|
331
|
-
case undefined:
|
|
332
|
-
case '--help':
|
|
333
|
-
case '-h':
|
|
334
|
-
case 'help':
|
|
335
|
-
process.stdout.write(HELP);
|
|
336
|
-
break;
|
|
337
|
-
default:
|
|
338
|
-
process.stderr.write(`wendkeep: unknown command "${cmd}"\n\n${HELP}`);
|
|
339
|
-
process.exit(2);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
main().catch((err) => {
|
|
344
|
-
process.stderr.write(`wendkeep: ${err?.stack || err}\n`);
|
|
345
|
-
process.exit(1);
|
|
346
|
-
});
|
|
4
|
+
await runCli();
|
|
@@ -73,6 +73,11 @@ pnpm exec wendkeep sync --yes
|
|
|
73
73
|
The project receives `.wendkeep.json`, managed Claude/Codex hooks, skill definitions, and an
|
|
74
74
|
initialized vault. Existing files are merged or preserved, and the selected vault is printed.
|
|
75
75
|
|
|
76
|
+
When MCP is enabled, `init` preserves existing properties and servers in `.mcp.json` and adds
|
|
77
|
+
`wendkeep-vault`. If the existing JSON is invalid, the original file remains byte-for-byte intact
|
|
78
|
+
and the reconciled proposal is written to `.mcp.json.new`. Since version 0.65, this composition is
|
|
79
|
+
owned by the private MCP kernel without changing commands, flags, or the public npm surface.
|
|
80
|
+
|
|
76
81
|
## Common errors and diagnosis
|
|
77
82
|
|
|
78
83
|
- Wrong vault: inspect `.wendkeep.json` and run `wendkeep doctor --vault <path>`.
|
|
@@ -74,6 +74,11 @@ O projeto recebe `.wendkeep.json`, hooks gerenciados de Claude/Codex, definiçõ
|
|
|
74
74
|
cofre inicializado. Arquivos preexistentes são mesclados ou preservados; o comando informa o
|
|
75
75
|
cofre efetivamente selecionado.
|
|
76
76
|
|
|
77
|
+
Quando MCP está habilitado, o `init` preserva propriedades e servidores existentes em `.mcp.json`
|
|
78
|
+
e adiciona `wendkeep-vault`. Se o JSON existente for inválido, o arquivo original permanece byte
|
|
79
|
+
a byte intacto e a proposta reconciliada é gravada em `.mcp.json.new`. Desde a versão 0.65, essa
|
|
80
|
+
composição pertence ao kernel MCP privado, sem alterar comandos, flags ou a superfície npm pública.
|
|
81
|
+
|
|
77
82
|
## Erros comuns e diagnóstico
|
|
78
83
|
|
|
79
84
|
- Cofre errado: confira `.wendkeep.json` e rode `wendkeep doctor --vault <path>`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
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": [
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"node": ">=18"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
|
-
"check": "node --check bin/wendkeep.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/project-vault.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
|
|
34
|
+
"check": "node --check bin/wendkeep.mjs && node --check packages/cli/src/index.mjs && node --check src/init.mjs && node --check src/doctor.mjs && node --check src/project-vault.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/flow-core.mjs && node --check hooks/flow-protected-policy.mjs && node --check hooks/git-snapshot.mjs && node --check hooks/vault-path-safety.mjs && node --check hooks/vault-runtime-store.mjs && node --check packages/harness/src/index.mjs && node --check packages/harness/src/flow-store.mjs && node --check packages/harness/src/operating-profile.mjs && node --check packages/harness/src/sensors-core.mjs && node --check packages/mcp/src/config.mjs && node --check packages/mcp/src/index.mjs && node --check packages/vault/src/index.mjs && node --check packages/vault/src/project-vault.mjs && node --check packages/vault/src/vault-path-safety.mjs && node --check packages/vault/src/locale.mjs && node --check packages/vault/src/memory-schema.mjs && node --check packages/vault/src/memory-mode.mjs && node --check packages/vault/src/memory-handoff.mjs && node --check packages/vault/src/memory-store.mjs && node --check packages/vault/src/validate-core.mjs && node --check packages/vault/src/validate-memory.mjs",
|
|
35
35
|
"test": "node --test",
|
|
36
36
|
"release": "node scripts/release.mjs",
|
|
37
37
|
"release:dry": "node scripts/release.mjs --dry-run",
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// wendkeep CLI — canonical private runtime.
|
|
2
|
+
// wendkeep init [--vault <path>] [--project <path>] [--no-mcp] [--yes] [--force]
|
|
3
|
+
// wendkeep hook <name> (invoked by the agent's settings.json; pipes stdin/stdout)
|
|
4
|
+
// wendkeep doctor [--vault <path>]
|
|
5
|
+
// wendkeep --version | --help
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { RUNNABLE_HOOKS } from '../../../src/taxonomy.mjs';
|
|
11
|
+
|
|
12
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const pkgRoot = join(here, '..', '..', '..');
|
|
14
|
+
const hooksDir = join(pkgRoot, 'hooks');
|
|
15
|
+
|
|
16
|
+
function version() {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8')).version;
|
|
19
|
+
} catch {
|
|
20
|
+
return '0.0.0';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const HELP = `wendkeep ${version()} — keep durable AI sessions in an Obsidian vault, with optional governance.
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
wendkeep init [options] Set up wendkeep in a project (cross-platform).
|
|
28
|
+
--vault <path> Obsidian vault folder (default: <project>/.<project-name>-vault).
|
|
29
|
+
--project <path> Project root to wire (default: current directory).
|
|
30
|
+
--profile <name> Operating profile: OFF, FLOW, GUIDE, GOVERN (default), or ASSURE.
|
|
31
|
+
--no-mcp Do not add the mcpvault MCP server to .mcp.json.
|
|
32
|
+
--companions <csv> Companion plugins/MCP to pin: context-mode,caveman,understand-anything
|
|
33
|
+
(default: none — opt in explicitly). dotcontext is legacy — the native a2 loop replaces it.
|
|
34
|
+
--no-companions Skip companion plugins/MCP entirely.
|
|
35
|
+
--no-colors Skip the Obsidian color system (.obsidian snippet + graph groups).
|
|
36
|
+
--dotcontext-mcp <v> dotcontext MCP placement: auto (default; skip project entry
|
|
37
|
+
if already global), project, or none.
|
|
38
|
+
--dotcontext-hooks <v> dotcontext hooks: full (default), light (no PostToolUse), none.
|
|
39
|
+
--yes, -y Non-interactive; accept defaults.
|
|
40
|
+
--force Overwrite existing wendkeep config blocks.
|
|
41
|
+
|
|
42
|
+
wendkeep hook <name> Run a session hook (used by settings.json). Reads the
|
|
43
|
+
agent's JSON on stdin. Names: ${RUNNABLE_HOOKS.join(', ')}.
|
|
44
|
+
|
|
45
|
+
wendkeep sync [--project P] Run init -> sync-defs -> doctor on the CURRENT project, in one
|
|
46
|
+
command — the three steps that repeat identically after every
|
|
47
|
+
package update. Stops at the first failing step. Install the
|
|
48
|
+
package first (npm i -D wendkeep@latest); a running process
|
|
49
|
+
cannot replace itself. · --vault P · --profile <name> · --yes.
|
|
50
|
+
|
|
51
|
+
wendkeep doctor [--vault P] Run a vault health check.
|
|
52
|
+
wendkeep change <sub> Change lifecycle: new [--simple] | use | bind <slug> --session <id> | continue | list | show |
|
|
53
|
+
status | done <id> | undone <id> | diff | archive [--force] | abandon | relink | backlink.
|
|
54
|
+
archive exige verdict (rode verify --deep); abandon descarta sem ADR.
|
|
55
|
+
backlink [--apply]: injeta o backlink pro proposta em design/tarefas/spec órfãos (open + _arquivo).
|
|
56
|
+
wendkeep theme sync Re-aplica o color system (snippet CSS + graph color groups) num vault
|
|
57
|
+
existente — recupera o grafo cinza sem re-init. --vault P.
|
|
58
|
+
wendkeep session <sub> Session registry: list | show <id> | use <id>.
|
|
59
|
+
wendkeep profile <sub> Operating profile: status | use <OFF|FLOW|GUIDE|GOVERN|ASSURE>.
|
|
60
|
+
--session <id> sets an audited session override; otherwise changes
|
|
61
|
+
the project default. The Vault/session/memory core is always active.
|
|
62
|
+
wendkeep flow <sub> Low-ceremony E -> V contract: start | status | show | finish | promote.
|
|
63
|
+
FLOW records scope, sensors and a receipt without creating a change.
|
|
64
|
+
wendkeep spec <sub> Specs: list | show | effective [--change] [--json] | migrate | rebase.
|
|
65
|
+
wendkeep sensors <sub> list | add <id> "<command>" [--severity --type --report].
|
|
66
|
+
wendkeep cost [opts] Aggregate AI-coding spend across the vault's sessions.
|
|
67
|
+
--since <date> · --top [N] (priciest) · --trend [day|week|month]
|
|
68
|
+
(+ run-rate projection) · --write (generate 00-Custo.md) · --json.
|
|
69
|
+
wendkeep cost rebuild Recalculate historical parent + subagent costs from SESSION_REGISTRY.
|
|
70
|
+
Dry-run by default · --apply writes notes + .brain/COST_REBUILD.json
|
|
71
|
+
· --session <id|file> · --limit N · --json.
|
|
72
|
+
wendkeep stats [--vault P] One shareable line: sessions · prompts · spend · span · models (--json).
|
|
73
|
+
wendkeep import [opts] Backfill: import this project's past Claude + Codex sessions into
|
|
74
|
+
the vault (deduped by session_id). --source all|claude|codex (default
|
|
75
|
+
all) · --stamp-ids (backfill session_id in existing notes) ·
|
|
76
|
+
--rescan-decisions (capture prose decisions from already-imported transcripts) ·
|
|
77
|
+
--from <dir> · --codex-from <dir> · --since <date> · --limit N ·
|
|
78
|
+
--dry-run · --json.
|
|
79
|
+
wendkeep verify [--deep] [--change s] Run a change's task sensors + record evidence (the gate);
|
|
80
|
+
--deep assembles the verification package for the wk-verify pass.
|
|
81
|
+
wendkeep dashboard [--force] (Re)generate the vault's folder-filtered Bases + 00-Dashboard MOC.
|
|
82
|
+
wendkeep renumber-decisions Renumber 04-Decisões to ADR-<NNNN>-<slug> in chronological order,
|
|
83
|
+
renaming files + rewriting every wikilink. Preview by default; --apply to
|
|
84
|
+
write. --vault P · --json.
|
|
85
|
+
wendkeep renumber-bugs Renumber 05-Bugs to BUG-<NNNN>-<slug> chronologically, moving notes
|
|
86
|
+
out of legacy "DIA N" subfolders into the month folder and rewriting
|
|
87
|
+
wikilinks. Preview by default; --apply · --vault P · --json.
|
|
88
|
+
wendkeep renumber-learnings Same for 06-Aprendizados/06-Learnings with APR-<NNNN>-<slug>.
|
|
89
|
+
wendkeep note new --type bug|learning "<título>" Create a numbered derived note (BUG-/APR-NNNN)
|
|
90
|
+
in the month folder and print its vault path. --date YYYY-MM-DD · --vault P.
|
|
91
|
+
wendkeep note relink [--apply] Backfill orphan derived notes (BUG/APR without a source session),
|
|
92
|
+
linking each to the modal source session of its type/month cohort. Dry-run
|
|
93
|
+
by default; --apply writes; skips notes with no sibling to infer from.
|
|
94
|
+
wendkeep note repair-frontmatter [--apply] Merge stacked frontmatter blocks in session notes
|
|
95
|
+
(damage from pre-lock concurrent writes) into a single block: base keys
|
|
96
|
+
from the original block, values from the newest. Dry-run by default;
|
|
97
|
+
--apply writes under the same lock as the hooks · --json.
|
|
98
|
+
wendkeep note repair-sections [--apply] Rebuild the derived sections (decisions/bugs/learnings)
|
|
99
|
+
in session notes from the linked derived notes — the body used to lag
|
|
100
|
+
behind the closing block. Dry-run by default · --apply · --json.
|
|
101
|
+
wendkeep lesson add "t" "l" Record a project-local lesson (injected at SessionStart).
|
|
102
|
+
wendkeep memory <sub> Shared memory v2: status | migrate [--apply] | repair |
|
|
103
|
+
reconcile <session> --by-session <session> --reason <text> [--apply] |
|
|
104
|
+
promote <candidate> | reject <candidate>. --vault P.
|
|
105
|
+
Reconcile is dry-run by default; the original attempt remains audited.
|
|
106
|
+
wendkeep validate-memory [path] Validate .brain/CORE.md against the compaction
|
|
107
|
+
protocol (cap 25, 3 sections, no secrets/PII).
|
|
108
|
+
--vault <path> validates the complete v2 bundle.
|
|
109
|
+
wendkeep sync-defs [opts] Copy versioned defs from the vault's .brain into the
|
|
110
|
+
project: .brain/agents/*.toml -> .codex/agents,
|
|
111
|
+
.brain/skills/<name> -> .claude/skills + .agents/skills. --vault P --project P.
|
|
112
|
+
--reseed re-semeia as skills wk-* com os seeds da versão instalada
|
|
113
|
+
(sobrescreve edições manuais nas wk-*) antes de copiar.
|
|
114
|
+
--check detecta drift sem modificar arquivos.
|
|
115
|
+
wendkeep --version Print version.
|
|
116
|
+
wendkeep --help Show this help.
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
function runHook(name) {
|
|
120
|
+
if (!name) {
|
|
121
|
+
process.stderr.write('wendkeep hook: missing hook name\n');
|
|
122
|
+
process.exit(2);
|
|
123
|
+
}
|
|
124
|
+
if (!RUNNABLE_HOOKS.includes(name)) {
|
|
125
|
+
process.stderr.write(`wendkeep hook: unknown hook "${name}". Known: ${RUNNABLE_HOOKS.join(', ')}\n`);
|
|
126
|
+
process.exit(2);
|
|
127
|
+
}
|
|
128
|
+
const file = join(hooksDir, `${name}.mjs`);
|
|
129
|
+
if (!existsSync(file)) {
|
|
130
|
+
process.stderr.write(`wendkeep hook: hook file not found: ${file}\n`);
|
|
131
|
+
process.exit(2);
|
|
132
|
+
}
|
|
133
|
+
// Spawn exactly as the agent would run `node <hook>.mjs`: stdio inherited so the
|
|
134
|
+
// hook's stdin (agent JSON) and stdout (hookSpecificOutput) pass through untouched.
|
|
135
|
+
const r = spawnSync(process.execPath, [file], { stdio: 'inherit' });
|
|
136
|
+
process.exit(r.status ?? 0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function optionValue(argv, name) {
|
|
140
|
+
const index = argv.indexOf(name);
|
|
141
|
+
if (index >= 0) return argv[index + 1] || '';
|
|
142
|
+
return argv.find((item) => item.startsWith(`${name}=`))?.slice(name.length + 1) || '';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function preferProjectVault(argv) {
|
|
146
|
+
// Existing command modules still consume OBSIDIAN_VAULT_PATH internally. Populate it
|
|
147
|
+
// only inside this CLI process from the provider-neutral project binding, overriding
|
|
148
|
+
// any inherited machine-global value. An explicit --vault remains authoritative.
|
|
149
|
+
if (optionValue(argv, '--vault')) return;
|
|
150
|
+
try {
|
|
151
|
+
const { resolveProjectVault } = await import('../../../src/project-vault.mjs');
|
|
152
|
+
// A sensor may itself invoke WendKeep. `verify`/`flow finish` already selected the
|
|
153
|
+
// authoritative Vault explicitly, so preserve that choice across the child process
|
|
154
|
+
// instead of letting the sensor cwd's project binding redirect it.
|
|
155
|
+
const sensorVault = process.env.WENDKEEP_SENSOR_VAULT;
|
|
156
|
+
if (sensorVault) {
|
|
157
|
+
const selected = resolveProjectVault({
|
|
158
|
+
startDir: optionValue(argv, '--project') || process.cwd(),
|
|
159
|
+
explicitVault: sensorVault,
|
|
160
|
+
});
|
|
161
|
+
process.env.OBSIDIAN_VAULT_PATH = selected.base;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const resolved = resolveProjectVault({ startDir: optionValue(argv, '--project') || process.cwd() });
|
|
165
|
+
process.env.OBSIDIAN_VAULT_PATH = resolved.base;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
// Backward-compatible manual CLI behavior: individual commands still explain
|
|
168
|
+
// --vault / legacy env only when no project binding exists. A configured but
|
|
169
|
+
// corrupt/missing/mismatched binding must abort before dispatch to another Vault.
|
|
170
|
+
if (error?.code !== 'WENDKEEP_VAULT_UNCONFIGURED') throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function main(argv) {
|
|
175
|
+
const [cmd, ...rest] = argv;
|
|
176
|
+
// Universal --help: any subcommand with --help/-h prints usage and never executes.
|
|
177
|
+
// Intercepted BEFORE vault resolution so it works anywhere — help must never depend
|
|
178
|
+
// on project state, and no command may treat --help as a runnable default.
|
|
179
|
+
if (cmd && (rest.includes('--help') || rest.includes('-h'))) {
|
|
180
|
+
if (cmd === 'flow') {
|
|
181
|
+
const { FLOW_HELP } = await import('../../../src/flow.mjs');
|
|
182
|
+
process.stdout.write(FLOW_HELP);
|
|
183
|
+
} else if (cmd === 'profile') {
|
|
184
|
+
const { PROFILE_HELP } = await import('../../../src/profile.mjs');
|
|
185
|
+
process.stdout.write(PROFILE_HELP);
|
|
186
|
+
} else {
|
|
187
|
+
process.stdout.write(HELP);
|
|
188
|
+
}
|
|
189
|
+
process.exit(0);
|
|
190
|
+
}
|
|
191
|
+
const validatesStandaloneCore = cmd === 'validate-memory'
|
|
192
|
+
&& !rest.includes('--vault')
|
|
193
|
+
&& !rest.some((item) => item.startsWith('--vault='));
|
|
194
|
+
if (cmd
|
|
195
|
+
&& !validatesStandaloneCore
|
|
196
|
+
// `sync` starts with `init` and resolves the freshly bound Vault itself. Pre-resolving
|
|
197
|
+
// here would prevent that repair step from reporting a corrupt binding as its own
|
|
198
|
+
// first-stage failure (and could never make it as far as the guarded init).
|
|
199
|
+
&& !['init', 'sync', 'hook', '--version', '-v', '--help', '-h', 'help'].includes(cmd)) {
|
|
200
|
+
await preferProjectVault(rest);
|
|
201
|
+
}
|
|
202
|
+
switch (cmd) {
|
|
203
|
+
case 'init': {
|
|
204
|
+
const { runInit } = await import('../../../src/init.mjs');
|
|
205
|
+
await runInit(rest);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
case 'hook':
|
|
209
|
+
runHook(rest[0]);
|
|
210
|
+
break;
|
|
211
|
+
case 'doctor': {
|
|
212
|
+
const { runDoctor } = await import('../../../src/doctor.mjs');
|
|
213
|
+
process.exit(runDoctor(rest));
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case 'sync': {
|
|
217
|
+
const { runSync } = await import('../../../src/sync.mjs');
|
|
218
|
+
process.exit(await runSync(rest));
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
case 'validate-memory': {
|
|
222
|
+
if (rest.includes('--vault') || rest.some((item) => item.startsWith('--vault='))) {
|
|
223
|
+
const { runValidateMemoryBundle } = await import('../../../src/memory.mjs');
|
|
224
|
+
runValidateMemoryBundle(rest);
|
|
225
|
+
} else {
|
|
226
|
+
const { runValidateMemory } = await import('../../../src/validate-core.mjs');
|
|
227
|
+
runValidateMemory(rest);
|
|
228
|
+
}
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
case 'memory': {
|
|
232
|
+
const { runMemory } = await import('../../../src/memory.mjs');
|
|
233
|
+
runMemory(rest);
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case 'sync-defs': {
|
|
237
|
+
const { runSyncDefs } = await import('../../../src/sync-defs.mjs');
|
|
238
|
+
process.exit(runSyncDefs(rest));
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case 'change': {
|
|
242
|
+
const { runChange } = await import('../../../src/change.mjs');
|
|
243
|
+
runChange(rest);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case 'session': {
|
|
247
|
+
const { runSession } = await import('../../../src/session.mjs');
|
|
248
|
+
runSession(rest);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case 'profile': {
|
|
252
|
+
const { runProfile } = await import('../../../src/profile.mjs');
|
|
253
|
+
process.exit(runProfile(rest));
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case 'flow': {
|
|
257
|
+
const { runFlow } = await import('../../../src/flow.mjs');
|
|
258
|
+
process.exit(await runFlow(rest));
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case 'theme': {
|
|
262
|
+
const { runTheme } = await import('../../../src/theme.mjs');
|
|
263
|
+
runTheme(rest);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
case 'verify': {
|
|
267
|
+
const { runVerify } = await import('../../../src/verify.mjs');
|
|
268
|
+
runVerify(rest);
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
case 'lesson': {
|
|
272
|
+
const { runLesson } = await import('../../../src/lessons.mjs');
|
|
273
|
+
runLesson(rest);
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case 'spec': {
|
|
277
|
+
const { runSpec } = await import('../../../src/spec.mjs');
|
|
278
|
+
runSpec(rest);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
case 'sensors': {
|
|
282
|
+
const { runSensors } = await import('../../../src/sensors.mjs');
|
|
283
|
+
runSensors(rest);
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case 'cost': {
|
|
287
|
+
const { runCost } = await import('../../../src/cost.mjs');
|
|
288
|
+
runCost(rest);
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
case 'stats': {
|
|
292
|
+
const { runStats } = await import('../../../src/stats.mjs');
|
|
293
|
+
runStats(rest);
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
case 'import': {
|
|
297
|
+
const { runImportCli } = await import('../../../src/import.mjs');
|
|
298
|
+
runImportCli(rest);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case 'dashboard': {
|
|
302
|
+
const { runDashboard } = await import('../../../src/vault-views.mjs');
|
|
303
|
+
runDashboard(rest);
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case 'renumber-decisions': {
|
|
307
|
+
const { runRenumberDecisions } = await import('../../../src/renumber.mjs');
|
|
308
|
+
runRenumberDecisions(rest);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
case 'renumber-bugs': {
|
|
312
|
+
const { runRenumberBugs } = await import('../../../src/renumber.mjs');
|
|
313
|
+
runRenumberBugs(rest);
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
case 'renumber-learnings': {
|
|
317
|
+
const { runRenumberLearnings } = await import('../../../src/renumber.mjs');
|
|
318
|
+
runRenumberLearnings(rest);
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case 'note': {
|
|
322
|
+
const { runNote } = await import('../../../src/note.mjs');
|
|
323
|
+
runNote(rest);
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
case '--version':
|
|
327
|
+
case '-v':
|
|
328
|
+
process.stdout.write(`${version()}\n`);
|
|
329
|
+
break;
|
|
330
|
+
case undefined:
|
|
331
|
+
case '--help':
|
|
332
|
+
case '-h':
|
|
333
|
+
case 'help':
|
|
334
|
+
process.stdout.write(HELP);
|
|
335
|
+
break;
|
|
336
|
+
default:
|
|
337
|
+
process.stderr.write(`wendkeep: unknown command "${cmd}"\n\n${HELP}`);
|
|
338
|
+
process.exit(2);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
343
|
+
try {
|
|
344
|
+
await main(argv);
|
|
345
|
+
} catch (err) {
|
|
346
|
+
process.stderr.write(`wendkeep: ${err?.stack || err}\n`);
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const MCP_SERVER_KEY = 'wendkeep-vault';
|
|
2
|
+
|
|
3
|
+
export function mcpServerEntry(vaultPath) {
|
|
4
|
+
return {
|
|
5
|
+
type: 'stdio',
|
|
6
|
+
command: 'npx',
|
|
7
|
+
args: ['-y', '@bitbonsai/mcpvault@latest', vaultPath],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function selectMcpServers(descriptors, skipIds = []) {
|
|
12
|
+
const skipSet = new Set(skipIds);
|
|
13
|
+
const servers = {};
|
|
14
|
+
for (const descriptor of descriptors || []) {
|
|
15
|
+
if (!descriptor || skipSet.has(descriptor.id)) continue;
|
|
16
|
+
if (typeof descriptor.key !== 'string' || !descriptor.key) continue;
|
|
17
|
+
if (!descriptor.entry || typeof descriptor.entry !== 'object') continue;
|
|
18
|
+
servers[descriptor.key] = descriptor.entry;
|
|
19
|
+
}
|
|
20
|
+
return servers;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function mergeMcpConfig(existing, {
|
|
24
|
+
vaultPath,
|
|
25
|
+
withVault = true,
|
|
26
|
+
servers = {},
|
|
27
|
+
} = {}) {
|
|
28
|
+
const config = existing && typeof existing === 'object' ? { ...existing } : {};
|
|
29
|
+
config.mcpServers = { ...(config.mcpServers || {}) };
|
|
30
|
+
if (withVault) config.mcpServers[MCP_SERVER_KEY] = mcpServerEntry(vaultPath);
|
|
31
|
+
Object.assign(config.mcpServers, servers);
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './config.mjs';
|
package/src/init.mjs
CHANGED
|
@@ -6,13 +6,12 @@ import { spawnSync } from 'node:child_process';
|
|
|
6
6
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { basename, isAbsolute, join, resolve } from 'node:path';
|
|
8
8
|
import { createInterface } from 'node:readline/promises';
|
|
9
|
+
import { MCP_SERVER_KEY, mergeMcpConfig } from '../packages/mcp/src/index.mjs';
|
|
9
10
|
import {
|
|
10
11
|
VAULT_FOLDERS,
|
|
11
12
|
SESSION_HOOKS,
|
|
12
13
|
CHANGE_NUDGE_HOOKS,
|
|
13
14
|
CHANGE_GATE_HOOKS,
|
|
14
|
-
MCP_SERVER_KEY,
|
|
15
|
-
mcpServerEntry,
|
|
16
15
|
hookCommand,
|
|
17
16
|
hookCommandLocal,
|
|
18
17
|
hookCommandLocalLegacy,
|
|
@@ -245,11 +244,11 @@ export function mergeCodexHooks(existing, { force = false } = {}) {
|
|
|
245
244
|
}
|
|
246
245
|
|
|
247
246
|
export function mergeMcp(existing, { vaultPath, withVault = true, companions = [], skipMcp = [] }) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
247
|
+
return mergeMcpConfig(existing, {
|
|
248
|
+
vaultPath,
|
|
249
|
+
withVault,
|
|
250
|
+
servers: companionMcpPatch(companions, skipMcp),
|
|
251
|
+
});
|
|
253
252
|
}
|
|
254
253
|
|
|
255
254
|
// Run caveman's cross-agent installer (non-Claude skill coverage). Downloads the
|
package/src/taxonomy.mjs
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MCP_SERVER_KEY,
|
|
3
|
+
mcpServerEntry,
|
|
4
|
+
selectMcpServers,
|
|
5
|
+
} from '../packages/mcp/src/index.mjs';
|
|
6
|
+
|
|
7
|
+
export { MCP_SERVER_KEY, mcpServerEntry };
|
|
8
|
+
|
|
1
9
|
// Shared, data-only constants for the wendkeep installer and CLI.
|
|
2
10
|
// Kept free of side effects so both bin/ and src/ can import it cheaply.
|
|
3
11
|
|
|
@@ -93,17 +101,6 @@ export const RUNNABLE_HOOKS = [
|
|
|
93
101
|
'plan-capture',
|
|
94
102
|
];
|
|
95
103
|
|
|
96
|
-
// The MCP server entry wendkeep wires into .mcp.json so the agent can read/write the
|
|
97
|
-
// vault. Uses the published mcpvault server (no secrets).
|
|
98
|
-
export function mcpServerEntry(vaultPath) {
|
|
99
|
-
return {
|
|
100
|
-
type: 'stdio',
|
|
101
|
-
command: 'npx',
|
|
102
|
-
args: ['-y', '@bitbonsai/mcpvault@latest', vaultPath],
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
export const MCP_SERVER_KEY = 'wendkeep-vault';
|
|
106
|
-
|
|
107
104
|
// The three Claude Code session hooks, expressed as `wendkeep hook <name>` so the
|
|
108
105
|
// installed package is the single source of truth (update with `npm update wendkeep`,
|
|
109
106
|
// no re-copying). Returned as a spec the merge logic folds into settings.json.
|
|
@@ -281,14 +278,11 @@ export function companionSettingsPatch(ids) {
|
|
|
281
278
|
// `skip` omits ids whose MCP is already configured elsewhere (e.g. dotcontext set
|
|
282
279
|
// globally in ~/.claude.json — avoids a duplicate project-scoped server).
|
|
283
280
|
export function companionMcpPatch(ids, skip = []) {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (c?.mcp) servers[c.mcp.key] = c.mcp.entry;
|
|
290
|
-
}
|
|
291
|
-
return servers;
|
|
281
|
+
const descriptors = ids.map((id) => {
|
|
282
|
+
const mcp = COMPANION_BY_ID[id]?.mcp;
|
|
283
|
+
return mcp ? { id, key: mcp.key, entry: mcp.entry } : { id };
|
|
284
|
+
});
|
|
285
|
+
return selectMcpServers(descriptors, skip);
|
|
292
286
|
}
|
|
293
287
|
|
|
294
288
|
// SessionStart hook specs wendkeep must author for companions that lack a native
|