aontu 0.52.1 → 0.54.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 (310) hide show
  1. package/README.md +88 -0
  2. package/bin/aontu-mcp.js +4 -0
  3. package/dist/agentsmd.d.ts +16 -0
  4. package/dist/agentsmd.js +107 -0
  5. package/dist/agentsmd.js.map +1 -0
  6. package/dist/aontu.d.ts +15 -3
  7. package/dist/aontu.js +126 -5
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +45 -1
  10. package/dist/cli.js +2845 -69
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +18 -0
  13. package/dist/ctx.js +44 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/diff.d.ts +22 -0
  16. package/dist/diff.js +141 -0
  17. package/dist/diff.js.map +1 -0
  18. package/dist/err.d.ts +3 -1
  19. package/dist/err.js +45 -8
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +13 -0
  22. package/dist/graph.js +110 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +145 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +235 -8
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +425 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +955 -110
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +343 -48
  36. package/dist/lsp.js.map +1 -1
  37. package/dist/mcp-server.d.ts +20 -0
  38. package/dist/mcp-server.js +147 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp.d.ts +42 -0
  41. package/dist/mcp.js +941 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +532 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +35 -0
  47. package/dist/mod.js +345 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +49 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +41 -0
  53. package/dist/provenance.js +336 -0
  54. package/dist/provenance.js.map +1 -0
  55. package/dist/query.d.ts +27 -0
  56. package/dist/query.js +294 -0
  57. package/dist/query.js.map +1 -0
  58. package/dist/reach.d.ts +15 -0
  59. package/dist/reach.js +168 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +23 -0
  62. package/dist/relation.js +230 -0
  63. package/dist/relation.js.map +1 -0
  64. package/dist/report-sarif.d.ts +14 -0
  65. package/dist/report-sarif.js +102 -0
  66. package/dist/report-sarif.js.map +1 -0
  67. package/dist/sig.d.ts +25 -0
  68. package/dist/sig.js +277 -0
  69. package/dist/sig.js.map +1 -0
  70. package/dist/sigdecl.d.ts +2 -0
  71. package/dist/sigdecl.js +11 -0
  72. package/dist/sigdecl.js.map +1 -0
  73. package/dist/siggate.d.ts +4 -0
  74. package/dist/siggate.js +90 -0
  75. package/dist/siggate.js.map +1 -0
  76. package/dist/site.d.ts +4 -0
  77. package/dist/site.js +31 -0
  78. package/dist/site.js.map +1 -1
  79. package/dist/std.d.ts +1 -0
  80. package/dist/std.js +129 -0
  81. package/dist/std.js.map +1 -0
  82. package/dist/subsume.d.ts +39 -0
  83. package/dist/subsume.js +573 -0
  84. package/dist/subsume.js.map +1 -0
  85. package/dist/trim.d.ts +19 -0
  86. package/dist/trim.js +155 -0
  87. package/dist/trim.js.map +1 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/dist/type.d.ts +17 -1
  90. package/dist/type.js.map +1 -1
  91. package/dist/unify.d.ts +2 -1
  92. package/dist/unify.js +234 -38
  93. package/dist/unify.js.map +1 -1
  94. package/dist/utility.d.ts +8 -1
  95. package/dist/utility.js +68 -1
  96. package/dist/utility.js.map +1 -1
  97. package/dist/val/AggFuncVal.d.ts +44 -0
  98. package/dist/val/AggFuncVal.js +364 -0
  99. package/dist/val/AggFuncVal.js.map +1 -0
  100. package/dist/val/ArithFuncVal.d.ts +31 -0
  101. package/dist/val/ArithFuncVal.js +62 -0
  102. package/dist/val/ArithFuncVal.js.map +1 -0
  103. package/dist/val/BagVal.d.ts +7 -0
  104. package/dist/val/BagVal.js +150 -11
  105. package/dist/val/BagVal.js.map +1 -1
  106. package/dist/val/CloseFuncVal.js +9 -1
  107. package/dist/val/CloseFuncVal.js.map +1 -1
  108. package/dist/val/ConjunctVal.d.ts +1 -1
  109. package/dist/val/ConjunctVal.js +19 -0
  110. package/dist/val/ConjunctVal.js.map +1 -1
  111. package/dist/val/ConstraintVal.d.ts +7 -1
  112. package/dist/val/ConstraintVal.js +523 -45
  113. package/dist/val/ConstraintVal.js.map +1 -1
  114. package/dist/val/ContainerKindVal.d.ts +35 -0
  115. package/dist/val/ContainerKindVal.js +99 -0
  116. package/dist/val/ContainerKindVal.js.map +1 -0
  117. package/dist/val/CopyFuncVal.d.ts +1 -2
  118. package/dist/val/CopyFuncVal.js.map +1 -1
  119. package/dist/val/Decimal.d.ts +1 -0
  120. package/dist/val/Decimal.js +13 -0
  121. package/dist/val/Decimal.js.map +1 -1
  122. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  123. package/dist/val/DeprecateFuncVal.js +47 -0
  124. package/dist/val/DeprecateFuncVal.js.map +1 -0
  125. package/dist/val/DisjunctVal.d.ts +1 -2
  126. package/dist/val/DisjunctVal.js +269 -53
  127. package/dist/val/DisjunctVal.js.map +1 -1
  128. package/dist/val/EachFuncVal.d.ts +15 -0
  129. package/dist/val/EachFuncVal.js +75 -0
  130. package/dist/val/EachFuncVal.js.map +1 -0
  131. package/dist/val/ExpectVal.js +62 -4
  132. package/dist/val/ExpectVal.js.map +1 -1
  133. package/dist/val/FeatureVal.js +1 -1
  134. package/dist/val/FeatureVal.js.map +1 -1
  135. package/dist/val/FilterFuncVal.d.ts +15 -0
  136. package/dist/val/FilterFuncVal.js +91 -0
  137. package/dist/val/FilterFuncVal.js.map +1 -0
  138. package/dist/val/FuncBaseVal.d.ts +7 -1
  139. package/dist/val/FuncBaseVal.js +183 -2
  140. package/dist/val/FuncBaseVal.js.map +1 -1
  141. package/dist/val/GraphAtomVal.d.ts +39 -0
  142. package/dist/val/GraphAtomVal.js +184 -0
  143. package/dist/val/GraphAtomVal.js.map +1 -0
  144. package/dist/val/HideFuncVal.js.map +1 -1
  145. package/dist/val/JunctionVal.js +24 -1
  146. package/dist/val/JunctionVal.js.map +1 -1
  147. package/dist/val/KeyFuncVal.d.ts +1 -1
  148. package/dist/val/KeyFuncVal.js +38 -30
  149. package/dist/val/KeyFuncVal.js.map +1 -1
  150. package/dist/val/ListVal.js +94 -14
  151. package/dist/val/ListVal.js.map +1 -1
  152. package/dist/val/LowerFuncVal.js.map +1 -1
  153. package/dist/val/MapVal.d.ts +1 -0
  154. package/dist/val/MapVal.js +146 -14
  155. package/dist/val/MapVal.js.map +1 -1
  156. package/dist/val/MatchFuncVal.d.ts +15 -0
  157. package/dist/val/MatchFuncVal.js +107 -0
  158. package/dist/val/MatchFuncVal.js.map +1 -0
  159. package/dist/val/MoveFuncVal.js.map +1 -1
  160. package/dist/val/NilVal.js +24 -0
  161. package/dist/val/NilVal.js.map +1 -1
  162. package/dist/val/OpBaseVal.d.ts +1 -1
  163. package/dist/val/OpBaseVal.js +19 -1
  164. package/dist/val/OpBaseVal.js.map +1 -1
  165. package/dist/val/OpenFuncVal.js +4 -1
  166. package/dist/val/OpenFuncVal.js.map +1 -1
  167. package/dist/val/PackFuncVal.d.ts +15 -0
  168. package/dist/val/PackFuncVal.js +108 -0
  169. package/dist/val/PackFuncVal.js.map +1 -0
  170. package/dist/val/PathFuncVal.d.ts +2 -2
  171. package/dist/val/PathFuncVal.js +75 -16
  172. package/dist/val/PathFuncVal.js.map +1 -1
  173. package/dist/val/PathVal.d.ts +25 -0
  174. package/dist/val/PathVal.js +150 -0
  175. package/dist/val/PathVal.js.map +1 -0
  176. package/dist/val/PlaceVal.d.ts +13 -0
  177. package/dist/val/PlaceVal.js +131 -0
  178. package/dist/val/PlaceVal.js.map +1 -0
  179. package/dist/val/PlusOpVal.d.ts +2 -1
  180. package/dist/val/PlusOpVal.js +61 -37
  181. package/dist/val/PlusOpVal.js.map +1 -1
  182. package/dist/val/PrefFuncVal.js.map +1 -1
  183. package/dist/val/PrefVal.d.ts +4 -2
  184. package/dist/val/PrefVal.js +223 -41
  185. package/dist/val/PrefVal.js.map +1 -1
  186. package/dist/val/RecurseVal.d.ts +19 -0
  187. package/dist/val/RecurseVal.js +217 -0
  188. package/dist/val/RecurseVal.js.map +1 -0
  189. package/dist/val/RefVal.d.ts +3 -2
  190. package/dist/val/RefVal.js +181 -66
  191. package/dist/val/RefVal.js.map +1 -1
  192. package/dist/val/ReferFuncVal.d.ts +59 -0
  193. package/dist/val/ReferFuncVal.js +604 -0
  194. package/dist/val/ReferFuncVal.js.map +1 -0
  195. package/dist/val/ScalarKindVal.d.ts +4 -3
  196. package/dist/val/ScalarKindVal.js +12 -12
  197. package/dist/val/ScalarKindVal.js.map +1 -1
  198. package/dist/val/SuperFuncVal.d.ts +4 -2
  199. package/dist/val/SuperFuncVal.js +118 -14
  200. package/dist/val/SuperFuncVal.js.map +1 -1
  201. package/dist/val/TopVal.d.ts +1 -1
  202. package/dist/val/TopVal.js.map +1 -1
  203. package/dist/val/TypeFuncVal.js.map +1 -1
  204. package/dist/val/UpperFuncVal.js.map +1 -1
  205. package/dist/val/Val.d.ts +7 -1
  206. package/dist/val/Val.js +170 -2
  207. package/dist/val/Val.js.map +1 -1
  208. package/dist/val/VarVal.js.map +1 -1
  209. package/dist/val/arith.d.ts +6 -0
  210. package/dist/val/arith.js +173 -0
  211. package/dist/val/arith.js.map +1 -0
  212. package/dist/vet.d.ts +45 -0
  213. package/dist/vet.js +802 -0
  214. package/dist/vet.js.map +1 -0
  215. package/dist/view.d.ts +85 -0
  216. package/dist/view.js +1948 -0
  217. package/dist/view.js.map +1 -0
  218. package/dist/walk.d.ts +2 -0
  219. package/dist/walk.js +91 -0
  220. package/dist/walk.js.map +1 -0
  221. package/grammar/aontu.gbnf +138 -0
  222. package/grammar/aontu.lark +118 -0
  223. package/grammar/aontu.tmLanguage.json +184 -0
  224. package/package.json +30 -8
  225. package/skill/SKILL.md +37 -0
  226. package/skill/error-codes.md +62 -0
  227. package/skill/examples.md +99 -0
  228. package/skill/grammar-card.md +56 -0
  229. package/src/agentsmd.ts +135 -0
  230. package/src/aontu.ts +166 -5
  231. package/src/cli.ts +3324 -72
  232. package/src/ctx.ts +93 -1
  233. package/src/diff.ts +198 -0
  234. package/src/err.ts +50 -8
  235. package/src/graph.ts +185 -0
  236. package/src/hcanon.ts +167 -0
  237. package/src/hints.ts +291 -8
  238. package/src/jsonschema.ts +552 -0
  239. package/src/lang.ts +1049 -110
  240. package/src/lsp.ts +351 -49
  241. package/src/mcp-server.ts +187 -0
  242. package/src/mcp.ts +1130 -0
  243. package/src/mod-tool.ts +718 -0
  244. package/src/mod.ts +453 -0
  245. package/src/patch.ts +628 -0
  246. package/src/provenance.ts +437 -0
  247. package/src/query.ts +381 -0
  248. package/src/reach.ts +213 -0
  249. package/src/relation.ts +298 -0
  250. package/src/report-sarif.ts +137 -0
  251. package/src/sig.ts +345 -0
  252. package/src/sigdecl.ts +11 -0
  253. package/src/siggate.ts +144 -0
  254. package/src/site.ts +36 -1
  255. package/src/std.ts +131 -0
  256. package/src/subsume.ts +739 -0
  257. package/src/trim.ts +195 -0
  258. package/src/tsconfig.json +10 -4
  259. package/src/type.ts +51 -2
  260. package/src/unify.ts +253 -38
  261. package/src/utility.ts +79 -1
  262. package/src/val/AggFuncVal.ts +546 -0
  263. package/src/val/ArithFuncVal.ts +108 -0
  264. package/src/val/BagVal.ts +158 -12
  265. package/src/val/CloseFuncVal.ts +9 -1
  266. package/src/val/ConjunctVal.ts +20 -0
  267. package/src/val/ConstraintVal.ts +576 -48
  268. package/src/val/ContainerKindVal.ts +158 -0
  269. package/src/val/CopyFuncVal.ts +0 -1
  270. package/src/val/Decimal.ts +15 -0
  271. package/src/val/DeprecateFuncVal.ts +84 -0
  272. package/src/val/DisjunctVal.ts +282 -59
  273. package/src/val/EachFuncVal.ts +133 -0
  274. package/src/val/ExpectVal.ts +65 -8
  275. package/src/val/FeatureVal.ts +1 -1
  276. package/src/val/FilterFuncVal.ts +154 -0
  277. package/src/val/FuncBaseVal.ts +206 -2
  278. package/src/val/GraphAtomVal.ts +264 -0
  279. package/src/val/HideFuncVal.ts +0 -2
  280. package/src/val/JunctionVal.ts +24 -1
  281. package/src/val/KeyFuncVal.ts +39 -35
  282. package/src/val/ListVal.ts +101 -15
  283. package/src/val/LowerFuncVal.ts +0 -1
  284. package/src/val/MapVal.ts +153 -14
  285. package/src/val/MatchFuncVal.ts +176 -0
  286. package/src/val/MoveFuncVal.ts +0 -2
  287. package/src/val/NilVal.ts +25 -0
  288. package/src/val/OpBaseVal.ts +20 -1
  289. package/src/val/OpenFuncVal.ts +4 -2
  290. package/src/val/PackFuncVal.ts +175 -0
  291. package/src/val/PathFuncVal.ts +107 -20
  292. package/src/val/PathVal.ts +221 -0
  293. package/src/val/PlaceVal.ts +193 -0
  294. package/src/val/PlusOpVal.ts +67 -39
  295. package/src/val/PrefFuncVal.ts +0 -1
  296. package/src/val/PrefVal.ts +251 -60
  297. package/src/val/RecurseVal.ts +285 -0
  298. package/src/val/RefVal.ts +183 -78
  299. package/src/val/ReferFuncVal.ts +732 -0
  300. package/src/val/ScalarKindVal.ts +12 -13
  301. package/src/val/SuperFuncVal.ts +137 -13
  302. package/src/val/TopVal.ts +1 -2
  303. package/src/val/TypeFuncVal.ts +0 -2
  304. package/src/val/UpperFuncVal.ts +0 -1
  305. package/src/val/Val.ts +216 -4
  306. package/src/val/VarVal.ts +0 -1
  307. package/src/val/arith.ts +319 -0
  308. package/src/vet.ts +1025 -0
  309. package/src/view.ts +2563 -0
  310. package/src/walk.ts +99 -0
package/src/ctx.ts CHANGED
@@ -21,6 +21,11 @@ type AontuContextConfig = {
21
21
  cc?: number
22
22
  err?: any[] // Omit<NilVal[], "push">
23
23
  explain?: any[] | boolean | null
24
+
25
+ // The provenance recorder (G7 phase 3), or absent for an
26
+ // uninstrumented run. Shared by reference down every descend, as the
27
+ // error list is, so one run has one record.
28
+ prov?: any
24
29
  fs?: any
25
30
  path?: string[]
26
31
  root?: Val
@@ -41,6 +46,26 @@ class AontuContext {
41
46
  path: string[] // Path to current Val.
42
47
  vc: number // Val counter to create unique val ids.
43
48
  cc: number = -1
49
+
50
+ // THE STAGING RULE (G8 phase 0,
51
+ // docs/capability-review/g8-generation.md). A value whose answer
52
+ // depends on WHERE IT IS -- `key()` today, the generation
53
+ // combinators next -- must not answer while anything is still
54
+ // moving it: resolved early it reports the position it was WRITTEN
55
+ // at rather than the one it ends up at. Such a value RESIDUATES
56
+ // while this is false, and fires exactly once on the pass where it
57
+ // is true.
58
+ //
59
+ // The pass loop (ts/src/unify.ts) sets it on the first pass whose
60
+ // input tree is IDENTICAL to the previous pass's: everything that
61
+ // was going to move has moved, and what is left is the staged
62
+ // values themselves, which is precisely the moment they may answer.
63
+ // It replaces a `ctx.cc < 3` pass count in KeyFuncVal -- a magic
64
+ // number, right for the documents it was tuned on and silently
65
+ // wrong for anything that took a fourth pass to place a value. The
66
+ // comment it replaces said as much: "this delay makes keys in
67
+ // spreads and refs work, but it is a hack - find a better way".
68
+ settle: boolean = false
44
69
  vars: Record<string, Val> = {}
45
70
  src?: string
46
71
  fs?: FST
@@ -50,6 +75,25 @@ class AontuContext {
50
75
 
51
76
  collect: boolean
52
77
 
78
+ // THE COMPLETENESS PROBE (the review's finding C). vet detects
79
+ // residue by GENERATING the anchored meet and keeping the
80
+ // incomplete-class failures. Generation honours the OUTPUT marks --
81
+ // `type()` and `hide()` say "do not emit this" -- so a `--at` anchor
82
+ // sitting under a mark generated nothing at all, reported nothing,
83
+ // and vetted VALID for data missing a required field, while the same
84
+ // anchor without the mark answered incomplete (use-cases/BUGS.md
85
+ // §14). A mark is a decision about OUTPUT; it is not a statement
86
+ // about what the data must satisfy, and `--at` names the truth to
87
+ // validate against explicitly. Under this flag the generation walk
88
+ // descends through marked values; nothing else changes, and no
89
+ // output is produced from a probe run -- only its findings are read.
90
+ probe: boolean = false
91
+
92
+ // The provenance recorder (G7 phase 3), or undefined for an
93
+ // uninstrumented run. Inherited by every descended and cloned
94
+ // context through the prototype chain, so one run has one record.
95
+ prov?: any
96
+
53
97
  // errlist: Omit<NilVal[], "push"> // Nil error log of current unify.
54
98
  err: any[]
55
99
  explain: any[] | null
@@ -71,7 +115,7 @@ class AontuContext {
71
115
  _pathTrie: Map<number, Map<string, { idx: number, path: string[] }>>
72
116
  _pathidxNext: { n: number }
73
117
 
74
- // Current `unite` recursion depth, checked against MAXDEPTH
118
+ // Current `unite` recursion depth, checked against the depth budget
75
119
  // (ts/src/unify.ts). Held in a shared mutable box, like _pathidxNext,
76
120
  // because clone() uses Object.create: the box is inherited by
77
121
  // reference, so a nested clone's increments are visible to the frame
@@ -79,6 +123,40 @@ class AontuContext {
79
123
  // directly on its Ctx pointer (go/unify.go, maxUniteDepth).
80
124
  _depth: { n: number }
81
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
+
145
+ // The evaluation budgets (G5 trust profile, docs/trust.md): integer
146
+ // counts of engine events, never wall-clock. Always present, defaults
147
+ // from the shared spec-visible constants (test/spec/budget.tsv), so
148
+ // the hot-path reads in unify.ts are plain property loads. Inherited
149
+ // by clone() through the prototype chain. `revisits` is NOT profile
150
+ // surface (the Go port has no revisit counter to configure — see
151
+ // TrustBudget in type.ts); it is carried here so unify.ts reads one
152
+ // budget object, at its fixed spec constant.
153
+ budget: { passes: number, revisits: number, depth: number }
154
+
155
+ // The include manifest sink (G5, docs/trust.md): every include the
156
+ // resolver reads is recorded here as { path, capability }, and
157
+ // Aontu.parse() sorts and dedups it onto the result's `deps`.
158
+ manifest: { path: string, capability: string }[]
159
+
82
160
  // Trial mode: set by DisjunctVal.unify while each member is tried
83
161
  // against the peer. When true, makeNilErr returns the shared
84
162
  // TRIAL_NIL sentinel instead of allocating a fresh NilVal, and
@@ -102,6 +180,7 @@ class AontuContext {
102
180
  this.src = cfg.src
103
181
 
104
182
  this.collect = cfg.collect ?? null != cfg.err
183
+ this.prov = cfg.prov
105
184
 
106
185
  this.err = cfg.err ?? []
107
186
  this.explain = Array.isArray(cfg.explain) ? cfg.explain : null
@@ -125,10 +204,23 @@ class AontuContext {
125
204
  this._pathTrie = new Map()
126
205
  this._pathidxNext = { n: 1 } // 0 reserved for the root path
127
206
  this._depth = { n: 0 }
207
+ this._reldecls = new Map()
128
208
  this._pathidx = 0
129
209
 
210
+ this.manifest = []
211
+
130
212
  this.opts = DEFAULT_OPTS()
131
213
  this.addopts(cfg.opts)
214
+
215
+ // Budget defaults are the shared spec-visible constants
216
+ // (test/spec/budget.tsv pins the boundaries in both ports); the
217
+ // trust profile may lower or raise them, deterministically.
218
+ const budget = (this.opts as any).trust?.budget ?? {}
219
+ this.budget = {
220
+ passes: budget.passes ?? 9,
221
+ revisits: 999,
222
+ depth: budget.depth ?? 1000,
223
+ }
132
224
  }
133
225
 
134
226
 
package/src/diff.ts ADDED
@@ -0,0 +1,198 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // PATH-ADDRESSED DIFF (G7 phase 6,
4
+ // docs/capability-review/g7-machine-access.md): what changed, at which
5
+ // paths, between two documents — the dyff-style answer, which
6
+ // deterministic canon makes possible without phantom noise. Two
7
+ // documents that mean the same thing canon the same way, so a diff of
8
+ // canons reports semantic change and not reformatting.
9
+ //
10
+ // The text compared is the HASH FORM (G6's `hcanon`), not the plain
11
+ // canon, for the reason G6 gives: canon drops closedness and the
12
+ // type/hide marks, so a canon diff calls `close({a:1})` and `{a:1}`
13
+ // identical. A false "changed" costs a needless read; a false
14
+ // "unchanged" is a change nobody reviewed, which is the one direction
15
+ // that must not happen.
16
+ //
17
+ // WHETHER a change is BREAKING is a different question, and it belongs
18
+ // to G3: `subsume` and `breaking` answer it with the lattice's own
19
+ // rules. This verb answers "what moved", which is what a reviewer
20
+ // reads first and what an agent needs before it can ask the other
21
+ // question at all.
22
+
23
+ import { Aontu } from './aontu'
24
+ import type { TrustOptions } from './type'
25
+ import { anchorAt } from './vet'
26
+ import type { VetFinding } from './vet'
27
+ import { cmpCodePoint } from './keyorder'
28
+ import { hcanon } from './hcanon'
29
+ import { evalFailure } from './query'
30
+
31
+
32
+ export type DiffKind = 'added' | 'removed' | 'changed'
33
+
34
+ export type DiffChange = {
35
+ kind: DiffKind
36
+ // The canon on each side, absent where there is no value.
37
+ left?: string
38
+ path: string
39
+ right?: string
40
+ }
41
+
42
+ export type DiffReport = {
43
+ changes: DiffChange[]
44
+ findings: VetFinding[]
45
+ ok: boolean
46
+ // True when nothing moved: the two documents mean the same thing.
47
+ same: boolean
48
+ }
49
+
50
+ export type DiffOptions = {
51
+ leftPath?: string
52
+ rightPath?: string
53
+ // Compare at this path of both documents, rather than at the root.
54
+ at?: string
55
+
56
+ // The trust profile this run evaluates under (G5, docs/trust.md).
57
+ // The source arrives from a caller, so the caller must be able to
58
+ // say what it may reach: without this the include chain is the
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'}`.
63
+ trust?: TrustOptions
64
+ }
65
+
66
+
67
+ function pathText(parts: string[]): string {
68
+ return '$' + (0 < parts.length ? '.' + parts.join('.') : '')
69
+ }
70
+
71
+
72
+ // Both sides of one node — never both absent: keys come from the
73
+ // union of the two bags, and list indices run to the longer side, so
74
+ // every walk has at least one value. Bags of the SAME kind recurse,
75
+ // which is what makes the report path-addressed rather than one line
76
+ // saying the whole document changed; everything else compares text.
77
+ function walk(
78
+ left: any, right: any, parts: string[], out: DiffChange[]): void {
79
+ if (null == left) {
80
+ out.push({ kind: 'added', path: pathText(parts), right: hcanon(right) })
81
+ return
82
+ }
83
+ if (null == right) {
84
+ out.push({ kind: 'removed', left: hcanon(left), path: pathText(parts) })
85
+ return
86
+ }
87
+
88
+ const bothMaps = true === left.isMap && true === right.isMap
89
+ const bothLists = true === left.isList && true === right.isList
90
+ if (bothMaps || bothLists) {
91
+ // The bag's OWN attributes, at pseudo-keys under it: a recursing
92
+ // bag never compares its own text, so what the children do not
93
+ // carry has to be compared here. The spread is part of what a bag
94
+ // MEANS; so are closedness and the marks, which is exactly why the
95
+ // hash form spells them (G6).
96
+ const lc = null == left.spread.cj ? undefined : hcanon(left.spread.cj)
97
+ const rc = null == right.spread.cj ? undefined : hcanon(right.spread.cj)
98
+ if (lc !== rc) {
99
+ out.push({
100
+ kind: null == lc ? 'added' : null == rc ? 'removed' : 'changed',
101
+ ...(null == lc ? {} : { left: lc }),
102
+ path: pathText(parts.concat('&')),
103
+ ...(null == rc ? {} : { right: rc }),
104
+ })
105
+ }
106
+ flag(left.closed, right.closed, parts, 'closed', out)
107
+ flag(left.mark?.type, right.mark?.type, parts, 'type', out)
108
+ flag(left.mark?.hide, right.mark?.hide, parts, 'hide', out)
109
+
110
+ const keys = bothMaps
111
+ ? [...new Set([
112
+ ...Object.keys(left.peg), ...Object.keys(right.peg),
113
+ ])].sort(cmpCodePoint)
114
+ : [...new Set([
115
+ ...Object.keys(left.peg), ...Object.keys(right.peg),
116
+ ])].sort((a, b) => Number(a) - Number(b))
117
+ for (const k of keys) {
118
+ walk(left.peg[k], right.peg[k], parts.concat(k), out)
119
+ }
120
+ return
121
+ }
122
+
123
+ const lh = hcanon(left)
124
+ const rh = hcanon(right)
125
+ if (lh !== rh) {
126
+ out.push({ kind: 'changed', left: lh, path: pathText(parts), right: rh })
127
+ }
128
+ }
129
+
130
+
131
+ // One boolean attribute of a bag, as a pseudo-key: `$.a.&closed` says
132
+ // the map at `$.a` was closed (or opened) without saying anything
133
+ // about its keys.
134
+ function flag(
135
+ left: any, right: any, parts: string[], name: string,
136
+ out: DiffChange[]): void {
137
+ const l = true === left
138
+ const r = true === right
139
+ if (l !== r) {
140
+ out.push({
141
+ kind: 'changed',
142
+ left: String(l),
143
+ path: pathText(parts.concat('&' + name)),
144
+ right: String(r),
145
+ })
146
+ }
147
+ }
148
+
149
+
150
+ function evalSide(
151
+ aontu: Aontu, src: string, path: string | undefined, at: string | undefined,
152
+ ): { node?: any, finding?: VetFinding } {
153
+ const ctx = aontu.ctx({ collect: true })
154
+ const parseOpts = null == path ? undefined : { path }
155
+ const root: any = aontu.unify(src, parseOpts, ctx)
156
+ if (0 < ctx.err.length) {
157
+ // The query surface's own fold: a document that does not stand up
158
+ // has no meaning to compare, and the engine's diagnosis is the
159
+ // report.
160
+ return { finding: evalFailure(ctx) }
161
+ }
162
+ const node: any = null == at ? root : anchorAt(root, at)
163
+ if (null == node) {
164
+ return {
165
+ finding: {
166
+ code: 'no_path',
167
+ class: 'reference',
168
+ severity: 'error',
169
+ path: at as string,
170
+ message: `The path ${at} names nothing in this document.`,
171
+ sites: [],
172
+ },
173
+ }
174
+ }
175
+ return { node }
176
+ }
177
+
178
+
179
+ // Diff two documents. Each is evaluated on its own — a document that
180
+ // does not stand up has no meaning to compare, and the report says so
181
+ // rather than diffing a wreck.
182
+ export function diff(
183
+ leftSrc: string, rightSrc: string, opts?: DiffOptions): DiffReport {
184
+ const options = opts ?? {}
185
+ const aontu = new Aontu(
186
+ null == options.trust ? undefined : { trust: options.trust })
187
+
188
+ const l = evalSide(aontu, leftSrc, options.leftPath, options.at)
189
+ const r = evalSide(aontu, rightSrc, options.rightPath, options.at)
190
+ const findings = [l.finding, r.finding].filter(Boolean) as VetFinding[]
191
+ if (0 < findings.length) {
192
+ return { changes: [], findings, ok: false, same: false }
193
+ }
194
+
195
+ const changes: DiffChange[] = []
196
+ walk(l.node, r.node, [], changes)
197
+ return { changes, findings: [], ok: true, same: 0 === changes.length }
198
+ }
package/src/err.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /* Copyright (c) 2021-2025 Richard Rodger, MIT License */
2
2
 
3
3
 
4
+ import { sep } from 'node:path'
5
+
4
6
  import { util } from '@tabnas/jsonic'
5
7
 
6
8
  import { Val, ErrContext } from './type'
@@ -15,6 +17,39 @@ import { hints } from './hints'
15
17
  const { errmsg, strinject } = util
16
18
 
17
19
 
20
+ // COLOUR IS A DECISION ABOUT THE DESTINATION, not about the message.
21
+ // Every error frame hardcoded the ANSI escapes, so a piped report and
22
+ // a `--jsonl` answer carried terminal control codes into whatever read
23
+ // them -- a log file, a CI annotation, an agent's parser (the review's
24
+ // finding F).
25
+ //
26
+ // NO_COLOR (no-color.org: set, to anything, means no colour) turns them
27
+ // off everywhere, library callers included. The CLI additionally turns
28
+ // them off when its stderr is not a terminal, through setColor: a
29
+ // library cannot see the destination, and a caller who has one is the
30
+ // only one who can say.
31
+ let COLOR: boolean | undefined
32
+
33
+ function setColor(on: boolean | undefined): void {
34
+ COLOR = on
35
+ }
36
+
37
+ function colorActive(): boolean {
38
+ if (null != COLOR) {
39
+ return COLOR
40
+ }
41
+ // `globalThis` always exists; `process` need not (this library runs
42
+ // in a browser too), so the optional chain starts at the part that
43
+ // can actually be missing -- and stays a CHAIN rather than becoming
44
+ // an `if`, because the browser arm is unreachable from any test this
45
+ // suite can run and ADR-002 does not accept an arm nothing takes.
46
+ // Set-but-EMPTY is the documented exception and does not disable
47
+ // colour (no-color.org).
48
+ const no = (globalThis as any).process?.env?.NO_COLOR
49
+ return null == no || '' === no
50
+ }
51
+
52
+
18
53
  function getHint(why: any, details?: Record<string, any>): string | undefined {
19
54
  if (hints[why]) {
20
55
  let txt = hints[why]
@@ -61,8 +96,8 @@ function descErr<NILS extends NilVal | NilVal[]>(
61
96
  let v1: any = err.primary
62
97
  let v2: any = err.secondary
63
98
 
64
- let v1src = resolveSrc(v1, errctx, 'primary')
65
- let v2src = resolveSrc(v2, errctx, 'secondary')
99
+ let v1src = resolveSrc(v1, errctx)
100
+ let v2src = resolveSrc(v2, errctx)
66
101
 
67
102
  // STRICT `!==` against the empty string. The loose `!=` here dropped
68
103
  // the list index 0, because `'' != 0` is FALSE in JavaScript ('' and
@@ -83,7 +118,7 @@ function descErr<NILS extends NilVal | NilVal[]>(
83
118
 
84
119
  err.msg = [
85
120
  errmsg({
86
- color: { active: true },
121
+ color: { active: colorActive() },
87
122
  name: 'aontu',
88
123
  code: err.why,
89
124
  txts: {
@@ -99,7 +134,7 @@ function descErr<NILS extends NilVal | NilVal[]>(
99
134
 
100
135
  (null != v1 && errmsg({
101
136
  // TODO: color should come from jsonic config
102
- color: { active: true, line: '\x1b[34m' },
137
+ color: { active: colorActive(), line: '\x1b[34m' },
103
138
  txts: {
104
139
  msg: 'Cannot ' + attempt + ' value: ' + v1.canon +
105
140
  (null == v2 ? '' : ' with value: ' + v2.canon), // + ' #' + err.id,
@@ -115,7 +150,7 @@ function descErr<NILS extends NilVal | NilVal[]>(
115
150
 
116
151
  (null != v2 && errmsg({
117
152
  // TODO: color should come from jsonic config
118
- color: { active: true, line: '\x1b[34m' },
153
+ color: { active: colorActive(), line: '\x1b[34m' },
119
154
  txts: {
120
155
  msg: 'Cannot ' + attempt + ' value: ' + v2.canon +
121
156
  ' with value: ' + v1.canon, // + ' #' + err.id,
@@ -147,13 +182,18 @@ function descErr<NILS extends NilVal | NilVal[]>(
147
182
 
148
183
  function resolveFile(url: string | undefined) {
149
184
  const cwd = process.cwd()
150
- let out = url?.replace(cwd + '/', '') ?? '<no-file>'
185
+ // The PLATFORM'S separator, not '/': a hardcoded slash never matched
186
+ // a Windows cwd, so every Windows report named the absolute path
187
+ // where the POSIX report (and the Go CLI, which prints the entry as
188
+ // typed) named `clash.aon` -- caught by the docs transcript for
189
+ // reading a conflict error, which pins the relative spelling.
190
+ let out = url?.replace(cwd + sep, '') ?? '<no-file>'
151
191
  out = out === cwd || '' === out ? '<no-file>' : out
152
192
  return out
153
193
  }
154
194
 
155
195
 
156
- function resolveSrc(v: Val, errctx: ErrContext | undefined, position: string) {
196
+ function resolveSrc(v: Val, errctx: ErrContext | undefined) {
157
197
  let src: string | undefined = undefined
158
198
  const url = v?.site.url
159
199
 
@@ -210,7 +250,7 @@ class AontuError extends Error {
210
250
  }
211
251
 
212
252
  errs: () => NilVal[]
213
- } /* node:coverage ignore next 9 */
253
+ } /* node:coverage ignore next 11 */
214
254
 
215
255
 
216
256
  export {
@@ -218,4 +258,6 @@ export {
218
258
  makeNilErr,
219
259
  descErr,
220
260
  AontuError,
261
+ setColor,
262
+ colorActive,
221
263
  }
package/src/graph.ts ADDED
@@ -0,0 +1,185 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // THE DERIVED STRUCTURE (G4 phase 3,
4
+ // docs/capability-review/g4-identity-relations.md): an evaluated
5
+ // document has, besides its value, a GRAPH — the set of checked links,
6
+ // each from one tree position to another.
7
+ //
8
+ // The graph is PATH-NATIVE (ADR-014). There is no second namespace to
9
+ // index: a node's address is its path, so the entity index the first
10
+ // design carried is exactly the set of paths already in the edges, and
11
+ // the node a link starts at is derived from where the link sits rather
12
+ // than declared by a mark.
13
+ //
14
+ // G4's deliverable is that this exists and is DETERMINISTIC. What is
15
+ // built on it — impact analysis ("what reaches $.services.auth?"),
16
+ // reachability, context-window-sized slices — is a traversal, and its
17
+ // exposure as verbs and projections belongs to G7. Relation properties
18
+ // (acyclicity, inverse consistency) are G4 phase 5's, and consume
19
+ // exactly this edge set.
20
+
21
+ import type { Val } from './type'
22
+
23
+ import { cmpCodePoint } from './keyorder'
24
+
25
+
26
+ export type Edge = {
27
+ // The node the link starts at, as a `$.dotted.path`: the link's own
28
+ // position with the relation key and any list indices stripped. `$`
29
+ // when the link sits at the top of the document.
30
+ from: string
31
+ // The RELATION: the key the link hangs under, so a link inside a
32
+ // list (`dependsOn: [refer() & "$.a"]`) is an edge under `dependsOn`
33
+ // rather than under `0`. A rel()-minted link carries its predicate
34
+ // declared rather than inferred.
35
+ key: string
36
+ // The address, as the link spells it.
37
+ to: string
38
+ // Where the link is, as a `$.dotted.path`, so a report can point at
39
+ // it.
40
+ at: string
41
+ // Set when the link sits inside a `hide()`-marked subtree. The link
42
+ // is still checked and still an edge, but a figure that draws it
43
+ // DISCLOSES what the document hides, so the view extractors skip it
44
+ // and report it as `hidden_contribution`.
45
+ hidden?: true
46
+ }
47
+
48
+ export type Graph = {
49
+ edges: Edge[]
50
+ // The positions of links written under an UNRESOLVED DISJUNCTION.
51
+ // ADR-007: an unresolved disjunction is not a value, so a link
52
+ // beneath one of its arms is not a fact and is not an edge -- but it
53
+ // is not nothing either, and a figure that silently dropped it would
54
+ // be the failure the views exist to avoid. Absent when there are
55
+ // none, so a graph of a decided document is the shape it always was.
56
+ disjunct?: string[]
57
+ }
58
+
59
+
60
+ const formatPath = (path: string[]): string =>
61
+ 0 === path.length ? '$' : '$.' + path.join('.')
62
+
63
+
64
+ // Digits-only segments are list indices, which is exactly how the rest
65
+ // of the engine spells them.
66
+ const isIndex = (seg: string): boolean => /^[0-9]+$/.test(seg)
67
+
68
+
69
+ // The node a link starts at and the relation it hangs under, derived
70
+ // from the link's own position.
71
+ //
72
+ // A DECLARED predicate (rel()-minted) is authoritative: the link is cut
73
+ // at the key the rel() sat on, wherever that is on the way down, which
74
+ // is what makes a MAP-valued relation report the relation rather than
75
+ // the inner label. Without one the relation is INFERRED: strip the list
76
+ // indices, and the first real key above the link is it.
77
+ const cut = (
78
+ at: string[], relkey: string | undefined
79
+ ): { from: string, key: string } => {
80
+ if (undefined !== relkey) {
81
+ for (let i = at.length - 1; 0 <= i; i--) {
82
+ if (at[i] === relkey) {
83
+ return { from: formatPath(at.slice(0, i)), key: relkey }
84
+ }
85
+ }
86
+ }
87
+ let i = at.length - 1
88
+ for (; 0 <= i && isIndex(at[i]); i--) { }
89
+ return 0 > i
90
+ ? { from: formatPath([]), key: relkey ?? '' }
91
+ : { from: formatPath(at.slice(0, i)), key: relkey ?? at[i] }
92
+ }
93
+
94
+
95
+ // The graph of an evaluated tree. Walks POSITIONS, not values: a
96
+ // reference or a spread can put one value object at several positions,
97
+ // and a walk guarded by object identity would find the first and miss
98
+ // every other place it is reached. The guard is therefore the ancestor
99
+ // chain — which is what a cycle actually is.
100
+ export function graphOf(root: Val): Graph {
101
+ const edges: Edge[] = []
102
+ const disjunct: string[] = []
103
+
104
+ // ONE WALK, with `undecided` saying which side of ADR-007 it is on:
105
+ // below an unresolved disjunction every link is a position the
106
+ // document has not decided, and nothing there is an edge.
107
+ const visit = (
108
+ node: any, path: string[], ancestors: Set<any>, hidden: boolean,
109
+ undecided: boolean
110
+ ): void => {
111
+ if (null == node || true !== node.isVal || ancestors.has(node)) {
112
+ return
113
+ }
114
+
115
+ hidden = hidden || true === node.mark?.hide
116
+
117
+ const link = node.link
118
+ if (null != link) {
119
+ if (undecided) {
120
+ disjunct.push(formatPath(path))
121
+ }
122
+ else {
123
+ const { from, key } = cut(path, node.relkey as string | undefined)
124
+ const edge: Edge = { from, key, to: link, at: formatPath(path) }
125
+ if (hidden) {
126
+ edge.hidden = true
127
+ }
128
+ edges.push(edge)
129
+ }
130
+ }
131
+
132
+ // A graph atom is TRANSPARENT here (RELATIONS P2): it carries the
133
+ // field's value at the field's own position, and the graph is about
134
+ // the value.
135
+ if (true === node.isGraphAtom && undefined !== node.held) {
136
+ visit(node.held, path, ancestors, hidden, undecided)
137
+ }
138
+
139
+ // An unresolved conjunction (a link waiting on a peer that never
140
+ // came, a constraint still open) holds its terms at the SAME
141
+ // position; every link among them is written there, and is an
142
+ // edge. This is what a match-selected branch or a deferred refer()
143
+ // looks like after evaluation.
144
+ if (true === node.isConjunct && Array.isArray(node.peg)) {
145
+ ancestors.add(node)
146
+ for (const term of node.peg) {
147
+ visit(term, path, ancestors, hidden, undecided)
148
+ }
149
+ ancestors.delete(node)
150
+ }
151
+
152
+ // AN UNRESOLVED DISJUNCTION IS NOT A VALUE (ADR-007), so a link
153
+ // under one of its arms is not an edge. Its POSITION is collected
154
+ // instead, so a figure can report what the document leaves
155
+ // undecided rather than drawing it or dropping it in silence.
156
+ if (true === node.isDisjunct && Array.isArray(node.peg)) {
157
+ ancestors.add(node)
158
+ for (const arm of node.peg) {
159
+ visit(arm, path, ancestors, hidden, true)
160
+ }
161
+ ancestors.delete(node)
162
+ }
163
+
164
+ if ((true === node.isMap || true === node.isList) && null != node.peg) {
165
+ ancestors.add(node)
166
+ for (const k of Object.keys(node.peg)) {
167
+ visit(node.peg[k], [...path, k], ancestors, hidden, undecided)
168
+ }
169
+ ancestors.delete(node)
170
+ }
171
+ }
172
+
173
+ visit(root, [], new Set(), false, false)
174
+
175
+ // DETERMINISTIC by construction, not by luck: edges by the position
176
+ // they are written at, which is unique — one link, one place. That
177
+ // holds through a conjunction too: its terms share the position, and
178
+ // two links there would have had to unify into one.
179
+ edges.sort((a, b) => cmpCodePoint(a.at, b.at))
180
+
181
+ if (0 < disjunct.length) {
182
+ return { edges, disjunct: [...new Set(disjunct)].sort(cmpCodePoint) }
183
+ }
184
+ return { edges }
185
+ }