@assuredloop/cli 0.2.3 → 0.2.4
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 +5 -2
- package/package.json +1 -1
- package/skills/assuredloop/SKILL.md +5 -0
- package/src/v4/checks.js +10 -2
- package/src/v4/state.js +6 -1
- package/src/v4/views.js +5 -2
package/README.md
CHANGED
|
@@ -181,7 +181,10 @@ central: {path: ../invoicer} # in an output repo: the central repo
|
|
|
181
181
|
ADRs are `specs/adr/NNNN-<slug>.md`, with `Status: proposed`, `accepted` or
|
|
182
182
|
`superseded` on the first line. The heading's marker holds `ADR-<n>` and its
|
|
183
183
|
`decides:`, `source:` and `supersedes:` links. The text of an accepted ADR
|
|
184
|
-
does not change; a new ADR supersedes it.
|
|
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`.
|
|
185
188
|
|
|
186
189
|
## Search
|
|
187
190
|
|
|
@@ -276,7 +279,7 @@ those commits. To move:
|
|
|
276
279
|
| The tool checks | The skill asks | The owner decides |
|
|
277
280
|
|---|---|---|
|
|
278
281
|
| `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 |
|
|
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 |
|
|
280
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 |
|
|
281
284
|
|
|
282
285
|
`al` checks that declarations are well formed and that references resolve.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@assuredloop/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
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": {
|
|
@@ -77,6 +77,11 @@ The export link MUST expire 30 minutes after the email is sent.
|
|
|
77
77
|
`governed-by:<ADR>`; `for:<task>`. A promise, design or approach paragraph
|
|
78
78
|
needs at least one link. In `specs/`, the ID of a change spec paragraph
|
|
79
79
|
names its request: `invoice-exports/SP-12`.
|
|
80
|
+
- 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.
|
|
80
85
|
- `al` writes every hash and every derived field. Do not write them by hand.
|
|
81
86
|
|
|
82
87
|
## Path 0: a fix
|
package/src/v4/checks.js
CHANGED
|
@@ -353,12 +353,20 @@ export function checks(top, { base, strict = false }) {
|
|
|
353
353
|
if (pending.length) add('info', 'disposition-pending', r.specFile, r.name, `${pending.length} of ${judged.length} paragraphs with a baseline effect have no disposition for their current version: ${pending.join(', ')}`);
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
// ADRs (schema.md 5).
|
|
356
|
+
// ADRs (schema.md 5). Links may be added to the heading marker of an
|
|
357
|
+
// accepted ADR: the same kind, each old link kept, the rest unchanged.
|
|
358
|
+
const linksAdded = (a, h) => a.status === 'accepted' && a.head && h.head && a.rest === h.rest && a.head.kind === h.head.kind
|
|
359
|
+
&& Object.entries(a.head.links).every(([k, refs]) => refs.every((x) => h.head.links[k].includes(x)));
|
|
357
360
|
for (const [id, a] of was?.adrs ?? []) {
|
|
358
361
|
if (!['accepted', 'superseded'].includes(a.status)) continue;
|
|
359
362
|
const h = now.adrs.get(id);
|
|
360
363
|
if (!h) add('not ok', 'adr-changed', a.file, id, `${id} was ${a.status} and its file is gone; an ADR is superseded, never edited or deleted`);
|
|
361
|
-
else if (h.body !== a.body) add('not ok', 'adr-changed', h.head ?? h.file, id, `the text of ${id} changed after it was accepted; write a new ADR that supersedes it`);
|
|
364
|
+
else if (h.body !== a.body && !linksAdded(a, h)) add('not ok', 'adr-changed', h.head ?? h.file, id, `the text of ${id} changed after it was accepted; write a new ADR that supersedes it`);
|
|
365
|
+
}
|
|
366
|
+
for (const a of now.adrs.values()) {
|
|
367
|
+
if (!['proposed', 'accepted'].includes(a.status)) continue;
|
|
368
|
+
const missing = ['source', 'decides'].filter((w) => !a.head?.links[w].length);
|
|
369
|
+
if (missing.length) add(strict ? 'not ok' : 'hint', 'adr-links', a.head ?? a.file, a.id, `${a.id} has ${missing.map((w) => `no ${w}:`).join(' and ')}; add the links to its marker`);
|
|
362
370
|
}
|
|
363
371
|
for (const a of now.adrs.values()) {
|
|
364
372
|
for (const old of a.head?.links.supersedes ?? []) {
|
package/src/v4/state.js
CHANGED
|
@@ -33,6 +33,10 @@ function names(top, rev, dir, folders) {
|
|
|
33
33
|
// The text after an ADR's Status line: what must not change once it is accepted.
|
|
34
34
|
const adrBody = (text) => text.replace(/\r\n?/g, '\n').replace(/^\s*Status:[^\n]*\n?/, '').replace(/^\n+/, '');
|
|
35
35
|
|
|
36
|
+
// The same with the line of the heading marker left out: what must not change
|
|
37
|
+
// when links are added to that marker (schema.md 5).
|
|
38
|
+
const adrRest = (text, head) => adrBody(text.replace(/\r\n?/g, '\n').split('\n').map((l, i) => (i === head.line - 1 ? '<!-- -->' : l)).join('\n'));
|
|
39
|
+
|
|
36
40
|
// The settings of a commit: its own config.yaml and schema.yaml, with the
|
|
37
41
|
// defaults of config.js when a file is absent or not a mapping.
|
|
38
42
|
export function settingsAt(top, rev) {
|
|
@@ -143,7 +147,8 @@ export function loadState(top, rev = null) {
|
|
|
143
147
|
const paragraphs = parseMarkdown(text, file, { kinds }).paragraphs;
|
|
144
148
|
const id = `ADR-${Number(m[1])}`;
|
|
145
149
|
const status = /^\s*Status:\s*([a-z]+)/i.exec(text)?.[1].toLowerCase() ?? null;
|
|
146
|
-
|
|
150
|
+
const head = paragraphs.find((p) => p.id === id) ?? null;
|
|
151
|
+
adrs.set(id, { id, file, status, body: adrBody(text), rest: head && adrRest(text, head), paragraphs, head });
|
|
147
152
|
}
|
|
148
153
|
return { top, rev, config, kinds, root, get, scopes, lints, spec, requests, adrs };
|
|
149
154
|
}
|
package/src/v4/views.js
CHANGED
|
@@ -419,6 +419,9 @@ function diffView(top, range, review) {
|
|
|
419
419
|
const now = loadState(top, headSha);
|
|
420
420
|
const was = loadState(top, base);
|
|
421
421
|
const findings = review ? checks(top, { base }).findings : [];
|
|
422
|
+
// The ADRs with no source link, which the view marks where it names them (schema.md 5).
|
|
423
|
+
const unsourced = new Set([...now.adrs.values()].filter((a) => !a.head?.links.source.length).map((a) => a.id));
|
|
424
|
+
const marks = (message) => [...new Set(message.match(/\bADR-\d+\b/g) ?? [])].filter((id) => unsourced.has(id)).map((id) => `; ${id} has no source:`).join('');
|
|
422
425
|
const body = [];
|
|
423
426
|
for (const scope of new Set([...now.scopes.keys(), ...was.scopes.keys()])) {
|
|
424
427
|
const request = requestOf(scope);
|
|
@@ -451,10 +454,10 @@ function diffView(top, range, review) {
|
|
|
451
454
|
}
|
|
452
455
|
if (!request) {
|
|
453
456
|
for (const a of now.adrs.values()) {
|
|
454
|
-
if ((a.head?.links.decides ?? []).includes(c.id) || p.links.governedBy.includes(a.id)) body.push(` governed by ${a.id} (${a.status ?? 'no status'})`);
|
|
457
|
+
if ((a.head?.links.decides ?? []).includes(c.id) || p.links.governedBy.includes(a.id)) body.push(` governed by ${a.id} (${a.status ?? 'no status'}${unsourced.has(a.id) ? '; no source:' : ''})`);
|
|
455
458
|
}
|
|
456
459
|
}
|
|
457
|
-
for (const f of findings.filter((x) => x.id === who && x.severity !== 'info')) body.push(` ${findingLine(f)}`);
|
|
460
|
+
for (const f of findings.filter((x) => x.id === who && x.severity !== 'info')) body.push(` ${findingLine(f)}${marks(f.message)}`);
|
|
458
461
|
}
|
|
459
462
|
}
|
|
460
463
|
if (!body.length) body.push('no paragraph changed in the range');
|