@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,151 +0,0 @@
1
- /** Shared CLI parsing and rendering for bounded typed traversal commands. */
2
-
3
- import { idFromPath } from "../core/concept";
4
- import {
5
- DEFAULT_TRAVERSAL_LIMIT,
6
- DEFAULT_TRAVERSAL_MAX_DEPTH,
7
- type ImpactResult,
8
- MAX_TRAVERSAL_DEPTH,
9
- MAX_TRAVERSAL_LIMIT,
10
- type PathResult,
11
- type TraversalDirection,
12
- type TraversalEndpointKind,
13
- type TraversalSnapshot,
14
- type TraversalStep,
15
- } from "../core/traversal";
16
- import { parseQualifiedWorkspaceId, qualifyWorkspaceId } from "../core/workspace-contract";
17
- import { singleLine } from "../errors";
18
- import type { Renderable } from "../output";
19
- import { optionValues, type ParsedArgs, singleOptionValue, usage } from "./args";
20
-
21
- export interface TraversalFlags {
22
- readonly direction: TraversalDirection;
23
- readonly edgeKinds?: readonly string[];
24
- readonly maxDepth: number;
25
- readonly limit: number;
26
- }
27
-
28
- export function parseTraversalFlags(parsed: ParsedArgs): TraversalFlags {
29
- const direction = requiredChoice(parsed, "direction", ["outbound", "inbound", "either"] as const);
30
- const edgeKinds = optionValues(parsed, "edge").map((value) => requiredValue("edge", value));
31
- if (new Set(edgeKinds).size !== edgeKinds.length) {
32
- throw usage("--edge values must be unique", "pass each authored edge kind at most once");
33
- }
34
- const maxDepth = boundedInteger(parsed, "max-depth", DEFAULT_TRAVERSAL_MAX_DEPTH, 0, MAX_TRAVERSAL_DEPTH);
35
- const limit = boundedInteger(parsed, "limit", DEFAULT_TRAVERSAL_LIMIT, 1, MAX_TRAVERSAL_LIMIT);
36
- return {
37
- direction,
38
- ...(edgeKinds.length > 0 ? { edgeKinds } : {}),
39
- maxDepth,
40
- limit,
41
- };
42
- }
43
-
44
- export function parseEndpointKind(parsed: ParsedArgs, name: string): TraversalEndpointKind {
45
- return requiredChoice(parsed, name, ["concept", "task"] as const);
46
- }
47
-
48
- export function normalizeEndpointId(raw: string, kind: TraversalEndpointKind, workspace: boolean): string {
49
- const value = raw.trim();
50
- if (value === "") throw usage("endpoint id must not be empty", "pass a concept or task id");
51
- if (!workspace) return kind === "concept" ? idFromPath(value) : value;
52
- try {
53
- const parsed = parseQualifiedWorkspaceId(value);
54
- return qualifyWorkspaceId(parsed.memberId, kind === "concept" ? idFromPath(parsed.sourceId) : parsed.sourceId);
55
- } catch {
56
- throw usage(`invalid workspace ${kind} id "${value}"`, "use the unambiguous <member-id>::<source-id> form");
57
- }
58
- }
59
-
60
- export function assertKnownEdgeKinds(snapshot: TraversalSnapshot, requested?: readonly string[]): void {
61
- if (requested === undefined) return;
62
- const known = new Set(snapshot.edges.map((edge) => edge.kind));
63
- const unknown = requested.find((kind) => !known.has(kind));
64
- if (unknown !== undefined) {
65
- throw usage(`unknown authored edge kind "${unknown}"`, "omit --edge to traverse all authored edge kinds");
66
- }
67
- }
68
-
69
- export function pathRenderable(data: PathResult): Renderable<PathResult> {
70
- return { kind: "path.result", data, pretty: renderPaths, plain: renderPaths };
71
- }
72
-
73
- export function impactRenderable(data: ImpactResult): Renderable<ImpactResult> {
74
- return {
75
- kind: "impact.result",
76
- data,
77
- pretty: renderImpact,
78
- plain: renderImpact,
79
- };
80
- }
81
-
82
- function renderPaths(data: PathResult): string {
83
- const header = `${data.shown} path${data.shown === 1 ? "" : "s"} from ${typed(data.from.kind, data.from.id)} to ${typed(data.to.kind, data.to.id)}`;
84
- const lines = data.paths.map((path, index) => `${index + 1}. ${renderChain(path.edges, data.from.id)}`);
85
- return `${[...lines, header, accounting(data)].join("\n")}\n`;
86
- }
87
-
88
- function renderImpact(data: ImpactResult): string {
89
- const lines = data.impacts.map(
90
- (impact) =>
91
- `${typed(impact.endpoint.kind, impact.endpoint.id)} ${impact.relationship} depth ${impact.depth} via ${renderChain(impact.evidence, data.root.id)}`,
92
- );
93
- const header = `${data.shown} impact${data.shown === 1 ? "" : "s"} from ${typed(data.root.kind, data.root.id)}`;
94
- return `${[...lines, header, accounting(data)].join("\n")}\n`;
95
- }
96
-
97
- function renderChain(steps: readonly TraversalStep[], rootId: string): string {
98
- if (steps.length === 0) return singleLine(rootId);
99
- return [
100
- singleLine(rootId),
101
- ...steps.flatMap((step) => [
102
- step.direction === "outbound" ? `-${singleLine(step.edge.kind)}->` : `<-${singleLine(step.edge.kind)}-`,
103
- singleLine(step.to.id),
104
- ]),
105
- ].join(" ");
106
- }
107
-
108
- function accounting(data: PathResult | ImpactResult): string {
109
- return `depth<=${data.limits.maxDepth}, edge-visits=${data.edgeVisits}/${data.limits.maxEdgeVisits}, complete=${String(data.complete)}, truncated=${String(data.truncated)}`;
110
- }
111
-
112
- function typed(kind: TraversalEndpointKind, id: string): string {
113
- return `${kind}:${singleLine(id)}`;
114
- }
115
-
116
- function requiredValue(name: string, raw: string): string {
117
- const value = raw.trim();
118
- if (value === "") throw usage(`--${name} needs a value`, `pass --${name}=<value>`);
119
- return value;
120
- }
121
-
122
- function requiredChoice<const T extends readonly string[]>(parsed: ParsedArgs, name: string, values: T): T[number] {
123
- const raw = singleOptionValue(parsed, name);
124
- if (raw === undefined || raw.trim() === "") {
125
- throw usage(`--${name} is required`, `pass --${name} <${values.join("|")}>`);
126
- }
127
- const value = raw.trim();
128
- if (!values.includes(value)) {
129
- throw usage(`invalid --${name} "${value}"`, `choose one of ${values.join(", ")}`);
130
- }
131
- return value as T[number];
132
- }
133
-
134
- function boundedInteger(
135
- parsed: ParsedArgs,
136
- name: string,
137
- defaultValue: number,
138
- minimum: number,
139
- maximum: number,
140
- ): number {
141
- const raw = singleOptionValue(parsed, name);
142
- if (raw === undefined) return defaultValue;
143
- if (!/^\d+$/u.test(raw)) {
144
- throw usage(`invalid --${name} "${raw}"`, `pass an integer from ${minimum} through ${maximum}`);
145
- }
146
- const value = Number.parseInt(raw, 10);
147
- if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
148
- throw usage(`--${name} must be between ${minimum} and ${maximum}`, `pass an integer no greater than ${maximum}`);
149
- }
150
- return value;
151
- }
@@ -1,226 +0,0 @@
1
- /**
2
- * commands/validate.ts — `lore validate [paths…]`: tiered per-file conformance reporting.
3
- *
4
- * The thin, side-effecting layer over the pure {@link validateFiles} engine (ADR-0007,
5
- * cli-surface §validate): it parses the command's own arguments, **discovers** the files to
6
- * check (a default whole-bundle walk, or the explicit `[paths…]` a pre-commit hook passes —
7
- * LORE-19 AC#2), reads their bytes, asks core for the tiered findings, renders the report, and
8
- * returns the exit code. All file discovery and I/O live here; all judgement lives in
9
- * `core/validate.ts`.
10
- *
11
- * Unlike `init`/`new`, a content failure is **not** a thrown {@link LoreError}: `validate` is a
12
- * reporter, so it emits the full `validate.report` on stdout and then *returns* exit `6` when any
13
- * error-tier finding exists (or any warning under `--strict`) — the report is the payload, the
14
- * exit code is the gate signal. Only a *usage* error (bad flag) or an *I/O* failure (an
15
- * unreadable path) throws, funneling through the router's one error seam like every command.
16
- */
17
-
18
- import { statSync } from "node:fs";
19
- import { join, resolve } from "node:path";
20
- import { walkMarkdown } from "../core/bundle";
21
- import { loadProfile } from "../core/profile";
22
- import { DOCS_DIR } from "../core/scaffold";
23
- import { canonicalType } from "../core/schema";
24
- import { type FileReport, type Finding, type ValidateReport, validateFiles } from "../core/validate";
25
- import { ANSI, EXIT_CODES, EXIT_OK, ioError, LoreError, paint, WarningCollector, type Writer } from "../errors";
26
- import { emit, type OutputContext, type Renderable } from "../output";
27
- import { assertFlagAtMostOnce, parseCommandArgs, singleOptionValue } from "./args";
28
- import { canonicalIdentity, readSource, toRepoRelative } from "./discover";
29
-
30
- /** Options for {@link runValidate}; `root` and the streams are injectable for tests. */
31
- export interface ValidateOptions {
32
- /** The repo root the (relative) target paths resolve against. */
33
- root: string;
34
- /** The resolved output mode/color (from `output.ts`). */
35
- output: OutputContext;
36
- /** The command's normalized positional + flag tokens from Commander. */
37
- args: readonly string[];
38
- /** stdout sink; defaults to `process.stdout`. */
39
- stdout?: Writer;
40
- /** stderr sink for discovery advisories (a skipped symlink, an unreadable sub-directory); defaults to `process.stderr`. */
41
- stderr?: Writer;
42
- }
43
-
44
- /** The parsed form of `lore validate`'s arguments. */
45
- interface ValidateArgs {
46
- /** Explicit target paths (files or directories); empty means the whole bundle. */
47
- paths: string[];
48
- /** `--type <T>`: limit the report to one concept type (canonical or case-insensitive). */
49
- type?: string;
50
- /** `--strict`: treat any warning as a failure for the exit code. */
51
- strict: boolean;
52
- }
53
-
54
- /** A discovered file ready for the pure engine: its repo-relative path and raw bytes. */
55
- interface SourceFile {
56
- path: string;
57
- raw: string;
58
- }
59
-
60
- /**
61
- * Run `lore validate`: parse the arguments, discover and read the target files, validate them,
62
- * emit the `validate.report`, and return the exit code — `0` when clean (or warnings-only without
63
- * `--strict`), `6` when any error-tier finding exists (or any warning under `--strict`). A bad
64
- * flag throws a `usage` {@link LoreError} (exit `2`); an unreadable path a `not_found`/`denied`.
65
- *
66
- * Discovery advisories (a `.md` concept skipped behind a symlink, an unreadable sub-directory) are
67
- * flushed to stderr — never silently swallowed, so a run that omits files says so — but, like every
68
- * advisory, they do not change the exit code.
69
- */
70
- export function runValidate(options: ValidateOptions): number {
71
- const parsed = parseValidateArgs(options.args);
72
- const profile = loadProfile({ root: options.root });
73
- const type = parsed.type === undefined ? undefined : canonicalType(parsed.type, profile);
74
- const walkWarnings = new WarningCollector();
75
- const files = collectFiles(options.root, parsed.paths, walkWarnings);
76
-
77
- const report = validateFiles(files, type, profile);
78
- emit(reportRenderable(report), options.output, options.stdout);
79
- walkWarnings.flush({ color: options.output.color, stderr: options.stderr });
80
-
81
- const failed = report.errorCount > 0 || (parsed.strict && report.warningCount > 0);
82
- return failed ? EXIT_CODES.validation : EXIT_OK;
83
- }
84
-
85
- // ── Argument parsing ───────────────────────────────────────────────────────────
86
-
87
- /**
88
- * Parse `validate`'s tokens into target paths and its flags. Commander has already resolved
89
- * lore's global flags, so anything `--`-prefixed here is a command flag: an unrecognized one is a
90
- * `usage` error. Both `--flag value` and `--flag=value` forms are accepted; `--type`'s value must
91
- * not itself be a flag-looking token (so a mis-ordered `--type --strict` fails loud rather than
92
- * eating `--strict`). A `--` ends option parsing so a path may begin with `-`.
93
- */
94
- function parseValidateArgs(args: readonly string[]): ValidateArgs {
95
- const parsed = parseCommandArgs(args, "validate");
96
- const type = singleOptionValue(parsed, "type");
97
- assertFlagAtMostOnce(parsed, "strict");
98
- if (type !== undefined && type.trim() === "") {
99
- throw usage("`--type` needs a value", "pass a type, e.g. --type ADR");
100
- }
101
- return { paths: parsed.positionals, type, strict: parsed.flags.has("strict") };
102
- }
103
-
104
- // ── File discovery ─────────────────────────────────────────────────────────────
105
-
106
- /**
107
- * Discover and read every file to validate. With no explicit paths the target is the whole
108
- * bundle (`docs/`); otherwise each path is taken verbatim — a **directory** is walked for `.md`
109
- * files (via the same {@link walkMarkdown} the bundle loader uses, so the walk is sorted,
110
- * symlink-safe, and `.md`-only — its skipped-symlink/unreadable-subdir advisories flow to
111
- * `warnings`), and a **file** is read directly even if it is not `.md` (the user named it
112
- * explicitly). Results are de-duplicated by the file's **canonical (realpath) identity**, so the
113
- * same physical file named twice — including via two casings on a case-insensitive filesystem, or
114
- * once directly and once under a walked directory — is validated and counted once.
115
- */
116
- function collectFiles(root: string, paths: readonly string[], warnings: WarningCollector): SourceFile[] {
117
- const targets = paths.length > 0 ? paths : [DOCS_DIR];
118
- const files: SourceFile[] = [];
119
- const seen = new Set<string>();
120
- for (const target of targets) {
121
- const abs = resolve(root, target);
122
- for (const absFile of expandTarget(abs, target, warnings)) {
123
- const identity = canonicalIdentity(absFile);
124
- if (seen.has(identity)) {
125
- continue;
126
- }
127
- seen.add(identity);
128
- const repoRel = toRepoRelative(root, absFile);
129
- files.push({ path: repoRel, raw: readSource(absFile, repoRel) });
130
- }
131
- }
132
- return files;
133
- }
134
-
135
- /**
136
- * Expand one target to the absolute file paths it names: a directory to every `.md` under it (a
137
- * sorted walk that does not follow symlinks *inside* the tree, routing its advisories to
138
- * `warnings`), a file to itself. A path that does not exist is a `not_found` {@link LoreError}
139
- * (exit `3`) naming the target the user gave, so a typo'd path fails loud rather than silently
140
- * validating nothing. An explicitly-named directory *is* followed through a top-level symlink
141
- * (the user named it); only links discovered beneath it are skipped, matching `loadBundle`.
142
- */
143
- function expandTarget(abs: string, target: string, warnings: WarningCollector): string[] {
144
- let stat: ReturnType<typeof statSync>;
145
- try {
146
- stat = statSync(abs);
147
- } catch (cause) {
148
- ioError(cause, {
149
- denied: { message: `cannot access "${target}"`, hint: "check filesystem permissions on that path" },
150
- notFound: { message: `path "${target}" does not exist`, hint: "check the path and try again" },
151
- input: { path: target },
152
- rethrowUnknown: true,
153
- });
154
- }
155
- if (stat.isDirectory()) {
156
- return walkMarkdown(abs, warnings).map((rel) => join(abs, rel));
157
- }
158
- return [abs];
159
- }
160
-
161
- // ── Output ─────────────────────────────────────────────────────────────────────
162
-
163
- /** The per-result-type rendering bundle for `validate` (output.ts dispatches on the mode). */
164
- function reportRenderable(data: ValidateReport): Renderable<ValidateReport> {
165
- return {
166
- kind: "validate.report",
167
- data,
168
- // Pretty and plain are the same layout; only color differs (plain is always ANSI-free), so a
169
- // single renderer keyed on the color flag keeps the two views from ever drifting.
170
- pretty: (report, opts) => renderReport(report, opts.color),
171
- plain: (report) => renderReport(report, false),
172
- };
173
- }
174
-
175
- /** One line per finding (colored by severity), a per-file `ok`/`skip` status otherwise, then a summary. */
176
- function renderReport(data: ValidateReport, color: boolean): string {
177
- const lines: string[] = [];
178
- for (const file of data.files) {
179
- lines.push(...fileLines(file, color));
180
- }
181
- lines.push(summaryLine(data, color));
182
- return lines.join("\n");
183
- }
184
-
185
- /**
186
- * The lines for one file: a single `ok`/`skip` status when there is nothing to report, else one
187
- * line per finding (`error`/`warning` + path + `[rule]` + message). Color paints only the
188
- * severity token, so the line stays diff-stable in plain mode.
189
- */
190
- function fileLines(file: FileReport, color: boolean): string[] {
191
- if (file.skipped) {
192
- return [`${paint("skip", ANSI.dim, color)} ${file.path} (not a concept)`];
193
- }
194
- if (file.findings.length === 0) {
195
- return [`${paint("ok", ANSI.green, color)} ${file.path}`];
196
- }
197
- return file.findings.map((finding) => findingLine(file.path, finding, color));
198
- }
199
-
200
- /** One finding line: `<severity> <path> [<rule>]: <message>`. */
201
- function findingLine(path: string, finding: Finding, color: boolean): string {
202
- const tone = finding.severity === "error" ? ANSI.red : ANSI.yellow;
203
- return `${paint(finding.severity, tone, color)} ${path} [${finding.rule}]: ${finding.message}`;
204
- }
205
-
206
- /** The trailing summary: file/error/warning/skip counts, the error count painted when nonzero. */
207
- function summaryLine(data: ValidateReport, color: boolean): string {
208
- const errors = `${data.errorCount} ${plural(data.errorCount, "error")}`;
209
- const painted = data.errorCount > 0 ? paint(errors, ANSI.red, color) : errors;
210
- return [
211
- `${data.files.length} ${plural(data.files.length, "file")}`,
212
- painted,
213
- `${data.warningCount} ${plural(data.warningCount, "warning")}`,
214
- `${data.skippedCount} skipped`,
215
- ].join(", ");
216
- }
217
-
218
- /** Pluralize a noun by count (`1 error`, `2 errors`). */
219
- function plural(count: number, noun: string): string {
220
- return count === 1 ? noun : `${noun}s`;
221
- }
222
-
223
- /** A `usage` {@link LoreError} (exit `2`) with an actionable hint. */
224
- function usage(message: string, hint: string): LoreError {
225
- return new LoreError("usage", message, hint);
226
- }