wendkeep 0.66.5 → 0.67.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.
@@ -18,3 +18,126 @@ export function redactSecrets(text) {
18
18
  .replace(/\b([A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|API_KEY)[A-Z0-9_]*)\s*[:=]\s*["']?[^"'\s]+/gi, '$1=[REDACTED_SECRET]')
19
19
  .replace(/:\/\/([^:\s/@]+):([^@\s/]+)@/g, '://[REDACTED_SECRET]@');
20
20
  }
21
+
22
+ function metadataPayloadLine(name, line) {
23
+ if (name === 'citation_entries') {
24
+ return /\|note=\[[^\]]*\]\s*$/i.test(line)
25
+ || /^[^\s<>]+:\d+(?:-\d+)?(?:\|[^\s].*)?$/i.test(line);
26
+ }
27
+ if (name === 'rollout_ids') {
28
+ return /^(?:[0-9a-f]{8,}(?:-[0-9a-f-]+)*|019f-[A-Za-z0-9_-]+)$/i.test(line);
29
+ }
30
+ return false;
31
+ }
32
+
33
+ function consumeTruncatedMetadata(source, name, tagEnd) {
34
+ let cursor = tagEnd;
35
+ let mode = name;
36
+ let openingLine = true;
37
+
38
+ while (cursor < source.length) {
39
+ const newline = source.indexOf('\n', cursor);
40
+ const lineEnd = newline === -1 ? source.length : newline;
41
+ const line = source.slice(cursor, lineEnd).replace(/\r$/, '');
42
+ const clean = line.trim();
43
+
44
+ if (!clean) {
45
+ if (!openingLine) return cursor;
46
+ } else if (/^<\/?(?:oai-mem-citation|citation_entries|rollout_ids)\b/i.test(clean)) {
47
+ const nested = [...clean.matchAll(/<(citation_entries|rollout_ids)\b[^>]*>/gi)].at(-1);
48
+ if (nested) mode = nested[1].toLowerCase();
49
+ } else if (!(openingLine && name !== 'oai-mem-citation') && !metadataPayloadLine(mode, clean)) {
50
+ return cursor;
51
+ }
52
+
53
+ if (newline === -1) return source.length;
54
+ cursor = newline + 1;
55
+ openingLine = false;
56
+ }
57
+ return cursor;
58
+ }
59
+
60
+ function openingPayloadLooksStructural(source, opening) {
61
+ const name = opening[1].toLowerCase();
62
+ const tagEnd = opening.index + opening[0].length;
63
+ const rest = source.slice(tagEnd);
64
+ if (new RegExp(`<\/${name}\\s*>`, 'i').test(rest)) return true;
65
+
66
+ if (name === 'oai-mem-citation') {
67
+ const child = /^[\t\r\n ]*<(citation_entries|rollout_ids)\b[^>]*>/i.exec(rest);
68
+ if (!child) return !rest.trim();
69
+ const childRest = rest.slice(child[0].length);
70
+ const lineEnd = childRest.search(/\r?\n/u);
71
+ const sameLine = childRest.slice(0, lineEnd === -1 ? childRest.length : lineEnd);
72
+ if (!sameLine.trim()) return true;
73
+ if (!/^[\t ]/u.test(childRest)) return true;
74
+ if (/^<\/?(?:oai-mem-citation|citation_entries|rollout_ids)\b/i.test(sameLine.trim())) return true;
75
+ return metadataPayloadLine(child[1].toLowerCase(), sameLine.trim());
76
+ }
77
+
78
+ const lineEnd = rest.search(/\r?\n/u);
79
+ const sameLine = rest.slice(0, lineEnd === -1 ? rest.length : lineEnd);
80
+ if (!sameLine.trim()) return true;
81
+ if (!/^[\t ]/u.test(rest)) return true;
82
+ if (/^<\/?(?:oai-mem-citation|citation_entries|rollout_ids)\b/i.test(sameLine.trim())) return true;
83
+ return metadataPayloadLine(name, sameLine.trim());
84
+ }
85
+
86
+ function metadataStart(source, opening) {
87
+ const tagStart = opening.index;
88
+ const before = source.slice(0, tagStart);
89
+ const adjacentSession = /<\/session>[\t\r\n ]*$/i.exec(before);
90
+ if (adjacentSession) return adjacentSession.index;
91
+
92
+ if (!openingPayloadLooksStructural(source, opening)) return -1;
93
+
94
+ const lineStart = before.lastIndexOf('\n') + 1;
95
+ if (!before.slice(lineStart).trim()) return tagStart;
96
+
97
+ const name = opening[1].toLowerCase();
98
+ if (name === 'oai-mem-citation'
99
+ && tagStart > 0
100
+ && !/\s/u.test(source[tagStart - 1])) {
101
+ return tagStart;
102
+ }
103
+ return -1;
104
+ }
105
+
106
+ function findAssistantMetadataRemoval(source) {
107
+ const openings = source.matchAll(/<(oai-mem-citation|citation_entries|rollout_ids)\b[^>]*>/gi);
108
+ for (const opening of openings) {
109
+ const start = metadataStart(source, opening);
110
+ if (start < 0) continue;
111
+
112
+ const name = opening[1].toLowerCase();
113
+ const tagEnd = opening.index + opening[0].length;
114
+ const closing = new RegExp(`<\/${name}\\s*>`, 'i').exec(source.slice(tagEnd));
115
+ const end = closing
116
+ ? tagEnd + closing.index + closing[0].length
117
+ : consumeTruncatedMetadata(source, name, tagEnd);
118
+ return { start, end };
119
+ }
120
+ return null;
121
+ }
122
+
123
+ function removeAssistantMetadata(source, removal) {
124
+ const before = source.slice(0, removal.start).trimEnd();
125
+ const after = source.slice(removal.end).trimStart();
126
+ if (!before) return after;
127
+ if (!after) return before;
128
+ return `${before}\n${after}`;
129
+ }
130
+
131
+ export function sanitizeAssistantMessage(text) {
132
+ let source = String(text || '');
133
+ if (!source) return '';
134
+
135
+ while (source) {
136
+ const removal = findAssistantMetadataRemoval(source);
137
+ if (!removal) break;
138
+ const next = removeAssistantMetadata(source, removal);
139
+ if (next.length >= source.length) break;
140
+ source = next;
141
+ }
142
+ return source;
143
+ }
@@ -1,4 +1,8 @@
1
- import { isBootstrapPrompt, redactSecrets } from './prompt-content.mjs';
1
+ import {
2
+ isBootstrapPrompt,
3
+ redactSecrets,
4
+ sanitizeAssistantMessage,
5
+ } from './prompt-content.mjs';
2
6
  import {
3
7
  addUsage,
4
8
  emptyTokenUsage,
@@ -73,6 +77,14 @@ function addConversation(turn, role, value) {
73
77
  }
74
78
  }
75
79
 
80
+ function addAssistantMessage(result, turn, value) {
81
+ const text = sanitizeAssistantMessage(value);
82
+ if (!text) return;
83
+ addUnique(result.assistantMessages, text);
84
+ addUnique(turn.assistantMessages, text);
85
+ addConversation(turn, 'Assistente', text);
86
+ }
87
+
76
88
  function normalizeRoot(value) {
77
89
  return String(value || '').replace(/\\+/g, '/').replace(/\/+$/, '');
78
90
  }
@@ -207,9 +219,7 @@ export function parseCodexTranscriptContent(content, options = {}) {
207
219
  const text = event.payload.message || event.payload.text || '';
208
220
  if (text) {
209
221
  const turn = ensureTurn(event.payload.turn_id || result.latestTurnId, event.timestamp);
210
- addUnique(result.assistantMessages, text);
211
- addUnique(turn.assistantMessages, text);
212
- addConversation(turn, 'Assistente', text);
222
+ addAssistantMessage(result, turn, text);
213
223
  }
214
224
  continue;
215
225
  }
@@ -234,9 +244,7 @@ export function parseCodexTranscriptContent(content, options = {}) {
234
244
  addConversation(turn, 'Usuário', text);
235
245
  }
236
246
  if (payload.role === 'assistant') {
237
- addUnique(result.assistantMessages, text);
238
- addUnique(turn.assistantMessages, text);
239
- addConversation(turn, 'Assistente', text);
247
+ addAssistantMessage(result, turn, text);
240
248
  }
241
249
  continue;
242
250
  }
@@ -356,9 +364,7 @@ export function parseClaudeTranscriptContent(content, options = {}) {
356
364
  const blocks = Array.isArray(event.message?.content) ? event.message.content : [];
357
365
  for (const block of blocks) {
358
366
  if (block?.type === 'text' && block.text && block.text.trim()) {
359
- addUnique(result.assistantMessages, block.text);
360
- addUnique(turn.assistantMessages, block.text);
361
- addConversation(turn, 'Assistente', block.text);
367
+ addAssistantMessage(result, turn, block.text);
362
368
  } else if (block?.type === 'tool_use') {
363
369
  const name = block.name || 'tool_use';
364
370
  addUnique(result.tools, name);
package/src/profile.mjs CHANGED
@@ -3,10 +3,12 @@
3
3
  import { mutateSessionRegistry, readSessionRegistry } from '../hooks/obsidian-common.mjs';
4
4
  import {
5
5
  DEFAULT_OPERATING_PROFILE,
6
+ evaluateTaskOperatingProfileLease,
6
7
  normalizeOperatingProfile,
7
8
  resolveOperatingProfile,
8
9
  setOperatingProfile,
9
10
  } from './operating-profile.mjs';
11
+ import { setSessionTaskOperatingProfile } from '../hooks/operating-profile-task-store.mjs';
10
12
  import { resolve } from 'node:path';
11
13
  import { findProjectBinding, resolveProjectVault, updateProjectBinding } from './project-vault.mjs';
12
14
 
@@ -14,12 +16,14 @@ export const PROFILE_HELP = `wendkeep profile <subcommand>
14
16
 
15
17
  status [--session <id>]
16
18
  use <OFF|FLOW|GUIDE|GOVERN|ASSURE> [--session <id>]
19
+ route <FLOW|GUIDE|GOVERN|ASSURE> --session <id> --reason <text>
17
20
 
18
- Common options: --project <path> --vault <path> --session <id> --json
21
+ Common options: --project <path> --vault <path> --session <id> --json --reason <text>
22
+ route creates a task-scoped choice for the current request; it never selects OFF.
19
23
  The Keep Core (Vault, session, and memory) remains active under every profile.
20
24
  `;
21
25
 
22
- const VALUE_OPTIONS = new Set(['--project', '--vault', '--session']);
26
+ const VALUE_OPTIONS = new Set(['--project', '--vault', '--session', '--reason']);
23
27
  const FLAG_OPTIONS = new Set(['--json']);
24
28
 
25
29
  function optionValue(argv, name) {
@@ -32,8 +36,8 @@ function commandArgs(argv) {
32
36
  const values = [];
33
37
  for (let index = 0; index < argv.length; index += 1) {
34
38
  const value = argv[index];
35
- if (['--project', '--vault', '--session'].includes(value)) { index += 1; continue; }
36
- if (value.startsWith('--project=') || value.startsWith('--vault=') || value.startsWith('--session=')) continue;
39
+ if (['--project', '--vault', '--session', '--reason'].includes(value)) { index += 1; continue; }
40
+ if (value.startsWith('--project=') || value.startsWith('--vault=') || value.startsWith('--session=') || value.startsWith('--reason=')) continue;
37
41
  if (value === '--json') continue;
38
42
  values.push(value);
39
43
  }
@@ -76,8 +80,15 @@ function canonicalPath(value) {
76
80
  function output(payload, json) {
77
81
  if (json) process.stdout.write(`${JSON.stringify(payload)}\n`);
78
82
  else {
79
- const scope = payload.scope === 'session' ? `session ${payload.session_id}` : 'project';
80
- process.stdout.write(`${payload.profile} (${scope}; ${payload.source})\n`);
83
+ const scope = payload.scope === 'task'
84
+ ? `task ${payload.session_id}`
85
+ : payload.scope === 'session' ? `session ${payload.session_id}` : 'project';
86
+ const details = [scope, payload.source];
87
+ if (payload.session_id && payload.base_profile && payload.task_lease?.state) {
88
+ details.push(`base=${payload.base_profile}/${payload.base_source}`);
89
+ details.push(`lease=${payload.task_lease.state}`);
90
+ }
91
+ process.stdout.write(`${payload.profile} (${details.join('; ')})\n`);
81
92
  }
82
93
  if (payload.binding_error) {
83
94
  const code = payload.binding_error.code || 'WENDKEEP_VAULT_CONFIG_INVALID';
@@ -129,10 +140,7 @@ export function setSessionOperatingProfile(vaultBase, sessionId, profile, { now
129
140
  });
130
141
  }
131
142
 
132
- function sessionProfile(vaultBase, sessionId, projectResolved) {
133
- const sessions = readSessionRegistry(vaultBase).sessions || {};
134
- if (!Object.hasOwn(sessions, sessionId)) throw new Error(`sessão não encontrada: ${sessionId}`);
135
- const entry = sessions[sessionId];
143
+ function sessionBaseProfile(entry, projectResolved) {
136
144
  if (Object.hasOwn(entry, 'operating_profile')) {
137
145
  try {
138
146
  return {
@@ -149,6 +157,44 @@ function sessionProfile(vaultBase, sessionId, projectResolved) {
149
157
  return { profile: projectResolved.profile, source: projectResolved.source };
150
158
  }
151
159
 
160
+ function sessionProfile(vaultBase, sessionId, projectResolved) {
161
+ const sessions = readSessionRegistry(vaultBase).sessions || {};
162
+ if (!Object.hasOwn(sessions, sessionId)) throw new Error(`sessão não encontrada: ${sessionId}`);
163
+ return sessionBaseProfile(sessions[sessionId], projectResolved);
164
+ }
165
+
166
+ function sessionProfileStatus(vaultBase, sessionId, projectResolved) {
167
+ const sessions = readSessionRegistry(vaultBase).sessions || {};
168
+ if (!Object.hasOwn(sessions, sessionId)) throw new Error(`sessão não encontrada: ${sessionId}`);
169
+ const entry = sessions[sessionId];
170
+ const base = sessionBaseProfile(entry, projectResolved);
171
+ const taskLease = evaluateTaskOperatingProfileLease(entry.operating_profile_task, {
172
+ sessionId,
173
+ turnId: entry.last_prompt_turn_id || '',
174
+ turnSequence: entry.last_turn_sequence,
175
+ });
176
+ return {
177
+ profile: taskLease.state === 'active' ? taskLease.profile : base.profile,
178
+ source: taskLease.state === 'active' ? 'task-lease' : base.source,
179
+ scope: taskLease.state === 'active' ? 'task' : 'session',
180
+ baseProfile: base.profile,
181
+ baseSource: base.source,
182
+ taskLease,
183
+ };
184
+ }
185
+
186
+ function sessionOutputPayload(effective, sessionId) {
187
+ return {
188
+ profile: effective.profile,
189
+ source: effective.source,
190
+ scope: effective.scope,
191
+ session_id: sessionId,
192
+ base_profile: effective.baseProfile,
193
+ base_source: effective.baseSource,
194
+ task_lease: effective.taskLease,
195
+ };
196
+ }
197
+
152
198
  export function runProfile(argv = []) {
153
199
  try { validateArgv(argv); }
154
200
  catch (error) { return fail(error.message); }
@@ -156,6 +202,8 @@ export function runProfile(argv = []) {
156
202
  const sub = args[0] || 'status';
157
203
  const json = argv.includes('--json');
158
204
  const sessionId = optionValue(argv, '--session') || '';
205
+ const reason = optionValue(argv, '--reason');
206
+ if (sub !== 'route' && reason) return fail('--reason só é aceito por profile route');
159
207
 
160
208
  let state;
161
209
  try { state = context(argv); }
@@ -166,20 +214,50 @@ export function runProfile(argv = []) {
166
214
  if (args.length > 1) return fail(`${sub} não aceita argumentos posicionais adicionais`);
167
215
  try {
168
216
  const effective = sessionId
169
- ? sessionProfile(state.vaultBase, sessionId, projectResolved)
170
- : { profile: projectResolved.profile, source: projectResolved.source };
217
+ ? sessionProfileStatus(state.vaultBase, sessionId, projectResolved)
218
+ : { profile: projectResolved.profile, source: projectResolved.source, scope: 'project' };
171
219
  output({
172
- profile: effective.profile,
173
- source: effective.source,
174
- scope: sessionId ? 'session' : 'project',
175
- session_id: sessionId || null,
220
+ ...(sessionId ? sessionOutputPayload(effective, sessionId) : {
221
+ profile: effective.profile,
222
+ source: effective.source,
223
+ scope: 'project',
224
+ session_id: null,
225
+ }),
176
226
  ...(state.resolved.bindingError ? { binding_error: state.resolved.bindingError } : {}),
177
227
  }, json);
178
228
  return 0;
179
229
  } catch (error) { return fail(error.message); }
180
230
  }
181
231
 
182
- if (sub !== 'use' && sub !== 'set') return fail('use status | use <OFF|FLOW|GUIDE|GOVERN|ASSURE>');
232
+ if (sub === 'route') {
233
+ if (args.length !== 2) return fail('route requer exatamente um perfil');
234
+ if (!sessionId) return fail('route requer --session <id>');
235
+ if (!reason) return fail('route requer --reason <text>');
236
+ try {
237
+ const base = sessionProfile(state.vaultBase, sessionId, projectResolved);
238
+ const lease = setSessionTaskOperatingProfile(
239
+ state.vaultBase,
240
+ sessionId,
241
+ args[1],
242
+ { reason },
243
+ );
244
+ output({
245
+ profile: lease.profile,
246
+ source: 'task-lease',
247
+ scope: 'task',
248
+ session_id: sessionId,
249
+ base_profile: base.profile,
250
+ base_source: base.source,
251
+ task_lease: lease,
252
+ ...(state.resolved.bindingError ? { binding_error: state.resolved.bindingError } : {}),
253
+ }, json);
254
+ return 0;
255
+ } catch (error) { return fail(error.message); }
256
+ }
257
+
258
+ if (sub !== 'use' && sub !== 'set') {
259
+ return fail('use status | use <OFF|FLOW|GUIDE|GOVERN|ASSURE> | route <FLOW|GUIDE|GOVERN|ASSURE>');
260
+ }
183
261
  if (args.length !== 2) return fail(`${sub} requer exatamente um perfil`);
184
262
  let profile;
185
263
  try { profile = normalizeOperatingProfile(args[1], { strict: true }); }
@@ -17,6 +17,24 @@ Use ao começar implementação, correção ou refatoração. **Keep Core perman
17
17
  em todos os perfis: Vault, sessão, identidade, memória, lessons e persistência. Na ausência de
18
18
  configuração válida, **GOVERN é o padrão** compatível.
19
19
 
20
+ ## Seleção temporária por solicitação
21
+
22
+ Antes de editar, classifique a implementação atual e registre a escolha auditável com
23
+ \`wendkeep profile route <FLOW|GUIDE|GOVERN|ASSURE> --session <id> --reason <texto>\`.
24
+ A lease vale somente para a solicitação atual; ao encerrá-la, o perfil persistente da
25
+ sessão/projeto volta a valer. **OFF nunca é uma escolha automática da LLM**: somente uma pessoa
26
+ pode persistir \`profile use OFF\` explicitamente.
27
+
28
+ - **FLOW:** ajuste local, reversível e de escopo fechado, sem contrato/spec, segurança,
29
+ dependência, CI/release ou policy.
30
+ - **GUIDE:** mudança compacta de comportamento que precisa de change/spec, sem revisão formal.
31
+ - **GOVERN:** escolha conservadora em caso de dúvida ou risco e para superfícies sensíveis.
32
+ - **ASSURE:** GOVERN quando confirmação explícita e handoff fazem parte do contrato.
33
+
34
+ O harness da LLM faz essa classificação semântica; o Wend Runtime valida e aplica a lease.
35
+ Se não houver uma sessão causal identificada ou o comando falhar, não fabrique estado: use o
36
+ perfil efetivo já injetado e trate \`GOVERN\` como fallback conservador quando ele for o padrão.
37
+
20
38
  <HARD-GATE>
21
39
  Antes de editar, leia o **perfil efetivo** injetado pelo WendKeep e siga somente sua rota:
22
40
  - \`OFF\`: não imponha processo Wend; a governança pertence ao **harness nativo da LLM**.
@@ -268,6 +286,24 @@ Use this when starting an implementation, fix, or refactor. **Keep Core is alway
268
286
  in every profile: Vault, session, identity, memory, lessons, and persistence. With no valid
269
287
  configuration, **GOVERN is the default** for compatibility.
270
288
 
289
+ ## Temporary selection per request
290
+
291
+ Before editing, classify the current implementation and record the auditable choice with
292
+ \`wendkeep profile route <FLOW|GUIDE|GOVERN|ASSURE> --session <id> --reason <text>\`.
293
+ The lease applies only to the current request; after it ends, the persistent session/project
294
+ profile becomes effective again. **OFF is never an automatic LLM choice**: only a human can
295
+ persist it explicitly through \`profile use OFF\`.
296
+
297
+ - **FLOW:** a local, reversible, bounded adjustment with no contract/spec, security, dependency,
298
+ CI/release, or policy impact.
299
+ - **GUIDE:** a compact behavior change that needs a change/spec but no formal review.
300
+ - **GOVERN:** the conservative choice when uncertain or risky, and for sensitive surfaces.
301
+ - **ASSURE:** GOVERN when explicit confirmation and handoff are part of the contract.
302
+
303
+ The LLM harness owns semantic classification; Wend Runtime validates and applies the lease. If
304
+ there is no causally identified session or the command fails, do not fabricate state: use the
305
+ already injected effective profile, with \`GOVERN\` as the conservative configured fallback.
306
+
271
307
  <HARD-GATE>
272
308
  Before editing, read the injected **effective profile** and follow only its route:
273
309
  - \`OFF\`: impose no Wend process; governance belongs to the **native LLM harness**.
@@ -619,7 +655,7 @@ size of the problem>
619
655
  // usuário ("implementa X", "corrige Y"), não com abstrações ("mudança não-trivial"). Gatilhos
620
656
  // concretos + instrução imperativa = a skill dispara sozinha (paridade Superpowers).
621
657
  const WK_SKILLS_PT = [
622
- skill('wk-workflow', 'Use quando o usuário pedir para implementar, criar, corrigir, refatorar, adicionar ou alterar código: leia o perfil efetivo e roteie OFF/FLOW/GUIDE/GOVERN/ASSURE ANTES de editar. Keep Core permanece ativo; GOVERN é o padrão compatível.', WORKFLOW),
658
+ skill('wk-workflow', 'Use quando o usuário pedir para implementar, criar, corrigir, refatorar, adicionar ou alterar código: classifique e registre a rota temporária FLOW/GUIDE/GOVERN/ASSURE ANTES de editar, então siga o perfil efetivo. Keep Core permanece ativo; OFF nunca é automático.', WORKFLOW),
623
659
  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),
624
660
  skill('wk-debugging', 'Use quando algo falha, quebra, dá erro ou regride — depuração sistemática por hipótese antes de corrigir.', DEBUGGING),
625
661
  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 }]),
@@ -628,7 +664,7 @@ const WK_SKILLS_PT = [
628
664
  ];
629
665
 
630
666
  const WK_SKILLS_EN = [
631
- skill('wk-workflow', 'Use when the user asks to implement, create, fix, refactor, add, or change code: read the effective profile and route OFF/FLOW/GUIDE/GOVERN/ASSURE BEFORE editing. Keep Core stays active; GOVERN is the compatible default.', WORKFLOW_EN),
667
+ skill('wk-workflow', 'Use when the user asks to implement, create, fix, refactor, add, or change code: classify and record the temporary FLOW/GUIDE/GOVERN/ASSURE route BEFORE editing, then follow the effective profile. Keep Core stays active; OFF is never automatic.', WORKFLOW_EN),
632
668
  skill('wk-tdd', 'Use when implementing any behaviour — Red/Green/Refactor with tests that discriminate (spec-derived, non-shallow litmus, adequacy).', TDD_EN),
633
669
  skill('wk-debugging', 'Use when something fails, breaks, errors or regresses — systematic hypothesis-driven debugging before fixing.', DEBUGGING_EN),
634
670
  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 }]),
package/src/sync-defs.mjs CHANGED
@@ -62,7 +62,12 @@ function renderAgentsSection(skills, sourceHash = '') {
62
62
 
63
63
  This project uses the [wendkeep](https://github.com/rogersialves/wendkeep) harness. **Keep Core is always active**
64
64
  in every profile: Vault, session, identity, memory, lessons, and persistence integrations.
65
- The effective profile is selected explicitly; missing or invalid configuration uses **GOVERN as the default**.
65
+ The persistent profile is selected explicitly; missing or invalid configuration uses **GOVERN as the default**.
66
+
67
+ Before an implementation, the native LLM harness classifies the current request and may create a
68
+ task-scoped lease with \`wendkeep profile route <FLOW|GUIDE|GOVERN|ASSURE> --session <id> --reason <text>\`.
69
+ The lease expires when that request ends and the persistent session/project profile becomes
70
+ effective again. **OFF is never selected adaptively**; only a human can persist \`profile use OFF\`.
66
71
 
67
72
  Route work by the effective profile:
68
73
  - **OFF** — Wend Runtime is disabled and governance belongs to the native LLM harness; Keep Core stays active.