aontu 0.62.0 → 0.64.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 (373) hide show
  1. package/README.md +7 -7
  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 +3 -3
  8. package/dist/aontu.js +4 -84
  9. package/dist/aontu.js.map +1 -1
  10. package/dist/aontumodel.d.ts +4 -0
  11. package/dist/aontumodel.js +27 -0
  12. package/dist/aontumodel.js.map +1 -0
  13. package/dist/casing.d.ts +5 -0
  14. package/dist/casing.js +90 -0
  15. package/dist/casing.js.map +1 -0
  16. package/dist/cli.d.ts +2 -2
  17. package/dist/cli.js +228 -935
  18. package/dist/cli.js.map +1 -1
  19. package/dist/ctx.js +0 -48
  20. package/dist/ctx.js.map +1 -1
  21. package/dist/diff.js +0 -32
  22. package/dist/diff.js.map +1 -1
  23. package/dist/err.js +0 -40
  24. package/dist/err.js.map +1 -1
  25. package/dist/escape.js +0 -45
  26. package/dist/escape.js.map +1 -1
  27. package/dist/exactjson.d.ts +0 -35
  28. package/dist/exactjson.js +0 -131
  29. package/dist/exactjson.js.map +1 -1
  30. package/dist/format.js +13 -203
  31. package/dist/format.js.map +1 -1
  32. package/dist/grammar.d.ts +9 -0
  33. package/dist/grammar.js +54 -0
  34. package/dist/grammar.js.map +1 -0
  35. package/dist/graph.js +0 -26
  36. package/dist/graph.js.map +1 -1
  37. package/dist/hcanon.js +0 -82
  38. package/dist/hcanon.js.map +1 -1
  39. package/dist/helpdoc.js +2 -2
  40. package/dist/helpdoc.js.map +1 -1
  41. package/dist/hints.d.ts +0 -6
  42. package/dist/hints.js +57 -55
  43. package/dist/hints.js.map +1 -1
  44. package/dist/jsonschema.js +0 -114
  45. package/dist/jsonschema.js.map +1 -1
  46. package/dist/keyorder.d.ts +0 -7
  47. package/dist/keyorder.js +0 -41
  48. package/dist/keyorder.js.map +1 -1
  49. package/dist/lang.js +17 -915
  50. package/dist/lang.js.map +1 -1
  51. package/dist/lsp-server.js +0 -16
  52. package/dist/lsp-server.js.map +1 -1
  53. package/dist/lsp.d.ts +1 -1
  54. package/dist/lsp.js +12 -159
  55. package/dist/lsp.js.map +1 -1
  56. package/dist/mcp-server.js +0 -26
  57. package/dist/mcp-server.js.map +1 -1
  58. package/dist/mcp.js +0 -149
  59. package/dist/mcp.js.map +1 -1
  60. package/dist/mod-tool.js +16 -131
  61. package/dist/mod-tool.js.map +1 -1
  62. package/dist/mod.js +0 -162
  63. package/dist/mod.js.map +1 -1
  64. package/dist/patch.js +0 -217
  65. package/dist/patch.js.map +1 -1
  66. package/dist/profile.d.ts +9 -0
  67. package/dist/profile.js +28 -0
  68. package/dist/profile.js.map +1 -0
  69. package/dist/provenance.js +0 -140
  70. package/dist/provenance.js.map +1 -1
  71. package/dist/query.js +0 -75
  72. package/dist/query.js.map +1 -1
  73. package/dist/reach.js +0 -43
  74. package/dist/reach.js.map +1 -1
  75. package/dist/relation.js +0 -61
  76. package/dist/relation.js.map +1 -1
  77. package/dist/report-sarif.d.ts +0 -11
  78. package/dist/report-sarif.js +0 -28
  79. package/dist/report-sarif.js.map +1 -1
  80. package/dist/sig.js +0 -35
  81. package/dist/sig.js.map +1 -1
  82. package/dist/sigdecl.js +1 -1
  83. package/dist/sigdecl.js.map +1 -1
  84. package/dist/siggate.js +0 -4
  85. package/dist/siggate.js.map +1 -1
  86. package/dist/site.js +3 -29
  87. package/dist/site.js.map +1 -1
  88. package/dist/subsume.d.ts +0 -10
  89. package/dist/subsume.js +0 -137
  90. package/dist/subsume.js.map +1 -1
  91. package/dist/template.d.ts +2 -1
  92. package/dist/template.js +58 -138
  93. package/dist/template.js.map +1 -1
  94. package/dist/trace.d.ts +21 -0
  95. package/dist/trace.js +107 -0
  96. package/dist/trace.js.map +1 -0
  97. package/dist/trim.js +0 -41
  98. package/dist/trim.js.map +1 -1
  99. package/dist/tsconfig.tsbuildinfo +1 -1
  100. package/dist/type.js.map +1 -1
  101. package/dist/unify.js +13 -248
  102. package/dist/unify.js.map +1 -1
  103. package/dist/utility.js +0 -22
  104. package/dist/utility.js.map +1 -1
  105. package/dist/val/AbnfFuncVal.d.ts +18 -0
  106. package/dist/val/AbnfFuncVal.js +132 -0
  107. package/dist/val/AbnfFuncVal.js.map +1 -0
  108. package/dist/val/AbsentVal.d.ts +11 -0
  109. package/dist/val/AbsentVal.js +30 -0
  110. package/dist/val/AbsentVal.js.map +1 -0
  111. package/dist/val/AggFuncVal.d.ts +10 -1
  112. package/dist/val/AggFuncVal.js +104 -116
  113. package/dist/val/AggFuncVal.js.map +1 -1
  114. package/dist/val/ArithFuncVal.js +0 -12
  115. package/dist/val/ArithFuncVal.js.map +1 -1
  116. package/dist/val/BagVal.js +1 -78
  117. package/dist/val/BagVal.js.map +1 -1
  118. package/dist/val/BigDecimalVal.js +0 -16
  119. package/dist/val/BigDecimalVal.js.map +1 -1
  120. package/dist/val/BigIntegerVal.js +0 -16
  121. package/dist/val/BigIntegerVal.js.map +1 -1
  122. package/dist/val/CloseFuncVal.js +0 -9
  123. package/dist/val/CloseFuncVal.js.map +1 -1
  124. package/dist/val/CmpFuncVal.d.ts +2 -0
  125. package/dist/val/CmpFuncVal.js +49 -72
  126. package/dist/val/CmpFuncVal.js.map +1 -1
  127. package/dist/val/ConjunctVal.js +0 -29
  128. package/dist/val/ConjunctVal.js.map +1 -1
  129. package/dist/val/ConstraintVal.js +0 -500
  130. package/dist/val/ConstraintVal.js.map +1 -1
  131. package/dist/val/ContainerKindVal.js +0 -2
  132. package/dist/val/ContainerKindVal.js.map +1 -1
  133. package/dist/val/CopyFuncVal.js +0 -3
  134. package/dist/val/CopyFuncVal.js.map +1 -1
  135. package/dist/val/Decimal.js +0 -179
  136. package/dist/val/Decimal.js.map +1 -1
  137. package/dist/val/DeprecateFuncVal.js.map +1 -1
  138. package/dist/val/DisjunctVal.js +0 -152
  139. package/dist/val/DisjunctVal.js.map +1 -1
  140. package/dist/val/EachFuncVal.js +0 -3
  141. package/dist/val/EachFuncVal.js.map +1 -1
  142. package/dist/val/EmitFuncVal.d.ts +1 -1
  143. package/dist/val/EmitFuncVal.js +6 -119
  144. package/dist/val/EmitFuncVal.js.map +1 -1
  145. package/dist/val/ExpectVal.js +0 -62
  146. package/dist/val/ExpectVal.js.map +1 -1
  147. package/dist/val/FilterFuncVal.js +0 -25
  148. package/dist/val/FilterFuncVal.js.map +1 -1
  149. package/dist/val/FuncBaseVal.d.ts +1 -0
  150. package/dist/val/FuncBaseVal.js +7 -127
  151. package/dist/val/FuncBaseVal.js.map +1 -1
  152. package/dist/val/GraphAtomVal.js +0 -15
  153. package/dist/val/GraphAtomVal.js.map +1 -1
  154. package/dist/val/HideFuncVal.js +0 -13
  155. package/dist/val/HideFuncVal.js.map +1 -1
  156. package/dist/val/IntegerVal.js +0 -61
  157. package/dist/val/IntegerVal.js.map +1 -1
  158. package/dist/val/JunctionVal.js +0 -20
  159. package/dist/val/JunctionVal.js.map +1 -1
  160. package/dist/val/KeyFuncVal.js +0 -46
  161. package/dist/val/KeyFuncVal.js.map +1 -1
  162. package/dist/val/ListVal.js +0 -57
  163. package/dist/val/ListVal.js.map +1 -1
  164. package/dist/val/LowerFuncVal.js +0 -11
  165. package/dist/val/LowerFuncVal.js.map +1 -1
  166. package/dist/val/MapVal.js +0 -151
  167. package/dist/val/MapVal.js.map +1 -1
  168. package/dist/val/MatchFuncVal.js +0 -27
  169. package/dist/val/MatchFuncVal.js.map +1 -1
  170. package/dist/val/{FormFuncVal.d.ts → MaybeFuncVal.d.ts} +5 -5
  171. package/dist/val/MaybeFuncVal.js +50 -0
  172. package/dist/val/MaybeFuncVal.js.map +1 -0
  173. package/dist/val/MoveFuncVal.js +0 -18
  174. package/dist/val/MoveFuncVal.js.map +1 -1
  175. package/dist/val/NilVal.js +0 -60
  176. package/dist/val/NilVal.js.map +1 -1
  177. package/dist/val/NomFuncVal.js +18 -54
  178. package/dist/val/NomFuncVal.js.map +1 -1
  179. package/dist/val/NumberVal.js +0 -15
  180. package/dist/val/NumberVal.js.map +1 -1
  181. package/dist/val/OpBaseVal.d.ts +1 -0
  182. package/dist/val/OpBaseVal.js +3 -15
  183. package/dist/val/OpBaseVal.js.map +1 -1
  184. package/dist/val/PackFuncVal.js +0 -34
  185. package/dist/val/PackFuncVal.js.map +1 -1
  186. package/dist/val/PathFuncVal.js +0 -6
  187. package/dist/val/PathFuncVal.js.map +1 -1
  188. package/dist/val/PathVal.js +0 -41
  189. package/dist/val/PathVal.js.map +1 -1
  190. package/dist/val/PlaceVal.js +0 -25
  191. package/dist/val/PlaceVal.js.map +1 -1
  192. package/dist/val/PlusOpVal.d.ts +1 -7
  193. package/dist/val/PlusOpVal.js +13 -74
  194. package/dist/val/PlusOpVal.js.map +1 -1
  195. package/dist/val/PrefFuncVal.js +0 -1
  196. package/dist/val/PrefFuncVal.js.map +1 -1
  197. package/dist/val/PrefVal.js +0 -167
  198. package/dist/val/PrefVal.js.map +1 -1
  199. package/dist/val/RecurseVal.js +0 -55
  200. package/dist/val/RecurseVal.js.map +1 -1
  201. package/dist/val/RefVal.js +0 -282
  202. package/dist/val/RefVal.js.map +1 -1
  203. package/dist/val/ReferFuncVal.js +3 -232
  204. package/dist/val/ReferFuncVal.js.map +1 -1
  205. package/dist/val/ScalarKindVal.js +0 -49
  206. package/dist/val/ScalarKindVal.js.map +1 -1
  207. package/dist/val/ScalarVal.js +0 -11
  208. package/dist/val/ScalarVal.js.map +1 -1
  209. package/dist/val/StrFuncVal.js +0 -18
  210. package/dist/val/StrFuncVal.js.map +1 -1
  211. package/dist/val/SuperFuncVal.js +0 -32
  212. package/dist/val/SuperFuncVal.js.map +1 -1
  213. package/dist/val/TopVal.js +0 -1
  214. package/dist/val/TopVal.js.map +1 -1
  215. package/dist/val/TranslateFuncVal.js +1 -3
  216. package/dist/val/TranslateFuncVal.js.map +1 -1
  217. package/dist/val/UpperFuncVal.js +0 -11
  218. package/dist/val/UpperFuncVal.js.map +1 -1
  219. package/dist/val/Val.d.ts +1 -0
  220. package/dist/val/Val.js +2 -133
  221. package/dist/val/Val.js.map +1 -1
  222. package/dist/val/VarVal.js +0 -12
  223. package/dist/val/VarVal.js.map +1 -1
  224. package/dist/val/arith.js +0 -37
  225. package/dist/val/arith.js.map +1 -1
  226. package/dist/val/caserange.js +0 -61
  227. package/dist/val/caserange.js.map +1 -1
  228. package/dist/val/members.js +0 -6
  229. package/dist/val/members.js.map +1 -1
  230. package/dist/val/numcmp.js +0 -11
  231. package/dist/val/numcmp.js.map +1 -1
  232. package/dist/val/numkind.js +0 -145
  233. package/dist/val/numkind.js.map +1 -1
  234. package/dist/val/valutil.js +0 -16
  235. package/dist/val/valutil.js.map +1 -1
  236. package/dist/vet.js +0 -461
  237. package/dist/vet.js.map +1 -1
  238. package/dist/view.js +0 -414
  239. package/dist/view.js.map +1 -1
  240. package/dist/walk.js +0 -41
  241. package/dist/walk.js.map +1 -1
  242. package/grammar/aontu.abnf +8 -6
  243. package/grammar/aontu.gbnf +4 -4
  244. package/grammar/aontu.lark +4 -4
  245. package/grammar/aontu.tmLanguage.json +1 -1
  246. package/package.json +4 -2
  247. package/skill/tasks.md +9 -7
  248. package/src/agentsmd.ts +0 -35
  249. package/src/alias.ts +0 -39
  250. package/src/allow.ts +1 -96
  251. package/src/aontu.ts +4 -116
  252. package/src/aontumodel.ts +26 -0
  253. package/src/casing.ts +95 -0
  254. package/src/cli.ts +258 -1024
  255. package/src/ctx.ts +0 -103
  256. package/src/diff.ts +0 -40
  257. package/src/err.ts +0 -40
  258. package/src/escape.ts +0 -46
  259. package/src/exactjson.ts +0 -131
  260. package/src/format.ts +14 -257
  261. package/src/grammar.ts +72 -0
  262. package/src/graph.ts +0 -61
  263. package/src/hcanon.ts +0 -82
  264. package/src/helpdoc.ts +2 -2
  265. package/src/hints.ts +69 -57
  266. package/src/jsonschema.ts +0 -123
  267. package/src/keyorder.ts +0 -42
  268. package/src/lang.ts +19 -931
  269. package/src/lsp-server.ts +0 -16
  270. package/src/lsp.ts +12 -180
  271. package/src/mcp-server.ts +0 -31
  272. package/src/mcp.ts +0 -169
  273. package/src/mod-tool.ts +18 -159
  274. package/src/mod.ts +0 -178
  275. package/src/patch.ts +0 -232
  276. package/src/profile.ts +42 -0
  277. package/src/provenance.ts +0 -183
  278. package/src/query.ts +0 -84
  279. package/src/reach.ts +0 -53
  280. package/src/relation.ts +0 -84
  281. package/src/report-sarif.ts +0 -48
  282. package/src/sig.ts +0 -35
  283. package/src/sigdecl.ts +1 -1
  284. package/src/siggate.ts +0 -30
  285. package/src/site.ts +3 -29
  286. package/src/subsume.ts +1 -161
  287. package/src/template.ts +69 -140
  288. package/src/trace.ts +157 -0
  289. package/src/trim.ts +0 -53
  290. package/src/type.ts +2 -45
  291. package/src/unify.ts +14 -257
  292. package/src/utility.ts +0 -31
  293. package/src/val/AbnfFuncVal.ts +181 -0
  294. package/src/val/AbsentVal.ts +54 -0
  295. package/src/val/AggFuncVal.ts +152 -188
  296. package/src/val/ArithFuncVal.ts +0 -20
  297. package/src/val/BagVal.ts +1 -78
  298. package/src/val/BigDecimalVal.ts +0 -16
  299. package/src/val/BigIntegerVal.ts +0 -16
  300. package/src/val/CloseFuncVal.ts +0 -9
  301. package/src/val/CmpFuncVal.ts +68 -184
  302. package/src/val/ConjunctVal.ts +0 -33
  303. package/src/val/ConstraintVal.ts +2 -537
  304. package/src/val/ContainerKindVal.ts +0 -18
  305. package/src/val/CopyFuncVal.ts +0 -5
  306. package/src/val/Decimal.ts +1 -185
  307. package/src/val/DeprecateFuncVal.ts +0 -10
  308. package/src/val/DisjunctVal.ts +0 -157
  309. package/src/val/EachFuncVal.ts +0 -40
  310. package/src/val/EmitFuncVal.ts +8 -208
  311. package/src/val/ExpectVal.ts +0 -62
  312. package/src/val/FilterFuncVal.ts +0 -55
  313. package/src/val/FuncBaseVal.ts +9 -130
  314. package/src/val/GraphAtomVal.ts +0 -42
  315. package/src/val/HideFuncVal.ts +0 -15
  316. package/src/val/IntegerVal.ts +0 -61
  317. package/src/val/JunctionVal.ts +0 -20
  318. package/src/val/KeyFuncVal.ts +0 -48
  319. package/src/val/ListVal.ts +0 -59
  320. package/src/val/LowerFuncVal.ts +0 -12
  321. package/src/val/MapVal.ts +0 -151
  322. package/src/val/MatchFuncVal.ts +0 -59
  323. package/src/val/MaybeFuncVal.ts +86 -0
  324. package/src/val/MoveFuncVal.ts +0 -20
  325. package/src/val/NilVal.ts +0 -60
  326. package/src/val/NomFuncVal.ts +10 -99
  327. package/src/val/NumberVal.ts +0 -16
  328. package/src/val/OpBaseVal.ts +4 -17
  329. package/src/val/PackFuncVal.ts +0 -63
  330. package/src/val/PathFuncVal.ts +0 -32
  331. package/src/val/PathVal.ts +0 -66
  332. package/src/val/PlaceVal.ts +0 -45
  333. package/src/val/PlusOpVal.ts +18 -75
  334. package/src/val/PrefFuncVal.ts +0 -1
  335. package/src/val/PrefVal.ts +0 -179
  336. package/src/val/RecurseVal.ts +0 -81
  337. package/src/val/RefVal.ts +1 -285
  338. package/src/val/ReferFuncVal.ts +4 -255
  339. package/src/val/ScalarKindVal.ts +0 -50
  340. package/src/val/ScalarVal.ts +0 -12
  341. package/src/val/StrFuncVal.ts +0 -44
  342. package/src/val/SuperFuncVal.ts +0 -42
  343. package/src/val/TopVal.ts +0 -1
  344. package/src/val/TranslateFuncVal.ts +1 -51
  345. package/src/val/UpperFuncVal.ts +0 -12
  346. package/src/val/Val.ts +3 -192
  347. package/src/val/VarVal.ts +0 -15
  348. package/src/val/arith.ts +0 -92
  349. package/src/val/caserange.ts +0 -62
  350. package/src/val/members.ts +0 -23
  351. package/src/val/numcmp.ts +1 -27
  352. package/src/val/numkind.ts +0 -149
  353. package/src/val/valutil.ts +0 -16
  354. package/src/vet.ts +1 -582
  355. package/src/view.ts +0 -507
  356. package/src/walk.ts +0 -41
  357. package/dist/lower.d.ts +0 -23
  358. package/dist/lower.js +0 -578
  359. package/dist/lower.js.map +0 -1
  360. package/dist/render.d.ts +0 -53
  361. package/dist/render.js +0 -547
  362. package/dist/render.js.map +0 -1
  363. package/dist/std.d.ts +0 -3
  364. package/dist/std.js +0 -672
  365. package/dist/std.js.map +0 -1
  366. package/dist/val/FormFuncVal.js +0 -55
  367. package/dist/val/FormFuncVal.js.map +0 -1
  368. package/dist/val/NamerFuncVal.d.ts +0 -12
  369. package/dist/val/NamerFuncVal.js +0 -176
  370. package/dist/val/NamerFuncVal.js.map +0 -1
  371. package/src/lower.ts +0 -636
  372. package/src/render.ts +0 -732
  373. package/src/std.ts +0 -683
package/dist/cli.js CHANGED
@@ -13,8 +13,8 @@ exports.runRelations = runRelations;
13
13
  exports.runReaches = runReaches;
14
14
  exports.runView = runView;
15
15
  exports.runJsonSchema = runJsonSchema;
16
- exports.runRender = runRender;
17
16
  exports.runTemplate = runTemplate;
17
+ exports.runTrace = runTrace;
18
18
  exports.runMod = runMod;
19
19
  exports.runHash = runHash;
20
20
  exports.runGet = runGet;
@@ -32,13 +32,6 @@ exports.runFmt = runFmt;
32
32
  exports.watchChange = watchChange;
33
33
  exports.watchSignature = watchSignature;
34
34
  exports.deprecatedAt = deprecatedAt;
35
- // Command-line interface for Aontu.
36
- //
37
- // aontu [options] [file]
38
- //
39
- // With a file argument, the file is evaluated and the result printed.
40
- // With no file on an interactive terminal, a REPL is started. With no
41
- // file and piped input, the source is read from stdin. See HELP below.
42
35
  // Named imports, not `import * as`: the namespace form makes tsc emit the
43
36
  const query_1 = require("./query");
44
37
  // __importStar downlevel helper, whose branches no supported Node takes.
@@ -47,8 +40,8 @@ const node_path_1 = require("node:path");
47
40
  const node_os_1 = require("node:os");
48
41
  const node_readline_1 = require("node:readline");
49
42
  const aontu_1 = require("./aontu");
43
+ const trace_1 = require("./trace");
50
44
  const template_1 = require("./template");
51
- const mcp_1 = require("./mcp");
52
45
  const report_sarif_1 = require("./report-sarif");
53
46
  const lsp_server_1 = require("./lsp-server");
54
47
  const mcp_server_1 = require("./mcp-server");
@@ -74,10 +67,10 @@ const HELP = `Usage: aontu [options] [file]
74
67
  aontu view <kind> [options] <file>...
75
68
  aontu view --views <path> [--check] [options] <file>
76
69
  aontu jsonschema [--at <path>] [--strict] [options] <file>
77
- aontu render [--at <path>] [--profile <file>]... [--unit <path>]
78
- [--stdout | --out <dir> | --check <dir> | --coverage]
79
- [--coverage-at <path>] [--strict] <file>
80
- aontu template [--resugar] [--check] [--marker <token>] <file>
70
+ aontu template [--resugar] [--check] [--marker <token>]
71
+ [--profile <file>] <file>
72
+ aontu trace [--at <path>] [--format json] [--marker <token>]
73
+ [--profile <file>] <file>
81
74
  aontu hash [options] <file>
82
75
  aontu mod tidy|verify|vendor|manifest [options] [dir]
83
76
  aontu get <path> [options] <file>
@@ -85,7 +78,8 @@ const HELP = `Usage: aontu [options] [file]
85
78
  aontu set <path>=<value>... --entry <file> --overlay <file>
86
79
  aontu allow --role <role> [--at <path>] <roles-file> <path>...
87
80
  aontu agentsmd [--write <AGENTS.md>] [--depth <n>] <file>
88
- aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] <file>...
81
+ aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>]
82
+ [--profile <file>] <file>...
89
83
  aontu help [topic] [--format text|json]
90
84
  aontu explain <code> | --list [--format text|json]
91
85
  aontu init [dir]
@@ -322,36 +316,11 @@ View exit codes: 0 rendered, 1 --check mismatch or lossy under
322
316
  --strict, 2 usage or --max-rows exceeded, 4 the document does not stand
323
317
  up on its own, or a relation, root or path that names nothing.
324
318
 
325
- Render options:
326
- --at <path> Render the value at this path ($.a.b); the root by
327
- default
328
- --profile <file> A profile document, profile: {lang, ...}, vetted
329
- against aontu:profile; repeatable, one per language
330
- --unit <path> Render only the unit with this path
331
- --stdout One unit's bytes and nothing else (with --unit when
332
- the instance has several)
333
- --out <dir> Write every unit below dir, or nothing; never deletes
334
- --check <dir> Compare every unit with dir/<path>; drift is listed
335
- --coverage Report what the render read and what it did not:
336
- model paths no output consumed, and rendered
337
- declarations no rule produced. Writes nothing
338
- --coverage-at <p> Measure coverage under this path only, instead of
339
- the document root
340
- --strict Refuse the opaque escapes (a text declaration, a raw
341
- block)
342
- --format <f> text (default) or json, the whole report; json
343
- carries the dispatch trace, one entry per emitted
344
- piece
345
-
346
- Render exit codes: 0 rendered, 1 lossy under --strict or drift under
347
- --check, 2 usage or I/O (a refused unit path included), 4 the document
348
- does not stand up or the instance is not aontu:code.
349
-
350
- A render entry file whose extension is not .aon is a TEMPLATE: a
351
- generator in the target's own syntax, whose marker lines carry aontu
352
- and whose other lines are output. It is desugared before it is
353
- evaluated, and --marker names the marker for a language the table does
354
- not know.
319
+ A template entry file whose extension is not .aon is a GENERATOR: a
320
+ document in the target's own syntax, whose marker lines carry aontu and
321
+ whose other lines are output. It is desugared before it is evaluated,
322
+ and a language the table does not know names its marker with --marker,
323
+ or declares it once in a profile file that --profile reads.
355
324
 
356
325
  Template options:
357
326
  --resugar The file is the canonical aontu; print the template
@@ -359,7 +328,9 @@ Template options:
359
328
  --check Desugar and resugar, and exit 1 if the file is not
360
329
  what the round trip answers
361
330
  --marker <t> The marker, when the extension does not name it
362
- (default //-, and #- --- /*- by extension)
331
+ (default //-, and #- --- /*- <!--- by extension)
332
+ --profile <f> A profile file, whose template.ext names the
333
+ extensions it marks and template.marker the marker
363
334
 
364
335
  The template verb prints the canonical aontu form of a generator
365
336
  written in the target's own syntax: a marked line is aontu source, and
@@ -452,16 +423,18 @@ Fmt options:
452
423
  shapes, on standard error, and print nothing else
453
424
  --strict With --lint, and exit 1 when there is a finding
454
425
  --marker <t> The file is a generator, and this is its marker
455
- (default //-, and #- --- /*- by extension)
426
+ (default //-, and #- --- /*- <!--- by extension)
427
+ --profile <f> A profile file, whose template.ext names the
428
+ extensions it marks and template.marker the marker
456
429
 
457
430
  The fmt verb prints one document in the agreed form; with no file it
458
431
  reads standard input. Several files need one of the options above.
459
432
 
460
- A file whose extension is not .aon is a GENERATOR, as it is for render:
461
- the aontu its marker lines carry is formatted, the marker stands at the
462
- left margin with the aontu indented after it, and every line of output
463
- is held on a line of its own. A file with no marker line in it is
464
- another language's, and is refused.
433
+ A file whose extension is not .aon is a GENERATOR, as it is for
434
+ template: the aontu its marker lines carry is formatted, the marker
435
+ stands at the left margin with the aontu indented after it, and every
436
+ line of output is held on a line of its own. A file with no marker line
437
+ in it is another language's, and is refused.
465
438
 
466
439
  Fmt exit codes: 0 formatted or clean, 1 a --check file would change or
467
440
  a --strict finding, 2 usage, 4 a document does not parse.
@@ -495,30 +468,7 @@ function version() {
495
468
  return '0.0.0';
496
469
  }
497
470
  }
498
- // The terminal colour escapes the parser puts in its message text. A
499
- // machine-readable report is no place for them, which is the rule
500
- // findingOf states in ts/src/vet.ts; the twin here rather than an
501
- // import because go/cmd/aontu carries its own for the same reason (the
502
- // engine's is not exported to its command).
503
471
  const EVAL_ANSI = new RegExp('\u001b\\[[0-9;]*m', 'g');
504
- // THE ENGINE'S DIAGNOSIS AS A FINDING (G11 phase 7). The bare command
505
- // was the one verb whose failure had no machine-readable form, so the
506
- // default entry point was the one an agent had to parse with a regular
507
- // expression.
508
- //
509
- // THE HEADLINE ONLY, and no `hint`. Both are parity decisions rather
510
- // than economies: the frames under the headline are drawn for a person
511
- // reading a terminal and only the first line is held to byte parity
512
- // between the ports (the rule findingOf states), and the hint TABLES
513
- // are deliberately not in parity while the code registry is -- so a
514
- // hint here would make the two ports answer differently for a code
515
- // only one of them explains. `aontu explain <code>` is where the hint
516
- // lives, which is what phase 3 built it for.
517
- //
518
- // The CLASS comes from the registry rather than from the nil, because
519
- // the registry is what both ports hold set-equal
520
- // (test/spec/errcodes.tsv). Mirrors evalFinding in
521
- // go/cmd/aontu/main.go.
522
472
  function evalFinding(code, text) {
523
473
  return {
524
474
  class: (0, hints_1.codeClass)(code),
@@ -526,10 +476,6 @@ function evalFinding(code, text) {
526
476
  message: text.split('\n')[0].replace(EVAL_ANSI, ''),
527
477
  path: '$',
528
478
  severity: 'error',
529
- // NO SITE. The bare command's failure is the whole document not
530
- // standing up, and the two sites a conflict names are in the
531
- // frames the text form prints; naming one of them here would be a
532
- // choice the engine has not made.
533
479
  sites: [],
534
480
  };
535
481
  }
@@ -537,12 +483,6 @@ function evalFinding(code, text) {
537
483
  // message, and the failure in the finding shape. Never throws.
538
484
  function evalSource(aontu, src, mode) {
539
485
  try {
540
- // exactJSON, not JSON.stringify: a document using the `0d` exact
541
- // leaves generates bigints and Decimals, which JSON.stringify cannot
542
- // write (D9). The CLI prints INDENTED JSON and the shared suite's
543
- // `gens` mode prints COMPACT JSON, but both go through this one
544
- // emitter -- an indent argument rather than a second implementation,
545
- // so the two cannot drift from each other or from the Go port.
546
486
  const text = 'canon' === mode
547
487
  ? aontu.unify(src).canon
548
488
  : (0, aontu_1.exactJSON)(aontu.generate(src), 2);
@@ -552,13 +492,6 @@ function evalSource(aontu, src, mode) {
552
492
  const msg = (err instanceof aontu_1.AontuError || true === err?.aontu)
553
493
  ? err.message
554
494
  : String(err?.message ?? err);
555
- // WHAT THE ENGINE COLLECTED, when it collected anything: an
556
- // AontuError carries the NilVals the run failed on, already
557
- // materialised (handleErrors in ts/src/aontu.ts), and their first
558
- // is the diagnosis every other verb reports. An error raised
559
- // outside the engine's own collection -- exactJSON's circular
560
- // refusal, a foreign object claiming to be one -- carries none,
561
- // and answers with the text alone rather than an invented code.
562
495
  const errs = 'function' === typeof err?.errs ? err.errs() : [];
563
496
  const first = errs[0];
564
497
  return {
@@ -623,16 +556,6 @@ function trustOpts(trust, entryRoot) {
623
556
  return { ...text, trustWarn: makeTrustWarn(), trustWarnRoot: entryRoot };
624
557
  }
625
558
  }
626
- // EVERY VERB honours the include capability, not just the bare
627
- // command. G5 wired `--trust`/`--include-root` to `aontu <file>` alone,
628
- // so `aontu vet schema.aon data.json` -- the surface an agent actually
629
- // scripts -- ran the full system resolver with no flag to confine it
630
- // and no warning (use-cases/REVIEW.md finding G). The flags are
631
- // stripped here, before each verb parses its own tail, so a verb only
632
- // has to pass the profile on to its engine.
633
- //
634
- // Returns undefined when the spelling is wrong, with the message
635
- // already printed: the caller answers the usage class.
636
559
  function takeTrust(argv) {
637
560
  const rest = [];
638
561
  let trust = { kind: 'system-warn', textExt: [] };
@@ -727,17 +650,6 @@ function runFile(file, mode, format, trust) {
727
650
  src = (0, node_fs_1.readFileSync)(file, 'utf8');
728
651
  }
729
652
  catch (err) {
730
- // A MISTYPED VERB READS AS A FILE NAME, and until G11 phase 2 that
731
- // was only said when there were TWO of them. The one-argument case
732
- // is the one an agent actually produces -- `aontu help`, `aontu
733
- // init`, `aontu ontology` -- and it answered `cannot read help:
734
- // ...`, which describes the symptom and hides the cause.
735
- //
736
- // The test is SHAPE, not existence: a bare word (no separator, no
737
- // extension) that cannot be read was meant as a verb, while
738
- // `./help`, `help.aon` and `/tmp/help` were meant as paths and keep
739
- // the file diagnosis and its exit 1. That is the same escape hatch
740
- // the subcommand dispatch documents. Mirrors go/cmd/aontu/main.go.
741
653
  if (looksLikeVerb(file)) {
742
654
  process.stderr.write(`aontu: \`${file}\` is not a file, and not a verb this port knows\n`);
743
655
  const near = nearestVerb(file, KNOWN_VERBS);
@@ -751,12 +663,6 @@ function runFile(file, mode, format, trust) {
751
663
  return 1;
752
664
  }
753
665
  const path = (0, node_path_1.resolve)(file);
754
- // `fs` IS WHAT MAKES A FRAME EXCERPT THE FILE IT NAMES. Without it,
755
- // err.ts's resolveSrc falls back to the ENTRY text, so a frame whose
756
- // arrow says `lib/types.aon:2:6` printed the entry's line 2 under it
757
- // -- a real file name over another file's line, which
758
- // docs/reference-api.md forbids in the same words it uses to require
759
- // the name.
760
666
  const aontu = new aontu_1.Aontu({
761
667
  path,
762
668
  errfs: { existsSync: node_fs_1.existsSync, readFileSync: node_fs_1.readFileSync },
@@ -892,14 +798,6 @@ function runRepl(initialMode, jsonl, trust) {
892
798
  rl.prompt();
893
799
  });
894
800
  rl.on('close', () => {
895
- // The closing newline is for a HUMAN, so it is written only for
896
- // one: it moves the terminal off the prompt line that `rl` left
897
- // hanging. In `--jsonl` there is no prompt, every answer already
898
- // ends in its own newline, and this one appended a bare empty line
899
- // to the stream -- a record that is not JSON, at the end of a
900
- // protocol whose whole contract is one JSON object per line. A
901
- // harness parsing every line it receives failed on it, after the
902
- // commands had all succeeded. Mirrors go/cmd/aontu/repl.go.
903
801
  if (!jsonl) {
904
802
  process.stdout.write('\n');
905
803
  }
@@ -909,15 +807,6 @@ function runRepl(initialMode, jsonl, trust) {
909
807
  process.exitCode = 0;
910
808
  });
911
809
  }
912
- // THE VET VERB (G2 phase 3).
913
- //
914
- // Exit codes are VERDICT CLASSES, not a pass/fail bit: an agent loop
915
- // branches on "the data contradicts the truth" (1) differently from
916
- // "the data has not supplied everything the truth requires" (3), and
917
- // differently again from "the schema itself is broken" (4), which is
918
- // never the data's fault. 2 stays what it already was for this CLI --
919
- // the caller got the invocation wrong -- which is why an unreadable
920
- // file is a 2 rather than a 4.
921
810
  const VET_EXIT = {
922
811
  valid: 0,
923
812
  invalid: 1,
@@ -961,14 +850,6 @@ function parseVetArgs(argv) {
961
850
  format = f;
962
851
  }
963
852
  else if ('--max-errors' === arg) {
964
- // ONE GRAMMAR, spelled the same way in both ports: decimal
965
- // digits, one to nine of them, at least 1. `Number()` alone
966
- // accepted `1.0`, `1e2`, `0x10` and ` 3`, which Go's parser
967
- // refuses -- so the same documented invocation meant different
968
- // things in the two shipped commands. The nine-digit ceiling is
969
- // where the ports would part company again: beyond it Go's
970
- // integer conversion saturates, and a cap nobody can reach is
971
- // not worth a divergence.
972
853
  const raw = argv[++i];
973
854
  if (!/^[0-9]{1,9}$/.test(raw ?? '') || 1 > Number(raw)) {
974
855
  return { err: 'aontu: --max-errors needs a positive whole number' };
@@ -1046,10 +927,6 @@ function renderFinding(f) {
1046
927
  out.push(` actual: ${f.actual}`);
1047
928
  }
1048
929
  for (const s of f.sites) {
1049
- // Every site carries the canon of the value it stands for: that is
1050
- // what makes the two sides of a conflict readable side by side. A
1051
- // site's file is always a string -- empty when the value belongs to
1052
- // neither document -- so there is nothing to coalesce here.
1053
930
  out.push(` ${s.role}: ${s.file}:${s.row}:${s.col} (${s.value})`);
1054
931
  }
1055
932
  return out.join('\n');
@@ -1141,18 +1018,9 @@ function vetOnce(args, trust) {
1141
1018
  process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
1142
1019
  return 2;
1143
1020
  }
1144
- // Each data file is vetted on its own, because a parsed tree is
1145
- // single-use (docs/reference-api.md) -- and because two data files
1146
- // are two candidates for the same truth, not one merged candidate.
1147
1021
  let verdict = 'valid';
1148
1022
  let truncated = false;
1149
1023
  const findings = [];
1150
- // COVERAGE ACROSS SEVERAL DATA FILES (G11 phase 5). Two data files
1151
- // are two candidates for one truth, so the schema side is the SAME
1152
- // for each: `declared` is taken once, and a declaration is unused
1153
- // only when NO file met it -- the intersection, because a
1154
- // declaration one file exercised is exercised. The data side adds
1155
- // up: leaves and checked leaves sum, and `unchecked` is the union.
1156
1024
  let cov;
1157
1025
  // Initialised rather than left undefined: it is filled in the same
1158
1026
  // block that sets `cov`, so a fallback at the read below would be an
@@ -1170,12 +1038,6 @@ function vetOnce(args, trust) {
1170
1038
  maxErrors: args.maxErrors,
1171
1039
  schemaUrl: args.schema,
1172
1040
  dataUrl: source.file,
1173
- // The paths as well as the labels: a relative `@"file"` load
1174
- // inside either document resolves from ITS OWN directory, the
1175
- // way `aontu <file>` already resolves one (runFile above). The
1176
- // path is passed AS TYPED, not resolved: it doubles as the
1177
- // label above, and a report that mixed the typed path with an
1178
- // absolute one would name the same file two ways.
1179
1041
  schemaPath: args.schema,
1180
1042
  dataPath: source.file,
1181
1043
  coverage: args.coverage,
@@ -1204,26 +1066,10 @@ function vetOnce(args, trust) {
1204
1066
  ? new Set([...unusedEvery].filter((u) => mine.has(u))) : mine;
1205
1067
  unusedSeen = true;
1206
1068
  }
1207
- // A SCHEMA-SIDE FAULT IS THE SAME FAULT FOR EVERY DATA FILE, so it
1208
- // is reported ONCE. `error` means exactly that -- the run could not
1209
- // be set up from the truth's side, never the data's (the exit table
1210
- // in docs/reference-api.md) -- so the report the first file
1211
- // produced is the report every later file would produce, character
1212
- // for character. Concatenating them repeated one broken schema N
1213
- // times and, past the cap, marked the report `truncated` over a
1214
- // single underlying fault. It only became visible once the `error`
1215
- // verdict started carrying findings at all: while the list was
1216
- // empty there was nothing to duplicate.
1217
1069
  if ('error' === report.verdict) {
1218
1070
  break;
1219
1071
  }
1220
1072
  }
1221
- // The cap is on the REPORT, not on each file. Capping every file's
1222
- // list and then concatenating them let `--max-errors 1` emit one
1223
- // finding PER FILE -- and leave `truncated` false while doing it,
1224
- // because no single file had been cut. The engine still caps each
1225
- // run, so a pathological file cannot flood the aggregate before it
1226
- // gets here; this is the second, honest cut.
1227
1073
  const cap = args.maxErrors ?? vet_1.VET_MAX_ERRORS;
1228
1074
  const kept = cap < findings.length ? findings.slice(0, cap) : findings;
1229
1075
  if (null != cov) {
@@ -1241,12 +1087,6 @@ function vetOnce(args, trust) {
1241
1087
  'sarif' === args.format ? renderVetSarif(report) :
1242
1088
  renderVetText(report);
1243
1089
  process.stdout.write(text + '\n');
1244
- // A VACUOUS CHECK IS A FAILED GATE UNDER `--strict-coverage`, and
1245
- // only under it: the verdict WORD is unchanged, so nothing that
1246
- // passes today starts failing, and a caller who wants the stronger
1247
- // gate asks for it. The reason goes to stderr, because stdout is a
1248
- // report contract -- a JSON consumer reads `coverage.vacuous` and a
1249
- // person reads this.
1250
1090
  if (true === args.strictCoverage && true === report.coverage?.vacuous) {
1251
1091
  process.stderr.write('aontu: no data leaf was constrained by the schema:' +
1252
1092
  ' this run checked nothing\n' +
@@ -1273,19 +1113,6 @@ function watchSignature(files) {
1273
1113
  function sleep(ms) {
1274
1114
  return new Promise((done) => setTimeout(done, ms));
1275
1115
  }
1276
- // Resolve true when any watched file's signature moves off `before`.
1277
- // This is the real waiter: it never resolves false, so a real watch
1278
- // runs until the process is interrupted; tests inject their own waiter
1279
- // to bound the loop, and pass a short pollMs when they drive this one
1280
- // directly. The interval is a required argument (the command passes
1281
- // WATCH_POLL_MS) so there is no defaulting branch a test could never
1282
- // take.
1283
- //
1284
- // The BASELINE is an argument, not a snapshot taken here: the loop
1285
- // records it BEFORE each vet run, so a save landing between the run's
1286
- // reads and the wait still compares as a change. A waiter that
1287
- // snapshotted on entry would adopt that unvetted save as its baseline
1288
- // and wait indefinitely on a stale report.
1289
1116
  async function watchChange(files, before, pollMs) {
1290
1117
  for (;;) {
1291
1118
  await sleep(pollMs);
@@ -1314,9 +1141,6 @@ async function watchVet(args, wait, trust) {
1314
1141
  }
1315
1142
  return code;
1316
1143
  }
1317
- // The vet verb. Non-watch runs are synchronous and return the exit
1318
- // class directly; `--watch` returns a promise that resolves only when
1319
- // the waiter says stop (never, for the real one).
1320
1144
  function runVet(argv, wait) {
1321
1145
  const trusted = takeTrust(argv);
1322
1146
  if (null == trusted) {
@@ -1527,30 +1351,7 @@ function parseBreakingArgs(argv) {
1527
1351
  },
1528
1352
  };
1529
1353
  }
1530
- // A source file the include resolver can actually load. `git#<rev>`
1531
- // materialises these and nothing else: an include names an Aontu
1532
- // document (`.aon`/`.aontu`, the two extensions `@"foo"` tries) or a
1533
- // JSON one, so the rest of a revision's tree cannot be part of any
1534
- // include closure and copying it would be pure cost.
1535
1354
  const INCLUDABLE = /\.(aon|aontu|jsonic|json)$/;
1536
- // Resolve one --against spelling to an old version.
1537
- //
1538
- // A `git#<rev>` spelling is the old version of the WHOLE TREE, not of
1539
- // the entry file alone. It used to be `git show <rev>:./<file>`, whose
1540
- // text was then evaluated with `generalPath`/`specificPath` pointing at
1541
- // the WORKING file -- so every `@"..."` include in the old document
1542
- // resolved against the working tree, and the "old" side was old entry
1543
- // text meeting new includes. A breaking change inside an included file
1544
- // therefore compared against itself and answered `compatible`: the
1545
- // documented CI gate silently un-gated every non-entry file of the
1546
- // multi-file layout real models use (use-cases/BUGS.md §26). The old
1547
- // tree's includable sources are copied into a temporary directory and
1548
- // the old document is evaluated from THERE.
1549
- //
1550
- // Sources outside the revision -- package includes under node_modules,
1551
- // the bundled `std/system` -- still resolve as they do today: they are
1552
- // not in the tree, and their versions travel with the lockfile rather
1553
- // than with this comparison.
1554
1355
  function oldVersion(spec, file) {
1555
1356
  if (!spec.startsWith('git#')) {
1556
1357
  try {
@@ -1578,23 +1379,9 @@ function oldVersion(spec, file) {
1578
1379
  // that only some failures take.
1579
1380
  const temp = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'aontu-against-'));
1580
1381
  try {
1581
- // THE REPO-RELATIVE PATH COMES FROM GIT, not from path arithmetic.
1582
- // Relativising `rev-parse --show-toplevel` against `resolve(file)`
1583
- // puts two DIFFERENT COORDINATE SYSTEMS on either side of the
1584
- // subtraction: git prints the real path, while the caller's is
1585
- // whatever they typed. On macOS a temp file under /var is
1586
- // /private/var to git, and on Windows a TMP short name
1587
- // (RUNNER~1) is the long form to git -- so the subtraction gave a
1588
- // `../..` climb, the entry was "not in that revision", and the
1589
- // documented CI spelling failed on both platforms while passing on
1590
- // Linux (this PR's own CI). `--show-prefix` is the same question
1591
- // asked in git's coordinates: the repo-relative directory of the
1592
- // cwd, already slash-separated and already normalised.
1593
1382
  const prefix = git(['rev-parse', '--show-prefix'], dir).trim();
1594
1383
  const entryRel = prefix + (0, node_path_1.basename)(file);
1595
1384
  const top = git(['rev-parse', '--show-toplevel'], dir).trim();
1596
- // `-z` so a path with a newline or a quote cannot be mistaken for
1597
- // two paths (git otherwise quotes such names).
1598
1385
  const listed = git(['ls-tree', '-r', '-z', '--name-only', rev], top)
1599
1386
  .split('\0').filter((p) => '' !== p);
1600
1387
  if (!listed.includes(entryRel)) {
@@ -1624,13 +1411,6 @@ function oldVersion(spec, file) {
1624
1411
  function policyCompat(newSrc, path, include) {
1625
1412
  const aontu = new aontu_1.Aontu();
1626
1413
  const ctx = aontu.ctx({ collect: true });
1627
- // The declaration is read by EVALUATING the document, so this leg
1628
- // runs the include resolver too and has to run it under BOTH of the
1629
- // verb's include options -- a `breaking --trust none` that read its
1630
- // own mode through an unconfined resolver would confine the
1631
- // comparison and not the question (use-cases/REVIEW.md finding G),
1632
- // and one that took the capability alone read no mode at all when
1633
- // the declaration arrived through a `--text-ext` include.
1634
1414
  const v = aontu.unify(newSrc, { path, ...(0, utility_1.includeOpts)(include) }, ctx);
1635
1415
  if (0 < ctx.err.length || true === v?.isNil) {
1636
1416
  return undefined;
@@ -1649,11 +1429,6 @@ function policyCompat(newSrc, path, include) {
1649
1429
  return 'backward' === m || 'forward' === m || 'full' === m || 'none' === m
1650
1430
  ? m : undefined;
1651
1431
  }
1652
- // Is the evaluated old version's value at the finding path deprecated?
1653
- // The --allow-deprecated-removal downgrade (G3 phase 4): removing (or
1654
- // otherwise changing) a value the old version already deprecated warns
1655
- // instead of breaking. The Go port exports the same reader as
1656
- // aontu.DeprecatedAt.
1657
1432
  function deprecatedAt(oldSrc, path, filePath) {
1658
1433
  const aontu = new aontu_1.Aontu();
1659
1434
  const ctx = aontu.ctx({ collect: true });
@@ -1720,9 +1495,6 @@ function runBreaking(argv) {
1720
1495
  process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
1721
1496
  return 2;
1722
1497
  }
1723
- // The declared mode: --mode overrides the document's own policy;
1724
- // neither means backward, the index's framing (v1-valid documents
1725
- // stay valid).
1726
1498
  const mode = args.mode ??
1727
1499
  policyCompat(newSrc, args.file, verbOpts(trust, entryRootOf(args.file))) ??
1728
1500
  'backward';
@@ -1755,8 +1527,6 @@ function runBreaking(argv) {
1755
1527
  if (null != old.temp) {
1756
1528
  temps.push(old.temp);
1757
1529
  }
1758
- // backward: the NEW document is the general side — every old
1759
- // instance must still be admitted. forward: the old one is.
1760
1530
  const checks = [];
1761
1531
  if ('backward' === mode || 'full' === mode) {
1762
1532
  checks.push({ general: [newSrc, args.file], specific: [oldSrc, spec] });
@@ -1771,17 +1541,9 @@ function runBreaking(argv) {
1771
1541
  at: args.at,
1772
1542
  generalUrl: check.general[1],
1773
1543
  specificUrl: check.specific[1],
1774
- // The old side's relative loads resolve from ITS own tree --
1775
- // the materialised revision for a git spelling, the named
1776
- // file's directory otherwise -- so an included file's change
1777
- // is part of the comparison rather than invisible to it.
1778
1544
  generalPath: check.general[1] === spec ? oldPath : args.file,
1779
1545
  specificPath: check.specific[1] === spec ? oldPath : args.file,
1780
1546
  });
1781
- // The deprecated-removal downgrade: a finding about a value the
1782
- // OLD version already deprecated becomes a warning, and warnings
1783
- // do not move the verdict. Deprecate-then-remove is the
1784
- // supported rename path (the design's own sequencing).
1785
1547
  let verdict = report.verdict;
1786
1548
  if (args.allowDeprecatedRemoval) {
1787
1549
  let liveFindings = 0;
@@ -1832,12 +1594,6 @@ function renderBreakingJson(report, mode) {
1832
1594
  findings: report.findings,
1833
1595
  }, 2);
1834
1596
  }
1835
- // ---------------------------------------------------------------------
1836
- // The trim reporter (G3 phase 6): report redundant entries as paths.
1837
- // Report-only — REWRITING needs G7's format-preserving patch surface —
1838
- // which is why --check is REQUIRED rather than defaulted: `aontu trim
1839
- // f.aon` reads as "trim this file", and doing something else silently
1840
- // is worse than saying so.
1841
1597
  const TRIM_HELP = 'aontu trim --check <file> (try --help)';
1842
1598
  const TRIM_EXIT = {
1843
1599
  clean: 0,
@@ -1907,8 +1663,6 @@ function runTrim(argv) {
1907
1663
  }
1908
1664
  function renderTrimText(report) {
1909
1665
  const head = `verdict: ${report.verdict}`;
1910
- // WHY, when the document could not be evaluated at all: rendered as
1911
- // vet renders a finding, because it IS one (the review's finding F).
1912
1666
  const errors = report.errors ?? [];
1913
1667
  if (0 < errors.length) {
1914
1668
  return [head, ''].concat(errors.map(renderFinding)).join('\n');
@@ -1957,27 +1711,10 @@ const VIEW_EDGES = ['upward', 'all', 'none'];
1957
1711
  // is a terminal, which is the CLI's to know and the library's never --
1958
1712
  // the same division err.ts already draws for the error frames.
1959
1713
  const VIEW_STYLES = ['auto', 'none', 'ansi', 'css'];
1960
- // `--style auto` resolved, which only the CLI can do. The mechanism is
1961
- // the PROFILE's and the library knows it -- an SVG carries its
1962
- // stylesheet unless told not to, which is what makes a figure stand
1963
- // alone. What the library cannot know is whether the DESTINATION is a
1964
- // terminal, so that is the only thing decided here: escapes on the
1965
- // text profile when stdout is a terminal and NO_COLOR is unset, the
1966
- // same two conditions the error frames use. `undefined` leaves the
1967
- // profile's own default in place.
1968
1714
  function viewStyleOf(asked, as) {
1969
1715
  if (undefined !== asked && 'auto' !== asked) {
1970
1716
  return asked;
1971
1717
  }
1972
- // STDOUT'S OWN TERMINAL-NESS, and NO_COLOR read here rather than
1973
- // through colorActive(). The figure goes to STDOUT and the error
1974
- // frames go to STDERR, and they are not the same destination: main()
1975
- // has already called setColor for stderr, so asking colorActive()
1976
- // would answer the wrong question twice --- no escapes for
1977
- // `aontu view tree m.aon 2>/dev/null` at a terminal, and escapes
1978
- // into the pipe for `aontu view tree m.aon | less`. The NO_COLOR
1979
- // rule is the one no-color.org states and err.ts implements:
1980
- // set, to anything but empty, means no colour.
1981
1718
  const no = process.env.NO_COLOR;
1982
1719
  return 'text' === as && true === process.stdout.isTTY
1983
1720
  && (null == no || '' === no) ? 'ansi' : undefined;
@@ -1999,25 +1736,13 @@ const VIEW_USAGE_CODES = [
1999
1736
  'view_document_shape', 'view_style_profile', 'view_style_unknown',
2000
1737
  ];
2001
1738
  const MOD_HELP = 'aontu mod tidy|verify|vendor|manifest [dir] (try --help)';
2002
- // The module tooling (G6 phase 3, ts/src/mod-tool.ts). All LOCAL:
2003
- // `tidy` resolves the closure from what is in the stores and rewrites
2004
- // the lockfile, `verify` asks whether the stores still mean what the
2005
- // lockfile pins and changes nothing, `vendor` materialises the locked
2006
- // closure into the project, `manifest` prints what a publish would
2007
- // push.
2008
- //
2009
- // TIDY AND VERIFY ARE DIFFERENT QUESTIONS, and that is why both exist.
2010
- // Tidy recomputes and rewrites by design -- a pin is what a module
2011
- // means NOW -- so it makes the lockfile agree with whatever the store
2012
- // holds, tampering included. Verify is the gate: a CI job runs it
2013
- // BEFORE tidy, or instead of it.
2014
- //
2015
- // `get` and `publish` are the NETWORK half of the design and are not in
2016
- // this build. They are named here rather than left to fall out as an
2017
- // unknown subcommand, because a reader of the design will type them and
2018
- // deserves to be told which half is missing rather than that the word
2019
- // is wrong.
2020
1739
  function runMod(argv) {
1740
+ const trusted = takeTrust(argv);
1741
+ if (null == trusted) {
1742
+ return 2;
1743
+ }
1744
+ argv = trusted.argv;
1745
+ const trust = trusted.trust;
2021
1746
  const rest = [];
2022
1747
  let format = 'text';
2023
1748
  let against;
@@ -2063,10 +1788,6 @@ function runMod(argv) {
2063
1788
  process.stderr.write(`aontu: mod needs tidy, verify, vendor or manifest\n${MOD_HELP}\n`);
2064
1789
  return 2;
2065
1790
  }
2066
- // THE OLD LAYOUT IS NAMED, NOT READ. The lockfile and the vendored
2067
- // closure moved under aontu_meta/; a project that still carries them
2068
- // at its root would otherwise look untouched by any of these verbs,
2069
- // which is the one silence worth breaking.
2070
1791
  if ((0, node_fs_1.existsSync)((0, node_path_1.join)(dir, 'aon_vendor')) || (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, 'mod-lock.aon'))) {
2071
1792
  process.stderr.write('aontu: aon_vendor/ and mod-lock.aon now live under aontu_meta/: ' +
2072
1793
  'move them, or run aontu mod tidy and aontu mod vendor\n');
@@ -2077,10 +1798,11 @@ function runMod(argv) {
2077
1798
  process.stderr.write('aontu: --against is a manifest option\n');
2078
1799
  return 2;
2079
1800
  }
2080
- const report = 'tidy' === sub ? (0, mod_tool_1.modTidy)(dir, modToolOptions()) :
2081
- 'verify' === sub ? (0, mod_tool_1.modVerify)(dir, modToolOptions()) :
2082
- 'vendor' === sub ? (0, mod_tool_1.modVendor)(dir, modToolOptions()) :
2083
- (0, mod_tool_1.modManifest)(dir, modToolOptions(), against);
1801
+ const modopts = modToolOptions(trust, (0, node_path_1.resolve)(dir));
1802
+ const report = 'tidy' === sub ? (0, mod_tool_1.modTidy)(dir, modopts) :
1803
+ 'verify' === sub ? (0, mod_tool_1.modVerify)(dir, modopts) :
1804
+ 'vendor' === sub ? (0, mod_tool_1.modVendor)(dir, modopts) :
1805
+ (0, mod_tool_1.modManifest)(dir, modopts, against);
2084
1806
  process.stdout.write(('json' === format ?
2085
1807
  (0, aontu_1.exactJSON)({ aontu: { version: version(), verb: 'mod ' + sub }, ...report }, 2) :
2086
1808
  modText(sub, report)) + '\n');
@@ -2090,9 +1812,6 @@ const MOD_SUBS = ['tidy', 'verify', 'vendor', 'manifest'];
2090
1812
  const MOD_EXIT = {
2091
1813
  ok: 0,
2092
1814
  missing: 1,
2093
- // A REFUSED GATE, with `breaking`: a store that no longer means what
2094
- // the lockfile pins is the integrity check saying no, and a CI job
2095
- // reading exit codes should not have to learn a third class for it.
2096
1815
  mismatch: 1,
2097
1816
  // Likewise a lockfile that does not cover the project: the gate has
2098
1817
  // nothing to check, which is a refusal and not a pass.
@@ -2104,11 +1823,16 @@ const MOD_EXIT = {
2104
1823
  // The tooling's evaluator: the same standalone evaluation the module
2105
1824
  // resolver verifies with (ts/src/mod.ts), and for the same reason —
2106
1825
  // only the engine can say what a module MEANS.
2107
- function modToolOptions() {
1826
+ function modToolOptions(trust, entryRoot) {
1827
+ const opts = verbOpts(trust, entryRoot);
1828
+ // The user cache lives outside any confinement root, so a confined
1829
+ // run reads the vendor tree only -- as the evaluator's own module
1830
+ // leg already does when a root is set.
1831
+ const rooted = null != opts.trust?.include?.root;
2108
1832
  return {
2109
- cache: (0, mod_1.modCacheDir)(),
1833
+ ...(rooted ? {} : { cache: (0, mod_1.modCacheDir)() }),
2110
1834
  eval: (src, path) => {
2111
- const a0 = new aontu_1.Aontu();
1835
+ const a0 = new aontu_1.Aontu(opts);
2112
1836
  const ctx = a0.ctx({ collect: true });
2113
1837
  const val = a0.unify(src, { path }, ctx);
2114
1838
  return {
@@ -2138,11 +1862,6 @@ function modText(sub, report) {
2138
1862
  for (const f of report.findings) {
2139
1863
  lines.push(f.path + ': ' + f.message);
2140
1864
  }
2141
- // What a manifest lacks is a declaration the module does not make
2142
- // or an entry file that is not there, and neither is something a
2143
- // fetch would supply -- so this is not the tail the other two
2144
- // subcommands share. The name says which kind it is: `mod.version`
2145
- // is a declaration, `service.aon` is a file.
2146
1865
  for (const miss of report.missing) {
2147
1866
  lines.push(miss + ': missing');
2148
1867
  }
@@ -2152,8 +1871,6 @@ function modText(sub, report) {
2152
1871
  for (const mod of report.verified) {
2153
1872
  lines.push(mod + ': verified');
2154
1873
  }
2155
- // BOTH HASHES, because the useful question is which way it moved:
2156
- // an empty `got` is a module that no longer stands up at all.
2157
1874
  for (const m of report.mismatched) {
2158
1875
  lines.push(m.mod + ': pinned ' + m.want + ' but the store means ' +
2159
1876
  ('' === m.got ? 'nothing (it does not evaluate)' : m.got));
@@ -2186,26 +1903,6 @@ function modText(sub, report) {
2186
1903
  }
2187
1904
  return lines.join('\n');
2188
1905
  }
2189
- // VACUITY SIGNALS (G11 phase 4,
2190
- // docs/capability-review/g11-agent-onramp.md).
2191
- //
2192
- // The same principle phase 5 applied to `vet`: a verb that did NOTHING
2193
- // and a verb that did its job answer the same. `aontu view tree` over a
2194
- // document declaring no relations printed one newline and exited 0;
2195
- // `aontu render` with no profile printed nothing and exited 0; `aontu
2196
- // relations` over a document declaring none answered `verdict: pass`.
2197
- // For a person at a terminal that is a shrug. For an unattended agent
2198
- // it is a green check mark on an empty box.
2199
- //
2200
- // ON STDERR, ALWAYS. stdout is a report contract -- a `--format json`
2201
- // consumer parses it -- and the exit code is a verdict class that
2202
- // callers already branch on. Neither changes here: what changes is
2203
- // that the caller is TOLD. A caller who wants it to be fatal has
2204
- // `vet --strict-coverage`, and the same argument would give the other
2205
- // verbs a flag of their own if one is ever asked for.
2206
- //
2207
- // The repository already ruled this for one verb, in G8 phase 6 on
2208
- // `trim`: "doing something else silently is worse than refusing".
2209
1906
  function vacuous(what, why) {
2210
1907
  process.stderr.write(`aontu: ${what}: ${why}\n`);
2211
1908
  }
@@ -2269,6 +1966,108 @@ function runRelations(argv) {
2269
1966
  }
2270
1967
  return RELATIONS_EXIT[report.verdict];
2271
1968
  }
1969
+ const TRACE_HELP = 'aontu trace [--at <path>] [--format json] [--marker <token>] ' +
1970
+ '[--profile <file>] <file>';
1971
+ // WHAT WROTE THIS LINE. Every piece a rule stamped, under the
1972
+ // component tree, with the file it reached, the rule set that wrote it
1973
+ // and the model node the dispatch matched.
1974
+ function runTrace(argv) {
1975
+ const trusted = takeTrust(argv);
1976
+ if (null == trusted) {
1977
+ return 2;
1978
+ }
1979
+ argv = trusted.argv;
1980
+ const trust = trusted.trust;
1981
+ const rest = [];
1982
+ const profileFiles = [];
1983
+ let format = 'text';
1984
+ let at = undefined;
1985
+ let marker = undefined;
1986
+ for (let i = 0; i < argv.length; i++) {
1987
+ const arg = argv[i];
1988
+ if ('-h' === arg || '--help' === arg) {
1989
+ process.stdout.write(HELP);
1990
+ return 0;
1991
+ }
1992
+ if ('--format' === arg) {
1993
+ const f = argv[++i];
1994
+ if ('text' !== f && 'json' !== f) {
1995
+ process.stderr.write('aontu: --format needs text or json\n');
1996
+ return 2;
1997
+ }
1998
+ format = f;
1999
+ }
2000
+ else if ('--at' === arg) {
2001
+ at = argv[++i];
2002
+ if (null == at) {
2003
+ process.stderr.write('aontu: --at needs a path\n');
2004
+ return 2;
2005
+ }
2006
+ }
2007
+ else if ('--marker' === arg) {
2008
+ marker = argv[++i];
2009
+ if (null == marker) {
2010
+ process.stderr.write('aontu: --marker needs a token\n');
2011
+ return 2;
2012
+ }
2013
+ }
2014
+ else if ('--profile' === arg) {
2015
+ const pf = argv[++i];
2016
+ if (null == pf) {
2017
+ process.stderr.write('aontu: --profile needs a file\n');
2018
+ return 2;
2019
+ }
2020
+ profileFiles.push(pf);
2021
+ }
2022
+ else if (arg.startsWith('-')) {
2023
+ process.stderr.write(`aontu: unknown trace option ${arg} (try --help)\n`);
2024
+ return 2;
2025
+ }
2026
+ else {
2027
+ rest.push(arg);
2028
+ }
2029
+ }
2030
+ if (1 !== rest.length) {
2031
+ process.stderr.write(`aontu: trace needs one file\n${TRACE_HELP}\n`);
2032
+ return 2;
2033
+ }
2034
+ let src;
2035
+ try {
2036
+ src = (0, node_fs_1.readFileSync)(rest[0], 'utf8');
2037
+ }
2038
+ catch (err) {
2039
+ process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
2040
+ return 2;
2041
+ }
2042
+ const declared = loadProfiles(profileFiles, trust);
2043
+ if ('number' === typeof declared) {
2044
+ return declared;
2045
+ }
2046
+ // A GENERATOR IS AN ENTRY, not a preprocessing step: the file whose
2047
+ // provenance is asked for is the one the author edits.
2048
+ if (!rest[0].endsWith('.aon')) {
2049
+ src = (0, template_1.desugarTemplate)(src, marker ??
2050
+ (0, template_1.markerFromProfiles)(declared, rest[0]) ?? (0, template_1.markerFor)(rest[0]));
2051
+ }
2052
+ const report = (0, trace_1.traceRun)(src, {
2053
+ path: rest[0], at,
2054
+ ...verbOpts(trust, entryRootOf(rest[0])),
2055
+ });
2056
+ if ('error' === report.verdict) {
2057
+ // An error report always carries its findings.
2058
+ const errors = report.errors;
2059
+ process.stderr.write(errors.map(renderFinding).join('\n') + '\n');
2060
+ return 4;
2061
+ }
2062
+ if ('json' === format) {
2063
+ process.stdout.write(JSON.stringify({ trace: report.trace }) + '\n');
2064
+ return 0;
2065
+ }
2066
+ for (const e of report.trace) {
2067
+ process.stdout.write([e.file, e.at, e.node, e.rule].join('\t') + '\n');
2068
+ }
2069
+ return 0;
2070
+ }
2272
2071
  function runReaches(argv) {
2273
2072
  const trusted = takeTrust(argv);
2274
2073
  if (null == trusted) {
@@ -2469,12 +2268,6 @@ function runView(argv) {
2469
2268
  rest.push(arg);
2470
2269
  }
2471
2270
  }
2472
- // ESCAPES NEVER GO INTO A FILE. A pinned golden holding terminal
2473
- // control codes is not a golden anybody can read, and a byte
2474
- // comparison against one would fail on the reader's terminal
2475
- // settings. `auto` resolves to `none` there on its own; asking for
2476
- // `ansi` explicitly is a usage error rather than a silent downgrade,
2477
- // so a script that wanted colour is told where it went.
2478
2271
  if ('ansi' === style && (undefined !== out || undefined !== opts.views)) {
2479
2272
  process.stderr.write('aontu: --style ansi writes to a terminal, not to a file\n');
2480
2273
  return 2;
@@ -2482,10 +2275,6 @@ function runView(argv) {
2482
2275
  // THE VIEW DOCUMENT draws every figure a document declares, so it
2483
2276
  // names no kind: the declarations do, one each.
2484
2277
  if (undefined !== opts.views) {
2485
- // A declaration names its own profile, so the style is left to
2486
- // each figure's own default; `--style none` still reaches every
2487
- // one of them, which is how a host page that binds the CSS
2488
- // variables asks for eight figures without eight stylesheets.
2489
2278
  opts.style = viewStyleOf(style, undefined);
2490
2279
  return runViewSet(rest, opts, trust, { format, check, strict, out });
2491
2280
  }
@@ -2553,17 +2342,6 @@ function runView(argv) {
2553
2342
  docs: files.slice(1).map((path, i) => ({ src: srcs[i + 1], path })),
2554
2343
  };
2555
2344
  const report = (0, view_1.view)(srcs[0], viewOpts);
2556
- // AN EMPTY FIGURE IS THE SAME BYTES AS A DRAWN ONE MINUS ITS
2557
- // CONTENT, and every profile spells "empty" differently: text draws
2558
- // nothing at all, mermaid still draws its `flowchart LR` header, the
2559
- // matrix still prints its count line. Rather than teach this one
2560
- // place each of those spellings -- a list that goes stale the first
2561
- // time a profile gains a header -- ASK THE SAME KIND TO DRAW AN
2562
- // EMPTY DOCUMENT and compare. Equal texts mean this document
2563
- // contributed nothing to the figure, whatever the profile.
2564
- //
2565
- // It costs one drawing of `{}`, which is the cheapest document
2566
- // there is, and only on a run that produced a figure at all.
2567
2345
  if ('error' !== report.verdict && null != report.text) {
2568
2346
  const bare = (0, view_1.view)('{}', viewOpts);
2569
2347
  if ('error' !== bare.verdict && bare.text === report.text) {
@@ -2612,13 +2390,6 @@ function runView(argv) {
2612
2390
  }
2613
2391
  return strict && 'lossy' === report.verdict ? 1 : VIEW_EXIT[report.verdict];
2614
2392
  }
2615
- // `aontu view --views <path> <file>`: every figure the document
2616
- // declares, from one evaluation, all or nothing.
2617
- //
2618
- // A declared `out` is resolved against the DOCUMENT's own directory,
2619
- // not the caller's: a view document is committed beside the figures it
2620
- // gates, and a gate that only passes from one working directory is not
2621
- // a gate.
2622
2393
  function runViewSet(rest, opts, trust, how) {
2623
2394
  if (1 !== rest.length) {
2624
2395
  process.stderr.write('aontu: view --views takes one file\n');
@@ -2742,8 +2513,6 @@ function renderViewJson(report) {
2742
2513
  }
2743
2514
  function renderRelationsText(report) {
2744
2515
  const head = `verdict: ${report.verdict}`;
2745
- // WHY, when the document could not be evaluated at all: rendered as
2746
- // vet renders a finding, because it IS one (the review's finding F).
2747
2516
  const errors = report.errors ?? [];
2748
2517
  if (0 < errors.length) {
2749
2518
  return [head, ''].concat(errors.map(renderFinding)).join('\n');
@@ -2765,19 +2534,6 @@ function renderRelationsJson(report) {
2765
2534
  ...(null == report.errors ? {} : { errors: report.errors }),
2766
2535
  }, 2);
2767
2536
  }
2768
- // ---------------------------------------------------------------------
2769
- // JSON SCHEMA EXPORT (SUPPORT.md act 2, the review's finding I): the
2770
- // bridge to every structured-output API, which constrains generation to
2771
- // JSON Schema and nothing else. Export the model, let the provider
2772
- // generate under it, then `vet` the result against the model itself --
2773
- // the hybrid an enterprise actually deploys, and impossible without
2774
- // this verb.
2775
- //
2776
- // THE SCHEMA GOES TO STDOUT AND THE LOSSES TO STDERR, so `aontu
2777
- // jsonschema x.aon > schema.json` writes a schema and still tells the
2778
- // reader what it could not carry. `--strict` makes a loss a refusal,
2779
- // for the CI job that would rather fail than ship a schema weaker than
2780
- // its model.
2781
2537
  const JSONSCHEMA_HELP = 'aontu jsonschema [--at <path>] [--strict] <file> (try --help)';
2782
2538
  function runJsonSchema(argv) {
2783
2539
  const trusted = takeTrust(argv);
@@ -2861,331 +2617,19 @@ function runJsonSchema(argv) {
2861
2617
  return 'error' === report.verdict ? 4 :
2862
2618
  strict && 'lossy' === report.verdict ? 1 : 0;
2863
2619
  }
2864
- // ---------------------------------------------------------------------
2865
- // THE RENDER VERB (docs/design/RENDER.0.md D8): evaluate a document,
2866
- // vet the value at --at against aontu:code, fold code.units into bytes,
2867
- // and put them where the flag says -- one unit on stdout, every unit
2868
- // below --out (all or nothing), or compared against --check. Exit codes
2869
- // mirror jsonschema's: 0 ok; 1 lossy under --strict or drift under
2870
- // --check; 2 usage or I/O, a refused unit path included; 4 the
2871
- // document does not stand up or the instance is not aontu:code.
2872
- const RENDER_HELP = 'aontu render [--at <path>] [--profile <file>]... [--unit <path>] ' +
2873
- '[--stdout | --out <dir> | --check <dir> | --coverage] ' +
2874
- '[--coverage-at <path>] [--strict] [--marker <token>] <file> (try --help)';
2875
- function runRender(argv) {
2620
+ const TEMPLATE_HELP = 'aontu template [--resugar] [--check] [--marker <token>] <file> (try --help)';
2621
+ function runTemplate(argv) {
2876
2622
  const trusted = takeTrust(argv);
2877
2623
  if (null == trusted) {
2878
2624
  return 2;
2879
2625
  }
2880
2626
  argv = trusted.argv;
2881
2627
  const trust = trusted.trust;
2882
- const files = [];
2883
- const profileFiles = [];
2884
- let format = 'text';
2885
- let at = undefined;
2886
- let unit = undefined;
2887
- let out = undefined;
2888
- let check = undefined;
2889
- let toStdout = false;
2890
- let strict = false;
2891
- let coverage = false;
2892
- let coverageAt = undefined;
2893
- let marker = undefined;
2894
- for (let i = 0; i < argv.length; i++) {
2895
- const arg = argv[i];
2896
- if ('-h' === arg || '--help' === arg) {
2897
- process.stdout.write(HELP);
2898
- return 0;
2899
- }
2900
- if ('--format' === arg) {
2901
- const f = argv[++i];
2902
- if ('text' !== f && 'json' !== f) {
2903
- process.stderr.write('aontu: --format needs text or json\n');
2904
- return 2;
2905
- }
2906
- format = f;
2907
- }
2908
- else if ('--at' === arg) {
2909
- at = argv[++i];
2910
- if (null == at) {
2911
- process.stderr.write('aontu: --at needs a path\n');
2912
- return 2;
2913
- }
2914
- }
2915
- else if ('--unit' === arg) {
2916
- unit = argv[++i];
2917
- if (null == unit) {
2918
- process.stderr.write('aontu: --unit needs a unit path\n');
2919
- return 2;
2920
- }
2921
- }
2922
- else if ('--profile' === arg) {
2923
- const pf = argv[++i];
2924
- if (null == pf) {
2925
- process.stderr.write('aontu: --profile needs a file\n');
2926
- return 2;
2927
- }
2928
- profileFiles.push(pf);
2929
- }
2930
- else if ('--out' === arg) {
2931
- out = argv[++i];
2932
- if (null == out) {
2933
- process.stderr.write('aontu: --out needs a directory\n');
2934
- return 2;
2935
- }
2936
- }
2937
- else if ('--check' === arg) {
2938
- check = argv[++i];
2939
- if (null == check) {
2940
- process.stderr.write('aontu: --check needs a directory\n');
2941
- return 2;
2942
- }
2943
- }
2944
- else if ('--stdout' === arg) {
2945
- toStdout = true;
2946
- }
2947
- else if ('--coverage' === arg) {
2948
- coverage = true;
2949
- }
2950
- else if ('--marker' === arg) {
2951
- marker = argv[++i];
2952
- if (null == marker) {
2953
- process.stderr.write('aontu: --marker needs a token\n');
2954
- return 2;
2955
- }
2956
- }
2957
- else if ('--coverage-at' === arg) {
2958
- coverageAt = argv[++i];
2959
- if (null == coverageAt) {
2960
- process.stderr.write('aontu: --coverage-at needs a path\n');
2961
- return 2;
2962
- }
2963
- }
2964
- else if ('--strict' === arg) {
2965
- strict = true;
2966
- }
2967
- else if (arg.startsWith('-')) {
2968
- process.stderr.write(`aontu: unknown render option ${arg} (try --help)\n`);
2969
- return 2;
2970
- }
2971
- else {
2972
- files.push(arg);
2973
- }
2974
- }
2975
- if (1 !== files.length) {
2976
- process.stderr.write(`aontu: render needs one file\n${RENDER_HELP}\n`);
2977
- return 2;
2978
- }
2979
- const modes = [toStdout, undefined !== out, undefined !== check, coverage]
2980
- .filter((on) => on).length;
2981
- if (1 < modes) {
2982
- process.stderr.write('aontu: render takes one of --stdout, --out, --check or --coverage\n');
2983
- return 2;
2984
- }
2985
- // A NARROWER MEASURE NEEDS SOMETHING TO NARROW. `--coverage-at`
2986
- // without `--coverage` asks for a report the run does not compute,
2987
- // and answering silently would be the wrong half of the request.
2988
- if (undefined !== coverageAt && !coverage) {
2989
- process.stderr.write('aontu: --coverage-at needs --coverage\n');
2990
- return 2;
2991
- }
2992
- let src;
2993
- try {
2994
- src = (0, node_fs_1.readFileSync)(files[0], 'utf8');
2995
- }
2996
- catch (err) {
2997
- process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
2998
- return 2;
2999
- }
3000
- // THE ENTRY MAY BE A TEMPLATE (TEMPLATE.0.md; P8), and its EXTENSION
3001
- // decides, as an include's extension decides what the include is
3002
- // (ADR-012): a generator is a file in the target's own syntax, so it
3003
- // carries the target's extension and never `.aon`. Desugared here
3004
- // rather than anywhere deeper, because a template is an entry
3005
- // spelling and not a value: an include is still aontu.
3006
- if (!files[0].endsWith('.aon')) {
3007
- src = (0, template_1.desugarTemplate)(src, marker ?? (0, template_1.markerFor)(files[0]));
3008
- }
3009
- // THE PROFILES (D5): each --profile file is a document whose root is
3010
- // `profile: {lang, ...}`, evaluated under the verb's trust and vetted
3011
- // against aontu:profile as a settled value before the fold reads it
3012
- // (renderProfile, which also fills the defaults). Two files claiming
3013
- // one lang is a usage error: the fold could not choose.
3014
- const profiles = [];
3015
- const langs = new Map();
3016
- for (const pf of profileFiles) {
3017
- let text;
3018
- try {
3019
- text = (0, node_fs_1.readFileSync)(pf, 'utf8');
3020
- }
3021
- catch (err) {
3022
- process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
3023
- return 2;
3024
- }
3025
- const loaded = (0, aontu_1.renderProfile)(text, { path: (0, node_path_1.resolve)(pf), ...verbOpts(trust, entryRootOf(pf)) });
3026
- if (undefined !== loaded.errors) {
3027
- process.stderr.write(loaded.errors.map(renderFinding).join('\n') + '\n');
3028
- return 4;
3029
- }
3030
- const profile = loaded.profile;
3031
- const prev = langs.get(profile.lang);
3032
- if (undefined !== prev) {
3033
- process.stderr.write(`aontu: two profiles claim ${profile.lang}: ${prev} and ${pf}\n`);
3034
- return 2;
3035
- }
3036
- langs.set(profile.lang, pf);
3037
- profiles.push(profile);
3038
- }
3039
- // A RENDER WITH NO PROFILE PRODUCES NO UNITS, and said so with zero
3040
- // bytes and exit 0. The profile is what maps a model onto a
3041
- // language, so without one there is nothing for the renderer to
3042
- // write -- which is a usable answer only if the caller is told.
3043
- const noProfiles = 0 === profiles.length;
3044
- const report = (0, aontu_1.render)(src, {
3045
- at, unit, strict, profiles, path: files[0],
3046
- coverage, coverageAt,
3047
- // THE JSON REPORT CARRIES THE TRACE (D9), which is what the shape
3048
- // there has always said; a text run computes it only when the
3049
- // coverage report needs it.
3050
- trace: 'json' === format,
3051
- ...verbOpts(trust, entryRootOf(files[0])),
3052
- });
3053
- // Said once, whatever the format: stdout stays the report.
3054
- if ('error' !== report.verdict && 0 === report.units.length) {
3055
- vacuous('nothing was rendered', noProfiles
3056
- ? 'no profile was given, and the document declares none' +
3057
- ' (see aontu help tasks)'
3058
- : 'the document produced no units under this profile');
3059
- }
3060
- if ('json' === format) {
3061
- process.stdout.write((0, aontu_1.exactJSON)({
3062
- aontu: { version: version(), verb: 'render' },
3063
- verdict: report.verdict,
3064
- units: report.units,
3065
- lossy: report.lossy,
3066
- ...(null == report.errors ? {} : { errors: report.errors }),
3067
- ...(null == report.trace ? {} : { trace: report.trace }),
3068
- ...(null == report.coverage ? {} : { coverage: report.coverage }),
3069
- }, 2) + '\n');
3070
- return renderExit(report, 0);
3071
- }
3072
- if ('error' === report.verdict) {
3073
- process.stderr.write(report.errors.map(renderFinding).join('\n') + '\n');
3074
- return renderExit(report, 0);
3075
- }
3076
- let drift = 0;
3077
- if (toStdout) {
3078
- // ONE UNIT'S BYTES AND NOTHING ELSE, so the output can be piped
3079
- // into a formatter or a file.
3080
- if (1 !== report.units.length) {
3081
- process.stderr.write('aontu: --stdout needs exactly one unit, and the instance has ' +
3082
- `${report.units.length}; --unit names one\n`);
3083
- return 2;
3084
- }
3085
- process.stdout.write(report.units[0].text);
3086
- }
3087
- else if (undefined !== out) {
3088
- // EVERY UNIT BELOW <dir>, OR NOTHING: every unit rendered first
3089
- // (the report above), and no file touched unless all did. The
3090
- // directory is realpath-confined; a unit path is already a relative
3091
- // descent (render_path refuses the rest), and the check here is
3092
- // against the symlink inside it. render never deletes.
3093
- for (const u of report.units) {
3094
- if ((0, mcp_1.outsideRoot)(out, (0, node_path_1.resolve)(out, u.path))) {
3095
- process.stderr.write(`aontu: ${u.path} escapes ${out}\n`);
3096
- return 2;
3097
- }
3098
- }
3099
- for (const u of report.units) {
3100
- const full = (0, node_path_1.resolve)(out, u.path);
3101
- try {
3102
- (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(full), { recursive: true });
3103
- (0, node_fs_1.writeFileSync)(full, u.text, 'utf8');
3104
- }
3105
- catch (err) {
3106
- process.stderr.write(`aontu: cannot write ${u.path}: ${err.message}\n`);
3107
- return 2;
3108
- }
3109
- process.stderr.write(`wrote ${u.path}\n`);
3110
- }
3111
- }
3112
- else if (undefined !== check) {
3113
- // RENDER AND COMPARE: a unit whose bytes differ from the file at
3114
- // <dir>/<path>, or whose file is absent, is drift, listed by path.
3115
- // The CI form.
3116
- for (const u of report.units) {
3117
- let have = undefined;
3118
- try {
3119
- have = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(check, u.path), 'utf8');
3120
- }
3121
- catch {
3122
- // Absent is drift, reported below.
3123
- }
3124
- if (undefined === have) {
3125
- drift++;
3126
- process.stderr.write(`aontu: ${u.path} is missing from ${check}\n`);
3127
- }
3128
- else if (have !== u.text) {
3129
- drift++;
3130
- process.stderr.write(`aontu: ${u.path} differs from the rendered unit\n`);
3131
- }
3132
- }
3133
- }
3134
- else if (coverage) {
3135
- // THE COVERAGE REPORT (P7), one line per finding and a count at
3136
- // the end: dead model first, then the declarations no rule
3137
- // produced. A clean report is the count line alone.
3138
- const cov = report.coverage;
3139
- for (const d of cov.dead) {
3140
- process.stdout.write(`dead: ${d}\n`);
3141
- }
3142
- for (const u of cov.unruled) {
3143
- process.stdout.write(`unruled: ${u.unit} ${u.path}\n`);
3144
- }
3145
- process.stdout.write(`coverage: ${cov.read.length} path(s) read, ${cov.dead.length} ` +
3146
- `no output consumed, ${cov.unruled.length} declaration(s) ` +
3147
- 'no rule produced\n');
3148
- }
3149
- else {
3150
- // THE SUMMARY: one line per unit -- its path, its language and its
3151
- // size -- since several units have no one text to print.
3152
- for (const u of report.units) {
3153
- process.stdout.write(`${u.path}\t${u.lang}\t${u.text.length} bytes\n`);
3154
- }
3155
- }
3156
- for (const l of report.lossy) {
3157
- process.stderr.write(`lossy: ${l.unit} ${l.path} tier ${l.tier} ${l.construct}: ${l.reason}\n`);
3158
- }
3159
- return renderExit(report, drift);
3160
- }
3161
- // D8's exit table over a report: a refused unit path is usage (2), a
3162
- // strict refusal is lossy (1), any other error is the document's (4);
3163
- // drift under --check is 1.
3164
- function renderExit(report, drift) {
3165
- if ('error' === report.verdict) {
3166
- const errors = report.errors;
3167
- if (errors.every((f) => 'render_path' === f.code)) {
3168
- return 2;
3169
- }
3170
- if (errors.every((f) => 'render_strict' === f.code)) {
3171
- return 1;
3172
- }
3173
- return 4;
3174
- }
3175
- return 0 < drift ? 1 : 0;
3176
- }
3177
- // ---------------------------------------------------------------------
3178
- // THE TEMPLATE SURFACE (docs/design/TEMPLATE.0.md; RENDER.0.md P8): the
3179
- // two transforms and the round trip between them. `render` reads a
3180
- // template directly, by its extension; this verb is for seeing the
3181
- // canonical form, for writing one by hand and sugaring it, and for the
3182
- // check that keeps a committed template and its meaning in agreement.
3183
- const TEMPLATE_HELP = 'aontu template [--resugar] [--check] [--marker <token>] <file> (try --help)';
3184
- function runTemplate(argv) {
3185
2628
  const files = [];
3186
2629
  let resugar = false;
3187
2630
  let check = false;
3188
2631
  let marker = undefined;
2632
+ const profileFiles = [];
3189
2633
  for (let i = 0; i < argv.length; i++) {
3190
2634
  const arg = argv[i];
3191
2635
  if ('-h' === arg || '--help' === arg) {
@@ -3205,6 +2649,14 @@ function runTemplate(argv) {
3205
2649
  return 2;
3206
2650
  }
3207
2651
  }
2652
+ else if ('--profile' === arg) {
2653
+ const pf = argv[++i];
2654
+ if (null == pf) {
2655
+ process.stderr.write('aontu: --profile needs a file\n');
2656
+ return 2;
2657
+ }
2658
+ profileFiles.push(pf);
2659
+ }
3208
2660
  else if (arg.startsWith('-')) {
3209
2661
  process.stderr.write(`aontu: unknown template option ${arg} (try --help)\n`);
3210
2662
  return 2;
@@ -3217,10 +2669,6 @@ function runTemplate(argv) {
3217
2669
  process.stderr.write(`aontu: template needs one file\n${TEMPLATE_HELP}\n`);
3218
2670
  return 2;
3219
2671
  }
3220
- // THE TWO ARE DIRECTIONS, NOT MODES THAT COMPOSE: `--check` reads a
3221
- // template and asks whether the round trip answers it back, and
3222
- // `--resugar` reads the canonical form instead. A run cannot be both
3223
- // at once, because the file is one thing or the other.
3224
2672
  if (resugar && check) {
3225
2673
  process.stderr.write('aontu: template takes one of --resugar or --check\n');
3226
2674
  return 2;
@@ -3233,18 +2681,13 @@ function runTemplate(argv) {
3233
2681
  process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
3234
2682
  return 2;
3235
2683
  }
3236
- const mark = marker ?? (0, template_1.markerFor)(files[0]);
2684
+ const declared = loadProfiles(profileFiles, trust);
2685
+ if ('number' === typeof declared) {
2686
+ return declared;
2687
+ }
2688
+ const mark = marker ?? (0, template_1.markerFromProfiles)(declared, files[0]) ??
2689
+ (0, template_1.markerFor)(files[0]);
3237
2690
  if (check) {
3238
- // THE ROUND TRIP IS THE CHECK (D6): the file held to the spelling
3239
- // the two transforms answer. What that names is a marker line the
3240
- // transform would not have written -- one without its space, or one
3241
- // whose aontu is indented after the marker rather than before it,
3242
- // since the marker keeps its own indentation. It does NOT name a
3243
- // changed body line: a template's whitespace is output, so a
3244
- // trimmed trailing space is still a valid template and it is
3245
- // `render --check` against the committed files that catches it.
3246
- // The first line that differs is the report, since a whole diff of
3247
- // a generator is the file again.
3248
2691
  const back = (0, template_1.resugarTemplate)((0, template_1.desugarTemplate)(src, mark), mark);
3249
2692
  if (back === src) {
3250
2693
  return 0;
@@ -3255,12 +2698,6 @@ function runTemplate(argv) {
3255
2698
  while (n < want.length && n < have.length && want[n] === have[n]) {
3256
2699
  n++;
3257
2700
  }
3258
- // THE TWO ARE THE SAME LENGTH, always: each transform maps one
3259
- // line to one line and applies the same trailing-newline rule, so
3260
- // `back` has as many lines as `src`. The loop above therefore stops
3261
- // at a real difference rather than by running out of either -- an
3262
- // equal prefix all the way to the end IS `back === src`, which
3263
- // returned above. So both indexes are in range here.
3264
2701
  process.stderr.write(`aontu: ${files[0]}:${n + 1} is not what the round trip answers\n` +
3265
2702
  ` have: ${JSON.stringify(have[n])}\n` +
3266
2703
  ` want: ${JSON.stringify(want[n])}\n`);
@@ -3270,13 +2707,38 @@ function runTemplate(argv) {
3270
2707
  (0, template_1.resugarTemplate)(src, mark) : (0, template_1.desugarTemplate)(src, mark));
3271
2708
  return 0;
3272
2709
  }
3273
- // ---------------------------------------------------------------------
3274
- // The canon-hash (G6 phase 1): the pin an agent, a lockfile or a
3275
- // registry stores for "this module, this meaning". The hash covers the
3276
- // module evaluated STANDALONE -- its own include closure resolved and
3277
- // unified at its own root, before any consumer context -- which is what
3278
- // makes the pin transitive: an edit two includes deep changes the
3279
- // unified root, hence the hash.
2710
+ // The profiles named by --profile, vetted, or the exit code that says
2711
+ // why not. A profile is a language declared as data: `template` and
2712
+ // `fmt` match one to a file by the extensions its `template.ext`
2713
+ // names.
2714
+ function loadProfiles(profileFiles, trust) {
2715
+ const profiles = [];
2716
+ const langs = new Map();
2717
+ for (const pf of profileFiles) {
2718
+ let text;
2719
+ try {
2720
+ text = (0, node_fs_1.readFileSync)(pf, 'utf8');
2721
+ }
2722
+ catch (err) {
2723
+ process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
2724
+ return 2;
2725
+ }
2726
+ const loaded = (0, aontu_1.loadProfile)(text, { path: (0, node_path_1.resolve)(pf), ...verbOpts(trust, entryRootOf(pf)) });
2727
+ if (undefined !== loaded.errors) {
2728
+ process.stderr.write(loaded.errors.map(renderFinding).join('\n') + '\n');
2729
+ return 4;
2730
+ }
2731
+ const profile = loaded.profile;
2732
+ const prev = langs.get(profile.lang);
2733
+ if (undefined !== prev) {
2734
+ process.stderr.write(`aontu: two profiles claim ${profile.lang}: ${prev} and ${pf}\n`);
2735
+ return 2;
2736
+ }
2737
+ langs.set(profile.lang, pf);
2738
+ profiles.push(profile);
2739
+ }
2740
+ return profiles;
2741
+ }
3280
2742
  const HASH_HELP = 'aontu hash <file> (try --help)';
3281
2743
  function runHash(argv) {
3282
2744
  const trusted = takeTrust(argv);
@@ -3331,14 +2793,6 @@ function runHash(argv) {
3331
2793
  const ctx = aontu.ctx({ collect: true });
3332
2794
  const v = aontu.unify(src, { path: files[0] }, ctx);
3333
2795
  if (0 < ctx.err.length || true === v?.isNil) {
3334
- // A document that does not stand up on its own has no meaning to
3335
- // pin, and a hash of a broken evaluation would be a pin that
3336
- // silently agrees with every other broken evaluation.
3337
- // WHY it does not stand up, not just that it does not: the same
3338
- // diagnosis `aontu <file>` prints (the review's finding F).
3339
- // evalFailure unconditionally, as every other call site does: it
3340
- // owns the "ctx.err is never empty here" contract, and a guard
3341
- // that pretends otherwise is a dead arm asserting nothing.
3342
2796
  process.stderr.write(`aontu: ${files[0]} does not evaluate on its own; nothing to hash\n` +
3343
2797
  renderFinding((0, query_1.evalFailure)(ctx)) + '\n');
3344
2798
  return 4;
@@ -3353,12 +2807,6 @@ function runHash(argv) {
3353
2807
  process.stdout.write(text + '\n');
3354
2808
  return 0;
3355
2809
  }
3356
- // ---------------------------------------------------------------------
3357
- // The query surface (G7 phase 1): one node of an evaluated document,
3358
- // selected by path and rendered. Evaluation is still GLOBAL -- what
3359
- // `get` buys is the size of the ANSWER, not the cost of producing it --
3360
- // and the projections are lattice abstractions, each a valid Aontu
3361
- // document that subsumes the truth it summarises.
3362
2810
  const GET_HELP = 'aontu get <path> <file> (try --help)';
3363
2811
  function runGet(argv) {
3364
2812
  const trusted = takeTrust(argv);
@@ -3527,11 +2975,6 @@ function runWhy(argv) {
3527
2975
  }
3528
2976
  return 'no_path' === report.findings[0]?.code ? 1 : 4;
3529
2977
  }
3530
- // One contribution per line, numbered in source order, each with what
3531
- // was written, where, and how it got here. A siteless contribution
3532
- // prints no location rather than a `-1:-1` that means nothing —
3533
- // exported for the direct test, because the site SHAPE allows one
3534
- // while no document has yet produced one (ADR-002).
3535
2978
  function renderWhyText(record) {
3536
2979
  const head = `${record.path} = ${record.value}`;
3537
2980
  if (0 === record.conjuncts.length) {
@@ -3547,12 +2990,6 @@ function renderWhyText(record) {
3547
2990
  ('literal' === c.role ? '' : ` (${c.role})`);
3548
2991
  })).join('\n');
3549
2992
  }
3550
- // ---------------------------------------------------------------------
3551
- // The overlay patch verb (G7 phase 5): change a document by APPENDING
3552
- // to an overlay, not by rewriting it. An overlay entry is just another
3553
- // conjunct and unification is order-independent, so this needs no
3554
- // rewriter — the format-preserving in-place edit is stage 2, and needs
3555
- // a comment-preserving CST the parser stack does not have.
3556
2993
  const SET_HELP = 'aontu set <path>=<value> --entry <file> --overlay <file> (try --help)';
3557
2994
  function runSet(argv) {
3558
2995
  const trusted = takeTrust(argv);
@@ -3659,27 +3096,10 @@ function runSet(argv) {
3659
3096
  }, 2) + '\n');
3660
3097
  }
3661
3098
  else {
3662
- // A replacement is REPORTED as the edit it is, not left for the
3663
- // reader to infer from a changed file: `where: what -> what`, in
3664
- // source spelling, because the spelling is what changed.
3665
- //
3666
- // PAST TENSE ONLY WHERE IT HAPPENED. A refused write leaves the
3667
- // file exactly as it was, and one assignment can be replaceable
3668
- // while another makes the whole run invalid — so `replaced:` there
3669
- // tells an operator the pin was changed when it was not, and unlike
3670
- // `--dry-run` there is nothing else on the line to say otherwise.
3671
3099
  const verb = wrote ? 'replaced' : 'would replace';
3672
3100
  const edits = report.replaced.map((r) => `${verb}: ${r.file}:${r.row}:${r.col} ${r.from} -> ${r.to}`);
3673
3101
  const head = [`verdict: ${report.verdict}`].concat(edits).join('\n') +
3674
3102
  (wrote ? `\nwrote: ${overlayFile}` : dryRun ? '\n(dry run)' : '');
3675
- // A SUCCESSFUL COMMAND WRITES ITS STATUS TO STDOUT, findings or
3676
- // not. Routing on `findings.length` was right while every finding
3677
- // this verb could produce was an ERROR; `--in-place` made a WARNING
3678
- // possible, and a run that held, wrote the file and exited 0 then
3679
- // sent its whole report to stderr — leaving stdout empty, so
3680
- // `$(aontu set ...)` captured nothing and only the JSON form
3681
- // behaved like a success. The verdict decides the stream; warnings
3682
- // are diagnostics and go to stderr beside it.
3683
3103
  const failed = 'invalid' === report.verdict || 'error' === report.verdict;
3684
3104
  const findingText = report.findings.map(renderFinding);
3685
3105
  if (failed) {
@@ -3698,14 +3118,6 @@ function runSet(argv) {
3698
3118
  }
3699
3119
  return VET_EXIT[report.verdict];
3700
3120
  }
3701
- // ---------------------------------------------------------------------
3702
- // The role gate (docs/design/ALLOW.0.md): may the role the caller is
3703
- // operating under modify these subtrees? Asked before `set`, by an
3704
- // agent whose skill names its role, and answered from a role model
3705
- // that is itself an aontu document. The verdict is the exit code, as
3706
- // it is for every gate here: 0 is yes, 1 is no, 4 is "the model that
3707
- // was to decide does not stand up", and an agent branches on nothing
3708
- // else.
3709
3121
  const ALLOW_HELP = 'aontu allow --role <role> <roles-file> <path> [more-paths...] (try --help)';
3710
3122
  const ALLOW_EXIT = {
3711
3123
  allowed: 0,
@@ -3808,14 +3220,6 @@ function runAllow(argv) {
3808
3220
  process.stderr.write('aontu: --role needs one key, without dots\n');
3809
3221
  return 2;
3810
3222
  }
3811
- // A path may arrive in `set`'s spelling, `$.a.b=1`, so a skill can
3812
- // hand the gate the very arguments the write will get. The text up
3813
- // to the first `=` is the path, and it starts with `$`: an empty
3814
- // argument, or a second file name, would otherwise read as a path
3815
- // and be answered. The VALUE is checked to be one value. `set`
3816
- // appends it as source after the flattened path, so a value carrying
3817
- // a second pair -- `3 secrets: key: "x"` -- writes a sibling of the
3818
- // overlay root, a subtree the gate was never asked about.
3819
3223
  const paths = [];
3820
3224
  for (const arg of asked) {
3821
3225
  const eq = arg.indexOf('=');
@@ -3949,28 +3353,17 @@ function runAgentsMd(argv) {
3949
3353
  process.stdout.write(`wrote: ${write}\n`);
3950
3354
  return 0;
3951
3355
  }
3952
- // Exit without truncating output.
3953
- //
3954
- // process.exit() terminates immediately, discarding anything still
3955
- // queued on stdout. A write to a PIPE is asynchronous once it exceeds
3956
- // the pipe buffer, so `write(big); exit(0)` silently truncated output at
3957
- // 65536 bytes — while a write to a TTY or a file, being synchronous,
3958
- // looked fine. Setting exitCode instead lets the process end naturally,
3959
- // after the queue drains.
3960
- //
3961
- // This predates the exact leaves but they make it trivially reachable
3962
- // (one long biginteger canon exceeds the buffer), and it lands squarely
3963
- // on the parity-probe discipline in AGENTS.md, which derives expected
3964
- // spec values by piping BOTH CLIs and comparing. A truncated pipe there
3965
- // reads as a port divergence.
3966
- // ---------------------------------------------------------------------
3967
- // The source formatter (docs/design/FMT.0.md): one agreed form, in the
3968
- // tradition of gofmt. The verb prints, lists, checks, diffs or rewrites;
3969
- // the form itself is the library's (ts/src/format.ts), and the two
3970
- // ports agree on it row by row in test/spec/fmt.tsv.
3971
- const FMT_HELP = 'aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] <file>... (try --help)';
3356
+ const FMT_HELP = 'aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] ' +
3357
+ '[--profile <file>] <file>... (try --help)';
3972
3358
  function runFmt(argv) {
3359
+ const trusted = takeTrust(argv);
3360
+ if (null == trusted) {
3361
+ return 2;
3362
+ }
3363
+ argv = trusted.argv;
3364
+ const trust = trusted.trust;
3973
3365
  const files = [];
3366
+ const profileFiles = [];
3974
3367
  let marker = undefined;
3975
3368
  const flags = {
3976
3369
  write: false, list: false, check: false, diff: false, lint: false, strict: false,
@@ -4002,14 +3395,22 @@ function runFmt(argv) {
4002
3395
  }
4003
3396
  else if ('--marker' === arg) {
4004
3397
  // THE MARKER SAYS THE FILE IS A GENERATOR, whatever its
4005
- // extension: `render` and `template` take the same option for
4006
- // the same reason, a language the table has never seen.
3398
+ // extension: `fmt` and `template` take the same option for the
3399
+ // same reason, a language the table has never seen.
4007
3400
  marker = argv[++i];
4008
3401
  if (null == marker) {
4009
3402
  process.stderr.write('aontu: --marker needs a token\n');
4010
3403
  return 2;
4011
3404
  }
4012
3405
  }
3406
+ else if ('--profile' === arg) {
3407
+ const pf = argv[++i];
3408
+ if (null == pf) {
3409
+ process.stderr.write('aontu: --profile needs a file\n');
3410
+ return 2;
3411
+ }
3412
+ profileFiles.push(pf);
3413
+ }
4013
3414
  else if (arg.startsWith('-')) {
4014
3415
  process.stderr.write(`aontu: unknown fmt option ${arg} (try --help)\n`);
4015
3416
  return 2;
@@ -4033,6 +3434,10 @@ function runFmt(argv) {
4033
3434
  process.stdin.on('end', () => resolve(fmtOne('<stdin>', src, flags, marker)));
4034
3435
  });
4035
3436
  }
3437
+ const declared = loadProfiles(profileFiles, trust);
3438
+ if ('number' === typeof declared) {
3439
+ return declared;
3440
+ }
4036
3441
  // Several files onto standard output would be one stream nobody can
4037
3442
  // split again (the note's X-6): the verb refuses unless an option
4038
3443
  // says what to do with each.
@@ -4051,32 +3456,18 @@ function runFmt(argv) {
4051
3456
  process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
4052
3457
  return 2;
4053
3458
  }
4054
- const mark = fmtMarker(file, src, marker);
3459
+ const mark = fmtMarker(file, src, marker ?? (0, template_1.markerFromProfiles)(declared, file));
4055
3460
  if (false === mark) {
4056
3461
  process.stderr.write(`aontu: ${file} is not aontu source (.aon, .aontu) and carries no ` +
4057
3462
  `${(0, template_1.markerFor)(file)} marker line, so there is no aontu in it to ` +
4058
- 'format; --marker names the marker for a language the table does ' +
4059
- 'not know\n');
3463
+ 'format; --marker names the marker for a language the table ' +
3464
+ 'does not know, and --profile reads one that declares it\n');
4060
3465
  return 2;
4061
3466
  }
4062
3467
  worst = Math.max(worst, fmtOne(file, src, flags, mark));
4063
3468
  }
4064
3469
  return worst;
4065
3470
  }
4066
- // WHAT A FILE IS, BY ITS EXTENSION (ADR-012's rule, and the one
4067
- // `render` reads an entry by): `.aon` and `.aontu` are aontu source,
4068
- // and anything else is a GENERATOR written in the target's own syntax
4069
- // (docs/design/TEMPLATE.0.md), whose marker lines carry the document
4070
- // this formats and whose other lines are output. `undefined` is aontu,
4071
- // a string is the generator's marker, and `false` is neither.
4072
- //
4073
- // A FILE WITH NO MARKER LINE IN IT IS NEITHER, and that is what keeps
4074
- // FMT.0.md §9's boundary where it stood: a `.json`, `.yaml` or `.toml`
4075
- // include is another language's file, and reading one as a generator
4076
- // would answer it back unchanged having understood none of it. The
4077
- // marker is the evidence that a file was written to carry aontu at
4078
- // all. `--marker` says so outright, and then the file is a generator
4079
- // whatever it is called.
4080
3471
  function fmtMarker(file, src, marker) {
4081
3472
  if (undefined !== marker) {
4082
3473
  return marker;
@@ -4132,11 +3523,8 @@ function fmtOne(name, src, flags, marker) {
4132
3523
  }
4133
3524
  return flags.check ? 1 : strict;
4134
3525
  }
4135
- // The real pair takes the process's own stdin and stdout, which no
4136
- // in-process test can lend it; the executable-entry tests in
4137
- // cli.test.ts run each through a child process instead, so these two
4138
- // lines are excluded from the in-process count, as the stdio wiring
4139
- // of lsp-server.ts is.
3526
+ // Excluded: the real pair takes the process stdio, so ts/test/cli.test.ts
3527
+ // drives each server through a child process instead.
4140
3528
  /* node:coverage ignore next 4 */
4141
3529
  const SERVERS = {
4142
3530
  lsp: () => void (0, lsp_server_1.main)(),
@@ -4176,19 +3564,6 @@ function parseTrustArg(value) {
4176
3564
  }
4177
3565
  return undefined;
4178
3566
  }
4179
- // THE TEACHING PACK, SERVED FROM THE COMMAND (G11 phase 1,
4180
- // docs/capability-review/g11-agent-onramp.md; mirrors
4181
- // go/cmd/aontu/help.go).
4182
- //
4183
- // HELP documents the TOOLCHAIN and says nothing about the LANGUAGE:
4184
- // `&`, the map template and the one construct an ontology cannot be
4185
- // written without, occurs zero times in it, while `template` occurs
4186
- // fourteen times and names an unrelated verb every time. docs/skill/
4187
- // was already the right content and already gated; the gap was
4188
- // DELIVERY, since it reached an installation as
4189
- // node_modules/aontu/skill/ where nothing looks. ts/src/helpdoc.ts is
4190
- // generated from those sources by ts/scripts/helpdoc.cjs and asserted
4191
- // byte-identical with them by ts/test/helpdoc.test.ts.
4192
3567
  const HELP_VERB_HELP = 'aontu help [topic] (try `aontu help` for the topics)';
4193
3568
  const EXPLAIN_HELP = 'aontu explain <code> (try `aontu explain --list`)';
4194
3569
  function helpIndexText(index) {
@@ -4260,22 +3635,6 @@ function runHelp(argv) {
4260
3635
  `aontu: topics are ${helpdoc_1.HELPDOC.map((t) => t.topic).join(', ')}\n`);
4261
3636
  return 2;
4262
3637
  }
4263
- // `aontu explain <code>` (G11 phase 3; mirrors
4264
- // go/cmd/aontu/explain.go).
4265
- //
4266
- // THE REGISTRY IS THE LIST, NOT THE HINT TABLE. test/spec/errcodes.tsv
4267
- // registers 157 codes and the spec suite asserts set equality between
4268
- // the file and codeClasses IN BOTH PORTS, so listing from codeClasses
4269
- // is listing the shared contract. The hint tables are smaller and are
4270
- // NOT in parity -- 130 entries here against 131 in Go, the extra being
4271
- // decimal_syntax, which this port never raises -- so listing from them
4272
- // would make `aontu explain --list` differ between ports over a
4273
- // difference that is not about what either port can report.
4274
- //
4275
- // A REGISTERED CODE WITH NO HINT ANSWERS WITH ITS CLASS AND SAYS SO.
4276
- // Twenty-seven registered codes carry no explanation text here; before
4277
- // this verb their absence was invisible, because a hint is only ever
4278
- // seen beside the error that raises it.
4279
3638
  // The dynamic prefixes a generated code extends (`func:upper`,
4280
3639
  // `op[+]`). Mirrors CODE_PREFIXES in ts/src/hints.ts, which is not
4281
3640
  // exported; a code that extends one is registered through its prefix
@@ -4399,20 +3758,6 @@ function runExplain(argv) {
4399
3758
  process.stdout.write(`code: ${code}\nclass: ${cls}\n\n${body}\n`);
4400
3759
  return 0;
4401
3760
  }
4402
- // `aontu init` (G11 phase 6,
4403
- // docs/capability-review/g11-agent-onramp.md).
4404
- //
4405
- // NOT SCAFFOLDING CONVENIENCE. The agent's most expensive failure is
4406
- // writing a FIRST document at all: the measurement that opened G11
4407
- // found one reaching for the wildcard its neighbours use and getting
4408
- // `verdict: valid` over data that violates it. A known-good starting
4409
- // document turns generation into editing, which is the operation a
4410
- // model is reliably good at.
4411
- //
4412
- // The trio is real, runnable and tested where it lives
4413
- // (docs/skill/init/, run by ts/test/helpdoc.test.ts), and staged into
4414
- // both ports by the same generator that stages the teaching pack, so
4415
- // the two write the same bytes.
4416
3761
  const INIT_HELP = 'aontu init [dir] (try --help)';
4417
3762
  function runInit(argv) {
4418
3763
  const dirs = [];
@@ -4432,10 +3777,6 @@ function runInit(argv) {
4432
3777
  return 2;
4433
3778
  }
4434
3779
  const dir = dirs[0] ?? '.';
4435
- // REFUSES TO OVERWRITE, and checks every member BEFORE writing any of
4436
- // them: a scaffold that wrote two files and then refused the third
4437
- // would leave a directory in a state neither the caller nor a re-run
4438
- // can reason about.
4439
3780
  const standing = helpdoc_1.INITDOC.filter((f) => (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, f.name)));
4440
3781
  if (0 < standing.length) {
4441
3782
  process.stderr.write(`aontu: ${dir} already holds ${standing.map((f) => f.name).join(', ')}\n` +
@@ -4459,17 +3800,11 @@ function runInit(argv) {
4459
3800
  'Learn the language: aontu help language\n');
4460
3801
  return 0;
4461
3802
  }
4462
- // EVERY VERB THIS PORT DISPATCHES, for the nearest-verb suggestion
4463
- // G11 phase 2 prints. A separate list from the if-chain in main()
4464
- // because the chain's arms have three different shapes and cannot be
4465
- // a table; ts/test/cli-help.test.ts keeps the two from drifting by
4466
- // running each name and requiring it not to fall through to the bare
4467
- // command.
4468
3803
  const KNOWN_VERBS = [
4469
3804
  'agentsmd', 'allow', 'breaking', 'explain', 'fmt', 'get', 'hash',
4470
3805
  'help', 'init', 'jsonschema', 'lsp', 'mcp', 'mod', 'reaches',
4471
- 'relations', 'render', 'set', 'subsume', 'template', 'trim', 'vet',
4472
- 'view', 'why',
3806
+ 'relations', 'set', 'subsume', 'template', 'trace', 'trim',
3807
+ 'vet', 'view', 'why',
4473
3808
  ];
4474
3809
  exports.KNOWN_VERBS = KNOWN_VERBS;
4475
3810
  // looksLikeVerb reports whether an unreadable argument was meant as a
@@ -4481,13 +3816,6 @@ function looksLikeVerb(arg) {
4481
3816
  !/[/\\.]/.test(arg) &&
4482
3817
  !arg.startsWith('-');
4483
3818
  }
4484
- // NEAREST-VERB SUGGESTION (G11 phase 2). Restricted
4485
- // Damerau-Levenshtein with a cap that grows with the word and stops at
4486
- // three: one edit is a convincing suggestion on any length, three is
4487
- // the most that can be believed on a long one, and an UNCAPPED
4488
- // nearest match on a three-letter typo names something unrelated with
4489
- // confidence. Mirrors go/cmd/aontu/help.go, including the sort, so
4490
- // the two ports suggest the same verb on a tie.
4491
3819
  function nearestVerb(word, verbs) {
4492
3820
  let best = '';
4493
3821
  let bestDist = Infinity;
@@ -4501,8 +3829,6 @@ function nearestVerb(word, verbs) {
4501
3829
  }
4502
3830
  return bestDist > limit ? '' : best;
4503
3831
  }
4504
- // Levenshtein with a transposition, iterative over two rows. Mirrors
4505
- // editDistance in go/cmd/aontu/help.go exactly.
4506
3832
  function editDistance(a, b) {
4507
3833
  const ar = [...a];
4508
3834
  const br = [...b];
@@ -4527,25 +3853,12 @@ function editDistance(a, b) {
4527
3853
  return prev[br.length];
4528
3854
  }
4529
3855
  function main(argv, servers = SERVERS) {
4530
- // COLOUR OFF WHEN THE DESTINATION IS NOT A TERMINAL. Error frames
4531
- // hardcoded their ANSI escapes, so a piped report and a `--jsonl`
4532
- // answer carried terminal control codes into whatever read them (the
4533
- // review's finding F). `NO_COLOR` is honoured by the library itself;
4534
- // only the CLI can see whether its stderr is a terminal, so only the
4535
- // CLI can make this call. `undefined` means "leave it to NO_COLOR".
4536
3856
  (0, aontu_1.setColor)(true === process.stderr.isTTY ? undefined : false);
4537
3857
  let mode = 'json';
4538
3858
  // THE REPORT FORM (G11 phase 7), default text: every existing caller
4539
3859
  // reads exactly what it always read, and a caller that asks for json
4540
3860
  // gets the failure in the finding shape every other verb reports.
4541
3861
  let format = 'text';
4542
- // A LIST, though the bare command evaluates exactly one document.
4543
- // It used to be one variable and the last argument won, which made a
4544
- // MISTYPED VERB a silent success: `aontu vet2 schema.aon good.json`
4545
- // printed good.json and exited 0, because `vet2` matched no
4546
- // subcommand, fell through to this loop as a file name, and was
4547
- // overwritten twice. In a tool loop that reads as a passing
4548
- // validation. Counting them is what lets the refusal below happen.
4549
3862
  const files = [];
4550
3863
  let trust = { kind: 'system-warn', textExt: [] };
4551
3864
  let textExt = [];
@@ -4554,15 +3867,6 @@ function main(argv, servers = SERVERS) {
4554
3867
  // than the design's --json, which would read as the `:json` output
4555
3868
  // mode the REPL already has.
4556
3869
  let jsonl = false;
4557
- // Subcommand dispatch, and deliberately only for a FIRST argument:
4558
- // `aontu vet` is the verb, while `aontu somefile vet` keeps meaning
4559
- // what it always did. A file named `vet` is still reachable as
4560
- // `aontu ./vet`.
4561
- //
4562
- // Promise.resolve either way: a non-watch run returns its exit class
4563
- // synchronously (and has already written its report), while `--watch`
4564
- // resolves only when the watch ends — so one await-shaped line serves
4565
- // both without a branch to keep covered.
4566
3870
  if ('vet' === argv[2]) {
4567
3871
  return void Promise.resolve(runVet(argv.slice(3))).then(finish);
4568
3872
  }
@@ -4620,12 +3924,12 @@ function main(argv, servers = SERVERS) {
4620
3924
  if ('jsonschema' === argv[2]) {
4621
3925
  return finish(runJsonSchema(argv.slice(3)));
4622
3926
  }
4623
- if ('render' === argv[2]) {
4624
- return finish(runRender(argv.slice(3)));
4625
- }
4626
3927
  if ('template' === argv[2]) {
4627
3928
  return finish(runTemplate(argv.slice(3)));
4628
3929
  }
3930
+ if ('trace' === argv[2]) {
3931
+ return finish(runTrace(argv.slice(3)));
3932
+ }
4629
3933
  if ('reaches' === argv[2]) {
4630
3934
  return finish(runReaches(argv.slice(3)));
4631
3935
  }
@@ -4699,23 +4003,12 @@ function main(argv, servers = SERVERS) {
4699
4003
  files.push(arg);
4700
4004
  }
4701
4005
  }
4702
- // ONE DOCUMENT. The bare form has always been `aontu [options]
4703
- // [file]`, singular, and anything past the first was silently
4704
- // discarded rather than refused -- so every way of getting the verb
4705
- // wrong (a typo, a verb this port does not have, a verb spelled for
4706
- // another tool) ended in a plausible answer about the wrong file.
4707
- // Exit 2, the usage class, and the message names the cause rather
4708
- // than the symptom: nothing here can tell a mistyped verb from a
4709
- // second file, but the reader can.
4710
4006
  if (1 < files.length) {
4711
4007
  process.stderr.write(`aontu: the bare command evaluates one document, and ${files.length}` +
4712
4008
  ' were given\naontu: a mistyped verb reads as a file name' +
4713
4009
  ' (try --help)\n');
4714
4010
  return finish(2);
4715
4011
  }
4716
- // The extensions ride with the capability from here on, so the three
4717
- // entry shapes below (file, REPL, stdin) each get them by threading
4718
- // the one value they already thread.
4719
4012
  trust = { ...trust, textExt };
4720
4013
  const file = files[0];
4721
4014
  if (null != file) {
@@ -4731,5 +4024,5 @@ function main(argv, servers = SERVERS) {
4731
4024
  else {
4732
4025
  runStdin(mode, format, trust).then((code) => finish(code));
4733
4026
  }
4734
- } /* node:coverage ignore next 20 */
4027
+ } /* node:coverage ignore next 21 */
4735
4028
  //# sourceMappingURL=cli.js.map