wendkeep 0.77.0 → 0.78.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 +23 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/docs/en/commands/changes-and-verification.md +8 -1
- package/docs/en/commands/verify.md +22 -5
- package/docs/pt-BR/commands/changes-and-verification.md +8 -1
- package/docs/pt-BR/commands/verify.md +22 -6
- package/hooks/change-core.mjs +2 -1
- package/hooks/harness-doctor.mjs +51 -1
- package/hooks/spec-core.mjs +26 -8
- package/package.json +2 -2
- package/packages/harness/src/sensors-core.mjs +57 -3
- package/packages/vault/src/evidence-envelope.mjs +73 -0
- package/packages/vault/src/index.mjs +1 -0
- package/packages/vault/src/memory-handoff.mjs +46 -5
- package/packages/vault/src/vault-path-safety.mjs +11 -0
- package/schema/wendkeep.evidence-envelope-v2.schema.json +92 -0
- package/src/change.mjs +93 -10
- package/src/evidence-envelope.mjs +288 -0
- package/src/skills-seed.mjs +11 -5
- package/src/verify.mjs +85 -22
package/src/skills-seed.mjs
CHANGED
|
@@ -281,7 +281,8 @@ nunca tivesse visto a implementação. Contexto fresco, read-only.
|
|
|
281
281
|
(isolamento real). Nos outros, entre num contexto limpo e re-derive do spec, não da memória.
|
|
282
282
|
- \`ok: false\` se algum requisito não tem cobertura que discrimina. Gap não é "quase lá" — é vermelho.
|
|
283
283
|
- Não conserte aqui. Gap vira tarefa de correção na change; re-verifica depois.
|
|
284
|
-
- O gate do \`archive\` **exige** \`verdict.json\` com \`ok\` cobrindo todo \`[req:]
|
|
284
|
+
- O gate do \`archive\` **exige** \`verdict.json\` com \`ok\` cobrindo todo \`[req:]\` e copiando
|
|
285
|
+
\`evidenceEnvelopeId\` + \`evidenceBinding\` de \`verificacao.json\`. Sem isso, não arquiva.
|
|
285
286
|
|
|
286
287
|
## Templates (nesta pasta)
|
|
287
288
|
- \`spec-reviewer-prompt.md\` — cole ao spawnar o subagente verificador (read-only, autor≠verificador).
|
|
@@ -489,7 +490,8 @@ the author — even if you wrote the code, enter as if you'd never seen it. Fres
|
|
|
489
490
|
- **Author ≠ verifier.** On Claude, spawn a read-only sub-agent for real isolation.
|
|
490
491
|
- \`ok: false\` if any requirement lacks discriminating coverage. A gap is red, not "almost".
|
|
491
492
|
- Don't fix here — a gap becomes a fix task; re-verify after.
|
|
492
|
-
- The archive gate **requires** a fresh \`verdict.json\`
|
|
493
|
+
- The archive gate **requires** a fresh \`verdict.json\` matching \`tasksHash\`,
|
|
494
|
+
\`effectiveSpecHash\`, \`evidenceEnvelopeId\`, and \`evidenceBinding\`, covering every \`[req:]\`.
|
|
493
495
|
|
|
494
496
|
## Templates (in this folder)
|
|
495
497
|
- \`spec-reviewer-prompt.md\` — hand it to the verifier sub-agent you spawn (read-only, author≠verifier).
|
|
@@ -507,6 +509,8 @@ const VERDICT_TEMPLATE = `{
|
|
|
507
509
|
],
|
|
508
510
|
"tasksHash": "<copie de verificacao.json — selo de frescor / copy from verificacao.json — freshness seal>",
|
|
509
511
|
"effectiveSpecHash": "<copie de verificacao.json / copy from verificacao.json>",
|
|
512
|
+
"evidenceEnvelopeId": "<copie de verificacao.json / copy from verificacao.json>",
|
|
513
|
+
"evidenceBinding": "<copie o objeto de verificacao.json / copy the object from verificacao.json>",
|
|
510
514
|
"notes": []
|
|
511
515
|
}
|
|
512
516
|
`;
|
|
@@ -532,7 +536,8 @@ Para cada \`[req:ID]\` da mudança:
|
|
|
532
536
|
|
|
533
537
|
Grave \`08-Mudanças/<slug>/verdict.json\` no formato de \`verdict-template.json\`. \`ok: false\` se
|
|
534
538
|
qualquer \`[req:]\` não tem cobertura que discrimina. Não conserte aqui — gap vira tarefa de
|
|
535
|
-
correção. \`tasksHash\` e \`
|
|
539
|
+
correção. \`tasksHash\`, \`effectiveSpecHash\`, \`evidenceEnvelopeId\` e \`evidenceBinding\` vêm do
|
|
540
|
+
pacote; alterações posteriores deixam o verdict stale e o binding nunca deve ser reconstruído à mão.
|
|
536
541
|
---
|
|
537
542
|
`;
|
|
538
543
|
|
|
@@ -556,8 +561,9 @@ For each \`[req:ID]\`:
|
|
|
556
561
|
4. Check the observable result against the criterion — not the code.
|
|
557
562
|
|
|
558
563
|
Write \`08-Changes/<slug>/verdict.json\` in the shape of \`verdict-template.json\`. \`ok: false\` if any
|
|
559
|
-
\`[req:]\` lacks discriminating coverage. Don't fix here — a gap becomes a fix task. \`tasksHash
|
|
560
|
-
and \`
|
|
564
|
+
\`[req:]\` lacks discriminating coverage. Don't fix here — a gap becomes a fix task. \`tasksHash\`,
|
|
565
|
+
\`effectiveSpecHash\`, \`evidenceEnvelopeId\`, and \`evidenceBinding\` are copied from the package;
|
|
566
|
+
never reconstruct the binding by hand (later checkout/task/spec edits make the verdict stale).
|
|
561
567
|
---
|
|
562
568
|
`;
|
|
563
569
|
|
package/src/verify.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `wendkeep verify [--change <slug>]` — run a change's task sensors, record evidence.
|
|
2
2
|
// Sensors run at the PROJECT root (--project or cwd); the change + evidence live in
|
|
3
3
|
// the VAULT. Writes 08-Mudanças/<slug>/evidencia.json; exit 1 if a critical sensor is red.
|
|
4
|
-
import { readFileSync, unlinkSync
|
|
4
|
+
import { readFileSync, unlinkSync } from 'node:fs';
|
|
5
5
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
6
6
|
import { parseTasks, activeChange, appendFixTasks, healSpecBacklinks } from '../hooks/change-core.mjs';
|
|
7
7
|
import {
|
|
@@ -21,6 +21,15 @@ import {
|
|
|
21
21
|
import { addLesson } from '../hooks/lessons-core.mjs';
|
|
22
22
|
import { getLocale } from '../hooks/locale.mjs';
|
|
23
23
|
import { resolveCommandActiveContext } from './active-context-runtime.mjs';
|
|
24
|
+
import { writeVaultFileAtomic } from '../packages/vault/src/vault-path-safety.mjs';
|
|
25
|
+
import { evidenceCheckoutBinding } from '../packages/vault/src/evidence-envelope.mjs';
|
|
26
|
+
import {
|
|
27
|
+
assertStableHead,
|
|
28
|
+
buildEvidenceEnvelope,
|
|
29
|
+
captureGitSnapshot,
|
|
30
|
+
resolveEvidenceIdentity,
|
|
31
|
+
sensorConfigSha256,
|
|
32
|
+
} from './evidence-envelope.mjs';
|
|
24
33
|
|
|
25
34
|
function today() {
|
|
26
35
|
const d = new Date();
|
|
@@ -34,6 +43,23 @@ function opt(argv, name) {
|
|
|
34
43
|
return eq ? eq.slice(name.length + 1) : undefined;
|
|
35
44
|
}
|
|
36
45
|
|
|
46
|
+
export function createChangeAuthorityWriter(vaultBase, changeDir, {
|
|
47
|
+
writeAtomic = writeVaultFileAtomic,
|
|
48
|
+
beforeRename,
|
|
49
|
+
} = {}) {
|
|
50
|
+
return (file, content) => writeAtomic(
|
|
51
|
+
vaultBase,
|
|
52
|
+
join(changeDir, file),
|
|
53
|
+
content,
|
|
54
|
+
'utf8',
|
|
55
|
+
{
|
|
56
|
+
scopeRoot: changeDir,
|
|
57
|
+
label: `artefato de evidência ${file}`,
|
|
58
|
+
...(beforeRename ? { beforeRename: (details) => beforeRename({ file, ...details }) } : {}),
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
37
63
|
export function runVerify(argv) {
|
|
38
64
|
const vaultRaw = opt(argv, '--vault') || process.env.OBSIDIAN_VAULT_PATH;
|
|
39
65
|
if (!vaultRaw) { process.stderr.write('wendkeep verify: no vault (--vault or OBSIDIAN_VAULT_PATH).\n'); process.exit(2); }
|
|
@@ -41,23 +67,23 @@ export function runVerify(argv) {
|
|
|
41
67
|
// --project wins; otherwise climb from cwd to the nearest project marker (agent shells
|
|
42
68
|
// keep their cwd across commands, so verify from a subdirectory is a recurring miss).
|
|
43
69
|
const projectRoot = resolve(opt(argv, '--project') || findProjectRoot(process.cwd()) || process.cwd());
|
|
70
|
+
const requestedSession = opt(argv, '--session') || process.env.CODEX_THREAD_ID || process.env.CLAUDE_SESSION_ID || '';
|
|
44
71
|
let commandContext = null;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
process.exit(2);
|
|
55
|
-
}
|
|
72
|
+
try {
|
|
73
|
+
commandContext = resolveCommandActiveContext({
|
|
74
|
+
vaultBase,
|
|
75
|
+
projectRoot,
|
|
76
|
+
sessionId: requestedSession,
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
process.stderr.write(`wendkeep verify: ${error.code || 'WENDKEEP_ACTIVE_CONTEXT_FAILED'}: ${error.message}\n`);
|
|
80
|
+
process.exit(2);
|
|
56
81
|
}
|
|
57
82
|
const slug = opt(argv, '--change') || activeChange(vaultBase, { context: commandContext });
|
|
58
83
|
if (!slug) { process.stderr.write('wendkeep verify: no change (--change or active).\n'); process.exit(2); }
|
|
59
84
|
|
|
60
85
|
const changeDir = join(vaultBase, getLocale(vaultBase).folders.changes, slug);
|
|
86
|
+
const writeAuthority = createChangeAuthorityWriter(vaultBase, changeDir);
|
|
61
87
|
let tarefas = '';
|
|
62
88
|
try { tarefas = readFileSync(join(changeDir, 'tarefas.md'), 'utf8'); }
|
|
63
89
|
catch { process.stderr.write(`wendkeep verify: change not found: ${slug}\n`); process.exit(2); }
|
|
@@ -65,7 +91,8 @@ export function runVerify(argv) {
|
|
|
65
91
|
// Backlink dos specs escritos à mão pro hub proposta (grafo conectado). Idempotente, fail-quiet.
|
|
66
92
|
try { healSpecBacklinks(changeDir, vaultBase); } catch { /* heal é bônus */ }
|
|
67
93
|
|
|
68
|
-
const
|
|
94
|
+
const tasks = parseTasks(tarefas);
|
|
95
|
+
const ids = requiredSensors(tasks);
|
|
69
96
|
const loaded = loadSensorsDetailed(projectRoot);
|
|
70
97
|
if (loaded.error) {
|
|
71
98
|
process.stderr.write(`wendkeep verify: wendkeep.sensors.json inválido em ${loaded.path}: ${loaded.error}\n`);
|
|
@@ -75,14 +102,52 @@ export function runVerify(argv) {
|
|
|
75
102
|
process.stderr.write(`wendkeep verify: wendkeep.sensors.json não encontrado em ${loaded.path} — rode da raiz do projeto ou use --project <raiz>\n`);
|
|
76
103
|
}
|
|
77
104
|
const sensors = loaded.sensors;
|
|
105
|
+
const reqIds = [...new Set(tasks.flatMap((task) => task.reqs ?? []))];
|
|
106
|
+
const effective = buildEffectiveRequirementPackage(vaultBase, changeDir, reqIds);
|
|
107
|
+
const tasksHash = tasksHashOf(tarefas);
|
|
108
|
+
const startedAt = new Date().toISOString();
|
|
109
|
+
let startSnapshot;
|
|
110
|
+
let identity;
|
|
111
|
+
try {
|
|
112
|
+
startSnapshot = captureGitSnapshot(projectRoot);
|
|
113
|
+
identity = resolveEvidenceIdentity({
|
|
114
|
+
vaultBase,
|
|
115
|
+
projectRoot,
|
|
116
|
+
changeSlug: slug,
|
|
117
|
+
sessionId: requestedSession,
|
|
118
|
+
context: commandContext,
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
process.stderr.write(`wendkeep verify: ${error.code || 'WENDKEEP_EVIDENCE_BINDING_FAILED'}: ${error.message}\n`);
|
|
122
|
+
process.exit(2);
|
|
123
|
+
}
|
|
78
124
|
const evidence = runSensors(sensors, ids, {
|
|
79
125
|
cwd: projectRoot,
|
|
80
126
|
env: sensorProcessEnv(vaultBase),
|
|
81
127
|
});
|
|
82
|
-
|
|
128
|
+
let finishSnapshot;
|
|
129
|
+
try {
|
|
130
|
+
finishSnapshot = captureGitSnapshot(projectRoot);
|
|
131
|
+
assertStableHead(startSnapshot, finishSnapshot);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
process.stderr.write(`wendkeep verify: ${error.code || 'WENDKEEP_EVIDENCE_BINDING_FAILED'}: ${error.message}\n`);
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
const envelope = buildEvidenceEnvelope({
|
|
137
|
+
identity,
|
|
138
|
+
changeSlug: slug,
|
|
139
|
+
snapshot: startSnapshot,
|
|
140
|
+
tasksSha256: tasksHash,
|
|
141
|
+
effectiveSpecSha256: `sha256:${effective.hash}`,
|
|
142
|
+
sensorConfigSha256: sensorConfigSha256(sensors, ids),
|
|
143
|
+
sensors: evidence,
|
|
144
|
+
startedAt,
|
|
145
|
+
finishedAt: new Date().toISOString(),
|
|
146
|
+
});
|
|
147
|
+
writeAuthority('evidencia.json', `${JSON.stringify(envelope, null, 2)}\n`);
|
|
83
148
|
// Freshness seal: bind this evidence to the tarefas.md it was produced against, so the archive
|
|
84
149
|
// gate can reject evidence gone stale (a sensor task added after this verify run).
|
|
85
|
-
|
|
150
|
+
writeAuthority('.evidence-hash', tasksHash);
|
|
86
151
|
|
|
87
152
|
// Mutation survivors -> fix tasks (Wave B), bounded at 3 rounds then escalate. A surviving
|
|
88
153
|
// mutant always fails verify (exit 1): the suite does not discriminate yet. A clean report
|
|
@@ -108,7 +173,7 @@ export function runVerify(argv) {
|
|
|
108
173
|
} else {
|
|
109
174
|
let added = 0;
|
|
110
175
|
for (const e of withSurvivors) added += appendFixTasks(changeDir, e.survivors, e.id);
|
|
111
|
-
|
|
176
|
+
writeAuthority('.mutation-round', String(round + 1));
|
|
112
177
|
process.stdout.write(`verify: ${added} fix-task(s) de mutação (rodada ${round + 1}/3)\n`);
|
|
113
178
|
}
|
|
114
179
|
process.stderr.write('verify: mutantes sobreviventes — a suíte não discrimina ainda.\n');
|
|
@@ -127,11 +192,7 @@ export function runVerify(argv) {
|
|
|
127
192
|
// --deep (Q2=B): assemble the verification package the wk-verify skill judges. A trivial
|
|
128
193
|
// change (no [req:] tasks, sensors green) gets an auto verdict — no agent pass needed.
|
|
129
194
|
if (argv.includes('--deep')) {
|
|
130
|
-
const tasks = parseTasks(tarefas);
|
|
131
|
-
const reqIds = [...new Set(tasks.flatMap((t) => t.reqs ?? []))];
|
|
132
|
-
const tasksHash = tasksHashOf(tarefas);
|
|
133
195
|
captureSpecBaseline(vaultBase, changeDir);
|
|
134
|
-
const effective = buildEffectiveRequirementPackage(vaultBase, changeDir, reqIds);
|
|
135
196
|
if (effective.errors.length) {
|
|
136
197
|
process.stderr.write(`verify --deep: spec efetiva inválida: ${effective.errors.join('; ')}\n`);
|
|
137
198
|
process.exit(1);
|
|
@@ -144,6 +205,8 @@ export function runVerify(argv) {
|
|
|
144
205
|
slug,
|
|
145
206
|
tasksHash,
|
|
146
207
|
effectiveSpecHash: effective.hash,
|
|
208
|
+
evidenceEnvelopeId: envelope.envelope_id,
|
|
209
|
+
evidenceBinding: evidenceCheckoutBinding(envelope),
|
|
147
210
|
requirements: effective.requirements.map((req) => {
|
|
148
211
|
return {
|
|
149
212
|
id: req.id,
|
|
@@ -157,9 +220,9 @@ export function runVerify(argv) {
|
|
|
157
220
|
tasks: tasks.map((t) => ({ id: t.id, text: t.text, req: t.req || null, reqs: t.reqs || [], done: t.done })),
|
|
158
221
|
sensors: evidence,
|
|
159
222
|
};
|
|
160
|
-
|
|
223
|
+
writeAuthority('verificacao.json', `${JSON.stringify(pkg, null, 2)}\n`);
|
|
161
224
|
if (reqIds.length === 0) {
|
|
162
|
-
|
|
225
|
+
writeAuthority('verdict.json', `${JSON.stringify({ slug, ok: true, coverage: [], tasksHash, effectiveSpecHash: effective.hash, evidenceEnvelopeId: envelope.envelope_id, evidenceBinding: pkg.evidenceBinding, notes: ['trivial: sem requisito'] }, null, 2)}\n`);
|
|
163
226
|
process.stdout.write('verify --deep: pacote + verdict trivial escritos\n');
|
|
164
227
|
} else {
|
|
165
228
|
process.stdout.write('verify --deep: pacote escrito — rode a skill wk-verify pra gravar verdict.json\n');
|