wendkeep 0.72.1 → 0.73.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.
@@ -14,6 +14,7 @@ import {
14
14
  profileSentinelId,
15
15
  resolveHookOperatingProfile,
16
16
  } from './operating-profile-runtime.mjs';
17
+ import { activeDelivery } from '../src/delivery.mjs';
17
18
 
18
19
  // Conservador de propósito: verbos de tarefa comuns (pt+en) + tamanho mínimo. Falso-negativo
19
20
  // custa só o nudge; falso-positivo em pergunta curta viraria ruído.
@@ -29,6 +30,16 @@ export function buildChangePing(vaultBase, sessionId, prompt = '', changeSlug =
29
30
  const policy = hookProfilePolicy(profile);
30
31
  if (!policy.harness) return null;
31
32
  const sentinelId = profileSentinelId(sessionId, profile);
33
+ const delivery = activeDelivery(vaultBase);
34
+ if (delivery) {
35
+ const hash = `delivery:${delivery.id}`;
36
+ if (readSentinel(vaultBase, 'ctx', sentinelId) === hash) return null;
37
+ writeSentinel(vaultBase, 'ctx', sentinelId, hash);
38
+ return {
39
+ context: `<active_delivery>Delivery ${delivery.id} ativa; capabilities: ${delivery.route.operation_risk.join(', ')}. Risco operacional autorizado sem nova change/spec.</active_delivery>`,
40
+ hash,
41
+ };
42
+ }
32
43
  const st = changeCtxState(vaultBase);
33
44
  if (st) {
34
45
  if (readSentinel(vaultBase, 'ctx', sentinelId) === st.hash) return null;
@@ -18,12 +18,15 @@ export function changeDirRel(slug, vaultBase) {
18
18
  return join(getLocale(vaultBase).folders.changes, slug);
19
19
  }
20
20
 
21
- export function renderChangeScaffold({ slug, sessionRel, dateStr, locale = 'pt-BR', simple = false }) {
21
+ export function renderChangeScaffold({ slug, sessionRel, dateStr, locale = 'pt-BR', simple = false, guide = false }) {
22
22
  const en = locale === 'en';
23
23
  const source = sessionRel ? `\n - "${wikilinkFromRel(sessionRel)}"` : ' []';
24
- const impact = simple ? 'none' : 'pending';
25
- const impactReason = simple
26
- ? (en ? 'Simple change with no product-contract impact.' : 'Mudança simples sem impacto no contrato do produto.')
24
+ const compact = simple || guide;
25
+ const impact = compact ? 'none' : 'pending';
26
+ const impactReason = compact
27
+ ? (guide
28
+ ? (en ? 'Compact GUIDE change with no product-contract impact.' : 'Change GUIDE compacta sem impacto no contrato do produto.')
29
+ : (en ? 'Simple change with no product-contract impact.' : 'Mudança simples sem impacto no contrato do produto.'))
27
30
  : '';
28
31
  const proposta = `---
29
32
  type: change
@@ -34,14 +37,18 @@ cssclasses:
34
37
  tags:
35
38
  - mudanca
36
39
  source:${source}
37
- spec_impact: ${impact}
40
+ ${guide ? 'work_kind: implementation\nprofile: GUIDE\ncontract_impact: none\n' : ''}spec_impact: ${impact}
38
41
  spec_impact_reason: ${JSON.stringify(impactReason)}
39
42
  specs: []
40
43
  ---
41
44
 
42
45
  # ${slug}
43
46
 
44
- ${en ? '## Why\n\n(reason for the change)\n\n## What changes\n\n(scope of the change)' : '## Por quê\n\n(motivo da mudança)\n\n## O que muda\n\n(escopo da mudança)'}
47
+ ${guide
48
+ ? (en
49
+ ? '## Objective\n\n(objective)\n\n## Acceptance criteria\n\n- [ ] (acceptance criterion)\n\n## Affected areas\n\n(affected areas)'
50
+ : '## Objetivo\n\n(objetivo)\n\n## Critérios de aceite\n\n- [ ] (critério de aceite)\n\n## Áreas afetadas\n\n(áreas afetadas)')
51
+ : (en ? '## Why\n\n(reason for the change)\n\n## What changes\n\n(scope of the change)' : '## Por quê\n\n(motivo da mudança)\n\n## O que muda\n\n(escopo da mudança)')}
45
52
  `;
46
53
  // Hub backlink: design/tarefas link the change's proposta so no generated artifact is a
47
54
  // graph island. Full-path (never basename — proposta/design exist in every change) so the
@@ -54,11 +61,13 @@ ${hubLink}
54
61
 
55
62
  ${en ? '## Approach\n\n(technical approach)' : '## Abordagem\n\n(abordagem técnica)'}
56
63
  `;
57
- const tarefas = `# ${slug} — ${en ? 'tasks' : 'tarefas'}
64
+ const tarefas = `# ${slug} — ${guide ? (en ? 'validation' : 'validação') : (en ? 'tasks' : 'tarefas')}
58
65
 
59
66
  ${hubLink}
60
67
 
61
- - [ ] 1.1 ${en ? '(first task)' : '(primeira tarefa)'}
68
+ ${guide
69
+ ? `${en ? '## Tests' : '## Testes'}\n\n- [ ] 1.1 ${en ? '(test or validation)' : '(teste ou validação)'}\n\n${en ? '## Result' : '## Resultado'}\n\n${en ? '(result)' : '(resultado)'}`
70
+ : `- [ ] 1.1 ${en ? '(first task)' : '(primeira tarefa)'}`}
62
71
  `;
63
72
  const reqHeading = en ? 'Requirement' : 'Requisito';
64
73
  const specDelta = `## ADDED Requirements
@@ -76,11 +85,29 @@ ${en ? '(behaviour / scenarios)' : '(comportamento / cenários)'}
76
85
  // proposta/design/tarefas still carry these was never actually planned — archiving it mints a
77
86
  // bogus ADR and pollutes _arquivo (seen in production). The archive gate blocks on them.
78
87
  const SCAFFOLD_MARKERS = [
79
- ['proposta.md', ['(motivo da mudança)', '(escopo da mudança)', '(reason for the change)', '(scope of the change)']],
88
+ ['proposta.md', [
89
+ '(motivo da mudança)', '(escopo da mudança)', '(reason for the change)', '(scope of the change)',
90
+ '(objetivo)', '(objective)', '(critério de aceite)', '(acceptance criterion)',
91
+ '(áreas afetadas)', '(affected areas)',
92
+ ]],
80
93
  ['design.md', ['(abordagem técnica)', '(technical approach)']],
81
- ['tarefas.md', ['(primeira tarefa)', '(first task)']],
94
+ ['tarefas.md', [
95
+ '(primeira tarefa)', '(first task)', '(teste ou validação)', '(test or validation)',
96
+ '(resultado)', '(result)',
97
+ ]],
82
98
  ];
83
99
 
100
+ export function isGuideCompactChange(dir) {
101
+ try {
102
+ const proposal = readFileSync(join(dir, 'proposta.md'), 'utf8');
103
+ return /^profile:\s*GUIDE\s*$/mi.test(proposal)
104
+ && /^contract_impact:\s*none\s*$/mi.test(proposal)
105
+ && /^spec_impact:\s*none\s*$/mi.test(proposal);
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+
84
111
  export function scaffoldPlaceholders(dir) {
85
112
  const found = [];
86
113
  for (const [file, markers] of SCAFFOLD_MARKERS) {
@@ -164,12 +191,13 @@ export function assertChangeScaffoldTargetsSafe(vaultBase, slug, {
164
191
  return { dir, rel: changeDirRel(slug, vaultBase) };
165
192
  }
166
193
 
167
- export function newChange(vaultBase, slug, { sessionRel = '', dateStr, simple = false }) {
194
+ export function newChange(vaultBase, slug, { sessionRel = '', dateStr, simple = false, guide = false }) {
195
+ const compact = simple || guide;
168
196
  const loc = getLocale(vaultBase);
169
- const { dir } = assertChangeScaffoldTargetsSafe(vaultBase, slug, { simple });
197
+ const { dir } = assertChangeScaffoldTargetsSafe(vaultBase, slug, { simple: compact });
170
198
  const existed = existsSync(join(dir, 'proposta.md'));
171
199
  mkdirVaultPath(vaultBase, dir, { label: 'destino da change' });
172
- const files = renderChangeScaffold({ slug, sessionRel, dateStr, locale: loc.id, simple });
200
+ const files = renderChangeScaffold({ slug, sessionRel, dateStr, locale: loc.id, simple: compact, guide });
173
201
  const write = (name, content) => {
174
202
  const f = join(dir, name);
175
203
  const checked = assertVaultPathSafe(vaultBase, f, {
@@ -186,7 +214,7 @@ export function newChange(vaultBase, slug, { sessionRel = '', dateStr, simple =
186
214
  // No `specs/exemplo` placeholder: it was pure noise (always hand-deleted). When a change
187
215
  // resolves `spec_impact: required`, the author writes `specs/<capability>/spec.md` directly
188
216
  // — the delta format lives in the wk-workflow skill (and `renderChangeScaffold().specDelta`).
189
- if (!simple) {
217
+ if (!compact) {
190
218
  write('design.md', files.design);
191
219
  }
192
220
  if (!existed) captureSpecBaseline(vaultBase, dir);
@@ -512,6 +540,7 @@ export function archiveChange(vaultBase, slug, { gate = gateGreen, dateStr, adrN
512
540
  const loc = getLocale(vaultBase);
513
541
  const chDir = loc.folders.changes;
514
542
  const src = join(vaultBase, chDir, slug);
543
+ const createAdr = !isGuideCompactChange(src);
515
544
  const verdict = gate(src);
516
545
  if (!verdict.ok) return { ok: false, failing: verdict.failing || [] };
517
546
 
@@ -524,14 +553,17 @@ export function archiveChange(vaultBase, slug, { gate = gateGreen, dateStr, adrN
524
553
  const adrRel = join(adrDirRel, `ADR-${num}-${slug}.md`);
525
554
 
526
555
  // Validate every later mutation target before spec promotion can change living state.
527
- const [checkedSource, checkedDestination] = assertVaultPathsSafe(vaultBase, [
556
+ const mutationTargets = [
528
557
  { path: src, allowMissing: false, expectedType: 'directory', label: 'change a arquivar' },
529
558
  { path: destAbs, expectedType: 'directory', label: 'destino da change arquivada' },
530
559
  { path: archiveRoot, expectedType: 'directory', label: 'raiz de changes arquivadas' },
531
- { path: join(vaultBase, adrDirRel), expectedType: 'directory', label: 'pasta mensal de ADR' },
532
- { path: join(vaultBase, adrRel), expectedType: 'file', label: 'ADR da change arquivada' },
533
560
  { path: join(vaultBase, POINTER), expectedType: 'file', label: 'ponteiro CURRENT_CHANGE.md' },
534
- ]);
561
+ ...(createAdr ? [
562
+ { path: join(vaultBase, adrDirRel), expectedType: 'directory', label: 'pasta mensal de ADR' },
563
+ { path: join(vaultBase, adrRel), expectedType: 'file', label: 'ADR da change arquivada' },
564
+ ] : []),
565
+ ];
566
+ const [checkedSource, checkedDestination] = assertVaultPathsSafe(vaultBase, mutationTargets);
535
567
  assertVaultPathsSafe(vaultBase, [
536
568
  { path: join(checkedSource.target, 'proposta.md'), expectedType: 'file', label: 'proposta da change' },
537
569
  { path: join(checkedSource.target, 'tarefas.md'), expectedType: 'file', label: 'tarefas da change' },
@@ -597,7 +629,7 @@ export function archiveChange(vaultBase, slug, { gate = gateGreen, dateStr, adrN
597
629
 
598
630
  // ADR goes in the same dated month folder as session-derived decisions (04-Decisões/ano/MM-MMM/)
599
631
  // — not the year root — so all ADRs sit together in the vault's convention.
600
- mkdirVaultPath(vaultBase, join(vaultBase, adrDirRel), { label: 'pasta mensal de ADR' });
632
+ if (createAdr) mkdirVaultPath(vaultBase, join(vaultBase, adrDirRel), { label: 'pasta mensal de ADR' });
601
633
  const capLine = promoted.length
602
634
  ? `\n\nCapabilities: ${promoted.map((c) => wikilinkFromRel(join(loc.folders.specs, c))).join(', ')}.`
603
635
  : '';
@@ -605,7 +637,7 @@ export function archiveChange(vaultBase, slug, { gate = gateGreen, dateStr, adrN
605
637
  // Rastro auditável (0.31.0): um archive forçado ou sem prova declarada fica marcado no ADR.
606
638
  const flagLines = `${adrFlags.forced ? '\nforced: true' : ''}${adrFlags.trivial ? '\ntrivial: true' : ''}`;
607
639
  const forcedNote = adrFlags.forced ? '\n\n> ⚠️ Arquivada com --force — havia tarefa(s) aberta(s) pulada(s) no gate.' : '';
608
- writeVaultFileSync(vaultBase, join(vaultBase, adrRel), `---
640
+ if (createAdr) writeVaultFileSync(vaultBase, join(vaultBase, adrRel), `---
609
641
  type: decision
610
642
  status: accepted
611
643
  date: ${dateStr}${flagLines}
@@ -625,7 +657,7 @@ Mudança ${changeWikilink} concluída e arquivada.${capLine}${reqLine}${forcedNo
625
657
  // Only clear the pointer when the archived change IS the active one — archiving some other
626
658
  // slug explicitly must not blank the pointer of a different, still-active change.
627
659
  if (activeChange(vaultBase) === slug) clearActiveChange(vaultBase);
628
- return { ok: true, failing: [], archivedRel: destRel, adrRel, promoted, specWarnings, linksRewritten };
660
+ return { ok: true, failing: [], archivedRel: destRel, adrRel: createAdr ? adrRel : '', promoted, specWarnings, linksRewritten };
629
661
  }
630
662
 
631
663
  // --- reescrita de wikilinks pós-move (0.35.0) ----------------------------------
@@ -12,6 +12,7 @@ import {
12
12
  profileSentinelId,
13
13
  resolveHookOperatingProfile,
14
14
  } from './operating-profile-runtime.mjs';
15
+ import { activeDelivery } from '../src/delivery.mjs';
15
16
 
16
17
  const CODE_EXT = /\.(ts|tsx|js|jsx|mjs|cjs|py|prisma|sql|go|rs|java|cs)$/i;
17
18
 
@@ -32,6 +33,7 @@ export function warnDecision(filePath, {
32
33
  if (!policy.requiresChange) return null;
33
34
  if (!filePath || !isCodeFile(filePath)) return null;
34
35
  if (activeChange(vaultBase)) return null;
36
+ if (activeDelivery(vaultBase)) return null;
35
37
  const abs = norm(isAbsolute(filePath) ? filePath : resolve(cwd, filePath));
36
38
  // Dentro do vault (NTFS é case-insensitive) ou em dirs de config de agente: não é código do projeto.
37
39
  if (abs.toLowerCase().startsWith(`${norm(resolve(vaultBase)).toLowerCase()}/`)) return null;
@@ -102,6 +102,8 @@ export function checkHarness(vaultBase, projectRoot) {
102
102
  const CHANGES_DIR = loc.folders.changes;
103
103
  const errors = [];
104
104
  const warnings = [];
105
+ const attention = [];
106
+ const repairable = [];
105
107
 
106
108
  // 1. wendkeep.sensors.json well-formed.
107
109
  const sensorsPath = join(projectRoot, 'wendkeep.sensors.json');
@@ -114,7 +116,7 @@ export function checkHarness(vaultBase, projectRoot) {
114
116
  }
115
117
 
116
118
  const specState = checkSpecsState(vaultBase);
117
- if (specState.missing) warnings.push('SPECS_STATE ausente — rode `wendkeep spec migrate`; 07-Specs deve ser gerado/read-only');
119
+ if (specState.missing) repairable.push('SPECS_STATE ausente — rode `wendkeep spec migrate`; 07-Specs deve ser gerado/read-only');
118
120
  else if (!specState.ok) errors.push(`07-Specs alterado fora do WendKeep: ${specState.changed.join(', ')} — mova a alteração para 08-Mudanças/<change>/specs`);
119
121
 
120
122
  // 2/3. Changes: malformed dirs; the active change's deltas add to knownReqs.
@@ -127,12 +129,18 @@ export function checkHarness(vaultBase, projectRoot) {
127
129
  try { entries = readdirSync(dir); } catch { continue; } // a file, not a change dir
128
130
  if (!entries.includes('proposta.md')) { errors.push(`change sem proposta.md: ${name}`); continue; }
129
131
  const impact = validateSpecImpact(dir);
130
- errors.push(...impact.errors.map((e) => `${name}: ${e}`));
131
- warnings.push(...impact.warnings.map((w) => `${name}: ${w}`));
132
+ for (const error of impact.errors) {
133
+ const rendered = `${name}: ${error}`;
134
+ if (/spec_impact pending/i.test(error)) attention.push(rendered);
135
+ else errors.push(rendered);
136
+ }
137
+ attention.push(...impact.warnings.map((w) => `${name}: ${w}`));
132
138
  let tasks = [];
133
139
  let tarefasMd = '';
134
140
  try { tarefasMd = readFileSync(join(dir, 'tarefas.md'), 'utf8'); tasks = parseTasks(tarefasMd); } catch { /* sem tarefas */ }
135
141
  const reqIds = [...new Set(tasks.flatMap((t) => t.reqs ?? []))];
142
+ const openTasks = tasks.filter((task) => !task.done);
143
+ if (openTasks.length) attention.push(`${name}: ${openTasks.length} tarefa(s) aberta(s)`);
136
144
  const effective = buildEffectiveRequirementPackage(vaultBase, dir, reqIds);
137
145
  errors.push(...effective.errors.map((e) => `${name}: spec efetiva inválida: ${e}`));
138
146
  if (effective.missing.length) errors.push(`req órfão em ${name}: ${effective.missing.map((id) => `[req:${id}]`).join(', ')} não existe na spec efetiva`);
@@ -140,7 +148,7 @@ export function checkHarness(vaultBase, projectRoot) {
140
148
  try { verdict = JSON.parse(readFileSync(join(dir, 'verdict.json'), 'utf8')); } catch { /* sem verdict */ }
141
149
  if (verdict && reqIds.length) {
142
150
  const v = evaluateVerdict(verdict, reqIds, { tasksHash: tasksHashOf(tarefasMd), effectiveSpecHash: effective.hash });
143
- if (!v.ok) warnings.push(`verdict stale/incompleto em ${name}${v.missing.length ? `: falta cobrir ${v.missing.join(', ')}` : ''}`);
151
+ if (!v.ok) attention.push(`verdict stale/incompleto em ${name}${v.missing.length ? `: falta cobrir ${v.missing.join(', ')}` : ''}`);
144
152
  }
145
153
  }
146
154
 
@@ -152,7 +160,7 @@ export function checkHarness(vaultBase, projectRoot) {
152
160
  }
153
161
  }
154
162
 
155
- return { errors, warnings };
163
+ return { errors, warnings, attention, repairable };
156
164
  }
157
165
 
158
166
  // --- diagnóstico de links do grafo (read-only, reusa os reparos em dry-run) -----
@@ -504,7 +504,7 @@ export function checkMemoryBundle(vaultBase, { registry } = {}) {
504
504
  ? '1 conflito ativo'
505
505
  : `${activeConflicts.length} conflitos ativos`;
506
506
  const purposes = groupConflictPurposes(activeConflicts).join('; ');
507
- failures.push(`${label} (${purposes}). Existem versões concorrentes e nenhum dado foi escolhido automaticamente. Conflito semântico exige curadoria humana; memory repair não escolhe vencedor. Próximo passo: ${memoryCurateCommand(vaultBase)}. Inventário avançado: ${memoryCandidatesCommand(vaultBase)}.`);
507
+ warnings.push(`${label} (${purposes}). Existem versões concorrentes e nenhum dado foi escolhido automaticamente. Conflito semântico degrada somente as chaves afetadas e exige curadoria humana; memory repair não escolhe vencedor. Próximo passo: ${memoryCurateCommand(vaultBase)}. Inventário avançado: ${memoryCandidatesCommand(vaultBase)}.`);
508
508
  }
509
509
  if (outbox.count) warnings.push(`${outbox.count} evento(s) pendente(s) na outbox; execute o projector quando seguro.`);
510
510
  if (ordinaryCandidates.length) warnings.push(`${ordinaryCandidates.length} candidate(s) aguardando curadoria humana.`);
@@ -514,7 +514,7 @@ export function checkMemoryBundle(vaultBase, { registry } = {}) {
514
514
  const semantic = bundle.semantic || {};
515
515
  return {
516
516
  ok,
517
- status: ok ? (warnings.length ? 'warning' : 'healthy') : 'blocked',
517
+ status: ok ? (activeConflicts.length ? 'degraded' : warnings.length ? 'warning' : 'healthy') : 'blocked',
518
518
  failures,
519
519
  warnings,
520
520
  metrics: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wendkeep",
3
- "version": "0.72.1",
3
+ "version": "0.73.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
- "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/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 web/observer/app.mjs && node --check hooks/observer-publish.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-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",
44
+ "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/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/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-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",
45
45
  "test": "node --test --test-concurrency=2",
46
46
  "test:core": "node scripts/run-scope.mjs core",
47
47
  "release": "node scripts/release.mjs",
@@ -48,9 +48,9 @@ Usage:
48
48
  package first (npm i -D wendkeep@latest); a running process
49
49
  cannot replace itself. · --vault P · --profile <name> · --yes.
50
50
 
51
- wendkeep doctor [--vault P] Run a vault health check.
51
+ wendkeep doctor [--vault P] Health check. --scope core|runtime · --strict for CI/release.
52
52
  wendkeep observer <sub> Local multi-project Observer: serve | register | publish | status.
53
- wendkeep change <sub> Change lifecycle: new [--simple] | use | bind <slug> --session <id> | continue | list | show |
53
+ wendkeep change <sub> Change lifecycle: new [--simple|--guide] | use | bind <slug> --session <id> | continue | list | show |
54
54
  status | done <id> | undone <id> | diff | archive [--force] | abandon | relink | backlink.
55
55
  archive exige verdict (rode verify --deep); abandon descarta sem ADR.
56
56
  backlink [--apply]: injeta o backlink pro proposta em design/tarefas/spec órfãos (open + _arquivo).
@@ -62,6 +62,8 @@ Usage:
62
62
  the project default. The Vault/session/memory core is always active.
63
63
  wendkeep flow <sub> Low-ceremony E -> V contract: start | status | show | finish | promote.
64
64
  FLOW records scope, sensors and a receipt without creating a change.
65
+ wendkeep delivery <sub> Operational delivery: start | status | finish | abandon.
66
+ Records authorization and an append-only receipt; never creates a change/spec/ADR.
65
67
  wendkeep spec <sub> Specs: list | show | effective [--change] [--json] | migrate | rebase.
66
68
  wendkeep sensors <sub> list | add <id> "<command>" [--severity --type --report].
67
69
  wendkeep cost [opts] Aggregate AI-coding spend across the vault's sessions.
@@ -184,6 +186,9 @@ async function main(argv) {
184
186
  if (cmd === 'flow') {
185
187
  const { FLOW_HELP } = await import('../../../src/flow.mjs');
186
188
  process.stdout.write(FLOW_HELP);
189
+ } else if (cmd === 'delivery') {
190
+ const { DELIVERY_HELP } = await import('../../../src/delivery.mjs');
191
+ process.stdout.write(DELIVERY_HELP);
187
192
  } else if (cmd === 'profile') {
188
193
  const { PROFILE_HELP } = await import('../../../src/profile.mjs');
189
194
  process.stdout.write(PROFILE_HELP);
@@ -273,6 +278,11 @@ async function main(argv) {
273
278
  process.exit(await runFlow(rest));
274
279
  break;
275
280
  }
281
+ case 'delivery': {
282
+ const { runDelivery } = await import('../../../src/delivery.mjs');
283
+ process.exit(runDelivery(rest));
284
+ break;
285
+ }
276
286
  case 'theme': {
277
287
  const { runTheme } = await import('../../../src/theme.mjs');
278
288
  runTheme(rest);
package/src/change.mjs CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  relinkChanges,
17
17
  backfillArtifactLinks,
18
18
  scaffoldPlaceholders,
19
+ isGuideCompactChange,
19
20
  } from '../hooks/change-core.mjs';
20
21
  import { evaluateGate, requiredSensors } from '../hooks/sensors-core.mjs';
21
22
  import { buildEffectiveRequirementPackage, evaluateVerdict, formatOrphanReqs, tasksHashOf, parseSpecsList, parseDelta, parseRequirements, applyDelta, validateSpecImpact } from '../hooks/spec-core.mjs';
@@ -61,7 +62,9 @@ export function runChange(argv) {
61
62
  // G2: link the active session into the proposta's source: (graph edge proposta->sessão).
62
63
  let sessionRel = '';
63
64
  try { sessionRel = readControl(vaultBase).session_file || ''; } catch { /* sem control */ }
64
- const r = newChange(vaultBase, slug, { dateStr: today(), simple: rest.includes('--simple'), sessionRel });
65
+ const r = newChange(vaultBase, slug, {
66
+ dateStr: today(), simple: rest.includes('--simple'), guide: rest.includes('--guide'), sessionRel,
67
+ });
65
68
  process.stdout.write(`change ${r.created ? 'created' : 'exists'}: ${r.rel} (active)\n`);
66
69
  process.exit(0);
67
70
  }
@@ -97,7 +100,7 @@ export function runChange(argv) {
97
100
  let sessionRel = '';
98
101
  try { sessionRel = readControl(vaultBase).session_file || ''; } catch { /* no control */ }
99
102
  const r = continueChange(vaultBase, archivedSlug, newSlug, {
100
- dateStr: today(), simple: rest.includes('--simple'), sessionRel,
103
+ dateStr: today(), simple: rest.includes('--simple'), guide: rest.includes('--guide'), sessionRel,
101
104
  });
102
105
  if (!r.ok) { process.stderr.write(`wendkeep change continue: ${r.error}\n`); process.exit(2); }
103
106
  process.stdout.write(`change created: ${r.rel} (continues ${r.archived}; active)\n`);
@@ -287,13 +290,16 @@ export function runChange(argv) {
287
290
  try { tasks = parseTasks(readFileSync(join(vaultBase, getLocale(vaultBase).folders.changes, slug, 'tarefas.md'), 'utf8')); } catch { /* sem tarefas */ }
288
291
  const forced = rest.includes('--force') && tasks.some((t) => !t.done);
289
292
  const trivial = !tasks.some((t) => t.req) && !tasks.some((t) => t.sensor);
290
- if (trivial) process.stderr.write('aviso: change trivial (sem [req:]/[sensor:]) — ADR marcado trivial: true\n');
293
+ const compactGuide = isGuideCompactChange(join(vaultBase, getLocale(vaultBase).folders.changes, slug));
294
+ if (trivial) process.stderr.write(compactGuide
295
+ ? 'aviso: GUIDE compacta sem [req:]/[sensor:] — resultado permanece auditável no archive, sem ADR automático\n'
296
+ : 'aviso: change trivial (sem [req:]/[sensor:]) — ADR marcado trivial: true\n');
291
297
  const r = archiveChange(vaultBase, slug, { dateStr: today(), adrNum: getNextAdrNumber(vaultBase), gate, adrFlags: { forced, trivial } });
292
298
  if (!r.ok) {
293
299
  process.stderr.write(`change archive BLOCKED (gate): ${r.failing.join('; ')}\n`);
294
300
  process.exit(1);
295
301
  }
296
- process.stdout.write(`archived: ${r.archivedRel}; ADR: ${r.adrRel}\n`);
302
+ process.stdout.write(`archived: ${r.archivedRel}${r.adrRel ? `; ADR: ${r.adrRel}` : '; GUIDE compacta: sem ADR'}\n`);
297
303
  if (r.promoted && r.promoted.length) process.stdout.write(`specs promovidas: ${r.promoted.join(', ')}\n`);
298
304
  if (r.specWarnings && r.specWarnings.length) for (const w of r.specWarnings) process.stderr.write(` aviso spec: ${w}\n`);
299
305
  process.exit(0);