@opum-ai/lore 0.1.0 → 0.1.1
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/README.md +33 -22
- package/bin/lore.cjs +35 -7
- package/package.json +17 -17
- package/src/adapters/backlog.ts +0 -1084
- package/src/adapters/git.ts +0 -221
- package/src/cli.ts +0 -667
- package/src/commands/agent.ts +0 -301
- package/src/commands/agents.ts +0 -302
- package/src/commands/args.ts +0 -209
- package/src/commands/changed.ts +0 -70
- package/src/commands/check.ts +0 -1031
- package/src/commands/codex-bridge.ts +0 -49
- package/src/commands/concurrency.ts +0 -48
- package/src/commands/context.ts +0 -292
- package/src/commands/discover.ts +0 -89
- package/src/commands/explorer.ts +0 -253
- package/src/commands/export.ts +0 -93
- package/src/commands/fswrite.ts +0 -928
- package/src/commands/graph.ts +0 -291
- package/src/commands/help.ts +0 -151
- package/src/commands/impact.ts +0 -59
- package/src/commands/init.ts +0 -583
- package/src/commands/instructions.ts +0 -91
- package/src/commands/link.ts +0 -929
- package/src/commands/new.ts +0 -476
- package/src/commands/orphans.ts +0 -457
- package/src/commands/path.ts +0 -67
- package/src/commands/provenance.ts +0 -68
- package/src/commands/query.ts +0 -312
- package/src/commands/reconcile-shared.ts +0 -280
- package/src/commands/rename.ts +0 -585
- package/src/commands/replace.ts +0 -320
- package/src/commands/scaffold.ts +0 -346
- package/src/commands/schema.ts +0 -293
- package/src/commands/snapshot.ts +0 -130
- package/src/commands/supersede.ts +0 -400
- package/src/commands/sync.ts +0 -371
- package/src/commands/tasks.ts +0 -271
- package/src/commands/traversal.ts +0 -151
- package/src/commands/validate.ts +0 -226
- package/src/config.ts +0 -598
- package/src/core/agent-bridge.ts +0 -287
- package/src/core/agent-context.ts +0 -498
- package/src/core/agent-profile.ts +0 -447
- package/src/core/bundle.ts +0 -893
- package/src/core/check.ts +0 -853
- package/src/core/codex-bridge.ts +0 -100
- package/src/core/concept.ts +0 -597
- package/src/core/consumer-scaffold.ts +0 -433
- package/src/core/context.ts +0 -271
- package/src/core/explorer-contract.ts +0 -441
- package/src/core/explorer-qualification.ts +0 -58
- package/src/core/explorer.ts +0 -518
- package/src/core/finding.ts +0 -31
- package/src/core/graph.ts +0 -201
- package/src/core/indexes.ts +0 -436
- package/src/core/instructions.ts +0 -209
- package/src/core/ladybug-driver.ts +0 -1795
- package/src/core/ladybug-lifecycle.ts +0 -1178
- package/src/core/ladybug-native.ts +0 -95
- package/src/core/ladybug-source.ts +0 -667
- package/src/core/links.ts +0 -681
- package/src/core/log.ts +0 -253
- package/src/core/managed-block.ts +0 -540
- package/src/core/manifest.ts +0 -718
- package/src/core/order.ts +0 -13
- package/src/core/profile.ts +0 -1007
- package/src/core/projection.ts +0 -195
- package/src/core/query.ts +0 -542
- package/src/core/reconcile.ts +0 -236
- package/src/core/replace.ts +0 -419
- package/src/core/retrieval.ts +0 -213
- package/src/core/rewrite.ts +0 -940
- package/src/core/scaffold.ts +0 -255
- package/src/core/schema.ts +0 -366
- package/src/core/snapshot-runtime.ts +0 -52
- package/src/core/snapshot-store.ts +0 -287
- package/src/core/snapshot.ts +0 -711
- package/src/core/template.ts +0 -429
- package/src/core/traversal.ts +0 -487
- package/src/core/validate.ts +0 -517
- package/src/core/workspace-contract.ts +0 -473
- package/src/core/workspace-projection.ts +0 -365
- package/src/core/workspace-retrieval.ts +0 -196
- package/src/core/workspace-source.ts +0 -174
- package/src/errors.ts +0 -697
- package/src/meta.ts +0 -7
- package/src/output.ts +0 -589
- package/src/scripts/upstream-backlog-watch.ts +0 -288
- package/src/state.ts +0 -390
|
@@ -1,287 +0,0 @@
|
|
|
1
|
-
/** Contained, symlink-safe storage for explicitly retained projection snapshots. */
|
|
2
|
-
|
|
3
|
-
import { randomBytes } from "node:crypto";
|
|
4
|
-
import {
|
|
5
|
-
chmodSync,
|
|
6
|
-
existsSync,
|
|
7
|
-
lstatSync,
|
|
8
|
-
mkdirSync,
|
|
9
|
-
readdirSync,
|
|
10
|
-
readFileSync,
|
|
11
|
-
renameSync,
|
|
12
|
-
rmdirSync,
|
|
13
|
-
rmSync,
|
|
14
|
-
writeFileSync,
|
|
15
|
-
} from "node:fs";
|
|
16
|
-
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
17
|
-
import { LoreError } from "../errors";
|
|
18
|
-
import { digestHex } from "./ladybug-source";
|
|
19
|
-
import {
|
|
20
|
-
parseRetainedSnapshot,
|
|
21
|
-
type RetainedScopeKind,
|
|
22
|
-
type RetainedSnapshot,
|
|
23
|
-
type RetainedSnapshotDescriptor,
|
|
24
|
-
SNAPSHOT_RETENTION_LIMIT,
|
|
25
|
-
serializeRetainedSnapshot,
|
|
26
|
-
snapshotDescriptor,
|
|
27
|
-
} from "./snapshot";
|
|
28
|
-
|
|
29
|
-
export const SNAPSHOT_CACHE_REL_ROOT = ".lore/cache/snapshots/1" as const;
|
|
30
|
-
|
|
31
|
-
export interface SnapshotScopeSelection {
|
|
32
|
-
readonly kind: RetainedScopeKind;
|
|
33
|
-
readonly scopeKey: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface RetainSnapshotResult {
|
|
37
|
-
readonly action: "retained" | "unchanged";
|
|
38
|
-
readonly snapshot: RetainedSnapshotDescriptor;
|
|
39
|
-
readonly retained: number;
|
|
40
|
-
readonly maximum: typeof SNAPSHOT_RETENTION_LIMIT;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface DeleteSnapshotResult {
|
|
44
|
-
readonly scope: SnapshotScopeSelection;
|
|
45
|
-
readonly deleted: readonly string[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function retainSnapshot(root: string, snapshot: RetainedSnapshot): RetainSnapshotResult {
|
|
49
|
-
const canonicalRoot = canonicalRepositoryRoot(root);
|
|
50
|
-
const normalized = parseRetainedSnapshot(snapshot);
|
|
51
|
-
const selection = { kind: normalized.scopeKind, scopeKey: normalized.scopeKey } as const;
|
|
52
|
-
const scopeRoot = scopePath(canonicalRoot, selection);
|
|
53
|
-
ensureScopeLayout(canonicalRoot, selection);
|
|
54
|
-
const existing = listSnapshots(canonicalRoot, selection);
|
|
55
|
-
const same = existing.find((candidate) => candidate.snapshotKey === normalized.snapshotKey);
|
|
56
|
-
if (same !== undefined) {
|
|
57
|
-
const loaded = loadSnapshot(canonicalRoot, selection, normalized.snapshotKey);
|
|
58
|
-
if (serializeRetainedSnapshot(loaded) !== serializeRetainedSnapshot(normalized)) {
|
|
59
|
-
throw new LoreError(
|
|
60
|
-
"validation",
|
|
61
|
-
"retained snapshot key refers to different bytes",
|
|
62
|
-
"delete the corrupt retained snapshot explicitly before retrying",
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
action: "unchanged",
|
|
67
|
-
snapshot: snapshotDescriptor(loaded),
|
|
68
|
-
retained: existing.length,
|
|
69
|
-
maximum: SNAPSHOT_RETENTION_LIMIT,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
if (existing.length >= SNAPSHOT_RETENTION_LIMIT) {
|
|
73
|
-
throw new LoreError(
|
|
74
|
-
"conflict",
|
|
75
|
-
`retained snapshot limit ${SNAPSHOT_RETENTION_LIMIT} reached for this scope`,
|
|
76
|
-
"delete an explicit retained snapshot before retaining another; Lore never evicts history silently",
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
const path = snapshotPath(scopeRoot, normalized.snapshotKey);
|
|
80
|
-
const temporary = join(scopeRoot, `.retaining-${randomBytes(16).toString("hex")}`);
|
|
81
|
-
try {
|
|
82
|
-
writeFileSync(temporary, serializeRetainedSnapshot(normalized), { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
83
|
-
renameSync(temporary, path);
|
|
84
|
-
chmodSync(path, 0o400);
|
|
85
|
-
} catch (cause) {
|
|
86
|
-
if (existsSync(temporary)) rmSync(temporary, { force: true });
|
|
87
|
-
throw mapStoreFailure(cause, "retain snapshot");
|
|
88
|
-
}
|
|
89
|
-
return {
|
|
90
|
-
action: "retained",
|
|
91
|
-
snapshot: snapshotDescriptor(normalized),
|
|
92
|
-
retained: existing.length + 1,
|
|
93
|
-
maximum: SNAPSHOT_RETENTION_LIMIT,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function listSnapshots(root: string, selection: SnapshotScopeSelection): RetainedSnapshotDescriptor[] {
|
|
98
|
-
const canonicalRoot = canonicalRepositoryRoot(root);
|
|
99
|
-
const path = scopePath(canonicalRoot, selection);
|
|
100
|
-
if (!existsSync(path)) return [];
|
|
101
|
-
assertRealDirectory(path, canonicalRoot);
|
|
102
|
-
const entries = readdirSync(path, { withFileTypes: true });
|
|
103
|
-
const snapshots: RetainedSnapshotDescriptor[] = [];
|
|
104
|
-
for (const entry of entries) {
|
|
105
|
-
if (!entry.isFile() || entry.isSymbolicLink() || !/^[0-9a-f]{64}\.json$/u.test(entry.name)) {
|
|
106
|
-
throw new LoreError(
|
|
107
|
-
"denied",
|
|
108
|
-
`unexpected retained snapshot entry ${entry.name}`,
|
|
109
|
-
"remove it manually after verifying the exact contained path",
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
const snapshotKey = `sha256:${entry.name.slice(0, -5)}`;
|
|
113
|
-
snapshots.push(snapshotDescriptor(loadSnapshot(canonicalRoot, selection, snapshotKey)));
|
|
114
|
-
}
|
|
115
|
-
return snapshots.sort((a, b) => compare(a.snapshotKey, b.snapshotKey));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function loadSnapshot(root: string, selection: SnapshotScopeSelection, selector: string): RetainedSnapshot {
|
|
119
|
-
const canonicalRoot = canonicalRepositoryRoot(root);
|
|
120
|
-
const descriptors = listSnapshotFiles(canonicalRoot, selection);
|
|
121
|
-
const exactKey = normalizeSnapshotKey(selector);
|
|
122
|
-
const candidates =
|
|
123
|
-
exactKey !== null
|
|
124
|
-
? descriptors.filter((descriptor) => descriptor.snapshotKey === exactKey)
|
|
125
|
-
: descriptors.filter((descriptor) =>
|
|
126
|
-
descriptor.repositories.some((repository) => repository.gitCommit === selector),
|
|
127
|
-
);
|
|
128
|
-
if (candidates.length === 0)
|
|
129
|
-
throw new LoreError("not_found", `retained snapshot ${JSON.stringify(selector)} was not found`);
|
|
130
|
-
if (candidates.length > 1) {
|
|
131
|
-
throw new LoreError(
|
|
132
|
-
"conflict",
|
|
133
|
-
`retained commit selector ${JSON.stringify(selector)} is ambiguous`,
|
|
134
|
-
"use the exact snapshot key",
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
const selected = candidates[0] as RetainedSnapshotDescriptor;
|
|
138
|
-
const path = snapshotPath(scopePath(canonicalRoot, selection), selected.snapshotKey);
|
|
139
|
-
let value: unknown;
|
|
140
|
-
try {
|
|
141
|
-
const stat = lstatSync(path);
|
|
142
|
-
if (!stat.isFile() || stat.isSymbolicLink())
|
|
143
|
-
throw new LoreError("denied", "retained snapshot must be a real regular file");
|
|
144
|
-
value = JSON.parse(readFileSync(path, "utf8"));
|
|
145
|
-
} catch (cause) {
|
|
146
|
-
if (cause instanceof LoreError) throw cause;
|
|
147
|
-
throw mapStoreFailure(cause, "read retained snapshot");
|
|
148
|
-
}
|
|
149
|
-
const snapshot = parseRetainedSnapshot(value);
|
|
150
|
-
if (
|
|
151
|
-
snapshot.scopeKind !== selection.kind ||
|
|
152
|
-
snapshot.scopeKey !== selection.scopeKey ||
|
|
153
|
-
snapshot.snapshotKey !== selected.snapshotKey
|
|
154
|
-
) {
|
|
155
|
-
throw new LoreError("validation", "retained snapshot path and embedded identity disagree");
|
|
156
|
-
}
|
|
157
|
-
return snapshot;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export function deleteSnapshots(
|
|
161
|
-
root: string,
|
|
162
|
-
selection: SnapshotScopeSelection,
|
|
163
|
-
options: { readonly snapshotKey?: string; readonly all?: boolean },
|
|
164
|
-
): DeleteSnapshotResult {
|
|
165
|
-
if ((options.snapshotKey === undefined) === (options.all !== true)) {
|
|
166
|
-
throw new LoreError("usage", "snapshot deletion needs exactly one snapshot key or --all");
|
|
167
|
-
}
|
|
168
|
-
const canonicalRoot = canonicalRepositoryRoot(root);
|
|
169
|
-
const scopeRoot = scopePath(canonicalRoot, selection);
|
|
170
|
-
const retained = listSnapshots(canonicalRoot, selection);
|
|
171
|
-
if (!existsSync(scopeRoot)) return { scope: selection, deleted: [] };
|
|
172
|
-
const targets =
|
|
173
|
-
options.all === true
|
|
174
|
-
? retained.map((snapshot) => snapshot.snapshotKey)
|
|
175
|
-
: retained
|
|
176
|
-
.filter((snapshot) => snapshot.snapshotKey === normalizeSnapshotKey(options.snapshotKey as string))
|
|
177
|
-
.map((snapshot) => snapshot.snapshotKey);
|
|
178
|
-
if (options.all !== true && targets.length === 0) {
|
|
179
|
-
throw new LoreError("not_found", `retained snapshot ${JSON.stringify(options.snapshotKey)} was not found`);
|
|
180
|
-
}
|
|
181
|
-
for (const snapshotKey of targets) {
|
|
182
|
-
const path = snapshotPath(scopeRoot, snapshotKey);
|
|
183
|
-
const stat = lstatSync(path);
|
|
184
|
-
if (!stat.isFile() || stat.isSymbolicLink())
|
|
185
|
-
throw new LoreError("denied", "refusing to delete a non-file retained snapshot entry");
|
|
186
|
-
rmSync(path, { force: false });
|
|
187
|
-
}
|
|
188
|
-
if (options.all === true && readdirSync(scopeRoot).length === 0) rmdirSync(scopeRoot);
|
|
189
|
-
return { scope: selection, deleted: targets.sort(compare) };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function listSnapshotFiles(root: string, selection: SnapshotScopeSelection): RetainedSnapshotDescriptor[] {
|
|
193
|
-
const scopeRoot = scopePath(root, selection);
|
|
194
|
-
if (!existsSync(scopeRoot)) return [];
|
|
195
|
-
assertRealDirectory(scopeRoot, root);
|
|
196
|
-
return readdirSync(scopeRoot, { withFileTypes: true })
|
|
197
|
-
.map((entry) => {
|
|
198
|
-
if (!entry.isFile() || entry.isSymbolicLink() || !/^[0-9a-f]{64}\.json$/u.test(entry.name)) {
|
|
199
|
-
throw new LoreError("denied", `unexpected retained snapshot entry ${entry.name}`);
|
|
200
|
-
}
|
|
201
|
-
const snapshotKey = `sha256:${entry.name.slice(0, -5)}`;
|
|
202
|
-
const path = join(scopeRoot, entry.name);
|
|
203
|
-
let value: unknown;
|
|
204
|
-
try {
|
|
205
|
-
value = JSON.parse(readFileSync(path, "utf8"));
|
|
206
|
-
} catch (cause) {
|
|
207
|
-
throw mapStoreFailure(cause, "read retained snapshot catalog");
|
|
208
|
-
}
|
|
209
|
-
const snapshot = parseRetainedSnapshot(value);
|
|
210
|
-
if (
|
|
211
|
-
snapshot.scopeKind !== selection.kind ||
|
|
212
|
-
snapshot.scopeKey !== selection.scopeKey ||
|
|
213
|
-
snapshot.snapshotKey !== snapshotKey
|
|
214
|
-
) {
|
|
215
|
-
throw new LoreError("validation", "retained snapshot catalog identity disagrees with its contained path");
|
|
216
|
-
}
|
|
217
|
-
return snapshotDescriptor(snapshot);
|
|
218
|
-
})
|
|
219
|
-
.sort((a, b) => compare(a.snapshotKey, b.snapshotKey));
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function ensureScopeLayout(root: string, selection: SnapshotScopeSelection): void {
|
|
223
|
-
const rel = `${SNAPSHOT_CACHE_REL_ROOT}/${selection.kind}/${digestHex(selection.scopeKey)}`;
|
|
224
|
-
let current = root;
|
|
225
|
-
for (const segment of rel.split("/")) {
|
|
226
|
-
current = join(current, segment);
|
|
227
|
-
assertContained(root, current);
|
|
228
|
-
if (!existsSync(current)) mkdirSync(current, { mode: 0o700 });
|
|
229
|
-
assertRealDirectory(current, root);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function scopePath(root: string, selection: SnapshotScopeSelection): string {
|
|
234
|
-
const path = join(root, SNAPSHOT_CACHE_REL_ROOT, selection.kind, digestHex(selection.scopeKey));
|
|
235
|
-
assertContained(root, path);
|
|
236
|
-
return path;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function snapshotPath(scopeRoot: string, snapshotKey: string): string {
|
|
240
|
-
return join(scopeRoot, `${digestHex(snapshotKey)}.json`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function normalizeSnapshotKey(value: string): string | null {
|
|
244
|
-
try {
|
|
245
|
-
return `sha256:${digestHex(value)}`;
|
|
246
|
-
} catch {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function assertRealDirectory(path: string, root: string): void {
|
|
252
|
-
assertContained(root, path);
|
|
253
|
-
const stat = lstatSync(path);
|
|
254
|
-
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
255
|
-
throw new LoreError(
|
|
256
|
-
"denied",
|
|
257
|
-
`refusing retained snapshot path through non-directory or symlink ${relative(root, path)}`,
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
function assertContained(root: string, path: string): void {
|
|
263
|
-
const rel = relative(root, path);
|
|
264
|
-
if (rel === "" || (!rel.startsWith("..") && !isAbsolute(rel))) return;
|
|
265
|
-
throw new LoreError("denied", "retained snapshot path escapes the repository");
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function canonicalRepositoryRoot(root: string): string {
|
|
269
|
-
const resolved = resolve(root);
|
|
270
|
-
const stat = lstatSync(resolved);
|
|
271
|
-
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
272
|
-
throw new LoreError("denied", "repository root must be a real directory");
|
|
273
|
-
return resolved;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function mapStoreFailure(cause: unknown, action: string): LoreError {
|
|
277
|
-
if (cause instanceof LoreError) return cause;
|
|
278
|
-
const code = (cause as NodeJS.ErrnoException).code;
|
|
279
|
-
if (code === "EACCES" || code === "EPERM") return new LoreError("denied", `cannot ${action}`);
|
|
280
|
-
if (code === "ENOENT") return new LoreError("not_found", `cannot ${action}`);
|
|
281
|
-
if (code === "EEXIST") return new LoreError("conflict", `cannot ${action}: target already exists`);
|
|
282
|
-
return new LoreError("validation", `cannot ${action}`, cause instanceof Error ? cause.message : undefined);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function compare(a: string, b: string): number {
|
|
286
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
287
|
-
}
|