aontu 0.55.0 → 0.56.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 +3 -3
- package/dist/agentsmd.d.ts +1 -0
- package/dist/agentsmd.js +10 -3
- package/dist/agentsmd.js.map +1 -1
- package/dist/aontu.d.ts +3 -2
- package/dist/aontu.js +5 -2
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +5 -2
- package/dist/cli.js +236 -49
- package/dist/cli.js.map +1 -1
- package/dist/diff.d.ts +1 -0
- package/dist/diff.js +3 -2
- package/dist/diff.js.map +1 -1
- package/dist/format.d.ts +17 -0
- package/dist/format.js +1000 -0
- package/dist/format.js.map +1 -0
- package/dist/hints.js +4 -0
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.d.ts +1 -0
- package/dist/jsonschema.js +3 -2
- package/dist/jsonschema.js.map +1 -1
- package/dist/lang.js +64 -5
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +2 -1
- package/dist/lsp.js +1 -1
- package/dist/lsp.js.map +1 -1
- package/dist/mcp.js +6 -4
- package/dist/mcp.js.map +1 -1
- package/dist/patch.d.ts +1 -0
- package/dist/patch.js +1 -0
- package/dist/patch.js.map +1 -1
- package/dist/query.d.ts +1 -0
- package/dist/query.js +4 -3
- package/dist/query.js.map +1 -1
- package/dist/reach.d.ts +1 -0
- package/dist/reach.js +3 -2
- package/dist/reach.js.map +1 -1
- package/dist/relation.d.ts +1 -0
- package/dist/relation.js +3 -2
- package/dist/relation.js.map +1 -1
- package/dist/std.js +2 -1
- package/dist/std.js.map +1 -1
- package/dist/subsume.d.ts +1 -0
- package/dist/subsume.js +3 -2
- package/dist/subsume.js.map +1 -1
- package/dist/trim.d.ts +1 -0
- package/dist/trim.js +3 -2
- package/dist/trim.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type.d.ts +1 -0
- package/dist/type.js.map +1 -1
- package/dist/utility.d.ts +8 -2
- package/dist/utility.js +9 -1
- package/dist/utility.js.map +1 -1
- package/dist/vet.d.ts +1 -0
- package/dist/vet.js +1 -1
- package/dist/vet.js.map +1 -1
- package/dist/view.d.ts +2 -1
- package/dist/view.js +379 -8
- package/dist/view.js.map +1 -1
- package/grammar/aontu.abnf +155 -0
- package/package.json +2 -1
- package/skill/grammar-card.md +5 -2
- package/src/agentsmd.ts +15 -3
- package/src/aontu.ts +7 -1
- package/src/cli.ts +267 -53
- package/src/diff.ts +7 -2
- package/src/format.ts +1146 -0
- package/src/hints.ts +5 -0
- package/src/jsonschema.ts +7 -1
- package/src/lang.ts +69 -5
- package/src/lsp.ts +5 -3
- package/src/mcp.ts +6 -4
- package/src/patch.ts +7 -0
- package/src/query.ts +8 -4
- package/src/reach.ts +7 -2
- package/src/relation.ts +7 -2
- package/src/std.ts +2 -1
- package/src/subsume.ts +7 -2
- package/src/trim.ts +7 -2
- package/src/type.ts +8 -0
- package/src/utility.ts +27 -2
- package/src/vet.ts +9 -3
- package/src/view.ts +442 -9
package/src/cli.ts
CHANGED
|
@@ -48,6 +48,9 @@ import type {
|
|
|
48
48
|
import type { QueryView } from './query'
|
|
49
49
|
import type { WhyRecord } from './provenance'
|
|
50
50
|
import { agentsMdSplice } from './agentsmd'
|
|
51
|
+
import { format, unifiedDiff } from './format'
|
|
52
|
+
import { includeOpts } from './utility'
|
|
53
|
+
import type { IncludeOptions } from './utility'
|
|
51
54
|
|
|
52
55
|
|
|
53
56
|
type Mode = 'json' | 'canon'
|
|
@@ -69,6 +72,7 @@ const HELP = `Usage: aontu [options] [file]
|
|
|
69
72
|
aontu why <path> [options] <file>
|
|
70
73
|
aontu set <path>=<value>... --entry <file> --overlay <file>
|
|
71
74
|
aontu agentsmd [--write <AGENTS.md>] <file>
|
|
75
|
+
aontu fmt [-w|-l|--check|-d] <file>...
|
|
72
76
|
|
|
73
77
|
Evaluate an Aontu source file and print the result as JSON.
|
|
74
78
|
With no file on an interactive terminal, start a REPL.
|
|
@@ -91,6 +95,10 @@ Options:
|
|
|
91
95
|
Every verb takes it too, and a bare root means the
|
|
92
96
|
document's own directory
|
|
93
97
|
--include-root <dir> Shorthand for --trust root:<dir>
|
|
98
|
+
--text-ext <e> Read these extensions as text too, comma-separated
|
|
99
|
+
and without dots (md,sql). .txt needs no flag; a
|
|
100
|
+
named format keeps its meaning, and .js stays
|
|
101
|
+
refused. Every verb takes it
|
|
94
102
|
|
|
95
103
|
Mod options:
|
|
96
104
|
--format <f> text (default) or json
|
|
@@ -182,8 +190,8 @@ Why options:
|
|
|
182
190
|
Why exit codes mirror get's: 0 explained, 1 the path names nothing,
|
|
183
191
|
2 usage, 4 the document does not stand up on its own.
|
|
184
192
|
|
|
185
|
-
View kinds: tree, matrix, graph, layer, sets, layers,
|
|
186
|
-
(the poset takes several files). The figure goes to stdout, the loss
|
|
193
|
+
View kinds: doc, lattice, tree, matrix, graph, layer, sets, layers,
|
|
194
|
+
ladder, poset (the poset takes several files). The figure goes to stdout, the loss
|
|
187
195
|
report to stderr. With --views it draws every figure a document
|
|
188
196
|
declares as data, from one evaluation: each declaration names its own
|
|
189
197
|
kind and out file, nothing is written unless every figure rendered,
|
|
@@ -191,13 +199,13 @@ and --check gates the committed set.
|
|
|
191
199
|
|
|
192
200
|
View options:
|
|
193
201
|
--as <profile> text | mermaid | dot | er | svg, per kind: doc,
|
|
194
|
-
tree, matrix, sets and layers draw text
|
|
195
|
-
or svg; graph draws mermaid (default),
|
|
196
|
-
layer draws text (default), mermaid or
|
|
197
|
-
and poset draw mermaid (default) or dot
|
|
202
|
+
lattice, tree, matrix, sets and layers draw text
|
|
203
|
+
(default) or svg; graph draws mermaid (default),
|
|
204
|
+
dot or er; layer draws text (default), mermaid or
|
|
205
|
+
svg; ladder and poset draw mermaid (default) or dot
|
|
198
206
|
--at <path> Restrict the figure to nodes under this path; the
|
|
199
|
-
subtree doc draws; the
|
|
200
|
-
where the poset compares
|
|
207
|
+
subtree doc draws; the subtree the lattice counts;
|
|
208
|
+
the path the ladder draws; where the poset compares
|
|
201
209
|
--views <path> Draw every figure the document declares at this
|
|
202
210
|
path, one evaluation, all or nothing; each
|
|
203
211
|
declaration names its own kind and out file
|
|
@@ -276,6 +284,19 @@ Agentsmd options:
|
|
|
276
284
|
Agentsmd exit codes: 0 generated, 2 usage, 4 the document does not
|
|
277
285
|
stand up on its own.
|
|
278
286
|
|
|
287
|
+
Fmt options:
|
|
288
|
+
-w, --write Rewrite each file in place, when its form would change
|
|
289
|
+
-l, --list Print the name of each file whose form would change
|
|
290
|
+
--check Like --list, and exit 1 when any would: the CI gate
|
|
291
|
+
-d, --diff Print a unified diff for each file whose form would
|
|
292
|
+
change
|
|
293
|
+
|
|
294
|
+
The fmt verb prints one document in the agreed form; with no file it
|
|
295
|
+
reads standard input. Several files need one of the options above.
|
|
296
|
+
|
|
297
|
+
Fmt exit codes: 0 formatted or clean, 1 a --check file would change,
|
|
298
|
+
2 usage, 4 a document does not parse.
|
|
299
|
+
|
|
279
300
|
REPL commands:
|
|
280
301
|
:help Show REPL help
|
|
281
302
|
:load <file> Evaluate a document and hold it for the commands below
|
|
@@ -333,11 +354,18 @@ function evalSource(
|
|
|
333
354
|
// entry root or goes through package resolution prints a one-line
|
|
334
355
|
// stderr warning naming the flag a future default will require
|
|
335
356
|
// (phase 6, the staged flip).
|
|
336
|
-
type TrustArg =
|
|
357
|
+
type TrustArg = (
|
|
337
358
|
| { kind: 'system-warn' }
|
|
338
359
|
| { kind: 'system' }
|
|
339
360
|
| { kind: 'none' }
|
|
340
361
|
| { kind: 'root', dir?: string }
|
|
362
|
+
// EXTENSIONS READ AS TEXT ride with the capability rather than
|
|
363
|
+
// beside it: both answer "what may an include read", both are
|
|
364
|
+
// stripped by takeTrust before a verb parses its own tail, and a
|
|
365
|
+
// verb that threads one and not the other is the G5 defect again --
|
|
366
|
+
// `aontu vet` running under a flag the bare command honoured and it
|
|
367
|
+
// did not.
|
|
368
|
+
) & { textExt: string[] }
|
|
341
369
|
|
|
342
370
|
|
|
343
371
|
// The one-line warning of the staged default flip. Once per (kind,
|
|
@@ -366,15 +394,16 @@ function makeTrustWarn(): (kind: 'escape' | 'pkg', path: string) => void {
|
|
|
366
394
|
// entryRoot (the entry file's directory, or the working directory for
|
|
367
395
|
// stdin/REPL).
|
|
368
396
|
function trustOpts(trust: TrustArg, entryRoot: string): any {
|
|
397
|
+
const text = 0 === trust.textExt.length ? {} : { textExt: trust.textExt }
|
|
369
398
|
switch (trust.kind) {
|
|
370
399
|
case 'none':
|
|
371
|
-
return { trust: { include: 'none' } }
|
|
400
|
+
return { ...text, trust: { include: 'none' } }
|
|
372
401
|
case 'root':
|
|
373
|
-
return { trust: { include: { root: trust.dir ?? entryRoot } } }
|
|
402
|
+
return { ...text, trust: { include: { root: trust.dir ?? entryRoot } } }
|
|
374
403
|
case 'system':
|
|
375
|
-
return {}
|
|
404
|
+
return { ...text }
|
|
376
405
|
default: // system-warn: today's default plus the warning window
|
|
377
|
-
return { trustWarn: makeTrustWarn(), trustWarnRoot: entryRoot }
|
|
406
|
+
return { ...text, trustWarn: makeTrustWarn(), trustWarnRoot: entryRoot }
|
|
378
407
|
}
|
|
379
408
|
}
|
|
380
409
|
|
|
@@ -392,7 +421,8 @@ function trustOpts(trust: TrustArg, entryRoot: string): any {
|
|
|
392
421
|
function takeTrust(argv: string[]):
|
|
393
422
|
{ argv: string[], trust: TrustArg } | undefined {
|
|
394
423
|
const rest: string[] = []
|
|
395
|
-
let trust: TrustArg = { kind: 'system-warn' }
|
|
424
|
+
let trust: TrustArg = { kind: 'system-warn', textExt: [] }
|
|
425
|
+
let textExt: string[] = []
|
|
396
426
|
for (let i = 0; i < argv.length; i++) {
|
|
397
427
|
const arg = argv[i]
|
|
398
428
|
if ('--trust' === arg) {
|
|
@@ -410,21 +440,47 @@ function takeTrust(argv: string[]):
|
|
|
410
440
|
process.stderr.write('aontu: --include-root needs a directory\n')
|
|
411
441
|
return undefined
|
|
412
442
|
}
|
|
413
|
-
trust = { kind: 'root', dir }
|
|
443
|
+
trust = { kind: 'root', dir, textExt }
|
|
444
|
+
}
|
|
445
|
+
else if ('--text-ext' === arg) {
|
|
446
|
+
const list = null == argv[i + 1] ? undefined : parseTextExt(argv[++i])
|
|
447
|
+
if (null == list) {
|
|
448
|
+
process.stderr.write(
|
|
449
|
+
'aontu: --text-ext needs extensions, without dots' +
|
|
450
|
+
' (--text-ext md,sql)\n')
|
|
451
|
+
return undefined
|
|
452
|
+
}
|
|
453
|
+
textExt = [...textExt, ...list]
|
|
414
454
|
}
|
|
415
455
|
else {
|
|
416
456
|
rest.push(arg)
|
|
417
457
|
}
|
|
418
458
|
}
|
|
419
|
-
return { argv: rest, trust }
|
|
459
|
+
return { argv: rest, trust: { ...trust, textExt } }
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
// `md,sql` or `.md,.sql` -- the dot is accepted and dropped, because a
|
|
464
|
+
// reader who has just written `@"notes.txt"` reaches for one. An empty
|
|
465
|
+
// element, or anything that is not an extension, is a usage error
|
|
466
|
+
// rather than a silently ignored word: a flag that quietly does
|
|
467
|
+
// nothing is how a document ends up refused with no reason visible.
|
|
468
|
+
function parseTextExt(arg: string): string[] | undefined {
|
|
469
|
+
const out: string[] = []
|
|
470
|
+
for (const raw of arg.split(',')) {
|
|
471
|
+
const ext = raw.trim().replace(/^\./, '').toLowerCase()
|
|
472
|
+
if ('' === ext || !/^[a-z0-9]+$/.test(ext)) {
|
|
473
|
+
return undefined
|
|
474
|
+
}
|
|
475
|
+
out.push(ext)
|
|
476
|
+
}
|
|
477
|
+
return out
|
|
420
478
|
}
|
|
421
479
|
|
|
422
480
|
|
|
423
481
|
// The evaluator options a REPL session's capability means.
|
|
424
482
|
function replTrust(state: ReplState, entryRoot: string): any {
|
|
425
|
-
|
|
426
|
-
state.trust ?? { kind: 'system-warn' }, entryRoot)
|
|
427
|
-
return null == capability ? {} : { trust: capability }
|
|
483
|
+
return verbOpts(state.trust ?? { kind: 'system-warn', textExt: [] }, entryRoot)
|
|
428
484
|
}
|
|
429
485
|
|
|
430
486
|
|
|
@@ -444,6 +500,20 @@ function verbTrust(trust: TrustArg, entryRoot: string): any {
|
|
|
444
500
|
}
|
|
445
501
|
|
|
446
502
|
|
|
503
|
+
// THE INCLUDE OPTIONS A VERB RUNS UNDER, spread into its engine call:
|
|
504
|
+
// the capability above, and the extensions `--text-ext` widened. Both
|
|
505
|
+
// are absent when unset rather than present-and-undefined, so a verb's
|
|
506
|
+
// options bag is byte-identical to what it was before either flag
|
|
507
|
+
// existed and no engine sees a key it has to ignore.
|
|
508
|
+
function verbOpts(trust: TrustArg, entryRoot: string): any {
|
|
509
|
+
const include = verbTrust(trust, entryRoot)
|
|
510
|
+
return {
|
|
511
|
+
...(undefined === include ? {} : { trust: include }),
|
|
512
|
+
...(0 === trust.textExt.length ? {} : { textExt: trust.textExt }),
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
|
|
447
517
|
// The directory a bare `--trust root` confines to for a verb: the
|
|
448
518
|
// primary document's own, matching the bare command's entry root.
|
|
449
519
|
function entryRootOf(file: string | undefined): string {
|
|
@@ -607,7 +677,7 @@ export function replCommand(
|
|
|
607
677
|
if (':why' === cmd) {
|
|
608
678
|
const report = why(src, path, {
|
|
609
679
|
path: state.name,
|
|
610
|
-
|
|
680
|
+
...verbOpts(state.trust ?? { kind: 'system-warn', textExt: [] },
|
|
611
681
|
entryRootOf(state.name)),
|
|
612
682
|
})
|
|
613
683
|
return report.ok
|
|
@@ -618,7 +688,7 @@ export function replCommand(
|
|
|
618
688
|
? 'keys' : 'canon' === state.mode ? 'canon' : 'json'
|
|
619
689
|
const report = get(src, path, {
|
|
620
690
|
view, path: state.name,
|
|
621
|
-
|
|
691
|
+
...verbOpts(state.trust ?? { kind: 'system-warn', textExt: [] },
|
|
622
692
|
entryRootOf(state.name)),
|
|
623
693
|
})
|
|
624
694
|
return report.ok
|
|
@@ -901,7 +971,7 @@ function vetOnce(args: VetArgs, trust: TrustArg): number {
|
|
|
901
971
|
|
|
902
972
|
for (const source of sources) {
|
|
903
973
|
const report = vet(schemaSrc, source.src, {
|
|
904
|
-
|
|
974
|
+
...verbOpts(trust, entryRootOf(args.schema)),
|
|
905
975
|
at: args.at,
|
|
906
976
|
closed: args.closed,
|
|
907
977
|
partial: args.partial,
|
|
@@ -1193,7 +1263,7 @@ function runSubsume(argv: string[]): number {
|
|
|
1193
1263
|
}
|
|
1194
1264
|
|
|
1195
1265
|
const report = subsume(generalSrc, specificSrc, {
|
|
1196
|
-
|
|
1266
|
+
...verbOpts(trust, entryRootOf(args.general)),
|
|
1197
1267
|
profile: args.profile,
|
|
1198
1268
|
at: args.at,
|
|
1199
1269
|
generalUrl: args.general,
|
|
@@ -1417,15 +1487,18 @@ function oldVersion(spec: string, file: string): OldVersion | undefined {
|
|
|
1417
1487
|
// a disjunction whose default is the declared mode. Undefined when the
|
|
1418
1488
|
// key is absent or does not spell a mode.
|
|
1419
1489
|
function policyCompat(
|
|
1420
|
-
newSrc: string, path: string,
|
|
1490
|
+
newSrc: string, path: string, include: IncludeOptions
|
|
1491
|
+
): BreakingMode | undefined {
|
|
1421
1492
|
const aontu = new Aontu()
|
|
1422
1493
|
const ctx = aontu.ctx({ collect: true })
|
|
1423
1494
|
// The declaration is read by EVALUATING the document, so this leg
|
|
1424
|
-
// runs the include resolver too and has to run it under the
|
|
1425
|
-
//
|
|
1426
|
-
// through an unconfined resolver would confine the
|
|
1427
|
-
// not the question (use-cases/REVIEW.md finding G)
|
|
1428
|
-
|
|
1495
|
+
// runs the include resolver too and has to run it under BOTH of the
|
|
1496
|
+
// verb's include options -- a `breaking --trust none` that read its
|
|
1497
|
+
// own mode through an unconfined resolver would confine the
|
|
1498
|
+
// comparison and not the question (use-cases/REVIEW.md finding G),
|
|
1499
|
+
// and one that took the capability alone read no mode at all when
|
|
1500
|
+
// the declaration arrived through a `--text-ext` include.
|
|
1501
|
+
const v: any = aontu.unify(newSrc, { path, ...includeOpts(include) }, ctx)
|
|
1429
1502
|
if (0 < ctx.err.length || true === v?.isNil) {
|
|
1430
1503
|
return undefined
|
|
1431
1504
|
}
|
|
@@ -1528,7 +1601,8 @@ function runBreaking(argv: string[]): number {
|
|
|
1528
1601
|
// stay valid).
|
|
1529
1602
|
const mode: BreakingMode =
|
|
1530
1603
|
args.mode ??
|
|
1531
|
-
policyCompat(newSrc, args.file,
|
|
1604
|
+
policyCompat(newSrc, args.file,
|
|
1605
|
+
verbOpts(trust, entryRootOf(args.file))) ??
|
|
1532
1606
|
'backward'
|
|
1533
1607
|
|
|
1534
1608
|
if ('none' === mode) {
|
|
@@ -1578,7 +1652,7 @@ function runBreaking(argv: string[]): number {
|
|
|
1578
1652
|
|
|
1579
1653
|
for (const check of checks) {
|
|
1580
1654
|
const report = subsume(check.general[0], check.specific[0], {
|
|
1581
|
-
|
|
1655
|
+
...verbOpts(trust, entryRootOf(args.file)),
|
|
1582
1656
|
at: args.at,
|
|
1583
1657
|
generalUrl: check.general[1],
|
|
1584
1658
|
specificUrl: check.specific[1],
|
|
@@ -1724,7 +1798,7 @@ function runTrim(argv: string[]): number {
|
|
|
1724
1798
|
}
|
|
1725
1799
|
|
|
1726
1800
|
const report = trimCheck(src, {
|
|
1727
|
-
path: files[0],
|
|
1801
|
+
path: files[0], ...verbOpts(trust, entryRootOf(files[0])),
|
|
1728
1802
|
})
|
|
1729
1803
|
const text = 'json' === format
|
|
1730
1804
|
? renderTrimJson(report)
|
|
@@ -1788,8 +1862,8 @@ const VIEW_HELP =
|
|
|
1788
1862
|
'aontu view <kind> [options] <file>... (try --help)'
|
|
1789
1863
|
|
|
1790
1864
|
const VIEW_KINDS: ViewKind[] =
|
|
1791
|
-
['doc', 'tree', 'matrix', 'graph', 'layer', 'sets', 'layers',
|
|
1792
|
-
'poset']
|
|
1865
|
+
['doc', 'lattice', 'tree', 'matrix', 'graph', 'layer', 'sets', 'layers',
|
|
1866
|
+
'ladder', 'poset']
|
|
1793
1867
|
|
|
1794
1868
|
const VIEW_PROFILES: ViewProfile[] = ['text', 'mermaid', 'dot', 'er', 'svg']
|
|
1795
1869
|
|
|
@@ -2110,7 +2184,7 @@ function runRelations(argv: string[]): number {
|
|
|
2110
2184
|
}
|
|
2111
2185
|
|
|
2112
2186
|
const report = relationCheck(src, {
|
|
2113
|
-
path: files[0],
|
|
2187
|
+
path: files[0], ...verbOpts(trust, entryRootOf(files[0])),
|
|
2114
2188
|
})
|
|
2115
2189
|
const text = 'json' === format
|
|
2116
2190
|
? renderRelationsJson(report)
|
|
@@ -2178,7 +2252,7 @@ function runReaches(argv: string[]): number {
|
|
|
2178
2252
|
|
|
2179
2253
|
const report = reachCheck(src, rest[0], rest[1], {
|
|
2180
2254
|
path: rest[2], relation,
|
|
2181
|
-
|
|
2255
|
+
...verbOpts(trust, entryRootOf(rest[2])),
|
|
2182
2256
|
})
|
|
2183
2257
|
const text = 'json' === format
|
|
2184
2258
|
? renderReachesJson(report)
|
|
@@ -2424,7 +2498,7 @@ function runView(argv: string[]): number {
|
|
|
2424
2498
|
kind,
|
|
2425
2499
|
path: files[0],
|
|
2426
2500
|
roots,
|
|
2427
|
-
|
|
2501
|
+
...verbOpts(trust, entryRootOf(files[0])),
|
|
2428
2502
|
docs: files.slice(1).map((path, i) => ({ src: srcs[i + 1], path })),
|
|
2429
2503
|
})
|
|
2430
2504
|
|
|
@@ -2503,7 +2577,7 @@ function runViewSet(
|
|
|
2503
2577
|
}
|
|
2504
2578
|
|
|
2505
2579
|
const report = viewSet(src, {
|
|
2506
|
-
...opts, path: file,
|
|
2580
|
+
...opts, path: file, ...verbOpts(trust, entryRootOf(file)),
|
|
2507
2581
|
})
|
|
2508
2582
|
|
|
2509
2583
|
if ('json' === how.format) {
|
|
@@ -2726,7 +2800,7 @@ function runJsonSchema(argv: string[]): number {
|
|
|
2726
2800
|
}
|
|
2727
2801
|
|
|
2728
2802
|
const report = jsonSchema(src, {
|
|
2729
|
-
at, path: files[0],
|
|
2803
|
+
at, path: files[0], ...verbOpts(trust, entryRootOf(files[0])),
|
|
2730
2804
|
})
|
|
2731
2805
|
|
|
2732
2806
|
if ('json' === format) {
|
|
@@ -2819,9 +2893,7 @@ function runHash(argv: string[]): number {
|
|
|
2819
2893
|
|
|
2820
2894
|
// The file's own directory is the include base, as every verb
|
|
2821
2895
|
// resolves a named file (vet's aontuForPath rule).
|
|
2822
|
-
const
|
|
2823
|
-
const aontu = new Aontu(
|
|
2824
|
-
null == capability ? undefined : { trust: capability })
|
|
2896
|
+
const aontu = new Aontu(verbOpts(trust, entryRootOf(files[0])))
|
|
2825
2897
|
const ctx = aontu.ctx({ collect: true })
|
|
2826
2898
|
const v: any = aontu.unify(src, { path: files[0] }, ctx)
|
|
2827
2899
|
if (0 < ctx.err.length || true === v?.isNil) {
|
|
@@ -2937,7 +3009,7 @@ function runGet(argv: string[]): number {
|
|
|
2937
3009
|
}
|
|
2938
3010
|
|
|
2939
3011
|
const report = get(src, path, {
|
|
2940
|
-
view, depth, path: file,
|
|
3012
|
+
view, depth, path: file, ...verbOpts(trust, entryRootOf(file)),
|
|
2941
3013
|
})
|
|
2942
3014
|
if ('json' === format) {
|
|
2943
3015
|
process.stdout.write(exactJSON({
|
|
@@ -3021,7 +3093,7 @@ function runWhy(argv: string[]): number {
|
|
|
3021
3093
|
}
|
|
3022
3094
|
|
|
3023
3095
|
const report = why(src, path, {
|
|
3024
|
-
path: file,
|
|
3096
|
+
path: file, ...verbOpts(trust, entryRootOf(file)),
|
|
3025
3097
|
})
|
|
3026
3098
|
if ('json' === format) {
|
|
3027
3099
|
process.stdout.write(exactJSON({
|
|
@@ -3156,7 +3228,7 @@ function runSet(argv: string[]): number {
|
|
|
3156
3228
|
}
|
|
3157
3229
|
|
|
3158
3230
|
const report = patch(entrySrc, overlaySrc, assignments, {
|
|
3159
|
-
|
|
3231
|
+
...verbOpts(trust, entryRootOf(entry)),
|
|
3160
3232
|
entryPath: entry,
|
|
3161
3233
|
overlayPath: overlayFile,
|
|
3162
3234
|
inPlace,
|
|
@@ -3291,7 +3363,7 @@ function runAgentsMd(argv: string[]): number {
|
|
|
3291
3363
|
|
|
3292
3364
|
const report = agentsMd(src, {
|
|
3293
3365
|
name: files[0], path: files[0],
|
|
3294
|
-
|
|
3366
|
+
...verbOpts(trust, entryRootOf(files[0])),
|
|
3295
3367
|
})
|
|
3296
3368
|
if (!report.ok) {
|
|
3297
3369
|
process.stderr.write(
|
|
@@ -3343,6 +3415,129 @@ function runAgentsMd(argv: string[]): number {
|
|
|
3343
3415
|
// on the parity-probe discipline in AGENTS.md, which derives expected
|
|
3344
3416
|
// spec values by piping BOTH CLIs and comparing. A truncated pipe there
|
|
3345
3417
|
// reads as a port divergence.
|
|
3418
|
+
// ---------------------------------------------------------------------
|
|
3419
|
+
// The source formatter (docs/design/FMT.0.md): one agreed form, in the
|
|
3420
|
+
// tradition of gofmt. The verb prints, lists, checks, diffs or rewrites;
|
|
3421
|
+
// the form itself is the library's (ts/src/format.ts), and the two
|
|
3422
|
+
// ports agree on it row by row in test/spec/fmt.tsv.
|
|
3423
|
+
|
|
3424
|
+
const FMT_HELP = 'aontu fmt [-w|-l|--check|-d] <file>... (try --help)'
|
|
3425
|
+
|
|
3426
|
+
type FmtFlags = { write: boolean, list: boolean, check: boolean, diff: boolean }
|
|
3427
|
+
|
|
3428
|
+
function runFmt(argv: string[]): number | Promise<number> {
|
|
3429
|
+
const files: string[] = []
|
|
3430
|
+
const flags: FmtFlags = { write: false, list: false, check: false, diff: false }
|
|
3431
|
+
|
|
3432
|
+
for (const arg of argv) {
|
|
3433
|
+
if ('-h' === arg || '--help' === arg) {
|
|
3434
|
+
process.stdout.write(HELP)
|
|
3435
|
+
return 0
|
|
3436
|
+
}
|
|
3437
|
+
if ('-w' === arg || '--write' === arg) {
|
|
3438
|
+
flags.write = true
|
|
3439
|
+
}
|
|
3440
|
+
else if ('-l' === arg || '--list' === arg) {
|
|
3441
|
+
flags.list = true
|
|
3442
|
+
}
|
|
3443
|
+
else if ('--check' === arg) {
|
|
3444
|
+
flags.check = true
|
|
3445
|
+
}
|
|
3446
|
+
else if ('-d' === arg || '--diff' === arg) {
|
|
3447
|
+
flags.diff = true
|
|
3448
|
+
}
|
|
3449
|
+
else if (arg.startsWith('-')) {
|
|
3450
|
+
process.stderr.write(`aontu: unknown fmt option ${arg} (try --help)\n`)
|
|
3451
|
+
return 2
|
|
3452
|
+
}
|
|
3453
|
+
else {
|
|
3454
|
+
files.push(arg)
|
|
3455
|
+
}
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
if (0 === files.length) {
|
|
3459
|
+
// Standard input: formatted onto standard output, or listed,
|
|
3460
|
+
// checked and diffed under the name <stdin>. It cannot be written
|
|
3461
|
+
// back.
|
|
3462
|
+
if (flags.write) {
|
|
3463
|
+
process.stderr.write(`aontu: --write needs a file\n${FMT_HELP}\n`)
|
|
3464
|
+
return 2
|
|
3465
|
+
}
|
|
3466
|
+
return new Promise((resolve) => {
|
|
3467
|
+
let src = ''
|
|
3468
|
+
process.stdin.setEncoding('utf8')
|
|
3469
|
+
process.stdin.on('data', (d) => (src += d))
|
|
3470
|
+
process.stdin.on('end', () => resolve(fmtOne('<stdin>', src, flags)))
|
|
3471
|
+
})
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
// Several files onto standard output would be one stream nobody can
|
|
3475
|
+
// split again (the note's X-6): the verb refuses unless an option
|
|
3476
|
+
// says what to do with each.
|
|
3477
|
+
if (1 < files.length && !fmtQuiet(flags)) {
|
|
3478
|
+
process.stderr.write(
|
|
3479
|
+
`aontu: fmt prints one file; with ${files.length}, say --write, ` +
|
|
3480
|
+
`--list, --check or --diff\n${FMT_HELP}\n`)
|
|
3481
|
+
return 2
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
let worst = 0
|
|
3485
|
+
for (const file of files) {
|
|
3486
|
+
let src: string
|
|
3487
|
+
try {
|
|
3488
|
+
src = readFileSync(file, 'utf8')
|
|
3489
|
+
}
|
|
3490
|
+
catch (err: any) {
|
|
3491
|
+
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
3492
|
+
return 2
|
|
3493
|
+
}
|
|
3494
|
+
worst = Math.max(worst, fmtOne(file, src, flags))
|
|
3495
|
+
}
|
|
3496
|
+
return worst
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3499
|
+
// An option that says what to do with a file that would change, in
|
|
3500
|
+
// place of printing it.
|
|
3501
|
+
function fmtQuiet(flags: FmtFlags): boolean {
|
|
3502
|
+
return flags.write || flags.list || flags.check || flags.diff
|
|
3503
|
+
}
|
|
3504
|
+
|
|
3505
|
+
// One document: 0 printed, clean or done; 1 a --check that would
|
|
3506
|
+
// change; 2 a file that cannot be written; 4 a document that does not
|
|
3507
|
+
// format, with the finding that says why.
|
|
3508
|
+
function fmtOne(name: string, src: string, flags: FmtFlags): number {
|
|
3509
|
+
const report = format(src, { path: name })
|
|
3510
|
+
if ('error' === report.verdict) {
|
|
3511
|
+
process.stderr.write(`aontu: ${name} was not formatted\n` +
|
|
3512
|
+
report.errors.map(renderFinding).join('\n') + '\n')
|
|
3513
|
+
return 4
|
|
3514
|
+
}
|
|
3515
|
+
if (!fmtQuiet(flags)) {
|
|
3516
|
+
process.stdout.write(report.text)
|
|
3517
|
+
return 0
|
|
3518
|
+
}
|
|
3519
|
+
if (!report.changed) {
|
|
3520
|
+
return 0
|
|
3521
|
+
}
|
|
3522
|
+
if (flags.list || flags.check) {
|
|
3523
|
+
process.stdout.write(name + '\n')
|
|
3524
|
+
}
|
|
3525
|
+
if (flags.diff) {
|
|
3526
|
+
process.stdout.write(unifiedDiff(name, src, report.text))
|
|
3527
|
+
}
|
|
3528
|
+
if (flags.write) {
|
|
3529
|
+
try {
|
|
3530
|
+
writeFileSync(name, report.text)
|
|
3531
|
+
}
|
|
3532
|
+
catch (err: any) {
|
|
3533
|
+
process.stderr.write(`aontu: cannot write ${name}: ${err.message}\n`)
|
|
3534
|
+
return 2
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
return flags.check ? 1 : 0
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
|
|
3346
3541
|
function finish(code: number): void {
|
|
3347
3542
|
process.exitCode = code
|
|
3348
3543
|
}
|
|
@@ -3352,16 +3547,16 @@ function finish(code: number): void {
|
|
|
3352
3547
|
// spelling, so the caller owns the usage error.
|
|
3353
3548
|
function parseTrustArg(value: string): TrustArg | undefined {
|
|
3354
3549
|
if ('system' === value) {
|
|
3355
|
-
return { kind: 'system' }
|
|
3550
|
+
return { kind: 'system', textExt: [] }
|
|
3356
3551
|
}
|
|
3357
3552
|
if ('none' === value) {
|
|
3358
|
-
return { kind: 'none' }
|
|
3553
|
+
return { kind: 'none', textExt: [] }
|
|
3359
3554
|
}
|
|
3360
3555
|
if ('root' === value) {
|
|
3361
|
-
return { kind: 'root' }
|
|
3556
|
+
return { kind: 'root', textExt: [] }
|
|
3362
3557
|
}
|
|
3363
3558
|
if (value.startsWith('root:') && 'root:'.length < value.length) {
|
|
3364
|
-
return { kind: 'root', dir: value.slice('root:'.length) }
|
|
3559
|
+
return { kind: 'root', dir: value.slice('root:'.length), textExt: [] }
|
|
3365
3560
|
}
|
|
3366
3561
|
return undefined
|
|
3367
3562
|
}
|
|
@@ -3385,7 +3580,8 @@ function main(argv: string[]): void {
|
|
|
3385
3580
|
// overwritten twice. In a tool loop that reads as a passing
|
|
3386
3581
|
// validation. Counting them is what lets the refusal below happen.
|
|
3387
3582
|
const files: string[] = []
|
|
3388
|
-
let trust: TrustArg = { kind: 'system-warn' }
|
|
3583
|
+
let trust: TrustArg = { kind: 'system-warn', textExt: [] }
|
|
3584
|
+
let textExt: string[] = []
|
|
3389
3585
|
// The REPL's SESSION protocol (G7 phase 7): one JSON line per
|
|
3390
3586
|
// answer, so a harness can drive the session. Named --jsonl rather
|
|
3391
3587
|
// than the design's --json, which would read as the `:json` output
|
|
@@ -3413,6 +3609,9 @@ function main(argv: string[]): void {
|
|
|
3413
3609
|
if ('agentsmd' === argv[2]) {
|
|
3414
3610
|
return finish(runAgentsMd(argv.slice(3)))
|
|
3415
3611
|
}
|
|
3612
|
+
if ('fmt' === argv[2]) {
|
|
3613
|
+
return void Promise.resolve(runFmt(argv.slice(3))).then(finish)
|
|
3614
|
+
}
|
|
3416
3615
|
|
|
3417
3616
|
if ('set' === argv[2]) {
|
|
3418
3617
|
return finish(runSet(argv.slice(3)))
|
|
@@ -3492,7 +3691,17 @@ function main(argv: string[]): void {
|
|
|
3492
3691
|
process.stderr.write('aontu: --include-root needs a directory\n')
|
|
3493
3692
|
return finish(2)
|
|
3494
3693
|
}
|
|
3495
|
-
trust = { kind: 'root', dir }
|
|
3694
|
+
trust = { kind: 'root', dir, textExt }
|
|
3695
|
+
}
|
|
3696
|
+
else if ('--text-ext' === arg) {
|
|
3697
|
+
const list = null == args[i + 1] ? undefined : parseTextExt(args[++i])
|
|
3698
|
+
if (null == list) {
|
|
3699
|
+
process.stderr.write(
|
|
3700
|
+
'aontu: --text-ext needs extensions, without dots' +
|
|
3701
|
+
' (--text-ext md,sql)\n')
|
|
3702
|
+
return finish(2)
|
|
3703
|
+
}
|
|
3704
|
+
textExt = [...textExt, ...list]
|
|
3496
3705
|
}
|
|
3497
3706
|
else if (arg.startsWith('-')) {
|
|
3498
3707
|
process.stderr.write(`aontu: unknown option ${arg} (try --help)\n`)
|
|
@@ -3519,6 +3728,11 @@ function main(argv: string[]): void {
|
|
|
3519
3728
|
return finish(2)
|
|
3520
3729
|
}
|
|
3521
3730
|
|
|
3731
|
+
// The extensions ride with the capability from here on, so the three
|
|
3732
|
+
// entry shapes below (file, REPL, stdin) each get them by threading
|
|
3733
|
+
// the one value they already thread.
|
|
3734
|
+
trust = { ...trust, textExt }
|
|
3735
|
+
|
|
3522
3736
|
const file = files[0]
|
|
3523
3737
|
if (null != file) {
|
|
3524
3738
|
finish(runFile(file, mode, trust))
|
|
@@ -3547,6 +3761,6 @@ export {
|
|
|
3547
3761
|
runJsonSchema,
|
|
3548
3762
|
runMod,
|
|
3549
3763
|
runHash, runGet,
|
|
3550
|
-
runWhy, renderWhyText, runSet, runAgentsMd,
|
|
3764
|
+
runWhy, renderWhyText, runSet, runAgentsMd, runFmt,
|
|
3551
3765
|
watchChange, watchSignature, vetWaiter, deprecatedAt,
|
|
3552
3766
|
}
|
package/src/diff.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
import { includeOpts } from './utility'
|
|
2
3
|
|
|
3
4
|
// PATH-ADDRESSED DIFF (G7 phase 6,
|
|
4
5
|
// docs/capability-review/g7-machine-access.md): what changed, at which
|
|
@@ -61,6 +62,11 @@ export type DiffOptions = {
|
|
|
61
62
|
// extension -- but reading is enough.) A server passes
|
|
62
63
|
// `{include:'none'}`.
|
|
63
64
|
trust?: TrustOptions
|
|
65
|
+
|
|
66
|
+
// Extensions additionally read as text (the CLI's `--text-ext`).
|
|
67
|
+
// Rides beside `trust` because it is the other half of what an
|
|
68
|
+
// include may read.
|
|
69
|
+
textExt?: string[]
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
|
|
@@ -182,8 +188,7 @@ function evalSide(
|
|
|
182
188
|
export function diff(
|
|
183
189
|
leftSrc: string, rightSrc: string, opts?: DiffOptions): DiffReport {
|
|
184
190
|
const options = opts ?? {}
|
|
185
|
-
const aontu = new Aontu(
|
|
186
|
-
null == options.trust ? undefined : { trust: options.trust })
|
|
191
|
+
const aontu = new Aontu(includeOpts(options))
|
|
187
192
|
|
|
188
193
|
const l = evalSide(aontu, leftSrc, options.leftPath, options.at)
|
|
189
194
|
const r = evalSide(aontu, rightSrc, options.rightPath, options.at)
|