aontu 0.60.0 → 0.62.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 (113) hide show
  1. package/README.md +3 -3
  2. package/dist/agentsmd.d.ts +1 -0
  3. package/dist/agentsmd.js +7 -1
  4. package/dist/agentsmd.js.map +1 -1
  5. package/dist/allow.d.ts +23 -0
  6. package/dist/allow.js +230 -0
  7. package/dist/allow.js.map +1 -0
  8. package/dist/aontu.d.ts +4 -2
  9. package/dist/aontu.js +4 -2
  10. package/dist/aontu.js.map +1 -1
  11. package/dist/cli.d.ts +10 -1
  12. package/dist/cli.js +1000 -47
  13. package/dist/cli.js.map +1 -1
  14. package/dist/format.d.ts +1 -0
  15. package/dist/format.js +154 -12
  16. package/dist/format.js.map +1 -1
  17. package/dist/helpdoc.d.ts +16 -0
  18. package/dist/helpdoc.js +59 -0
  19. package/dist/helpdoc.js.map +1 -0
  20. package/dist/hints.js +15 -2
  21. package/dist/hints.js.map +1 -1
  22. package/dist/lang.js +93 -40
  23. package/dist/lang.js.map +1 -1
  24. package/dist/lower.d.ts +3 -0
  25. package/dist/lower.js +3 -0
  26. package/dist/lower.js.map +1 -1
  27. package/dist/lsp.d.ts +1 -1
  28. package/dist/lsp.js +1 -1
  29. package/dist/lsp.js.map +1 -1
  30. package/dist/relation.d.ts +2 -0
  31. package/dist/relation.js +7 -1
  32. package/dist/relation.js.map +1 -1
  33. package/dist/render.js +26 -21
  34. package/dist/render.js.map +1 -1
  35. package/dist/sigdecl.js +1 -1
  36. package/dist/sigdecl.js.map +1 -1
  37. package/dist/std.js +112 -69
  38. package/dist/std.js.map +1 -1
  39. package/dist/template.d.ts +2 -1
  40. package/dist/template.js +51 -15
  41. package/dist/template.js.map +1 -1
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/dist/val/AggFuncVal.js +2 -2
  44. package/dist/val/AggFuncVal.js.map +1 -1
  45. package/dist/val/CmpFuncVal.d.ts +20 -0
  46. package/dist/val/CmpFuncVal.js +245 -0
  47. package/dist/val/CmpFuncVal.js.map +1 -0
  48. package/dist/val/EachFuncVal.d.ts +1 -2
  49. package/dist/val/EachFuncVal.js +15 -29
  50. package/dist/val/EachFuncVal.js.map +1 -1
  51. package/dist/val/FormFuncVal.js.map +1 -1
  52. package/dist/val/LowerFuncVal.js +17 -1
  53. package/dist/val/LowerFuncVal.js.map +1 -1
  54. package/dist/val/NamerFuncVal.d.ts +12 -0
  55. package/dist/val/NamerFuncVal.js +176 -0
  56. package/dist/val/NamerFuncVal.js.map +1 -0
  57. package/dist/val/NilVal.js +29 -3
  58. package/dist/val/NilVal.js.map +1 -1
  59. package/dist/val/NomFuncVal.d.ts +12 -0
  60. package/dist/val/NomFuncVal.js +187 -0
  61. package/dist/val/NomFuncVal.js.map +1 -0
  62. package/dist/val/PackFuncVal.js +3 -3
  63. package/dist/val/PackFuncVal.js.map +1 -1
  64. package/dist/val/RefVal.js +1 -1
  65. package/dist/val/TranslateFuncVal.d.ts +12 -0
  66. package/dist/val/TranslateFuncVal.js +101 -0
  67. package/dist/val/TranslateFuncVal.js.map +1 -0
  68. package/dist/val/UpperFuncVal.js +17 -1
  69. package/dist/val/UpperFuncVal.js.map +1 -1
  70. package/dist/val/caserange.d.ts +3 -0
  71. package/dist/val/caserange.js +110 -0
  72. package/dist/val/caserange.js.map +1 -0
  73. package/dist/vet.d.ts +12 -0
  74. package/dist/vet.js +209 -1
  75. package/dist/vet.js.map +1 -1
  76. package/grammar/aontu.abnf +1 -1
  77. package/grammar/aontu.gbnf +1 -1
  78. package/grammar/aontu.lark +1 -1
  79. package/grammar/aontu.tmLanguage.json +1 -1
  80. package/package.json +1 -1
  81. package/skill/SKILL.md +8 -0
  82. package/skill/init/check.sh +28 -0
  83. package/skill/init/data.aon +12 -0
  84. package/skill/init/model.aon +19 -0
  85. package/skill/tasks.md +151 -0
  86. package/src/agentsmd.ts +13 -2
  87. package/src/allow.ts +316 -0
  88. package/src/aontu.ts +10 -1
  89. package/src/cli.ts +1131 -53
  90. package/src/format.ts +191 -14
  91. package/src/helpdoc.ts +77 -0
  92. package/src/hints.ts +16 -2
  93. package/src/lang.ts +98 -42
  94. package/src/lower.ts +3 -3
  95. package/src/lsp.ts +1 -1
  96. package/src/relation.ts +21 -1
  97. package/src/render.ts +26 -21
  98. package/src/sigdecl.ts +1 -1
  99. package/src/std.ts +112 -69
  100. package/src/template.ts +55 -15
  101. package/src/val/AggFuncVal.ts +2 -2
  102. package/src/val/CmpFuncVal.ts +411 -0
  103. package/src/val/EachFuncVal.ts +49 -50
  104. package/src/val/LowerFuncVal.ts +18 -1
  105. package/src/val/NilVal.ts +29 -3
  106. package/src/val/NomFuncVal.ts +287 -0
  107. package/src/val/PackFuncVal.ts +3 -3
  108. package/src/val/RefVal.ts +1 -1
  109. package/src/val/TranslateFuncVal.ts +182 -0
  110. package/src/val/UpperFuncVal.ts +18 -1
  111. package/src/val/caserange.ts +115 -0
  112. package/src/vet.ts +287 -1
  113. package/src/val/FormFuncVal.ts +0 -119
package/src/std.ts CHANGED
@@ -1,19 +1,23 @@
1
1
  /* Copyright (c) 2025 Richard Rodger, MIT License */
2
2
 
3
- // THE BUNDLED VOCABULARY (G4 phase 4,
4
- // docs/capability-review/g4-identity-relations.md): `@"std/system"` is
5
- // served from the engine itself — no filesystem, no package resolution
6
- // — so a document may use it under every include capability except
7
- // `none`, and the hermeticity posture is not widened by a source that
8
- // never leaves the process.
3
+ // THE LANGUAGE-SUPPLIED MODELS (G4 phase 4,
4
+ // docs/capability-review/g4-identity-relations.md): `@"aontu:system"`
5
+ // is served from the engine itself — no filesystem, no package
6
+ // resolution — so a document may use it under every include capability
7
+ // except `none`, and the hermeticity posture is not widened by a source
8
+ // that never leaves the process.
9
+ //
10
+ // EVERY name here carries the `aontu:` prefix (ADR-028): a
11
+ // language-supplied schema is spelled one way, and the scheme is what
12
+ // makes it unshadowable. There is no second, bare-name spelling.
9
13
  //
10
14
  // The TEXT is the shared artifact: go/std.go carries the same bytes,
11
- // and test/spec/std-system.tsv pins its canon and its canon-hash in
15
+ // and test/spec/aontu-system.tsv pins its canon and its canon-hash in
12
16
  // both engines, so the two copies cannot drift without a red suite.
13
17
  // It carries no backtick for that reason: one string literal per port,
14
18
  // and Go's raw string has no escape.
15
19
 
16
- const STD_SYSTEM = `# std/system --- the SYSTEM VOCABULARY (G4 phase 4). Ports, components
20
+ const STD_SYSTEM = `# aontu:system --- the SYSTEM VOCABULARY (G4 phase 4). Ports, components
17
21
  # and relations need no syntax: they are schemas. Everything here is
18
22
  # ordinary unification --- conjunction, spreads, marks, defaults ---
19
23
  # so the vocabulary costs the language nothing, and an author who wants
@@ -24,31 +28,57 @@ const STD_SYSTEM = `# std/system --- the SYSTEM VOCABULARY (G4 phase 4). Ports,
24
28
  # identity and version makes "v1 and v2 describe the same entity"
25
29
  # inexpressible.
26
30
 
27
- std: {
28
-
31
+ aontu: System: {
29
32
  # One end of a connection.
30
- Port: type({
31
- direction: *in | out | inout
32
- protocol?: string
33
- })
33
+ Port: type({ direction:*in | out | inout protocol?:string })
34
34
 
35
35
  # A node with ports. Where a Component sits in the tree is what it
36
36
  # is a component OF -- containment is the document's own structure
37
37
  # and needs no mark of its own.
38
- Component: type({
39
- ports?: {&: $.std.Port}
40
- })
38
+ Component: type({ ports?:{ &: $.aontu.System.Port } })
41
39
 
42
40
  # A component that is a service. Written out rather than as
43
- # $.std.Component & {kind: service}: a reference from one member of
41
+ # $.aontu.System.Component & {kind: service}: a reference from one member of
44
42
  # this file to another does not survive being INCLUDED into a
45
43
  # document (the marks the include carries make the referring member
46
44
  # unusable), so the vocabulary states each schema on its own.
47
- Service: type({
48
- kind: service
49
- ports?: {&: $.std.Port}
50
- })
51
-
45
+ Service: type({ kind:service ports?:{ &: $.aontu.System.Port } })
46
+
47
+ # A semantic version (semver.org 2.0.0) as an ORDERED TUPLE: major,
48
+ # minor, patch, pre-release. A list and not a dotted string, because a
49
+ # version is COMPARED and the comparison runs component by component
50
+ # from the left: "1.10.0" sorts below "1.9.0" as text. A list and not
51
+ # a map, because a map has no order of its own to compare along.
52
+ #
53
+ # THE TAIL DEFAULTS, so [1] is [1 0 0 ""] and [1 2] is [1 2 0 ""].
54
+ # A major-only version is the common case and should cost one token.
55
+ #
56
+ # LEADING ZEROES ARE IMPOSSIBLE HERE rather than merely forbidden:
57
+ # the numeric parts are integers, and 01 is not a distinct integer
58
+ # literal. The spec's "MUST NOT contain leading zeroes" needs no rule.
59
+ #
60
+ # THE PRE-RELEASE CHECK IS PARTIAL, and deliberately says so. The
61
+ # spec's grammar is dot-separated identifiers, each non-empty, each
62
+ # [0-9A-Za-z-], numeric ones without leading zeroes -- which as a
63
+ # regex is a quantified group containing a quantifier, and re()
64
+ # refuses that shape outright (constraint_pattern) because it
65
+ # backtracks exponentially. So the alphabet is checked and the
66
+ # STRUCTURE is not: "beta_1" is refused, "alpha..1" and "01" are not.
67
+ # Carrying the pre-release as a LIST of identifiers would check it in
68
+ # full, one identifier per element, and is the change to make if that
69
+ # matters more than [1 0 0 ""] does.
70
+ #
71
+ # BUILD METADATA IS NOT CARRIED. The spec has it, and the spec also
72
+ # says it MUST be ignored when determining precedence -- so a type
73
+ # whose purpose is comparison is the wrong place for it.
74
+ Semver: type(
75
+ [
76
+ integer & min(0)
77
+ *0 | (integer & min(0))
78
+ *0 | (integer & min(0))
79
+ *"" | (string & re("^[0-9A-Za-z.-]+$"))
80
+ ] & length(4)
81
+ )
52
82
  # (The Relation schema that used to sit here is retired with the
53
83
  # relations: magic key, RELATIONS.0.md P2: a relation is declared
54
84
  # by the graph atoms at its field -- rel(t) & acyclic() &
@@ -57,17 +87,17 @@ std: {
57
87
  `
58
88
 
59
89
 
60
- const STD_VIEW = `# std/view --- the FIGURE VOCABULARY (VIEWS.0.md, "6. The view
90
+ const STD_VIEW = `# aontu:view --- the FIGURE VOCABULARY (VIEWS.0.md, "6. The view
61
91
  # document"). A view document declares its figures as data, and a
62
92
  # declaration is just a map: this is the schema for one, so a typo is
63
93
  # refused where every other mistake in an aontu document is refused --
64
94
  # at evaluation, by unification -- rather than by the verb that reads
65
95
  # it afterwards.
66
96
  #
67
- # @"std/view"
97
+ # @"aontu:view"
68
98
  # @"./system.aon"
69
99
  #
70
- # views: {&: $.view.Figure} & {
100
+ # views: {&: $.aontu.View.Figure} & {
71
101
  # arch: {kind: matrix, order: partition, out: "docs/arch.dsm.txt"}
72
102
  # }
73
103
  #
@@ -81,8 +111,7 @@ const STD_VIEW = `# std/view --- the FIGURE VOCABULARY (VIEWS.0.md, "6. The view
81
111
  # canon-hash. This file carries no backtick: it is one string literal
82
112
  # per port, and Go raw strings have no escape.
83
113
 
84
- view: {
85
-
114
+ aontu: View: {
86
115
  # One declared figure. The kind says what to draw and out says where
87
116
  # it belongs; everything else narrows the drawing, and each option
88
117
  # belongs to the kinds that read it.
@@ -186,6 +215,7 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
186
215
  k: "prim"
187
216
  prim: "string" | "int" | "bigint" | "float" | "decimal" | "bool" | "null" | "any"
188
217
  })
218
+
189
219
  %ref = close({ k:"ref" name:%name unit?:string })
190
220
  %leaf = %prim | %ref | %text
191
221
 
@@ -208,6 +238,7 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
208
238
  })
209
239
 
210
240
  %member = close({ name:%name value?:string | number doc?:%doc x?:{} })
241
+
211
242
  %param = close({
212
243
  name: %name
213
244
  type: %type
@@ -221,12 +252,14 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
221
252
  %inline = string & re("^[^\n\r]*$") | %ref
222
253
  %line = close({ k:"line" at:*0 | integer & min(0) & max(64) of:[&: %inline] })
223
254
  %blank = close({ k:"blank" n:*1 | integer & min(1) & max(16) })
255
+
224
256
  %raw = close({
225
257
  k: "raw"
226
258
  at: *0 | integer & min(0) & max(64)
227
259
  text: string
228
260
  reindent: *true | boolean
229
261
  })
262
+
230
263
  %piece = %line | %blank | %raw | string & re("^[^\n\r]*$")
231
264
  %frag = close({ k:"frag" of:[&: %piece] })
232
265
  %body = %frag | close({ k:"abstract" })
@@ -241,7 +274,9 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
241
274
  check?: [&: %check]
242
275
  x?: {}
243
276
  })
277
+
244
278
  %enum = close({ k:"enum" name:%name doc?:%doc members:[&: %member] x?:{} })
279
+
245
280
  %alias = close({
246
281
  k: "alias"
247
282
  name: %name
@@ -250,6 +285,7 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
250
285
  check?: [&: %check]
251
286
  x?: {}
252
287
  })
288
+
253
289
  %const = close({
254
290
  k: "const"
255
291
  name: %name
@@ -258,6 +294,7 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
258
294
  value: string | number | boolean | null
259
295
  x?: {}
260
296
  })
297
+
261
298
  %func = close({
262
299
  k: "func"
263
300
  name: %name
@@ -290,7 +327,7 @@ const STD_CODE = String.raw`# aontu:code --- THE OUTPUT VOCABULARY. An aontu tra
290
327
 
291
328
  %source = close({ path?:string hash?:string & re("^aon1-[A-Za-z0-9_-]+$") })
292
329
 
293
- code: close({ source?:%source units:[&: %unit] })
330
+ aontu: Code: close({ source?:%source units:[&: %unit] })
294
331
  `
295
332
 
296
333
 
@@ -364,7 +401,7 @@ const STD_PROFILE = String.raw`# aontu:profile --- THE PROFILE VOCABULARY. A pro
364
401
  banner?: string
365
402
  })
366
403
 
367
- profile: %profile
404
+ aontu: Profile: %profile
368
405
  `
369
406
 
370
407
 
@@ -381,7 +418,7 @@ const STD_LANG_TEXT = String.raw`# aontu:lang/text --- THE TEXT PROFILE. The pro
381
418
 
382
419
  @"aontu:profile"
383
420
 
384
- profile: { lang:"text" indent:{ unit:" " width:2 } }
421
+ aontu: Profile: { lang:"text" indent:{ unit:" " width:2 } }
385
422
  `
386
423
 
387
424
  // THE TYPESCRIPT PROFILE (docs/design/RENDER.0.md D5, P5): the data the
@@ -402,7 +439,7 @@ const STD_LANG_TYPESCRIPT = String.raw`# aontu:lang/typescript --- THE TYPESCRIP
402
439
 
403
440
  @"aontu:profile"
404
441
 
405
- profile: {
442
+ aontu: Profile: {
406
443
  lang: "typescript"
407
444
  lowering: "typescript"
408
445
  indent: { unit:" " width:2 }
@@ -410,13 +447,15 @@ profile: {
410
447
  comment: block: { open:"/*" prefix:" * " close:" */" }
411
448
  comment: doc: { open:"/**" prefix:" * " close:" */" }
412
449
  str: quote: "\""
413
- str: escape: "8": "\\b"
414
- str: escape: "9": "\\t"
415
- str: escape: "10": "\\n"
416
- str: escape: "12": "\\f"
417
- str: escape: "13": "\\r"
418
- str: escape: "34": "\\\""
419
- str: escape: "92": "\\\\"
450
+ str: escape: {
451
+ "8": "\\b"
452
+ "9": "\\t"
453
+ "10": "\\n"
454
+ "12": "\\f"
455
+ "13": "\\r"
456
+ "34": "\\\""
457
+ "92": "\\\\"
458
+ }
420
459
  ident: {
421
460
  chars: "ascii-word"
422
461
  reserved: [
@@ -477,14 +516,16 @@ profile: {
477
516
  case: alias: "pascal"
478
517
  acronyms: []
479
518
  }
480
- types: prim: string: "string"
481
- types: prim: int: "number"
482
- types: prim: bigint: "bigint"
483
- types: prim: float: "number"
484
- types: prim: decimal: "string"
485
- types: prim: bool: "boolean"
486
- types: prim: null: "null"
487
- types: prim: any: "unknown"
519
+ types: prim: {
520
+ string: "string"
521
+ int: "number"
522
+ bigint: "bigint"
523
+ float: "number"
524
+ decimal: "string"
525
+ bool: "boolean"
526
+ null: "null"
527
+ any: "unknown"
528
+ }
488
529
  types: list: { close:"[]" childPrec:2 }
489
530
  types: map: { open:"Record<" close:">" }
490
531
  types: opt: { close:" | null" prec:1 childPrec:2 }
@@ -512,21 +553,23 @@ const STD_LANG_GO = String.raw`# aontu:lang/go --- THE GO PROFILE. The data a un
512
553
 
513
554
  @"aontu:profile"
514
555
 
515
- profile: {
556
+ aontu: Profile: {
516
557
  lang: "go"
517
558
  lowering: "go"
518
559
  indent: { unit:"\t" width:1 }
519
560
  comment: { line:prefix:"// " block:{ open:"/*" close:"*/" } doc:prefix:"// " }
520
561
  str: quote: "\""
521
- str: escape: "7": "\\a"
522
- str: escape: "8": "\\b"
523
- str: escape: "9": "\\t"
524
- str: escape: "10": "\\n"
525
- str: escape: "11": "\\v"
526
- str: escape: "12": "\\f"
527
- str: escape: "13": "\\r"
528
- str: escape: "34": "\\\""
529
- str: escape: "92": "\\\\"
562
+ str: escape: {
563
+ "7": "\\a"
564
+ "8": "\\b"
565
+ "9": "\\t"
566
+ "10": "\\n"
567
+ "11": "\\v"
568
+ "12": "\\f"
569
+ "13": "\\r"
570
+ "34": "\\\""
571
+ "92": "\\\\"
572
+ }
530
573
  ident: {
531
574
  chars: "ascii-word"
532
575
  reserved: [
@@ -605,14 +648,16 @@ profile: {
605
648
  "XSS"
606
649
  ]
607
650
  }
608
- types: prim: string: "string"
609
- types: prim: int: "int64"
610
- types: prim: bigint: "string"
611
- types: prim: float: "float64"
612
- types: prim: decimal: "string"
613
- types: prim: bool: "bool"
614
- types: prim: null: "any"
615
- types: prim: any: "any"
651
+ types: prim: {
652
+ string: "string"
653
+ int: "int64"
654
+ bigint: "string"
655
+ float: "float64"
656
+ decimal: "string"
657
+ bool: "bool"
658
+ null: "any"
659
+ any: "any"
660
+ }
616
661
  types: list: open: "[]"
617
662
  types: map: open: "map["
618
663
  types: opt: open: "*"
@@ -621,10 +666,8 @@ profile: {
621
666
  `
622
667
 
623
668
  export const STD_SOURCES: Record<string, string> = {
624
- 'std/system': STD_SYSTEM,
625
- 'std/system.aon': STD_SYSTEM,
626
- 'std/view': STD_VIEW,
627
- 'std/view.aon': STD_VIEW,
669
+ 'aontu:system': STD_SYSTEM,
670
+ 'aontu:view': STD_VIEW,
628
671
  'aontu:code': STD_CODE,
629
672
  'aontu:lang/go': STD_LANG_GO,
630
673
  'aontu:lang/text': STD_LANG_TEXT,
package/src/template.ts CHANGED
@@ -24,12 +24,22 @@
24
24
  // comment, and derives for a language this table has never seen: the
25
25
  // caller passes the token.
26
26
  //
27
- // A MARKER IS RECOGNISED AFTER LEADING WHITESPACE AND KEEPS ITS OWN
28
- // INDENTATION (D2). That is what lets a template be written exactly
29
- // where its output appears (D7): a body line is verbatim, so a line
30
- // indented two spaces in the template is indented two spaces in the
31
- // generated file, and the marker lines around it are indented to match
32
- // the code they sit in rather than to the aontu they carry.
27
+ // A MARKER IS RECOGNISED AFTER LEADING WHITESPACE, AND THE INDENTATION
28
+ // THE RESUGARING WRITES IS THE AONTU'S (D2, amended 2026-09-07). An
29
+ // output line is verbatim -- a line indented two spaces in the template
30
+ // is indented two spaces in the generated file (D7) -- and that is the
31
+ // half a reader of the TARGET needs. The other half is the document:
32
+ // the marker lines carry a tree, and a tree nobody can see the shape of
33
+ // is the one thing this surface took away. So the marker stands at the
34
+ // left margin and the aontu is indented AFTER it, which `aontu fmt`
35
+ // (FMT.0.md M-BM-'3.14) is what writes.
36
+ //
37
+ // Reading is unchanged and stays generous: a marker after leading
38
+ // whitespace is a marker, and its own indentation still counts toward
39
+ // the aontu it carries, so a template written the other way desugars to
40
+ // the same document. What changes is the spelling the round trip
41
+ // answers, and `template --check` names the difference as the drift it
42
+ // is.
33
43
  //
34
44
  // THE SUGAR IS THE FIXPOINT OF THE TWO TRANSFORMS (D6), and that is
35
45
  // the whole of the resugaring's safety. A canonical line that looks
@@ -128,13 +138,19 @@ function indentOf(line: string): number {
128
138
  }
129
139
 
130
140
 
131
- // The line without its indentation or its trailing spaces and tabs.
132
- function trimLine(line: string): string {
141
+ // The line without its trailing spaces and tabs.
142
+ function trimEnd(line: string): string {
133
143
  let end = line.length
134
144
  while (0 < end && (' ' === line[end - 1] || '\t' === line[end - 1])) {
135
145
  end--
136
146
  }
137
- return line.slice(indentOf(line), end)
147
+ return line.slice(0, end)
148
+ }
149
+
150
+
151
+ // The line without its indentation or its trailing spaces and tabs.
152
+ function trimLine(line: string): string {
153
+ return trimEnd(line).slice(indentOf(line))
138
154
  }
139
155
 
140
156
 
@@ -159,12 +175,15 @@ function readLine(line: string, marker: string): Line {
159
175
  // The block form's closer is part of the marker, not of the aontu.
160
176
  // A block marker line that never closes is not a marker line: the
161
177
  // language's own parser would not read it as a comment either.
178
+ // Only the TRAILING space goes: what stands between the opener and
179
+ // the aontu is the aontu's indentation, exactly as it is for a line
180
+ // marker, and the one-space rule below takes the marker's own.
162
181
  if (isBlock(marker)) {
163
182
  const end = body.lastIndexOf(BLOCK_CLOSE)
164
183
  if (end < 0) {
165
184
  return { marker: false, indent: '', text: line }
166
185
  }
167
- body = trimLine(body.slice(0, end))
186
+ body = trimEnd(body.slice(0, end))
168
187
  }
169
188
  // ONE SPACE AFTER THE MARKER IS THE MARKER'S, so `//- x: 1` carries
170
189
  // `x: 1` and the resugaring writes the space back. A marker written
@@ -273,19 +292,40 @@ function resugarTemplate(src: string, marker?: string): string {
273
292
  if (undefined !== target && desugarTemplate(target, mark) === trimLine(line)) {
274
293
  return target
275
294
  }
276
- const cut = indentOf(line)
277
- const text = line.slice(cut)
278
- const open = line.slice(0, cut) + mark
295
+ // THE MARKER STANDS AT THE LEFT MARGIN and the line's indentation
296
+ // is written after it, so the aontu's own shape is on the page.
297
+ // The one space is the marker's, which the reading takes back.
298
+ const text = trimEnd(line)
279
299
  const close = isBlock(mark) ? ' ' + BLOCK_CLOSE : ''
280
- return '' === text ? open + close : open + ' ' + text + close
300
+ return '' === text ? mark + close : mark + ' ' + text + close
281
301
  })
282
302
  return out.join('\n') + (tail ? '\n' : '')
283
- } /* node:coverage ignore next 8 */
303
+ }
304
+
305
+
306
+ // WHICH LINES OF THE DESUGARED DOCUMENT ARE THE TARGET'S. The
307
+ // desugaring is line for line, so this is one flag per line of what
308
+ // `desugarTemplate` returns: true where the template's line was not a
309
+ // marker, and so where the document's line is one quoted line of the
310
+ // generated file.
311
+ //
312
+ // `aontu fmt` reads it (FMT.0.md M-BM-'3.14) to hold those lines on lines of
313
+ // their own. A body element is a string like any other, and the packing
314
+ // budget would put three of them on one line -- which is aontu where
315
+ // three lines of output were, and a generator that writes them as one.
316
+ function templateOutputs(src: string, marker: string): boolean[] {
317
+ const lines = src.split('\n')
318
+ if (1 < lines.length && '' === lines[lines.length - 1]) {
319
+ lines.pop()
320
+ }
321
+ return lines.map((line) => !readLine(line, marker).marker)
322
+ } /* node:coverage ignore next 9 */
284
323
 
285
324
 
286
325
  export {
287
326
  desugarTemplate,
288
327
  resugarTemplate,
328
+ templateOutputs,
289
329
  markerFor,
290
330
  DEFAULT_MARKER,
291
331
  }
@@ -204,8 +204,8 @@ function unpref(v: any): any {
204
204
  //
205
205
  // total: sum(pick($.lines, amountCents))
206
206
  //
207
- // IT IS NOT `each` WITH A CLEVER TEMPLATE. `each(d, t)` MEETS each
208
- // child with `t`, and a meet cannot select: `each($.lines, _.amount)`
207
+ // IT IS NOT `each` WITH A CLEVER TEMPLATE. `each(d, _ & t)` MEETS each
208
+ // child with `t`, and a meet cannot select: `each($.lines, _ & _.amount)`
209
209
  // asks for a child that is simultaneously the whole record and one of
210
210
  // its fields, which is why every spelling of it answers `no_path`.
211
211
  // Selection is a different operation and gets its own verb.