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.
- package/CHANGELOG.md +30 -0
- package/README.en.md +28 -11
- package/README.md +28 -11
- package/docs/en/commands/changes-and-verification.md +10 -5
- package/docs/en/commands/maintenance-and-diagnostics.md +17 -9
- package/docs/en/commands/operating-profiles.md +28 -3
- package/docs/pt-BR/commands/changes-and-verification.md +10 -5
- package/docs/pt-BR/commands/maintenance-and-diagnostics.md +12 -5
- package/docs/pt-BR/commands/operating-profiles.md +28 -3
- package/hooks/brain-inject.mjs +6 -6
- package/hooks/change-context.mjs +11 -0
- package/hooks/change-core.mjs +53 -21
- package/hooks/change-warn.mjs +2 -0
- package/hooks/harness-doctor.mjs +13 -5
- package/hooks/vault-health.mjs +2 -2
- package/package.json +2 -2
- package/packages/cli/src/index.mjs +12 -2
- package/src/change.mjs +10 -4
- package/src/delivery.mjs +303 -0
- package/src/doctor.mjs +47 -10
- package/src/release-provenance.mjs +47 -0
- package/src/skills-seed.mjs +25 -9
- package/src/sync-defs.mjs +5 -2
- package/src/sync.mjs +2 -2
- package/src/work-kind.mjs +62 -0
package/hooks/change-context.mjs
CHANGED
|
@@ -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;
|
package/hooks/change-core.mjs
CHANGED
|
@@ -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
|
|
25
|
-
const
|
|
26
|
-
|
|
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
|
-
${
|
|
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
|
-
|
|
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', [
|
|
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', [
|
|
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 (!
|
|
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
|
|
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) ----------------------------------
|
package/hooks/change-warn.mjs
CHANGED
|
@@ -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;
|
package/hooks/harness-doctor.mjs
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
131
|
-
|
|
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)
|
|
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) -----
|
package/hooks/vault-health.mjs
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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]
|
|
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, {
|
|
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
|
-
|
|
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}
|
|
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);
|