@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,291 +0,0 @@
1
- /**
2
- * commands/graph.ts — `lore graph [<id>] [--dot] [--depth <n>]`.
3
- *
4
- * The thin, read-only layer that emits the bundle's cross-link graph (cli-surface
5
- * §graph; LORE-31). Commander supplies a verified indexed {@link BundleGraph}
6
- * with automatic reference fallback; direct core callers retain the reference
7
- * loader. The selected graph is then shaped as follows:
8
- *
9
- * - with **no `<id>`** exports the whole bundle;
10
- * - with an `<id>` exports the **subgraph** rooted there, bounded to `--depth`
11
- * hops ({@link subgraph}; unbounded when `--depth` is omitted).
12
- *
13
- * Output follows the uniform CLI modes: a human node/edge listing at a TTY (or
14
- * piped plain), and the `{schemaVersion, kind: "graph.export", data}` envelope
15
- * under the global `--json` — there is no command-specific JSON flag, so machine
16
- * consumers use the same `--json` they use everywhere. `--dot` is the one
17
- * representation override: it emits Graphviz DOT instead of the listing, so `lore
18
- * graph --dot | dot -Tpng` works (a piped stdout auto-selects plain). `--dot` and
19
- * `--json` are mutually exclusive — DOT has no envelope form.
20
- *
21
- * The positional `<id>` is normalized through {@link idFromPath} exactly as `lore
22
- * rename`/`supersede` normalize theirs, so a path-form, `./`-prefixed, or
23
- * `.md`-suffixed id resolves to the same bundle key (consistent id acceptance
24
- * across the id-taking commands).
25
- *
26
- * Validation lives here (the byte/shape computation stays pure in `core/graph.ts`
27
- * and `core/query.ts`): an unknown flag, a repeated/value-less or value-bearing
28
- * `--dot`, a repeated/value-less/non-integer/too-large `--depth`, a `--depth`
29
- * without a root `<id>`, `--dot` together with `--json`, or a stray second
30
- * positional is a `usage` error (exit 2); a root `<id>` absent from the bundle
31
- * surfaces as the `not_found` error (exit 3) {@link subgraph} throws.
32
- */
33
-
34
- import { join } from "node:path";
35
- import type { BacklogAdapter } from "../adapters/backlog";
36
- import { loadBundle } from "../core/bundle";
37
- import { idFromPath } from "../core/concept";
38
- import { buildGraphExport, type GraphExport, toDot } from "../core/graph";
39
- import { loadProfile } from "../core/profile";
40
- import { subgraph } from "../core/query";
41
- import { loadRetrievalGraph, type RetrievalGraphLoader } from "../core/retrieval";
42
- import { DOCS_DIR } from "../core/scaffold";
43
- import { parseQualifiedWorkspaceId, qualifyWorkspaceId } from "../core/workspace-contract";
44
- import type { WorkspaceRetrievalContext, WorkspaceRetrievalSelection } from "../core/workspace-retrieval";
45
- import { EXIT_OK, LoreError, singleLine, WarningCollector, type Writer } from "../errors";
46
- import { emit, type OutputContext, type Renderable } from "../output";
47
- import { assertFlagAtMostOnce, parseCommandArgs, singleOptionValue, workspaceSelection } from "./args";
48
-
49
- /** Options for {@link runGraph}; `root` and the streams are injectable for tests. */
50
- export interface GraphOptions {
51
- /** The repo root the `docs/` bundle resolves against. */
52
- root: string;
53
- /** The resolved output mode/color (from `output.ts`). */
54
- output: OutputContext;
55
- /** The command's normalized positional + flag tokens from Commander. */
56
- args: readonly string[];
57
- /** stdout sink; defaults to `process.stdout`. */
58
- stdout?: Writer;
59
- /** stderr sink for advisory warnings; defaults to `process.stderr`. */
60
- stderr?: Writer;
61
- /** Backlog snapshot seam used only by indexed projection freshness/builds. */
62
- adapter?: BacklogAdapter;
63
- /** Indexed/reference selector injected by the Commander handler or conformance tests. */
64
- retrieval?: RetrievalGraphLoader;
65
- }
66
-
67
- /** The parsed form of `lore graph`'s arguments. */
68
- interface GraphArgs {
69
- /** The root concept id (positional, already {@link idFromPath}-normalized); `undefined` exports the whole bundle. */
70
- id?: string;
71
- /** Emit Graphviz DOT instead of the node/edge listing (`--dot`). */
72
- dot: boolean;
73
- /** The hop radius (`--depth`); `undefined` means unbounded. Requires `id`. */
74
- depth?: number;
75
- readonly workspace?: WorkspaceRetrievalSelection;
76
- }
77
-
78
- /**
79
- * Run `lore graph`: parse the arguments, load the bundle, narrow to the rooted
80
- * subgraph when an `<id>` is given, shape the export, emit the `graph.export`,
81
- * and return `0`. A bad flag/positional (including `--dot` with `--json`) throws
82
- * a `usage` {@link LoreError} (exit `2`); an `<id>` not in the bundle a
83
- * `not_found` one (exit `3`).
84
- */
85
- export function runGraph(options: GraphOptions): number | Promise<number> {
86
- const parsed = parseGraphArgs(options.args);
87
- if (parsed.dot && options.output.mode === "json") {
88
- throw usage("--dot cannot be combined with --json", "DOT has no JSON envelope; pass one of --dot or --json");
89
- }
90
- const advisories = new WarningCollector();
91
- const retrieval = options.retrieval ?? (parsed.workspace !== undefined ? loadRetrievalGraph : undefined);
92
- if (retrieval !== undefined) {
93
- return retrieval({
94
- root: options.root,
95
- warnings: advisories,
96
- adapter: options.adapter,
97
- ...(parsed.workspace !== undefined ? { workspace: parsed.workspace } : {}),
98
- }).then(async (loaded) => {
99
- try {
100
- return finishGraph(options, parsed, loaded.graph, advisories, loaded.workspace);
101
- } finally {
102
- await loaded.dispose?.();
103
- }
104
- });
105
- }
106
- const profile = loadProfile({ root: options.root });
107
- const graph = loadBundle(join(options.root, DOCS_DIR), { warnings: advisories, profile });
108
- return finishGraph(options, parsed, graph, advisories);
109
- }
110
-
111
- function finishGraph(
112
- options: GraphOptions,
113
- parsed: GraphArgs,
114
- graph: ReturnType<typeof loadBundle>,
115
- advisories: WarningCollector,
116
- workspace?: WorkspaceRetrievalContext,
117
- ): number {
118
- // Flush load warnings before the subgraph lookup, which throws not_found for an
119
- // unknown root — otherwise an advisory that explains *why* a file is not a
120
- // concept would be discarded on exactly the path that most needs it.
121
- advisories.flush({ color: options.output.color, stderr: options.stderr });
122
-
123
- let data: GraphExport;
124
- if (parsed.id === undefined) {
125
- data = buildGraphExport(graph, workspaceOptions(workspace));
126
- } else {
127
- const include = subgraph(graph, parsed.id, parsed.depth ?? Number.POSITIVE_INFINITY);
128
- data = buildGraphExport(graph, {
129
- include,
130
- root: parsed.id,
131
- depth: parsed.depth,
132
- ...workspaceOptions(workspace, include),
133
- });
134
- }
135
-
136
- emit(graphRenderable(data, parsed.dot), options.output, options.stdout);
137
- return EXIT_OK;
138
- }
139
-
140
- // ── Argument parsing ───────────────────────────────────────────────────────────
141
-
142
- /**
143
- * Parse `graph`'s tokens into the optional root `<id>` positional, the boolean
144
- * `--dot`, and the value flag `--depth <n>` (also accepting `--depth=<n>`). The
145
- * Commander has already resolved Lore's global flags, so a `--`-prefixed token here
146
- * is a command flag: an unrecognized one is a `usage` error, as is a repeated or
147
- * value-bearing `--dot`, a repeated/value-less/non-integer/too-large `--depth`, a
148
- * `--depth` with no root, or a second positional. A `--` ends option parsing. The
149
- * `<id>` is {@link idFromPath}-normalized so path/`.md`/`./` forms resolve.
150
- */
151
- function parseGraphArgs(args: readonly string[]): GraphArgs {
152
- const parsed = parseCommandArgs(args, "graph");
153
- const workspace = workspaceSelection(parsed);
154
- const positionals = parsed.positionals;
155
- assertFlagAtMostOnce(parsed, "dot");
156
- const rawDepth = singleOptionValue(parsed, "depth");
157
- if (rawDepth === "") throw usage("--depth needs a value", "pass a value, e.g. `--depth 2`");
158
- const depth = rawDepth === undefined ? undefined : parseDepth(rawDepth);
159
- if (positionals.length > 1) {
160
- throw usage(`unexpected argument "${positionals[1]}"`, "run `lore graph [<id>] [--dot] [--depth <n>]`");
161
- }
162
- const raw = positionals[0];
163
- if (depth !== undefined && raw === undefined) {
164
- throw usage(
165
- "--depth needs a root <id>",
166
- "give the concept to bound the radius from, e.g. `lore graph <id> --depth 2`",
167
- );
168
- }
169
- return {
170
- id: raw !== undefined ? normalizeGraphId(raw, workspace !== undefined) : undefined,
171
- dot: parsed.flags.has("dot"),
172
- depth,
173
- workspace,
174
- };
175
- }
176
-
177
- function normalizeGraphId(raw: string, workspace: boolean): string {
178
- if (!workspace) return idFromPath(raw);
179
- try {
180
- const parsed = parseQualifiedWorkspaceId(raw);
181
- return qualifyWorkspaceId(parsed.memberId, idFromPath(parsed.sourceId));
182
- } catch {
183
- throw usage(`invalid workspace concept id "${raw}"`, "use the unambiguous <member-id>::<source-id> form");
184
- }
185
- }
186
-
187
- function workspaceOptions(workspace?: WorkspaceRetrievalContext, include?: ReadonlySet<string>) {
188
- if (workspace === undefined) return {};
189
- const links =
190
- include === undefined
191
- ? workspace.links
192
- : workspace.links.filter(
193
- (link) =>
194
- include.has(qualifyWorkspaceId(link.from.memberId, link.from.sourceId)) &&
195
- include.has(qualifyWorkspaceId(link.to.memberId, link.to.sourceId)),
196
- );
197
- return {
198
- workspace: workspace.scope,
199
- provenanceById: workspace.provenanceById,
200
- workspaceLinks: links,
201
- };
202
- }
203
-
204
- /** Parse a `--depth` value as a non-negative, safe integer (`0` = root only). */
205
- function parseDepth(value: string): number {
206
- // Accept only a bare run of digits — Number() would coerce "1.5"/"0x2"/" 2 "/"1e3".
207
- if (!/^\d+$/.test(value)) {
208
- throw usage(
209
- `invalid --depth "${value}"`,
210
- "pass a non-negative integer, e.g. `--depth 2`; --depth needs a value before a separate flag-looking token",
211
- );
212
- }
213
- const depth = Number.parseInt(value, 10);
214
- // A >2^53 run of digits parses without error but loses precision, so the echoed
215
- // `depth` would differ from what the user typed; reject it rather than lie.
216
- if (!Number.isSafeInteger(depth)) {
217
- throw usage(`--depth "${value}" is too large`, "pass a smaller non-negative integer");
218
- }
219
- return depth;
220
- }
221
-
222
- /**
223
- * Read a value flag's argument: its inline `--flag=value` form when present, else
224
- * the **next** token. A missing/empty value — or a next token that is itself an
225
- * option (`--depth --dot`) — is a `usage` error rather than a silently swallowed
226
- * flag (mirroring `lore schema`'s value-flag guard).
227
- */
228
- // ── Output ─────────────────────────────────────────────────────────────────────
229
-
230
- /**
231
- * The per-result rendering bundle for `graph` (output.ts dispatches on the mode).
232
- * `--json` always carries the structured {@link GraphExport}; the pretty/plain
233
- * text is the DOT serialization under `--dot`, else a human node/edge listing.
234
- * The two text modes render identically (the data is structural — no severities
235
- * to color), so pretty and plain share one renderer.
236
- */
237
- function graphRenderable(data: GraphExport, dot: boolean): Renderable<GraphExport> {
238
- const render = dot ? toDot : renderText;
239
- return { kind: "graph.export", data, pretty: render, plain: render };
240
- }
241
-
242
- /**
243
- * A human/pipe-stable listing: a `<n> concepts, <m> edges, ~<t> tokens` header
244
- * (annotating the scope when it is a subgraph), one `<id> [type] ~<tok>` line
245
- * per node, then one `<from> -<kind>-> <to>` line per edge (`(dangling: <target>)`
246
- * for a broken reference). ANSI-free and deterministic.
247
- *
248
- * `node.id`/`node.type`/`node.title`/edge endpoints all come from bundle-controlled
249
- * frontmatter (an edge's `from`/`to` are concept ids; `target` is the reference
250
- * as parsed, which for a dangling `specs`/frontmatter edge can carry whatever a
251
- * YAML scalar allows — including an embedded newline; `node.type` mirrors
252
- * `frontmatter.type`, and `requireType` (schema.ts) only trims the *ends* of the
253
- * value while unknown types are warn-only, so an interior newline in `type:`
254
- * survives bundle load unchanged), so each is run through {@link singleLine}
255
- * before it lands in a line — the same guard every other bundle-text renderer
256
- * applies (managed-block.ts, indexes.ts, context.ts, query.ts, log.ts) — so an
257
- * embedded newline/control character cannot split one node or edge into extra
258
- * physical lines. `data.root` is included for the same reason: though only
259
- * reachable via a concept id that itself embeds a newline, guarding it keeps
260
- * the header consistent with every id printed below it.
261
- */
262
- function renderText(data: GraphExport): string {
263
- const root = data.root !== undefined ? singleLine(data.root) : undefined;
264
- const scope =
265
- root !== undefined ? ` rooted at ${root}${data.depth !== undefined ? ` (depth ${data.depth})` : ""}` : "";
266
- const lines = [
267
- ...(data.workspace !== undefined
268
- ? [`workspace: ${singleLine(data.workspace.workspaceId)} (${data.workspace.repositories.length} repositories)`]
269
- : []),
270
- `${data.nodes.length} ${plural(data.nodes.length, "concept")}, ${data.edges.length} ${plural(data.edges.length, "edge")}, ~${data.tokenEstimate} tokens (chars/4)${scope}`,
271
- ];
272
- for (const node of data.nodes) {
273
- const title = node.title !== undefined ? ` ${singleLine(node.title)}` : "";
274
- lines.push(` ${singleLine(node.id)} [${singleLine(node.type)}] ~${node.tokenEstimate}${title}`);
275
- }
276
- for (const edge of data.edges) {
277
- const dest = edge.to !== null ? singleLine(edge.to) : `(dangling: ${singleLine(edge.target)})`;
278
- lines.push(` ${singleLine(edge.from)} -${edge.kind}-> ${dest}`);
279
- }
280
- return lines.join("\n");
281
- }
282
-
283
- /** Pluralize a noun by count (`1 concept` / `2 concepts`). */
284
- function plural(count: number, noun: string): string {
285
- return count === 1 ? noun : `${noun}s`;
286
- }
287
-
288
- /** A `usage` {@link LoreError} (exit `2`) with an actionable hint. */
289
- function usage(message: string, hint: string): LoreError {
290
- return new LoreError("usage", message, hint);
291
- }
@@ -1,151 +0,0 @@
1
- /**
2
- * commands/help.ts — `lore help [<command>]` (cli-surface §help).
3
- *
4
- * The thin, read-only layer over the capability manifest in `core/manifest.ts`:
5
- * it parses the one optional `<command>` positional and emits either the whole
6
- * manifest or one command's entry. Like `instructions`, there is no bundle to
7
- * load and no config to read, so it needs neither `root` nor a `WarningCollector`.
8
- *
9
- * - `lore help` — top-level help (the command catalog), rendered from the manifest.
10
- * - `lore help <command>` — one command's detailed help; an unknown `<command>`
11
- * is a `not_found` error (exit 3) whose hint lists the valid names.
12
- * - `--json` — the `{schemaVersion, kind: "help.manifest", data}` envelope, where
13
- * `data` is the full {@link Manifest} (or, for `lore help <command> --json`, the
14
- * same {@link Manifest} shape scoped to the one requested command, so a consumer
15
- * parses both forms identically).
16
- *
17
- * {@link renderTopLevelHelp} is exported so the router's `--help`/no-command
18
- * short-circuit renders from the same manifest — one source for all help text, no
19
- * separately-maintained `USAGE` literal to drift.
20
- */
21
-
22
- import {
23
- buildManifest,
24
- findManifestCommand,
25
- type Manifest,
26
- type ManifestCommand,
27
- type ManifestFlag,
28
- } from "../core/manifest";
29
- import { EXIT_OK, LoreError, type Writer } from "../errors";
30
- import { VERSION } from "../meta";
31
- import { emit, type OutputContext, type Renderable } from "../output";
32
- import { parseCommandArgs, usage } from "./args";
33
-
34
- /** Options for {@link runHelp}; the stream is injectable for tests. */
35
- export interface HelpOptions {
36
- /** The resolved output mode/color (from `output.ts`). */
37
- output: OutputContext;
38
- /** The command's normalized positional tokens from Commander. */
39
- args: readonly string[];
40
- /** stdout sink; defaults to `process.stdout`. */
41
- stdout?: Writer;
42
- }
43
-
44
- /** Run `lore help`: parse the optional `<command>`, resolve it, emit its (or the whole) manifest, and return `0`. */
45
- export function runHelp(options: HelpOptions): number {
46
- const name = parseHelpArgs(options.args);
47
- const manifest = buildManifest();
48
- if (name !== undefined) {
49
- const command = findManifestCommand(name);
50
- if (command === undefined) {
51
- const validNames = manifest.commands.map((c) => c.name).join(", ");
52
- throw new LoreError(
53
- "not_found",
54
- `unknown command "${name}"`,
55
- `valid commands: ${validNames}; run \`lore help\` to list them`,
56
- { command: name },
57
- );
58
- }
59
- emit(commandHelpRenderable(manifest, command), options.output, options.stdout);
60
- return EXIT_OK;
61
- }
62
- emit(topLevelHelpRenderable(manifest), options.output, options.stdout);
63
- return EXIT_OK;
64
- }
65
-
66
- /** Parse `help`'s tokens via the shared parser (no known flags): at most one positional (the command name); a flag or second positional is a `usage` error. */
67
- function parseHelpArgs(args: readonly string[]): string | undefined {
68
- const { positionals } = parseCommandArgs(args, "help");
69
- if (positionals.length > 1) {
70
- throw usage(`unexpected argument "${positionals[1]}"`, "run `lore help [<command>]`");
71
- }
72
- return positionals[0];
73
- }
74
-
75
- /** The whole-manifest {@link Renderable}: `data` is the full manifest; pretty/plain are the top-level command catalog. */
76
- function topLevelHelpRenderable(manifest: Manifest): Renderable<Manifest> {
77
- const text = renderTopLevelHelp(manifest);
78
- return { kind: "help.manifest", data: manifest, pretty: () => text, plain: () => text };
79
- }
80
-
81
- /** One command's {@link Renderable}: `data` is the manifest scoped to that command (same shape as the full one); pretty/plain are its detailed help. */
82
- function commandHelpRenderable(manifest: Manifest, command: ManifestCommand): Renderable<Manifest> {
83
- const text = renderCommandHelp(command);
84
- const data: Manifest = { ...manifest, commands: [command] };
85
- return { kind: "help.manifest", data, pretty: () => text, plain: () => text };
86
- }
87
-
88
- /**
89
- * Render the top-level help — the header, the command catalog, and the global
90
- * options — from the manifest. Uncolored, so the `lore help` command and the
91
- * `lore --help` flag (which the router feeds this same text) are byte-identical.
92
- * Exported for the router's `--help`/no-command short-circuit.
93
- */
94
- export function renderTopLevelHelp(manifest: Manifest = buildManifest()): string {
95
- const nameWidth = Math.max(...manifest.commands.map((c) => c.name.length));
96
- const commands = manifest.commands
97
- .map((c) => ` ${c.name.padEnd(nameWidth)} ${c.summary}${c.args ? ` (lore ${c.name} ${c.args})` : ""}`)
98
- .join("\n");
99
- const optWidth = Math.max(...manifest.globalFlags.map((f) => globalFlagLabel(f).length));
100
- const options = manifest.globalFlags.map((f) => ` ${globalFlagLabel(f).padEnd(optWidth)} ${f.summary}`).join("\n");
101
- return `lore ${VERSION} — OKF-native documentation CLI
102
-
103
- Usage:
104
- lore <command> [options]
105
-
106
- Commands:
107
- ${commands}
108
-
109
- Run \`lore help <command>\` for a command's arguments, flags, output, and exit codes.
110
-
111
- Options:
112
- ${options}
113
-
114
- Docs: docs/index.md`;
115
- }
116
-
117
- /** Render one command's detailed help: usage line, flags, output kind, exit codes, and examples. */
118
- function renderCommandHelp(command: ManifestCommand): string {
119
- const lines: string[] = [
120
- `lore ${command.name} — ${command.summary}`,
121
- "",
122
- "Usage:",
123
- ` lore ${command.name}${command.args ? ` ${command.args}` : ""}`,
124
- ];
125
- if (command.flags.length > 0) {
126
- const width = Math.max(...command.flags.map((f) => commandFlagLabel(f).length));
127
- lines.push("", "Flags:");
128
- for (const flag of command.flags) {
129
- const note = flag.repeatable ? " (repeatable)" : "";
130
- lines.push(` ${commandFlagLabel(flag).padEnd(width)} ${flag.summary}${note}`);
131
- }
132
- }
133
- lines.push("", `Output: ${command.kind} (--json envelope)`, `Exit codes: ${command.exitCodes.join(", ")}`);
134
- if (command.examples.length > 0) {
135
- lines.push("", "Examples:");
136
- for (const example of command.examples) {
137
- lines.push(` ${example}`);
138
- }
139
- }
140
- return lines.join("\n");
141
- }
142
-
143
- /** `-v, --version` when a flag has a short alias, else `--json`. */
144
- function globalFlagLabel(flag: ManifestFlag): string {
145
- return flag.alias !== undefined ? `-${flag.alias}, --${flag.name}` : `--${flag.name}`;
146
- }
147
-
148
- /** `--type <value>` for a value-taking flag, else `--strict`. */
149
- function commandFlagLabel(flag: ManifestFlag): string {
150
- return flag.takesValue ? `--${flag.name} <value>` : `--${flag.name}`;
151
- }
@@ -1,59 +0,0 @@
1
- /** `lore impact`: bounded deterministic impact expansion across exact authored typed edges. */
2
-
3
- import type { BacklogAdapter } from "../adapters/backlog";
4
- import { loadRetrievalGraph, type RetrievalGraphLoader } from "../core/retrieval";
5
- import { findImpact } from "../core/traversal";
6
- import { EXIT_OK, LoreError, WarningCollector, type Writer } from "../errors";
7
- import { emit, type OutputContext } from "../output";
8
- import { parseCommandArgs, usage, workspaceSelection } from "./args";
9
- import {
10
- assertKnownEdgeKinds,
11
- impactRenderable,
12
- normalizeEndpointId,
13
- parseEndpointKind,
14
- parseTraversalFlags,
15
- } from "./traversal";
16
-
17
- export interface ImpactCommandOptions {
18
- readonly root: string;
19
- readonly output: OutputContext;
20
- readonly args: readonly string[];
21
- readonly stdout?: Writer;
22
- readonly stderr?: Writer;
23
- readonly adapter?: BacklogAdapter;
24
- readonly retrieval?: RetrievalGraphLoader;
25
- }
26
-
27
- export async function runImpact(options: ImpactCommandOptions): Promise<number> {
28
- const parsed = parseCommandArgs(options.args, "impact");
29
- const workspace = workspaceSelection(parsed);
30
- if (parsed.positionals.length !== 1) {
31
- throw usage("impact needs exactly one <id>", "run `lore impact <id> --kind <kind> --direction <direction>`");
32
- }
33
- const kind = parseEndpointKind(parsed, "kind");
34
- const flags = parseTraversalFlags(parsed);
35
- const advisories = new WarningCollector();
36
- const loaded = await (options.retrieval ?? loadRetrievalGraph)({
37
- root: options.root,
38
- warnings: advisories,
39
- adapter: options.adapter,
40
- includeTraversal: true,
41
- ...(workspace !== undefined ? { workspace } : {}),
42
- });
43
- try {
44
- advisories.flush({ color: options.output.color, stderr: options.stderr });
45
- if (loaded.traversal === undefined) throw new LoreError("validation", "traversal snapshot was not loaded");
46
- assertKnownEdgeKinds(loaded.traversal, flags.edgeKinds);
47
- const data = findImpact(loaded.traversal, {
48
- root: {
49
- kind,
50
- id: normalizeEndpointId(parsed.positionals[0] as string, kind, workspace !== undefined),
51
- },
52
- ...flags,
53
- });
54
- emit(impactRenderable(data), options.output, options.stdout);
55
- return EXIT_OK;
56
- } finally {
57
- await loaded.dispose?.();
58
- }
59
- }