aontu 0.61.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/agentsmd.d.ts +1 -0
- package/dist/agentsmd.js +7 -28
- package/dist/agentsmd.js.map +1 -1
- package/dist/alias.js.map +1 -1
- package/dist/allow.d.ts +23 -0
- package/dist/allow.js +138 -0
- package/dist/allow.js.map +1 -0
- package/dist/aontu.d.ts +4 -2
- package/dist/aontu.js +4 -80
- package/dist/aontu.js.map +1 -1
- package/dist/aontumodel.d.ts +4 -0
- package/dist/aontumodel.js +33 -0
- package/dist/aontumodel.js.map +1 -0
- package/dist/cli.d.ts +10 -1
- package/dist/cli.js +877 -479
- 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 +55 -189
- 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.d.ts +16 -0
- package/dist/helpdoc.js +59 -0
- package/dist/helpdoc.js.map +1 -0
- package/dist/hints.d.ts +0 -6
- package/dist/hints.js +59 -47
- 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 +32 -877
- package/dist/lang.js.map +1 -1
- package/dist/lower.d.ts +3 -0
- package/dist/lower.js +14 -61
- package/dist/lower.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 -113
- package/dist/mcp.js.map +1 -1
- package/dist/mod-tool.js +0 -130
- 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/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.d.ts +2 -0
- package/dist/relation.js +3 -58
- package/dist/relation.js.map +1 -1
- package/dist/render.js +33 -143
- package/dist/render.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/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 +12 -242
- 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 +20 -0
- package/dist/val/CmpFuncVal.js +188 -0
- package/dist/val/CmpFuncVal.js.map +1 -0
- 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.d.ts +1 -2
- package/dist/val/EachFuncVal.js +12 -29
- 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 +11 -6
- 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 +2 -36
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/NomFuncVal.d.ts +12 -0
- package/dist/val/NomFuncVal.js +153 -0
- package/dist/val/NomFuncVal.js.map +1 -0
- 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 +0 -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.d.ts +12 -0
- package/dist/val/TranslateFuncVal.js +99 -0
- package/dist/val/TranslateFuncVal.js.map +1 -0
- package/dist/val/UpperFuncVal.js +11 -6
- 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.d.ts +3 -0
- package/dist/val/caserange.js +49 -0
- package/dist/val/caserange.js.map +1 -0
- 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.d.ts +12 -0
- package/dist/vet.js +159 -412
- 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 +9 -7
- package/grammar/aontu.gbnf +5 -5
- package/grammar/aontu.lark +5 -5
- package/grammar/aontu.tmLanguage.json +1 -1
- package/package.json +4 -2
- package/skill/SKILL.md +8 -0
- package/skill/init/check.sh +28 -0
- package/skill/init/data.aon +12 -0
- package/skill/init/model.aon +19 -0
- package/skill/tasks.md +151 -0
- package/src/agentsmd.ts +8 -32
- package/src/alias.ts +0 -39
- package/src/allow.ts +221 -0
- package/src/aontu.ts +10 -108
- package/src/aontumodel.ts +32 -0
- package/src/cli.ts +1009 -540
- 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 +63 -234
- package/src/grammar.ts +72 -0
- package/src/graph.ts +0 -61
- package/src/hcanon.ts +0 -82
- package/src/helpdoc.ts +77 -0
- package/src/hints.ts +72 -49
- package/src/jsonschema.ts +0 -123
- package/src/keyorder.ts +0 -42
- package/src/lang.ts +39 -895
- package/src/lower.ts +15 -65
- 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 -130
- package/src/mod-tool.ts +0 -158
- package/src/mod.ts +0 -178
- package/src/patch.ts +0 -232
- package/src/provenance.ts +0 -183
- package/src/query.ts +0 -84
- package/src/reach.ts +0 -53
- package/src/relation.ts +7 -71
- package/src/render.ts +33 -180
- 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/trim.ts +0 -53
- package/src/type.ts +2 -45
- package/src/unify.ts +13 -251
- 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 +249 -0
- 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 +12 -53
- 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 +12 -7
- 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 +2 -36
- package/src/val/NomFuncVal.ts +200 -0
- 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 +0 -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 +132 -0
- package/src/val/UpperFuncVal.ts +12 -7
- 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 +53 -0
- 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 +209 -504
- package/src/view.ts +0 -507
- package/src/walk.ts +0 -41
- package/dist/std.d.ts +0 -3
- package/dist/std.js +0 -637
- package/dist/std.js.map +0 -1
- package/dist/val/FormFuncVal.js +0 -55
- package/dist/val/FormFuncVal.js.map +0 -1
- package/src/std.ts +0 -648
- package/src/val/FormFuncVal.ts +0 -119
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.vetWaiter = void 0;
|
|
4
|
+
exports.vetWaiter = exports.KNOWN_VERBS = void 0;
|
|
5
5
|
exports.replCommand = replCommand;
|
|
6
6
|
exports.evalSource = evalSource;
|
|
7
7
|
exports.main = main;
|
|
@@ -18,21 +18,20 @@ exports.runTemplate = runTemplate;
|
|
|
18
18
|
exports.runMod = runMod;
|
|
19
19
|
exports.runHash = runHash;
|
|
20
20
|
exports.runGet = runGet;
|
|
21
|
+
exports.runHelp = runHelp;
|
|
22
|
+
exports.runExplain = runExplain;
|
|
23
|
+
exports.runInit = runInit;
|
|
24
|
+
exports.nearestVerb = nearestVerb;
|
|
25
|
+
exports.looksLikeVerb = looksLikeVerb;
|
|
21
26
|
exports.runWhy = runWhy;
|
|
22
27
|
exports.renderWhyText = renderWhyText;
|
|
23
28
|
exports.runSet = runSet;
|
|
29
|
+
exports.runAllow = runAllow;
|
|
24
30
|
exports.runAgentsMd = runAgentsMd;
|
|
25
31
|
exports.runFmt = runFmt;
|
|
26
32
|
exports.watchChange = watchChange;
|
|
27
33
|
exports.watchSignature = watchSignature;
|
|
28
34
|
exports.deprecatedAt = deprecatedAt;
|
|
29
|
-
// Command-line interface for Aontu.
|
|
30
|
-
//
|
|
31
|
-
// aontu [options] [file]
|
|
32
|
-
//
|
|
33
|
-
// With a file argument, the file is evaluated and the result printed.
|
|
34
|
-
// With no file on an interactive terminal, a REPL is started. With no
|
|
35
|
-
// file and piped input, the source is read from stdin. See HELP below.
|
|
36
35
|
// Named imports, not `import * as`: the namespace form makes tsc emit the
|
|
37
36
|
const query_1 = require("./query");
|
|
38
37
|
// __importStar downlevel helper, whose branches no supported Node takes.
|
|
@@ -55,6 +54,9 @@ const view_1 = require("./view");
|
|
|
55
54
|
const agentsmd_1 = require("./agentsmd");
|
|
56
55
|
const format_1 = require("./format");
|
|
57
56
|
const utility_1 = require("./utility");
|
|
57
|
+
const helpdoc_1 = require("./helpdoc");
|
|
58
|
+
const hints_1 = require("./hints");
|
|
59
|
+
const keyorder_1 = require("./keyorder");
|
|
58
60
|
const HELP = `Usage: aontu [options] [file]
|
|
59
61
|
aontu vet [options] <schema> <data> [more-data...]
|
|
60
62
|
aontu subsume [options] <general> <specific>
|
|
@@ -68,14 +70,20 @@ const HELP = `Usage: aontu [options] [file]
|
|
|
68
70
|
aontu render [--at <path>] [--profile <file>]... [--unit <path>]
|
|
69
71
|
[--stdout | --out <dir> | --check <dir> | --coverage]
|
|
70
72
|
[--coverage-at <path>] [--strict] <file>
|
|
71
|
-
aontu template [--resugar] [--check] [--marker <token>]
|
|
73
|
+
aontu template [--resugar] [--check] [--marker <token>]
|
|
74
|
+
[--profile <file>] <file>
|
|
72
75
|
aontu hash [options] <file>
|
|
73
76
|
aontu mod tidy|verify|vendor|manifest [options] [dir]
|
|
74
77
|
aontu get <path> [options] <file>
|
|
75
78
|
aontu why <path> [options] <file>
|
|
76
79
|
aontu set <path>=<value>... --entry <file> --overlay <file>
|
|
77
|
-
aontu
|
|
78
|
-
aontu
|
|
80
|
+
aontu allow --role <role> [--at <path>] <roles-file> <path>...
|
|
81
|
+
aontu agentsmd [--write <AGENTS.md>] [--depth <n>] <file>
|
|
82
|
+
aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>]
|
|
83
|
+
[--profile <file>] <file>...
|
|
84
|
+
aontu help [topic] [--format text|json]
|
|
85
|
+
aontu explain <code> | --list [--format text|json]
|
|
86
|
+
aontu init [dir]
|
|
79
87
|
aontu lsp
|
|
80
88
|
aontu mcp [--root <dir>]
|
|
81
89
|
|
|
@@ -83,6 +91,34 @@ Evaluate an aontu source file and print the result as JSON.
|
|
|
83
91
|
With no file on an interactive terminal, start a REPL.
|
|
84
92
|
With no file and piped input, read the source from stdin.
|
|
85
93
|
|
|
94
|
+
NEW TO THE LANGUAGE? This page documents the TOOL. The documentation
|
|
95
|
+
of the LANGUAGE travels inside this binary, and this is how to reach it:
|
|
96
|
+
|
|
97
|
+
aontu help List the topics this binary carries
|
|
98
|
+
aontu help tasks Which verb does the job you have
|
|
99
|
+
aontu help language The whole grammar, on one page
|
|
100
|
+
aontu help examples The ladder, from plain JSON upward
|
|
101
|
+
aontu help codes What a refusal means
|
|
102
|
+
aontu help grammar The published ABNF
|
|
103
|
+
aontu explain <code> What one error code a report carries means
|
|
104
|
+
aontu explain --list Every registered code with its class
|
|
105
|
+
|
|
106
|
+
Every one of those answers with no network and no checkout. The
|
|
107
|
+
long-form documentation -- the tutorial, the language and API
|
|
108
|
+
references, the how-to guides -- is in docs/ of the repository, which
|
|
109
|
+
is where to go when the topics above are not enough; the contributor
|
|
110
|
+
and agent guide is AGENTS.md beside it.
|
|
111
|
+
|
|
112
|
+
NOTHING TO EDIT YET? aontu init [dir] writes a working model, an
|
|
113
|
+
instance of it, and the four checks to run -- so the first
|
|
114
|
+
document is an edit of something that already holds, rather than an
|
|
115
|
+
invention. It refuses to overwrite.
|
|
116
|
+
|
|
117
|
+
The one construct to know before writing anything: &: inside a map is
|
|
118
|
+
a TEMPLATE that every key of that map must satisfy. A quoted "*" is a
|
|
119
|
+
key named *, not a wildcard, and a schema written that way constrains
|
|
120
|
+
nothing while still reporting valid.
|
|
121
|
+
|
|
86
122
|
The vet verb validates data documents against a schema document and
|
|
87
123
|
reports what does not hold, as text or as a machine-readable object.
|
|
88
124
|
|
|
@@ -92,7 +128,11 @@ query between a document and its own earlier versions.
|
|
|
92
128
|
|
|
93
129
|
Options:
|
|
94
130
|
-c, --canon Print the canonical form instead of generated JSON
|
|
95
|
-
|
|
131
|
+
--format <f> text (default) or json. The json form wraps the
|
|
132
|
+
answer as {aontu, findings, ok, out}, so a failure
|
|
133
|
+
here reads like every other verb's
|
|
134
|
+
-h, --help Show this help and exit (the verbs and their flags);
|
|
135
|
+
aontu help is the LANGUAGE, and lists its own topics
|
|
96
136
|
--jsonl REPL: answer every command as one JSON line
|
|
97
137
|
-v, --version Print the version and exit
|
|
98
138
|
--trust <t> Include capability: system (default), none, or
|
|
@@ -123,12 +163,28 @@ Vet options:
|
|
|
123
163
|
--closed Refuse keys the anchor does not declare
|
|
124
164
|
--partial Residue is reported but does not fail the run
|
|
125
165
|
--max-errors <n> Cap the finding list (default 20)
|
|
166
|
+
--coverage Report what the check EXAMINED: how many data
|
|
167
|
+
leaves a schema declaration constrained, the
|
|
168
|
+
shallowest data paths none did, and the
|
|
169
|
+
declarations no data met
|
|
170
|
+
--strict-coverage --coverage, and exit 1 when the run was VACUOUS --
|
|
171
|
+
when no data leaf was constrained at all. The
|
|
172
|
+
verdict word is unchanged, so nothing that passes
|
|
173
|
+
today starts failing without this flag
|
|
174
|
+
--coverage-at <p> Measure coverage under this path of the data only
|
|
126
175
|
--format <f> text (default), json or sarif
|
|
127
176
|
--watch Re-run whenever a watched file changes
|
|
128
177
|
|
|
178
|
+
A check that examined NOTHING and a check that passed answer the same
|
|
179
|
+
without --coverage. The usual cause is a schema written with the
|
|
180
|
+
wildcard other tools use: a quoted "*" is a key NAMED *, not a
|
|
181
|
+
template, so it constrains nothing and the run still reports valid.
|
|
182
|
+
The template is &: -- see aontu help language.
|
|
183
|
+
|
|
129
184
|
Vet exit codes:
|
|
130
185
|
0 valid data unifies, and is concrete (or --partial)
|
|
131
|
-
1 invalid at least one contradiction
|
|
186
|
+
1 invalid at least one contradiction, or a vacuous run under
|
|
187
|
+
--strict-coverage
|
|
132
188
|
2 usage bad option, or a file that cannot be read
|
|
133
189
|
3 incomplete no contradiction, but the truth is not yet satisfied
|
|
134
190
|
4 error the schema is unusable on its own
|
|
@@ -264,8 +320,9 @@ up on its own, or a relation, root or path that names nothing.
|
|
|
264
320
|
Render options:
|
|
265
321
|
--at <path> Render the value at this path ($.a.b); the root by
|
|
266
322
|
default
|
|
267
|
-
--profile <file> A profile document,
|
|
268
|
-
against aontu:
|
|
323
|
+
--profile <file> A profile document, aontu: render: Lang: {lang,
|
|
324
|
+
...}, vetted against aontu:render; repeatable,
|
|
325
|
+
one per language
|
|
269
326
|
--unit <path> Render only the unit with this path
|
|
270
327
|
--stdout One unit's bytes and nothing else (with --unit when
|
|
271
328
|
the instance has several)
|
|
@@ -289,8 +346,8 @@ does not stand up or the instance is not aontu:code.
|
|
|
289
346
|
A render entry file whose extension is not .aon is a TEMPLATE: a
|
|
290
347
|
generator in the target's own syntax, whose marker lines carry aontu
|
|
291
348
|
and whose other lines are output. It is desugared before it is
|
|
292
|
-
evaluated, and
|
|
293
|
-
|
|
349
|
+
evaluated, and a language the table does not know names its marker with
|
|
350
|
+
--marker, or declares it once in a profile file that --profile reads.
|
|
294
351
|
|
|
295
352
|
Template options:
|
|
296
353
|
--resugar The file is the canonical aontu; print the template
|
|
@@ -298,7 +355,9 @@ Template options:
|
|
|
298
355
|
--check Desugar and resugar, and exit 1 if the file is not
|
|
299
356
|
what the round trip answers
|
|
300
357
|
--marker <t> The marker, when the extension does not name it
|
|
301
|
-
(default //-, and #- --- /*- by extension)
|
|
358
|
+
(default //-, and #- --- /*- <!--- by extension)
|
|
359
|
+
--profile <f> A profile file, whose template.ext names the
|
|
360
|
+
extensions it marks and template.marker the marker
|
|
302
361
|
|
|
303
362
|
The template verb prints the canonical aontu form of a generator
|
|
304
363
|
written in the target's own syntax: a marked line is aontu source, and
|
|
@@ -326,14 +385,61 @@ change contradicts a pinned value -- aontu why locates it, and
|
|
|
326
385
|
--in-place rewrites it), 2 usage, 3 incomplete, 4 the entry does not
|
|
327
386
|
stand up on its own.
|
|
328
387
|
|
|
388
|
+
Allow options:
|
|
389
|
+
--role <role> The role the caller is operating under (required)
|
|
390
|
+
--at <path> Where the roles map lives in the role model
|
|
391
|
+
(default $.roles)
|
|
392
|
+
--format <f> text (default) or json
|
|
393
|
+
|
|
394
|
+
The allow verb asks a role model whether a role may modify every one
|
|
395
|
+
of the given subtrees, and answers before the change is made. The
|
|
396
|
+
role model is an aontu document: one entry per role, each carrying
|
|
397
|
+
allow (the subtrees it may modify) and optionally deny (the ones it
|
|
398
|
+
may not), as path strings starting at $; * in a path matches any one
|
|
399
|
+
key. A path is allowed when an allow entry is at or above it, and
|
|
400
|
+
refused when a deny entry is at, above or below it, whatever the
|
|
401
|
+
order. Every path starts with $, and may be spelled as set's
|
|
402
|
+
assignment, <path>=<value>, whose value must be one value: a value
|
|
403
|
+
carrying a second pair would write a subtree the gate was not asked
|
|
404
|
+
about.
|
|
405
|
+
|
|
406
|
+
Allow exit codes: 0 allowed (every path), 1 refused (at least one
|
|
407
|
+
path, or a role the model does not declare), 2 usage, 4 the role
|
|
408
|
+
model does not stand up on its own.
|
|
409
|
+
|
|
329
410
|
Agentsmd options:
|
|
330
411
|
--write <file> Splice the stanza into this file between the
|
|
331
412
|
aontu:begin and aontu:end markers, appending them
|
|
332
413
|
when they are absent; the rest is left alone
|
|
414
|
+
--depth <n> How deep the shape line projects (default 2). Two
|
|
415
|
+
levels name the root keys and say top under them; a
|
|
416
|
+
caller that wants the fields asks for them
|
|
333
417
|
|
|
334
418
|
Agentsmd exit codes: 0 generated, 2 usage, 4 the document does not
|
|
335
419
|
stand up on its own.
|
|
336
420
|
|
|
421
|
+
Help options:
|
|
422
|
+
--format <f> text (default) or json, the topic and its text
|
|
423
|
+
|
|
424
|
+
The help verb prints the embedded teaching pack: the language, not the
|
|
425
|
+
tool. With no topic it lists them. Topics are tasks, language,
|
|
426
|
+
examples, codes and grammar; the corpus is generated from docs/skill/
|
|
427
|
+
and grammar/aontu.abnf, so it cannot drift from those sources.
|
|
428
|
+
|
|
429
|
+
Help exit codes: 0 printed, 2 an unknown topic (the topics are listed)
|
|
430
|
+
or a bad option.
|
|
431
|
+
|
|
432
|
+
Explain options:
|
|
433
|
+
--list Every registered error code with its class
|
|
434
|
+
--format <f> text (default) or json
|
|
435
|
+
|
|
436
|
+
The explain verb answers what one error code means, from the same
|
|
437
|
+
table the engine attaches to a finding. Every registered code has an
|
|
438
|
+
entry, so a code read out of a report always resolves.
|
|
439
|
+
|
|
440
|
+
Explain exit codes: 0 explained, 2 an unknown code (near matches are
|
|
441
|
+
named) or a bad option.
|
|
442
|
+
|
|
337
443
|
Fmt options:
|
|
338
444
|
-w, --write Rewrite each file in place, when its form would change
|
|
339
445
|
-l, --list Print the name of each file whose form would change
|
|
@@ -344,7 +450,9 @@ Fmt options:
|
|
|
344
450
|
shapes, on standard error, and print nothing else
|
|
345
451
|
--strict With --lint, and exit 1 when there is a finding
|
|
346
452
|
--marker <t> The file is a generator, and this is its marker
|
|
347
|
-
(default //-, and #- --- /*- by extension)
|
|
453
|
+
(default //-, and #- --- /*- <!--- by extension)
|
|
454
|
+
--profile <f> A profile file, whose template.ext names the
|
|
455
|
+
extensions it marks and template.marker the marker
|
|
348
456
|
|
|
349
457
|
The fmt verb prints one document in the agreed form; with no file it
|
|
350
458
|
reads standard input. Several files need one of the options above.
|
|
@@ -387,27 +495,58 @@ function version() {
|
|
|
387
495
|
return '0.0.0';
|
|
388
496
|
}
|
|
389
497
|
}
|
|
498
|
+
const EVAL_ANSI = new RegExp('\u001b\\[[0-9;]*m', 'g');
|
|
499
|
+
function evalFinding(code, text) {
|
|
500
|
+
return {
|
|
501
|
+
class: (0, hints_1.codeClass)(code),
|
|
502
|
+
code,
|
|
503
|
+
message: text.split('\n')[0].replace(EVAL_ANSI, ''),
|
|
504
|
+
path: '$',
|
|
505
|
+
severity: 'error',
|
|
506
|
+
sites: [],
|
|
507
|
+
};
|
|
508
|
+
}
|
|
390
509
|
// Evaluate source, returning either the rendered output or the error
|
|
391
|
-
// message. Never throws.
|
|
510
|
+
// message, and the failure in the finding shape. Never throws.
|
|
392
511
|
function evalSource(aontu, src, mode) {
|
|
393
512
|
try {
|
|
394
|
-
// exactJSON, not JSON.stringify: a document using the `0d` exact
|
|
395
|
-
// leaves generates bigints and Decimals, which JSON.stringify cannot
|
|
396
|
-
// write (D9). The CLI prints INDENTED JSON and the shared suite's
|
|
397
|
-
// `gens` mode prints COMPACT JSON, but both go through this one
|
|
398
|
-
// emitter -- an indent argument rather than a second implementation,
|
|
399
|
-
// so the two cannot drift from each other or from the Go port.
|
|
400
513
|
const text = 'canon' === mode
|
|
401
514
|
? aontu.unify(src).canon
|
|
402
515
|
: (0, aontu_1.exactJSON)(aontu.generate(src), 2);
|
|
403
|
-
return { ok: true, text };
|
|
516
|
+
return { ok: true, text, findings: [] };
|
|
404
517
|
}
|
|
405
518
|
catch (err) {
|
|
406
519
|
const msg = (err instanceof aontu_1.AontuError || true === err?.aontu)
|
|
407
520
|
? err.message
|
|
408
521
|
: String(err?.message ?? err);
|
|
409
|
-
|
|
522
|
+
const errs = 'function' === typeof err?.errs ? err.errs() : [];
|
|
523
|
+
const first = errs[0];
|
|
524
|
+
return {
|
|
525
|
+
ok: false,
|
|
526
|
+
text: msg,
|
|
527
|
+
findings: null == first ? [] : [evalFinding(first.why, msg)],
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// The bare command's answer, in the form the caller asked for. The
|
|
532
|
+
// text form is what it has always printed, on the stream the verdict
|
|
533
|
+
// chooses; `--format json` is the same answer as one object, on
|
|
534
|
+
// stdout, so a harness reads one stream and one shape either way.
|
|
535
|
+
// Mirrors emit in go/cmd/aontu/main.go.
|
|
536
|
+
function emitEval(res, format) {
|
|
537
|
+
if ('json' === format) {
|
|
538
|
+
process.stdout.write((0, aontu_1.exactJSON)({
|
|
539
|
+
aontu: { version: version(), verb: 'eval' },
|
|
540
|
+
findings: res.findings,
|
|
541
|
+
ok: res.ok,
|
|
542
|
+
out: res.ok ? res.text : '',
|
|
543
|
+
}, 2) + '\n');
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
;
|
|
547
|
+
(res.ok ? process.stdout : process.stderr).write(res.text + '\n');
|
|
410
548
|
}
|
|
549
|
+
return res.ok ? 0 : 1;
|
|
411
550
|
}
|
|
412
551
|
// The one-line warning of the staged default flip. Once per (kind,
|
|
413
552
|
// path): a fixpoint re-resolves nothing (includes load at parse), but
|
|
@@ -444,16 +583,6 @@ function trustOpts(trust, entryRoot) {
|
|
|
444
583
|
return { ...text, trustWarn: makeTrustWarn(), trustWarnRoot: entryRoot };
|
|
445
584
|
}
|
|
446
585
|
}
|
|
447
|
-
// EVERY VERB honours the include capability, not just the bare
|
|
448
|
-
// command. G5 wired `--trust`/`--include-root` to `aontu <file>` alone,
|
|
449
|
-
// so `aontu vet schema.aon data.json` -- the surface an agent actually
|
|
450
|
-
// scripts -- ran the full system resolver with no flag to confine it
|
|
451
|
-
// and no warning (use-cases/REVIEW.md finding G). The flags are
|
|
452
|
-
// stripped here, before each verb parses its own tail, so a verb only
|
|
453
|
-
// has to pass the profile on to its engine.
|
|
454
|
-
//
|
|
455
|
-
// Returns undefined when the spelling is wrong, with the message
|
|
456
|
-
// already printed: the caller answers the usage class.
|
|
457
586
|
function takeTrust(argv) {
|
|
458
587
|
const rest = [];
|
|
459
588
|
let trust = { kind: 'system-warn', textExt: [] };
|
|
@@ -542,40 +671,40 @@ function verbOpts(trust, entryRoot) {
|
|
|
542
671
|
function entryRootOf(file) {
|
|
543
672
|
return null == file ? process.cwd() : (0, node_path_1.dirname)((0, node_path_1.resolve)(file));
|
|
544
673
|
}
|
|
545
|
-
function runFile(file, mode, trust) {
|
|
674
|
+
function runFile(file, mode, format, trust) {
|
|
546
675
|
let src;
|
|
547
676
|
try {
|
|
548
677
|
src = (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
549
678
|
}
|
|
550
679
|
catch (err) {
|
|
680
|
+
if (looksLikeVerb(file)) {
|
|
681
|
+
process.stderr.write(`aontu: \`${file}\` is not a file, and not a verb this port knows\n`);
|
|
682
|
+
const near = nearestVerb(file, KNOWN_VERBS);
|
|
683
|
+
if ('' !== near) {
|
|
684
|
+
process.stderr.write(`aontu: did you mean \`aontu ${near}\`?\n`);
|
|
685
|
+
}
|
|
686
|
+
process.stderr.write('aontu: `aontu --help` lists the verbs, `aontu help` the topics\n');
|
|
687
|
+
return 2;
|
|
688
|
+
}
|
|
551
689
|
process.stderr.write(`aontu: cannot read ${file}: ${err.message}\n`);
|
|
552
690
|
return 1;
|
|
553
691
|
}
|
|
554
692
|
const path = (0, node_path_1.resolve)(file);
|
|
555
|
-
// `fs` IS WHAT MAKES A FRAME EXCERPT THE FILE IT NAMES. Without it,
|
|
556
|
-
// err.ts's resolveSrc falls back to the ENTRY text, so a frame whose
|
|
557
|
-
// arrow says `lib/types.aon:2:6` printed the entry's line 2 under it
|
|
558
|
-
// -- a real file name over another file's line, which
|
|
559
|
-
// docs/reference-api.md forbids in the same words it uses to require
|
|
560
|
-
// the name.
|
|
561
693
|
const aontu = new aontu_1.Aontu({
|
|
562
694
|
path,
|
|
563
695
|
errfs: { existsSync: node_fs_1.existsSync, readFileSync: node_fs_1.readFileSync },
|
|
564
696
|
...trustOpts(trust, (0, node_path_1.dirname)(path)),
|
|
565
697
|
});
|
|
566
|
-
|
|
567
|
-
(res.ok ? process.stdout : process.stderr).write(res.text + '\n');
|
|
568
|
-
return res.ok ? 0 : 1;
|
|
698
|
+
return emitEval(evalSource(aontu, src, mode), format);
|
|
569
699
|
}
|
|
570
|
-
function runStdin(mode, trust) {
|
|
700
|
+
function runStdin(mode, format, trust) {
|
|
571
701
|
return new Promise((resolve) => {
|
|
572
702
|
let src = '';
|
|
573
703
|
process.stdin.setEncoding('utf8');
|
|
574
704
|
process.stdin.on('data', (d) => (src += d));
|
|
575
705
|
process.stdin.on('end', () => {
|
|
576
706
|
const res = evalSource(new aontu_1.Aontu(trustOpts(trust, process.cwd())), src, mode);
|
|
577
|
-
(
|
|
578
|
-
resolve(res.ok ? 0 : 1);
|
|
707
|
+
resolve(emitEval(res, format));
|
|
579
708
|
});
|
|
580
709
|
});
|
|
581
710
|
}
|
|
@@ -696,14 +825,6 @@ function runRepl(initialMode, jsonl, trust) {
|
|
|
696
825
|
rl.prompt();
|
|
697
826
|
});
|
|
698
827
|
rl.on('close', () => {
|
|
699
|
-
// The closing newline is for a HUMAN, so it is written only for
|
|
700
|
-
// one: it moves the terminal off the prompt line that `rl` left
|
|
701
|
-
// hanging. In `--jsonl` there is no prompt, every answer already
|
|
702
|
-
// ends in its own newline, and this one appended a bare empty line
|
|
703
|
-
// to the stream -- a record that is not JSON, at the end of a
|
|
704
|
-
// protocol whose whole contract is one JSON object per line. A
|
|
705
|
-
// harness parsing every line it receives failed on it, after the
|
|
706
|
-
// commands had all succeeded. Mirrors go/cmd/aontu/repl.go.
|
|
707
828
|
if (!jsonl) {
|
|
708
829
|
process.stdout.write('\n');
|
|
709
830
|
}
|
|
@@ -713,15 +834,6 @@ function runRepl(initialMode, jsonl, trust) {
|
|
|
713
834
|
process.exitCode = 0;
|
|
714
835
|
});
|
|
715
836
|
}
|
|
716
|
-
// THE VET VERB (G2 phase 3).
|
|
717
|
-
//
|
|
718
|
-
// Exit codes are VERDICT CLASSES, not a pass/fail bit: an agent loop
|
|
719
|
-
// branches on "the data contradicts the truth" (1) differently from
|
|
720
|
-
// "the data has not supplied everything the truth requires" (3), and
|
|
721
|
-
// differently again from "the schema itself is broken" (4), which is
|
|
722
|
-
// never the data's fault. 2 stays what it already was for this CLI --
|
|
723
|
-
// the caller got the invocation wrong -- which is why an unreadable
|
|
724
|
-
// file is a 2 rather than a 4.
|
|
725
837
|
const VET_EXIT = {
|
|
726
838
|
valid: 0,
|
|
727
839
|
invalid: 1,
|
|
@@ -739,6 +851,9 @@ function parseVetArgs(argv) {
|
|
|
739
851
|
let partial = false;
|
|
740
852
|
let maxErrors;
|
|
741
853
|
let watch = false;
|
|
854
|
+
let coverage = false;
|
|
855
|
+
let strictCoverage = false;
|
|
856
|
+
let coverageAt;
|
|
742
857
|
for (let i = 0; i < argv.length; i++) {
|
|
743
858
|
const arg = argv[i];
|
|
744
859
|
// `-h`/`--help` before anything else, INCLUDING the file count:
|
|
@@ -762,14 +877,6 @@ function parseVetArgs(argv) {
|
|
|
762
877
|
format = f;
|
|
763
878
|
}
|
|
764
879
|
else if ('--max-errors' === arg) {
|
|
765
|
-
// ONE GRAMMAR, spelled the same way in both ports: decimal
|
|
766
|
-
// digits, one to nine of them, at least 1. `Number()` alone
|
|
767
|
-
// accepted `1.0`, `1e2`, `0x10` and ` 3`, which Go's parser
|
|
768
|
-
// refuses -- so the same documented invocation meant different
|
|
769
|
-
// things in the two shipped commands. The nine-digit ceiling is
|
|
770
|
-
// where the ports would part company again: beyond it Go's
|
|
771
|
-
// integer conversion saturates, and a cap nobody can reach is
|
|
772
|
-
// not worth a divergence.
|
|
773
880
|
const raw = argv[++i];
|
|
774
881
|
if (!/^[0-9]{1,9}$/.test(raw ?? '') || 1 > Number(raw)) {
|
|
775
882
|
return { err: 'aontu: --max-errors needs a positive whole number' };
|
|
@@ -782,6 +889,24 @@ function parseVetArgs(argv) {
|
|
|
782
889
|
else if ('--partial' === arg) {
|
|
783
890
|
partial = true;
|
|
784
891
|
}
|
|
892
|
+
else if ('--coverage' === arg) {
|
|
893
|
+
coverage = true;
|
|
894
|
+
}
|
|
895
|
+
else if ('--strict-coverage' === arg) {
|
|
896
|
+
// IMPLIES THE ACCOUNTING, because a gate cannot fire on what was
|
|
897
|
+
// never measured. Asking for the strict form and having to
|
|
898
|
+
// remember `--coverage` beside it is a usage trap with one
|
|
899
|
+
// correct answer, so the flag takes it.
|
|
900
|
+
coverage = true;
|
|
901
|
+
strictCoverage = true;
|
|
902
|
+
}
|
|
903
|
+
else if ('--coverage-at' === arg) {
|
|
904
|
+
coverageAt = argv[++i];
|
|
905
|
+
if (null == coverageAt) {
|
|
906
|
+
return { err: 'aontu: --coverage-at needs a path' };
|
|
907
|
+
}
|
|
908
|
+
coverage = true;
|
|
909
|
+
}
|
|
785
910
|
else if ('--watch' === arg) {
|
|
786
911
|
watch = true;
|
|
787
912
|
}
|
|
@@ -805,6 +930,9 @@ function parseVetArgs(argv) {
|
|
|
805
930
|
partial,
|
|
806
931
|
maxErrors,
|
|
807
932
|
watch,
|
|
933
|
+
coverage,
|
|
934
|
+
strictCoverage,
|
|
935
|
+
coverageAt,
|
|
808
936
|
},
|
|
809
937
|
};
|
|
810
938
|
}
|
|
@@ -826,10 +954,6 @@ function renderFinding(f) {
|
|
|
826
954
|
out.push(` actual: ${f.actual}`);
|
|
827
955
|
}
|
|
828
956
|
for (const s of f.sites) {
|
|
829
|
-
// Every site carries the canon of the value it stands for: that is
|
|
830
|
-
// what makes the two sides of a conflict readable side by side. A
|
|
831
|
-
// site's file is always a string -- empty when the value belongs to
|
|
832
|
-
// neither document -- so there is nothing to coalesce here.
|
|
833
957
|
out.push(` ${s.role}: ${s.file}:${s.row}:${s.col} (${s.value})`);
|
|
834
958
|
}
|
|
835
959
|
return out.join('\n');
|
|
@@ -837,11 +961,46 @@ function renderFinding(f) {
|
|
|
837
961
|
function renderVetText(report) {
|
|
838
962
|
const head = `verdict: ${report.verdict}` +
|
|
839
963
|
(report.truncated ? ' (findings truncated)' : '');
|
|
840
|
-
|
|
841
|
-
|
|
964
|
+
const body = 0 === report.findings.length ? []
|
|
965
|
+
: ['', ...report.findings.map(renderFinding)];
|
|
966
|
+
const cover = null == report.coverage ? []
|
|
967
|
+
: ['', ...renderVetCoverage(report.coverage)];
|
|
968
|
+
return [head, ...body, ...cover].join('\n');
|
|
969
|
+
}
|
|
970
|
+
// The coverage block (G11 phase 5). VACUOUS FIRST and in the
|
|
971
|
+
// imperative, because it is the one line that changes what the reader
|
|
972
|
+
// should do: a `valid` verdict above it means nothing.
|
|
973
|
+
function renderVetCoverage(c) {
|
|
974
|
+
const out = [];
|
|
975
|
+
if (c.vacuous) {
|
|
976
|
+
out.push('coverage: VACUOUS — no data leaf was constrained' +
|
|
977
|
+
' by the schema; this run checked nothing');
|
|
842
978
|
}
|
|
843
|
-
|
|
979
|
+
out.push(`coverage: ${c.checked}/${c.leaves} data leaves checked,` +
|
|
980
|
+
` ${c.declared} schema declarations`);
|
|
981
|
+
// The lists are the SHALLOWEST paths, so each names a subtree rather
|
|
982
|
+
// than every leaf under it, and both are capped: a report a reader
|
|
983
|
+
// scrolls past is a report nobody reads.
|
|
984
|
+
for (const [label, paths] of [
|
|
985
|
+
['unchecked', c.unchecked], ['unused', c.unused],
|
|
986
|
+
]) {
|
|
987
|
+
if (0 === paths.length) {
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
const shown = paths.slice(0, COVERAGE_LIST_MAX);
|
|
991
|
+
for (const p of shown) {
|
|
992
|
+
out.push(` ${label}: ${p}`);
|
|
993
|
+
}
|
|
994
|
+
if (shown.length < paths.length) {
|
|
995
|
+
out.push(` ${label}: … and ${paths.length - shown.length} more`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return out;
|
|
844
999
|
}
|
|
1000
|
+
// How many coverage paths the TEXT form prints per list. The JSON form
|
|
1001
|
+
// carries every one: a machine reads the whole list, a person reads the
|
|
1002
|
+
// first few and the count.
|
|
1003
|
+
const COVERAGE_LIST_MAX = 10;
|
|
845
1004
|
// The machine-readable form. `aontu` names the producer, so a report
|
|
846
1005
|
// read from a file or a pipe says which version and which verb made it
|
|
847
1006
|
// without the consumer having to know.
|
|
@@ -851,6 +1010,7 @@ function renderVetJson(report) {
|
|
|
851
1010
|
verdict: report.verdict,
|
|
852
1011
|
truncated: report.truncated,
|
|
853
1012
|
findings: report.findings,
|
|
1013
|
+
...(null == report.coverage ? {} : { coverage: report.coverage }),
|
|
854
1014
|
}, 2);
|
|
855
1015
|
}
|
|
856
1016
|
// The machine-interchange form (G2 phase 5): SARIF 2.1.0, rendered by
|
|
@@ -885,12 +1045,17 @@ function vetOnce(args, trust) {
|
|
|
885
1045
|
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
|
|
886
1046
|
return 2;
|
|
887
1047
|
}
|
|
888
|
-
// Each data file is vetted on its own, because a parsed tree is
|
|
889
|
-
// single-use (docs/reference-api.md) -- and because two data files
|
|
890
|
-
// are two candidates for the same truth, not one merged candidate.
|
|
891
1048
|
let verdict = 'valid';
|
|
892
1049
|
let truncated = false;
|
|
893
1050
|
const findings = [];
|
|
1051
|
+
let cov;
|
|
1052
|
+
// Initialised rather than left undefined: it is filled in the same
|
|
1053
|
+
// block that sets `cov`, so a fallback at the read below would be an
|
|
1054
|
+
// arm nothing can take. The FIRST file replaces it wholesale, which
|
|
1055
|
+
// is what makes the fold an intersection rather than an empty set.
|
|
1056
|
+
let unusedEvery = new Set();
|
|
1057
|
+
let unusedSeen = false;
|
|
1058
|
+
const uncheckedAll = new Set();
|
|
894
1059
|
for (const source of sources) {
|
|
895
1060
|
const report = (0, aontu_1.vet)(schemaSrc, source.src, {
|
|
896
1061
|
...verbOpts(trust, entryRootOf(args.schema)),
|
|
@@ -900,51 +1065,62 @@ function vetOnce(args, trust) {
|
|
|
900
1065
|
maxErrors: args.maxErrors,
|
|
901
1066
|
schemaUrl: args.schema,
|
|
902
1067
|
dataUrl: source.file,
|
|
903
|
-
// The paths as well as the labels: a relative `@"file"` load
|
|
904
|
-
// inside either document resolves from ITS OWN directory, the
|
|
905
|
-
// way `aontu <file>` already resolves one (runFile above). The
|
|
906
|
-
// path is passed AS TYPED, not resolved: it doubles as the
|
|
907
|
-
// label above, and a report that mixed the typed path with an
|
|
908
|
-
// absolute one would name the same file two ways.
|
|
909
1068
|
schemaPath: args.schema,
|
|
910
1069
|
dataPath: source.file,
|
|
1070
|
+
coverage: args.coverage,
|
|
1071
|
+
coverageAt: args.coverageAt,
|
|
911
1072
|
});
|
|
912
1073
|
if (VET_RANK[verdict] < VET_RANK[report.verdict]) {
|
|
913
1074
|
verdict = report.verdict;
|
|
914
1075
|
}
|
|
915
1076
|
truncated = truncated || report.truncated;
|
|
916
1077
|
findings.push(...report.findings);
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1078
|
+
if (null != report.coverage) {
|
|
1079
|
+
const c = report.coverage;
|
|
1080
|
+
cov = null == cov ? { ...c } : {
|
|
1081
|
+
checked: cov.checked + c.checked,
|
|
1082
|
+
declared: c.declared,
|
|
1083
|
+
leaves: cov.leaves + c.leaves,
|
|
1084
|
+
unchecked: [],
|
|
1085
|
+
unused: [],
|
|
1086
|
+
vacuous: false,
|
|
1087
|
+
};
|
|
1088
|
+
for (const p of c.unchecked) {
|
|
1089
|
+
uncheckedAll.add(p);
|
|
1090
|
+
}
|
|
1091
|
+
const mine = new Set(c.unused);
|
|
1092
|
+
unusedEvery = unusedSeen
|
|
1093
|
+
? new Set([...unusedEvery].filter((u) => mine.has(u))) : mine;
|
|
1094
|
+
unusedSeen = true;
|
|
1095
|
+
}
|
|
927
1096
|
if ('error' === report.verdict) {
|
|
928
1097
|
break;
|
|
929
1098
|
}
|
|
930
1099
|
}
|
|
931
|
-
// The cap is on the REPORT, not on each file. Capping every file's
|
|
932
|
-
// list and then concatenating them let `--max-errors 1` emit one
|
|
933
|
-
// finding PER FILE -- and leave `truncated` false while doing it,
|
|
934
|
-
// because no single file had been cut. The engine still caps each
|
|
935
|
-
// run, so a pathological file cannot flood the aggregate before it
|
|
936
|
-
// gets here; this is the second, honest cut.
|
|
937
1100
|
const cap = args.maxErrors ?? vet_1.VET_MAX_ERRORS;
|
|
938
1101
|
const kept = cap < findings.length ? findings.slice(0, cap) : findings;
|
|
1102
|
+
if (null != cov) {
|
|
1103
|
+
cov.unchecked = [...uncheckedAll].sort(keyorder_1.cmpCodePoint);
|
|
1104
|
+
cov.unused = [...unusedEvery].sort(keyorder_1.cmpCodePoint);
|
|
1105
|
+
cov.vacuous = 0 === cov.checked && 0 < cov.leaves;
|
|
1106
|
+
}
|
|
939
1107
|
const report = {
|
|
940
1108
|
verdict,
|
|
941
1109
|
truncated: truncated || cap < findings.length,
|
|
942
1110
|
findings: kept,
|
|
1111
|
+
...(null == cov ? {} : { coverage: cov }),
|
|
943
1112
|
};
|
|
944
1113
|
const text = 'json' === args.format ? renderVetJson(report) :
|
|
945
1114
|
'sarif' === args.format ? renderVetSarif(report) :
|
|
946
1115
|
renderVetText(report);
|
|
947
1116
|
process.stdout.write(text + '\n');
|
|
1117
|
+
if (true === args.strictCoverage && true === report.coverage?.vacuous) {
|
|
1118
|
+
process.stderr.write('aontu: no data leaf was constrained by the schema:' +
|
|
1119
|
+
' this run checked nothing\n' +
|
|
1120
|
+
'aontu: `aontu help language` — a map template is `&:`,' +
|
|
1121
|
+
' and a quoted "*" is a key named *\n');
|
|
1122
|
+
return 1;
|
|
1123
|
+
}
|
|
948
1124
|
return VET_EXIT[verdict];
|
|
949
1125
|
}
|
|
950
1126
|
// How often `--watch` polls for a change. Polling by mtime+size rather
|
|
@@ -964,19 +1140,6 @@ function watchSignature(files) {
|
|
|
964
1140
|
function sleep(ms) {
|
|
965
1141
|
return new Promise((done) => setTimeout(done, ms));
|
|
966
1142
|
}
|
|
967
|
-
// Resolve true when any watched file's signature moves off `before`.
|
|
968
|
-
// This is the real waiter: it never resolves false, so a real watch
|
|
969
|
-
// runs until the process is interrupted; tests inject their own waiter
|
|
970
|
-
// to bound the loop, and pass a short pollMs when they drive this one
|
|
971
|
-
// directly. The interval is a required argument (the command passes
|
|
972
|
-
// WATCH_POLL_MS) so there is no defaulting branch a test could never
|
|
973
|
-
// take.
|
|
974
|
-
//
|
|
975
|
-
// The BASELINE is an argument, not a snapshot taken here: the loop
|
|
976
|
-
// records it BEFORE each vet run, so a save landing between the run's
|
|
977
|
-
// reads and the wait still compares as a change. A waiter that
|
|
978
|
-
// snapshotted on entry would adopt that unvetted save as its baseline
|
|
979
|
-
// and wait indefinitely on a stale report.
|
|
980
1143
|
async function watchChange(files, before, pollMs) {
|
|
981
1144
|
for (;;) {
|
|
982
1145
|
await sleep(pollMs);
|
|
@@ -1005,9 +1168,6 @@ async function watchVet(args, wait, trust) {
|
|
|
1005
1168
|
}
|
|
1006
1169
|
return code;
|
|
1007
1170
|
}
|
|
1008
|
-
// The vet verb. Non-watch runs are synchronous and return the exit
|
|
1009
|
-
// class directly; `--watch` returns a promise that resolves only when
|
|
1010
|
-
// the waiter says stop (never, for the real one).
|
|
1011
1171
|
function runVet(argv, wait) {
|
|
1012
1172
|
const trusted = takeTrust(argv);
|
|
1013
1173
|
if (null == trusted) {
|
|
@@ -1218,30 +1378,7 @@ function parseBreakingArgs(argv) {
|
|
|
1218
1378
|
},
|
|
1219
1379
|
};
|
|
1220
1380
|
}
|
|
1221
|
-
// A source file the include resolver can actually load. `git#<rev>`
|
|
1222
|
-
// materialises these and nothing else: an include names an Aontu
|
|
1223
|
-
// document (`.aon`/`.aontu`, the two extensions `@"foo"` tries) or a
|
|
1224
|
-
// JSON one, so the rest of a revision's tree cannot be part of any
|
|
1225
|
-
// include closure and copying it would be pure cost.
|
|
1226
1381
|
const INCLUDABLE = /\.(aon|aontu|jsonic|json)$/;
|
|
1227
|
-
// Resolve one --against spelling to an old version.
|
|
1228
|
-
//
|
|
1229
|
-
// A `git#<rev>` spelling is the old version of the WHOLE TREE, not of
|
|
1230
|
-
// the entry file alone. It used to be `git show <rev>:./<file>`, whose
|
|
1231
|
-
// text was then evaluated with `generalPath`/`specificPath` pointing at
|
|
1232
|
-
// the WORKING file -- so every `@"..."` include in the old document
|
|
1233
|
-
// resolved against the working tree, and the "old" side was old entry
|
|
1234
|
-
// text meeting new includes. A breaking change inside an included file
|
|
1235
|
-
// therefore compared against itself and answered `compatible`: the
|
|
1236
|
-
// documented CI gate silently un-gated every non-entry file of the
|
|
1237
|
-
// multi-file layout real models use (use-cases/BUGS.md §26). The old
|
|
1238
|
-
// tree's includable sources are copied into a temporary directory and
|
|
1239
|
-
// the old document is evaluated from THERE.
|
|
1240
|
-
//
|
|
1241
|
-
// Sources outside the revision -- package includes under node_modules,
|
|
1242
|
-
// the bundled `std/system` -- still resolve as they do today: they are
|
|
1243
|
-
// not in the tree, and their versions travel with the lockfile rather
|
|
1244
|
-
// than with this comparison.
|
|
1245
1382
|
function oldVersion(spec, file) {
|
|
1246
1383
|
if (!spec.startsWith('git#')) {
|
|
1247
1384
|
try {
|
|
@@ -1269,23 +1406,9 @@ function oldVersion(spec, file) {
|
|
|
1269
1406
|
// that only some failures take.
|
|
1270
1407
|
const temp = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'aontu-against-'));
|
|
1271
1408
|
try {
|
|
1272
|
-
// THE REPO-RELATIVE PATH COMES FROM GIT, not from path arithmetic.
|
|
1273
|
-
// Relativising `rev-parse --show-toplevel` against `resolve(file)`
|
|
1274
|
-
// puts two DIFFERENT COORDINATE SYSTEMS on either side of the
|
|
1275
|
-
// subtraction: git prints the real path, while the caller's is
|
|
1276
|
-
// whatever they typed. On macOS a temp file under /var is
|
|
1277
|
-
// /private/var to git, and on Windows a TMP short name
|
|
1278
|
-
// (RUNNER~1) is the long form to git -- so the subtraction gave a
|
|
1279
|
-
// `../..` climb, the entry was "not in that revision", and the
|
|
1280
|
-
// documented CI spelling failed on both platforms while passing on
|
|
1281
|
-
// Linux (this PR's own CI). `--show-prefix` is the same question
|
|
1282
|
-
// asked in git's coordinates: the repo-relative directory of the
|
|
1283
|
-
// cwd, already slash-separated and already normalised.
|
|
1284
1409
|
const prefix = git(['rev-parse', '--show-prefix'], dir).trim();
|
|
1285
1410
|
const entryRel = prefix + (0, node_path_1.basename)(file);
|
|
1286
1411
|
const top = git(['rev-parse', '--show-toplevel'], dir).trim();
|
|
1287
|
-
// `-z` so a path with a newline or a quote cannot be mistaken for
|
|
1288
|
-
// two paths (git otherwise quotes such names).
|
|
1289
1412
|
const listed = git(['ls-tree', '-r', '-z', '--name-only', rev], top)
|
|
1290
1413
|
.split('\0').filter((p) => '' !== p);
|
|
1291
1414
|
if (!listed.includes(entryRel)) {
|
|
@@ -1315,13 +1438,6 @@ function oldVersion(spec, file) {
|
|
|
1315
1438
|
function policyCompat(newSrc, path, include) {
|
|
1316
1439
|
const aontu = new aontu_1.Aontu();
|
|
1317
1440
|
const ctx = aontu.ctx({ collect: true });
|
|
1318
|
-
// The declaration is read by EVALUATING the document, so this leg
|
|
1319
|
-
// runs the include resolver too and has to run it under BOTH of the
|
|
1320
|
-
// verb's include options -- a `breaking --trust none` that read its
|
|
1321
|
-
// own mode through an unconfined resolver would confine the
|
|
1322
|
-
// comparison and not the question (use-cases/REVIEW.md finding G),
|
|
1323
|
-
// and one that took the capability alone read no mode at all when
|
|
1324
|
-
// the declaration arrived through a `--text-ext` include.
|
|
1325
1441
|
const v = aontu.unify(newSrc, { path, ...(0, utility_1.includeOpts)(include) }, ctx);
|
|
1326
1442
|
if (0 < ctx.err.length || true === v?.isNil) {
|
|
1327
1443
|
return undefined;
|
|
@@ -1340,11 +1456,6 @@ function policyCompat(newSrc, path, include) {
|
|
|
1340
1456
|
return 'backward' === m || 'forward' === m || 'full' === m || 'none' === m
|
|
1341
1457
|
? m : undefined;
|
|
1342
1458
|
}
|
|
1343
|
-
// Is the evaluated old version's value at the finding path deprecated?
|
|
1344
|
-
// The --allow-deprecated-removal downgrade (G3 phase 4): removing (or
|
|
1345
|
-
// otherwise changing) a value the old version already deprecated warns
|
|
1346
|
-
// instead of breaking. The Go port exports the same reader as
|
|
1347
|
-
// aontu.DeprecatedAt.
|
|
1348
1459
|
function deprecatedAt(oldSrc, path, filePath) {
|
|
1349
1460
|
const aontu = new aontu_1.Aontu();
|
|
1350
1461
|
const ctx = aontu.ctx({ collect: true });
|
|
@@ -1411,9 +1522,6 @@ function runBreaking(argv) {
|
|
|
1411
1522
|
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
|
|
1412
1523
|
return 2;
|
|
1413
1524
|
}
|
|
1414
|
-
// The declared mode: --mode overrides the document's own policy;
|
|
1415
|
-
// neither means backward, the index's framing (v1-valid documents
|
|
1416
|
-
// stay valid).
|
|
1417
1525
|
const mode = args.mode ??
|
|
1418
1526
|
policyCompat(newSrc, args.file, verbOpts(trust, entryRootOf(args.file))) ??
|
|
1419
1527
|
'backward';
|
|
@@ -1446,8 +1554,6 @@ function runBreaking(argv) {
|
|
|
1446
1554
|
if (null != old.temp) {
|
|
1447
1555
|
temps.push(old.temp);
|
|
1448
1556
|
}
|
|
1449
|
-
// backward: the NEW document is the general side — every old
|
|
1450
|
-
// instance must still be admitted. forward: the old one is.
|
|
1451
1557
|
const checks = [];
|
|
1452
1558
|
if ('backward' === mode || 'full' === mode) {
|
|
1453
1559
|
checks.push({ general: [newSrc, args.file], specific: [oldSrc, spec] });
|
|
@@ -1462,17 +1568,9 @@ function runBreaking(argv) {
|
|
|
1462
1568
|
at: args.at,
|
|
1463
1569
|
generalUrl: check.general[1],
|
|
1464
1570
|
specificUrl: check.specific[1],
|
|
1465
|
-
// The old side's relative loads resolve from ITS own tree --
|
|
1466
|
-
// the materialised revision for a git spelling, the named
|
|
1467
|
-
// file's directory otherwise -- so an included file's change
|
|
1468
|
-
// is part of the comparison rather than invisible to it.
|
|
1469
1571
|
generalPath: check.general[1] === spec ? oldPath : args.file,
|
|
1470
1572
|
specificPath: check.specific[1] === spec ? oldPath : args.file,
|
|
1471
1573
|
});
|
|
1472
|
-
// The deprecated-removal downgrade: a finding about a value the
|
|
1473
|
-
// OLD version already deprecated becomes a warning, and warnings
|
|
1474
|
-
// do not move the verdict. Deprecate-then-remove is the
|
|
1475
|
-
// supported rename path (the design's own sequencing).
|
|
1476
1574
|
let verdict = report.verdict;
|
|
1477
1575
|
if (args.allowDeprecatedRemoval) {
|
|
1478
1576
|
let liveFindings = 0;
|
|
@@ -1523,12 +1621,6 @@ function renderBreakingJson(report, mode) {
|
|
|
1523
1621
|
findings: report.findings,
|
|
1524
1622
|
}, 2);
|
|
1525
1623
|
}
|
|
1526
|
-
// ---------------------------------------------------------------------
|
|
1527
|
-
// The trim reporter (G3 phase 6): report redundant entries as paths.
|
|
1528
|
-
// Report-only — REWRITING needs G7's format-preserving patch surface —
|
|
1529
|
-
// which is why --check is REQUIRED rather than defaulted: `aontu trim
|
|
1530
|
-
// f.aon` reads as "trim this file", and doing something else silently
|
|
1531
|
-
// is worse than saying so.
|
|
1532
1624
|
const TRIM_HELP = 'aontu trim --check <file> (try --help)';
|
|
1533
1625
|
const TRIM_EXIT = {
|
|
1534
1626
|
clean: 0,
|
|
@@ -1598,8 +1690,6 @@ function runTrim(argv) {
|
|
|
1598
1690
|
}
|
|
1599
1691
|
function renderTrimText(report) {
|
|
1600
1692
|
const head = `verdict: ${report.verdict}`;
|
|
1601
|
-
// WHY, when the document could not be evaluated at all: rendered as
|
|
1602
|
-
// vet renders a finding, because it IS one (the review's finding F).
|
|
1603
1693
|
const errors = report.errors ?? [];
|
|
1604
1694
|
if (0 < errors.length) {
|
|
1605
1695
|
return [head, ''].concat(errors.map(renderFinding)).join('\n');
|
|
@@ -1648,27 +1738,10 @@ const VIEW_EDGES = ['upward', 'all', 'none'];
|
|
|
1648
1738
|
// is a terminal, which is the CLI's to know and the library's never --
|
|
1649
1739
|
// the same division err.ts already draws for the error frames.
|
|
1650
1740
|
const VIEW_STYLES = ['auto', 'none', 'ansi', 'css'];
|
|
1651
|
-
// `--style auto` resolved, which only the CLI can do. The mechanism is
|
|
1652
|
-
// the PROFILE's and the library knows it -- an SVG carries its
|
|
1653
|
-
// stylesheet unless told not to, which is what makes a figure stand
|
|
1654
|
-
// alone. What the library cannot know is whether the DESTINATION is a
|
|
1655
|
-
// terminal, so that is the only thing decided here: escapes on the
|
|
1656
|
-
// text profile when stdout is a terminal and NO_COLOR is unset, the
|
|
1657
|
-
// same two conditions the error frames use. `undefined` leaves the
|
|
1658
|
-
// profile's own default in place.
|
|
1659
1741
|
function viewStyleOf(asked, as) {
|
|
1660
1742
|
if (undefined !== asked && 'auto' !== asked) {
|
|
1661
1743
|
return asked;
|
|
1662
1744
|
}
|
|
1663
|
-
// STDOUT'S OWN TERMINAL-NESS, and NO_COLOR read here rather than
|
|
1664
|
-
// through colorActive(). The figure goes to STDOUT and the error
|
|
1665
|
-
// frames go to STDERR, and they are not the same destination: main()
|
|
1666
|
-
// has already called setColor for stderr, so asking colorActive()
|
|
1667
|
-
// would answer the wrong question twice --- no escapes for
|
|
1668
|
-
// `aontu view tree m.aon 2>/dev/null` at a terminal, and escapes
|
|
1669
|
-
// into the pipe for `aontu view tree m.aon | less`. The NO_COLOR
|
|
1670
|
-
// rule is the one no-color.org states and err.ts implements:
|
|
1671
|
-
// set, to anything but empty, means no colour.
|
|
1672
1745
|
const no = process.env.NO_COLOR;
|
|
1673
1746
|
return 'text' === as && true === process.stdout.isTTY
|
|
1674
1747
|
&& (null == no || '' === no) ? 'ansi' : undefined;
|
|
@@ -1690,24 +1763,6 @@ const VIEW_USAGE_CODES = [
|
|
|
1690
1763
|
'view_document_shape', 'view_style_profile', 'view_style_unknown',
|
|
1691
1764
|
];
|
|
1692
1765
|
const MOD_HELP = 'aontu mod tidy|verify|vendor|manifest [dir] (try --help)';
|
|
1693
|
-
// The module tooling (G6 phase 3, ts/src/mod-tool.ts). All LOCAL:
|
|
1694
|
-
// `tidy` resolves the closure from what is in the stores and rewrites
|
|
1695
|
-
// the lockfile, `verify` asks whether the stores still mean what the
|
|
1696
|
-
// lockfile pins and changes nothing, `vendor` materialises the locked
|
|
1697
|
-
// closure into the project, `manifest` prints what a publish would
|
|
1698
|
-
// push.
|
|
1699
|
-
//
|
|
1700
|
-
// TIDY AND VERIFY ARE DIFFERENT QUESTIONS, and that is why both exist.
|
|
1701
|
-
// Tidy recomputes and rewrites by design -- a pin is what a module
|
|
1702
|
-
// means NOW -- so it makes the lockfile agree with whatever the store
|
|
1703
|
-
// holds, tampering included. Verify is the gate: a CI job runs it
|
|
1704
|
-
// BEFORE tidy, or instead of it.
|
|
1705
|
-
//
|
|
1706
|
-
// `get` and `publish` are the NETWORK half of the design and are not in
|
|
1707
|
-
// this build. They are named here rather than left to fall out as an
|
|
1708
|
-
// unknown subcommand, because a reader of the design will type them and
|
|
1709
|
-
// deserves to be told which half is missing rather than that the word
|
|
1710
|
-
// is wrong.
|
|
1711
1766
|
function runMod(argv) {
|
|
1712
1767
|
const rest = [];
|
|
1713
1768
|
let format = 'text';
|
|
@@ -1754,10 +1809,6 @@ function runMod(argv) {
|
|
|
1754
1809
|
process.stderr.write(`aontu: mod needs tidy, verify, vendor or manifest\n${MOD_HELP}\n`);
|
|
1755
1810
|
return 2;
|
|
1756
1811
|
}
|
|
1757
|
-
// THE OLD LAYOUT IS NAMED, NOT READ. The lockfile and the vendored
|
|
1758
|
-
// closure moved under aontu_meta/; a project that still carries them
|
|
1759
|
-
// at its root would otherwise look untouched by any of these verbs,
|
|
1760
|
-
// which is the one silence worth breaking.
|
|
1761
1812
|
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(dir, 'aon_vendor')) || (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, 'mod-lock.aon'))) {
|
|
1762
1813
|
process.stderr.write('aontu: aon_vendor/ and mod-lock.aon now live under aontu_meta/: ' +
|
|
1763
1814
|
'move them, or run aontu mod tidy and aontu mod vendor\n');
|
|
@@ -1781,9 +1832,6 @@ const MOD_SUBS = ['tidy', 'verify', 'vendor', 'manifest'];
|
|
|
1781
1832
|
const MOD_EXIT = {
|
|
1782
1833
|
ok: 0,
|
|
1783
1834
|
missing: 1,
|
|
1784
|
-
// A REFUSED GATE, with `breaking`: a store that no longer means what
|
|
1785
|
-
// the lockfile pins is the integrity check saying no, and a CI job
|
|
1786
|
-
// reading exit codes should not have to learn a third class for it.
|
|
1787
1835
|
mismatch: 1,
|
|
1788
1836
|
// Likewise a lockfile that does not cover the project: the gate has
|
|
1789
1837
|
// nothing to check, which is a refusal and not a pass.
|
|
@@ -1829,11 +1877,6 @@ function modText(sub, report) {
|
|
|
1829
1877
|
for (const f of report.findings) {
|
|
1830
1878
|
lines.push(f.path + ': ' + f.message);
|
|
1831
1879
|
}
|
|
1832
|
-
// What a manifest lacks is a declaration the module does not make
|
|
1833
|
-
// or an entry file that is not there, and neither is something a
|
|
1834
|
-
// fetch would supply -- so this is not the tail the other two
|
|
1835
|
-
// subcommands share. The name says which kind it is: `mod.version`
|
|
1836
|
-
// is a declaration, `service.aon` is a file.
|
|
1837
1880
|
for (const miss of report.missing) {
|
|
1838
1881
|
lines.push(miss + ': missing');
|
|
1839
1882
|
}
|
|
@@ -1843,8 +1886,6 @@ function modText(sub, report) {
|
|
|
1843
1886
|
for (const mod of report.verified) {
|
|
1844
1887
|
lines.push(mod + ': verified');
|
|
1845
1888
|
}
|
|
1846
|
-
// BOTH HASHES, because the useful question is which way it moved:
|
|
1847
|
-
// an empty `got` is a module that no longer stands up at all.
|
|
1848
1889
|
for (const m of report.mismatched) {
|
|
1849
1890
|
lines.push(m.mod + ': pinned ' + m.want + ' but the store means ' +
|
|
1850
1891
|
('' === m.got ? 'nothing (it does not evaluate)' : m.got));
|
|
@@ -1877,6 +1918,9 @@ function modText(sub, report) {
|
|
|
1877
1918
|
}
|
|
1878
1919
|
return lines.join('\n');
|
|
1879
1920
|
}
|
|
1921
|
+
function vacuous(what, why) {
|
|
1922
|
+
process.stderr.write(`aontu: ${what}: ${why}\n`);
|
|
1923
|
+
}
|
|
1880
1924
|
function runRelations(argv) {
|
|
1881
1925
|
const trusted = takeTrust(argv);
|
|
1882
1926
|
if (null == trusted) {
|
|
@@ -1921,12 +1965,20 @@ function runRelations(argv) {
|
|
|
1921
1965
|
return 2;
|
|
1922
1966
|
}
|
|
1923
1967
|
const report = (0, aontu_1.relationCheck)(src, {
|
|
1924
|
-
path: files[0],
|
|
1968
|
+
path: files[0], count: true,
|
|
1969
|
+
...verbOpts(trust, entryRootOf(files[0])),
|
|
1925
1970
|
});
|
|
1926
1971
|
const text = 'json' === format
|
|
1927
1972
|
? renderRelationsJson(report)
|
|
1928
1973
|
: renderRelationsText(report);
|
|
1929
1974
|
process.stdout.write(text + '\n');
|
|
1975
|
+
// `pass` over NO declarations is the vacuous case, and the engine
|
|
1976
|
+
// knows it exactly: `_reldecls` is empty. The count is asked for
|
|
1977
|
+
// here rather than derived, so the answer costs no second
|
|
1978
|
+
// evaluation.
|
|
1979
|
+
if (0 === report.declared) {
|
|
1980
|
+
vacuous('this document declares no relations', '`pass` means nothing was checked, not that the graph is sound');
|
|
1981
|
+
}
|
|
1930
1982
|
return RELATIONS_EXIT[report.verdict];
|
|
1931
1983
|
}
|
|
1932
1984
|
function runReaches(argv) {
|
|
@@ -2129,12 +2181,6 @@ function runView(argv) {
|
|
|
2129
2181
|
rest.push(arg);
|
|
2130
2182
|
}
|
|
2131
2183
|
}
|
|
2132
|
-
// ESCAPES NEVER GO INTO A FILE. A pinned golden holding terminal
|
|
2133
|
-
// control codes is not a golden anybody can read, and a byte
|
|
2134
|
-
// comparison against one would fail on the reader's terminal
|
|
2135
|
-
// settings. `auto` resolves to `none` there on its own; asking for
|
|
2136
|
-
// `ansi` explicitly is a usage error rather than a silent downgrade,
|
|
2137
|
-
// so a script that wanted colour is told where it went.
|
|
2138
2184
|
if ('ansi' === style && (undefined !== out || undefined !== opts.views)) {
|
|
2139
2185
|
process.stderr.write('aontu: --style ansi writes to a terminal, not to a file\n');
|
|
2140
2186
|
return 2;
|
|
@@ -2142,10 +2188,6 @@ function runView(argv) {
|
|
|
2142
2188
|
// THE VIEW DOCUMENT draws every figure a document declares, so it
|
|
2143
2189
|
// names no kind: the declarations do, one each.
|
|
2144
2190
|
if (undefined !== opts.views) {
|
|
2145
|
-
// A declaration names its own profile, so the style is left to
|
|
2146
|
-
// each figure's own default; `--style none` still reaches every
|
|
2147
|
-
// one of them, which is how a host page that binds the CSS
|
|
2148
|
-
// variables asks for eight figures without eight stylesheets.
|
|
2149
2191
|
opts.style = viewStyleOf(style, undefined);
|
|
2150
2192
|
return runViewSet(rest, opts, trust, { format, check, strict, out });
|
|
2151
2193
|
}
|
|
@@ -2203,7 +2245,7 @@ function runView(argv) {
|
|
|
2203
2245
|
return 2;
|
|
2204
2246
|
}
|
|
2205
2247
|
}
|
|
2206
|
-
const
|
|
2248
|
+
const viewOpts = {
|
|
2207
2249
|
...opts,
|
|
2208
2250
|
style: viewStyleOf(style, opts.as ?? (0, view_1.viewDefaultProfile)(kind)),
|
|
2209
2251
|
kind,
|
|
@@ -2211,7 +2253,15 @@ function runView(argv) {
|
|
|
2211
2253
|
roots,
|
|
2212
2254
|
...verbOpts(trust, entryRootOf(files[0])),
|
|
2213
2255
|
docs: files.slice(1).map((path, i) => ({ src: srcs[i + 1], path })),
|
|
2214
|
-
}
|
|
2256
|
+
};
|
|
2257
|
+
const report = (0, view_1.view)(srcs[0], viewOpts);
|
|
2258
|
+
if ('error' !== report.verdict && null != report.text) {
|
|
2259
|
+
const bare = (0, view_1.view)('{}', viewOpts);
|
|
2260
|
+
if ('error' !== bare.verdict && bare.text === report.text) {
|
|
2261
|
+
vacuous('nothing to draw', 'this figure is what the same view draws for an empty document' +
|
|
2262
|
+
' — the model declares nothing this kind can show');
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2215
2265
|
if ('json' === format) {
|
|
2216
2266
|
process.stdout.write(renderViewJson(report) + '\n');
|
|
2217
2267
|
}
|
|
@@ -2253,13 +2303,6 @@ function runView(argv) {
|
|
|
2253
2303
|
}
|
|
2254
2304
|
return strict && 'lossy' === report.verdict ? 1 : VIEW_EXIT[report.verdict];
|
|
2255
2305
|
}
|
|
2256
|
-
// `aontu view --views <path> <file>`: every figure the document
|
|
2257
|
-
// declares, from one evaluation, all or nothing.
|
|
2258
|
-
//
|
|
2259
|
-
// A declared `out` is resolved against the DOCUMENT's own directory,
|
|
2260
|
-
// not the caller's: a view document is committed beside the figures it
|
|
2261
|
-
// gates, and a gate that only passes from one working directory is not
|
|
2262
|
-
// a gate.
|
|
2263
2306
|
function runViewSet(rest, opts, trust, how) {
|
|
2264
2307
|
if (1 !== rest.length) {
|
|
2265
2308
|
process.stderr.write('aontu: view --views takes one file\n');
|
|
@@ -2383,8 +2426,6 @@ function renderViewJson(report) {
|
|
|
2383
2426
|
}
|
|
2384
2427
|
function renderRelationsText(report) {
|
|
2385
2428
|
const head = `verdict: ${report.verdict}`;
|
|
2386
|
-
// WHY, when the document could not be evaluated at all: rendered as
|
|
2387
|
-
// vet renders a finding, because it IS one (the review's finding F).
|
|
2388
2429
|
const errors = report.errors ?? [];
|
|
2389
2430
|
if (0 < errors.length) {
|
|
2390
2431
|
return [head, ''].concat(errors.map(renderFinding)).join('\n');
|
|
@@ -2406,19 +2447,6 @@ function renderRelationsJson(report) {
|
|
|
2406
2447
|
...(null == report.errors ? {} : { errors: report.errors }),
|
|
2407
2448
|
}, 2);
|
|
2408
2449
|
}
|
|
2409
|
-
// ---------------------------------------------------------------------
|
|
2410
|
-
// JSON SCHEMA EXPORT (SUPPORT.md act 2, the review's finding I): the
|
|
2411
|
-
// bridge to every structured-output API, which constrains generation to
|
|
2412
|
-
// JSON Schema and nothing else. Export the model, let the provider
|
|
2413
|
-
// generate under it, then `vet` the result against the model itself --
|
|
2414
|
-
// the hybrid an enterprise actually deploys, and impossible without
|
|
2415
|
-
// this verb.
|
|
2416
|
-
//
|
|
2417
|
-
// THE SCHEMA GOES TO STDOUT AND THE LOSSES TO STDERR, so `aontu
|
|
2418
|
-
// jsonschema x.aon > schema.json` writes a schema and still tells the
|
|
2419
|
-
// reader what it could not carry. `--strict` makes a loss a refusal,
|
|
2420
|
-
// for the CI job that would rather fail than ship a schema weaker than
|
|
2421
|
-
// its model.
|
|
2422
2450
|
const JSONSCHEMA_HELP = 'aontu jsonschema [--at <path>] [--strict] <file> (try --help)';
|
|
2423
2451
|
function runJsonSchema(argv) {
|
|
2424
2452
|
const trusted = takeTrust(argv);
|
|
@@ -2502,14 +2530,6 @@ function runJsonSchema(argv) {
|
|
|
2502
2530
|
return 'error' === report.verdict ? 4 :
|
|
2503
2531
|
strict && 'lossy' === report.verdict ? 1 : 0;
|
|
2504
2532
|
}
|
|
2505
|
-
// ---------------------------------------------------------------------
|
|
2506
|
-
// THE RENDER VERB (docs/design/RENDER.0.md D8): evaluate a document,
|
|
2507
|
-
// vet the value at --at against aontu:code, fold code.units into bytes,
|
|
2508
|
-
// and put them where the flag says -- one unit on stdout, every unit
|
|
2509
|
-
// below --out (all or nothing), or compared against --check. Exit codes
|
|
2510
|
-
// mirror jsonschema's: 0 ok; 1 lossy under --strict or drift under
|
|
2511
|
-
// --check; 2 usage or I/O, a refused unit path included; 4 the
|
|
2512
|
-
// document does not stand up or the instance is not aontu:code.
|
|
2513
2533
|
const RENDER_HELP = 'aontu render [--at <path>] [--profile <file>]... [--unit <path>] ' +
|
|
2514
2534
|
'[--stdout | --out <dir> | --check <dir> | --coverage] ' +
|
|
2515
2535
|
'[--coverage-at <path>] [--strict] [--marker <token>] <file> (try --help)';
|
|
@@ -2638,45 +2658,20 @@ function runRender(argv) {
|
|
|
2638
2658
|
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
|
|
2639
2659
|
return 2;
|
|
2640
2660
|
}
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
// carries the target's extension and never `.aon`. Desugared here
|
|
2645
|
-
// rather than anywhere deeper, because a template is an entry
|
|
2646
|
-
// spelling and not a value: an include is still aontu.
|
|
2647
|
-
if (!files[0].endsWith('.aon')) {
|
|
2648
|
-
src = (0, template_1.desugarTemplate)(src, marker ?? (0, template_1.markerFor)(files[0]));
|
|
2649
|
-
}
|
|
2650
|
-
// THE PROFILES (D5): each --profile file is a document whose root is
|
|
2651
|
-
// `profile: {lang, ...}`, evaluated under the verb's trust and vetted
|
|
2652
|
-
// against aontu:profile as a settled value before the fold reads it
|
|
2653
|
-
// (renderProfile, which also fills the defaults). Two files claiming
|
|
2654
|
-
// one lang is a usage error: the fold could not choose.
|
|
2655
|
-
const profiles = [];
|
|
2656
|
-
const langs = new Map();
|
|
2657
|
-
for (const pf of profileFiles) {
|
|
2658
|
-
let text;
|
|
2659
|
-
try {
|
|
2660
|
-
text = (0, node_fs_1.readFileSync)(pf, 'utf8');
|
|
2661
|
-
}
|
|
2662
|
-
catch (err) {
|
|
2663
|
-
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
|
|
2664
|
-
return 2;
|
|
2665
|
-
}
|
|
2666
|
-
const loaded = (0, aontu_1.renderProfile)(text, { path: (0, node_path_1.resolve)(pf), ...verbOpts(trust, entryRootOf(pf)) });
|
|
2667
|
-
if (undefined !== loaded.errors) {
|
|
2668
|
-
process.stderr.write(loaded.errors.map(renderFinding).join('\n') + '\n');
|
|
2669
|
-
return 4;
|
|
2670
|
-
}
|
|
2671
|
-
const profile = loaded.profile;
|
|
2672
|
-
const prev = langs.get(profile.lang);
|
|
2673
|
-
if (undefined !== prev) {
|
|
2674
|
-
process.stderr.write(`aontu: two profiles claim ${profile.lang}: ${prev} and ${pf}\n`);
|
|
2675
|
-
return 2;
|
|
2676
|
-
}
|
|
2677
|
-
langs.set(profile.lang, pf);
|
|
2678
|
-
profiles.push(profile);
|
|
2661
|
+
const loadedProfiles = loadProfiles(profileFiles, trust);
|
|
2662
|
+
if ('number' === typeof loadedProfiles) {
|
|
2663
|
+
return loadedProfiles;
|
|
2679
2664
|
}
|
|
2665
|
+
const profiles = loadedProfiles;
|
|
2666
|
+
if (!files[0].endsWith('.aon')) {
|
|
2667
|
+
src = (0, template_1.desugarTemplate)(src, marker ??
|
|
2668
|
+
(0, template_1.markerFromProfiles)(profiles, files[0]) ?? (0, template_1.markerFor)(files[0]));
|
|
2669
|
+
}
|
|
2670
|
+
// A RENDER WITH NO PROFILE PRODUCES NO UNITS, and said so with zero
|
|
2671
|
+
// bytes and exit 0. The profile is what maps a model onto a
|
|
2672
|
+
// language, so without one there is nothing for the renderer to
|
|
2673
|
+
// write -- which is a usable answer only if the caller is told.
|
|
2674
|
+
const noProfiles = 0 === profiles.length;
|
|
2680
2675
|
const report = (0, aontu_1.render)(src, {
|
|
2681
2676
|
at, unit, strict, profiles, path: files[0],
|
|
2682
2677
|
coverage, coverageAt,
|
|
@@ -2686,6 +2681,13 @@ function runRender(argv) {
|
|
|
2686
2681
|
trace: 'json' === format,
|
|
2687
2682
|
...verbOpts(trust, entryRootOf(files[0])),
|
|
2688
2683
|
});
|
|
2684
|
+
// Said once, whatever the format: stdout stays the report.
|
|
2685
|
+
if ('error' !== report.verdict && 0 === report.units.length) {
|
|
2686
|
+
vacuous('nothing was rendered', noProfiles
|
|
2687
|
+
? 'no profile was given, and the document declares none' +
|
|
2688
|
+
' (see aontu help tasks)'
|
|
2689
|
+
: 'the document produced no units under this profile');
|
|
2690
|
+
}
|
|
2689
2691
|
if ('json' === format) {
|
|
2690
2692
|
process.stdout.write((0, aontu_1.exactJSON)({
|
|
2691
2693
|
aontu: { version: version(), verb: 'render' },
|
|
@@ -2803,18 +2805,19 @@ function renderExit(report, drift) {
|
|
|
2803
2805
|
}
|
|
2804
2806
|
return 0 < drift ? 1 : 0;
|
|
2805
2807
|
}
|
|
2806
|
-
// ---------------------------------------------------------------------
|
|
2807
|
-
// THE TEMPLATE SURFACE (docs/design/TEMPLATE.0.md; RENDER.0.md P8): the
|
|
2808
|
-
// two transforms and the round trip between them. `render` reads a
|
|
2809
|
-
// template directly, by its extension; this verb is for seeing the
|
|
2810
|
-
// canonical form, for writing one by hand and sugaring it, and for the
|
|
2811
|
-
// check that keeps a committed template and its meaning in agreement.
|
|
2812
2808
|
const TEMPLATE_HELP = 'aontu template [--resugar] [--check] [--marker <token>] <file> (try --help)';
|
|
2813
2809
|
function runTemplate(argv) {
|
|
2810
|
+
const trusted = takeTrust(argv);
|
|
2811
|
+
if (null == trusted) {
|
|
2812
|
+
return 2;
|
|
2813
|
+
}
|
|
2814
|
+
argv = trusted.argv;
|
|
2815
|
+
const trust = trusted.trust;
|
|
2814
2816
|
const files = [];
|
|
2815
2817
|
let resugar = false;
|
|
2816
2818
|
let check = false;
|
|
2817
2819
|
let marker = undefined;
|
|
2820
|
+
const profileFiles = [];
|
|
2818
2821
|
for (let i = 0; i < argv.length; i++) {
|
|
2819
2822
|
const arg = argv[i];
|
|
2820
2823
|
if ('-h' === arg || '--help' === arg) {
|
|
@@ -2834,6 +2837,14 @@ function runTemplate(argv) {
|
|
|
2834
2837
|
return 2;
|
|
2835
2838
|
}
|
|
2836
2839
|
}
|
|
2840
|
+
else if ('--profile' === arg) {
|
|
2841
|
+
const pf = argv[++i];
|
|
2842
|
+
if (null == pf) {
|
|
2843
|
+
process.stderr.write('aontu: --profile needs a file\n');
|
|
2844
|
+
return 2;
|
|
2845
|
+
}
|
|
2846
|
+
profileFiles.push(pf);
|
|
2847
|
+
}
|
|
2837
2848
|
else if (arg.startsWith('-')) {
|
|
2838
2849
|
process.stderr.write(`aontu: unknown template option ${arg} (try --help)\n`);
|
|
2839
2850
|
return 2;
|
|
@@ -2846,10 +2857,6 @@ function runTemplate(argv) {
|
|
|
2846
2857
|
process.stderr.write(`aontu: template needs one file\n${TEMPLATE_HELP}\n`);
|
|
2847
2858
|
return 2;
|
|
2848
2859
|
}
|
|
2849
|
-
// THE TWO ARE DIRECTIONS, NOT MODES THAT COMPOSE: `--check` reads a
|
|
2850
|
-
// template and asks whether the round trip answers it back, and
|
|
2851
|
-
// `--resugar` reads the canonical form instead. A run cannot be both
|
|
2852
|
-
// at once, because the file is one thing or the other.
|
|
2853
2860
|
if (resugar && check) {
|
|
2854
2861
|
process.stderr.write('aontu: template takes one of --resugar or --check\n');
|
|
2855
2862
|
return 2;
|
|
@@ -2862,18 +2869,13 @@ function runTemplate(argv) {
|
|
|
2862
2869
|
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
|
|
2863
2870
|
return 2;
|
|
2864
2871
|
}
|
|
2865
|
-
const
|
|
2872
|
+
const declared = loadProfiles(profileFiles, trust);
|
|
2873
|
+
if ('number' === typeof declared) {
|
|
2874
|
+
return declared;
|
|
2875
|
+
}
|
|
2876
|
+
const mark = marker ?? (0, template_1.markerFromProfiles)(declared, files[0]) ??
|
|
2877
|
+
(0, template_1.markerFor)(files[0]);
|
|
2866
2878
|
if (check) {
|
|
2867
|
-
// THE ROUND TRIP IS THE CHECK (D6): the file held to the spelling
|
|
2868
|
-
// the two transforms answer. What that names is a marker line the
|
|
2869
|
-
// transform would not have written -- one without its space, or one
|
|
2870
|
-
// whose aontu is indented after the marker rather than before it,
|
|
2871
|
-
// since the marker keeps its own indentation. It does NOT name a
|
|
2872
|
-
// changed body line: a template's whitespace is output, so a
|
|
2873
|
-
// trimmed trailing space is still a valid template and it is
|
|
2874
|
-
// `render --check` against the committed files that catches it.
|
|
2875
|
-
// The first line that differs is the report, since a whole diff of
|
|
2876
|
-
// a generator is the file again.
|
|
2877
2879
|
const back = (0, template_1.resugarTemplate)((0, template_1.desugarTemplate)(src, mark), mark);
|
|
2878
2880
|
if (back === src) {
|
|
2879
2881
|
return 0;
|
|
@@ -2884,12 +2886,6 @@ function runTemplate(argv) {
|
|
|
2884
2886
|
while (n < want.length && n < have.length && want[n] === have[n]) {
|
|
2885
2887
|
n++;
|
|
2886
2888
|
}
|
|
2887
|
-
// THE TWO ARE THE SAME LENGTH, always: each transform maps one
|
|
2888
|
-
// line to one line and applies the same trailing-newline rule, so
|
|
2889
|
-
// `back` has as many lines as `src`. The loop above therefore stops
|
|
2890
|
-
// at a real difference rather than by running out of either -- an
|
|
2891
|
-
// equal prefix all the way to the end IS `back === src`, which
|
|
2892
|
-
// returned above. So both indexes are in range here.
|
|
2893
2889
|
process.stderr.write(`aontu: ${files[0]}:${n + 1} is not what the round trip answers\n` +
|
|
2894
2890
|
` have: ${JSON.stringify(have[n])}\n` +
|
|
2895
2891
|
` want: ${JSON.stringify(want[n])}\n`);
|
|
@@ -2899,13 +2895,38 @@ function runTemplate(argv) {
|
|
|
2899
2895
|
(0, template_1.resugarTemplate)(src, mark) : (0, template_1.desugarTemplate)(src, mark));
|
|
2900
2896
|
return 0;
|
|
2901
2897
|
}
|
|
2902
|
-
//
|
|
2903
|
-
//
|
|
2904
|
-
//
|
|
2905
|
-
//
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2898
|
+
// The profiles named by --profile, vetted, or the exit code that says
|
|
2899
|
+
// why not. A profile is a language declared as data: `render` matches
|
|
2900
|
+
// one to a unit by `lang`, and `template` and `fmt` match one to a file
|
|
2901
|
+
// by the extensions its `template.ext` names.
|
|
2902
|
+
function loadProfiles(profileFiles, trust) {
|
|
2903
|
+
const profiles = [];
|
|
2904
|
+
const langs = new Map();
|
|
2905
|
+
for (const pf of profileFiles) {
|
|
2906
|
+
let text;
|
|
2907
|
+
try {
|
|
2908
|
+
text = (0, node_fs_1.readFileSync)(pf, 'utf8');
|
|
2909
|
+
}
|
|
2910
|
+
catch (err) {
|
|
2911
|
+
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
|
|
2912
|
+
return 2;
|
|
2913
|
+
}
|
|
2914
|
+
const loaded = (0, aontu_1.renderProfile)(text, { path: (0, node_path_1.resolve)(pf), ...verbOpts(trust, entryRootOf(pf)) });
|
|
2915
|
+
if (undefined !== loaded.errors) {
|
|
2916
|
+
process.stderr.write(loaded.errors.map(renderFinding).join('\n') + '\n');
|
|
2917
|
+
return 4;
|
|
2918
|
+
}
|
|
2919
|
+
const profile = loaded.profile;
|
|
2920
|
+
const prev = langs.get(profile.lang);
|
|
2921
|
+
if (undefined !== prev) {
|
|
2922
|
+
process.stderr.write(`aontu: two profiles claim ${profile.lang}: ${prev} and ${pf}\n`);
|
|
2923
|
+
return 2;
|
|
2924
|
+
}
|
|
2925
|
+
langs.set(profile.lang, pf);
|
|
2926
|
+
profiles.push(profile);
|
|
2927
|
+
}
|
|
2928
|
+
return profiles;
|
|
2929
|
+
}
|
|
2909
2930
|
const HASH_HELP = 'aontu hash <file> (try --help)';
|
|
2910
2931
|
function runHash(argv) {
|
|
2911
2932
|
const trusted = takeTrust(argv);
|
|
@@ -2960,14 +2981,6 @@ function runHash(argv) {
|
|
|
2960
2981
|
const ctx = aontu.ctx({ collect: true });
|
|
2961
2982
|
const v = aontu.unify(src, { path: files[0] }, ctx);
|
|
2962
2983
|
if (0 < ctx.err.length || true === v?.isNil) {
|
|
2963
|
-
// A document that does not stand up on its own has no meaning to
|
|
2964
|
-
// pin, and a hash of a broken evaluation would be a pin that
|
|
2965
|
-
// silently agrees with every other broken evaluation.
|
|
2966
|
-
// WHY it does not stand up, not just that it does not: the same
|
|
2967
|
-
// diagnosis `aontu <file>` prints (the review's finding F).
|
|
2968
|
-
// evalFailure unconditionally, as every other call site does: it
|
|
2969
|
-
// owns the "ctx.err is never empty here" contract, and a guard
|
|
2970
|
-
// that pretends otherwise is a dead arm asserting nothing.
|
|
2971
2984
|
process.stderr.write(`aontu: ${files[0]} does not evaluate on its own; nothing to hash\n` +
|
|
2972
2985
|
renderFinding((0, query_1.evalFailure)(ctx)) + '\n');
|
|
2973
2986
|
return 4;
|
|
@@ -2982,12 +2995,6 @@ function runHash(argv) {
|
|
|
2982
2995
|
process.stdout.write(text + '\n');
|
|
2983
2996
|
return 0;
|
|
2984
2997
|
}
|
|
2985
|
-
// ---------------------------------------------------------------------
|
|
2986
|
-
// The query surface (G7 phase 1): one node of an evaluated document,
|
|
2987
|
-
// selected by path and rendered. Evaluation is still GLOBAL -- what
|
|
2988
|
-
// `get` buys is the size of the ANSWER, not the cost of producing it --
|
|
2989
|
-
// and the projections are lattice abstractions, each a valid Aontu
|
|
2990
|
-
// document that subsumes the truth it summarises.
|
|
2991
2998
|
const GET_HELP = 'aontu get <path> <file> (try --help)';
|
|
2992
2999
|
function runGet(argv) {
|
|
2993
3000
|
const trusted = takeTrust(argv);
|
|
@@ -3156,11 +3163,6 @@ function runWhy(argv) {
|
|
|
3156
3163
|
}
|
|
3157
3164
|
return 'no_path' === report.findings[0]?.code ? 1 : 4;
|
|
3158
3165
|
}
|
|
3159
|
-
// One contribution per line, numbered in source order, each with what
|
|
3160
|
-
// was written, where, and how it got here. A siteless contribution
|
|
3161
|
-
// prints no location rather than a `-1:-1` that means nothing —
|
|
3162
|
-
// exported for the direct test, because the site SHAPE allows one
|
|
3163
|
-
// while no document has yet produced one (ADR-002).
|
|
3164
3166
|
function renderWhyText(record) {
|
|
3165
3167
|
const head = `${record.path} = ${record.value}`;
|
|
3166
3168
|
if (0 === record.conjuncts.length) {
|
|
@@ -3176,12 +3178,6 @@ function renderWhyText(record) {
|
|
|
3176
3178
|
('literal' === c.role ? '' : ` (${c.role})`);
|
|
3177
3179
|
})).join('\n');
|
|
3178
3180
|
}
|
|
3179
|
-
// ---------------------------------------------------------------------
|
|
3180
|
-
// The overlay patch verb (G7 phase 5): change a document by APPENDING
|
|
3181
|
-
// to an overlay, not by rewriting it. An overlay entry is just another
|
|
3182
|
-
// conjunct and unification is order-independent, so this needs no
|
|
3183
|
-
// rewriter — the format-preserving in-place edit is stage 2, and needs
|
|
3184
|
-
// a comment-preserving CST the parser stack does not have.
|
|
3185
3181
|
const SET_HELP = 'aontu set <path>=<value> --entry <file> --overlay <file> (try --help)';
|
|
3186
3182
|
function runSet(argv) {
|
|
3187
3183
|
const trusted = takeTrust(argv);
|
|
@@ -3288,27 +3284,10 @@ function runSet(argv) {
|
|
|
3288
3284
|
}, 2) + '\n');
|
|
3289
3285
|
}
|
|
3290
3286
|
else {
|
|
3291
|
-
// A replacement is REPORTED as the edit it is, not left for the
|
|
3292
|
-
// reader to infer from a changed file: `where: what -> what`, in
|
|
3293
|
-
// source spelling, because the spelling is what changed.
|
|
3294
|
-
//
|
|
3295
|
-
// PAST TENSE ONLY WHERE IT HAPPENED. A refused write leaves the
|
|
3296
|
-
// file exactly as it was, and one assignment can be replaceable
|
|
3297
|
-
// while another makes the whole run invalid — so `replaced:` there
|
|
3298
|
-
// tells an operator the pin was changed when it was not, and unlike
|
|
3299
|
-
// `--dry-run` there is nothing else on the line to say otherwise.
|
|
3300
3287
|
const verb = wrote ? 'replaced' : 'would replace';
|
|
3301
3288
|
const edits = report.replaced.map((r) => `${verb}: ${r.file}:${r.row}:${r.col} ${r.from} -> ${r.to}`);
|
|
3302
3289
|
const head = [`verdict: ${report.verdict}`].concat(edits).join('\n') +
|
|
3303
3290
|
(wrote ? `\nwrote: ${overlayFile}` : dryRun ? '\n(dry run)' : '');
|
|
3304
|
-
// A SUCCESSFUL COMMAND WRITES ITS STATUS TO STDOUT, findings or
|
|
3305
|
-
// not. Routing on `findings.length` was right while every finding
|
|
3306
|
-
// this verb could produce was an ERROR; `--in-place` made a WARNING
|
|
3307
|
-
// possible, and a run that held, wrote the file and exited 0 then
|
|
3308
|
-
// sent its whole report to stderr — leaving stdout empty, so
|
|
3309
|
-
// `$(aontu set ...)` captured nothing and only the JSON form
|
|
3310
|
-
// behaved like a success. The verdict decides the stream; warnings
|
|
3311
|
-
// are diagnostics and go to stderr beside it.
|
|
3312
3291
|
const failed = 'invalid' === report.verdict || 'error' === report.verdict;
|
|
3313
3292
|
const findingText = report.findings.map(renderFinding);
|
|
3314
3293
|
if (failed) {
|
|
@@ -3327,6 +3306,148 @@ function runSet(argv) {
|
|
|
3327
3306
|
}
|
|
3328
3307
|
return VET_EXIT[report.verdict];
|
|
3329
3308
|
}
|
|
3309
|
+
const ALLOW_HELP = 'aontu allow --role <role> <roles-file> <path> [more-paths...] (try --help)';
|
|
3310
|
+
const ALLOW_EXIT = {
|
|
3311
|
+
allowed: 0,
|
|
3312
|
+
refused: 1,
|
|
3313
|
+
error: 4,
|
|
3314
|
+
};
|
|
3315
|
+
// One line per asked path: the answer, and the entry that gave it, as
|
|
3316
|
+
// a path into the role model so `aontu why` can locate the rule.
|
|
3317
|
+
function renderAllowDecision(d, role) {
|
|
3318
|
+
const head = `${d.path}: ${d.allowed ? 'allowed' : 'refused'}`;
|
|
3319
|
+
switch (d.reason) {
|
|
3320
|
+
case 'allow':
|
|
3321
|
+
case 'deny':
|
|
3322
|
+
return `${head} by ${d.by} (${d.pattern})`;
|
|
3323
|
+
case 'uncovered':
|
|
3324
|
+
return `${head} (no allow entry of ${role} covers it)`;
|
|
3325
|
+
default:
|
|
3326
|
+
return `${head} (role ${role} is not declared)`;
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
function renderAllowText(report) {
|
|
3330
|
+
const lines = [`verdict: ${report.verdict}`, `role: ${report.role}`]
|
|
3331
|
+
.concat(report.paths.map((d) => renderAllowDecision(d, report.role)));
|
|
3332
|
+
if (0 === report.findings.length) {
|
|
3333
|
+
return lines.join('\n');
|
|
3334
|
+
}
|
|
3335
|
+
return lines.concat('', report.findings.map(renderFinding)).join('\n');
|
|
3336
|
+
}
|
|
3337
|
+
// Does the text after `=` parse as exactly one value? Parsed, never
|
|
3338
|
+
// evaluated, with loads denied: the question is the shape of the
|
|
3339
|
+
// argument, and reading a file to answer it would be the write the
|
|
3340
|
+
// gate exists to precede.
|
|
3341
|
+
function oneValue(value) {
|
|
3342
|
+
try {
|
|
3343
|
+
const probe = new aontu_1.Aontu({ trust: { include: 'none' } })
|
|
3344
|
+
.parse('v: ' + value);
|
|
3345
|
+
return 1 === Object.keys(probe.peg).length;
|
|
3346
|
+
}
|
|
3347
|
+
catch {
|
|
3348
|
+
return false;
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
function runAllow(argv) {
|
|
3352
|
+
const trusted = takeTrust(argv);
|
|
3353
|
+
if (null == trusted) {
|
|
3354
|
+
return 2;
|
|
3355
|
+
}
|
|
3356
|
+
argv = trusted.argv;
|
|
3357
|
+
const trust = trusted.trust;
|
|
3358
|
+
const rest = [];
|
|
3359
|
+
let role;
|
|
3360
|
+
let at;
|
|
3361
|
+
let format = 'text';
|
|
3362
|
+
for (let i = 0; i < argv.length; i++) {
|
|
3363
|
+
const arg = argv[i];
|
|
3364
|
+
if ('-h' === arg || '--help' === arg) {
|
|
3365
|
+
process.stdout.write(HELP);
|
|
3366
|
+
return 0;
|
|
3367
|
+
}
|
|
3368
|
+
if ('--role' === arg) {
|
|
3369
|
+
role = argv[++i];
|
|
3370
|
+
if (null == role) {
|
|
3371
|
+
process.stderr.write('aontu: --role needs a role name\n');
|
|
3372
|
+
return 2;
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
else if ('--at' === arg) {
|
|
3376
|
+
at = argv[++i];
|
|
3377
|
+
if (null == at) {
|
|
3378
|
+
process.stderr.write('aontu: --at needs a path\n');
|
|
3379
|
+
return 2;
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
else if ('--format' === arg) {
|
|
3383
|
+
const f = argv[++i];
|
|
3384
|
+
if ('text' !== f && 'json' !== f) {
|
|
3385
|
+
process.stderr.write('aontu: --format needs text or json\n');
|
|
3386
|
+
return 2;
|
|
3387
|
+
}
|
|
3388
|
+
format = f;
|
|
3389
|
+
}
|
|
3390
|
+
else if (arg.startsWith('-')) {
|
|
3391
|
+
process.stderr.write(`aontu: unknown allow option ${arg} (try --help)\n`);
|
|
3392
|
+
return 2;
|
|
3393
|
+
}
|
|
3394
|
+
else {
|
|
3395
|
+
rest.push(arg);
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
if (null == role || rest.length < 2) {
|
|
3399
|
+
process.stderr.write(`aontu: allow needs --role, a role model and at least one path\n` +
|
|
3400
|
+
`${ALLOW_HELP}\n`);
|
|
3401
|
+
return 2;
|
|
3402
|
+
}
|
|
3403
|
+
const [file, ...asked] = rest;
|
|
3404
|
+
// A role is ONE KEY of the roles map. A dotted name would be read as
|
|
3405
|
+
// a path by `why` when it follows the entry the report names, and an
|
|
3406
|
+
// empty one names the map itself.
|
|
3407
|
+
if ('' === role || role.includes('.')) {
|
|
3408
|
+
process.stderr.write('aontu: --role needs one key, without dots\n');
|
|
3409
|
+
return 2;
|
|
3410
|
+
}
|
|
3411
|
+
const paths = [];
|
|
3412
|
+
for (const arg of asked) {
|
|
3413
|
+
const eq = arg.indexOf('=');
|
|
3414
|
+
const path = eq < 0 ? arg : arg.slice(0, eq);
|
|
3415
|
+
if (!path.startsWith('$')) {
|
|
3416
|
+
process.stderr.write(`aontu: a path starts with $ (got ${JSON.stringify(arg)})\n`);
|
|
3417
|
+
return 2;
|
|
3418
|
+
}
|
|
3419
|
+
if (0 <= eq && !oneValue(arg.slice(eq + 1))) {
|
|
3420
|
+
process.stderr.write(`aontu: the value of ${path} is not one value\n`);
|
|
3421
|
+
return 2;
|
|
3422
|
+
}
|
|
3423
|
+
paths.push(path);
|
|
3424
|
+
}
|
|
3425
|
+
let src;
|
|
3426
|
+
try {
|
|
3427
|
+
src = (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
3428
|
+
}
|
|
3429
|
+
catch (err) {
|
|
3430
|
+
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
|
|
3431
|
+
return 2;
|
|
3432
|
+
}
|
|
3433
|
+
const report = (0, aontu_1.allow)(src, role, paths, {
|
|
3434
|
+
at, path: file, ...verbOpts(trust, entryRootOf(file)),
|
|
3435
|
+
});
|
|
3436
|
+
const text = 'json' === format ?
|
|
3437
|
+
(0, aontu_1.exactJSON)({
|
|
3438
|
+
aontu: { version: version(), verb: 'allow' },
|
|
3439
|
+
findings: report.findings,
|
|
3440
|
+
paths: report.paths,
|
|
3441
|
+
role: report.role,
|
|
3442
|
+
verdict: report.verdict,
|
|
3443
|
+
}, 2) :
|
|
3444
|
+
renderAllowText(report);
|
|
3445
|
+
// The report IS the answer, refused or not, so it goes to stdout as
|
|
3446
|
+
// vet's does; the exit code carries the verdict for a caller that
|
|
3447
|
+
// reads nothing else.
|
|
3448
|
+
process.stdout.write(text + '\n');
|
|
3449
|
+
return ALLOW_EXIT[report.verdict];
|
|
3450
|
+
}
|
|
3330
3451
|
// ---------------------------------------------------------------------
|
|
3331
3452
|
// The generated AGENTS.md stanza (G7 phase 6): the prose entrypoint,
|
|
3332
3453
|
// derived from the definition, so it cannot drift from the formal
|
|
@@ -3341,6 +3462,10 @@ function runAgentsMd(argv) {
|
|
|
3341
3462
|
const trust = trusted.trust;
|
|
3342
3463
|
const files = [];
|
|
3343
3464
|
let write;
|
|
3465
|
+
// The SHAPE's depth (G11 phase 7). Default 2, unchanged: the stanza
|
|
3466
|
+
// is spliced into a file people read, and a deeper shape is a
|
|
3467
|
+
// question the caller asks rather than one it is handed.
|
|
3468
|
+
let depth = 2;
|
|
3344
3469
|
for (let i = 0; i < argv.length; i++) {
|
|
3345
3470
|
const arg = argv[i];
|
|
3346
3471
|
if ('-h' === arg || '--help' === arg) {
|
|
@@ -3354,6 +3479,14 @@ function runAgentsMd(argv) {
|
|
|
3354
3479
|
return 2;
|
|
3355
3480
|
}
|
|
3356
3481
|
}
|
|
3482
|
+
else if ('--depth' === arg) {
|
|
3483
|
+
const n = Number(argv[++i]);
|
|
3484
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
3485
|
+
process.stderr.write('aontu: --depth needs a positive integer\n');
|
|
3486
|
+
return 2;
|
|
3487
|
+
}
|
|
3488
|
+
depth = n;
|
|
3489
|
+
}
|
|
3357
3490
|
else if (arg.startsWith('-')) {
|
|
3358
3491
|
process.stderr.write(`aontu: unknown agentsmd option ${arg} (try --help)\n`);
|
|
3359
3492
|
return 2;
|
|
@@ -3375,7 +3508,7 @@ function runAgentsMd(argv) {
|
|
|
3375
3508
|
return 2;
|
|
3376
3509
|
}
|
|
3377
3510
|
const report = (0, aontu_1.agentsMd)(src, {
|
|
3378
|
-
name: files[0], path: files[0],
|
|
3511
|
+
depth, name: files[0], path: files[0],
|
|
3379
3512
|
...verbOpts(trust, entryRootOf(files[0])),
|
|
3380
3513
|
});
|
|
3381
3514
|
if (!report.ok) {
|
|
@@ -3408,28 +3541,17 @@ function runAgentsMd(argv) {
|
|
|
3408
3541
|
process.stdout.write(`wrote: ${write}\n`);
|
|
3409
3542
|
return 0;
|
|
3410
3543
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
// process.exit() terminates immediately, discarding anything still
|
|
3414
|
-
// queued on stdout. A write to a PIPE is asynchronous once it exceeds
|
|
3415
|
-
// the pipe buffer, so `write(big); exit(0)` silently truncated output at
|
|
3416
|
-
// 65536 bytes — while a write to a TTY or a file, being synchronous,
|
|
3417
|
-
// looked fine. Setting exitCode instead lets the process end naturally,
|
|
3418
|
-
// after the queue drains.
|
|
3419
|
-
//
|
|
3420
|
-
// This predates the exact leaves but they make it trivially reachable
|
|
3421
|
-
// (one long biginteger canon exceeds the buffer), and it lands squarely
|
|
3422
|
-
// on the parity-probe discipline in AGENTS.md, which derives expected
|
|
3423
|
-
// spec values by piping BOTH CLIs and comparing. A truncated pipe there
|
|
3424
|
-
// reads as a port divergence.
|
|
3425
|
-
// ---------------------------------------------------------------------
|
|
3426
|
-
// The source formatter (docs/design/FMT.0.md): one agreed form, in the
|
|
3427
|
-
// tradition of gofmt. The verb prints, lists, checks, diffs or rewrites;
|
|
3428
|
-
// the form itself is the library's (ts/src/format.ts), and the two
|
|
3429
|
-
// ports agree on it row by row in test/spec/fmt.tsv.
|
|
3430
|
-
const FMT_HELP = 'aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] <file>... (try --help)';
|
|
3544
|
+
const FMT_HELP = 'aontu fmt [-w|-l|--check|-d|--lint] [--marker <token>] ' +
|
|
3545
|
+
'[--profile <file>] <file>... (try --help)';
|
|
3431
3546
|
function runFmt(argv) {
|
|
3547
|
+
const trusted = takeTrust(argv);
|
|
3548
|
+
if (null == trusted) {
|
|
3549
|
+
return 2;
|
|
3550
|
+
}
|
|
3551
|
+
argv = trusted.argv;
|
|
3552
|
+
const trust = trusted.trust;
|
|
3432
3553
|
const files = [];
|
|
3554
|
+
const profileFiles = [];
|
|
3433
3555
|
let marker = undefined;
|
|
3434
3556
|
const flags = {
|
|
3435
3557
|
write: false, list: false, check: false, diff: false, lint: false, strict: false,
|
|
@@ -3469,6 +3591,14 @@ function runFmt(argv) {
|
|
|
3469
3591
|
return 2;
|
|
3470
3592
|
}
|
|
3471
3593
|
}
|
|
3594
|
+
else if ('--profile' === arg) {
|
|
3595
|
+
const pf = argv[++i];
|
|
3596
|
+
if (null == pf) {
|
|
3597
|
+
process.stderr.write('aontu: --profile needs a file\n');
|
|
3598
|
+
return 2;
|
|
3599
|
+
}
|
|
3600
|
+
profileFiles.push(pf);
|
|
3601
|
+
}
|
|
3472
3602
|
else if (arg.startsWith('-')) {
|
|
3473
3603
|
process.stderr.write(`aontu: unknown fmt option ${arg} (try --help)\n`);
|
|
3474
3604
|
return 2;
|
|
@@ -3492,6 +3622,10 @@ function runFmt(argv) {
|
|
|
3492
3622
|
process.stdin.on('end', () => resolve(fmtOne('<stdin>', src, flags, marker)));
|
|
3493
3623
|
});
|
|
3494
3624
|
}
|
|
3625
|
+
const declared = loadProfiles(profileFiles, trust);
|
|
3626
|
+
if ('number' === typeof declared) {
|
|
3627
|
+
return declared;
|
|
3628
|
+
}
|
|
3495
3629
|
// Several files onto standard output would be one stream nobody can
|
|
3496
3630
|
// split again (the note's X-6): the verb refuses unless an option
|
|
3497
3631
|
// says what to do with each.
|
|
@@ -3510,32 +3644,18 @@ function runFmt(argv) {
|
|
|
3510
3644
|
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
|
|
3511
3645
|
return 2;
|
|
3512
3646
|
}
|
|
3513
|
-
const mark = fmtMarker(file, src, marker);
|
|
3647
|
+
const mark = fmtMarker(file, src, marker ?? (0, template_1.markerFromProfiles)(declared, file));
|
|
3514
3648
|
if (false === mark) {
|
|
3515
3649
|
process.stderr.write(`aontu: ${file} is not aontu source (.aon, .aontu) and carries no ` +
|
|
3516
3650
|
`${(0, template_1.markerFor)(file)} marker line, so there is no aontu in it to ` +
|
|
3517
|
-
'format; --marker names the marker for a language the table
|
|
3518
|
-
'not know\n');
|
|
3651
|
+
'format; --marker names the marker for a language the table ' +
|
|
3652
|
+
'does not know, and --profile reads one that declares it\n');
|
|
3519
3653
|
return 2;
|
|
3520
3654
|
}
|
|
3521
3655
|
worst = Math.max(worst, fmtOne(file, src, flags, mark));
|
|
3522
3656
|
}
|
|
3523
3657
|
return worst;
|
|
3524
3658
|
}
|
|
3525
|
-
// WHAT A FILE IS, BY ITS EXTENSION (ADR-012's rule, and the one
|
|
3526
|
-
// `render` reads an entry by): `.aon` and `.aontu` are aontu source,
|
|
3527
|
-
// and anything else is a GENERATOR written in the target's own syntax
|
|
3528
|
-
// (docs/design/TEMPLATE.0.md), whose marker lines carry the document
|
|
3529
|
-
// this formats and whose other lines are output. `undefined` is aontu,
|
|
3530
|
-
// a string is the generator's marker, and `false` is neither.
|
|
3531
|
-
//
|
|
3532
|
-
// A FILE WITH NO MARKER LINE IN IT IS NEITHER, and that is what keeps
|
|
3533
|
-
// FMT.0.md §9's boundary where it stood: a `.json`, `.yaml` or `.toml`
|
|
3534
|
-
// include is another language's file, and reading one as a generator
|
|
3535
|
-
// would answer it back unchanged having understood none of it. The
|
|
3536
|
-
// marker is the evidence that a file was written to carry aontu at
|
|
3537
|
-
// all. `--marker` says so outright, and then the file is a generator
|
|
3538
|
-
// whatever it is called.
|
|
3539
3659
|
function fmtMarker(file, src, marker) {
|
|
3540
3660
|
if (undefined !== marker) {
|
|
3541
3661
|
return marker;
|
|
@@ -3591,11 +3711,8 @@ function fmtOne(name, src, flags, marker) {
|
|
|
3591
3711
|
}
|
|
3592
3712
|
return flags.check ? 1 : strict;
|
|
3593
3713
|
}
|
|
3594
|
-
//
|
|
3595
|
-
//
|
|
3596
|
-
// cli.test.ts run each through a child process instead, so these two
|
|
3597
|
-
// lines are excluded from the in-process count, as the stdio wiring
|
|
3598
|
-
// of lsp-server.ts is.
|
|
3714
|
+
// Excluded: the real pair takes the process stdio, so ts/test/cli.test.ts
|
|
3715
|
+
// drives each server through a child process instead.
|
|
3599
3716
|
/* node:coverage ignore next 4 */
|
|
3600
3717
|
const SERVERS = {
|
|
3601
3718
|
lsp: () => void (0, lsp_server_1.main)(),
|
|
@@ -3635,22 +3752,301 @@ function parseTrustArg(value) {
|
|
|
3635
3752
|
}
|
|
3636
3753
|
return undefined;
|
|
3637
3754
|
}
|
|
3755
|
+
const HELP_VERB_HELP = 'aontu help [topic] (try `aontu help` for the topics)';
|
|
3756
|
+
const EXPLAIN_HELP = 'aontu explain <code> (try `aontu explain --list`)';
|
|
3757
|
+
function helpIndexText(index) {
|
|
3758
|
+
const width = index.reduce((w, t) => Math.max(w, t.topic.length), 0);
|
|
3759
|
+
return 'aontu help <topic> — the language, offline.\n\n' +
|
|
3760
|
+
index.map((t) => ' ' + t.topic.padEnd(width) + ' ' + t.summary).join('\n') +
|
|
3761
|
+
'\n\n' +
|
|
3762
|
+
'`aontu --help` documents the verbs, their flags and their exit\n' +
|
|
3763
|
+
'codes. `aontu explain <code>` explains one error code.\n' +
|
|
3764
|
+
'Start at `aontu help tasks` if you know the job but not the verb.';
|
|
3765
|
+
}
|
|
3766
|
+
function runHelp(argv) {
|
|
3767
|
+
let format = 'text';
|
|
3768
|
+
const topics = [];
|
|
3769
|
+
for (let i = 0; i < argv.length; i++) {
|
|
3770
|
+
const arg = argv[i];
|
|
3771
|
+
if ('-h' === arg || '--help' === arg) {
|
|
3772
|
+
process.stdout.write(HELP);
|
|
3773
|
+
return 0;
|
|
3774
|
+
}
|
|
3775
|
+
else if ('--format' === arg) {
|
|
3776
|
+
const f = argv[++i];
|
|
3777
|
+
if ('text' !== f && 'json' !== f) {
|
|
3778
|
+
process.stderr.write('aontu: --format needs text or json\n');
|
|
3779
|
+
return 2;
|
|
3780
|
+
}
|
|
3781
|
+
format = f;
|
|
3782
|
+
}
|
|
3783
|
+
else if (arg.startsWith('-')) {
|
|
3784
|
+
process.stderr.write(`aontu: unknown help option ${arg} (try --help)\n`);
|
|
3785
|
+
return 2;
|
|
3786
|
+
}
|
|
3787
|
+
else {
|
|
3788
|
+
topics.push(arg);
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
if (1 < topics.length) {
|
|
3792
|
+
process.stderr.write(`aontu: help takes one topic\n${HELP_VERB_HELP}\n`);
|
|
3793
|
+
return 2;
|
|
3794
|
+
}
|
|
3795
|
+
if (0 === topics.length) {
|
|
3796
|
+
process.stdout.write(('json' === format
|
|
3797
|
+
? (0, aontu_1.exactJSON)({
|
|
3798
|
+
aontu: { version: version(), verb: 'help' },
|
|
3799
|
+
topics: helpdoc_1.HELPDOC.map((t) => ({ topic: t.topic, summary: t.summary, source: t.source })),
|
|
3800
|
+
}, 2)
|
|
3801
|
+
: helpIndexText(helpdoc_1.HELPDOC)) + '\n');
|
|
3802
|
+
return 0;
|
|
3803
|
+
}
|
|
3804
|
+
const found = helpdoc_1.HELPDOC.find((t) => topics[0] === t.topic);
|
|
3805
|
+
if (null != found) {
|
|
3806
|
+
if ('json' === format) {
|
|
3807
|
+
process.stdout.write((0, aontu_1.exactJSON)({
|
|
3808
|
+
aontu: { version: version(), verb: 'help' },
|
|
3809
|
+
topic: found.topic,
|
|
3810
|
+
summary: found.summary,
|
|
3811
|
+
source: found.source,
|
|
3812
|
+
text: found.text,
|
|
3813
|
+
}, 2) + '\n');
|
|
3814
|
+
return 0;
|
|
3815
|
+
}
|
|
3816
|
+
process.stdout.write(found.text);
|
|
3817
|
+
return 0;
|
|
3818
|
+
}
|
|
3819
|
+
// AN UNKNOWN TOPIC IS A USAGE ERROR AND NAMES THE ALTERNATIVES,
|
|
3820
|
+
// because the caller who typed it has no other way to find out what
|
|
3821
|
+
// exists -- that is the whole condition this verb was added for.
|
|
3822
|
+
process.stderr.write(`aontu: no help topic \`${topics[0]}\`\n` +
|
|
3823
|
+
`aontu: topics are ${helpdoc_1.HELPDOC.map((t) => t.topic).join(', ')}\n`);
|
|
3824
|
+
return 2;
|
|
3825
|
+
}
|
|
3826
|
+
// The dynamic prefixes a generated code extends (`func:upper`,
|
|
3827
|
+
// `op[+]`). Mirrors CODE_PREFIXES in ts/src/hints.ts, which is not
|
|
3828
|
+
// exported; a code that extends one is registered through its prefix
|
|
3829
|
+
// and carries that prefix's hint.
|
|
3830
|
+
const EXPLAIN_PREFIXES = ['func:', 'op:', 'op[', 'var[', 'ref['];
|
|
3831
|
+
function explainCode(code) {
|
|
3832
|
+
const cls = (0, hints_1.codeClass)(code);
|
|
3833
|
+
let hint = hints_1.hints[code] ?? '';
|
|
3834
|
+
let registered = null != hints_1.codeClasses[code];
|
|
3835
|
+
if (!registered) {
|
|
3836
|
+
for (const prefix of EXPLAIN_PREFIXES) {
|
|
3837
|
+
if (code.startsWith(prefix)) {
|
|
3838
|
+
// No guard on `hint` here: every hint key is also a registry
|
|
3839
|
+
// key (the spec suite asserts codeClasses set-equal with
|
|
3840
|
+
// test/spec/errcodes.tsv, and hints is a subset of it), so a
|
|
3841
|
+
// code that reaches this loop is unregistered and therefore
|
|
3842
|
+
// has no hint of its own.
|
|
3843
|
+
registered = true;
|
|
3844
|
+
hint = hints_1.hints[prefix] ?? '';
|
|
3845
|
+
break;
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
return { cls, hint, registered };
|
|
3850
|
+
}
|
|
3851
|
+
// Every code in the shared registry, sorted by code point so both
|
|
3852
|
+
// ports list them in the same order.
|
|
3853
|
+
function explainCodes() {
|
|
3854
|
+
return Object.keys(hints_1.codeClasses).sort(keyorder_1.cmpCodePoint);
|
|
3855
|
+
}
|
|
3856
|
+
function explainListText(format) {
|
|
3857
|
+
const codes = explainCodes();
|
|
3858
|
+
if ('json' === format) {
|
|
3859
|
+
return (0, aontu_1.exactJSON)({
|
|
3860
|
+
aontu: { version: version(), verb: 'explain' },
|
|
3861
|
+
codes: codes.map((code) => ({
|
|
3862
|
+
code,
|
|
3863
|
+
class: (0, hints_1.codeClass)(code),
|
|
3864
|
+
// Whether this port carries explanation text for the code. The
|
|
3865
|
+
// registry is in parity; the hint tables are not, so a consumer
|
|
3866
|
+
// that wants only explained codes can filter rather than guess.
|
|
3867
|
+
explained: '' !== explainCode(code).hint,
|
|
3868
|
+
})),
|
|
3869
|
+
}, 2);
|
|
3870
|
+
}
|
|
3871
|
+
const width = codes.reduce((w, c) => Math.max(w, c.length), 0);
|
|
3872
|
+
return codes.map((c) => c.padEnd(width) + ' ' + (0, hints_1.codeClass)(c) +
|
|
3873
|
+
('' === explainCode(c).hint ? ' (no text)' : '')).join('\n');
|
|
3874
|
+
}
|
|
3875
|
+
function runExplain(argv) {
|
|
3876
|
+
let format = 'text';
|
|
3877
|
+
let list = false;
|
|
3878
|
+
const codes = [];
|
|
3879
|
+
for (let i = 0; i < argv.length; i++) {
|
|
3880
|
+
const arg = argv[i];
|
|
3881
|
+
if ('-h' === arg || '--help' === arg) {
|
|
3882
|
+
process.stdout.write(HELP);
|
|
3883
|
+
return 0;
|
|
3884
|
+
}
|
|
3885
|
+
else if ('--list' === arg) {
|
|
3886
|
+
list = true;
|
|
3887
|
+
}
|
|
3888
|
+
else if ('--format' === arg) {
|
|
3889
|
+
const f = argv[++i];
|
|
3890
|
+
if ('text' !== f && 'json' !== f) {
|
|
3891
|
+
process.stderr.write('aontu: --format needs text or json\n');
|
|
3892
|
+
return 2;
|
|
3893
|
+
}
|
|
3894
|
+
format = f;
|
|
3895
|
+
}
|
|
3896
|
+
else if (arg.startsWith('-')) {
|
|
3897
|
+
process.stderr.write(`aontu: unknown explain option ${arg} (try --help)\n`);
|
|
3898
|
+
return 2;
|
|
3899
|
+
}
|
|
3900
|
+
else {
|
|
3901
|
+
codes.push(arg);
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
if (list) {
|
|
3905
|
+
if (0 < codes.length) {
|
|
3906
|
+
process.stderr.write(`aontu: --list takes no code\n${EXPLAIN_HELP}\n`);
|
|
3907
|
+
return 2;
|
|
3908
|
+
}
|
|
3909
|
+
process.stdout.write(explainListText(format) + '\n');
|
|
3910
|
+
return 0;
|
|
3911
|
+
}
|
|
3912
|
+
if (1 !== codes.length) {
|
|
3913
|
+
process.stderr.write(`aontu: explain needs one code\n${EXPLAIN_HELP}\n`);
|
|
3914
|
+
return 2;
|
|
3915
|
+
}
|
|
3916
|
+
const code = codes[0];
|
|
3917
|
+
const { cls, hint, registered } = explainCode(code);
|
|
3918
|
+
if (!registered) {
|
|
3919
|
+
// AN UNKNOWN CODE IS A USAGE ERROR AND NAMES NEAR MATCHES. A
|
|
3920
|
+
// caller reading a code out of a report has almost certainly typed
|
|
3921
|
+
// it correctly, so the likely cause is a code from another tool or
|
|
3922
|
+
// a truncated one, and the near matches say which.
|
|
3923
|
+
process.stderr.write(`aontu: no such error code \`${code}\`\n`);
|
|
3924
|
+
const near = nearestVerb(code, explainCodes());
|
|
3925
|
+
if ('' !== near) {
|
|
3926
|
+
process.stderr.write(`aontu: did you mean \`${near}\`?\n`);
|
|
3927
|
+
}
|
|
3928
|
+
process.stderr.write('aontu: `aontu explain --list` lists every registered code\n');
|
|
3929
|
+
return 2;
|
|
3930
|
+
}
|
|
3931
|
+
if ('json' === format) {
|
|
3932
|
+
process.stdout.write((0, aontu_1.exactJSON)({
|
|
3933
|
+
aontu: { version: version(), verb: 'explain' },
|
|
3934
|
+
code,
|
|
3935
|
+
class: cls,
|
|
3936
|
+
hint,
|
|
3937
|
+
}, 2) + '\n');
|
|
3938
|
+
return 0;
|
|
3939
|
+
}
|
|
3940
|
+
// A REGISTERED CODE WITH NO HINT SAYS SO rather than printing an
|
|
3941
|
+
// empty block, which would read as an explanation that happened to
|
|
3942
|
+
// be blank.
|
|
3943
|
+
const body = '' === hint
|
|
3944
|
+
? '(no explanation text is registered for this code)'
|
|
3945
|
+
: hint;
|
|
3946
|
+
process.stdout.write(`code: ${code}\nclass: ${cls}\n\n${body}\n`);
|
|
3947
|
+
return 0;
|
|
3948
|
+
}
|
|
3949
|
+
const INIT_HELP = 'aontu init [dir] (try --help)';
|
|
3950
|
+
function runInit(argv) {
|
|
3951
|
+
const dirs = [];
|
|
3952
|
+
for (const arg of argv) {
|
|
3953
|
+
if ('-h' === arg || '--help' === arg) {
|
|
3954
|
+
process.stdout.write(HELP);
|
|
3955
|
+
return 0;
|
|
3956
|
+
}
|
|
3957
|
+
if (arg.startsWith('-')) {
|
|
3958
|
+
process.stderr.write(`aontu: unknown init option ${arg} (try --help)\n`);
|
|
3959
|
+
return 2;
|
|
3960
|
+
}
|
|
3961
|
+
dirs.push(arg);
|
|
3962
|
+
}
|
|
3963
|
+
if (1 < dirs.length) {
|
|
3964
|
+
process.stderr.write(`aontu: init takes one directory\n${INIT_HELP}\n`);
|
|
3965
|
+
return 2;
|
|
3966
|
+
}
|
|
3967
|
+
const dir = dirs[0] ?? '.';
|
|
3968
|
+
const standing = helpdoc_1.INITDOC.filter((f) => (0, node_fs_1.existsSync)((0, node_path_1.join)(dir, f.name)));
|
|
3969
|
+
if (0 < standing.length) {
|
|
3970
|
+
process.stderr.write(`aontu: ${dir} already holds ${standing.map((f) => f.name).join(', ')}\n` +
|
|
3971
|
+
'aontu: init never overwrites; move them aside or name an' +
|
|
3972
|
+
' empty directory\n');
|
|
3973
|
+
return 2;
|
|
3974
|
+
}
|
|
3975
|
+
try {
|
|
3976
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
3977
|
+
for (const f of helpdoc_1.INITDOC) {
|
|
3978
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, f.name), f.text, { mode: f.mode });
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
catch (err) {
|
|
3982
|
+
process.stderr.write(`aontu: cannot write in ${dir}: ${err.message}\n`);
|
|
3983
|
+
return 2;
|
|
3984
|
+
}
|
|
3985
|
+
process.stdout.write(helpdoc_1.INITDOC.map((f) => (0, node_path_1.join)(dir, f.name)).join('\n') + '\n' +
|
|
3986
|
+
'\nA model, an instance of it, and the four questions to ask.\n' +
|
|
3987
|
+
'Run the checks: sh ' + (0, node_path_1.join)(dir, 'check.sh') + '\n' +
|
|
3988
|
+
'Learn the language: aontu help language\n');
|
|
3989
|
+
return 0;
|
|
3990
|
+
}
|
|
3991
|
+
const KNOWN_VERBS = [
|
|
3992
|
+
'agentsmd', 'allow', 'breaking', 'explain', 'fmt', 'get', 'hash',
|
|
3993
|
+
'help', 'init', 'jsonschema', 'lsp', 'mcp', 'mod', 'reaches',
|
|
3994
|
+
'relations', 'render', 'set', 'subsume', 'template', 'trim', 'vet',
|
|
3995
|
+
'view', 'why',
|
|
3996
|
+
];
|
|
3997
|
+
exports.KNOWN_VERBS = KNOWN_VERBS;
|
|
3998
|
+
// looksLikeVerb reports whether an unreadable argument was meant as a
|
|
3999
|
+
// verb rather than as a path. A bare word has no separator and no
|
|
4000
|
+
// extension; `./help`, `help.aon`, `/tmp/help` and `sub/dir` are paths
|
|
4001
|
+
// and keep the file diagnosis. Mirrors go/cmd/aontu/main.go.
|
|
4002
|
+
function looksLikeVerb(arg) {
|
|
4003
|
+
return '' !== arg &&
|
|
4004
|
+
!/[/\\.]/.test(arg) &&
|
|
4005
|
+
!arg.startsWith('-');
|
|
4006
|
+
}
|
|
4007
|
+
function nearestVerb(word, verbs) {
|
|
4008
|
+
let best = '';
|
|
4009
|
+
let bestDist = Infinity;
|
|
4010
|
+
const limit = Math.min(3, 1 + Math.floor(word.length / 4));
|
|
4011
|
+
for (const v of [...verbs].sort(keyorder_1.cmpCodePoint)) {
|
|
4012
|
+
const d = editDistance(word.toLowerCase(), v);
|
|
4013
|
+
if (d < bestDist) {
|
|
4014
|
+
best = v;
|
|
4015
|
+
bestDist = d;
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
return bestDist > limit ? '' : best;
|
|
4019
|
+
}
|
|
4020
|
+
function editDistance(a, b) {
|
|
4021
|
+
const ar = [...a];
|
|
4022
|
+
const br = [...b];
|
|
4023
|
+
let prev2 = new Array(br.length + 1).fill(0);
|
|
4024
|
+
let prev = new Array(br.length + 1).fill(0).map((_, j) => j);
|
|
4025
|
+
let cur = new Array(br.length + 1).fill(0);
|
|
4026
|
+
for (let i = 1; i <= ar.length; i++) {
|
|
4027
|
+
cur[0] = i;
|
|
4028
|
+
for (let j = 1; j <= br.length; j++) {
|
|
4029
|
+
const cost = ar[i - 1] === br[j - 1] ? 0 : 1;
|
|
4030
|
+
let m = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
|
|
4031
|
+
if (1 < i && 1 < j &&
|
|
4032
|
+
ar[i - 1] === br[j - 2] && ar[i - 2] === br[j - 1] &&
|
|
4033
|
+
prev2[j - 2] + 1 < m) {
|
|
4034
|
+
m = prev2[j - 2] + 1;
|
|
4035
|
+
}
|
|
4036
|
+
cur[j] = m;
|
|
4037
|
+
}
|
|
4038
|
+
prev2 = [...prev];
|
|
4039
|
+
prev = [...cur];
|
|
4040
|
+
}
|
|
4041
|
+
return prev[br.length];
|
|
4042
|
+
}
|
|
3638
4043
|
function main(argv, servers = SERVERS) {
|
|
3639
|
-
// COLOUR OFF WHEN THE DESTINATION IS NOT A TERMINAL. Error frames
|
|
3640
|
-
// hardcoded their ANSI escapes, so a piped report and a `--jsonl`
|
|
3641
|
-
// answer carried terminal control codes into whatever read them (the
|
|
3642
|
-
// review's finding F). `NO_COLOR` is honoured by the library itself;
|
|
3643
|
-
// only the CLI can see whether its stderr is a terminal, so only the
|
|
3644
|
-
// CLI can make this call. `undefined` means "leave it to NO_COLOR".
|
|
3645
4044
|
(0, aontu_1.setColor)(true === process.stderr.isTTY ? undefined : false);
|
|
3646
4045
|
let mode = 'json';
|
|
3647
|
-
//
|
|
3648
|
-
//
|
|
3649
|
-
//
|
|
3650
|
-
|
|
3651
|
-
// subcommand, fell through to this loop as a file name, and was
|
|
3652
|
-
// overwritten twice. In a tool loop that reads as a passing
|
|
3653
|
-
// validation. Counting them is what lets the refusal below happen.
|
|
4046
|
+
// THE REPORT FORM (G11 phase 7), default text: every existing caller
|
|
4047
|
+
// reads exactly what it always read, and a caller that asks for json
|
|
4048
|
+
// gets the failure in the finding shape every other verb reports.
|
|
4049
|
+
let format = 'text';
|
|
3654
4050
|
const files = [];
|
|
3655
4051
|
let trust = { kind: 'system-warn', textExt: [] };
|
|
3656
4052
|
let textExt = [];
|
|
@@ -3659,15 +4055,6 @@ function main(argv, servers = SERVERS) {
|
|
|
3659
4055
|
// than the design's --json, which would read as the `:json` output
|
|
3660
4056
|
// mode the REPL already has.
|
|
3661
4057
|
let jsonl = false;
|
|
3662
|
-
// Subcommand dispatch, and deliberately only for a FIRST argument:
|
|
3663
|
-
// `aontu vet` is the verb, while `aontu somefile vet` keeps meaning
|
|
3664
|
-
// what it always did. A file named `vet` is still reachable as
|
|
3665
|
-
// `aontu ./vet`.
|
|
3666
|
-
//
|
|
3667
|
-
// Promise.resolve either way: a non-watch run returns its exit class
|
|
3668
|
-
// synchronously (and has already written its report), while `--watch`
|
|
3669
|
-
// resolves only when the watch ends — so one await-shaped line serves
|
|
3670
|
-
// both without a branch to keep covered.
|
|
3671
4058
|
if ('vet' === argv[2]) {
|
|
3672
4059
|
return void Promise.resolve(runVet(argv.slice(3))).then(finish);
|
|
3673
4060
|
}
|
|
@@ -3693,6 +4080,9 @@ function main(argv, servers = SERVERS) {
|
|
|
3693
4080
|
if ('set' === argv[2]) {
|
|
3694
4081
|
return finish(runSet(argv.slice(3)));
|
|
3695
4082
|
}
|
|
4083
|
+
if ('allow' === argv[2]) {
|
|
4084
|
+
return finish(runAllow(argv.slice(3)));
|
|
4085
|
+
}
|
|
3696
4086
|
if ('why' === argv[2]) {
|
|
3697
4087
|
return finish(runWhy(argv.slice(3)));
|
|
3698
4088
|
}
|
|
@@ -3702,6 +4092,17 @@ function main(argv, servers = SERVERS) {
|
|
|
3702
4092
|
if ('hash' === argv[2]) {
|
|
3703
4093
|
return finish(runHash(argv.slice(3)));
|
|
3704
4094
|
}
|
|
4095
|
+
// G11 phases 1 and 3. Dispatched with the rest, so `aontu ./help`
|
|
4096
|
+
// still reads a file named help exactly as `aontu ./vet` does.
|
|
4097
|
+
if ('help' === argv[2]) {
|
|
4098
|
+
return finish(runHelp(argv.slice(3)));
|
|
4099
|
+
}
|
|
4100
|
+
if ('explain' === argv[2]) {
|
|
4101
|
+
return finish(runExplain(argv.slice(3)));
|
|
4102
|
+
}
|
|
4103
|
+
if ('init' === argv[2]) {
|
|
4104
|
+
return finish(runInit(argv.slice(3)));
|
|
4105
|
+
}
|
|
3705
4106
|
if ('mod' === argv[2]) {
|
|
3706
4107
|
return finish(runMod(argv.slice(3)));
|
|
3707
4108
|
}
|
|
@@ -3748,6 +4149,14 @@ function main(argv, servers = SERVERS) {
|
|
|
3748
4149
|
}
|
|
3749
4150
|
trust = parsed;
|
|
3750
4151
|
}
|
|
4152
|
+
else if ('--format' === arg) {
|
|
4153
|
+
const f = args[++i];
|
|
4154
|
+
if ('text' !== f && 'json' !== f) {
|
|
4155
|
+
process.stderr.write('aontu: --format needs text or json\n');
|
|
4156
|
+
return finish(2);
|
|
4157
|
+
}
|
|
4158
|
+
format = f;
|
|
4159
|
+
}
|
|
3751
4160
|
else if ('--jsonl' === arg) {
|
|
3752
4161
|
jsonl = true;
|
|
3753
4162
|
// A JSONL answer is machine-read by definition, even when the
|
|
@@ -3782,27 +4191,16 @@ function main(argv, servers = SERVERS) {
|
|
|
3782
4191
|
files.push(arg);
|
|
3783
4192
|
}
|
|
3784
4193
|
}
|
|
3785
|
-
// ONE DOCUMENT. The bare form has always been `aontu [options]
|
|
3786
|
-
// [file]`, singular, and anything past the first was silently
|
|
3787
|
-
// discarded rather than refused -- so every way of getting the verb
|
|
3788
|
-
// wrong (a typo, a verb this port does not have, a verb spelled for
|
|
3789
|
-
// another tool) ended in a plausible answer about the wrong file.
|
|
3790
|
-
// Exit 2, the usage class, and the message names the cause rather
|
|
3791
|
-
// than the symptom: nothing here can tell a mistyped verb from a
|
|
3792
|
-
// second file, but the reader can.
|
|
3793
4194
|
if (1 < files.length) {
|
|
3794
4195
|
process.stderr.write(`aontu: the bare command evaluates one document, and ${files.length}` +
|
|
3795
4196
|
' were given\naontu: a mistyped verb reads as a file name' +
|
|
3796
4197
|
' (try --help)\n');
|
|
3797
4198
|
return finish(2);
|
|
3798
4199
|
}
|
|
3799
|
-
// The extensions ride with the capability from here on, so the three
|
|
3800
|
-
// entry shapes below (file, REPL, stdin) each get them by threading
|
|
3801
|
-
// the one value they already thread.
|
|
3802
4200
|
trust = { ...trust, textExt };
|
|
3803
4201
|
const file = files[0];
|
|
3804
4202
|
if (null != file) {
|
|
3805
|
-
finish(runFile(file, mode, trust));
|
|
4203
|
+
finish(runFile(file, mode, format, trust));
|
|
3806
4204
|
}
|
|
3807
4205
|
// `--jsonl` overrides the TTY gate: the mode exists to be DRIVEN by
|
|
3808
4206
|
// a harness over a pipe, so gating it on an interactive terminal
|
|
@@ -3812,7 +4210,7 @@ function main(argv, servers = SERVERS) {
|
|
|
3812
4210
|
runRepl(mode, jsonl, trust);
|
|
3813
4211
|
}
|
|
3814
4212
|
else {
|
|
3815
|
-
runStdin(mode, trust).then((code) => finish(code));
|
|
4213
|
+
runStdin(mode, format, trust).then((code) => finish(code));
|
|
3816
4214
|
}
|
|
3817
|
-
} /* node:coverage ignore next
|
|
4215
|
+
} /* node:coverage ignore next 20 */
|
|
3818
4216
|
//# sourceMappingURL=cli.js.map
|