@opum-ai/lore 0.1.0 → 0.2.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.
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
package/src/core/log.ts DELETED
@@ -1,253 +0,0 @@
1
- /**
2
- * log.ts — the bundle's `log.md` as a **git-history-derived**, byte-stable artifact (LORE-47).
3
- *
4
- * Where `index.md` is regenerated from the bundle graph, `log.md` is a per-folder change log
5
- * derived from the repository's own commit history: for each bundle folder, the commits that
6
- * touched a file under it. This module owns two things:
7
- *
8
- * - **The `GitAdapter` seam** — the *third* injectable deterministic seam, alongside the clock
9
- * and the Backlog subprocess (lore-design §8; [ADR-0014](../../docs/adr/0014-core-has-no-llm-dependency.md)).
10
- * git is *local, deterministic computation* — not a network model — so reading history over a
11
- * **pinned commit range** is reproducible, offline-safe, and air-gap-safe. Core depends only on
12
- * the interface; the real adapter that shells `git log` is impure command-layer wiring (the same
13
- * boundary the real `() => new Date()` clock sits on), supplied where `lore sync` is built. Tests
14
- * inject a fake adapter returning a fixed fake history, never real `git`.
15
- *
16
- * - **The pure `generateLog`** — given a set of {@link GitCommit}s, produce the exact `log.md`
17
- * bytes. It is total and order-independent: folders are directory-sorted and the commits under
18
- * each are sorted by `(timestamp, hash)`, so the same history always yields byte-identical output
19
- * (idempotent) regardless of the order the adapter returned commits in (lore-design §8: directory
20
- * walks are sorted; no nondeterminism in core).
21
- *
22
- * **Drift-gate exemption (ADR-0007).** Because a git-derived `log.md` changes on *every* commit, it
23
- * is materialized at **`lore sync`** time and **excluded** from `lore check`'s regenerate-and-compare
24
- * drift gate. Gating it would report permanent drift (the gate's own commit would invalidate it) and
25
- * break on shallow/read-only CI checkouts where full history is absent. `index.md` and the
26
- * `<!-- lore:tasks -->` managed blocks stay gated as before.
27
- *
28
- * Per the core contract (lore-design §2.1) this module is pure: no filesystem, no spawn, no clock.
29
- */
30
-
31
- import { posix } from "node:path";
32
- import { singleLine } from "../errors";
33
- import { compareCodeUnits } from "./order";
34
- import { DOCS_DIR } from "./scaffold";
35
-
36
- /**
37
- * One commit as the {@link GitAdapter} surfaces it — the minimal, deterministic projection
38
- * `generateLog` needs. All fields are stable for a given commit in a given repository.
39
- */
40
- export interface GitCommit {
41
- /** The commit hash (full or a stable abbreviation). Emitted verbatim and used as the sort tiebreak. */
42
- readonly hash: string;
43
- /** The committer date as an ISO-8601 string with offset (never a `Date`, mirroring ADR-0006 §2). */
44
- readonly timestamp: string;
45
- /** The commit message's first line (subject). Treated as single-line; any stray newline is collapsed. */
46
- readonly subject: string;
47
- /** The repo-relative POSIX paths the commit touched (in any order; `generateLog` groups + sorts). */
48
- readonly files: readonly string[];
49
- }
50
-
51
- /**
52
- * A **pinned** commit range — the determinism boundary. `to` is the inclusive upper bound (a tag
53
- * or sha, e.g. `HEAD` resolved to a sha by the caller); `from`, when given, is the exclusive lower
54
- * bound. Pinning both ends is what makes a generated `log.md` reproducible: the same range over the
55
- * same repository yields the same commits.
56
- */
57
- export interface GitLogRange {
58
- /** Exclusive lower bound (a tag/sha); absent → from the start of history. */
59
- readonly from?: string;
60
- /** Inclusive upper bound (a tag/sha) — pin this to a concrete sha for a reproducible log. */
61
- readonly to: string;
62
- }
63
-
64
- /**
65
- * The injectable git seam (lore-design §8, the third after clock + Backlog). The real
66
- * implementation shells `git log` and lives at the command layer (impure, like the real clock);
67
- * tests inject a fake returning a fixed fake history. Core knows only this interface.
68
- */
69
- export interface GitAdapter {
70
- /**
71
- * The commits touching `root` within `range`, in any order. `root` is a **pathspec**, not a
72
- * post-filter hint: an implementation is expected to scope the underlying `git log` walk itself
73
- * (e.g. `-- docs`) so a commit that never touched anything under `root` is never even considered,
74
- * let alone returned — the real adapter (`adapters/git.ts`) does exactly that, so `lore sync`
75
- * never walks or buffers unrelated repository history just to build a docs-scoped `log.md`.
76
- * Optional only so a fake ignoring it stays a one-liner; {@link buildLog} always passes it.
77
- */
78
- history(range: GitLogRange, root?: string): readonly GitCommit[];
79
- }
80
-
81
- /** Options for {@link generateLog}. */
82
- export interface GenerateLogOptions {
83
- /**
84
- * The bundle root the log is scoped to (default {@link DOCS_DIR}, `"docs"`). A commit's files
85
- * outside this root are ignored, so `log.md` reflects bundle history, not unrelated source churn.
86
- * Compared by path segment, so `"docs"` matches `docs/x.md` but never a sibling like `docsite/x.md`.
87
- * An empty string falls back to the default (an empty root would match nothing).
88
- */
89
- readonly root?: string;
90
- /** The document's top-level heading (default `"Change log"`). */
91
- readonly title?: string;
92
- }
93
-
94
- /** One commit as it renders in `log.md` — the commit's identity plus its subject collapsed once. */
95
- interface LogEntry {
96
- readonly hash: string;
97
- readonly timestamp: string;
98
- readonly subject: string;
99
- /**
100
- * The absolute instant the {@link timestamp} denotes (epoch ms), or `NaN` when it carries no
101
- * explicit offset (so a host-local — machine-dependent — parse is never trusted) or is
102
- * unparseable. Computed once at construction so {@link compareEntries} parses each commit's
103
- * timestamp **once** total, not O(N log N) times inside the sort comparator.
104
- */
105
- readonly instant: number;
106
- }
107
-
108
- /**
109
- * Build a `log.md`'s bytes from a {@link GitAdapter} over a pinned range — the function `lore sync`
110
- * calls with the real adapter and tests call with a fake one, so the seam is exercised end-to-end.
111
- * The same resolved `root` that scopes `log.md`'s content is also passed to `adapter.history` as a
112
- * pathspec, so the real adapter can scope the underlying `git log` walk itself — not just the
113
- * `generateLog` grouping below — to the bundle root (LORE-143). The pure {@link generateLog} does
114
- * the byte computation; this only resolves the history through the seam.
115
- */
116
- export function buildLog(adapter: GitAdapter, range: GitLogRange, options: GenerateLogOptions = {}): string {
117
- return generateLog(adapter.history(range, resolveRoot(options.root)), options);
118
- }
119
-
120
- /**
121
- * Render the byte-stable `log.md` for `commits`: group each commit under every bundle folder it
122
- * touched (the immediate parent directory of a touched file under the bundle root), emit folders in
123
- * directory-sorted order, and under each folder list its commits sorted by `(timestamp, hash)`.
124
- *
125
- * Pure, total, and order-independent — the same set of commits always produces byte-identical
126
- * output, so re-running `lore sync` with no new history is a byte-level no-op. A commit that touched
127
- * no file under the bundle root contributes nothing (it never appears). An empty history yields just
128
- * the heading, so the file is always well-formed.
129
- */
130
- export function generateLog(commits: readonly GitCommit[], options: GenerateLogOptions = {}): string {
131
- const root = resolveRoot(options.root);
132
- const title = options.title ?? "Change log";
133
-
134
- // folder → the commits touching it. `foldersTouched` already returns a *set* of folders, so each
135
- // commit is appended at most once per folder — no dedup keyed by hash, which would otherwise
136
- // collapse two genuinely distinct commits that share an abbreviated hash. The subject is collapsed
137
- // once here (per commit), not once per folder it lands in.
138
- const byFolder = new Map<string, LogEntry[]>();
139
- for (const commit of commits) {
140
- const folders = foldersTouched(commit.files, root);
141
- if (folders.size === 0) {
142
- continue;
143
- }
144
- const entry: LogEntry = {
145
- hash: commit.hash,
146
- timestamp: commit.timestamp,
147
- subject: singleLine(commit.subject),
148
- instant: toInstant(commit.timestamp),
149
- };
150
- for (const folder of folders) {
151
- const bucket = byFolder.get(folder);
152
- if (bucket === undefined) {
153
- byFolder.set(folder, [entry]);
154
- } else {
155
- bucket.push(entry);
156
- }
157
- }
158
- }
159
-
160
- const sections = [...byFolder.entries()]
161
- .sort(([a], [b]) => compareCodeUnits(a, b))
162
- .map(([folder, entries]) => {
163
- const lines = entries.sort(compareEntries).map((e) => `- ${e.timestamp} ${e.hash} ${e.subject}`);
164
- return `## ${folder}\n\n${lines.join("\n")}\n`;
165
- });
166
-
167
- return [`# ${title}\n`, ...sections].join("\n");
168
- }
169
-
170
- /**
171
- * Normalize a {@link GenerateLogOptions.root}/{@link GitAdapter.history} root to the bundle root it
172
- * denotes, so equivalent spellings of the same root canonicalize identically (LORE-243): a leading
173
- * `./`, a trailing `/.`, and internal redundant separators (`//`, `/./`) are all collapsed via
174
- * {@link posix.normalize} before the trailing slash(es) are stripped — so `./docs`, `docs/.`,
175
- * `./docs/`, and `docs//adr`/`docs/./adr` all resolve exactly like `docs`/`docs/adr`. Without the
176
- * `posix.normalize` pass, the `${root}/` probe in {@link isUnderRoot} would compare against the
177
- * un-normalized root and match nothing — a silently empty log. Falls back to {@link DOCS_DIR} for an
178
- * absent, empty, or all-slashes root (`normalize` reduces those to `"."` or `"/"`, neither of which
179
- * would match anything as a bundle root). Shared by {@link generateLog} (post-filtering) and
180
- * {@link buildLog} (the pathspec handed to {@link GitAdapter.history}) so the two always agree on
181
- * exactly which root scopes a given `log.md`.
182
- */
183
- function resolveRoot(root: string | undefined): string {
184
- const normalized = posix.normalize(root || DOCS_DIR).replace(/\/+$/, "");
185
- return normalized === "" || normalized === "." ? DOCS_DIR : normalized;
186
- }
187
-
188
- /**
189
- * The distinct bundle folders a commit's `files` touch, scoped to `root`. A folder is the immediate
190
- * parent directory of a touched file (`docs/adr/0014.md` → `docs/adr`). A file directly under the
191
- * root (`docs/index.md` → `docs`) groups under the root itself. Files outside the root are dropped.
192
- */
193
- function foldersTouched(files: readonly string[], root: string): Set<string> {
194
- const folders = new Set<string>();
195
- for (const file of files) {
196
- if (isUnderRoot(file, root)) {
197
- folders.add(posix.dirname(file));
198
- }
199
- }
200
- return folders;
201
- }
202
-
203
- /**
204
- * True when `file` sits **strictly under** the bundle root (`<root>/…`). A file whose path *equals*
205
- * the root is deliberately excluded: it is a file literally named `docs` (not a bundle document), and
206
- * `posix.dirname` would put it under the root's *parent* (`.`), emitting a section above the bundle.
207
- * A document directly under the root — `docs/index.md` — still matches and groups under `## docs`.
208
- */
209
- function isUnderRoot(file: string, root: string): boolean {
210
- return file.startsWith(`${root}/`);
211
- }
212
-
213
- /** Matches the offset suffix of an ISO-8601 timestamp — `Z`, or `±HH:MM`/`±HHMM` (colon optional). */
214
- const ISO_OFFSET = /(?:Z|[+-]\d{2}:?\d{2})$/;
215
-
216
- /**
217
- * The absolute instant `timestamp` denotes, in epoch milliseconds, or `NaN` when it does not denote
218
- * one. Only an **offset-bearing** ISO-8601 timestamp (the {@link GitCommit.timestamp} contract) is a
219
- * fixed instant; an offset-*less* one would be parsed in the host's local time zone — a
220
- * machine-dependent result that has no place in byte-stable output — so it is reported as `NaN` and
221
- * left to the deterministic text tiebreak, never trusted as a number.
222
- */
223
- function toInstant(timestamp: string): number {
224
- return ISO_OFFSET.test(timestamp) ? Date.parse(timestamp) : Number.NaN;
225
- }
226
-
227
- /**
228
- * Order entries by ascending absolute **instant**, then by deterministic, machine-independent
229
- * tiebreaks. Timestamps are ISO-8601 *with offset* (see {@link GitCommit.timestamp}), so two equal
230
- * instants written in different offsets compare equal — a lexical compare would order them by
231
- * wall-clock text instead. Each entry's instant is precomputed ({@link LogEntry.instant}); when both
232
- * are real instants they order numerically, and an entry with a real instant always precedes one
233
- * without (an offset-less/unparseable timestamp). On an exact instant tie — or when neither has an
234
- * instant — the order falls through to `(timestamp, hash, subject)` code-unit compares, so it stays
235
- * **total and reproducible** (the subject tiebreak keeps two commits sharing an instant *and* an
236
- * abbreviated hash from falling back to input order).
237
- */
238
- function compareEntries(a: LogEntry, b: LogEntry): number {
239
- const aHasInstant = !Number.isNaN(a.instant);
240
- const bHasInstant = !Number.isNaN(b.instant);
241
- if (aHasInstant && bHasInstant) {
242
- if (a.instant !== b.instant) {
243
- return a.instant < b.instant ? -1 : 1;
244
- }
245
- } else if (aHasInstant !== bHasInstant) {
246
- return aHasInstant ? -1 : 1;
247
- }
248
- return (
249
- compareCodeUnits(a.timestamp, b.timestamp) ||
250
- compareCodeUnits(a.hash, b.hash) ||
251
- compareCodeUnits(a.subject, b.subject)
252
- );
253
- }