@cavelang/cli 0.15.0 → 0.20.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/README.md +7 -3
- package/dist/src/cli.d.ts +26 -3
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +338 -12
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/main.js +6 -1
- package/dist/src/main.js.map +1 -1
- package/package.json +16 -15
- package/src/cli.ts +346 -12
- package/src/index.ts +2 -2
- package/src/main.ts +5 -1
package/src/cli.ts
CHANGED
|
@@ -8,11 +8,14 @@
|
|
|
8
8
|
* - `cave parse [file]` — lint / dump the AST (`--json`)
|
|
9
9
|
* - `cave highlight [file]` — ANSI syntax colors (async, routed in `main.ts`)
|
|
10
10
|
* - `cave add [--db <path>] [file…]` — ingest into a store (`--strict`, `--check`)
|
|
11
|
-
* - `cave query [--db <path>] '<pattern>'` — CAVE-Q (`--json`, `--all`, `--aliases`, `--as-of`)
|
|
11
|
+
* - `cave query [--db <path>] '<pattern>'` — CAVE-Q (`--json`, `--all`, `--aliases`, `--as-of`, `--resolve`)
|
|
12
|
+
* - `cave resolve [--db <path>]` — contested facts under the §26 policy (`--aliases`, `--policy`, `--json`)
|
|
12
13
|
* - `cave derive [--db <path>] [rules.cave…]` — fire rules (`--dry-run`, `--full`, `--list`, `--retract`)
|
|
13
14
|
* - `cave act [--db <path>] <name> [param=value…]` — execute an action (spec §25; `--declare`, `--list`, `--retract`)
|
|
14
15
|
* - `cave check [--db <path>]` — knowledge health report (`--stale`, `--json`)
|
|
15
|
-
* - `cave
|
|
16
|
+
* - `cave suggest-alias [--db <path>]` — same-entity candidates (spec §27; async, routed in `main.ts`)
|
|
17
|
+
* - `cave sync [--db <path>] <source>` — merge another store by row identity (spec §28; `--dry-run`, `--as`, `--into`)
|
|
18
|
+
* - `cave export [--db <path>]` — canonical text out (`--current`, `--tx`)
|
|
16
19
|
* - `cave connect [--db <path>] <source>` — deterministic structured ingestion (async, routed in `main.ts`)
|
|
17
20
|
* - `cave eval <suite...>` — golden-fixture extraction/query/reconstruction evals (async, routed in `main.ts`)
|
|
18
21
|
* - `cave reconstruct [--db <path>] <seed...>` — §18 reconstruction from seed cues (async, routed in `main.ts`)
|
|
@@ -31,7 +34,10 @@ import { parseDocument } from '@cavelang/parser'
|
|
|
31
34
|
import { Registry, standardRegistry } from '@cavelang/canonical'
|
|
32
35
|
import { defaultDbPath, open } from '@cavelang/store'
|
|
33
36
|
import { query as caveQuery } from '@cavelang/query'
|
|
34
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
check as caveCheck, defaultMinScore, defaultStaleDays, gatedIngest, judgePrompt, parseJudgeReply,
|
|
39
|
+
suggestAliases, writeSuggestions
|
|
40
|
+
} from '@cavelang/shape'
|
|
35
41
|
import type { Report, Violation } from '@cavelang/shape'
|
|
36
42
|
import { declareRules, defaultMaxPasses, defaultMinConf, derive, listRules, retractRule } from '@cavelang/rules'
|
|
37
43
|
import type { Declaration, DeriveReport } from '@cavelang/rules'
|
|
@@ -40,7 +46,9 @@ import type { ActReport } from '@cavelang/act'
|
|
|
40
46
|
import {
|
|
41
47
|
Demo, heuristicPolicy, llmPolicy, reconstruct, reconstructAsync, shellComplete, sqliteStore
|
|
42
48
|
} from '@cavelang/loop'
|
|
43
|
-
import {
|
|
49
|
+
import { labelOf, sanitizeLabel, syncFile, syncText } from '@cavelang/sync'
|
|
50
|
+
import type { SyncReport } from '@cavelang/sync'
|
|
51
|
+
import { emitClaim, txOfLine } from '@cavelang/canonical'
|
|
44
52
|
|
|
45
53
|
export type Output = {
|
|
46
54
|
readonly code: number
|
|
@@ -61,12 +69,15 @@ Usage:
|
|
|
61
69
|
cave highlight [file...] print CAVE text with ANSI syntax colors
|
|
62
70
|
cave add [--db <path>] [file...] ingest into a store [--strict] [--check] [--no-prelude] [--no-src]
|
|
63
71
|
cave import [--db <path>] [file...] restore/merge from CAVE text (add without @src: stamping)
|
|
64
|
-
cave query [--db <path>] <pattern> run a CAVE-Q pattern [--json] [--all] [--aliases] [--as-of <t>] [--no-prelude]
|
|
72
|
+
cave query [--db <path>] <pattern> run a CAVE-Q pattern [--json] [--all] [--aliases] [--as-of <t>] [--resolve] [--no-prelude]
|
|
73
|
+
cave resolve [--db <path>] contested facts + winners (spec §26) [--aliases] [--policy] [--json]
|
|
65
74
|
cave derive [--db <path>] [rules.cave..] declare + fire rules (spec §24) [--dry-run] [--full] [--list] [--retract <rule>]
|
|
66
75
|
cave act [--db <path>] <name> [p=v...] execute an action (spec §25) [--dry-run] [--no-check] [--hooks <file>]
|
|
67
76
|
cave act --declare [file...] declare actions from a CAVE document; --list / --retract <name> manage them
|
|
68
77
|
cave check [--db <path>] knowledge health report (spec §20) [--stale <days>] [--json]
|
|
69
|
-
cave
|
|
78
|
+
cave suggest-alias [--db <path>] propose same-entity ALIAS candidates (spec §27) [--min <s>] [--agent] [--write]
|
|
79
|
+
cave sync [--db <path>] <source> merge another store by row identity (spec §28) [--dry-run] [--as] [--into]
|
|
80
|
+
cave export [--db <path>] [--out <file>] emit canonical CAVE text [--current] [--tx] [--no-prelude]
|
|
70
81
|
cave mcp [--db <path>] serve the engine as an MCP server on stdio [--no-prelude]
|
|
71
82
|
cave ingest [--db <path>] <globs/urls..> LLM-driven ingestion of files and web pages
|
|
72
83
|
cave eval <suite..> --agent '<command>' golden-fixture extraction/query/reconstruction evals (items 9, 10)
|
|
@@ -153,7 +164,10 @@ Canonical CAVE text is the interchange format (spec §2.2): importing a
|
|
|
153
164
|
file written by \`cave export\` replays its claims append-only, preserving
|
|
154
165
|
the belief series, qualifier edges and in-band declarations. Unlike
|
|
155
166
|
\`cave add\`, import never stamps actor provenance — replayed claims must
|
|
156
|
-
keep the claim keys they were exported with (spec §9.5).
|
|
167
|
+
keep the claim keys they were exported with (spec §9.5). Import always
|
|
168
|
+
mints fresh transaction ids — ;@ annotations in a \`cave export --tx\`
|
|
169
|
+
file read as ordinary comments; to merge under the recorded row
|
|
170
|
+
identity, use \`cave sync\` (spec §28.4).
|
|
157
171
|
|
|
158
172
|
Examples:
|
|
159
173
|
cave export --db old.db --out backup.cave
|
|
@@ -162,7 +176,7 @@ Examples:
|
|
|
162
176
|
query: `cave query — run a CAVE-Q pattern against a store
|
|
163
177
|
|
|
164
178
|
Usage:
|
|
165
|
-
cave query [--db <path>] <pattern> [WHERE <filter>] [--json] [--all] [--aliases] [--as-of <t>] [--no-prelude]
|
|
179
|
+
cave query [--db <path>] <pattern> [WHERE <filter>] [--json] [--all] [--aliases] [--as-of <t>] [--resolve] [--no-prelude]
|
|
166
180
|
|
|
167
181
|
Options:
|
|
168
182
|
${dbHelp}
|
|
@@ -172,6 +186,11 @@ Options:
|
|
|
172
186
|
--as-of <t> resolve beliefs as of a past moment (spec §12.3): a date
|
|
173
187
|
(whole day included), a timestamp (whole second), or a
|
|
174
188
|
transaction id — rows recorded later are invisible
|
|
189
|
+
--resolve match resolved winners only (spec §26): contested facts —
|
|
190
|
+
same fact from several sources, or opposite polarity —
|
|
191
|
+
collapse to the row the resolution policy picks
|
|
192
|
+
(precedence class, reliability-weighted confidence, tx);
|
|
193
|
+
incompatible with --all
|
|
175
194
|
--no-prelude open the store without the standard verb registry
|
|
176
195
|
|
|
177
196
|
Patterns are claim triples with ?variables and optional metadata filters
|
|
@@ -185,8 +204,40 @@ Examples:
|
|
|
185
204
|
cave query --db k.db 'terrier EXTENDS+ animal'
|
|
186
205
|
cave query --db k.db '?x USES postgres' --aliases
|
|
187
206
|
cave query --db k.db 'server IS compromised' --as-of 2026-01-15
|
|
207
|
+
cave query --db k.db 'service HAS owner: ?who' --resolve
|
|
188
208
|
cave query --db k.db '?x ?verb ?y @production' --json`,
|
|
189
209
|
|
|
210
|
+
resolve: `cave resolve — contested facts and their winners (spec §26)
|
|
211
|
+
|
|
212
|
+
Usage:
|
|
213
|
+
cave resolve [--db <path>] [--aliases] [--policy] [--json] [--no-prelude]
|
|
214
|
+
|
|
215
|
+
Options:
|
|
216
|
+
${dbHelp}
|
|
217
|
+
--aliases widen resolution groups through current ALIAS claims
|
|
218
|
+
(spec §13.6) — aliased names contest one fact
|
|
219
|
+
--policy print the effective resolution policy instead: the
|
|
220
|
+
built-in precedence ladder merged with in-band
|
|
221
|
+
source/<name> HAS precedence: / HAS reliability: claims
|
|
222
|
+
--json emit the report as JSON
|
|
223
|
+
--no-prelude open the store without the standard verb registry
|
|
224
|
+
|
|
225
|
+
Lists every fact more than one belief series currently speaks about —
|
|
226
|
+
same fact from different sources (§9.5 actor stamps, content sources)
|
|
227
|
+
or opposite polarity — with the candidates ranked as the resolution
|
|
228
|
+
policy sees them: precedence class first, reliability-weighted
|
|
229
|
+
confidence second, latest transaction last. The winner is what
|
|
230
|
+
\`cave query --resolve\` matches; the rest are invisible there. Declare
|
|
231
|
+
policy overrides in-band, e.g.:
|
|
232
|
+
|
|
233
|
+
source/scanner-a HAS reliability: 60%
|
|
234
|
+
source/ingest HAS precedence: 1
|
|
235
|
+
|
|
236
|
+
Examples:
|
|
237
|
+
cave resolve --db k.db
|
|
238
|
+
cave resolve --db k.db --aliases
|
|
239
|
+
cave resolve --db k.db --policy`,
|
|
240
|
+
|
|
190
241
|
derive: `cave derive — declare and fire rules (spec §24)
|
|
191
242
|
|
|
192
243
|
Usage:
|
|
@@ -296,21 +347,115 @@ Examples:
|
|
|
296
347
|
cave check --db k.db --stale 30
|
|
297
348
|
cave check --db k.db --json | jq '.violations'`,
|
|
298
349
|
|
|
350
|
+
'suggest-alias': `cave suggest-alias — propose same-entity ALIAS candidates for review (spec §27)
|
|
351
|
+
|
|
352
|
+
Usage:
|
|
353
|
+
cave suggest-alias [--db <path>] [options]
|
|
354
|
+
|
|
355
|
+
Options:
|
|
356
|
+
${dbHelp}
|
|
357
|
+
--min <s> minimum evidence score, 0..1 or N% (default 60%)
|
|
358
|
+
--limit <n> at most n suggestions, strongest first
|
|
359
|
+
--agent <template> LLM judge filtering the candidates — the cave
|
|
360
|
+
ingest/eval shell contract: the prompt (each pair
|
|
361
|
+
with both sides' current claims) is piped to stdin
|
|
362
|
+
and {prompt-file} is substituted; stdout replies
|
|
363
|
+
with a JSON array of confirmed suggestion numbers
|
|
364
|
+
--timeout <seconds> judge timeout (default 120)
|
|
365
|
+
--write append the suggestions, stamped @src:suggest/alias,
|
|
366
|
+
instead of only printing them
|
|
367
|
+
--json print suggestions with scores and signals as JSON
|
|
368
|
+
--no-prelude open the store without the standard verb registry
|
|
369
|
+
|
|
370
|
+
Candidates come from string similarity (case/separator drift, segment
|
|
371
|
+
containment, prefixes, typos) and shared rare attribute values; shared
|
|
372
|
+
relations strengthen a candidate but never create one. Suggested claims
|
|
373
|
+
are questions, not merges: confidence 30–50% puts them in cave check's
|
|
374
|
+
review band, and a pair with any recorded ALIAS history — merged,
|
|
375
|
+
rejected or unmerged — is never suggested again.
|
|
376
|
+
|
|
377
|
+
Output is CAVE text: review it, edit it, pipe it into cave add — or
|
|
378
|
+
--write it and review afterwards. A written suggestion is a positive
|
|
379
|
+
claim, so the opt-in §13.6 closure links it until reviewed:
|
|
380
|
+
|
|
381
|
+
confirm echo 'dupe ALIAS canonical' | cave add --db k.db
|
|
382
|
+
reject echo 'dupe ALIAS canonical @src:suggest/alias @ 0%' | cave add --db k.db
|
|
383
|
+
|
|
384
|
+
(rejection retracts the suggestion's own series — contexts are part of
|
|
385
|
+
claim identity, §9.2, so a plain @ 0% append would start a new series
|
|
386
|
+
and leave the suggested link standing).
|
|
387
|
+
|
|
388
|
+
Examples:
|
|
389
|
+
cave suggest-alias --db k.db
|
|
390
|
+
cave suggest-alias --db k.db --min 80% --limit 10
|
|
391
|
+
cave suggest-alias --db k.db | cave add --db k.db
|
|
392
|
+
cave suggest-alias --db k.db --agent 'claude -p' --write`,
|
|
393
|
+
|
|
394
|
+
sync: `cave sync — merge another store into this one by row identity (spec §28)
|
|
395
|
+
|
|
396
|
+
Usage:
|
|
397
|
+
cave sync [--db <path>] <source> [--as <label>] [--into <label>]
|
|
398
|
+
[--dry-run] [--no-record] [--json] [--no-prelude]
|
|
399
|
+
|
|
400
|
+
Options:
|
|
401
|
+
${dbHelp}
|
|
402
|
+
--as <label> origin label in the merge record (default: the source
|
|
403
|
+
file's basename stem; stdin defaults to "stdin")
|
|
404
|
+
--into <label> target label in the merge record (default: the database
|
|
405
|
+
file's basename stem)
|
|
406
|
+
--dry-run compute the full report inside a rolled-back transaction
|
|
407
|
+
--no-record do not append the store/<from> SYNCED-INTO store/<into>
|
|
408
|
+
merge record
|
|
409
|
+
--json emit the report as JSON
|
|
410
|
+
--no-prelude open the store without the standard verb registry
|
|
411
|
+
|
|
412
|
+
The source is a CAVE store file (recognized by the SQLite header),
|
|
413
|
+
canonical text with ;@ transaction annotations (cave export --tx), or -
|
|
414
|
+
for annotated text on stdin. Rows merge by identity: a row's UUIDv7 is
|
|
415
|
+
its id everywhere, so present rows skip, re-runs merge nothing, and two
|
|
416
|
+
stores syncing each other converge. Contradictions coexist (spec §9.4)
|
|
417
|
+
and resolve at read time (cave resolve, --resolve); everything appended
|
|
418
|
+
after a merge outsorts everything merged, whatever the origin machine's
|
|
419
|
+
clock read (spec §28.2). An effective merge appends one record claim,
|
|
420
|
+
stamped @src:sync — the sync log is its belief series.
|
|
421
|
+
|
|
422
|
+
Branching (spec §28.6): commit the full annotated export — the text is
|
|
423
|
+
the store — and rebuild working stores from it. A checkout is plumbing
|
|
424
|
+
(--no-record); landing reviewed text into a live store is a real merge
|
|
425
|
+
event (let it record). Never hand-merge exports: sync both sides into
|
|
426
|
+
a fresh store and re-export — the union — which git can run for you as
|
|
427
|
+
a merge driver on *.cave files.
|
|
428
|
+
|
|
429
|
+
Examples:
|
|
430
|
+
cave sync --db main.db laptop.db
|
|
431
|
+
cave sync --db main.db laptop.db --dry-run --json
|
|
432
|
+
cave export --db laptop.db --tx | cave sync --db main.db -
|
|
433
|
+
cave sync --db a.db b.db && cave sync --db b.db a.db # converge both
|
|
434
|
+
cave sync --db work.db knowledge.cave --no-record # checkout (§28.6)
|
|
435
|
+
cave sync --db main.db knowledge.cave --as reorg-auth # land the branch`,
|
|
436
|
+
|
|
299
437
|
export: `cave export — emit canonical CAVE text from a store
|
|
300
438
|
|
|
301
439
|
Usage:
|
|
302
|
-
cave export [--db <path>] [--out <file>] [--current] [--no-prelude]
|
|
440
|
+
cave export [--db <path>] [--out <file>] [--current] [--tx] [--no-prelude]
|
|
303
441
|
|
|
304
442
|
Options:
|
|
305
443
|
${dbHelp}
|
|
306
444
|
--out <file> write to a file instead of stdout
|
|
307
445
|
--current current beliefs only (skip superseded rows)
|
|
446
|
+
--tx precede every claim line with its ;@ transaction
|
|
447
|
+
annotation (spec §28.4) — the text carries row identity,
|
|
448
|
+
so cave sync replays it idempotently; other readers see
|
|
449
|
+
ordinary comments
|
|
308
450
|
--no-prelude open the store without the standard verb registry
|
|
309
451
|
|
|
310
452
|
Examples:
|
|
311
453
|
cave export --db k.db
|
|
312
454
|
cave export --db k.db --out backup.cave
|
|
313
|
-
cave export --db k.db --current --out snapshot.cave
|
|
455
|
+
cave export --db k.db --current --out snapshot.cave
|
|
456
|
+
cave export --db k.db --tx | cave sync --db other.db -
|
|
457
|
+
cave export --db work.db --tx --out knowledge.cave # the committed,
|
|
458
|
+
# reviewable store text (spec §28.6)`,
|
|
314
459
|
|
|
315
460
|
reconstruct: `cave reconstruct — active memory reconstruction from seed cues (spec §18)
|
|
316
461
|
|
|
@@ -479,6 +624,7 @@ export const queryCommand = (argv: readonly string[]): Output => {
|
|
|
479
624
|
all: { type: 'boolean' },
|
|
480
625
|
aliases: { type: 'boolean' },
|
|
481
626
|
'as-of': { type: 'string' },
|
|
627
|
+
resolve: { type: 'boolean' },
|
|
482
628
|
'no-prelude': { type: 'boolean' }
|
|
483
629
|
},
|
|
484
630
|
allowPositionals: true
|
|
@@ -492,6 +638,7 @@ export const queryCommand = (argv: readonly string[]): Output => {
|
|
|
492
638
|
const matches = caveQuery(store, pattern, {
|
|
493
639
|
all: values.all === true,
|
|
494
640
|
aliases: values.aliases === true,
|
|
641
|
+
resolve: values.resolve === true,
|
|
495
642
|
...values['as-of'] === undefined ? {} : { asOf: values['as-of'] }
|
|
496
643
|
})
|
|
497
644
|
if (values.json === true) {
|
|
@@ -516,6 +663,60 @@ export const queryCommand = (argv: readonly string[]): Output => {
|
|
|
516
663
|
}
|
|
517
664
|
}
|
|
518
665
|
|
|
666
|
+
/**
|
|
667
|
+
* `cave resolve` — the human view of contradiction resolution (spec §26.4):
|
|
668
|
+
* contested facts with their candidates ranked as the policy sees them,
|
|
669
|
+
* or the effective policy itself under `--policy`.
|
|
670
|
+
*/
|
|
671
|
+
export const resolveCommand = (argv: readonly string[]): Output => {
|
|
672
|
+
const { values } = parseArgs({
|
|
673
|
+
args: [...argv],
|
|
674
|
+
options: {
|
|
675
|
+
db: { type: 'string' },
|
|
676
|
+
aliases: { type: 'boolean' },
|
|
677
|
+
policy: { type: 'boolean' },
|
|
678
|
+
json: { type: 'boolean' },
|
|
679
|
+
'no-prelude': { type: 'boolean' }
|
|
680
|
+
},
|
|
681
|
+
allowPositionals: false
|
|
682
|
+
})
|
|
683
|
+
const percent = (value: number): string => `${Math.round(value * 1000) / 10}%`
|
|
684
|
+
const store = open(values.db ?? defaultDbPath(), values['no-prelude'] === true ? { registry: Registry.empty } : {})
|
|
685
|
+
try {
|
|
686
|
+
if (values.policy === true) {
|
|
687
|
+
const policy = store.resolutionPolicy()
|
|
688
|
+
if (values.json === true) {
|
|
689
|
+
return ok(`${JSON.stringify(policy, undefined, 2)}\n`)
|
|
690
|
+
}
|
|
691
|
+
const width = Math.max(...policy.map(entry => entry.prefix.length + 7))
|
|
692
|
+
const lines = policy.map(entry => {
|
|
693
|
+
const subject = entry.prefix === '' ? 'source' : `source/${entry.prefix}`
|
|
694
|
+
const dimensions = [
|
|
695
|
+
...entry.precedence === undefined ? [] : [`precedence ${entry.precedence}`],
|
|
696
|
+
...entry.reliability === undefined ? [] : [`reliability ${percent(entry.reliability)}`]
|
|
697
|
+
]
|
|
698
|
+
return `${subject.padEnd(width)} ${dimensions.join(', ')}`
|
|
699
|
+
})
|
|
700
|
+
return ok(`${lines.join('\n')}\n`)
|
|
701
|
+
}
|
|
702
|
+
const contested = store.contested({ aliases: values.aliases === true })
|
|
703
|
+
if (values.json === true) {
|
|
704
|
+
return ok(`${JSON.stringify(contested, undefined, 2)}\n`)
|
|
705
|
+
}
|
|
706
|
+
if (contested.length === 0) {
|
|
707
|
+
return ok('no contested facts\n')
|
|
708
|
+
}
|
|
709
|
+
const lines = contested.flatMap(group =>
|
|
710
|
+
group.rows.map((row, at) =>
|
|
711
|
+
`${at === 0 ? '' : ' over '}${row.raw_line} ; class ${row.res_class}, effective ${percent(row.res_conf)}`))
|
|
712
|
+
return ok(`${lines.join('\n')}\n`)
|
|
713
|
+
} catch (error) {
|
|
714
|
+
return fail(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
715
|
+
} finally {
|
|
716
|
+
store.close()
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
519
720
|
export const deriveCommand = (argv: readonly string[]): Output => {
|
|
520
721
|
const { values, positionals } = parseArgs({
|
|
521
722
|
args: [...argv],
|
|
@@ -831,12 +1032,141 @@ export const checkCommand = (argv: readonly string[]): Output => {
|
|
|
831
1032
|
}
|
|
832
1033
|
}
|
|
833
1034
|
|
|
1035
|
+
/**
|
|
1036
|
+
* `cave suggest-alias` — alias discovery (spec §27): same-entity
|
|
1037
|
+
* candidates as suggested `ALIAS` claims for review. Async (the optional
|
|
1038
|
+
* judge runs a shell agent), so `main.ts` routes it separately, like
|
|
1039
|
+
* `reconstruct`.
|
|
1040
|
+
*/
|
|
1041
|
+
export const suggestAliasCommand = async (argv: readonly string[]): Promise<Output> => {
|
|
1042
|
+
try {
|
|
1043
|
+
const { values } = parseArgs({
|
|
1044
|
+
args: [...argv],
|
|
1045
|
+
options: {
|
|
1046
|
+
db: { type: 'string' },
|
|
1047
|
+
min: { type: 'string' },
|
|
1048
|
+
limit: { type: 'string' },
|
|
1049
|
+
agent: { type: 'string' },
|
|
1050
|
+
timeout: { type: 'string' },
|
|
1051
|
+
write: { type: 'boolean' },
|
|
1052
|
+
json: { type: 'boolean' },
|
|
1053
|
+
'no-prelude': { type: 'boolean' },
|
|
1054
|
+
help: { type: 'boolean', short: 'h' }
|
|
1055
|
+
},
|
|
1056
|
+
allowPositionals: false
|
|
1057
|
+
})
|
|
1058
|
+
if (values.help === true) {
|
|
1059
|
+
return ok(`${commandHelp['suggest-alias']}\n`)
|
|
1060
|
+
}
|
|
1061
|
+
const minScore = values.min === undefined ?
|
|
1062
|
+
defaultMinScore :
|
|
1063
|
+
values.min.endsWith('%') ? Number(values.min.slice(0, -1)) / 100 : Number(values.min)
|
|
1064
|
+
if (!Number.isFinite(minScore) || minScore < 0 || minScore > 1) {
|
|
1065
|
+
return fail(`cave suggest-alias: --min expects a score in 0..1 or N%, got ${JSON.stringify(values.min)}\n`)
|
|
1066
|
+
}
|
|
1067
|
+
const limit = values.limit === undefined ? undefined : Number(values.limit)
|
|
1068
|
+
if (limit !== undefined && (!Number.isInteger(limit) || limit < 1)) {
|
|
1069
|
+
return fail(`cave suggest-alias: --limit expects a positive integer, got ${JSON.stringify(values.limit)}\n`)
|
|
1070
|
+
}
|
|
1071
|
+
const timeoutSeconds = values.timeout === undefined ? undefined : Number(values.timeout)
|
|
1072
|
+
if (timeoutSeconds !== undefined && (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)) {
|
|
1073
|
+
return fail(`cave suggest-alias: --timeout must be a positive number of seconds, got '${values.timeout}'\n`)
|
|
1074
|
+
}
|
|
1075
|
+
const store = open(values.db ?? defaultDbPath(), values['no-prelude'] === true ? { registry: Registry.empty } : {})
|
|
1076
|
+
try {
|
|
1077
|
+
let suggestions = suggestAliases(store, { minScore, ...limit === undefined ? {} : { limit } })
|
|
1078
|
+
if (values.agent !== undefined && suggestions.length > 0) {
|
|
1079
|
+
const reply = await shellComplete(values.agent, timeoutSeconds === undefined ? {} : { timeoutSeconds })(
|
|
1080
|
+
judgePrompt(store, suggestions)
|
|
1081
|
+
)
|
|
1082
|
+
suggestions = parseJudgeReply(reply, suggestions.length).map(index => suggestions[index]!)
|
|
1083
|
+
}
|
|
1084
|
+
if (values.json === true) {
|
|
1085
|
+
return ok(`${JSON.stringify(suggestions, undefined, 2)}\n`)
|
|
1086
|
+
}
|
|
1087
|
+
if (suggestions.length === 0) {
|
|
1088
|
+
return ok('no alias suggestions\n')
|
|
1089
|
+
}
|
|
1090
|
+
const lines = suggestions.map(suggestion => suggestion.line)
|
|
1091
|
+
if (values.write === true) {
|
|
1092
|
+
const { appended } = writeSuggestions(store, suggestions)
|
|
1093
|
+
return ok(`${lines.join('\n')}\nappended ${appended} suggested alias claim(s)\n`)
|
|
1094
|
+
}
|
|
1095
|
+
return ok(`${lines.join('\n')}\n`)
|
|
1096
|
+
} finally {
|
|
1097
|
+
store.close()
|
|
1098
|
+
}
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
return fail(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* `cave sync` — store merge (spec §28): another store's rows arrive by
|
|
1106
|
+
* identity — a store file merges through SQL, `;@`-annotated canonical
|
|
1107
|
+
* text (`cave export --tx`, or `-` for stdin) replays under its recorded
|
|
1108
|
+
* ids. Idempotent: re-runs merge nothing and append no record.
|
|
1109
|
+
*/
|
|
1110
|
+
export const syncCommand = (argv: readonly string[]): Output => {
|
|
1111
|
+
const { values, positionals } = parseArgs({
|
|
1112
|
+
args: [...argv],
|
|
1113
|
+
options: {
|
|
1114
|
+
db: { type: 'string' },
|
|
1115
|
+
as: { type: 'string' },
|
|
1116
|
+
into: { type: 'string' },
|
|
1117
|
+
'dry-run': { type: 'boolean' },
|
|
1118
|
+
'no-record': { type: 'boolean' },
|
|
1119
|
+
json: { type: 'boolean' },
|
|
1120
|
+
'no-prelude': { type: 'boolean' }
|
|
1121
|
+
},
|
|
1122
|
+
allowPositionals: true
|
|
1123
|
+
})
|
|
1124
|
+
const [source, ...extra] = positionals
|
|
1125
|
+
if (source === undefined || extra.length > 0) {
|
|
1126
|
+
return fail('cave sync: exactly one source is required — a CAVE store file, ;@-annotated text, or - for stdin (spec §28.5)\n')
|
|
1127
|
+
}
|
|
1128
|
+
const dbPath = values.db ?? defaultDbPath()
|
|
1129
|
+
const store = open(dbPath, values['no-prelude'] === true ? { registry: Registry.empty } : {})
|
|
1130
|
+
try {
|
|
1131
|
+
const options = {
|
|
1132
|
+
into: values.into === undefined ? labelOf(dbPath) : sanitizeLabel(values.into),
|
|
1133
|
+
record: values['no-record'] !== true,
|
|
1134
|
+
dryRun: values['dry-run'] === true,
|
|
1135
|
+
...values.as === undefined ? {} : { from: sanitizeLabel(values.as) }
|
|
1136
|
+
}
|
|
1137
|
+
const report: SyncReport = source === '-' ?
|
|
1138
|
+
syncText(store, readFileSync(0, 'utf8'), { from: 'stdin', ...options }) :
|
|
1139
|
+
syncFile(store, source, options)
|
|
1140
|
+
if (values.json === true) {
|
|
1141
|
+
return { code: report.problems.length > 0 ? 1 : 0, out: `${JSON.stringify(report, undefined, 2)}\n`, err: '' }
|
|
1142
|
+
}
|
|
1143
|
+
if (report.problems.length > 0) {
|
|
1144
|
+
const detail = report.problems.map(problem => ` line ${problem.line}: ${problem.message}`).join('\n')
|
|
1145
|
+
return fail(`cave sync: ${source}: ${report.problems.length} problem(s), nothing merged\n${detail}\n`)
|
|
1146
|
+
}
|
|
1147
|
+
const lines = [
|
|
1148
|
+
`merged ${report.merged} claim(s), ${report.edges} edge(s)` +
|
|
1149
|
+
(report.skipped > 0 ? `, ${report.skipped} already present` : '') +
|
|
1150
|
+
(report.dryRun ? ' (dry run)' : '')
|
|
1151
|
+
]
|
|
1152
|
+
if (report.record !== undefined) {
|
|
1153
|
+
lines.push(`record: ${report.record}`)
|
|
1154
|
+
}
|
|
1155
|
+
return ok(`${lines.join('\n')}\n`)
|
|
1156
|
+
} catch (error) {
|
|
1157
|
+
return fail(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
1158
|
+
} finally {
|
|
1159
|
+
store.close()
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
834
1163
|
export const exportCommand = (argv: readonly string[]): Output => {
|
|
835
1164
|
const { values } = parseArgs({
|
|
836
1165
|
args: [...argv],
|
|
837
1166
|
options: {
|
|
838
1167
|
db: { type: 'string' },
|
|
839
1168
|
current: { type: 'boolean' },
|
|
1169
|
+
tx: { type: 'boolean' },
|
|
840
1170
|
out: { type: 'string' },
|
|
841
1171
|
'no-prelude': { type: 'boolean' }
|
|
842
1172
|
},
|
|
@@ -844,12 +1174,12 @@ export const exportCommand = (argv: readonly string[]): Output => {
|
|
|
844
1174
|
})
|
|
845
1175
|
const store = open(values.db ?? defaultDbPath(), values['no-prelude'] === true ? { registry: Registry.empty } : {})
|
|
846
1176
|
try {
|
|
847
|
-
const text = store.exportText({ current: values.current === true })
|
|
1177
|
+
const text = store.exportText({ current: values.current === true, tx: values.tx === true })
|
|
848
1178
|
if (values.out === undefined) {
|
|
849
1179
|
return ok(text)
|
|
850
1180
|
}
|
|
851
1181
|
writeFileSync(values.out, text)
|
|
852
|
-
const claims = text === '' ? 0 : text.trimEnd().split('\n').length
|
|
1182
|
+
const claims = text === '' ? 0 : text.trimEnd().split('\n').filter(line => txOfLine(line) === undefined).length
|
|
853
1183
|
return ok(`exported ${claims} claim(s) to ${values.out}\n`)
|
|
854
1184
|
} finally {
|
|
855
1185
|
store.close()
|
|
@@ -969,12 +1299,16 @@ export const cave = (argv: readonly string[]): Output => {
|
|
|
969
1299
|
return importCommand(rest)
|
|
970
1300
|
case 'query':
|
|
971
1301
|
return queryCommand(rest)
|
|
1302
|
+
case 'resolve':
|
|
1303
|
+
return resolveCommand(rest)
|
|
972
1304
|
case 'derive':
|
|
973
1305
|
return deriveCommand(rest)
|
|
974
1306
|
case 'act':
|
|
975
1307
|
return actCommand(rest)
|
|
976
1308
|
case 'check':
|
|
977
1309
|
return checkCommand(rest)
|
|
1310
|
+
case 'sync':
|
|
1311
|
+
return syncCommand(rest)
|
|
978
1312
|
case 'export':
|
|
979
1313
|
return exportCommand(rest)
|
|
980
1314
|
case 'demo':
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@cavelang/cli` — the `cave` command (parse / add / query / derive / act / check / export / reconstruct / demo).
|
|
2
|
+
* `@cavelang/cli` — the `cave` command (parse / add / query / resolve / derive / act / check / suggest-alias / sync / export / reconstruct / demo).
|
|
3
3
|
* Programmatic access to the same implementations lives here.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export { actCommand, addCommand, cave, checkCommand, commandHelp, demoCommand, deriveCommand, exportCommand, helpCommand, highlightCommand, importCommand, parseCommand, queryCommand, reconstructCommand, usage, versionCommand } from './cli.ts'
|
|
6
|
+
export { actCommand, addCommand, cave, checkCommand, commandHelp, demoCommand, deriveCommand, exportCommand, helpCommand, highlightCommand, importCommand, parseCommand, queryCommand, reconstructCommand, resolveCommand, suggestAliasCommand, syncCommand, usage, versionCommand } from './cli.ts'
|
|
7
7
|
export type { Output } from './cli.ts'
|
package/src/main.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** `cave` binary entry point — see `cli.ts` for the command implementations. */
|
|
3
3
|
|
|
4
|
-
import { cave, highlightCommand, reconstructCommand } from './cli.ts'
|
|
4
|
+
import { cave, highlightCommand, reconstructCommand, suggestAliasCommand } from './cli.ts'
|
|
5
5
|
|
|
6
6
|
const write = (code: number, out: string, err: string): void => {
|
|
7
7
|
if (out !== '') {
|
|
@@ -38,6 +38,10 @@ if (argv[0] === 'mcp') {
|
|
|
38
38
|
// Async: the LLM policy runs a shell agent once per step.
|
|
39
39
|
const { code, out, err } = await reconstructCommand(argv.slice(1))
|
|
40
40
|
write(code, out, err)
|
|
41
|
+
} else if (argv[0] === 'suggest-alias') {
|
|
42
|
+
// Async: the optional judge runs a shell agent.
|
|
43
|
+
const { code, out, err } = await suggestAliasCommand(argv.slice(1))
|
|
44
|
+
write(code, out, err)
|
|
41
45
|
} else if (argv[0] === 'highlight' && !argv.includes('--help') && !argv.includes('-h')) {
|
|
42
46
|
// Async (grammar WASM loads on first use); --help stays on the sync path.
|
|
43
47
|
const { code, out, err } = await highlightCommand(argv.slice(1))
|