wendkeep 0.76.3 → 0.76.5

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.
@@ -10,6 +10,11 @@ import {
10
10
  assertVaultPathSafe, assertVaultPathsSafe, mkdirVaultPath, renameVaultPath,
11
11
  unlinkVaultFile, writeVaultFileSync,
12
12
  } from './vault-path-safety.mjs';
13
+ import {
14
+ clearActiveContextChange,
15
+ resolveActiveContext,
16
+ setActiveContextChange,
17
+ } from './active-context-store.mjs';
13
18
 
14
19
  export const ARCHIVE_DIR = '_arquivo';
15
20
  const POINTER = '.brain/CURRENT_CHANGE.md';
@@ -118,7 +123,14 @@ export function scaffoldPlaceholders(dir) {
118
123
  return found;
119
124
  }
120
125
 
121
- export function activeChange(vaultBase) {
126
+ export function activeChange(vaultBase, { context } = {}) {
127
+ if (context) {
128
+ try { return String(resolveActiveContext(vaultBase, context).change_slug || '').trim(); }
129
+ catch (error) {
130
+ if (error?.code === 'WENDKEEP_ACTIVE_CONTEXT_NOT_FOUND') return '';
131
+ throw error;
132
+ }
133
+ }
122
134
  try {
123
135
  const m = readFileSync(join(vaultBase, POINTER), 'utf8').match(/^change:\s*(.+)$/m);
124
136
  return m ? m[1].trim() : '';
@@ -127,7 +139,8 @@ export function activeChange(vaultBase) {
127
139
  }
128
140
  }
129
141
 
130
- export function setActiveChange(vaultBase, slug) {
142
+ export function setActiveChange(vaultBase, slug, { context } = {}) {
143
+ if (context) return setActiveContextChange(vaultBase, context, slug);
131
144
  mkdirVaultPath(vaultBase, join(vaultBase, '.brain'), { label: 'raiz de controle da change' });
132
145
  writeVaultFileSync(
133
146
  vaultBase,
@@ -138,7 +151,8 @@ export function setActiveChange(vaultBase, slug) {
138
151
  );
139
152
  }
140
153
 
141
- export function clearActiveChange(vaultBase) {
154
+ export function clearActiveChange(vaultBase, { context } = {}) {
155
+ if (context) return clearActiveContextChange(vaultBase, context);
142
156
  const p = join(vaultBase, POINTER);
143
157
  const checked = assertVaultPathSafe(vaultBase, p, {
144
158
  expectedType: 'file', label: 'ponteiro CURRENT_CHANGE.md',
@@ -191,7 +205,9 @@ export function assertChangeScaffoldTargetsSafe(vaultBase, slug, {
191
205
  return { dir, rel: changeDirRel(slug, vaultBase) };
192
206
  }
193
207
 
194
- export function newChange(vaultBase, slug, { sessionRel = '', dateStr, simple = false, guide = false }) {
208
+ export function newChange(vaultBase, slug, {
209
+ sessionRel = '', dateStr, simple = false, guide = false, context,
210
+ }) {
195
211
  const compact = simple || guide;
196
212
  const loc = getLocale(vaultBase);
197
213
  const { dir } = assertChangeScaffoldTargetsSafe(vaultBase, slug, { simple: compact });
@@ -218,14 +234,14 @@ export function newChange(vaultBase, slug, { sessionRel = '', dateStr, simple =
218
234
  write('design.md', files.design);
219
235
  }
220
236
  if (!existed) captureSpecBaseline(vaultBase, dir);
221
- setActiveChange(vaultBase, slug);
237
+ setActiveChange(vaultBase, slug, { context });
222
238
  return { rel: changeDirRel(slug, vaultBase), created: !existed };
223
239
  }
224
240
 
225
- export function useChange(vaultBase, slug) {
241
+ export function useChange(vaultBase, slug, { context } = {}) {
226
242
  const dir = join(vaultBase, getLocale(vaultBase).folders.changes, slug);
227
243
  if (!existsSync(join(dir, 'proposta.md'))) return { ok: false, error: `change aberta não encontrada: ${slug}` };
228
- setActiveChange(vaultBase, slug);
244
+ setActiveChange(vaultBase, slug, { context });
229
245
  return { ok: true, rel: changeDirRel(slug, vaultBase) };
230
246
  }
231
247
 
@@ -314,8 +330,8 @@ export function listChanges(vaultBase) {
314
330
  // comandos implícitos; provider/session nunca filtram a fila, para que outro agente possa assumir
315
331
  // o trabalho. O hash leva o conteúdo inteiro de cada tarefas.md — não apenas as contagens — pois
316
332
  // ele controla a reinjeção por sessão dos hooks.
317
- export function allChangesState(vaultBase) {
318
- const current = activeChange(vaultBase);
333
+ export function allChangesState(vaultBase, { context } = {}) {
334
+ const current = activeChange(vaultBase, { context });
319
335
  const { active } = listChanges(vaultBase);
320
336
  const chDir = getLocale(vaultBase).folders.changes;
321
337
  const fingerprint = [`current:${current}`];
@@ -407,16 +423,16 @@ export function buildActiveChangeInjection(vaultBase, options = {}) {
407
423
  return renderOpenChanges(allChangesState(vaultBase), options);
408
424
  }
409
425
 
410
- export function activeChangeLink(vaultBase) {
411
- const slug = activeChange(vaultBase);
426
+ export function activeChangeLink(vaultBase, { context } = {}) {
427
+ const slug = activeChange(vaultBase, { context });
412
428
  return slug ? `Change ativa: [[${getLocale(vaultBase).folders.changes}/${slug}/proposta]]` : '';
413
429
  }
414
430
 
415
431
  // --- Estado rápido do gate + sentinelas por sessão (0.31.0) --------------------
416
432
  // Fonte única e barata (só leituras, tudo fail-open) do estado do gate, consumida pelos hooks
417
433
  // de lifecycle (change-guard/change-nag/change-context) e pelo CLI. null sem change ativa.
418
- export function quickGateState(vaultBase) {
419
- const slug = activeChange(vaultBase);
434
+ export function quickGateState(vaultBase, { context } = {}) {
435
+ const slug = activeChange(vaultBase, { context });
420
436
  if (!slug) return null;
421
437
  const dir = join(vaultBase, getLocale(vaultBase).folders.changes, slug);
422
438
  let tarefasMd = '';
@@ -536,7 +552,9 @@ export function gateGreen() {
536
552
  return { ok: true, failing: [] };
537
553
  }
538
554
 
539
- export function archiveChange(vaultBase, slug, { gate = gateGreen, dateStr, adrNum, adrFlags = {} }) {
555
+ export function archiveChange(vaultBase, slug, {
556
+ gate = gateGreen, dateStr, adrNum, adrFlags = {}, context,
557
+ }) {
540
558
  const loc = getLocale(vaultBase);
541
559
  const chDir = loc.folders.changes;
542
560
  const src = join(vaultBase, chDir, slug);
@@ -656,7 +674,7 @@ Mudança ${changeWikilink} concluída e arquivada.${capLine}${reqLine}${forcedNo
656
674
 
657
675
  // Only clear the pointer when the archived change IS the active one — archiving some other
658
676
  // slug explicitly must not blank the pointer of a different, still-active change.
659
- if (activeChange(vaultBase) === slug) clearActiveChange(vaultBase);
677
+ if (activeChange(vaultBase, { context }) === slug) clearActiveChange(vaultBase, { context });
660
678
  return { ok: true, failing: [], archivedRel: destRel, adrRel: createAdr ? adrRel : '', promoted, specWarnings, linksRewritten };
661
679
  }
662
680
 
@@ -849,7 +867,7 @@ export function relinkChanges(vaultBase, { apply = false } = {}) {
849
867
  // Abandono (0.31.0): a saída legítima para uma change que não vai adiante — o que antes só o
850
868
  // `archive --force` "resolvia", minting um ADR falso. Sem ADR, sem promoteSpecs (abandono não é
851
869
  // decisão arquitetural nem promove contrato); move para _arquivo com sufixo -abandonada.
852
- export function abandonChange(vaultBase, slug, { dateStr }) {
870
+ export function abandonChange(vaultBase, slug, { dateStr, context } = {}) {
853
871
  const chDir = getLocale(vaultBase).folders.changes;
854
872
  const src = join(vaultBase, chDir, slug);
855
873
  const checkedProposal = assertVaultPathSafe(vaultBase, join(src, 'proposta.md'), {
@@ -882,6 +900,6 @@ export function abandonChange(vaultBase, slug, { dateStr }) {
882
900
  } catch { /* proposta sem frontmatter — segue */ }
883
901
  let linksRewritten = 0;
884
902
  try { linksRewritten = rewriteChangeLinks(vaultBase, `${chDir}/${slug}`, destRel.replaceAll('\\', '/')); } catch { /* abandono já íntegro */ }
885
- if (activeChange(vaultBase) === slug) clearActiveChange(vaultBase);
903
+ if (activeChange(vaultBase, { context }) === slug) clearActiveChange(vaultBase, { context });
886
904
  return { ok: true, failing: [], archivedRel: destRel, linksRewritten };
887
905
  }
@@ -13,6 +13,7 @@ import {
13
13
  resolveHookOperatingProfile,
14
14
  } from './operating-profile-runtime.mjs';
15
15
  import { activeDelivery } from '../src/delivery.mjs';
16
+ import { resolveCommandActiveContext } from '../src/active-context-runtime.mjs';
16
17
 
17
18
  const CODE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|py|prisma|sql|go|rs|java|cs)$/i;
18
19
 
@@ -28,12 +29,13 @@ export function warnDecision(filePath, {
28
29
  cwd = '.',
29
30
  sessionId = '',
30
31
  profile = 'GOVERN',
32
+ context = null,
31
33
  } = {}) {
32
34
  const policy = hookProfilePolicy(profile);
33
35
  if (!policy.requiresChange) return null;
34
36
  if (!filePath || !isCodeFile(filePath)) return null;
35
- if (activeChange(vaultBase)) return null;
36
- if (activeDelivery(vaultBase)) return null;
37
+ if (activeChange(vaultBase, { context })) return null;
38
+ if (activeDelivery(vaultBase, { context })) return null;
37
39
  const abs = norm(isAbsolute(filePath) ? filePath : resolve(cwd, filePath));
38
40
  // Dentro do vault (NTFS é case-insensitive) ou em dirs de config de agente: não é código do projeto.
39
41
  if (abs.toLowerCase().startsWith(`${norm(resolve(vaultBase)).toLowerCase()}/`)) return null;
@@ -52,13 +54,20 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
52
54
  try {
53
55
  const input = readHookInput();
54
56
  const runtime = resolveHookOperatingProfile({ input });
57
+ const sid = input.session_id || input.sessionId || '';
58
+ const commandContext = runtime.bindingError ? null : resolveCommandActiveContext({
59
+ vaultBase: runtime.vaultBase,
60
+ projectRoot: input.cwd || process.cwd(),
61
+ sessionId: sid,
62
+ });
55
63
  const warn = runtime.bindingError
56
64
  ? profileRuntimeError(runtime.bindingError)
57
65
  : warnDecision(input.tool_input?.file_path, {
58
66
  vaultBase: runtime.vaultBase,
59
67
  cwd: input.cwd || '.',
60
- sessionId: input.session_id || input.sessionId || '',
68
+ sessionId: sid,
61
69
  profile: runtime.profile,
70
+ context: commandContext,
62
71
  });
63
72
  if (!warn) { writeHookOutput({}); }
64
73
  else writeHookOutput({ hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: warn } });
@@ -276,9 +276,12 @@ export function readSessionRegistry(vaultBase) {
276
276
 
277
277
  try {
278
278
  const parsed = JSON.parse(readFileSync(checked.target, 'utf-8'));
279
+ const root = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
279
280
  return {
280
- version: Math.max(2, parsed.version || 1),
281
- sessions: parsed.sessions && typeof parsed.sessions === 'object' ? parsed.sessions : {},
281
+ ...root,
282
+ version: Math.max(2, root.version || 1),
283
+ sessions: root.sessions && typeof root.sessions === 'object' && !Array.isArray(root.sessions)
284
+ ? root.sessions : {},
282
285
  };
283
286
  } catch {
284
287
  return { version: 2, sessions: {} };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.76.3",
3
+ "version": "0.76.5",
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": [
@@ -64,6 +64,7 @@ Usage:
64
64
  Inspect or explicitly recover a quarantined causal scope conflict.
65
65
  wendkeep change <sub> Change lifecycle: new [--simple|--guide] | use | bind <slug> --session <id> | continue | list | show |
66
66
  status | done <id> | undone <id> | diff | archive [--force] | abandon | relink | backlink.
67
+ --session <id> selects the causal active_context for implicit change operations.
67
68
  archive exige verdict (rode verify --deep); abandon descarta sem ADR.
68
69
  backlink [--apply]: injeta o backlink pro proposta em design/tarefas/spec órfãos (open + _arquivo).
69
70
  wendkeep theme sync Re-aplica o color system (snippet CSS + graph color groups) num vault
@@ -76,7 +77,7 @@ Usage:
76
77
  FLOW records scope, sensors and a receipt without creating a change.
77
78
  wendkeep delivery <sub> Operational delivery: start | status | finish | abandon.
78
79
  Records authorization and an append-only receipt; never creates a change/spec/ADR.
79
- wendkeep spec <sub> Specs: list | show | effective [--change] [--json] | migrate | rebase.
80
+ wendkeep spec <sub> Specs: list | show | effective [--change] [--session] [--json] | migrate | rebase.
80
81
  wendkeep sensors <sub> list | add <id> "<command>" [--severity --type --report].
81
82
  wendkeep cost [opts] Aggregate AI-coding spend across the vault's sessions.
82
83
  --since <date> · --top [N] (priciest) · --trend [day|week|month]
@@ -91,7 +92,7 @@ Usage:
91
92
  --rescan-decisions (capture prose decisions from already-imported transcripts) ·
92
93
  --from <dir> · --codex-from <dir> · --since <date> · --limit N ·
93
94
  --dry-run · --json.
94
- wendkeep verify [--deep] [--change s] Run a change's task sensors + record evidence (the gate);
95
+ wendkeep verify [--deep] [--change s] [--session id] Run a change's task sensors + record evidence (the gate);
95
96
  --deep assembles the verification package for the wk-verify pass.
96
97
  wendkeep dashboard [--force] (Re)generate the vault's folder-filtered Bases + 00-Dashboard MOC.
97
98
  wendkeep renumber-decisions Renumber 04-Decisões to ADR-<NNNN>-<slug> in chronological order,
@@ -0,0 +1,131 @@
1
+ import { readSessionRegistry } from '../hooks/obsidian-common.mjs';
2
+ import { migrateLegacyActiveContext } from '../hooks/active-context-store.mjs';
3
+ import { captureProjectScope, compareProjectScopes } from '../hooks/project-scope.mjs';
4
+ import {
5
+ discoverWorktreeRepository,
6
+ readWorktreeRegistry,
7
+ worktreeIdentity,
8
+ } from '../packages/vault/src/worktree-metadata.mjs';
9
+ import { readProjectForValidation } from '../packages/vault/src/validate-memory.mjs';
10
+
11
+ function runtimeError(code, message) {
12
+ const error = new Error(message);
13
+ error.code = code;
14
+ return error;
15
+ }
16
+
17
+ function activeSession(entry) {
18
+ return entry?.status === 'active'
19
+ && entry?.project_scope?.complete === true
20
+ && typeof entry?.work_session_id === 'string'
21
+ && entry.work_session_id.trim();
22
+ }
23
+
24
+ export function resolveRuntimeActiveContext({
25
+ vaultBase,
26
+ projectRoot = process.cwd(),
27
+ sessionId = '',
28
+ spawn,
29
+ } = {}) {
30
+ const project = readProjectForValidation(vaultBase);
31
+ if (!project.ok || !project.projectId) {
32
+ throw runtimeError('WENDKEEP_ACTIVE_CONTEXT_IDENTITY_UNAVAILABLE', 'PROJECT.json não prova project_id');
33
+ }
34
+ let repository;
35
+ try { repository = discoverWorktreeRepository({ startDir: projectRoot, ...(spawn ? { spawn } : {}) }); }
36
+ catch (error) {
37
+ throw runtimeError('WENDKEEP_ACTIVE_CONTEXT_IDENTITY_UNAVAILABLE', error.message);
38
+ }
39
+ const metadata = readWorktreeRegistry(repository).registry;
40
+ if (!metadata) {
41
+ throw runtimeError(
42
+ 'WENDKEEP_ACTIVE_CONTEXT_IDENTITY_UNAVAILABLE',
43
+ 'registry de worktrees ausente; não é seguro inventar repository_id/worktree_id',
44
+ );
45
+ }
46
+ if (metadata.projectId !== project.projectId) {
47
+ throw runtimeError('WENDKEEP_ACTIVE_CONTEXT_IDENTITY_MISMATCH', 'registry de worktrees pertence a outro projeto');
48
+ }
49
+
50
+ const registry = readSessionRegistry(vaultBase);
51
+ const sessions = registry.sessions || {};
52
+ const worktreeId = worktreeIdentity(metadata.repositoryId, repository.gitDir);
53
+ const requested = String(sessionId || '').trim();
54
+ const rows = requested ? [[requested, sessions[requested]]] : Object.entries(sessions);
55
+ const matches = [];
56
+ for (const [candidateId, entry] of rows) {
57
+ if (!activeSession(entry)) continue;
58
+ const actual = captureProjectScope({
59
+ input: { cwd: projectRoot },
60
+ projectRoot,
61
+ projectId: project.projectId,
62
+ provider: entry.provider || '',
63
+ sessionId: candidateId,
64
+ ...(spawn ? { spawn } : {}),
65
+ });
66
+ if (!compareProjectScopes(entry.project_scope, actual).ok) continue;
67
+ matches.push({ sessionId: candidateId, entry, actual });
68
+ }
69
+ if (!matches.length) {
70
+ throw runtimeError(
71
+ 'WENDKEEP_ACTIVE_CONTEXT_NOT_FOUND',
72
+ requested ? 'sessão causal não corresponde à worktree atual' : 'nenhuma sessão ativa corresponde à worktree atual',
73
+ );
74
+ }
75
+ let selected = matches[0];
76
+ if (matches.length > 1) {
77
+ const active = Object.values(registry.active_contexts || {}).filter((context) => (
78
+ context?.state === 'active'
79
+ && context.project_id === project.projectId
80
+ && context.repository_id === metadata.repositoryId
81
+ && context.worktree_id === worktreeId
82
+ ));
83
+ const narrowed = active.length === 1
84
+ ? matches.filter(({ entry }) => String(entry.work_session_id) === active[0].work_session_id)
85
+ : [];
86
+ if (narrowed.length !== 1) {
87
+ throw runtimeError(
88
+ 'WENDKEEP_ACTIVE_CONTEXT_AMBIGUOUS',
89
+ 'mais de uma sessão ativa corresponde à worktree; informe --session',
90
+ );
91
+ }
92
+ [selected] = narrowed;
93
+ }
94
+ return {
95
+ projectId: project.projectId,
96
+ repositoryId: metadata.repositoryId,
97
+ worktreeId,
98
+ workSessionId: String(selected.entry.work_session_id),
99
+ branch: selected.actual.branch,
100
+ headSha: selected.actual.head,
101
+ sessionId: selected.sessionId,
102
+ };
103
+ }
104
+
105
+ export function resolveCommandActiveContext({
106
+ vaultBase,
107
+ projectRoot = process.cwd(),
108
+ sessionId = '',
109
+ spawn,
110
+ } = {}) {
111
+ const requestedSession = String(sessionId || '').trim();
112
+ let identity;
113
+ try {
114
+ identity = resolveRuntimeActiveContext({
115
+ vaultBase, projectRoot, sessionId: requestedSession, ...(spawn ? { spawn } : {}),
116
+ });
117
+ } catch (error) {
118
+ const registry = readSessionRegistry(vaultBase);
119
+ const initialized = Object.keys(registry.active_contexts || {}).length > 0;
120
+ const legacyUnavailable = error?.code === 'WENDKEEP_ACTIVE_CONTEXT_IDENTITY_UNAVAILABLE'
121
+ || (!requestedSession && error?.code === 'WENDKEEP_ACTIVE_CONTEXT_NOT_FOUND');
122
+ if (!initialized && legacyUnavailable) return null;
123
+ throw error;
124
+ }
125
+ migrateLegacyActiveContext(vaultBase, {
126
+ identityForSession: (candidateId) => resolveRuntimeActiveContext({
127
+ vaultBase, projectRoot, sessionId: candidateId, ...(spawn ? { spawn } : {}),
128
+ }),
129
+ });
130
+ return identity;
131
+ }
package/src/change.mjs CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  backfillArtifactLinks,
18
18
  scaffoldPlaceholders,
19
19
  isGuideCompactChange,
20
+ setActiveChange,
20
21
  } from '../hooks/change-core.mjs';
21
22
  import { evaluateGate, requiredSensors } from '../hooks/sensors-core.mjs';
22
23
  import { buildEffectiveRequirementPackage, evaluateVerdict, formatOrphanReqs, tasksHashOf, parseSpecsList, parseDelta, parseRequirements, applyDelta, validateSpecImpact } from '../hooks/spec-core.mjs';
@@ -24,6 +25,7 @@ import { getNextAdrNumber, readControl, readSessionRegistry, upsertSessionRegist
24
25
  import { getLocale } from '../hooks/locale.mjs';
25
26
  import { enqueueObserverDocumentChange } from './observer-sql-publish.mjs';
26
27
  import { readProjectForValidation } from '../packages/vault/src/validate-memory.mjs';
28
+ import { resolveCommandActiveContext } from './active-context-runtime.mjs';
27
29
 
28
30
  function observerMarkdownUnder(vaultBase, relativeRoot) {
29
31
  const output = [];
@@ -71,6 +73,24 @@ export function runChange(argv) {
71
73
  const vaultBase = resolveVault(rest);
72
74
  const VALUE_FLAGS = new Set(['--vault', '--change', '--project', '--session']);
73
75
  const slugArg = () => rest.find((a, i) => !a.startsWith('-') && !VALUE_FLAGS.has(rest[i - 1]));
76
+ const projectRoot = resolve(opt(rest, '--project') || process.cwd());
77
+ let contextResolved = false;
78
+ let resolvedContext = null;
79
+ const context = () => {
80
+ if (contextResolved) return resolvedContext;
81
+ contextResolved = true;
82
+ const sessionId = opt(rest, '--session')
83
+ || process.env.CODEX_THREAD_ID
84
+ || process.env.CLAUDE_SESSION_ID
85
+ || '';
86
+ try {
87
+ resolvedContext = resolveCommandActiveContext({ vaultBase, projectRoot, sessionId });
88
+ return resolvedContext;
89
+ } catch (error) {
90
+ process.stderr.write(`wendkeep change: ${error.code || 'WENDKEEP_ACTIVE_CONTEXT_FAILED'}: ${error.message}\n`);
91
+ process.exit(2);
92
+ }
93
+ };
74
94
 
75
95
  if (sub === 'new') {
76
96
  const slug = slugArg();
@@ -80,6 +100,7 @@ export function runChange(argv) {
80
100
  try { sessionRel = readControl(vaultBase).session_file || ''; } catch { /* sem control */ }
81
101
  const r = newChange(vaultBase, slug, {
82
102
  dateStr: today(), simple: rest.includes('--simple'), guide: rest.includes('--guide'), sessionRel,
103
+ context: context(),
83
104
  });
84
105
  process.stdout.write(`change ${r.created ? 'created' : 'exists'}: ${r.rel} (active)\n`);
85
106
  process.exit(0);
@@ -88,7 +109,7 @@ export function runChange(argv) {
88
109
  if (sub === 'use') {
89
110
  const slug = slugArg();
90
111
  if (!slug) { process.stderr.write('wendkeep change use: missing <slug>\n'); process.exit(2); }
91
- const r = useChange(vaultBase, slug);
112
+ const r = useChange(vaultBase, slug, { context: context() });
92
113
  if (!r.ok) { process.stderr.write(`wendkeep change use: ${r.error}\n`); process.exit(2); }
93
114
  process.stdout.write(`current change: ${slug}\n`);
94
115
  process.exit(0);
@@ -102,6 +123,8 @@ export function runChange(argv) {
102
123
  if (!state.changes.some((item) => item.slug === slug)) { process.stderr.write(`wendkeep change bind: open change not found: ${slug}\n`); process.exit(2); }
103
124
  if (!readSessionRegistry(vaultBase).sessions?.[sessionId]) { process.stderr.write(`wendkeep change bind: session not found: ${sessionId}\n`); process.exit(2); }
104
125
  upsertSessionRegistry(vaultBase, sessionId, { change_slug: slug });
126
+ const selectedContext = context();
127
+ if (selectedContext) setActiveChange(vaultBase, slug, { context: selectedContext });
105
128
  process.stdout.write(`session ${sessionId} -> change ${slug}\n`);
106
129
  process.exit(0);
107
130
  }
@@ -117,6 +140,7 @@ export function runChange(argv) {
117
140
  try { sessionRel = readControl(vaultBase).session_file || ''; } catch { /* no control */ }
118
141
  const r = continueChange(vaultBase, archivedSlug, newSlug, {
119
142
  dateStr: today(), simple: rest.includes('--simple'), guide: rest.includes('--guide'), sessionRel,
143
+ context: context(),
120
144
  });
121
145
  if (!r.ok) { process.stderr.write(`wendkeep change continue: ${r.error}\n`); process.exit(2); }
122
146
  process.stdout.write(`change created: ${r.rel} (continues ${r.archived}; active)\n`);
@@ -124,7 +148,7 @@ export function runChange(argv) {
124
148
  }
125
149
 
126
150
  if (sub === 'list') {
127
- const state = allChangesState(vaultBase);
151
+ const state = allChangesState(vaultBase, { context: context() });
128
152
  const { archived } = listChanges(vaultBase);
129
153
  process.stdout.write(`${renderOpenChanges(state, { tag: '' }) || 'open changes: (none)'}\n`);
130
154
  process.stdout.write(`archived: ${archived.join(', ') || '(none)'}\n`);
@@ -147,7 +171,7 @@ export function runChange(argv) {
147
171
  if (sub === 'status') {
148
172
  const slug = slugArg();
149
173
  if (!slug) {
150
- const state = allChangesState(vaultBase);
174
+ const state = allChangesState(vaultBase, { context: context() });
151
175
  if (!state.changes.length && !state.pointerWarning) {
152
176
  process.stderr.write('wendkeep change status: no open changes\n');
153
177
  process.exit(2);
@@ -161,7 +185,7 @@ export function runChange(argv) {
161
185
  catch { process.stderr.write(`wendkeep change status: not found: ${slug}\n`); process.exit(2); }
162
186
  const tasks = parseTasks(tarefasMd);
163
187
  const done = tasks.filter((t) => t.done).length;
164
- process.stdout.write(`change: ${slug}${slug === activeChange(vaultBase) ? ' (ativa)' : ''}\n`);
188
+ process.stdout.write(`change: ${slug}${slug === activeChange(vaultBase, { context: context() }) ? ' (ativa)' : ''}\n`);
165
189
  let specs = [];
166
190
  try { specs = parseSpecsList(readFileSync(join(dir, 'proposta.md'), 'utf8')); } catch { /* sem proposta */ }
167
191
  process.stdout.write(`specs: ${specs.join(', ') || '(nenhuma)'}\n`);
@@ -194,7 +218,7 @@ export function runChange(argv) {
194
218
  if (sub === 'done' || sub === 'undone') {
195
219
  const taskId = slugArg();
196
220
  if (!taskId) { process.stderr.write(`wendkeep change ${sub}: missing <taskId>\n`); process.exit(2); }
197
- const slug = opt(rest, '--change') || activeChange(vaultBase);
221
+ const slug = opt(rest, '--change') || activeChange(vaultBase, { context: context() });
198
222
  if (!slug) { process.stderr.write(`wendkeep change ${sub}: no active change\n`); process.exit(2); }
199
223
  const dir = join(vaultBase, getLocale(vaultBase).folders.changes, slug);
200
224
  let ok = false;
@@ -205,7 +229,7 @@ export function runChange(argv) {
205
229
  }
206
230
 
207
231
  if (sub === 'diff') {
208
- const slug = slugArg() || activeChange(vaultBase);
232
+ const slug = slugArg() || activeChange(vaultBase, { context: context() });
209
233
  if (!slug) { process.stderr.write('wendkeep change diff: no change (arg or active)\n'); process.exit(2); }
210
234
  const dir = join(vaultBase, getLocale(vaultBase).folders.changes, slug);
211
235
  let specs = [];
@@ -228,7 +252,8 @@ export function runChange(argv) {
228
252
  }
229
253
 
230
254
  if (sub === 'archive') {
231
- const slug = slugArg() || activeChange(vaultBase);
255
+ const selectedContext = context();
256
+ const slug = slugArg() || activeChange(vaultBase, { context: selectedContext });
232
257
  if (!slug) { process.stderr.write('wendkeep change archive: missing <slug> and no active change\n'); process.exit(2); }
233
258
  // Real gate (Pilar C): every sensor a task declared must be green in evidencia.json.
234
259
  const gate = (dir) => {
@@ -310,7 +335,10 @@ export function runChange(argv) {
310
335
  if (trivial) process.stderr.write(compactGuide
311
336
  ? 'aviso: GUIDE compacta sem [req:]/[sensor:] — resultado permanece auditável no archive, sem ADR automático\n'
312
337
  : 'aviso: change trivial (sem [req:]/[sensor:]) — ADR marcado trivial: true\n');
313
- const r = archiveChange(vaultBase, slug, { dateStr: today(), adrNum: getNextAdrNumber(vaultBase), gate, adrFlags: { forced, trivial } });
338
+ const r = archiveChange(vaultBase, slug, {
339
+ dateStr: today(), adrNum: getNextAdrNumber(vaultBase), gate,
340
+ adrFlags: { forced, trivial }, context: selectedContext,
341
+ });
314
342
  if (!r.ok) {
315
343
  process.stderr.write(`change archive BLOCKED (gate): ${r.failing.join('; ')}\n`);
316
344
  process.exit(1);
@@ -366,9 +394,10 @@ export function runChange(argv) {
366
394
  }
367
395
 
368
396
  if (sub === 'abandon') {
369
- const slug = slugArg() || activeChange(vaultBase);
397
+ const selectedContext = context();
398
+ const slug = slugArg() || activeChange(vaultBase, { context: selectedContext });
370
399
  if (!slug) { process.stderr.write('wendkeep change abandon: missing <slug> and no active change\n'); process.exit(2); }
371
- const r = abandonChange(vaultBase, slug, { dateStr: today() });
400
+ const r = abandonChange(vaultBase, slug, { dateStr: today(), context: selectedContext });
372
401
  if (!r.ok) { process.stderr.write(`wendkeep change abandon: ${r.failing.join('; ')}\n`); process.exit(2); }
373
402
  process.stdout.write(`abandoned: ${r.archivedRel}\n`);
374
403
  process.exit(0);