@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.
Files changed (90) hide show
  1. package/README.md +33 -22
  2. package/bin/lore.cjs +35 -7
  3. package/package.json +17 -17
  4. package/src/adapters/backlog.ts +0 -1084
  5. package/src/adapters/git.ts +0 -221
  6. package/src/cli.ts +0 -667
  7. package/src/commands/agent.ts +0 -301
  8. package/src/commands/agents.ts +0 -302
  9. package/src/commands/args.ts +0 -209
  10. package/src/commands/changed.ts +0 -70
  11. package/src/commands/check.ts +0 -1031
  12. package/src/commands/codex-bridge.ts +0 -49
  13. package/src/commands/concurrency.ts +0 -48
  14. package/src/commands/context.ts +0 -292
  15. package/src/commands/discover.ts +0 -89
  16. package/src/commands/explorer.ts +0 -253
  17. package/src/commands/export.ts +0 -93
  18. package/src/commands/fswrite.ts +0 -928
  19. package/src/commands/graph.ts +0 -291
  20. package/src/commands/help.ts +0 -151
  21. package/src/commands/impact.ts +0 -59
  22. package/src/commands/init.ts +0 -583
  23. package/src/commands/instructions.ts +0 -91
  24. package/src/commands/link.ts +0 -929
  25. package/src/commands/new.ts +0 -476
  26. package/src/commands/orphans.ts +0 -457
  27. package/src/commands/path.ts +0 -67
  28. package/src/commands/provenance.ts +0 -68
  29. package/src/commands/query.ts +0 -312
  30. package/src/commands/reconcile-shared.ts +0 -280
  31. package/src/commands/rename.ts +0 -585
  32. package/src/commands/replace.ts +0 -320
  33. package/src/commands/scaffold.ts +0 -346
  34. package/src/commands/schema.ts +0 -293
  35. package/src/commands/snapshot.ts +0 -130
  36. package/src/commands/supersede.ts +0 -400
  37. package/src/commands/sync.ts +0 -371
  38. package/src/commands/tasks.ts +0 -271
  39. package/src/commands/traversal.ts +0 -151
  40. package/src/commands/validate.ts +0 -226
  41. package/src/config.ts +0 -598
  42. package/src/core/agent-bridge.ts +0 -287
  43. package/src/core/agent-context.ts +0 -498
  44. package/src/core/agent-profile.ts +0 -447
  45. package/src/core/bundle.ts +0 -893
  46. package/src/core/check.ts +0 -853
  47. package/src/core/codex-bridge.ts +0 -100
  48. package/src/core/concept.ts +0 -597
  49. package/src/core/consumer-scaffold.ts +0 -433
  50. package/src/core/context.ts +0 -271
  51. package/src/core/explorer-contract.ts +0 -441
  52. package/src/core/explorer-qualification.ts +0 -58
  53. package/src/core/explorer.ts +0 -518
  54. package/src/core/finding.ts +0 -31
  55. package/src/core/graph.ts +0 -201
  56. package/src/core/indexes.ts +0 -436
  57. package/src/core/instructions.ts +0 -209
  58. package/src/core/ladybug-driver.ts +0 -1795
  59. package/src/core/ladybug-lifecycle.ts +0 -1178
  60. package/src/core/ladybug-native.ts +0 -95
  61. package/src/core/ladybug-source.ts +0 -667
  62. package/src/core/links.ts +0 -681
  63. package/src/core/log.ts +0 -253
  64. package/src/core/managed-block.ts +0 -540
  65. package/src/core/manifest.ts +0 -718
  66. package/src/core/order.ts +0 -13
  67. package/src/core/profile.ts +0 -1007
  68. package/src/core/projection.ts +0 -195
  69. package/src/core/query.ts +0 -542
  70. package/src/core/reconcile.ts +0 -236
  71. package/src/core/replace.ts +0 -419
  72. package/src/core/retrieval.ts +0 -213
  73. package/src/core/rewrite.ts +0 -940
  74. package/src/core/scaffold.ts +0 -255
  75. package/src/core/schema.ts +0 -366
  76. package/src/core/snapshot-runtime.ts +0 -52
  77. package/src/core/snapshot-store.ts +0 -287
  78. package/src/core/snapshot.ts +0 -711
  79. package/src/core/template.ts +0 -429
  80. package/src/core/traversal.ts +0 -487
  81. package/src/core/validate.ts +0 -517
  82. package/src/core/workspace-contract.ts +0 -473
  83. package/src/core/workspace-projection.ts +0 -365
  84. package/src/core/workspace-retrieval.ts +0 -196
  85. package/src/core/workspace-source.ts +0 -174
  86. package/src/errors.ts +0 -697
  87. package/src/meta.ts +0 -7
  88. package/src/output.ts +0 -589
  89. package/src/scripts/upstream-backlog-watch.ts +0 -288
  90. package/src/state.ts +0 -390
@@ -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
- }