@arjunkhera/atlas 0.4.45 → 0.4.47
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/.claude-plugin/plugin.json +1 -1
- package/door/cli.mjs +9 -41
- package/door/lib/approve.mjs +193 -35
- package/door/lib/owner-only.mjs +1 -1
- package/door/lib/signed-act.mjs +39 -0
- package/door/lib/tooling.mjs +32 -1
- package/door/lib/upkeep.mjs +14 -82
- package/owner-only.yaml +17 -9
- package/package.json +1 -1
- package/shape/check.mjs +402 -487
- package/shape/releases.json +5 -0
- package/skills/lead/SKILL.md +8 -6
- package/work/lib/delivery.mjs +22 -6
- package/door/lib/kind-drift.mjs +0 -86
package/shape/releases.json
CHANGED
|
@@ -52,6 +52,11 @@
|
|
|
52
52
|
"check_version": 9,
|
|
53
53
|
"sha256": "87896abfc09afd22724cfa645bbb5ef029d83887caf70f38b88d00b21cddef02",
|
|
54
54
|
"workflow_sha256": "fcddebef030e6564f97e1a5fb81c8ad1b3084ebef9622167c6565698312dcdd7"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"check_version": 10,
|
|
58
|
+
"sha256": "8943d8ea9f1af9a16c677942053b6ec7f9f767080d52f626d0960d7f36499732",
|
|
59
|
+
"workflow_sha256": "fcddebef030e6564f97e1a5fb81c8ad1b3084ebef9622167c6565698312dcdd7"
|
|
55
60
|
}
|
|
56
61
|
]
|
|
57
62
|
}
|
package/skills/lead/SKILL.md
CHANGED
|
@@ -83,9 +83,12 @@ the turn ends. Do not wait for a second miss.
|
|
|
83
83
|
|
|
84
84
|
## 5. The area rule
|
|
85
85
|
|
|
86
|
-
1. A repo
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
1. A repo lists its areas in `atlas.yaml`, under `areas:`. Its entry
|
|
87
|
+
file can list them too. Then run Atlas flows only in a listed area.
|
|
88
|
+
Outside them, say that the area is not listed, and stop. A repo with
|
|
89
|
+
no `areas:` is one area.
|
|
90
|
+
2. Some areas hold outside text, such as email. In `atlas.yaml` such an
|
|
91
|
+
area says `outside_text: true`. A session must hold no
|
|
89
92
|
tool that reads that outside text until Atlas has its outside-text
|
|
90
93
|
reader. If the session holds such a tool, stop and say so.
|
|
91
94
|
3. Until then, work in such an area uses made-up test data only.
|
|
@@ -100,10 +103,9 @@ set. A test keeps the two lists equal.
|
|
|
100
103
|
| `atlas install` | Once on a new Mac, when the owner asks for Atlas there |
|
|
101
104
|
| `atlas upgrade` | When the owner asks to upgrade Atlas. Call the `atlas_upgrade` tool instead: the Bash sandbox blocks this command. Run the command by hand only where that tool is missing. In a cloud session, call the tool with `check: true` at the first reply, as section 7 says |
|
|
102
105
|
| `atlas doctor` | When Atlas seems missing or broken in a session |
|
|
103
|
-
| `atlas check` | Before you ask for a merge in a repo that has the shape check |
|
|
106
|
+
| `atlas check` | Before you ask for a merge in a repo that has the shape check. `--json` prints the counts of listed parts, procedures and jobs |
|
|
104
107
|
| `atlas status` | To learn whether a repo's check is current or behind. `atlas status --all --json <repo>` lists each Atlas copy in a repo, with have, want and state |
|
|
105
108
|
| `atlas tooling` | When `atlas status` says behind; `pr` opens the pull request |
|
|
106
|
-
| `atlas kind-drift` | When the repo record and `atlas.yaml` may disagree on the kind; this command retires in stage 2, the onboarding stage |
|
|
107
109
|
| `atlas code-read` | To resolve the citations of a code digest |
|
|
108
110
|
| `atlas ste` | Before each publish; `--share` before a page goes to anyone else |
|
|
109
111
|
| `atlas tests` | `write` puts the test kit in an area. `check` runs before a merge. `kept` runs the kept tests of a path 1 area and writes their evidence. `proof`, `verdict`, `named`, `covers` and `rerun` read the results of a run. `changed`, `before` and `after` run the test checks of the gate: changed tests, new cases fail on old code, old cases pass on new code. `sweep` and `issues` run the daily sweep |
|
|
@@ -112,7 +114,7 @@ set. A test keeps the two lists equal.
|
|
|
112
114
|
| `atlas jobs` | The `atlas-jobs` workflow calls `check`, then `file`, once a day. A session calls it only to see what the watcher would file: `check`, then `file --dry-run` |
|
|
113
115
|
| `atlas upkeep` | `check` before you ask for a merge. It reads the docs index, dead doc links, the code each doc names and stale pages. In a pull request it also reads the tracker item, the changed designs, and STE on the changed lines. The daily upkeep run calls it on the main branch. `replay` before the doc-sources rule changes how it gates. `procedures` runs the procedure steps tagged `atlas-run`; CI runs it with `--base` on each pull request |
|
|
114
116
|
| `atlas gate` | `apply` once per repo, by the owner on his Mac, after the repo's `required_checks` exist. `--dry-run` first. `--off` and `--on` are the break glass. On a plan with no rulesets it reports advisory mode. `ready` before each merge, `check` in the `atlas-gate` workflow, `replay` before a change to the owner-only list. `audit` in the daily upkeep run, and with `--commit` in the push job of a repo that has one |
|
|
115
|
-
| `atlas approve` | Never from a session. Only the owner runs it, on his Mac, with his key's touch. When the gate says "owner" for a pull request, ask him once for the signed act: name each pull request, its class and its cause. Put the owner pull requests of one day in one ask, so one touch signs them. `act`, `verify` and `key-check` work the same way. Each morning with open audit issues, ask him for `audit
|
|
117
|
+
| `atlas approve` | Never from a session. Only the owner runs it, on his Mac, with his key's touch. When the gate says "owner" for a pull request, ask him once for the signed act: name each pull request, its class and its cause. Put the owner pull requests of one day in one ask, so one touch signs them. `act`, `verify` and `key-check` work the same way. Each morning with open audit issues, ask him for `audit`, and `audit-code <pr>` per audit-code merge; record its words as one decision, then close each issue |
|
|
116
118
|
| `atlas judge` | In the fresh-agent test only, as the `atlas:fresh-agent-test` skill says. `seal` after the pick, `brief` to read a sealed ask, `run` for each sealed task, `check` after the grade |
|
|
117
119
|
|
|
118
120
|
A person merges every file that `atlas tooling` writes.
|
package/work/lib/delivery.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import { refuse, text, isoDate, url, stringList, sha256, repoKinds, LIVE_KINDS }
|
|
|
16
16
|
import { oneLine, ROW_LINE_MAX, priorityOrderOf, byPriority, orderNote, waitsOnLine, blockersOf, blocksOf, blockedPhrase, waitsShown } from './questions.mjs';
|
|
17
17
|
import { parseYaml } from '../../shape/check.mjs';
|
|
18
18
|
import { ACTIONS_APP_ID } from '../../door/lib/gate.mjs';
|
|
19
|
-
import { MARKER as SIGNED_MARKER, SIGNERS_FILE, parseSigners, verifyTexts, findAct, wordsFingerprint, AUDIT_CHECK, AUDIT_CHECKS, AUDIT_TEST, preKeyKeys } from '../../door/lib/signed-act.mjs';
|
|
19
|
+
import { MARKER as SIGNED_MARKER, SIGNERS_FILE, parseSigners, verifyTexts, findAct, wordsFingerprint, AUDIT_CHECK, AUDIT_CHECKS, AUDIT_TEST, AUDIT_CODE_TEST, auditCodeDiff, preKeyKeys } from '../../door/lib/signed-act.mjs';
|
|
20
20
|
|
|
21
21
|
export const DELIVERY_WRITE_VERBS = Object.freeze(['item_done', 'item_reopen']);
|
|
22
22
|
export const DELIVERY_READ_VERBS = Object.freeze(['in_flight']);
|
|
@@ -467,8 +467,22 @@ export function createDeliveryVerbs(core) {
|
|
|
467
467
|
// act for the push audit red may count, against the keys at the tip of
|
|
468
468
|
// the default branch (preKeyKeys in signed-act.mjs holds the limits). The
|
|
469
469
|
// audit runs only on the default branch, so the merge must be into it.
|
|
470
|
+
// Item f36c6b0f: the red audit check of a merge that changed the audit
|
|
471
|
+
// code takes only an accept-code act, which names the merge and the
|
|
472
|
+
// fingerprint of its audit-code change. An accept act never takes such
|
|
473
|
+
// a merge: the batch of atlas approve audit does not show its diff.
|
|
474
|
+
const auditRed = check === AUDIT_CHECK && testName === AUDIT_TEST;
|
|
475
|
+
const codeRed = AUDIT_CHECKS.includes(check) && testName === AUDIT_CODE_TEST;
|
|
476
|
+
let code = null;
|
|
477
|
+
if (auditRed || codeRed) {
|
|
478
|
+
code = auditCodeDiff(commit?.files);
|
|
479
|
+
if (code.unknown) refuse(`GitHub gave no full file list of the merge commit of ${pull.url}, so no accept act can be checked`);
|
|
480
|
+
if (auditRed && code.files.length) refuse(`${pull.url} changed the audit code (${code.files.join(', ')}), so an accept act does not count; the owner reads that diff and signs with atlas approve audit-code ${pull.number}, and the test is "${AUDIT_CODE_TEST}"`);
|
|
481
|
+
if (codeRed && (!Array.isArray(commit?.parents) || commit.parents.length !== 2)) refuse(`${pull.url} did not merge with a merge commit, so its audit-code diff is not its whole change; an accept-code act does not count`);
|
|
482
|
+
if (codeRed && !code.files.length) refuse(`${pull.url} changed no audit code, so the test "${AUDIT_CODE_TEST}" does not apply; the owner signs with atlas approve audit`);
|
|
483
|
+
}
|
|
470
484
|
let prekey = null;
|
|
471
|
-
if (!keys.length &&
|
|
485
|
+
if (!keys.length && (auditRed || codeRed)) {
|
|
472
486
|
const main = (await github(`repos/${pull.repo}`))?.default_branch;
|
|
473
487
|
prekey = typeof main === 'string' && main === pull.base
|
|
474
488
|
? await preKeyKeys(githubOrNull, pull.repo, { parent, branch: main })
|
|
@@ -491,9 +505,11 @@ export function createDeliveryVerbs(core) {
|
|
|
491
505
|
// cannot carry the touch to items the owner never named (review S2). The
|
|
492
506
|
// words hold the time of the list, and a decision in any state counts,
|
|
493
507
|
// so a replaced decision cannot hand the touch on (review 2, S3).
|
|
494
|
-
if (!found && words.trim() &&
|
|
508
|
+
if (!found && words.trim() && (auditRed || codeRed)) {
|
|
495
509
|
const print = wordsFingerprint(words);
|
|
496
|
-
found =
|
|
510
|
+
found = auditRed
|
|
511
|
+
? findAct(verified, { kind: 'accept', repo: pull.repo, pr: pull.number, head, words: print, check })
|
|
512
|
+
: findAct(verified, { kind: 'accept-code', repo: pull.repo, pr: pull.number, head, merge: String(pull.merge_commit_sha).toLowerCase(), diff: code.print, words: print, check });
|
|
497
513
|
if (found) {
|
|
498
514
|
const same = decisions.filter((row) => typeof row.fields?.text === 'string' && wordsFingerprint(row.fields.text) === print);
|
|
499
515
|
if (same.length !== 1 || same[0].id !== decisionId) refuse(`the accept act on ${pull.url} matches the words of ${same.length} decisions; exactly one decision, in any state, may hold the words the owner signed`);
|
|
@@ -501,9 +517,9 @@ export function createDeliveryVerbs(core) {
|
|
|
501
517
|
}
|
|
502
518
|
if (!found) {
|
|
503
519
|
const why = verified.find((row) => row.error)?.error;
|
|
504
|
-
refuse(`accepted_red needs the owner's signed waiver on ${pull.url} for check ${check}, head ${head.slice(0, 12)} and decision ${decisionId}; none counts${why ? ` (a block was refused: ${why})` : ''}. The owner runs: atlas approve act waiver, and posts the block on the pull request; or, for an audit red, atlas approve audit, and the decision holds the words it printed`);
|
|
520
|
+
refuse(`accepted_red needs the owner's signed waiver on ${pull.url} for check ${check}, head ${head.slice(0, 12)} and decision ${decisionId}; none counts${why ? ` (a block was refused: ${why})` : ''}. The owner runs: atlas approve act waiver, and posts the block on the pull request; or, for an audit red, atlas approve audit (atlas approve audit-code <pr> for a merge that changed the audit code), and the decision holds the words it printed`);
|
|
505
521
|
}
|
|
506
|
-
return { key: found.key, signed_at: found.signed_at, act: found.act.kind, ...(found.act.kind
|
|
522
|
+
return { key: found.key, signed_at: found.signed_at, act: found.act.kind, ...(['accept', 'accept-code'].includes(found.act.kind) ? { run: String(found.act.run) } : {}), ...(prekey?.from ? { first_key: prekey.from } : {}) };
|
|
507
523
|
}
|
|
508
524
|
|
|
509
525
|
async function acceptedRed(value, pulls, item) {
|
package/door/lib/kind-drift.mjs
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
// The facts helper's kind drift report.
|
|
2
|
-
//
|
|
3
|
-
// Milestone M2, slice L2 stage one: "The facts helper compares the `kind` in
|
|
4
|
-
// `atlas.yaml` with the `kind` on the repo record and reports drift."
|
|
5
|
-
//
|
|
6
|
-
// The kind decides which of the six files a repository needs, so the two
|
|
7
|
-
// copies disagreeing is not a detail. A repo record that says `service` while
|
|
8
|
-
// atlas.yaml says `library` means four required parts read "not applicable" in
|
|
9
|
-
// CI and "required" everywhere else.
|
|
10
|
-
//
|
|
11
|
-
// Which of the two is the source is open question O-9, and the owner settles it
|
|
12
|
-
// before slice L2 ends. This report is neutral on that: it names both values
|
|
13
|
-
// and says they differ. It never picks a winner, and it never writes.
|
|
14
|
-
//
|
|
15
|
-
// Where the record comes from. Reading it live needs the Atlas work verbs and
|
|
16
|
-
// an Engram owner token, which CI does not have and this module does not hold.
|
|
17
|
-
// So the record's kind is an input. Pass one repo's kind, or the JSON the
|
|
18
|
-
// `registry_list` verb returns.
|
|
19
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
20
|
-
import { join } from 'node:path';
|
|
21
|
-
import { parseYaml, SPECS } from '../../shape/check.mjs';
|
|
22
|
-
import { REPO_KINDS, repoKinds } from '../../work/lib/verb-fields.mjs';
|
|
23
|
-
|
|
24
|
-
// Stage 1: the repo record holds a list of kinds, and atlas.yaml one kind.
|
|
25
|
-
// They agree when the list holds the file's kind. This report retires in
|
|
26
|
-
// stage 2, with the fixed kind tables.
|
|
27
|
-
export const KINDS = REPO_KINDS;
|
|
28
|
-
const asList = (value) => (value === null || value === undefined ? null : (Array.isArray(value) ? value : String(value).split(',').map((one) => one.trim()).filter(Boolean)));
|
|
29
|
-
|
|
30
|
-
// The kind a repository's own file declares, with the repo id beside it.
|
|
31
|
-
export function fileKind(root) {
|
|
32
|
-
const path = join(root, 'atlas.yaml');
|
|
33
|
-
if (!existsSync(path)) return { kind: null, repo: null, error: 'atlas.yaml does not exist' };
|
|
34
|
-
try {
|
|
35
|
-
const facts = parseYaml(readFileSync(path, 'utf8'));
|
|
36
|
-
return { kind: facts?.kind ?? null, repo: facts?.repo ?? null, spec: facts?.spec ?? null, error: null };
|
|
37
|
-
} catch (error) {
|
|
38
|
-
// The parse error names a line and a column and never the content. That is
|
|
39
|
-
// the rule the whole check follows (amendment A1.6, section 4.9).
|
|
40
|
-
return { kind: null, repo: null, error: `atlas.yaml cannot be read: ${error.message}` };
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// One repository, one verdict.
|
|
45
|
-
export function driftOne({ root, recordKind = null, recordRepo = null }) {
|
|
46
|
-
const file = fileKind(root);
|
|
47
|
-
const known = (kind) => kind === null || KINDS.includes(kind);
|
|
48
|
-
const recordKinds = asList(recordKind);
|
|
49
|
-
recordKind = recordKinds && recordKinds.length ? recordKinds.join(', ') : null;
|
|
50
|
-
const row = {
|
|
51
|
-
root,
|
|
52
|
-
repo: file.repo ?? recordRepo ?? null,
|
|
53
|
-
file_kind: file.kind,
|
|
54
|
-
record_kind: recordKind,
|
|
55
|
-
spec: file.spec ?? null,
|
|
56
|
-
state: 'unknown',
|
|
57
|
-
why: '',
|
|
58
|
-
};
|
|
59
|
-
if (file.error) { row.state = 'unreadable'; row.why = file.error; return row; }
|
|
60
|
-
if (!known(file.kind)) { row.state = 'unreadable'; row.why = `atlas.yaml names kind "${file.kind}"; a kind is one of ${KINDS.join(', ')}`; return row; }
|
|
61
|
-
if (recordKind === null) { row.state = 'unknown'; row.why = 'no repo record kind was given, so there is nothing to compare with. Read it with the registry_list verb and pass it in.'; return row; }
|
|
62
|
-
const strange = recordKinds.find((kind) => !known(kind));
|
|
63
|
-
if (strange) { row.state = 'unreadable'; row.why = `the repo record names kind "${strange}"; a kind is one of ${KINDS.join(', ')}`; return row; }
|
|
64
|
-
if (file.kind === null) { row.state = 'drift'; row.why = `the repo record says "${recordKind}" and atlas.yaml names no kind at all, so CI cannot tell which files this repo needs`; return row; }
|
|
65
|
-
if (recordKinds.includes(file.kind)) { row.state = 'agree'; row.why = recordKinds.length === 1 ? `both say "${file.kind}"` : `the repo record's kinds (${recordKind}) hold "${file.kind}"`; return row; }
|
|
66
|
-
row.state = 'drift';
|
|
67
|
-
row.why = `atlas.yaml says "${file.kind}" and the repo record says "${recordKind}". The kind decides which parts are required, so the check in CI and the work verbs are scoring two different repos. Open question O-9 says which one is the source.`;
|
|
68
|
-
return row;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// The registry the `registry_list` verb returns, as { repoId: kinds }. A
|
|
72
|
-
// row from before stage 1 has only kind, and reads as a list of one.
|
|
73
|
-
export function kindsFromRegistry(registry) {
|
|
74
|
-
const out = {};
|
|
75
|
-
const kindsOf = (repo) => { const list = repoKinds(repo); return list.length ? list : null; };
|
|
76
|
-
for (const product of registry?.products ?? []) {
|
|
77
|
-
for (const repo of product?.repos ?? []) if (repo?.registry_id) out[repo.registry_id] = kindsOf(repo);
|
|
78
|
-
}
|
|
79
|
-
for (const repo of registry?.repos ?? []) if (repo?.registry_id) out[repo.registry_id] = kindsOf(repo);
|
|
80
|
-
return out;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function drift(rows) {
|
|
84
|
-
const out = rows.map(driftOne);
|
|
85
|
-
return { rows: out, drifting: out.filter((row) => row.state === 'drift').length, specs_read: Object.keys(SPECS).map(Number) };
|
|
86
|
-
}
|