aontu 0.66.0 → 0.68.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 -1
- package/dist/allow.js +2 -2
- package/dist/allow.js.map +1 -1
- package/dist/aontu.d.ts +1 -1
- package/dist/aontu.js +1 -1
- package/dist/cli.d.ts +5 -1
- package/dist/cli.js +155 -36
- package/dist/cli.js.map +1 -1
- package/dist/helpdoc.js +1 -1
- package/dist/helpdoc.js.map +1 -1
- package/dist/hints.js +144 -1
- package/dist/hints.js.map +1 -1
- package/dist/lang.js +46 -10
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/mcp-server.js +1 -1
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/dist/query.d.ts +1 -1
- package/dist/query.js +7 -9
- package/dist/query.js.map +1 -1
- package/dist/val/RefVal.js +18 -6
- package/dist/val/RefVal.js.map +1 -1
- package/dist/vet.d.ts +3 -1
- package/dist/vet.js +88 -13
- package/dist/vet.js.map +1 -1
- package/dist/view.js +2 -4
- package/dist/view.js.map +1 -1
- package/package.json +2 -2
- package/skill/error-codes.md +25 -1
- package/src/allow.ts +3 -3
- package/src/aontu.ts +1 -1
- package/src/cli.ts +175 -37
- package/src/helpdoc.ts +1 -1
- package/src/hints.ts +197 -1
- package/src/lang.ts +47 -10
- package/src/mcp-server.ts +1 -1
- package/src/mcp.ts +1 -1
- package/src/query.ts +8 -13
- package/src/tsconfig.json +2 -1
- package/src/val/RefVal.ts +18 -7
- package/src/vet.ts +100 -13
- package/src/view.ts +3 -7
- package/dist/tsconfig.tsbuildinfo +0 -1
package/src/cli.ts
CHANGED
|
@@ -166,22 +166,30 @@ query between a document and its own earlier versions.
|
|
|
166
166
|
|
|
167
167
|
Options:
|
|
168
168
|
-c, --canon Print the canonical form instead of generated JSON
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
(the bare command's, as --jsonl is; model get has
|
|
170
|
+
its own)
|
|
171
|
+
--format <f> text (default) or json, on every verb that answers a
|
|
172
|
+
report. The json form is one object opening with an
|
|
173
|
+
aontu block; the bare command's carries findings, ok
|
|
174
|
+
and out
|
|
172
175
|
-h, --help Show this help and exit (the verbs and their flags);
|
|
173
176
|
aontu help is the LANGUAGE, and lists its own topics
|
|
174
177
|
--jsonl REPL: answer every command as one JSON line
|
|
175
178
|
-v, --version Print the version and exit
|
|
176
|
-
--trust <t> Include capability: system
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
--trust <t> Include capability: system, none, or root[:dir] to
|
|
180
|
+
confine @"..." below a directory. A bare root means
|
|
181
|
+
the entry root: the document's own directory, or the
|
|
182
|
+
project's for the package verbs. Every verb takes it
|
|
183
|
+
but help, explain, init and lsp, which read no
|
|
184
|
+
document, and mcp, which is the npm build's server
|
|
185
|
+
and confines with --root. Unset, a run behaves as
|
|
186
|
+
system, and the bare command warns once for an
|
|
187
|
+
include that leaves the entry root
|
|
180
188
|
--include-root <dir> Shorthand for --trust root:<dir>
|
|
181
|
-
--text-ext <e> Read these extensions as text too, comma-separated
|
|
182
|
-
|
|
189
|
+
--text-ext <e> Read these extensions as text too, comma-separated,
|
|
190
|
+
with or without dots (md,sql). .txt needs no flag; a
|
|
183
191
|
named format keeps its meaning, and .js stays
|
|
184
|
-
refused.
|
|
192
|
+
refused. The verbs that take --trust take it too
|
|
185
193
|
|
|
186
194
|
Package verbs (a module is imported; a package is published):
|
|
187
195
|
sync Make the project correct: resolve by minimum version
|
|
@@ -708,7 +716,7 @@ function takeTrust(argv: string[], io: Io = PROCESS_IO):
|
|
|
708
716
|
}
|
|
709
717
|
else if ('--include-root' === arg) {
|
|
710
718
|
const dir = argv[++i]
|
|
711
|
-
if (null == dir) {
|
|
719
|
+
if (null == dir || '' === dir) {
|
|
712
720
|
io.err('aontu: --include-root needs a directory\n')
|
|
713
721
|
return undefined
|
|
714
722
|
}
|
|
@@ -2704,20 +2712,42 @@ function pkgNetText(verb: string, report: any): string {
|
|
|
2704
2712
|
const MODEL_HELP = 'aontu model get|why|set ... (try --help)'
|
|
2705
2713
|
|
|
2706
2714
|
// One document, interrogated or edited (ADR-039 part 5).
|
|
2715
|
+
// Where the subcommand is, past any global flag that precedes it.
|
|
2716
|
+
// `takeTrust` strips those anywhere in a tail, so the subcommand has
|
|
2717
|
+
// to be found past a flag AND past its value: in `--text-ext get` the
|
|
2718
|
+
// `get` is the extension list, not the subcommand.
|
|
2719
|
+
const MODEL_SUBS = ['get', 'why', 'set']
|
|
2720
|
+
const GLOBAL_VALUED = ['--trust', '--include-root', '--text-ext']
|
|
2721
|
+
|
|
2722
|
+
|
|
2723
|
+
function modelSubAt(argv: string[]): number {
|
|
2724
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2725
|
+
if (GLOBAL_VALUED.includes(argv[i])) {
|
|
2726
|
+
i++
|
|
2727
|
+
continue
|
|
2728
|
+
}
|
|
2729
|
+
return MODEL_SUBS.includes(argv[i]) ? i : -1
|
|
2730
|
+
}
|
|
2731
|
+
return -1
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
|
|
2707
2735
|
function runModel(argv: string[]): number {
|
|
2708
2736
|
const sub = argv[0]
|
|
2709
2737
|
if ('-h' === sub || '--help' === sub) {
|
|
2710
2738
|
process.stdout.write(HELP)
|
|
2711
2739
|
return 0
|
|
2712
2740
|
}
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2741
|
+
const at = modelSubAt(argv)
|
|
2742
|
+
if (0 <= at) {
|
|
2743
|
+
const tail = argv.slice(0, at).concat(argv.slice(at + 1))
|
|
2744
|
+
if ('get' === argv[at]) {
|
|
2745
|
+
return runGet(tail)
|
|
2746
|
+
}
|
|
2747
|
+
if ('why' === argv[at]) {
|
|
2748
|
+
return runWhy(tail)
|
|
2749
|
+
}
|
|
2750
|
+
return runSet(tail)
|
|
2721
2751
|
}
|
|
2722
2752
|
process.stderr.write(`aontu: model needs get, why or set\n${MODEL_HELP}\n`)
|
|
2723
2753
|
return 2
|
|
@@ -2931,6 +2961,70 @@ function isDirectory(path: string): boolean {
|
|
|
2931
2961
|
}
|
|
2932
2962
|
|
|
2933
2963
|
|
|
2964
|
+
// A `File` the write path skips, RENAMED rather than removed: dropping
|
|
2965
|
+
// the node would take its children's claims with it and hide drift the
|
|
2966
|
+
// write path does make. The skip is the File's own save alone, so what
|
|
2967
|
+
// separates the runs is its output path, composed by the runtime rather
|
|
2968
|
+
// than here.
|
|
2969
|
+
const EXCLUDED_NAME = '.aontu-check-excluded-'
|
|
2970
|
+
|
|
2971
|
+
|
|
2972
|
+
// `exclude` as the runtime reads it: `true`, or a string or list member
|
|
2973
|
+
// equal to the node's COMPONENT path, the chain of `name` props above
|
|
2974
|
+
// it, which a Project's `folder` is not part of. The Go runtime honours
|
|
2975
|
+
// the boolean alone, so the ports' `--check` answers differ for the
|
|
2976
|
+
// path forms (test/spec/divergent.tsv).
|
|
2977
|
+
function excludedFile(exclude: any, at: string[]): boolean {
|
|
2978
|
+
if (true === exclude) {
|
|
2979
|
+
return true
|
|
2980
|
+
}
|
|
2981
|
+
const path = at.join('/')
|
|
2982
|
+
if ('string' === typeof exclude) {
|
|
2983
|
+
return exclude === path
|
|
2984
|
+
}
|
|
2985
|
+
return Array.isArray(exclude) && exclude.includes(path)
|
|
2986
|
+
}
|
|
2987
|
+
|
|
2988
|
+
|
|
2989
|
+
function renameExcluded(node: any, at: string[], cut: number[]): any {
|
|
2990
|
+
if (Array.isArray(node)) {
|
|
2991
|
+
return node.map((child) => renameExcluded(child, at, cut))
|
|
2992
|
+
}
|
|
2993
|
+
// A hand-written tree carries nodes with no `props` and nodes with
|
|
2994
|
+
// no `children`, and neither needs an arm of its own.
|
|
2995
|
+
const props: any = node.props ?? {}
|
|
2996
|
+
const below = 'string' === typeof props.name ? at.concat(props.name) : at
|
|
2997
|
+
if ('File' === node.cmp && excludedFile(props.exclude, below)) {
|
|
2998
|
+
cut.push(1)
|
|
2999
|
+
return { ...node, props: { ...props, name: EXCLUDED_NAME + cut.length } }
|
|
3000
|
+
}
|
|
3001
|
+
if (!Array.isArray(node.children)) {
|
|
3002
|
+
return node
|
|
3003
|
+
}
|
|
3004
|
+
return { ...node, children: renameExcluded(node.children, below, cut) }
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
|
|
3008
|
+
// A file the runtime declined to touch, its copy on disk carrying the
|
|
3009
|
+
// protect marker, is in none of the lists it answers with: they hold
|
|
3010
|
+
// what was DONE to a file, and nothing was. The run's own record names
|
|
3011
|
+
// it. `since` drops what earlier runs left.
|
|
3012
|
+
function renderSkipped(folder: string, since: number): string[] {
|
|
3013
|
+
const at = join(folder, '.jostraca', 'jostraca.meta.log')
|
|
3014
|
+
let meta: any
|
|
3015
|
+
try {
|
|
3016
|
+
meta = JSON.parse(readFileSync(at, 'utf8'))
|
|
3017
|
+
}
|
|
3018
|
+
catch (err: any) {
|
|
3019
|
+
return []
|
|
3020
|
+
}
|
|
3021
|
+
return Object.entries(meta?.files ?? {})
|
|
3022
|
+
.filter(([_, f]: [string, any]) => 'skip' === f?.action && since <= f?.when)
|
|
3023
|
+
.map(([path]) => path)
|
|
3024
|
+
.sort(cmpCodePoint)
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
|
|
2934
3028
|
async function runRender(argv: string[]): Promise<number> {
|
|
2935
3029
|
const trusted = takeTrust(argv)
|
|
2936
3030
|
if (null == trusted) {
|
|
@@ -3081,7 +3175,22 @@ async function runRender(argv: string[]): Promise<number> {
|
|
|
3081
3175
|
try {
|
|
3082
3176
|
if (check) {
|
|
3083
3177
|
const res = await runtime.check({ folder }, root)
|
|
3084
|
-
|
|
3178
|
+
let drift = res.drift.map((d: any) => ({ kind: d.kind, path: d.path }))
|
|
3179
|
+
// `--check` answers "would `render` change anything", so a file
|
|
3180
|
+
// the write path leaves alone is not held to the generator's
|
|
3181
|
+
// bytes. The skip is gated on the target BEING there -- `render`
|
|
3182
|
+
// writes an absent one -- so drift at a path with nothing at it
|
|
3183
|
+
// survives, which is the `missing` a deleted file reports.
|
|
3184
|
+
const cut: number[] = []
|
|
3185
|
+
const renamed = renameExcluded(tree, [], cut)
|
|
3186
|
+
if (0 < cut.length) {
|
|
3187
|
+
const kept = new Set<string>((await Jostraca().check(
|
|
3188
|
+
{ folder }, cmpTree(renamed, { raw: true }))).checked)
|
|
3189
|
+
const skipped = new Set<string>(
|
|
3190
|
+
res.checked.filter((p: string) => !kept.has(p)))
|
|
3191
|
+
drift = drift.filter((d: any) => !skipped.has(d.path) ||
|
|
3192
|
+
!existsSync(join(folder, d.path)))
|
|
3193
|
+
}
|
|
3085
3194
|
if ('json' === format) {
|
|
3086
3195
|
process.stdout.write(exactJSON({
|
|
3087
3196
|
aontu: { version: version(), verb: 'render' },
|
|
@@ -3098,14 +3207,21 @@ async function runRender(argv: string[]): Promise<number> {
|
|
|
3098
3207
|
return 0 === drift.length ? 0 : 1
|
|
3099
3208
|
}
|
|
3100
3209
|
|
|
3210
|
+
const since = Date.now()
|
|
3101
3211
|
const res = await runtime.generate({ folder }, root)
|
|
3212
|
+
const skipped = renderSkipped(folder, since)
|
|
3102
3213
|
if ('json' === format) {
|
|
3103
3214
|
process.stdout.write(exactJSON({
|
|
3104
3215
|
aontu: { version: version(), verb: 'render' },
|
|
3105
3216
|
verdict: 'ok',
|
|
3106
|
-
files: res.files,
|
|
3217
|
+
files: { ...res.files, skipped },
|
|
3107
3218
|
}, 2) + '\n')
|
|
3108
3219
|
}
|
|
3220
|
+
else {
|
|
3221
|
+
for (const path of skipped) {
|
|
3222
|
+
process.stdout.write(`skipped: ${path}\n`)
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3109
3225
|
return 0
|
|
3110
3226
|
}
|
|
3111
3227
|
catch (err: any) {
|
|
@@ -3932,7 +4048,7 @@ function runHash(argv: string[]): number {
|
|
|
3932
4048
|
if (0 < ctx.err.length || true === v?.isNil) {
|
|
3933
4049
|
process.stderr.write(
|
|
3934
4050
|
`aontu: ${files[0]} does not evaluate on its own; nothing to hash\n` +
|
|
3935
|
-
renderFinding(evalFailure(ctx)) + '\n')
|
|
4051
|
+
renderFinding(evalFailure(ctx, v)) + '\n')
|
|
3936
4052
|
return 4
|
|
3937
4053
|
}
|
|
3938
4054
|
|
|
@@ -4923,6 +5039,17 @@ function runHelp(argv: string[]): number {
|
|
|
4923
5039
|
// and carries that prefix's hint.
|
|
4924
5040
|
const EXPLAIN_PREFIXES = ['func:', 'op:', 'op[', 'var[', 'ref[']
|
|
4925
5041
|
|
|
5042
|
+
// A report prints the namespaced spelling in its brackets, which is the
|
|
5043
|
+
// span a reader copies. The answer names the registered one.
|
|
5044
|
+
const EXPLAIN_NAMESPACE = 'aontu/'
|
|
5045
|
+
|
|
5046
|
+
|
|
5047
|
+
function canonExplainCode(code: string): string {
|
|
5048
|
+
return code.startsWith(EXPLAIN_NAMESPACE)
|
|
5049
|
+
? code.slice(EXPLAIN_NAMESPACE.length)
|
|
5050
|
+
: code
|
|
5051
|
+
}
|
|
5052
|
+
|
|
4926
5053
|
|
|
4927
5054
|
function explainCode(
|
|
4928
5055
|
code: string): { cls: string, hint: string, registered: boolean } {
|
|
@@ -4962,9 +5089,10 @@ function explainListText(format: SubsumeFormat): string {
|
|
|
4962
5089
|
codes: codes.map((code) => ({
|
|
4963
5090
|
code,
|
|
4964
5091
|
class: codeClass(code),
|
|
4965
|
-
// Whether this port carries explanation text for the code.
|
|
4966
|
-
//
|
|
4967
|
-
//
|
|
5092
|
+
// Whether this port carries explanation text for the code. A
|
|
5093
|
+
// gate holds every registered code explained, so this reads
|
|
5094
|
+
// true throughout; it stays because the registry is
|
|
5095
|
+
// append-only and a consumer should filter rather than guess.
|
|
4968
5096
|
explained: '' !== explainCode(code).hint,
|
|
4969
5097
|
})),
|
|
4970
5098
|
}, 2)
|
|
@@ -4972,7 +5100,21 @@ function explainListText(format: SubsumeFormat): string {
|
|
|
4972
5100
|
const width = codes.reduce((w, c) => Math.max(w, c.length), 0)
|
|
4973
5101
|
return codes.map((c) =>
|
|
4974
5102
|
c.padEnd(width) + ' ' + codeClass(c) +
|
|
4975
|
-
(
|
|
5103
|
+
noTextMark(explainCode(c).hint)).join('\n')
|
|
5104
|
+
}
|
|
5105
|
+
|
|
5106
|
+
|
|
5107
|
+
// The registry is append-only, so a code can be registered before its
|
|
5108
|
+
// text is written; saying so beats printing an empty block.
|
|
5109
|
+
function explainBody(hint: string): string {
|
|
5110
|
+
return '' === hint
|
|
5111
|
+
? '(no explanation text is registered for this code)'
|
|
5112
|
+
: hint
|
|
5113
|
+
}
|
|
5114
|
+
|
|
5115
|
+
|
|
5116
|
+
function noTextMark(hint: string): string {
|
|
5117
|
+
return '' === hint ? ' (no text)' : ''
|
|
4976
5118
|
}
|
|
4977
5119
|
|
|
4978
5120
|
|
|
@@ -5022,7 +5164,7 @@ function runExplain(argv: string[]): number {
|
|
|
5022
5164
|
return 2
|
|
5023
5165
|
}
|
|
5024
5166
|
|
|
5025
|
-
const code = codes[0]
|
|
5167
|
+
const code = canonExplainCode(codes[0])
|
|
5026
5168
|
const { cls, hint, registered } = explainCode(code)
|
|
5027
5169
|
if (!registered) {
|
|
5028
5170
|
// AN UNKNOWN CODE IS A USAGE ERROR AND NAMES NEAR MATCHES. A
|
|
@@ -5048,13 +5190,8 @@ function runExplain(argv: string[]): number {
|
|
|
5048
5190
|
}, 2) + '\n')
|
|
5049
5191
|
return 0
|
|
5050
5192
|
}
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
// be blank.
|
|
5054
|
-
const body = '' === hint
|
|
5055
|
-
? '(no explanation text is registered for this code)'
|
|
5056
|
-
: hint
|
|
5057
|
-
process.stdout.write(`code: ${code}\nclass: ${cls}\n\n${body}\n`)
|
|
5193
|
+
process.stdout.write(
|
|
5194
|
+
`code: ${code}\nclass: ${cls}\n\n${explainBody(hint)}\n`)
|
|
5058
5195
|
return 0
|
|
5059
5196
|
}
|
|
5060
5197
|
|
|
@@ -5322,7 +5459,7 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
5322
5459
|
}
|
|
5323
5460
|
else if ('--include-root' === arg) {
|
|
5324
5461
|
const dir = args[++i]
|
|
5325
|
-
if (null == dir) {
|
|
5462
|
+
if (null == dir || '' === dir) {
|
|
5326
5463
|
process.stderr.write('aontu: --include-root needs a directory\n')
|
|
5327
5464
|
return finish(2)
|
|
5328
5465
|
}
|
|
@@ -5371,7 +5508,7 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
5371
5508
|
else {
|
|
5372
5509
|
runStdin(mode, format, trust).then((code) => finish(code))
|
|
5373
5510
|
}
|
|
5374
|
-
} /* node:coverage ignore next
|
|
5511
|
+
} /* node:coverage ignore next 22 */
|
|
5375
5512
|
|
|
5376
5513
|
|
|
5377
5514
|
// No require.main guard here: bin/aontu.js is the executable entry and
|
|
@@ -5385,9 +5522,10 @@ export {
|
|
|
5385
5522
|
runJsonSchema,
|
|
5386
5523
|
runTemplate,
|
|
5387
5524
|
runTrace,
|
|
5388
|
-
runRender,
|
|
5525
|
+
runRender, renderSkipped,
|
|
5389
5526
|
runPkg, runModel, runPackageVerb, pkgToolOptions, serveUntilInterrupted,
|
|
5390
5527
|
runHash, runGet, runHelp, runExplain, runInit, nearestVerb,
|
|
5528
|
+
explainBody, noTextMark, canonExplainCode,
|
|
5391
5529
|
looksLikeVerb,
|
|
5392
5530
|
KNOWN_VERBS,
|
|
5393
5531
|
runWhy, renderWhyText, runSet, runAllow, runAgentsMd, runFmt,
|
package/src/helpdoc.ts
CHANGED
|
@@ -35,7 +35,7 @@ const HELPDOC: HelpTopic[] = [
|
|
|
35
35
|
"topic": "codes",
|
|
36
36
|
"summary": "what a refusal means, and what to do about it",
|
|
37
37
|
"source": "docs/skill/error-codes.md",
|
|
38
|
-
"text": "# What a refusal means\n\nEvery aontu error carries a **code**, and every code has a **class**.\nThe class says what kind of thing went wrong, which is what decides\nyour next move. The full registry — every code, its class, and the\nversion it appeared in — is\n[`test/spec/errcodes.tsv`](../../test/spec/errcodes.tsv), which the\ntest suite holds to the engine in both implementations.\n\n| Class | Meaning | What to do |\n|-------|---------|------------|\n| `parse` | the text is not a document | fix the syntax at the site the frame points at |\n| `conflict` | two values cannot both hold | one of them is wrong: `aontu model why <path>` names both and where they were written |\n| `incomplete` | nothing contradicts, but the value is not concrete | supply what is missing, or accept it with `--partial` |\n| `reference` | a path names nothing | check the spelling; `aontu model get $ --keys` lists what is there |\n| `compat` | a change breaks an earlier version | that is `aontu subsume` / `aontu breaking` talking: widen the change or version it |\n| `budget` | evaluation hit a deterministic limit | usually a cycle; simplify, or raise the budget deliberately |\n| `internal` | the engine surprised itself | a bug worth reporting |\n\n## The repair loop\n\n```\naontu vet schema.aon mine.aon --format json\n```\n\nThe exit code branches for you: `0` valid, `1` invalid (fix the\ndata), `3` incomplete (supply more), `4` the schema itself is\nunusable (fix the truth, not the data). Every finding carries\n`path`, `code`, and the sites on both sides — the data site first,\nbecause that is the one to edit. A site names **the file whose text\nit excerpts**, so its row and column are safe to edit at even when\nthe document loads others.\n\nRead `hint` before guessing. `message` is the one-line headline;\n`hint` is the engine's own explanation of the failure class with the\noffending values filled in, and for several codes it names the fix\noutright — `lossy_integer_literal` tells you to write the literal as\n`0d…`.
|
|
38
|
+
"text": "# What a refusal means\n\nEvery aontu error carries a **code**, and every code has a **class**.\nThe class says what kind of thing went wrong, which is what decides\nyour next move. The full registry — every code, its class, and the\nversion it appeared in — is\n[`test/spec/errcodes.tsv`](../../test/spec/errcodes.tsv), which the\ntest suite holds to the engine in both implementations.\n\n| Class | Meaning | What to do |\n|-------|---------|------------|\n| `parse` | the text is not a document | fix the syntax at the site the frame points at |\n| `conflict` | two values cannot both hold | one of them is wrong: `aontu model why <path>` names both and where they were written |\n| `incomplete` | nothing contradicts, but the value is not concrete | supply what is missing, or accept it with `--partial` |\n| `reference` | a path names nothing | check the spelling; `aontu model get $ --keys` lists what is there |\n| `compat` | a change breaks an earlier version | that is `aontu subsume` / `aontu breaking` talking: widen the change or version it |\n| `budget` | evaluation hit a deterministic limit | usually a cycle; simplify, or raise the budget deliberately |\n| `internal` | the engine surprised itself | a bug worth reporting |\n\n## Looking a code up\n\n```\naontu explain constraint # what one code means, and its class\naontu explain --list # every registered code, with its class\n```\n\nA report prints two bracketed spans, and only one of them is a code:\n\n```\n$.note.n1.id: constraint [conflict]\n [aontu/constraint]: Cannot unify values at path $.note.n1.id\n```\n\n`[conflict]` is the **class**. `[aontu/constraint]` is the **code**,\ncarrying the `aontu/` prefix. `explain` takes either spelling —\n`constraint` or `aontu/constraint` — and answers under the registered\none. The headline carries the bare code as well, and so does the `code`\nfield of a `--format json` report.\n\n## The repair loop\n\n```\naontu vet schema.aon mine.aon --format json\n```\n\nThe exit code branches for you: `0` valid, `1` invalid (fix the\ndata), `3` incomplete (supply more), `4` the schema itself is\nunusable (fix the truth, not the data). Every finding carries\n`path`, `code`, and the sites on both sides — the data site first,\nbecause that is the one to edit. A site names **the file whose text\nit excerpts**, so its row and column are safe to edit at even when\nthe document loads others.\n\nRead `hint` before guessing. `message` is the one-line headline;\n`hint` is the engine's own explanation of the failure class with the\noffending values filled in, and for several codes it names the fix\noutright — `lossy_integer_literal` tells you to write the literal as\n`0d…`. Every code in the registry has hint text, which is what\n`aontu explain` prints. A finding carries it inline for an exact\nregistry code; a **dynamic** code (`func:upper`, `op[+]`) is registered\nthrough its prefix, and only `explain` falls back to the prefix's text,\nso such a finding carries no `hint`.\n\nFor a conflict, `aontu model why <path> mine.aon` lists every contribution\nto that path with its role and source line, which turns \"these\ndisagree\" into \"these two lines disagree\".\n\nThen fix it:\n\n```\naontu model set '$.replicas=5' --entry schema.aon --overlay mine.aon --in-place\n```\n\n`--in-place` rewrites the pinned literal **where it was written**, so\ncomments and layout survive. Without it, `set` APPENDS — which is the\nright thing when the document left a hole, and cannot work when it\npinned the wrong value, because unification only narrows.\n\nThe edit is verified before a byte is written: a site carries the\nsource text it covers, and the text at the span must match it. Where\nthat cannot be established — the value comes from a `&:` template or a\n`$ref`, two statements pin the path, the site names the opening token\nof a compound like `min(1)`, or the overlay `@\"includes\"` another\ndocument — the assignment is appended as usual and a **warning** says\nwhich case it hit. A warning never changes the verdict, so asking for\n`--in-place` cannot make a run fail that would have succeeded.\n"
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"topic": "grammar",
|
package/src/hints.ts
CHANGED
|
@@ -181,7 +181,7 @@ const hints: Record<string, string> = {
|
|
|
181
181
|
|
|
182
182
|
merge_conflict: 'A version-control conflict marker was found in the source. The\nfile still holds an unresolved merge: resolve it and remove the\n`<<<<<<<`, `=======` and `>>>>>>>` lines before unifying.\n \nExamples:\n <<<<<<< HEAD -> nil # A conflict marker, not a `<` operation;\n ======= -> nil # ... nor a chain of `=` characters;\n >>>>>>> other -> nil # ... nor a `>` operation.',
|
|
183
183
|
|
|
184
|
-
include_denied: 'An @"..." include was refused by the active trust profile\n(docs/trust.md). The document asked to read a source the evaluation\'s\ninclude capability does not allow: widen the capability if the read is\nintended, or remove the include if it is not.\n \nExamples:\n a:@"./in-root.aon"
|
|
184
|
+
include_denied: 'An @"..." include was refused by the active trust profile\n(docs/trust.md). The document asked to read a source the evaluation\'s\ninclude capability does not allow: widen the capability if the read is\nintended, or remove the include if it is not.\n \nExamples:\n a:@"./in-root.aon" -> {..} # Inside the confinement root: allowed;\n a:@"../secret.aon" -> nil # ... but escaping the root is denied;\n a:@"/etc/hostname" -> nil # ... and so is an absolute path outside it.',
|
|
185
185
|
include_extension: 'An @\"...\" include named a file the engine does not read. THE\nEXTENSION DECIDES which of two things a file is: `.aon` and\n`.aontu` are aontu source, with the whole language in them, and\n`.json`, `.jsonld`, `.jsonc`, `.json5`, `.jsonic`, `.jsc`, `.toml`,\n`.yaml`, `.yml` and `.ini` are configuration DATA, read by that\nformat\'s own parser. Anything else -- and a name with no extension\n-- is refused rather than guessed at: a guess produces a document\nthat looks right and is not.\n \nExamples:\n a:@\"model.aon\" -> {..} # aontu source;\n a:@\"server.toml\" -> {..} # ... config data, read as TOML;\n a:@\"notes.txt\" -> nil # ... but text is not a document;\n a:@\"data\" -> nil # ... and neither is an unnamed kind.',
|
|
186
186
|
|
|
187
187
|
func_arity: 'This function was called with the wrong number of arguments:\n{func} takes {want}, but was given {got}.\n \nExamples:\n upper(\"a\") -> \"A\" # One argument, which is what upper takes;\n upper(\"a\",\"b\") -> nil # ... so two is a mistake in the source;\n key() -> \"\" # key takes none, or one level count;\n neq(1,2,3) -> neq # ... and neq takes one or more exclusions.',
|
|
@@ -542,6 +542,201 @@ const hints: Record<string, string> = {
|
|
|
542
542
|
// Close operation
|
|
543
543
|
'close': 'Failed to close structure. The structure could not be closed.',
|
|
544
544
|
|
|
545
|
+
// Parse: the source text is malformed or unusable
|
|
546
|
+
'parse': 'The document could not be turned into a value. This wraps the\n' +
|
|
547
|
+
'failure that stopped it -- a syntax error, or a source the include\n' +
|
|
548
|
+
'machinery refused -- and that inner code and its frame say which,\n' +
|
|
549
|
+
'so a report names the inner code rather than this one.\n' +
|
|
550
|
+
' \nNote that `parse` is also the name of a CLASS, and the class is\n' +
|
|
551
|
+
'what a refusal shows in the second bracket: `syntax [parse]` is\n' +
|
|
552
|
+
'the code `syntax` in the class `parse`.',
|
|
553
|
+
|
|
554
|
+
'syntax': 'The text is not valid Aontu syntax. The frame points at the\n' +
|
|
555
|
+
'character the parser stopped on; the fault is usually just before\n' +
|
|
556
|
+
'it, and commenting out the suspect lines with # isolates which.',
|
|
557
|
+
|
|
558
|
+
'parse_unknown':
|
|
559
|
+
'A parsed value arrived in a kind the language has no value for.\n' +
|
|
560
|
+
'Aontu builds from maps, lists, strings, numbers, booleans and nil,\n' +
|
|
561
|
+
'so a value outside that set is a defect in whatever produced it\n' +
|
|
562
|
+
'rather than something a document can write.',
|
|
563
|
+
|
|
564
|
+
'negative': 'Only a number can be negated. A `-` in front of a\n' +
|
|
565
|
+
'non-numeric value is an error rather than a missing value.\n' +
|
|
566
|
+
' \nA `-` is a PREFIX here and never an operator between two values,\n' +
|
|
567
|
+
'so subtraction is sub(a, b).' +
|
|
568
|
+
'\n \nExamples:\n' +
|
|
569
|
+
' a: -1 -> -1 # A number negates;\n' +
|
|
570
|
+
' a: -x -> nil # ... a bare word does not;\n' +
|
|
571
|
+
' a: k-x -> "k-x" # ... and this is one word, not a minus;\n' +
|
|
572
|
+
' a: sub(5,3) -> 2 # Subtraction is a function.',
|
|
573
|
+
|
|
574
|
+
'not_number': 'This numeric literal is not a finite number. A literal\n' +
|
|
575
|
+
'past the range a double can hold (1e999) overflows to infinity\n' +
|
|
576
|
+
'while lexing, which is an error value and not a number. Write it\n' +
|
|
577
|
+
'within range, or as a decimal literal with the 0d escape.',
|
|
578
|
+
|
|
579
|
+
'incomplete_expression':
|
|
580
|
+
'The expression has no terms. An operator or a pair of parentheses\n' +
|
|
581
|
+
'was written with nothing for it to work on -- `a:()` is the bare\n' +
|
|
582
|
+
'case. Supply the operand, or delete the construct.',
|
|
583
|
+
|
|
584
|
+
'alias_in_path':
|
|
585
|
+
'An alias is not a path segment. The alias namespace and the path\n' +
|
|
586
|
+
'namespace are disjoint, so `$.%foo` is refused at any depth: an\n' +
|
|
587
|
+
'alias is reached by writing `%foo` and only that.' +
|
|
588
|
+
'\n \nExamples:\n' +
|
|
589
|
+
' %port = min(1) -> declared;\n' +
|
|
590
|
+
' listen: %port -> the alias, reached by its own name;\n' +
|
|
591
|
+
' listen: $.%port -> nil # Not a path segment.',
|
|
592
|
+
|
|
593
|
+
'alias_not_toplevel':
|
|
594
|
+
'An alias declaration sits at the root of the document. A nested\n' +
|
|
595
|
+
'`x: { %a = 1 }` is refused because `%a` resolves from the root: the\n' +
|
|
596
|
+
'declaration would be erased from the output, being a declaration,\n' +
|
|
597
|
+
'and still unreachable by any reference, not being at the root.\n' +
|
|
598
|
+
'Where the declaration LANDS decides this, not where it was written,\n' +
|
|
599
|
+
'so an include spliced at the root may declare one and an include\n' +
|
|
600
|
+
'taken as a value may not.',
|
|
601
|
+
|
|
602
|
+
'patch_assignment':
|
|
603
|
+
'This is not a <path>=<value> assignment. The path is what stands\n' +
|
|
604
|
+
'before the first `=` and the value is what follows it, so an\n' +
|
|
605
|
+
'argument carrying only one of them cannot be applied.',
|
|
606
|
+
|
|
607
|
+
// Reference: a name or path resolves to nothing editable
|
|
608
|
+
'multisource_not_found':
|
|
609
|
+
'The source named here was not found. For a file include, check the\n' +
|
|
610
|
+
'path as written, which is resolved against the document that writes\n' +
|
|
611
|
+
'it; for an `aontu:` name the message lists the models the language\n' +
|
|
612
|
+
'supplies, and a name outside that set is never looked for on disk.',
|
|
613
|
+
|
|
614
|
+
'patch_ambiguous':
|
|
615
|
+
'More than one statement pins this path, so there is no single\n' +
|
|
616
|
+
'literal to rewrite in place. The sites on the finding are all of\n' +
|
|
617
|
+
'them: edit the one that should change, or narrow the overlay so\n' +
|
|
618
|
+
'that only one pins the path.',
|
|
619
|
+
|
|
620
|
+
'patch_not_editable':
|
|
621
|
+
'There is no literal at this path to rewrite in place. The value is\n' +
|
|
622
|
+
'reached through a reference, arrives only once the overlay loads\n' +
|
|
623
|
+
'another document, or is produced rather than written -- the finding\n' +
|
|
624
|
+
'names which, and the sites say where it does come from. Edit there,\n' +
|
|
625
|
+
'or let set append instead of asking for --in-place.',
|
|
626
|
+
|
|
627
|
+
'var': 'This variable has no value, and an unresolved variable cannot\n' +
|
|
628
|
+
'be generated. A `$name` variable is supplied by the CALLER, never\n' +
|
|
629
|
+
'by the document: bind it through `ctx.vars` in TypeScript or pass\n' +
|
|
630
|
+
'it to `UnifyVars` in Go. If the value was meant to come from the\n' +
|
|
631
|
+
'document, a path reference ($.a.b) or an alias (%name) is what\n' +
|
|
632
|
+
'reads one.',
|
|
633
|
+
|
|
634
|
+
// Internal: the engine surprised itself
|
|
635
|
+
'patch_span_mismatch':
|
|
636
|
+
'The overlay does not hold the text the site says is there, so the\n' +
|
|
637
|
+
'span cannot be verified and the edit is refused rather than\n' +
|
|
638
|
+
'written: splicing without verifying the span corrupts the file.\n' +
|
|
639
|
+
'The span is checked against the very text it was derived from, so\n' +
|
|
640
|
+
'a document changing underneath cannot produce this. It is an\n' +
|
|
641
|
+
'engine provenance defect -- the one refusal classed internal for\n' +
|
|
642
|
+
'that reason -- and worth reporting.',
|
|
643
|
+
|
|
644
|
+
'unify_failed':
|
|
645
|
+
'The document does not evaluate, and the failure carried no more\n' +
|
|
646
|
+
'specific code. This stands in where a nil reaches the report\n' +
|
|
647
|
+
'without one, so the other findings in the same run are what say\n' +
|
|
648
|
+
'what actually went wrong.',
|
|
649
|
+
|
|
650
|
+
'unknown_op':
|
|
651
|
+
'The parser produced an operator form the value builder does not\n' +
|
|
652
|
+
'recognise. Reaching this is an engine defect rather than a fault\n' +
|
|
653
|
+
'in the document.',
|
|
654
|
+
|
|
655
|
+
// Compat: the subsumption and outcome vocabulary
|
|
656
|
+
'compat_narrowed':
|
|
657
|
+
'The general value does not admit the specific one: the specific\n' +
|
|
658
|
+
'side allows something the general side refuses -- a wider kind, a\n' +
|
|
659
|
+
'wider residual, or a value the general residual excludes. A\n' +
|
|
660
|
+
'document the specific side accepts can therefore fail against the\n' +
|
|
661
|
+
'general one. Which document is on which side is the comparison\'s:\n' +
|
|
662
|
+
'subsume takes them in the order given, and breaking decides by its\n' +
|
|
663
|
+
'mode.',
|
|
664
|
+
|
|
665
|
+
'compat_required_added':
|
|
666
|
+
'The general value requires this key and the specific value does\n' +
|
|
667
|
+
'not, either because the key is absent there or because it is\n' +
|
|
668
|
+
'optional there. Instances without the key are admitted where the\n' +
|
|
669
|
+
'general side refuses them.',
|
|
670
|
+
|
|
671
|
+
'compat_default_changed':
|
|
672
|
+
'The general and specific values have different effective defaults,\n' +
|
|
673
|
+
'so a document resolving to one of them against one side resolves\n' +
|
|
674
|
+
'to the other -- or stops resolving -- against the other, with\n' +
|
|
675
|
+
'nothing in the document itself changing. Which document is on\n' +
|
|
676
|
+
'which side is the comparison\'s own.',
|
|
677
|
+
|
|
678
|
+
'compat_marks_changed':
|
|
679
|
+
'The marks on this value differ between the general and specific\n' +
|
|
680
|
+
'sides: one carries type() or hide() where the other does not. A\n' +
|
|
681
|
+
'mark changes what the value contributes to a generated document,\n' +
|
|
682
|
+
'so the two are not the same declaration even where what they admit\n' +
|
|
683
|
+
'agrees. Reported under the gen profile, which is the one that\n' +
|
|
684
|
+
'compares them.',
|
|
685
|
+
|
|
686
|
+
'compat_outcome_changed':
|
|
687
|
+
'This path resolved to one value before and resolves to a different\n' +
|
|
688
|
+
'one now. Nothing refuses, so the change is silent: a consumer\n' +
|
|
689
|
+
'reading this path gets a new answer with no error to say so.',
|
|
690
|
+
|
|
691
|
+
'compat_undetermined':
|
|
692
|
+
'This path resolved to a value before and nothing resolves it now.\n' +
|
|
693
|
+
'The declaration became incomplete rather than wrong, so a consumer\n' +
|
|
694
|
+
'that read a value here reads nothing.',
|
|
695
|
+
|
|
696
|
+
'deprecated':
|
|
697
|
+
'This value is marked deprecated. The record carries the author\'s\n' +
|
|
698
|
+
'message, and where they supplied them, what to use instead and the\n' +
|
|
699
|
+
'version it was deprecated in. Nothing refuses: a deprecation is a\n' +
|
|
700
|
+
'warning and never changes a verdict.',
|
|
701
|
+
|
|
702
|
+
'pref_not_instance':
|
|
703
|
+
'The preferred value is not repeated among the remaining\n' +
|
|
704
|
+
'alternatives of its disjunction. Nothing is refused and the\n' +
|
|
705
|
+
'preference still holds -- the default stays admitted, and\n' +
|
|
706
|
+
'generation selects it -- so this is ADVISORY. It catches the typo\n' +
|
|
707
|
+
'where a default was meant to name one of the alternatives beside\n' +
|
|
708
|
+
'it and names something else instead.',
|
|
709
|
+
|
|
710
|
+
'sub_unresolved':
|
|
711
|
+
'An unresolved value has no admitted set to compare. Either a\n' +
|
|
712
|
+
'residue is still standing here, or the two sides are value formers\n' +
|
|
713
|
+
'no comparison rule covers, so the answer is undecided rather than\n' +
|
|
714
|
+
'yes or no.',
|
|
715
|
+
|
|
716
|
+
'sub_evaluate_only':
|
|
717
|
+
'An evaluate-only check makes the admitted set opaque. must() is\n' +
|
|
718
|
+
'checked by running it and never by reasoning about what it admits,\n' +
|
|
719
|
+
'so a value carrying one cannot be compared and the answer is\n' +
|
|
720
|
+
'undecided rather than yes or no.',
|
|
721
|
+
|
|
722
|
+
'sub_disjunct_distribution':
|
|
723
|
+
'An alternative is not admitted member by member, and no concrete\n' +
|
|
724
|
+
'value settles it either way. Comparing a disjunction member-wise\n' +
|
|
725
|
+
'is sound when it answers yes; a no needs a counterexample, and\n' +
|
|
726
|
+
'there is none here, so the answer is undecided.',
|
|
727
|
+
|
|
728
|
+
'sub_default_indeterminate':
|
|
729
|
+
'The effective default is not a single value, because preferences\n' +
|
|
730
|
+
'of equal rank disagree. Nothing can be decided about the default\n' +
|
|
731
|
+
'until one of them is ranked (`**x`) or they are made to agree, so\n' +
|
|
732
|
+
'the answer is undecided rather than yes or no.',
|
|
733
|
+
|
|
734
|
+
'sub_path_dependent_spread':
|
|
735
|
+
'A spread template that depends on where it lands cannot be\n' +
|
|
736
|
+
'compared structurally. What it produces is known only once it is\n' +
|
|
737
|
+
'applied to a path, so the general and specific sides cannot be\n' +
|
|
738
|
+
'held against each other and the answer is undecided.',
|
|
739
|
+
|
|
545
740
|
// Dynamic patterns (these serve as prefixes)
|
|
546
741
|
'func:': 'Function error: ',
|
|
547
742
|
'op:': 'Operator error: ',
|
|
@@ -780,6 +975,7 @@ const codeClasses: Record<string, string> = {
|
|
|
780
975
|
|
|
781
976
|
// internal -- the engine reached a state it should not reach
|
|
782
977
|
internal: 'internal',
|
|
978
|
+
unify_failed: 'internal',
|
|
783
979
|
unify_no_res: 'internal',
|
|
784
980
|
unknown_op: 'internal',
|
|
785
981
|
}
|