@opum-ai/lore 0.1.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.
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/bin/lore.cjs +109 -0
- package/package.json +67 -0
- package/src/adapters/backlog.ts +1084 -0
- package/src/adapters/git.ts +221 -0
- package/src/cli.ts +667 -0
- package/src/commands/agent.ts +301 -0
- package/src/commands/agents.ts +302 -0
- package/src/commands/args.ts +209 -0
- package/src/commands/changed.ts +70 -0
- package/src/commands/check.ts +1031 -0
- package/src/commands/codex-bridge.ts +49 -0
- package/src/commands/concurrency.ts +48 -0
- package/src/commands/context.ts +292 -0
- package/src/commands/discover.ts +89 -0
- package/src/commands/explorer.ts +253 -0
- package/src/commands/export.ts +93 -0
- package/src/commands/fswrite.ts +928 -0
- package/src/commands/graph.ts +291 -0
- package/src/commands/help.ts +151 -0
- package/src/commands/impact.ts +59 -0
- package/src/commands/init.ts +583 -0
- package/src/commands/instructions.ts +91 -0
- package/src/commands/link.ts +929 -0
- package/src/commands/new.ts +476 -0
- package/src/commands/orphans.ts +457 -0
- package/src/commands/path.ts +67 -0
- package/src/commands/provenance.ts +68 -0
- package/src/commands/query.ts +312 -0
- package/src/commands/reconcile-shared.ts +280 -0
- package/src/commands/rename.ts +585 -0
- package/src/commands/replace.ts +320 -0
- package/src/commands/scaffold.ts +346 -0
- package/src/commands/schema.ts +293 -0
- package/src/commands/snapshot.ts +130 -0
- package/src/commands/supersede.ts +400 -0
- package/src/commands/sync.ts +371 -0
- package/src/commands/tasks.ts +271 -0
- package/src/commands/traversal.ts +151 -0
- package/src/commands/validate.ts +226 -0
- package/src/config.ts +598 -0
- package/src/core/agent-bridge.ts +287 -0
- package/src/core/agent-context.ts +498 -0
- package/src/core/agent-profile.ts +447 -0
- package/src/core/bundle.ts +893 -0
- package/src/core/check.ts +853 -0
- package/src/core/codex-bridge.ts +100 -0
- package/src/core/concept.ts +597 -0
- package/src/core/consumer-scaffold.ts +433 -0
- package/src/core/context.ts +271 -0
- package/src/core/explorer-contract.ts +441 -0
- package/src/core/explorer-qualification.ts +58 -0
- package/src/core/explorer.ts +518 -0
- package/src/core/finding.ts +31 -0
- package/src/core/graph.ts +201 -0
- package/src/core/indexes.ts +436 -0
- package/src/core/instructions.ts +209 -0
- package/src/core/ladybug-driver.ts +1795 -0
- package/src/core/ladybug-lifecycle.ts +1178 -0
- package/src/core/ladybug-native.ts +95 -0
- package/src/core/ladybug-source.ts +667 -0
- package/src/core/links.ts +681 -0
- package/src/core/log.ts +253 -0
- package/src/core/managed-block.ts +540 -0
- package/src/core/manifest.ts +718 -0
- package/src/core/order.ts +13 -0
- package/src/core/profile.ts +1007 -0
- package/src/core/projection.ts +195 -0
- package/src/core/query.ts +542 -0
- package/src/core/reconcile.ts +236 -0
- package/src/core/replace.ts +419 -0
- package/src/core/retrieval.ts +213 -0
- package/src/core/rewrite.ts +940 -0
- package/src/core/scaffold.ts +255 -0
- package/src/core/schema.ts +366 -0
- package/src/core/snapshot-runtime.ts +52 -0
- package/src/core/snapshot-store.ts +287 -0
- package/src/core/snapshot.ts +711 -0
- package/src/core/template.ts +429 -0
- package/src/core/traversal.ts +487 -0
- package/src/core/validate.ts +517 -0
- package/src/core/workspace-contract.ts +473 -0
- package/src/core/workspace-projection.ts +365 -0
- package/src/core/workspace-retrieval.ts +196 -0
- package/src/core/workspace-source.ts +174 -0
- package/src/errors.ts +697 -0
- package/src/meta.ts +7 -0
- package/src/output.ts +589 -0
- package/src/scripts/upstream-backlog-watch.ts +288 -0
- package/src/state.ts +390 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* upstream-backlog-watch.ts — LORE-254.
|
|
4
|
+
*
|
|
5
|
+
* Repo-maintenance script, **not** part of the `lore` CLI surface (no `--json` envelope, no
|
|
6
|
+
* exit-code contract, not reachable through `lore <command>` at all). It is invoked directly by
|
|
7
|
+
* `.github/workflows/upstream-backlog-watch.yml` on a schedule.
|
|
8
|
+
*
|
|
9
|
+
* Why this exists: MrLesk/Backlog.md merged commit 22a091b (PR #790 / BACK-545, stable `--json`
|
|
10
|
+
* output on `task list`/`task view`/`search`) to `main` on 2026-07-16, but has not yet **tagged**
|
|
11
|
+
* a release that contains it (the latest tag as of writing, v1.48.0, predates the merge). LORE-253
|
|
12
|
+
* — the adapter migration that unblocks lore's own first npm release — is blocked on that tag
|
|
13
|
+
* existing, and nothing upstream notifies lore when it ships. This script polls MrLesk/Backlog.md's
|
|
14
|
+
* Releases API and, the first time it finds a release whose commit history actually **contains**
|
|
15
|
+
* 22a091b (not merely a release numbered higher than v1.48.0 — a newer tag could still predate the
|
|
16
|
+
* merge), opens a tracking issue in THIS repo labeled `upstream-watch` so the maintainer can pick up
|
|
17
|
+
* LORE-253. See `docs/runbooks/upstream-backlog-md-json-tag-watch.md` for the full runbook (where the signal
|
|
18
|
+
* lands, who acts on it) and `docs/runbooks/backlog-json-patch.md` §8.1 for what LORE-253 does once
|
|
19
|
+
* unblocked.
|
|
20
|
+
*
|
|
21
|
+
* Kept stateless by design: rather than persisting "already scanned up to tag X", it re-derives
|
|
22
|
+
* "already surfaced" by asking GitHub whether a tracking issue carrying TRACKING_LABEL already
|
|
23
|
+
* exists (open OR closed) before doing any upstream work. Once a qualifying release is found and
|
|
24
|
+
* reported once, every later upstream release also contains 22a091b in its history forever after
|
|
25
|
+
* (commits don't leave history) — so the existing-issue check is what stops this from re-notifying
|
|
26
|
+
* on every future upstream release, not a per-tag dedupe.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const UPSTREAM_REPO = "MrLesk/Backlog.md";
|
|
30
|
+
const TARGET_COMMIT = "22a091b570d44c4f302ca47e7fd36fa28ad8bcb0";
|
|
31
|
+
const TARGET_COMMIT_PR_URL = "https://github.com/MrLesk/Backlog.md/pull/790";
|
|
32
|
+
// PR #790 merged 2026-07-16 (docs/runbooks/backlog-json-patch.md §8.1) -- any release published
|
|
33
|
+
// strictly before this cannot possibly contain the commit, so it bounds the scan to real
|
|
34
|
+
// candidates instead of walking MrLesk/Backlog.md's entire release history on every run.
|
|
35
|
+
const TARGET_COMMIT_MERGED_AT = "2026-07-16T00:00:00Z";
|
|
36
|
+
export const TRACKING_LABEL = "upstream-watch";
|
|
37
|
+
const TRACKING_LABEL_COLOR = "6f42c1";
|
|
38
|
+
const TRACKING_LABEL_DESCRIPTION = "MrLesk/Backlog.md shipped a --json-capable tag (LORE-254); see the linked issue.";
|
|
39
|
+
|
|
40
|
+
export interface ReleaseInfo {
|
|
41
|
+
tagName: string;
|
|
42
|
+
publishedAt: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface QualifyingRelease {
|
|
46
|
+
tagName: string;
|
|
47
|
+
sha: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type WatchResult =
|
|
51
|
+
| { action: "already-surfaced" }
|
|
52
|
+
| { action: "no-match" }
|
|
53
|
+
| { action: "opened"; tagName: string; issueUrl: string };
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Keep only releases that could possibly contain {@link TARGET_COMMIT}: everything published
|
|
57
|
+
* at/after {@link TARGET_COMMIT_MERGED_AT}. GitHub's Releases API list order is `created_at` desc
|
|
58
|
+
* (not guaranteed `published_at` order — a release drafted before the cutoff but published after
|
|
59
|
+
* it can sort below an older-published release), so this is a plain filter rather than a
|
|
60
|
+
* newest-first prefix-take: the list is already capped at `per_page=100` in memory, so a full
|
|
61
|
+
* scan costs nothing extra and doesn't risk silently dropping a qualifying release.
|
|
62
|
+
*/
|
|
63
|
+
export function candidateReleases(releases: ReleaseInfo[], cutoffIso: string = TARGET_COMMIT_MERGED_AT): ReleaseInfo[] {
|
|
64
|
+
return releases.filter((release) => release.publishedAt >= cutoffIso);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* GitHub's compare API (`/compare/{base}...{head}`) `status` field: `"identical"` or `"ahead"`
|
|
69
|
+
* (head ahead of base) both mean `base` is an ancestor of (or equal to) `head` — i.e. the tag's
|
|
70
|
+
* history actually contains the commit. This is what distinguishes a real `--json`-capable tag
|
|
71
|
+
* from one that is merely numbered newer (`"behind"` / `"diverged"`).
|
|
72
|
+
*/
|
|
73
|
+
export function isAncestorCompareStatus(status: string): boolean {
|
|
74
|
+
return status === "identical" || status === "ahead";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The tracking issue body for a release confirmed to contain {@link TARGET_COMMIT}. */
|
|
78
|
+
export function renderIssueBody(release: QualifyingRelease): string {
|
|
79
|
+
const releaseUrl = `https://github.com/${UPSTREAM_REPO}/releases/tag/${release.tagName}`;
|
|
80
|
+
return [
|
|
81
|
+
`MrLesk/Backlog.md tag [\`${release.tagName}\`](${releaseUrl}) (commit \`${release.sha}\`) is the first`,
|
|
82
|
+
`tagged release whose history contains commit \`${TARGET_COMMIT}\``,
|
|
83
|
+
`([PR #790](${TARGET_COMMIT_PR_URL}) / BACK-545 — stable \`--json\` output on \`task list\`/\`task view\`/\`search\`),`,
|
|
84
|
+
"confirmed via the GitHub compare API (not merely a tag numbered newer than v1.48.0).",
|
|
85
|
+
"",
|
|
86
|
+
'This is the external event **LORE-253** ("Migrate backlog adapter to the released --json',
|
|
87
|
+
"Backlog.md...\") and `docs/runbooks/release-publishing.md`'s release Prerequisites were both",
|
|
88
|
+
"waiting on — it is also the last gate on lore's own first npm release.",
|
|
89
|
+
"",
|
|
90
|
+
"**Next step:** `backlog task view LORE-253 --plain`, then follow",
|
|
91
|
+
"`docs/runbooks/backlog-json-patch.md` §8.1 step 4 to point `src/adapters/backlog.ts` at the",
|
|
92
|
+
"published package instead of the interim pinned-commit build.",
|
|
93
|
+
"",
|
|
94
|
+
"Detected by `.github/workflows/upstream-backlog-watch.yml` (LORE-254); see",
|
|
95
|
+
"`docs/runbooks/upstream-backlog-md-json-tag-watch.md` for how this signal works and who acts on it.",
|
|
96
|
+
].join("\n");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ── I/O boundary ─────────────────────────────────────────────────────────────
|
|
100
|
+
// Everything below talks to the GitHub REST API. Every function threads through an injectable
|
|
101
|
+
// `fetcher` (defaulting to the real global `fetch` only at the `watchOnce` boundary) so the whole
|
|
102
|
+
// orchestration can be exercised in tests against a stub instead of the network.
|
|
103
|
+
|
|
104
|
+
type Fetcher = typeof fetch;
|
|
105
|
+
|
|
106
|
+
class GithubApiError extends Error {
|
|
107
|
+
constructor(
|
|
108
|
+
method: string,
|
|
109
|
+
url: string,
|
|
110
|
+
readonly status: number,
|
|
111
|
+
body: string,
|
|
112
|
+
) {
|
|
113
|
+
super(`GitHub API ${method} ${url} -> ${status}: ${body}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function githubApi<T>(fetcher: Fetcher, token: string, method: string, path: string, body?: unknown): Promise<T> {
|
|
118
|
+
const url = path.startsWith("http") ? path : `https://api.github.com${path}`;
|
|
119
|
+
const response = await fetcher(url, {
|
|
120
|
+
method,
|
|
121
|
+
headers: {
|
|
122
|
+
Accept: "application/vnd.github+json",
|
|
123
|
+
Authorization: `Bearer ${token}`,
|
|
124
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
125
|
+
"User-Agent": "lore-upstream-backlog-watch (LORE-254)",
|
|
126
|
+
...(body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
127
|
+
},
|
|
128
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
129
|
+
});
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
throw new GithubApiError(method, url, response.status, await response.text());
|
|
132
|
+
}
|
|
133
|
+
return (await response.json()) as T;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface UpstreamReleaseApiRow {
|
|
137
|
+
tag_name: string;
|
|
138
|
+
published_at: string | null;
|
|
139
|
+
draft: boolean;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function listUpstreamReleases(fetcher: Fetcher, token: string): Promise<ReleaseInfo[]> {
|
|
143
|
+
const rows = await githubApi<UpstreamReleaseApiRow[]>(
|
|
144
|
+
fetcher,
|
|
145
|
+
token,
|
|
146
|
+
"GET",
|
|
147
|
+
`/repos/${UPSTREAM_REPO}/releases?per_page=100`,
|
|
148
|
+
);
|
|
149
|
+
return rows
|
|
150
|
+
.filter((row): row is UpstreamReleaseApiRow & { published_at: string } => !row.draft && row.published_at !== null)
|
|
151
|
+
.map((row) => ({ tagName: row.tag_name, publishedAt: row.published_at }));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function resolveTagSha(fetcher: Fetcher, token: string, tag: string): Promise<string> {
|
|
155
|
+
const commit = await githubApi<{ sha: string }>(fetcher, token, "GET", `/repos/${UPSTREAM_REPO}/commits/${tag}`);
|
|
156
|
+
return commit.sha;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function compareAgainstTarget(fetcher: Fetcher, token: string, sha: string): Promise<string> {
|
|
160
|
+
const comparison = await githubApi<{ status: string }>(
|
|
161
|
+
fetcher,
|
|
162
|
+
token,
|
|
163
|
+
"GET",
|
|
164
|
+
`/repos/${UPSTREAM_REPO}/compare/${TARGET_COMMIT}...${sha}`,
|
|
165
|
+
);
|
|
166
|
+
return comparison.status;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The first candidate release, in GitHub's Releases API list order (`created_at` desc — not
|
|
171
|
+
* necessarily `published_at` order), whose history contains {@link TARGET_COMMIT}, if any.
|
|
172
|
+
*/
|
|
173
|
+
async function findQualifyingRelease(fetcher: Fetcher, token: string): Promise<QualifyingRelease | null> {
|
|
174
|
+
const releases = await listUpstreamReleases(fetcher, token);
|
|
175
|
+
for (const candidate of candidateReleases(releases)) {
|
|
176
|
+
const sha = await resolveTagSha(fetcher, token, candidate.tagName);
|
|
177
|
+
const status = await compareAgainstTarget(fetcher, token, sha);
|
|
178
|
+
if (isAncestorCompareStatus(status)) {
|
|
179
|
+
return { tagName: candidate.tagName, sha };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface OwnIssueApiRow {
|
|
186
|
+
pull_request?: unknown;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function trackingIssueAlreadyExists(fetcher: Fetcher, token: string, repoSlug: string): Promise<boolean> {
|
|
190
|
+
const rows = await githubApi<OwnIssueApiRow[]>(
|
|
191
|
+
fetcher,
|
|
192
|
+
token,
|
|
193
|
+
"GET",
|
|
194
|
+
`/repos/${repoSlug}/issues?labels=${TRACKING_LABEL}&state=all&per_page=100`,
|
|
195
|
+
);
|
|
196
|
+
// The issues endpoint also returns pull requests carrying the label; a real hit must be an
|
|
197
|
+
// actual issue.
|
|
198
|
+
return rows.some((row) => !row.pull_request);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function ensureTrackingLabelExists(fetcher: Fetcher, token: string, repoSlug: string): Promise<void> {
|
|
202
|
+
try {
|
|
203
|
+
await githubApi(fetcher, token, "POST", `/repos/${repoSlug}/labels`, {
|
|
204
|
+
name: TRACKING_LABEL,
|
|
205
|
+
color: TRACKING_LABEL_COLOR,
|
|
206
|
+
description: TRACKING_LABEL_DESCRIPTION,
|
|
207
|
+
});
|
|
208
|
+
} catch (error) {
|
|
209
|
+
// 422 = the label already exists; anything else is a real failure.
|
|
210
|
+
if (!(error instanceof GithubApiError && error.status === 422)) {
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
interface CreatedIssueApiRow {
|
|
217
|
+
html_url: string;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function openTrackingIssue(
|
|
221
|
+
fetcher: Fetcher,
|
|
222
|
+
token: string,
|
|
223
|
+
repoSlug: string,
|
|
224
|
+
release: QualifyingRelease,
|
|
225
|
+
): Promise<string> {
|
|
226
|
+
const created = await githubApi<CreatedIssueApiRow>(fetcher, token, "POST", `/repos/${repoSlug}/issues`, {
|
|
227
|
+
title: `Upstream Backlog.md ${release.tagName} is --json-capable — LORE-253 is unblocked`,
|
|
228
|
+
body: renderIssueBody(release),
|
|
229
|
+
labels: [TRACKING_LABEL],
|
|
230
|
+
});
|
|
231
|
+
return created.html_url;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Run one check: skip entirely if already surfaced, otherwise probe upstream and open the
|
|
236
|
+
* tracking issue on a hit. `fetcher` defaults to the real global `fetch` — tests pass a stub.
|
|
237
|
+
*/
|
|
238
|
+
export async function watchOnce(
|
|
239
|
+
env: { repoSlug: string; token: string },
|
|
240
|
+
fetcher: Fetcher = fetch,
|
|
241
|
+
): Promise<WatchResult> {
|
|
242
|
+
if (await trackingIssueAlreadyExists(fetcher, env.token, env.repoSlug)) {
|
|
243
|
+
return { action: "already-surfaced" };
|
|
244
|
+
}
|
|
245
|
+
const found = await findQualifyingRelease(fetcher, env.token);
|
|
246
|
+
if (!found) {
|
|
247
|
+
return { action: "no-match" };
|
|
248
|
+
}
|
|
249
|
+
await ensureTrackingLabelExists(fetcher, env.token, env.repoSlug);
|
|
250
|
+
const issueUrl = await openTrackingIssue(fetcher, env.token, env.repoSlug, found);
|
|
251
|
+
return { action: "opened", tagName: found.tagName, issueUrl };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function main(): Promise<number> {
|
|
255
|
+
const repoSlug = process.env.GITHUB_REPOSITORY;
|
|
256
|
+
const token = process.env.GITHUB_TOKEN;
|
|
257
|
+
if (!repoSlug || !token) {
|
|
258
|
+
console.error("GITHUB_REPOSITORY and GITHUB_TOKEN must both be set (this script runs under GitHub Actions).");
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
const result = await watchOnce({ repoSlug, token });
|
|
262
|
+
switch (result.action) {
|
|
263
|
+
case "already-surfaced":
|
|
264
|
+
console.log(`A "${TRACKING_LABEL}" tracking issue already exists in ${repoSlug}; nothing to do.`);
|
|
265
|
+
break;
|
|
266
|
+
case "no-match":
|
|
267
|
+
console.log(`No MrLesk/Backlog.md release yet contains ${TARGET_COMMIT}.`);
|
|
268
|
+
break;
|
|
269
|
+
case "opened":
|
|
270
|
+
console.log(`Opened ${result.issueUrl} for upstream tag ${result.tagName}.`);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Same shape as cli.ts's own entrypoint guard (LORE-70): set `process.exitCode` rather than call
|
|
277
|
+
// `process.exit()` so pending stdout/stderr writes flush before the process actually ends.
|
|
278
|
+
if (import.meta.main) {
|
|
279
|
+
Promise.resolve(main()).then(
|
|
280
|
+
(code) => {
|
|
281
|
+
process.exitCode = code;
|
|
282
|
+
},
|
|
283
|
+
(error) => {
|
|
284
|
+
console.error(error);
|
|
285
|
+
process.exitCode = 1;
|
|
286
|
+
},
|
|
287
|
+
);
|
|
288
|
+
}
|