@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.
@@ -7,7 +7,8 @@
7
7
  //
8
8
  // 1. Docs index. docs/index.md lists each .md file under docs/. A file in a
9
9
  // design folder (a folder that holds design.yaml) counts as listed when the
10
- // index lists that folder's design.yaml. Each listed path exists.
10
+ // index lists that folder's design.yaml. Each listed path exists. The rule
11
+ // is the shape check's own, docsIndexRule (roadmap R62 line 5).
11
12
  // 2. Dead links. Each relative link in a tracked .md file names a file or a
12
13
  // folder that exists. archive/ is frozen, and test fixtures are planted on
13
14
  // purpose, so both are skipped; so is text inside code.
@@ -24,8 +25,9 @@
24
25
  // and a stale page is listed.
25
26
  //
26
27
  // It reads the tree and the event file, and writes nothing.
27
- import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
28
- import { join, dirname, posix, resolve } from 'node:path';
28
+ import { readFileSync, existsSync } from 'node:fs';
29
+ import { join, dirname, resolve } from 'node:path';
30
+ import { DOCS_INDEX_PATH, docsIndexRule, markdownFiles, proseOf, linkTargets } from '../../shape/check.mjs';
29
31
  import { listChanged } from './changed-tests.mjs';
30
32
  import { docSourceFindings, replay } from './doc-sources.mjs';
31
33
  import { proceduresCommand } from './procedure-steps.mjs';
@@ -37,62 +39,13 @@ import { changedDocFindings } from './changed-docs.mjs';
37
39
  // touched doc with no update and no mark a note, not a finding.
38
40
  export const TOUCHED_DOCS_GATE = true;
39
41
 
40
- const INDEX = 'docs/index.md';
41
- const SKIP_DIRS = new Set(['node_modules', 'archive', 'fixtures']);
42
+ const INDEX = DOCS_INDEX_PATH;
42
43
  const MAX_LISTED = 20;
43
44
 
44
- // Every .md file under the root, as a posix path. Dot folders, node_modules,
45
- // archive/ and any folder named fixtures are skipped.
46
- export function markdownFiles(root, under = '') {
47
- const out = [];
48
- const walk = (rel) => {
49
- let entries;
50
- try { entries = readdirSync(join(root, rel), { withFileTypes: true }); } catch { return; }
51
- for (const entry of entries) {
52
- if (entry.name.startsWith('.')) continue;
53
- const path = rel ? `${rel}/${entry.name}` : entry.name;
54
- if (entry.isDirectory()) { if (!SKIP_DIRS.has(entry.name)) walk(path); }
55
- else if (entry.isFile() && entry.name.endsWith('.md')) out.push(path);
56
- }
57
- };
58
- walk(under);
59
- return out.sort();
60
- }
61
-
62
- // Text outside code: fenced blocks of three or more marks, and inline code.
63
- export function proseOf(text) {
64
- const lines = String(text ?? '').split('\n');
65
- const kept = [];
66
- let fence = null;
67
- for (const one of lines) {
68
- const open = one.match(/^\s*(`{3,}|~{3,})/);
69
- if (fence) {
70
- if (open && open[1][0] === fence[0] && open[1].length >= fence.length && !one.trim().slice(open[1].length).trim()) fence = null;
71
- kept.push('');
72
- continue;
73
- }
74
- if (open) { fence = open[1]; kept.push(''); continue; }
75
- kept.push(one.replace(/`+[^`\n]*`+/g, ''));
76
- }
77
- return kept.join('\n');
78
- }
79
-
80
- // The relative link targets in one markdown text, with their line numbers.
81
- export function linkTargets(text) {
82
- const out = [];
83
- proseOf(text).split('\n').forEach((one, index) => {
84
- for (const [, angled, bare] of one.matchAll(/\]\(\s*(?:<([^>\n]+)>|([^)\s]+))(?:\s+"[^"]*")?\s*\)/g)) {
85
- const raw = angled ?? bare;
86
- const target = raw.split('#')[0].split('?')[0];
87
- if (!target || /^[a-z][a-z0-9+.-]*:/i.test(raw) || raw.startsWith('#')) continue;
88
- if (/[{}$*\\]/.test(target)) continue;
89
- let decoded = target;
90
- try { decoded = decodeURIComponent(target); } catch { /* keep it as written */ }
91
- out.push({ target: decoded, line: index + 1 });
92
- }
93
- });
94
- return out;
95
- }
45
+ // markdownFiles, proseOf and linkTargets live in the shape check, beside the
46
+ // one docs index rule (roadmap R62 line 5). They stay exported here, so their
47
+ // readers keep one door.
48
+ export { markdownFiles, proseOf, linkTargets };
96
49
 
97
50
  export function deadLinks(root) {
98
51
  const dead = [];
@@ -106,32 +59,11 @@ export function deadLinks(root) {
106
59
  return dead;
107
60
  }
108
61
 
109
- // The nearest folder at or above `file` that holds design.yaml, or null.
110
- function designFolderOf(root, file) {
111
- let dir = posix.dirname(file);
112
- while (dir && dir !== '.' && dir !== 'docs') {
113
- if (existsSync(join(root, dir, 'design.yaml'))) return dir;
114
- dir = posix.dirname(dir);
115
- }
116
- return null;
117
- }
118
-
62
+ // The docs index rule is the shape check's, so the check and this command
63
+ // give one count on one tree (roadmap R62 line 5).
119
64
  export function docsIndex(root) {
120
- if (!existsSync(join(root, 'docs')) || !statSync(join(root, 'docs')).isDirectory()) return { checked: false, unlisted: [], gone: [] };
121
- if (!existsSync(join(root, INDEX))) return { checked: true, missing: true, unlisted: markdownFiles(root, 'docs'), gone: [] };
122
- const listed = new Set();
123
- for (const { target } of linkTargets(readFileSync(join(root, INDEX), 'utf8'))) {
124
- if (target.startsWith('/')) continue;
125
- const path = posix.normalize(posix.join('docs', target)).replace(/\/$/, '');
126
- if (path.startsWith('docs/')) listed.add(path);
127
- }
128
- const unlisted = markdownFiles(root, 'docs').filter((path) => {
129
- if (path === INDEX || listed.has(path)) return false;
130
- const folder = designFolderOf(root, path);
131
- return !(folder && listed.has(`${folder}/design.yaml`));
132
- });
133
- const gone = [...listed].filter((path) => !existsSync(join(root, path))).sort();
134
- return { checked: true, unlisted, gone };
65
+ const { checked, missing, unlisted, gone } = docsIndexRule(root);
66
+ return missing ? { checked, missing, unlisted, gone } : { checked, unlisted, gone };
135
67
  }
136
68
 
137
69
  const UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
package/owner-only.yaml CHANGED
@@ -218,22 +218,30 @@ floor:
218
218
  # does not follow names out of it (shape/check.mjs, the exemption).
219
219
  entry_map: docs/index.md
220
220
 
221
- # The keys of atlas.yaml whose change is always owner-only. A change to any
222
- # other key is owner-only too, until roadmap R62 lists the safe ones. The one
223
- # change that passes is an addition under `parts:`.
221
+ # The key rule for atlas.yaml (roadmap R2 line 11, R62 line 3). Each key of
222
+ # atlas.yaml is on one of the two lists. A pull request may add entries under
223
+ # an add_only key and merge on green; any other change to it, and any change
224
+ # to a fixed key, comes to the owner. A key on neither list comes to the owner
225
+ # too, until a pull request puts it on one.
224
226
  atlas_yaml:
225
227
  add_only:
226
228
  - parts
229
+ - procedures
230
+ - files
231
+ - kinds_of_change
227
232
  fixed:
228
- - owner_only
233
+ - spec
234
+ - product
235
+ - repo
229
236
  - areas
237
+ - owner_only
230
238
  - required_checks
231
- - product
232
239
  - product_paths
233
- - repo
234
- - kind
235
- - kinds
236
240
  - up_to_date
237
241
  - brake
242
+ - live
243
+ - jobs
244
+ - design
238
245
  - generated
239
- - spec
246
+ - verbatim
247
+ - private_terms
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arjunkhera/atlas",
3
- "version": "0.4.45",
3
+ "version": "0.4.47",
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",