@cavelang/cli 0.34.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +2 -2
  2. package/dist/internal/automate/main.d.ts.map +1 -1
  3. package/dist/internal/automate/main.js +2 -0
  4. package/dist/internal/automate/main.js.map +1 -1
  5. package/dist/internal/connect/declared.d.ts +225 -0
  6. package/dist/internal/connect/declared.d.ts.map +1 -0
  7. package/dist/internal/connect/declared.js +521 -0
  8. package/dist/internal/connect/declared.js.map +1 -0
  9. package/dist/internal/connect/index.d.ts +4 -2
  10. package/dist/internal/connect/index.d.ts.map +1 -1
  11. package/dist/internal/connect/index.js +3 -1
  12. package/dist/internal/connect/index.js.map +1 -1
  13. package/dist/internal/connect/main.d.ts.map +1 -1
  14. package/dist/internal/connect/main.js +352 -10
  15. package/dist/internal/connect/main.js.map +1 -1
  16. package/dist/internal/connect/run.d.ts +47 -1
  17. package/dist/internal/connect/run.d.ts.map +1 -1
  18. package/dist/internal/connect/run.js +81 -33
  19. package/dist/internal/connect/run.js.map +1 -1
  20. package/dist/internal/connect/source.d.ts +33 -2
  21. package/dist/internal/connect/source.d.ts.map +1 -1
  22. package/dist/internal/connect/source.js +168 -8
  23. package/dist/internal/connect/source.js.map +1 -1
  24. package/dist/internal/connect/template.d.ts +18 -0
  25. package/dist/internal/connect/template.d.ts.map +1 -1
  26. package/dist/internal/connect/template.js +28 -0
  27. package/dist/internal/connect/template.js.map +1 -1
  28. package/dist/internal/ingest/main.d.ts.map +1 -1
  29. package/dist/internal/ingest/main.js +2 -1
  30. package/dist/internal/ingest/main.js.map +1 -1
  31. package/dist/internal/mcp/main.d.ts.map +1 -1
  32. package/dist/internal/mcp/main.js +2 -0
  33. package/dist/internal/mcp/main.js.map +1 -1
  34. package/dist/internal/rules/rule.d.ts.map +1 -1
  35. package/dist/internal/rules/rule.js +1 -22
  36. package/dist/internal/rules/rule.js.map +1 -1
  37. package/dist/internal/view/main.d.ts.map +1 -1
  38. package/dist/internal/view/main.js +2 -1
  39. package/dist/internal/view/main.js.map +1 -1
  40. package/dist/src/cli.d.ts +14 -0
  41. package/dist/src/cli.d.ts.map +1 -1
  42. package/dist/src/cli.js +161 -38
  43. package/dist/src/cli.js.map +1 -1
  44. package/dist/src/commands.d.ts +2 -2
  45. package/dist/src/commands.js +2 -2
  46. package/dist/src/commands.js.map +1 -1
  47. package/dist/src/dispatch.d.ts.map +1 -1
  48. package/dist/src/dispatch.js +7 -1
  49. package/dist/src/dispatch.js.map +1 -1
  50. package/dist/src/doctor.d.ts +1 -1
  51. package/dist/src/doctor.d.ts.map +1 -1
  52. package/dist/src/doctor.js +26 -1
  53. package/dist/src/doctor.js.map +1 -1
  54. package/dist/src/index.d.ts +1 -1
  55. package/dist/src/index.d.ts.map +1 -1
  56. package/dist/src/index.js +1 -1
  57. package/dist/src/index.js.map +1 -1
  58. package/package.json +19 -19
  59. package/src/cli.ts +187 -36
  60. package/src/commands.ts +2 -2
  61. package/src/dispatch.ts +7 -1
  62. package/src/doctor.ts +28 -2
  63. package/src/index.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cavelang/cli",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "type": "module",
5
5
  "description": "cave — CLI for parsing, storing, querying and exporting CAVE knowledge.",
6
6
  "keywords": [
@@ -97,26 +97,26 @@
97
97
  "@modelcontextprotocol/server": "2.0.0",
98
98
  "@mozilla/readability": "^0.6.0",
99
99
  "linkedom": "^0.18.13",
100
- "@cavelang/canonical": "0.34.0",
101
- "@cavelang/fusion": "0.34.0",
102
- "@cavelang/highlight": "0.34.0",
103
- "@cavelang/core": "0.34.0",
104
- "@cavelang/parser": "0.34.0",
105
- "@cavelang/query": "0.34.0",
106
- "@cavelang/store": "0.34.0"
100
+ "@cavelang/canonical": "0.35.0",
101
+ "@cavelang/fusion": "0.35.0",
102
+ "@cavelang/core": "0.35.0",
103
+ "@cavelang/highlight": "0.35.0",
104
+ "@cavelang/parser": "0.35.0",
105
+ "@cavelang/query": "0.35.0",
106
+ "@cavelang/store": "0.35.0"
107
107
  },
108
108
  "devDependencies": {
109
- "@cavelang/act": "0.34.0",
110
- "@cavelang/connect": "0.34.0",
111
- "@cavelang/automate": "0.34.0",
112
- "@cavelang/ingest": "0.34.0",
113
- "@cavelang/loop": "0.34.0",
114
- "@cavelang/eval": "0.34.0",
115
- "@cavelang/rules": "0.34.0",
116
- "@cavelang/mcp": "0.34.0",
117
- "@cavelang/shape": "0.34.0",
118
- "@cavelang/view": "0.34.0",
119
- "@cavelang/sync": "0.34.0"
109
+ "@cavelang/act": "0.35.0",
110
+ "@cavelang/automate": "0.35.0",
111
+ "@cavelang/eval": "0.35.0",
112
+ "@cavelang/connect": "0.35.0",
113
+ "@cavelang/ingest": "0.35.0",
114
+ "@cavelang/loop": "0.35.0",
115
+ "@cavelang/rules": "0.35.0",
116
+ "@cavelang/shape": "0.35.0",
117
+ "@cavelang/mcp": "0.35.0",
118
+ "@cavelang/sync": "0.35.0",
119
+ "@cavelang/view": "0.35.0"
120
120
  },
121
121
  "scripts": {
122
122
  "build": "tsc -b && node scripts/consolidate.mjs",
package/src/cli.ts CHANGED
@@ -41,8 +41,9 @@ import { parseArgs } from 'node:util'
41
41
  import { Version } from '@cavelang/core'
42
42
  import { parseDocument, Token } from '@cavelang/parser'
43
43
  import { Registry, standardRegistry } from '@cavelang/canonical'
44
- import { LocateError, Sensitivity, backup as backupStore, defaultDbPath, openAt, restoreBackup, verifyBackup } from '@cavelang/store'
44
+ import { backup as backupStore, defaultDbPath, kindOf, LocateError, openAt, restoreBackup, Sensitivity, verifyBackup } from '@cavelang/store'
45
45
  import type { OpenIntent, Store } from '@cavelang/store'
46
+ import { Declared, assemble as assembleSources } from '@cavelang/connect'
46
47
  import { defaultLimit as defaultQueryLimit, maxLimit as maxQueryLimit, page as caveQueryPage } from '@cavelang/query'
47
48
  import {
48
49
  check as caveCheck, defaultMinScore, defaultStaleDays, gatedIngest, judgePrompt, parseJudgeReply,
@@ -252,7 +253,7 @@ Examples:
252
253
  query: `cave query — run a CAVE-Q pattern against a store
253
254
 
254
255
  Usage:
255
- cave query [--db <path>] <pattern> [WHERE <filter>] [--limit <n>] [--cursor <token>] [--json] [--all] [--aliases] [--as-of <t>] [--at <t>] [--resolve] [--no-prelude]
256
+ cave query [--db <path>] <pattern> [WHERE <filter>] [--limit <n>] [--cursor <token>] [--json] [--all] [--aliases] [--as-of <t>] [--at <t>] [--resolve] [--sources] [--no-prelude]
256
257
 
257
258
  Options:
258
259
  ${dbHelp}
@@ -278,6 +279,12 @@ Options:
278
279
  collapse to the row the resolution policy picks
279
280
  (precedence class, reliability-weighted confidence, tx);
280
281
  incompatible with --all
282
+ --sources overlay the store's declared sources (spec §23.4) inside a
283
+ rolled-back transaction — federation-lite: external data is
284
+ consulted at query time, nothing persists. The answer is
285
+ whole (no --cursor, --limit is not a page size); a text
286
+ store followed its local sources on open, so only URL
287
+ sources are loaded here
281
288
  --no-prelude open the store without the standard verb registry
282
289
 
283
290
  Patterns are claim triples with ?variables and optional metadata filters
@@ -297,6 +304,7 @@ Examples:
297
304
  cave query --db k.db 'revenue IS' --at 2026-07
298
305
  cave query --db k.db 'alice WORKS-AT ?org' --at 2021
299
306
  cave query --db k.db 'service HAS owner: ?who' --resolve
307
+ cave query --db k.db '?who WORKS-AT acme' --sources
300
308
  cave query --db k.db '?x ?verb ?y @production' --json`,
301
309
 
302
310
  search: `cave search — full-text search over claims and comments (spec §13.2)
@@ -797,9 +805,45 @@ const readStdin = (): string => {
797
805
  const openDb = (values: { db?: string, 'no-prelude'?: boolean }, intent: OpenIntent): Store =>
798
806
  openAt(values.db ?? defaultDbPath(), {
799
807
  intent,
808
+ // A text store follows its declared sources on open (spec §23.4).
809
+ assemble: assembleSources,
800
810
  ...values['no-prelude'] === true ? { registry: Registry.empty } : {}
801
811
  })
802
812
 
813
+ const sourcesRollback = Symbol('cave query --sources rollback')
814
+
815
+ /**
816
+ * Runs `body` with the store's declared sources overlaid (spec §23.4): the
817
+ * sources, loaded beforehand (URLs included), append inside a transaction
818
+ * that always rolls back, so external data is consulted at query time and
819
+ * nothing persists, digests included.
820
+ */
821
+ const withSources = <T>(store: Store, discovered: Declared.Discovery, body: () => T): undefined | { value: T } => {
822
+ let result: undefined | { value: T }
823
+ try {
824
+ store.transaction(() => {
825
+ // Discovered on a snapshot: if a writer changed the declarations
826
+ // since, the sequence is stale and the caller rediscovers.
827
+ if (!Declared.sameDeclarations(discovered.baseline, Declared.declarationState(store))) {
828
+ throw sourcesRollback
829
+ }
830
+ for (const source of discovered.sequence) {
831
+ const report = Declared.run(store, source, { force: true })
832
+ if (report.failures.length > 0) {
833
+ throw new LocateError(`source/${source.declared.name}: ${report.failures.map(failure => `${failure.record}: ${failure.problems.join('; ')}`).join('; ')}`)
834
+ }
835
+ }
836
+ result = { value: body() }
837
+ throw sourcesRollback
838
+ })
839
+ } catch (error) {
840
+ if (error !== sourcesRollback) {
841
+ throw error
842
+ }
843
+ }
844
+ return result
845
+ }
846
+
803
847
  const readInput = (files: readonly string[]): string =>
804
848
  files.length === 0 || (files.length === 1 && files[0] === '-') ?
805
849
  readStdin() :
@@ -929,39 +973,150 @@ export const addCommand = ingestCommand('add')
929
973
  */
930
974
  export const importCommand = ingestCommand('import')
931
975
 
932
- export const queryCommand = (argv: readonly string[]): Output => {
933
- const { values, positionals } = parseArgs({
934
- args: [...argv],
935
- options: {
936
- db: { type: 'string' },
937
- json: { type: 'boolean' },
938
- all: { type: 'boolean' },
939
- aliases: { type: 'boolean' },
940
- 'as-of': { type: 'string' },
941
- at: { type: 'string' },
942
- resolve: { type: 'boolean' },
943
- limit: { type: 'string' },
944
- cursor: { type: 'string' },
945
- 'no-prelude': { type: 'boolean' }
946
- },
947
- allowPositionals: true
948
- })
976
+ const queryOptions = {
977
+ db: { type: 'string' },
978
+ json: { type: 'boolean' },
979
+ all: { type: 'boolean' },
980
+ aliases: { type: 'boolean' },
981
+ 'as-of': { type: 'string' },
982
+ at: { type: 'string' },
983
+ resolve: { type: 'boolean' },
984
+ sources: { type: 'boolean' },
985
+ limit: { type: 'string' },
986
+ cursor: { type: 'string' },
987
+ 'no-prelude': { type: 'boolean' }
988
+ } as const
989
+
990
+ type QueryValues = {
991
+ db?: string
992
+ json?: boolean
993
+ all?: boolean
994
+ aliases?: boolean
995
+ 'as-of'?: string
996
+ at?: string
997
+ resolve?: boolean
998
+ sources?: boolean
999
+ limit?: string
1000
+ cursor?: string
1001
+ 'no-prelude'?: boolean
1002
+ }
1003
+
1004
+ const parseQuery = (argv: readonly string[]): { values: QueryValues, pattern: string } | { failure: Output } => {
1005
+ const { values, positionals } = parseArgs({ args: [...argv], options: queryOptions, allowPositionals: true })
949
1006
  if (positionals.length === 0) {
950
- return fail('cave query: a pattern is required (spec §12.1)\n')
1007
+ return { failure: fail('cave query: a pattern is required (spec §12.1)\n') }
1008
+ }
1009
+ if (values.sources === true && values.cursor !== undefined) {
1010
+ return { failure: fail('cave query: --cursor does not apply to --sources — the overlay exists for one invocation and is answered whole\n') }
1011
+ }
1012
+ return { values, pattern: positionals.join('\n') }
1013
+ }
1014
+
1015
+ /**
1016
+ * `cave query --sources` — the store with its declared sources overlaid
1017
+ * (spec §23.4). Asynchronous because the sources load first, URLs
1018
+ * included, before the overlay appends inside a transaction that rolls
1019
+ * back; the dispatcher routes `--sources` here. A text store followed its
1020
+ * local sources on open, so only what assembly could not follow — URL
1021
+ * sources and what they declare — is loaded. The overlay exists for this
1022
+ * invocation alone, so the answer is whole: pages are followed inside the
1023
+ * transaction and no cursor is handed out.
1024
+ */
1025
+ export const querySourcesCommand = async (
1026
+ argv: readonly string[],
1027
+ runtime: { readonly fetchImpl?: Declared.DiscoverOptions['fetchImpl'] } = {}
1028
+ ): Promise<Output> => {
1029
+ const parsed = parseQuery(argv)
1030
+ if ('failure' in parsed) {
1031
+ return parsed.failure
1032
+ }
1033
+ const { values, pattern } = parsed
1034
+ if (values.sources !== true) {
1035
+ return queryCommand(argv)
1036
+ }
1037
+ const root = values.db ?? defaultDbPath()
1038
+ // The overlay appends inside a transaction it rolls back: writable, but
1039
+ // never creating or migrating (spec §13.7).
1040
+ const store = openDb(values, 'scratch')
1041
+ try {
1042
+ for (let attempt = 0; attempt < Declared.overlayAttempts; attempt += 1) {
1043
+ const discovered = await Declared.discovery(store, root, {
1044
+ // The overlay applies every source it loads (force), so discovery
1045
+ // simulates that; a text store skips what assembly already followed.
1046
+ force: true,
1047
+ ...kindOf(root) === 'text' ? { skipFollowed: true } : {},
1048
+ ...runtime.fetchImpl === undefined ? {} : { fetchImpl: runtime.fetchImpl }
1049
+ })
1050
+ const page = withSources(store, discovered, () => wholeQuery(store, values, pattern))
1051
+ if (page !== undefined) {
1052
+ return renderQueryPage(store, values, pattern, () => page.value)
1053
+ }
1054
+ }
1055
+ throw Declared.staleOverlay()
1056
+ } catch (error) {
1057
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`)
1058
+ } finally {
1059
+ store.close()
1060
+ }
1061
+ }
1062
+
1063
+ export const queryCommand = (argv: readonly string[]): Output => {
1064
+ const parsed = parseQuery(argv)
1065
+ if ('failure' in parsed) {
1066
+ return parsed.failure
1067
+ }
1068
+ const { values, pattern } = parsed
1069
+ if (values.sources === true) {
1070
+ // Loading sources cannot be synchronous: the in-process entry for the
1071
+ // overlay is `querySourcesCommand`, which the dispatcher uses.
1072
+ return fail('cave query: --sources loads the declared sources first and runs asynchronously — use querySourcesCommand\n')
951
1073
  }
952
- const pattern = positionals.join('\n')
953
1074
  const store = openDb(values, 'read')
954
1075
  try {
955
- const limit = values.limit === undefined ? defaultQueryLimit : Number(values.limit)
956
- const result = caveQueryPage(store, pattern, {
957
- all: values.all === true,
958
- aliases: values.aliases === true,
959
- resolve: values.resolve === true,
960
- limit,
961
- ...values.cursor === undefined ? {} : { cursor: values.cursor },
962
- ...values['as-of'] === undefined ? {} : { asOf: values['as-of'] },
963
- ...values.at === undefined ? {} : { at: values.at }
964
- })
1076
+ return renderQueryPage(store, values, pattern, () => queryPage(store, values, pattern))
1077
+ } catch (error) {
1078
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`)
1079
+ } finally {
1080
+ store.close()
1081
+ }
1082
+ }
1083
+
1084
+ /** Every match of the overlay: the pages are followed here, inside the transaction that holds it. */
1085
+ const wholeQuery = (store: Store, values: QueryValues, pattern: string): ReturnType<typeof caveQueryPage> => {
1086
+ const matches: ReturnType<typeof caveQueryPage>['matches'][number][] = []
1087
+ let first: undefined | ReturnType<typeof caveQueryPage>
1088
+ let cursor: undefined | string
1089
+ for (;;) {
1090
+ const page = queryPage(store, { ...values, limit: '1000', ...cursor === undefined ? {} : { cursor } }, pattern)
1091
+ first ??= page
1092
+ matches.push(...page.matches)
1093
+ if (page.next === undefined) {
1094
+ const { next: _next, ...rest } = first
1095
+ return { ...rest, matches }
1096
+ }
1097
+ cursor = page.next
1098
+ }
1099
+ }
1100
+
1101
+ const queryPage = (store: Store, values: QueryValues, pattern: string): ReturnType<typeof caveQueryPage> =>
1102
+ caveQueryPage(store, pattern, {
1103
+ all: values.all === true,
1104
+ aliases: values.aliases === true,
1105
+ resolve: values.resolve === true,
1106
+ limit: values.limit === undefined ? defaultQueryLimit : Number(values.limit),
1107
+ ...values.cursor === undefined ? {} : { cursor: values.cursor },
1108
+ ...values['as-of'] === undefined ? {} : { asOf: values['as-of'] },
1109
+ ...values.at === undefined ? {} : { at: values.at }
1110
+ })
1111
+
1112
+ const renderQueryPage = (
1113
+ store: Store,
1114
+ values: QueryValues,
1115
+ pattern: string,
1116
+ page: () => ReturnType<typeof caveQueryPage>
1117
+ ): Output => {
1118
+ {
1119
+ const result = page()
965
1120
  if (values.json === true) {
966
1121
  return ok(`${JSON.stringify(result, undefined, 2)}\n`)
967
1122
  }
@@ -990,10 +1145,6 @@ export const queryCommand = (argv: readonly string[]): Output => {
990
1145
  line
991
1146
  })
992
1147
  return ok(`${lines.join('\n')}\n${result.next === undefined ? '' : `next: ${result.next}\n`}`)
993
- } catch (error) {
994
- return fail(`${error instanceof Error ? error.message : String(error)}\n`)
995
- } finally {
996
- store.close()
997
1148
  }
998
1149
  }
999
1150
 
@@ -1482,7 +1633,7 @@ export const backupCommand = (argv: readonly string[]): Output => {
1482
1633
  if (sameFile(dbPath, values.out)) {
1483
1634
  return fail(`cave backup: --out '${values.out}' is the source database — refusing to overwrite it\n`)
1484
1635
  }
1485
- const store = openAt(dbPath, { intent: 'read' })
1636
+ const store = openAt(dbPath, { intent: 'read', assemble: assembleSources })
1486
1637
  try {
1487
1638
  return ok(snapshotLine('created', backupStore(store, values.out, { force: values.force === true })))
1488
1639
  } finally {
package/src/commands.ts CHANGED
@@ -18,7 +18,7 @@ export const commandRegistry = [
18
18
  { name: 'highlight', importantOptions: [] },
19
19
  { name: 'add', importantOptions: ['--strict', '--check', '--no-prelude', '--no-src'] },
20
20
  { name: 'import', importantOptions: ['--strict', '--no-prelude'] },
21
- { name: 'query', importantOptions: ['--json', '--limit', '--cursor', '--all', '--aliases', '--as-of', '--at', '--resolve', '--no-prelude'] },
21
+ { name: 'query', importantOptions: ['--json', '--limit', '--cursor', '--all', '--aliases', '--as-of', '--at', '--resolve', '--sources', '--no-prelude'] },
22
22
  { name: 'search', importantOptions: ['--raw', '--limit', '--json', '--no-prelude'] },
23
23
  { name: 'resolve', importantOptions: ['--aliases', '--policy', '--json', '--no-prelude'] },
24
24
  { name: 'derive', importantOptions: ['--dry-run', '--full', '--aliases', '--min-conf', '--max-passes', '--list', '--retract', '--json', '--no-prelude'] },
@@ -36,7 +36,7 @@ export const commandRegistry = [
36
36
  { name: 'mcp', importantOptions: ['--read-only', '--permissions', '--tools', '--hooks', '--no-prelude', '--src', '--no-src'], delegated: true },
37
37
  { name: 'ingest', importantOptions: ['--agent', '--stdout', '--lenient', '--plan', '--dry-run', '--json', '--no-prelude'], delegated: true },
38
38
  { name: 'eval', importantOptions: ['--agent', '--judge', '--runs', '--stdout', '--min', '--json', '--no-prelude'], delegated: true },
39
- { name: 'connect', importantOptions: ['--map', '--key', '--watch', '--prune', '--query', '--dry-run', '--no-prelude'], delegated: true },
39
+ { name: 'connect', importantOptions: ['--map', '--key', '--watch', '--prune', '--query', '--dry-run', '--list', '--no-prelude'], delegated: true },
40
40
  { name: 'reconstruct', importantOptions: ['--query', '--agent', '--steps', '--claims', '--timeout', '--trace', '--no-prelude'] },
41
41
  { name: 'doctor', importantOptions: ['--hooks', '--json'] },
42
42
  { name: 'demo', importantOptions: [] },
package/src/dispatch.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /** One promise-based command dispatcher and process lifecycle for every command. */
2
2
 
3
- import { cave, highlightCommand, reconstructCommand, suggestAliasCommand } from './cli.ts'
3
+ import { cave, highlightCommand, querySourcesCommand, reconstructCommand, suggestAliasCommand } from './cli.ts'
4
4
  import { delegatedCommandNames } from './commands.ts'
5
5
  import type { Output } from './cli.ts'
6
6
 
@@ -78,6 +78,12 @@ const execute = async (raw: readonly string[], runtime: CommandRuntime): Promise
78
78
  const { runServe } = await import('@cavelang/view')
79
79
  return await runServe(rest, context)
80
80
  }
81
+ case 'q':
82
+ case 'query':
83
+ if (rest.includes('--sources')) {
84
+ return await runOutput(runtime, argv, querySourcesCommand)
85
+ }
86
+ break
81
87
  case 'reconstruct':
82
88
  return await runOutput(runtime, argv,
83
89
  args => reconstructCommand(args, { ...runtime.signal === undefined ? {} : { signal: runtime.signal } }))
package/src/doctor.ts CHANGED
@@ -6,7 +6,8 @@ import { dirname, join, parse } from 'node:path'
6
6
  import { parseArgs } from 'node:util'
7
7
  import { Version } from '@cavelang/core'
8
8
  import { directCommand, runProcessSync } from '@cavelang/loop'
9
- import { Schema, defaultDbPath } from '@cavelang/store'
9
+ import { LocateError, Schema, defaultDbPath, kindOf, openText } from '@cavelang/store'
10
+ import { assemble } from '@cavelang/connect'
10
11
  import { nodeSqliteAdapter } from '@cavelang/store/adapter/node'
11
12
  import type { SqliteDatabase } from '@cavelang/store/adapter'
12
13
 
@@ -32,7 +33,7 @@ export type DoctorReport = {
32
33
  readonly configuration: {
33
34
  readonly database: {
34
35
  readonly source: 'flag' | 'environment' | 'default'
35
- readonly kind: 'memory' | 'file'
36
+ readonly kind: 'memory' | 'file' | 'text'
36
37
  readonly exists: boolean
37
38
  readonly schemaVersion?: number
38
39
  readonly claims?: number
@@ -216,6 +217,31 @@ const inspectDatabase = (path: string, source: DatabaseConfiguration['source']):
216
217
  'Run cave add to create it, or pass --db for an existing store.')]
217
218
  }
218
219
  }
220
+ if (!memory && kindOf(path) === 'text') {
221
+ // A CAVE text file is a read-only store assembled in memory (spec
222
+ // §13.7, §23.4): replay it, sources included, and count what it holds.
223
+ const configuration: DatabaseConfiguration = { ...base, kind: 'text' }
224
+ try {
225
+ const store = openText(path, { assemble })
226
+ try {
227
+ const claims = store.currentBeliefs().filter(row => row.conf > 0).length
228
+ return {
229
+ configuration: { ...configuration, claims },
230
+ checks: [check('store.database', 'pass',
231
+ `CAVE text store assembled in memory (${claims} current claim(s)); text stores are read-only`)]
232
+ }
233
+ } finally {
234
+ store.close()
235
+ }
236
+ } catch (error) {
237
+ return {
238
+ configuration,
239
+ checks: [check('store.database', 'fail',
240
+ `The CAVE text store does not load: ${error instanceof LocateError ? error.message.split('\n')[0] : error instanceof Error ? error.message : String(error)}`,
241
+ 'Fix the file (cave parse) or the source it declares; a text store is read-only.')]
242
+ }
243
+ }
244
+ }
219
245
 
220
246
  let db: SqliteDatabase | undefined
221
247
  let observed = base
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * and `runCli` expose the shared awaited command lifecycle.
5
5
  */
6
6
 
7
- export { actCommand, addCommand, backupCommand, cave, checkCommand, commandHelp, demoCommand, deriveCommand, exportCommand, generateCommand, helpCommand, highlightCommand, importCommand, parseCommand, queryCommand, reconstructCommand, reportCommand, resolveCommand, restoreCommand, searchCommand, suggestAliasCommand, syncCommand, usage, versionCommand } from './cli.ts'
7
+ export { actCommand, addCommand, backupCommand, cave, checkCommand, commandHelp, demoCommand, deriveCommand, exportCommand, generateCommand, helpCommand, highlightCommand, importCommand, parseCommand, queryCommand, querySourcesCommand, reconstructCommand, reportCommand, resolveCommand, restoreCommand, searchCommand, suggestAliasCommand, syncCommand, usage, versionCommand } from './cli.ts'
8
8
  export { dispatch, runCli } from './dispatch.ts'
9
9
  export type { CommandRuntime } from './dispatch.ts'
10
10
  export { commandRegistry } from './commands.ts'