aontu 0.65.0 → 0.66.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/dist/aontu.d.ts +1 -1
- package/dist/aontu.js +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +220 -2
- package/dist/cli.js.map +1 -1
- package/dist/helpdoc.js +1 -1
- package/dist/helpdoc.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/grammar/aontu.gbnf +8 -18
- package/grammar/aontu.lark +6 -14
- package/grammar/aontu.tmLanguage.json +4 -6
- package/package.json +3 -2
- package/skill/tasks.md +12 -7
- package/src/aontu.ts +1 -1
- package/src/cli.ts +243 -3
- package/src/helpdoc.ts +1 -1
package/grammar/aontu.gbnf
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Aontu — published grammar (GBNF), for constrained decoding.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# This grammar covers the DOCUMENTED EMISSION SURFACE: what a model
|
|
4
|
+
# should be allowed to write, which is a superset of JSON plus the
|
|
5
|
+
# operators, constraints and marks the language's own canonical form
|
|
6
|
+
# emits.
|
|
7
7
|
#
|
|
8
8
|
# It is CONSERVATIVE BY CONSTRUCTION: it may accept less than the
|
|
9
9
|
# parser does, never more. Two deliberate exclusions:
|
|
@@ -21,19 +21,9 @@
|
|
|
21
21
|
|
|
22
22
|
root ::= ws value ws
|
|
23
23
|
|
|
24
|
-
# No pipe. `|>`
|
|
25
|
-
# `x
|
|
26
|
-
#
|
|
27
|
-
# That ADR says "the operator from both grammars", and both kept it
|
|
28
|
-
# anyway — the engine, the suite, the error code and the docs were
|
|
29
|
-
# done, these two files were not. Nothing caught it, and nothing here
|
|
30
|
-
# could have: the parity discipline above runs one way, requiring every
|
|
31
|
-
# canon output to PARSE, so a rule canon never emits is unreachable and
|
|
32
|
-
# a test that only reads this file cannot tell a dead rule from a live
|
|
33
|
-
# one. What it cost was the direction that matters to this file's
|
|
34
|
-
# consumer — a decoder constrained by the old text could emit `|>` and
|
|
35
|
-
# have the engine refuse it with `aontu/unexpected`. Fixed before the
|
|
36
|
-
# next release, so no published grammar ever disagreed with its engine.
|
|
24
|
+
# No pipe. `|>` is not in the language; `x |> f(a)` is written
|
|
25
|
+
# `f(x, a)`. A decoder constrained by this file cannot emit a spelling
|
|
26
|
+
# the engine refuses with `aontu/unexpected`.
|
|
37
27
|
value ::= disjunct
|
|
38
28
|
|
|
39
29
|
disjunct ::= conjunct ( ws "|" ws conjunct )*
|
|
@@ -108,7 +98,7 @@ ref ::= "$" ( "." segment )+ | ( "." segment )+
|
|
|
108
98
|
# `a:6-2` as a parse error), so admitting it over-approximated.
|
|
109
99
|
segment ::= ( [a-zA-Z0-9_] )+
|
|
110
100
|
|
|
111
|
-
# The placeholder: a hole a call is filled through
|
|
101
|
+
# The placeholder: a hole a call is filled through. BARE
|
|
112
102
|
# only -- `"_"` is an ordinary string, and a longer bare word
|
|
113
103
|
# containing it is ordinary text.
|
|
114
104
|
place ::= "_"
|
package/grammar/aontu.lark
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// Aontu — published grammar (lark), for constrained decoding.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// named there is named here.
|
|
3
|
+
// The same rules as grammar/aontu.gbnf, for the other consumer: keep
|
|
4
|
+
// the two in step, and note that ts/test/grammar.test.ts checks that
|
|
5
|
+
// every rule named there is named here.
|
|
7
6
|
//
|
|
8
7
|
// The gbnf file is the one the parity test INTERPRETS against the
|
|
9
8
|
// shared suite's canonical output; this file is its transcription, so
|
|
@@ -15,16 +14,9 @@
|
|
|
15
14
|
|
|
16
15
|
root: value
|
|
17
16
|
|
|
18
|
-
// No pipe. `|>`
|
|
19
|
-
// `x
|
|
20
|
-
//
|
|
21
|
-
// That ADR says "the operator from both grammars", and both kept it
|
|
22
|
-
// anyway — the engine, the suite, the error code and the docs were
|
|
23
|
-
// done, these two files were not. Nothing caught it: a rule nothing
|
|
24
|
-
// derives never matches, so every canon still parsed and every test
|
|
25
|
-
// stayed green while the grammars offered a spelling the parser
|
|
26
|
-
// refuses with `aontu/unexpected`. Fixed before the next release, so
|
|
27
|
-
// no published grammar ever disagreed with its engine.
|
|
17
|
+
// No pipe. `|>` is not in the language; `x |> f(a)` is written
|
|
18
|
+
// `f(x, a)`. The engine refuses the old spelling with
|
|
19
|
+
// `aontu/unexpected`, and all three published grammars agree with it.
|
|
28
20
|
value: disjunct
|
|
29
21
|
|
|
30
22
|
disjunct: conjunct ("|" conjunct)*
|
|
@@ -14,16 +14,14 @@
|
|
|
14
14
|
"rejects AS THOUGH it were valid, which is why the builtin list below is",
|
|
15
15
|
"the engine's own set and not a hand-picked sample of it.",
|
|
16
16
|
"",
|
|
17
|
-
"THE BUILTIN LIST IS A COPY, AND A COPY DRIFTS
|
|
18
|
-
"`same` — never a builtin — and was missing `acyclic`, `inverse` and",
|
|
19
|
-
"`rel` for two releases, with every test green. So the same guard the",
|
|
17
|
+
"THE BUILTIN LIST IS A COPY, AND A COPY DRIFTS, so the same guard the",
|
|
20
18
|
"other two grammars have covers this file: ts/test/grammar.test.ts,",
|
|
21
19
|
"`the-textmate-grammar-names-exactly-the-engine-builtins`, asserts this",
|
|
22
20
|
"list against BUILTIN_FUNCS in BOTH directions. Add a builtin to the",
|
|
23
21
|
"engine without adding it here and that test fails.",
|
|
24
22
|
"",
|
|
25
|
-
"No `|>`:
|
|
26
|
-
"
|
|
23
|
+
"No `|>`: the pipe is not in the language, and all three published",
|
|
24
|
+
"grammars agree about that."
|
|
27
25
|
],
|
|
28
26
|
"name": "aontu",
|
|
29
27
|
"displayName": "Aontu",
|
|
@@ -130,7 +128,7 @@
|
|
|
130
128
|
"match": "\\b(true|false|null)\\b"
|
|
131
129
|
},
|
|
132
130
|
{
|
|
133
|
-
"comment": "`_` is a literal of the language
|
|
131
|
+
"comment": "`_` is a literal of the language, not text.",
|
|
134
132
|
"name": "constant.language.placeholder.aontu",
|
|
135
133
|
"match": "(?<![A-Za-z0-9_])_(?![A-Za-z0-9_])"
|
|
136
134
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aontu",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.66.0",
|
|
4
4
|
"main": "dist/aontu.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"types": "dist/aontu.d.ts",
|
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
"@tabnas/parser": "0.9.7",
|
|
79
79
|
"@tabnas/path": "0.3.2",
|
|
80
80
|
"@tabnas/toml": "0.5.7",
|
|
81
|
-
"@tabnas/yaml": "0.5.7"
|
|
81
|
+
"@tabnas/yaml": "0.5.7",
|
|
82
|
+
"jostraca": "0.38.0"
|
|
82
83
|
},
|
|
83
84
|
"devDependencies": {
|
|
84
85
|
"@tabnas/railroad": "0.3.6",
|
package/skill/tasks.md
CHANGED
|
@@ -82,16 +82,21 @@ aontu trim --check model.aon # entries whose removal changes nothing
|
|
|
82
82
|
## Generate code from a model, and hold it
|
|
83
83
|
|
|
84
84
|
```
|
|
85
|
-
aontu
|
|
86
|
-
aontu
|
|
85
|
+
aontu render gen.aon build # write the files below build/
|
|
86
|
+
aontu render --check gen.aon build # write nothing; exit 1 when build/ has drifted
|
|
87
|
+
aontu render gen/ build # every generator directly in gen/, as one run
|
|
88
|
+
aontu model get out gen.aon # the component tree: files, and the lines in them
|
|
89
|
+
aontu trace gen.aon # what rule wrote each line
|
|
87
90
|
```
|
|
88
91
|
|
|
89
92
|
A generator answers a **component tree** -- `file(name, [lines])` and
|
|
90
|
-
its neighbours -- which
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
its neighbours -- which `aontu render` hands to jostraca, the generator
|
|
94
|
+
runtime, to write. A tree that is one file is written to the path
|
|
95
|
+
itself, and a folder of generators is one run. `--check` compares with
|
|
96
|
+
what is committed: the model is the
|
|
97
|
+
truth, the code is the claim, and drift is a finding. `aontu trace`
|
|
98
|
+
answers the other direction: for a line of the output, the model node
|
|
99
|
+
and the rule behind it.
|
|
95
100
|
|
|
96
101
|
## Ask what a model says, and why
|
|
97
102
|
|
package/src/aontu.ts
CHANGED
package/src/cli.ts
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
import { evalFailure } from './query'
|
|
6
6
|
// __importStar downlevel helper, whose branches no supported Node takes.
|
|
7
7
|
import {
|
|
8
|
-
existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync,
|
|
8
|
+
existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync,
|
|
9
|
+
writeFileSync,
|
|
9
10
|
} from 'node:fs'
|
|
11
|
+
import type { Dirent } from 'node:fs'
|
|
10
12
|
import { basename, dirname, join, resolve } from 'node:path'
|
|
11
13
|
import { tmpdir } from 'node:os'
|
|
12
14
|
import { createInterface } from 'node:readline'
|
|
@@ -97,6 +99,9 @@ const HELP = `Usage: aontu [options] [file]
|
|
|
97
99
|
[--profile <file>] <file>
|
|
98
100
|
aontu trace [--at <path>] [--format json] [--marker <token>]
|
|
99
101
|
[--profile <file>] <file>
|
|
102
|
+
aontu render [--check] [--at <path>] [--format json]
|
|
103
|
+
[--marker <token>] [--profile <file>]
|
|
104
|
+
<file|folder> <path>
|
|
100
105
|
aontu hash [options] <file>
|
|
101
106
|
aontu sync [--frozen] [options] [dir]
|
|
102
107
|
aontu add <pkg>[@<version>] [options] [dir]
|
|
@@ -411,6 +416,31 @@ every other line is a line of output.
|
|
|
411
416
|
|
|
412
417
|
Template exit codes: 0 written, 1 --check drift, 2 usage or I/O.
|
|
413
418
|
|
|
419
|
+
Render options:
|
|
420
|
+
--check Compare what the generator writes with what <path>
|
|
421
|
+
holds, write nothing, and exit 1 on drift
|
|
422
|
+
--at <path> Where the component tree lives in the document
|
|
423
|
+
(default $.out)
|
|
424
|
+
--format json Print the files written, or the drift, as JSON
|
|
425
|
+
--marker <t> The file is a generator, and this is its marker
|
|
426
|
+
(default //-, and #- --- /*- <!--- by extension)
|
|
427
|
+
--profile <f> A profile file, whose template.ext names the
|
|
428
|
+
extensions it marks and template.marker the marker
|
|
429
|
+
|
|
430
|
+
The render verb writes the component tree a generator answers. The
|
|
431
|
+
tree is handed to jostraca, the generator runtime, which writes the
|
|
432
|
+
files: a tree that is one file is written to <path> itself, unless
|
|
433
|
+
<path> is a directory, and any other tree is written below <path>.
|
|
434
|
+
With --check nothing is written and <path> is compared with what the
|
|
435
|
+
generator writes, one "kind: file" line per difference.
|
|
436
|
+
A folder as the generator is a set: every regular file directly in
|
|
437
|
+
it, dotfiles aside, in name order, and their trees are written below
|
|
438
|
+
<path> as one run, so a path two of them claim is refused. A file with
|
|
439
|
+
no marker line in it is refused by name.
|
|
440
|
+
|
|
441
|
+
Render exit codes: 0 written or clean, 1 --check drift, 2 usage or
|
|
442
|
+
I/O, 4 the document does not stand up, or --at names nothing.
|
|
443
|
+
|
|
414
444
|
Model set options:
|
|
415
445
|
--entry <file> The document the change is checked against
|
|
416
446
|
--overlay <file> The file the change is appended to (created if
|
|
@@ -2880,6 +2910,211 @@ function runTrace(argv: string[]): number {
|
|
|
2880
2910
|
}
|
|
2881
2911
|
|
|
2882
2912
|
|
|
2913
|
+
// ---------------------------------------------------------------------
|
|
2914
|
+
// The writer (ADR-040). A generator answers a component tree; this verb
|
|
2915
|
+
// hands it to jostraca, which writes the files below a path or holds
|
|
2916
|
+
// them to it.
|
|
2917
|
+
|
|
2918
|
+
const RENDER_HELP =
|
|
2919
|
+
'aontu render [--check] [--at <path>] [--format json] ' +
|
|
2920
|
+
'[--marker <token>] [--profile <file>] <file|folder> <path> (try --help)'
|
|
2921
|
+
|
|
2922
|
+
|
|
2923
|
+
// Loaded at the call, so every other verb starts without it.
|
|
2924
|
+
function generatorRuntime(): any {
|
|
2925
|
+
return require('jostraca')
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
|
|
2929
|
+
function isDirectory(path: string): boolean {
|
|
2930
|
+
return true === statSync(path, { throwIfNoEntry: false })?.isDirectory()
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
|
|
2934
|
+
async function runRender(argv: string[]): Promise<number> {
|
|
2935
|
+
const trusted = takeTrust(argv)
|
|
2936
|
+
if (null == trusted) {
|
|
2937
|
+
return 2
|
|
2938
|
+
}
|
|
2939
|
+
argv = trusted.argv
|
|
2940
|
+
const trust = trusted.trust
|
|
2941
|
+
const rest: string[] = []
|
|
2942
|
+
const profileFiles: string[] = []
|
|
2943
|
+
let format: SubsumeFormat = 'text'
|
|
2944
|
+
let at: string | undefined = undefined
|
|
2945
|
+
let marker: string | undefined = undefined
|
|
2946
|
+
let check = false
|
|
2947
|
+
|
|
2948
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2949
|
+
const arg = argv[i]
|
|
2950
|
+
if ('-h' === arg || '--help' === arg) {
|
|
2951
|
+
process.stdout.write(HELP)
|
|
2952
|
+
return 0
|
|
2953
|
+
}
|
|
2954
|
+
if ('--check' === arg) {
|
|
2955
|
+
check = true
|
|
2956
|
+
}
|
|
2957
|
+
else if ('--format' === arg) {
|
|
2958
|
+
const f = argv[++i]
|
|
2959
|
+
if ('text' !== f && 'json' !== f) {
|
|
2960
|
+
process.stderr.write('aontu: --format needs text or json\n')
|
|
2961
|
+
return 2
|
|
2962
|
+
}
|
|
2963
|
+
format = f
|
|
2964
|
+
}
|
|
2965
|
+
else if ('--at' === arg) {
|
|
2966
|
+
at = argv[++i]
|
|
2967
|
+
if (null == at || '' === at) {
|
|
2968
|
+
process.stderr.write('aontu: --at needs a path\n')
|
|
2969
|
+
return 2
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
else if ('--marker' === arg) {
|
|
2973
|
+
marker = argv[++i]
|
|
2974
|
+
if (null == marker || '' === marker) {
|
|
2975
|
+
process.stderr.write('aontu: --marker needs a token\n')
|
|
2976
|
+
return 2
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
else if ('--profile' === arg) {
|
|
2980
|
+
const pf = argv[++i]
|
|
2981
|
+
if (null == pf || '' === pf) {
|
|
2982
|
+
process.stderr.write('aontu: --profile needs a file\n')
|
|
2983
|
+
return 2
|
|
2984
|
+
}
|
|
2985
|
+
profileFiles.push(pf)
|
|
2986
|
+
}
|
|
2987
|
+
else if (arg.startsWith('-')) {
|
|
2988
|
+
process.stderr.write(`aontu: unknown render option ${arg} (try --help)\n`)
|
|
2989
|
+
return 2
|
|
2990
|
+
}
|
|
2991
|
+
else {
|
|
2992
|
+
rest.push(arg)
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
if (2 !== rest.length) {
|
|
2997
|
+
process.stderr.write(`aontu: render needs a file and a path\n${RENDER_HELP}\n`)
|
|
2998
|
+
return 2
|
|
2999
|
+
}
|
|
3000
|
+
const [file, dest] = rest
|
|
3001
|
+
|
|
3002
|
+
const declared = loadProfiles(profileFiles, trust)
|
|
3003
|
+
if ('number' === typeof declared) {
|
|
3004
|
+
return declared
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
// A FOLDER IS A SET OF GENERATORS: every regular file directly in it,
|
|
3008
|
+
// dotfiles aside, in code-point order, written as one tree.
|
|
3009
|
+
let entries: Dirent[] | undefined
|
|
3010
|
+
try {
|
|
3011
|
+
entries = readdirSync(file, { withFileTypes: true })
|
|
3012
|
+
}
|
|
3013
|
+
catch (err: any) {
|
|
3014
|
+
entries = undefined
|
|
3015
|
+
}
|
|
3016
|
+
const set = undefined !== entries
|
|
3017
|
+
const files = undefined === entries ? [file] :
|
|
3018
|
+
entries.filter((e) => e.isFile() && !e.name.startsWith('.'))
|
|
3019
|
+
.map((e) => e.name).sort(cmpCodePoint).map((n) => join(file, n))
|
|
3020
|
+
if (0 === files.length) {
|
|
3021
|
+
process.stderr.write(`aontu: ${file} holds no generator\n`)
|
|
3022
|
+
return 2
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
const trees: any[] = []
|
|
3026
|
+
for (const f of files) {
|
|
3027
|
+
let src: string
|
|
3028
|
+
try {
|
|
3029
|
+
src = readFileSync(f, 'utf8')
|
|
3030
|
+
}
|
|
3031
|
+
catch (err: any) {
|
|
3032
|
+
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
3033
|
+
return 2
|
|
3034
|
+
}
|
|
3035
|
+
if (!/[.](aon|aontu)$/.test(f)) {
|
|
3036
|
+
const mark = marker ?? markerFromProfiles(declared, f) ?? markerFor(f)
|
|
3037
|
+
if (!templateOutputs(src, mark).some((out) => !out)) {
|
|
3038
|
+
process.stderr.write(`aontu: ${f} carries no ${mark} marker line, ` +
|
|
3039
|
+
'so there is no aontu in it to render\n')
|
|
3040
|
+
return 2
|
|
3041
|
+
}
|
|
3042
|
+
src = desugarTemplate(src, mark)
|
|
3043
|
+
}
|
|
3044
|
+
const report = get(src, at ?? '$.out', {
|
|
3045
|
+
view: 'json', path: f, ...verbOpts(trust, entryRootOf(f)),
|
|
3046
|
+
})
|
|
3047
|
+
if (!report.ok) {
|
|
3048
|
+
process.stderr.write(report.findings.map(renderFinding).join('\n') + '\n')
|
|
3049
|
+
return 4
|
|
3050
|
+
}
|
|
3051
|
+
const tree = JSON.parse(report.out)
|
|
3052
|
+
// A `File` without a name is refused: the runtime ports disagree
|
|
3053
|
+
// about it.
|
|
3054
|
+
if ('File' === tree?.cmp && 'string' !== typeof tree.props?.name) {
|
|
3055
|
+
process.stderr.write(`aontu: ${f}: the file at ${at ?? '$.out'} has no name\n`)
|
|
3056
|
+
return 4
|
|
3057
|
+
}
|
|
3058
|
+
trees.push(tree)
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
// ONE FILE GOES TO THE PATH ITSELF, unless the path is a directory; a
|
|
3062
|
+
// set is written below the path whatever its trees are.
|
|
3063
|
+
let folder = dest
|
|
3064
|
+
const tree: any = set ? trees.flatMap((t) => Array.isArray(t) ? t : [t]) : trees[0]
|
|
3065
|
+
if (!set && 'File' === tree?.cmp && !isDirectory(dest)) {
|
|
3066
|
+
tree.props.name = basename(dest)
|
|
3067
|
+
folder = dirname(dest)
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
const { cmpTree, Jostraca } = generatorRuntime()
|
|
3071
|
+
let root: any
|
|
3072
|
+
try {
|
|
3073
|
+
root = cmpTree(tree, { raw: true })
|
|
3074
|
+
}
|
|
3075
|
+
catch (err: any) {
|
|
3076
|
+
process.stderr.write(`aontu: ${file}: ${err.message}\n`)
|
|
3077
|
+
return 4
|
|
3078
|
+
}
|
|
3079
|
+
const runtime = Jostraca()
|
|
3080
|
+
|
|
3081
|
+
try {
|
|
3082
|
+
if (check) {
|
|
3083
|
+
const res = await runtime.check({ folder }, root)
|
|
3084
|
+
const drift = res.drift.map((d: any) => ({ kind: d.kind, path: d.path }))
|
|
3085
|
+
if ('json' === format) {
|
|
3086
|
+
process.stdout.write(exactJSON({
|
|
3087
|
+
aontu: { version: version(), verb: 'render' },
|
|
3088
|
+
verdict: 0 === drift.length ? 'ok' : 'drift',
|
|
3089
|
+
checked: res.checked,
|
|
3090
|
+
drift,
|
|
3091
|
+
}, 2) + '\n')
|
|
3092
|
+
}
|
|
3093
|
+
else {
|
|
3094
|
+
for (const d of drift) {
|
|
3095
|
+
process.stdout.write(`${d.kind}: ${d.path}\n`)
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
return 0 === drift.length ? 0 : 1
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
const res = await runtime.generate({ folder }, root)
|
|
3102
|
+
if ('json' === format) {
|
|
3103
|
+
process.stdout.write(exactJSON({
|
|
3104
|
+
aontu: { version: version(), verb: 'render' },
|
|
3105
|
+
verdict: 'ok',
|
|
3106
|
+
files: res.files,
|
|
3107
|
+
}, 2) + '\n')
|
|
3108
|
+
}
|
|
3109
|
+
return 0
|
|
3110
|
+
}
|
|
3111
|
+
catch (err: any) {
|
|
3112
|
+
process.stderr.write(`aontu: ${err.message}\n`)
|
|
3113
|
+
return 2
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
|
|
2883
3118
|
function runReaches(argv: string[]): number {
|
|
2884
3119
|
const trusted = takeTrust(argv)
|
|
2885
3120
|
if (null == trusted) {
|
|
@@ -4880,8 +5115,8 @@ function runInit(argv: string[]): number {
|
|
|
4880
5115
|
const KNOWN_VERBS = [
|
|
4881
5116
|
'add', 'agentsmd', 'allow', 'breaking', 'explain', 'fmt', 'get', 'hash',
|
|
4882
5117
|
'help', 'init', 'jsonschema', 'lsp', 'mcp', 'model', 'pkg', 'publish',
|
|
4883
|
-
'reaches', 'relations', 'remove', '
|
|
4884
|
-
'trim', 'vet', 'view', 'why',
|
|
5118
|
+
'reaches', 'relations', 'remove', 'render', 'subsume', 'sync', 'template',
|
|
5119
|
+
'trace', 'trim', 'vet', 'view', 'why',
|
|
4885
5120
|
]
|
|
4886
5121
|
|
|
4887
5122
|
|
|
@@ -5029,6 +5264,10 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
5029
5264
|
return finish(runTrace(argv.slice(3)))
|
|
5030
5265
|
}
|
|
5031
5266
|
|
|
5267
|
+
if ('render' === argv[2]) {
|
|
5268
|
+
return void runRender(argv.slice(3)).then(finish)
|
|
5269
|
+
}
|
|
5270
|
+
|
|
5032
5271
|
if ('reaches' === argv[2]) {
|
|
5033
5272
|
return finish(runReaches(argv.slice(3)))
|
|
5034
5273
|
}
|
|
@@ -5146,6 +5385,7 @@ export {
|
|
|
5146
5385
|
runJsonSchema,
|
|
5147
5386
|
runTemplate,
|
|
5148
5387
|
runTrace,
|
|
5388
|
+
runRender,
|
|
5149
5389
|
runPkg, runModel, runPackageVerb, pkgToolOptions, serveUntilInterrupted,
|
|
5150
5390
|
runHash, runGet, runHelp, runExplain, runInit, nearestVerb,
|
|
5151
5391
|
looksLikeVerb,
|
package/src/helpdoc.ts
CHANGED
|
@@ -17,7 +17,7 @@ const HELPDOC: HelpTopic[] = [
|
|
|
17
17
|
"topic": "tasks",
|
|
18
18
|
"summary": "what do you want to do? — the verb for a job",
|
|
19
19
|
"source": "docs/skill/tasks.md",
|
|
20
|
-
"text": "# What do you want to do?\n\nThe verb for a job, found by the word you arrived with. aontu's own\nvocabulary is on the right; yours is probably on the left.\n\n## Start from a document that works\n\nNothing written yet? Do not invent the first document, edit one:\n\n```\naontu init # into the working directory\naontu init model/ # or into a named one\nsh check.sh # the four questions, on what was just written\n```\n\n`init` writes `model.aon` (an entity map constrained with `&:`),\n`data.aon` (an instance of it that holds) and `check.sh` (the four\nchecks to run after every edit). It never overwrites: if any\nof the three already stands there, it refuses and writes none of them.\n\n## Describe a domain\n\nAn **ontology**, a **schema**, a **data model**, a **contract** — in\naontu these are all one thing: a document. Write the entities as a\n**map keyed by name**, and say what every entry must satisfy with the\n`&:` template:\n\n```\nentity: {\n &: {\n table: string\n fields: { &: { type: string, required: *false | boolean } }\n }\n planet: { table: \"planets\", fields: { name: { type: \"string\" } } }\n moon: { table: \"moons\", fields: { name: { type: \"string\" } } }\n}\n```\n\n`&:` is the construct to reach for first, and the one most easily\nmissed. It meets **every** key of the map it sits in. A quoted `\"*\"`\nis not a wildcard — it is a key named `*`, and a schema written that\nway constrains nothing while still reporting `valid`.\n\nPrefer a named map to a list unless the order is a fact (a migration\nsequence, a rule table tried in order). A key is an address, a name\nother parts can refer to, and a diff that shows one insertion instead\nof every following element renumbered.\n\n## Check data against a model\n\n```\naontu vet model.aon data.aon\n```\n\nExit `0` valid, `1` a contradiction, `3` incomplete (nothing\ncontradicts, but the model is not yet satisfied), `4` the model does\nnot stand up on its own. `--format json` for the machine-readable\nreport, `--closed` to refuse keys the model does not declare,\n`--max-errors <n>` to cap the list.\n\n**Make the check prove it checked something.** A check that examined\nnothing answers exactly like one that passed:\n\n```\naontu vet --coverage model.aon data.aon # what did it examine?\naontu vet --strict-coverage model.aon data.aon # exit 1 if nothing\n```\n\n`--coverage` reports how many data leaves a declaration constrained,\nthe data paths none did, and the declarations no data met.\n`--strict-coverage` exits 1 when the answer is nothing. The usual\ncause is the `\"*\"` mistake above: reach for `&:`.\n\n## Check the model is coherent with itself\n\n```\naontu relations model.aon # declared entity edges: targets resolve, no cycles\naontu reaches planet moon model.aon # does one entity reach another, at any remove?\naontu trim --check model.aon # entries whose removal changes nothing\n```\n\n## Generate code from a model, and hold it\n\n```\naontu model get out gen.aon
|
|
20
|
+
"text": "# What do you want to do?\n\nThe verb for a job, found by the word you arrived with. aontu's own\nvocabulary is on the right; yours is probably on the left.\n\n## Start from a document that works\n\nNothing written yet? Do not invent the first document, edit one:\n\n```\naontu init # into the working directory\naontu init model/ # or into a named one\nsh check.sh # the four questions, on what was just written\n```\n\n`init` writes `model.aon` (an entity map constrained with `&:`),\n`data.aon` (an instance of it that holds) and `check.sh` (the four\nchecks to run after every edit). It never overwrites: if any\nof the three already stands there, it refuses and writes none of them.\n\n## Describe a domain\n\nAn **ontology**, a **schema**, a **data model**, a **contract** — in\naontu these are all one thing: a document. Write the entities as a\n**map keyed by name**, and say what every entry must satisfy with the\n`&:` template:\n\n```\nentity: {\n &: {\n table: string\n fields: { &: { type: string, required: *false | boolean } }\n }\n planet: { table: \"planets\", fields: { name: { type: \"string\" } } }\n moon: { table: \"moons\", fields: { name: { type: \"string\" } } }\n}\n```\n\n`&:` is the construct to reach for first, and the one most easily\nmissed. It meets **every** key of the map it sits in. A quoted `\"*\"`\nis not a wildcard — it is a key named `*`, and a schema written that\nway constrains nothing while still reporting `valid`.\n\nPrefer a named map to a list unless the order is a fact (a migration\nsequence, a rule table tried in order). A key is an address, a name\nother parts can refer to, and a diff that shows one insertion instead\nof every following element renumbered.\n\n## Check data against a model\n\n```\naontu vet model.aon data.aon\n```\n\nExit `0` valid, `1` a contradiction, `3` incomplete (nothing\ncontradicts, but the model is not yet satisfied), `4` the model does\nnot stand up on its own. `--format json` for the machine-readable\nreport, `--closed` to refuse keys the model does not declare,\n`--max-errors <n>` to cap the list.\n\n**Make the check prove it checked something.** A check that examined\nnothing answers exactly like one that passed:\n\n```\naontu vet --coverage model.aon data.aon # what did it examine?\naontu vet --strict-coverage model.aon data.aon # exit 1 if nothing\n```\n\n`--coverage` reports how many data leaves a declaration constrained,\nthe data paths none did, and the declarations no data met.\n`--strict-coverage` exits 1 when the answer is nothing. The usual\ncause is the `\"*\"` mistake above: reach for `&:`.\n\n## Check the model is coherent with itself\n\n```\naontu relations model.aon # declared entity edges: targets resolve, no cycles\naontu reaches planet moon model.aon # does one entity reach another, at any remove?\naontu trim --check model.aon # entries whose removal changes nothing\n```\n\n## Generate code from a model, and hold it\n\n```\naontu render gen.aon build # write the files below build/\naontu render --check gen.aon build # write nothing; exit 1 when build/ has drifted\naontu render gen/ build # every generator directly in gen/, as one run\naontu model get out gen.aon # the component tree: files, and the lines in them\naontu trace gen.aon # what rule wrote each line\n```\n\nA generator answers a **component tree** -- `file(name, [lines])` and\nits neighbours -- which `aontu render` hands to jostraca, the generator\nruntime, to write. A tree that is one file is written to the path\nitself, and a folder of generators is one run. `--check` compares with\nwhat is committed: the model is the\ntruth, the code is the claim, and drift is a finding. `aontu trace`\nanswers the other direction: for a line of the output, the model node\nand the rule behind it.\n\n## Ask what a model says, and why\n\n```\naontu model get $.entity.planet.table model.aon\naontu model why $.entity.planet.table model.aon\naontu model get $.entity --keys model.aon\naontu model get $.entity --types model.aon\n```\n\n`why` names **every** contribution to a value, with the file and line\neach was written on. It is the first thing to run when a value is not\nwhat you expected, and the second thing to run when `vet` refuses.\n\n## Change a value without editing the file\n\n```\naontu model set '$.entity.planet.table=planet_v2' --entry model.aon --overlay local.aon\n```\n\nThe change is checked before it is written; a change that contradicts\na pinned value is refused, and the file is left alone.\n\n## Gate a change to the model itself\n\n```\naontu subsume old.aon new.aon # does the general admit every specific?\naontu breaking --against git#HEAD~1 model.aon\naontu hash model.aon # a pin that survives reformatting\n```\n\n## Hand a model to another agent\n\n```\naontu agentsmd --write AGENTS.md model.aon\n```\n\nSplices a derived stanza — the pin, the root keys, the shape, and the\ncommands spelled with paths that exist — between two markers, and\nleaves the rest of the file alone. Re-run it in the commit that\nchanges the model.\n\n## When something refuses\n\nRead the code in the brackets, then look it up:\n\n```\naontu explain no_scalar_unify\n```\n\nEvery finding carries a `code`, a `class`, a `path` and the **two\nsites** that disagree — the value and the constraint it failed, each\nwith its file and line. The class says what kind of answer it is:\n`conflict` (two things cannot both hold), `incomplete` (nothing is\nwrong yet, something is missing), `parse`, `reference`, `budget`.\n\nFull index: [`error-codes.md`](error-codes.md). The language on one\npage: [`grammar-card.md`](grammar-card.md). The worked ladder from\nplain JSON upward: [`examples.md`](examples.md).\n"
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"topic": "language",
|