@arjunkhera/atlas 0.4.5 → 0.4.6

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlas",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "description": "Atlas: the delivery lifecycle, its crews and the Atlas tools, for any repository.",
5
5
  "author": {
6
6
  "name": "Arjun Khera"
@@ -17,6 +17,7 @@
17
17
  // It reads the tree and the event file, and writes nothing.
18
18
  import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
19
19
  import { join, dirname, posix, resolve } from 'node:path';
20
+ import { listChanged } from './changed-tests.mjs';
20
21
 
21
22
  const INDEX = 'docs/index.md';
22
23
  const SKIP_DIRS = new Set(['node_modules', 'archive', 'fixtures']);
@@ -155,6 +156,41 @@ export function prTextFindings(body) {
155
156
  return findings;
156
157
  }
157
158
 
159
+ // The rows of the changed-test list that a person must name under "Changed
160
+ // tests": test cases and scenario words. A helper, a runner config or the kit
161
+ // manifest (whose version moves in each release) may be named, but need not be.
162
+ const NAMED_GROUPS = new Set(['unit-test', 'check-block', 'scenario-words']);
163
+
164
+ // "Changed tests" against the changed-test list of the pull request (roadmap
165
+ // R8 line 4, design docs/design-docs/pr-template/). The list comes from git,
166
+ // between the base and the head commits of the event. With no list, the
167
+ // section says "none"; with a list, it names the path of each row.
168
+ export function changedTestsFindings(body, rows) {
169
+ const section = prSections(body).get('changed tests');
170
+ if (section === undefined) return [];
171
+ const paths = [...new Set(rows.filter((row) => NAMED_GROUPS.has(row.group)).map((row) => row.path))].sort();
172
+ // An empty list needs a section that opens with the word None.
173
+ if (!paths.length) return /^\s*none\b/i.test(section) ? [] : ['pr text: "Changed tests" names tests, but the changed-test list is empty; write "None."'];
174
+ // A path counts only as a whole name: `src/a.test.mjs.old` does not name `src/a.test.mjs`.
175
+ const named = (path) => new RegExp(`(^|[^\\w./-])${path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}($|\\.(?![\\w/-])|[^\\w./-])`).test(section);
176
+ const missing = paths.filter((path) => !named(path));
177
+ if (!missing.length) return [];
178
+ return [`pr text: "Changed tests" does not name ${missing.length} changed test file(s): ${missing.slice(0, MAX_LISTED).join(', ')}`];
179
+ }
180
+
181
+ // The changed-test list of the event's pull request, or why there is none.
182
+ // A root that is no git work tree is not checked. A git tree that lacks a
183
+ // commit of the event is an error, because the check in CI fetches them all.
184
+ function changedRows(root, pr) {
185
+ const base = pr?.base?.sha;
186
+ const head = pr?.head?.sha;
187
+ if (!base || !head) return { checked: false, why: 'the event names no base and head commit' };
188
+ if (!existsSync(join(root, '.git'))) return { checked: false, why: 'the root is no git work tree' };
189
+ try { return { checked: true, rows: listChanged({ root, base, head }).rows }; } catch (error) {
190
+ return { checked: true, error: error.message };
191
+ }
192
+ }
193
+
158
194
  // Does the pull request name its tracker item? Read from a GitHub event file.
159
195
  export function itemNamed(event) {
160
196
  const pr = event?.pull_request;
@@ -188,7 +224,12 @@ export function upkeepCheck(root, { event = null } = {}) {
188
224
  // The heading check holds a pull request that names an item, in a repo that
189
225
  // keeps the Atlas template. A repo without the template is not held yet.
190
226
  const text = { checked: Boolean(item.named) && existsSync(join(root, PR_TEMPLATE_PATH)) };
191
- if (text.checked) findings.push(...prTextFindings(event.pull_request.body));
227
+ if (text.checked) {
228
+ findings.push(...prTextFindings(event.pull_request.body));
229
+ text.tests = changedRows(root, event.pull_request);
230
+ if (text.tests.error) findings.push(`pr text: the changed-test list could not be made: ${text.tests.error}`);
231
+ else if (text.tests.checked) findings.push(...changedTestsFindings(event.pull_request.body, text.tests.rows));
232
+ }
192
233
  return { green: findings.length === 0, findings, index, dead, item, text };
193
234
  }
194
235
 
@@ -204,6 +245,7 @@ export function upkeepCommand({ what, event: eventFile, json = false }, root, li
204
245
  line(`dead links: ${result.dead.length}`);
205
246
  line(`item: ${!result.item.checked ? 'not a pull request' : result.item.ok ? result.item.how : 'none named'}`);
206
247
  line(`pr text: ${result.text.checked ? 'the six headings were read' : 'not checked'}`);
248
+ if (result.text.checked) line(`changed tests: ${result.text.tests?.checked ? (result.text.tests.error ? 'no list' : `${result.text.tests.rows.filter((row) => NAMED_GROUPS.has(row.group)).length} row(s) to name`) : `not checked (${result.text.tests?.why})`}`);
207
249
  for (const finding of result.findings) line(` - ${finding}`);
208
250
  line(`upkeep check: ${result.green ? 'GREEN' : 'RED'}`);
209
251
  return result.green ? 0 : 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arjunkhera/atlas",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
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",