wendkeep 0.76.8 → 0.77.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 +30 -0
- package/README.en.md +4 -3
- package/README.md +4 -3
- package/docs/en/commands/context.md +8 -0
- package/docs/en/commands/worktrees.md +80 -32
- package/docs/pt-BR/commands/context.md +7 -0
- package/docs/pt-BR/commands/worktrees.md +80 -31
- package/hooks/active-context-handoff-evidence.mjs +6 -7
- package/hooks/active-context-store.mjs +7 -1
- package/hooks/brain-inject.mjs +30 -8
- package/hooks/change-context.mjs +9 -3
- package/hooks/change-core.mjs +4 -3
- package/package.json +2 -2
- package/packages/cli/src/index.mjs +1 -1
- package/src/active-context-runtime.mjs +12 -2
- package/src/worktree-cleanup.mjs +712 -0
- package/src/worktree.mjs +130 -19
package/hooks/brain-inject.mjs
CHANGED
|
@@ -7,7 +7,12 @@ import { join } from 'node:path';
|
|
|
7
7
|
import { pathToFileURL } from 'node:url';
|
|
8
8
|
import { readHookInput, writeHookOutput } from './obsidian-common.mjs';
|
|
9
9
|
import { brainDir } from './brain-core.mjs';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
activeChange,
|
|
12
|
+
buildActiveChangeInjection,
|
|
13
|
+
changeCtxState,
|
|
14
|
+
writeSentinel,
|
|
15
|
+
} from './change-core.mjs';
|
|
11
16
|
import { buildLessonsInjection } from './lessons-core.mjs';
|
|
12
17
|
import { getLocale } from './locale.mjs';
|
|
13
18
|
import { resolveSessionEntry } from './session-identity.mjs';
|
|
@@ -20,6 +25,7 @@ import {
|
|
|
20
25
|
} from './operating-profile-runtime.mjs';
|
|
21
26
|
import { assertVaultPathSafe } from './vault-path-safety.mjs';
|
|
22
27
|
import { CORE_LIMITS, validateCore } from '../src/validate-core.mjs';
|
|
28
|
+
import { resolveCommandActiveContext } from '../src/active-context-runtime.mjs';
|
|
23
29
|
|
|
24
30
|
// The process ROUTER — the enforcement layer. The wk-* skills are passive files; without a
|
|
25
31
|
// standing instruction the model plans in chat, leaves the change scaffold raw and forces the
|
|
@@ -220,7 +226,9 @@ function budgetNotice(priority, layer, message) {
|
|
|
220
226
|
return `<wk_budget_notice priority="${priority}" layer="${layer}">${message}</wk_budget_notice>`;
|
|
221
227
|
}
|
|
222
228
|
|
|
223
|
-
export function buildInjection(vaultBase, input = {}, {
|
|
229
|
+
export function buildInjection(vaultBase, input = {}, {
|
|
230
|
+
profile = 'GOVERN', bindingError = null, context = null,
|
|
231
|
+
} = {}) {
|
|
224
232
|
const brain = detectMemoryMode(vaultBase).mode === 'v2'
|
|
225
233
|
? buildV2Memory(vaultBase)
|
|
226
234
|
: buildLegacyMemory(vaultBase);
|
|
@@ -236,10 +244,14 @@ export function buildInjection(vaultBase, input = {}, { profile = 'GOVERN', bind
|
|
|
236
244
|
// visible below, but the standing change router would contradict that contract.
|
|
237
245
|
const router = policy.requiresChange ? processRouter(getLocale(vaultBase).id) : '';
|
|
238
246
|
const { identity, entry } = resolveSessionEntry(vaultBase, input);
|
|
239
|
-
const
|
|
240
|
-
|
|
247
|
+
const contextualChange = context ? activeChange(vaultBase, { context }) : '';
|
|
248
|
+
const sessionChange = contextualChange || (!context && identity.state === 'resolved' ? entry?.change_slug : '');
|
|
249
|
+
const focus = sessionChange
|
|
250
|
+
? `<session_change>${boundAncillaryText(`Change vinculada a esta sessão: ${sessionChange}. Este vínculo prevalece para writes automáticos; todas as pendências continuam visíveis acima.`, '<session_change></session_change>'.length)}</session_change>`
|
|
241
251
|
: '';
|
|
242
|
-
const allChanges = buildActiveChangeInjection(vaultBase, {
|
|
252
|
+
const allChanges = buildActiveChangeInjection(vaultBase, {
|
|
253
|
+
context, maxLineChars: INJECTION_LIMITS.lineChars,
|
|
254
|
+
});
|
|
243
255
|
|
|
244
256
|
// Global priority is deterministic: memory/router/focus, then changes, then lessons.
|
|
245
257
|
let output = joinInjection([brain, profileNotice, router, focus, allChanges, lessons]);
|
|
@@ -253,6 +265,7 @@ export function buildInjection(vaultBase, input = {}, { profile = 'GOVERN', bind
|
|
|
253
265
|
// Second pressure step: non-current changes leave the hot context before the current one.
|
|
254
266
|
const nonCurrentEvicted = budgetNotice(2, 'non-current-changes', 'Changes não atuais omitidas depois das lessons.');
|
|
255
267
|
const currentChange = buildActiveChangeInjection(vaultBase, {
|
|
268
|
+
context,
|
|
256
269
|
currentOnly: true,
|
|
257
270
|
maxLineChars: INJECTION_LIMITS.lineChars,
|
|
258
271
|
});
|
|
@@ -264,6 +277,7 @@ export function buildInjection(vaultBase, input = {}, { profile = 'GOVERN', bind
|
|
|
264
277
|
const fixed = joinInjection([brain, profileNotice, router, focus, lessonsEvicted, nonCurrentEvicted, currentSummarized]);
|
|
265
278
|
const remaining = Math.max(512, INJECTION_LIMITS.totalBytes - byteLength(fixed) - 1);
|
|
266
279
|
const boundedCurrent = buildActiveChangeInjection(vaultBase, {
|
|
280
|
+
context,
|
|
267
281
|
currentOnly: true,
|
|
268
282
|
maxBytes: remaining,
|
|
269
283
|
maxLineChars: INJECTION_LIMITS.lineChars,
|
|
@@ -285,21 +299,29 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
285
299
|
const input = readHookInput();
|
|
286
300
|
const runtime = resolveHookOperatingProfile({ input });
|
|
287
301
|
const vaultBase = runtime.vaultBase;
|
|
302
|
+
const { identity } = runtime;
|
|
303
|
+
const sid = identity.state === 'resolved'
|
|
304
|
+
? identity.canonicalConversationId : (input.session_id || input.sessionId || '');
|
|
305
|
+
const commandContext = runtime.bindingError ? null : resolveCommandActiveContext({
|
|
306
|
+
vaultBase,
|
|
307
|
+
projectRoot: input.cwd || process.cwd(),
|
|
308
|
+
sessionId: sid,
|
|
309
|
+
requireExisting: true,
|
|
310
|
+
});
|
|
288
311
|
writeHookOutput({
|
|
289
312
|
hookSpecificOutput: {
|
|
290
313
|
hookEventName: 'SessionStart',
|
|
291
314
|
additionalContext: buildInjection(vaultBase, input, {
|
|
292
315
|
profile: runtime.profile,
|
|
293
316
|
bindingError: runtime.bindingError,
|
|
317
|
+
context: commandContext,
|
|
294
318
|
}),
|
|
295
319
|
},
|
|
296
320
|
});
|
|
297
321
|
// Sentinela do change-context: o backlog completo acabou de ser injetado aqui, então o hook
|
|
298
322
|
// UserPromptSubmit não precisa re-pingar no 1º prompt. Bônus — nunca derruba a injeção.
|
|
299
323
|
if (runtime.policy.harness && !runtime.bindingError) try {
|
|
300
|
-
const st = changeCtxState(vaultBase);
|
|
301
|
-
const { identity } = runtime;
|
|
302
|
-
const sid = identity.state === 'resolved' ? identity.canonicalConversationId : (input.session_id || input.sessionId || '');
|
|
324
|
+
const st = changeCtxState(vaultBase, { context: commandContext });
|
|
303
325
|
if (st) {
|
|
304
326
|
writeSentinel(vaultBase, 'ctx', profileSentinelId(sid, runtime.profile), st.hash);
|
|
305
327
|
}
|
package/hooks/change-context.mjs
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
import { pathToFileURL } from 'node:url';
|
|
9
9
|
import { readHookInput, writeHookOutput } from './obsidian-common.mjs';
|
|
10
10
|
import { profileRuntimeError } from './brain-inject.mjs';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
activeChange, changeCtxState, readSentinel, renderOpenChanges, writeSentinel,
|
|
13
|
+
} from './change-core.mjs';
|
|
12
14
|
import {
|
|
13
15
|
hookProfilePolicy,
|
|
14
16
|
profileSentinelId,
|
|
@@ -43,7 +45,7 @@ export function buildChangePing(vaultBase, sessionId, prompt = '', changeSlug =
|
|
|
43
45
|
hash,
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
|
-
const st = changeCtxState(vaultBase);
|
|
48
|
+
const st = changeCtxState(vaultBase, { context });
|
|
47
49
|
if (st) {
|
|
48
50
|
if (readSentinel(vaultBase, 'ctx', sentinelId) === st.hash) return null;
|
|
49
51
|
writeSentinel(vaultBase, 'ctx', sentinelId, st.hash);
|
|
@@ -75,10 +77,14 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
75
77
|
vaultBase,
|
|
76
78
|
projectRoot: input.cwd || process.cwd(),
|
|
77
79
|
sessionId: sid,
|
|
80
|
+
requireExisting: true,
|
|
78
81
|
});
|
|
82
|
+
const changeSlug = commandContext
|
|
83
|
+
? activeChange(vaultBase, { context: commandContext })
|
|
84
|
+
: (entry?.change_slug || '');
|
|
79
85
|
const ping = runtime.bindingError
|
|
80
86
|
? { context: profileRuntimeError(runtime.bindingError) }
|
|
81
|
-
: buildChangePing(vaultBase, sid, input.prompt || '',
|
|
87
|
+
: buildChangePing(vaultBase, sid, input.prompt || '', changeSlug, {
|
|
82
88
|
profile: runtime.profile,
|
|
83
89
|
context: commandContext,
|
|
84
90
|
});
|
package/hooks/change-core.mjs
CHANGED
|
@@ -420,7 +420,8 @@ export function renderOpenChanges(state, {
|
|
|
420
420
|
// Mantém o nome exportado para consumidores internos existentes, mas agora injeta o backlog
|
|
421
421
|
// completo em vez de ocultar changes não selecionadas.
|
|
422
422
|
export function buildActiveChangeInjection(vaultBase, options = {}) {
|
|
423
|
-
|
|
423
|
+
const { context, ...renderOptions } = options;
|
|
424
|
+
return renderOpenChanges(allChangesState(vaultBase, { context }), renderOptions);
|
|
424
425
|
}
|
|
425
426
|
|
|
426
427
|
export function activeChangeLink(vaultBase, { context } = {}) {
|
|
@@ -479,8 +480,8 @@ export function writeSentinel(vaultBase, kind, sid, value = '1') {
|
|
|
479
480
|
// Estado global usado pelo change-context: hash cobre qualquer tarefa aberta, inclusive de uma
|
|
480
481
|
// change que não esteja no ponteiro. As propriedades slug/openTasks preservam compatibilidade com
|
|
481
482
|
// consumidores antigos e descrevem somente a atual.
|
|
482
|
-
export function changeCtxState(vaultBase) {
|
|
483
|
-
const state = allChangesState(vaultBase);
|
|
483
|
+
export function changeCtxState(vaultBase, { context } = {}) {
|
|
484
|
+
const state = allChangesState(vaultBase, { context });
|
|
484
485
|
if (!state.changes.length && !state.pointerWarning) return null;
|
|
485
486
|
const selected = state.changes.find((change) => change.current);
|
|
486
487
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.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": [
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"node": ">=18"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
|
-
"precheck": "node --check src/worktree.mjs && node --check src/context.mjs && node --check src/active-context-health.mjs && node --check packages/vault/src/worktree-metadata.mjs",
|
|
44
|
+
"precheck": "node --check src/worktree.mjs && node --check src/worktree-cleanup.mjs && node --check src/context.mjs && node --check src/active-context-health.mjs && node --check src/active-context-runtime.mjs && node --check hooks/active-context-store.mjs && node --check hooks/change-core.mjs && node --check hooks/brain-inject.mjs && node --check hooks/change-context.mjs && node --check packages/vault/src/worktree-metadata.mjs",
|
|
45
45
|
"check": "node --check scripts/release.mjs && node --check scripts/release-plan.mjs && node --check scripts/release-provenance.mjs && node --check scripts/run-scope.mjs && node --check src/release-provenance.mjs && 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/active-context-health.mjs && node --check src/project-vault.mjs && node --check src/observer-auth.mjs && node --check src/observer-privacy.mjs && node --check src/observer-snapshot.mjs && node --check src/observer-store.mjs && node --check src/observer-memory.mjs && node --check src/observer-memory-publish.mjs && node --check src/observer-sql-store.mjs && node --check src/observer-sql-migrate.mjs && node --check src/observer-sql-publish.mjs && node --check src/observer-transcript-store.mjs && node --check src/observer-server.mjs && node --check src/observer.mjs && node --check src/observer-publish.mjs && node --check src/operating-profile.mjs && node --check src/profile.mjs && node --check src/flow.mjs && node --check src/work-kind.mjs && node --check src/delivery.mjs && node --check web/observer/app.mjs && node --check hooks/observer-publish.mjs && node --check hooks/evidence-context.mjs && node --check hooks/active-context-handoff-evidence.mjs && node --check hooks/evidence-recall.mjs && node --check hooks/memory-scope.mjs && node --check hooks/operating-profile-runtime.mjs && node --check hooks/operating-profile-task-store.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/integrations/src/host-hooks.mjs && node --check packages/integrations/src/hook-envelope.mjs && node --check packages/integrations/src/prompt-content.mjs && node --check packages/integrations/src/transcript-usage.mjs && node --check packages/integrations/src/transcripts.mjs && node --check packages/integrations/src/session-identity.mjs && node --check packages/integrations/src/index.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-scope.mjs && node --check packages/vault/src/memory-candidate-policy.mjs && node --check packages/vault/src/evidence-recall.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",
|
|
46
46
|
"test": "node --test --test-concurrency=2",
|
|
47
47
|
"test:core": "node scripts/run-scope.mjs core",
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { readSessionRegistry } from '../hooks/obsidian-common.mjs';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
activeContextRegistryInitialized,
|
|
4
|
+
migrateLegacyActiveContext,
|
|
5
|
+
resolveActiveContext,
|
|
6
|
+
} from '../hooks/active-context-store.mjs';
|
|
3
7
|
import { captureProjectScope, compareProjectScopes } from '../hooks/project-scope.mjs';
|
|
4
8
|
import {
|
|
5
9
|
discoverWorktreeRepository,
|
|
@@ -107,6 +111,7 @@ export function resolveCommandActiveContext({
|
|
|
107
111
|
projectRoot = process.cwd(),
|
|
108
112
|
sessionId = '',
|
|
109
113
|
spawn,
|
|
114
|
+
requireExisting = false,
|
|
110
115
|
} = {}) {
|
|
111
116
|
const requestedSession = String(sessionId || '').trim();
|
|
112
117
|
let identity;
|
|
@@ -116,7 +121,7 @@ export function resolveCommandActiveContext({
|
|
|
116
121
|
});
|
|
117
122
|
} catch (error) {
|
|
118
123
|
const registry = readSessionRegistry(vaultBase);
|
|
119
|
-
const initialized =
|
|
124
|
+
const initialized = activeContextRegistryInitialized(registry);
|
|
120
125
|
const legacyUnavailable = error?.code === 'WENDKEEP_ACTIVE_CONTEXT_IDENTITY_UNAVAILABLE'
|
|
121
126
|
|| (!requestedSession && error?.code === 'WENDKEEP_ACTIVE_CONTEXT_NOT_FOUND');
|
|
122
127
|
if (!initialized && legacyUnavailable) return null;
|
|
@@ -127,5 +132,10 @@ export function resolveCommandActiveContext({
|
|
|
127
132
|
vaultBase, projectRoot, sessionId: candidateId, ...(spawn ? { spawn } : {}),
|
|
128
133
|
}),
|
|
129
134
|
});
|
|
135
|
+
if (requireExisting) {
|
|
136
|
+
const registry = readSessionRegistry(vaultBase);
|
|
137
|
+
if (!activeContextRegistryInitialized(registry)) return null;
|
|
138
|
+
resolveActiveContext(vaultBase, identity);
|
|
139
|
+
}
|
|
130
140
|
return identity;
|
|
131
141
|
}
|