wendkeep 0.30.0 → 0.32.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 +109 -0
- package/bin/wendkeep.mjs +4 -1
- package/hooks/brain-inject.mjs +19 -12
- package/hooks/change-context.mjs +61 -0
- package/hooks/change-core.mjs +143 -12
- package/hooks/change-guard.mjs +59 -0
- package/hooks/change-nag.mjs +31 -0
- package/hooks/change-warn.mjs +48 -0
- package/hooks/decision-capture.mjs +41 -2
- package/hooks/harness-doctor.mjs +4 -1
- package/hooks/plan-capture.mjs +168 -0
- package/hooks/session-stop.mjs +11 -1
- package/hooks/spec-core.mjs +73 -4
- package/hooks/subagent-usage.mjs +3 -1
- package/hooks/vault-health.mjs +6 -5
- package/package.json +1 -1
- package/src/change.mjs +48 -14
- package/src/init.mjs +56 -14
- package/src/skills-seed.mjs +56 -13
- package/src/sync-defs.mjs +8 -0
- package/src/taxonomy.mjs +35 -0
package/src/init.mjs
CHANGED
|
@@ -9,9 +9,13 @@ import { createInterface } from 'node:readline/promises';
|
|
|
9
9
|
import {
|
|
10
10
|
VAULT_FOLDERS,
|
|
11
11
|
SESSION_HOOKS,
|
|
12
|
+
CHANGE_NUDGE_HOOKS,
|
|
13
|
+
CHANGE_GATE_HOOKS,
|
|
12
14
|
MCP_SERVER_KEY,
|
|
13
15
|
mcpServerEntry,
|
|
14
16
|
hookCommand,
|
|
17
|
+
hookCommandLocal,
|
|
18
|
+
hookCommandLocalLegacy,
|
|
15
19
|
deriveVaultDirName,
|
|
16
20
|
selectableCompanions,
|
|
17
21
|
resolveCompanions,
|
|
@@ -91,27 +95,62 @@ function backup(path) {
|
|
|
91
95
|
|
|
92
96
|
// --- merges -----------------------------------------------------------------
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
// Comando preferido para um hook: node-direto quando o projeto tem o pacote local (alta
|
|
99
|
+
// frequência sem cold-start de npx — ver R3 do design 0.31.0); senão o npx portátil.
|
|
100
|
+
export function hookCommandFor(name, projectPath) {
|
|
101
|
+
try {
|
|
102
|
+
if (projectPath && existsSync(join(projectPath, 'node_modules', 'wendkeep', 'hooks', `${name}.mjs`))) {
|
|
103
|
+
return hookCommandLocal(name);
|
|
104
|
+
}
|
|
105
|
+
} catch { /* fs indisponível — npx */ }
|
|
106
|
+
return hookCommand(name);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function localHookAvailable(name, projectPath) {
|
|
110
|
+
try {
|
|
111
|
+
return !!projectPath && existsSync(join(projectPath, 'node_modules', 'wendkeep', 'hooks', `${name}.mjs`));
|
|
112
|
+
} catch { return false; }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function localHookArg(name) {
|
|
116
|
+
return `${'${CLAUDE_PROJECT_DIR}'}/node_modules/wendkeep/hooks/${name}.mjs`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function mergeSettings(existing, { vaultPath, withMcp, force, companions = [], skipMcp = [], dotcontextHookLevel = 'full', projectPath = '' }) {
|
|
95
120
|
const s = existing && typeof existing === 'object' ? { ...existing } : {};
|
|
96
121
|
s.hooks = { ...(s.hooks || {}) };
|
|
97
122
|
let added = 0;
|
|
98
|
-
// wendkeep's own session hooks +
|
|
99
|
-
// `order` (default 0) so higher-order companion
|
|
100
|
-
// fold AFTER wendkeep's own within each event. A spec
|
|
101
|
-
// (dotcontext dispatch) instead of a wendkeep hook `name`.
|
|
102
|
-
const allSpecs = [
|
|
103
|
-
|
|
104
|
-
|
|
123
|
+
// wendkeep's own session hooks + the change-lifecycle hooks (0.31.0, default) + any
|
|
124
|
+
// companion-authored hooks. Stable-sort by `order` (default 0) so higher-order companion
|
|
125
|
+
// hooks (e.g. dotcontext's order 100) fold AFTER wendkeep's own within each event. A spec
|
|
126
|
+
// may carry its own `command` (dotcontext dispatch) instead of a wendkeep hook `name`.
|
|
127
|
+
const allSpecs = [
|
|
128
|
+
...SESSION_HOOKS,
|
|
129
|
+
...CHANGE_NUDGE_HOOKS,
|
|
130
|
+
...CHANGE_GATE_HOOKS,
|
|
131
|
+
...companionHookSpecs(companions, { dotcontextHookLevel }),
|
|
132
|
+
].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
105
133
|
for (const h of allSpecs) {
|
|
106
|
-
const
|
|
134
|
+
const useLocal = !h.command && h.preferLocal && localHookAvailable(h.name, projectPath);
|
|
135
|
+
const command = h.command ?? (useLocal ? 'node' : hookCommand(h.name));
|
|
136
|
+
const args = useLocal ? [localHookArg(h.name)] : undefined;
|
|
137
|
+
// Dual-recognition: um hook nomeado é reconhecido tanto na forma npx quanto na node-direta,
|
|
138
|
+
// para que trocar a forma preferida (ou re-initar noutra máquina) nunca duplique o grupo.
|
|
139
|
+
const candidates = h.command ? [h.command] : [hookCommand(h.name), hookCommandLocal(h.name), hookCommandLocalLegacy(h.name)];
|
|
140
|
+
const ownsHook = (x) => candidates.includes(x.command)
|
|
141
|
+
|| (x.command === 'node' && Array.isArray(x.args) && x.args[0] === localHookArg(h.name));
|
|
107
142
|
const groups = Array.isArray(s.hooks[h.event]) ? [...s.hooks[h.event]] : [];
|
|
108
|
-
const owning = groups.find((g) => (g.hooks || []).some(
|
|
143
|
+
const owning = groups.find((g) => (g.hooks || []).some(ownsHook));
|
|
109
144
|
if (owning) {
|
|
110
145
|
// Already wired: never add a duplicate group (the old `if (present && !force)` fell through
|
|
111
146
|
// under --force and appended a second identical group). Under --force, refresh the managed
|
|
112
147
|
// entry's fields in place — without disturbing any sibling hooks the user grouped with it.
|
|
113
|
-
|
|
114
|
-
|
|
148
|
+
const hk = owning.hooks.find(ownsHook);
|
|
149
|
+
const brokenRelative = hk?.command === hookCommandLocalLegacy(h.name);
|
|
150
|
+
if (force || brokenRelative) {
|
|
151
|
+
hk.command = command;
|
|
152
|
+
if (args) hk.args = args;
|
|
153
|
+
else delete hk.args;
|
|
115
154
|
hk.timeout = h.timeout;
|
|
116
155
|
if (h.statusMessage) hk.statusMessage = h.statusMessage;
|
|
117
156
|
if (h.matcher && (owning.hooks || []).length === 1) owning.matcher = h.matcher;
|
|
@@ -120,6 +159,7 @@ export function mergeSettings(existing, { vaultPath, withMcp, force, companions
|
|
|
120
159
|
continue;
|
|
121
160
|
}
|
|
122
161
|
const entry = { type: 'command', command, timeout: h.timeout, statusMessage: h.statusMessage };
|
|
162
|
+
if (args) entry.args = args;
|
|
123
163
|
const group = h.matcher ? { matcher: h.matcher, hooks: [entry] } : { hooks: [entry] };
|
|
124
164
|
groups.push(group);
|
|
125
165
|
s.hooks[h.event] = groups;
|
|
@@ -477,12 +517,12 @@ export async function runInit(argv) {
|
|
|
477
517
|
const settingsRead = readJsonSafe(settingsPath);
|
|
478
518
|
if (!settingsRead.ok) {
|
|
479
519
|
// Unparseable existing file — never clobber. Drop a .new for manual merge.
|
|
480
|
-
const fresh = mergeSettings(null, { vaultPath, withMcp: args.mcp, force: true, companions, skipMcp, dotcontextHookLevel }).settings;
|
|
520
|
+
const fresh = mergeSettings(null, { vaultPath, withMcp: args.mcp, force: true, companions, skipMcp, dotcontextHookLevel, projectPath }).settings;
|
|
481
521
|
writeJson(`${settingsPath}.new`, fresh);
|
|
482
522
|
log(M.settingsBadJson(settingsPath));
|
|
483
523
|
} else {
|
|
484
524
|
const hadFile = settingsRead.data !== null;
|
|
485
|
-
const { settings, added } = mergeSettings(settingsRead.data, { vaultPath, withMcp: args.mcp, force: args.force, companions, skipMcp, dotcontextHookLevel });
|
|
525
|
+
const { settings, added } = mergeSettings(settingsRead.data, { vaultPath, withMcp: args.mcp, force: args.force, companions, skipMcp, dotcontextHookLevel, projectPath });
|
|
486
526
|
if (hadFile) backup(settingsPath);
|
|
487
527
|
writeJson(settingsPath, settings);
|
|
488
528
|
log(M.settings(hadFile ? M.merged : M.created, added, hadFile ? M.bakSaved : ''));
|
|
@@ -509,6 +549,8 @@ export async function runInit(argv) {
|
|
|
509
549
|
log(M.mcpSkipped);
|
|
510
550
|
}
|
|
511
551
|
|
|
552
|
+
log(' [!] ignore runtime do wendkeep no Git quando o vault for versionado: .brain/.change-*');
|
|
553
|
+
|
|
512
554
|
// 4. Vault color system (.obsidian) -----------------------------------------
|
|
513
555
|
if (args.noColors) {
|
|
514
556
|
log(M.colorsSkipped);
|
package/src/skills-seed.mjs
CHANGED
|
@@ -16,6 +16,12 @@ const WORKFLOW = `# Loop a2 — o ciclo de trabalho do wendkeep
|
|
|
16
16
|
Use ao começar qualquer mudança não-trivial. O loop mantém memória (vault) e prova
|
|
17
17
|
(sensores) juntas, tudo linkado no grafo do Obsidian.
|
|
18
18
|
|
|
19
|
+
<HARD-GATE>
|
|
20
|
+
NÃO edite arquivos de código antes do passo 2 (Propose / \`wendkeep change new\`).
|
|
21
|
+
Toda tarefa não-trivial passa pelo loop — planejar no chat e sair editando deixa o
|
|
22
|
+
vault cego. Exceção única: mudança trivial (typo, 1 linha).
|
|
23
|
+
</HARD-GATE>
|
|
24
|
+
|
|
19
25
|
## Os passos
|
|
20
26
|
|
|
21
27
|
1. **Explore** — entenda o problema antes de propor. Leia o código/contexto relevante.
|
|
@@ -25,6 +31,11 @@ Use ao começar qualquer mudança não-trivial. O loop mantém memória (vault)
|
|
|
25
31
|
- \`tarefas.md\` — a lista de tarefas \`- [ ] N.N descrição\`.
|
|
26
32
|
A mudança vira a *ativa* (ponteiro \`.brain/CURRENT_CHANGE.md\`) e é injetada no
|
|
27
33
|
próximo SessionStart, então você retoma o trabalho em curso automaticamente.
|
|
34
|
+
Antes de implementar, resolva \`spec_impact\` na proposta:
|
|
35
|
+
- \`required\`: liste a capability em \`specs:\` e preencha
|
|
36
|
+
\`specs/<capability>/spec.md\` com ADDED/MODIFIED/REMOVED; ligue tarefas com \`[req:ID]\`.
|
|
37
|
+
- \`none\`: registre uma justificativa real em \`spec_impact_reason\`.
|
|
38
|
+
\`pending\` nunca é estado pronto para implementação ou archive.
|
|
28
39
|
3. **Apply** — implemente cada tarefa de \`tarefas.md\` com disciplina **wk-tdd**
|
|
29
40
|
(teste vermelho antes do código). Marque \`- [x]\` ao concluir. Declare nas tarefas:
|
|
30
41
|
- \`[sensor:<id>]\` — a prova automatizada (roda no verify).
|
|
@@ -133,6 +144,7 @@ qualquer código.
|
|
|
133
144
|
Antes de fechar o design, resolva cada zona cinza: decide com o usuário, ou registra como
|
|
134
145
|
**assumption assinada** ("assumo X porque Y — corrija se errado"). Cinza declinado pelo
|
|
135
146
|
usuário é *registrado*, não descartado no silêncio. Nada sai do design silenciosamente ambíguo.
|
|
147
|
+
Declare também a capability e se o design tem \`spec_impact: required\` ou \`none\`.
|
|
136
148
|
|
|
137
149
|
## Tabela out-of-scope
|
|
138
150
|
|
|
@@ -164,6 +176,10 @@ Mapeie os arquivos: quais criar/modificar e a responsabilidade de cada um. Arqui
|
|
|
164
176
|
mudam juntos ficam juntos; um arquivo, uma responsabilidade. É aqui que a decomposição
|
|
165
177
|
trava.
|
|
166
178
|
|
|
179
|
+
Resolva o contrato antes de decompor: \`spec_impact: required\` exige capability + delta real em
|
|
180
|
+
\`specs/<capability>/spec.md\`; \`spec_impact: none\` exige justificativa. Cada comportamento do
|
|
181
|
+
delta recebe ID e as tarefas correspondentes usam \`[req:ID]\`.
|
|
182
|
+
|
|
167
183
|
## Tarefas bite-sized (TDD)
|
|
168
184
|
|
|
169
185
|
Cada tarefa termina num entregável testável de forma independente. Cada passo é uma ação
|
|
@@ -222,12 +238,21 @@ const WORKFLOW_EN = `# The a2 loop — wendkeep's work cycle
|
|
|
222
238
|
Use it when starting any non-trivial change. The loop keeps memory (vault) and proof
|
|
223
239
|
(sensors) together, wikilinked in the Obsidian graph.
|
|
224
240
|
|
|
241
|
+
<HARD-GATE>
|
|
242
|
+
Do NOT edit code files before step 2 (Propose / \`wendkeep change new\`).
|
|
243
|
+
Every non-trivial task goes through the loop — planning in chat and editing right away
|
|
244
|
+
leaves the vault blind. Single exception: a trivial change (typo, one line).
|
|
245
|
+
</HARD-GATE>
|
|
246
|
+
|
|
225
247
|
## Steps
|
|
226
248
|
|
|
227
249
|
1. **Explore** — understand the problem before proposing.
|
|
228
250
|
2. **Propose** — \`wendkeep change new <slug>\` scaffolds \`08-Changes/<slug>/\`
|
|
229
251
|
(proposta/design/tarefas + a \`specs/\` delta). The change becomes *active* and is
|
|
230
252
|
injected at the next SessionStart.
|
|
253
|
+
Before implementation, resolve \`spec_impact\`: \`required\` needs the capability listed in
|
|
254
|
+
\`specs:\` plus a real \`specs/<capability>/spec.md\` delta and \`[req:ID]\` links; \`none\`
|
|
255
|
+
needs a real \`spec_impact_reason\`. \`pending\` is never ready for implementation/archive.
|
|
231
256
|
3. **Apply** — implement each task in tarefas.md with **wk-tdd** (red test first). Tag tasks:
|
|
232
257
|
\`[sensor:<id>]\` (automated proof) and \`[req:<ID>]\` (the spec requirement it satisfies).
|
|
233
258
|
4. **Verify** — \`wendkeep verify\` runs the sensors; then \`wendkeep verify --deep\` builds
|
|
@@ -298,6 +323,7 @@ Use it when the idea is still vague. Turn it into an approved design BEFORE writ
|
|
|
298
323
|
## Closure gate — no dangling ambiguity
|
|
299
324
|
Resolve every gray area: decide with the user, or log a **signed-off assumption** ("assuming X
|
|
300
325
|
because Y — correct me if wrong"). A declined gray area is recorded, not silently dropped.
|
|
326
|
+
Also declare the capability and whether the design has \`spec_impact: required\` or \`none\`.
|
|
301
327
|
|
|
302
328
|
## Out-of-scope table
|
|
303
329
|
List explicitly what the change does **not** do. Undeclared scope becomes creep.
|
|
@@ -319,6 +345,10 @@ Use it after an approved design. Produce a plan an engineer with no project cont
|
|
|
319
345
|
Map the files: what to create/modify and each one's responsibility. Files that change together
|
|
320
346
|
live together; one file, one responsibility.
|
|
321
347
|
|
|
348
|
+
Resolve the contract first: \`spec_impact: required\` needs a capability and a real delta at
|
|
349
|
+
\`specs/<capability>/spec.md\`; \`spec_impact: none\` needs a reason. Give each behaviour an ID
|
|
350
|
+
and link the corresponding tasks with \`[req:ID]\`.
|
|
351
|
+
|
|
322
352
|
## Bite-sized tasks (TDD)
|
|
323
353
|
Each task ends in an independently testable deliverable. Each step is a 2–5 min action:
|
|
324
354
|
write the failing test (show code) → run and see it fail (exact command + expected) → minimal
|
|
@@ -422,6 +452,10 @@ comes from the package (freshness seal; edit a task later and the gate rejects i
|
|
|
422
452
|
|
|
423
453
|
const PLAN_TEMPLATE_PT = `# Template — plano de tarefas (TDD, bite-sized)
|
|
424
454
|
|
|
455
|
+
## Impacto em specs
|
|
456
|
+
- \`spec_impact\`: \`required\` | \`none\`
|
|
457
|
+
- Capability/delta: \`specs/<capability>/spec.md\` ou justificativa de \`none\`
|
|
458
|
+
|
|
425
459
|
## Arquivos
|
|
426
460
|
- Criar: \`caminho/exato.mjs\`
|
|
427
461
|
- Modificar: \`caminho/existente.mjs:120-140\`
|
|
@@ -445,6 +479,10 @@ tarefas (uma função é \`x()\` em toda parte). Cada tarefa termina num entreg
|
|
|
445
479
|
|
|
446
480
|
const PLAN_TEMPLATE_EN = `# Template — task plan (TDD, bite-sized)
|
|
447
481
|
|
|
482
|
+
## Spec impact
|
|
483
|
+
- \`spec_impact\`: \`required\` | \`none\`
|
|
484
|
+
- Capability/delta: \`specs/<capability>/spec.md\` or the \`none\` rationale
|
|
485
|
+
|
|
448
486
|
## Files
|
|
449
487
|
- Create: \`exact/path.mjs\`
|
|
450
488
|
- Modify: \`exact/existing.mjs:120-140\`
|
|
@@ -518,21 +556,24 @@ size of the problem>
|
|
|
518
556
|
<verifiable criteria: for each requirement, the test that fails → passes>
|
|
519
557
|
`;
|
|
520
558
|
|
|
559
|
+
// As descriptions são o gatilho de ativação: o harness casa a description com o PEDIDO do
|
|
560
|
+
// usuário ("implementa X", "corrige Y"), não com abstrações ("mudança não-trivial"). Gatilhos
|
|
561
|
+
// concretos + instrução imperativa = a skill dispara sozinha (paridade Superpowers).
|
|
521
562
|
const WK_SKILLS_PT = [
|
|
522
|
-
skill('wk-workflow', 'Use
|
|
563
|
+
skill('wk-workflow', 'Use SEMPRE que o usuário pedir para implementar, criar, corrigir, refatorar, adicionar ou alterar código — qualquer tarefa de código não-trivial. Invoque ANTES de editar qualquer arquivo: orquestra o loop a2 (wendkeep change new → tarefas → verify → archive) e registra tudo no vault.', WORKFLOW),
|
|
523
564
|
skill('wk-tdd', 'Use ao implementar qualquer comportamento — Red/Green/Refactor com testes que discriminam (derivados do spec, litmus não-raso, adequação).', TDD),
|
|
524
|
-
skill('wk-debugging', 'Use quando algo falha ou
|
|
525
|
-
skill('wk-brainstorming', 'Use quando a ideia ainda é vaga — vira design aprovado, com closure gate e tabela out-of-scope, antes de código.', BRAINSTORMING, [{ name: 'design-template.md', content: DESIGN_TEMPLATE_PT }]),
|
|
526
|
-
skill('wk-planning', 'Use após um design aprovado — decompõe em plano de tarefas TDD bite-sized.', PLANNING, [{ name: 'plan-template.md', content: PLAN_TEMPLATE_PT }]),
|
|
565
|
+
skill('wk-debugging', 'Use quando algo falha, quebra, dá erro ou regride — depuração sistemática por hipótese antes de corrigir.', DEBUGGING),
|
|
566
|
+
skill('wk-brainstorming', 'Use quando a ideia ainda é vaga ou o usuário quer discutir/planejar uma feature (inclusive em plan mode) — vira design aprovado, com closure gate e tabela out-of-scope, antes de código.', BRAINSTORMING, [{ name: 'design-template.md', content: DESIGN_TEMPLATE_PT }]),
|
|
567
|
+
skill('wk-planning', 'Use após um design aprovado ou um plano aceito (inclusive plan mode) — decompõe em plano de tarefas TDD bite-sized e registra na change ativa.', PLANNING, [{ name: 'plan-template.md', content: PLAN_TEMPLATE_PT }]),
|
|
527
568
|
skill('wk-verify', 'Use no verify deep — passe independente read-only (autor≠verificador) que re-deriva a cobertura do spec e grava verdict.json.', VERIFY, [{ name: 'spec-reviewer-prompt.md', content: REVIEWER_PROMPT_PT }, { name: 'verdict-template.json', content: VERDICT_TEMPLATE }]),
|
|
528
569
|
];
|
|
529
570
|
|
|
530
571
|
const WK_SKILLS_EN = [
|
|
531
|
-
skill('wk-workflow', 'Use
|
|
572
|
+
skill('wk-workflow', 'Use WHENEVER the user asks to implement, create, fix, refactor, add or change code — any non-trivial coding task. Invoke BEFORE editing any file: it orchestrates the a2 loop (wendkeep change new → tasks → verify → archive) and records everything in the vault.', WORKFLOW_EN),
|
|
532
573
|
skill('wk-tdd', 'Use when implementing any behaviour — Red/Green/Refactor with tests that discriminate (spec-derived, non-shallow litmus, adequacy).', TDD_EN),
|
|
533
|
-
skill('wk-debugging', 'Use when something fails or
|
|
534
|
-
skill('wk-brainstorming', 'Use when the idea is still vague — turns it into an approved design, with a closure gate and out-of-scope table, before code.', BRAINSTORMING_EN, [{ name: 'design-template.md', content: DESIGN_TEMPLATE_EN }]),
|
|
535
|
-
skill('wk-planning', 'Use after an approved design — decomposes it into a bite-sized TDD task plan.', PLANNING_EN, [{ name: 'plan-template.md', content: PLAN_TEMPLATE_EN }]),
|
|
574
|
+
skill('wk-debugging', 'Use when something fails, breaks, errors or regresses — systematic hypothesis-driven debugging before fixing.', DEBUGGING_EN),
|
|
575
|
+
skill('wk-brainstorming', 'Use when the idea is still vague or the user wants to discuss/plan a feature (plan mode included) — turns it into an approved design, with a closure gate and out-of-scope table, before code.', BRAINSTORMING_EN, [{ name: 'design-template.md', content: DESIGN_TEMPLATE_EN }]),
|
|
576
|
+
skill('wk-planning', 'Use after an approved design or an accepted plan (plan mode included) — decomposes it into a bite-sized TDD task plan recorded in the active change.', PLANNING_EN, [{ name: 'plan-template.md', content: PLAN_TEMPLATE_EN }]),
|
|
536
577
|
skill('wk-verify', 'Use in verify deep — an independent read-only pass (author≠verifier) that re-derives spec coverage and writes verdict.json.', VERIFY_EN, [{ name: 'spec-reviewer-prompt.md', content: REVIEWER_PROMPT_EN }, { name: 'verdict-template.json', content: VERDICT_TEMPLATE }]),
|
|
537
578
|
];
|
|
538
579
|
|
|
@@ -545,20 +586,22 @@ export const WK_SKILLS = WK_SKILLS_PT;
|
|
|
545
586
|
// Seed each skill into <brainDir>/skills/<name>/ if absent (non-destructive): SKILL.md plus any
|
|
546
587
|
// bundled template/prompt files. Existing files are never overwritten, so re-seeding an older
|
|
547
588
|
// install just fills in the new template files alongside its SKILL.md.
|
|
548
|
-
|
|
589
|
+
// { refresh: true } (sync-defs --reseed) SOBRESCREVE as wk-* com os seeds atuais — é como um
|
|
590
|
+
// vault existente recebe descriptions/HARD-GATE novos (edições manuais nas wk-* são perdidas).
|
|
591
|
+
export function seedWkSkills(brainDir, localeId = 'pt-BR', { refresh = false } = {}) {
|
|
549
592
|
const created = [];
|
|
550
593
|
for (const s of wkSkills(localeId)) {
|
|
551
594
|
const dir = join(brainDir, 'skills', s.name);
|
|
552
595
|
mkdirSync(dir, { recursive: true });
|
|
553
|
-
const
|
|
596
|
+
const write = (name, content) => {
|
|
554
597
|
const f = join(dir, name);
|
|
555
|
-
if (!existsSync(f)) {
|
|
598
|
+
if (refresh || !existsSync(f)) {
|
|
556
599
|
writeFileSync(f, content, 'utf8');
|
|
557
600
|
created.push(f);
|
|
558
601
|
}
|
|
559
602
|
};
|
|
560
|
-
|
|
561
|
-
for (const file of s.files || [])
|
|
603
|
+
write('SKILL.md', s.body);
|
|
604
|
+
for (const file of s.files || []) write(file.name, file.content);
|
|
562
605
|
}
|
|
563
606
|
return created;
|
|
564
607
|
}
|
package/src/sync-defs.mjs
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
// cross-platform robustness.
|
|
8
8
|
import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
|
|
9
9
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
10
|
+
import { seedWkSkills } from './skills-seed.mjs';
|
|
11
|
+
import { getLocale } from '../hooks/locale.mjs';
|
|
10
12
|
|
|
11
13
|
// Managed AGENTS.md section (0.8.0): the agent-agnostic distribution channel. Codex, Amp,
|
|
12
14
|
// Cursor, Zed et al. read AGENTS.md — one file covers them all. Only the content between
|
|
@@ -114,6 +116,12 @@ export function runSyncDefs(argv) {
|
|
|
114
116
|
}
|
|
115
117
|
const vaultBase = isAbsolute(base) ? base : resolve(process.cwd(), base);
|
|
116
118
|
const projectPath = resolve(project || process.cwd());
|
|
119
|
+
// --reseed (0.31.0): sobrescreve as wk-* de .brain/skills com os seeds da versão instalada
|
|
120
|
+
// ANTES de copiar — é como um vault existente recebe descriptions/HARD-GATE novos.
|
|
121
|
+
if (argv.includes('--reseed')) {
|
|
122
|
+
const n = seedWkSkills(join(vaultBase, '.brain'), getLocale(vaultBase).id, { refresh: true });
|
|
123
|
+
process.stdout.write(`wendkeep sync-defs: ${n.length} arquivo(s) de skill re-semeados em .brain/skills\n`);
|
|
124
|
+
}
|
|
117
125
|
const r = syncDefs(vaultBase, projectPath);
|
|
118
126
|
process.stdout.write(
|
|
119
127
|
`wendkeep sync-defs: ${r.agents.length} agent(s) -> .codex/agents, ${r.skills.length} skill(s) -> .claude/skills\n`,
|
package/src/taxonomy.mjs
CHANGED
|
@@ -50,6 +50,11 @@ export const HOOK_FILES = [
|
|
|
50
50
|
'task-log.mjs',
|
|
51
51
|
'vault-health.mjs',
|
|
52
52
|
'understand-inject.mjs',
|
|
53
|
+
'change-context.mjs',
|
|
54
|
+
'change-warn.mjs',
|
|
55
|
+
'change-guard.mjs',
|
|
56
|
+
'change-nag.mjs',
|
|
57
|
+
'plan-capture.mjs',
|
|
53
58
|
];
|
|
54
59
|
|
|
55
60
|
// Hook scripts that are safe to invoke directly via `wendkeep hook <name>`.
|
|
@@ -68,6 +73,11 @@ export const RUNNABLE_HOOKS = [
|
|
|
68
73
|
'brain-reindex',
|
|
69
74
|
'vault-health',
|
|
70
75
|
'understand-inject',
|
|
76
|
+
'change-context',
|
|
77
|
+
'change-warn',
|
|
78
|
+
'change-guard',
|
|
79
|
+
'change-nag',
|
|
80
|
+
'plan-capture',
|
|
71
81
|
];
|
|
72
82
|
|
|
73
83
|
// The MCP server entry wendkeep wires into .mcp.json so the agent can read/write the
|
|
@@ -107,6 +117,31 @@ export function hookCommand(name) {
|
|
|
107
117
|
return `npx wendkeep hook ${name}`;
|
|
108
118
|
}
|
|
109
119
|
|
|
120
|
+
// Forma node-direta do comando de hook: 1 processo (~100-250ms) em vez dos 3 do npx (cold-start
|
|
121
|
+
// de segundos no Windows). Usada pelos hooks de ALTA FREQUÊNCIA (por prompt / por tool-call)
|
|
122
|
+
// quando o projeto tem wendkeep instalado localmente; o init decide (hookCommandFor).
|
|
123
|
+
export function hookCommandLocal(name) {
|
|
124
|
+
return `node "${'${CLAUDE_PROJECT_DIR}'}/node_modules/wendkeep/hooks/${name}.mjs"`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function hookCommandLocalLegacy(name) {
|
|
128
|
+
return `node node_modules/wendkeep/hooks/${name}.mjs`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Hooks do lifecycle de change (0.31.0) — enforcement do loop a2. Nudges (contexto/aviso/
|
|
132
|
+
// cobrança/captura de plano) e gate (deny/ask no Bash). Separados em dois grupos para
|
|
133
|
+
// preservar a opção futura de gates opt-in; hoje o init wira TODOS por default.
|
|
134
|
+
// preferLocal: alta frequência → invocação node-direta quando houver instalação local.
|
|
135
|
+
export const CHANGE_NUDGE_HOOKS = [
|
|
136
|
+
{ event: 'UserPromptSubmit', matcher: null, name: 'change-context', timeout: 15, order: 10, preferLocal: true, statusMessage: 'wendkeep: change ping' },
|
|
137
|
+
{ event: 'PostToolUse', matcher: 'Edit|Write|MultiEdit', name: 'change-warn', timeout: 10, order: 10, preferLocal: true, statusMessage: 'wendkeep: change warn' },
|
|
138
|
+
{ event: 'PostToolUse', matcher: 'ExitPlanMode', name: 'plan-capture', timeout: 15, order: 10, preferLocal: true, statusMessage: 'wendkeep: capturing approved plan' },
|
|
139
|
+
{ event: 'Stop', matcher: null, name: 'change-nag', timeout: 15, order: 10, preferLocal: true, statusMessage: 'wendkeep: open tasks check' },
|
|
140
|
+
];
|
|
141
|
+
export const CHANGE_GATE_HOOKS = [
|
|
142
|
+
{ event: 'PreToolUse', matcher: 'Bash', name: 'change-guard', timeout: 10, order: 10, preferLocal: true, statusMessage: 'wendkeep: change gate' },
|
|
143
|
+
];
|
|
144
|
+
|
|
110
145
|
// --- companion plugins / MCP --------------------------------------------------
|
|
111
146
|
// Optional tools wendkeep init can pin alongside the vault. Each is wired through
|
|
112
147
|
// the MOST agent-agnostic mechanism it supports; the Claude Code plugin entry
|