wendkeep 0.32.0 → 0.34.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 +51 -0
- package/README.md +14 -11
- package/README.pt-BR.md +3 -3
- package/bin/wendkeep.mjs +5 -4
- package/hooks/brain-inject.mjs +3 -3
- package/hooks/change-context.mjs +6 -15
- package/hooks/change-core.mjs +105 -22
- package/hooks/harness-doctor.mjs +16 -32
- package/hooks/spec-core.mjs +163 -3
- package/package.json +1 -1
- package/src/change.mjs +62 -9
- package/src/doctor.mjs +6 -0
- package/src/init.mjs +10 -5
- package/src/skills-seed.mjs +35 -24
- package/src/spec.mjs +68 -2
- package/src/sync-defs.mjs +84 -14
- package/src/verify.mjs +27 -3
package/hooks/spec-core.mjs
CHANGED
|
@@ -11,6 +11,14 @@ export function tasksHashOf(md) {
|
|
|
11
11
|
return createHash('sha1').update(String(md)).digest('hex').slice(0, 12);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export function contentHashOf(value) {
|
|
15
|
+
return createHash('sha256').update(String(value)).digest('hex');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const SPECS_STATE_FILE = '.brain/SPECS_STATE.json';
|
|
19
|
+
export const SPEC_BASELINE_FILE = '.spec-base.json';
|
|
20
|
+
export const MANAGED_SPEC_MARKER = '<!-- wendkeep:managed-spec — generated from 08-Mudanças; do not edit directly -->';
|
|
21
|
+
|
|
14
22
|
// Parse is BILINGUAL always (mixed vaults never break); rendering follows the vault locale.
|
|
15
23
|
const REQ_RE = /^### (?:Requisito|Requirement):\s*(.+)$/gm;
|
|
16
24
|
|
|
@@ -21,7 +29,7 @@ export function parseRequirements(md) {
|
|
|
21
29
|
for (let i = 0; i < matches.length; i += 1) {
|
|
22
30
|
const raw = matches[i][1].trim();
|
|
23
31
|
// Identity is the ID (e.g. GATE-1) when the heading is "<ID> — <nome>"; else the whole text.
|
|
24
|
-
const idM = raw.match(/^([A-Z][A-Z0-9]*-\d+)\s*—\s*(.+)$/);
|
|
32
|
+
const idM = raw.match(/^([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*-\d+)\s*—\s*(.+)$/);
|
|
25
33
|
const id = idM ? idM[1] : null;
|
|
26
34
|
const name = idM ? idM[2].trim() : raw;
|
|
27
35
|
const start = matches[i].index + matches[i][0].length;
|
|
@@ -75,7 +83,148 @@ export function applyDelta(reqs, delta) {
|
|
|
75
83
|
export function renderSpec(capability, reqs, { footer, reqHeading = 'Requisito' } = {}) {
|
|
76
84
|
const blocks = reqs.map((r) => `### ${reqHeading}: ${r.id ? `${r.id} — ${r.name}` : r.name}\n${r.body}`).join('\n\n');
|
|
77
85
|
const foot = footer ? `\n\n> ${footer}\n` : '\n';
|
|
78
|
-
return
|
|
86
|
+
return `${MANAGED_SPEC_MARKER}\n---\ntype: spec\ncssclasses:\n - topic-spec\ntags:\n - spec\n---\n\n# ${capability}\n\n## Requisitos\n\n${blocks}${foot}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readLivingSpecs(vaultBase) {
|
|
90
|
+
const specsDir = join(vaultBase, getLocale(vaultBase).folders.specs);
|
|
91
|
+
const specs = {};
|
|
92
|
+
let files = [];
|
|
93
|
+
try { files = readdirSync(specsDir).filter((f) => f.endsWith('.md') && f !== 'README.md'); } catch { return specs; }
|
|
94
|
+
for (const file of files) {
|
|
95
|
+
const capability = file.replace(/\.md$/, '');
|
|
96
|
+
const md = readFileSync(join(specsDir, file), 'utf8');
|
|
97
|
+
specs[capability] = {
|
|
98
|
+
hash: contentHashOf(md),
|
|
99
|
+
requirements: Object.fromEntries(parseRequirements(md).map((r) => [r.id || r.name, contentHashOf(JSON.stringify(r))])),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return specs;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function livingSpecCapabilities(vaultBase) {
|
|
106
|
+
return Object.keys(readLivingSpecs(vaultBase));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function adoptSpecsState(vaultBase) {
|
|
110
|
+
const state = { version: 1, generatedAt: new Date().toISOString(), specs: readLivingSpecs(vaultBase) };
|
|
111
|
+
ensureDir(join(vaultBase, '.brain'));
|
|
112
|
+
writeFileSync(join(vaultBase, SPECS_STATE_FILE), `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
113
|
+
return state;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function readSpecsState(vaultBase) {
|
|
117
|
+
try { return JSON.parse(readFileSync(join(vaultBase, SPECS_STATE_FILE), 'utf8')); } catch { return null; }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function checkSpecsState(vaultBase) {
|
|
121
|
+
const recorded = readSpecsState(vaultBase);
|
|
122
|
+
if (!recorded) return { ok: false, missing: true, changed: [] };
|
|
123
|
+
const current = readLivingSpecs(vaultBase);
|
|
124
|
+
const names = new Set([...Object.keys(recorded.specs || {}), ...Object.keys(current)]);
|
|
125
|
+
const changed = [...names].filter((name) => recorded.specs?.[name]?.hash !== current[name]?.hash);
|
|
126
|
+
return { ok: changed.length === 0, missing: false, changed, current, recorded };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function recordPromotedSpecs(vaultBase, capabilities) {
|
|
130
|
+
const existing = readSpecsState(vaultBase);
|
|
131
|
+
if (!existing) return adoptSpecsState(vaultBase);
|
|
132
|
+
const current = readLivingSpecs(vaultBase);
|
|
133
|
+
const specs = { ...(existing.specs || {}) };
|
|
134
|
+
for (const capability of capabilities) {
|
|
135
|
+
if (current[capability]) specs[capability] = current[capability];
|
|
136
|
+
else delete specs[capability];
|
|
137
|
+
}
|
|
138
|
+
const state = { version: 1, generatedAt: new Date().toISOString(), specs };
|
|
139
|
+
writeFileSync(join(vaultBase, SPECS_STATE_FILE), `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
140
|
+
return state;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function captureSpecBaseline(vaultBase, changeDir, { refresh = false } = {}) {
|
|
144
|
+
const path = join(changeDir, SPEC_BASELINE_FILE);
|
|
145
|
+
if (!refresh && existsSync(path)) {
|
|
146
|
+
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { /* rebuild malformed baseline */ }
|
|
147
|
+
}
|
|
148
|
+
const baseline = { version: 1, capturedAt: new Date().toISOString(), specs: readLivingSpecs(vaultBase) };
|
|
149
|
+
writeFileSync(path, `${JSON.stringify(baseline, null, 2)}\n`, 'utf8');
|
|
150
|
+
return baseline;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function specConflicts(vaultBase, changeDir, capabilities = discoverSpecDeltas(changeDir)) {
|
|
154
|
+
let baseline = null;
|
|
155
|
+
try { baseline = JSON.parse(readFileSync(join(changeDir, SPEC_BASELINE_FILE), 'utf8')); } catch { /* legacy change */ }
|
|
156
|
+
if (!baseline) return [];
|
|
157
|
+
const current = readLivingSpecs(vaultBase);
|
|
158
|
+
const conflicts = [];
|
|
159
|
+
for (const capability of capabilities) {
|
|
160
|
+
let delta;
|
|
161
|
+
try { delta = parseDelta(readFileSync(join(changeDir, 'specs', capability, 'spec.md'), 'utf8')); } catch { continue; }
|
|
162
|
+
const baseReqs = baseline.specs?.[capability]?.requirements || {};
|
|
163
|
+
const currentReqs = current[capability]?.requirements || {};
|
|
164
|
+
for (const req of delta.added || []) {
|
|
165
|
+
const key = req.id || req.name;
|
|
166
|
+
if (!(key in baseReqs) && key in currentReqs) conflicts.push(`${capability}:${key} foi adicionado por outra change`);
|
|
167
|
+
}
|
|
168
|
+
for (const req of delta.modified || []) {
|
|
169
|
+
const key = req.id || req.name;
|
|
170
|
+
if (baseReqs[key] !== currentReqs[key]) conflicts.push(`${capability}:${key} mudou desde a abertura da change`);
|
|
171
|
+
}
|
|
172
|
+
for (const key of delta.removed || []) {
|
|
173
|
+
if (baseReqs[key] !== currentReqs[key]) conflicts.push(`${capability}:${key} mudou desde a abertura da change`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return conflicts;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function resolveEffectiveSpecs(vaultBase, changeDir, capabilities = discoverSpecDeltas(changeDir)) {
|
|
180
|
+
const specsDir = join(vaultBase, getLocale(vaultBase).folders.specs);
|
|
181
|
+
const result = [];
|
|
182
|
+
const warnings = [];
|
|
183
|
+
const errors = [];
|
|
184
|
+
for (const capability of capabilities) {
|
|
185
|
+
let living = [];
|
|
186
|
+
try { living = parseRequirements(readFileSync(join(specsDir, `${capability}.md`), 'utf8')); } catch { /* new capability */ }
|
|
187
|
+
let deltaMd = '';
|
|
188
|
+
try { deltaMd = readFileSync(join(changeDir, 'specs', capability, 'spec.md'), 'utf8'); } catch { /* unchanged living capability */ }
|
|
189
|
+
if (deltaMd && isPlaceholderDelta(deltaMd)) deltaMd = '';
|
|
190
|
+
const delta = deltaMd ? parseDelta(deltaMd) : { added: [], modified: [], removed: [] };
|
|
191
|
+
const operations = new Map(living.map((r) => [r.id || r.name, { operation: 'BASE', source: 'living' }]));
|
|
192
|
+
for (const r of delta.added) operations.set(r.id || r.name, { operation: 'ADDED', source: 'change' });
|
|
193
|
+
for (const r of delta.modified) operations.set(r.id || r.name, { operation: 'MODIFIED', source: 'change' });
|
|
194
|
+
for (const key of delta.removed) operations.delete(key);
|
|
195
|
+
const applied = applyDelta(living, delta);
|
|
196
|
+
warnings.push(...applied.warnings.map((w) => `${capability}: ${w}`));
|
|
197
|
+
for (const warning of applied.warnings) {
|
|
198
|
+
if (/^(ADDED já existe|MODIFIED inexistente|REMOVED inexistente)/.test(warning)) errors.push(`${capability}: ${warning}`);
|
|
199
|
+
}
|
|
200
|
+
result.push({
|
|
201
|
+
capability,
|
|
202
|
+
requirements: applied.reqs.map((r) => ({ ...r, capability, ...(operations.get(r.id || r.name) || { operation: 'BASE', source: 'living' }) })),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const serializable = result.map((s) => ({ capability: s.capability, requirements: s.requirements }));
|
|
206
|
+
return { specs: result, requirements: result.flatMap((s) => s.requirements), warnings, errors, hash: contentHashOf(JSON.stringify(serializable)) };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function buildEffectiveRequirementPackage(vaultBase, changeDir, reqIds = []) {
|
|
210
|
+
let listed = [];
|
|
211
|
+
try { listed = parseSpecsList(readFileSync(join(changeDir, 'proposta.md'), 'utf8')); } catch { /* caller validates change */ }
|
|
212
|
+
const changed = [...new Set([...listed, ...discoverSpecDeltas(changeDir)])];
|
|
213
|
+
const capabilities = [...new Set([...livingSpecCapabilities(vaultBase), ...changed])];
|
|
214
|
+
const effective = resolveEffectiveSpecs(vaultBase, changeDir, capabilities);
|
|
215
|
+
const byId = new Map(effective.requirements.filter((r) => r.id).map((r) => [r.id, r]));
|
|
216
|
+
const missing = reqIds.filter((id) => !byId.has(id));
|
|
217
|
+
const requirements = reqIds.map((id) => byId.get(id)).filter(Boolean);
|
|
218
|
+
const relevantCaps = new Set([...changed, ...requirements.map((r) => r.capability)]);
|
|
219
|
+
const relevant = effective.specs.filter((spec) => relevantCaps.has(spec.capability));
|
|
220
|
+
return {
|
|
221
|
+
...effective,
|
|
222
|
+
specs: relevant,
|
|
223
|
+
requirements,
|
|
224
|
+
missing,
|
|
225
|
+
changedCapabilities: changed,
|
|
226
|
+
hash: contentHashOf(JSON.stringify(relevant)),
|
|
227
|
+
};
|
|
79
228
|
}
|
|
80
229
|
|
|
81
230
|
export function parseSpecsList(propostaMd) {
|
|
@@ -160,6 +309,13 @@ export function promoteSpecs(vaultBase, changeDir, specs, { changeWikilink, date
|
|
|
160
309
|
const specsDir = loc.folders.specs;
|
|
161
310
|
const promoted = [];
|
|
162
311
|
const warnings = [];
|
|
312
|
+
const state = checkSpecsState(vaultBase);
|
|
313
|
+
const unmanaged = state.missing ? [] : state.changed.filter((capability) => specs.includes(capability));
|
|
314
|
+
if (unmanaged.length) {
|
|
315
|
+
throw new Error(`07-Specs alterado fora do WendKeep: ${unmanaged.join(', ')} — mova o delta para 08-Mudanças/<change>/specs`);
|
|
316
|
+
}
|
|
317
|
+
const conflicts = specConflicts(vaultBase, changeDir, specs);
|
|
318
|
+
if (conflicts.length) throw new Error(`conflito de spec: ${conflicts.join('; ')} — reconcilie o delta e rode \`wendkeep spec rebase --change <slug> --accept-current\``);
|
|
163
319
|
for (const cap of specs) {
|
|
164
320
|
let deltaMd;
|
|
165
321
|
try { deltaMd = readFileSync(join(changeDir, 'specs', cap, 'spec.md'), 'utf8'); }
|
|
@@ -176,13 +332,14 @@ export function promoteSpecs(vaultBase, changeDir, specs, { changeWikilink, date
|
|
|
176
332
|
writeFileSync(livePath, renderSpec(cap, applied.reqs, { footer, reqHeading: loc.reqHeading }), 'utf8');
|
|
177
333
|
promoted.push(cap);
|
|
178
334
|
}
|
|
335
|
+
recordPromotedSpecs(vaultBase, promoted);
|
|
179
336
|
return { promoted, warnings };
|
|
180
337
|
}
|
|
181
338
|
|
|
182
339
|
// Gate check for the independent verdict (Wave A). A requirement-bearing change must have
|
|
183
340
|
// a verdict that is ok and covers every declared req id. A requirement-less change passes:
|
|
184
341
|
// nothing for an independent verifier to check — the sensor gate is already the proof.
|
|
185
|
-
export function evaluateVerdict(verdict, reqIds, { tasksHash } = {}) {
|
|
342
|
+
export function evaluateVerdict(verdict, reqIds, { tasksHash, effectiveSpecHash } = {}) {
|
|
186
343
|
const ids = reqIds || [];
|
|
187
344
|
if (ids.length === 0) return { ok: true, missing: [] };
|
|
188
345
|
if (!verdict || verdict.ok !== true) return { ok: false, missing: [] };
|
|
@@ -191,6 +348,9 @@ export function evaluateVerdict(verdict, reqIds, { tasksHash } = {}) {
|
|
|
191
348
|
if (tasksHash && verdict.tasksHash && verdict.tasksHash !== tasksHash) {
|
|
192
349
|
return { ok: false, missing: [], stale: true };
|
|
193
350
|
}
|
|
351
|
+
if (effectiveSpecHash && verdict.effectiveSpecHash && verdict.effectiveSpecHash !== effectiveSpecHash) {
|
|
352
|
+
return { ok: false, missing: [], stale: true };
|
|
353
|
+
}
|
|
194
354
|
const covered = new Set((verdict.coverage || []).filter((c) => c.covered).map((c) => c.req));
|
|
195
355
|
const missing = ids.filter((r) => !covered.has(r));
|
|
196
356
|
return { ok: missing.length === 0, missing };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "A persistent-memory harness for AI coding agents on your Obsidian vault: turn-by-turn session capture plus a native, zero-dependency spec→change→verify→archive loop (sensor-gated, independent verdict, mutation discrimination). Local-first, agent-agnostic (Claude Code, Codex, Cursor…).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/change.mjs
CHANGED
|
@@ -3,8 +3,12 @@ import { readFileSync } from 'node:fs';
|
|
|
3
3
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import {
|
|
5
5
|
newChange,
|
|
6
|
+
useChange,
|
|
7
|
+
continueChange,
|
|
6
8
|
activeChange,
|
|
9
|
+
allChangesState,
|
|
7
10
|
listChanges,
|
|
11
|
+
renderOpenChanges,
|
|
8
12
|
parseTasks,
|
|
9
13
|
setTaskDone,
|
|
10
14
|
archiveChange,
|
|
@@ -12,7 +16,7 @@ import {
|
|
|
12
16
|
scaffoldPlaceholders,
|
|
13
17
|
} from '../hooks/change-core.mjs';
|
|
14
18
|
import { evaluateGate, requiredSensors } from '../hooks/sensors-core.mjs';
|
|
15
|
-
import { evaluateVerdict, tasksHashOf, parseSpecsList, parseDelta, parseRequirements, applyDelta, validateSpecImpact } from '../hooks/spec-core.mjs';
|
|
19
|
+
import { buildEffectiveRequirementPackage, evaluateVerdict, tasksHashOf, parseSpecsList, parseDelta, parseRequirements, applyDelta, validateSpecImpact } from '../hooks/spec-core.mjs';
|
|
16
20
|
import { getNextAdrNumber, readControl } from '../hooks/obsidian-common.mjs';
|
|
17
21
|
import { getLocale } from '../hooks/locale.mjs';
|
|
18
22
|
|
|
@@ -60,10 +64,36 @@ export function runChange(argv) {
|
|
|
60
64
|
process.exit(0);
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
if (sub === 'use') {
|
|
68
|
+
const slug = slugArg();
|
|
69
|
+
if (!slug) { process.stderr.write('wendkeep change use: missing <slug>\n'); process.exit(2); }
|
|
70
|
+
const r = useChange(vaultBase, slug);
|
|
71
|
+
if (!r.ok) { process.stderr.write(`wendkeep change use: ${r.error}\n`); process.exit(2); }
|
|
72
|
+
process.stdout.write(`current change: ${slug}\n`);
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (sub === 'continue') {
|
|
77
|
+
const positionals = rest.filter((a, i) => !a.startsWith('-') && !VALUE_FLAGS.has(rest[i - 1]));
|
|
78
|
+
const [archivedSlug, newSlug] = positionals;
|
|
79
|
+
if (!archivedSlug || !newSlug) {
|
|
80
|
+
process.stderr.write('wendkeep change continue: use <archived-slug> <new-slug>\n');
|
|
81
|
+
process.exit(2);
|
|
82
|
+
}
|
|
83
|
+
let sessionRel = '';
|
|
84
|
+
try { sessionRel = readControl(vaultBase).session_file || ''; } catch { /* no control */ }
|
|
85
|
+
const r = continueChange(vaultBase, archivedSlug, newSlug, {
|
|
86
|
+
dateStr: today(), simple: rest.includes('--simple'), sessionRel,
|
|
87
|
+
});
|
|
88
|
+
if (!r.ok) { process.stderr.write(`wendkeep change continue: ${r.error}\n`); process.exit(2); }
|
|
89
|
+
process.stdout.write(`change created: ${r.rel} (continues ${r.archived}; active)\n`);
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
63
93
|
if (sub === 'list') {
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
process.stdout.write(
|
|
94
|
+
const state = allChangesState(vaultBase);
|
|
95
|
+
const { archived } = listChanges(vaultBase);
|
|
96
|
+
process.stdout.write(`${renderOpenChanges(state, { tag: '' }) || 'open changes: (none)'}\n`);
|
|
67
97
|
process.stdout.write(`archived: ${archived.join(', ') || '(none)'}\n`);
|
|
68
98
|
process.exit(0);
|
|
69
99
|
}
|
|
@@ -82,8 +112,16 @@ export function runChange(argv) {
|
|
|
82
112
|
}
|
|
83
113
|
|
|
84
114
|
if (sub === 'status') {
|
|
85
|
-
const slug = slugArg()
|
|
86
|
-
if (!slug) {
|
|
115
|
+
const slug = slugArg();
|
|
116
|
+
if (!slug) {
|
|
117
|
+
const state = allChangesState(vaultBase);
|
|
118
|
+
if (!state.changes.length && !state.pointerWarning) {
|
|
119
|
+
process.stderr.write('wendkeep change status: no open changes\n');
|
|
120
|
+
process.exit(2);
|
|
121
|
+
}
|
|
122
|
+
process.stdout.write(`${renderOpenChanges(state, { tag: '' })}\n`);
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
87
125
|
const dir = join(vaultBase, getLocale(vaultBase).folders.changes, slug);
|
|
88
126
|
let tarefasMd;
|
|
89
127
|
try { tarefasMd = readFileSync(join(dir, 'tarefas.md'), 'utf8'); }
|
|
@@ -103,12 +141,16 @@ export function runChange(argv) {
|
|
|
103
141
|
if (evidence) for (const e of evidence) process.stdout.write(` ${e.status === 'green' ? '✓' : '✗'} ${e.id} (${e.severity || 'critical'})\n`);
|
|
104
142
|
else process.stdout.write('evidencia: ausente\n');
|
|
105
143
|
const reqIds = [...new Set(tasks.map((t) => t.req).filter(Boolean))];
|
|
144
|
+
const effective = buildEffectiveRequirementPackage(vaultBase, dir, reqIds);
|
|
145
|
+
if (effective.errors.length || effective.missing.length) {
|
|
146
|
+
process.stdout.write(`spec efetiva: inválida (${[...effective.errors, ...effective.missing.map((id) => `req órfão ${id}`)].join('; ')})\n`);
|
|
147
|
+
}
|
|
106
148
|
let verdict = null;
|
|
107
149
|
try { verdict = JSON.parse(readFileSync(join(dir, 'verdict.json'), 'utf8')); } catch { /* sem verdict */ }
|
|
108
150
|
if (!verdict) process.stdout.write(`verdict: ausente — rode \`wendkeep verify --deep\`${reqIds.length ? ' + wk-verify' : ' (verdict trivial automático)'}\n`);
|
|
109
151
|
else if (!reqIds.length) process.stdout.write(`verdict: ${verdict.ok === true ? 'ok (trivial)' : 'não-ok — re-verifique'}\n`);
|
|
110
152
|
else {
|
|
111
|
-
const v = evaluateVerdict(verdict, reqIds, { tasksHash: tasksHashOf(tarefasMd) });
|
|
153
|
+
const v = evaluateVerdict(verdict, reqIds, { tasksHash: tasksHashOf(tarefasMd), effectiveSpecHash: effective.hash });
|
|
112
154
|
process.stdout.write(`verdict: ${v.ok ? 'ok' : v.stale ? 'stale — re-verifique' : `incompleto: falta ${v.missing.join(', ')}`}\n`);
|
|
113
155
|
}
|
|
114
156
|
try { process.stdout.write(`mutation-round: ${readFileSync(join(dir, '.mutation-round'), 'utf8').trim()}/3\n`); } catch { /* sem rodadas */ }
|
|
@@ -185,6 +227,9 @@ export function runChange(argv) {
|
|
|
185
227
|
}
|
|
186
228
|
}
|
|
187
229
|
const reqIds = [...new Set(tasks.map((t) => t.req).filter(Boolean))];
|
|
230
|
+
const effective = buildEffectiveRequirementPackage(vaultBase, dir, reqIds);
|
|
231
|
+
if (effective.errors.length) return { ok: false, failing: [`spec efetiva inválida: ${effective.errors.join('; ')}`] };
|
|
232
|
+
if (effective.missing.length) return { ok: false, failing: [`requisito(s) órfão(s) na spec efetiva: ${effective.missing.join(', ')}`] };
|
|
188
233
|
let evidence = [];
|
|
189
234
|
try { evidence = JSON.parse(readFileSync(join(dir, 'evidencia.json'), 'utf8')); } catch { /* no evidence */ }
|
|
190
235
|
const s = evaluateGate(evidence, required);
|
|
@@ -204,8 +249,16 @@ export function runChange(argv) {
|
|
|
204
249
|
if (verdict.tasksHash && verdict.tasksHash !== hash) {
|
|
205
250
|
return { ok: false, failing: [`verdict stale (tarefas.md mudou depois da verificação) — re-verifique: \`wendkeep verify --deep\`${reqIds.length ? ' + wk-verify' : ''}`] };
|
|
206
251
|
}
|
|
252
|
+
let verification = null;
|
|
253
|
+
try { verification = JSON.parse(readFileSync(join(dir, 'verificacao.json'), 'utf8')); } catch { /* none */ }
|
|
254
|
+
if (verification?.effectiveSpecHash && verification.effectiveSpecHash !== effective.hash) {
|
|
255
|
+
return { ok: false, failing: ['pacote de verificação stale (spec efetiva mudou) — rode `wendkeep verify --deep` novamente'] };
|
|
256
|
+
}
|
|
257
|
+
if (reqIds.length && verification?.effectiveSpecHash && !verdict.effectiveSpecHash) {
|
|
258
|
+
return { ok: false, failing: ['verdict sem effectiveSpecHash — rode a skill wk-verify novamente'] };
|
|
259
|
+
}
|
|
207
260
|
if (reqIds.length) {
|
|
208
|
-
const v = evaluateVerdict(verdict, reqIds, { tasksHash: hash });
|
|
261
|
+
const v = evaluateVerdict(verdict, reqIds, { tasksHash: hash, effectiveSpecHash: effective.hash });
|
|
209
262
|
if (!v.ok) {
|
|
210
263
|
if (v.stale) return { ok: false, failing: ['verdict stale (tarefas.md mudou depois da verificação) — re-verifique: `wendkeep verify --deep` + wk-verify'] };
|
|
211
264
|
return { ok: false, failing: [`verdict incompleto: falta ${v.missing.join(', ')}`] };
|
|
@@ -240,6 +293,6 @@ export function runChange(argv) {
|
|
|
240
293
|
process.exit(0);
|
|
241
294
|
}
|
|
242
295
|
|
|
243
|
-
process.stderr.write(`wendkeep change: unknown subcommand "${sub}". Known: new, list, show, status, done, undone, diff, archive, abandon.\n`);
|
|
296
|
+
process.stderr.write(`wendkeep change: unknown subcommand "${sub}". Known: new, use, continue, list, show, status, done, undone, diff, archive, abandon.\n`);
|
|
244
297
|
process.exit(2);
|
|
245
298
|
}
|
package/src/doctor.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { existsSync } from 'node:fs';
|
|
|
5
5
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { checkHarness } from '../hooks/harness-doctor.mjs';
|
|
8
|
+
import { checkSyncDefs } from './sync-defs.mjs';
|
|
8
9
|
|
|
9
10
|
export function runDoctor(argv) {
|
|
10
11
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -40,6 +41,11 @@ export function runDoctor(argv) {
|
|
|
40
41
|
|
|
41
42
|
// 2. Harness integrity (Wave B).
|
|
42
43
|
const { errors, warnings } = checkHarness(vaultBase, projectRoot);
|
|
44
|
+
const defs = checkSyncDefs(vaultBase, projectRoot);
|
|
45
|
+
if (!defs.ok) {
|
|
46
|
+
warnings.push(...defs.issues.map((issue) => `defs: ${issue}`));
|
|
47
|
+
warnings.push('defs stale — rode `wendkeep sync-defs --reseed` e reinicie Claude Code/Codex');
|
|
48
|
+
}
|
|
43
49
|
process.stdout.write(`\n[harness] ${errors.length} erro(s), ${warnings.length} aviso(s)\n`);
|
|
44
50
|
for (const e of errors) process.stdout.write(` ✗ ${e}\n`);
|
|
45
51
|
for (const w of warnings) process.stdout.write(` ! ${w}\n`);
|
package/src/init.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// OBSIDIAN_VAULT_PATH into .claude/settings.json, and adds the mcpvault server to
|
|
4
4
|
// .mcp.json. Idempotent: re-running only adds what is missing.
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { basename, isAbsolute, join, resolve } from 'node:path';
|
|
8
8
|
import { createInterface } from 'node:readline/promises';
|
|
9
9
|
import {
|
|
@@ -40,6 +40,7 @@ import { seedDefinitions, syncDefs } from './sync-defs.mjs';
|
|
|
40
40
|
import { seedWkSkills } from './skills-seed.mjs';
|
|
41
41
|
import { LOCALES, DEFAULT_LOCALE, getLocale, clearLocaleCache, vaultFolders } from '../hooks/locale.mjs';
|
|
42
42
|
import { seedDotcontext, globalHasDotcontext, resolveDotcontextSkipMcp, renderSensorsJson } from './dotcontext-seed.mjs';
|
|
43
|
+
import { adoptSpecsState, SPECS_STATE_FILE } from '../hooks/spec-core.mjs';
|
|
43
44
|
|
|
44
45
|
function parseArgs(argv) {
|
|
45
46
|
const args = { mcp: true, yes: false, force: false };
|
|
@@ -278,7 +279,7 @@ const MESSAGES = {
|
|
|
278
279
|
colorsOn: 'wendkeep-colors (snippet + grupos do grafo)',
|
|
279
280
|
taxonomy: (n, c, loc, readme, views) => ` [1/4] taxonomia do vault: ${n} pastas (${c} criadas, locale ${loc})${readme}, .brain + change/spec + sensores semeados${views}`,
|
|
280
281
|
readmeCreated: ', README.md criado', viewsNote: (n) => `, ${n} view(s) + dashboard`,
|
|
281
|
-
defs: (s, a) => ` defs entregues: ${s} skill(s) -> .claude/skills, ${a} agent(s) -> .codex/agents`,
|
|
282
|
+
defs: (s, a) => ` defs entregues: ${s} skill(s) -> .claude/skills + .agents/skills, ${a} agent(s) -> .codex/agents`,
|
|
282
283
|
settingsBadJson: (p) => ` [2/4] settings.json existe mas não é JSON válido -> escrevi ${p}.new (mescle à mão)`,
|
|
283
284
|
settings: (verb, added, bak) => ` [2/4] settings.json ${verb} (${added} hook(s) wirados, OBSIDIAN_VAULT_PATH setado${bak})`,
|
|
284
285
|
mcpBadJson: (p) => ` [3/4] .mcp.json existe mas não é JSON válido -> escrevi ${p}.new (mescle à mão)`,
|
|
@@ -303,7 +304,7 @@ const MESSAGES = {
|
|
|
303
304
|
colorsOn: 'wendkeep-colors (snippet + graph groups)',
|
|
304
305
|
taxonomy: (n, c, loc, readme, views) => ` [1/4] vault taxonomy: ${n} folders (${c} created, locale ${loc})${readme}, .brain + change/spec + sensors seeded${views}`,
|
|
305
306
|
readmeCreated: ', README.md created', viewsNote: (n) => `, ${n} view(s) + dashboard`,
|
|
306
|
-
defs: (s, a) => ` defs delivered: ${s} skill(s) -> .claude/skills, ${a} agent(s) -> .codex/agents`,
|
|
307
|
+
defs: (s, a) => ` defs delivered: ${s} skill(s) -> .claude/skills + .agents/skills, ${a} agent(s) -> .codex/agents`,
|
|
307
308
|
settingsBadJson: (p) => ` [2/4] settings.json exists but is not valid JSON -> wrote ${p}.new (merge by hand)`,
|
|
308
309
|
settings: (verb, added, bak) => ` [2/4] settings.json ${verb} (${added} hook(s) wired, OBSIDIAN_VAULT_PATH set${bak})`,
|
|
309
310
|
mcpBadJson: (p) => ` [3/4] .mcp.json exists but is not valid JSON -> wrote ${p}.new (merge by hand)`,
|
|
@@ -482,8 +483,12 @@ export async function runInit(argv) {
|
|
|
482
483
|
const specsReadme = join(vaultPath, loc.folders.specs, 'README.md');
|
|
483
484
|
if (!existsSync(specsReadme)) {
|
|
484
485
|
writeFileSync(specsReadme, en
|
|
485
|
-
? `# Specs — living contract\n\
|
|
486
|
-
: `# Specs — contrato
|
|
486
|
+
? `# Specs — generated living contract\n\nRead-only: do not author here. Write deltas only in \`${loc.folders.changes}/<slug>/specs/\`; archive promotes them here.\n`
|
|
487
|
+
: `# Specs — contrato consolidado gerado\n\nSomente leitura: não edite aqui. Escreva deltas apenas em \`${loc.folders.changes}/<slug>/specs/\`; o archive promove para cá.\n`, 'utf8');
|
|
488
|
+
}
|
|
489
|
+
if (!existsSync(join(vaultPath, SPECS_STATE_FILE))) {
|
|
490
|
+
const livingSpecs = readdirSync(join(vaultPath, loc.folders.specs)).filter((name) => name.endsWith('.md') && name !== 'README.md');
|
|
491
|
+
if (!livingSpecs.length) adoptSpecsState(vaultPath);
|
|
487
492
|
}
|
|
488
493
|
const changeTpl = join(vaultPath, 'Templates', 'Change.md');
|
|
489
494
|
if (!existsSync(changeTpl)) {
|
package/src/skills-seed.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/skills-seed.mjs — native, zero-dep process skills (Pilar A: the HOW layer).
|
|
2
2
|
// Seeded into the vault's .brain/skills; distributed by `wendkeep sync-defs` to
|
|
3
|
-
// .claude/skills. Wendkeep-flavored
|
|
3
|
+
// .claude/skills + .agents/skills. Wendkeep-flavored, concise native prose.
|
|
4
4
|
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
|
|
@@ -29,8 +29,9 @@ vault cego. Exceção única: mudança trivial (typo, 1 linha).
|
|
|
29
29
|
- \`proposta.md\` — *por quê* e *o que muda* (o WHAT).
|
|
30
30
|
- \`design.md\` — a abordagem técnica.
|
|
31
31
|
- \`tarefas.md\` — a lista de tarefas \`- [ ] N.N descrição\`.
|
|
32
|
-
A mudança vira a *
|
|
33
|
-
|
|
32
|
+
A mudança vira a *atual* (ponteiro global \`.brain/CURRENT_CHANGE.md\`). Podem existir
|
|
33
|
+
várias changes abertas: hooks e \`change list/status\` mostram todas as pendências; comandos
|
|
34
|
+
sem \`--change\` usam somente a atual.
|
|
34
35
|
Antes de implementar, resolva \`spec_impact\` na proposta:
|
|
35
36
|
- \`required\`: liste a capability em \`specs:\` e preencha
|
|
36
37
|
\`specs/<capability>/spec.md\` com ADDED/MODIFIED/REMOVED; ligue tarefas com \`[req:ID]\`.
|
|
@@ -40,12 +41,13 @@ vault cego. Exceção única: mudança trivial (typo, 1 linha).
|
|
|
40
41
|
(teste vermelho antes do código). Marque \`- [x]\` ao concluir. Declare nas tarefas:
|
|
41
42
|
- \`[sensor:<id>]\` — a prova automatizada (roda no verify).
|
|
42
43
|
- \`[req:<ID>]\` — o requisito do spec que a tarefa satisfaz (ex.: \`[req:GATE-1]\`),
|
|
43
|
-
|
|
44
|
+
quando a change mexe numa capability. Toda autoria de spec ocorre somente em
|
|
45
|
+
\`08-Mudanças/<slug>/specs/<capability>/spec.md\`; \`07-Specs\` é gerado/read-only.
|
|
44
46
|
Ex.: \`- [ ] 2.1 valida CORE [req:MEM-1] [sensor:memory-validation]\`.
|
|
45
47
|
4. **Verify** — \`wendkeep verify\` roda os sensores → \`evidencia.json\`. Depois
|
|
46
48
|
\`wendkeep verify --deep\` monta o *pacote de verificação* pro passe independente.
|
|
47
49
|
5. **Verify deep** — a skill **wk-verify** (passe fresco, autor≠verificador) lê o pacote,
|
|
48
|
-
|
|
50
|
+
usa somente os requisitos autocontidos de \`verificacao.json\` e grava \`verdict.json\`. Change trivial (sem
|
|
49
51
|
\`[req:]\`) recebe verdict automático — pula este passe.
|
|
50
52
|
6. **Archive** — \`wendkeep change archive <slug>\`. O *gate* exige sensores verdes **E**
|
|
51
53
|
\`verdict.json\` cobrindo os \`[req:]\`. Passando, promove os deltas pro \`07-Specs\`,
|
|
@@ -53,7 +55,9 @@ vault cego. Exceção única: mudança trivial (typo, 1 linha).
|
|
|
53
55
|
|
|
54
56
|
## Regras
|
|
55
57
|
|
|
56
|
-
-
|
|
58
|
+
- Várias changes podem ficar abertas. \`CURRENT_CHANGE.md\` marca uma atual, sem esconder as
|
|
59
|
+
outras pendências. Claude, Codex ou outro agente assumem uma change existente com
|
|
60
|
+
\`wendkeep change use <slug>\` ou \`--change <slug>\` quando disponível.
|
|
57
61
|
- Se uma tarefa não precisa de prova automatizada, não declare sensor — o gate só exige
|
|
58
62
|
o que você declarou. Sem \`[sensor:]\`, o archive não trava.
|
|
59
63
|
- A proposta linka a sessão de origem; a sessão linka a mudança ativa. É de propósito:
|
|
@@ -74,7 +78,8 @@ vermelho pedindo por ele — e nunca escreva um teste que passaria sob a impleme
|
|
|
74
78
|
|
|
75
79
|
## Derive do spec, não do código
|
|
76
80
|
|
|
77
|
-
Escreva a asserção a partir do *critério de aceite*
|
|
81
|
+
Escreva a asserção a partir do *critério de aceite* da spec efetiva
|
|
82
|
+
(\`wendkeep spec effective --change <slug>\`), não lendo a
|
|
78
83
|
implementação. Cada asserção mira o resultado que o spec definiu. Escrever o teste lendo o
|
|
79
84
|
código = ele só confirma o que o código já faz, bugs inclusos.
|
|
80
85
|
|
|
@@ -211,13 +216,13 @@ nunca tivesse visto a implementação. Contexto fresco, read-only.
|
|
|
211
216
|
## O que fazer
|
|
212
217
|
|
|
213
218
|
1. Leia o pacote \`08-Mudanças/<slug>/verificacao.json\` (requisitos, tarefas, evidência).
|
|
214
|
-
2. **Re-derive a cobertura do
|
|
215
|
-
|
|
219
|
+
2. **Re-derive a cobertura do pacote autocontido** — pra cada requisito completo em
|
|
220
|
+
\`verificacao.json\`, cheque se o comportamento está coberto por um teste que discrimina (não
|
|
216
221
|
passaria sob impl errada). Evidência \`arquivo:linha\`.
|
|
217
222
|
3. Outcome check ancorado no spec: o resultado observável bate com o critério de aceite?
|
|
218
223
|
4. Grave \`08-Mudanças/<slug>/verdict.json\`:
|
|
219
|
-
\`{ "slug": "...", "ok": true, "coverage": [{ "req": "GATE-1", "covered": true, "evidence": "arquivo:linha" }], "tasksHash": "<copie do verificacao.json>", "notes": [] }\`.
|
|
220
|
-
|
|
224
|
+
\`{ "slug": "...", "ok": true, "coverage": [{ "req": "GATE-1", "covered": true, "evidence": "arquivo:linha" }], "tasksHash": "<copie do verificacao.json>", "effectiveSpecHash": "<copie do verificacao.json>", "notes": [] }\`.
|
|
225
|
+
\`tasksHash\` e \`effectiveSpecHash\` vêm do pacote — são selos de frescor; sem eles (ou com tarefas/spec alteradas
|
|
221
226
|
depois), o gate rejeita o verdict como stale.
|
|
222
227
|
|
|
223
228
|
## Regras
|
|
@@ -248,13 +253,15 @@ leaves the vault blind. Single exception: a trivial change (typo, one line).
|
|
|
248
253
|
|
|
249
254
|
1. **Explore** — understand the problem before proposing.
|
|
250
255
|
2. **Propose** — \`wendkeep change new <slug>\` scaffolds \`08-Changes/<slug>/\`
|
|
251
|
-
(proposta/design/tarefas + a \`specs/\` delta). The change becomes *
|
|
252
|
-
|
|
256
|
+
(proposta/design/tarefas + a \`specs/\` delta). The change becomes *current* through global
|
|
257
|
+
\`.brain/CURRENT_CHANGE.md\`. Multiple changes may stay open; hooks and \`change list/status\`
|
|
258
|
+
show every pending task, while commands without \`--change\` use only the current change.
|
|
253
259
|
Before implementation, resolve \`spec_impact\`: \`required\` needs the capability listed in
|
|
254
260
|
\`specs:\` plus a real \`specs/<capability>/spec.md\` delta and \`[req:ID]\` links; \`none\`
|
|
255
261
|
needs a real \`spec_impact_reason\`. \`pending\` is never ready for implementation/archive.
|
|
256
262
|
3. **Apply** — implement each task in tarefas.md with **wk-tdd** (red test first). Tag tasks:
|
|
257
263
|
\`[sensor:<id>]\` (automated proof) and \`[req:<ID>]\` (the spec requirement it satisfies).
|
|
264
|
+
Author specs only in \`08-Changes/<slug>/specs/\`; \`07-Specs\` is generated/read-only.
|
|
258
265
|
4. **Verify** — \`wendkeep verify\` runs the sensors; then \`wendkeep verify --deep\` builds
|
|
259
266
|
the verification package.
|
|
260
267
|
5. **Verify deep** — the **wk-verify** skill (fresh, author≠verifier) writes \`verdict.json\`.
|
|
@@ -263,7 +270,9 @@ leaves the vault blind. Single exception: a trivial change (typo, one line).
|
|
|
263
270
|
verdict AND no open tasks. It promotes the delta into \`07-Specs\` and mints an ADR.
|
|
264
271
|
|
|
265
272
|
## Rules
|
|
266
|
-
-
|
|
273
|
+
- Multiple changes may stay open. \`CURRENT_CHANGE.md\` marks one current change without hiding
|
|
274
|
+
other pending tasks. Any agent may take over an existing change with
|
|
275
|
+
\`wendkeep change use <slug>\` or \`--change <slug>\` where available.
|
|
267
276
|
- No \`[sensor:]\` on a task = no automated gate for it. No \`[req:]\` = no independent verdict.
|
|
268
277
|
- The graph links session ↔ change ↔ requirement ↔ decision. That is the point.
|
|
269
278
|
`;
|
|
@@ -280,7 +289,7 @@ would pass under the wrong implementation.
|
|
|
280
289
|
4. **Refactor** with the greens protecting you.
|
|
281
290
|
|
|
282
291
|
## Derive from the spec, not the code
|
|
283
|
-
Write assertions from the requirement
|
|
292
|
+
Write assertions from the effective requirement (\`wendkeep spec effective --change <slug>\`), not by reading the
|
|
284
293
|
implementation. Reading the code to write the test = it only confirms what the code already does.
|
|
285
294
|
|
|
286
295
|
## Non-shallow litmus
|
|
@@ -369,17 +378,18 @@ the author — even if you wrote the code, enter as if you'd never seen it. Fres
|
|
|
369
378
|
|
|
370
379
|
## What to do
|
|
371
380
|
1. Read the package \`08-Changes/<slug>/verificacao.json\` (requirements, tasks, evidence).
|
|
372
|
-
2. **Re-derive coverage from
|
|
381
|
+
2. **Re-derive coverage from the self-contained package** — for each complete requirement in
|
|
382
|
+
\`verificacao.json\`, check its behaviour
|
|
373
383
|
is covered by a test that discriminates (wouldn't pass under a wrong impl). \`file:line\` evidence.
|
|
374
384
|
3. Spec-anchored outcome check: does the observable result match the acceptance criterion?
|
|
375
385
|
4. Write \`08-Changes/<slug>/verdict.json\`:
|
|
376
|
-
\`{ "slug": "...", "ok": true, "coverage": [{ "req": "GATE-1", "covered": true, "evidence": "file:line" }], "tasksHash": "<copy from the package>", "notes": [] }\`.
|
|
386
|
+
\`{ "slug": "...", "ok": true, "coverage": [{ "req": "GATE-1", "covered": true, "evidence": "file:line" }], "tasksHash": "<copy from the package>", "effectiveSpecHash": "<copy from the package>", "notes": [] }\`.
|
|
377
387
|
|
|
378
388
|
## Rules
|
|
379
389
|
- **Author ≠ verifier.** On Claude, spawn a read-only sub-agent for real isolation.
|
|
380
390
|
- \`ok: false\` if any requirement lacks discriminating coverage. A gap is red, not "almost".
|
|
381
391
|
- Don't fix here — a gap becomes a fix task; re-verify after.
|
|
382
|
-
- The archive gate **requires** a fresh \`verdict.json\` (matching \`tasksHash\`) covering every \`[req:]\`.
|
|
392
|
+
- The archive gate **requires** a fresh \`verdict.json\` (matching \`tasksHash\` and \`effectiveSpecHash\`) covering every \`[req:]\`.
|
|
383
393
|
|
|
384
394
|
## Templates (in this folder)
|
|
385
395
|
- \`spec-reviewer-prompt.md\` — hand it to the verifier sub-agent you spawn (read-only, author≠verifier).
|
|
@@ -396,6 +406,7 @@ const VERDICT_TEMPLATE = `{
|
|
|
396
406
|
{ "req": "GATE-1", "covered": true, "evidence": "tests/foo.test.mjs:42" }
|
|
397
407
|
],
|
|
398
408
|
"tasksHash": "<copie de verificacao.json — selo de frescor / copy from verificacao.json — freshness seal>",
|
|
409
|
+
"effectiveSpecHash": "<copie de verificacao.json / copy from verificacao.json>",
|
|
399
410
|
"notes": []
|
|
400
411
|
}
|
|
401
412
|
`;
|
|
@@ -409,8 +420,8 @@ Claude). Ele NÃO é o autor: entra fresco, read-only, não edita nada.
|
|
|
409
420
|
Você é o verificador independente de uma mudança do wendkeep. Não escreveu este código —
|
|
410
421
|
entre como se nunca o tivesse visto. Read-only.
|
|
411
422
|
|
|
412
|
-
Leia o pacote \`08-Mudanças/<slug>/verificacao.json\` (requisitos
|
|
413
|
-
|
|
423
|
+
Leia somente o pacote autocontido \`08-Mudanças/<slug>/verificacao.json\` (requisitos completos,
|
|
424
|
+
tarefas e evidência). Não reabra \`07-Specs\`: ele ainda não contém deltas não arquivados.
|
|
414
425
|
|
|
415
426
|
Para cada \`[req:ID]\` da mudança:
|
|
416
427
|
1. Leia o **critério de aceite do requisito** no spec — NÃO leia a implementação primeiro.
|
|
@@ -421,7 +432,7 @@ Para cada \`[req:ID]\` da mudança:
|
|
|
421
432
|
|
|
422
433
|
Grave \`08-Mudanças/<slug>/verdict.json\` no formato de \`verdict-template.json\`. \`ok: false\` se
|
|
423
434
|
qualquer \`[req:]\` não tem cobertura que discrimina. Não conserte aqui — gap vira tarefa de
|
|
424
|
-
correção.
|
|
435
|
+
correção. \`tasksHash\` e \`effectiveSpecHash\` vêm do pacote; alterações posteriores deixam o verdict stale.
|
|
425
436
|
---
|
|
426
437
|
`;
|
|
427
438
|
|
|
@@ -434,8 +445,8 @@ It is NOT the author: fresh context, read-only, edits nothing.
|
|
|
434
445
|
You are the independent verifier of a wendkeep change. You did not write this code — enter as if
|
|
435
446
|
you'd never seen it. Read-only.
|
|
436
447
|
|
|
437
|
-
Read the
|
|
438
|
-
|
|
448
|
+
Read only the self-contained \`08-Changes/<slug>/verificacao.json\` package (complete requirements,
|
|
449
|
+
tasks, evidence). Do not reopen \`07-Specs\`: it does not contain unarchived deltas yet.
|
|
439
450
|
|
|
440
451
|
For each \`[req:ID]\`:
|
|
441
452
|
1. Read the requirement's **acceptance criterion** in the spec — do NOT read the implementation first.
|
|
@@ -446,7 +457,7 @@ For each \`[req:ID]\`:
|
|
|
446
457
|
|
|
447
458
|
Write \`08-Changes/<slug>/verdict.json\` in the shape of \`verdict-template.json\`. \`ok: false\` if any
|
|
448
459
|
\`[req:]\` lacks discriminating coverage. Don't fix here — a gap becomes a fix task. \`tasksHash\`
|
|
449
|
-
|
|
460
|
+
and \`effectiveSpecHash\` come from the package (freshness seals; later task/spec edits make verdict stale).
|
|
450
461
|
---
|
|
451
462
|
`;
|
|
452
463
|
|