omgbase 0.29.0 → 0.30.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.
- package/dist/build-info.json +2 -2
- package/dist/src/cmd/admin.js +19 -3
- package/dist/src/cmd/diff.js +5 -3
- package/dist/src/cmd/hist.js +1 -1
- package/dist/src/cmd/history.js +325 -12
- package/dist/src/cmd/mcp.js +30 -3
- package/dist/src/cmd/show.js +21 -8
- package/dist/src/seams.js +8 -1
- package/package.json +4 -3
package/dist/build-info.json
CHANGED
package/dist/src/cmd/admin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import { rebuildIndex, runGc, buildEmbedTasks, buildDocEmbedTasks, indexIdentity, requireSemantic, resolveSettings, workspaceSettings, repoOwnSettings, writeWorkspaceSettings, writeRepoSettings, ftsIndexedRowCount, liveLeafCount, sizeReport, activeAlerts, refreshLegacyBytesAlert, readCapability, openCoverageFrom, captureFor, enableSemantic, disableSemantic, semanticProviderOf, SemanticProviderMissing, withWriterLock, vacuum, reclaimableBytes, humanBytes, formatShare, } from "@omgbase/core";
|
|
2
|
+
import { historyProviderOf, NATIVE_PROVIDER, requireProvider, providerConfigOf, CapabilityUnavailable, rebuildIndex, runGc, buildEmbedTasks, buildDocEmbedTasks, indexIdentity, requireSemantic, resolveSettings, workspaceSettings, repoOwnSettings, writeWorkspaceSettings, writeRepoSettings, ftsIndexedRowCount, liveLeafCount, sizeReport, activeAlerts, refreshLegacyBytesAlert, readCapability, openCoverageFrom, captureFor, enableSemantic, disableSemantic, semanticProviderOf, SemanticProviderMissing, withWriterLock, vacuum, reclaimableBytes, humanBytes, formatShare, } from "@omgbase/core";
|
|
3
3
|
import { CliUsageError, EngineErrorLike, EXIT_OK, EXIT_ERROR, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
import { columns } from "../render.js";
|
|
5
5
|
import { loadEmbedding, drainEmbeddings } from "./_embed.js";
|
|
@@ -136,7 +136,21 @@ function runDoctor(cli, args) {
|
|
|
136
136
|
const manifest = readCapability(db, repo.repoId, "history");
|
|
137
137
|
const since = openCoverageFrom(manifest);
|
|
138
138
|
const recorder = captureFor(db, repo.repoId);
|
|
139
|
-
|
|
139
|
+
const provider = historyProviderOf(manifest);
|
|
140
|
+
if (provider !== NATIVE_PROVIDER && since !== null) {
|
|
141
|
+
// Phase 6a (spec/cli §6, 5.5): a git-provider repository's capture is the worker's — the check reads how far the
|
|
142
|
+
// branch tip lags the heads; `provider git: <n> heads not yet checkpointed`, or `unavailable` when the provider cannot run.
|
|
143
|
+
try {
|
|
144
|
+
const uncaptured = requireProvider(db, repo.repoId, provider).reader(repo.repoId, providerConfigOf(manifest)).uncapturedHeads(repo.repoId, since);
|
|
145
|
+
checks.push({ name: "capture complete", ok: uncaptured === 0, detail: `provider ${provider}: ${uncaptured} heads not yet checkpointed` });
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
if (!(e instanceof CapabilityUnavailable))
|
|
149
|
+
throw e;
|
|
150
|
+
checks.push({ name: "capture complete", ok: false, detail: `provider ${provider}: unavailable (${e.data().reason ?? e.message})` });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else if (recorder && since !== null) {
|
|
140
154
|
const uncaptured = recorder.uncapturedHeads(repo.repoId, since);
|
|
141
155
|
checks.push({ name: "capture complete", ok: uncaptured === 0, detail: `${uncaptured} heads without a revision` });
|
|
142
156
|
}
|
|
@@ -237,8 +251,10 @@ function capabilityLine(capabilities, name) {
|
|
|
237
251
|
const c = capabilities.find((x) => x.capability === name);
|
|
238
252
|
if (!c || c.state === "disabled")
|
|
239
253
|
return "disabled";
|
|
254
|
+
// Phase 6a: a provider row says which — `enabled since <ts> (git)`.
|
|
255
|
+
const provider = typeof c.config.provider === "string" ? ` (${c.config.provider})` : "";
|
|
240
256
|
if (name === "history")
|
|
241
|
-
return c.coverage_from ? `enabled since ${c.coverage_from}` :
|
|
257
|
+
return c.coverage_from ? `enabled since ${c.coverage_from}${provider}` : `enabled${provider}`;
|
|
242
258
|
const model = c.config.model;
|
|
243
259
|
return typeof model === "string" ? `enabled (${model})` : "enabled";
|
|
244
260
|
}
|
package/dist/src/cmd/diff.js
CHANGED
|
@@ -44,7 +44,7 @@ async function runDiff(cli, args) {
|
|
|
44
44
|
// Through the recorder seam (Phase 4a): `capability_unavailable { history, unavailable | disabled }` when the build has no module or nothing was ever captured;
|
|
45
45
|
// a revision the recorder does not hold answers by the spec/surface §2.1 decision table (Phase 4b).
|
|
46
46
|
try {
|
|
47
|
-
result = requireHistoryReader(ws.store.db).docDiffUnified(info.docId, {
|
|
47
|
+
result = requireHistoryReader(ws.store.db, repo.repoId).docDiffUnified(info.docId, {
|
|
48
48
|
...(values.from ? { fromRev: values.from } : {}),
|
|
49
49
|
...(values.to ? { toRev: values.to } : {}),
|
|
50
50
|
label: ref,
|
|
@@ -62,8 +62,10 @@ async function runDiff(cli, args) {
|
|
|
62
62
|
let entries;
|
|
63
63
|
if (cli.flags.server)
|
|
64
64
|
entries = await remoteCall(cli, "docs_diff", { ...docLocator(ref), from_rev: result.from, to_rev: result.to });
|
|
65
|
-
else
|
|
66
|
-
|
|
65
|
+
else {
|
|
66
|
+
const ws = cli.workspace();
|
|
67
|
+
entries = requireHistoryReader(ws.store.db, cli.repo(ws).repoId).diffBlocks(result._id, result.from, result.to);
|
|
68
|
+
}
|
|
67
69
|
if (cli.flags.mode !== "human")
|
|
68
70
|
return emitMachine(cli, entries, { items: entries, ids: entries.map((e) => e._id) });
|
|
69
71
|
if (entries.length === 0) {
|
package/dist/src/cmd/hist.js
CHANGED
|
@@ -33,7 +33,7 @@ async function runHist(cli, args) {
|
|
|
33
33
|
if (!resolved || resolved.kind !== "block")
|
|
34
34
|
throw new EngineErrorLike("block_missing", `hist needs a block id; got ${ref}`);
|
|
35
35
|
// Through the recorder seam (Phase 4a): `capability_unavailable { history, unavailable | disabled }` when the build has no module or nothing was ever captured.
|
|
36
|
-
changes = requireHistoryReader(ws.store.db).blocksHistory(resolved.blockId, values.n ? { limit: Number(values.n) } : {});
|
|
36
|
+
changes = requireHistoryReader(ws.store.db, repo.repoId).blocksHistory(resolved.blockId, values.n ? { limit: Number(values.n) } : {});
|
|
37
37
|
}
|
|
38
38
|
// A change carries no id of its own, so `--ids` is the `--json` document.
|
|
39
39
|
if (cli.flags.mode !== "human")
|
package/dist/src/cmd/history.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
-
import { evaluateRetention, retentionPolicy, exportHistory, attachArchive, detachArchive, listArchives, humanBytes, withWriterLock, enableHistory, disableHistory, purgeHistory, openCoverageFrom, RetentionPolicyError, NotASegment, StaleSegment, ARCHIVE_DIR, } from "@omgbase/core";
|
|
4
|
+
import { evaluateRetention, retentionPolicy, exportHistory, attachArchive, detachArchive, listArchives, humanBytes, withWriterLock, enableHistory, disableHistory, purgeHistory, openCoverageFrom, historyProviderOf, readCapability, requireProvider, providerConfigOf, NATIVE_PROVIDER, HistoryProviderConflict, RetentionPolicyError, NotASegment, StaleSegment, ARCHIVE_DIR, EngineError, fsVaultSink, } from "@omgbase/core";
|
|
5
|
+
import { parseCadence, GIT_PROVIDER, GitHistoryProvider, PUSH_POLICIES, FETCH_POLICIES, rewrittenMessage } from "@omgbase/git-adapter";
|
|
5
6
|
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
6
7
|
import { columns } from "../render.js";
|
|
7
8
|
// `omg history <enable|disable|purge|plan|export|attach|detach|list>` (spec/cli
|
|
@@ -16,15 +17,25 @@ import { columns } from "../render.js";
|
|
|
16
17
|
// the segment registry). No MCP tool: the catalog's traces are
|
|
17
18
|
// `version.capabilities.history`, the `revisions.archived` attribute,
|
|
18
19
|
// `capability_unavailable` and `revision_archived`.
|
|
19
|
-
const SUBCOMMANDS = ["enable", "disable", "purge", "plan", "export", "attach", "detach", "list"];
|
|
20
|
+
const SUBCOMMANDS = ["enable", "disable", "purge", "checkpoint", "push", "fetch", "remote", "plan", "export", "attach", "detach", "list"];
|
|
21
|
+
const REMOTE_SUBCOMMANDS = ["set", "unset", "status", "resolve", "reconcile"];
|
|
22
|
+
const RECONCILE_MODES = ["rebase", "merge", "adopt", "fork"];
|
|
23
|
+
const PROVIDERS = [NATIVE_PROVIDER, GIT_PROVIDER];
|
|
20
24
|
function renderCard(cli) {
|
|
21
25
|
return renderHelp(cli, {
|
|
22
26
|
name: "history",
|
|
23
|
-
summary: "History capture (enable/disable/purge per repository) and retention: plan what the policy would archive, export it to a segment, attach/detach/list segments",
|
|
27
|
+
summary: "History capture (enable/disable/purge per repository; native or git provider) and retention: plan what the policy would archive, export it to a segment, attach/detach/list segments",
|
|
24
28
|
usage: [
|
|
25
|
-
"history enable [--best-effort]",
|
|
29
|
+
"history enable [--best-effort] [--provider native|git] [--branch <b>] [--cadence every|<ms>]",
|
|
26
30
|
"history disable",
|
|
27
31
|
"history purge [--before <ts>]",
|
|
32
|
+
"history checkpoint",
|
|
33
|
+
"history remote set --url <url> [--name <remote>] [--branch <b>] [--push off|on-checkpoint|periodic] [--fetch off|periodic|webhook]",
|
|
34
|
+
"history remote unset | status",
|
|
35
|
+
"history push",
|
|
36
|
+
"history fetch",
|
|
37
|
+
"history remote resolve <path> --ours|--theirs",
|
|
38
|
+
"history remote reconcile --rebase|--merge|--adopt|--fork",
|
|
28
39
|
"history plan [--before <ts>] [--policy <json|@file>]",
|
|
29
40
|
"history export [--prune] [--dest <dir>] [--before <ts>] [--policy <json|@file>] [--dry-run]",
|
|
30
41
|
"history attach <file|dir>",
|
|
@@ -35,7 +46,18 @@ function renderCard(cli) {
|
|
|
35
46
|
["enable", "turn capture on for the repository: one baseline commit (origin import) records every live document's current head, a coverage interval opens, every later write is captured; idempotent"],
|
|
36
47
|
["disable", "turn capture off: the coverage interval closes, later writes record no history (the feed still does); what was captured stays readable; idempotent"],
|
|
37
48
|
["--best-effort", "(enable) do not require capture: a writer without the history module may still write (default: such a writer is refused — writer_requires)"],
|
|
38
|
-
["
|
|
49
|
+
["--provider <p>", "(enable) who keeps the history: native (the store's own tables, captured in every commit — the default) or git (a bare repository at .omgbase/git, committed from the rows by the checkpoint worker); one provider per repository — switch with disable, then enable --provider"],
|
|
50
|
+
["--branch <b>", "(enable --provider git) the branch the commits go on (default omgbase)"],
|
|
51
|
+
["--cadence <c>", "(enable --provider git) every: one commit per feed row (default); <ms>: the rows pending when the worker runs fold into one commit"],
|
|
52
|
+
["checkpoint", "(git provider) run the checkpoint worker once: the feed rows since the branch tip become commits; prints { committed, from_seq, to_seq, tip, rescanned }"],
|
|
53
|
+
["remote set", "(git provider) bind a remote: its URL (never with credentials — git resolves access through the host), the remote branch (default: the local one), when to push (off — the default; on-checkpoint — after every checkpoint that committed; periodic — on every freshness sweep) and when to fetch (off; periodic — on every sweep; webhook — reserved: an external caller runs history fetch)"],
|
|
54
|
+
["remote unset", "(git provider) forget the remote, its tracking refs and its alerts"],
|
|
55
|
+
["remote status", "(git provider) local refs only, no network: { remote, branch, local_tip, remote_tip, pushed_through, state: in-sync|ahead|behind|diverged|unknown }"],
|
|
56
|
+
["push", "(git provider) push the branch and the ids ref — never --force; a rejected push raises git_remote_diverged and exits 1 (run fetch to merge, then push again)"],
|
|
57
|
+
["fetch", "(git provider) fetch the remote branch: fast-forward (the remote's changes enter the vault as external edits), a tree-level three-way merge when both sides moved (one merge commit), conflicts kept as <stem>.remote-<sha>.md beside yours with git_merge_conflict raised, or git_remote_diverged when the remote was rewritten (push and fetch pause)"],
|
|
58
|
+
["remote resolve", "(git provider) settle one conflict: --ours keeps your bytes, --theirs takes the remote's; the sidecar leaves; the merge commit is written when none remains"],
|
|
59
|
+
["remote reconcile", "(git provider; only while git_remote_diverged or git_remote_forked is active) --rebase replays your unpushed checkpoints onto the new remote head (trailers preserved, the ids ref rebased alongside); --merge one merge commit keeping both histories; --adopt the remote's head becomes truth (its tree into the vault, your edits kept beside as <stem>.local-<sha>.md, your old tip on refs/omgbase/backup/<ts>); --fork pushes your branch as <branch>-omgbase-<ts> for a human to merge with git"],
|
|
60
|
+
["purge", "delete the repository's captured history — every commit, or with --before those before an instant (a live head's revision stays while capture is on); the recorder's id holds are released; the history tables are dropped when no repository is enabled and nothing is left; on a git-provider repository the bare repository and the worker's cursor are removed"],
|
|
39
61
|
["plan", "per document: revisions kept / to archive and the bytes only they reach; the policy in effect"],
|
|
40
62
|
["export", "write ONE new segment file (default dest .omgbase/archives/) holding the archived revisions' bodies; --prune then squashes the store and records the segment"],
|
|
41
63
|
["attach <path>", "record a segment file (or every segment of a directory that belongs to this repo) so reads at its revisions work again"],
|
|
@@ -240,13 +262,17 @@ function interval(i) {
|
|
|
240
262
|
*/
|
|
241
263
|
function renderLifecycle(cli, verb, r) {
|
|
242
264
|
const row = r.row;
|
|
265
|
+
const provider = historyProviderOf(row);
|
|
243
266
|
if (cli.flags.mode !== "human") {
|
|
244
|
-
|
|
267
|
+
// The git provider's enable (spec/cli §6, 5.5) adds `provider` and the branch `tip` of its baseline commit.
|
|
268
|
+
return emitMachine(cli, { state: row.state, coverage: row.coverage, writer_requires: row.writer_requires, changed: r.changed, ...(provider !== NATIVE_PROVIDER ? { provider } : {}), ...(r.baseline ? { baseline: r.baseline } : {}), ...(r.tip !== undefined ? { tip: r.tip } : {}) });
|
|
245
269
|
}
|
|
246
270
|
const { io, style } = cli;
|
|
247
271
|
if (verb === "enable") {
|
|
248
272
|
const since = openCoverageFrom(row);
|
|
249
|
-
|
|
273
|
+
const via = provider === NATIVE_PROVIDER ? "" : ` via ${provider}`;
|
|
274
|
+
const baseline = r.baseline ? (provider === NATIVE_PROVIDER ? ` (baseline ${r.baseline.commit}: ${plural(r.baseline.revisions, "revision")})` : ` (baseline ${r.baseline.commit}: ${plural(r.baseline.revisions, "document")}${r.tip ? `, tip ${r.tip}` : ""})`) : "";
|
|
275
|
+
io.err(style.dim(` history ${r.changed ? "enabled" : "already enabled"}${since ? ` since ${since}` : ""}${via}${baseline}`));
|
|
250
276
|
}
|
|
251
277
|
else {
|
|
252
278
|
io.err(style.dim(r.changed || row.coverage.length > 0 ? ` history ${r.changed ? "disabled" : "already disabled"} (captured ${row.coverage.map(interval).join(", ")})` : " history already disabled"));
|
|
@@ -265,8 +291,13 @@ function renderLifecycle(cli, verb, r) {
|
|
|
265
291
|
*/
|
|
266
292
|
function renderPurge(cli, r) {
|
|
267
293
|
if (cli.flags.mode !== "human")
|
|
268
|
-
return emitMachine(cli, { purged: r.purged, claims_released: r.claims_released, coverage: r.coverage, tables_dropped: r.tables_dropped });
|
|
294
|
+
return emitMachine(cli, { purged: r.purged, claims_released: r.claims_released, coverage: r.coverage, tables_dropped: r.tables_dropped, ...(r.provider ? { provider: r.provider } : {}) });
|
|
269
295
|
const { io, style } = cli;
|
|
296
|
+
// The git provider (spec/cli §6, 5.5): the bare repository and the worker's cursor leave; ` purged the git history (<path> removed)` / ` nothing to purge (<path> is already gone)`.
|
|
297
|
+
if (r.provider) {
|
|
298
|
+
io.err(style.dim(r.provider.removed ? ` purged the ${r.provider.name} history (${r.provider.path} removed, cursor cleared)` : ` nothing to purge (${r.provider.path} is already gone)`));
|
|
299
|
+
return EXIT_OK;
|
|
300
|
+
}
|
|
270
301
|
const p = r.purged;
|
|
271
302
|
const total = Object.values(p).reduce((n, v) => n + v, 0) + r.claims_released;
|
|
272
303
|
if (total === 0 && !r.tables_dropped) {
|
|
@@ -283,16 +314,294 @@ function runPurge(cli, args) {
|
|
|
283
314
|
const ws = cli.workspace();
|
|
284
315
|
const repo = cli.repo(ws);
|
|
285
316
|
const before = parseBefore(values.before);
|
|
317
|
+
// A git history is one line of commits: `--before` has no meaning there (spec/cli §6, 5.5).
|
|
318
|
+
if (before !== undefined && historyProviderOf(readCapability(ws.store.db, repo.repoId, "history")) !== NATIVE_PROVIDER)
|
|
319
|
+
throw new CliUsageError("--before has no meaning for the git provider: history purge removes the whole bare repository");
|
|
286
320
|
const result = withWriterLock(ws.omgbaseDir, () => purgeHistory(ws.store.db, repo.repoId, { ...(before !== undefined ? { before } : {}) }));
|
|
287
321
|
return renderPurge(cli, result);
|
|
288
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* `history checkpoint` (spec/cli §6, 5.5; spec/store §5.12): the git provider's
|
|
325
|
+
* worker, once, under the writer lock; `--json` the receipt `{ committed,
|
|
326
|
+
* from_seq, to_seq, tip, rescanned }`, human ` checkpointed <n> commit(s)
|
|
327
|
+
* (seq <from>..<to>), tip <sha>` / ` nothing to checkpoint (tip <sha>)` /
|
|
328
|
+
* ` rescanned: 1 commit at seq <head>, tip <sha>`. On a native repository it
|
|
329
|
+
* is a usage error: the recorder captures in every commit.
|
|
330
|
+
*/
|
|
331
|
+
function runCheckpoint(cli, args) {
|
|
332
|
+
parseArgs({ args, allowPositionals: false, options: {} });
|
|
333
|
+
const ws = cli.workspace();
|
|
334
|
+
const repo = cli.repo(ws);
|
|
335
|
+
const row = readCapability(ws.store.db, repo.repoId, "history");
|
|
336
|
+
const provider = historyProviderOf(row);
|
|
337
|
+
if (provider === NATIVE_PROVIDER)
|
|
338
|
+
throw new CliUsageError("history checkpoint is the git provider's: this repository's history is native (captured in every commit)", `run '${cli.prog} history enable --provider git' after '${cli.prog} history disable' to switch`);
|
|
339
|
+
if (!row || row.state !== "enabled")
|
|
340
|
+
throw new CliUsageError(`history is disabled for this repository — enable it with '${cli.prog} history enable --provider ${provider}'`);
|
|
341
|
+
const result = withWriterLock(ws.omgbaseDir, () => requireProvider(ws.store.db, repo.repoId, provider).checkpoint(repo.repoId, providerConfigOf(row)));
|
|
342
|
+
if (cli.flags.mode !== "human")
|
|
343
|
+
return emitMachine(cli, result);
|
|
344
|
+
const { io, style } = cli;
|
|
345
|
+
if (result.rescanned)
|
|
346
|
+
io.err(style.dim(` rescanned: 1 commit at seq ${result.to_seq}, tip ${result.tip}`));
|
|
347
|
+
else if (result.committed === 0)
|
|
348
|
+
io.err(style.dim(` nothing to checkpoint (tip ${result.tip ?? "none"})`));
|
|
349
|
+
else
|
|
350
|
+
io.err(style.dim(` checkpointed ${plural(result.committed, "commit")} (seq ${result.from_seq}..${result.to_seq}), tip ${result.tip}`));
|
|
351
|
+
// `push: "on-checkpoint"` (cli 5.6): the push that followed, or why it did not.
|
|
352
|
+
if (result.push)
|
|
353
|
+
io.err(style.dim("error" in result.push ? ` push failed: ${result.push.error}` : result.push.pushed ? ` pushed ${result.push.tip}${result.push.pushed_through === null ? "" : ` (through seq ${result.push.pushed_through})`}` : ` nothing to push`));
|
|
354
|
+
return EXIT_OK;
|
|
355
|
+
}
|
|
356
|
+
/** The selected repository as a git-provider repository, or the usage errors every remote verb shares. */
|
|
357
|
+
function gitContext(cli, verb) {
|
|
358
|
+
const ws = cli.workspace();
|
|
359
|
+
const repo = cli.repo(ws);
|
|
360
|
+
const row = readCapability(ws.store.db, repo.repoId, "history");
|
|
361
|
+
const provider = historyProviderOf(row);
|
|
362
|
+
if (provider === NATIVE_PROVIDER)
|
|
363
|
+
throw new CliUsageError(`history ${verb} is the git provider's: this repository's history is native (captured in every commit)`, `run '${cli.prog} history enable --provider git' after '${cli.prog} history disable' to switch`);
|
|
364
|
+
if (!row || row.state !== "enabled")
|
|
365
|
+
throw new CliUsageError(`history is disabled for this repository — enable it with '${cli.prog} history enable --provider ${provider}'`);
|
|
366
|
+
const p = requireProvider(ws.store.db, repo.repoId, provider);
|
|
367
|
+
if (!(p instanceof GitHistoryProvider))
|
|
368
|
+
throw new CliUsageError(`history ${verb} is the git provider's (this repository's provider is ${provider})`);
|
|
369
|
+
return { ws, repoId: repo.repoId, rootPath: repo.rootPath, row, provider: p };
|
|
370
|
+
}
|
|
371
|
+
/** The vault the remote half writes inbound content into: the repository's filesystem root through the observe path. A sourceless repository has none. */
|
|
372
|
+
function vaultOf(cli, ctx, verb) {
|
|
373
|
+
if (ctx.rootPath === null)
|
|
374
|
+
throw new CliUsageError(`history ${verb} needs a repository with a filesystem source: inbound content is written into its root as external edits`, `attach one with '${cli.prog} source add <dir> --repo <slug>'`);
|
|
375
|
+
return fsVaultSink(ctx.ws.store, ctx.repoId, ctx.rootPath);
|
|
376
|
+
}
|
|
377
|
+
/** A thrown `EngineError` of the remote half (`git_remote_diverged`, `git_merge_conflict`) renders as the engine error it is (exit 1); anything else propagates. */
|
|
378
|
+
function remoteError(e) {
|
|
379
|
+
if (e instanceof EngineError)
|
|
380
|
+
throw new EngineErrorLike(e.code, e.message, { data: e.data, retriable: e.retriable });
|
|
381
|
+
throw e;
|
|
382
|
+
}
|
|
383
|
+
function runRemote(cli, args) {
|
|
384
|
+
const [sub, ...rest] = args;
|
|
385
|
+
if (sub === undefined || !REMOTE_SUBCOMMANDS.includes(sub))
|
|
386
|
+
throw new CliUsageError(`history remote takes one of ${REMOTE_SUBCOMMANDS.join("|")}${sub === undefined ? "" : ` (got ${JSON.stringify(sub)})`}`, `run '${cli.prog} history --help'`);
|
|
387
|
+
switch (sub) {
|
|
388
|
+
case "set": return runRemoteSet(cli, rest);
|
|
389
|
+
case "unset": return runRemoteUnset(cli, rest);
|
|
390
|
+
case "status": return runRemoteStatus(cli, rest);
|
|
391
|
+
case "resolve": return runRemoteResolve(cli, rest);
|
|
392
|
+
case "reconcile": return runRemoteReconcile(cli, rest);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function runRemoteSet(cli, args) {
|
|
396
|
+
const { values } = parseArgs({ args, allowPositionals: false, options: { url: { type: "string" }, name: { type: "string" }, branch: { type: "string" }, push: { type: "string" }, fetch: { type: "string" } } });
|
|
397
|
+
if (values.url === undefined)
|
|
398
|
+
throw new CliUsageError("history remote set requires --url <url>");
|
|
399
|
+
if (values.push !== undefined && !PUSH_POLICIES.includes(values.push))
|
|
400
|
+
throw new CliUsageError(`--push must be one of ${PUSH_POLICIES.join("|")} (got ${JSON.stringify(values.push)})`);
|
|
401
|
+
if (values.fetch !== undefined && !FETCH_POLICIES.includes(values.fetch))
|
|
402
|
+
throw new CliUsageError(`--fetch must be one of ${FETCH_POLICIES.join("|")} (got ${JSON.stringify(values.fetch)})`);
|
|
403
|
+
const ctx = gitContext(cli, "remote set");
|
|
404
|
+
let config;
|
|
405
|
+
try {
|
|
406
|
+
config = withWriterLock(ctx.ws.omgbaseDir, () => ctx.provider.remoteSet(ctx.repoId, providerConfigOf(ctx.row), { url: values.url, ...(values.name !== undefined ? { name: values.name } : {}), ...(values.branch !== undefined ? { branch: values.branch } : {}), ...(values.push !== undefined ? { push: values.push } : {}), ...(values.fetch !== undefined ? { fetch: values.fetch } : {}) }));
|
|
407
|
+
}
|
|
408
|
+
catch (e) {
|
|
409
|
+
// The binding's own refusals (credentials in the URL, a bad name, branch or policy) are usage errors.
|
|
410
|
+
if (e instanceof Error && !(e instanceof EngineError) && /credentials|not a remote name|not a branch name|--push|--fetch|--url/.test(e.message))
|
|
411
|
+
throw new CliUsageError(e.message);
|
|
412
|
+
throw e;
|
|
413
|
+
}
|
|
414
|
+
const r = config.remote;
|
|
415
|
+
if (cli.flags.mode !== "human")
|
|
416
|
+
return emitMachine(cli, { remote: r });
|
|
417
|
+
cli.io.err(cli.style.dim(` remote ${r.name} → ${r.url} (branch ${r.branch}, push ${r.push}, fetch ${r.fetch})`));
|
|
418
|
+
return EXIT_OK;
|
|
419
|
+
}
|
|
420
|
+
function runRemoteUnset(cli, args) {
|
|
421
|
+
parseArgs({ args, allowPositionals: false, options: {} });
|
|
422
|
+
const ctx = gitContext(cli, "remote unset");
|
|
423
|
+
const had = ctx.row.config.remote?.name ?? null;
|
|
424
|
+
withWriterLock(ctx.ws.omgbaseDir, () => ctx.provider.remoteUnset(ctx.repoId, providerConfigOf(ctx.row)));
|
|
425
|
+
if (cli.flags.mode !== "human")
|
|
426
|
+
return emitMachine(cli, { remote: null });
|
|
427
|
+
cli.io.err(cli.style.dim(had === null ? " no remote was bound" : ` remote ${had} unbound`));
|
|
428
|
+
return EXIT_OK;
|
|
429
|
+
}
|
|
430
|
+
function renderRemoteStatus(cli, s) {
|
|
431
|
+
if (cli.flags.mode !== "human")
|
|
432
|
+
return emitMachine(cli, s);
|
|
433
|
+
const { io, style } = cli;
|
|
434
|
+
const rows = [
|
|
435
|
+
[style.dim("remote"), `${s.remote}/${s.branch}`],
|
|
436
|
+
[style.dim("state"), s.state],
|
|
437
|
+
[style.dim("local tip"), s.local_tip ?? "—"],
|
|
438
|
+
[style.dim("remote tip"), s.remote_tip ?? "—"],
|
|
439
|
+
[style.dim("pushed through"), s.pushed_through === null ? "—" : `seq ${s.pushed_through}`],
|
|
440
|
+
];
|
|
441
|
+
for (const line of columns(rows, [{}, {}]))
|
|
442
|
+
io.out(` ${line}`);
|
|
443
|
+
return EXIT_OK;
|
|
444
|
+
}
|
|
445
|
+
function runRemoteStatus(cli, args) {
|
|
446
|
+
parseArgs({ args, allowPositionals: false, options: {} });
|
|
447
|
+
const ctx = gitContext(cli, "remote status");
|
|
448
|
+
if (!ctx.row.config.remote)
|
|
449
|
+
throw new CliUsageError("no remote is bound to this repository's git history", `bind one with '${cli.prog} history remote set --url <url>'`);
|
|
450
|
+
return renderRemoteStatus(cli, ctx.provider.remoteStatus(ctx.repoId, providerConfigOf(ctx.row)));
|
|
451
|
+
}
|
|
452
|
+
function runPush(cli, args) {
|
|
453
|
+
parseArgs({ args, allowPositionals: false, options: {} });
|
|
454
|
+
const ctx = gitContext(cli, "push");
|
|
455
|
+
if (!ctx.row.config.remote)
|
|
456
|
+
throw new CliUsageError("no remote is bound to this repository's git history", `bind one with '${cli.prog} history remote set --url <url>'`);
|
|
457
|
+
let r;
|
|
458
|
+
try {
|
|
459
|
+
r = withWriterLock(ctx.ws.omgbaseDir, () => ctx.provider.push(ctx.repoId, providerConfigOf(ctx.row)));
|
|
460
|
+
}
|
|
461
|
+
catch (e) {
|
|
462
|
+
remoteError(e);
|
|
463
|
+
}
|
|
464
|
+
if (cli.flags.mode !== "human")
|
|
465
|
+
return emitMachine(cli, r);
|
|
466
|
+
const remote = ctx.row.config.remote;
|
|
467
|
+
const ids = r.ids === "rejected" ? " (the ids ref was rejected: the remote's identity record is another engine's)" : "";
|
|
468
|
+
cli.io.err(cli.style.dim(r.pushed ? ` pushed ${r.tip} to ${remote.name}/${remote.branch}${r.pushed_through === null ? "" : ` (through seq ${r.pushed_through})`}${ids}` : ` nothing to push (${remote.name}/${remote.branch} at ${r.tip ?? "none"})${ids}`));
|
|
469
|
+
return EXIT_OK;
|
|
470
|
+
}
|
|
471
|
+
function renderFetch(cli, remote, f) {
|
|
472
|
+
if (f.state === "diverged") {
|
|
473
|
+
// Detect, stop, alert (spec/store §5.12 "Divergence"): the verb itself fails so the operator sees it; the alert line follows.
|
|
474
|
+
throw new EngineErrorLike("git_remote_diverged", rewrittenMessage(remote.name, remote.branch, f.remote_tip, f.old_remote_tip ?? null), { data: { remote: remote.name, branch: remote.branch, local_tip: f.tip, remote_tip: f.remote_tip, old_remote_tip: f.old_remote_tip ?? null }, retriable: true });
|
|
475
|
+
}
|
|
476
|
+
if (cli.flags.mode !== "human")
|
|
477
|
+
return emitMachine(cli, f);
|
|
478
|
+
const { io, style } = cli;
|
|
479
|
+
switch (f.state) {
|
|
480
|
+
case "nothing":
|
|
481
|
+
io.err(style.dim(` nothing to fetch from ${remote.name}/${remote.branch}${f.remote_tip ? ` (at ${f.remote_tip})` : ""}`));
|
|
482
|
+
break;
|
|
483
|
+
case "fast-forward":
|
|
484
|
+
io.err(style.dim(` fast-forwarded to ${f.tip}: ${plural(f.applied.length, "file")} written into the vault`));
|
|
485
|
+
break;
|
|
486
|
+
case "merged":
|
|
487
|
+
io.err(style.dim(` merged ${remote.name}/${remote.branch} at ${f.remote_tip}: ${plural(f.applied.length, "file")} written into the vault, merge commit ${f.tip}`));
|
|
488
|
+
break;
|
|
489
|
+
case "conflict":
|
|
490
|
+
io.err(style.dim(` ${plural(f.conflicts.length, "conflict")}: ${f.conflicts.join(", ")} — the remote's versions are beside yours; settle each with ${cli.prog} history remote resolve <path> --ours|--theirs`));
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
return EXIT_OK;
|
|
494
|
+
}
|
|
495
|
+
function runFetch(cli, args) {
|
|
496
|
+
parseArgs({ args, allowPositionals: false, options: {} });
|
|
497
|
+
const ctx = gitContext(cli, "fetch");
|
|
498
|
+
if (!ctx.row.config.remote)
|
|
499
|
+
throw new CliUsageError("no remote is bound to this repository's git history", `bind one with '${cli.prog} history remote set --url <url>'`);
|
|
500
|
+
const vault = vaultOf(cli, ctx, "fetch");
|
|
501
|
+
let f;
|
|
502
|
+
try {
|
|
503
|
+
f = withWriterLock(ctx.ws.omgbaseDir, () => ctx.provider.fetch(ctx.repoId, providerConfigOf(ctx.row), vault));
|
|
504
|
+
}
|
|
505
|
+
catch (e) {
|
|
506
|
+
remoteError(e);
|
|
507
|
+
}
|
|
508
|
+
return renderFetch(cli, ctx.row.config.remote, f);
|
|
509
|
+
}
|
|
510
|
+
function runRemoteResolve(cli, args) {
|
|
511
|
+
const { values, positionals } = parseArgs({ args, allowPositionals: true, options: { ours: { type: "boolean" }, theirs: { type: "boolean" } } });
|
|
512
|
+
const path = positionals[0];
|
|
513
|
+
if (!path)
|
|
514
|
+
throw new CliUsageError("history remote resolve requires a <path>");
|
|
515
|
+
if (Boolean(values.ours) === Boolean(values.theirs))
|
|
516
|
+
throw new CliUsageError("history remote resolve takes exactly one of --ours or --theirs");
|
|
517
|
+
const ctx = gitContext(cli, "remote resolve");
|
|
518
|
+
const vault = vaultOf(cli, ctx, "remote resolve");
|
|
519
|
+
const storage = path.replace(/^\/+/, "");
|
|
520
|
+
let r;
|
|
521
|
+
try {
|
|
522
|
+
r = withWriterLock(ctx.ws.omgbaseDir, () => ctx.provider.resolve(ctx.repoId, providerConfigOf(ctx.row), vault, storage, values.ours ? "ours" : "theirs"));
|
|
523
|
+
}
|
|
524
|
+
catch (e) {
|
|
525
|
+
if (e instanceof Error && !(e instanceof EngineError) && /no merge conflict is pending|is not a pending conflict/.test(e.message))
|
|
526
|
+
throw new CliUsageError(e.message);
|
|
527
|
+
remoteError(e);
|
|
528
|
+
}
|
|
529
|
+
if (cli.flags.mode !== "human")
|
|
530
|
+
return emitMachine(cli, r);
|
|
531
|
+
cli.io.err(cli.style.dim(` resolved ${r.path} (--${r.choice}); ${plural(r.remaining.length, "conflict")} remaining${r.merged ? `; merge commit ${r.merged}` : ""}`));
|
|
532
|
+
return EXIT_OK;
|
|
533
|
+
}
|
|
534
|
+
function renderReconcile(cli, r) {
|
|
535
|
+
if (cli.flags.mode !== "human")
|
|
536
|
+
return emitMachine(cli, r);
|
|
537
|
+
const { io, style } = cli;
|
|
538
|
+
const conflicts = r.conflicts.length > 0 ? `; ${plural(r.conflicts.length, "conflict")}: ${r.conflicts.join(", ")} (settle each with ${cli.prog} history remote resolve <path> --ours|--theirs)` : "";
|
|
539
|
+
const applied = `${plural(r.applied.length, "file")} written into the vault`;
|
|
540
|
+
switch (r.mode) {
|
|
541
|
+
case "rebase":
|
|
542
|
+
io.err(style.dim(` rebased ${plural(r.rewritten, "commit")} onto the remote's head, tip ${r.tip}; the previous tip is kept on refs/omgbase/backup; ${applied}${conflicts}`));
|
|
543
|
+
break;
|
|
544
|
+
case "merge":
|
|
545
|
+
io.err(style.dim(r.rewritten === 1 ? ` merged the remote's head: merge commit ${r.tip}; the previous tip is kept on refs/omgbase/backup; ${applied}` : ` merge pending: ${applied}${conflicts}`));
|
|
546
|
+
break;
|
|
547
|
+
case "adopt":
|
|
548
|
+
io.err(style.dim(` adopted the remote's head ${r.tip}; the previous tip is kept on refs/omgbase/backup; ${applied}${conflicts}`));
|
|
549
|
+
break;
|
|
550
|
+
case "fork":
|
|
551
|
+
io.err(style.dim(` pushed the engine's branch as ${r.fork}; merge it with git, then run ${cli.prog} history remote reconcile --adopt`));
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
return EXIT_OK;
|
|
555
|
+
}
|
|
556
|
+
function runRemoteReconcile(cli, args) {
|
|
557
|
+
const { values } = parseArgs({ args, allowPositionals: false, options: { rebase: { type: "boolean" }, merge: { type: "boolean" }, adopt: { type: "boolean" }, fork: { type: "boolean" } } });
|
|
558
|
+
const modes = RECONCILE_MODES.filter((m) => values[m]);
|
|
559
|
+
if (modes.length !== 1)
|
|
560
|
+
throw new CliUsageError(`history remote reconcile takes exactly one of ${RECONCILE_MODES.map((m) => `--${m}`).join(" | ")}`);
|
|
561
|
+
const ctx = gitContext(cli, "remote reconcile");
|
|
562
|
+
const vault = vaultOf(cli, ctx, "remote reconcile");
|
|
563
|
+
let r;
|
|
564
|
+
try {
|
|
565
|
+
r = withWriterLock(ctx.ws.omgbaseDir, () => ctx.provider.reconcile(ctx.repoId, providerConfigOf(ctx.row), vault, modes[0]));
|
|
566
|
+
}
|
|
567
|
+
catch (e) {
|
|
568
|
+
if (e instanceof Error && !(e instanceof EngineError) && /is not diverged|no remote is bound/.test(e.message))
|
|
569
|
+
throw new CliUsageError(e.message);
|
|
570
|
+
remoteError(e);
|
|
571
|
+
}
|
|
572
|
+
return renderReconcile(cli, r);
|
|
573
|
+
}
|
|
289
574
|
function runEnable(cli, args) {
|
|
290
|
-
const { values } = parseArgs({ args, allowPositionals: false, options: { "best-effort": { type: "boolean" } } });
|
|
575
|
+
const { values } = parseArgs({ args, allowPositionals: false, options: { "best-effort": { type: "boolean" }, provider: { type: "string" }, branch: { type: "string" }, cadence: { type: "string" } } });
|
|
576
|
+
const provider = values.provider ?? NATIVE_PROVIDER;
|
|
577
|
+
if (!PROVIDERS.includes(provider))
|
|
578
|
+
throw new CliUsageError(`--provider must be one of ${PROVIDERS.join("|")} (got ${JSON.stringify(provider)})`);
|
|
579
|
+
if (provider === NATIVE_PROVIDER && (values.branch !== undefined || values.cadence !== undefined))
|
|
580
|
+
throw new CliUsageError("--branch and --cadence go with --provider git");
|
|
581
|
+
const providerOptions = {};
|
|
582
|
+
if (values.branch !== undefined)
|
|
583
|
+
providerOptions.branch = values.branch;
|
|
584
|
+
if (values.cadence !== undefined) {
|
|
585
|
+
try {
|
|
586
|
+
providerOptions.cadence = parseCadence(values.cadence);
|
|
587
|
+
}
|
|
588
|
+
catch (e) {
|
|
589
|
+
throw new CliUsageError(e.message);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
291
592
|
const ws = cli.workspace();
|
|
292
593
|
const repo = cli.repo(ws);
|
|
293
594
|
// A write (the baseline commit): under the workspace writer lock like every other.
|
|
294
|
-
|
|
295
|
-
|
|
595
|
+
try {
|
|
596
|
+
const result = withWriterLock(ws.omgbaseDir, () => enableHistory(ws.store.db, repo.repoId, { writerRequires: !values["best-effort"], ...(provider !== NATIVE_PROVIDER ? { provider, providerOptions } : {}) }));
|
|
597
|
+
return renderLifecycle(cli, "enable", result);
|
|
598
|
+
}
|
|
599
|
+
catch (e) {
|
|
600
|
+
// spec/store §5.12: one provider per repository — the switch is `disable` then `enable --provider`.
|
|
601
|
+
if (e instanceof HistoryProviderConflict)
|
|
602
|
+
throw new CliUsageError(e.message);
|
|
603
|
+
throw e;
|
|
604
|
+
}
|
|
296
605
|
}
|
|
297
606
|
function runDisable(cli, args) {
|
|
298
607
|
parseArgs({ args, allowPositionals: false, options: {} });
|
|
@@ -312,6 +621,10 @@ function runHistory(cli, args) {
|
|
|
312
621
|
case "enable": return runEnable(cli, rest);
|
|
313
622
|
case "disable": return runDisable(cli, rest);
|
|
314
623
|
case "purge": return runPurge(cli, rest);
|
|
624
|
+
case "checkpoint": return runCheckpoint(cli, rest);
|
|
625
|
+
case "push": return runPush(cli, rest);
|
|
626
|
+
case "fetch": return runFetch(cli, rest);
|
|
627
|
+
case "remote": return runRemote(cli, rest);
|
|
315
628
|
case "plan": return runPlan(cli, rest);
|
|
316
629
|
case "export": return runExport(cli, rest);
|
|
317
630
|
case "attach": return runAttach(cli, rest);
|
|
@@ -319,5 +632,5 @@ function runHistory(cli, args) {
|
|
|
319
632
|
case "list": return runList(cli, rest);
|
|
320
633
|
}
|
|
321
634
|
}
|
|
322
|
-
export const cmdHistory = { name: "history", summary: "Capture: enable/disable/purge per repository; retention: plan/export archive segments, attach/detach/list", run: (cli, a) => runHistory(cli, a) };
|
|
635
|
+
export const cmdHistory = { name: "history", summary: "Capture: enable/disable/purge per repository (native or git provider), checkpoint, remote set/status, push/fetch, resolve/reconcile; retention: plan/export archive segments, attach/detach/list", run: (cli, a) => runHistory(cli, a) };
|
|
323
636
|
//# sourceMappingURL=history.js.map
|
package/dist/src/cmd/mcp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import { serveStdio, Watcher, WatchLease, watchLeaseLive, freshnessSweep, withWriterLock, awaitReady, WATCH_READY_PATIENCE_MS, EmbedDrainer, EngineError, resolveSettings, parseToolsProfile, TOOLS_PROFILES, } from "@omgbase/core";
|
|
2
|
+
import { serveStdio, Watcher, WatchLease, watchLeaseLive, freshnessSweep, withWriterLock, awaitReady, WATCH_READY_PATIENCE_MS, EmbedDrainer, EngineError, resolveSettings, parseToolsProfile, TOOLS_PROFILES, readCapability, historyProviderOf, NATIVE_PROVIDER, runProviderCheckpoint, } from "@omgbase/core";
|
|
3
|
+
import { createCheckpointScheduler, readGitConfig } from "@omgbase/git-adapter";
|
|
3
4
|
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
4
5
|
import { installSpecSeams, WRITER_LOCK_TIMEOUT_ENV } from "../seams.js";
|
|
5
6
|
import { loadEmbedding } from "./_embed.js";
|
|
@@ -124,6 +125,26 @@ async function runMcp(cli, args) {
|
|
|
124
125
|
onError: (err) => cli.io.err(cli.style.dim(`[mcp] embed drain failed: ${String(err)}`)),
|
|
125
126
|
})
|
|
126
127
|
: null;
|
|
128
|
+
// The git history provider's cadence (spec/store §5.12 "Cadence", 17.6 —
|
|
129
|
+
// Phase 6b): the mutation hook below also runs the checkpoint worker — under
|
|
130
|
+
// `every` synchronously after each successful write (the git history is
|
|
131
|
+
// complete without `omg sync`), under `{ debounce_ms }` once, `debounce_ms`
|
|
132
|
+
// after the last mutation (a burst folds into one commit). The worker runs
|
|
133
|
+
// under the workspace writer lock (re-entrant when the hook fires inside a
|
|
134
|
+
// locked tool call); the watcher's checkpoints notify it too.
|
|
135
|
+
const historyRow = readCapability(ws.store.db, repo.repoId, "history");
|
|
136
|
+
const gitHistory = historyRow !== null && historyRow.state === "enabled" && historyProviderOf(historyRow) !== NATIVE_PROVIDER;
|
|
137
|
+
const scheduler = gitHistory
|
|
138
|
+
? createCheckpointScheduler({
|
|
139
|
+
cadence: readGitConfig({ ...historyRow.config, provider: historyProviderOf(historyRow) }).cadence,
|
|
140
|
+
run: () => { withWriterLock(ws.omgbaseDir, () => runProviderCheckpoint(ws.store, repo.repoId)); },
|
|
141
|
+
onError: (err) => cli.io.err(cli.style.dim(`[mcp] git checkpoint failed: ${String(err)}`)),
|
|
142
|
+
})
|
|
143
|
+
: null;
|
|
144
|
+
if (scheduler) {
|
|
145
|
+
const cadence = readGitConfig({ ...historyRow.config, provider: historyProviderOf(historyRow) }).cadence;
|
|
146
|
+
cli.io.err(cli.style.dim(`[mcp] git checkpoint on mutation: ${cadence === "every" ? "every write" : `${cadence.debounce_ms} ms after the last write`}`));
|
|
147
|
+
}
|
|
127
148
|
// Decide whether to run the in-process watcher. Off if --no-watch, or if a
|
|
128
149
|
// live watcher already holds the lease (another `omg watch`/`omg mcp`).
|
|
129
150
|
const leaseHeld = watchLeaseLive(ws.omgbaseDir);
|
|
@@ -148,8 +169,9 @@ async function runMcp(cli, args) {
|
|
|
148
169
|
onCheckpoint: (r) => {
|
|
149
170
|
if (r.ingested.length > 0 || r.deleted.length > 0) {
|
|
150
171
|
cli.io.err(cli.style.dim(`[watch] batch: +${r.ingested.length} -${r.deleted.length}`));
|
|
151
|
-
// A file change the watcher ingested may also need (re-)embedding.
|
|
172
|
+
// A file change the watcher ingested may also need (re-)embedding — and a git checkpoint.
|
|
152
173
|
drainer?.schedule();
|
|
174
|
+
scheduler?.notify();
|
|
153
175
|
}
|
|
154
176
|
},
|
|
155
177
|
onError: (err) => cli.io.err(cli.style.dim(`[watch] error: ${String(err)}`)),
|
|
@@ -203,7 +225,7 @@ async function runMcp(cli, args) {
|
|
|
203
225
|
repoId: repo.repoId,
|
|
204
226
|
omgbaseDir: ws.omgbaseDir,
|
|
205
227
|
...(repo.rootPath ? { rootPath: repo.rootPath } : {}),
|
|
206
|
-
...(drainer ? { onMutation: () => drainer
|
|
228
|
+
...(drainer || scheduler ? { onMutation: () => { drainer?.schedule(); scheduler?.notify(); } } : {}),
|
|
207
229
|
...(embedQuery ? { embedQuery } : {}),
|
|
208
230
|
...(loaded ? { embedProvider: loaded.providerName } : {}),
|
|
209
231
|
...(semanticUnavailable ? { semanticUnavailable } : {}),
|
|
@@ -230,6 +252,11 @@ async function runMcp(cli, args) {
|
|
|
230
252
|
catch { /* reported via onError */ }
|
|
231
253
|
await drainer.close();
|
|
232
254
|
}
|
|
255
|
+
// A debounced git checkpoint still pending runs now (its error is reported, never thrown).
|
|
256
|
+
if (scheduler) {
|
|
257
|
+
scheduler.flush();
|
|
258
|
+
scheduler.close();
|
|
259
|
+
}
|
|
233
260
|
if (embedding)
|
|
234
261
|
await embedding.close();
|
|
235
262
|
await handle.close();
|
package/dist/src/cmd/show.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import { resolveRef, nodesGet, docsRead, docLinks,
|
|
2
|
+
import { resolveRef, nodesGet, docsRead, docLinks, historyReaderIfAvailable, docPropertiesMerged } from "@omgbase/core";
|
|
3
3
|
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
import { expandBlockArgs } from "./_mutate.js";
|
|
5
5
|
function showOne(cli, ref, include) {
|
|
@@ -15,20 +15,24 @@ function showOne(cli, ref, include) {
|
|
|
15
15
|
if (!res)
|
|
16
16
|
throw new EngineErrorLike("doc_missing", `no document ${ref}`);
|
|
17
17
|
const links = docLinks(ws.store, resolved.docId, { direction: "both" });
|
|
18
|
-
// The authored keys, merged (frontmatter + inline — the `_data` view)
|
|
19
|
-
// computed rows
|
|
20
|
-
// spec/
|
|
21
|
-
//
|
|
22
|
-
|
|
18
|
+
// The authored keys, merged (frontmatter + inline — the `_data` view), then
|
|
19
|
+
// the computed rows under their own heading from `properties.computed`
|
|
20
|
+
// (`_title`/`_tags`, `spec/properties` 2.0 — the store's own keys now).
|
|
21
|
+
// The merged bag carries every source, so the computed keys are subtracted
|
|
22
|
+
// from it BY NAME rather than by sigil: through properties 1.2 a
|
|
23
|
+
// `!k.startsWith("$")` filter did this, and the 2.0 rename silently
|
|
24
|
+
// defeated it — `_title` appeared in both sections (caught by spec/cli).
|
|
23
25
|
const computed = res.properties.computed ?? {};
|
|
26
|
+
const properties = Object.fromEntries(Object.entries(docPropertiesMerged(ws.store.db, resolved.docId)).filter(([k]) => !(k in computed)));
|
|
24
27
|
const payload = { kind: "document", ...res };
|
|
25
28
|
return { payload, render: () => renderDocCard(cli, res._path, properties, computed, links) };
|
|
26
29
|
}
|
|
27
30
|
const node = nodesGet(ws.store, resolved.docId, resolved.blockId, { resolution: "full" });
|
|
28
31
|
if (!node)
|
|
29
32
|
throw new EngineErrorLike("block_missing", `no block ${ref}`);
|
|
30
|
-
// `--include history` on a store with nothing captured (Phase 4a): the card simply carries no history
|
|
31
|
-
|
|
33
|
+
// `--include history` on a store with nothing captured (Phase 4a): the card simply carries no history; on a
|
|
34
|
+
// git-provider repository (Phase 6a) there is no block lineage to show — the same empty list.
|
|
35
|
+
const history = include.includes("history") ? blockHistoryOrNone(ws.store.db, repo.repoId, resolved.blockId) : undefined;
|
|
32
36
|
return { payload: { ...node, ...(history ? { history } : {}) }, render: () => renderBlockCard(cli, node, history) };
|
|
33
37
|
}
|
|
34
38
|
function runShow(cli, args) {
|
|
@@ -117,5 +121,14 @@ function renderBlockCard(cli, node, history) {
|
|
|
117
121
|
function fmt(v) {
|
|
118
122
|
return typeof v === "string" ? v : JSON.stringify(v);
|
|
119
123
|
}
|
|
124
|
+
/** A block's last five changes through the repository's reader, or `[]` when the reader has none or does not record block lineage. */
|
|
125
|
+
function blockHistoryOrNone(db, repoId, blockId) {
|
|
126
|
+
try {
|
|
127
|
+
return historyReaderIfAvailable(db, repoId)?.blocksHistory(blockId, { limit: 5 }) ?? [];
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
120
133
|
export const cmdShow = { name: "show", summary: "Metadata card for a node", run: (cli, a) => runShow(cli, a) };
|
|
121
134
|
//# sourceMappingURL=show.js.map
|
package/dist/src/seams.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { pinClock, sequentialMinter, setIdMinter, setWriterLockDefaults } from "@omgbase/core";
|
|
1
|
+
import { pinClock, registerHistoryProvider, sequentialMinter, setIdMinter, setWriterLockDefaults } from "@omgbase/core";
|
|
2
|
+
import { DEFAULT_IDENTITY, FIXTURE_IDENTITY, gitHistoryProvider } from "@omgbase/git-adapter";
|
|
2
3
|
import { CliUsageError } from "./output.js";
|
|
3
4
|
// The two conformance seams of spec/surface §7.1 (and spec/cli §2.6), read
|
|
4
5
|
// from the environment at process start — BEFORE any workspace opens, so no id
|
|
@@ -63,6 +64,12 @@ export function installSpecSeams(env = process.env) {
|
|
|
63
64
|
setWriterLockDefaults({ timeoutMs: ms });
|
|
64
65
|
seams.writerLockTimeoutMs = ms;
|
|
65
66
|
}
|
|
67
|
+
// The git history provider (modular-core Phase 6a; spec/store §5.12): the
|
|
68
|
+
// composition layer attaches it here, before any store opens — this binary
|
|
69
|
+
// is the host. Under either conformance seam its commits carry the fixture
|
|
70
|
+
// identity `omgbase <omgbase@fixture>` (spec/store §9.4), so the shas the
|
|
71
|
+
// fixtures pin follow from the inputs alone.
|
|
72
|
+
registerHistoryProvider("git", gitHistoryProvider({ identity: seams.minter || seams.clock !== null ? FIXTURE_IDENTITY : DEFAULT_IDENTITY }));
|
|
66
73
|
installed = seams;
|
|
67
74
|
return seams;
|
|
68
75
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omgbase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.1",
|
|
4
4
|
"description": "Open Markdown Graph Base — CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -45,9 +45,10 @@
|
|
|
45
45
|
"omg": "./dist/src/main.js"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@omgbase/core": "^0.
|
|
48
|
+
"@omgbase/core": "^0.34.0",
|
|
49
49
|
"@omgbase/fs-adapter": "^0.2.0",
|
|
50
|
-
"@omgbase/
|
|
50
|
+
"@omgbase/git-adapter": "^0.1.1",
|
|
51
|
+
"@omgbase/sync": "^0.7.4",
|
|
51
52
|
"yaml": "^2.9.0"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|