aval-adr 1.7.2 → 1.9.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 +45 -423
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -5,27 +5,31 @@ true now.**
|
|
|
5
5
|
|
|
6
6
|
Most ADR tooling helps you write and browse decision records. `aval` answers a
|
|
7
7
|
different question, the one a coding agent actually needs before it writes a
|
|
8
|
-
line of code:
|
|
8
|
+
line of code: what is decided here, now. The records are the evidence, the
|
|
9
|
+
current architecture is computed from them, and the answer is a verdict with an
|
|
10
|
+
exit code:
|
|
9
11
|
|
|
10
12
|
```console
|
|
11
13
|
$ aval resolve storage.object-store --scope homelab
|
|
12
|
-
active ADR-
|
|
14
|
+
active ADR-0002 Ceph RGW
|
|
15
|
+
scope: homelab
|
|
13
16
|
$ echo $?
|
|
14
17
|
0
|
|
15
18
|
```
|
|
16
19
|
|
|
17
|
-
and, just as importantly, refuses to guess when it should not:
|
|
20
|
+
and, just as importantly, it refuses to guess when it should not:
|
|
18
21
|
|
|
19
22
|
```console
|
|
20
23
|
$ aval resolve data.realtime-processing
|
|
21
|
-
undecided no accepted decision for
|
|
24
|
+
undecided no accepted decision for data.realtime-processing
|
|
22
25
|
$ echo $?
|
|
23
26
|
4
|
|
24
27
|
|
|
25
28
|
$ aval resolve api.gateway
|
|
26
|
-
contradiction
|
|
27
|
-
ADR-
|
|
28
|
-
ADR-
|
|
29
|
+
contradiction 2 heads for api.gateway; the corpus disagrees with itself
|
|
30
|
+
ADR-0001 introduced in 8d68d23f
|
|
31
|
+
ADR-0002 introduced in 7941cf9b
|
|
32
|
+
Stop. Do not pick one; write the ADR that replaces both.
|
|
29
33
|
$ echo $?
|
|
30
34
|
5
|
|
31
35
|
```
|
|
@@ -33,435 +37,53 @@ $ echo $?
|
|
|
33
37
|
An agent branches on a number. It never reads three documents and reasons its
|
|
34
38
|
way to a plausible wrong answer.
|
|
35
39
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
ADRs are not the current architecture. **ADRs are the evidence from which the
|
|
39
|
-
current architecture is computed.**
|
|
40
|
-
|
|
41
|
-
- A **decision key** names one architectural question, from a controlled
|
|
42
|
-
vocabulary. `storage.object-store`, not "the storage doc".
|
|
43
|
-
- An **ADR** is an event that changes one or more keys.
|
|
44
|
-
- **Superseded is never written down.** It is derived from supersession edges.
|
|
45
|
-
A hand-written status is copied state, and copied state drifts.
|
|
46
|
-
- **`HEADS.md` is a projection**, regenerated and byte-compared in CI. Nobody
|
|
47
|
-
edits it.
|
|
48
|
-
|
|
49
|
-
## Typed non-answers
|
|
50
|
-
|
|
51
|
-
The design is mostly about what happens when there is no clean answer, because
|
|
52
|
-
that is where an agent left to its own judgment does damage.
|
|
53
|
-
|
|
54
|
-
| Exit | Verdict | Means |
|
|
55
|
-
|---|---|---|
|
|
56
|
-
| 0 | `active` | here is the decision |
|
|
57
|
-
| 4 | `undecided` | the key exists, nothing decided it |
|
|
58
|
-
| 5 | `contradiction` | competing heads. Stop, do not pick one. |
|
|
59
|
-
| 6 | `retired` | an ADR deliberately retired this key |
|
|
60
|
-
| 7 | `unknown` | no such key or scope, or a key not decided on that axis |
|
|
61
|
-
|
|
62
|
-
## Which decisions bear on this change
|
|
63
|
-
|
|
64
|
-
`resolve` needs a key, which is a chicken-and-egg problem at the start of a
|
|
65
|
-
task: the way to learn that a decision governs the file you are about to edit
|
|
66
|
-
is to already know its name. The alternative is `HEADS.md`, which is every
|
|
67
|
-
decision the repository has ever made.
|
|
68
|
-
|
|
69
|
-
`aval relevant` ranks the vocabulary against what you are about to touch:
|
|
70
|
-
|
|
71
|
-
```console
|
|
72
|
-
$ aval relevant --path crates/aval/src/mcp.rs --text "release a new version by pushing a tag"
|
|
73
|
-
advisory A ranking is a suggestion: it resolves nothing. Only `aval resolve <key>` answers.
|
|
74
|
-
|
|
75
|
-
14.2104 release.trigger active ADR-0008 A pushed version tag, never a merge
|
|
76
|
-
11.5524 release.version-scheme active ADR-0012 Semantic Versioning 2.0.0
|
|
77
|
-
4.9182 ci.test-gate-stage active ADR-0009 pre-commit
|
|
78
|
-
4.7105 forge.primary undecided no accepted decision for forge.primary
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**This is retrieval, not resolution**, and the output says so on every run. The
|
|
82
|
-
order is a guess about attention. What is not a guess is the verdict on each
|
|
83
|
-
row: it comes from the same `resolve` the rest of the tool runs, which is what
|
|
84
|
-
makes the `undecided` row the interesting one — nobody decided it, and an agent
|
|
85
|
-
that quietly fills the gap is doing the thing the corpus exists to prevent.
|
|
86
|
-
|
|
87
|
-
### The signals
|
|
88
|
-
|
|
89
|
-
| Signal | Weight | What it reads |
|
|
90
|
-
|---|---|---|
|
|
91
|
-
| text | 1.0 | BM25 over `--text`, against one document per key: the key's name and description, then the title, choice, reason and body of whatever decides it, and superseded titles at a third of the weight |
|
|
92
|
-
| path | 0.6 | the same BM25 over the words in each `--path` — directories and file stems, extensions dropped |
|
|
93
|
-
| mention | 2.0 per path, three at most | a record's body names that path itself: backticked, as a markdown link, or as a glob matched by its literal directory |
|
|
94
|
-
| co-change | 0.5 per path, three at most | `git log` says the commits that wrote the record also touched that path |
|
|
95
|
-
|
|
96
|
-
A mention outranks any amount of word overlap, because it is the one signal an
|
|
97
|
-
author put there on purpose. Co-change is weakest and capped hardest: a record
|
|
98
|
-
and a config file in one commit may share nothing but a Tuesday. The tokenizer
|
|
99
|
-
is frozen — lowercase, ASCII-fold, split on every non-alphanumeric, drop stop
|
|
100
|
-
words and one-character tokens, light suffix stemming — and the conformance
|
|
101
|
-
battery compares the resulting order byte for byte, so changing it is a change
|
|
102
|
-
somebody reviews rather than a ranking that quietly moved.
|
|
103
|
-
|
|
104
|
-
`--changed` adds what git reports modified, staged and untracked, which is the
|
|
105
|
-
whole query for "what does this branch touch". `--scope S` ranks only the keys
|
|
106
|
-
answerable at S and resolves each one there. `--top N` defaults to 5, and a row
|
|
107
|
-
must also score a fifth of the top row to be printed, so a query with one good
|
|
108
|
-
answer reports one. Rules (below) that match the same words are listed after
|
|
109
|
-
the keys, clearly apart.
|
|
110
|
-
|
|
111
|
-
Exit is **always 0**, usage errors aside. A ranking has no verdict to report,
|
|
112
|
-
and "I ranked and found little" must not share a code with "I could not look".
|
|
113
|
-
|
|
114
|
-
### For a router
|
|
115
|
-
|
|
116
|
-
`--json` carries a `dependencies` array — the same keys, compacted:
|
|
117
|
-
|
|
118
|
-
```json
|
|
119
|
-
{"dependencies":[
|
|
120
|
-
{"key":"release.trigger","state":"active","exit":0,"adr":"ADR-0008","unresolved":false},
|
|
121
|
-
{"key":"forge.primary","state":"undecided","exit":4,"unresolved":true}]}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
One row per ranked key, in ranked order, and `unresolved` is true for exactly
|
|
125
|
-
`undecided` and `contradiction`. That is what a dispatcher reads —
|
|
126
|
-
[relais](https://github.com/fredericrous/relais) routes on unresolved decision
|
|
127
|
-
dependencies — so a change whose decisions are not settled goes to a person
|
|
128
|
-
instead of to a worker. The full verdict, with the choice and the reason, is in
|
|
129
|
-
`keys`; `why` on each row says which signal earned it its place, and
|
|
130
|
-
`decided_elsewhere` names the scopes that do decide a key the asked scope does
|
|
131
|
-
not.
|
|
132
|
-
|
|
133
|
-
## In front of an agent
|
|
134
|
-
|
|
135
|
-
A corpus that resolves is half the point. The other half is that whatever is
|
|
136
|
-
about to write code starts from what was decided:
|
|
137
|
-
|
|
138
|
-
```console
|
|
139
|
-
$ aval hook install
|
|
140
|
-
wrote .claude/hooks/aval-heads.sh (created)
|
|
141
|
-
wrote .claude/settings.json (merged)
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
It writes a session-start hook that prints the current heads, and merges one
|
|
145
|
-
entry into `.claude/settings.json` without disturbing what else is there. The
|
|
146
|
-
hook is **silent** when `aval` is not installed, so committing it cannot fail a
|
|
147
|
-
colleague's session, and silent when there is no corpus to report.
|
|
148
|
-
|
|
149
|
-
Repo-specific caveats go in `.claude/aval-hook.local.md`. The hook appends that
|
|
150
|
-
file; installing again never touches it. `--check` is the drift detector for
|
|
151
|
-
CI: exit 0 wired, exit 1 stale.
|
|
152
|
-
|
|
153
|
-
The script's second line names the aval that wrote it:
|
|
40
|
+
## Install
|
|
154
41
|
|
|
155
42
|
```sh
|
|
156
|
-
|
|
157
|
-
# aval-hook: written by aval 1.5.0
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
A script from a **newer** aval is not stale, and an older aval leaves it alone
|
|
161
|
-
rather than downgrading it, so a workstation that upgrades first does not
|
|
162
|
-
redden a CI job pinned to the release before. Both modes also print a `note`
|
|
163
|
-
for every `AVAL_VERSION:` pin under `.github/workflows` or `.forgejo/workflows`
|
|
164
|
-
that is behind or ahead of the running aval — advisory, never an exit code,
|
|
165
|
-
never an edit to the workflow.
|
|
166
|
-
|
|
167
|
-
Where the repository vendors packs, the hook also asks whether they are still
|
|
168
|
-
what their sources publish — at most once an hour per repository, under a
|
|
169
|
-
five-second budget that kills the remote call rather than waiting on it, and
|
|
170
|
-
speaking only when a pack is behind or edited:
|
|
171
|
-
|
|
172
|
-
```
|
|
173
|
-
VENDORED DECISIONS ARE BEHIND THEIR SOURCE. The fleet has decided something
|
|
174
|
-
this repository has not adopted yet, so the heads below may be superseded:
|
|
175
|
-
behind fleet has 1bb600e, main now names e73638d
|
|
176
|
-
|
|
177
|
-
1 pack(s) behind. Run `aval add <source>` for each, read the diff, then `aval heads --write`.
|
|
43
|
+
curl -fsSL https://raw.githubusercontent.com/fredericrous/aval/main/install/install.sh | sh
|
|
178
44
|
```
|
|
179
45
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
(default `~/.cache/aval`), never in the repository. `aval add --check` by hand
|
|
184
|
-
always answers in full.
|
|
185
|
-
|
|
186
|
-
The hook pushes; **`aval mcp` lets an agent pull** — the same answers as native
|
|
187
|
-
tools, asked at the moment the question comes up rather than only at the top of
|
|
188
|
-
a session:
|
|
189
|
-
|
|
190
|
-
```console
|
|
191
|
-
$ claude mcp add aval -- aval mcp
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
Nine read-only tools: `aval_resolve`, `aval_relevant`, `aval_keys`,
|
|
195
|
-
`aval_heads`, `aval_show`, `aval_history`, `aval_rules`, `aval_rule` and
|
|
196
|
-
`aval_repos`. They resolve through the same graph the CLI does and return the
|
|
197
|
-
same bytes `--json` would, so the two surfaces cannot drift apart.
|
|
198
|
-
|
|
199
|
-
`aval_relevant` is the one whose result is **not** an answer, and its
|
|
200
|
-
description says so where a model will read it: the ranking is advisory, what
|
|
201
|
-
is authoritative is the verdict beside each key, and an `undecided` row is the
|
|
202
|
-
reason to have called it.
|
|
203
|
-
|
|
204
|
-
The distinction that matters: **a verdict is not an error**. `undecided`,
|
|
205
|
-
`retired`, `unknown` and `contradiction` all come back as ordinary results with
|
|
206
|
-
`isError: false`, because each is an answer. Reporting `contradiction` as a tool
|
|
207
|
-
failure would teach an agent to retry or work around the one verdict that means
|
|
208
|
-
*stop and ask a human* — so the flag is reserved for a corpus that would not
|
|
209
|
-
load, or a name it does not carry.
|
|
210
|
-
|
|
211
|
-
Nothing on that surface writes. Resolving answers a question; deciding is not
|
|
212
|
-
something to do on an agent's behalf.
|
|
213
|
-
|
|
214
|
-
**From the parent of all your projects**, where there is no corpus above and
|
|
215
|
-
several below, the same server is a **workspace**: every tool takes `repo` to name one.
|
|
216
|
-
`resolve` and `history` answer for all of them when it is omitted — a map
|
|
217
|
-
keyed by name, which is the cross-repository question in a single call —
|
|
218
|
-
while `keys`, `heads` and `show` ask for a name, because every repository's
|
|
219
|
-
heads at once is a lot of context to fetch by forgetting an argument.
|
|
220
|
-
`aval repos` says what was found, and `--all-repos` renders the same map from
|
|
221
|
-
the shell:
|
|
222
|
-
|
|
223
|
-
```console
|
|
224
|
-
$ aval resolve stack.sql-layer --scope effect-stack --all-repos
|
|
225
|
-
== decisions ==
|
|
226
|
-
active ADR-0002 @effect/sql
|
|
227
|
-
|
|
228
|
-
== homelab ==
|
|
229
|
-
unknown no such key `stack.sql-layer`
|
|
230
|
-
…
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
A linked worktree is detected and left out when its parent is also there, so
|
|
234
|
-
one corpus never answers twice; it stays addressable by name.
|
|
235
|
-
|
|
236
|
-
Codes `1`, `2` and `3` mean the tool failed, was misused, or could not read the
|
|
237
|
-
corpus. They never overlap a verdict, so "I could not look" is never mistaken
|
|
238
|
-
for "I looked and found nothing".
|
|
239
|
-
|
|
240
|
-
## Scopes
|
|
46
|
+
Or `brew install fredericrous/tap/aval`, `cargo install aval`, or
|
|
47
|
+
`npx aval-adr`. Windows, version pins and gating a repository with one line of
|
|
48
|
+
[amont](https://github.com/fredericrous/amont): [installing and gating](docs/install.md).
|
|
241
49
|
|
|
242
|
-
|
|
243
|
-
silently change another, so scoped divergence and in-place replacement are
|
|
244
|
-
different edges:
|
|
245
|
-
|
|
246
|
-
```yaml
|
|
247
|
-
- key: cni.routing-mode
|
|
248
|
-
scope: cloud
|
|
249
|
-
choice: VXLAN tunnel + WireGuard
|
|
250
|
-
first: true
|
|
251
|
-
overrides: ADR-0006 # the global native-routing head STAYS a head
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## Commands
|
|
50
|
+
## Use
|
|
255
51
|
|
|
256
52
|
| | |
|
|
257
53
|
|---|---|
|
|
258
54
|
| `aval resolve <key> [--scope S]` | the authoritative lookup |
|
|
259
|
-
| `aval relevant [--path P]… [--text W] [--changed]
|
|
55
|
+
| `aval relevant [--path P]… [--text W] [--changed]` | which keys bear on what you are about to touch, ranked, each with its verdict. Advisory: it resolves nothing |
|
|
56
|
+
| `aval keys` | the vocabulary: every key, and where it is decided |
|
|
260
57
|
| `aval check` | every invariant; what the git hook and CI run |
|
|
261
|
-
| `aval heads [--write\|--check]` | the projection |
|
|
262
|
-
| `aval
|
|
263
|
-
| `aval
|
|
264
|
-
| `aval
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
---
|
|
285
|
-
|
|
286
|
-
## names.reveal-intent [constraint]
|
|
287
|
-
|
|
288
|
-
Names reveal intention: an identifier says what it holds, in the vocabulary
|
|
289
|
-
of the domain, and a reader never decodes an abbreviation.
|
|
290
|
-
|
|
291
|
-
The body is the translation — what this means here, and what it does not cover.
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
A rule has no authority of its own: it is active exactly while the record that
|
|
295
|
-
adopts it still holds, so superseding that record retires its rules with it.
|
|
296
|
-
There are no per-rule supersession edges, because the graph already tracks the
|
|
297
|
-
record — a rule whose meaning changes gets a new id.
|
|
298
|
-
|
|
299
|
-
```console
|
|
300
|
-
$ aval rules
|
|
301
|
-
constraint names.reveal-intent Names reveal intention: an identifier says what it holds …
|
|
302
|
-
heuristic functions.few-arguments A function takes no more inputs than it uses …
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
A `constraint` is followed and a review blocks on it; the session hook prints
|
|
306
|
-
every active one. A `heuristic` is followed unless a reviewer argues why not,
|
|
307
|
-
in that place, and is fetched on demand. Precedence, which the hook also
|
|
308
|
-
prints: the decision at the scope asked, then the default-scope decision, then
|
|
309
|
-
these rules, then the book a rule cites — as explanation only. Remembered
|
|
310
|
-
advice from that book does not outrank a rule here.
|
|
311
|
-
|
|
312
|
-
### Traits: rules that only apply where the thing exists
|
|
313
|
-
|
|
314
|
-
Rules about command-line programs mean nothing in a repository that ships none.
|
|
315
|
-
A rule file says what it is about, the fleet corpus declares the vocabulary,
|
|
316
|
-
and each repository declares which of its parts have which traits:
|
|
317
|
-
|
|
318
|
-
```yaml
|
|
319
|
-
# a rule file's frontmatter
|
|
320
|
-
applies: [cli]
|
|
321
|
-
|
|
322
|
-
# the producer's .adr.yaml — travels in its pack
|
|
323
|
-
traits: [cli, ui]
|
|
324
|
-
|
|
325
|
-
# a consumer's .adr.yaml — its own, never in a pack
|
|
326
|
-
areas:
|
|
327
|
-
"web/**": [ui]
|
|
328
|
-
"cmd/**": [cli]
|
|
329
|
-
disclaims:
|
|
330
|
-
"tools/**": [cli]
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
With `areas` declared, `aval rules` and the hook list only rules that apply to
|
|
334
|
-
the traits declared, and **say what they hid** — on stderr, in `omitted` on
|
|
335
|
-
every `--json` and tool payload, and in one hook line printed even when nothing
|
|
336
|
-
is hidden. A hidden rule is still active: `aval rule <id>` explains it and
|
|
337
|
-
`--all-traits` lists it. Without `areas`, nothing changes.
|
|
338
|
-
|
|
339
|
-
`aval traits --detect` proposes areas from the files git tracks, and `aval
|
|
340
|
-
traits --check` reports what the declaration misses, locally per package, plus
|
|
341
|
-
any glob that matches nothing. Detection is advisory and leans toward
|
|
342
|
-
reporting: a false positive costs one `disclaims` line, while a miss would hide
|
|
343
|
-
a constraint. It never filters anything on its own.
|
|
344
|
-
|
|
345
|
-
## Sharing one decision across repositories
|
|
346
|
-
|
|
347
|
-
A decision made once should be readable everywhere it applies. `aval pack`
|
|
348
|
-
publishes a corpus's declarations; `aval add` vendors them into another
|
|
349
|
-
repository, which then resolves them as if they were its own.
|
|
350
|
-
|
|
351
|
-
```console
|
|
352
|
-
$ aval add github:acme/decisions
|
|
353
|
-
$ aval resolve stack.sql-layer --scope effect-stack
|
|
354
|
-
active decisions:ADR-0002 @effect/sql
|
|
355
|
-
vendored: from the `decisions` pack; change it there, not here
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
A consumer needs no corpus of its own — a registry with `packs:` and no `dir:`
|
|
359
|
-
is enough. Transport is git and only git, so a private repository and a forge
|
|
360
|
-
behind a client certificate both work with your own credentials and no token
|
|
361
|
-
issued to this tool.
|
|
362
|
-
|
|
363
|
-
The vendored file is `.adr/packs/<name>.pack`, with the extension the producer's
|
|
364
|
-
own published file carries and no formatter claims. A `.adr/packs/<name>.yaml`
|
|
365
|
-
written before 1.3 still loads, and the next `aval add` moves it and rewrites
|
|
366
|
-
its registry line. What makes it still the published pack is what it
|
|
367
|
-
*declares*, not its bytes — so a formatter that reformatted it has changed
|
|
368
|
-
nothing, while a hand-edited decision is reported by `aval add --check` as
|
|
369
|
-
`edited`.
|
|
370
|
-
|
|
371
|
-
What a consumer cannot do is quietly disagree. A local record deciding a slot a
|
|
372
|
-
pack already decides is two heads for one slot, which is exit 5 — the invariant
|
|
373
|
-
the model already had, and the reason vendoring is worth anything. What it
|
|
374
|
-
*can* do is answer the same key at a scope of its own: one repository may hold
|
|
375
|
-
a SQL layer in the browser and another in the app server without either being
|
|
376
|
-
a disagreement with the fleet's answer at the fleet's scope.
|
|
377
|
-
|
|
378
|
-
Nothing in a pack is ever executed, so there is no trust prompt to match
|
|
379
|
-
`amont trust`. The review gate is the pull request that adds the file.
|
|
380
|
-
|
|
381
|
-
A pack goes stale silently otherwise, so there are two ways to be told. The
|
|
382
|
-
session hook asks (see [In front of an agent](#in-front-of-an-agent)). And a
|
|
383
|
-
consumer's CI can ask, as an advisory job, where its runner holds a credential
|
|
384
|
-
that can read the source — a private corpus is reachable from a private
|
|
385
|
-
consumer's runner with a deploy key, not from a public one's without:
|
|
386
|
-
|
|
387
|
-
```yaml
|
|
388
|
-
packs:
|
|
389
|
-
name: vendored decisions are current (advisory, non-blocking)
|
|
390
|
-
runs-on: ubuntu-latest
|
|
391
|
-
continue-on-error: true
|
|
392
|
-
steps:
|
|
393
|
-
- uses: actions/checkout@v4
|
|
394
|
-
- uses: webfactory/ssh-agent@v0.9.0 # or however the runner reaches the source
|
|
395
|
-
with:
|
|
396
|
-
ssh-private-key: ${{ secrets.DECISIONS_READ_KEY }}
|
|
397
|
-
- run: |
|
|
398
|
-
if ! aval add --check --quiet --budget 30 > packs.txt; then
|
|
399
|
-
cat packs.txt
|
|
400
|
-
echo "::warning::vendored decisions are behind or edited — run aval add"
|
|
401
|
-
fi
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
Non-blocking for the same reason a dependency advisory is: a decision that
|
|
405
|
-
changed upstream is information about the fleet, not a defect in the change
|
|
406
|
-
under review.
|
|
407
|
-
|
|
408
|
-
That "inert" scopes to execution. A pack's text does reach an agent's context,
|
|
409
|
-
so the hook and the MCP tools both say that a record's wording is data rather
|
|
410
|
-
than instruction, and a value carrying a control character or a bidi override
|
|
411
|
-
is refused — the reviewer and the model must see the same bytes.
|
|
412
|
-
|
|
413
|
-
## Status
|
|
414
|
-
|
|
415
|
-
Published, and in use: the resolver, the invariants, the projection, the CLI,
|
|
416
|
-
vendoring and the tool surface all work, against real corpora rather than
|
|
417
|
-
fixtures. [`SEMANTICS.md`](SEMANTICS.md) is normative and is the place to
|
|
418
|
-
start if you intend to write records against this.
|
|
419
|
-
|
|
420
|
-
**1.0.** The verdicts, their exit codes, the note strings, the `--json` field
|
|
421
|
-
names and the frontmatter dialect are stable; §15 of [`SEMANTICS.md`](SEMANTICS.md)
|
|
422
|
-
says exactly what that covers, and what it deliberately does not.
|
|
423
|
-
|
|
424
|
-
## Install
|
|
425
|
-
|
|
426
|
-
curl -fsSL https://raw.githubusercontent.com/fredericrous/aval/main/install/install.sh | sh
|
|
427
|
-
|
|
428
|
-
Pin a version or move the destination with `AVAL_VERSION` and `AVAL_BIN_DIR`.
|
|
429
|
-
Windows: `irm https://raw.githubusercontent.com/fredericrous/aval/main/install/install.ps1 | iex`.
|
|
430
|
-
|
|
431
|
-
Also `cargo install aval`, and `npx aval-adr` — the npm package carries the
|
|
432
|
-
suffix because plain `aval` was taken in 2016 by an unrelated property
|
|
433
|
-
validator; the binary it installs is still `aval`.
|
|
434
|
-
|
|
435
|
-
Nothing is gated by installing. To gate a repository, one committed line in its
|
|
436
|
-
[`amont.conf`](https://github.com/fredericrous/amont):
|
|
437
|
-
|
|
438
|
-
pre-commit adr *+.adr.yaml block aval check
|
|
439
|
-
|
|
440
|
-
Records live under `dir`, and a specification that carries decisions can be
|
|
441
|
-
named where it is rather than moved:
|
|
442
|
-
|
|
443
|
-
```yaml
|
|
444
|
-
dir: docs/adr
|
|
445
|
-
sources:
|
|
446
|
-
- docs/spec-change-proposals.md
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
Literal paths, not patterns: a listed file that goes missing is an error, where
|
|
450
|
-
a pattern that stops matching would drop the record and let a superseded
|
|
451
|
-
decision come back as the current one.
|
|
452
|
-
|
|
453
|
-
The `+` keeps it inert in any repository without a `.adr.yaml`, and a missing
|
|
454
|
-
binary is reported as a gap rather than blocking a commit.
|
|
58
|
+
| `aval heads [--write\|--check]` | the `HEADS.md` projection |
|
|
59
|
+
| `aval hook install` | put the heads in front of an agent at session start |
|
|
60
|
+
| `aval mcp` | the same answers as read-only MCP tools |
|
|
61
|
+
| `aval add <source>` | vendor another repository's decisions |
|
|
62
|
+
|
|
63
|
+
Every command, option and exit code: [commands](docs/commands.md).
|
|
64
|
+
|
|
65
|
+
## Documentation
|
|
66
|
+
|
|
67
|
+
- [Installing and gating](docs/install.md)
|
|
68
|
+
- [The model](docs/concepts.md) — keys, records, scopes, and the typed non-answers
|
|
69
|
+
- [Commands](docs/commands.md)
|
|
70
|
+
- [Which decisions bear on this change](docs/relevance.md) — `aval relevant` and its signals
|
|
71
|
+
- [In front of an agent](docs/agents.md) — the session hook and the MCP server
|
|
72
|
+
- [Rules and traits](docs/rules-and-traits.md) — what a decision does not settle
|
|
73
|
+
- [Sharing one decision across repositories](docs/sharing.md) — packs and vendoring
|
|
74
|
+
- [`SEMANTICS.md`](SEMANTICS.md) — the normative specification, and the place to
|
|
75
|
+
start if you intend to write records against this
|
|
76
|
+
- [`CHANGELOG.md`](CHANGELOG.md)
|
|
77
|
+
|
|
78
|
+
The verdicts, their exit codes, the note strings, the `--json` field names and
|
|
79
|
+
the frontmatter dialect are stable since 1.0; §15 of `SEMANTICS.md` says exactly
|
|
80
|
+
what that covers.
|
|
455
81
|
|
|
456
82
|
## Building
|
|
457
83
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
No external dependencies, by design and enforced in CI. `aval` runs on the
|
|
462
|
-
pre-commit path, so it pulls in nothing. `make check` uses rustup's shim when
|
|
463
|
-
one is present, because a Homebrew cargo earlier on `PATH` ignores the
|
|
464
|
-
toolchain pin and would lint with a different clippy than CI.
|
|
84
|
+
`make check` runs what CI runs. No external dependencies, by design and
|
|
85
|
+
enforced in CI: `aval` runs on the pre-commit path, so it pulls in nothing.
|
|
86
|
+
More in [building](docs/building.md).
|
|
465
87
|
|
|
466
88
|
## License
|
|
467
89
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aval-adr",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Ask what the current architecture decision is, and get a typed answer",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adr",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"node": ">=18"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@aval-adr/darwin-arm64": "1.
|
|
36
|
-
"@aval-adr/darwin-x64": "1.
|
|
37
|
-
"@aval-adr/linux-arm64-gnu": "1.
|
|
38
|
-
"@aval-adr/linux-x64-gnu": "1.
|
|
39
|
-
"@aval-adr/linux-x64-musl": "1.
|
|
40
|
-
"@aval-adr/win32-x64": "1.
|
|
35
|
+
"@aval-adr/darwin-arm64": "1.9.0",
|
|
36
|
+
"@aval-adr/darwin-x64": "1.9.0",
|
|
37
|
+
"@aval-adr/linux-arm64-gnu": "1.9.0",
|
|
38
|
+
"@aval-adr/linux-x64-gnu": "1.9.0",
|
|
39
|
+
"@aval-adr/linux-x64-musl": "1.9.0",
|
|
40
|
+
"@aval-adr/win32-x64": "1.9.0"
|
|
41
41
|
}
|
|
42
42
|
}
|