aontu 0.62.0 → 0.63.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 (356) hide show
  1. package/README.md +1 -1
  2. package/dist/agentsmd.js +0 -27
  3. package/dist/agentsmd.js.map +1 -1
  4. package/dist/alias.js.map +1 -1
  5. package/dist/allow.js +0 -92
  6. package/dist/allow.js.map +1 -1
  7. package/dist/aontu.d.ts +1 -1
  8. package/dist/aontu.js +1 -79
  9. package/dist/aontu.js.map +1 -1
  10. package/dist/aontumodel.d.ts +4 -0
  11. package/dist/aontumodel.js +33 -0
  12. package/dist/aontumodel.js.map +1 -0
  13. package/dist/cli.js +101 -620
  14. package/dist/cli.js.map +1 -1
  15. package/dist/ctx.js +0 -48
  16. package/dist/ctx.js.map +1 -1
  17. package/dist/diff.js +0 -32
  18. package/dist/diff.js.map +1 -1
  19. package/dist/err.js +0 -40
  20. package/dist/err.js.map +1 -1
  21. package/dist/escape.js +0 -45
  22. package/dist/escape.js.map +1 -1
  23. package/dist/exactjson.d.ts +0 -35
  24. package/dist/exactjson.js +0 -131
  25. package/dist/exactjson.js.map +1 -1
  26. package/dist/format.js +13 -203
  27. package/dist/format.js.map +1 -1
  28. package/dist/grammar.d.ts +9 -0
  29. package/dist/grammar.js +54 -0
  30. package/dist/grammar.js.map +1 -0
  31. package/dist/graph.js +0 -26
  32. package/dist/graph.js.map +1 -1
  33. package/dist/hcanon.js +0 -82
  34. package/dist/hcanon.js.map +1 -1
  35. package/dist/helpdoc.js +1 -1
  36. package/dist/helpdoc.js.map +1 -1
  37. package/dist/hints.d.ts +0 -6
  38. package/dist/hints.js +54 -55
  39. package/dist/hints.js.map +1 -1
  40. package/dist/jsonschema.js +0 -114
  41. package/dist/jsonschema.js.map +1 -1
  42. package/dist/keyorder.d.ts +0 -7
  43. package/dist/keyorder.js +0 -41
  44. package/dist/keyorder.js.map +1 -1
  45. package/dist/lang.js +17 -915
  46. package/dist/lang.js.map +1 -1
  47. package/dist/lower.js +11 -61
  48. package/dist/lower.js.map +1 -1
  49. package/dist/lsp-server.js +0 -16
  50. package/dist/lsp-server.js.map +1 -1
  51. package/dist/lsp.d.ts +1 -1
  52. package/dist/lsp.js +12 -159
  53. package/dist/lsp.js.map +1 -1
  54. package/dist/mcp-server.js +0 -26
  55. package/dist/mcp-server.js.map +1 -1
  56. package/dist/mcp.js +0 -113
  57. package/dist/mcp.js.map +1 -1
  58. package/dist/mod-tool.js +0 -130
  59. package/dist/mod-tool.js.map +1 -1
  60. package/dist/mod.js +0 -162
  61. package/dist/mod.js.map +1 -1
  62. package/dist/patch.js +0 -217
  63. package/dist/patch.js.map +1 -1
  64. package/dist/provenance.js +0 -140
  65. package/dist/provenance.js.map +1 -1
  66. package/dist/query.js +0 -75
  67. package/dist/query.js.map +1 -1
  68. package/dist/reach.js +0 -43
  69. package/dist/reach.js.map +1 -1
  70. package/dist/relation.js +0 -61
  71. package/dist/relation.js.map +1 -1
  72. package/dist/render.js +20 -135
  73. package/dist/render.js.map +1 -1
  74. package/dist/report-sarif.d.ts +0 -11
  75. package/dist/report-sarif.js +0 -28
  76. package/dist/report-sarif.js.map +1 -1
  77. package/dist/sig.js +0 -35
  78. package/dist/sig.js.map +1 -1
  79. package/dist/sigdecl.js +1 -1
  80. package/dist/sigdecl.js.map +1 -1
  81. package/dist/siggate.js +0 -4
  82. package/dist/siggate.js.map +1 -1
  83. package/dist/site.js +3 -29
  84. package/dist/site.js.map +1 -1
  85. package/dist/subsume.d.ts +0 -10
  86. package/dist/subsume.js +0 -137
  87. package/dist/subsume.js.map +1 -1
  88. package/dist/template.d.ts +2 -1
  89. package/dist/template.js +58 -138
  90. package/dist/template.js.map +1 -1
  91. package/dist/trim.js +0 -41
  92. package/dist/trim.js.map +1 -1
  93. package/dist/tsconfig.tsbuildinfo +1 -1
  94. package/dist/type.js.map +1 -1
  95. package/dist/unify.js +12 -242
  96. package/dist/unify.js.map +1 -1
  97. package/dist/utility.js +0 -22
  98. package/dist/utility.js.map +1 -1
  99. package/dist/val/AbnfFuncVal.d.ts +18 -0
  100. package/dist/val/AbnfFuncVal.js +132 -0
  101. package/dist/val/AbnfFuncVal.js.map +1 -0
  102. package/dist/val/AbsentVal.d.ts +11 -0
  103. package/dist/val/AbsentVal.js +30 -0
  104. package/dist/val/AbsentVal.js.map +1 -0
  105. package/dist/val/AggFuncVal.d.ts +10 -1
  106. package/dist/val/AggFuncVal.js +104 -116
  107. package/dist/val/AggFuncVal.js.map +1 -1
  108. package/dist/val/ArithFuncVal.js +0 -12
  109. package/dist/val/ArithFuncVal.js.map +1 -1
  110. package/dist/val/BagVal.js +1 -78
  111. package/dist/val/BagVal.js.map +1 -1
  112. package/dist/val/BigDecimalVal.js +0 -16
  113. package/dist/val/BigDecimalVal.js.map +1 -1
  114. package/dist/val/BigIntegerVal.js +0 -16
  115. package/dist/val/BigIntegerVal.js.map +1 -1
  116. package/dist/val/CloseFuncVal.js +0 -9
  117. package/dist/val/CloseFuncVal.js.map +1 -1
  118. package/dist/val/CmpFuncVal.js +2 -59
  119. package/dist/val/CmpFuncVal.js.map +1 -1
  120. package/dist/val/ConjunctVal.js +0 -29
  121. package/dist/val/ConjunctVal.js.map +1 -1
  122. package/dist/val/ConstraintVal.js +0 -500
  123. package/dist/val/ConstraintVal.js.map +1 -1
  124. package/dist/val/ContainerKindVal.js +0 -2
  125. package/dist/val/ContainerKindVal.js.map +1 -1
  126. package/dist/val/CopyFuncVal.js +0 -3
  127. package/dist/val/CopyFuncVal.js.map +1 -1
  128. package/dist/val/Decimal.js +0 -179
  129. package/dist/val/Decimal.js.map +1 -1
  130. package/dist/val/DeprecateFuncVal.js.map +1 -1
  131. package/dist/val/DisjunctVal.js +0 -152
  132. package/dist/val/DisjunctVal.js.map +1 -1
  133. package/dist/val/EachFuncVal.js +0 -3
  134. package/dist/val/EachFuncVal.js.map +1 -1
  135. package/dist/val/EmitFuncVal.d.ts +1 -1
  136. package/dist/val/EmitFuncVal.js +6 -119
  137. package/dist/val/EmitFuncVal.js.map +1 -1
  138. package/dist/val/ExpectVal.js +0 -62
  139. package/dist/val/ExpectVal.js.map +1 -1
  140. package/dist/val/FilterFuncVal.js +0 -25
  141. package/dist/val/FilterFuncVal.js.map +1 -1
  142. package/dist/val/FuncBaseVal.d.ts +1 -0
  143. package/dist/val/FuncBaseVal.js +7 -127
  144. package/dist/val/FuncBaseVal.js.map +1 -1
  145. package/dist/val/GraphAtomVal.js +0 -15
  146. package/dist/val/GraphAtomVal.js.map +1 -1
  147. package/dist/val/HideFuncVal.js +0 -13
  148. package/dist/val/HideFuncVal.js.map +1 -1
  149. package/dist/val/IntegerVal.js +0 -61
  150. package/dist/val/IntegerVal.js.map +1 -1
  151. package/dist/val/JunctionVal.js +0 -20
  152. package/dist/val/JunctionVal.js.map +1 -1
  153. package/dist/val/KeyFuncVal.js +0 -46
  154. package/dist/val/KeyFuncVal.js.map +1 -1
  155. package/dist/val/ListVal.js +0 -57
  156. package/dist/val/ListVal.js.map +1 -1
  157. package/dist/val/LowerFuncVal.js +0 -11
  158. package/dist/val/LowerFuncVal.js.map +1 -1
  159. package/dist/val/MapVal.js +0 -151
  160. package/dist/val/MapVal.js.map +1 -1
  161. package/dist/val/MatchFuncVal.js +0 -27
  162. package/dist/val/MatchFuncVal.js.map +1 -1
  163. package/dist/val/{FormFuncVal.d.ts → MaybeFuncVal.d.ts} +5 -5
  164. package/dist/val/MaybeFuncVal.js +50 -0
  165. package/dist/val/MaybeFuncVal.js.map +1 -0
  166. package/dist/val/MoveFuncVal.js +0 -18
  167. package/dist/val/MoveFuncVal.js.map +1 -1
  168. package/dist/val/NilVal.js +0 -60
  169. package/dist/val/NilVal.js.map +1 -1
  170. package/dist/val/NomFuncVal.js +8 -42
  171. package/dist/val/NomFuncVal.js.map +1 -1
  172. package/dist/val/NumberVal.js +0 -15
  173. package/dist/val/NumberVal.js.map +1 -1
  174. package/dist/val/OpBaseVal.d.ts +1 -0
  175. package/dist/val/OpBaseVal.js +3 -15
  176. package/dist/val/OpBaseVal.js.map +1 -1
  177. package/dist/val/PackFuncVal.js +0 -34
  178. package/dist/val/PackFuncVal.js.map +1 -1
  179. package/dist/val/PathFuncVal.js +0 -6
  180. package/dist/val/PathFuncVal.js.map +1 -1
  181. package/dist/val/PathVal.js +0 -41
  182. package/dist/val/PathVal.js.map +1 -1
  183. package/dist/val/PlaceVal.js +0 -25
  184. package/dist/val/PlaceVal.js.map +1 -1
  185. package/dist/val/PlusOpVal.d.ts +1 -7
  186. package/dist/val/PlusOpVal.js +13 -74
  187. package/dist/val/PlusOpVal.js.map +1 -1
  188. package/dist/val/PrefFuncVal.js +0 -1
  189. package/dist/val/PrefFuncVal.js.map +1 -1
  190. package/dist/val/PrefVal.js +0 -167
  191. package/dist/val/PrefVal.js.map +1 -1
  192. package/dist/val/RecurseVal.js +0 -55
  193. package/dist/val/RecurseVal.js.map +1 -1
  194. package/dist/val/RefVal.js +0 -282
  195. package/dist/val/RefVal.js.map +1 -1
  196. package/dist/val/ReferFuncVal.js +0 -232
  197. package/dist/val/ReferFuncVal.js.map +1 -1
  198. package/dist/val/ScalarKindVal.js +0 -49
  199. package/dist/val/ScalarKindVal.js.map +1 -1
  200. package/dist/val/ScalarVal.js +0 -11
  201. package/dist/val/ScalarVal.js.map +1 -1
  202. package/dist/val/StrFuncVal.js +0 -18
  203. package/dist/val/StrFuncVal.js.map +1 -1
  204. package/dist/val/SuperFuncVal.js +0 -32
  205. package/dist/val/SuperFuncVal.js.map +1 -1
  206. package/dist/val/TopVal.js +0 -1
  207. package/dist/val/TopVal.js.map +1 -1
  208. package/dist/val/TranslateFuncVal.js +1 -3
  209. package/dist/val/TranslateFuncVal.js.map +1 -1
  210. package/dist/val/UpperFuncVal.js +0 -11
  211. package/dist/val/UpperFuncVal.js.map +1 -1
  212. package/dist/val/Val.d.ts +1 -0
  213. package/dist/val/Val.js +2 -133
  214. package/dist/val/Val.js.map +1 -1
  215. package/dist/val/VarVal.js +0 -12
  216. package/dist/val/VarVal.js.map +1 -1
  217. package/dist/val/arith.js +0 -37
  218. package/dist/val/arith.js.map +1 -1
  219. package/dist/val/caserange.js +0 -61
  220. package/dist/val/caserange.js.map +1 -1
  221. package/dist/val/members.js +0 -6
  222. package/dist/val/members.js.map +1 -1
  223. package/dist/val/numcmp.js +0 -11
  224. package/dist/val/numcmp.js.map +1 -1
  225. package/dist/val/numkind.js +0 -145
  226. package/dist/val/numkind.js.map +1 -1
  227. package/dist/val/valutil.js +0 -16
  228. package/dist/val/valutil.js.map +1 -1
  229. package/dist/vet.js +0 -461
  230. package/dist/vet.js.map +1 -1
  231. package/dist/view.js +0 -414
  232. package/dist/view.js.map +1 -1
  233. package/dist/walk.js +0 -41
  234. package/dist/walk.js.map +1 -1
  235. package/grammar/aontu.abnf +8 -6
  236. package/grammar/aontu.gbnf +4 -4
  237. package/grammar/aontu.lark +4 -4
  238. package/grammar/aontu.tmLanguage.json +1 -1
  239. package/package.json +4 -2
  240. package/src/agentsmd.ts +0 -35
  241. package/src/alias.ts +0 -39
  242. package/src/allow.ts +1 -96
  243. package/src/aontu.ts +3 -110
  244. package/src/aontumodel.ts +32 -0
  245. package/src/cli.ts +113 -682
  246. package/src/ctx.ts +0 -103
  247. package/src/diff.ts +0 -40
  248. package/src/err.ts +0 -40
  249. package/src/escape.ts +0 -46
  250. package/src/exactjson.ts +0 -131
  251. package/src/format.ts +14 -257
  252. package/src/grammar.ts +72 -0
  253. package/src/graph.ts +0 -61
  254. package/src/hcanon.ts +0 -82
  255. package/src/helpdoc.ts +1 -1
  256. package/src/hints.ts +66 -57
  257. package/src/jsonschema.ts +0 -123
  258. package/src/keyorder.ts +0 -42
  259. package/src/lang.ts +19 -931
  260. package/src/lower.ts +12 -62
  261. package/src/lsp-server.ts +0 -16
  262. package/src/lsp.ts +12 -180
  263. package/src/mcp-server.ts +0 -31
  264. package/src/mcp.ts +0 -130
  265. package/src/mod-tool.ts +0 -158
  266. package/src/mod.ts +0 -178
  267. package/src/patch.ts +0 -232
  268. package/src/provenance.ts +0 -183
  269. package/src/query.ts +0 -84
  270. package/src/reach.ts +0 -53
  271. package/src/relation.ts +0 -84
  272. package/src/render.ts +20 -172
  273. package/src/report-sarif.ts +0 -48
  274. package/src/sig.ts +0 -35
  275. package/src/sigdecl.ts +1 -1
  276. package/src/siggate.ts +0 -30
  277. package/src/site.ts +3 -29
  278. package/src/subsume.ts +1 -161
  279. package/src/template.ts +69 -140
  280. package/src/trim.ts +0 -53
  281. package/src/type.ts +2 -45
  282. package/src/unify.ts +13 -251
  283. package/src/utility.ts +0 -31
  284. package/src/val/AbnfFuncVal.ts +181 -0
  285. package/src/val/AbsentVal.ts +54 -0
  286. package/src/val/AggFuncVal.ts +152 -188
  287. package/src/val/ArithFuncVal.ts +0 -20
  288. package/src/val/BagVal.ts +1 -78
  289. package/src/val/BigDecimalVal.ts +0 -16
  290. package/src/val/BigIntegerVal.ts +0 -16
  291. package/src/val/CloseFuncVal.ts +0 -9
  292. package/src/val/CmpFuncVal.ts +4 -166
  293. package/src/val/ConjunctVal.ts +0 -33
  294. package/src/val/ConstraintVal.ts +2 -537
  295. package/src/val/ContainerKindVal.ts +0 -18
  296. package/src/val/CopyFuncVal.ts +0 -5
  297. package/src/val/Decimal.ts +1 -185
  298. package/src/val/DeprecateFuncVal.ts +0 -10
  299. package/src/val/DisjunctVal.ts +0 -157
  300. package/src/val/EachFuncVal.ts +0 -40
  301. package/src/val/EmitFuncVal.ts +8 -208
  302. package/src/val/ExpectVal.ts +0 -62
  303. package/src/val/FilterFuncVal.ts +0 -55
  304. package/src/val/FuncBaseVal.ts +9 -130
  305. package/src/val/GraphAtomVal.ts +0 -42
  306. package/src/val/HideFuncVal.ts +0 -15
  307. package/src/val/IntegerVal.ts +0 -61
  308. package/src/val/JunctionVal.ts +0 -20
  309. package/src/val/KeyFuncVal.ts +0 -48
  310. package/src/val/ListVal.ts +0 -59
  311. package/src/val/LowerFuncVal.ts +0 -12
  312. package/src/val/MapVal.ts +0 -151
  313. package/src/val/MatchFuncVal.ts +0 -59
  314. package/src/val/MaybeFuncVal.ts +86 -0
  315. package/src/val/MoveFuncVal.ts +0 -20
  316. package/src/val/NilVal.ts +0 -60
  317. package/src/val/NomFuncVal.ts +8 -95
  318. package/src/val/NumberVal.ts +0 -16
  319. package/src/val/OpBaseVal.ts +4 -17
  320. package/src/val/PackFuncVal.ts +0 -63
  321. package/src/val/PathFuncVal.ts +0 -32
  322. package/src/val/PathVal.ts +0 -66
  323. package/src/val/PlaceVal.ts +0 -45
  324. package/src/val/PlusOpVal.ts +18 -75
  325. package/src/val/PrefFuncVal.ts +0 -1
  326. package/src/val/PrefVal.ts +0 -179
  327. package/src/val/RecurseVal.ts +0 -81
  328. package/src/val/RefVal.ts +1 -285
  329. package/src/val/ReferFuncVal.ts +0 -255
  330. package/src/val/ScalarKindVal.ts +0 -50
  331. package/src/val/ScalarVal.ts +0 -12
  332. package/src/val/StrFuncVal.ts +0 -44
  333. package/src/val/SuperFuncVal.ts +0 -42
  334. package/src/val/TopVal.ts +0 -1
  335. package/src/val/TranslateFuncVal.ts +1 -51
  336. package/src/val/UpperFuncVal.ts +0 -12
  337. package/src/val/Val.ts +3 -192
  338. package/src/val/VarVal.ts +0 -15
  339. package/src/val/arith.ts +0 -92
  340. package/src/val/caserange.ts +0 -62
  341. package/src/val/members.ts +0 -23
  342. package/src/val/numcmp.ts +1 -27
  343. package/src/val/numkind.ts +0 -149
  344. package/src/val/valutil.ts +0 -16
  345. package/src/vet.ts +1 -582
  346. package/src/view.ts +0 -507
  347. package/src/walk.ts +0 -41
  348. package/dist/std.d.ts +0 -3
  349. package/dist/std.js +0 -672
  350. package/dist/std.js.map +0 -1
  351. package/dist/val/FormFuncVal.js +0 -55
  352. package/dist/val/FormFuncVal.js.map +0 -1
  353. package/dist/val/NamerFuncVal.d.ts +0 -12
  354. package/dist/val/NamerFuncVal.js +0 -176
  355. package/dist/val/NamerFuncVal.js.map +0 -1
  356. package/src/std.ts +0 -683
package/src/cli.ts CHANGED
@@ -1,12 +1,5 @@
1
1
  /* Copyright (c) 2025 Richard Rodger, MIT License */
2
2
 
3
- // Command-line interface for Aontu.
4
- //
5
- // aontu [options] [file]
6
- //
7
- // With a file argument, the file is evaluated and the result printed.
8
- // With no file on an interactive terminal, a REPL is started. With no
9
- // file and piped input, the source is read from stdin. See HELP below.
10
3
 
11
4
  // Named imports, not `import * as`: the namespace form makes tsc emit the
12
5
  import { evalFailure } from './query'
@@ -29,7 +22,10 @@ import {
29
22
  } from './aontu'
30
23
  import type { AllowDecision, AllowReport, AllowVerdict } from './allow'
31
24
  import type { RenderCoverage, RenderReport } from './render'
32
- import { desugarTemplate, resugarTemplate, templateOutputs, markerFor } from './template'
25
+ import {
26
+ desugarTemplate, resugarTemplate, templateOutputs, markerFor,
27
+ markerFromProfiles,
28
+ } from './template'
33
29
  import { outsideRoot } from './mcp'
34
30
  import { sarifReport } from './report-sarif'
35
31
  import { main as lspMain } from './lsp-server'
@@ -90,7 +86,8 @@ const HELP = `Usage: aontu [options] [file]
90
86
  aontu render [--at <path>] [--profile <file>]... [--unit <path>]
91
87
  [--stdout | --out <dir> | --check <dir> | --coverage]
92
88
  [--coverage-at <path>] [--strict] <file>
93
- aontu template [--resugar] [--check] [--marker <token>] <file>
89
+ aontu template [--resugar] [--check] [--marker <token>]
90
+ [--profile <file>] <file>
94
91
  aontu hash [options] <file>
95
92
  aontu mod tidy|verify|vendor|manifest [options] [dir]
96
93
  aontu get <path> [options] <file>
@@ -98,7 +95,8 @@ const HELP = `Usage: aontu [options] [file]
98
95
  aontu set <path>=<value>... --entry <file> --overlay <file>
99
96
  aontu allow --role <role> [--at <path>] <roles-file> <path>...
100
97
  aontu agentsmd [--write <AGENTS.md>] [--depth <n>] <file>
101
- aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] <file>...
98
+ aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>]
99
+ [--profile <file>] <file>...
102
100
  aontu help [topic] [--format text|json]
103
101
  aontu explain <code> | --list [--format text|json]
104
102
  aontu init [dir]
@@ -338,8 +336,9 @@ up on its own, or a relation, root or path that names nothing.
338
336
  Render options:
339
337
  --at <path> Render the value at this path ($.a.b); the root by
340
338
  default
341
- --profile <file> A profile document, profile: {lang, ...}, vetted
342
- against aontu:profile; repeatable, one per language
339
+ --profile <file> A profile document, aontu: render: Lang: {lang,
340
+ ...}, vetted against aontu:render; repeatable,
341
+ one per language
343
342
  --unit <path> Render only the unit with this path
344
343
  --stdout One unit's bytes and nothing else (with --unit when
345
344
  the instance has several)
@@ -363,8 +362,8 @@ does not stand up or the instance is not aontu:code.
363
362
  A render entry file whose extension is not .aon is a TEMPLATE: a
364
363
  generator in the target's own syntax, whose marker lines carry aontu
365
364
  and whose other lines are output. It is desugared before it is
366
- evaluated, and --marker names the marker for a language the table does
367
- not know.
365
+ evaluated, and a language the table does not know names its marker with
366
+ --marker, or declares it once in a profile file that --profile reads.
368
367
 
369
368
  Template options:
370
369
  --resugar The file is the canonical aontu; print the template
@@ -372,7 +371,9 @@ Template options:
372
371
  --check Desugar and resugar, and exit 1 if the file is not
373
372
  what the round trip answers
374
373
  --marker <t> The marker, when the extension does not name it
375
- (default //-, and #- --- /*- by extension)
374
+ (default //-, and #- --- /*- <!--- by extension)
375
+ --profile <f> A profile file, whose template.ext names the
376
+ extensions it marks and template.marker the marker
376
377
 
377
378
  The template verb prints the canonical aontu form of a generator
378
379
  written in the target's own syntax: a marked line is aontu source, and
@@ -465,7 +466,9 @@ Fmt options:
465
466
  shapes, on standard error, and print nothing else
466
467
  --strict With --lint, and exit 1 when there is a finding
467
468
  --marker <t> The file is a generator, and this is its marker
468
- (default //-, and #- --- /*- by extension)
469
+ (default //-, and #- --- /*- <!--- by extension)
470
+ --profile <f> A profile file, whose template.ext names the
471
+ extensions it marks and template.marker the marker
469
472
 
470
473
  The fmt verb prints one document in the agreed form; with no file it
471
474
  reads standard input. Several files need one of the options above.
@@ -512,32 +515,9 @@ function version(): string {
512
515
  }
513
516
 
514
517
 
515
- // The terminal colour escapes the parser puts in its message text. A
516
- // machine-readable report is no place for them, which is the rule
517
- // findingOf states in ts/src/vet.ts; the twin here rather than an
518
- // import because go/cmd/aontu carries its own for the same reason (the
519
- // engine's is not exported to its command).
520
518
  const EVAL_ANSI = new RegExp('\u001b\\[[0-9;]*m', 'g')
521
519
 
522
520
 
523
- // THE ENGINE'S DIAGNOSIS AS A FINDING (G11 phase 7). The bare command
524
- // was the one verb whose failure had no machine-readable form, so the
525
- // default entry point was the one an agent had to parse with a regular
526
- // expression.
527
- //
528
- // THE HEADLINE ONLY, and no `hint`. Both are parity decisions rather
529
- // than economies: the frames under the headline are drawn for a person
530
- // reading a terminal and only the first line is held to byte parity
531
- // between the ports (the rule findingOf states), and the hint TABLES
532
- // are deliberately not in parity while the code registry is -- so a
533
- // hint here would make the two ports answer differently for a code
534
- // only one of them explains. `aontu explain <code>` is where the hint
535
- // lives, which is what phase 3 built it for.
536
- //
537
- // The CLASS comes from the registry rather than from the nil, because
538
- // the registry is what both ports hold set-equal
539
- // (test/spec/errcodes.tsv). Mirrors evalFinding in
540
- // go/cmd/aontu/main.go.
541
521
  function evalFinding(code: string, text: string): VetFinding {
542
522
  return {
543
523
  class: codeClass(code),
@@ -545,10 +525,6 @@ function evalFinding(code: string, text: string): VetFinding {
545
525
  message: text.split('\n')[0].replace(EVAL_ANSI, ''),
546
526
  path: '$',
547
527
  severity: 'error',
548
- // NO SITE. The bare command's failure is the whole document not
549
- // standing up, and the two sites a conflict names are in the
550
- // frames the text form prints; naming one of them here would be a
551
- // choice the engine has not made.
552
528
  sites: [],
553
529
  }
554
530
  }
@@ -562,12 +538,6 @@ function evalSource(
562
538
  mode: Mode,
563
539
  ): { ok: boolean; text: string; findings: VetFinding[] } {
564
540
  try {
565
- // exactJSON, not JSON.stringify: a document using the `0d` exact
566
- // leaves generates bigints and Decimals, which JSON.stringify cannot
567
- // write (D9). The CLI prints INDENTED JSON and the shared suite's
568
- // `gens` mode prints COMPACT JSON, but both go through this one
569
- // emitter -- an indent argument rather than a second implementation,
570
- // so the two cannot drift from each other or from the Go port.
571
541
  const text = 'canon' === mode
572
542
  ? aontu.unify(src).canon
573
543
  : exactJSON(aontu.generate(src), 2)
@@ -577,13 +547,6 @@ function evalSource(
577
547
  const msg = (err instanceof AontuError || true === err?.aontu)
578
548
  ? err.message
579
549
  : String(err?.message ?? err)
580
- // WHAT THE ENGINE COLLECTED, when it collected anything: an
581
- // AontuError carries the NilVals the run failed on, already
582
- // materialised (handleErrors in ts/src/aontu.ts), and their first
583
- // is the diagnosis every other verb reports. An error raised
584
- // outside the engine's own collection -- exactJSON's circular
585
- // refusal, a foreign object claiming to be one -- carries none,
586
- // and answers with the text alone rather than an invented code.
587
550
  const errs: any[] = 'function' === typeof err?.errs ? err.errs() : []
588
551
  const first: any = errs[0]
589
552
  return {
@@ -619,23 +582,11 @@ function emitEval(
619
582
  }
620
583
 
621
584
 
622
- // The include capability the main verb runs with (G5, docs/trust.md).
623
- // `--trust` and `--include-root` set it explicitly; the default is
624
- // 'system' WITH the warning window: every resolution that escapes the
625
- // entry root or goes through package resolution prints a one-line
626
- // stderr warning naming the flag a future default will require
627
- // (phase 6, the staged flip).
628
585
  type TrustArg = (
629
586
  | { kind: 'system-warn' }
630
587
  | { kind: 'system' }
631
588
  | { kind: 'none' }
632
589
  | { kind: 'root', dir?: string }
633
- // EXTENSIONS READ AS TEXT ride with the capability rather than
634
- // beside it: both answer "what may an include read", both are
635
- // stripped by takeTrust before a verb parses its own tail, and a
636
- // verb that threads one and not the other is the G5 defect again --
637
- // `aontu vet` running under a flag the bare command honoured and it
638
- // did not.
639
590
  ) & { textExt: string[] }
640
591
 
641
592
 
@@ -679,16 +630,6 @@ function trustOpts(trust: TrustArg, entryRoot: string): any {
679
630
  }
680
631
 
681
632
 
682
- // EVERY VERB honours the include capability, not just the bare
683
- // command. G5 wired `--trust`/`--include-root` to `aontu <file>` alone,
684
- // so `aontu vet schema.aon data.json` -- the surface an agent actually
685
- // scripts -- ran the full system resolver with no flag to confine it
686
- // and no warning (use-cases/REVIEW.md finding G). The flags are
687
- // stripped here, before each verb parses its own tail, so a verb only
688
- // has to pass the profile on to its engine.
689
- //
690
- // Returns undefined when the spelling is wrong, with the message
691
- // already printed: the caller answers the usage class.
692
633
  function takeTrust(argv: string[]):
693
634
  { argv: string[], trust: TrustArg } | undefined {
694
635
  const rest: string[] = []
@@ -799,17 +740,6 @@ function runFile(
799
740
  src = readFileSync(file, 'utf8')
800
741
  }
801
742
  catch (err: any) {
802
- // A MISTYPED VERB READS AS A FILE NAME, and until G11 phase 2 that
803
- // was only said when there were TWO of them. The one-argument case
804
- // is the one an agent actually produces -- `aontu help`, `aontu
805
- // init`, `aontu ontology` -- and it answered `cannot read help:
806
- // ...`, which describes the symptom and hides the cause.
807
- //
808
- // The test is SHAPE, not existence: a bare word (no separator, no
809
- // extension) that cannot be read was meant as a verb, while
810
- // `./help`, `help.aon` and `/tmp/help` were meant as paths and keep
811
- // the file diagnosis and its exit 1. That is the same escape hatch
812
- // the subcommand dispatch documents. Mirrors go/cmd/aontu/main.go.
813
743
  if (looksLikeVerb(file)) {
814
744
  process.stderr.write(
815
745
  `aontu: \`${file}\` is not a file, and not a verb this port knows\n`)
@@ -826,12 +756,6 @@ function runFile(
826
756
  }
827
757
 
828
758
  const path = resolve(file)
829
- // `fs` IS WHAT MAKES A FRAME EXCERPT THE FILE IT NAMES. Without it,
830
- // err.ts's resolveSrc falls back to the ENTRY text, so a frame whose
831
- // arrow says `lib/types.aon:2:6` printed the entry's line 2 under it
832
- // -- a real file name over another file's line, which
833
- // docs/reference-api.md forbids in the same words it uses to require
834
- // the name.
835
759
  const aontu = new Aontu({
836
760
  path,
837
761
  errfs: { existsSync, readFileSync },
@@ -856,15 +780,6 @@ function runStdin(
856
780
  }
857
781
 
858
782
 
859
- // THE REPL AS AN INSPECTION TOOL (G7 phase 7): `:load` holds a
860
- // document, and `:get`, `:keys` and `:why` ask the query and
861
- // provenance surfaces about it, so the session is a place to
862
- // INTERROGATE a definition rather than only to evaluate snippets.
863
- //
864
- // The command handler is a PURE FUNCTION of (state, line): a readline
865
- // loop is untestable, and every answer this REPL gives has to be as
866
- // checkable as the CLI's. File reading is injected for the same
867
- // reason.
868
783
  export type ReplState = {
869
784
  // How a value renders: the `:canon` / `:json` toggle.
870
785
  mode: Mode
@@ -873,11 +788,6 @@ export type ReplState = {
873
788
  jsonl: boolean
874
789
  name?: string
875
790
  src?: string
876
- // The include capability the session evaluates under. `--trust` and
877
- // `--include-root` were parsed and then DROPPED on the way to the
878
- // REPL, so `--jsonl` -- the surface built to be driven by a harness
879
- // -- ran unconfined however it was invoked (use-cases/REVIEW.md
880
- // finding G). The state carries it, so every line honours it.
881
791
  trust?: TrustArg
882
792
  }
883
793
 
@@ -1032,14 +942,6 @@ function runRepl(initialMode: Mode, jsonl: boolean, trust: TrustArg): void {
1032
942
  })
1033
943
 
1034
944
  rl.on('close', () => {
1035
- // The closing newline is for a HUMAN, so it is written only for
1036
- // one: it moves the terminal off the prompt line that `rl` left
1037
- // hanging. In `--jsonl` there is no prompt, every answer already
1038
- // ends in its own newline, and this one appended a bare empty line
1039
- // to the stream -- a record that is not JSON, at the end of a
1040
- // protocol whose whole contract is one JSON object per line. A
1041
- // harness parsing every line it receives failed on it, after the
1042
- // commands had all succeeded. Mirrors go/cmd/aontu/repl.go.
1043
945
  if (!jsonl) {
1044
946
  process.stdout.write('\n')
1045
947
  }
@@ -1051,16 +953,6 @@ function runRepl(initialMode: Mode, jsonl: boolean, trust: TrustArg): void {
1051
953
  }
1052
954
 
1053
955
 
1054
-
1055
- // THE VET VERB (G2 phase 3).
1056
- //
1057
- // Exit codes are VERDICT CLASSES, not a pass/fail bit: an agent loop
1058
- // branches on "the data contradicts the truth" (1) differently from
1059
- // "the data has not supplied everything the truth requires" (3), and
1060
- // differently again from "the schema itself is broken" (4), which is
1061
- // never the data's fault. 2 stays what it already was for this CLI --
1062
- // the caller got the invocation wrong -- which is why an unreadable
1063
- // file is a 2 rather than a 4.
1064
956
  const VET_EXIT: Record<VetVerdict, number> = {
1065
957
  valid: 0,
1066
958
  invalid: 1,
@@ -1130,14 +1022,6 @@ function parseVetArgs(argv: string[]): { args?: VetArgs; err?: string } {
1130
1022
  format = f
1131
1023
  }
1132
1024
  else if ('--max-errors' === arg) {
1133
- // ONE GRAMMAR, spelled the same way in both ports: decimal
1134
- // digits, one to nine of them, at least 1. `Number()` alone
1135
- // accepted `1.0`, `1e2`, `0x10` and ` 3`, which Go's parser
1136
- // refuses -- so the same documented invocation meant different
1137
- // things in the two shipped commands. The nine-digit ceiling is
1138
- // where the ports would part company again: beyond it Go's
1139
- // integer conversion saturates, and a cap nobody can reach is
1140
- // not worth a divergence.
1141
1025
  const raw = argv[++i]
1142
1026
  if (!/^[0-9]{1,9}$/.test(raw ?? '') || 1 > Number(raw)) {
1143
1027
  return { err: 'aontu: --max-errors needs a positive whole number' }
@@ -1220,10 +1104,6 @@ function renderFinding(f: VetFinding): string {
1220
1104
  out.push(` actual: ${f.actual}`)
1221
1105
  }
1222
1106
  for (const s of f.sites) {
1223
- // Every site carries the canon of the value it stands for: that is
1224
- // what makes the two sides of a conflict readable side by side. A
1225
- // site's file is always a string -- empty when the value belongs to
1226
- // neither document -- so there is nothing to coalesce here.
1227
1107
  out.push(` ${s.role}: ${s.file}:${s.row}:${s.col} (${s.value})`)
1228
1108
  }
1229
1109
 
@@ -1333,18 +1213,9 @@ function vetOnce(args: VetArgs, trust: TrustArg): number {
1333
1213
  return 2
1334
1214
  }
1335
1215
 
1336
- // Each data file is vetted on its own, because a parsed tree is
1337
- // single-use (docs/reference-api.md) -- and because two data files
1338
- // are two candidates for the same truth, not one merged candidate.
1339
1216
  let verdict: VetVerdict = 'valid'
1340
1217
  let truncated = false
1341
1218
  const findings: VetFinding[] = []
1342
- // COVERAGE ACROSS SEVERAL DATA FILES (G11 phase 5). Two data files
1343
- // are two candidates for one truth, so the schema side is the SAME
1344
- // for each: `declared` is taken once, and a declaration is unused
1345
- // only when NO file met it -- the intersection, because a
1346
- // declaration one file exercised is exercised. The data side adds
1347
- // up: leaves and checked leaves sum, and `unchecked` is the union.
1348
1219
  let cov: VetCoverage | undefined
1349
1220
  // Initialised rather than left undefined: it is filled in the same
1350
1221
  // block that sets `cov`, so a fallback at the read below would be an
@@ -1363,12 +1234,6 @@ function vetOnce(args: VetArgs, trust: TrustArg): number {
1363
1234
  maxErrors: args.maxErrors,
1364
1235
  schemaUrl: args.schema,
1365
1236
  dataUrl: source.file,
1366
- // The paths as well as the labels: a relative `@"file"` load
1367
- // inside either document resolves from ITS OWN directory, the
1368
- // way `aontu <file>` already resolves one (runFile above). The
1369
- // path is passed AS TYPED, not resolved: it doubles as the
1370
- // label above, and a report that mixed the typed path with an
1371
- // absolute one would name the same file two ways.
1372
1237
  schemaPath: args.schema,
1373
1238
  dataPath: source.file,
1374
1239
  coverage: args.coverage,
@@ -1400,27 +1265,11 @@ function vetOnce(args: VetArgs, trust: TrustArg): number {
1400
1265
  unusedSeen = true
1401
1266
  }
1402
1267
 
1403
- // A SCHEMA-SIDE FAULT IS THE SAME FAULT FOR EVERY DATA FILE, so it
1404
- // is reported ONCE. `error` means exactly that -- the run could not
1405
- // be set up from the truth's side, never the data's (the exit table
1406
- // in docs/reference-api.md) -- so the report the first file
1407
- // produced is the report every later file would produce, character
1408
- // for character. Concatenating them repeated one broken schema N
1409
- // times and, past the cap, marked the report `truncated` over a
1410
- // single underlying fault. It only became visible once the `error`
1411
- // verdict started carrying findings at all: while the list was
1412
- // empty there was nothing to duplicate.
1413
1268
  if ('error' === report.verdict) {
1414
1269
  break
1415
1270
  }
1416
1271
  }
1417
1272
 
1418
- // The cap is on the REPORT, not on each file. Capping every file's
1419
- // list and then concatenating them let `--max-errors 1` emit one
1420
- // finding PER FILE -- and leave `truncated` false while doing it,
1421
- // because no single file had been cut. The engine still caps each
1422
- // run, so a pathological file cannot flood the aggregate before it
1423
- // gets here; this is the second, honest cut.
1424
1273
  const cap = args.maxErrors ?? VET_MAX_ERRORS
1425
1274
  const kept = cap < findings.length ? findings.slice(0, cap) : findings
1426
1275
 
@@ -1442,12 +1291,6 @@ function vetOnce(args: VetArgs, trust: TrustArg): number {
1442
1291
 
1443
1292
  process.stdout.write(text + '\n')
1444
1293
 
1445
- // A VACUOUS CHECK IS A FAILED GATE UNDER `--strict-coverage`, and
1446
- // only under it: the verdict WORD is unchanged, so nothing that
1447
- // passes today starts failing, and a caller who wants the stronger
1448
- // gate asks for it. The reason goes to stderr, because stdout is a
1449
- // report contract -- a JSON consumer reads `coverage.vacuous` and a
1450
- // person reads this.
1451
1294
  if (true === args.strictCoverage && true === report.coverage?.vacuous) {
1452
1295
  process.stderr.write(
1453
1296
  'aontu: no data leaf was constrained by the schema:' +
@@ -1483,19 +1326,6 @@ function sleep(ms: number): Promise<void> {
1483
1326
  }
1484
1327
 
1485
1328
 
1486
- // Resolve true when any watched file's signature moves off `before`.
1487
- // This is the real waiter: it never resolves false, so a real watch
1488
- // runs until the process is interrupted; tests inject their own waiter
1489
- // to bound the loop, and pass a short pollMs when they drive this one
1490
- // directly. The interval is a required argument (the command passes
1491
- // WATCH_POLL_MS) so there is no defaulting branch a test could never
1492
- // take.
1493
- //
1494
- // The BASELINE is an argument, not a snapshot taken here: the loop
1495
- // records it BEFORE each vet run, so a save landing between the run's
1496
- // reads and the wait still compares as a change. A waiter that
1497
- // snapshotted on entry would adopt that unvetted save as its baseline
1498
- // and wait indefinitely on a stale report.
1499
1329
  async function watchChange(
1500
1330
  files: string[], before: string, pollMs: number): Promise<boolean> {
1501
1331
  for (;;) {
@@ -1535,9 +1365,6 @@ async function watchVet(
1535
1365
  }
1536
1366
 
1537
1367
 
1538
- // The vet verb. Non-watch runs are synchronous and return the exit
1539
- // class directly; `--watch` returns a promise that resolves only when
1540
- // the waiter says stop (never, for the real one).
1541
1368
  function runVet(argv: string[], wait?: VetWaiter): number | Promise<number> {
1542
1369
  const trusted = takeTrust(argv)
1543
1370
  if (null == trusted) {
@@ -1715,14 +1542,6 @@ type BreakingArgs = {
1715
1542
  file: string
1716
1543
  against: string[]
1717
1544
  mode?: BreakingMode
1718
- // `--at`: compare a SUBTREE of both versions. The gate's own
1719
- // sub-question, and the one a real repository needs -- a document's
1720
- // top level carries the module's version string and its policy
1721
- // block, which are supposed to change between releases and which
1722
- // make the whole-document comparison answer about them rather than
1723
- // about the contract (use-cases/REVIEW.md finding D). `subsume` has
1724
- // taken it since G3; `breaking` did not, so the only way to gate a
1725
- // subtree was to split the file.
1726
1545
  at?: string
1727
1546
  allowUndecided: boolean
1728
1547
  allowDeprecatedRemoval: boolean
@@ -1806,36 +1625,10 @@ function parseBreakingArgs(
1806
1625
  }
1807
1626
  }
1808
1627
 
1809
- // One resolved `--against` spelling: the old document's text, the path
1810
- // its own relative includes must resolve from, and (for a git spelling)
1811
- // the temporary tree to remove when the run is done.
1812
1628
  type OldVersion = { src: string, path: string, temp?: string }
1813
1629
 
1814
- // A source file the include resolver can actually load. `git#<rev>`
1815
- // materialises these and nothing else: an include names an Aontu
1816
- // document (`.aon`/`.aontu`, the two extensions `@"foo"` tries) or a
1817
- // JSON one, so the rest of a revision's tree cannot be part of any
1818
- // include closure and copying it would be pure cost.
1819
1630
  const INCLUDABLE = /\.(aon|aontu|jsonic|json)$/
1820
1631
 
1821
- // Resolve one --against spelling to an old version.
1822
- //
1823
- // A `git#<rev>` spelling is the old version of the WHOLE TREE, not of
1824
- // the entry file alone. It used to be `git show <rev>:./<file>`, whose
1825
- // text was then evaluated with `generalPath`/`specificPath` pointing at
1826
- // the WORKING file -- so every `@"..."` include in the old document
1827
- // resolved against the working tree, and the "old" side was old entry
1828
- // text meeting new includes. A breaking change inside an included file
1829
- // therefore compared against itself and answered `compatible`: the
1830
- // documented CI gate silently un-gated every non-entry file of the
1831
- // multi-file layout real models use (use-cases/BUGS.md §26). The old
1832
- // tree's includable sources are copied into a temporary directory and
1833
- // the old document is evaluated from THERE.
1834
- //
1835
- // Sources outside the revision -- package includes under node_modules,
1836
- // the bundled `std/system` -- still resolve as they do today: they are
1837
- // not in the tree, and their versions travel with the lockfile rather
1838
- // than with this comparison.
1839
1632
  function oldVersion(spec: string, file: string): OldVersion | undefined {
1840
1633
  if (!spec.startsWith('git#')) {
1841
1634
  try {
@@ -1867,24 +1660,10 @@ function oldVersion(spec: string, file: string): OldVersion | undefined {
1867
1660
  // that only some failures take.
1868
1661
  const temp = mkdtempSync(join(tmpdir(), 'aontu-against-'))
1869
1662
  try {
1870
- // THE REPO-RELATIVE PATH COMES FROM GIT, not from path arithmetic.
1871
- // Relativising `rev-parse --show-toplevel` against `resolve(file)`
1872
- // puts two DIFFERENT COORDINATE SYSTEMS on either side of the
1873
- // subtraction: git prints the real path, while the caller's is
1874
- // whatever they typed. On macOS a temp file under /var is
1875
- // /private/var to git, and on Windows a TMP short name
1876
- // (RUNNER~1) is the long form to git -- so the subtraction gave a
1877
- // `../..` climb, the entry was "not in that revision", and the
1878
- // documented CI spelling failed on both platforms while passing on
1879
- // Linux (this PR's own CI). `--show-prefix` is the same question
1880
- // asked in git's coordinates: the repo-relative directory of the
1881
- // cwd, already slash-separated and already normalised.
1882
1663
  const prefix = git(['rev-parse', '--show-prefix'], dir).trim()
1883
1664
  const entryRel = prefix + basename(file)
1884
1665
  const top = git(['rev-parse', '--show-toplevel'], dir).trim()
1885
1666
 
1886
- // `-z` so a path with a newline or a quote cannot be mistaken for
1887
- // two paths (git otherwise quotes such names).
1888
1667
  const listed = git(['ls-tree', '-r', '-z', '--name-only', rev], top)
1889
1668
  .split('\0').filter((p) => '' !== p)
1890
1669
  if (!listed.includes(entryRel)) {
@@ -1919,13 +1698,6 @@ function policyCompat(
1919
1698
  ): BreakingMode | undefined {
1920
1699
  const aontu = new Aontu()
1921
1700
  const ctx = aontu.ctx({ collect: true })
1922
- // The declaration is read by EVALUATING the document, so this leg
1923
- // runs the include resolver too and has to run it under BOTH of the
1924
- // verb's include options -- a `breaking --trust none` that read its
1925
- // own mode through an unconfined resolver would confine the
1926
- // comparison and not the question (use-cases/REVIEW.md finding G),
1927
- // and one that took the capability alone read no mode at all when
1928
- // the declaration arrived through a `--text-ext` include.
1929
1701
  const v: any = aontu.unify(newSrc, { path, ...includeOpts(include) }, ctx)
1930
1702
  if (0 < ctx.err.length || true === v?.isNil) {
1931
1703
  return undefined
@@ -1945,11 +1717,6 @@ function policyCompat(
1945
1717
  ? m : undefined
1946
1718
  }
1947
1719
 
1948
- // Is the evaluated old version's value at the finding path deprecated?
1949
- // The --allow-deprecated-removal downgrade (G3 phase 4): removing (or
1950
- // otherwise changing) a value the old version already deprecated warns
1951
- // instead of breaking. The Go port exports the same reader as
1952
- // aontu.DeprecatedAt.
1953
1720
  function deprecatedAt(oldSrc: string, path: string, filePath: string): boolean {
1954
1721
  const aontu = new Aontu()
1955
1722
  const ctx = aontu.ctx({ collect: true })
@@ -2024,9 +1791,6 @@ function runBreaking(argv: string[]): number {
2024
1791
  return 2
2025
1792
  }
2026
1793
 
2027
- // The declared mode: --mode overrides the document's own policy;
2028
- // neither means backward, the index's framing (v1-valid documents
2029
- // stay valid).
2030
1794
  const mode: BreakingMode =
2031
1795
  args.mode ??
2032
1796
  policyCompat(newSrc, args.file,
@@ -2066,8 +1830,6 @@ function runBreaking(argv: string[]): number {
2066
1830
  temps.push(old.temp)
2067
1831
  }
2068
1832
 
2069
- // backward: the NEW document is the general side — every old
2070
- // instance must still be admitted. forward: the old one is.
2071
1833
  const checks: Array<{ general: [string, string], specific: [string, string] }> = []
2072
1834
  if ('backward' === mode || 'full' === mode) {
2073
1835
  checks.push({ general: [newSrc, args.file], specific: [oldSrc, spec] })
@@ -2084,18 +1846,10 @@ function runBreaking(argv: string[]): number {
2084
1846
  at: args.at,
2085
1847
  generalUrl: check.general[1],
2086
1848
  specificUrl: check.specific[1],
2087
- // The old side's relative loads resolve from ITS own tree --
2088
- // the materialised revision for a git spelling, the named
2089
- // file's directory otherwise -- so an included file's change
2090
- // is part of the comparison rather than invisible to it.
2091
1849
  generalPath: check.general[1] === spec ? oldPath : args.file,
2092
1850
  specificPath: check.specific[1] === spec ? oldPath : args.file,
2093
1851
  })
2094
1852
 
2095
- // The deprecated-removal downgrade: a finding about a value the
2096
- // OLD version already deprecated becomes a warning, and warnings
2097
- // do not move the verdict. Deprecate-then-remove is the
2098
- // supported rename path (the design's own sequencing).
2099
1853
  let verdict = report.verdict
2100
1854
  if (args.allowDeprecatedRemoval) {
2101
1855
  let liveFindings = 0
@@ -2153,13 +1907,6 @@ function renderBreakingJson(report: SubsumeReport, mode: string): string {
2153
1907
  }
2154
1908
 
2155
1909
 
2156
- // ---------------------------------------------------------------------
2157
- // The trim reporter (G3 phase 6): report redundant entries as paths.
2158
- // Report-only — REWRITING needs G7's format-preserving patch surface —
2159
- // which is why --check is REQUIRED rather than defaulted: `aontu trim
2160
- // f.aon` reads as "trim this file", and doing something else silently
2161
- // is worse than saying so.
2162
-
2163
1910
  const TRIM_HELP = 'aontu trim --check <file> (try --help)'
2164
1911
 
2165
1912
  const TRIM_EXIT: Record<TrimVerdict, number> = {
@@ -2237,8 +1984,6 @@ function runTrim(argv: string[]): number {
2237
1984
 
2238
1985
  function renderTrimText(report: TrimReport): string {
2239
1986
  const head = `verdict: ${report.verdict}`
2240
- // WHY, when the document could not be evaluated at all: rendered as
2241
- // vet renders a finding, because it IS one (the review's finding F).
2242
1987
  const errors = report.errors ?? []
2243
1988
  if (0 < errors.length) {
2244
1989
  return [head, ''].concat(errors.map(renderFinding)).join('\n')
@@ -2303,29 +2048,12 @@ const VIEW_EDGES: ViewEdges[] = ['upward', 'all', 'none']
2303
2048
  // the same division err.ts already draws for the error frames.
2304
2049
  const VIEW_STYLES = ['auto', 'none', 'ansi', 'css']
2305
2050
 
2306
- // `--style auto` resolved, which only the CLI can do. The mechanism is
2307
- // the PROFILE's and the library knows it -- an SVG carries its
2308
- // stylesheet unless told not to, which is what makes a figure stand
2309
- // alone. What the library cannot know is whether the DESTINATION is a
2310
- // terminal, so that is the only thing decided here: escapes on the
2311
- // text profile when stdout is a terminal and NO_COLOR is unset, the
2312
- // same two conditions the error frames use. `undefined` leaves the
2313
- // profile's own default in place.
2314
2051
  function viewStyleOf(
2315
2052
  asked: string | undefined, as: ViewProfile | undefined
2316
2053
  ): ViewStyle | undefined {
2317
2054
  if (undefined !== asked && 'auto' !== asked) {
2318
2055
  return asked as ViewStyle
2319
2056
  }
2320
- // STDOUT'S OWN TERMINAL-NESS, and NO_COLOR read here rather than
2321
- // through colorActive(). The figure goes to STDOUT and the error
2322
- // frames go to STDERR, and they are not the same destination: main()
2323
- // has already called setColor for stderr, so asking colorActive()
2324
- // would answer the wrong question twice --- no escapes for
2325
- // `aontu view tree m.aon 2>/dev/null` at a terminal, and escapes
2326
- // into the pipe for `aontu view tree m.aon | less`. The NO_COLOR
2327
- // rule is the one no-color.org states and err.ts implements:
2328
- // set, to anything but empty, means no colour.
2329
2057
  const no = process.env.NO_COLOR
2330
2058
  return 'text' === as && true === process.stdout.isTTY
2331
2059
  && (null == no || '' === no) ? 'ansi' : undefined
@@ -2351,24 +2079,6 @@ const VIEW_USAGE_CODES = [
2351
2079
 
2352
2080
  const MOD_HELP = 'aontu mod tidy|verify|vendor|manifest [dir] (try --help)'
2353
2081
 
2354
- // The module tooling (G6 phase 3, ts/src/mod-tool.ts). All LOCAL:
2355
- // `tidy` resolves the closure from what is in the stores and rewrites
2356
- // the lockfile, `verify` asks whether the stores still mean what the
2357
- // lockfile pins and changes nothing, `vendor` materialises the locked
2358
- // closure into the project, `manifest` prints what a publish would
2359
- // push.
2360
- //
2361
- // TIDY AND VERIFY ARE DIFFERENT QUESTIONS, and that is why both exist.
2362
- // Tidy recomputes and rewrites by design -- a pin is what a module
2363
- // means NOW -- so it makes the lockfile agree with whatever the store
2364
- // holds, tampering included. Verify is the gate: a CI job runs it
2365
- // BEFORE tidy, or instead of it.
2366
- //
2367
- // `get` and `publish` are the NETWORK half of the design and are not in
2368
- // this build. They are named here rather than left to fall out as an
2369
- // unknown subcommand, because a reader of the design will type them and
2370
- // deserves to be told which half is missing rather than that the word
2371
- // is wrong.
2372
2082
  function runMod(argv: string[]): number {
2373
2083
  const rest: string[] = []
2374
2084
  let format: SubsumeFormat = 'text'
@@ -2422,10 +2132,6 @@ function runMod(argv: string[]): number {
2422
2132
  return 2
2423
2133
  }
2424
2134
 
2425
- // THE OLD LAYOUT IS NAMED, NOT READ. The lockfile and the vendored
2426
- // closure moved under aontu_meta/; a project that still carries them
2427
- // at its root would otherwise look untouched by any of these verbs,
2428
- // which is the one silence worth breaking.
2429
2135
  if (existsSync(join(dir, 'aon_vendor')) || existsSync(join(dir, 'mod-lock.aon'))) {
2430
2136
  process.stderr.write(
2431
2137
  'aontu: aon_vendor/ and mod-lock.aon now live under aontu_meta/: ' +
@@ -2469,9 +2175,6 @@ type ModVerdict =
2469
2175
  const MOD_EXIT: Record<ModVerdict, number> = {
2470
2176
  ok: 0,
2471
2177
  missing: 1,
2472
- // A REFUSED GATE, with `breaking`: a store that no longer means what
2473
- // the lockfile pins is the integrity check saying no, and a CI job
2474
- // reading exit codes should not have to learn a third class for it.
2475
2178
  mismatch: 1,
2476
2179
  // Likewise a lockfile that does not cover the project: the gate has
2477
2180
  // nothing to check, which is a refusal and not a pass.
@@ -2522,11 +2225,6 @@ function modText(sub: string, report: any): string {
2522
2225
  for (const f of report.findings) {
2523
2226
  lines.push(f.path + ': ' + f.message)
2524
2227
  }
2525
- // What a manifest lacks is a declaration the module does not make
2526
- // or an entry file that is not there, and neither is something a
2527
- // fetch would supply -- so this is not the tail the other two
2528
- // subcommands share. The name says which kind it is: `mod.version`
2529
- // is a declaration, `service.aon` is a file.
2530
2228
  for (const miss of report.missing) {
2531
2229
  lines.push(miss + ': missing')
2532
2230
  }
@@ -2537,8 +2235,6 @@ function modText(sub: string, report: any): string {
2537
2235
  for (const mod of report.verified) {
2538
2236
  lines.push(mod + ': verified')
2539
2237
  }
2540
- // BOTH HASHES, because the useful question is which way it moved:
2541
- // an empty `got` is a module that no longer stands up at all.
2542
2238
  for (const m of report.mismatched) {
2543
2239
  lines.push(m.mod + ': pinned ' + m.want + ' but the store means ' +
2544
2240
  ('' === m.got ? 'nothing (it does not evaluate)' : m.got))
@@ -2574,26 +2270,6 @@ function modText(sub: string, report: any): string {
2574
2270
  }
2575
2271
 
2576
2272
 
2577
- // VACUITY SIGNALS (G11 phase 4,
2578
- // docs/capability-review/g11-agent-onramp.md).
2579
- //
2580
- // The same principle phase 5 applied to `vet`: a verb that did NOTHING
2581
- // and a verb that did its job answer the same. `aontu view tree` over a
2582
- // document declaring no relations printed one newline and exited 0;
2583
- // `aontu render` with no profile printed nothing and exited 0; `aontu
2584
- // relations` over a document declaring none answered `verdict: pass`.
2585
- // For a person at a terminal that is a shrug. For an unattended agent
2586
- // it is a green check mark on an empty box.
2587
- //
2588
- // ON STDERR, ALWAYS. stdout is a report contract -- a `--format json`
2589
- // consumer parses it -- and the exit code is a verdict class that
2590
- // callers already branch on. Neither changes here: what changes is
2591
- // that the caller is TOLD. A caller who wants it to be fatal has
2592
- // `vet --strict-coverage`, and the same argument would give the other
2593
- // verbs a flag of their own if one is ever asked for.
2594
- //
2595
- // The repository already ruled this for one verb, in G8 phase 6 on
2596
- // `trim`: "doing something else silently is worse than refusing".
2597
2273
  function vacuous(what: string, why: string): void {
2598
2274
  process.stderr.write(`aontu: ${what}: ${why}\n`)
2599
2275
  }
@@ -2881,12 +2557,6 @@ function runView(argv: string[]): number {
2881
2557
  }
2882
2558
  }
2883
2559
 
2884
- // ESCAPES NEVER GO INTO A FILE. A pinned golden holding terminal
2885
- // control codes is not a golden anybody can read, and a byte
2886
- // comparison against one would fail on the reader's terminal
2887
- // settings. `auto` resolves to `none` there on its own; asking for
2888
- // `ansi` explicitly is a usage error rather than a silent downgrade,
2889
- // so a script that wanted colour is told where it went.
2890
2560
  if ('ansi' === style && (undefined !== out || undefined !== opts.views)) {
2891
2561
  process.stderr.write(
2892
2562
  'aontu: --style ansi writes to a terminal, not to a file\n')
@@ -2896,10 +2566,6 @@ function runView(argv: string[]): number {
2896
2566
  // THE VIEW DOCUMENT draws every figure a document declares, so it
2897
2567
  // names no kind: the declarations do, one each.
2898
2568
  if (undefined !== opts.views) {
2899
- // A declaration names its own profile, so the style is left to
2900
- // each figure's own default; `--style none` still reaches every
2901
- // one of them, which is how a host page that binds the CSS
2902
- // variables asks for eight figures without eight stylesheets.
2903
2569
  opts.style = viewStyleOf(style, undefined)
2904
2570
  return runViewSet(rest, opts, trust, { format, check, strict, out })
2905
2571
  }
@@ -2975,17 +2641,6 @@ function runView(argv: string[]): number {
2975
2641
  }
2976
2642
  const report = view(srcs[0], viewOpts)
2977
2643
 
2978
- // AN EMPTY FIGURE IS THE SAME BYTES AS A DRAWN ONE MINUS ITS
2979
- // CONTENT, and every profile spells "empty" differently: text draws
2980
- // nothing at all, mermaid still draws its `flowchart LR` header, the
2981
- // matrix still prints its count line. Rather than teach this one
2982
- // place each of those spellings -- a list that goes stale the first
2983
- // time a profile gains a header -- ASK THE SAME KIND TO DRAW AN
2984
- // EMPTY DOCUMENT and compare. Equal texts mean this document
2985
- // contributed nothing to the figure, whatever the profile.
2986
- //
2987
- // It costs one drawing of `{}`, which is the cheapest document
2988
- // there is, and only on a run that produced a figure at all.
2989
2644
  if ('error' !== report.verdict && null != report.text) {
2990
2645
  const bare = view('{}', viewOpts)
2991
2646
  if ('error' !== bare.verdict && bare.text === report.text) {
@@ -3039,13 +2694,6 @@ function runView(argv: string[]): number {
3039
2694
  return strict && 'lossy' === report.verdict ? 1 : VIEW_EXIT[report.verdict]
3040
2695
  }
3041
2696
 
3042
- // `aontu view --views <path> <file>`: every figure the document
3043
- // declares, from one evaluation, all or nothing.
3044
- //
3045
- // A declared `out` is resolved against the DOCUMENT's own directory,
3046
- // not the caller's: a view document is committed beside the figures it
3047
- // gates, and a gate that only passes from one working directory is not
3048
- // a gate.
3049
2697
  function runViewSet(
3050
2698
  rest: string[], opts: ViewOptions, trust: TrustArg,
3051
2699
  how: { format: SubsumeFormat, check: boolean, strict: boolean, out?: string }
@@ -3186,8 +2834,6 @@ function renderViewJson(report: ViewReport): string {
3186
2834
 
3187
2835
  function renderRelationsText(report: RelationReport): string {
3188
2836
  const head = `verdict: ${report.verdict}`
3189
- // WHY, when the document could not be evaluated at all: rendered as
3190
- // vet renders a finding, because it IS one (the review's finding F).
3191
2837
  const errors = report.errors ?? []
3192
2838
  if (0 < errors.length) {
3193
2839
  return [head, ''].concat(errors.map(renderFinding)).join('\n')
@@ -3213,21 +2859,6 @@ function renderRelationsJson(report: RelationReport): string {
3213
2859
  }
3214
2860
 
3215
2861
 
3216
-
3217
- // ---------------------------------------------------------------------
3218
- // JSON SCHEMA EXPORT (SUPPORT.md act 2, the review's finding I): the
3219
- // bridge to every structured-output API, which constrains generation to
3220
- // JSON Schema and nothing else. Export the model, let the provider
3221
- // generate under it, then `vet` the result against the model itself --
3222
- // the hybrid an enterprise actually deploys, and impossible without
3223
- // this verb.
3224
- //
3225
- // THE SCHEMA GOES TO STDOUT AND THE LOSSES TO STDERR, so `aontu
3226
- // jsonschema x.aon > schema.json` writes a schema and still tells the
3227
- // reader what it could not carry. `--strict` makes a loss a refusal,
3228
- // for the CI job that would rather fail than ship a schema weaker than
3229
- // its model.
3230
-
3231
2862
  const JSONSCHEMA_HELP =
3232
2863
  'aontu jsonschema [--at <path>] [--strict] <file> (try --help)'
3233
2864
 
@@ -3323,14 +2954,6 @@ function runJsonSchema(argv: string[]): number {
3323
2954
  strict && 'lossy' === report.verdict ? 1 : 0
3324
2955
  }
3325
2956
 
3326
- // ---------------------------------------------------------------------
3327
- // THE RENDER VERB (docs/design/RENDER.0.md D8): evaluate a document,
3328
- // vet the value at --at against aontu:code, fold code.units into bytes,
3329
- // and put them where the flag says -- one unit on stdout, every unit
3330
- // below --out (all or nothing), or compared against --check. Exit codes
3331
- // mirror jsonschema's: 0 ok; 1 lossy under --strict or drift under
3332
- // --check; 2 usage or I/O, a refused unit path included; 4 the
3333
- // document does not stand up or the instance is not aontu:code.
3334
2957
 
3335
2958
  const RENDER_HELP =
3336
2959
  'aontu render [--at <path>] [--profile <file>]... [--unit <path>] ' +
@@ -3468,47 +3091,15 @@ function runRender(argv: string[]): number {
3468
3091
  return 2
3469
3092
  }
3470
3093
 
3471
- // THE ENTRY MAY BE A TEMPLATE (TEMPLATE.0.md; P8), and its EXTENSION
3472
- // decides, as an include's extension decides what the include is
3473
- // (ADR-012): a generator is a file in the target's own syntax, so it
3474
- // carries the target's extension and never `.aon`. Desugared here
3475
- // rather than anywhere deeper, because a template is an entry
3476
- // spelling and not a value: an include is still aontu.
3477
- if (!files[0].endsWith('.aon')) {
3478
- src = desugarTemplate(src, marker ?? markerFor(files[0]))
3094
+ const loadedProfiles = loadProfiles(profileFiles, trust)
3095
+ if ('number' === typeof loadedProfiles) {
3096
+ return loadedProfiles
3479
3097
  }
3098
+ const profiles = loadedProfiles
3480
3099
 
3481
- // THE PROFILES (D5): each --profile file is a document whose root is
3482
- // `profile: {lang, ...}`, evaluated under the verb's trust and vetted
3483
- // against aontu:profile as a settled value before the fold reads it
3484
- // (renderProfile, which also fills the defaults). Two files claiming
3485
- // one lang is a usage error: the fold could not choose.
3486
- const profiles: any[] = []
3487
- const langs = new Map<string, string>()
3488
- for (const pf of profileFiles) {
3489
- let text: string
3490
- try {
3491
- text = readFileSync(pf, 'utf8')
3492
- }
3493
- catch (err: any) {
3494
- process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
3495
- return 2
3496
- }
3497
- const loaded = renderProfile(text,
3498
- { path: resolve(pf), ...verbOpts(trust, entryRootOf(pf)) })
3499
- if (undefined !== loaded.errors) {
3500
- process.stderr.write(loaded.errors.map(renderFinding).join('\n') + '\n')
3501
- return 4
3502
- }
3503
- const profile = loaded.profile
3504
- const prev = langs.get(profile.lang)
3505
- if (undefined !== prev) {
3506
- process.stderr.write(
3507
- `aontu: two profiles claim ${profile.lang}: ${prev} and ${pf}\n`)
3508
- return 2
3509
- }
3510
- langs.set(profile.lang, pf)
3511
- profiles.push(profile)
3100
+ if (!files[0].endsWith('.aon')) {
3101
+ src = desugarTemplate(src, marker ??
3102
+ markerFromProfiles(profiles, files[0]) ?? markerFor(files[0]))
3512
3103
  }
3513
3104
 
3514
3105
  // A RENDER WITH NO PROFILE PRODUCES NO UNITS, and said so with zero
@@ -3660,21 +3251,21 @@ function renderExit(report: RenderReport, drift: number): number {
3660
3251
  }
3661
3252
 
3662
3253
 
3663
- // ---------------------------------------------------------------------
3664
- // THE TEMPLATE SURFACE (docs/design/TEMPLATE.0.md; RENDER.0.md P8): the
3665
- // two transforms and the round trip between them. `render` reads a
3666
- // template directly, by its extension; this verb is for seeing the
3667
- // canonical form, for writing one by hand and sugaring it, and for the
3668
- // check that keeps a committed template and its meaning in agreement.
3669
-
3670
3254
  const TEMPLATE_HELP =
3671
3255
  'aontu template [--resugar] [--check] [--marker <token>] <file> (try --help)'
3672
3256
 
3673
3257
  function runTemplate(argv: string[]): number {
3258
+ const trusted = takeTrust(argv)
3259
+ if (null == trusted) {
3260
+ return 2
3261
+ }
3262
+ argv = trusted.argv
3263
+ const trust = trusted.trust
3674
3264
  const files: string[] = []
3675
3265
  let resugar = false
3676
3266
  let check = false
3677
3267
  let marker: string | undefined = undefined
3268
+ const profileFiles: string[] = []
3678
3269
 
3679
3270
  for (let i = 0; i < argv.length; i++) {
3680
3271
  const arg = argv[i]
@@ -3695,6 +3286,14 @@ function runTemplate(argv: string[]): number {
3695
3286
  return 2
3696
3287
  }
3697
3288
  }
3289
+ else if ('--profile' === arg) {
3290
+ const pf = argv[++i]
3291
+ if (null == pf) {
3292
+ process.stderr.write('aontu: --profile needs a file\n')
3293
+ return 2
3294
+ }
3295
+ profileFiles.push(pf)
3296
+ }
3698
3297
  else if (arg.startsWith('-')) {
3699
3298
  process.stderr.write(
3700
3299
  `aontu: unknown template option ${arg} (try --help)\n`)
@@ -3709,10 +3308,6 @@ function runTemplate(argv: string[]): number {
3709
3308
  process.stderr.write(`aontu: template needs one file\n${TEMPLATE_HELP}\n`)
3710
3309
  return 2
3711
3310
  }
3712
- // THE TWO ARE DIRECTIONS, NOT MODES THAT COMPOSE: `--check` reads a
3713
- // template and asks whether the round trip answers it back, and
3714
- // `--resugar` reads the canonical form instead. A run cannot be both
3715
- // at once, because the file is one thing or the other.
3716
3311
  if (resugar && check) {
3717
3312
  process.stderr.write(
3718
3313
  'aontu: template takes one of --resugar or --check\n')
@@ -3728,19 +3323,15 @@ function runTemplate(argv: string[]): number {
3728
3323
  return 2
3729
3324
  }
3730
3325
 
3731
- const mark = marker ?? markerFor(files[0])
3326
+ const declared = loadProfiles(profileFiles, trust)
3327
+ if ('number' === typeof declared) {
3328
+ return declared
3329
+ }
3330
+
3331
+ const mark = marker ?? markerFromProfiles(declared, files[0]) ??
3332
+ markerFor(files[0])
3732
3333
 
3733
3334
  if (check) {
3734
- // THE ROUND TRIP IS THE CHECK (D6): the file held to the spelling
3735
- // the two transforms answer. What that names is a marker line the
3736
- // transform would not have written -- one without its space, or one
3737
- // whose aontu is indented after the marker rather than before it,
3738
- // since the marker keeps its own indentation. It does NOT name a
3739
- // changed body line: a template's whitespace is output, so a
3740
- // trimmed trailing space is still a valid template and it is
3741
- // `render --check` against the committed files that catches it.
3742
- // The first line that differs is the report, since a whole diff of
3743
- // a generator is the file again.
3744
3335
  const back = resugarTemplate(desugarTemplate(src, mark), mark)
3745
3336
  if (back === src) {
3746
3337
  return 0
@@ -3751,12 +3342,6 @@ function runTemplate(argv: string[]): number {
3751
3342
  while (n < want.length && n < have.length && want[n] === have[n]) {
3752
3343
  n++
3753
3344
  }
3754
- // THE TWO ARE THE SAME LENGTH, always: each transform maps one
3755
- // line to one line and applies the same trailing-newline rule, so
3756
- // `back` has as many lines as `src`. The loop above therefore stops
3757
- // at a real difference rather than by running out of either -- an
3758
- // equal prefix all the way to the end IS `back === src`, which
3759
- // returned above. So both indexes are in range here.
3760
3345
  process.stderr.write(
3761
3346
  `aontu: ${files[0]}:${n + 1} is not what the round trip answers\n` +
3762
3347
  ` have: ${JSON.stringify(have[n])}\n` +
@@ -3770,13 +3355,43 @@ function runTemplate(argv: string[]): number {
3770
3355
  }
3771
3356
 
3772
3357
 
3773
- // ---------------------------------------------------------------------
3774
- // The canon-hash (G6 phase 1): the pin an agent, a lockfile or a
3775
- // registry stores for "this module, this meaning". The hash covers the
3776
- // module evaluated STANDALONE -- its own include closure resolved and
3777
- // unified at its own root, before any consumer context -- which is what
3778
- // makes the pin transitive: an edit two includes deep changes the
3779
- // unified root, hence the hash.
3358
+ // The profiles named by --profile, vetted, or the exit code that says
3359
+ // why not. A profile is a language declared as data: `render` matches
3360
+ // one to a unit by `lang`, and `template` and `fmt` match one to a file
3361
+ // by the extensions its `template.ext` names.
3362
+ function loadProfiles(
3363
+ profileFiles: string[], trust: TrustArg
3364
+ ): any[] | number {
3365
+ const profiles: any[] = []
3366
+ const langs = new Map<string, string>()
3367
+ for (const pf of profileFiles) {
3368
+ let text: string
3369
+ try {
3370
+ text = readFileSync(pf, 'utf8')
3371
+ }
3372
+ catch (err: any) {
3373
+ process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
3374
+ return 2
3375
+ }
3376
+ const loaded = renderProfile(text,
3377
+ { path: resolve(pf), ...verbOpts(trust, entryRootOf(pf)) })
3378
+ if (undefined !== loaded.errors) {
3379
+ process.stderr.write(loaded.errors.map(renderFinding).join('\n') + '\n')
3380
+ return 4
3381
+ }
3382
+ const profile = loaded.profile
3383
+ const prev = langs.get(profile.lang)
3384
+ if (undefined !== prev) {
3385
+ process.stderr.write(
3386
+ `aontu: two profiles claim ${profile.lang}: ${prev} and ${pf}\n`)
3387
+ return 2
3388
+ }
3389
+ langs.set(profile.lang, pf)
3390
+ profiles.push(profile)
3391
+ }
3392
+ return profiles
3393
+ }
3394
+
3780
3395
 
3781
3396
  const HASH_HELP = 'aontu hash <file> (try --help)'
3782
3397
 
@@ -3837,14 +3452,6 @@ function runHash(argv: string[]): number {
3837
3452
  const ctx = aontu.ctx({ collect: true })
3838
3453
  const v: any = aontu.unify(src, { path: files[0] }, ctx)
3839
3454
  if (0 < ctx.err.length || true === v?.isNil) {
3840
- // A document that does not stand up on its own has no meaning to
3841
- // pin, and a hash of a broken evaluation would be a pin that
3842
- // silently agrees with every other broken evaluation.
3843
- // WHY it does not stand up, not just that it does not: the same
3844
- // diagnosis `aontu <file>` prints (the review's finding F).
3845
- // evalFailure unconditionally, as every other call site does: it
3846
- // owns the "ctx.err is never empty here" contract, and a guard
3847
- // that pretends otherwise is a dead arm asserting nothing.
3848
3455
  process.stderr.write(
3849
3456
  `aontu: ${files[0]} does not evaluate on its own; nothing to hash\n` +
3850
3457
  renderFinding(evalFailure(ctx)) + '\n')
@@ -3863,13 +3470,6 @@ function runHash(argv: string[]): number {
3863
3470
  }
3864
3471
 
3865
3472
 
3866
- // ---------------------------------------------------------------------
3867
- // The query surface (G7 phase 1): one node of an evaluated document,
3868
- // selected by path and rendered. Evaluation is still GLOBAL -- what
3869
- // `get` buys is the size of the ANSWER, not the cost of producing it --
3870
- // and the projections are lattice abstractions, each a valid Aontu
3871
- // document that subsumes the truth it summarises.
3872
-
3873
3473
  const GET_HELP = 'aontu get <path> <file> (try --help)'
3874
3474
 
3875
3475
  function runGet(argv: string[]): number {
@@ -4057,11 +3657,6 @@ function runWhy(argv: string[]): number {
4057
3657
  }
4058
3658
 
4059
3659
 
4060
- // One contribution per line, numbered in source order, each with what
4061
- // was written, where, and how it got here. A siteless contribution
4062
- // prints no location rather than a `-1:-1` that means nothing —
4063
- // exported for the direct test, because the site SHAPE allows one
4064
- // while no document has yet produced one (ADR-002).
4065
3660
  function renderWhyText(record: WhyRecord): string {
4066
3661
  const head = `${record.path} = ${record.value}`
4067
3662
  if (0 === record.conjuncts.length) {
@@ -4079,13 +3674,6 @@ function renderWhyText(record: WhyRecord): string {
4079
3674
  }
4080
3675
 
4081
3676
 
4082
- // ---------------------------------------------------------------------
4083
- // The overlay patch verb (G7 phase 5): change a document by APPENDING
4084
- // to an overlay, not by rewriting it. An overlay entry is just another
4085
- // conjunct and unification is order-independent, so this needs no
4086
- // rewriter — the format-preserving in-place edit is stage 2, and needs
4087
- // a comment-preserving CST the parser stack does not have.
4088
-
4089
3677
  const SET_HELP =
4090
3678
  'aontu set <path>=<value> --entry <file> --overlay <file> (try --help)'
4091
3679
 
@@ -4202,29 +3790,12 @@ function runSet(argv: string[]): number {
4202
3790
  }, 2) + '\n')
4203
3791
  }
4204
3792
  else {
4205
- // A replacement is REPORTED as the edit it is, not left for the
4206
- // reader to infer from a changed file: `where: what -> what`, in
4207
- // source spelling, because the spelling is what changed.
4208
- //
4209
- // PAST TENSE ONLY WHERE IT HAPPENED. A refused write leaves the
4210
- // file exactly as it was, and one assignment can be replaceable
4211
- // while another makes the whole run invalid — so `replaced:` there
4212
- // tells an operator the pin was changed when it was not, and unlike
4213
- // `--dry-run` there is nothing else on the line to say otherwise.
4214
3793
  const verb = wrote ? 'replaced' : 'would replace'
4215
3794
  const edits = report.replaced.map((r) =>
4216
3795
  `${verb}: ${r.file}:${r.row}:${r.col} ${r.from} -> ${r.to}`)
4217
3796
  const head = [`verdict: ${report.verdict}`].concat(edits).join('\n') +
4218
3797
  (wrote ? `\nwrote: ${overlayFile}` : dryRun ? '\n(dry run)' : '')
4219
3798
 
4220
- // A SUCCESSFUL COMMAND WRITES ITS STATUS TO STDOUT, findings or
4221
- // not. Routing on `findings.length` was right while every finding
4222
- // this verb could produce was an ERROR; `--in-place` made a WARNING
4223
- // possible, and a run that held, wrote the file and exited 0 then
4224
- // sent its whole report to stderr — leaving stdout empty, so
4225
- // `$(aontu set ...)` captured nothing and only the JSON form
4226
- // behaved like a success. The verdict decides the stream; warnings
4227
- // are diagnostics and go to stderr beside it.
4228
3799
  const failed = 'invalid' === report.verdict || 'error' === report.verdict
4229
3800
  const findingText = report.findings.map(renderFinding)
4230
3801
  if (failed) {
@@ -4246,15 +3817,6 @@ function runSet(argv: string[]): number {
4246
3817
  }
4247
3818
 
4248
3819
 
4249
- // ---------------------------------------------------------------------
4250
- // The role gate (docs/design/ALLOW.0.md): may the role the caller is
4251
- // operating under modify these subtrees? Asked before `set`, by an
4252
- // agent whose skill names its role, and answered from a role model
4253
- // that is itself an aontu document. The verdict is the exit code, as
4254
- // it is for every gate here: 0 is yes, 1 is no, 4 is "the model that
4255
- // was to decide does not stand up", and an agent branches on nothing
4256
- // else.
4257
-
4258
3820
  const ALLOW_HELP =
4259
3821
  'aontu allow --role <role> <roles-file> <path> [more-paths...] (try --help)'
4260
3822
 
@@ -4372,14 +3934,6 @@ function runAllow(argv: string[]): number {
4372
3934
  return 2
4373
3935
  }
4374
3936
 
4375
- // A path may arrive in `set`'s spelling, `$.a.b=1`, so a skill can
4376
- // hand the gate the very arguments the write will get. The text up
4377
- // to the first `=` is the path, and it starts with `$`: an empty
4378
- // argument, or a second file name, would otherwise read as a path
4379
- // and be answered. The VALUE is checked to be one value. `set`
4380
- // appends it as source after the flattened path, so a value carrying
4381
- // a second pair -- `3 secrets: key: "x"` -- writes a sibling of the
4382
- // overlay root, a subtree the gate was never asked about.
4383
3937
  const paths: string[] = []
4384
3938
  for (const arg of asked) {
4385
3939
  const eq = arg.indexOf('=')
@@ -4535,35 +4089,23 @@ function runAgentsMd(argv: string[]): number {
4535
4089
  }
4536
4090
 
4537
4091
 
4538
- // Exit without truncating output.
4539
- //
4540
- // process.exit() terminates immediately, discarding anything still
4541
- // queued on stdout. A write to a PIPE is asynchronous once it exceeds
4542
- // the pipe buffer, so `write(big); exit(0)` silently truncated output at
4543
- // 65536 bytes — while a write to a TTY or a file, being synchronous,
4544
- // looked fine. Setting exitCode instead lets the process end naturally,
4545
- // after the queue drains.
4546
- //
4547
- // This predates the exact leaves but they make it trivially reachable
4548
- // (one long biginteger canon exceeds the buffer), and it lands squarely
4549
- // on the parity-probe discipline in AGENTS.md, which derives expected
4550
- // spec values by piping BOTH CLIs and comparing. A truncated pipe there
4551
- // reads as a port divergence.
4552
- // ---------------------------------------------------------------------
4553
- // The source formatter (docs/design/FMT.0.md): one agreed form, in the
4554
- // tradition of gofmt. The verb prints, lists, checks, diffs or rewrites;
4555
- // the form itself is the library's (ts/src/format.ts), and the two
4556
- // ports agree on it row by row in test/spec/fmt.tsv.
4557
-
4558
4092
  const FMT_HELP =
4559
- 'aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] <file>... (try --help)'
4093
+ 'aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] ' +
4094
+ '[--profile <file>] <file>... (try --help)'
4560
4095
 
4561
4096
  type FmtFlags = {
4562
4097
  write: boolean, list: boolean, check: boolean, diff: boolean, lint: boolean, strict: boolean,
4563
4098
  }
4564
4099
 
4565
4100
  function runFmt(argv: string[]): number | Promise<number> {
4101
+ const trusted = takeTrust(argv)
4102
+ if (null == trusted) {
4103
+ return 2
4104
+ }
4105
+ argv = trusted.argv
4106
+ const trust = trusted.trust
4566
4107
  const files: string[] = []
4108
+ const profileFiles: string[] = []
4567
4109
  let marker: string | undefined = undefined
4568
4110
  const flags: FmtFlags = {
4569
4111
  write: false, list: false, check: false, diff: false, lint: false, strict: false,
@@ -4604,6 +4146,14 @@ function runFmt(argv: string[]): number | Promise<number> {
4604
4146
  return 2
4605
4147
  }
4606
4148
  }
4149
+ else if ('--profile' === arg) {
4150
+ const pf = argv[++i]
4151
+ if (null == pf) {
4152
+ process.stderr.write('aontu: --profile needs a file\n')
4153
+ return 2
4154
+ }
4155
+ profileFiles.push(pf)
4156
+ }
4607
4157
  else if (arg.startsWith('-')) {
4608
4158
  process.stderr.write(`aontu: unknown fmt option ${arg} (try --help)\n`)
4609
4159
  return 2
@@ -4629,6 +4179,11 @@ function runFmt(argv: string[]): number | Promise<number> {
4629
4179
  })
4630
4180
  }
4631
4181
 
4182
+ const declared = loadProfiles(profileFiles, trust)
4183
+ if ('number' === typeof declared) {
4184
+ return declared
4185
+ }
4186
+
4632
4187
  // Several files onto standard output would be one stream nobody can
4633
4188
  // split again (the note's X-6): the verb refuses unless an option
4634
4189
  // says what to do with each.
@@ -4649,13 +4204,14 @@ function runFmt(argv: string[]): number | Promise<number> {
4649
4204
  process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
4650
4205
  return 2
4651
4206
  }
4652
- const mark = fmtMarker(file, src, marker)
4207
+ const mark = fmtMarker(file, src,
4208
+ marker ?? markerFromProfiles(declared, file))
4653
4209
  if (false === mark) {
4654
4210
  process.stderr.write(
4655
4211
  `aontu: ${file} is not aontu source (.aon, .aontu) and carries no ` +
4656
4212
  `${markerFor(file)} marker line, so there is no aontu in it to ` +
4657
- 'format; --marker names the marker for a language the table does ' +
4658
- 'not know\n')
4213
+ 'format; --marker names the marker for a language the table ' +
4214
+ 'does not know, and --profile reads one that declares it\n')
4659
4215
  return 2
4660
4216
  }
4661
4217
  worst = Math.max(worst, fmtOne(file, src, flags, mark))
@@ -4663,20 +4219,6 @@ function runFmt(argv: string[]): number | Promise<number> {
4663
4219
  return worst
4664
4220
  }
4665
4221
 
4666
- // WHAT A FILE IS, BY ITS EXTENSION (ADR-012's rule, and the one
4667
- // `render` reads an entry by): `.aon` and `.aontu` are aontu source,
4668
- // and anything else is a GENERATOR written in the target's own syntax
4669
- // (docs/design/TEMPLATE.0.md), whose marker lines carry the document
4670
- // this formats and whose other lines are output. `undefined` is aontu,
4671
- // a string is the generator's marker, and `false` is neither.
4672
- //
4673
- // A FILE WITH NO MARKER LINE IN IT IS NEITHER, and that is what keeps
4674
- // FMT.0.md §9's boundary where it stood: a `.json`, `.yaml` or `.toml`
4675
- // include is another language's file, and reading one as a generator
4676
- // would answer it back unchanged having understood none of it. The
4677
- // marker is the evidence that a file was written to carry aontu at
4678
- // all. `--marker` says so outright, and then the file is a generator
4679
- // whatever it is called.
4680
4222
  function fmtMarker(
4681
4223
  file: string, src: string, marker: string | undefined): string | undefined | false {
4682
4224
  if (undefined !== marker) {
@@ -4738,26 +4280,13 @@ function fmtOne(
4738
4280
  }
4739
4281
 
4740
4282
 
4741
- // ---------------------------------------------------------------------
4742
- // The servers as verbs: `aontu lsp` runs the language server and
4743
- // `aontu mcp` the MCP server over the CLI's own streams, so the one
4744
- // command on PATH is the editor's and the agent's server too, and a
4745
- // version manager (docs/design/ENV.0.md) has one thing to resolve. The
4746
- // standalone bins (aontu-lsp, aontu-mcp) run the same functions. Each
4747
- // server owns its exit; the CLI only dispatches. The pair is a
4748
- // parameter of main so that a test can see the dispatch without a
4749
- // server taking the test's own stdin.
4750
-
4751
4283
  type Servers = {
4752
4284
  lsp: () => void
4753
4285
  mcp: (argv: string[]) => void
4754
4286
  }
4755
4287
 
4756
- // The real pair takes the process's own stdin and stdout, which no
4757
- // in-process test can lend it; the executable-entry tests in
4758
- // cli.test.ts run each through a child process instead, so these two
4759
- // lines are excluded from the in-process count, as the stdio wiring
4760
- // of lsp-server.ts is.
4288
+ // Excluded: the real pair takes the process stdio, so ts/test/cli.test.ts
4289
+ // drives each server through a child process instead.
4761
4290
  /* node:coverage ignore next 4 */
4762
4291
  const SERVERS: Servers = {
4763
4292
  lsp: () => void lspMain(),
@@ -4804,20 +4333,6 @@ function parseTrustArg(value: string): TrustArg | undefined {
4804
4333
  }
4805
4334
 
4806
4335
 
4807
- // THE TEACHING PACK, SERVED FROM THE COMMAND (G11 phase 1,
4808
- // docs/capability-review/g11-agent-onramp.md; mirrors
4809
- // go/cmd/aontu/help.go).
4810
- //
4811
- // HELP documents the TOOLCHAIN and says nothing about the LANGUAGE:
4812
- // `&`, the map template and the one construct an ontology cannot be
4813
- // written without, occurs zero times in it, while `template` occurs
4814
- // fourteen times and names an unrelated verb every time. docs/skill/
4815
- // was already the right content and already gated; the gap was
4816
- // DELIVERY, since it reached an installation as
4817
- // node_modules/aontu/skill/ where nothing looks. ts/src/helpdoc.ts is
4818
- // generated from those sources by ts/scripts/helpdoc.cjs and asserted
4819
- // byte-identical with them by ts/test/helpdoc.test.ts.
4820
-
4821
4336
  const HELP_VERB_HELP = 'aontu help [topic] (try `aontu help` for the topics)'
4822
4337
  const EXPLAIN_HELP = 'aontu explain <code> (try `aontu explain --list`)'
4823
4338
 
@@ -4903,23 +4418,6 @@ function runHelp(argv: string[]): number {
4903
4418
  }
4904
4419
 
4905
4420
 
4906
- // `aontu explain <code>` (G11 phase 3; mirrors
4907
- // go/cmd/aontu/explain.go).
4908
- //
4909
- // THE REGISTRY IS THE LIST, NOT THE HINT TABLE. test/spec/errcodes.tsv
4910
- // registers 157 codes and the spec suite asserts set equality between
4911
- // the file and codeClasses IN BOTH PORTS, so listing from codeClasses
4912
- // is listing the shared contract. The hint tables are smaller and are
4913
- // NOT in parity -- 130 entries here against 131 in Go, the extra being
4914
- // decimal_syntax, which this port never raises -- so listing from them
4915
- // would make `aontu explain --list` differ between ports over a
4916
- // difference that is not about what either port can report.
4917
- //
4918
- // A REGISTERED CODE WITH NO HINT ANSWERS WITH ITS CLASS AND SAYS SO.
4919
- // Twenty-seven registered codes carry no explanation text here; before
4920
- // this verb their absence was invisible, because a hint is only ever
4921
- // seen beside the error that raises it.
4922
-
4923
4421
  // The dynamic prefixes a generated code extends (`func:upper`,
4924
4422
  // `op[+]`). Mirrors CODE_PREFIXES in ts/src/hints.ts, which is not
4925
4423
  // exported; a code that extends one is registered through its prefix
@@ -5062,21 +4560,6 @@ function runExplain(argv: string[]): number {
5062
4560
  }
5063
4561
 
5064
4562
 
5065
- // `aontu init` (G11 phase 6,
5066
- // docs/capability-review/g11-agent-onramp.md).
5067
- //
5068
- // NOT SCAFFOLDING CONVENIENCE. The agent's most expensive failure is
5069
- // writing a FIRST document at all: the measurement that opened G11
5070
- // found one reaching for the wildcard its neighbours use and getting
5071
- // `verdict: valid` over data that violates it. A known-good starting
5072
- // document turns generation into editing, which is the operation a
5073
- // model is reliably good at.
5074
- //
5075
- // The trio is real, runnable and tested where it lives
5076
- // (docs/skill/init/, run by ts/test/helpdoc.test.ts), and staged into
5077
- // both ports by the same generator that stages the teaching pack, so
5078
- // the two write the same bytes.
5079
-
5080
4563
  const INIT_HELP = 'aontu init [dir] (try --help)'
5081
4564
 
5082
4565
 
@@ -5101,10 +4584,6 @@ function runInit(argv: string[]): number {
5101
4584
  }
5102
4585
  const dir = dirs[0] ?? '.'
5103
4586
 
5104
- // REFUSES TO OVERWRITE, and checks every member BEFORE writing any of
5105
- // them: a scaffold that wrote two files and then refused the third
5106
- // would leave a directory in a state neither the caller nor a re-run
5107
- // can reason about.
5108
4587
  const standing = INITDOC.filter((f) => existsSync(join(dir, f.name)))
5109
4588
  if (0 < standing.length) {
5110
4589
  process.stderr.write(
@@ -5134,12 +4613,6 @@ function runInit(argv: string[]): number {
5134
4613
  }
5135
4614
 
5136
4615
 
5137
- // EVERY VERB THIS PORT DISPATCHES, for the nearest-verb suggestion
5138
- // G11 phase 2 prints. A separate list from the if-chain in main()
5139
- // because the chain's arms have three different shapes and cannot be
5140
- // a table; ts/test/cli-help.test.ts keeps the two from drifting by
5141
- // running each name and requiring it not to fall through to the bare
5142
- // command.
5143
4616
  const KNOWN_VERBS = [
5144
4617
  'agentsmd', 'allow', 'breaking', 'explain', 'fmt', 'get', 'hash',
5145
4618
  'help', 'init', 'jsonschema', 'lsp', 'mcp', 'mod', 'reaches',
@@ -5159,13 +4632,6 @@ function looksLikeVerb(arg: string): boolean {
5159
4632
  }
5160
4633
 
5161
4634
 
5162
- // NEAREST-VERB SUGGESTION (G11 phase 2). Restricted
5163
- // Damerau-Levenshtein with a cap that grows with the word and stops at
5164
- // three: one edit is a convincing suggestion on any length, three is
5165
- // the most that can be believed on a long one, and an UNCAPPED
5166
- // nearest match on a three-letter typo names something unrelated with
5167
- // confidence. Mirrors go/cmd/aontu/help.go, including the sort, so
5168
- // the two ports suggest the same verb on a tie.
5169
4635
  function nearestVerb(word: string, verbs: string[]): string {
5170
4636
  let best = ''
5171
4637
  let bestDist = Infinity
@@ -5181,8 +4647,6 @@ function nearestVerb(word: string, verbs: string[]): string {
5181
4647
  }
5182
4648
 
5183
4649
 
5184
- // Levenshtein with a transposition, iterative over two rows. Mirrors
5185
- // editDistance in go/cmd/aontu/help.go exactly.
5186
4650
  function editDistance(a: string, b: string): number {
5187
4651
  const ar = [...a]
5188
4652
  const br = [...b]
@@ -5209,12 +4673,6 @@ function editDistance(a: string, b: string): number {
5209
4673
 
5210
4674
 
5211
4675
  function main(argv: string[], servers: Servers = SERVERS): void {
5212
- // COLOUR OFF WHEN THE DESTINATION IS NOT A TERMINAL. Error frames
5213
- // hardcoded their ANSI escapes, so a piped report and a `--jsonl`
5214
- // answer carried terminal control codes into whatever read them (the
5215
- // review's finding F). `NO_COLOR` is honoured by the library itself;
5216
- // only the CLI can see whether its stderr is a terminal, so only the
5217
- // CLI can make this call. `undefined` means "leave it to NO_COLOR".
5218
4676
  setColor(true === process.stderr.isTTY ? undefined : false)
5219
4677
 
5220
4678
  let mode: Mode = 'json'
@@ -5222,13 +4680,6 @@ function main(argv: string[], servers: Servers = SERVERS): void {
5222
4680
  // reads exactly what it always read, and a caller that asks for json
5223
4681
  // gets the failure in the finding shape every other verb reports.
5224
4682
  let format: EvalFormat = 'text'
5225
- // A LIST, though the bare command evaluates exactly one document.
5226
- // It used to be one variable and the last argument won, which made a
5227
- // MISTYPED VERB a silent success: `aontu vet2 schema.aon good.json`
5228
- // printed good.json and exited 0, because `vet2` matched no
5229
- // subcommand, fell through to this loop as a file name, and was
5230
- // overwritten twice. In a tool loop that reads as a passing
5231
- // validation. Counting them is what lets the refusal below happen.
5232
4683
  const files: string[] = []
5233
4684
  let trust: TrustArg = { kind: 'system-warn', textExt: [] }
5234
4685
  let textExt: string[] = []
@@ -5238,15 +4689,6 @@ function main(argv: string[], servers: Servers = SERVERS): void {
5238
4689
  // mode the REPL already has.
5239
4690
  let jsonl = false
5240
4691
 
5241
- // Subcommand dispatch, and deliberately only for a FIRST argument:
5242
- // `aontu vet` is the verb, while `aontu somefile vet` keeps meaning
5243
- // what it always did. A file named `vet` is still reachable as
5244
- // `aontu ./vet`.
5245
- //
5246
- // Promise.resolve either way: a non-watch run returns its exit class
5247
- // synchronously (and has already written its report), while `--watch`
5248
- // resolves only when the watch ends — so one await-shaped line serves
5249
- // both without a branch to keep covered.
5250
4692
  if ('vet' === argv[2]) {
5251
4693
  return void Promise.resolve(runVet(argv.slice(3))).then(finish)
5252
4694
  }
@@ -5403,14 +4845,6 @@ function main(argv: string[], servers: Servers = SERVERS): void {
5403
4845
  }
5404
4846
  }
5405
4847
 
5406
- // ONE DOCUMENT. The bare form has always been `aontu [options]
5407
- // [file]`, singular, and anything past the first was silently
5408
- // discarded rather than refused -- so every way of getting the verb
5409
- // wrong (a typo, a verb this port does not have, a verb spelled for
5410
- // another tool) ended in a plausible answer about the wrong file.
5411
- // Exit 2, the usage class, and the message names the cause rather
5412
- // than the symptom: nothing here can tell a mistyped verb from a
5413
- // second file, but the reader can.
5414
4848
  if (1 < files.length) {
5415
4849
  process.stderr.write(
5416
4850
  `aontu: the bare command evaluates one document, and ${files.length}` +
@@ -5419,9 +4853,6 @@ function main(argv: string[], servers: Servers = SERVERS): void {
5419
4853
  return finish(2)
5420
4854
  }
5421
4855
 
5422
- // The extensions ride with the capability from here on, so the three
5423
- // entry shapes below (file, REPL, stdin) each get them by threading
5424
- // the one value they already thread.
5425
4856
  trust = { ...trust, textExt }
5426
4857
 
5427
4858
  const file = files[0]