@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
package/src/adapters/git.ts
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* adapters/git.ts — the real, `git log`-shelling {@link GitAdapter} (`core/log.ts`, LORE-26).
|
|
3
|
-
*
|
|
4
|
-
* `core/log.ts`'s `GitAdapter.history()` is **synchronous** (LORE-47's already-shipped contract:
|
|
5
|
-
* `buildLog`/`generateLog` are pure, sync functions), so this real implementation shells `git`
|
|
6
|
-
* via `Bun.spawnSync` — not the async `Bun.spawn` seam `state.ts`/`adapters/backlog.ts` use for
|
|
7
|
-
* their own, independently-async, concerns. This is a *different* git concern from `state.ts`
|
|
8
|
-
* (which commits `backlog/`): this module only ever *reads* history to build `docs/`'s `log.md`,
|
|
9
|
-
* and never writes.
|
|
10
|
-
*
|
|
11
|
-
* Parsing strategy: git's `--name-only` output separates a commit's file list from the next
|
|
12
|
-
* commit's header with a blank line, which is a reliable, well-documented convention — but rather
|
|
13
|
-
* than lean on that alone, each commit's formatted header is prefixed with a control-character
|
|
14
|
-
* sentinel line ({@link SENTINEL}) that is *practically never* a real subject or file path, so
|
|
15
|
-
* splitting the whole output into per-commit blocks never depends on counting blank lines. That
|
|
16
|
-
* sentinel is a fixed string, not a proof: a commit subject, body, or touched file path CAN
|
|
17
|
-
* legitimately (if vanishingly rarely) contain the exact same bytes, which would otherwise silently
|
|
18
|
-
* misparse block boundaries — `history()` closes that gap with a `git rev-list --count` cross-check
|
|
19
|
-
* (see `countCommits`) rather than trusting the sentinel split unconditionally.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import type { GitAdapter, GitCommit, GitLogRange } from "../core/log";
|
|
23
|
-
import { DOCS_DIR } from "../core/scaffold";
|
|
24
|
-
import { LoreError, stderrHint } from "../errors";
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* A control-character line prefixing every commit's formatted header — practically never a
|
|
28
|
-
* legitimate subject or file path, but NOT provably impossible (see this module's own doc comment
|
|
29
|
-
* and {@link parseHistory}); `history()`'s `git rev-list --count` cross-check is what actually
|
|
30
|
-
* guards against a collision instead of silently trusting this string is unique.
|
|
31
|
-
*/
|
|
32
|
-
const SENTINEL = "\x01lore:log-entry\x01";
|
|
33
|
-
|
|
34
|
-
/** `git log --pretty=format:` string: one sentinel line, then hash/committer-date/subject each on their own line. */
|
|
35
|
-
const PRETTY_FORMAT = `${SENTINEL}%n%H%n%cI%n%s`;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Wrap a path in git's `:(literal)` pathspec magic so git matches it byte-for-byte, mirroring the
|
|
39
|
-
* `:(literal)` convention `state.ts` adopted in LORE-49: a root containing pathspec-magic characters
|
|
40
|
-
* (`*`, `?`, `[`, or a leading `:`) would otherwise be silently reinterpreted as pathspec magic
|
|
41
|
-
* instead of a literal directory name. Production only ever passes the constant "docs" today, so
|
|
42
|
-
* this is currently a no-op in practice — but `GenerateLogOptions.root` and this module's `history()`
|
|
43
|
-
* seam are exported, and a profile-configurable docs root is a plausible future.
|
|
44
|
-
*/
|
|
45
|
-
function literalPathspec(path: string): string {
|
|
46
|
-
return `:(literal)${path}`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Build the real {@link GitAdapter}, shelling `git` in `cwd` (the repo root). */
|
|
50
|
-
export function realGitAdapter(cwd: string): GitAdapter {
|
|
51
|
-
return {
|
|
52
|
-
history(range: GitLogRange, root: string = DOCS_DIR): readonly GitCommit[] {
|
|
53
|
-
// `--relative` (a no-op when `cwd` is the git repository's own top level) makes `--name-only`
|
|
54
|
-
// report paths relative to `cwd` instead of git's default of always relative to the repo's
|
|
55
|
-
// top level: without it, a bundle nested below the repo root (docs/backlog not at the git
|
|
56
|
-
// top level) would get every file path prefixed with that nesting, which core/log.ts's
|
|
57
|
-
// `isUnderRoot` (matched against the bundle-relative `docs` root) would never recognize as
|
|
58
|
-
// under the bundle -- silently producing an empty log.md forever.
|
|
59
|
-
//
|
|
60
|
-
// The trailing `-- <root>` pathspec (LORE-143) is what actually scopes the walk: git prunes
|
|
61
|
-
// any commit whose diff touches nothing under `root` *before* it ever reaches this process,
|
|
62
|
-
// rather than this adapter buffering the entire repository's history for `core/log.ts` to
|
|
63
|
-
// discard commit-by-commit. Interpreted relative to `cwd`, exactly like `--relative` above, so
|
|
64
|
-
// the same nested-bundle case still scopes to `<cwd>/<root>`, not the repo top level's.
|
|
65
|
-
// `-c core.quotePath=false` is a global option (must precede the `log` subcommand): without
|
|
66
|
-
// it, git's default C-style quoting renders any non-ASCII byte in a `--name-only` path as
|
|
67
|
-
// an escaped octal sequence inside a quoted string (e.g. `"caf\303\251.md"` for `café.md`)
|
|
68
|
-
// instead of the raw UTF-8 bytes, which `parseHistory` has no unquoting logic for — every
|
|
69
|
-
// non-ASCII path would round-trip mangled into the generated log.md.
|
|
70
|
-
const args = [
|
|
71
|
-
"-c",
|
|
72
|
-
"core.quotePath=false",
|
|
73
|
-
"log",
|
|
74
|
-
"--name-only",
|
|
75
|
-
"--relative",
|
|
76
|
-
`--pretty=format:${PRETTY_FORMAT}`,
|
|
77
|
-
...rangeArgs(range),
|
|
78
|
-
"--",
|
|
79
|
-
literalPathspec(root),
|
|
80
|
-
];
|
|
81
|
-
const proc = Bun.spawnSync(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
82
|
-
if (proc.exitCode !== 0) {
|
|
83
|
-
throw new LoreError(
|
|
84
|
-
"drift",
|
|
85
|
-
`\`git log\` exited ${proc.exitCode}: could not build log.md`,
|
|
86
|
-
stderrHint(proc.stderr.toString("utf8")) ??
|
|
87
|
-
"check that this is a git repository and the given range is valid",
|
|
88
|
-
{ exitCode: proc.exitCode, range },
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
const commits = parseHistory(proc.stdout.toString("utf8"));
|
|
92
|
-
|
|
93
|
-
// Guard against SENTINEL-collision corruption (see parseHistory's own doc comment): a commit
|
|
94
|
-
// subject, body, or touched file path that happens to literally contain the SENTINEL byte
|
|
95
|
-
// sequence introduces a spurious split point, silently turning one real commit into multiple
|
|
96
|
-
// malformed ones (or misattributing a file path as a hash/timestamp). That corruption always
|
|
97
|
-
// changes how many blocks `parseHistory` ends up with, so cross-checking the parsed count
|
|
98
|
-
// against `git rev-list --count` for the *identical* range and pathspec — independent ground
|
|
99
|
-
// truth that never goes through SENTINEL-delimited parsing at all — reliably catches it and
|
|
100
|
-
// fails loud instead of silently emitting a corrupted log.md.
|
|
101
|
-
const expected = countCommits(cwd, range, root);
|
|
102
|
-
if (commits.length !== expected) {
|
|
103
|
-
throw new LoreError(
|
|
104
|
-
"drift",
|
|
105
|
-
`\`git log\` parsing produced ${commits.length} commit(s) but \`git rev-list --count\` reports ${expected} for the same range: a commit subject, body, or touched file path likely contains the literal control-character sequence lore uses to delimit each commit's block, corrupting the split`,
|
|
106
|
-
"rename the offending file or amend the commit message so it no longer contains the byte sequence \\x01lore:log-entry\\x01, then retry",
|
|
107
|
-
{ range, root, parsedCount: commits.length, expectedCount: expected },
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
return commits;
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Resolve `HEAD` to a concrete sha in `cwd`, or `null` when there is no `HEAD` yet (a freshly
|
|
117
|
-
* `lore init`ed repository with no commits at all). Callers treat `null` as "no history" and skip
|
|
118
|
-
* `history()` entirely, rather than pinning to a sha that does not exist.
|
|
119
|
-
*
|
|
120
|
-
* `git rev-parse HEAD` exits non-zero both for this legitimate "empty repo" case AND for a
|
|
121
|
-
* genuinely broken one (not a git repository at all, a corrupted `.git`, a malformed `HEAD` file,
|
|
122
|
-
* …) — the two are NOT the same condition and must not collapse to the same `null` result: only the
|
|
123
|
-
* first should silently skip history (there is none to skip to), while the second must fail loud,
|
|
124
|
-
* matching {@link realGitAdapter}'s own `history()` behavior for the identical condition.
|
|
125
|
-
*
|
|
126
|
-
* Disambiguated with a second, cheap check: `git symbolic-ref -q HEAD`. A genuinely unborn branch's
|
|
127
|
-
* `HEAD` is a well-formed symbolic ref (e.g. `ref: refs/heads/main`) whose TARGET simply doesn't
|
|
128
|
-
* exist yet — `symbolic-ref` only validates and dereferences the ref's own on-disk FORMAT, it never
|
|
129
|
-
* checks whether the target exists, so it still succeeds in that case. A corrupted-but-present
|
|
130
|
-
* `.git` (e.g. `HEAD` containing an invalid ref name, or bytes that are not a well-formed symbolic
|
|
131
|
-
* ref at all) fails this check instead, correctly falling through to the throw below — unlike
|
|
132
|
-
* `git rev-parse --git-dir` (the previous disambiguator), which only proves ".git exists and is
|
|
133
|
-
* minimally readable" and succeeds even when `HEAD` itself is malformed, silently misclassifying
|
|
134
|
-
* that corruption as the benign unborn-branch case.
|
|
135
|
-
*
|
|
136
|
-
* @throws LoreError `drift` (exit 6) when `HEAD` fails to resolve for any reason OTHER than the
|
|
137
|
-
* repository being real but commit-less.
|
|
138
|
-
*/
|
|
139
|
-
export function resolveHeadSha(cwd: string): string | null {
|
|
140
|
-
const head = Bun.spawnSync(["git", "rev-parse", "HEAD"], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
141
|
-
if (head.exitCode === 0) {
|
|
142
|
-
return head.stdout.toString("utf8").trim();
|
|
143
|
-
}
|
|
144
|
-
const symbolicRef = Bun.spawnSync(["git", "symbolic-ref", "-q", "HEAD"], {
|
|
145
|
-
cwd,
|
|
146
|
-
stdout: "pipe",
|
|
147
|
-
stderr: "pipe",
|
|
148
|
-
});
|
|
149
|
-
if (symbolicRef.exitCode === 0) {
|
|
150
|
-
return null; // a real repository whose HEAD is a well-formed symbolic ref with no commits yet
|
|
151
|
-
}
|
|
152
|
-
throw new LoreError(
|
|
153
|
-
"drift",
|
|
154
|
-
`\`git rev-parse HEAD\` exited ${head.exitCode}: could not resolve the repository's history`,
|
|
155
|
-
stderrHint(head.stderr.toString("utf8")) ?? "check that this is a git repository",
|
|
156
|
-
{ exitCode: head.exitCode },
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** `git log`'s range arguments: `from..to` when `from` is given, else just `to` (from repo start). */
|
|
161
|
-
function rangeArgs(range: GitLogRange): string[] {
|
|
162
|
-
return [range.from !== undefined ? `${range.from}..${range.to}` : range.to];
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* The ground-truth number of commits touching `root` within `range`, via `git rev-list --count`
|
|
167
|
-
* scoped with the identical range and `-- <root>` pathspec `history()` used for `git log` — used
|
|
168
|
-
* only to cross-check {@link parseHistory}'s output against a code path that never depends on
|
|
169
|
-
* SENTINEL-delimited parsing, so a corrupted split (see {@link parseHistory}) is never silently
|
|
170
|
-
* trusted. Failing here reuses `history()`'s own "could not build log.md" `drift` error, since it
|
|
171
|
-
* is the identical class of failure (git itself rejecting the range/repository).
|
|
172
|
-
*/
|
|
173
|
-
function countCommits(cwd: string, range: GitLogRange, root: string): number {
|
|
174
|
-
const proc = Bun.spawnSync(["git", "rev-list", "--count", ...rangeArgs(range), "--", literalPathspec(root)], {
|
|
175
|
-
cwd,
|
|
176
|
-
stdout: "pipe",
|
|
177
|
-
stderr: "pipe",
|
|
178
|
-
});
|
|
179
|
-
if (proc.exitCode !== 0) {
|
|
180
|
-
throw new LoreError(
|
|
181
|
-
"drift",
|
|
182
|
-
`\`git rev-list --count\` exited ${proc.exitCode}: could not build log.md`,
|
|
183
|
-
stderrHint(proc.stderr.toString("utf8")) ?? "check that this is a git repository and the given range is valid",
|
|
184
|
-
{ exitCode: proc.exitCode, range },
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
return Number.parseInt(proc.stdout.toString("utf8").trim(), 10);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Split `git log`'s stdout into one {@link GitCommit} per {@link SENTINEL}-prefixed block: the
|
|
192
|
-
* block's first three lines are the hash, ISO committer date, and subject (in that order — the
|
|
193
|
-
* exact {@link PRETTY_FORMAT} layout); every remaining non-empty line is a touched file path (the
|
|
194
|
-
* blank separator line `--name-only` puts before the file list is dropped by the empty-line filter).
|
|
195
|
-
*
|
|
196
|
-
* This split is NOT provably collision-proof on its own: if a commit's subject, body, or a touched
|
|
197
|
-
* file path literally contains the {@link SENTINEL} byte sequence, `String.prototype.split` treats
|
|
198
|
-
* that occurrence as a block boundary too, silently splitting one real commit into multiple
|
|
199
|
-
* malformed ones (e.g. a file path ending up parsed as a `hash`). This function does not — and
|
|
200
|
-
* cannot, from the split output alone — detect that; `history()` is the one that guards against it,
|
|
201
|
-
* by cross-checking `commits.length` against an independent `git rev-list --count` of the same
|
|
202
|
-
* range, which never depends on SENTINEL-delimited parsing at all.
|
|
203
|
-
*/
|
|
204
|
-
function parseHistory(output: string): GitCommit[] {
|
|
205
|
-
const commits: GitCommit[] = [];
|
|
206
|
-
for (const block of output.split(`${SENTINEL}\n`)) {
|
|
207
|
-
if (block.trim() === "") {
|
|
208
|
-
continue; // the split's leading part, before the first sentinel, is always empty
|
|
209
|
-
}
|
|
210
|
-
const lines = block.split("\n");
|
|
211
|
-
const hash = lines[0];
|
|
212
|
-
const timestamp = lines[1];
|
|
213
|
-
const subject = lines[2];
|
|
214
|
-
if (hash === undefined || timestamp === undefined || subject === undefined) {
|
|
215
|
-
continue; // defensive: a well-formed block always has all three header lines
|
|
216
|
-
}
|
|
217
|
-
const files = lines.slice(3).filter((line) => line !== "");
|
|
218
|
-
commits.push({ hash, timestamp, subject, files });
|
|
219
|
-
}
|
|
220
|
-
return commits;
|
|
221
|
-
}
|