wendkeep 0.76.9 → 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 +39 -0
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/docs/en/commands/changes-and-verification.md +8 -1
- package/docs/en/commands/verify.md +22 -5
- package/docs/en/commands/worktrees.md +80 -32
- package/docs/pt-BR/commands/changes-and-verification.md +8 -1
- package/docs/pt-BR/commands/verify.md +22 -6
- package/docs/pt-BR/commands/worktrees.md +80 -31
- 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/cli/src/index.mjs +1 -1
- 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/worktree-cleanup.mjs +712 -0
- package/src/worktree.mjs +130 -19
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');
|