ushman-ledger 0.3.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/AGENTS.md +41 -0
- package/CHANGELOG.md +23 -0
- package/LICENSE.md +21 -0
- package/README.md +233 -0
- package/dist/archive-journal.d.ts +63 -0
- package/dist/archive-journal.d.ts.map +1 -0
- package/dist/archive-journal.js +220 -0
- package/dist/archive.d.ts +30 -0
- package/dist/archive.d.ts.map +1 -0
- package/dist/archive.js +117 -0
- package/dist/async.d.ts +2 -0
- package/dist/async.d.ts.map +1 -0
- package/dist/async.js +20 -0
- package/dist/blobs.d.ts +10 -0
- package/dist/blobs.d.ts.map +1 -0
- package/dist/blobs.js +58 -0
- package/dist/builders.d.ts +465 -0
- package/dist/builders.d.ts.map +1 -0
- package/dist/builders.js +73 -0
- package/dist/candidate-paths.d.ts +3 -0
- package/dist/candidate-paths.d.ts.map +1 -0
- package/dist/candidate-paths.js +11 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +562 -0
- package/dist/coverage.d.ts +8 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/coverage.js +128 -0
- package/dist/doctor.d.ts +9 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +172 -0
- package/dist/handle.d.ts +28 -0
- package/dist/handle.d.ts.map +1 -0
- package/dist/handle.js +90 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/json.d.ts +4 -0
- package/dist/json.d.ts.map +1 -0
- package/dist/json.js +25 -0
- package/dist/lab-min.d.ts +9 -0
- package/dist/lab-min.d.ts.map +1 -0
- package/dist/lab-min.js +23 -0
- package/dist/list.d.ts +582 -0
- package/dist/list.d.ts.map +1 -0
- package/dist/list.js +139 -0
- package/dist/manifest-update.d.ts +13 -0
- package/dist/manifest-update.d.ts.map +1 -0
- package/dist/manifest-update.js +43 -0
- package/dist/note.d.ts +13 -0
- package/dist/note.d.ts.map +1 -0
- package/dist/note.js +15 -0
- package/dist/patch-metadata.d.ts +37 -0
- package/dist/patch-metadata.d.ts.map +1 -0
- package/dist/patch-metadata.js +300 -0
- package/dist/read-index.d.ts +114 -0
- package/dist/read-index.d.ts.map +1 -0
- package/dist/read-index.js +210 -0
- package/dist/record.d.ts +25 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +268 -0
- package/dist/recovery.d.ts +39 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +189 -0
- package/dist/render/analytics-summary.d.ts +58 -0
- package/dist/render/analytics-summary.d.ts.map +1 -0
- package/dist/render/analytics-summary.js +151 -0
- package/dist/render/dependency-graph.d.ts +3 -0
- package/dist/render/dependency-graph.d.ts.map +1 -0
- package/dist/render/dependency-graph.js +18 -0
- package/dist/render/jsonl.d.ts +3 -0
- package/dist/render/jsonl.d.ts.map +1 -0
- package/dist/render/jsonl.js +8 -0
- package/dist/render/retro.d.ts +6 -0
- package/dist/render/retro.d.ts.map +1 -0
- package/dist/render/retro.js +124 -0
- package/dist/render/timeline-html.d.ts +3 -0
- package/dist/render/timeline-html.d.ts.map +1 -0
- package/dist/render/timeline-html.js +37 -0
- package/dist/schema/entry.d.ts +3298 -0
- package/dist/schema/entry.d.ts.map +1 -0
- package/dist/schema/entry.js +619 -0
- package/dist/schema/manifest.d.ts +42 -0
- package/dist/schema/manifest.d.ts.map +1 -0
- package/dist/schema/manifest.js +27 -0
- package/dist/schema/note.d.ts +10 -0
- package/dist/schema/note.d.ts.map +1 -0
- package/dist/schema/note.js +2 -0
- package/dist/storage/filesystem.d.ts +35 -0
- package/dist/storage/filesystem.d.ts.map +1 -0
- package/dist/storage/filesystem.js +258 -0
- package/dist/storage/lock.d.ts +18 -0
- package/dist/storage/lock.d.ts.map +1 -0
- package/dist/storage/lock.js +224 -0
- package/dist/uuid.d.ts +7 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +25 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type CoverageReport = {
|
|
2
|
+
readonly coverageRatio: number;
|
|
3
|
+
readonly coveredFiles: string[];
|
|
4
|
+
readonly totalFiles: number;
|
|
5
|
+
readonly uncoveredFiles: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare const computeCoverage: (workspaceRoot: string) => Promise<CoverageReport>;
|
|
8
|
+
//# sourceMappingURL=coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../src/coverage.ts"],"names":[],"mappings":"AAgGA,MAAM,MAAM,cAAc,GAAG;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,CAAC;CACrC,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,cAAc,CAgDnF,CAAC"}
|
package/dist/coverage.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { mapWithConcurrencyLimit } from "./async.js";
|
|
4
|
+
import { CANDIDATE_EXCLUDE_GLOBS, CANDIDATE_FILE_GLOBS } from "./candidate-paths.js";
|
|
5
|
+
import { readLabManifestMin } from "./lab-min.js";
|
|
6
|
+
import { loadLedgerState } from "./recovery.js";
|
|
7
|
+
const EXCLUDED_ROOTS = new Set(CANDIDATE_EXCLUDE_GLOBS.map((glob) => glob.replace(/\/\*\*$/u, '')));
|
|
8
|
+
const CANDIDATE_DIRECTORIES = CANDIDATE_FILE_GLOBS.filter((glob) => glob.endsWith('/**/*')).map((glob) => glob.slice(0, -5));
|
|
9
|
+
const CANDIDATE_FILES = CANDIDATE_FILE_GLOBS.filter((glob) => !glob.endsWith('/**/*'));
|
|
10
|
+
const FILE_STAT_CONCURRENCY = 16;
|
|
11
|
+
const toPosix = (value) => value.replaceAll(path.sep, '/');
|
|
12
|
+
const isMissingPathError = (error) => {
|
|
13
|
+
const code = error.code;
|
|
14
|
+
return code === 'ENOENT' || code === 'ENOTDIR';
|
|
15
|
+
};
|
|
16
|
+
const shouldExclude = (relativePath) => {
|
|
17
|
+
const normalized = toPosix(relativePath);
|
|
18
|
+
for (const prefix of EXCLUDED_ROOTS) {
|
|
19
|
+
if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
};
|
|
25
|
+
const walkDirectory = async (root, current) => {
|
|
26
|
+
const dirPath = path.join(root, current);
|
|
27
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
28
|
+
const files = [];
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
const relative = current ? path.join(current, entry.name) : entry.name;
|
|
31
|
+
if (shouldExclude(relative)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
files.push(...(await walkDirectory(root, relative)));
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
files.push(toPosix(relative));
|
|
39
|
+
}
|
|
40
|
+
return files;
|
|
41
|
+
};
|
|
42
|
+
const collectCandidateFiles = async (workspaceRoot) => {
|
|
43
|
+
const files = new Set();
|
|
44
|
+
const directoryResults = await Promise.all(CANDIDATE_DIRECTORIES.map(async (relative) => {
|
|
45
|
+
try {
|
|
46
|
+
return await walkDirectory(workspaceRoot, relative);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (!isMissingPathError(error)) {
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}));
|
|
55
|
+
for (const directoryFiles of directoryResults) {
|
|
56
|
+
for (const filePath of directoryFiles) {
|
|
57
|
+
files.add(filePath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const relative of CANDIDATE_FILES) {
|
|
61
|
+
try {
|
|
62
|
+
const fileStat = await stat(path.join(workspaceRoot, relative));
|
|
63
|
+
if (fileStat.isFile()) {
|
|
64
|
+
files.add(relative);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (!isMissingPathError(error)) {
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return [...files].sort((left, right) => left.localeCompare(right));
|
|
74
|
+
};
|
|
75
|
+
const getWorkspaceInitMs = async (workspaceRoot) => {
|
|
76
|
+
// Coverage intentionally ignores files that predate workspace initialization.
|
|
77
|
+
const labManifest = await readLabManifestMin(workspaceRoot);
|
|
78
|
+
if (labManifest.createdAt) {
|
|
79
|
+
const parsed = Date.parse(labManifest.createdAt);
|
|
80
|
+
if (!Number.isNaN(parsed)) {
|
|
81
|
+
return parsed;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const labManifestStat = await stat(path.join(workspaceRoot, '.lab', 'lab.json'));
|
|
85
|
+
return labManifestStat.birthtimeMs || labManifestStat.mtimeMs;
|
|
86
|
+
};
|
|
87
|
+
export const computeCoverage = async (workspaceRoot) => {
|
|
88
|
+
const [{ readIndex }, candidateFiles, workspaceInitMs] = await Promise.all([
|
|
89
|
+
loadLedgerState(workspaceRoot),
|
|
90
|
+
collectCandidateFiles(workspaceRoot),
|
|
91
|
+
getWorkspaceInitMs(workspaceRoot),
|
|
92
|
+
]);
|
|
93
|
+
const coverageIndex = new Set(readIndex.coveredFiles.map((filePath) => toPosix(filePath)));
|
|
94
|
+
const candidateStats = await mapWithConcurrencyLimit(candidateFiles, FILE_STAT_CONCURRENCY, async (relativePath) => {
|
|
95
|
+
try {
|
|
96
|
+
return {
|
|
97
|
+
mtimeMs: (await stat(path.join(workspaceRoot, relativePath))).mtimeMs,
|
|
98
|
+
relativePath,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (isMissingPathError(error)) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
const existingCandidateStats = candidateStats.filter((candidateStat) => candidateStat !== null);
|
|
109
|
+
const coveredFiles = [];
|
|
110
|
+
const uncoveredFiles = [];
|
|
111
|
+
for (const { mtimeMs, relativePath } of existingCandidateStats) {
|
|
112
|
+
if (mtimeMs <= workspaceInitMs) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (coverageIndex.has(relativePath)) {
|
|
116
|
+
coveredFiles.push(relativePath);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
uncoveredFiles.push(relativePath);
|
|
120
|
+
}
|
|
121
|
+
const totalFiles = coveredFiles.length + uncoveredFiles.length;
|
|
122
|
+
return {
|
|
123
|
+
coverageRatio: totalFiles === 0 ? 1 : coveredFiles.length / totalFiles,
|
|
124
|
+
coveredFiles,
|
|
125
|
+
totalFiles,
|
|
126
|
+
uncoveredFiles,
|
|
127
|
+
};
|
|
128
|
+
};
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type PreparedLedgerState } from './recovery.ts';
|
|
2
|
+
export declare const runLedgerDoctor: (workspaceRoot: string, options?: {
|
|
3
|
+
readonly skipPrepare?: boolean;
|
|
4
|
+
readonly state?: PreparedLedgerState;
|
|
5
|
+
}) => Promise<{
|
|
6
|
+
issues: string[];
|
|
7
|
+
ok: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAMA,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAsL1E,eAAO,MAAM,eAAe,GACxB,eAAe,MAAM,EACrB,UAAS;IAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,mBAAmB,CAAA;CAAO;;;EAyCzF,CAAC"}
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { mapWithConcurrencyLimit } from "./async.js";
|
|
3
|
+
import { resolveBlobPath } from "./blobs.js";
|
|
4
|
+
import { sha256File } from "./json.js";
|
|
5
|
+
import { getOrderedEntryLocations, readManifestEntryBatch } from "./list.js";
|
|
6
|
+
import { isReadIndexCurrent, readReadIndex } from "./read-index.js";
|
|
7
|
+
import { loadLedgerState } from "./recovery.js";
|
|
8
|
+
import { readManifest } from "./storage/filesystem.js";
|
|
9
|
+
const BLOB_HASH_CONCURRENCY = 16;
|
|
10
|
+
const ENTRY_READ_BATCH_SIZE = 32;
|
|
11
|
+
const checkPrevChain = (entry, previousByPhase, issues) => {
|
|
12
|
+
const expectedPrev = previousByPhase.get(entry.phase) ?? null;
|
|
13
|
+
if (entry.prevEntryId !== expectedPrev) {
|
|
14
|
+
issues.push(`Phase ${entry.phase} has broken prevEntryId chain at ${entry.id}: expected ${expectedPrev ?? 'null'}, found ${entry.prevEntryId ?? 'null'}.`);
|
|
15
|
+
}
|
|
16
|
+
previousByPhase.set(entry.phase, entry.id);
|
|
17
|
+
};
|
|
18
|
+
const checkBlobPresence = async (workspaceRoot, blobChecks) => {
|
|
19
|
+
const results = await mapWithConcurrencyLimit(blobChecks, BLOB_HASH_CONCURRENCY, async ({ blobHash, entryId }) => {
|
|
20
|
+
try {
|
|
21
|
+
const blobPath = resolveBlobPath(workspaceRoot, blobHash);
|
|
22
|
+
await stat(blobPath);
|
|
23
|
+
const actualHash = await sha256File(blobPath);
|
|
24
|
+
if (actualHash !== blobHash) {
|
|
25
|
+
return `Blob ${blobHash} for ${entryId} is corrupted (found ${actualHash}).`;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return `Missing blob ${blobHash} for ${entryId}.`;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return results.filter((issue) => issue !== null);
|
|
34
|
+
};
|
|
35
|
+
const checkManifestCounts = (manifest, entryCount) => {
|
|
36
|
+
const issues = [];
|
|
37
|
+
if (manifest.entryCount !== entryCount) {
|
|
38
|
+
issues.push(`Manifest entryCount ${manifest.entryCount} does not match disk entries ${entryCount}.`);
|
|
39
|
+
}
|
|
40
|
+
if (manifest.lastSequence !== entryCount) {
|
|
41
|
+
issues.push(`Manifest lastSequence ${manifest.lastSequence} does not match disk entries ${entryCount}.`);
|
|
42
|
+
}
|
|
43
|
+
return issues;
|
|
44
|
+
};
|
|
45
|
+
const finalizeManifestChecks = ({ entryCount, issues, latestByPhase, manifest, unseenManifestEntryIds, }) => {
|
|
46
|
+
issues.push(...checkManifestCounts(manifest, entryCount));
|
|
47
|
+
for (const entryId of unseenManifestEntryIds) {
|
|
48
|
+
issues.push(`Manifest entry location points to missing disk entry ${entryId}.`);
|
|
49
|
+
}
|
|
50
|
+
for (const [phase, latest] of latestByPhase.entries()) {
|
|
51
|
+
if (manifest.perPhaseLatest[phase] !== latest.entryId) {
|
|
52
|
+
issues.push(`Manifest perPhaseLatest mismatch for ${phase}: expected ${latest.entryId}, found ${manifest.perPhaseLatest[phase] ?? 'missing'}.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const checkManifestSequenceOrder = (entryLocations, issues) => {
|
|
57
|
+
for (let index = 0; index < entryLocations.length; index += 1) {
|
|
58
|
+
const [entryId, location] = entryLocations[index];
|
|
59
|
+
const expectedSequence = index + 1;
|
|
60
|
+
if (location.sequence !== expectedSequence) {
|
|
61
|
+
issues.push(`Manifest sequence mismatch for ${entryId}: expected ${expectedSequence}, found ${location.sequence}.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const buildReadFailure = (error) => ({
|
|
66
|
+
issues: [`Failed to read ledger state: ${error instanceof Error ? (error.message ?? error.name) : String(error)}.`],
|
|
67
|
+
ok: false,
|
|
68
|
+
});
|
|
69
|
+
const inspectDoctorEntry = ({ blobChecks, entry, issues, latestByPhase, manifest, previousByPhase, unseenManifestEntryIds, }) => {
|
|
70
|
+
unseenManifestEntryIds.delete(entry.id);
|
|
71
|
+
checkPrevChain(entry, previousByPhase, issues);
|
|
72
|
+
const manifestLocation = manifest.entryLocations[entry.id];
|
|
73
|
+
if (!manifestLocation) {
|
|
74
|
+
issues.push(`Manifest is missing entry location for ${entry.id}.`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (manifestLocation.phase !== entry.phase) {
|
|
78
|
+
issues.push(`Manifest phase mismatch for ${entry.id}: expected ${entry.phase}, found ${manifestLocation.phase}.`);
|
|
79
|
+
}
|
|
80
|
+
const currentLatest = latestByPhase.get(entry.phase);
|
|
81
|
+
if (!currentLatest || manifestLocation.sequence > currentLatest.sequence) {
|
|
82
|
+
latestByPhase.set(entry.phase, { entryId: entry.id, sequence: manifestLocation.sequence });
|
|
83
|
+
}
|
|
84
|
+
if (entry.kind !== 'agent-patch' && entry.kind !== 'operator-patch') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const blobHash of new Set([entry.diff.blobSha256, ...(entry.links.blobs ?? [])])) {
|
|
88
|
+
blobChecks.push({ blobHash, entryId: entry.id });
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const collectDoctorState = async (workspaceRoot, manifest, readIndex) => {
|
|
92
|
+
const issues = [];
|
|
93
|
+
const previousByPhase = new Map();
|
|
94
|
+
const latestByPhase = new Map();
|
|
95
|
+
const unseenManifestEntryIds = new Set(Object.keys(manifest.entryLocations));
|
|
96
|
+
const blobChecks = [];
|
|
97
|
+
let entryCount = 0;
|
|
98
|
+
const orderedEntries = getOrderedEntryLocations(manifest, readIndex, {});
|
|
99
|
+
checkManifestSequenceOrder(orderedEntries, issues);
|
|
100
|
+
for (let index = 0; index < orderedEntries.length; index += ENTRY_READ_BATCH_SIZE) {
|
|
101
|
+
const batch = orderedEntries.slice(index, index + ENTRY_READ_BATCH_SIZE);
|
|
102
|
+
const resolvedEntries = await readManifestEntryBatch({
|
|
103
|
+
allowMissing: true,
|
|
104
|
+
entryLocations: batch,
|
|
105
|
+
workspaceRoot,
|
|
106
|
+
});
|
|
107
|
+
for (const resolvedEntry of resolvedEntries) {
|
|
108
|
+
if (!resolvedEntry.entry) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
entryCount += 1;
|
|
112
|
+
inspectDoctorEntry({
|
|
113
|
+
blobChecks,
|
|
114
|
+
entry: resolvedEntry.entry,
|
|
115
|
+
issues,
|
|
116
|
+
latestByPhase,
|
|
117
|
+
manifest,
|
|
118
|
+
previousByPhase,
|
|
119
|
+
unseenManifestEntryIds,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
blobChecks,
|
|
125
|
+
entryCount,
|
|
126
|
+
issues,
|
|
127
|
+
latestByPhase,
|
|
128
|
+
unseenManifestEntryIds,
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
export const runLedgerDoctor = async (workspaceRoot, options = {}) => {
|
|
132
|
+
let preparedState;
|
|
133
|
+
try {
|
|
134
|
+
if (options.state) {
|
|
135
|
+
preparedState = options.state;
|
|
136
|
+
}
|
|
137
|
+
else if (!options.skipPrepare) {
|
|
138
|
+
preparedState = await loadLedgerState(workspaceRoot);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const manifest = await readManifest(workspaceRoot);
|
|
142
|
+
const readIndex = await readReadIndex(workspaceRoot);
|
|
143
|
+
if (!readIndex || !isReadIndexCurrent(readIndex, manifest)) {
|
|
144
|
+
throw new Error('runLedgerDoctor skipPrepare requires a current read index snapshot.');
|
|
145
|
+
}
|
|
146
|
+
preparedState = { manifest, readIndex };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return buildReadFailure(error);
|
|
151
|
+
}
|
|
152
|
+
let doctorState;
|
|
153
|
+
try {
|
|
154
|
+
doctorState = await collectDoctorState(workspaceRoot, preparedState.manifest, preparedState.readIndex);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
return buildReadFailure(error);
|
|
158
|
+
}
|
|
159
|
+
const { blobChecks, entryCount, issues, latestByPhase, unseenManifestEntryIds } = doctorState;
|
|
160
|
+
issues.push(...(await checkBlobPresence(workspaceRoot, blobChecks)));
|
|
161
|
+
finalizeManifestChecks({
|
|
162
|
+
entryCount,
|
|
163
|
+
issues,
|
|
164
|
+
latestByPhase,
|
|
165
|
+
manifest: preparedState.manifest,
|
|
166
|
+
unseenManifestEntryIds,
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
issues,
|
|
170
|
+
ok: issues.length === 0,
|
|
171
|
+
};
|
|
172
|
+
};
|
package/dist/handle.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { computeCoverage } from './coverage.ts';
|
|
2
|
+
import { runLedgerDoctor } from './doctor.ts';
|
|
3
|
+
import { type LedgerFilter } from './list.ts';
|
|
4
|
+
import { appendNote, type NoteBody } from './note.ts';
|
|
5
|
+
import type { LedgerEntry, LedgerPhase } from './schema/entry.ts';
|
|
6
|
+
export type LedgerHandle = {
|
|
7
|
+
readonly archive: (outPath: string) => Promise<{
|
|
8
|
+
integrityHash: string;
|
|
9
|
+
}>;
|
|
10
|
+
readonly computeCoverage: () => Promise<Awaited<ReturnType<typeof computeCoverage>>>;
|
|
11
|
+
readonly doctor: () => Promise<Awaited<ReturnType<typeof runLedgerDoctor>>>;
|
|
12
|
+
readonly list: (filter?: LedgerFilter) => AsyncIterable<LedgerEntry>;
|
|
13
|
+
readonly note: (subkind: Parameters<typeof appendNote>[1], body: NoteBody) => Promise<{
|
|
14
|
+
id: string;
|
|
15
|
+
}>;
|
|
16
|
+
readonly record: (entry: unknown) => Promise<{
|
|
17
|
+
id: string;
|
|
18
|
+
}>;
|
|
19
|
+
readonly render: (options: {
|
|
20
|
+
fresh?: boolean;
|
|
21
|
+
out?: string;
|
|
22
|
+
phase?: LedgerPhase;
|
|
23
|
+
to: 'analytics-summary' | 'dependency-graph' | 'jsonl' | 'retro' | 'timeline-html';
|
|
24
|
+
}) => Promise<string>;
|
|
25
|
+
readonly show: (entryId: string) => Promise<LedgerEntry | null>;
|
|
26
|
+
};
|
|
27
|
+
export declare const openLedger: (workspaceRoot: string) => Promise<LedgerHandle>;
|
|
28
|
+
//# sourceMappingURL=handle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handle.d.ts","sourceRoot":"","sources":["../src/handle.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAA8B,KAAK,YAAY,EAAe,MAAM,WAAW,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAQtD,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAwDlE,MAAM,MAAM,YAAY,GAAG;IACvB,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1E,QAAQ,CAAC,eAAe,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC;IACrF,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC;IAC5E,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,YAAY,KAAK,aAAa,CAAC,WAAW,CAAC,CAAC;IACrE,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtG,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;QACvB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,WAAW,CAAC;QACpB,EAAE,EAAE,mBAAmB,GAAG,kBAAkB,GAAG,OAAO,GAAG,OAAO,GAAG,eAAe,CAAC;KACtF,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACnE,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,YAAY,CAsC5E,CAAC"}
|
package/dist/handle.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { archiveLedger } from "./archive.js";
|
|
2
|
+
import { computeCoverage } from "./coverage.js";
|
|
3
|
+
import { runLedgerDoctor } from "./doctor.js";
|
|
4
|
+
import { readLabManifestMin } from "./lab-min.js";
|
|
5
|
+
import { iterateEntriesFromManifest, listEntries } from "./list.js";
|
|
6
|
+
import { appendNote } from "./note.js";
|
|
7
|
+
import { appendRecord, readEntryById } from "./record.js";
|
|
8
|
+
import { loadLedgerState, prepareLedgerState } from "./recovery.js";
|
|
9
|
+
import { renderAnalyticsSummary } from "./render/analytics-summary.js";
|
|
10
|
+
import { renderDependencyGraph } from "./render/dependency-graph.js";
|
|
11
|
+
import { renderJsonl } from "./render/jsonl.js";
|
|
12
|
+
import { renderRetroMarkdown } from "./render/retro.js";
|
|
13
|
+
import { renderTimelineHtml } from "./render/timeline-html.js";
|
|
14
|
+
import { resolveLedgerPaths, writeAtomicTextFile } from "./storage/filesystem.js";
|
|
15
|
+
const renderEntries = async ({ filter, target, useCache, workspaceRoot, }) => {
|
|
16
|
+
const { manifest, readIndex } = await loadLedgerState(workspaceRoot);
|
|
17
|
+
switch (target) {
|
|
18
|
+
case 'analytics-summary':
|
|
19
|
+
return renderAnalyticsSummary({
|
|
20
|
+
cachePath: resolveLedgerPaths(workspaceRoot).analyticsSummaryFile,
|
|
21
|
+
entries: iterateEntriesFromManifest(workspaceRoot, manifest, readIndex, filter),
|
|
22
|
+
filter,
|
|
23
|
+
manifest,
|
|
24
|
+
readIndex,
|
|
25
|
+
useCache,
|
|
26
|
+
});
|
|
27
|
+
case 'dependency-graph':
|
|
28
|
+
return renderDependencyGraph(iterateEntriesFromManifest(workspaceRoot, manifest, readIndex, filter));
|
|
29
|
+
case 'jsonl':
|
|
30
|
+
return renderJsonl(iterateEntriesFromManifest(workspaceRoot, manifest, readIndex, filter));
|
|
31
|
+
case 'timeline-html':
|
|
32
|
+
return renderTimelineHtml(iterateEntriesFromManifest(workspaceRoot, manifest, readIndex, filter));
|
|
33
|
+
case 'retro':
|
|
34
|
+
return renderRetroMarkdown({
|
|
35
|
+
entries: iterateEntriesFromManifest(workspaceRoot, manifest, readIndex, filter),
|
|
36
|
+
manifest,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const resolveRenderOutputPath = (workspaceRoot, target) => {
|
|
41
|
+
const paths = resolveLedgerPaths(workspaceRoot);
|
|
42
|
+
switch (target) {
|
|
43
|
+
case 'analytics-summary':
|
|
44
|
+
return paths.analyticsSummaryFile;
|
|
45
|
+
case 'retro':
|
|
46
|
+
return paths.renderFile;
|
|
47
|
+
case 'timeline-html':
|
|
48
|
+
return paths.renderTimelineFile;
|
|
49
|
+
default:
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
export const openLedger = async (workspaceRoot) => {
|
|
54
|
+
await readLabManifestMin(workspaceRoot);
|
|
55
|
+
await prepareLedgerState(workspaceRoot);
|
|
56
|
+
return {
|
|
57
|
+
archive: async (outPath) => archiveLedger(workspaceRoot, outPath),
|
|
58
|
+
computeCoverage: async () => computeCoverage(workspaceRoot),
|
|
59
|
+
doctor: async () => runLedgerDoctor(workspaceRoot),
|
|
60
|
+
list: (filter = {}) => listEntries(workspaceRoot, filter),
|
|
61
|
+
note: async (subkind, body) => {
|
|
62
|
+
const result = await appendNote(workspaceRoot, subkind, body);
|
|
63
|
+
return { id: result.id };
|
|
64
|
+
},
|
|
65
|
+
record: async (entry) => {
|
|
66
|
+
const result = await appendRecord(workspaceRoot, entry);
|
|
67
|
+
return { id: result.id };
|
|
68
|
+
},
|
|
69
|
+
render: async (options) => {
|
|
70
|
+
const content = await renderEntries({
|
|
71
|
+
filter: { phase: options.phase },
|
|
72
|
+
target: options.to,
|
|
73
|
+
useCache: options.to === 'analytics-summary' ? options.fresh !== true : false,
|
|
74
|
+
workspaceRoot,
|
|
75
|
+
});
|
|
76
|
+
const outputPath = options.out ?? resolveRenderOutputPath(workspaceRoot, options.to);
|
|
77
|
+
if (outputPath) {
|
|
78
|
+
await writeAtomicTextFile(outputPath, `${content}${content.endsWith('\n') ? '' : '\n'}`);
|
|
79
|
+
}
|
|
80
|
+
return content;
|
|
81
|
+
},
|
|
82
|
+
show: async (entryId) => {
|
|
83
|
+
const { manifest } = await loadLedgerState(workspaceRoot);
|
|
84
|
+
if (!manifest.entryLocations[entryId]) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return readEntryById(workspaceRoot, manifest, entryId);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { buildAgentPatchRecord, buildCorrectionRecord, buildDescopeBriefRecord, buildMergeReturnRecord, buildMergeReturnRejectedRecord, buildOperatorDecisionRecord, buildOperatorPatchRecord, buildRevertRecord, buildReworkTestRetiredRecord, buildRollbackRecord, buildStageTransitionRecord, buildStripDecisionRevertedRecord, buildValidatorResultRecord, } from './builders.ts';
|
|
2
|
+
export { runLedgerCli } from './cli.ts';
|
|
3
|
+
export { type CoverageReport, computeCoverage } from './coverage.ts';
|
|
4
|
+
export { type LedgerHandle, openLedger } from './handle.ts';
|
|
5
|
+
export type { LedgerFilter } from './list.ts';
|
|
6
|
+
export { derivePatchPayloadFromDiffText, parseStructuredPatch } from './patch-metadata.ts';
|
|
7
|
+
export { type AnalyticsSummary, AnalyticsSummarySchema } from './render/analytics-summary.ts';
|
|
8
|
+
export { type AgentPatchDiff, AgentPatchDiffSchema, AgentPatchEntrySchema, AgentPatchRecordSchema, CorrectionEntrySchema, type CorrectionPayload, CorrectionPayloadSchema, CorrectionRecordSchema, DescopeBriefEntrySchema, type DescopeBriefPayload, DescopeBriefPayloadSchema, DescopeBriefRecordSchema, EmitterSchema, type LedgerEntry, LedgerEntrySchema, type LedgerKind, LedgerLinksSchema, type LedgerPhase, LedgerPhaseSchema, type LedgerRecord, LedgerRecordSchema, MergeReturnEntrySchema, type MergeReturnPayload, MergeReturnPayloadSchema, MergeReturnRecordSchema, MergeReturnRejectedEntrySchema, MergeReturnRejectedPayloadSchema, MergeReturnRejectedRecordSchema, NoteEntrySchema, type OperatorDecisionAction, OperatorDecisionActionSchema, OperatorDecisionEntrySchema, OperatorDecisionPayloadSchema, OperatorDecisionRecordSchema, OperatorPatchEntrySchema, OperatorPatchRecordSchema, type PatchHunk, PatchHunkSchema, type PatchPayload, PatchPayloadSchema, PatchPayloadWriteSchema, parseLedgerEntry, parseLedgerRecord, RevertEntrySchema, RevertPayloadSchema, RevertRecordSchema, ReworkTestRetiredEntrySchema, type ReworkTestRetiredPayload, ReworkTestRetiredPayloadSchema, ReworkTestRetiredRecordSchema, RollbackEntrySchema, type RollbackPayload, RollbackPayloadSchema, RollbackRecordSchema, RuntimeEventEntrySchema, StageTransitionEntrySchema, type StageTransitionPayload, StageTransitionPayloadSchema, StageTransitionRecordSchema, StripDecisionRevertedEntrySchema, type StripDecisionRevertedPayload, StripDecisionRevertedPayloadSchema, StripDecisionRevertedRecordSchema, ToolInvocationEntrySchema, ToolInvocationRecordSchema, ValidatorResultEntrySchema, ValidatorResultPayloadSchema, ValidatorResultRecordSchema, } from './schema/entry.ts';
|
|
9
|
+
export { type NoteSubkind, NoteSubkindSchema } from './schema/note.ts';
|
|
10
|
+
export { resolveLedgerPaths } from './storage/filesystem.ts';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,8BAA8B,EAC9B,2BAA2B,EAC3B,wBAAwB,EACxB,iBAAiB,EACjB,4BAA4B,EAC5B,mBAAmB,EACnB,0BAA0B,EAC1B,gCAAgC,EAChC,0BAA0B,GAC7B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,KAAK,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,EAAE,KAAK,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC5D,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,8BAA8B,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3F,OAAO,EAAE,KAAK,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC9F,OAAO,EACH,KAAK,cAAc,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,iBAAiB,EACtB,uBAAuB,EACvB,sBAAsB,EACtB,uBAAuB,EACvB,KAAK,mBAAmB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,aAAa,EACb,KAAK,WAAW,EAChB,iBAAiB,EACjB,KAAK,UAAU,EACf,iBAAiB,EACjB,KAAK,WAAW,EAChB,iBAAiB,EACjB,KAAK,YAAY,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,KAAK,kBAAkB,EACvB,wBAAwB,EACxB,uBAAuB,EACvB,8BAA8B,EAC9B,gCAAgC,EAChC,+BAA+B,EAC/B,eAAe,EACf,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,2BAA2B,EAC3B,6BAA6B,EAC7B,4BAA4B,EAC5B,wBAAwB,EACxB,yBAAyB,EACzB,KAAK,SAAS,EACd,eAAe,EACf,KAAK,YAAY,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,4BAA4B,EAC5B,KAAK,wBAAwB,EAC7B,8BAA8B,EAC9B,6BAA6B,EAC7B,mBAAmB,EACnB,KAAK,eAAe,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,0BAA0B,EAC1B,KAAK,sBAAsB,EAC3B,4BAA4B,EAC5B,2BAA2B,EAC3B,gCAAgC,EAChC,KAAK,4BAA4B,EACjC,kCAAkC,EAClC,iCAAiC,EACjC,yBAAyB,EACzB,0BAA0B,EAC1B,0BAA0B,EAC1B,4BAA4B,EAC5B,2BAA2B,GAC9B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { buildAgentPatchRecord, buildCorrectionRecord, buildDescopeBriefRecord, buildMergeReturnRecord, buildMergeReturnRejectedRecord, buildOperatorDecisionRecord, buildOperatorPatchRecord, buildRevertRecord, buildReworkTestRetiredRecord, buildRollbackRecord, buildStageTransitionRecord, buildStripDecisionRevertedRecord, buildValidatorResultRecord, } from "./builders.js";
|
|
2
|
+
export { runLedgerCli } from "./cli.js";
|
|
3
|
+
export { computeCoverage } from "./coverage.js";
|
|
4
|
+
export { openLedger } from "./handle.js";
|
|
5
|
+
export { derivePatchPayloadFromDiffText, parseStructuredPatch } from "./patch-metadata.js";
|
|
6
|
+
export { AnalyticsSummarySchema } from "./render/analytics-summary.js";
|
|
7
|
+
export { AgentPatchDiffSchema, AgentPatchEntrySchema, AgentPatchRecordSchema, CorrectionEntrySchema, CorrectionPayloadSchema, CorrectionRecordSchema, DescopeBriefEntrySchema, DescopeBriefPayloadSchema, DescopeBriefRecordSchema, EmitterSchema, LedgerEntrySchema, LedgerLinksSchema, LedgerPhaseSchema, LedgerRecordSchema, MergeReturnEntrySchema, MergeReturnPayloadSchema, MergeReturnRecordSchema, MergeReturnRejectedEntrySchema, MergeReturnRejectedPayloadSchema, MergeReturnRejectedRecordSchema, NoteEntrySchema, OperatorDecisionActionSchema, OperatorDecisionEntrySchema, OperatorDecisionPayloadSchema, OperatorDecisionRecordSchema, OperatorPatchEntrySchema, OperatorPatchRecordSchema, PatchHunkSchema, PatchPayloadSchema, PatchPayloadWriteSchema, parseLedgerEntry, parseLedgerRecord, RevertEntrySchema, RevertPayloadSchema, RevertRecordSchema, ReworkTestRetiredEntrySchema, ReworkTestRetiredPayloadSchema, ReworkTestRetiredRecordSchema, RollbackEntrySchema, RollbackPayloadSchema, RollbackRecordSchema, RuntimeEventEntrySchema, StageTransitionEntrySchema, StageTransitionPayloadSchema, StageTransitionRecordSchema, StripDecisionRevertedEntrySchema, StripDecisionRevertedPayloadSchema, StripDecisionRevertedRecordSchema, ToolInvocationEntrySchema, ToolInvocationRecordSchema, ValidatorResultEntrySchema, ValidatorResultPayloadSchema, ValidatorResultRecordSchema, } from "./schema/entry.js";
|
|
8
|
+
export { NoteSubkindSchema } from "./schema/note.js";
|
|
9
|
+
export { resolveLedgerPaths } from "./storage/filesystem.js";
|
package/dist/json.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,eAAe,GAAI,OAAO,OAAO,EAAE,gBAAc,KAAG,MAMhE,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,OAAO,UAAU,GAAG,MAAM,KAAG,MAA0D,CAAC;AAElH,eAAO,MAAM,UAAU,GAAU,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAajE,CAAC"}
|
package/dist/json.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
3
|
+
import stringify from 'safe-stable-stringify';
|
|
4
|
+
export const stableStringify = (value, pretty = false) => {
|
|
5
|
+
const text = stringify(value, undefined, pretty ? 2 : undefined);
|
|
6
|
+
if (text === undefined) {
|
|
7
|
+
throw new Error('Failed to serialize value.');
|
|
8
|
+
}
|
|
9
|
+
return text;
|
|
10
|
+
};
|
|
11
|
+
export const sha256Hex = (value) => createHash('sha256').update(value).digest('hex');
|
|
12
|
+
export const sha256File = async (filePath) => {
|
|
13
|
+
const hash = createHash('sha256');
|
|
14
|
+
await new Promise((resolve, reject) => {
|
|
15
|
+
const stream = createReadStream(filePath);
|
|
16
|
+
stream.on('data', (chunk) => {
|
|
17
|
+
hash.update(chunk);
|
|
18
|
+
});
|
|
19
|
+
stream.on('end', () => {
|
|
20
|
+
resolve();
|
|
21
|
+
});
|
|
22
|
+
stream.on('error', reject);
|
|
23
|
+
});
|
|
24
|
+
return hash.digest('hex');
|
|
25
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const LabManifestMin: z.ZodObject<{
|
|
3
|
+
createdAt: z.ZodOptional<z.ZodString>;
|
|
4
|
+
schemaVersion: z.ZodLiteral<"ushman-lab/v4.0">;
|
|
5
|
+
workspaceId: z.ZodString;
|
|
6
|
+
}, z.core.$loose>;
|
|
7
|
+
export type LabManifestMin = z.infer<typeof LabManifestMin>;
|
|
8
|
+
export declare const readLabManifestMin: (workspaceRoot: string) => Promise<LabManifestMin>;
|
|
9
|
+
//# sourceMappingURL=lab-min.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lab-min.d.ts","sourceRoot":"","sources":["../src/lab-min.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,cAAc;;;;iBAMT,CAAC;AAEnB,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAE5D,eAAO,MAAM,kBAAkB,GAAU,eAAe,MAAM,KAAG,OAAO,CAAC,cAAc,CAWtF,CAAC"}
|
package/dist/lab-min.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export const LabManifestMin = z
|
|
5
|
+
.object({
|
|
6
|
+
createdAt: z.string().optional(),
|
|
7
|
+
schemaVersion: z.literal('ushman-lab/v4.0'),
|
|
8
|
+
workspaceId: z.string().uuid(),
|
|
9
|
+
})
|
|
10
|
+
.passthrough();
|
|
11
|
+
export const readLabManifestMin = async (workspaceRoot) => {
|
|
12
|
+
const filePath = path.join(workspaceRoot, '.lab', 'lab.json');
|
|
13
|
+
try {
|
|
14
|
+
const text = await readFile(filePath, 'utf8');
|
|
15
|
+
return LabManifestMin.parse(JSON.parse(text));
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === 'ENOENT') {
|
|
19
|
+
throw new Error(`Workspace is not initialized: missing ${filePath}.`);
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
};
|