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.
- package/README.md +7 -7
- package/dist/agentsmd.js +0 -27
- package/dist/agentsmd.js.map +1 -1
- package/dist/alias.js.map +1 -1
- package/dist/allow.js +0 -92
- package/dist/allow.js.map +1 -1
- package/dist/aontu.d.ts +3 -3
- package/dist/aontu.js +4 -84
- package/dist/aontu.js.map +1 -1
- package/dist/aontumodel.d.ts +4 -0
- package/dist/aontumodel.js +27 -0
- package/dist/aontumodel.js.map +1 -0
- package/dist/casing.d.ts +5 -0
- package/dist/casing.js +90 -0
- package/dist/casing.js.map +1 -0
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +228 -935
- package/dist/cli.js.map +1 -1
- package/dist/ctx.js +0 -48
- package/dist/ctx.js.map +1 -1
- package/dist/diff.js +0 -32
- package/dist/diff.js.map +1 -1
- package/dist/err.js +0 -40
- package/dist/err.js.map +1 -1
- package/dist/escape.js +0 -45
- package/dist/escape.js.map +1 -1
- package/dist/exactjson.d.ts +0 -35
- package/dist/exactjson.js +0 -131
- package/dist/exactjson.js.map +1 -1
- package/dist/format.js +13 -203
- package/dist/format.js.map +1 -1
- package/dist/grammar.d.ts +9 -0
- package/dist/grammar.js +54 -0
- package/dist/grammar.js.map +1 -0
- package/dist/graph.js +0 -26
- package/dist/graph.js.map +1 -1
- package/dist/hcanon.js +0 -82
- package/dist/hcanon.js.map +1 -1
- package/dist/helpdoc.js +2 -2
- package/dist/helpdoc.js.map +1 -1
- package/dist/hints.d.ts +0 -6
- package/dist/hints.js +57 -55
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.js +0 -114
- package/dist/jsonschema.js.map +1 -1
- package/dist/keyorder.d.ts +0 -7
- package/dist/keyorder.js +0 -41
- package/dist/keyorder.js.map +1 -1
- package/dist/lang.js +17 -915
- package/dist/lang.js.map +1 -1
- package/dist/lsp-server.js +0 -16
- package/dist/lsp-server.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/lsp.js +12 -159
- package/dist/lsp.js.map +1 -1
- package/dist/mcp-server.js +0 -26
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp.js +0 -149
- package/dist/mcp.js.map +1 -1
- package/dist/mod-tool.js +16 -131
- package/dist/mod-tool.js.map +1 -1
- package/dist/mod.js +0 -162
- package/dist/mod.js.map +1 -1
- package/dist/patch.js +0 -217
- package/dist/patch.js.map +1 -1
- package/dist/profile.d.ts +9 -0
- package/dist/profile.js +28 -0
- package/dist/profile.js.map +1 -0
- package/dist/provenance.js +0 -140
- package/dist/provenance.js.map +1 -1
- package/dist/query.js +0 -75
- package/dist/query.js.map +1 -1
- package/dist/reach.js +0 -43
- package/dist/reach.js.map +1 -1
- package/dist/relation.js +0 -61
- package/dist/relation.js.map +1 -1
- package/dist/report-sarif.d.ts +0 -11
- package/dist/report-sarif.js +0 -28
- package/dist/report-sarif.js.map +1 -1
- package/dist/sig.js +0 -35
- package/dist/sig.js.map +1 -1
- package/dist/sigdecl.js +1 -1
- package/dist/sigdecl.js.map +1 -1
- package/dist/siggate.js +0 -4
- package/dist/siggate.js.map +1 -1
- package/dist/site.js +3 -29
- package/dist/site.js.map +1 -1
- package/dist/subsume.d.ts +0 -10
- package/dist/subsume.js +0 -137
- package/dist/subsume.js.map +1 -1
- package/dist/template.d.ts +2 -1
- package/dist/template.js +58 -138
- package/dist/template.js.map +1 -1
- package/dist/trace.d.ts +21 -0
- package/dist/trace.js +107 -0
- package/dist/trace.js.map +1 -0
- package/dist/trim.js +0 -41
- package/dist/trim.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type.js.map +1 -1
- package/dist/unify.js +13 -248
- package/dist/unify.js.map +1 -1
- package/dist/utility.js +0 -22
- package/dist/utility.js.map +1 -1
- package/dist/val/AbnfFuncVal.d.ts +18 -0
- package/dist/val/AbnfFuncVal.js +132 -0
- package/dist/val/AbnfFuncVal.js.map +1 -0
- package/dist/val/AbsentVal.d.ts +11 -0
- package/dist/val/AbsentVal.js +30 -0
- package/dist/val/AbsentVal.js.map +1 -0
- package/dist/val/AggFuncVal.d.ts +10 -1
- package/dist/val/AggFuncVal.js +104 -116
- package/dist/val/AggFuncVal.js.map +1 -1
- package/dist/val/ArithFuncVal.js +0 -12
- package/dist/val/ArithFuncVal.js.map +1 -1
- package/dist/val/BagVal.js +1 -78
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/BigDecimalVal.js +0 -16
- package/dist/val/BigDecimalVal.js.map +1 -1
- package/dist/val/BigIntegerVal.js +0 -16
- package/dist/val/BigIntegerVal.js.map +1 -1
- package/dist/val/CloseFuncVal.js +0 -9
- package/dist/val/CloseFuncVal.js.map +1 -1
- package/dist/val/CmpFuncVal.d.ts +2 -0
- package/dist/val/CmpFuncVal.js +49 -72
- package/dist/val/CmpFuncVal.js.map +1 -1
- package/dist/val/ConjunctVal.js +0 -29
- package/dist/val/ConjunctVal.js.map +1 -1
- package/dist/val/ConstraintVal.js +0 -500
- package/dist/val/ConstraintVal.js.map +1 -1
- package/dist/val/ContainerKindVal.js +0 -2
- package/dist/val/ContainerKindVal.js.map +1 -1
- package/dist/val/CopyFuncVal.js +0 -3
- package/dist/val/CopyFuncVal.js.map +1 -1
- package/dist/val/Decimal.js +0 -179
- package/dist/val/Decimal.js.map +1 -1
- package/dist/val/DeprecateFuncVal.js.map +1 -1
- package/dist/val/DisjunctVal.js +0 -152
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/EachFuncVal.js +0 -3
- package/dist/val/EachFuncVal.js.map +1 -1
- package/dist/val/EmitFuncVal.d.ts +1 -1
- package/dist/val/EmitFuncVal.js +6 -119
- package/dist/val/EmitFuncVal.js.map +1 -1
- package/dist/val/ExpectVal.js +0 -62
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FilterFuncVal.js +0 -25
- package/dist/val/FilterFuncVal.js.map +1 -1
- package/dist/val/FuncBaseVal.d.ts +1 -0
- package/dist/val/FuncBaseVal.js +7 -127
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/GraphAtomVal.js +0 -15
- package/dist/val/GraphAtomVal.js.map +1 -1
- package/dist/val/HideFuncVal.js +0 -13
- package/dist/val/HideFuncVal.js.map +1 -1
- package/dist/val/IntegerVal.js +0 -61
- package/dist/val/IntegerVal.js.map +1 -1
- package/dist/val/JunctionVal.js +0 -20
- package/dist/val/JunctionVal.js.map +1 -1
- package/dist/val/KeyFuncVal.js +0 -46
- package/dist/val/KeyFuncVal.js.map +1 -1
- package/dist/val/ListVal.js +0 -57
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/LowerFuncVal.js +0 -11
- package/dist/val/LowerFuncVal.js.map +1 -1
- package/dist/val/MapVal.js +0 -151
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/MatchFuncVal.js +0 -27
- package/dist/val/MatchFuncVal.js.map +1 -1
- package/dist/val/{FormFuncVal.d.ts → MaybeFuncVal.d.ts} +5 -5
- package/dist/val/MaybeFuncVal.js +50 -0
- package/dist/val/MaybeFuncVal.js.map +1 -0
- package/dist/val/MoveFuncVal.js +0 -18
- package/dist/val/MoveFuncVal.js.map +1 -1
- package/dist/val/NilVal.js +0 -60
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/NomFuncVal.js +18 -54
- package/dist/val/NomFuncVal.js.map +1 -1
- package/dist/val/NumberVal.js +0 -15
- package/dist/val/NumberVal.js.map +1 -1
- package/dist/val/OpBaseVal.d.ts +1 -0
- package/dist/val/OpBaseVal.js +3 -15
- package/dist/val/OpBaseVal.js.map +1 -1
- package/dist/val/PackFuncVal.js +0 -34
- package/dist/val/PackFuncVal.js.map +1 -1
- package/dist/val/PathFuncVal.js +0 -6
- package/dist/val/PathFuncVal.js.map +1 -1
- package/dist/val/PathVal.js +0 -41
- package/dist/val/PathVal.js.map +1 -1
- package/dist/val/PlaceVal.js +0 -25
- package/dist/val/PlaceVal.js.map +1 -1
- package/dist/val/PlusOpVal.d.ts +1 -7
- package/dist/val/PlusOpVal.js +13 -74
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/PrefFuncVal.js +0 -1
- package/dist/val/PrefFuncVal.js.map +1 -1
- package/dist/val/PrefVal.js +0 -167
- package/dist/val/PrefVal.js.map +1 -1
- package/dist/val/RecurseVal.js +0 -55
- package/dist/val/RecurseVal.js.map +1 -1
- package/dist/val/RefVal.js +0 -282
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/ReferFuncVal.js +3 -232
- package/dist/val/ReferFuncVal.js.map +1 -1
- package/dist/val/ScalarKindVal.js +0 -49
- package/dist/val/ScalarKindVal.js.map +1 -1
- package/dist/val/ScalarVal.js +0 -11
- package/dist/val/ScalarVal.js.map +1 -1
- package/dist/val/StrFuncVal.js +0 -18
- package/dist/val/StrFuncVal.js.map +1 -1
- package/dist/val/SuperFuncVal.js +0 -32
- package/dist/val/SuperFuncVal.js.map +1 -1
- package/dist/val/TopVal.js +0 -1
- package/dist/val/TopVal.js.map +1 -1
- package/dist/val/TranslateFuncVal.js +1 -3
- package/dist/val/TranslateFuncVal.js.map +1 -1
- package/dist/val/UpperFuncVal.js +0 -11
- package/dist/val/UpperFuncVal.js.map +1 -1
- package/dist/val/Val.d.ts +1 -0
- package/dist/val/Val.js +2 -133
- package/dist/val/Val.js.map +1 -1
- package/dist/val/VarVal.js +0 -12
- package/dist/val/VarVal.js.map +1 -1
- package/dist/val/arith.js +0 -37
- package/dist/val/arith.js.map +1 -1
- package/dist/val/caserange.js +0 -61
- package/dist/val/caserange.js.map +1 -1
- package/dist/val/members.js +0 -6
- package/dist/val/members.js.map +1 -1
- package/dist/val/numcmp.js +0 -11
- package/dist/val/numcmp.js.map +1 -1
- package/dist/val/numkind.js +0 -145
- package/dist/val/numkind.js.map +1 -1
- package/dist/val/valutil.js +0 -16
- package/dist/val/valutil.js.map +1 -1
- package/dist/vet.js +0 -461
- package/dist/vet.js.map +1 -1
- package/dist/view.js +0 -414
- package/dist/view.js.map +1 -1
- package/dist/walk.js +0 -41
- package/dist/walk.js.map +1 -1
- package/grammar/aontu.abnf +8 -6
- package/grammar/aontu.gbnf +4 -4
- package/grammar/aontu.lark +4 -4
- package/grammar/aontu.tmLanguage.json +1 -1
- package/package.json +4 -2
- package/skill/tasks.md +9 -7
- package/src/agentsmd.ts +0 -35
- package/src/alias.ts +0 -39
- package/src/allow.ts +1 -96
- package/src/aontu.ts +4 -116
- package/src/aontumodel.ts +26 -0
- package/src/casing.ts +95 -0
- package/src/cli.ts +258 -1024
- package/src/ctx.ts +0 -103
- package/src/diff.ts +0 -40
- package/src/err.ts +0 -40
- package/src/escape.ts +0 -46
- package/src/exactjson.ts +0 -131
- package/src/format.ts +14 -257
- package/src/grammar.ts +72 -0
- package/src/graph.ts +0 -61
- package/src/hcanon.ts +0 -82
- package/src/helpdoc.ts +2 -2
- package/src/hints.ts +69 -57
- package/src/jsonschema.ts +0 -123
- package/src/keyorder.ts +0 -42
- package/src/lang.ts +19 -931
- package/src/lsp-server.ts +0 -16
- package/src/lsp.ts +12 -180
- package/src/mcp-server.ts +0 -31
- package/src/mcp.ts +0 -169
- package/src/mod-tool.ts +18 -159
- package/src/mod.ts +0 -178
- package/src/patch.ts +0 -232
- package/src/profile.ts +42 -0
- package/src/provenance.ts +0 -183
- package/src/query.ts +0 -84
- package/src/reach.ts +0 -53
- package/src/relation.ts +0 -84
- package/src/report-sarif.ts +0 -48
- package/src/sig.ts +0 -35
- package/src/sigdecl.ts +1 -1
- package/src/siggate.ts +0 -30
- package/src/site.ts +3 -29
- package/src/subsume.ts +1 -161
- package/src/template.ts +69 -140
- package/src/trace.ts +157 -0
- package/src/trim.ts +0 -53
- package/src/type.ts +2 -45
- package/src/unify.ts +14 -257
- package/src/utility.ts +0 -31
- package/src/val/AbnfFuncVal.ts +181 -0
- package/src/val/AbsentVal.ts +54 -0
- package/src/val/AggFuncVal.ts +152 -188
- package/src/val/ArithFuncVal.ts +0 -20
- package/src/val/BagVal.ts +1 -78
- package/src/val/BigDecimalVal.ts +0 -16
- package/src/val/BigIntegerVal.ts +0 -16
- package/src/val/CloseFuncVal.ts +0 -9
- package/src/val/CmpFuncVal.ts +68 -184
- package/src/val/ConjunctVal.ts +0 -33
- package/src/val/ConstraintVal.ts +2 -537
- package/src/val/ContainerKindVal.ts +0 -18
- package/src/val/CopyFuncVal.ts +0 -5
- package/src/val/Decimal.ts +1 -185
- package/src/val/DeprecateFuncVal.ts +0 -10
- package/src/val/DisjunctVal.ts +0 -157
- package/src/val/EachFuncVal.ts +0 -40
- package/src/val/EmitFuncVal.ts +8 -208
- package/src/val/ExpectVal.ts +0 -62
- package/src/val/FilterFuncVal.ts +0 -55
- package/src/val/FuncBaseVal.ts +9 -130
- package/src/val/GraphAtomVal.ts +0 -42
- package/src/val/HideFuncVal.ts +0 -15
- package/src/val/IntegerVal.ts +0 -61
- package/src/val/JunctionVal.ts +0 -20
- package/src/val/KeyFuncVal.ts +0 -48
- package/src/val/ListVal.ts +0 -59
- package/src/val/LowerFuncVal.ts +0 -12
- package/src/val/MapVal.ts +0 -151
- package/src/val/MatchFuncVal.ts +0 -59
- package/src/val/MaybeFuncVal.ts +86 -0
- package/src/val/MoveFuncVal.ts +0 -20
- package/src/val/NilVal.ts +0 -60
- package/src/val/NomFuncVal.ts +10 -99
- package/src/val/NumberVal.ts +0 -16
- package/src/val/OpBaseVal.ts +4 -17
- package/src/val/PackFuncVal.ts +0 -63
- package/src/val/PathFuncVal.ts +0 -32
- package/src/val/PathVal.ts +0 -66
- package/src/val/PlaceVal.ts +0 -45
- package/src/val/PlusOpVal.ts +18 -75
- package/src/val/PrefFuncVal.ts +0 -1
- package/src/val/PrefVal.ts +0 -179
- package/src/val/RecurseVal.ts +0 -81
- package/src/val/RefVal.ts +1 -285
- package/src/val/ReferFuncVal.ts +4 -255
- package/src/val/ScalarKindVal.ts +0 -50
- package/src/val/ScalarVal.ts +0 -12
- package/src/val/StrFuncVal.ts +0 -44
- package/src/val/SuperFuncVal.ts +0 -42
- package/src/val/TopVal.ts +0 -1
- package/src/val/TranslateFuncVal.ts +1 -51
- package/src/val/UpperFuncVal.ts +0 -12
- package/src/val/Val.ts +3 -192
- package/src/val/VarVal.ts +0 -15
- package/src/val/arith.ts +0 -92
- package/src/val/caserange.ts +0 -62
- package/src/val/members.ts +0 -23
- package/src/val/numcmp.ts +1 -27
- package/src/val/numkind.ts +0 -149
- package/src/val/valutil.ts +0 -16
- package/src/vet.ts +1 -582
- package/src/view.ts +0 -507
- package/src/walk.ts +0 -41
- package/dist/lower.d.ts +0 -23
- package/dist/lower.js +0 -578
- package/dist/lower.js.map +0 -1
- package/dist/render.d.ts +0 -53
- package/dist/render.js +0 -547
- package/dist/render.js.map +0 -1
- package/dist/std.d.ts +0 -3
- package/dist/std.js +0 -672
- package/dist/std.js.map +0 -1
- package/dist/val/FormFuncVal.js +0 -55
- package/dist/val/FormFuncVal.js.map +0 -1
- package/dist/val/NamerFuncVal.d.ts +0 -12
- package/dist/val/NamerFuncVal.js +0 -176
- package/dist/val/NamerFuncVal.js.map +0 -1
- package/src/lower.ts +0 -636
- package/src/render.ts +0 -732
- package/src/std.ts +0 -683
package/src/cli.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
2
|
|
|
3
|
-
// Command-line interface for Aontu.
|
|
4
|
-
//
|
|
5
|
-
// aontu [options] [file]
|
|
6
|
-
//
|
|
7
|
-
// With a file argument, the file is evaluated and the result printed.
|
|
8
|
-
// With no file on an interactive terminal, a REPL is started. With no
|
|
9
|
-
// file and piped input, the source is read from stdin. See HELP below.
|
|
10
3
|
|
|
11
4
|
// Named imports, not `import * as`: the namespace form makes tsc emit the
|
|
12
5
|
import { evalFailure } from './query'
|
|
@@ -24,13 +17,14 @@ import {
|
|
|
24
17
|
hcanon, canonHash,
|
|
25
18
|
get, why, patch, agentsMd,
|
|
26
19
|
allow,
|
|
27
|
-
|
|
28
|
-
renderProfile,
|
|
20
|
+
loadProfile,
|
|
29
21
|
} from './aontu'
|
|
30
22
|
import type { AllowDecision, AllowReport, AllowVerdict } from './allow'
|
|
31
|
-
import
|
|
32
|
-
import {
|
|
33
|
-
|
|
23
|
+
import { traceRun } from './trace'
|
|
24
|
+
import {
|
|
25
|
+
desugarTemplate, resugarTemplate, templateOutputs, markerFor,
|
|
26
|
+
markerFromProfiles,
|
|
27
|
+
} from './template'
|
|
34
28
|
import { sarifReport } from './report-sarif'
|
|
35
29
|
import { main as lspMain } from './lsp-server'
|
|
36
30
|
import { main as mcpMain } from './mcp-server'
|
|
@@ -87,10 +81,10 @@ const HELP = `Usage: aontu [options] [file]
|
|
|
87
81
|
aontu view <kind> [options] <file>...
|
|
88
82
|
aontu view --views <path> [--check] [options] <file>
|
|
89
83
|
aontu jsonschema [--at <path>] [--strict] [options] <file>
|
|
90
|
-
aontu
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
84
|
+
aontu template [--resugar] [--check] [--marker <token>]
|
|
85
|
+
[--profile <file>] <file>
|
|
86
|
+
aontu trace [--at <path>] [--format json] [--marker <token>]
|
|
87
|
+
[--profile <file>] <file>
|
|
94
88
|
aontu hash [options] <file>
|
|
95
89
|
aontu mod tidy|verify|vendor|manifest [options] [dir]
|
|
96
90
|
aontu get <path> [options] <file>
|
|
@@ -98,7 +92,8 @@ const HELP = `Usage: aontu [options] [file]
|
|
|
98
92
|
aontu set <path>=<value>... --entry <file> --overlay <file>
|
|
99
93
|
aontu allow --role <role> [--at <path>] <roles-file> <path>...
|
|
100
94
|
aontu agentsmd [--write <AGENTS.md>] [--depth <n>] <file>
|
|
101
|
-
aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>]
|
|
95
|
+
aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>]
|
|
96
|
+
[--profile <file>] <file>...
|
|
102
97
|
aontu help [topic] [--format text|json]
|
|
103
98
|
aontu explain <code> | --list [--format text|json]
|
|
104
99
|
aontu init [dir]
|
|
@@ -335,36 +330,11 @@ View exit codes: 0 rendered, 1 --check mismatch or lossy under
|
|
|
335
330
|
--strict, 2 usage or --max-rows exceeded, 4 the document does not stand
|
|
336
331
|
up on its own, or a relation, root or path that names nothing.
|
|
337
332
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
--unit <path> Render only the unit with this path
|
|
344
|
-
--stdout One unit's bytes and nothing else (with --unit when
|
|
345
|
-
the instance has several)
|
|
346
|
-
--out <dir> Write every unit below dir, or nothing; never deletes
|
|
347
|
-
--check <dir> Compare every unit with dir/<path>; drift is listed
|
|
348
|
-
--coverage Report what the render read and what it did not:
|
|
349
|
-
model paths no output consumed, and rendered
|
|
350
|
-
declarations no rule produced. Writes nothing
|
|
351
|
-
--coverage-at <p> Measure coverage under this path only, instead of
|
|
352
|
-
the document root
|
|
353
|
-
--strict Refuse the opaque escapes (a text declaration, a raw
|
|
354
|
-
block)
|
|
355
|
-
--format <f> text (default) or json, the whole report; json
|
|
356
|
-
carries the dispatch trace, one entry per emitted
|
|
357
|
-
piece
|
|
358
|
-
|
|
359
|
-
Render exit codes: 0 rendered, 1 lossy under --strict or drift under
|
|
360
|
-
--check, 2 usage or I/O (a refused unit path included), 4 the document
|
|
361
|
-
does not stand up or the instance is not aontu:code.
|
|
362
|
-
|
|
363
|
-
A render entry file whose extension is not .aon is a TEMPLATE: a
|
|
364
|
-
generator in the target's own syntax, whose marker lines carry aontu
|
|
365
|
-
and whose other lines are output. It is desugared before it is
|
|
366
|
-
evaluated, and --marker names the marker for a language the table does
|
|
367
|
-
not know.
|
|
333
|
+
A template entry file whose extension is not .aon is a GENERATOR: a
|
|
334
|
+
document in the target's own syntax, whose marker lines carry aontu and
|
|
335
|
+
whose other lines are output. It is desugared before it is evaluated,
|
|
336
|
+
and a language the table does not know names its marker with --marker,
|
|
337
|
+
or declares it once in a profile file that --profile reads.
|
|
368
338
|
|
|
369
339
|
Template options:
|
|
370
340
|
--resugar The file is the canonical aontu; print the template
|
|
@@ -372,7 +342,9 @@ Template options:
|
|
|
372
342
|
--check Desugar and resugar, and exit 1 if the file is not
|
|
373
343
|
what the round trip answers
|
|
374
344
|
--marker <t> The marker, when the extension does not name it
|
|
375
|
-
(default //-, and #- --- /*- by extension)
|
|
345
|
+
(default //-, and #- --- /*- <!--- by extension)
|
|
346
|
+
--profile <f> A profile file, whose template.ext names the
|
|
347
|
+
extensions it marks and template.marker the marker
|
|
376
348
|
|
|
377
349
|
The template verb prints the canonical aontu form of a generator
|
|
378
350
|
written in the target's own syntax: a marked line is aontu source, and
|
|
@@ -465,16 +437,18 @@ Fmt options:
|
|
|
465
437
|
shapes, on standard error, and print nothing else
|
|
466
438
|
--strict With --lint, and exit 1 when there is a finding
|
|
467
439
|
--marker <t> The file is a generator, and this is its marker
|
|
468
|
-
(default //-, and #- --- /*- by extension)
|
|
440
|
+
(default //-, and #- --- /*- <!--- by extension)
|
|
441
|
+
--profile <f> A profile file, whose template.ext names the
|
|
442
|
+
extensions it marks and template.marker the marker
|
|
469
443
|
|
|
470
444
|
The fmt verb prints one document in the agreed form; with no file it
|
|
471
445
|
reads standard input. Several files need one of the options above.
|
|
472
446
|
|
|
473
|
-
A file whose extension is not .aon is a GENERATOR, as it is for
|
|
474
|
-
the aontu its marker lines carry is formatted, the marker
|
|
475
|
-
left margin with the aontu indented after it, and every
|
|
476
|
-
is held on a line of its own. A file with no marker line
|
|
477
|
-
another language's, and is refused.
|
|
447
|
+
A file whose extension is not .aon is a GENERATOR, as it is for
|
|
448
|
+
template: the aontu its marker lines carry is formatted, the marker
|
|
449
|
+
stands at the left margin with the aontu indented after it, and every
|
|
450
|
+
line of output is held on a line of its own. A file with no marker line
|
|
451
|
+
in it is another language's, and is refused.
|
|
478
452
|
|
|
479
453
|
Fmt exit codes: 0 formatted or clean, 1 a --check file would change or
|
|
480
454
|
a --strict finding, 2 usage, 4 a document does not parse.
|
|
@@ -512,32 +486,9 @@ function version(): string {
|
|
|
512
486
|
}
|
|
513
487
|
|
|
514
488
|
|
|
515
|
-
// The terminal colour escapes the parser puts in its message text. A
|
|
516
|
-
// machine-readable report is no place for them, which is the rule
|
|
517
|
-
// findingOf states in ts/src/vet.ts; the twin here rather than an
|
|
518
|
-
// import because go/cmd/aontu carries its own for the same reason (the
|
|
519
|
-
// engine's is not exported to its command).
|
|
520
489
|
const EVAL_ANSI = new RegExp('\u001b\\[[0-9;]*m', 'g')
|
|
521
490
|
|
|
522
491
|
|
|
523
|
-
// THE ENGINE'S DIAGNOSIS AS A FINDING (G11 phase 7). The bare command
|
|
524
|
-
// was the one verb whose failure had no machine-readable form, so the
|
|
525
|
-
// default entry point was the one an agent had to parse with a regular
|
|
526
|
-
// expression.
|
|
527
|
-
//
|
|
528
|
-
// THE HEADLINE ONLY, and no `hint`. Both are parity decisions rather
|
|
529
|
-
// than economies: the frames under the headline are drawn for a person
|
|
530
|
-
// reading a terminal and only the first line is held to byte parity
|
|
531
|
-
// between the ports (the rule findingOf states), and the hint TABLES
|
|
532
|
-
// are deliberately not in parity while the code registry is -- so a
|
|
533
|
-
// hint here would make the two ports answer differently for a code
|
|
534
|
-
// only one of them explains. `aontu explain <code>` is where the hint
|
|
535
|
-
// lives, which is what phase 3 built it for.
|
|
536
|
-
//
|
|
537
|
-
// The CLASS comes from the registry rather than from the nil, because
|
|
538
|
-
// the registry is what both ports hold set-equal
|
|
539
|
-
// (test/spec/errcodes.tsv). Mirrors evalFinding in
|
|
540
|
-
// go/cmd/aontu/main.go.
|
|
541
492
|
function evalFinding(code: string, text: string): VetFinding {
|
|
542
493
|
return {
|
|
543
494
|
class: codeClass(code),
|
|
@@ -545,10 +496,6 @@ function evalFinding(code: string, text: string): VetFinding {
|
|
|
545
496
|
message: text.split('\n')[0].replace(EVAL_ANSI, ''),
|
|
546
497
|
path: '$',
|
|
547
498
|
severity: 'error',
|
|
548
|
-
// NO SITE. The bare command's failure is the whole document not
|
|
549
|
-
// standing up, and the two sites a conflict names are in the
|
|
550
|
-
// frames the text form prints; naming one of them here would be a
|
|
551
|
-
// choice the engine has not made.
|
|
552
499
|
sites: [],
|
|
553
500
|
}
|
|
554
501
|
}
|
|
@@ -562,12 +509,6 @@ function evalSource(
|
|
|
562
509
|
mode: Mode,
|
|
563
510
|
): { ok: boolean; text: string; findings: VetFinding[] } {
|
|
564
511
|
try {
|
|
565
|
-
// exactJSON, not JSON.stringify: a document using the `0d` exact
|
|
566
|
-
// leaves generates bigints and Decimals, which JSON.stringify cannot
|
|
567
|
-
// write (D9). The CLI prints INDENTED JSON and the shared suite's
|
|
568
|
-
// `gens` mode prints COMPACT JSON, but both go through this one
|
|
569
|
-
// emitter -- an indent argument rather than a second implementation,
|
|
570
|
-
// so the two cannot drift from each other or from the Go port.
|
|
571
512
|
const text = 'canon' === mode
|
|
572
513
|
? aontu.unify(src).canon
|
|
573
514
|
: exactJSON(aontu.generate(src), 2)
|
|
@@ -577,13 +518,6 @@ function evalSource(
|
|
|
577
518
|
const msg = (err instanceof AontuError || true === err?.aontu)
|
|
578
519
|
? err.message
|
|
579
520
|
: String(err?.message ?? err)
|
|
580
|
-
// WHAT THE ENGINE COLLECTED, when it collected anything: an
|
|
581
|
-
// AontuError carries the NilVals the run failed on, already
|
|
582
|
-
// materialised (handleErrors in ts/src/aontu.ts), and their first
|
|
583
|
-
// is the diagnosis every other verb reports. An error raised
|
|
584
|
-
// outside the engine's own collection -- exactJSON's circular
|
|
585
|
-
// refusal, a foreign object claiming to be one -- carries none,
|
|
586
|
-
// and answers with the text alone rather than an invented code.
|
|
587
521
|
const errs: any[] = 'function' === typeof err?.errs ? err.errs() : []
|
|
588
522
|
const first: any = errs[0]
|
|
589
523
|
return {
|
|
@@ -619,23 +553,11 @@ function emitEval(
|
|
|
619
553
|
}
|
|
620
554
|
|
|
621
555
|
|
|
622
|
-
// The include capability the main verb runs with (G5, docs/trust.md).
|
|
623
|
-
// `--trust` and `--include-root` set it explicitly; the default is
|
|
624
|
-
// 'system' WITH the warning window: every resolution that escapes the
|
|
625
|
-
// entry root or goes through package resolution prints a one-line
|
|
626
|
-
// stderr warning naming the flag a future default will require
|
|
627
|
-
// (phase 6, the staged flip).
|
|
628
556
|
type TrustArg = (
|
|
629
557
|
| { kind: 'system-warn' }
|
|
630
558
|
| { kind: 'system' }
|
|
631
559
|
| { kind: 'none' }
|
|
632
560
|
| { kind: 'root', dir?: string }
|
|
633
|
-
// EXTENSIONS READ AS TEXT ride with the capability rather than
|
|
634
|
-
// beside it: both answer "what may an include read", both are
|
|
635
|
-
// stripped by takeTrust before a verb parses its own tail, and a
|
|
636
|
-
// verb that threads one and not the other is the G5 defect again --
|
|
637
|
-
// `aontu vet` running under a flag the bare command honoured and it
|
|
638
|
-
// did not.
|
|
639
561
|
) & { textExt: string[] }
|
|
640
562
|
|
|
641
563
|
|
|
@@ -679,16 +601,6 @@ function trustOpts(trust: TrustArg, entryRoot: string): any {
|
|
|
679
601
|
}
|
|
680
602
|
|
|
681
603
|
|
|
682
|
-
// EVERY VERB honours the include capability, not just the bare
|
|
683
|
-
// command. G5 wired `--trust`/`--include-root` to `aontu <file>` alone,
|
|
684
|
-
// so `aontu vet schema.aon data.json` -- the surface an agent actually
|
|
685
|
-
// scripts -- ran the full system resolver with no flag to confine it
|
|
686
|
-
// and no warning (use-cases/REVIEW.md finding G). The flags are
|
|
687
|
-
// stripped here, before each verb parses its own tail, so a verb only
|
|
688
|
-
// has to pass the profile on to its engine.
|
|
689
|
-
//
|
|
690
|
-
// Returns undefined when the spelling is wrong, with the message
|
|
691
|
-
// already printed: the caller answers the usage class.
|
|
692
604
|
function takeTrust(argv: string[]):
|
|
693
605
|
{ argv: string[], trust: TrustArg } | undefined {
|
|
694
606
|
const rest: string[] = []
|
|
@@ -799,17 +711,6 @@ function runFile(
|
|
|
799
711
|
src = readFileSync(file, 'utf8')
|
|
800
712
|
}
|
|
801
713
|
catch (err: any) {
|
|
802
|
-
// A MISTYPED VERB READS AS A FILE NAME, and until G11 phase 2 that
|
|
803
|
-
// was only said when there were TWO of them. The one-argument case
|
|
804
|
-
// is the one an agent actually produces -- `aontu help`, `aontu
|
|
805
|
-
// init`, `aontu ontology` -- and it answered `cannot read help:
|
|
806
|
-
// ...`, which describes the symptom and hides the cause.
|
|
807
|
-
//
|
|
808
|
-
// The test is SHAPE, not existence: a bare word (no separator, no
|
|
809
|
-
// extension) that cannot be read was meant as a verb, while
|
|
810
|
-
// `./help`, `help.aon` and `/tmp/help` were meant as paths and keep
|
|
811
|
-
// the file diagnosis and its exit 1. That is the same escape hatch
|
|
812
|
-
// the subcommand dispatch documents. Mirrors go/cmd/aontu/main.go.
|
|
813
714
|
if (looksLikeVerb(file)) {
|
|
814
715
|
process.stderr.write(
|
|
815
716
|
`aontu: \`${file}\` is not a file, and not a verb this port knows\n`)
|
|
@@ -826,12 +727,6 @@ function runFile(
|
|
|
826
727
|
}
|
|
827
728
|
|
|
828
729
|
const path = resolve(file)
|
|
829
|
-
// `fs` IS WHAT MAKES A FRAME EXCERPT THE FILE IT NAMES. Without it,
|
|
830
|
-
// err.ts's resolveSrc falls back to the ENTRY text, so a frame whose
|
|
831
|
-
// arrow says `lib/types.aon:2:6` printed the entry's line 2 under it
|
|
832
|
-
// -- a real file name over another file's line, which
|
|
833
|
-
// docs/reference-api.md forbids in the same words it uses to require
|
|
834
|
-
// the name.
|
|
835
730
|
const aontu = new Aontu({
|
|
836
731
|
path,
|
|
837
732
|
errfs: { existsSync, readFileSync },
|
|
@@ -856,15 +751,6 @@ function runStdin(
|
|
|
856
751
|
}
|
|
857
752
|
|
|
858
753
|
|
|
859
|
-
// THE REPL AS AN INSPECTION TOOL (G7 phase 7): `:load` holds a
|
|
860
|
-
// document, and `:get`, `:keys` and `:why` ask the query and
|
|
861
|
-
// provenance surfaces about it, so the session is a place to
|
|
862
|
-
// INTERROGATE a definition rather than only to evaluate snippets.
|
|
863
|
-
//
|
|
864
|
-
// The command handler is a PURE FUNCTION of (state, line): a readline
|
|
865
|
-
// loop is untestable, and every answer this REPL gives has to be as
|
|
866
|
-
// checkable as the CLI's. File reading is injected for the same
|
|
867
|
-
// reason.
|
|
868
754
|
export type ReplState = {
|
|
869
755
|
// How a value renders: the `:canon` / `:json` toggle.
|
|
870
756
|
mode: Mode
|
|
@@ -873,11 +759,6 @@ export type ReplState = {
|
|
|
873
759
|
jsonl: boolean
|
|
874
760
|
name?: string
|
|
875
761
|
src?: string
|
|
876
|
-
// The include capability the session evaluates under. `--trust` and
|
|
877
|
-
// `--include-root` were parsed and then DROPPED on the way to the
|
|
878
|
-
// REPL, so `--jsonl` -- the surface built to be driven by a harness
|
|
879
|
-
// -- ran unconfined however it was invoked (use-cases/REVIEW.md
|
|
880
|
-
// finding G). The state carries it, so every line honours it.
|
|
881
762
|
trust?: TrustArg
|
|
882
763
|
}
|
|
883
764
|
|
|
@@ -1032,14 +913,6 @@ function runRepl(initialMode: Mode, jsonl: boolean, trust: TrustArg): void {
|
|
|
1032
913
|
})
|
|
1033
914
|
|
|
1034
915
|
rl.on('close', () => {
|
|
1035
|
-
// The closing newline is for a HUMAN, so it is written only for
|
|
1036
|
-
// one: it moves the terminal off the prompt line that `rl` left
|
|
1037
|
-
// hanging. In `--jsonl` there is no prompt, every answer already
|
|
1038
|
-
// ends in its own newline, and this one appended a bare empty line
|
|
1039
|
-
// to the stream -- a record that is not JSON, at the end of a
|
|
1040
|
-
// protocol whose whole contract is one JSON object per line. A
|
|
1041
|
-
// harness parsing every line it receives failed on it, after the
|
|
1042
|
-
// commands had all succeeded. Mirrors go/cmd/aontu/repl.go.
|
|
1043
916
|
if (!jsonl) {
|
|
1044
917
|
process.stdout.write('\n')
|
|
1045
918
|
}
|
|
@@ -1051,16 +924,6 @@ function runRepl(initialMode: Mode, jsonl: boolean, trust: TrustArg): void {
|
|
|
1051
924
|
}
|
|
1052
925
|
|
|
1053
926
|
|
|
1054
|
-
|
|
1055
|
-
// THE VET VERB (G2 phase 3).
|
|
1056
|
-
//
|
|
1057
|
-
// Exit codes are VERDICT CLASSES, not a pass/fail bit: an agent loop
|
|
1058
|
-
// branches on "the data contradicts the truth" (1) differently from
|
|
1059
|
-
// "the data has not supplied everything the truth requires" (3), and
|
|
1060
|
-
// differently again from "the schema itself is broken" (4), which is
|
|
1061
|
-
// never the data's fault. 2 stays what it already was for this CLI --
|
|
1062
|
-
// the caller got the invocation wrong -- which is why an unreadable
|
|
1063
|
-
// file is a 2 rather than a 4.
|
|
1064
927
|
const VET_EXIT: Record<VetVerdict, number> = {
|
|
1065
928
|
valid: 0,
|
|
1066
929
|
invalid: 1,
|
|
@@ -1130,14 +993,6 @@ function parseVetArgs(argv: string[]): { args?: VetArgs; err?: string } {
|
|
|
1130
993
|
format = f
|
|
1131
994
|
}
|
|
1132
995
|
else if ('--max-errors' === arg) {
|
|
1133
|
-
// ONE GRAMMAR, spelled the same way in both ports: decimal
|
|
1134
|
-
// digits, one to nine of them, at least 1. `Number()` alone
|
|
1135
|
-
// accepted `1.0`, `1e2`, `0x10` and ` 3`, which Go's parser
|
|
1136
|
-
// refuses -- so the same documented invocation meant different
|
|
1137
|
-
// things in the two shipped commands. The nine-digit ceiling is
|
|
1138
|
-
// where the ports would part company again: beyond it Go's
|
|
1139
|
-
// integer conversion saturates, and a cap nobody can reach is
|
|
1140
|
-
// not worth a divergence.
|
|
1141
996
|
const raw = argv[++i]
|
|
1142
997
|
if (!/^[0-9]{1,9}$/.test(raw ?? '') || 1 > Number(raw)) {
|
|
1143
998
|
return { err: 'aontu: --max-errors needs a positive whole number' }
|
|
@@ -1220,10 +1075,6 @@ function renderFinding(f: VetFinding): string {
|
|
|
1220
1075
|
out.push(` actual: ${f.actual}`)
|
|
1221
1076
|
}
|
|
1222
1077
|
for (const s of f.sites) {
|
|
1223
|
-
// Every site carries the canon of the value it stands for: that is
|
|
1224
|
-
// what makes the two sides of a conflict readable side by side. A
|
|
1225
|
-
// site's file is always a string -- empty when the value belongs to
|
|
1226
|
-
// neither document -- so there is nothing to coalesce here.
|
|
1227
1078
|
out.push(` ${s.role}: ${s.file}:${s.row}:${s.col} (${s.value})`)
|
|
1228
1079
|
}
|
|
1229
1080
|
|
|
@@ -1333,18 +1184,9 @@ function vetOnce(args: VetArgs, trust: TrustArg): number {
|
|
|
1333
1184
|
return 2
|
|
1334
1185
|
}
|
|
1335
1186
|
|
|
1336
|
-
// Each data file is vetted on its own, because a parsed tree is
|
|
1337
|
-
// single-use (docs/reference-api.md) -- and because two data files
|
|
1338
|
-
// are two candidates for the same truth, not one merged candidate.
|
|
1339
1187
|
let verdict: VetVerdict = 'valid'
|
|
1340
1188
|
let truncated = false
|
|
1341
1189
|
const findings: VetFinding[] = []
|
|
1342
|
-
// COVERAGE ACROSS SEVERAL DATA FILES (G11 phase 5). Two data files
|
|
1343
|
-
// are two candidates for one truth, so the schema side is the SAME
|
|
1344
|
-
// for each: `declared` is taken once, and a declaration is unused
|
|
1345
|
-
// only when NO file met it -- the intersection, because a
|
|
1346
|
-
// declaration one file exercised is exercised. The data side adds
|
|
1347
|
-
// up: leaves and checked leaves sum, and `unchecked` is the union.
|
|
1348
1190
|
let cov: VetCoverage | undefined
|
|
1349
1191
|
// Initialised rather than left undefined: it is filled in the same
|
|
1350
1192
|
// block that sets `cov`, so a fallback at the read below would be an
|
|
@@ -1363,12 +1205,6 @@ function vetOnce(args: VetArgs, trust: TrustArg): number {
|
|
|
1363
1205
|
maxErrors: args.maxErrors,
|
|
1364
1206
|
schemaUrl: args.schema,
|
|
1365
1207
|
dataUrl: source.file,
|
|
1366
|
-
// The paths as well as the labels: a relative `@"file"` load
|
|
1367
|
-
// inside either document resolves from ITS OWN directory, the
|
|
1368
|
-
// way `aontu <file>` already resolves one (runFile above). The
|
|
1369
|
-
// path is passed AS TYPED, not resolved: it doubles as the
|
|
1370
|
-
// label above, and a report that mixed the typed path with an
|
|
1371
|
-
// absolute one would name the same file two ways.
|
|
1372
1208
|
schemaPath: args.schema,
|
|
1373
1209
|
dataPath: source.file,
|
|
1374
1210
|
coverage: args.coverage,
|
|
@@ -1400,27 +1236,11 @@ function vetOnce(args: VetArgs, trust: TrustArg): number {
|
|
|
1400
1236
|
unusedSeen = true
|
|
1401
1237
|
}
|
|
1402
1238
|
|
|
1403
|
-
// A SCHEMA-SIDE FAULT IS THE SAME FAULT FOR EVERY DATA FILE, so it
|
|
1404
|
-
// is reported ONCE. `error` means exactly that -- the run could not
|
|
1405
|
-
// be set up from the truth's side, never the data's (the exit table
|
|
1406
|
-
// in docs/reference-api.md) -- so the report the first file
|
|
1407
|
-
// produced is the report every later file would produce, character
|
|
1408
|
-
// for character. Concatenating them repeated one broken schema N
|
|
1409
|
-
// times and, past the cap, marked the report `truncated` over a
|
|
1410
|
-
// single underlying fault. It only became visible once the `error`
|
|
1411
|
-
// verdict started carrying findings at all: while the list was
|
|
1412
|
-
// empty there was nothing to duplicate.
|
|
1413
1239
|
if ('error' === report.verdict) {
|
|
1414
1240
|
break
|
|
1415
1241
|
}
|
|
1416
1242
|
}
|
|
1417
1243
|
|
|
1418
|
-
// The cap is on the REPORT, not on each file. Capping every file's
|
|
1419
|
-
// list and then concatenating them let `--max-errors 1` emit one
|
|
1420
|
-
// finding PER FILE -- and leave `truncated` false while doing it,
|
|
1421
|
-
// because no single file had been cut. The engine still caps each
|
|
1422
|
-
// run, so a pathological file cannot flood the aggregate before it
|
|
1423
|
-
// gets here; this is the second, honest cut.
|
|
1424
1244
|
const cap = args.maxErrors ?? VET_MAX_ERRORS
|
|
1425
1245
|
const kept = cap < findings.length ? findings.slice(0, cap) : findings
|
|
1426
1246
|
|
|
@@ -1442,12 +1262,6 @@ function vetOnce(args: VetArgs, trust: TrustArg): number {
|
|
|
1442
1262
|
|
|
1443
1263
|
process.stdout.write(text + '\n')
|
|
1444
1264
|
|
|
1445
|
-
// A VACUOUS CHECK IS A FAILED GATE UNDER `--strict-coverage`, and
|
|
1446
|
-
// only under it: the verdict WORD is unchanged, so nothing that
|
|
1447
|
-
// passes today starts failing, and a caller who wants the stronger
|
|
1448
|
-
// gate asks for it. The reason goes to stderr, because stdout is a
|
|
1449
|
-
// report contract -- a JSON consumer reads `coverage.vacuous` and a
|
|
1450
|
-
// person reads this.
|
|
1451
1265
|
if (true === args.strictCoverage && true === report.coverage?.vacuous) {
|
|
1452
1266
|
process.stderr.write(
|
|
1453
1267
|
'aontu: no data leaf was constrained by the schema:' +
|
|
@@ -1483,19 +1297,6 @@ function sleep(ms: number): Promise<void> {
|
|
|
1483
1297
|
}
|
|
1484
1298
|
|
|
1485
1299
|
|
|
1486
|
-
// Resolve true when any watched file's signature moves off `before`.
|
|
1487
|
-
// This is the real waiter: it never resolves false, so a real watch
|
|
1488
|
-
// runs until the process is interrupted; tests inject their own waiter
|
|
1489
|
-
// to bound the loop, and pass a short pollMs when they drive this one
|
|
1490
|
-
// directly. The interval is a required argument (the command passes
|
|
1491
|
-
// WATCH_POLL_MS) so there is no defaulting branch a test could never
|
|
1492
|
-
// take.
|
|
1493
|
-
//
|
|
1494
|
-
// The BASELINE is an argument, not a snapshot taken here: the loop
|
|
1495
|
-
// records it BEFORE each vet run, so a save landing between the run's
|
|
1496
|
-
// reads and the wait still compares as a change. A waiter that
|
|
1497
|
-
// snapshotted on entry would adopt that unvetted save as its baseline
|
|
1498
|
-
// and wait indefinitely on a stale report.
|
|
1499
1300
|
async function watchChange(
|
|
1500
1301
|
files: string[], before: string, pollMs: number): Promise<boolean> {
|
|
1501
1302
|
for (;;) {
|
|
@@ -1535,9 +1336,6 @@ async function watchVet(
|
|
|
1535
1336
|
}
|
|
1536
1337
|
|
|
1537
1338
|
|
|
1538
|
-
// The vet verb. Non-watch runs are synchronous and return the exit
|
|
1539
|
-
// class directly; `--watch` returns a promise that resolves only when
|
|
1540
|
-
// the waiter says stop (never, for the real one).
|
|
1541
1339
|
function runVet(argv: string[], wait?: VetWaiter): number | Promise<number> {
|
|
1542
1340
|
const trusted = takeTrust(argv)
|
|
1543
1341
|
if (null == trusted) {
|
|
@@ -1715,14 +1513,6 @@ type BreakingArgs = {
|
|
|
1715
1513
|
file: string
|
|
1716
1514
|
against: string[]
|
|
1717
1515
|
mode?: BreakingMode
|
|
1718
|
-
// `--at`: compare a SUBTREE of both versions. The gate's own
|
|
1719
|
-
// sub-question, and the one a real repository needs -- a document's
|
|
1720
|
-
// top level carries the module's version string and its policy
|
|
1721
|
-
// block, which are supposed to change between releases and which
|
|
1722
|
-
// make the whole-document comparison answer about them rather than
|
|
1723
|
-
// about the contract (use-cases/REVIEW.md finding D). `subsume` has
|
|
1724
|
-
// taken it since G3; `breaking` did not, so the only way to gate a
|
|
1725
|
-
// subtree was to split the file.
|
|
1726
1516
|
at?: string
|
|
1727
1517
|
allowUndecided: boolean
|
|
1728
1518
|
allowDeprecatedRemoval: boolean
|
|
@@ -1806,36 +1596,10 @@ function parseBreakingArgs(
|
|
|
1806
1596
|
}
|
|
1807
1597
|
}
|
|
1808
1598
|
|
|
1809
|
-
// One resolved `--against` spelling: the old document's text, the path
|
|
1810
|
-
// its own relative includes must resolve from, and (for a git spelling)
|
|
1811
|
-
// the temporary tree to remove when the run is done.
|
|
1812
1599
|
type OldVersion = { src: string, path: string, temp?: string }
|
|
1813
1600
|
|
|
1814
|
-
// A source file the include resolver can actually load. `git#<rev>`
|
|
1815
|
-
// materialises these and nothing else: an include names an Aontu
|
|
1816
|
-
// document (`.aon`/`.aontu`, the two extensions `@"foo"` tries) or a
|
|
1817
|
-
// JSON one, so the rest of a revision's tree cannot be part of any
|
|
1818
|
-
// include closure and copying it would be pure cost.
|
|
1819
1601
|
const INCLUDABLE = /\.(aon|aontu|jsonic|json)$/
|
|
1820
1602
|
|
|
1821
|
-
// Resolve one --against spelling to an old version.
|
|
1822
|
-
//
|
|
1823
|
-
// A `git#<rev>` spelling is the old version of the WHOLE TREE, not of
|
|
1824
|
-
// the entry file alone. It used to be `git show <rev>:./<file>`, whose
|
|
1825
|
-
// text was then evaluated with `generalPath`/`specificPath` pointing at
|
|
1826
|
-
// the WORKING file -- so every `@"..."` include in the old document
|
|
1827
|
-
// resolved against the working tree, and the "old" side was old entry
|
|
1828
|
-
// text meeting new includes. A breaking change inside an included file
|
|
1829
|
-
// therefore compared against itself and answered `compatible`: the
|
|
1830
|
-
// documented CI gate silently un-gated every non-entry file of the
|
|
1831
|
-
// multi-file layout real models use (use-cases/BUGS.md §26). The old
|
|
1832
|
-
// tree's includable sources are copied into a temporary directory and
|
|
1833
|
-
// the old document is evaluated from THERE.
|
|
1834
|
-
//
|
|
1835
|
-
// Sources outside the revision -- package includes under node_modules,
|
|
1836
|
-
// the bundled `std/system` -- still resolve as they do today: they are
|
|
1837
|
-
// not in the tree, and their versions travel with the lockfile rather
|
|
1838
|
-
// than with this comparison.
|
|
1839
1603
|
function oldVersion(spec: string, file: string): OldVersion | undefined {
|
|
1840
1604
|
if (!spec.startsWith('git#')) {
|
|
1841
1605
|
try {
|
|
@@ -1867,24 +1631,10 @@ function oldVersion(spec: string, file: string): OldVersion | undefined {
|
|
|
1867
1631
|
// that only some failures take.
|
|
1868
1632
|
const temp = mkdtempSync(join(tmpdir(), 'aontu-against-'))
|
|
1869
1633
|
try {
|
|
1870
|
-
// THE REPO-RELATIVE PATH COMES FROM GIT, not from path arithmetic.
|
|
1871
|
-
// Relativising `rev-parse --show-toplevel` against `resolve(file)`
|
|
1872
|
-
// puts two DIFFERENT COORDINATE SYSTEMS on either side of the
|
|
1873
|
-
// subtraction: git prints the real path, while the caller's is
|
|
1874
|
-
// whatever they typed. On macOS a temp file under /var is
|
|
1875
|
-
// /private/var to git, and on Windows a TMP short name
|
|
1876
|
-
// (RUNNER~1) is the long form to git -- so the subtraction gave a
|
|
1877
|
-
// `../..` climb, the entry was "not in that revision", and the
|
|
1878
|
-
// documented CI spelling failed on both platforms while passing on
|
|
1879
|
-
// Linux (this PR's own CI). `--show-prefix` is the same question
|
|
1880
|
-
// asked in git's coordinates: the repo-relative directory of the
|
|
1881
|
-
// cwd, already slash-separated and already normalised.
|
|
1882
1634
|
const prefix = git(['rev-parse', '--show-prefix'], dir).trim()
|
|
1883
1635
|
const entryRel = prefix + basename(file)
|
|
1884
1636
|
const top = git(['rev-parse', '--show-toplevel'], dir).trim()
|
|
1885
1637
|
|
|
1886
|
-
// `-z` so a path with a newline or a quote cannot be mistaken for
|
|
1887
|
-
// two paths (git otherwise quotes such names).
|
|
1888
1638
|
const listed = git(['ls-tree', '-r', '-z', '--name-only', rev], top)
|
|
1889
1639
|
.split('\0').filter((p) => '' !== p)
|
|
1890
1640
|
if (!listed.includes(entryRel)) {
|
|
@@ -1919,13 +1669,6 @@ function policyCompat(
|
|
|
1919
1669
|
): BreakingMode | undefined {
|
|
1920
1670
|
const aontu = new Aontu()
|
|
1921
1671
|
const ctx = aontu.ctx({ collect: true })
|
|
1922
|
-
// The declaration is read by EVALUATING the document, so this leg
|
|
1923
|
-
// runs the include resolver too and has to run it under BOTH of the
|
|
1924
|
-
// verb's include options -- a `breaking --trust none` that read its
|
|
1925
|
-
// own mode through an unconfined resolver would confine the
|
|
1926
|
-
// comparison and not the question (use-cases/REVIEW.md finding G),
|
|
1927
|
-
// and one that took the capability alone read no mode at all when
|
|
1928
|
-
// the declaration arrived through a `--text-ext` include.
|
|
1929
1672
|
const v: any = aontu.unify(newSrc, { path, ...includeOpts(include) }, ctx)
|
|
1930
1673
|
if (0 < ctx.err.length || true === v?.isNil) {
|
|
1931
1674
|
return undefined
|
|
@@ -1945,11 +1688,6 @@ function policyCompat(
|
|
|
1945
1688
|
? m : undefined
|
|
1946
1689
|
}
|
|
1947
1690
|
|
|
1948
|
-
// Is the evaluated old version's value at the finding path deprecated?
|
|
1949
|
-
// The --allow-deprecated-removal downgrade (G3 phase 4): removing (or
|
|
1950
|
-
// otherwise changing) a value the old version already deprecated warns
|
|
1951
|
-
// instead of breaking. The Go port exports the same reader as
|
|
1952
|
-
// aontu.DeprecatedAt.
|
|
1953
1691
|
function deprecatedAt(oldSrc: string, path: string, filePath: string): boolean {
|
|
1954
1692
|
const aontu = new Aontu()
|
|
1955
1693
|
const ctx = aontu.ctx({ collect: true })
|
|
@@ -2024,9 +1762,6 @@ function runBreaking(argv: string[]): number {
|
|
|
2024
1762
|
return 2
|
|
2025
1763
|
}
|
|
2026
1764
|
|
|
2027
|
-
// The declared mode: --mode overrides the document's own policy;
|
|
2028
|
-
// neither means backward, the index's framing (v1-valid documents
|
|
2029
|
-
// stay valid).
|
|
2030
1765
|
const mode: BreakingMode =
|
|
2031
1766
|
args.mode ??
|
|
2032
1767
|
policyCompat(newSrc, args.file,
|
|
@@ -2066,8 +1801,6 @@ function runBreaking(argv: string[]): number {
|
|
|
2066
1801
|
temps.push(old.temp)
|
|
2067
1802
|
}
|
|
2068
1803
|
|
|
2069
|
-
// backward: the NEW document is the general side — every old
|
|
2070
|
-
// instance must still be admitted. forward: the old one is.
|
|
2071
1804
|
const checks: Array<{ general: [string, string], specific: [string, string] }> = []
|
|
2072
1805
|
if ('backward' === mode || 'full' === mode) {
|
|
2073
1806
|
checks.push({ general: [newSrc, args.file], specific: [oldSrc, spec] })
|
|
@@ -2084,18 +1817,10 @@ function runBreaking(argv: string[]): number {
|
|
|
2084
1817
|
at: args.at,
|
|
2085
1818
|
generalUrl: check.general[1],
|
|
2086
1819
|
specificUrl: check.specific[1],
|
|
2087
|
-
// The old side's relative loads resolve from ITS own tree --
|
|
2088
|
-
// the materialised revision for a git spelling, the named
|
|
2089
|
-
// file's directory otherwise -- so an included file's change
|
|
2090
|
-
// is part of the comparison rather than invisible to it.
|
|
2091
1820
|
generalPath: check.general[1] === spec ? oldPath : args.file,
|
|
2092
1821
|
specificPath: check.specific[1] === spec ? oldPath : args.file,
|
|
2093
1822
|
})
|
|
2094
1823
|
|
|
2095
|
-
// The deprecated-removal downgrade: a finding about a value the
|
|
2096
|
-
// OLD version already deprecated becomes a warning, and warnings
|
|
2097
|
-
// do not move the verdict. Deprecate-then-remove is the
|
|
2098
|
-
// supported rename path (the design's own sequencing).
|
|
2099
1824
|
let verdict = report.verdict
|
|
2100
1825
|
if (args.allowDeprecatedRemoval) {
|
|
2101
1826
|
let liveFindings = 0
|
|
@@ -2153,13 +1878,6 @@ function renderBreakingJson(report: SubsumeReport, mode: string): string {
|
|
|
2153
1878
|
}
|
|
2154
1879
|
|
|
2155
1880
|
|
|
2156
|
-
// ---------------------------------------------------------------------
|
|
2157
|
-
// The trim reporter (G3 phase 6): report redundant entries as paths.
|
|
2158
|
-
// Report-only — REWRITING needs G7's format-preserving patch surface —
|
|
2159
|
-
// which is why --check is REQUIRED rather than defaulted: `aontu trim
|
|
2160
|
-
// f.aon` reads as "trim this file", and doing something else silently
|
|
2161
|
-
// is worse than saying so.
|
|
2162
|
-
|
|
2163
1881
|
const TRIM_HELP = 'aontu trim --check <file> (try --help)'
|
|
2164
1882
|
|
|
2165
1883
|
const TRIM_EXIT: Record<TrimVerdict, number> = {
|
|
@@ -2237,8 +1955,6 @@ function runTrim(argv: string[]): number {
|
|
|
2237
1955
|
|
|
2238
1956
|
function renderTrimText(report: TrimReport): string {
|
|
2239
1957
|
const head = `verdict: ${report.verdict}`
|
|
2240
|
-
// WHY, when the document could not be evaluated at all: rendered as
|
|
2241
|
-
// vet renders a finding, because it IS one (the review's finding F).
|
|
2242
1958
|
const errors = report.errors ?? []
|
|
2243
1959
|
if (0 < errors.length) {
|
|
2244
1960
|
return [head, ''].concat(errors.map(renderFinding)).join('\n')
|
|
@@ -2303,29 +2019,12 @@ const VIEW_EDGES: ViewEdges[] = ['upward', 'all', 'none']
|
|
|
2303
2019
|
// the same division err.ts already draws for the error frames.
|
|
2304
2020
|
const VIEW_STYLES = ['auto', 'none', 'ansi', 'css']
|
|
2305
2021
|
|
|
2306
|
-
// `--style auto` resolved, which only the CLI can do. The mechanism is
|
|
2307
|
-
// the PROFILE's and the library knows it -- an SVG carries its
|
|
2308
|
-
// stylesheet unless told not to, which is what makes a figure stand
|
|
2309
|
-
// alone. What the library cannot know is whether the DESTINATION is a
|
|
2310
|
-
// terminal, so that is the only thing decided here: escapes on the
|
|
2311
|
-
// text profile when stdout is a terminal and NO_COLOR is unset, the
|
|
2312
|
-
// same two conditions the error frames use. `undefined` leaves the
|
|
2313
|
-
// profile's own default in place.
|
|
2314
2022
|
function viewStyleOf(
|
|
2315
2023
|
asked: string | undefined, as: ViewProfile | undefined
|
|
2316
2024
|
): ViewStyle | undefined {
|
|
2317
2025
|
if (undefined !== asked && 'auto' !== asked) {
|
|
2318
2026
|
return asked as ViewStyle
|
|
2319
2027
|
}
|
|
2320
|
-
// STDOUT'S OWN TERMINAL-NESS, and NO_COLOR read here rather than
|
|
2321
|
-
// through colorActive(). The figure goes to STDOUT and the error
|
|
2322
|
-
// frames go to STDERR, and they are not the same destination: main()
|
|
2323
|
-
// has already called setColor for stderr, so asking colorActive()
|
|
2324
|
-
// would answer the wrong question twice --- no escapes for
|
|
2325
|
-
// `aontu view tree m.aon 2>/dev/null` at a terminal, and escapes
|
|
2326
|
-
// into the pipe for `aontu view tree m.aon | less`. The NO_COLOR
|
|
2327
|
-
// rule is the one no-color.org states and err.ts implements:
|
|
2328
|
-
// set, to anything but empty, means no colour.
|
|
2329
2028
|
const no = process.env.NO_COLOR
|
|
2330
2029
|
return 'text' === as && true === process.stdout.isTTY
|
|
2331
2030
|
&& (null == no || '' === no) ? 'ansi' : undefined
|
|
@@ -2351,25 +2050,13 @@ const VIEW_USAGE_CODES = [
|
|
|
2351
2050
|
|
|
2352
2051
|
const MOD_HELP = 'aontu mod tidy|verify|vendor|manifest [dir] (try --help)'
|
|
2353
2052
|
|
|
2354
|
-
// The module tooling (G6 phase 3, ts/src/mod-tool.ts). All LOCAL:
|
|
2355
|
-
// `tidy` resolves the closure from what is in the stores and rewrites
|
|
2356
|
-
// the lockfile, `verify` asks whether the stores still mean what the
|
|
2357
|
-
// lockfile pins and changes nothing, `vendor` materialises the locked
|
|
2358
|
-
// closure into the project, `manifest` prints what a publish would
|
|
2359
|
-
// push.
|
|
2360
|
-
//
|
|
2361
|
-
// TIDY AND VERIFY ARE DIFFERENT QUESTIONS, and that is why both exist.
|
|
2362
|
-
// Tidy recomputes and rewrites by design -- a pin is what a module
|
|
2363
|
-
// means NOW -- so it makes the lockfile agree with whatever the store
|
|
2364
|
-
// holds, tampering included. Verify is the gate: a CI job runs it
|
|
2365
|
-
// BEFORE tidy, or instead of it.
|
|
2366
|
-
//
|
|
2367
|
-
// `get` and `publish` are the NETWORK half of the design and are not in
|
|
2368
|
-
// this build. They are named here rather than left to fall out as an
|
|
2369
|
-
// unknown subcommand, because a reader of the design will type them and
|
|
2370
|
-
// deserves to be told which half is missing rather than that the word
|
|
2371
|
-
// is wrong.
|
|
2372
2053
|
function runMod(argv: string[]): number {
|
|
2054
|
+
const trusted = takeTrust(argv)
|
|
2055
|
+
if (null == trusted) {
|
|
2056
|
+
return 2
|
|
2057
|
+
}
|
|
2058
|
+
argv = trusted.argv
|
|
2059
|
+
const trust = trusted.trust
|
|
2373
2060
|
const rest: string[] = []
|
|
2374
2061
|
let format: SubsumeFormat = 'text'
|
|
2375
2062
|
let against: string | undefined
|
|
@@ -2422,10 +2109,6 @@ function runMod(argv: string[]): number {
|
|
|
2422
2109
|
return 2
|
|
2423
2110
|
}
|
|
2424
2111
|
|
|
2425
|
-
// THE OLD LAYOUT IS NAMED, NOT READ. The lockfile and the vendored
|
|
2426
|
-
// closure moved under aontu_meta/; a project that still carries them
|
|
2427
|
-
// at its root would otherwise look untouched by any of these verbs,
|
|
2428
|
-
// which is the one silence worth breaking.
|
|
2429
2112
|
if (existsSync(join(dir, 'aon_vendor')) || existsSync(join(dir, 'mod-lock.aon'))) {
|
|
2430
2113
|
process.stderr.write(
|
|
2431
2114
|
'aontu: aon_vendor/ and mod-lock.aon now live under aontu_meta/: ' +
|
|
@@ -2439,11 +2122,13 @@ function runMod(argv: string[]): number {
|
|
|
2439
2122
|
return 2
|
|
2440
2123
|
}
|
|
2441
2124
|
|
|
2125
|
+
const modopts = modToolOptions(trust, resolve(dir))
|
|
2126
|
+
|
|
2442
2127
|
const report =
|
|
2443
|
-
'tidy' === sub ? modTidy(dir,
|
|
2444
|
-
'verify' === sub ? modVerify(dir,
|
|
2445
|
-
'vendor' === sub ? modVendor(dir,
|
|
2446
|
-
modManifest(dir,
|
|
2128
|
+
'tidy' === sub ? modTidy(dir, modopts) :
|
|
2129
|
+
'verify' === sub ? modVerify(dir, modopts) :
|
|
2130
|
+
'vendor' === sub ? modVendor(dir, modopts) :
|
|
2131
|
+
modManifest(dir, modopts, against)
|
|
2447
2132
|
|
|
2448
2133
|
process.stdout.write(('json' === format ?
|
|
2449
2134
|
exactJSON({ aontu: { version: version(), verb: 'mod ' + sub }, ...report },
|
|
@@ -2469,9 +2154,6 @@ type ModVerdict =
|
|
|
2469
2154
|
const MOD_EXIT: Record<ModVerdict, number> = {
|
|
2470
2155
|
ok: 0,
|
|
2471
2156
|
missing: 1,
|
|
2472
|
-
// A REFUSED GATE, with `breaking`: a store that no longer means what
|
|
2473
|
-
// the lockfile pins is the integrity check saying no, and a CI job
|
|
2474
|
-
// reading exit codes should not have to learn a third class for it.
|
|
2475
2157
|
mismatch: 1,
|
|
2476
2158
|
// Likewise a lockfile that does not cover the project: the gate has
|
|
2477
2159
|
// nothing to check, which is a refusal and not a pass.
|
|
@@ -2485,11 +2167,16 @@ const MOD_EXIT: Record<ModVerdict, number> = {
|
|
|
2485
2167
|
// The tooling's evaluator: the same standalone evaluation the module
|
|
2486
2168
|
// resolver verifies with (ts/src/mod.ts), and for the same reason —
|
|
2487
2169
|
// only the engine can say what a module MEANS.
|
|
2488
|
-
function modToolOptions() {
|
|
2170
|
+
function modToolOptions(trust: TrustArg, entryRoot: string) {
|
|
2171
|
+
const opts = verbOpts(trust, entryRoot)
|
|
2172
|
+
// The user cache lives outside any confinement root, so a confined
|
|
2173
|
+
// run reads the vendor tree only -- as the evaluator's own module
|
|
2174
|
+
// leg already does when a root is set.
|
|
2175
|
+
const rooted = null != (opts.trust as any)?.include?.root
|
|
2489
2176
|
return {
|
|
2490
|
-
cache: modCacheDir(),
|
|
2177
|
+
...(rooted ? {} : { cache: modCacheDir() }),
|
|
2491
2178
|
eval: (src: string, path: string) => {
|
|
2492
|
-
const a0 = new Aontu()
|
|
2179
|
+
const a0 = new Aontu(opts)
|
|
2493
2180
|
const ctx = a0.ctx({ collect: true })
|
|
2494
2181
|
const val: any = a0.unify(src, { path }, ctx)
|
|
2495
2182
|
return {
|
|
@@ -2522,11 +2209,6 @@ function modText(sub: string, report: any): string {
|
|
|
2522
2209
|
for (const f of report.findings) {
|
|
2523
2210
|
lines.push(f.path + ': ' + f.message)
|
|
2524
2211
|
}
|
|
2525
|
-
// What a manifest lacks is a declaration the module does not make
|
|
2526
|
-
// or an entry file that is not there, and neither is something a
|
|
2527
|
-
// fetch would supply -- so this is not the tail the other two
|
|
2528
|
-
// subcommands share. The name says which kind it is: `mod.version`
|
|
2529
|
-
// is a declaration, `service.aon` is a file.
|
|
2530
2212
|
for (const miss of report.missing) {
|
|
2531
2213
|
lines.push(miss + ': missing')
|
|
2532
2214
|
}
|
|
@@ -2537,8 +2219,6 @@ function modText(sub: string, report: any): string {
|
|
|
2537
2219
|
for (const mod of report.verified) {
|
|
2538
2220
|
lines.push(mod + ': verified')
|
|
2539
2221
|
}
|
|
2540
|
-
// BOTH HASHES, because the useful question is which way it moved:
|
|
2541
|
-
// an empty `got` is a module that no longer stands up at all.
|
|
2542
2222
|
for (const m of report.mismatched) {
|
|
2543
2223
|
lines.push(m.mod + ': pinned ' + m.want + ' but the store means ' +
|
|
2544
2224
|
('' === m.got ? 'nothing (it does not evaluate)' : m.got))
|
|
@@ -2574,26 +2254,6 @@ function modText(sub: string, report: any): string {
|
|
|
2574
2254
|
}
|
|
2575
2255
|
|
|
2576
2256
|
|
|
2577
|
-
// VACUITY SIGNALS (G11 phase 4,
|
|
2578
|
-
// docs/capability-review/g11-agent-onramp.md).
|
|
2579
|
-
//
|
|
2580
|
-
// The same principle phase 5 applied to `vet`: a verb that did NOTHING
|
|
2581
|
-
// and a verb that did its job answer the same. `aontu view tree` over a
|
|
2582
|
-
// document declaring no relations printed one newline and exited 0;
|
|
2583
|
-
// `aontu render` with no profile printed nothing and exited 0; `aontu
|
|
2584
|
-
// relations` over a document declaring none answered `verdict: pass`.
|
|
2585
|
-
// For a person at a terminal that is a shrug. For an unattended agent
|
|
2586
|
-
// it is a green check mark on an empty box.
|
|
2587
|
-
//
|
|
2588
|
-
// ON STDERR, ALWAYS. stdout is a report contract -- a `--format json`
|
|
2589
|
-
// consumer parses it -- and the exit code is a verdict class that
|
|
2590
|
-
// callers already branch on. Neither changes here: what changes is
|
|
2591
|
-
// that the caller is TOLD. A caller who wants it to be fatal has
|
|
2592
|
-
// `vet --strict-coverage`, and the same argument would give the other
|
|
2593
|
-
// verbs a flag of their own if one is ever asked for.
|
|
2594
|
-
//
|
|
2595
|
-
// The repository already ruled this for one verb, in G8 phase 6 on
|
|
2596
|
-
// `trim`: "doing something else silently is worse than refusing".
|
|
2597
2257
|
function vacuous(what: string, why: string): void {
|
|
2598
2258
|
process.stderr.write(`aontu: ${what}: ${why}\n`)
|
|
2599
2259
|
}
|
|
@@ -2665,6 +2325,120 @@ function runRelations(argv: string[]): number {
|
|
|
2665
2325
|
return RELATIONS_EXIT[report.verdict]
|
|
2666
2326
|
}
|
|
2667
2327
|
|
|
2328
|
+
const TRACE_HELP =
|
|
2329
|
+
'aontu trace [--at <path>] [--format json] [--marker <token>] ' +
|
|
2330
|
+
'[--profile <file>] <file>'
|
|
2331
|
+
|
|
2332
|
+
|
|
2333
|
+
// WHAT WROTE THIS LINE. Every piece a rule stamped, under the
|
|
2334
|
+
// component tree, with the file it reached, the rule set that wrote it
|
|
2335
|
+
// and the model node the dispatch matched.
|
|
2336
|
+
function runTrace(argv: string[]): number {
|
|
2337
|
+
const trusted = takeTrust(argv)
|
|
2338
|
+
if (null == trusted) {
|
|
2339
|
+
return 2
|
|
2340
|
+
}
|
|
2341
|
+
argv = trusted.argv
|
|
2342
|
+
const trust = trusted.trust
|
|
2343
|
+
const rest: string[] = []
|
|
2344
|
+
const profileFiles: string[] = []
|
|
2345
|
+
let format: 'text' | 'json' = 'text'
|
|
2346
|
+
let at: string | undefined = undefined
|
|
2347
|
+
let marker: string | undefined = undefined
|
|
2348
|
+
|
|
2349
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2350
|
+
const arg = argv[i]
|
|
2351
|
+
if ('-h' === arg || '--help' === arg) {
|
|
2352
|
+
process.stdout.write(HELP)
|
|
2353
|
+
return 0
|
|
2354
|
+
}
|
|
2355
|
+
if ('--format' === arg) {
|
|
2356
|
+
const f = argv[++i]
|
|
2357
|
+
if ('text' !== f && 'json' !== f) {
|
|
2358
|
+
process.stderr.write('aontu: --format needs text or json\n')
|
|
2359
|
+
return 2
|
|
2360
|
+
}
|
|
2361
|
+
format = f
|
|
2362
|
+
}
|
|
2363
|
+
else if ('--at' === arg) {
|
|
2364
|
+
at = argv[++i]
|
|
2365
|
+
if (null == at) {
|
|
2366
|
+
process.stderr.write('aontu: --at needs a path\n')
|
|
2367
|
+
return 2
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
else if ('--marker' === arg) {
|
|
2371
|
+
marker = argv[++i]
|
|
2372
|
+
if (null == marker) {
|
|
2373
|
+
process.stderr.write('aontu: --marker needs a token\n')
|
|
2374
|
+
return 2
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
else if ('--profile' === arg) {
|
|
2378
|
+
const pf = argv[++i]
|
|
2379
|
+
if (null == pf) {
|
|
2380
|
+
process.stderr.write('aontu: --profile needs a file\n')
|
|
2381
|
+
return 2
|
|
2382
|
+
}
|
|
2383
|
+
profileFiles.push(pf)
|
|
2384
|
+
}
|
|
2385
|
+
else if (arg.startsWith('-')) {
|
|
2386
|
+
process.stderr.write(`aontu: unknown trace option ${arg} (try --help)\n`)
|
|
2387
|
+
return 2
|
|
2388
|
+
}
|
|
2389
|
+
else {
|
|
2390
|
+
rest.push(arg)
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
if (1 !== rest.length) {
|
|
2395
|
+
process.stderr.write(`aontu: trace needs one file\n${TRACE_HELP}\n`)
|
|
2396
|
+
return 2
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
let src: string
|
|
2400
|
+
try {
|
|
2401
|
+
src = readFileSync(rest[0], 'utf8')
|
|
2402
|
+
}
|
|
2403
|
+
catch (err: any) {
|
|
2404
|
+
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
2405
|
+
return 2
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
const declared = loadProfiles(profileFiles, trust)
|
|
2409
|
+
if ('number' === typeof declared) {
|
|
2410
|
+
return declared
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// A GENERATOR IS AN ENTRY, not a preprocessing step: the file whose
|
|
2414
|
+
// provenance is asked for is the one the author edits.
|
|
2415
|
+
if (!rest[0].endsWith('.aon')) {
|
|
2416
|
+
src = desugarTemplate(src, marker ??
|
|
2417
|
+
markerFromProfiles(declared, rest[0]) ?? markerFor(rest[0]))
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
const report = traceRun(src, {
|
|
2421
|
+
path: rest[0], at,
|
|
2422
|
+
...verbOpts(trust, entryRootOf(rest[0])),
|
|
2423
|
+
})
|
|
2424
|
+
if ('error' === report.verdict) {
|
|
2425
|
+
// An error report always carries its findings.
|
|
2426
|
+
const errors = report.errors as VetFinding[]
|
|
2427
|
+
process.stderr.write(errors.map(renderFinding).join('\n') + '\n')
|
|
2428
|
+
return 4
|
|
2429
|
+
}
|
|
2430
|
+
if ('json' === format) {
|
|
2431
|
+
process.stdout.write(JSON.stringify({ trace: report.trace }) + '\n')
|
|
2432
|
+
return 0
|
|
2433
|
+
}
|
|
2434
|
+
for (const e of report.trace) {
|
|
2435
|
+
process.stdout.write(
|
|
2436
|
+
[e.file, e.at, e.node, e.rule].join('\t') + '\n')
|
|
2437
|
+
}
|
|
2438
|
+
return 0
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
|
|
2668
2442
|
function runReaches(argv: string[]): number {
|
|
2669
2443
|
const trusted = takeTrust(argv)
|
|
2670
2444
|
if (null == trusted) {
|
|
@@ -2881,12 +2655,6 @@ function runView(argv: string[]): number {
|
|
|
2881
2655
|
}
|
|
2882
2656
|
}
|
|
2883
2657
|
|
|
2884
|
-
// ESCAPES NEVER GO INTO A FILE. A pinned golden holding terminal
|
|
2885
|
-
// control codes is not a golden anybody can read, and a byte
|
|
2886
|
-
// comparison against one would fail on the reader's terminal
|
|
2887
|
-
// settings. `auto` resolves to `none` there on its own; asking for
|
|
2888
|
-
// `ansi` explicitly is a usage error rather than a silent downgrade,
|
|
2889
|
-
// so a script that wanted colour is told where it went.
|
|
2890
2658
|
if ('ansi' === style && (undefined !== out || undefined !== opts.views)) {
|
|
2891
2659
|
process.stderr.write(
|
|
2892
2660
|
'aontu: --style ansi writes to a terminal, not to a file\n')
|
|
@@ -2896,10 +2664,6 @@ function runView(argv: string[]): number {
|
|
|
2896
2664
|
// THE VIEW DOCUMENT draws every figure a document declares, so it
|
|
2897
2665
|
// names no kind: the declarations do, one each.
|
|
2898
2666
|
if (undefined !== opts.views) {
|
|
2899
|
-
// A declaration names its own profile, so the style is left to
|
|
2900
|
-
// each figure's own default; `--style none` still reaches every
|
|
2901
|
-
// one of them, which is how a host page that binds the CSS
|
|
2902
|
-
// variables asks for eight figures without eight stylesheets.
|
|
2903
2667
|
opts.style = viewStyleOf(style, undefined)
|
|
2904
2668
|
return runViewSet(rest, opts, trust, { format, check, strict, out })
|
|
2905
2669
|
}
|
|
@@ -2975,17 +2739,6 @@ function runView(argv: string[]): number {
|
|
|
2975
2739
|
}
|
|
2976
2740
|
const report = view(srcs[0], viewOpts)
|
|
2977
2741
|
|
|
2978
|
-
// AN EMPTY FIGURE IS THE SAME BYTES AS A DRAWN ONE MINUS ITS
|
|
2979
|
-
// CONTENT, and every profile spells "empty" differently: text draws
|
|
2980
|
-
// nothing at all, mermaid still draws its `flowchart LR` header, the
|
|
2981
|
-
// matrix still prints its count line. Rather than teach this one
|
|
2982
|
-
// place each of those spellings -- a list that goes stale the first
|
|
2983
|
-
// time a profile gains a header -- ASK THE SAME KIND TO DRAW AN
|
|
2984
|
-
// EMPTY DOCUMENT and compare. Equal texts mean this document
|
|
2985
|
-
// contributed nothing to the figure, whatever the profile.
|
|
2986
|
-
//
|
|
2987
|
-
// It costs one drawing of `{}`, which is the cheapest document
|
|
2988
|
-
// there is, and only on a run that produced a figure at all.
|
|
2989
2742
|
if ('error' !== report.verdict && null != report.text) {
|
|
2990
2743
|
const bare = view('{}', viewOpts)
|
|
2991
2744
|
if ('error' !== bare.verdict && bare.text === report.text) {
|
|
@@ -3039,13 +2792,6 @@ function runView(argv: string[]): number {
|
|
|
3039
2792
|
return strict && 'lossy' === report.verdict ? 1 : VIEW_EXIT[report.verdict]
|
|
3040
2793
|
}
|
|
3041
2794
|
|
|
3042
|
-
// `aontu view --views <path> <file>`: every figure the document
|
|
3043
|
-
// declares, from one evaluation, all or nothing.
|
|
3044
|
-
//
|
|
3045
|
-
// A declared `out` is resolved against the DOCUMENT's own directory,
|
|
3046
|
-
// not the caller's: a view document is committed beside the figures it
|
|
3047
|
-
// gates, and a gate that only passes from one working directory is not
|
|
3048
|
-
// a gate.
|
|
3049
2795
|
function runViewSet(
|
|
3050
2796
|
rest: string[], opts: ViewOptions, trust: TrustArg,
|
|
3051
2797
|
how: { format: SubsumeFormat, check: boolean, strict: boolean, out?: string }
|
|
@@ -3186,8 +2932,6 @@ function renderViewJson(report: ViewReport): string {
|
|
|
3186
2932
|
|
|
3187
2933
|
function renderRelationsText(report: RelationReport): string {
|
|
3188
2934
|
const head = `verdict: ${report.verdict}`
|
|
3189
|
-
// WHY, when the document could not be evaluated at all: rendered as
|
|
3190
|
-
// vet renders a finding, because it IS one (the review's finding F).
|
|
3191
2935
|
const errors = report.errors ?? []
|
|
3192
2936
|
if (0 < errors.length) {
|
|
3193
2937
|
return [head, ''].concat(errors.map(renderFinding)).join('\n')
|
|
@@ -3213,21 +2957,6 @@ function renderRelationsJson(report: RelationReport): string {
|
|
|
3213
2957
|
}
|
|
3214
2958
|
|
|
3215
2959
|
|
|
3216
|
-
|
|
3217
|
-
// ---------------------------------------------------------------------
|
|
3218
|
-
// JSON SCHEMA EXPORT (SUPPORT.md act 2, the review's finding I): the
|
|
3219
|
-
// bridge to every structured-output API, which constrains generation to
|
|
3220
|
-
// JSON Schema and nothing else. Export the model, let the provider
|
|
3221
|
-
// generate under it, then `vet` the result against the model itself --
|
|
3222
|
-
// the hybrid an enterprise actually deploys, and impossible without
|
|
3223
|
-
// this verb.
|
|
3224
|
-
//
|
|
3225
|
-
// THE SCHEMA GOES TO STDOUT AND THE LOSSES TO STDERR, so `aontu
|
|
3226
|
-
// jsonschema x.aon > schema.json` writes a schema and still tells the
|
|
3227
|
-
// reader what it could not carry. `--strict` makes a loss a refusal,
|
|
3228
|
-
// for the CI job that would rather fail than ship a schema weaker than
|
|
3229
|
-
// its model.
|
|
3230
|
-
|
|
3231
2960
|
const JSONSCHEMA_HELP =
|
|
3232
2961
|
'aontu jsonschema [--at <path>] [--strict] <file> (try --help)'
|
|
3233
2962
|
|
|
@@ -3323,358 +3052,22 @@ function runJsonSchema(argv: string[]): number {
|
|
|
3323
3052
|
strict && 'lossy' === report.verdict ? 1 : 0
|
|
3324
3053
|
}
|
|
3325
3054
|
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
// mirror jsonschema's: 0 ok; 1 lossy under --strict or drift under
|
|
3332
|
-
// --check; 2 usage or I/O, a refused unit path included; 4 the
|
|
3333
|
-
// document does not stand up or the instance is not aontu:code.
|
|
3334
|
-
|
|
3335
|
-
const RENDER_HELP =
|
|
3336
|
-
'aontu render [--at <path>] [--profile <file>]... [--unit <path>] ' +
|
|
3337
|
-
'[--stdout | --out <dir> | --check <dir> | --coverage] ' +
|
|
3338
|
-
'[--coverage-at <path>] [--strict] [--marker <token>] <file> (try --help)'
|
|
3339
|
-
|
|
3340
|
-
function runRender(argv: string[]): number {
|
|
3055
|
+
|
|
3056
|
+
const TEMPLATE_HELP =
|
|
3057
|
+
'aontu template [--resugar] [--check] [--marker <token>] <file> (try --help)'
|
|
3058
|
+
|
|
3059
|
+
function runTemplate(argv: string[]): number {
|
|
3341
3060
|
const trusted = takeTrust(argv)
|
|
3342
3061
|
if (null == trusted) {
|
|
3343
3062
|
return 2
|
|
3344
3063
|
}
|
|
3345
3064
|
argv = trusted.argv
|
|
3346
3065
|
const trust = trusted.trust
|
|
3347
|
-
const files: string[] = []
|
|
3348
|
-
const profileFiles: string[] = []
|
|
3349
|
-
let format: SubsumeFormat = 'text'
|
|
3350
|
-
let at: string | undefined = undefined
|
|
3351
|
-
let unit: string | undefined = undefined
|
|
3352
|
-
let out: string | undefined = undefined
|
|
3353
|
-
let check: string | undefined = undefined
|
|
3354
|
-
let toStdout = false
|
|
3355
|
-
let strict = false
|
|
3356
|
-
let coverage = false
|
|
3357
|
-
let coverageAt: string | undefined = undefined
|
|
3358
|
-
let marker: string | undefined = undefined
|
|
3359
|
-
|
|
3360
|
-
for (let i = 0; i < argv.length; i++) {
|
|
3361
|
-
const arg = argv[i]
|
|
3362
|
-
if ('-h' === arg || '--help' === arg) {
|
|
3363
|
-
process.stdout.write(HELP)
|
|
3364
|
-
return 0
|
|
3365
|
-
}
|
|
3366
|
-
if ('--format' === arg) {
|
|
3367
|
-
const f = argv[++i]
|
|
3368
|
-
if ('text' !== f && 'json' !== f) {
|
|
3369
|
-
process.stderr.write('aontu: --format needs text or json\n')
|
|
3370
|
-
return 2
|
|
3371
|
-
}
|
|
3372
|
-
format = f
|
|
3373
|
-
}
|
|
3374
|
-
else if ('--at' === arg) {
|
|
3375
|
-
at = argv[++i]
|
|
3376
|
-
if (null == at) {
|
|
3377
|
-
process.stderr.write('aontu: --at needs a path\n')
|
|
3378
|
-
return 2
|
|
3379
|
-
}
|
|
3380
|
-
}
|
|
3381
|
-
else if ('--unit' === arg) {
|
|
3382
|
-
unit = argv[++i]
|
|
3383
|
-
if (null == unit) {
|
|
3384
|
-
process.stderr.write('aontu: --unit needs a unit path\n')
|
|
3385
|
-
return 2
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
else if ('--profile' === arg) {
|
|
3389
|
-
const pf = argv[++i]
|
|
3390
|
-
if (null == pf) {
|
|
3391
|
-
process.stderr.write('aontu: --profile needs a file\n')
|
|
3392
|
-
return 2
|
|
3393
|
-
}
|
|
3394
|
-
profileFiles.push(pf)
|
|
3395
|
-
}
|
|
3396
|
-
else if ('--out' === arg) {
|
|
3397
|
-
out = argv[++i]
|
|
3398
|
-
if (null == out) {
|
|
3399
|
-
process.stderr.write('aontu: --out needs a directory\n')
|
|
3400
|
-
return 2
|
|
3401
|
-
}
|
|
3402
|
-
}
|
|
3403
|
-
else if ('--check' === arg) {
|
|
3404
|
-
check = argv[++i]
|
|
3405
|
-
if (null == check) {
|
|
3406
|
-
process.stderr.write('aontu: --check needs a directory\n')
|
|
3407
|
-
return 2
|
|
3408
|
-
}
|
|
3409
|
-
}
|
|
3410
|
-
else if ('--stdout' === arg) {
|
|
3411
|
-
toStdout = true
|
|
3412
|
-
}
|
|
3413
|
-
else if ('--coverage' === arg) {
|
|
3414
|
-
coverage = true
|
|
3415
|
-
}
|
|
3416
|
-
else if ('--marker' === arg) {
|
|
3417
|
-
marker = argv[++i]
|
|
3418
|
-
if (null == marker) {
|
|
3419
|
-
process.stderr.write('aontu: --marker needs a token\n')
|
|
3420
|
-
return 2
|
|
3421
|
-
}
|
|
3422
|
-
}
|
|
3423
|
-
else if ('--coverage-at' === arg) {
|
|
3424
|
-
coverageAt = argv[++i]
|
|
3425
|
-
if (null == coverageAt) {
|
|
3426
|
-
process.stderr.write('aontu: --coverage-at needs a path\n')
|
|
3427
|
-
return 2
|
|
3428
|
-
}
|
|
3429
|
-
}
|
|
3430
|
-
else if ('--strict' === arg) {
|
|
3431
|
-
strict = true
|
|
3432
|
-
}
|
|
3433
|
-
else if (arg.startsWith('-')) {
|
|
3434
|
-
process.stderr.write(
|
|
3435
|
-
`aontu: unknown render option ${arg} (try --help)\n`)
|
|
3436
|
-
return 2
|
|
3437
|
-
}
|
|
3438
|
-
else {
|
|
3439
|
-
files.push(arg)
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
|
|
3443
|
-
if (1 !== files.length) {
|
|
3444
|
-
process.stderr.write(`aontu: render needs one file\n${RENDER_HELP}\n`)
|
|
3445
|
-
return 2
|
|
3446
|
-
}
|
|
3447
|
-
const modes = [toStdout, undefined !== out, undefined !== check, coverage]
|
|
3448
|
-
.filter((on) => on).length
|
|
3449
|
-
if (1 < modes) {
|
|
3450
|
-
process.stderr.write(
|
|
3451
|
-
'aontu: render takes one of --stdout, --out, --check or --coverage\n')
|
|
3452
|
-
return 2
|
|
3453
|
-
}
|
|
3454
|
-
// A NARROWER MEASURE NEEDS SOMETHING TO NARROW. `--coverage-at`
|
|
3455
|
-
// without `--coverage` asks for a report the run does not compute,
|
|
3456
|
-
// and answering silently would be the wrong half of the request.
|
|
3457
|
-
if (undefined !== coverageAt && !coverage) {
|
|
3458
|
-
process.stderr.write('aontu: --coverage-at needs --coverage\n')
|
|
3459
|
-
return 2
|
|
3460
|
-
}
|
|
3461
|
-
|
|
3462
|
-
let src: string
|
|
3463
|
-
try {
|
|
3464
|
-
src = readFileSync(files[0], 'utf8')
|
|
3465
|
-
}
|
|
3466
|
-
catch (err: any) {
|
|
3467
|
-
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
3468
|
-
return 2
|
|
3469
|
-
}
|
|
3470
|
-
|
|
3471
|
-
// THE ENTRY MAY BE A TEMPLATE (TEMPLATE.0.md; P8), and its EXTENSION
|
|
3472
|
-
// decides, as an include's extension decides what the include is
|
|
3473
|
-
// (ADR-012): a generator is a file in the target's own syntax, so it
|
|
3474
|
-
// carries the target's extension and never `.aon`. Desugared here
|
|
3475
|
-
// rather than anywhere deeper, because a template is an entry
|
|
3476
|
-
// spelling and not a value: an include is still aontu.
|
|
3477
|
-
if (!files[0].endsWith('.aon')) {
|
|
3478
|
-
src = desugarTemplate(src, marker ?? markerFor(files[0]))
|
|
3479
|
-
}
|
|
3480
|
-
|
|
3481
|
-
// THE PROFILES (D5): each --profile file is a document whose root is
|
|
3482
|
-
// `profile: {lang, ...}`, evaluated under the verb's trust and vetted
|
|
3483
|
-
// against aontu:profile as a settled value before the fold reads it
|
|
3484
|
-
// (renderProfile, which also fills the defaults). Two files claiming
|
|
3485
|
-
// one lang is a usage error: the fold could not choose.
|
|
3486
|
-
const profiles: any[] = []
|
|
3487
|
-
const langs = new Map<string, string>()
|
|
3488
|
-
for (const pf of profileFiles) {
|
|
3489
|
-
let text: string
|
|
3490
|
-
try {
|
|
3491
|
-
text = readFileSync(pf, 'utf8')
|
|
3492
|
-
}
|
|
3493
|
-
catch (err: any) {
|
|
3494
|
-
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
3495
|
-
return 2
|
|
3496
|
-
}
|
|
3497
|
-
const loaded = renderProfile(text,
|
|
3498
|
-
{ path: resolve(pf), ...verbOpts(trust, entryRootOf(pf)) })
|
|
3499
|
-
if (undefined !== loaded.errors) {
|
|
3500
|
-
process.stderr.write(loaded.errors.map(renderFinding).join('\n') + '\n')
|
|
3501
|
-
return 4
|
|
3502
|
-
}
|
|
3503
|
-
const profile = loaded.profile
|
|
3504
|
-
const prev = langs.get(profile.lang)
|
|
3505
|
-
if (undefined !== prev) {
|
|
3506
|
-
process.stderr.write(
|
|
3507
|
-
`aontu: two profiles claim ${profile.lang}: ${prev} and ${pf}\n`)
|
|
3508
|
-
return 2
|
|
3509
|
-
}
|
|
3510
|
-
langs.set(profile.lang, pf)
|
|
3511
|
-
profiles.push(profile)
|
|
3512
|
-
}
|
|
3513
|
-
|
|
3514
|
-
// A RENDER WITH NO PROFILE PRODUCES NO UNITS, and said so with zero
|
|
3515
|
-
// bytes and exit 0. The profile is what maps a model onto a
|
|
3516
|
-
// language, so without one there is nothing for the renderer to
|
|
3517
|
-
// write -- which is a usable answer only if the caller is told.
|
|
3518
|
-
const noProfiles = 0 === profiles.length
|
|
3519
|
-
const report = render(src, {
|
|
3520
|
-
at, unit, strict, profiles, path: files[0],
|
|
3521
|
-
coverage, coverageAt,
|
|
3522
|
-
// THE JSON REPORT CARRIES THE TRACE (D9), which is what the shape
|
|
3523
|
-
// there has always said; a text run computes it only when the
|
|
3524
|
-
// coverage report needs it.
|
|
3525
|
-
trace: 'json' === format,
|
|
3526
|
-
...verbOpts(trust, entryRootOf(files[0])),
|
|
3527
|
-
})
|
|
3528
|
-
|
|
3529
|
-
// Said once, whatever the format: stdout stays the report.
|
|
3530
|
-
if ('error' !== report.verdict && 0 === report.units.length) {
|
|
3531
|
-
vacuous('nothing was rendered',
|
|
3532
|
-
noProfiles
|
|
3533
|
-
? 'no profile was given, and the document declares none' +
|
|
3534
|
-
' (see aontu help tasks)'
|
|
3535
|
-
: 'the document produced no units under this profile')
|
|
3536
|
-
}
|
|
3537
|
-
|
|
3538
|
-
if ('json' === format) {
|
|
3539
|
-
process.stdout.write(exactJSON({
|
|
3540
|
-
aontu: { version: version(), verb: 'render' },
|
|
3541
|
-
verdict: report.verdict,
|
|
3542
|
-
units: report.units,
|
|
3543
|
-
lossy: report.lossy,
|
|
3544
|
-
...(null == report.errors ? {} : { errors: report.errors }),
|
|
3545
|
-
...(null == report.trace ? {} : { trace: report.trace }),
|
|
3546
|
-
...(null == report.coverage ? {} : { coverage: report.coverage }),
|
|
3547
|
-
}, 2) + '\n')
|
|
3548
|
-
return renderExit(report, 0)
|
|
3549
|
-
}
|
|
3550
|
-
if ('error' === report.verdict) {
|
|
3551
|
-
process.stderr.write(
|
|
3552
|
-
(report.errors as VetFinding[]).map(renderFinding).join('\n') + '\n')
|
|
3553
|
-
return renderExit(report, 0)
|
|
3554
|
-
}
|
|
3555
|
-
|
|
3556
|
-
let drift = 0
|
|
3557
|
-
if (toStdout) {
|
|
3558
|
-
// ONE UNIT'S BYTES AND NOTHING ELSE, so the output can be piped
|
|
3559
|
-
// into a formatter or a file.
|
|
3560
|
-
if (1 !== report.units.length) {
|
|
3561
|
-
process.stderr.write(
|
|
3562
|
-
'aontu: --stdout needs exactly one unit, and the instance has ' +
|
|
3563
|
-
`${report.units.length}; --unit names one\n`)
|
|
3564
|
-
return 2
|
|
3565
|
-
}
|
|
3566
|
-
process.stdout.write(report.units[0].text)
|
|
3567
|
-
}
|
|
3568
|
-
else if (undefined !== out) {
|
|
3569
|
-
// EVERY UNIT BELOW <dir>, OR NOTHING: every unit rendered first
|
|
3570
|
-
// (the report above), and no file touched unless all did. The
|
|
3571
|
-
// directory is realpath-confined; a unit path is already a relative
|
|
3572
|
-
// descent (render_path refuses the rest), and the check here is
|
|
3573
|
-
// against the symlink inside it. render never deletes.
|
|
3574
|
-
for (const u of report.units) {
|
|
3575
|
-
if (outsideRoot(out, resolve(out, u.path))) {
|
|
3576
|
-
process.stderr.write(`aontu: ${u.path} escapes ${out}\n`)
|
|
3577
|
-
return 2
|
|
3578
|
-
}
|
|
3579
|
-
}
|
|
3580
|
-
for (const u of report.units) {
|
|
3581
|
-
const full = resolve(out, u.path)
|
|
3582
|
-
try {
|
|
3583
|
-
mkdirSync(dirname(full), { recursive: true })
|
|
3584
|
-
writeFileSync(full, u.text, 'utf8')
|
|
3585
|
-
}
|
|
3586
|
-
catch (err: any) {
|
|
3587
|
-
process.stderr.write(`aontu: cannot write ${u.path}: ${err.message}\n`)
|
|
3588
|
-
return 2
|
|
3589
|
-
}
|
|
3590
|
-
process.stderr.write(`wrote ${u.path}\n`)
|
|
3591
|
-
}
|
|
3592
|
-
}
|
|
3593
|
-
else if (undefined !== check) {
|
|
3594
|
-
// RENDER AND COMPARE: a unit whose bytes differ from the file at
|
|
3595
|
-
// <dir>/<path>, or whose file is absent, is drift, listed by path.
|
|
3596
|
-
// The CI form.
|
|
3597
|
-
for (const u of report.units) {
|
|
3598
|
-
let have: string | undefined = undefined
|
|
3599
|
-
try {
|
|
3600
|
-
have = readFileSync(resolve(check, u.path), 'utf8')
|
|
3601
|
-
}
|
|
3602
|
-
catch {
|
|
3603
|
-
// Absent is drift, reported below.
|
|
3604
|
-
}
|
|
3605
|
-
if (undefined === have) {
|
|
3606
|
-
drift++
|
|
3607
|
-
process.stderr.write(`aontu: ${u.path} is missing from ${check}\n`)
|
|
3608
|
-
}
|
|
3609
|
-
else if (have !== u.text) {
|
|
3610
|
-
drift++
|
|
3611
|
-
process.stderr.write(`aontu: ${u.path} differs from the rendered unit\n`)
|
|
3612
|
-
}
|
|
3613
|
-
}
|
|
3614
|
-
}
|
|
3615
|
-
else if (coverage) {
|
|
3616
|
-
// THE COVERAGE REPORT (P7), one line per finding and a count at
|
|
3617
|
-
// the end: dead model first, then the declarations no rule
|
|
3618
|
-
// produced. A clean report is the count line alone.
|
|
3619
|
-
const cov = report.coverage as RenderCoverage
|
|
3620
|
-
for (const d of cov.dead) {
|
|
3621
|
-
process.stdout.write(`dead: ${d}\n`)
|
|
3622
|
-
}
|
|
3623
|
-
for (const u of cov.unruled) {
|
|
3624
|
-
process.stdout.write(`unruled: ${u.unit} ${u.path}\n`)
|
|
3625
|
-
}
|
|
3626
|
-
process.stdout.write(
|
|
3627
|
-
`coverage: ${cov.read.length} path(s) read, ${cov.dead.length} ` +
|
|
3628
|
-
`no output consumed, ${cov.unruled.length} declaration(s) ` +
|
|
3629
|
-
'no rule produced\n')
|
|
3630
|
-
}
|
|
3631
|
-
else {
|
|
3632
|
-
// THE SUMMARY: one line per unit -- its path, its language and its
|
|
3633
|
-
// size -- since several units have no one text to print.
|
|
3634
|
-
for (const u of report.units) {
|
|
3635
|
-
process.stdout.write(`${u.path}\t${u.lang}\t${u.text.length} bytes\n`)
|
|
3636
|
-
}
|
|
3637
|
-
}
|
|
3638
|
-
for (const l of report.lossy) {
|
|
3639
|
-
process.stderr.write(
|
|
3640
|
-
`lossy: ${l.unit} ${l.path} tier ${l.tier} ${l.construct}: ${l.reason}\n`)
|
|
3641
|
-
}
|
|
3642
|
-
return renderExit(report, drift)
|
|
3643
|
-
}
|
|
3644
|
-
|
|
3645
|
-
// D8's exit table over a report: a refused unit path is usage (2), a
|
|
3646
|
-
// strict refusal is lossy (1), any other error is the document's (4);
|
|
3647
|
-
// drift under --check is 1.
|
|
3648
|
-
function renderExit(report: RenderReport, drift: number): number {
|
|
3649
|
-
if ('error' === report.verdict) {
|
|
3650
|
-
const errors = report.errors as VetFinding[]
|
|
3651
|
-
if (errors.every((f) => 'render_path' === f.code)) {
|
|
3652
|
-
return 2
|
|
3653
|
-
}
|
|
3654
|
-
if (errors.every((f) => 'render_strict' === f.code)) {
|
|
3655
|
-
return 1
|
|
3656
|
-
}
|
|
3657
|
-
return 4
|
|
3658
|
-
}
|
|
3659
|
-
return 0 < drift ? 1 : 0
|
|
3660
|
-
}
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
// ---------------------------------------------------------------------
|
|
3664
|
-
// THE TEMPLATE SURFACE (docs/design/TEMPLATE.0.md; RENDER.0.md P8): the
|
|
3665
|
-
// two transforms and the round trip between them. `render` reads a
|
|
3666
|
-
// template directly, by its extension; this verb is for seeing the
|
|
3667
|
-
// canonical form, for writing one by hand and sugaring it, and for the
|
|
3668
|
-
// check that keeps a committed template and its meaning in agreement.
|
|
3669
|
-
|
|
3670
|
-
const TEMPLATE_HELP =
|
|
3671
|
-
'aontu template [--resugar] [--check] [--marker <token>] <file> (try --help)'
|
|
3672
|
-
|
|
3673
|
-
function runTemplate(argv: string[]): number {
|
|
3674
3066
|
const files: string[] = []
|
|
3675
3067
|
let resugar = false
|
|
3676
3068
|
let check = false
|
|
3677
3069
|
let marker: string | undefined = undefined
|
|
3070
|
+
const profileFiles: string[] = []
|
|
3678
3071
|
|
|
3679
3072
|
for (let i = 0; i < argv.length; i++) {
|
|
3680
3073
|
const arg = argv[i]
|
|
@@ -3695,6 +3088,14 @@ function runTemplate(argv: string[]): number {
|
|
|
3695
3088
|
return 2
|
|
3696
3089
|
}
|
|
3697
3090
|
}
|
|
3091
|
+
else if ('--profile' === arg) {
|
|
3092
|
+
const pf = argv[++i]
|
|
3093
|
+
if (null == pf) {
|
|
3094
|
+
process.stderr.write('aontu: --profile needs a file\n')
|
|
3095
|
+
return 2
|
|
3096
|
+
}
|
|
3097
|
+
profileFiles.push(pf)
|
|
3098
|
+
}
|
|
3698
3099
|
else if (arg.startsWith('-')) {
|
|
3699
3100
|
process.stderr.write(
|
|
3700
3101
|
`aontu: unknown template option ${arg} (try --help)\n`)
|
|
@@ -3709,10 +3110,6 @@ function runTemplate(argv: string[]): number {
|
|
|
3709
3110
|
process.stderr.write(`aontu: template needs one file\n${TEMPLATE_HELP}\n`)
|
|
3710
3111
|
return 2
|
|
3711
3112
|
}
|
|
3712
|
-
// THE TWO ARE DIRECTIONS, NOT MODES THAT COMPOSE: `--check` reads a
|
|
3713
|
-
// template and asks whether the round trip answers it back, and
|
|
3714
|
-
// `--resugar` reads the canonical form instead. A run cannot be both
|
|
3715
|
-
// at once, because the file is one thing or the other.
|
|
3716
3113
|
if (resugar && check) {
|
|
3717
3114
|
process.stderr.write(
|
|
3718
3115
|
'aontu: template takes one of --resugar or --check\n')
|
|
@@ -3728,19 +3125,15 @@ function runTemplate(argv: string[]): number {
|
|
|
3728
3125
|
return 2
|
|
3729
3126
|
}
|
|
3730
3127
|
|
|
3731
|
-
const
|
|
3128
|
+
const declared = loadProfiles(profileFiles, trust)
|
|
3129
|
+
if ('number' === typeof declared) {
|
|
3130
|
+
return declared
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
const mark = marker ?? markerFromProfiles(declared, files[0]) ??
|
|
3134
|
+
markerFor(files[0])
|
|
3732
3135
|
|
|
3733
3136
|
if (check) {
|
|
3734
|
-
// THE ROUND TRIP IS THE CHECK (D6): the file held to the spelling
|
|
3735
|
-
// the two transforms answer. What that names is a marker line the
|
|
3736
|
-
// transform would not have written -- one without its space, or one
|
|
3737
|
-
// whose aontu is indented after the marker rather than before it,
|
|
3738
|
-
// since the marker keeps its own indentation. It does NOT name a
|
|
3739
|
-
// changed body line: a template's whitespace is output, so a
|
|
3740
|
-
// trimmed trailing space is still a valid template and it is
|
|
3741
|
-
// `render --check` against the committed files that catches it.
|
|
3742
|
-
// The first line that differs is the report, since a whole diff of
|
|
3743
|
-
// a generator is the file again.
|
|
3744
3137
|
const back = resugarTemplate(desugarTemplate(src, mark), mark)
|
|
3745
3138
|
if (back === src) {
|
|
3746
3139
|
return 0
|
|
@@ -3751,12 +3144,6 @@ function runTemplate(argv: string[]): number {
|
|
|
3751
3144
|
while (n < want.length && n < have.length && want[n] === have[n]) {
|
|
3752
3145
|
n++
|
|
3753
3146
|
}
|
|
3754
|
-
// THE TWO ARE THE SAME LENGTH, always: each transform maps one
|
|
3755
|
-
// line to one line and applies the same trailing-newline rule, so
|
|
3756
|
-
// `back` has as many lines as `src`. The loop above therefore stops
|
|
3757
|
-
// at a real difference rather than by running out of either -- an
|
|
3758
|
-
// equal prefix all the way to the end IS `back === src`, which
|
|
3759
|
-
// returned above. So both indexes are in range here.
|
|
3760
3147
|
process.stderr.write(
|
|
3761
3148
|
`aontu: ${files[0]}:${n + 1} is not what the round trip answers\n` +
|
|
3762
3149
|
` have: ${JSON.stringify(have[n])}\n` +
|
|
@@ -3770,13 +3157,43 @@ function runTemplate(argv: string[]): number {
|
|
|
3770
3157
|
}
|
|
3771
3158
|
|
|
3772
3159
|
|
|
3773
|
-
//
|
|
3774
|
-
//
|
|
3775
|
-
//
|
|
3776
|
-
//
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3160
|
+
// The profiles named by --profile, vetted, or the exit code that says
|
|
3161
|
+
// why not. A profile is a language declared as data: `template` and
|
|
3162
|
+
// `fmt` match one to a file by the extensions its `template.ext`
|
|
3163
|
+
// names.
|
|
3164
|
+
function loadProfiles(
|
|
3165
|
+
profileFiles: string[], trust: TrustArg
|
|
3166
|
+
): any[] | number {
|
|
3167
|
+
const profiles: any[] = []
|
|
3168
|
+
const langs = new Map<string, string>()
|
|
3169
|
+
for (const pf of profileFiles) {
|
|
3170
|
+
let text: string
|
|
3171
|
+
try {
|
|
3172
|
+
text = readFileSync(pf, 'utf8')
|
|
3173
|
+
}
|
|
3174
|
+
catch (err: any) {
|
|
3175
|
+
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
3176
|
+
return 2
|
|
3177
|
+
}
|
|
3178
|
+
const loaded = loadProfile(text,
|
|
3179
|
+
{ path: resolve(pf), ...verbOpts(trust, entryRootOf(pf)) })
|
|
3180
|
+
if (undefined !== loaded.errors) {
|
|
3181
|
+
process.stderr.write(loaded.errors.map(renderFinding).join('\n') + '\n')
|
|
3182
|
+
return 4
|
|
3183
|
+
}
|
|
3184
|
+
const profile = loaded.profile
|
|
3185
|
+
const prev = langs.get(profile.lang)
|
|
3186
|
+
if (undefined !== prev) {
|
|
3187
|
+
process.stderr.write(
|
|
3188
|
+
`aontu: two profiles claim ${profile.lang}: ${prev} and ${pf}\n`)
|
|
3189
|
+
return 2
|
|
3190
|
+
}
|
|
3191
|
+
langs.set(profile.lang, pf)
|
|
3192
|
+
profiles.push(profile)
|
|
3193
|
+
}
|
|
3194
|
+
return profiles
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3780
3197
|
|
|
3781
3198
|
const HASH_HELP = 'aontu hash <file> (try --help)'
|
|
3782
3199
|
|
|
@@ -3837,14 +3254,6 @@ function runHash(argv: string[]): number {
|
|
|
3837
3254
|
const ctx = aontu.ctx({ collect: true })
|
|
3838
3255
|
const v: any = aontu.unify(src, { path: files[0] }, ctx)
|
|
3839
3256
|
if (0 < ctx.err.length || true === v?.isNil) {
|
|
3840
|
-
// A document that does not stand up on its own has no meaning to
|
|
3841
|
-
// pin, and a hash of a broken evaluation would be a pin that
|
|
3842
|
-
// silently agrees with every other broken evaluation.
|
|
3843
|
-
// WHY it does not stand up, not just that it does not: the same
|
|
3844
|
-
// diagnosis `aontu <file>` prints (the review's finding F).
|
|
3845
|
-
// evalFailure unconditionally, as every other call site does: it
|
|
3846
|
-
// owns the "ctx.err is never empty here" contract, and a guard
|
|
3847
|
-
// that pretends otherwise is a dead arm asserting nothing.
|
|
3848
3257
|
process.stderr.write(
|
|
3849
3258
|
`aontu: ${files[0]} does not evaluate on its own; nothing to hash\n` +
|
|
3850
3259
|
renderFinding(evalFailure(ctx)) + '\n')
|
|
@@ -3863,13 +3272,6 @@ function runHash(argv: string[]): number {
|
|
|
3863
3272
|
}
|
|
3864
3273
|
|
|
3865
3274
|
|
|
3866
|
-
// ---------------------------------------------------------------------
|
|
3867
|
-
// The query surface (G7 phase 1): one node of an evaluated document,
|
|
3868
|
-
// selected by path and rendered. Evaluation is still GLOBAL -- what
|
|
3869
|
-
// `get` buys is the size of the ANSWER, not the cost of producing it --
|
|
3870
|
-
// and the projections are lattice abstractions, each a valid Aontu
|
|
3871
|
-
// document that subsumes the truth it summarises.
|
|
3872
|
-
|
|
3873
3275
|
const GET_HELP = 'aontu get <path> <file> (try --help)'
|
|
3874
3276
|
|
|
3875
3277
|
function runGet(argv: string[]): number {
|
|
@@ -4057,11 +3459,6 @@ function runWhy(argv: string[]): number {
|
|
|
4057
3459
|
}
|
|
4058
3460
|
|
|
4059
3461
|
|
|
4060
|
-
// One contribution per line, numbered in source order, each with what
|
|
4061
|
-
// was written, where, and how it got here. A siteless contribution
|
|
4062
|
-
// prints no location rather than a `-1:-1` that means nothing —
|
|
4063
|
-
// exported for the direct test, because the site SHAPE allows one
|
|
4064
|
-
// while no document has yet produced one (ADR-002).
|
|
4065
3462
|
function renderWhyText(record: WhyRecord): string {
|
|
4066
3463
|
const head = `${record.path} = ${record.value}`
|
|
4067
3464
|
if (0 === record.conjuncts.length) {
|
|
@@ -4079,13 +3476,6 @@ function renderWhyText(record: WhyRecord): string {
|
|
|
4079
3476
|
}
|
|
4080
3477
|
|
|
4081
3478
|
|
|
4082
|
-
// ---------------------------------------------------------------------
|
|
4083
|
-
// The overlay patch verb (G7 phase 5): change a document by APPENDING
|
|
4084
|
-
// to an overlay, not by rewriting it. An overlay entry is just another
|
|
4085
|
-
// conjunct and unification is order-independent, so this needs no
|
|
4086
|
-
// rewriter — the format-preserving in-place edit is stage 2, and needs
|
|
4087
|
-
// a comment-preserving CST the parser stack does not have.
|
|
4088
|
-
|
|
4089
3479
|
const SET_HELP =
|
|
4090
3480
|
'aontu set <path>=<value> --entry <file> --overlay <file> (try --help)'
|
|
4091
3481
|
|
|
@@ -4202,29 +3592,12 @@ function runSet(argv: string[]): number {
|
|
|
4202
3592
|
}, 2) + '\n')
|
|
4203
3593
|
}
|
|
4204
3594
|
else {
|
|
4205
|
-
// A replacement is REPORTED as the edit it is, not left for the
|
|
4206
|
-
// reader to infer from a changed file: `where: what -> what`, in
|
|
4207
|
-
// source spelling, because the spelling is what changed.
|
|
4208
|
-
//
|
|
4209
|
-
// PAST TENSE ONLY WHERE IT HAPPENED. A refused write leaves the
|
|
4210
|
-
// file exactly as it was, and one assignment can be replaceable
|
|
4211
|
-
// while another makes the whole run invalid — so `replaced:` there
|
|
4212
|
-
// tells an operator the pin was changed when it was not, and unlike
|
|
4213
|
-
// `--dry-run` there is nothing else on the line to say otherwise.
|
|
4214
3595
|
const verb = wrote ? 'replaced' : 'would replace'
|
|
4215
3596
|
const edits = report.replaced.map((r) =>
|
|
4216
3597
|
`${verb}: ${r.file}:${r.row}:${r.col} ${r.from} -> ${r.to}`)
|
|
4217
3598
|
const head = [`verdict: ${report.verdict}`].concat(edits).join('\n') +
|
|
4218
3599
|
(wrote ? `\nwrote: ${overlayFile}` : dryRun ? '\n(dry run)' : '')
|
|
4219
3600
|
|
|
4220
|
-
// A SUCCESSFUL COMMAND WRITES ITS STATUS TO STDOUT, findings or
|
|
4221
|
-
// not. Routing on `findings.length` was right while every finding
|
|
4222
|
-
// this verb could produce was an ERROR; `--in-place` made a WARNING
|
|
4223
|
-
// possible, and a run that held, wrote the file and exited 0 then
|
|
4224
|
-
// sent its whole report to stderr — leaving stdout empty, so
|
|
4225
|
-
// `$(aontu set ...)` captured nothing and only the JSON form
|
|
4226
|
-
// behaved like a success. The verdict decides the stream; warnings
|
|
4227
|
-
// are diagnostics and go to stderr beside it.
|
|
4228
3601
|
const failed = 'invalid' === report.verdict || 'error' === report.verdict
|
|
4229
3602
|
const findingText = report.findings.map(renderFinding)
|
|
4230
3603
|
if (failed) {
|
|
@@ -4246,15 +3619,6 @@ function runSet(argv: string[]): number {
|
|
|
4246
3619
|
}
|
|
4247
3620
|
|
|
4248
3621
|
|
|
4249
|
-
// ---------------------------------------------------------------------
|
|
4250
|
-
// The role gate (docs/design/ALLOW.0.md): may the role the caller is
|
|
4251
|
-
// operating under modify these subtrees? Asked before `set`, by an
|
|
4252
|
-
// agent whose skill names its role, and answered from a role model
|
|
4253
|
-
// that is itself an aontu document. The verdict is the exit code, as
|
|
4254
|
-
// it is for every gate here: 0 is yes, 1 is no, 4 is "the model that
|
|
4255
|
-
// was to decide does not stand up", and an agent branches on nothing
|
|
4256
|
-
// else.
|
|
4257
|
-
|
|
4258
3622
|
const ALLOW_HELP =
|
|
4259
3623
|
'aontu allow --role <role> <roles-file> <path> [more-paths...] (try --help)'
|
|
4260
3624
|
|
|
@@ -4372,14 +3736,6 @@ function runAllow(argv: string[]): number {
|
|
|
4372
3736
|
return 2
|
|
4373
3737
|
}
|
|
4374
3738
|
|
|
4375
|
-
// A path may arrive in `set`'s spelling, `$.a.b=1`, so a skill can
|
|
4376
|
-
// hand the gate the very arguments the write will get. The text up
|
|
4377
|
-
// to the first `=` is the path, and it starts with `$`: an empty
|
|
4378
|
-
// argument, or a second file name, would otherwise read as a path
|
|
4379
|
-
// and be answered. The VALUE is checked to be one value. `set`
|
|
4380
|
-
// appends it as source after the flattened path, so a value carrying
|
|
4381
|
-
// a second pair -- `3 secrets: key: "x"` -- writes a sibling of the
|
|
4382
|
-
// overlay root, a subtree the gate was never asked about.
|
|
4383
3739
|
const paths: string[] = []
|
|
4384
3740
|
for (const arg of asked) {
|
|
4385
3741
|
const eq = arg.indexOf('=')
|
|
@@ -4535,35 +3891,23 @@ function runAgentsMd(argv: string[]): number {
|
|
|
4535
3891
|
}
|
|
4536
3892
|
|
|
4537
3893
|
|
|
4538
|
-
// Exit without truncating output.
|
|
4539
|
-
//
|
|
4540
|
-
// process.exit() terminates immediately, discarding anything still
|
|
4541
|
-
// queued on stdout. A write to a PIPE is asynchronous once it exceeds
|
|
4542
|
-
// the pipe buffer, so `write(big); exit(0)` silently truncated output at
|
|
4543
|
-
// 65536 bytes — while a write to a TTY or a file, being synchronous,
|
|
4544
|
-
// looked fine. Setting exitCode instead lets the process end naturally,
|
|
4545
|
-
// after the queue drains.
|
|
4546
|
-
//
|
|
4547
|
-
// This predates the exact leaves but they make it trivially reachable
|
|
4548
|
-
// (one long biginteger canon exceeds the buffer), and it lands squarely
|
|
4549
|
-
// on the parity-probe discipline in AGENTS.md, which derives expected
|
|
4550
|
-
// spec values by piping BOTH CLIs and comparing. A truncated pipe there
|
|
4551
|
-
// reads as a port divergence.
|
|
4552
|
-
// ---------------------------------------------------------------------
|
|
4553
|
-
// The source formatter (docs/design/FMT.0.md): one agreed form, in the
|
|
4554
|
-
// tradition of gofmt. The verb prints, lists, checks, diffs or rewrites;
|
|
4555
|
-
// the form itself is the library's (ts/src/format.ts), and the two
|
|
4556
|
-
// ports agree on it row by row in test/spec/fmt.tsv.
|
|
4557
|
-
|
|
4558
3894
|
const FMT_HELP =
|
|
4559
|
-
'aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>]
|
|
3895
|
+
'aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] ' +
|
|
3896
|
+
'[--profile <file>] <file>... (try --help)'
|
|
4560
3897
|
|
|
4561
3898
|
type FmtFlags = {
|
|
4562
3899
|
write: boolean, list: boolean, check: boolean, diff: boolean, lint: boolean, strict: boolean,
|
|
4563
3900
|
}
|
|
4564
3901
|
|
|
4565
3902
|
function runFmt(argv: string[]): number | Promise<number> {
|
|
3903
|
+
const trusted = takeTrust(argv)
|
|
3904
|
+
if (null == trusted) {
|
|
3905
|
+
return 2
|
|
3906
|
+
}
|
|
3907
|
+
argv = trusted.argv
|
|
3908
|
+
const trust = trusted.trust
|
|
4566
3909
|
const files: string[] = []
|
|
3910
|
+
const profileFiles: string[] = []
|
|
4567
3911
|
let marker: string | undefined = undefined
|
|
4568
3912
|
const flags: FmtFlags = {
|
|
4569
3913
|
write: false, list: false, check: false, diff: false, lint: false, strict: false,
|
|
@@ -4596,14 +3940,22 @@ function runFmt(argv: string[]): number | Promise<number> {
|
|
|
4596
3940
|
}
|
|
4597
3941
|
else if ('--marker' === arg) {
|
|
4598
3942
|
// THE MARKER SAYS THE FILE IS A GENERATOR, whatever its
|
|
4599
|
-
// extension: `
|
|
4600
|
-
//
|
|
3943
|
+
// extension: `fmt` and `template` take the same option for the
|
|
3944
|
+
// same reason, a language the table has never seen.
|
|
4601
3945
|
marker = argv[++i]
|
|
4602
3946
|
if (null == marker) {
|
|
4603
3947
|
process.stderr.write('aontu: --marker needs a token\n')
|
|
4604
3948
|
return 2
|
|
4605
3949
|
}
|
|
4606
3950
|
}
|
|
3951
|
+
else if ('--profile' === arg) {
|
|
3952
|
+
const pf = argv[++i]
|
|
3953
|
+
if (null == pf) {
|
|
3954
|
+
process.stderr.write('aontu: --profile needs a file\n')
|
|
3955
|
+
return 2
|
|
3956
|
+
}
|
|
3957
|
+
profileFiles.push(pf)
|
|
3958
|
+
}
|
|
4607
3959
|
else if (arg.startsWith('-')) {
|
|
4608
3960
|
process.stderr.write(`aontu: unknown fmt option ${arg} (try --help)\n`)
|
|
4609
3961
|
return 2
|
|
@@ -4629,6 +3981,11 @@ function runFmt(argv: string[]): number | Promise<number> {
|
|
|
4629
3981
|
})
|
|
4630
3982
|
}
|
|
4631
3983
|
|
|
3984
|
+
const declared = loadProfiles(profileFiles, trust)
|
|
3985
|
+
if ('number' === typeof declared) {
|
|
3986
|
+
return declared
|
|
3987
|
+
}
|
|
3988
|
+
|
|
4632
3989
|
// Several files onto standard output would be one stream nobody can
|
|
4633
3990
|
// split again (the note's X-6): the verb refuses unless an option
|
|
4634
3991
|
// says what to do with each.
|
|
@@ -4649,13 +4006,14 @@ function runFmt(argv: string[]): number | Promise<number> {
|
|
|
4649
4006
|
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
4650
4007
|
return 2
|
|
4651
4008
|
}
|
|
4652
|
-
const mark = fmtMarker(file, src,
|
|
4009
|
+
const mark = fmtMarker(file, src,
|
|
4010
|
+
marker ?? markerFromProfiles(declared, file))
|
|
4653
4011
|
if (false === mark) {
|
|
4654
4012
|
process.stderr.write(
|
|
4655
4013
|
`aontu: ${file} is not aontu source (.aon, .aontu) and carries no ` +
|
|
4656
4014
|
`${markerFor(file)} marker line, so there is no aontu in it to ` +
|
|
4657
|
-
'format; --marker names the marker for a language the table
|
|
4658
|
-
'not know\n')
|
|
4015
|
+
'format; --marker names the marker for a language the table ' +
|
|
4016
|
+
'does not know, and --profile reads one that declares it\n')
|
|
4659
4017
|
return 2
|
|
4660
4018
|
}
|
|
4661
4019
|
worst = Math.max(worst, fmtOne(file, src, flags, mark))
|
|
@@ -4663,20 +4021,6 @@ function runFmt(argv: string[]): number | Promise<number> {
|
|
|
4663
4021
|
return worst
|
|
4664
4022
|
}
|
|
4665
4023
|
|
|
4666
|
-
// WHAT A FILE IS, BY ITS EXTENSION (ADR-012's rule, and the one
|
|
4667
|
-
// `render` reads an entry by): `.aon` and `.aontu` are aontu source,
|
|
4668
|
-
// and anything else is a GENERATOR written in the target's own syntax
|
|
4669
|
-
// (docs/design/TEMPLATE.0.md), whose marker lines carry the document
|
|
4670
|
-
// this formats and whose other lines are output. `undefined` is aontu,
|
|
4671
|
-
// a string is the generator's marker, and `false` is neither.
|
|
4672
|
-
//
|
|
4673
|
-
// A FILE WITH NO MARKER LINE IN IT IS NEITHER, and that is what keeps
|
|
4674
|
-
// FMT.0.md §9's boundary where it stood: a `.json`, `.yaml` or `.toml`
|
|
4675
|
-
// include is another language's file, and reading one as a generator
|
|
4676
|
-
// would answer it back unchanged having understood none of it. The
|
|
4677
|
-
// marker is the evidence that a file was written to carry aontu at
|
|
4678
|
-
// all. `--marker` says so outright, and then the file is a generator
|
|
4679
|
-
// whatever it is called.
|
|
4680
4024
|
function fmtMarker(
|
|
4681
4025
|
file: string, src: string, marker: string | undefined): string | undefined | false {
|
|
4682
4026
|
if (undefined !== marker) {
|
|
@@ -4738,26 +4082,13 @@ function fmtOne(
|
|
|
4738
4082
|
}
|
|
4739
4083
|
|
|
4740
4084
|
|
|
4741
|
-
// ---------------------------------------------------------------------
|
|
4742
|
-
// The servers as verbs: `aontu lsp` runs the language server and
|
|
4743
|
-
// `aontu mcp` the MCP server over the CLI's own streams, so the one
|
|
4744
|
-
// command on PATH is the editor's and the agent's server too, and a
|
|
4745
|
-
// version manager (docs/design/ENV.0.md) has one thing to resolve. The
|
|
4746
|
-
// standalone bins (aontu-lsp, aontu-mcp) run the same functions. Each
|
|
4747
|
-
// server owns its exit; the CLI only dispatches. The pair is a
|
|
4748
|
-
// parameter of main so that a test can see the dispatch without a
|
|
4749
|
-
// server taking the test's own stdin.
|
|
4750
|
-
|
|
4751
4085
|
type Servers = {
|
|
4752
4086
|
lsp: () => void
|
|
4753
4087
|
mcp: (argv: string[]) => void
|
|
4754
4088
|
}
|
|
4755
4089
|
|
|
4756
|
-
//
|
|
4757
|
-
//
|
|
4758
|
-
// cli.test.ts run each through a child process instead, so these two
|
|
4759
|
-
// lines are excluded from the in-process count, as the stdio wiring
|
|
4760
|
-
// of lsp-server.ts is.
|
|
4090
|
+
// Excluded: the real pair takes the process stdio, so ts/test/cli.test.ts
|
|
4091
|
+
// drives each server through a child process instead.
|
|
4761
4092
|
/* node:coverage ignore next 4 */
|
|
4762
4093
|
const SERVERS: Servers = {
|
|
4763
4094
|
lsp: () => void lspMain(),
|
|
@@ -4804,20 +4135,6 @@ function parseTrustArg(value: string): TrustArg | undefined {
|
|
|
4804
4135
|
}
|
|
4805
4136
|
|
|
4806
4137
|
|
|
4807
|
-
// THE TEACHING PACK, SERVED FROM THE COMMAND (G11 phase 1,
|
|
4808
|
-
// docs/capability-review/g11-agent-onramp.md; mirrors
|
|
4809
|
-
// go/cmd/aontu/help.go).
|
|
4810
|
-
//
|
|
4811
|
-
// HELP documents the TOOLCHAIN and says nothing about the LANGUAGE:
|
|
4812
|
-
// `&`, the map template and the one construct an ontology cannot be
|
|
4813
|
-
// written without, occurs zero times in it, while `template` occurs
|
|
4814
|
-
// fourteen times and names an unrelated verb every time. docs/skill/
|
|
4815
|
-
// was already the right content and already gated; the gap was
|
|
4816
|
-
// DELIVERY, since it reached an installation as
|
|
4817
|
-
// node_modules/aontu/skill/ where nothing looks. ts/src/helpdoc.ts is
|
|
4818
|
-
// generated from those sources by ts/scripts/helpdoc.cjs and asserted
|
|
4819
|
-
// byte-identical with them by ts/test/helpdoc.test.ts.
|
|
4820
|
-
|
|
4821
4138
|
const HELP_VERB_HELP = 'aontu help [topic] (try `aontu help` for the topics)'
|
|
4822
4139
|
const EXPLAIN_HELP = 'aontu explain <code> (try `aontu explain --list`)'
|
|
4823
4140
|
|
|
@@ -4903,23 +4220,6 @@ function runHelp(argv: string[]): number {
|
|
|
4903
4220
|
}
|
|
4904
4221
|
|
|
4905
4222
|
|
|
4906
|
-
// `aontu explain <code>` (G11 phase 3; mirrors
|
|
4907
|
-
// go/cmd/aontu/explain.go).
|
|
4908
|
-
//
|
|
4909
|
-
// THE REGISTRY IS THE LIST, NOT THE HINT TABLE. test/spec/errcodes.tsv
|
|
4910
|
-
// registers 157 codes and the spec suite asserts set equality between
|
|
4911
|
-
// the file and codeClasses IN BOTH PORTS, so listing from codeClasses
|
|
4912
|
-
// is listing the shared contract. The hint tables are smaller and are
|
|
4913
|
-
// NOT in parity -- 130 entries here against 131 in Go, the extra being
|
|
4914
|
-
// decimal_syntax, which this port never raises -- so listing from them
|
|
4915
|
-
// would make `aontu explain --list` differ between ports over a
|
|
4916
|
-
// difference that is not about what either port can report.
|
|
4917
|
-
//
|
|
4918
|
-
// A REGISTERED CODE WITH NO HINT ANSWERS WITH ITS CLASS AND SAYS SO.
|
|
4919
|
-
// Twenty-seven registered codes carry no explanation text here; before
|
|
4920
|
-
// this verb their absence was invisible, because a hint is only ever
|
|
4921
|
-
// seen beside the error that raises it.
|
|
4922
|
-
|
|
4923
4223
|
// The dynamic prefixes a generated code extends (`func:upper`,
|
|
4924
4224
|
// `op[+]`). Mirrors CODE_PREFIXES in ts/src/hints.ts, which is not
|
|
4925
4225
|
// exported; a code that extends one is registered through its prefix
|
|
@@ -5062,21 +4362,6 @@ function runExplain(argv: string[]): number {
|
|
|
5062
4362
|
}
|
|
5063
4363
|
|
|
5064
4364
|
|
|
5065
|
-
// `aontu init` (G11 phase 6,
|
|
5066
|
-
// docs/capability-review/g11-agent-onramp.md).
|
|
5067
|
-
//
|
|
5068
|
-
// NOT SCAFFOLDING CONVENIENCE. The agent's most expensive failure is
|
|
5069
|
-
// writing a FIRST document at all: the measurement that opened G11
|
|
5070
|
-
// found one reaching for the wildcard its neighbours use and getting
|
|
5071
|
-
// `verdict: valid` over data that violates it. A known-good starting
|
|
5072
|
-
// document turns generation into editing, which is the operation a
|
|
5073
|
-
// model is reliably good at.
|
|
5074
|
-
//
|
|
5075
|
-
// The trio is real, runnable and tested where it lives
|
|
5076
|
-
// (docs/skill/init/, run by ts/test/helpdoc.test.ts), and staged into
|
|
5077
|
-
// both ports by the same generator that stages the teaching pack, so
|
|
5078
|
-
// the two write the same bytes.
|
|
5079
|
-
|
|
5080
4365
|
const INIT_HELP = 'aontu init [dir] (try --help)'
|
|
5081
4366
|
|
|
5082
4367
|
|
|
@@ -5101,10 +4386,6 @@ function runInit(argv: string[]): number {
|
|
|
5101
4386
|
}
|
|
5102
4387
|
const dir = dirs[0] ?? '.'
|
|
5103
4388
|
|
|
5104
|
-
// REFUSES TO OVERWRITE, and checks every member BEFORE writing any of
|
|
5105
|
-
// them: a scaffold that wrote two files and then refused the third
|
|
5106
|
-
// would leave a directory in a state neither the caller nor a re-run
|
|
5107
|
-
// can reason about.
|
|
5108
4389
|
const standing = INITDOC.filter((f) => existsSync(join(dir, f.name)))
|
|
5109
4390
|
if (0 < standing.length) {
|
|
5110
4391
|
process.stderr.write(
|
|
@@ -5134,17 +4415,11 @@ function runInit(argv: string[]): number {
|
|
|
5134
4415
|
}
|
|
5135
4416
|
|
|
5136
4417
|
|
|
5137
|
-
// EVERY VERB THIS PORT DISPATCHES, for the nearest-verb suggestion
|
|
5138
|
-
// G11 phase 2 prints. A separate list from the if-chain in main()
|
|
5139
|
-
// because the chain's arms have three different shapes and cannot be
|
|
5140
|
-
// a table; ts/test/cli-help.test.ts keeps the two from drifting by
|
|
5141
|
-
// running each name and requiring it not to fall through to the bare
|
|
5142
|
-
// command.
|
|
5143
4418
|
const KNOWN_VERBS = [
|
|
5144
4419
|
'agentsmd', 'allow', 'breaking', 'explain', 'fmt', 'get', 'hash',
|
|
5145
4420
|
'help', 'init', 'jsonschema', 'lsp', 'mcp', 'mod', 'reaches',
|
|
5146
|
-
'relations', '
|
|
5147
|
-
'view', 'why',
|
|
4421
|
+
'relations', 'set', 'subsume', 'template', 'trace', 'trim',
|
|
4422
|
+
'vet', 'view', 'why',
|
|
5148
4423
|
]
|
|
5149
4424
|
|
|
5150
4425
|
|
|
@@ -5159,13 +4434,6 @@ function looksLikeVerb(arg: string): boolean {
|
|
|
5159
4434
|
}
|
|
5160
4435
|
|
|
5161
4436
|
|
|
5162
|
-
// NEAREST-VERB SUGGESTION (G11 phase 2). Restricted
|
|
5163
|
-
// Damerau-Levenshtein with a cap that grows with the word and stops at
|
|
5164
|
-
// three: one edit is a convincing suggestion on any length, three is
|
|
5165
|
-
// the most that can be believed on a long one, and an UNCAPPED
|
|
5166
|
-
// nearest match on a three-letter typo names something unrelated with
|
|
5167
|
-
// confidence. Mirrors go/cmd/aontu/help.go, including the sort, so
|
|
5168
|
-
// the two ports suggest the same verb on a tie.
|
|
5169
4437
|
function nearestVerb(word: string, verbs: string[]): string {
|
|
5170
4438
|
let best = ''
|
|
5171
4439
|
let bestDist = Infinity
|
|
@@ -5181,8 +4449,6 @@ function nearestVerb(word: string, verbs: string[]): string {
|
|
|
5181
4449
|
}
|
|
5182
4450
|
|
|
5183
4451
|
|
|
5184
|
-
// Levenshtein with a transposition, iterative over two rows. Mirrors
|
|
5185
|
-
// editDistance in go/cmd/aontu/help.go exactly.
|
|
5186
4452
|
function editDistance(a: string, b: string): number {
|
|
5187
4453
|
const ar = [...a]
|
|
5188
4454
|
const br = [...b]
|
|
@@ -5209,12 +4475,6 @@ function editDistance(a: string, b: string): number {
|
|
|
5209
4475
|
|
|
5210
4476
|
|
|
5211
4477
|
function main(argv: string[], servers: Servers = SERVERS): void {
|
|
5212
|
-
// COLOUR OFF WHEN THE DESTINATION IS NOT A TERMINAL. Error frames
|
|
5213
|
-
// hardcoded their ANSI escapes, so a piped report and a `--jsonl`
|
|
5214
|
-
// answer carried terminal control codes into whatever read them (the
|
|
5215
|
-
// review's finding F). `NO_COLOR` is honoured by the library itself;
|
|
5216
|
-
// only the CLI can see whether its stderr is a terminal, so only the
|
|
5217
|
-
// CLI can make this call. `undefined` means "leave it to NO_COLOR".
|
|
5218
4478
|
setColor(true === process.stderr.isTTY ? undefined : false)
|
|
5219
4479
|
|
|
5220
4480
|
let mode: Mode = 'json'
|
|
@@ -5222,13 +4482,6 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
5222
4482
|
// reads exactly what it always read, and a caller that asks for json
|
|
5223
4483
|
// gets the failure in the finding shape every other verb reports.
|
|
5224
4484
|
let format: EvalFormat = 'text'
|
|
5225
|
-
// A LIST, though the bare command evaluates exactly one document.
|
|
5226
|
-
// It used to be one variable and the last argument won, which made a
|
|
5227
|
-
// MISTYPED VERB a silent success: `aontu vet2 schema.aon good.json`
|
|
5228
|
-
// printed good.json and exited 0, because `vet2` matched no
|
|
5229
|
-
// subcommand, fell through to this loop as a file name, and was
|
|
5230
|
-
// overwritten twice. In a tool loop that reads as a passing
|
|
5231
|
-
// validation. Counting them is what lets the refusal below happen.
|
|
5232
4485
|
const files: string[] = []
|
|
5233
4486
|
let trust: TrustArg = { kind: 'system-warn', textExt: [] }
|
|
5234
4487
|
let textExt: string[] = []
|
|
@@ -5238,15 +4491,6 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
5238
4491
|
// mode the REPL already has.
|
|
5239
4492
|
let jsonl = false
|
|
5240
4493
|
|
|
5241
|
-
// Subcommand dispatch, and deliberately only for a FIRST argument:
|
|
5242
|
-
// `aontu vet` is the verb, while `aontu somefile vet` keeps meaning
|
|
5243
|
-
// what it always did. A file named `vet` is still reachable as
|
|
5244
|
-
// `aontu ./vet`.
|
|
5245
|
-
//
|
|
5246
|
-
// Promise.resolve either way: a non-watch run returns its exit class
|
|
5247
|
-
// synchronously (and has already written its report), while `--watch`
|
|
5248
|
-
// resolves only when the watch ends — so one await-shaped line serves
|
|
5249
|
-
// both without a branch to keep covered.
|
|
5250
4494
|
if ('vet' === argv[2]) {
|
|
5251
4495
|
return void Promise.resolve(runVet(argv.slice(3))).then(finish)
|
|
5252
4496
|
}
|
|
@@ -5316,14 +4560,15 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
5316
4560
|
return finish(runJsonSchema(argv.slice(3)))
|
|
5317
4561
|
}
|
|
5318
4562
|
|
|
5319
|
-
if ('render' === argv[2]) {
|
|
5320
|
-
return finish(runRender(argv.slice(3)))
|
|
5321
|
-
}
|
|
5322
4563
|
|
|
5323
4564
|
if ('template' === argv[2]) {
|
|
5324
4565
|
return finish(runTemplate(argv.slice(3)))
|
|
5325
4566
|
}
|
|
5326
4567
|
|
|
4568
|
+
if ('trace' === argv[2]) {
|
|
4569
|
+
return finish(runTrace(argv.slice(3)))
|
|
4570
|
+
}
|
|
4571
|
+
|
|
5327
4572
|
if ('reaches' === argv[2]) {
|
|
5328
4573
|
return finish(runReaches(argv.slice(3)))
|
|
5329
4574
|
}
|
|
@@ -5403,14 +4648,6 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
5403
4648
|
}
|
|
5404
4649
|
}
|
|
5405
4650
|
|
|
5406
|
-
// ONE DOCUMENT. The bare form has always been `aontu [options]
|
|
5407
|
-
// [file]`, singular, and anything past the first was silently
|
|
5408
|
-
// discarded rather than refused -- so every way of getting the verb
|
|
5409
|
-
// wrong (a typo, a verb this port does not have, a verb spelled for
|
|
5410
|
-
// another tool) ended in a plausible answer about the wrong file.
|
|
5411
|
-
// Exit 2, the usage class, and the message names the cause rather
|
|
5412
|
-
// than the symptom: nothing here can tell a mistyped verb from a
|
|
5413
|
-
// second file, but the reader can.
|
|
5414
4651
|
if (1 < files.length) {
|
|
5415
4652
|
process.stderr.write(
|
|
5416
4653
|
`aontu: the bare command evaluates one document, and ${files.length}` +
|
|
@@ -5419,9 +4656,6 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
5419
4656
|
return finish(2)
|
|
5420
4657
|
}
|
|
5421
4658
|
|
|
5422
|
-
// The extensions ride with the capability from here on, so the three
|
|
5423
|
-
// entry shapes below (file, REPL, stdin) each get them by threading
|
|
5424
|
-
// the one value they already thread.
|
|
5425
4659
|
trust = { ...trust, textExt }
|
|
5426
4660
|
|
|
5427
4661
|
const file = files[0]
|
|
@@ -5438,7 +4672,7 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
5438
4672
|
else {
|
|
5439
4673
|
runStdin(mode, format, trust).then((code) => finish(code))
|
|
5440
4674
|
}
|
|
5441
|
-
} /* node:coverage ignore next
|
|
4675
|
+
} /* node:coverage ignore next 21 */
|
|
5442
4676
|
|
|
5443
4677
|
|
|
5444
4678
|
// No require.main guard here: bin/aontu.js is the executable entry and
|
|
@@ -5450,8 +4684,8 @@ export {
|
|
|
5450
4684
|
runReaches,
|
|
5451
4685
|
runView,
|
|
5452
4686
|
runJsonSchema,
|
|
5453
|
-
runRender,
|
|
5454
4687
|
runTemplate,
|
|
4688
|
+
runTrace,
|
|
5455
4689
|
runMod,
|
|
5456
4690
|
runHash, runGet, runHelp, runExplain, runInit, nearestVerb,
|
|
5457
4691
|
looksLikeVerb,
|