@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
@@ -1,271 +0,0 @@
1
- /**
2
- * context.ts — assemble a concept and its neighborhood into a token-budgeted
3
- * **context pack** for an agent (cli-surface §context, LORE-34).
4
- *
5
- * `lore context <id>` answers "give me this concept plus enough of what surrounds
6
- * it to reason about it, within a token budget." This module is the pure shaping
7
- * layer behind it: it takes an already-loaded {@link BundleGraph}, gathers the
8
- * target's neighborhood via the shared {@link subgraph} traversal `lore graph`
9
- * also uses, and produces a {@link ContextExport} — the target concept's **full
10
- * body** plus a **one-line `summary` compaction** of each neighbor — trimmed to a
11
- * `--max-tokens` budget.
12
- *
13
- * It is deliberately **structural, not ranked** (design §2.1, ADR-0015): there are
14
- * no relevance heuristics here. Neighbors are taken in the traversal's
15
- * **nearest-first** discovery order (depth-1 before depth-2, deterministic within
16
- * a level), and the budget fill keeps a prefix of that order — "the closest
17
- * neighbors that fit." Scoring/relevance is `lore query`'s job (BM25); this command
18
- * is the predictable, reproducible expansion an agent can rely on.
19
- *
20
- * ## Token model (all figures are the chars/4 heuristic, never a real tokenizer)
21
- *
22
- * Every figure is {@link estimateTokens} — the project's shared chars/4 rule. The
23
- * two roles in a pack are charged over the bytes each actually contributes:
24
- *
25
- * - The **target** is emitted in full, so it is charged
26
- * {@link BundleGraph.tokenEstimate}`(root)` — the whole-concept estimate, which is
27
- * the *same* `~tokens` `lore graph` reports for that concept (cross-command
28
- * consistency, and a reuse of the one memoized estimator).
29
- * - A **neighbor** is compacted to a one-line entry, so it is charged the chars/4 of
30
- * that entry's content — its `id`, `type`, `title` (when present), and `summary`
31
- * (when present) — not the whole concept (which would defeat the compaction) and
32
- * not the summary alone (which would under-count the always-present id/type, or a
33
- * long `title` sitting behind a short/absent `summary`, letting a wide
34
- * neighborhood overrun the budget).
35
- *
36
- * The export's `tokenEstimate` is the target's estimate plus every **included**
37
- * neighbor's — the size of the pack actually emitted. Neighbors are added in
38
- * nearest-first order while the running total stays within `--max-tokens`; the fill
39
- * **stops at the first neighbor that would exceed the budget** (a predictable
40
- * nearest-first prefix, not a greedy "skip the big one and keep filling"), and any
41
- * remaining neighbors are dropped with `truncated` set. The **target is always
42
- * present** — a context without its subject is meaningless — so a `--max-tokens`
43
- * smaller than the target still returns the target with zero neighbors; that pack is
44
- * **over budget**, and `truncated` is set in that case too (so a `truncated: false`
45
- * is an honest "everything fit", never a silent overrun the target alone caused).
46
- *
47
- * Like the rest of `core/` (design §2.1) this module is pure: it reads the graph
48
- * and returns plain data or throws a {@link LoreError}; it never touches the
49
- * filesystem, prints, or reads flags.
50
- */
51
-
52
- import { singleLine } from "../errors";
53
- import { type BundleGraph, estimateTokens, frontmatterScalar } from "./bundle";
54
- import type { Concept } from "./concept";
55
- import { subgraph } from "./query";
56
- import type { WorkspaceRecordProvenance, WorkspaceResultScope } from "./workspace-contract";
57
-
58
- /** The target concept at the center of a {@link ContextExport} — emitted in full. */
59
- export interface ContextTarget {
60
- /** The concept id (bundle-root-relative, e.g. `stories/bulk-archive`). */
61
- readonly id: string;
62
- /** The concept's resolved `type` (mirrors `frontmatter.type`). */
63
- readonly type: string;
64
- /** The concept's `title` frontmatter, when present and a non-empty scalar; omitted otherwise. */
65
- readonly title?: string;
66
- /** The concept's full markdown body (verbatim, the pack's primary content). */
67
- readonly body: string;
68
- /** The chars/4 estimate over the target's full serialized bytes (== `lore graph`'s node estimate). */
69
- readonly tokenEstimate: number;
70
- /** Complete locator-free provenance in explicit workspace mode. */
71
- readonly provenance?: WorkspaceRecordProvenance;
72
- }
73
-
74
- /** One neighbor in a {@link ContextExport} — compacted to its one-line `summary`. */
75
- export interface ContextNeighbor {
76
- /** The neighbor concept id. */
77
- readonly id: string;
78
- /** The neighbor's resolved `type`. */
79
- readonly type: string;
80
- /** The neighbor's `title` frontmatter, when present and a non-empty scalar; omitted otherwise. */
81
- readonly title?: string;
82
- /**
83
- * The neighbor's one-line compaction: its `summary` frontmatter, else its `title`,
84
- * collapsed to a single line. Omitted when the concept carries neither — the
85
- * neighbor still appears (its id/type are the structural signal), it just has no
86
- * sentence to show.
87
- */
88
- readonly summary?: string;
89
- /**
90
- * The chars/4 estimate of the emitted entry — `id` + `type` + `title` (when
91
- * present) + `summary` (when present) — what this neighbor costs the budget.
92
- * `title` and `summary` are charged independently even when `summary` is the
93
- * `title` fallback (the neighbor object emits both fields under `--json`), so a
94
- * long `title` behind a short/absent `summary` is never undercounted.
95
- */
96
- readonly tokenEstimate: number;
97
- /** Complete locator-free provenance in explicit workspace mode. */
98
- readonly provenance?: WorkspaceRecordProvenance;
99
- }
100
-
101
- /** The `context.export` payload: the target's full body, the neighbor compaction, and the budget accounting. */
102
- export interface ContextExport {
103
- /** The target concept id the pack is centered on. */
104
- readonly root: string;
105
- /** The neighbor radius used (hops from the target; `0` = target only). */
106
- readonly depth: number;
107
- /** The token budget when one was given (`--max-tokens`); omitted when the pack was bounded only by `--depth`. */
108
- readonly maxTokens?: number;
109
- /** The target concept, emitted in full. */
110
- readonly target: ContextTarget;
111
- /** The included neighbors, in nearest-first order. */
112
- readonly neighbors: readonly ContextNeighbor[];
113
- /** The pack's chars/4 token estimate: the target's estimate plus every included neighbor's. */
114
- readonly tokenEstimate: number;
115
- /** The total number of neighbors within `--depth` before any budget trim. */
116
- readonly total: number;
117
- /** The number of neighbors actually included (`shown <= total`). */
118
- readonly shown: number;
119
- /**
120
- * `true` when the pack does not fully fit the budget: either the budget dropped
121
- * one or more neighbors (`shown < total`), or the always-present target alone
122
- * pushes `tokenEstimate` past `--max-tokens` (an over-budget pack with no neighbor
123
- * to drop). `false` is therefore an honest "the whole neighborhood is here and
124
- * within budget", never a silent overrun.
125
- */
126
- readonly truncated: boolean;
127
- /** Explicit selected workspace scope; absent for repository-local output. */
128
- readonly workspace?: WorkspaceResultScope;
129
- }
130
-
131
- /** Options for {@link buildContext}. */
132
- export interface BuildContextOptions {
133
- /** The neighbor radius in hops from the target. Defaults to `1`. */
134
- readonly depth?: number;
135
- /**
136
- * The token budget for the whole pack. When omitted, no neighbor is dropped for
137
- * size — the pack is bounded only by `depth`.
138
- */
139
- readonly maxTokens?: number;
140
- }
141
-
142
- /** The default neighbor radius when `--depth` is not given (cli-surface §context). */
143
- export const DEFAULT_DEPTH = 1;
144
-
145
- /**
146
- * Build a {@link ContextExport} for `root`: the target concept's full body plus a
147
- * one-line compaction of its neighbors out to `depth` hops, trimmed to `maxTokens`.
148
- *
149
- * The neighborhood comes from the shared {@link subgraph} traversal (undirected,
150
- * cycle-tolerant, depth-bounded) — the same one `lore graph` roots its export at —
151
- * so the two commands agree on what "around this concept" means. The traversal's
152
- * discovery order is **nearest-first**; the target is dropped from it (it is the
153
- * subject, carried separately) and the remaining neighbors are taken in that order.
154
- *
155
- * Budgeting and the token model are documented in the module header. The **target
156
- * is always included**; only neighbors are trimmed, and the fill stops at the first
157
- * neighbor that would push the running total past `maxTokens`.
158
- *
159
- * @throws LoreError `not_found` (exit 3) via {@link subgraph} when `root` names no
160
- * concept in the bundle.
161
- */
162
- export function buildContext(graph: BundleGraph, root: string, options: BuildContextOptions = {}): ContextExport {
163
- const depth = options.depth ?? DEFAULT_DEPTH;
164
- const { maxTokens } = options;
165
-
166
- // subgraph throws not_found for an unknown root, so this is also the id guard.
167
- const reached = subgraph(graph, root, depth);
168
- const targetConcept = conceptAt(graph, root);
169
- const target: ContextTarget = {
170
- id: root,
171
- type: targetConcept.type,
172
- ...titleField(targetConcept.frontmatter.title),
173
- body: targetConcept.body,
174
- tokenEstimate: graph.tokenEstimate(root),
175
- };
176
-
177
- // Every reached id but the root is a neighbor (subgraph yields only real concepts),
178
- // so the candidate count is known without materializing the dropped ones.
179
- const total = reached.size - 1;
180
- // Single nearest-first fill: subgraph iterates root-first then by level, so dropping
181
- // the root visits depth-1 before depth-2 — exactly the order the budget should keep.
182
- // Include each neighbor while it still fits and STOP at the first that would exceed
183
- // the budget (a predictable prefix), so a dropped neighbor's summary is never even
184
- // computed. An omitted budget keeps every neighbor.
185
- const neighbors: ContextNeighbor[] = [];
186
- let tokenEstimate = target.tokenEstimate;
187
- for (const id of reached) {
188
- if (id === root) {
189
- continue;
190
- }
191
- const neighbor = neighborOf(conceptAt(graph, id), id);
192
- if (maxTokens !== undefined && tokenEstimate + neighbor.tokenEstimate > maxTokens) {
193
- break;
194
- }
195
- neighbors.push(neighbor);
196
- tokenEstimate += neighbor.tokenEstimate;
197
- }
198
-
199
- const overBudget = maxTokens !== undefined && tokenEstimate > maxTokens;
200
- return {
201
- root,
202
- depth,
203
- ...(maxTokens !== undefined ? { maxTokens } : {}),
204
- target,
205
- neighbors,
206
- tokenEstimate,
207
- total,
208
- shown: neighbors.length,
209
- truncated: neighbors.length < total || overBudget,
210
- };
211
- }
212
-
213
- /**
214
- * Shape one neighbor concept into its compacted {@link ContextNeighbor}: its `type`,
215
- * optional `title`, the one-line summary (its `summary`, falling back to its
216
- * `title`), and the chars/4 cost of the emitted entry (`id` + `type` + `title` +
217
- * `summary`). The `title` scalar is coerced once and reused for both the `title`
218
- * field and the summary fallback; it is charged in the cost even when `summary`
219
- * duplicates it via that fallback, so a long `title` behind a short/absent
220
- * `summary` is never undercounted.
221
- */
222
- function neighborOf(concept: Concept, id: string): ContextNeighbor {
223
- const title = frontmatterScalar(concept.frontmatter.title);
224
- const summary = oneLine(frontmatterScalar(concept.frontmatter.summary) ?? title);
225
- const titlePart = title !== undefined ? ` ${title}` : "";
226
- const summaryPart = summary !== undefined ? ` ${summary}` : "";
227
- return {
228
- id,
229
- type: concept.type,
230
- ...(title !== undefined ? { title } : {}),
231
- ...(summary !== undefined ? { summary } : {}),
232
- tokenEstimate: estimateTokens(`${id} ${concept.type}${titlePart}${summaryPart}`),
233
- };
234
- }
235
-
236
- /**
237
- * The concept at `id`, which the caller already knows is in the bundle — every id
238
- * {@link subgraph} returns names a real concept (the root is checked, and every
239
- * other reached id came from an {@link Edge} whose endpoints are bundle concepts).
240
- * Asserting that here keeps the build straight-line, with no dead "missing concept"
241
- * branch the traversal contract makes unreachable.
242
- */
243
- function conceptAt(graph: BundleGraph, id: string): Concept {
244
- return graph.concepts.get(id) as Concept;
245
- }
246
-
247
- /**
248
- * A `title` frontmatter value as a `{ title }` field to spread, or `{}` when it has
249
- * no display form. Uses the shared {@link frontmatterScalar} so the `title` a target
250
- * or neighbor reports is **byte-identical** to what `lore graph` reports for the same
251
- * concept (verbatim string, finite number/boolean coerced, else absent).
252
- */
253
- function titleField(value: unknown): { title?: string } {
254
- const title = frontmatterScalar(value);
255
- return title !== undefined ? { title } : {};
256
- }
257
-
258
- /**
259
- * Collapse an already-coerced scalar ({@link frontmatterScalar}) to a single trimmed,
260
- * non-empty line for the neighbor compaction, or `undefined`. Unlike the `title`
261
- * field — which is kept verbatim to match `lore graph` — the summary is a one-line
262
- * display, so {@link singleLine} + trim ensures a multi-line YAML scalar cannot
263
- * smuggle extra lines into the pack.
264
- */
265
- function oneLine(value: string | undefined): string | undefined {
266
- if (value === undefined) {
267
- return undefined;
268
- }
269
- const line = singleLine(value).trim();
270
- return line === "" ? undefined : line;
271
- }