@arjunkhera/atlas 0.3.46 → 0.3.49
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 +10 -3
- package/door/lib/fails-before.mjs +65 -9
- package/door/lib/owner-only.mjs +778 -0
- package/door/lib/passes-after.mjs +212 -0
- package/door/lib/pull-request.mjs +3 -2
- package/door/lib/tooling.mjs +34 -10
- package/door/lib/upkeep.mjs +47 -6
- package/door/templates/pull_request_template.md +25 -0
- package/owner-only.yaml +238 -0
- package/package.json +3 -2
- package/shape/check.mjs +44 -3
- package/shape/releases.json +5 -0
- package/skills/lead/SKILL.md +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arjunkhera/atlas",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.49",
|
|
4
4
|
"description": "Atlas: the delivery lifecycle, its crews and the Atlas tools, as a Claude Code plugin for any repository.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"agents/",
|
|
27
27
|
"hooks/",
|
|
28
28
|
".claude-plugin/",
|
|
29
|
-
".mcp.json"
|
|
29
|
+
".mcp.json",
|
|
30
|
+
"owner-only.yaml"
|
|
30
31
|
],
|
|
31
32
|
"peerDependencies": {
|
|
32
33
|
"playwright": ">=1.40.0"
|
package/shape/check.mjs
CHANGED
|
@@ -34,7 +34,7 @@ export const sha256 = (text) => createHash('sha256').update(String(text ?? ''),
|
|
|
34
34
|
// Whether a copy is current or behind is not decided here. CI has no package
|
|
35
35
|
// and no network. The session verb decides it, from the release list that
|
|
36
36
|
// ships beside this file in the package, shape/releases.json.
|
|
37
|
-
export const CHECK_VERSION =
|
|
37
|
+
export const CHECK_VERSION = 5;
|
|
38
38
|
|
|
39
39
|
// ---------------------------------------------------------------------------
|
|
40
40
|
// The spec. One object per version. The check reads a version it has here and
|
|
@@ -774,7 +774,11 @@ function indentOf(line) {
|
|
|
774
774
|
return line.length - line.trimStart().length;
|
|
775
775
|
}
|
|
776
776
|
|
|
777
|
-
|
|
777
|
+
// `strict` is for atlas.yaml. It refuses a key that appears twice, an anchor,
|
|
778
|
+
// an alias and a merge key. Each one lets two readers see two different maps
|
|
779
|
+
// in one file, and the owner-only reader and this check must see the same one
|
|
780
|
+
// (security review of the owner-only list, 24 October 2026).
|
|
781
|
+
export function parseYaml(source, { strict = false } = {}) {
|
|
778
782
|
const raw = String(source ?? '').replace(/\r\n?/g, '\n').split('\n');
|
|
779
783
|
const lines = [];
|
|
780
784
|
for (let index = 0; index < raw.length; index += 1) {
|
|
@@ -825,6 +829,7 @@ export function parseYaml(source) {
|
|
|
825
829
|
const body = line.text.trimStart();
|
|
826
830
|
if (!body.startsWith('- ') && body !== '-') break;
|
|
827
831
|
const rest = body === '-' ? '' : body.slice(2);
|
|
832
|
+
if (strict && /^[&*]/.test(rest.trim())) throw new YamlError('an anchor or an alias is not read in strict mode', line.number, line.indent);
|
|
828
833
|
state.at += 1;
|
|
829
834
|
if (!rest.trim()) { out.push(value(indent + 1)); continue; }
|
|
830
835
|
const match = KEY.exec(rest);
|
|
@@ -851,6 +856,11 @@ export function parseYaml(source) {
|
|
|
851
856
|
if (!match) throw new YamlError('this line is neither a key nor a list entry', line.number, line.indent);
|
|
852
857
|
const key = match[1] !== undefined ? match[1] : match[2] !== undefined ? match[2].replaceAll("''", "'") : match[3].trim();
|
|
853
858
|
const rest = (match[4] ?? '').trim();
|
|
859
|
+
if (strict) {
|
|
860
|
+
if (Object.hasOwn(out, key)) throw new YamlError(`the key "${key}" appears twice`, line.number, line.indent);
|
|
861
|
+
if (key === '<<' || /^[&*]/.test(key)) throw new YamlError('a merge key, an anchor or an alias is not read in strict mode', line.number, line.indent);
|
|
862
|
+
if (/^[&*]/.test(rest)) throw new YamlError('an anchor or an alias is not read in strict mode', line.number, line.indent);
|
|
863
|
+
}
|
|
854
864
|
state.at += 1;
|
|
855
865
|
if (/^[|>][-+]?\d*$/.test(rest)) { out[key] = blockScalar(rest, indent); continue; }
|
|
856
866
|
if (rest && !rest.startsWith('#')) { out[key] = scalar(rest, line.number); continue; }
|
|
@@ -1428,11 +1438,41 @@ function byReference(context, spec) {
|
|
|
1428
1438
|
return applyExemption([...found.values()].sort((a, b) => a.path.localeCompare(b.path)));
|
|
1429
1439
|
}
|
|
1430
1440
|
|
|
1441
|
+
// The eight classes of the owner-only list. The authority is the package's
|
|
1442
|
+
// owner-only.yaml, and a test keeps the two equal. This copy exists because the
|
|
1443
|
+
// check runs alone in each repo's CI, where the package is not there.
|
|
1444
|
+
export const OWNER_ONLY_CLASSES = Object.freeze(['money', 'credentials', 'outside-visibility', 'lost-data', 'security', 'new-service', 'rule-book', 'direction']);
|
|
1445
|
+
|
|
1446
|
+
const stringList = (value) => Array.isArray(value) && value.every((entry) => typeof entry === 'string' && entry.trim() !== '');
|
|
1447
|
+
|
|
1448
|
+
// The three keys of the owner-only list in atlas.yaml. Each is optional. When
|
|
1449
|
+
// one is there, its shape is checked, and an unknown class is an error.
|
|
1450
|
+
export function ownerOnlyErrors(facts) {
|
|
1451
|
+
const errors = [];
|
|
1452
|
+
if (!facts || typeof facts !== 'object') return errors;
|
|
1453
|
+
if (Object.hasOwn(facts, 'owner_only')) {
|
|
1454
|
+
const list = facts.owner_only;
|
|
1455
|
+
if (!Array.isArray(list)) errors.push('atlas.yaml: owner_only is not a list');
|
|
1456
|
+
else list.forEach((entry, index) => {
|
|
1457
|
+
const at = `atlas.yaml: owner_only[${index}]`;
|
|
1458
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { errors.push(`${at} is not a map with a path and a class`); return; }
|
|
1459
|
+
for (const key of Object.keys(entry)) if (!['path', 'class', 'why', 'except'].includes(key)) errors.push(`${at} has the key "${key}"; the keys are path, class, why and except`);
|
|
1460
|
+
if (typeof entry.path !== 'string' || !entry.path.trim()) errors.push(`${at} has no path`);
|
|
1461
|
+
if (!OWNER_ONLY_CLASSES.includes(entry.class)) errors.push(`${at}.class "${entry.class}" is not a class of owner-only.yaml; the classes are ${OWNER_ONLY_CLASSES.join(', ')}`);
|
|
1462
|
+
if (entry.except !== undefined && !stringList(entry.except)) errors.push(`${at}.except is not a list of paths`);
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
for (const key of ['required_checks', 'product_paths']) {
|
|
1466
|
+
if (Object.hasOwn(facts, key) && !stringList(facts[key])) errors.push(`atlas.yaml: ${key} is not a list of names`);
|
|
1467
|
+
}
|
|
1468
|
+
return errors;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1431
1471
|
export function readFacts(root) {
|
|
1432
1472
|
const text = readText(root, 'atlas.yaml');
|
|
1433
1473
|
if (text === null || !text.trim()) return { facts: null, error: null };
|
|
1434
1474
|
try {
|
|
1435
|
-
const data = parseYaml(text);
|
|
1475
|
+
const data = parseYaml(text, { strict: true });
|
|
1436
1476
|
if (!data || typeof data !== 'object' || Array.isArray(data)) return { facts: null, error: 'atlas.yaml is not a map of keys' };
|
|
1437
1477
|
return { facts: data, error: null };
|
|
1438
1478
|
} catch (error) { return { facts: null, error: `atlas.yaml cannot be read: ${error.message}` }; }
|
|
@@ -1460,6 +1500,7 @@ export function scan({ root, repoId = null, extractors = [], secretPatterns: pat
|
|
|
1460
1500
|
if (missed.length) errors.push(`${missed.length} secret pattern(s) no longer catch their own canary: ${missed.join(', ')}; this check cannot be trusted`);
|
|
1461
1501
|
if (!facts && !factsError) errors.push('atlas.yaml does not exist, so the check cannot learn the spec version or the repo kind');
|
|
1462
1502
|
if (factsError) errors.push(factsError);
|
|
1503
|
+
errors.push(...ownerOnlyErrors(facts));
|
|
1463
1504
|
const kind = scoreAs ? scoreAs.kind ?? null : facts?.kind ?? null;
|
|
1464
1505
|
const wantedSpec = scoreAs ? scoreAs.spec : facts?.spec;
|
|
1465
1506
|
let spec = null;
|
package/shape/releases.json
CHANGED
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
"check_version": 4,
|
|
21
21
|
"sha256": "3357ea247694634c3535998368683eceffaf863221dee5fec9e43a03042afb95",
|
|
22
22
|
"first_package": "0.3.36"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"check_version": 5,
|
|
26
|
+
"sha256": "8f36633fe693ebf7a7cc0f7237d49eb4720f67d371d4758d556bab51965ec334",
|
|
27
|
+
"first_package": "0.3.48"
|
|
23
28
|
}
|
|
24
29
|
]
|
|
25
30
|
}
|
package/skills/lead/SKILL.md
CHANGED
|
@@ -101,7 +101,7 @@ set. A test keeps the two lists equal.
|
|
|
101
101
|
| `atlas kind-drift` | When the repo record and `atlas.yaml` may disagree on the kind; this command retires in stage 2, the onboarding stage |
|
|
102
102
|
| `atlas code-read` | To resolve the citations of a code digest |
|
|
103
103
|
| `atlas ste` | Before each publish; `--share` before a page goes to anyone else |
|
|
104
|
-
| `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` and `
|
|
104
|
+
| `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 |
|
|
105
105
|
| `atlas design` | `folder` to learn where designs live; `check` before you show a design; `build` to make its page |
|
|
106
106
|
| `atlas view` | `build --product <id>` makes the project view page of one product, when the owner asks for it or after an Atlas release changes it. Publish it with only the grant the command prints |
|
|
107
107
|
| `atlas upkeep` | `check` before you ask for a merge: the docs index, dead doc links, and the tracker item a pull request names. The daily upkeep run calls it on the main branch |
|