vdelta 0.1.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/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/adapters/vitest/capture.d.ts +50 -0
- package/dist/adapters/vitest/capture.js +7 -0
- package/dist/adapters/vitest/capture.js.map +1 -0
- package/dist/adapters/vitest/recorder.d.ts +31 -0
- package/dist/adapters/vitest/recorder.js +214 -0
- package/dist/adapters/vitest/recorder.js.map +1 -0
- package/dist/adapters/vitest/reporter.d.ts +15 -0
- package/dist/adapters/vitest/reporter.js +97 -0
- package/dist/adapters/vitest/reporter.js.map +1 -0
- package/dist/canonical.d.ts +9 -0
- package/dist/canonical.js +45 -0
- package/dist/canonical.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +264 -0
- package/dist/cli.js.map +1 -0
- package/dist/compare.d.ts +37 -0
- package/dist/compare.js +476 -0
- package/dist/compare.js.map +1 -0
- package/dist/digest.d.ts +5 -0
- package/dist/digest.js +14 -0
- package/dist/digest.js.map +1 -0
- package/dist/gate.d.ts +11 -0
- package/dist/gate.js +135 -0
- package/dist/gate.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/redact.d.ts +10 -0
- package/dist/redact.js +49 -0
- package/dist/redact.js.map +1 -0
- package/dist/render.d.ts +6 -0
- package/dist/render.js +53 -0
- package/dist/render.js.map +1 -0
- package/dist/run.d.ts +19 -0
- package/dist/run.js +158 -0
- package/dist/run.js.map +1 -0
- package/dist/schema.d.ts +236 -0
- package/dist/schema.js +395 -0
- package/dist/schema.js.map +1 -0
- package/dist/store.d.ts +38 -0
- package/dist/store.js +161 -0
- package/dist/store.js.map +1 -0
- package/dist/tree-digest.d.ts +11 -0
- package/dist/tree-digest.js +103 -0
- package/dist/tree-digest.js.map +1 -0
- package/package.json +53 -0
- package/spec/veridelta-1.md +1093 -0
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type RunRecord } from './schema.js';
|
|
2
|
+
export declare class StoreCorruptError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class LockHeldError extends Error {
|
|
6
|
+
constructor();
|
|
7
|
+
}
|
|
8
|
+
/** run_id = content address of the record excluding the recording group (§3.5). */
|
|
9
|
+
export declare function computeRunId(record: RunRecord): string;
|
|
10
|
+
export declare class RunStore {
|
|
11
|
+
readonly dir: string;
|
|
12
|
+
constructor(worktreeRoot: string);
|
|
13
|
+
private get runsDir();
|
|
14
|
+
private get indexPath();
|
|
15
|
+
private get lastPath();
|
|
16
|
+
private get lockPath();
|
|
17
|
+
ensure(): void;
|
|
18
|
+
/** Advisory lock via mkdir; throws LockHeldError when already held (fail-open at the caller, INV-5). */
|
|
19
|
+
acquireLock(): void;
|
|
20
|
+
releaseLock(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Persist a record. Atomic (tmp+rename). Content-identical re-records are
|
|
23
|
+
* idempotent: no duplicate index line, no rewrite of the immutable record.
|
|
24
|
+
*/
|
|
25
|
+
writeRun(record: RunRecord): {
|
|
26
|
+
runId: string;
|
|
27
|
+
isNew: boolean;
|
|
28
|
+
};
|
|
29
|
+
/** Insertion-ordered run ids, oldest first, de-duplicated (§4.3 floor). */
|
|
30
|
+
listRunIds(): string[];
|
|
31
|
+
lastRunId(): string | null;
|
|
32
|
+
/** Resolve a possibly-prefixed run id to a stored full id (§3.5 MAY). */
|
|
33
|
+
resolveRunId(idOrPrefix: string): string | null;
|
|
34
|
+
/** Read and validate a record; parse/validation failures are store corruption. */
|
|
35
|
+
readRun(runId: string): RunRecord;
|
|
36
|
+
/** INV-10: recompute the content address and compare with the stored id. */
|
|
37
|
+
verifyIntegrity(runId: string): boolean;
|
|
38
|
+
}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-addressed run store (spec §4): immutable records, atomic writes,
|
|
3
|
+
* fail-open advisory lock, enforced gitignore. Recency is store insertion
|
|
4
|
+
* order (the append-only index), never timestamps (§7.8).
|
|
5
|
+
*/
|
|
6
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmdirSync, writeFileSync, } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import { canonicalJson } from './canonical.js';
|
|
10
|
+
import { sha256Hex } from './digest.js';
|
|
11
|
+
import { parseRunRecord } from './schema.js';
|
|
12
|
+
export class StoreCorruptError extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'StoreCorruptError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class LockHeldError extends Error {
|
|
19
|
+
constructor() {
|
|
20
|
+
super('advisory lock is held');
|
|
21
|
+
this.name = 'LockHeldError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const RUN_ID_RE = /^run_[0-9a-f]{64}$/;
|
|
25
|
+
/** run_id = content address of the record excluding the recording group (§3.5). */
|
|
26
|
+
export function computeRunId(record) {
|
|
27
|
+
const { recording: _recording, ...addressed } = record;
|
|
28
|
+
return `run_${sha256Hex(canonicalJson(addressed))}`;
|
|
29
|
+
}
|
|
30
|
+
export class RunStore {
|
|
31
|
+
dir;
|
|
32
|
+
constructor(worktreeRoot) {
|
|
33
|
+
this.dir = join(worktreeRoot, '.veridelta');
|
|
34
|
+
}
|
|
35
|
+
get runsDir() {
|
|
36
|
+
return join(this.dir, 'runs');
|
|
37
|
+
}
|
|
38
|
+
get indexPath() {
|
|
39
|
+
return join(this.dir, 'index');
|
|
40
|
+
}
|
|
41
|
+
get lastPath() {
|
|
42
|
+
return join(this.dir, 'last');
|
|
43
|
+
}
|
|
44
|
+
get lockPath() {
|
|
45
|
+
return join(this.dir, 'lock');
|
|
46
|
+
}
|
|
47
|
+
ensure() {
|
|
48
|
+
mkdirSync(this.runsDir, { recursive: true });
|
|
49
|
+
const gi = join(this.dir, '.gitignore');
|
|
50
|
+
if (!existsSync(gi))
|
|
51
|
+
writeFileSync(gi, '*\n');
|
|
52
|
+
}
|
|
53
|
+
/** Advisory lock via mkdir; throws LockHeldError when already held (fail-open at the caller, INV-5). */
|
|
54
|
+
acquireLock() {
|
|
55
|
+
try {
|
|
56
|
+
mkdirSync(this.lockPath);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw new LockHeldError();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
releaseLock() {
|
|
63
|
+
try {
|
|
64
|
+
rmdirSync(this.lockPath);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// releasing a lock we no longer hold is not an error path worth failing on
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Persist a record. Atomic (tmp+rename). Content-identical re-records are
|
|
72
|
+
* idempotent: no duplicate index line, no rewrite of the immutable record.
|
|
73
|
+
*/
|
|
74
|
+
writeRun(record) {
|
|
75
|
+
const runId = computeRunId(record);
|
|
76
|
+
const path = join(this.runsDir, `${runId}.json`);
|
|
77
|
+
const isNew = !existsSync(path);
|
|
78
|
+
if (isNew) {
|
|
79
|
+
const tmp = join(this.runsDir, `.tmp-${randomUUID()}`);
|
|
80
|
+
writeFileSync(tmp, `${JSON.stringify(record, null, 1)}\n`);
|
|
81
|
+
renameSync(tmp, path);
|
|
82
|
+
appendFileSync(this.indexPath, `${runId}\n`);
|
|
83
|
+
}
|
|
84
|
+
const tmpLast = join(this.dir, `.tmp-last-${randomUUID()}`);
|
|
85
|
+
writeFileSync(tmpLast, `${runId}\n`);
|
|
86
|
+
renameSync(tmpLast, this.lastPath);
|
|
87
|
+
return { runId, isNew };
|
|
88
|
+
}
|
|
89
|
+
/** Insertion-ordered run ids, oldest first, de-duplicated (§4.3 floor). */
|
|
90
|
+
listRunIds() {
|
|
91
|
+
if (!existsSync(this.indexPath))
|
|
92
|
+
return [];
|
|
93
|
+
let raw;
|
|
94
|
+
try {
|
|
95
|
+
raw = readFileSync(this.indexPath, 'utf8');
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new StoreCorruptError('index unreadable');
|
|
99
|
+
}
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
const out = [];
|
|
102
|
+
for (const line of raw.split('\n')) {
|
|
103
|
+
const id = line.trim();
|
|
104
|
+
if (id === '')
|
|
105
|
+
continue;
|
|
106
|
+
if (!RUN_ID_RE.test(id))
|
|
107
|
+
throw new StoreCorruptError(`malformed index line: ${id}`);
|
|
108
|
+
if (!seen.has(id)) {
|
|
109
|
+
seen.add(id);
|
|
110
|
+
out.push(id);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
lastRunId() {
|
|
116
|
+
if (!existsSync(this.lastPath))
|
|
117
|
+
return null;
|
|
118
|
+
const id = readFileSync(this.lastPath, 'utf8').trim();
|
|
119
|
+
if (!RUN_ID_RE.test(id))
|
|
120
|
+
throw new StoreCorruptError(`malformed last pointer: ${id}`);
|
|
121
|
+
return id;
|
|
122
|
+
}
|
|
123
|
+
/** Resolve a possibly-prefixed run id to a stored full id (§3.5 MAY). */
|
|
124
|
+
resolveRunId(idOrPrefix) {
|
|
125
|
+
if (RUN_ID_RE.test(idOrPrefix)) {
|
|
126
|
+
return existsSync(join(this.runsDir, `${idOrPrefix}.json`)) ? idOrPrefix : null;
|
|
127
|
+
}
|
|
128
|
+
if (!existsSync(this.runsDir))
|
|
129
|
+
return null;
|
|
130
|
+
const matches = readdirSync(this.runsDir)
|
|
131
|
+
.filter((f) => f.endsWith('.json') && f.startsWith(idOrPrefix))
|
|
132
|
+
.map((f) => f.slice(0, -'.json'.length))
|
|
133
|
+
.filter((f) => RUN_ID_RE.test(f));
|
|
134
|
+
return matches.length === 1 ? (matches[0] ?? null) : null;
|
|
135
|
+
}
|
|
136
|
+
/** Read and validate a record; parse/validation failures are store corruption. */
|
|
137
|
+
readRun(runId) {
|
|
138
|
+
const path = join(this.runsDir, `${runId}.json`);
|
|
139
|
+
let raw;
|
|
140
|
+
try {
|
|
141
|
+
raw = readFileSync(path, 'utf8');
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
throw new StoreCorruptError(`run record missing: ${runId}`);
|
|
145
|
+
}
|
|
146
|
+
let parsed;
|
|
147
|
+
try {
|
|
148
|
+
parsed = JSON.parse(raw);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
throw new StoreCorruptError(`run record unparseable: ${runId}`);
|
|
152
|
+
}
|
|
153
|
+
return parseRunRecord(parsed);
|
|
154
|
+
}
|
|
155
|
+
/** INV-10: recompute the content address and compare with the stored id. */
|
|
156
|
+
verifyIntegrity(runId) {
|
|
157
|
+
const record = this.readRun(runId);
|
|
158
|
+
return computeRunId(record) === runId;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,cAAc,EACd,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,EACV,SAAS,EACT,aAAa,GACd,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,cAAc,EAAkB,MAAM,aAAa,CAAA;AAE5D,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;IACjC,CAAC;CACF;AAED,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtC;QACE,KAAK,CAAC,uBAAuB,CAAC,CAAA;QAC9B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAA;IAC7B,CAAC;CACF;AAED,MAAM,SAAS,GAAG,oBAAoB,CAAA;AAEtC,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC5C,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,SAAS,EAAE,GAAG,MAAM,CAAA;IACtD,OAAO,OAAO,SAAS,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,EAAE,CAAA;AACrD,CAAC;AAED,MAAM,OAAO,QAAQ;IACV,GAAG,CAAQ;IAEpB,YAAY,YAAoB;QAC9B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAA;IAC7C,CAAC;IAED,IAAY,OAAO;QACjB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAC/B,CAAC;IAED,IAAY,SAAS;QACnB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAChC,CAAC;IAED,IAAY,QAAQ;QAClB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAC/B,CAAC;IAED,IAAY,QAAQ;QAClB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAC/B,CAAC;IAED,MAAM;QACJ,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAA;QACvC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAAE,aAAa,CAAC,EAAE,EAAE,KAAK,CAAC,CAAA;IAC/C,CAAC;IAED,wGAAwG;IACxG,WAAW;QACT,IAAI,CAAC;YACH,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,aAAa,EAAE,CAAA;QAC3B,CAAC;IACH,CAAC;IAED,WAAW;QACT,IAAI,CAAC;YACH,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,2EAA2E;QAC7E,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,QAAQ,CAAC,MAAiB;QACxB,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;QAClC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;QAChD,MAAM,KAAK,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QAC/B,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,UAAU,EAAE,EAAE,CAAC,CAAA;YACtD,aAAa,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;YAC1D,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;YACrB,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,IAAI,CAAC,CAAA;QAC9C,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,UAAU,EAAE,EAAE,CAAC,CAAA;QAC3D,aAAa,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC,CAAA;QACpC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QAClC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IACzB,CAAC;IAED,2EAA2E;IAC3E,UAAU;QACR,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC;YAAE,OAAO,EAAE,CAAA;QAC1C,IAAI,GAAW,CAAA;QACf,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,iBAAiB,CAAC,kBAAkB,CAAC,CAAA;QACjD,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;QAC9B,MAAM,GAAG,GAAa,EAAE,CAAA;QACxB,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;YACtB,IAAI,EAAE,KAAK,EAAE;gBAAE,SAAQ;YACvB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,MAAM,IAAI,iBAAiB,CAAC,yBAAyB,EAAE,EAAE,CAAC,CAAA;YACnF,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;gBAClB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;gBACZ,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YACd,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IAED,SAAS;QACP,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAA;QAC3C,MAAM,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAA;QACrD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,iBAAiB,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAA;QACrF,OAAO,EAAE,CAAA;IACX,CAAC;IAED,yEAAyE;IACzE,YAAY,CAAC,UAAkB;QAC7B,IAAI,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,OAAO,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,UAAU,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAA;QACjF,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAA;QAC1C,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;aACtC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;aAC9D,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;aACvC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QACnC,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC3D,CAAC;IAED,kFAAkF;IAClF,OAAO,CAAC,KAAa;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;QAChD,IAAI,GAAW,CAAA;QACf,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,iBAAiB,CAAC,uBAAuB,KAAK,EAAE,CAAC,CAAA;QAC7D,CAAC;QACD,IAAI,MAAe,CAAA;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,iBAAiB,CAAC,2BAA2B,KAAK,EAAE,CAAC,CAAA;QACjE,CAAC;QACD,OAAO,cAAc,CAAC,MAAM,CAAC,CAAA;IAC/B,CAAC;IAED,4EAA4E;IAC5E,eAAe,CAAC,KAAa;QAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAClC,OAAO,YAAY,CAAC,MAAM,CAAC,KAAK,KAAK,CAAA;IACvC,CAAC;CACF"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function treeDigest(worktree: string): Promise<string>;
|
|
2
|
+
export declare function gitHead(worktree: string): Promise<string | null>;
|
|
3
|
+
export declare function gitBranch(worktree: string): Promise<string>;
|
|
4
|
+
export declare function gitRepoRoot(dir: string): Promise<string | null>;
|
|
5
|
+
/** Resolve a ref to {commitSha, treeOid}; null when unresolvable. */
|
|
6
|
+
export declare function resolveRef(worktree: string, ref: string): Promise<{
|
|
7
|
+
commit: string;
|
|
8
|
+
tree: string;
|
|
9
|
+
} | null>;
|
|
10
|
+
/** Deterministic digest material for the dirty state (status + diff vs HEAD). */
|
|
11
|
+
export declare function dirtyDiffMaterial(worktree: string): Promise<string>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tree_digest (spec §3.5, algorithm proven in
|
|
3
|
+
* claudedocs/2026-07-16-expA-tree-digest.md, ported verbatim):
|
|
4
|
+
* a git tree object id over tracked+staged+unstaged+untracked files,
|
|
5
|
+
* excluding committed-gitignore'd paths, computed against a dedicated
|
|
6
|
+
* throwaway index seeded from HEAD, with host-dependent git settings pinned.
|
|
7
|
+
* Rendered as a bare 40-hex OID (documented deviation from sha256:).
|
|
8
|
+
*/
|
|
9
|
+
import { execFile } from 'node:child_process';
|
|
10
|
+
import { promisify } from 'node:util';
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
import { rm } from 'node:fs/promises';
|
|
13
|
+
import { tmpdir } from 'node:os';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
const execFileP = promisify(execFile);
|
|
16
|
+
const PIN = [
|
|
17
|
+
'-c', 'core.autocrlf=false',
|
|
18
|
+
'-c', 'core.eol=lf',
|
|
19
|
+
'-c', 'core.excludesFile=/dev/null',
|
|
20
|
+
'-c', 'advice.addEmbeddedRepo=false',
|
|
21
|
+
];
|
|
22
|
+
async function git(worktree, args, env) {
|
|
23
|
+
const { stdout } = await execFileP('git', [...PIN, '-C', worktree, ...args], {
|
|
24
|
+
env: { ...process.env, ...env },
|
|
25
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
26
|
+
});
|
|
27
|
+
return stdout.trim();
|
|
28
|
+
}
|
|
29
|
+
export async function treeDigest(worktree) {
|
|
30
|
+
const idx = join(tmpdir(), `vd-idx-${randomUUID()}`);
|
|
31
|
+
const idxEnv = { GIT_INDEX_FILE: idx };
|
|
32
|
+
try {
|
|
33
|
+
let hasHead = true;
|
|
34
|
+
try {
|
|
35
|
+
await git(worktree, ['rev-parse', '--verify', '-q', 'HEAD']);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
hasHead = false;
|
|
39
|
+
}
|
|
40
|
+
if (hasHead) {
|
|
41
|
+
await git(worktree, ['read-tree', 'HEAD'], idxEnv);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
await git(worktree, ['read-tree', '--empty'], idxEnv);
|
|
45
|
+
}
|
|
46
|
+
await git(worktree, ['add', '-A'], idxEnv);
|
|
47
|
+
return await git(worktree, ['write-tree'], idxEnv);
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
await rm(idx, { force: true });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export async function gitHead(worktree) {
|
|
54
|
+
try {
|
|
55
|
+
return await git(worktree, ['rev-parse', 'HEAD']);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export async function gitBranch(worktree) {
|
|
62
|
+
try {
|
|
63
|
+
const b = await git(worktree, ['symbolic-ref', '--short', '-q', 'HEAD']);
|
|
64
|
+
return b === '' ? 'DETACHED' : b;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return 'DETACHED';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export async function gitRepoRoot(dir) {
|
|
71
|
+
try {
|
|
72
|
+
return await git(dir, ['rev-parse', '--show-toplevel']);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Resolve a ref to {commitSha, treeOid}; null when unresolvable. */
|
|
79
|
+
export async function resolveRef(worktree, ref) {
|
|
80
|
+
try {
|
|
81
|
+
const commit = await git(worktree, ['rev-parse', '--verify', `${ref}^{commit}`]);
|
|
82
|
+
const tree = await git(worktree, ['rev-parse', '--verify', `${ref}^{tree}`]);
|
|
83
|
+
return { commit, tree };
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Deterministic digest material for the dirty state (status + diff vs HEAD). */
|
|
90
|
+
export async function dirtyDiffMaterial(worktree) {
|
|
91
|
+
try {
|
|
92
|
+
const status = await git(worktree, ['status', '--porcelain=v1', '--untracked-files=all']);
|
|
93
|
+
let diff = '';
|
|
94
|
+
if ((await gitHead(worktree)) !== null) {
|
|
95
|
+
diff = await git(worktree, ['diff', 'HEAD', '--no-color']);
|
|
96
|
+
}
|
|
97
|
+
return `${status}\n${diff}`;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return '';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=tree-digest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-digest.js","sourceRoot":"","sources":["../src/tree-digest.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vdelta",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reference implementation of veridelta/1 — proof-carrying verification deltas for coding-agent development loops",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/it-all-playpark/veridelta.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/it-all-playpark/veridelta#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/it-all-playpark/veridelta/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"verification",
|
|
16
|
+
"testing",
|
|
17
|
+
"test-evidence",
|
|
18
|
+
"provenance",
|
|
19
|
+
"coding-agent",
|
|
20
|
+
"ai-agent",
|
|
21
|
+
"vitest",
|
|
22
|
+
"quality-gate"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"vdelta": "dist/cli.js"
|
|
27
|
+
},
|
|
28
|
+
"main": "./dist/index.js",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": "./dist/index.js",
|
|
31
|
+
"./vitest": "./dist/adapters/vitest/reporter.js"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"spec"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc -p tsconfig.build.json",
|
|
42
|
+
"prepublishOnly": "npm run build && npm run test",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:unit": "vitest run --project unit",
|
|
45
|
+
"test:conformance": "vitest run --project conformance",
|
|
46
|
+
"typecheck": "tsc --noEmit"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^24.0.0",
|
|
50
|
+
"typescript": "^5.9.0",
|
|
51
|
+
"vitest": "^4.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|