@assuredloop/cli 0.2.3 → 1.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,
@@ -109,16 +111,16 @@ The user picks the path. The skill has the steps of each path.
109
111
  | 0. Fix | no promise changes: it restores what the spec says, or claims a typo in any paragraph | none | no | the PR merges |
110
112
  | 1. Small promise amend | one or a few promise paragraphs change | a short request; the PR edits `specs/` directly | yes | the PR merges |
111
113
  | 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 |
114
+ | 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
115
  | 3. Epic | big, in parts over time | as 2, with optional task references | as 2 | the close rule holds |
114
116
  | 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
117
 
116
118
  Every PR states its path and its claim in a commit message, for example
117
119
  `Tier: 0 — typo in INV-3` or `Tier: 2 — invoice-payments, part 1`. `al check`
118
120
  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.
121
+ against the kinds and the changed paragraphs. Where they disagree, it gives
122
+ a hint, for example "path 0, but a rule paragraph is changed with no typo
123
+ claim". It checks the labels, not their meaning.
122
124
 
123
125
  **The close rule.** `al conclude <name>` refuses while a promise change has
124
126
  no sign-off, a spike's current question is not signed, or a change paragraph
@@ -140,7 +142,10 @@ with a baseline effect has no valid disposition for its current version.
140
142
  - `al index`: write the derived facts and the new bindings, and list the AI
141
143
  hints to refresh. `al index --align <ID>` moves the bindings of one
142
144
  paragraph to its targets' current text, on purpose.
143
- - `al check [--strict]`: the checks of this branch against its base.
145
+ - `al check [--strict] [--issues <folder>]`: the checks of this branch
146
+ against its base. With `--issues <folder>`, it also checks the work items
147
+ that the caller saved in the folder (see "Hints from the linked work
148
+ items").
144
149
  - `al context [<name> | <ID>]`: where the project, a request or a paragraph
145
150
  stands. `--diff <range> --for review` is the review view. `--audit` gives a
146
151
  request's whole trace; `--at <commit>` any v4 state.
@@ -173,15 +178,101 @@ results: .assuredloop/results # where result files are
173
178
  outputs: # in the central repo: its output repos
174
179
  - {name: invoicer-web, path: ../invoicer-web, commit: <sha>}
175
180
  central: {path: ../invoicer} # in an output repo: the central repo
181
+ phrases: # text for the review to look at; each match is a hint
182
+ - the requester # a phrase: ignoring case, across a line break too
183
+ - {pattern: "Avery (asked|wants)"} # a JavaScript regular expression
176
184
  ```
177
185
 
186
+ `al check` gives the hint `phrase` for each match of `phrases:` in the spec
187
+ docs, the ADRs and the Markdown files of the open requests, but their
188
+ `origin/` folders; with `--issues <folder>`, in the work items too. The list is empty by
189
+ default. A bad entry or an invalid pattern stops `al check` with exit 2.
190
+
178
191
  `.assuredloop/schema.yaml` holds the kinds and the link words, with a version.
179
192
  `al spec --add-ids` writes both files when they are absent.
180
193
 
181
194
  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.
195
+ `superseded by ADR-<n>` on the first line. The heading's marker holds
196
+ `ADR-<n>` and its `decides:`, `source:` and `supersedes:` links.
197
+
198
+ - Any text may be rewritten in other words when its meaning stays the same,
199
+ an accepted or superseded ADR's text too. A new ADR that supersedes it is
200
+ needed only when the decision changes, in whole or in part.
201
+ - The review judges whether a rewrite keeps the decision; `al` does not. So
202
+ a changed ADR text, or a changed or removed link in its marker, gives a
203
+ hint: check that its decision is unchanged. Links added to the marker of
204
+ an accepted ADR, with nothing else changed, give no finding.
205
+ - After a rewrite, `al index --align <ID>` moves the bindings of paragraph
206
+ `<ID>` to the ADR's new text, for an accepted or a superseded ADR.
207
+ - `al check` gives a hint for a proposed or accepted ADR with no `source:`
208
+ or no `decides:`.
209
+ - A current place that names a superseded ADR also names the ADR that
210
+ supersedes it, in the same marker or the same paragraph. Current places
211
+ are the spec docs, the open requests and the proposed and accepted ADRs.
212
+ The chain itself and dated records are exempt: an ADR that supersedes it,
213
+ a superseded ADR, an ADR's `## History`, a request's `## Decisions`, the
214
+ `origin/` files and the archived requests. Else `al check` gives
215
+ `not ok superseded-cited`.
216
+ - A superseded ADR's status line names the ADR that supersedes it, and that
217
+ ADR's marker has `supersedes:` for it. Else `al check` gives
218
+ `not ok superseded-status`.
219
+
220
+ ## Hints from the linked work items
221
+
222
+ A project may keep its work items in any tracker, or in none. `al` never
223
+ contacts a tracker. The caller saves the items in a folder, and
224
+ `al check --issues <folder>` checks the items that the `tasks:` of the open
225
+ requests name. Without `--issues`, `al check` reads no item. A task's `ref`
226
+ names an item as `#n`, `owner/repo#n`, `<name>#n` for an output repo that
227
+ config names, an issue URL on any host, or the item's own `ref`, for
228
+ example `PROJ-12`. Each finding is a hint:
229
+
230
+ - `issue-missing`: a task names an item that is not in the folder;
231
+ - `issue-superseded`: the item's text names a superseded ADR without the ADR
232
+ that supersedes it, by the rule of `superseded-cited`;
233
+ - `issue-bare-ref`: a bare `#n` in an item of another repository, which
234
+ GitHub reads as that repository's issue. The request's own repository
235
+ comes from `git remote get-url origin`;
236
+ - `issue-not-sub`: the item is not a child of the request's own item, the
237
+ item that the `url` of an origin source names (`al record <name> origin
238
+ --url <its URL>`);
239
+ - `phrase`: a phrase of `phrases:` matches;
240
+ - `issue-unread`: a file in the folder is not valid JSON, or holds an entry
241
+ that is not an item.
242
+
243
+ The folder holds `*.json` files. Each file holds one item, or a list of
244
+ items. An item is the JSON that the GitHub CLI prints with
245
+ `--json number,url,body,comments,parent`, so GitHub issues need no
246
+ conversion:
247
+
248
+ | Field | What it is | Needed |
249
+ |---|---|---|
250
+ | `body` | the item's text | yes |
251
+ | `url` | its address, `https://<host>/owner/repo/issues/n` for GitHub | one of `url`, `number` with `repository`, or `ref` |
252
+ | `number` | its number in its repository | |
253
+ | `comments` | a list, each with `body`, and `isMinimized` true for a hidden comment | no |
254
+ | `parent` | the parent item, with `url` and `number`, or `ref`; `null` for none | no: with no `parent`, the child check is not made |
255
+ | `ref` | the reference that a task names, for another tracker, for example `PROJ-12` | for another tracker |
256
+ | `repository` | `owner/repo`, or a project key | no |
257
+
258
+ To save GitHub issues, GitHub Enterprise included, save each issue with
259
+ `gh issue view`. `gh issue list` gives at most 100 comments of an issue.
260
+
261
+ ```sh
262
+ mkdir -p /tmp/items
263
+ gh issue view 31 --repo acme/invoicer \
264
+ --json number,url,body,comments,parent > /tmp/items/invoicer-31.json
265
+ GH_HOST=github.example.com gh issue view 5 --repo acme/web \
266
+ --json number,url,body,comments,parent > /tmp/items/web-5.json
267
+ al check --issues /tmp/items
268
+ ```
269
+
270
+ An item of another tracker:
271
+
272
+ ```json
273
+ {"ref": "PROJ-12", "repository": "PROJ", "body": "Build the export (ADR-3).",
274
+ "comments": [{"body": "Done.", "isMinimized": false}], "parent": {"ref": "PROJ-1"}}
275
+ ```
185
276
 
186
277
  ## Search
187
278
 
@@ -263,21 +354,63 @@ does not change; `al` hashes each paragraph without its marker.
263
354
 
264
355
  **From 0.1.0.** v4 (0.2.0) does not read 0.1.0 records. Keep 0.1.0 pinned
265
356
  until you move. Older records stay readable as files; run the pinned 0.1.0 at
266
- those commits. To move:
357
+ those commits.
358
+
359
+ In a project where `.assuredloop` is a file, as 0.1.0 writes it, each `al`
360
+ command refuses with exit 2 and one line: `al: .assuredloop is a file, so the
361
+ project has 0.1.0 records, which v4 does not read; keep 0.1.0 pinned, or move
362
+ as "From 0.1.0" in the README says`. It reads and writes nothing else, so
363
+ `al check` never passes there. `al --version` and the usage message still
364
+ work. To move:
267
365
 
268
366
  1. Finish each open 0.1.0 request on its pinned copy, or start it again
269
367
  under v4.
270
368
  2. Install 0.2.0 (see "Install").
271
- 3. Run `al spec --add-ids`, as in steps 1-5 above. It marks every paragraph
369
+ 3. Remove the 0.1.0 file: `git rm .assuredloop`. v4 keeps its settings in
370
+ the folder `.assuredloop/`. If the file had a `root:` line, write the
371
+ same line in `.assuredloop/config.yaml` (see "Settings").
372
+ 4. Run `al spec --add-ids`, as in steps 1-5 above. It marks every paragraph
272
373
  and writes the adoption record.
273
374
 
375
+ ## Hints, and the rules that block
376
+
377
+ `al check` gives the AI review hints, and the review decides. A finding is
378
+ `not ok` only for a rule that meets two tests: breaking it lets an
379
+ unapproved or wrong decision be built, and an exact check of the records,
380
+ hashes and links decides it, with no judgement. These rules are:
381
+
382
+ - **An unsigned requirement** (`not ok unsigned-requirement`). In each
383
+ request whose record has `requirements:`, open or archived, the latest
384
+ version of each requirement has a valid sign-off. A sign-off is valid only
385
+ when a `covers` entry matches the requirement's id, version and `sha256`
386
+ exactly; its file is in the request's `origin/` folder, and the hash of
387
+ its signed text equals the sign-off's `sha256`; and it records when it was
388
+ signed and the signer's words. A requirement in `request.md` that is not
389
+ its latest recorded version is not signed. An entry that `al check` cannot
390
+ read is `not ok` too. A promise paragraph that changes with no signed
391
+ requirement that covers it stays `not ok signoff-coverage`.
392
+ - **Changed record evidence** (`not ok record-evidence`). Each origin file
393
+ under `sources:`, and each sign-off file, still has the hash that the
394
+ record lists. `al check` hashes the snapshot's text after its `---` line,
395
+ as `al record` did.
396
+ - **A citation of a superseded ADR** (`not ok superseded-cited` and
397
+ `superseded-status`; see "Settings").
398
+
399
+ A request record that cannot be read (`record-unreadable`) and a spec root
400
+ that cannot be read (`spec-root`) are `not ok` too. Every other finding is a
401
+ hint in every mode, `--strict` included: the marker and ID lints, the path
402
+ claim, a link that does not resolve, an ADR's changed text or missing links,
403
+ an invalid disposition, and the rest. `--strict` changes only the exit code:
404
+ it is 1 when a `not ok` prints.
405
+
274
406
  ## What is enforced, and by whom
275
407
 
276
408
  | The tool checks | The skill asks | The owner decides |
277
409
  |---|---|---|
278
410
  | `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 |
279
- | 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; 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 |
280
- | `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 |
411
+ | `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 |
412
+ | 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 |
413
+ | `check --strict` exits 1 on a `not ok`, when a project opts in | | |
281
414
 
282
415
  `al` checks that declarations are well formed and that references resolve.
283
416
  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.3",
3
+ "version": "1.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.
@@ -77,6 +81,20 @@ The export link MUST expire 30 minutes after the email is sent.
77
81
  `governed-by:<ADR>`; `for:<task>`. A promise, design or approach paragraph
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`.
84
+ - An ADR in `specs/adr/` has one marker on its heading: `ADR-<n>`, its kind,
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. Where a spec paragraph, an open request or a proposed or accepted
96
+ ADR names ADR-n, name ADR-m beside it: in the same marker for a link, in
97
+ the same paragraph for a word. Else `al check` gives `not ok`.
80
98
  - `al` writes every hash and every derived field. Do not write them by hand.
81
99
 
82
100
  ## Path 0: a fix
@@ -122,9 +140,9 @@ The export link MUST expire 30 minutes after the email is sent.
122
140
  1. `al new <name> --tier 1d --from -`, with a one-line note of where the
123
141
  correction comes from.
124
142
  2. Change only names or wording of design paragraphs in `specs/`. Add or
125
- remove no paragraph. Do not change an accepted ADR's text: a new ADR
126
- supersedes it.
127
- 3. Claim `Tier: 1d — <what changes>`. `al check` gives `not ok` when the PR
143
+ remove no paragraph. You may reword an ADR when its decision stays; a
144
+ changed decision is a new ADR that supersedes it.
145
+ 3. Claim `Tier: 1d — <what changes>`. `al check` gives a hint when the PR
128
146
  touches a promise paragraph, or adds or removes a design paragraph. No
129
147
  sign-off is needed.
130
148
  4. Run `al conclude <name> --yes` with the PR.
@@ -133,8 +151,11 @@ The export link MUST expire 30 minutes after the email is sent.
133
151
 
134
152
  1. `al new <name> --tier 2 --from -`. Write the organized requirement as in
135
153
  path 1, with `Out:` and `Assumed:` lines where they help. Draft freely;
136
- deliver with the owner's sign-off or after it. A change that touches no
137
- promise kind needs no sign-off.
154
+ deliver with the owner's sign-off or after it. Each R<n> needs the
155
+ owner's sign-off, also when the change touches no promise kind: until it
156
+ is signed, `al check` gives `not ok unsigned-requirement`. For a
157
+ design-only change, which needs no sign-off, write the organized section
158
+ with no `### R<n>`: a short note of what the design changes and why.
138
159
  2. Write the design in `requests/<name>/spec.md`, the change spec: the
139
160
  promises this change adds or changes, its design, its plan. The order
140
161
  of the paragraphs is free. The recommended order is one section per
@@ -193,7 +214,12 @@ project uses them, list them in the record as
193
214
  `tasks: [{id: T2, ref: "issue #34", delivers: [R3]}]`, and name the task in
194
215
  the commit messages of its PRs. Plan, step and migration paragraphs may live
195
216
  in the tracker issue that the task's `ref` names, instead of in the change
196
- spec. Run `al conclude <name> --yes` with the last part.
217
+ spec. `al` never contacts a tracker: save the items in a folder (the README
218
+ has the format and the `gh` commands), and `al check --issues <folder>`
219
+ gives hints on them: an item missing from the folder, a superseded ADR
220
+ named without its successor, a bare `#n` in another repository's item, an
221
+ item that is not a child of the request's own item, and the phrases of
222
+ `phrases:` in `.assuredloop/config.yaml`. Run `al conclude <name> --yes` with the last part.
197
223
 
198
224
  ## Path S: a spike
199
225