aontu 0.53.0 → 0.55.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 (197) hide show
  1. package/dist/aontu.d.ts +3 -2
  2. package/dist/aontu.js +36 -7
  3. package/dist/aontu.js.map +1 -1
  4. package/dist/cli.d.ts +2 -1
  5. package/dist/cli.js +500 -8
  6. package/dist/cli.js.map +1 -1
  7. package/dist/ctx.d.ts +5 -0
  8. package/dist/ctx.js +1 -0
  9. package/dist/ctx.js.map +1 -1
  10. package/dist/diff.js.map +1 -1
  11. package/dist/err.js +7 -1
  12. package/dist/err.js.map +1 -1
  13. package/dist/graph.d.ts +2 -5
  14. package/dist/graph.js +83 -46
  15. package/dist/graph.js.map +1 -1
  16. package/dist/hcanon.js +9 -10
  17. package/dist/hcanon.js.map +1 -1
  18. package/dist/hints.js +94 -26
  19. package/dist/hints.js.map +1 -1
  20. package/dist/jsonschema.js +34 -0
  21. package/dist/jsonschema.js.map +1 -1
  22. package/dist/lang.js +593 -191
  23. package/dist/lang.js.map +1 -1
  24. package/dist/lsp.d.ts +1 -1
  25. package/dist/lsp.js +86 -6
  26. package/dist/lsp.js.map +1 -1
  27. package/dist/mcp.js +153 -6
  28. package/dist/mcp.js.map +1 -1
  29. package/dist/mod-tool.js +42 -8
  30. package/dist/mod-tool.js.map +1 -1
  31. package/dist/mod.d.ts +4 -0
  32. package/dist/mod.js +97 -2
  33. package/dist/mod.js.map +1 -1
  34. package/dist/patch.d.ts +5 -0
  35. package/dist/patch.js +25 -25
  36. package/dist/patch.js.map +1 -1
  37. package/dist/provenance.d.ts +1 -0
  38. package/dist/provenance.js +2 -1
  39. package/dist/provenance.js.map +1 -1
  40. package/dist/query.js.map +1 -1
  41. package/dist/reach.d.ts +1 -0
  42. package/dist/reach.js +49 -21
  43. package/dist/reach.js.map +1 -1
  44. package/dist/relation.d.ts +4 -0
  45. package/dist/relation.js +125 -200
  46. package/dist/relation.js.map +1 -1
  47. package/dist/sig.d.ts +25 -0
  48. package/dist/sig.js +277 -0
  49. package/dist/sig.js.map +1 -0
  50. package/dist/sigdecl.d.ts +2 -0
  51. package/dist/sigdecl.js +11 -0
  52. package/dist/sigdecl.js.map +1 -0
  53. package/dist/siggate.d.ts +4 -0
  54. package/dist/siggate.js +90 -0
  55. package/dist/siggate.js.map +1 -0
  56. package/dist/std.js +75 -16
  57. package/dist/std.js.map +1 -1
  58. package/dist/subsume.js +57 -10
  59. package/dist/subsume.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/dist/unify.d.ts +2 -2
  62. package/dist/unify.js +93 -114
  63. package/dist/unify.js.map +1 -1
  64. package/dist/utility.d.ts +1 -2
  65. package/dist/utility.js +7 -61
  66. package/dist/utility.js.map +1 -1
  67. package/dist/val/AggFuncVal.d.ts +12 -1
  68. package/dist/val/AggFuncVal.js +165 -3
  69. package/dist/val/AggFuncVal.js.map +1 -1
  70. package/dist/val/BagVal.d.ts +2 -0
  71. package/dist/val/BagVal.js +56 -8
  72. package/dist/val/BagVal.js.map +1 -1
  73. package/dist/val/ConstraintVal.js +33 -5
  74. package/dist/val/ConstraintVal.js.map +1 -1
  75. package/dist/val/ContainerKindVal.d.ts +35 -0
  76. package/dist/val/ContainerKindVal.js +99 -0
  77. package/dist/val/ContainerKindVal.js.map +1 -0
  78. package/dist/val/CopyFuncVal.js +0 -7
  79. package/dist/val/CopyFuncVal.js.map +1 -1
  80. package/dist/val/DisjunctVal.d.ts +1 -2
  81. package/dist/val/DisjunctVal.js +146 -39
  82. package/dist/val/DisjunctVal.js.map +1 -1
  83. package/dist/val/ExpectVal.js +37 -2
  84. package/dist/val/ExpectVal.js.map +1 -1
  85. package/dist/val/FuncBaseVal.d.ts +1 -0
  86. package/dist/val/FuncBaseVal.js +19 -8
  87. package/dist/val/FuncBaseVal.js.map +1 -1
  88. package/dist/val/GraphAtomVal.d.ts +39 -0
  89. package/dist/val/GraphAtomVal.js +184 -0
  90. package/dist/val/GraphAtomVal.js.map +1 -0
  91. package/dist/val/JunctionVal.js +22 -5
  92. package/dist/val/JunctionVal.js.map +1 -1
  93. package/dist/val/ListVal.js +27 -34
  94. package/dist/val/ListVal.js.map +1 -1
  95. package/dist/val/MapVal.d.ts +1 -0
  96. package/dist/val/MapVal.js +82 -35
  97. package/dist/val/MapVal.js.map +1 -1
  98. package/dist/val/PathFuncVal.d.ts +2 -2
  99. package/dist/val/PathFuncVal.js +75 -16
  100. package/dist/val/PathFuncVal.js.map +1 -1
  101. package/dist/val/PathVal.d.ts +25 -0
  102. package/dist/val/PathVal.js +150 -0
  103. package/dist/val/PathVal.js.map +1 -0
  104. package/dist/val/PlusOpVal.d.ts +2 -1
  105. package/dist/val/PlusOpVal.js +50 -35
  106. package/dist/val/PlusOpVal.js.map +1 -1
  107. package/dist/val/PrefVal.d.ts +2 -0
  108. package/dist/val/PrefVal.js +159 -32
  109. package/dist/val/PrefVal.js.map +1 -1
  110. package/dist/val/RecurseVal.d.ts +19 -0
  111. package/dist/val/RecurseVal.js +217 -0
  112. package/dist/val/RecurseVal.js.map +1 -0
  113. package/dist/val/RefVal.d.ts +2 -1
  114. package/dist/val/RefVal.js +105 -72
  115. package/dist/val/RefVal.js.map +1 -1
  116. package/dist/val/ReferFuncVal.d.ts +30 -7
  117. package/dist/val/ReferFuncVal.js +395 -94
  118. package/dist/val/ReferFuncVal.js.map +1 -1
  119. package/dist/val/ScalarKindVal.d.ts +4 -2
  120. package/dist/val/ScalarKindVal.js +12 -1
  121. package/dist/val/ScalarKindVal.js.map +1 -1
  122. package/dist/val/SuperFuncVal.d.ts +4 -2
  123. package/dist/val/SuperFuncVal.js +118 -14
  124. package/dist/val/SuperFuncVal.js.map +1 -1
  125. package/dist/val/TopVal.d.ts +1 -1
  126. package/dist/val/Val.d.ts +2 -3
  127. package/dist/val/Val.js +39 -20
  128. package/dist/val/Val.js.map +1 -1
  129. package/dist/val/arith.js +4 -1
  130. package/dist/val/arith.js.map +1 -1
  131. package/dist/vet.d.ts +1 -0
  132. package/dist/vet.js +32 -1
  133. package/dist/vet.js.map +1 -1
  134. package/dist/view.d.ts +90 -0
  135. package/dist/view.js +2168 -0
  136. package/dist/view.js.map +1 -0
  137. package/grammar/aontu.gbnf +18 -10
  138. package/grammar/aontu.lark +15 -10
  139. package/grammar/aontu.tmLanguage.json +184 -0
  140. package/package.json +10 -3
  141. package/skill/grammar-card.md +1 -2
  142. package/src/aontu.ts +37 -7
  143. package/src/cli.ts +553 -8
  144. package/src/ctx.ts +20 -0
  145. package/src/diff.ts +4 -2
  146. package/src/err.ts +8 -1
  147. package/src/graph.ts +125 -75
  148. package/src/hcanon.ts +9 -11
  149. package/src/hints.ts +112 -29
  150. package/src/jsonschema.ts +41 -0
  151. package/src/lang.ts +642 -203
  152. package/src/lsp.ts +75 -6
  153. package/src/mcp.ts +164 -6
  154. package/src/mod-tool.ts +48 -9
  155. package/src/mod.ts +110 -1
  156. package/src/patch.ts +31 -27
  157. package/src/provenance.ts +8 -1
  158. package/src/query.ts +4 -2
  159. package/src/reach.ts +52 -23
  160. package/src/relation.ts +139 -236
  161. package/src/sig.ts +345 -0
  162. package/src/sigdecl.ts +11 -0
  163. package/src/siggate.ts +144 -0
  164. package/src/std.ts +77 -16
  165. package/src/subsume.ts +59 -10
  166. package/src/unify.ts +102 -125
  167. package/src/utility.ts +7 -67
  168. package/src/val/AggFuncVal.ts +231 -4
  169. package/src/val/BagVal.ts +58 -9
  170. package/src/val/ConstraintVal.ts +34 -5
  171. package/src/val/ContainerKindVal.ts +158 -0
  172. package/src/val/CopyFuncVal.ts +0 -7
  173. package/src/val/DisjunctVal.ts +152 -40
  174. package/src/val/ExpectVal.ts +39 -4
  175. package/src/val/FuncBaseVal.ts +21 -8
  176. package/src/val/GraphAtomVal.ts +264 -0
  177. package/src/val/JunctionVal.ts +23 -6
  178. package/src/val/ListVal.ts +30 -37
  179. package/src/val/MapVal.ts +87 -39
  180. package/src/val/PathFuncVal.ts +107 -19
  181. package/src/val/PathVal.ts +221 -0
  182. package/src/val/PlusOpVal.ts +56 -37
  183. package/src/val/PrefVal.ts +186 -38
  184. package/src/val/RecurseVal.ts +285 -0
  185. package/src/val/RefVal.ts +105 -83
  186. package/src/val/ReferFuncVal.ts +445 -100
  187. package/src/val/ScalarKindVal.ts +12 -0
  188. package/src/val/SuperFuncVal.ts +137 -13
  189. package/src/val/TopVal.ts +1 -1
  190. package/src/val/Val.ts +44 -35
  191. package/src/val/arith.ts +4 -1
  192. package/src/vet.ts +41 -4
  193. package/src/view.ts +2882 -0
  194. package/dist/val/IdFuncVal.d.ts +0 -13
  195. package/dist/val/IdFuncVal.js +0 -54
  196. package/dist/val/IdFuncVal.js.map +0 -1
  197. package/src/val/IdFuncVal.ts +0 -91
package/src/cli.ts CHANGED
@@ -40,6 +40,11 @@ import type { TrimReport, TrimVerdict } from './trim'
40
40
  import type { RelationReport, RelationVerdict } from './relation'
41
41
  import { reachCheck } from './reach'
42
42
  import type { ReachReport, ReachVerdict } from './reach'
43
+ import { view, viewSet, viewDefaultProfile } from './view'
44
+ import type {
45
+ ViewEdges, ViewFigure, ViewKind, ViewLoss, ViewOptions, ViewProfile,
46
+ ViewReport, ViewSetReport, ViewStyle, ViewVerdict,
47
+ } from './view'
43
48
  import type { QueryView } from './query'
44
49
  import type { WhyRecord } from './provenance'
45
50
  import { agentsMdSplice } from './agentsmd'
@@ -55,6 +60,8 @@ const HELP = `Usage: aontu [options] [file]
55
60
  aontu trim --check [options] <file>
56
61
  aontu relations [options] <file>
57
62
  aontu reaches <from> <to> [--relation <name>] [options] <file>
63
+ aontu view <kind> [options] <file>...
64
+ aontu view --views <path> [--check] [options] <file>
58
65
  aontu jsonschema [--at <path>] [--strict] [options] <file>
59
66
  aontu hash [options] <file>
60
67
  aontu mod tidy|verify|vendor|manifest [options] [dir]
@@ -175,6 +182,72 @@ Why options:
175
182
  Why exit codes mirror get's: 0 explained, 1 the path names nothing,
176
183
  2 usage, 4 the document does not stand up on its own.
177
184
 
185
+ View kinds: tree, matrix, graph, layer, sets, layers, ladder, poset
186
+ (the poset takes several files). The figure goes to stdout, the loss
187
+ report to stderr. With --views it draws every figure a document
188
+ declares as data, from one evaluation: each declaration names its own
189
+ kind and out file, nothing is written unless every figure rendered,
190
+ and --check gates the committed set.
191
+
192
+ View options:
193
+ --as <profile> text | mermaid | dot | er | svg, per kind: doc,
194
+ tree, matrix, sets and layers draw text (default)
195
+ or svg; graph draws mermaid (default), dot or er;
196
+ layer draws text (default), mermaid or svg; ladder
197
+ and poset draw mermaid (default) or dot
198
+ --at <path> Restrict the figure to nodes under this path; the
199
+ subtree doc draws; the path the ladder draws;
200
+ where the poset compares
201
+ --views <path> Draw every figure the document declares at this
202
+ path, one evaluation, all or nothing; each
203
+ declaration names its own kind and out file
204
+ -o, --out <file> Write the figure here instead of stdout
205
+ --check Exit 1 if --out differs from what would be drawn;
206
+ nothing is written
207
+ --strict Exit 1 when the loss report holds anything beyond
208
+ edges_deduped, inverse_suppressed and crossings
209
+ --depth <n> doc: how many levels of key to draw (default 3)
210
+ --max-rows <n> Refuse a figure above this many rows (default 60)
211
+ --style <s> auto (default), none, ansi or css. A figure's
212
+ marks carry their meaning -- a direct cell, a
213
+ closure cell, an upward edge -- and each profile
214
+ has one way to show it: SGR escapes for text, CSS
215
+ classes for svg. auto picks that mechanism where
216
+ the destination can carry it: escapes only on a
217
+ terminal (NO_COLOR is honoured), and an svg keeps
218
+ the stylesheet that makes it standalone. none
219
+ drops both; on svg the classes stay and only the
220
+ stylesheet goes, for a host page that has already
221
+ bound --av-ink and its kin. Escapes are never
222
+ written to a file
223
+ --format <f> text (default) or json, the whole report
224
+ --relation <n> tree, matrix, layer: draw over this relation only;
225
+ graph: keep this predicate (repeatable)
226
+ --root <path> tree: draw only the subtree under this node;
227
+ repeatable
228
+ --order <o> matrix: canon (default) or partition
229
+ --closure matrix: mark transitively reachable cells +
230
+ --group-by <k> graph: one subgraph per distinct value of field k;
231
+ layer: one band per value (required)
232
+ --layers <a,b> layer: the bands in this order, top first; without
233
+ it the order is derived from the relation
234
+ --edges <e> layer: which of the relation's edges to draw over
235
+ the bands -- upward (the violations, the default
236
+ for text and svg), all (mermaid's default) or none
237
+ --label <k> graph: label each node with field k
238
+ --sets <path> sets: the map whose keys are the sets
239
+ --member <k> sets: the field holding each set's members
240
+ --universe <p> sets: the full element domain, so the empty
241
+ column exists
242
+ --min-degree <n> sets: drop intersections below this degree
243
+ --min-size <n> layers: drop intersections below this many paths
244
+ --max-cols <n> sets, layers: elide columns beyond this many
245
+ --profile <p> poset: values | defaults (default) | gen
246
+
247
+ View exit codes: 0 rendered, 1 --check mismatch or lossy under
248
+ --strict, 2 usage or --max-rows exceeded, 4 the document does not stand
249
+ up on its own, or a relation, root or path that names nothing.
250
+
178
251
  Set options:
179
252
  --entry <file> The document the change is checked against
180
253
  --overlay <file> The file the change is appended to (created if
@@ -1711,6 +1784,69 @@ const REACHES_EXIT: Record<ReachVerdict, number> = {
1711
1784
  error: 4,
1712
1785
  }
1713
1786
 
1787
+ const VIEW_HELP =
1788
+ 'aontu view <kind> [options] <file>... (try --help)'
1789
+
1790
+ const VIEW_KINDS: ViewKind[] =
1791
+ ['doc', 'tree', 'matrix', 'graph', 'layer', 'sets', 'layers', 'ladder',
1792
+ 'poset']
1793
+
1794
+ const VIEW_PROFILES: ViewProfile[] = ['text', 'mermaid', 'dot', 'er', 'svg']
1795
+
1796
+ const VIEW_EDGES: ViewEdges[] = ['upward', 'all', 'none']
1797
+
1798
+ // The styles the CLI accepts (VIEWS.0.md, "7. Styling"). `auto` is
1799
+ // here and NOT in ViewStyle: resolving it means knowing whether stdout
1800
+ // is a terminal, which is the CLI's to know and the library's never --
1801
+ // the same division err.ts already draws for the error frames.
1802
+ const VIEW_STYLES = ['auto', 'none', 'ansi', 'css']
1803
+
1804
+ // `--style auto` resolved, which only the CLI can do. The mechanism is
1805
+ // the PROFILE's and the library knows it -- an SVG carries its
1806
+ // stylesheet unless told not to, which is what makes a figure stand
1807
+ // alone. What the library cannot know is whether the DESTINATION is a
1808
+ // terminal, so that is the only thing decided here: escapes on the
1809
+ // text profile when stdout is a terminal and NO_COLOR is unset, the
1810
+ // same two conditions the error frames use. `undefined` leaves the
1811
+ // profile's own default in place.
1812
+ function viewStyleOf(
1813
+ asked: string | undefined, as: ViewProfile | undefined
1814
+ ): ViewStyle | undefined {
1815
+ if (undefined !== asked && 'auto' !== asked) {
1816
+ return asked as ViewStyle
1817
+ }
1818
+ // STDOUT'S OWN TERMINAL-NESS, and NO_COLOR read here rather than
1819
+ // through colorActive(). The figure goes to STDOUT and the error
1820
+ // frames go to STDERR, and they are not the same destination: main()
1821
+ // has already called setColor for stderr, so asking colorActive()
1822
+ // would answer the wrong question twice --- no escapes for
1823
+ // `aontu view tree m.aon 2>/dev/null` at a terminal, and escapes
1824
+ // into the pipe for `aontu view tree m.aon | less`. The NO_COLOR
1825
+ // rule is the one no-color.org states and err.ts implements:
1826
+ // set, to anything but empty, means no colour.
1827
+ const no = process.env.NO_COLOR
1828
+ return 'text' === as && true === process.stdout.isTTY
1829
+ && (null == no || '' === no) ? 'ansi' : undefined
1830
+ }
1831
+
1832
+ // The figure was drawn (0, `lossy` included: the loss report says
1833
+ // what it could not draw, and --strict is the gate on that), or the
1834
+ // document could not be drawn (4). An EMPTY figure is a drawing, not
1835
+ // a failure: a model with no links has nothing to draw, honestly.
1836
+ const VIEW_EXIT: Record<ViewVerdict, number> = {
1837
+ rendered: 0,
1838
+ lossy: 0,
1839
+ error: 4,
1840
+ }
1841
+
1842
+ // The refusals that are USAGE, not the document's fault: exit 2, as
1843
+ // every other verb's usage errors do.
1844
+ const VIEW_USAGE_CODES = [
1845
+ 'view_kind_unknown', 'view_profile_unknown', 'view_rows_exceeded',
1846
+ 'view_at_required', 'view_sets_required', 'view_group_required',
1847
+ 'view_document_shape', 'view_style_profile', 'view_style_unknown',
1848
+ ]
1849
+
1714
1850
  const MOD_HELP = 'aontu mod tidy|verify|vendor|manifest [dir] (try --help)'
1715
1851
 
1716
1852
  // The module tooling (G6 phase 3, ts/src/mod-tool.ts). All LOCAL:
@@ -1773,7 +1909,8 @@ function runMod(argv: string[]): number {
1773
1909
  if ('get' === sub || 'publish' === sub) {
1774
1910
  process.stderr.write(
1775
1911
  'aontu: mod ' + sub + ' needs a registry client, which this build ' +
1776
- 'does not ship (docs/capability-review/g6-distribution.md)\n')
1912
+ 'does not ship; vendor the module by hand and run ' +
1913
+ "'aontu mod tidy'\n")
1777
1914
  return 2
1778
1915
  }
1779
1916
 
@@ -2026,7 +2163,7 @@ function runReaches(argv: string[]): number {
2026
2163
 
2027
2164
  if (3 !== rest.length) {
2028
2165
  process.stderr.write(
2029
- `aontu: reaches needs two entities and one file\n${REACHES_HELP}\n`)
2166
+ `aontu: reaches needs two node paths and one file\n${REACHES_HELP}\n`)
2030
2167
  return 2
2031
2168
  }
2032
2169
 
@@ -2074,6 +2211,412 @@ function renderReachesJson(report: ReachReport): string {
2074
2211
  }, 2)
2075
2212
  }
2076
2213
 
2214
+ // ---------------------------------------------------------------------
2215
+ // The tree view (docs/design/VIEWS.0.md, ts/src/view.ts): the drawn
2216
+ // edge set, as text a golden diff can check.
2217
+
2218
+ function runView(argv: string[]): number {
2219
+ const trusted = takeTrust(argv)
2220
+ if (null == trusted) {
2221
+ return 2
2222
+ }
2223
+ argv = trusted.argv
2224
+ const trust = trusted.trust
2225
+ const rest: string[] = []
2226
+ let format: SubsumeFormat = 'text'
2227
+ let out: string | undefined = undefined
2228
+ let check = false
2229
+ let strict = false
2230
+ const relations: string[] = []
2231
+ const roots: string[] = []
2232
+ const opts: ViewOptions = {}
2233
+ // The style ASKED FOR, which may be `auto` -- a word ViewStyle does
2234
+ // not have, because resolving it is the CLI's job.
2235
+ let style: string | undefined = undefined
2236
+
2237
+ // A flag that takes a value, read into `opts` by name.
2238
+ const valued: Record<string, keyof ViewOptions> = {
2239
+ '--as': 'as', '--at': 'at', '--order': 'order', '--group-by': 'groupBy',
2240
+ '--label': 'label', '--sets': 'sets', '--member': 'member',
2241
+ '--universe': 'universe', '--profile': 'profile', '--views': 'views',
2242
+ '--edges': 'edges',
2243
+ }
2244
+ const counted: Record<string, keyof ViewOptions> = {
2245
+ '--max-rows': 'maxRows', '--max-cols': 'maxCols',
2246
+ '--min-degree': 'minDegree', '--min-size': 'minSize',
2247
+ '--depth': 'depth',
2248
+ }
2249
+
2250
+ for (let i = 0; i < argv.length; i++) {
2251
+ const arg = argv[i]
2252
+ if ('-h' === arg || '--help' === arg) {
2253
+ process.stdout.write(HELP)
2254
+ return 0
2255
+ }
2256
+ if ('--format' === arg) {
2257
+ const f = argv[++i]
2258
+ if ('text' !== f && 'json' !== f) {
2259
+ process.stderr.write('aontu: --format needs text or json\n')
2260
+ return 2
2261
+ }
2262
+ format = f
2263
+ }
2264
+ else if ('--relation' === arg) {
2265
+ const relation = argv[++i]
2266
+ if (null == relation || '' === relation) {
2267
+ process.stderr.write('aontu: --relation needs a name\n')
2268
+ return 2
2269
+ }
2270
+ relations.push(relation)
2271
+ }
2272
+ else if ('--root' === arg) {
2273
+ const root = argv[++i]
2274
+ if (null == root) {
2275
+ process.stderr.write('aontu: --root needs a node path\n')
2276
+ return 2
2277
+ }
2278
+ roots.push(root)
2279
+ }
2280
+ else if ('-o' === arg || '--out' === arg) {
2281
+ out = argv[++i]
2282
+ if (null == out) {
2283
+ process.stderr.write('aontu: --out needs a file\n')
2284
+ return 2
2285
+ }
2286
+ }
2287
+ else if ('--style' === arg) {
2288
+ style = argv[++i]
2289
+ if (null == style || !VIEW_STYLES.includes(style)) {
2290
+ process.stderr.write(
2291
+ `aontu: --style needs one of ${VIEW_STYLES.join(', ')}\n`)
2292
+ return 2
2293
+ }
2294
+ }
2295
+ else if ('--check' === arg) {
2296
+ check = true
2297
+ }
2298
+ else if ('--strict' === arg) {
2299
+ strict = true
2300
+ }
2301
+ else if ('--closure' === arg) {
2302
+ opts.closure = true
2303
+ }
2304
+ else if ('--layers' === arg) {
2305
+ const v = argv[++i]
2306
+ if (null == v || '' === v) {
2307
+ process.stderr.write('aontu: --layers needs a comma-separated list\n')
2308
+ return 2
2309
+ }
2310
+ opts.layers = v.split(',')
2311
+ }
2312
+ else if (undefined !== valued[arg]) {
2313
+ const v = argv[++i]
2314
+ if (null == v || '' === v) {
2315
+ process.stderr.write(`aontu: ${arg} needs a value\n`)
2316
+ return 2
2317
+ }
2318
+ (opts as any)[valued[arg]] = v
2319
+ }
2320
+ else if (undefined !== counted[arg]) {
2321
+ const v = argv[++i]
2322
+ if (null == v || !/^[0-9]+$/.test(v)) {
2323
+ process.stderr.write(`aontu: ${arg} needs a count\n`)
2324
+ return 2
2325
+ }
2326
+ (opts as any)[counted[arg]] = parseInt(v, 10)
2327
+ }
2328
+ else if (arg.startsWith('-')) {
2329
+ process.stderr.write(
2330
+ `aontu: unknown view option ${arg} (try --help)\n`)
2331
+ return 2
2332
+ }
2333
+ else {
2334
+ rest.push(arg)
2335
+ }
2336
+ }
2337
+
2338
+ // ESCAPES NEVER GO INTO A FILE. A pinned golden holding terminal
2339
+ // control codes is not a golden anybody can read, and a byte
2340
+ // comparison against one would fail on the reader's terminal
2341
+ // settings. `auto` resolves to `none` there on its own; asking for
2342
+ // `ansi` explicitly is a usage error rather than a silent downgrade,
2343
+ // so a script that wanted colour is told where it went.
2344
+ if ('ansi' === style && (undefined !== out || undefined !== opts.views)) {
2345
+ process.stderr.write(
2346
+ 'aontu: --style ansi writes to a terminal, not to a file\n')
2347
+ return 2
2348
+ }
2349
+
2350
+ // THE VIEW DOCUMENT draws every figure a document declares, so it
2351
+ // names no kind: the declarations do, one each.
2352
+ if (undefined !== opts.views) {
2353
+ // A declaration names its own profile, so the style is left to
2354
+ // each figure's own default; `--style none` still reaches every
2355
+ // one of them, which is how a host page that binds the CSS
2356
+ // variables asks for eight figures without eight stylesheets.
2357
+ opts.style = viewStyleOf(style, undefined)
2358
+ return runViewSet(rest, opts, trust, { format, check, strict, out })
2359
+ }
2360
+
2361
+ if (2 > rest.length) {
2362
+ process.stderr.write(
2363
+ `aontu: view needs a kind and a file\n${VIEW_HELP}\n`)
2364
+ return 2
2365
+ }
2366
+ const kind = rest[0] as ViewKind
2367
+ if (!VIEW_KINDS.includes(kind)) {
2368
+ process.stderr.write(
2369
+ `aontu: unknown view kind ${kind} (the kinds are: ${VIEW_KINDS.join(', ')})\n`)
2370
+ return 2
2371
+ }
2372
+ if (undefined !== opts.as && !VIEW_PROFILES.includes(opts.as)) {
2373
+ process.stderr.write(
2374
+ `aontu: --as needs one of ${VIEW_PROFILES.join(', ')}\n`)
2375
+ return 2
2376
+ }
2377
+ if (undefined !== opts.order && 'canon' !== opts.order && 'partition' !== opts.order) {
2378
+ process.stderr.write('aontu: --order needs canon or partition\n')
2379
+ return 2
2380
+ }
2381
+ if (undefined !== opts.edges && !VIEW_EDGES.includes(opts.edges)) {
2382
+ process.stderr.write(
2383
+ `aontu: --edges needs one of ${VIEW_EDGES.join(', ')}\n`)
2384
+ return 2
2385
+ }
2386
+ if (undefined !== opts.profile && !['values', 'defaults', 'gen'].includes(opts.profile)) {
2387
+ process.stderr.write('aontu: --profile needs values, defaults or gen\n')
2388
+ return 2
2389
+ }
2390
+ if ('poset' !== kind && 2 !== rest.length) {
2391
+ process.stderr.write(`aontu: view ${kind} takes one file\n`)
2392
+ return 2
2393
+ }
2394
+ if ('graph' === kind) {
2395
+ opts.relations = relations
2396
+ }
2397
+ else if (1 < relations.length) {
2398
+ process.stderr.write(`aontu: view ${kind} takes one --relation\n`)
2399
+ return 2
2400
+ }
2401
+ else {
2402
+ opts.relation = relations[0]
2403
+ }
2404
+ if (check && undefined === out) {
2405
+ process.stderr.write('aontu: --check needs --out\n')
2406
+ return 2
2407
+ }
2408
+
2409
+ const files = rest.slice(1)
2410
+ const srcs: string[] = []
2411
+ for (const file of files) {
2412
+ try {
2413
+ srcs.push(readFileSync(file, 'utf8'))
2414
+ }
2415
+ catch (err: any) {
2416
+ process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
2417
+ return 2
2418
+ }
2419
+ }
2420
+
2421
+ const report = view(srcs[0], {
2422
+ ...opts,
2423
+ style: viewStyleOf(style, opts.as ?? viewDefaultProfile(kind)),
2424
+ kind,
2425
+ path: files[0],
2426
+ roots,
2427
+ trust: verbTrust(trust, entryRootOf(files[0])),
2428
+ docs: files.slice(1).map((path, i) => ({ src: srcs[i + 1], path })),
2429
+ })
2430
+
2431
+ if ('json' === format) {
2432
+ process.stdout.write(renderViewJson(report) + '\n')
2433
+ }
2434
+ else if ('error' === report.verdict) {
2435
+ process.stderr.write(
2436
+ (report.errors as VetFinding[]).map(renderFinding).join('\n') + '\n')
2437
+ }
2438
+ else {
2439
+ // THE FIGURE AND NOTHING ELSE on stdout (or in the file): stdout is
2440
+ // what a golden diff reads, and a verdict line would be part of
2441
+ // every drawing. The loss report goes to stderr, so a figure
2442
+ // written to a file still tells the reader what it could not draw.
2443
+ const text = report.text + '\n'
2444
+ if (undefined === out) {
2445
+ process.stdout.write(text)
2446
+ }
2447
+ else if (check) {
2448
+ let have: string | undefined = undefined
2449
+ try {
2450
+ have = readFileSync(out, 'utf8')
2451
+ }
2452
+ catch (_err: any) {
2453
+ // Absent is a mismatch.
2454
+ }
2455
+ if (have !== text) {
2456
+ process.stderr.write(`aontu: ${out} differs from the ${kind} figure\n`)
2457
+ return 1
2458
+ }
2459
+ }
2460
+ else {
2461
+ writeFileSync(out, text, 'utf8')
2462
+ }
2463
+ if (0 < report.loss.length) {
2464
+ process.stderr.write(renderViewLoss(report.loss) + '\n')
2465
+ }
2466
+ }
2467
+
2468
+ if ('error' === report.verdict) {
2469
+ const code = (report.errors as VetFinding[])[0]?.code
2470
+ return VIEW_USAGE_CODES.includes(code) ? 2 : VIEW_EXIT.error
2471
+ }
2472
+ return strict && 'lossy' === report.verdict ? 1 : VIEW_EXIT[report.verdict]
2473
+ }
2474
+
2475
+ // `aontu view --views <path> <file>`: every figure the document
2476
+ // declares, from one evaluation, all or nothing.
2477
+ //
2478
+ // A declared `out` is resolved against the DOCUMENT's own directory,
2479
+ // not the caller's: a view document is committed beside the figures it
2480
+ // gates, and a gate that only passes from one working directory is not
2481
+ // a gate.
2482
+ function runViewSet(
2483
+ rest: string[], opts: ViewOptions, trust: TrustArg,
2484
+ how: { format: SubsumeFormat, check: boolean, strict: boolean, out?: string }
2485
+ ): number {
2486
+ if (1 !== rest.length) {
2487
+ process.stderr.write('aontu: view --views takes one file\n')
2488
+ return 2
2489
+ }
2490
+ if (undefined !== how.out) {
2491
+ process.stderr.write(
2492
+ 'aontu: --out is per figure in a view document; each declares its own\n')
2493
+ return 2
2494
+ }
2495
+ const file = rest[0]
2496
+ let src: string
2497
+ try {
2498
+ src = readFileSync(file, 'utf8')
2499
+ }
2500
+ catch (err: any) {
2501
+ process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
2502
+ return 2
2503
+ }
2504
+
2505
+ const report = viewSet(src, {
2506
+ ...opts, path: file, trust: verbTrust(trust, entryRootOf(file)),
2507
+ })
2508
+
2509
+ if ('json' === how.format) {
2510
+ process.stdout.write(renderViewSetJson(report) + '\n')
2511
+ }
2512
+ else if (undefined !== report.errors) {
2513
+ process.stderr.write(report.errors.map(renderFinding).join('\n') + '\n')
2514
+ }
2515
+ else {
2516
+ for (const fig of report.views) {
2517
+ if (undefined !== fig.errors) {
2518
+ process.stderr.write(`${fig.name} (${fig.kind}):\n` +
2519
+ fig.errors.map(renderFinding).join('\n') + '\n')
2520
+ }
2521
+ else if (0 < fig.loss.length) {
2522
+ process.stderr.write(renderViewLoss(fig.loss)
2523
+ .split('\n').map((l) => `${fig.name} ${l}`).join('\n') + '\n')
2524
+ }
2525
+ }
2526
+ }
2527
+ if ('error' === report.verdict) {
2528
+ return setExit(report)
2529
+ }
2530
+
2531
+ // EVERY FIGURE RENDERED, so the whole set is written -- or, under
2532
+ // --check, the whole set is compared and every difference named.
2533
+ const dir = dirname(resolve(file))
2534
+ let differ = 0
2535
+ for (const fig of report.views) {
2536
+ const path = resolve(dir, fig.out)
2537
+ const text = fig.text + '\n'
2538
+ if (how.check) {
2539
+ let have: string | undefined = undefined
2540
+ try {
2541
+ have = readFileSync(path, 'utf8')
2542
+ }
2543
+ catch (_err: any) {
2544
+ // Absent is a mismatch.
2545
+ }
2546
+ if (have !== text) {
2547
+ differ++
2548
+ process.stderr.write(
2549
+ `aontu: ${fig.out} differs from the ${fig.name} figure\n`)
2550
+ }
2551
+ }
2552
+ else {
2553
+ try {
2554
+ writeFileSync(path, text, 'utf8')
2555
+ }
2556
+ catch (err: any) {
2557
+ process.stderr.write(`aontu: cannot write ${err.path}: ${err.message}\n`)
2558
+ return 2
2559
+ }
2560
+ if ('json' !== how.format) {
2561
+ process.stderr.write(`wrote ${fig.out} ${fig.name} (${fig.kind})\n`)
2562
+ }
2563
+ }
2564
+ }
2565
+ if (0 < differ) {
2566
+ return 1
2567
+ }
2568
+ return how.strict && 'lossy' === report.verdict ? 1 : VIEW_EXIT[report.verdict]
2569
+ }
2570
+
2571
+
2572
+ // A set's exit code is the worst of its figures': a usage refusal
2573
+ // anywhere is usage, and any other refusal is the document's fault.
2574
+ function setExit(report: ViewSetReport): number {
2575
+ const codes = [
2576
+ ...(report.errors ?? []),
2577
+ ...report.views.flatMap((v) => v.errors ?? []),
2578
+ ].map((e) => e.code)
2579
+ return codes.some((c) => VIEW_USAGE_CODES.includes(c))
2580
+ ? 2 : VIEW_EXIT.error
2581
+ }
2582
+
2583
+
2584
+ function renderViewSetJson(report: ViewSetReport): string {
2585
+ return exactJSON({
2586
+ aontu: { version: version(), verb: 'view' },
2587
+ verdict: report.verdict,
2588
+ views: report.views.map((v: ViewFigure) => ({
2589
+ name: v.name,
2590
+ kind: v.kind,
2591
+ out: v.out,
2592
+ verdict: v.verdict,
2593
+ ...(null == v.text ? {} : { text: v.text }),
2594
+ loss: v.loss,
2595
+ ...(null == v.errors ? {} : { errors: v.errors }),
2596
+ })),
2597
+ ...(null == report.errors ? {} : { errors: report.errors }),
2598
+ }, 2)
2599
+ }
2600
+
2601
+
2602
+ // One line per code: the code, the count, and the detail if any.
2603
+ function renderViewLoss(loss: ViewLoss[]): string {
2604
+ return loss.map((l) =>
2605
+ `${l.code} ${l.count}` +
2606
+ (undefined === l.detail ? '' : ' ' + l.detail.join(' '))).join('\n')
2607
+ }
2608
+
2609
+ function renderViewJson(report: ViewReport): string {
2610
+ return exactJSON({
2611
+ aontu: { version: version(), verb: 'view' },
2612
+ kind: report.kind,
2613
+ verdict: report.verdict,
2614
+ ...(null == report.text ? {} : { text: report.text }),
2615
+ loss: report.loss,
2616
+ ...(null == report.errors ? {} : { errors: report.errors }),
2617
+ }, 2)
2618
+ }
2619
+
2077
2620
  function renderRelationsText(report: RelationReport): string {
2078
2621
  const head = `verdict: ${report.verdict}`
2079
2622
  // WHY, when the document could not be evaluated at all: rendered as
@@ -2088,11 +2631,8 @@ function renderRelationsText(report: RelationReport): string {
2088
2631
  const lines = report.findings.map((f) =>
2089
2632
  'relation_cycle' === f.code
2090
2633
  ? `${f.at} ${f.relation}: cycle ${f.detail.join(' -> ')}`
2091
- : 'relation_target_unmet' === f.code
2092
- ? `${f.at} ${f.relation}: ${f.detail[1]} is not what ` +
2093
- `${f.relation} targets (${f.detail[2]})`
2094
- : `${f.at} ${f.relation}: ${f.detail[1]} does not list ` +
2095
- `${f.detail[0]} under ${f.detail[2]}`)
2634
+ : `${f.at} ${f.relation}: ${f.detail[1]} does not list ` +
2635
+ `${f.detail[0]} under ${f.detail[2]}`)
2096
2636
  return [head, ''].concat(lines).join('\n')
2097
2637
  }
2098
2638
 
@@ -2906,6 +3446,10 @@ function main(argv: string[]): void {
2906
3446
  return finish(runReaches(argv.slice(3)))
2907
3447
  }
2908
3448
 
3449
+ if ('view' === argv[2]) {
3450
+ return finish(runView(argv.slice(3)))
3451
+ }
3452
+
2909
3453
  if ('trim' === argv[2]) {
2910
3454
  return finish(runTrim(argv.slice(3)))
2911
3455
  }
@@ -2989,7 +3533,7 @@ function main(argv: string[]): void {
2989
3533
  else {
2990
3534
  runStdin(mode, trust).then((code) => finish(code))
2991
3535
  }
2992
- } /* node:coverage ignore next 15 */
3536
+ } /* node:coverage ignore next 16 */
2993
3537
 
2994
3538
 
2995
3539
  // No require.main guard here: bin/aontu.js is the executable entry and
@@ -2999,6 +3543,7 @@ function main(argv: string[]): void {
2999
3543
  export {
3000
3544
  evalSource, main, runVet, runSubsume, runBreaking, runTrim, runRelations,
3001
3545
  runReaches,
3546
+ runView,
3002
3547
  runJsonSchema,
3003
3548
  runMod,
3004
3549
  runHash, runGet,
package/src/ctx.ts CHANGED
@@ -123,6 +123,25 @@ class AontuContext {
123
123
  // directly on its Ctx pointer (go/unify.go, maxUniteDepth).
124
124
  _depth: { n: number }
125
125
 
126
+ // The relation declarations this evaluation accumulates (RELATIONS
127
+ // P2): predicate -> what its graph atoms said. One Map per
128
+ // evaluation, created here and inherited by reference through
129
+ // clone()'s prototype chain, exactly as _depth's box is -- a clone
130
+ // made before the first registration must still share the registry.
131
+ _reldecls: Map<string, { acyclic?: boolean, inverses: Set<string> }>
132
+
133
+ // The tree a recursive residual's target resolves against when the
134
+ // meet's own root does not contain it (RECURSION.0.md). Normally
135
+ // undefined: a residual expands by walking ctx.root, and the root
136
+ // holds the definition. An ANCHORED vet run meets a subtree LIFTED
137
+ // out of the settled schema, so `$.spec.Step` names nothing in the
138
+ // meet's root -- the residual held its peer forever and the data
139
+ // under it vetted VALID unchecked. Vet sets this to the settled
140
+ // schema root for anchored runs; RecurseVal.body falls back to it
141
+ // only when the root walk finds nothing. Inherited by clone()
142
+ // through the prototype chain.
143
+ _fixroot: any
144
+
126
145
  // The evaluation budgets (G5 trust profile, docs/trust.md): integer
127
146
  // counts of engine events, never wall-clock. Always present, defaults
128
147
  // from the shared spec-visible constants (test/spec/budget.tsv), so
@@ -185,6 +204,7 @@ class AontuContext {
185
204
  this._pathTrie = new Map()
186
205
  this._pathidxNext = { n: 1 } // 0 reserved for the root path
187
206
  this._depth = { n: 0 }
207
+ this._reldecls = new Map()
188
208
  this._pathidx = 0
189
209
 
190
210
  this.manifest = []
package/src/diff.ts CHANGED
@@ -56,8 +56,10 @@ export type DiffOptions = {
56
56
  // The trust profile this run evaluates under (G5, docs/trust.md).
57
57
  // The source arrives from a caller, so the caller must be able to
58
58
  // say what it may reach: without this the include chain is the
59
- // default one, and `@"x.js"` is arbitrary code execution in the
60
- // evaluating process. A server passes `{include:'none'}`.
59
+ // default one, and `@"../../etc/passwd.aon"` reads whatever the
60
+ // process can. (`@"x.js"` no longer executes -- ADR-012 refuses the
61
+ // extension -- but reading is enough.) A server passes
62
+ // `{include:'none'}`.
61
63
  trust?: TrustOptions
62
64
  }
63
65