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.
@@ -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 { buildActiveChangeInjection, changeCtxState, writeSentinel } from './change-core.mjs';
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 = {}, { profile = 'GOVERN', bindingError = null } = {}) {
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 focus = identity.state === 'resolved' && entry?.change_slug
240
- ? `<session_change>${boundAncillaryText(`Change vinculada a esta sessão: ${entry.change_slug}. Este vínculo prevalece para writes automáticos; todas as pendências continuam visíveis acima.`, '<session_change></session_change>'.length)}</session_change>`
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, { maxLineChars: INJECTION_LIMITS.lineChars });
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
  }
@@ -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 { changeCtxState, readSentinel, renderOpenChanges, writeSentinel } from './change-core.mjs';
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 || '', entry?.change_slug || '', {
87
+ : buildChangePing(vaultBase, sid, input.prompt || '', changeSlug, {
82
88
  profile: runtime.profile,
83
89
  context: commandContext,
84
90
  });
@@ -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
- return renderOpenChanges(allChangesState(vaultBase), options);
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.76.8",
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",
@@ -249,7 +249,7 @@ async function main(argv) {
249
249
  }
250
250
  case 'worktree': {
251
251
  const { runWorktree } = await import('../../../src/worktree.mjs');
252
- process.exit(runWorktree(rest));
252
+ process.exit(await runWorktree(rest));
253
253
  break;
254
254
  }
255
255
  case 'context': {
@@ -1,5 +1,9 @@
1
1
  import { readSessionRegistry } from '../hooks/obsidian-common.mjs';
2
- import { migrateLegacyActiveContext } from '../hooks/active-context-store.mjs';
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 = Object.keys(registry.active_contexts || {}).length > 0;
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
  }