wendkeep 0.78.0 → 0.80.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 +67 -0
- package/README.en.md +58 -3
- package/README.md +58 -3
- package/docs/en/commands/changes-and-verification.md +116 -1
- package/docs/en/commands/operating-profiles.md +49 -5
- package/docs/en/commands/sessions-and-import.md +6 -0
- package/docs/en/commands/verify.md +54 -0
- package/docs/en/commands/worktrees.md +39 -4
- package/docs/pt-BR/commands/changes-and-verification.md +115 -1
- package/docs/pt-BR/commands/operating-profiles.md +51 -5
- package/docs/pt-BR/commands/sessions-and-import.md +7 -0
- package/docs/pt-BR/commands/verify.md +53 -0
- package/docs/pt-BR/commands/worktrees.md +38 -3
- package/hooks/active-context-store.mjs +530 -2
- package/hooks/change-core.mjs +220 -123
- package/hooks/obsidian-common.mjs +175 -9
- package/hooks/session-stop.mjs +40 -1
- package/hooks/spec-core.mjs +93 -29
- package/package.json +2 -2
- package/packages/cli/src/index.mjs +7 -0
- package/packages/vault/src/memory-handoff.mjs +15 -0
- package/schema/artifact-manifest-v1.schema.json +35 -0
- package/schema/handoff-contract-v1.schema.json +37 -0
- package/schema/task-contract-v1.schema.json +57 -0
- package/schema/wendkeep.provenance-receipt-v2.schema.json +66 -0
- package/src/archive-operation-lock.mjs +235 -0
- package/src/change.mjs +1780 -79
- package/src/delivery.mjs +724 -67
- package/src/memory.mjs +2 -1
- package/src/provenance-gate.mjs +575 -0
- package/src/provenance-sources.mjs +547 -0
- package/src/receipt-ledger.mjs +841 -0
- package/src/release-provenance.mjs +48 -0
- package/src/task-contracts.mjs +510 -0
- package/src/task-leases.mjs +105 -0
- package/src/task.mjs +115 -0
- package/src/verify.mjs +32 -0
- package/src/worktree-cleanup.mjs +1733 -118
- package/src/worktree.mjs +94 -5
package/hooks/session-stop.mjs
CHANGED
|
@@ -27,6 +27,11 @@ import {
|
|
|
27
27
|
import { enqueueMemoryEvent, projectMemoryOutbox } from './memory-store.mjs';
|
|
28
28
|
import { detectMemoryMode } from './memory-mode.mjs';
|
|
29
29
|
import { sanitizeMemoryText } from './memory-schema.mjs';
|
|
30
|
+
import {
|
|
31
|
+
buildStructuredTaskHandoff,
|
|
32
|
+
buildTaskContractSnapshot,
|
|
33
|
+
evaluateTaskContracts,
|
|
34
|
+
} from '../src/task-contracts.mjs';
|
|
30
35
|
import { assertVaultPathSafe } from './vault-path-safety.mjs';
|
|
31
36
|
import {
|
|
32
37
|
projectStopMemoryAttempt,
|
|
@@ -1406,6 +1411,40 @@ export async function main({
|
|
|
1406
1411
|
summary: finalSummary,
|
|
1407
1412
|
noteRel: sessionRel,
|
|
1408
1413
|
});
|
|
1414
|
+
let structuredSharedHandoff = sharedHandoff;
|
|
1415
|
+
const effectiveProfile = String(
|
|
1416
|
+
handoffEvidenceAuthority?.context?.operating_profile_task?.profile
|
|
1417
|
+
|| entry?.operating_profile_task?.profile
|
|
1418
|
+
|| 'GOVERN',
|
|
1419
|
+
).toUpperCase();
|
|
1420
|
+
if (handoffEvidenceAuthority?.mode === 'contextual') {
|
|
1421
|
+
try {
|
|
1422
|
+
const taskSnapshot = activeContextChangeSlug
|
|
1423
|
+
? buildTaskContractSnapshot({
|
|
1424
|
+
vaultBase,
|
|
1425
|
+
projectRoot: input.cwd || process.cwd(),
|
|
1426
|
+
changeSlug: activeContextChangeSlug,
|
|
1427
|
+
identity: handoffEvidenceAuthority.identity,
|
|
1428
|
+
})
|
|
1429
|
+
: null;
|
|
1430
|
+
structuredSharedHandoff = buildStructuredTaskHandoff({
|
|
1431
|
+
profile: effectiveProfile,
|
|
1432
|
+
sessionId,
|
|
1433
|
+
snapshot: taskSnapshot,
|
|
1434
|
+
evaluations: taskSnapshot ? evaluateTaskContracts(taskSnapshot) : [],
|
|
1435
|
+
context: handoffEvidenceAuthority.context,
|
|
1436
|
+
shared: sharedHandoff,
|
|
1437
|
+
});
|
|
1438
|
+
} catch (error) {
|
|
1439
|
+
if (effectiveProfile === 'ASSURE') {
|
|
1440
|
+
const detail = `${error?.code || 'HANDOFF_STRUCTURED_REQUIRED'}: ${error?.message || error}`;
|
|
1441
|
+
process.stderr.write(`[wendkeep] Stop ASSURE bloqueado: ${detail}\n`);
|
|
1442
|
+
writeHookOutput({ systemMessage: `wendkeep: Stop ASSURE exige handoff estruturado: ${detail}` });
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
structuredSharedHandoff = sharedHandoff;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1409
1448
|
memoryHandoff = {
|
|
1410
1449
|
projectId,
|
|
1411
1450
|
identity,
|
|
@@ -1418,7 +1457,7 @@ export async function main({
|
|
|
1418
1457
|
observedAt: turnIdentity.observedAt || new Date(0).toISOString(),
|
|
1419
1458
|
summary: finalSummary,
|
|
1420
1459
|
evidence: memoryEvidence,
|
|
1421
|
-
...(
|
|
1460
|
+
...(structuredSharedHandoff ? { shared: structuredSharedHandoff } : {}),
|
|
1422
1461
|
};
|
|
1423
1462
|
memoryAttempt = stageMemory(vaultBase, {
|
|
1424
1463
|
handoff: memoryHandoff,
|
package/hooks/spec-core.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// hooks/spec-core.mjs — living spec (07-Specs) + change delta merge (OpenSpec native).
|
|
2
|
-
// Pure parsing/merge +
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
2
|
+
// Pure parsing/merge + read-only promotion planning. No import from change-core (avoids a cycle).
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
5
|
-
import { join } from 'node:path';
|
|
5
|
+
import { join, relative } from 'node:path';
|
|
6
6
|
import { getLocale } from './locale.mjs';
|
|
7
7
|
import {
|
|
8
|
-
assertVaultPathSafe, assertVaultPathsSafe, mkdirVaultPath,
|
|
8
|
+
assertVaultPathSafe, assertVaultPathsSafe, mkdirVaultPath,
|
|
9
|
+
writeVaultFileAtomic, writeVaultFileSync,
|
|
9
10
|
} from './vault-path-safety.mjs';
|
|
10
11
|
|
|
11
12
|
// Canonical SHA-256 fingerprint of tarefas.md — freshness binding between package/verdict and gate.
|
|
@@ -148,9 +149,20 @@ export function checkSpecsState(vaultBase) {
|
|
|
148
149
|
return { ok: changed.length === 0, missing: false, changed, current, recorded };
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
function recordPromotedSpecs(vaultBase, capabilities) {
|
|
152
|
+
function recordPromotedSpecs(vaultBase, capabilities, { writeAtomic = writeVaultFileSync } = {}) {
|
|
152
153
|
const existing = readSpecsState(vaultBase);
|
|
153
|
-
if (!existing)
|
|
154
|
+
if (!existing) {
|
|
155
|
+
const state = { version: 1, generatedAt: new Date().toISOString(), specs: readLivingSpecs(vaultBase) };
|
|
156
|
+
mkdirVaultPath(vaultBase, join(vaultBase, '.brain'), { label: 'raiz do estado de specs' });
|
|
157
|
+
writeAtomic(
|
|
158
|
+
vaultBase,
|
|
159
|
+
join(vaultBase, SPECS_STATE_FILE),
|
|
160
|
+
`${JSON.stringify(state, null, 2)}\n`,
|
|
161
|
+
'utf8',
|
|
162
|
+
{ label: 'estado consolidado de specs' },
|
|
163
|
+
);
|
|
164
|
+
return state;
|
|
165
|
+
}
|
|
154
166
|
const current = readLivingSpecs(vaultBase);
|
|
155
167
|
const specs = { ...(existing.specs || {}) };
|
|
156
168
|
for (const capability of capabilities) {
|
|
@@ -158,7 +170,7 @@ function recordPromotedSpecs(vaultBase, capabilities) {
|
|
|
158
170
|
else delete specs[capability];
|
|
159
171
|
}
|
|
160
172
|
const state = { version: 1, generatedAt: new Date().toISOString(), specs };
|
|
161
|
-
|
|
173
|
+
writeAtomic(
|
|
162
174
|
vaultBase,
|
|
163
175
|
join(vaultBase, SPECS_STATE_FILE),
|
|
164
176
|
`${JSON.stringify(state, null, 2)}\n`,
|
|
@@ -348,12 +360,10 @@ export function discoverSpecDeltas(changeDir) {
|
|
|
348
360
|
// per-change (many changes promote into the same file; the per-change record lives in
|
|
349
361
|
// _arquivo). Generated + read-only. Written on init and refreshed on every archive so
|
|
350
362
|
// existing vaults self-heal. Bilingual by vault locale.
|
|
351
|
-
|
|
363
|
+
function specsReadmeBody(vaultBase) {
|
|
352
364
|
const loc = getLocale(vaultBase);
|
|
353
365
|
const en = loc.id === 'en';
|
|
354
|
-
|
|
355
|
-
mkdirVaultPath(vaultBase, dir, { label: 'raiz de specs consolidadas' });
|
|
356
|
-
const body = en
|
|
366
|
+
return en
|
|
357
367
|
? `# Specs — generated living contract
|
|
358
368
|
|
|
359
369
|
**One file per _capability_, not per change.** Each file is the current, cumulative contract
|
|
@@ -382,7 +392,18 @@ Pense como código-fonte vs commits: esta pasta é o *código atual* de cada cap
|
|
|
382
392
|
\`wendkeep change archive\` promove para esta pasta.
|
|
383
393
|
- Histórico por mudança → \`${loc.folders.changes}/_arquivo/\`. Contrato atual → aqui.
|
|
384
394
|
`;
|
|
385
|
-
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function renderSpecsReadme(vaultBase) {
|
|
398
|
+
return specsReadmeBody(vaultBase);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function ensureSpecsReadme(vaultBase, { writeAtomic = writeVaultFileSync } = {}) {
|
|
402
|
+
const loc = getLocale(vaultBase);
|
|
403
|
+
const dir = join(vaultBase, loc.folders.specs);
|
|
404
|
+
mkdirVaultPath(vaultBase, dir, { label: 'raiz de specs consolidadas' });
|
|
405
|
+
const body = specsReadmeBody(vaultBase);
|
|
406
|
+
writeAtomic(vaultBase, join(dir, 'README.md'), body, 'utf8', { label: 'README de specs' });
|
|
386
407
|
}
|
|
387
408
|
|
|
388
409
|
export function assertSpecPromotionTargetsSafe(vaultBase, changeDir, specs) {
|
|
@@ -426,8 +447,14 @@ export function assertSpecPromotionTargetsSafe(vaultBase, changeDir, specs) {
|
|
|
426
447
|
return { specsRoot: checkedRoot.target };
|
|
427
448
|
}
|
|
428
449
|
|
|
429
|
-
//
|
|
430
|
-
|
|
450
|
+
// Derive the immutable before/postimage plan used by the archive CLI. This helper
|
|
451
|
+
// is deliberately read-only: publication is private to src/change.mjs, after the
|
|
452
|
+
// provenance gate, receipt, recapture, and operation lock have all succeeded.
|
|
453
|
+
export function buildSpecPromotionPlan(vaultBase, changeDir, specs, {
|
|
454
|
+
changeWikilink,
|
|
455
|
+
dateStr,
|
|
456
|
+
recoveryRoot,
|
|
457
|
+
} = {}) {
|
|
431
458
|
const loc = getLocale(vaultBase);
|
|
432
459
|
const specsDir = loc.folders.specs;
|
|
433
460
|
const promoted = [];
|
|
@@ -459,25 +486,62 @@ export function promoteSpecs(vaultBase, changeDir, specs, { changeWikilink, date
|
|
|
459
486
|
content: renderSpec(cap, applied.reqs, { footer, reqHeading: loc.reqHeading }),
|
|
460
487
|
});
|
|
461
488
|
}
|
|
462
|
-
|
|
489
|
+
const statePath = join(vaultBase, SPECS_STATE_FILE);
|
|
490
|
+
const readmePath = join(specsRoot, 'README.md');
|
|
491
|
+
const existingState = readSpecsState(vaultBase);
|
|
492
|
+
const nextSpecs = existingState ? { ...(existingState.specs || {}) } : readLivingSpecs(vaultBase);
|
|
463
493
|
for (const item of materialized) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
item.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
{ label: `spec consolidada ${item.capability}` },
|
|
470
|
-
);
|
|
471
|
-
promoted.push(item.capability);
|
|
494
|
+
nextSpecs[item.capability] = {
|
|
495
|
+
hash: contentHashOf(item.content),
|
|
496
|
+
requirements: Object.fromEntries(parseRequirements(item.content)
|
|
497
|
+
.map((requirement) => [requirement.id || requirement.name, contentHashOf(JSON.stringify(requirement))])),
|
|
498
|
+
};
|
|
472
499
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
500
|
+
const stateContent = `${JSON.stringify({
|
|
501
|
+
version: 1,
|
|
502
|
+
generatedAt: new Date().toISOString(),
|
|
503
|
+
specs: nextSpecs,
|
|
504
|
+
}, null, 2)}\n`;
|
|
505
|
+
const planned = [
|
|
506
|
+
...materialized.map((item) => ({
|
|
507
|
+
kind: 'capability', capability: item.capability, path: item.livePath, content: item.content,
|
|
508
|
+
})),
|
|
509
|
+
{ kind: 'state', path: statePath, content: stateContent },
|
|
510
|
+
{ kind: 'readme', path: readmePath, content: specsReadmeBody(vaultBase) },
|
|
511
|
+
];
|
|
512
|
+
const image = (content, exists = true) => ({
|
|
513
|
+
exists,
|
|
514
|
+
content_base64: Buffer.from(content, 'utf8').toString('base64'),
|
|
515
|
+
digest: `sha256:${contentHashOf(content)}`,
|
|
516
|
+
});
|
|
517
|
+
const promotionRecoveryRoot = recoveryRoot
|
|
518
|
+
|| join(vaultBase, '.brain', 'runtime', 'spec-promotion-plans', randomUUID());
|
|
519
|
+
const plan = {
|
|
520
|
+
schema_version: 1,
|
|
521
|
+
entries: planned.map((entry, index) => {
|
|
522
|
+
const beforeExists = existsSync(entry.path);
|
|
523
|
+
const beforeContent = beforeExists ? readFileSync(entry.path, 'utf8') : '';
|
|
524
|
+
return {
|
|
525
|
+
kind: entry.kind,
|
|
526
|
+
capability: entry.capability || null,
|
|
527
|
+
target: relative(vaultBase, entry.path).replaceAll('\\', '/'),
|
|
528
|
+
claim_target: relative(vaultBase, join(promotionRecoveryRoot, `${index}-${randomUUID()}.before`)).replaceAll('\\', '/'),
|
|
529
|
+
candidate_target: relative(vaultBase, join(promotionRecoveryRoot, `${index}-${randomUUID()}.candidate`)).replaceAll('\\', '/'),
|
|
530
|
+
before: image(beforeContent, beforeExists),
|
|
531
|
+
after: image(entry.content, true),
|
|
532
|
+
};
|
|
533
|
+
}),
|
|
534
|
+
};
|
|
535
|
+
const changes = materialized.map((item) => ({
|
|
536
|
+
capability: item.capability,
|
|
537
|
+
before_digest: plan.entries.find((entry) => entry.capability === item.capability).before.digest,
|
|
538
|
+
after_digest: `sha256:${contentHashOf(item.content)}`,
|
|
539
|
+
}));
|
|
540
|
+
plan.changes = changes;
|
|
541
|
+
promoted.push(...materialized.map((item) => item.capability));
|
|
542
|
+
return { promoted, warnings, changes, plan };
|
|
476
543
|
}
|
|
477
544
|
|
|
478
|
-
// Gate check for the independent verdict (Wave A). A requirement-bearing change must have
|
|
479
|
-
// a verdict that is ok and covers every declared req id. A requirement-less change passes:
|
|
480
|
-
// nothing for an independent verifier to check — the sensor gate is already the proof.
|
|
481
545
|
export function evaluateVerdict(verdict, reqIds, {
|
|
482
546
|
tasksHash,
|
|
483
547
|
effectiveSpecHash,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wendkeep",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.80.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
|
-
"precheck": "node --check src/worktree.mjs && node --check src/worktree-cleanup.mjs && node --check src/evidence-envelope.mjs && node --check src/context.mjs && node --check src/active-context-health.mjs && node --check src/active-context-runtime.mjs && node --check hooks/active-context-store.mjs && node --check hooks/change-core.mjs && node --check hooks/brain-inject.mjs && node --check hooks/change-context.mjs && node --check packages/vault/src/worktree-metadata.mjs && node --check packages/vault/src/evidence-envelope.mjs",
|
|
44
|
+
"precheck": "node --check src/task-contracts.mjs && node --check src/task-leases.mjs && node --check src/task.mjs && node --check src/change.mjs && node --check src/archive-operation-lock.mjs && node --check src/worktree.mjs && node --check src/worktree-cleanup.mjs && node --check src/provenance-gate.mjs && node --check src/provenance-sources.mjs && node --check src/receipt-ledger.mjs && node --check src/evidence-envelope.mjs && node --check src/context.mjs && node --check src/active-context-health.mjs && node --check src/active-context-runtime.mjs && node --check hooks/active-context-store.mjs && node --check hooks/change-core.mjs && node --check hooks/brain-inject.mjs && node --check hooks/change-context.mjs && node --check hooks/session-stop.mjs && node --check packages/vault/src/worktree-metadata.mjs && node --check packages/vault/src/evidence-envelope.mjs && node --check packages/vault/src/memory-handoff.mjs",
|
|
45
45
|
"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/active-context-health.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/evidence-context.mjs && node --check hooks/active-context-handoff-evidence.mjs && node --check hooks/evidence-recall.mjs && node --check hooks/memory-scope.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-scope.mjs && node --check packages/vault/src/memory-candidate-policy.mjs && node --check packages/vault/src/evidence-recall.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",
|
|
46
46
|
"test": "node --test --test-concurrency=2",
|
|
47
47
|
"test:core": "node scripts/run-scope.mjs core",
|
|
@@ -64,6 +64,8 @@ Usage:
|
|
|
64
64
|
wendkeep context repair --key <repository:worktree:work-session> --revision <n> --reason <text> --session <id> [--json]
|
|
65
65
|
Inspect or explicitly recover a quarantined causal scope conflict.
|
|
66
66
|
Repair revalidates orphan/removed contexts or expired request leases without deleting history.
|
|
67
|
+
wendkeep task <sub> Typed task contracts: list | show | evaluate | claim | release.
|
|
68
|
+
Resolves the change from the causal active context; supports --session/--change/--json.
|
|
67
69
|
wendkeep change <sub> Change lifecycle: new [--simple|--guide] | use | bind <slug> --session <id> | continue | list | show |
|
|
68
70
|
status | done <id> | undone <id> | diff | archive [--force] | abandon | relink | backlink.
|
|
69
71
|
--session <id> selects the causal active_context for implicit change operations.
|
|
@@ -292,6 +294,11 @@ async function main(argv) {
|
|
|
292
294
|
runChange(rest);
|
|
293
295
|
break;
|
|
294
296
|
}
|
|
297
|
+
case 'task': {
|
|
298
|
+
const { runTask } = await import('../../../src/task.mjs');
|
|
299
|
+
process.exit(runTask(rest));
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
295
302
|
case 'session': {
|
|
296
303
|
const { runSession } = await import('../../../src/session.mjs');
|
|
297
304
|
runSession(rest);
|
|
@@ -61,6 +61,10 @@ export function normalizeSharedHandoff(shared) {
|
|
|
61
61
|
const value = sanitizeValue(shared[field]);
|
|
62
62
|
if (hasMeaningfulValue(value)) normalized[field] = value;
|
|
63
63
|
}
|
|
64
|
+
if (Object.hasOwn(shared, 'handoff_contract')) {
|
|
65
|
+
const contract = sanitizeValue(shared.handoff_contract);
|
|
66
|
+
if (hasMeaningfulValue(contract)) normalized.handoff_contract = contract;
|
|
67
|
+
}
|
|
64
68
|
|
|
65
69
|
return Object.keys(normalized).length ? normalized : null;
|
|
66
70
|
}
|
|
@@ -257,6 +261,17 @@ export function buildSessionMemoryEvents({
|
|
|
257
261
|
const events = [];
|
|
258
262
|
|
|
259
263
|
if (normalizedShared) {
|
|
264
|
+
if (normalizedShared.handoff_contract) {
|
|
265
|
+
const contract = normalizedShared.handoff_contract;
|
|
266
|
+
events.push(makeEvent(context, {
|
|
267
|
+
memoryKey: 'handoff.latest',
|
|
268
|
+
value: contract,
|
|
269
|
+
authority: contract.schema_version === 1 && contract.authority === 'verified'
|
|
270
|
+
? 'verified' : 'reported',
|
|
271
|
+
evidence: Array.isArray(contract.evidence) && contract.evidence.length
|
|
272
|
+
? contract.evidence : [noteRel],
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
260
275
|
for (const [field, memoryKey] of SHARED_HANDOFF_FIELDS) {
|
|
261
276
|
if (!Object.hasOwn(normalizedShared, field)) continue;
|
|
262
277
|
events.push(makeEvent(context, {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://wendkeep.dev/schema/artifact-manifest-v1.schema.json",
|
|
4
|
+
"title": "WendKeep Artifact Manifest v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schema_version", "artifacts"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"schema_version": { "const": 1 },
|
|
10
|
+
"artifacts": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"items": { "$ref": "#/$defs/artifact" }
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"$defs": {
|
|
16
|
+
"artifact": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"additionalProperties": false,
|
|
19
|
+
"required": ["name", "type"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"name": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" },
|
|
22
|
+
"type": { "enum": ["name", "path", "glob", "file-count"] },
|
|
23
|
+
"path": { "type": "string" },
|
|
24
|
+
"glob": { "type": "string" },
|
|
25
|
+
"min": { "type": "integer", "minimum": 0 },
|
|
26
|
+
"max": { "type": "integer", "minimum": 0 },
|
|
27
|
+
"fromFilesystem": { "type": "boolean" }
|
|
28
|
+
},
|
|
29
|
+
"allOf": [
|
|
30
|
+
{ "if": { "properties": { "type": { "const": "path" } } }, "then": { "required": ["path"] } },
|
|
31
|
+
{ "if": { "properties": { "type": { "enum": ["glob", "file-count"] } } }, "then": { "required": ["glob"] } }
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://wendkeep.dev/schema/handoff-contract-v1.schema.json",
|
|
4
|
+
"title": "WendKeep Handoff Contract v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version", "handoff_id", "from", "to", "active_context_id", "task_id",
|
|
9
|
+
"task_contract_id", "artifacts", "evidence", "decisions", "next_actions", "blockers",
|
|
10
|
+
"head_sha", "tasks_sha256", "spec_sha256", "authority"
|
|
11
|
+
],
|
|
12
|
+
"properties": {
|
|
13
|
+
"schema_version": { "const": 1 },
|
|
14
|
+
"handoff_id": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
15
|
+
"from": { "type": "string", "minLength": 1 },
|
|
16
|
+
"to": { "type": "string", "minLength": 1 },
|
|
17
|
+
"active_context_id": { "type": "string", "minLength": 1 },
|
|
18
|
+
"task_id": { "type": "string" },
|
|
19
|
+
"task_contract_id": { "type": "string" },
|
|
20
|
+
"artifacts": { "$ref": "#/$defs/stringArray" },
|
|
21
|
+
"evidence": { "$ref": "#/$defs/stringArray" },
|
|
22
|
+
"decisions": { "$ref": "#/$defs/stringArray" },
|
|
23
|
+
"next_actions": { "$ref": "#/$defs/stringArray" },
|
|
24
|
+
"blockers": { "$ref": "#/$defs/stringArray" },
|
|
25
|
+
"head_sha": { "type": "string", "minLength": 1 },
|
|
26
|
+
"tasks_sha256": { "type": "string", "minLength": 1 },
|
|
27
|
+
"spec_sha256": { "type": "string", "minLength": 1 },
|
|
28
|
+
"authority": { "enum": ["verified", "reported"] }
|
|
29
|
+
},
|
|
30
|
+
"$defs": {
|
|
31
|
+
"stringArray": {
|
|
32
|
+
"type": "array",
|
|
33
|
+
"items": { "type": "string" },
|
|
34
|
+
"uniqueItems": true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://wendkeep.dev/schema/task-contract-v1.schema.json",
|
|
4
|
+
"title": "WendKeep Task Contract v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"schema_version", "contract_id", "task_id", "change_slug", "title", "phase", "status",
|
|
9
|
+
"inputs", "expected_outputs", "acceptance_criteria", "requirement_ids",
|
|
10
|
+
"required_sensors", "required_artifacts", "dependencies", "owner", "work_session_id",
|
|
11
|
+
"evidence_envelope_id", "checked", "authored_sha256", "binding"
|
|
12
|
+
],
|
|
13
|
+
"properties": {
|
|
14
|
+
"schema_version": { "const": 1 },
|
|
15
|
+
"contract_id": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
16
|
+
"task_id": { "type": "string", "minLength": 1 },
|
|
17
|
+
"change_slug": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$" },
|
|
18
|
+
"title": { "type": "string" },
|
|
19
|
+
"phase": { "enum": ["execute", "verify"] },
|
|
20
|
+
"status": { "enum": ["ready", "blocked", "pending-evaluation", "completed", "stale"] },
|
|
21
|
+
"inputs": { "$ref": "#/$defs/stringArray" },
|
|
22
|
+
"expected_outputs": { "$ref": "#/$defs/stringArray" },
|
|
23
|
+
"acceptance_criteria": { "$ref": "#/$defs/stringArray" },
|
|
24
|
+
"requirement_ids": { "$ref": "#/$defs/stringArray" },
|
|
25
|
+
"required_sensors": { "$ref": "#/$defs/stringArray" },
|
|
26
|
+
"required_artifacts": { "$ref": "#/$defs/stringArray" },
|
|
27
|
+
"dependencies": { "$ref": "#/$defs/stringArray" },
|
|
28
|
+
"owner": { "type": ["string", "null"] },
|
|
29
|
+
"work_session_id": { "type": ["string", "null"] },
|
|
30
|
+
"evidence_envelope_id": { "type": ["string", "null"] },
|
|
31
|
+
"checked": { "type": "boolean" },
|
|
32
|
+
"authored_sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
|
33
|
+
"binding": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"additionalProperties": false,
|
|
36
|
+
"required": [
|
|
37
|
+
"project_id", "active_context_id", "head_sha", "tasks_sha256",
|
|
38
|
+
"effective_spec_sha256", "artifact_manifest_sha256"
|
|
39
|
+
],
|
|
40
|
+
"properties": {
|
|
41
|
+
"project_id": { "type": "string", "minLength": 1 },
|
|
42
|
+
"active_context_id": { "type": "string", "minLength": 1 },
|
|
43
|
+
"head_sha": { "type": "string", "minLength": 1 },
|
|
44
|
+
"tasks_sha256": { "type": "string", "minLength": 1 },
|
|
45
|
+
"effective_spec_sha256": { "type": "string", "minLength": 1 },
|
|
46
|
+
"artifact_manifest_sha256": { "type": "string", "minLength": 1 }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"$defs": {
|
|
51
|
+
"stringArray": {
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": { "type": "string" },
|
|
54
|
+
"uniqueItems": true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/rogersialves/wendkeep/blob/main/schema/wendkeep.provenance-receipt-v2.schema.json",
|
|
4
|
+
"title": "WendKeep Provenance Receipt v2",
|
|
5
|
+
"description": "A canonical, hash-chained receipt for a provenance-gated operation.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"schema_version",
|
|
10
|
+
"sequence",
|
|
11
|
+
"receipt_id",
|
|
12
|
+
"previous_hash",
|
|
13
|
+
"receipt_hash",
|
|
14
|
+
"kind",
|
|
15
|
+
"subject",
|
|
16
|
+
"claims",
|
|
17
|
+
"observations",
|
|
18
|
+
"recorded_at"
|
|
19
|
+
],
|
|
20
|
+
"properties": {
|
|
21
|
+
"schema_version": {
|
|
22
|
+
"const": 2
|
|
23
|
+
},
|
|
24
|
+
"sequence": {
|
|
25
|
+
"type": "integer",
|
|
26
|
+
"minimum": 1
|
|
27
|
+
},
|
|
28
|
+
"receipt_id": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"pattern": "^sha256:[a-f0-9]{64}$"
|
|
31
|
+
},
|
|
32
|
+
"previous_hash": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"pattern": "^sha256:[a-f0-9]{64}$",
|
|
35
|
+
"description": "Hash of the previous v2 receipt, or the canonical legacy/empty prefix for genesis."
|
|
36
|
+
},
|
|
37
|
+
"receipt_hash": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"pattern": "^sha256:[a-f0-9]{64}$",
|
|
40
|
+
"description": "SHA-256 of the canonical receipt without receipt_hash."
|
|
41
|
+
},
|
|
42
|
+
"kind": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"pattern": "^[a-z][a-z0-9._-]{1,63}$"
|
|
45
|
+
},
|
|
46
|
+
"subject": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"description": "Sanitized identity and current operation target.",
|
|
49
|
+
"additionalProperties": true
|
|
50
|
+
},
|
|
51
|
+
"claims": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"description": "Sanitized claims made by the operation.",
|
|
54
|
+
"additionalProperties": true
|
|
55
|
+
},
|
|
56
|
+
"observations": {
|
|
57
|
+
"type": "object",
|
|
58
|
+
"description": "Sanitized source observations keyed by adapter/kind.",
|
|
59
|
+
"additionalProperties": true
|
|
60
|
+
},
|
|
61
|
+
"recorded_at": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"format": "date-time"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|