@assuredloop/cli 0.2.4 → 2.0.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 CHANGED
@@ -10,8 +10,10 @@ AssuredLoop keeps an AI focused on one change. It holds together:
10
10
  - the code, the tests and the documents that came with the change.
11
11
 
12
12
  Its command, `al`, keeps these records, checks them, and says what to do
13
- next. It refuses only in its own `al conclude`. Every other check is a hint;
14
- a project that wants blocks turns on `al check --strict` in its CI.
13
+ next. It refuses only in its own `al conclude`. `al check` gives the AI
14
+ review hints, and the review decides. Only a few exact rules are `not ok`
15
+ (see "Hints, and the rules that block"). A project that wants them to block
16
+ turns on `al check --strict` in its CI.
15
17
 
16
18
  AssuredLoop is not a lifecycle tool. It has no phases, no roles, no task
17
19
  states and no approvals beyond the owner's sign-off. How and when you design,
@@ -95,7 +97,15 @@ Search at level 2 needs the library `@huggingface/transformers` 4.3.1 (see
95
97
  - **Bindings.** When a link is first indexed, `al index` records the hash of
96
98
  both ends. Ordinary indexing never moves a binding. When the target
97
99
  changes, the source gets a hint until someone checks it and runs
98
- `al index --align <ID>`.
100
+ `al index --align <ID>`. When the source paragraph's own text changes,
101
+ it gets the hint `holder-changed` until it is aligned in the same way.
102
+ This hint covers the links that a paragraph of a spec doc, an ADR or an
103
+ open change spec still declares, to a target that still resolves, in the
104
+ records of the open requests. A requirement is out, because a changed
105
+ requirement needs a new sign-off. An output file is out. When no current
106
+ marker declares a link any more, `al index` removes its binding, in the
107
+ open and the archived records, and names it in its output. Git history
108
+ keeps it.
99
109
  - **Consolidation is by hand.** A PR copies a change paragraph into `specs/`
100
110
  word for word, and records its disposition: `incorporated`, `removed`,
101
111
  `superseded` or `abandoned`. No command writes the spec.
@@ -109,16 +119,16 @@ The user picks the path. The skill has the steps of each path.
109
119
  | 0. Fix | no promise changes: it restores what the spec says, or claims a typo in any paragraph | none | no | the PR merges |
110
120
  | 1. Small promise amend | one or a few promise paragraphs change | a short request; the PR edits `specs/` directly | yes | the PR merges |
111
121
  | 1d. Small design correction | names or wording of design paragraphs only | a short request with a one-line source note; the PR edits `specs/` directly | no | the PR merges |
112
- | 2. Big change | a real design, one or a few PRs | a request and a change spec | if a promise kind changes | the close rule holds |
122
+ | 2. Big change | a real design, one or a few PRs | a request and a change spec | if a promise kind changes, or the request has an R<n> | the close rule holds |
113
123
  | 3. Epic | big, in parts over time | as 2, with optional task references | as 2 | the close rule holds |
114
124
  | S. Spike | not yet clear what to build | a request with an organized question; the findings open with the answer | yes, on the question | the answer names the question version |
115
125
 
116
126
  Every PR states its path and its claim in a commit message, for example
117
127
  `Tier: 0 — typo in INV-3` or `Tier: 2 — invoice-payments, part 1`. `al check`
118
128
  reads the newest `Tier:` line in the branch's commits, and checks the claim
119
- against the kinds and the changed paragraphs: "path 0, but a rule paragraph
120
- is changed with no typo claim" is `not ok`. It checks the labels, not their
121
- meaning.
129
+ against the kinds and the changed paragraphs. Where they disagree, it gives
130
+ a hint, for example "path 0, but a rule paragraph is changed with no typo
131
+ claim". It checks the labels, not their meaning.
122
132
 
123
133
  **The close rule.** `al conclude <name>` refuses while a promise change has
124
134
  no sign-off, a spike's current question is not signed, or a change paragraph
@@ -137,10 +147,14 @@ with a baseline effect has no valid disposition for its current version.
137
147
  - `al spec`: every doc's IDs, display numbers and kinds.
138
148
  `al spec --add-ids <file> [--prefix <P>]`: give each unmarked paragraph the
139
149
  next free ID.
140
- - `al index`: write the derived facts and the new bindings, and list the AI
141
- hints to refresh. `al index --align <ID>` moves the bindings of one
142
- paragraph to its targets' current text, on purpose.
143
- - `al check [--strict]`: the checks of this branch against its base.
150
+ - `al index`: write the derived facts and the new bindings, remove the
151
+ bindings of links that no marker declares, and list the AI hints to
152
+ refresh. `al index --align <ID>` moves the bindings of one paragraph to its
153
+ targets' current text and to its own, on purpose.
154
+ - `al check [--strict] [--issues <folder>]`: the checks of this branch
155
+ against its base. With `--issues <folder>`, it also checks the work items
156
+ that the caller saved in the folder (see "Hints from the linked work
157
+ items").
144
158
  - `al context [<name> | <ID>]`: where the project, a request or a paragraph
145
159
  stands. `--diff <range> --for review` is the review view. `--audit` gives a
146
160
  request's whole trace; `--at <commit>` any v4 state.
@@ -173,18 +187,208 @@ results: .assuredloop/results # where result files are
173
187
  outputs: # in the central repo: its output repos
174
188
  - {name: invoicer-web, path: ../invoicer-web, commit: <sha>}
175
189
  central: {path: ../invoicer} # in an output repo: the central repo
190
+ phrases: # text for the review to look at; each match is a hint
191
+ - the requester # a phrase: ignoring case, across a line break too
192
+ - {pattern: "Avery (asked|wants)"} # a JavaScript regular expression
176
193
  ```
177
194
 
195
+ `al check` gives the hint `phrase` for each match of `phrases:` in the spec
196
+ docs, the ADRs and the Markdown files of the open requests, but their
197
+ `origin/` folders; with `--issues <folder>`, in the work items too. The list is empty by
198
+ default. A bad entry or an invalid pattern stops `al check` with exit 2.
199
+
178
200
  `.assuredloop/schema.yaml` holds the kinds and the link words, with a version.
179
201
  `al spec --add-ids` writes both files when they are absent.
180
202
 
181
203
  ADRs are `specs/adr/NNNN-<slug>.md`, with `Status: proposed`, `accepted` or
182
- `superseded` on the first line. The heading's marker holds `ADR-<n>` and its
183
- `decides:`, `source:` and `supersedes:` links. The text of an accepted ADR
184
- does not change; a new ADR supersedes it. You may add links to the marker of
185
- an accepted ADR when nothing else changes: keep its ID, its kind and each old
186
- link. `al check` gives a hint for a proposed or accepted ADR with no
187
- `source:` or no `decides:`; under `--strict`, it is `not ok`.
204
+ `superseded by ADR-<n>` on the first line. A status line may name several
205
+ ADRs that supersede it: `superseded by ADR-11, ADR-12` or
206
+ `ADR-11 and ADR-12`. The heading's marker holds `ADR-<n>` and its
207
+ `decides:`, `source:` and `supersedes:` links.
208
+
209
+ - Any text may be rewritten in other words when its meaning stays the same,
210
+ an accepted or superseded ADR's text too. A new ADR that supersedes it is
211
+ needed only when the decision changes, in whole or in part.
212
+ - The review judges whether a rewrite keeps the decision; `al` does not. So
213
+ a changed ADR text, a changed or removed link in its marker, or any other
214
+ change of its marker line, gives a hint: check that its decision is
215
+ unchanged. Links added to the marker of an accepted ADR, with nothing else
216
+ changed, give no finding.
217
+ - `al check` gives a hint for a marker line of an ADR that holds anything
218
+ other than its ID, its kind and links: an unknown token, a second `<!--`
219
+ or `-->`, or an empty item in a list of links. A line that starts like a
220
+ marker, but that `al` does not read as one, gets the same hint.
221
+ - `al check` gives a hint for an ADR whose first line is not
222
+ `Status: proposed`, `Status: accepted` or `Status: superseded by ADR-<n>`,
223
+ with one ADR or a list of them.
224
+ - After a rewrite, `al index --align <ID>` moves the bindings of paragraph
225
+ `<ID>` to the ADR's new text, for an accepted or a superseded ADR.
226
+ - `al check` gives a hint for a proposed or accepted ADR with no `source:`
227
+ or no `decides:`.
228
+ - A current place that names a superseded ADR also names the ADR that
229
+ supersedes it, in the same marker or the same paragraph. Current places
230
+ are the spec docs, the open requests and the proposed and accepted ADRs.
231
+ The chain itself and dated records are exempt: an ADR that supersedes it,
232
+ a superseded ADR, an ADR's `## History`, a request's `## Decisions`, the
233
+ `origin/` files and the archived requests. Else `al check` gives
234
+ `not ok superseded-cited`.
235
+ - A superseded ADR's status line names the ADR that supersedes it, and that
236
+ ADR's marker has `supersedes:` for it. Else `al check` gives
237
+ `not ok superseded-status`.
238
+
239
+ ## Hints from the linked work items
240
+
241
+ A project may keep its work items in any tracker, or in none. `al` never
242
+ contacts a tracker. The caller saves the items in a folder, and
243
+ `al check --issues <folder>` checks the items that the `tasks:` of the open
244
+ requests name. Without `--issues`, `al check` reads no item. A task's `ref`
245
+ names an item as `#n`, `owner/repo#n`, `<name>#n` for an output repo that
246
+ config names, an issue URL on any host, or the item's own `ref`, for
247
+ example `PROJ-12`. Each finding is a hint:
248
+
249
+ - `issue-missing`: a task names an item that is not in the folder;
250
+ - `issue-superseded`: the item's text names a superseded ADR without the ADR
251
+ that supersedes it, by the rule of `superseded-cited`;
252
+ - `issue-bare-ref`: a bare `#n` in an item of another repository, which
253
+ GitHub reads as that repository's issue. The request's own repository
254
+ comes from `git remote get-url origin`;
255
+ - `issue-not-sub`: the item is not a child of the request's own item, the
256
+ item that `assuredloop-requests.json` in the folder names (below);
257
+ - `phrase`: a phrase of `phrases:` matches;
258
+ - `issue-unread`: a file in the folder is not valid JSON, or holds an entry
259
+ that is not an item.
260
+
261
+ The folder holds `*.json` files. Each file holds one item, or a list of
262
+ items. An item is the JSON that the GitHub CLI prints with
263
+ `--json number,url,body,comments,parent`, so GitHub issues need no
264
+ conversion:
265
+
266
+ | Field | What it is | Needed |
267
+ |---|---|---|
268
+ | `body` | the item's text | yes |
269
+ | `url` | its address, `https://<host>/owner/repo/issues/n` for GitHub | one of `url`, `number` with `repository`, or `ref` |
270
+ | `number` | its number in its repository | |
271
+ | `comments` | a list, each with `body`, and `isMinimized` true for a hidden comment | no |
272
+ | `parent` | the parent item, with `url` and `number`, or `ref`; `null` for none | no: with no `parent`, the child check is not made |
273
+ | `ref` | the reference that a task names, for another tracker, for example `PROJ-12` | for another tracker |
274
+ | `repository` | `owner/repo`, or a project key | no |
275
+
276
+ To save GitHub issues, GitHub Enterprise included, save each issue with
277
+ `gh issue view`. `gh issue list` gives at most 100 comments of an issue.
278
+
279
+ ```sh
280
+ mkdir -p /tmp/items
281
+ gh issue view 31 --repo acme/invoicer \
282
+ --json number,url,body,comments,parent > /tmp/items/invoicer-31.json
283
+ GH_HOST=github.example.com gh issue view 5 --repo acme/web \
284
+ --json number,url,body,comments,parent > /tmp/items/web-5.json
285
+ al check --issues /tmp/items
286
+ ```
287
+
288
+ An item of another tracker:
289
+
290
+ ```json
291
+ {"ref": "PROJ-12", "repository": "PROJ", "body": "Build the export (ADR-3).",
292
+ "comments": [{"body": "Done.", "isMinimized": false}], "parent": {"ref": "PROJ-1"}}
293
+ ```
294
+
295
+ The folder also says which item is each request's own item, in the file
296
+ `assuredloop-requests.json`. `al` reads the own item from this file only,
297
+ never from an origin source. Without the file, or for a request that it
298
+ does not list, `issue-not-sub` is not checked, and the `Not known` line
299
+ says so once for each request. The file is not read as an item. A file that
300
+ is not valid gets `issue-unread` and is ignored; `al check` goes on.
301
+
302
+ - `format`: the exact text `assuredloop-items/1`;
303
+ - `requests`: each key is a request's name, and its value is that request's
304
+ own item, in a form that a task's `ref` uses: a URL, `owner/repo#n`, or
305
+ another tracker's key, for example `PROJ-1`.
306
+
307
+ ```json
308
+ {
309
+ "format": "assuredloop-items/1",
310
+ "requests": {
311
+ "invoice-exports": "https://github.com/acme/invoicer/issues/30",
312
+ "pay-run": "PROJ-1"
313
+ }
314
+ }
315
+ ```
316
+
317
+ To write it for a GitHub issue:
318
+
319
+ ```sh
320
+ printf '{"format": "assuredloop-items/1", "requests": {"invoice-exports": "%s"}}\n' \
321
+ "$(gh issue view 30 --repo acme/invoicer --json url --jq .url)" \
322
+ > /tmp/items/assuredloop-requests.json
323
+ ```
324
+
325
+ The JSON Schema (draft 2020-12) of each file, for a person or an AI that
326
+ writes them. An item file:
327
+
328
+ ```json
329
+ {
330
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
331
+ "title": "an item file: one item, or a list of items",
332
+ "oneOf": [
333
+ {"$ref": "#/$defs/item"},
334
+ {"type": "array", "items": {"$ref": "#/$defs/item"}}
335
+ ],
336
+ "$defs": {
337
+ "item": {
338
+ "type": "object",
339
+ "required": ["body"],
340
+ "anyOf": [
341
+ {"required": ["url"], "properties": {"url": {"type": "string", "pattern": "^https?://[^/\\s]+/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/issues/[0-9]+([^A-Za-z0-9_][\\s\\S]*)?$"}}},
342
+ {"required": ["number", "repository"]},
343
+ {"required": ["ref"]}
344
+ ],
345
+ "properties": {
346
+ "body": {"type": "string"},
347
+ "url": {"type": "string"},
348
+ "number": {"type": "integer"},
349
+ "comments": {
350
+ "type": "array",
351
+ "items": {
352
+ "type": "object",
353
+ "required": ["body"],
354
+ "properties": {"body": {"type": "string"}, "isMinimized": {"type": "boolean"}}
355
+ }
356
+ },
357
+ "parent": {
358
+ "oneOf": [
359
+ {"type": "null"},
360
+ {
361
+ "type": "object",
362
+ "properties": {"url": {"type": "string"}, "number": {"type": "integer"}, "ref": {"type": "string", "minLength": 1}}
363
+ }
364
+ ]
365
+ },
366
+ "ref": {"type": "string", "minLength": 1},
367
+ "repository": {"type": "string", "minLength": 1}
368
+ }
369
+ }
370
+ }
371
+ }
372
+ ```
373
+
374
+ `assuredloop-requests.json`:
375
+
376
+ ```json
377
+ {
378
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
379
+ "title": "assuredloop-requests.json",
380
+ "type": "object",
381
+ "required": ["format", "requests"],
382
+ "additionalProperties": false,
383
+ "properties": {
384
+ "format": {"const": "assuredloop-items/1"},
385
+ "requests": {
386
+ "type": "object",
387
+ "additionalProperties": {"type": "string", "minLength": 1}
388
+ }
389
+ }
390
+ }
391
+ ```
188
392
 
189
393
  ## Search
190
394
 
@@ -266,21 +470,70 @@ does not change; `al` hashes each paragraph without its marker.
266
470
 
267
471
  **From 0.1.0.** v4 (0.2.0) does not read 0.1.0 records. Keep 0.1.0 pinned
268
472
  until you move. Older records stay readable as files; run the pinned 0.1.0 at
269
- those commits. To move:
473
+ those commits.
474
+
475
+ In a project where `.assuredloop` is a file, as 0.1.0 writes it, each `al`
476
+ command refuses with exit 2 and one line: `al: .assuredloop is a file, so the
477
+ project has 0.1.0 records, which v4 does not read; keep 0.1.0 pinned, or move
478
+ as "From 0.1.0" in the README says`. It reads and writes nothing else, so
479
+ `al check` never passes there. `al --version` and the usage message still
480
+ work. To move:
270
481
 
271
482
  1. Finish each open 0.1.0 request on its pinned copy, or start it again
272
483
  under v4.
273
484
  2. Install 0.2.0 (see "Install").
274
- 3. Run `al spec --add-ids`, as in steps 1-5 above. It marks every paragraph
485
+ 3. Remove the 0.1.0 file: `git rm .assuredloop`. v4 keeps its settings in
486
+ the folder `.assuredloop/`. If the file had a `root:` line, write the
487
+ same line in `.assuredloop/config.yaml` (see "Settings").
488
+ 4. Run `al spec --add-ids`, as in steps 1-5 above. It marks every paragraph
275
489
  and writes the adoption record.
276
490
 
491
+ ## Hints, and the rules that block
492
+
493
+ `al check` gives the AI review hints, and the review decides. A finding is
494
+ `not ok` only for a rule that meets two tests: breaking it lets an
495
+ unapproved or wrong decision be built, and an exact check of the records,
496
+ hashes and links decides it, with no judgement. These rules are:
497
+
498
+ - **An unsigned requirement** (`not ok unsigned-requirement`). In each
499
+ request whose record has `requirements:`, open or archived, the latest
500
+ version of each requirement has a valid sign-off. A sign-off is valid only
501
+ when a `covers` entry matches the requirement's id, version and `sha256`
502
+ exactly; its file is in the request's `origin/` folder, and the hash of
503
+ its signed text equals the sign-off's `sha256`; and it records when it was
504
+ signed and the signer's words. A requirement in `request.md` that is not
505
+ its latest recorded version is not signed. An entry that `al check` cannot
506
+ read is `not ok` too. A promise paragraph that changes with no signed
507
+ requirement that covers it stays `not ok signoff-coverage`.
508
+ - **Changed record evidence** (`not ok record-evidence`). Each origin file
509
+ under `sources:`, and each sign-off file, still has the hash that the
510
+ record lists. `al check` hashes the snapshot's text after its `---` line,
511
+ as `al record` did.
512
+ - **A citation of a superseded ADR** (`not ok superseded-cited` and
513
+ `superseded-status`; see "Settings").
514
+
515
+ A request record that cannot be read (`record-unreadable`) and a spec root
516
+ that cannot be read (`spec-root`) are `not ok` too. Every other finding is a
517
+ hint in every mode, `--strict` included: the marker and ID lints, the path
518
+ claim, a link that does not resolve, an ADR's changed text or missing links,
519
+ an invalid disposition, and the rest. `--strict` changes only the exit code:
520
+ it is 1 when a `not ok` prints.
521
+
522
+ `al check` gives the hint "promise kind?" (`promise-kind`) for a paragraph
523
+ that is not a promise kind and holds a requirement word: MUST, SHALL, SHOULD,
524
+ MAY, never or always. When the paragraph is a promise, change its kind. When
525
+ it applies a promise, for example a flow step that keeps a rule, give it
526
+ `builds-on:` the promise paragraph in its marker. The hint then goes. A
527
+ `builds-on:` to another kind, or to an ID that does not exist, keeps it.
528
+
277
529
  ## What is enforced, and by whom
278
530
 
279
531
  | The tool checks | The skill asks | The owner decides |
280
532
  |---|---|---|
281
533
  | `al conclude` refuses on its three conditions (see "The close rule") | write the owner's words and the organized requirement, and ask for the sign-off | what is wanted, by signing the organized requirement |
282
- | IDs: missing, used twice, lost, used again; kinds and links; links that do not resolve; sign-off coverage of promise changes; path and typo claims; dispositions and exact equality; stale bindings; overlapping changes; ADR text changes; a proposed or accepted ADR with no `source:` or no `decides:`; result applicability; a design paragraph that links to none of its requirement's promise paragraphs in its change spec; a `for:` that names no task, as hints | choose the path, and state it in a commit message and the PR | whether a changed requirement is signed again |
283
- | `check --strict` exits 1 on a `not ok`, when a project opts in | record each later decision, with its source | decisions whose source is the owner, such as dropping or keeping work |
534
+ | `not ok`: sign-off coverage of promise changes, unsigned requirement versions, changed record evidence, citations of superseded ADRs and their status lines, and records or a spec root that cannot be read | choose the path, and state it in a commit message and the PR | whether a changed requirement is signed again |
535
+ | hints for the review: IDs missing, used twice, lost or used again; kinds and links; links that do not resolve; path and typo claims; dispositions and exact equality; stale bindings; overlapping changes; ADR text changes; a proposed or accepted ADR with no `source:` or no `decides:`; result applicability; a design paragraph that links to none of its requirement's promise paragraphs in its change spec; a `for:` that names no task; matches of the configured phrases; with `--issues <folder>`, the hints from the linked work items | record each later decision, with its source | decisions whose source is the owner, such as dropping or keeping work |
536
+ | `check --strict` exits 1 on a `not ok`, when a project opts in | | |
284
537
 
285
538
  `al` checks that declarations are well formed and that references resolve.
286
539
  It does not check that a kind is right, that a requirement is fully
package/bin/al.js CHANGED
@@ -2,7 +2,8 @@
2
2
  // al, the v4 command (T17 made it the one in package.json's bin).
3
3
  import { parseArgs } from 'node:util';
4
4
  import { execFileSync } from 'node:child_process';
5
- import { readFileSync, realpathSync } from 'node:fs';
5
+ import { readFileSync, realpathSync, statSync } from 'node:fs';
6
+ import { join } from 'node:path';
6
7
  import { fileURLToPath } from 'node:url';
7
8
  import { check } from '../src/v4/check.js';
8
9
  import { Fail, spec } from '../src/v4/spec.js';
@@ -28,7 +29,7 @@ const COMMANDS = {
28
29
  },
29
30
  index: { run: index, positionals: 0, options: { align: { type: 'string' } } },
30
31
  spec: { run: spec, positionals: (o) => (o['add-ids'] === undefined ? Infinity : 0), options: { 'add-ids': { type: 'string' }, prefix: { type: 'string' }, yes: { type: 'boolean' } } },
31
- check: { run: check, positionals: 0, options: { strict: { type: 'boolean' } } },
32
+ check: { run: check, positionals: 0, options: { strict: { type: 'boolean' }, issues: { type: 'string' } } },
32
33
  export: { run: exportCommand, positionals: 0, options: { at: { type: 'string' }, out: { type: 'string' }, yes: { type: 'boolean' } } },
33
34
  search: {
34
35
  run: search,
@@ -102,6 +103,11 @@ async function main(argv) {
102
103
  throw new Fail(`extra argument ${JSON.stringify(extra)}: ${used} takes ${n === 0 ? 'no' : n} positional argument${n === 1 ? '' : 's'}`,
103
104
  'quote an option value that holds spaces, and leave out the extra argument');
104
105
  }
106
+ // 0.1.0 kept its settings in a file .assuredloop; v4 keeps a folder (#241).
107
+ if (statSync(join(top, '.assuredloop'), { throwIfNoEntry: false })?.isFile()) {
108
+ throw new Fail('.assuredloop is a file, so the project has 0.1.0 records, which v4 does not read; keep 0.1.0 pinned, or move as "From 0.1.0" in the README says',
109
+ 'npm install --global @assuredloop/cli@0.1.0, or move as "From 0.1.0" in the README says');
110
+ }
105
111
  let out;
106
112
  try {
107
113
  out = await command.run({ top, cwd, args: parsed.positionals, opts: parsed.values });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assuredloop/cli",
3
- "version": "0.2.4",
3
+ "version": "2.0.0",
4
4
  "description": "AssuredLoop keeps an AI focused on one change: the al command gathers and checks a change's requirement, spec, decisions, code and tests.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,8 +10,9 @@ description: >-
10
10
  # AssuredLoop v4
11
11
 
12
12
  `al` keeps the records of what was asked, decided, changed and delivered,
13
- and gives hints. It refuses only in `al conclude`. How and when you design,
14
- test and review is your project's way of working; this skill does not set it.
13
+ and gives the review hints. The review decides. It refuses only in
14
+ `al conclude`. How and when you design, test and review is your project's
15
+ way of working; this skill does not set it.
15
16
 
16
17
  ## Start here, on every path
17
18
 
@@ -31,16 +32,19 @@ test and review is your project's way of working; this skill does not set it.
31
32
  | 0 | No promise changes: a fix that restores what the spec says, or a typo in any paragraph. | none | no |
32
33
  | 1 | One or a few promise paragraphs change. | a short request; no change spec | yes |
33
34
  | 1d | Only names or wording of design paragraphs change. | a short request; no change spec | no |
34
- | 2 | A real design, in one or a few PRs. | a request and a change spec | if a promise changes |
35
+ | 2 | A real design, in one or a few PRs. | a request and a change spec | if a promise changes, or the request has an R<n> |
35
36
  | 3 | A big change, delivered in parts over time. | as path 2, part by part | as path 2 |
36
37
  | S | It is not clear yet what to build. | a request with a question | yes, on the question |
37
38
 
38
39
  The promise kinds are purpose, scope, rule, limit and definition. Work that
39
40
  grows changes its path: a small change that needs a design becomes path 2.
40
41
 
41
- 4. Before you ask for review, run `al index`, then `al check`. Fix each
42
- `not ok`, or say in the PR why it stays. A hint is an observation, not a
43
- verdict.
42
+ 4. Before you ask for review, run `al index`, then `al check`. A `not ok` is
43
+ one of the few exact rules: a promise change or a requirement version
44
+ with no valid sign-off, changed record evidence, a citation of a
45
+ superseded ADR, or a record that cannot be read. Fix each one. A hint is
46
+ for the review, not a verdict: deal with it, or say in the PR why it
47
+ stays. A finding has the same severity with or without `--strict`.
44
48
  5. Put the claim in a commit message and in the PR, for example
45
49
  `Tier: 1 — amends INV-10`. `al check` reads the newest `Tier:` line of the
46
50
  branch.
@@ -63,7 +67,7 @@ The export link MUST expire 30 minutes after the email is sent.
63
67
  marker. Its ID is retired and never used again. `al check` gives a hint
64
68
  that names the deletion; when it is on purpose, nothing more is needed. In
65
69
  `specs/`, a removal needs `removes:<ID>` in a change spec: a lost ID there
66
- is `not ok`.
70
+ gets a hint that asks for it.
67
71
  - For a new paragraph, write the text with no marker. Then run
68
72
  `al spec --add-ids <file> --yes`: it gives each unmarked paragraph the next
69
73
  free ID. Then write the kind and the links into each new marker.
@@ -78,10 +82,21 @@ The export link MUST expire 30 minutes after the email is sent.
78
82
  needs at least one link. In `specs/`, the ID of a change spec paragraph
79
83
  names its request: `invoice-exports/SP-12`.
80
84
  - An ADR in `specs/adr/` has one marker on its heading: `ADR-<n>`, its kind,
81
- and its `decides:`, `source:` and `supersedes:` links. Under `--strict`,
82
- `al check` asks each proposed or accepted ADR for `source:` and
83
- `decides:`. You may add links to the marker of an accepted ADR. Change
84
- nothing else in it: its ID, its kind, its old links and its text stay.
85
+ and its `decides:`, `source:` and `supersedes:` links. `al check` gives a
86
+ hint for each proposed or accepted ADR with no `source:` or no
87
+ `decides:`.
88
+ - Any text may be rewritten in other words when its meaning stays the same,
89
+ an accepted or superseded ADR's text too. Write a new ADR that supersedes
90
+ it only when the decision changes, in whole or in part. `al check` gives a
91
+ hint on a changed ADR: check that its decision is unchanged. After the
92
+ rewrite, run `al index --align <ID>` for each paragraph that links to it.
93
+ - When an ADR is superseded, set its first line to
94
+ `Status: superseded by ADR-<m>`, and put `supersedes:ADR-<n>` in the marker
95
+ of ADR-m. When several ADRs supersede it, name each one:
96
+ `Status: superseded by ADR-11, ADR-12`. Where a spec paragraph, an open
97
+ request or a proposed or accepted ADR names ADR-n, name an ADR that
98
+ supersedes it beside it: in the same marker for a link, in the same
99
+ paragraph for a word. Else `al check` gives `not ok`.
85
100
  - `al` writes every hash and every derived field. Do not write them by hand.
86
101
 
87
102
  ## Path 0: a fix
@@ -127,9 +142,9 @@ The export link MUST expire 30 minutes after the email is sent.
127
142
  1. `al new <name> --tier 1d --from -`, with a one-line note of where the
128
143
  correction comes from.
129
144
  2. Change only names or wording of design paragraphs in `specs/`. Add or
130
- remove no paragraph. Do not change an accepted ADR's text: a new ADR
131
- supersedes it.
132
- 3. Claim `Tier: 1d — <what changes>`. `al check` gives `not ok` when the PR
145
+ remove no paragraph. You may reword an ADR when its decision stays; a
146
+ changed decision is a new ADR that supersedes it.
147
+ 3. Claim `Tier: 1d — <what changes>`. `al check` gives a hint when the PR
133
148
  touches a promise paragraph, or adds or removes a design paragraph. No
134
149
  sign-off is needed.
135
150
  4. Run `al conclude <name> --yes` with the PR.
@@ -138,8 +153,11 @@ The export link MUST expire 30 minutes after the email is sent.
138
153
 
139
154
  1. `al new <name> --tier 2 --from -`. Write the organized requirement as in
140
155
  path 1, with `Out:` and `Assumed:` lines where they help. Draft freely;
141
- deliver with the owner's sign-off or after it. A change that touches no
142
- promise kind needs no sign-off.
156
+ deliver with the owner's sign-off or after it. Each R<n> needs the
157
+ owner's sign-off, also when the change touches no promise kind: until it
158
+ is signed, `al check` gives `not ok unsigned-requirement`. For a
159
+ design-only change, which needs no sign-off, write the organized section
160
+ with no `### R<n>`: a short note of what the design changes and why.
143
161
  2. Write the design in `requests/<name>/spec.md`, the change spec: the
144
162
  promises this change adds or changes, its design, its plan. The order
145
163
  of the paragraphs is free. The recommended order is one section per
@@ -198,7 +216,12 @@ project uses them, list them in the record as
198
216
  `tasks: [{id: T2, ref: "issue #34", delivers: [R3]}]`, and name the task in
199
217
  the commit messages of its PRs. Plan, step and migration paragraphs may live
200
218
  in the tracker issue that the task's `ref` names, instead of in the change
201
- spec. Run `al conclude <name> --yes` with the last part.
219
+ spec. `al` never contacts a tracker: save the items in a folder (the README
220
+ has the format and the `gh` commands), and `al check --issues <folder>`
221
+ gives hints on them: an item missing from the folder, a superseded ADR
222
+ named without its successor, a bare `#n` in another repository's item, an
223
+ item that is not a child of the request's own item, and the phrases of
224
+ `phrases:` in `.assuredloop/config.yaml`. Run `al conclude <name> --yes` with the last part.
202
225
 
203
226
  ## Path S: a spike
204
227