@carecard/validate 3.18.0 → 3.20.0

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.
Files changed (36) hide show
  1. package/.agents/skills/carecard-workspace-standards/SKILL.md +49 -20
  2. package/.agents/skills/github-pr-create-update/SKILL.md +22 -1
  3. package/.agents/skills/github-pr-merge-cleanup/SKILL.md +22 -1
  4. package/.agents/skills/logged-in-user-profile-page/SKILL.md +22 -1
  5. package/.agents/skills/pkg-publish/SKILL.md +22 -1
  6. package/.agents/skills/pkg-validate-coding-standards-and-best-practices/SKILL.md +47 -9
  7. package/.agents/skills/pkg-validate-validation-library/SKILL.md +45 -14
  8. package/.agents/skills/software-design-patterns-and-clean-code/SKILL.md +23 -3
  9. package/.codex/AGENTS.md +41 -10
  10. package/.github/workflows/auto-draft-pr.yml +173 -151
  11. package/.github/workflows/ci.yml +35 -32
  12. package/.husky/pre-commit +4 -4
  13. package/.prettierrc.js +10 -9
  14. package/AGENTS.md +19 -0
  15. package/eslint.config.mjs +60 -8
  16. package/index.d.ts +108 -107
  17. package/index.js +6 -6
  18. package/lib/validate.js +262 -158
  19. package/lib/validateNewUserRoleRequest.js +86 -60
  20. package/lib/validateProperties.js +326 -326
  21. package/lib/validateWhitelistProperties.js +182 -142
  22. package/lint-staged.config.mjs +36 -0
  23. package/package.json +66 -60
  24. package/readme.md +22 -1
  25. package/scripts/canonicalTestCommand.test.mjs +32 -0
  26. package/scripts/packageTaskRunner.audit.mjs +37 -0
  27. package/scripts/packageTaskRunner.test.mjs +50 -72
  28. package/scripts/runPackageTask.mjs +103 -58
  29. package/scripts/testOrder/randomizeTestOrder.cjs +35 -25
  30. package/scripts/testOrder/randomizeTestOrder.test.mjs +19 -19
  31. package/scripts/testOrder/testOrderPolicy.audit.mjs +54 -0
  32. package/scripts/testParallel/parallelTestPolicy.audit.mjs +63 -0
  33. package/scripts/testParallel/runIndexedMochaTests.cjs +45 -43
  34. package/scripts/testParallel/runIndexedMochaTests.test.mjs +13 -7
  35. package/scripts/testOrder/testOrderPolicy.test.mjs +0 -48
  36. package/scripts/testParallel/parallelTestPolicy.test.mjs +0 -43
@@ -0,0 +1,37 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import test from 'node:test';
4
+
5
+ const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
6
+ const composedCommandPattern = /&&|\|\||\bsh -c\b|\bnode -e\b/u;
7
+ const runnerCommandPattern = /^node scripts\/runPackageTask\.mjs ([A-Za-z0-9:_-]+)$/u;
8
+
9
+ async function loadPackageTaskRunner() {
10
+ return import(new URL('./runPackageTask.mjs', import.meta.url));
11
+ }
12
+
13
+ test('delegates composed package tasks to the repository runner', async () => {
14
+ const runnerCommands = Object.entries(packageJson.scripts ?? {}).filter(([, command]) =>
15
+ runnerCommandPattern.test(command),
16
+ );
17
+
18
+ for (const [scriptName, command] of Object.entries(packageJson.scripts ?? {})) {
19
+ assert.doesNotMatch(
20
+ command,
21
+ composedCommandPattern,
22
+ `${scriptName} must delegate composition to runPackageTask.mjs`,
23
+ );
24
+ }
25
+
26
+ assert.ok(runnerCommands.length > 0, 'at least one package task must use the runner');
27
+ const { packageTasks } = await loadPackageTaskRunner();
28
+ for (const [scriptName] of runnerCommands) {
29
+ assert.ok(packageTasks[scriptName], `${scriptName} must have a runner task`);
30
+ }
31
+ });
32
+
33
+ test('does not suppress command execution errors at the CLI boundary', () => {
34
+ const runnerSource = readFileSync(new URL('./runPackageTask.mjs', import.meta.url), 'utf8');
35
+
36
+ assert.doesNotMatch(runnerSource, /catch\s*\{/u);
37
+ });
@@ -1,86 +1,64 @@
1
1
  import assert from 'node:assert/strict';
2
- import { readFileSync } from 'node:fs';
3
2
  import test from 'node:test';
4
3
 
5
- const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
6
- const composedCommandPattern = /&&|\|\||\bsh -c\b|\bnode -e\b/u;
7
- const runnerCommandPattern = /^node scripts\/runPackageTask\.mjs ([A-Za-z0-9:_-]+)$/u;
8
-
9
4
  async function loadPackageTaskRunner() {
10
- return import(new URL('./runPackageTask.mjs', import.meta.url));
5
+ return import(new URL('./runPackageTask.mjs', import.meta.url));
11
6
  }
12
7
 
13
- test('delegates composed package tasks to the repository runner', async () => {
14
- const runnerCommands = Object.entries(packageJson.scripts ?? {}).filter(([, command]) => runnerCommandPattern.test(command));
15
-
16
- for (const [scriptName, command] of Object.entries(packageJson.scripts ?? {})) {
17
- assert.doesNotMatch(command, composedCommandPattern, `${scriptName} must delegate composition to runPackageTask.mjs`);
18
- }
19
-
20
- assert.ok(runnerCommands.length > 0, 'at least one package task must use the runner');
21
- const { packageTasks } = await loadPackageTaskRunner();
22
- for (const [scriptName] of runnerCommands) {
23
- assert.ok(packageTasks[scriptName], `${scriptName} must have a runner task`);
24
- }
25
- });
26
-
27
8
  test('runs task steps in order and stops at the first failure', async () => {
28
- const { runPackageTask } = await loadPackageTaskRunner();
29
- const executedCommands = [];
30
- const fixtureTasks = {
31
- fixture: [{ command: 'first' }, { command: 'second' }, { command: 'third' }],
32
- };
33
-
34
- const exitCode = runPackageTask(
35
- 'fixture',
36
- step => {
37
- executedCommands.push(step.command);
38
- return step.command === 'second' ? 7 : 0;
39
- },
40
- fixtureTasks,
41
- );
42
-
43
- assert.equal(exitCode, 7);
44
- assert.deepEqual(executedCommands, ['first', 'second']);
9
+ const { runPackageTask } = await loadPackageTaskRunner();
10
+ const executedCommands = [];
11
+ const fixtureTasks = {
12
+ fixture: [{ command: 'first' }, { command: 'second' }, { command: 'third' }],
13
+ };
14
+
15
+ const exitCode = runPackageTask(
16
+ 'fixture',
17
+ step => {
18
+ executedCommands.push(step.command);
19
+ return step.command === 'second' ? 7 : 0;
20
+ },
21
+ fixtureTasks,
22
+ );
23
+
24
+ assert.equal(exitCode, 7);
25
+ assert.deepEqual(executedCommands, ['first', 'second']);
45
26
  });
46
27
 
47
28
  test('runs conditional steps only when their required output is missing', async () => {
48
- const { runPackageTask } = await loadPackageTaskRunner();
49
- const executedCommands = [];
50
- const fixtureTasks = {
51
- fixture: [{ command: 'build', whenMissing: 'dist/runtime.js' }, { command: 'execute' }],
52
- };
53
-
54
- const exitCode = runPackageTask(
55
- 'fixture',
56
- step => {
57
- executedCommands.push(step.command);
58
- return 0;
59
- },
60
- fixtureTasks,
61
- () => true,
62
- );
63
-
64
- assert.equal(exitCode, 0);
65
- assert.deepEqual(executedCommands, ['execute']);
66
- });
67
-
68
- test('does not suppress command execution errors at the CLI boundary', () => {
69
- const runnerSource = readFileSync(new URL('./runPackageTask.mjs', import.meta.url), 'utf8');
70
-
71
- assert.doesNotMatch(runnerSource, /catch\s*\{/u);
29
+ const { runPackageTask } = await loadPackageTaskRunner();
30
+ const executedCommands = [];
31
+ const fixtureTasks = {
32
+ fixture: [{ command: 'build', whenMissing: 'dist/runtime.js' }, { command: 'execute' }],
33
+ };
34
+
35
+ const exitCode = runPackageTask(
36
+ 'fixture',
37
+ step => {
38
+ executedCommands.push(step.command);
39
+ return 0;
40
+ },
41
+ fixtureTasks,
42
+ () => true,
43
+ );
44
+
45
+ assert.equal(exitCode, 0);
46
+ assert.deepEqual(executedCommands, ['execute']);
72
47
  });
73
48
 
74
49
  test('merges task environment overrides without mutating inherited values', async () => {
75
- const { createTaskEnvironment } = await loadPackageTaskRunner();
76
- const inheritedEnvironment = { NODE_ENV: 'test', PATH: '/bin' };
77
-
78
- const environment = createTaskEnvironment({ NODE_ENV: 'production', DB_ENV: 'privileged' }, inheritedEnvironment);
79
-
80
- assert.deepEqual(environment, {
81
- NODE_ENV: 'production',
82
- PATH: '/bin',
83
- DB_ENV: 'privileged',
84
- });
85
- assert.deepEqual(inheritedEnvironment, { NODE_ENV: 'test', PATH: '/bin' });
50
+ const { createTaskEnvironment } = await loadPackageTaskRunner();
51
+ const inheritedEnvironment = { NODE_ENV: 'test', PATH: '/bin' };
52
+
53
+ const environment = createTaskEnvironment(
54
+ { NODE_ENV: 'production', DB_ENV: 'privileged' },
55
+ inheritedEnvironment,
56
+ );
57
+
58
+ assert.deepEqual(environment, {
59
+ NODE_ENV: 'production',
60
+ PATH: '/bin',
61
+ DB_ENV: 'privileged',
62
+ });
63
+ assert.deepEqual(inheritedEnvironment, { NODE_ENV: 'test', PATH: '/bin' });
86
64
  });
@@ -4,87 +4,132 @@ import { resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
 
6
6
  export const packageTasks = Object.freeze({
7
- test: [
8
- { command: 'npm', arguments: ['run', 'test:order'] },
9
- { command: 'node', arguments: ['test/index.test.js'] },
10
- ],
11
- 'test:types': [
12
- { command: 'tsc', arguments: ['--noEmit'] },
13
- {
14
- command: 'node',
15
- arguments: [
16
- '-e',
17
- "process.stdout.write('\\n ✔ Type tests passed: tsc --noEmit reported 0 errors across index.d.ts and test/**/*.ts\\n\\n')",
18
- ],
19
- },
20
- ],
21
- 'test:coverage': [
22
- { command: 'npm', arguments: ['run', 'test:order'] },
23
- { command: 'tsc', arguments: ['--noEmit'] },
24
- { command: 'nyc', arguments: ['node', 'test/index.test.js'] },
25
- ],
26
- 'test:All': [
27
- { command: 'npm', arguments: ['run', 'test:coverage'] },
28
- { command: 'npm', arguments: ['run', 'test:types'] },
29
- ],
7
+ 'validate:audits': [
8
+ {
9
+ command: 'node',
10
+ arguments: [
11
+ '--test',
12
+ 'scripts/packageTaskRunner.audit.mjs',
13
+ 'scripts/testOrder/testOrderPolicy.audit.mjs',
14
+ 'scripts/testParallel/parallelTestPolicy.audit.mjs',
15
+ ],
16
+ },
17
+ {
18
+ command: 'mocha',
19
+ arguments: [
20
+ '--reporter',
21
+ 'spec',
22
+ '--timeout',
23
+ '5000',
24
+ 'test/config/repositoryIsolation.audit.js',
25
+ 'test/config/tddGuidanceDocs.audit.js',
26
+ 'test/dependencyOverrides.audit.js',
27
+ ],
28
+ },
29
+ ],
30
+ test: [
31
+ { command: 'npm', arguments: ['run', 'validate:audits'] },
32
+ { command: 'npm', arguments: ['run', 'test:order'] },
33
+ { command: 'tsc', arguments: ['--noEmit'] },
34
+ { command: 'nyc', arguments: ['node', 'test/index.test.js'] },
35
+ ],
36
+ 'test:types': [
37
+ { command: 'tsc', arguments: ['--noEmit'] },
38
+ {
39
+ command: 'node',
40
+ arguments: [
41
+ '-e',
42
+ "process.stdout.write('\\n ✔ Type tests passed: tsc --noEmit reported 0 errors across index.d.ts and test/**/*.ts\\n\\n')",
43
+ ],
44
+ },
45
+ ],
46
+ 'test:coverage': [
47
+ { command: 'npm', arguments: ['run', 'test:order'] },
48
+ { command: 'tsc', arguments: ['--noEmit'] },
49
+ { command: 'nyc', arguments: ['node', 'test/index.test.js'] },
50
+ ],
51
+ 'test:All': [{ command: 'npm', arguments: ['test'] }],
30
52
  });
31
53
 
32
54
  export function createTaskEnvironment(overrides = {}, inheritedEnvironment = process.env) {
33
- return { ...inheritedEnvironment, ...overrides };
55
+ return { ...inheritedEnvironment, ...overrides };
34
56
  }
35
57
 
36
58
  function getTaskExitCode(result) {
37
- if (result.error) throw result.error;
38
- if (typeof result.status === 'number') return result.status;
39
- if (result.signal === 'SIGINT') return 130;
40
- if (result.signal === 'SIGTERM') return 143;
41
- return 1;
59
+ if (result.error) {
60
+ throw result.error;
61
+ }
62
+ if (typeof result.status === 'number') {
63
+ return result.status;
64
+ }
65
+ if (result.signal === 'SIGINT') {
66
+ return 130;
67
+ }
68
+ if (result.signal === 'SIGTERM') {
69
+ return 143;
70
+ }
71
+ return 1;
42
72
  }
43
73
 
44
74
  export function executeTaskStep(taskStep) {
45
- if (taskStep.removePath) {
46
- rmSync(taskStep.removePath, { recursive: true, force: true });
47
- return 0;
48
- }
75
+ if (taskStep.removePath) {
76
+ rmSync(taskStep.removePath, { recursive: true, force: true });
77
+ return 0;
78
+ }
49
79
 
50
- const result = spawnSync(taskStep.command, taskStep.arguments ?? [], {
51
- env: createTaskEnvironment(taskStep.environment),
52
- shell: false,
53
- stdio: 'inherit',
54
- });
55
- return getTaskExitCode(result);
80
+ const result = spawnSync(taskStep.command, taskStep.arguments ?? [], {
81
+ env: createTaskEnvironment(taskStep.environment),
82
+ shell: false,
83
+ stdio: 'inherit',
84
+ });
85
+ return getTaskExitCode(result);
56
86
  }
57
87
 
58
88
  function shouldRunTaskStep(taskStep, pathExists) {
59
- return !taskStep.whenMissing || !pathExists(taskStep.whenMissing);
89
+ return !taskStep.whenMissing || !pathExists(taskStep.whenMissing);
60
90
  }
61
91
 
62
- export function runPackageTask(taskName, executeTask = executeTaskStep, taskDefinitions = packageTasks, pathExists = existsSync) {
63
- const taskSteps = taskDefinitions[taskName];
64
- if (!Array.isArray(taskSteps)) throw new Error('Unknown package task.');
92
+ export function runPackageTask(
93
+ taskName,
94
+ executeTask = executeTaskStep,
95
+ taskDefinitions = packageTasks,
96
+ pathExists = existsSync,
97
+ ) {
98
+ const taskSteps = taskDefinitions[taskName];
99
+ if (!Array.isArray(taskSteps)) {
100
+ throw new Error('Unknown package task.');
101
+ }
65
102
 
66
- for (const taskStep of taskSteps) {
67
- if (!shouldRunTaskStep(taskStep, pathExists)) continue;
68
- const exitCode = executeTask(taskStep);
69
- if (exitCode !== 0) return exitCode;
103
+ for (const taskStep of taskSteps) {
104
+ if (!shouldRunTaskStep(taskStep, pathExists)) {
105
+ continue;
70
106
  }
71
- return 0;
107
+ const exitCode = executeTask(taskStep);
108
+ if (exitCode !== 0) {
109
+ return exitCode;
110
+ }
111
+ }
112
+ return 0;
72
113
  }
73
114
 
74
115
  function isDirectExecution() {
75
- if (!process.argv[1]) return false;
76
- return resolve(process.argv[1]) === fileURLToPath(import.meta.url);
116
+ if (!process.argv[1]) {
117
+ return false;
118
+ }
119
+ return resolve(process.argv[1]) === fileURLToPath(import.meta.url);
77
120
  }
78
121
 
79
122
  function runCommandLineTask() {
80
- const taskName = process.argv[2];
81
- if (!taskName || !packageTasks[taskName]) {
82
- process.stderr.write('[PACKAGE_TASK_CONFIG] Unknown package task.\n');
83
- process.exitCode = 2;
84
- return;
85
- }
123
+ const taskName = process.argv[2];
124
+ if (!taskName || !packageTasks[taskName]) {
125
+ process.stderr.write('[PACKAGE_TASK_CONFIG] Unknown package task.\n');
126
+ process.exitCode = 2;
127
+ return;
128
+ }
86
129
 
87
- process.exitCode = runPackageTask(taskName);
130
+ process.exitCode = runPackageTask(taskName);
88
131
  }
89
132
 
90
- if (isDirectExecution()) runCommandLineTask();
133
+ if (isDirectExecution()) {
134
+ runCommandLineTask();
135
+ }
@@ -3,38 +3,48 @@
3
3
  const MAX_TEST_ORDER_SEED = 2_147_483_647;
4
4
 
5
5
  function resolveTestOrderSeed(configuredSeed) {
6
- if (configuredSeed === undefined) return undefined;
7
- if (!/^[1-9]\d*$/.test(configuredSeed)) throw new Error('TEST_ORDER_SEED must be a positive 32-bit integer.');
8
- const seed = Number(configuredSeed);
9
- if (!Number.isSafeInteger(seed) || seed > MAX_TEST_ORDER_SEED) throw new Error('TEST_ORDER_SEED must be a positive 32-bit integer.');
10
- return seed;
6
+ if (configuredSeed === undefined) {
7
+ return undefined;
8
+ }
9
+ if (!/^[1-9]\d*$/.test(configuredSeed)) {
10
+ throw new Error('TEST_ORDER_SEED must be a positive 32-bit integer.');
11
+ }
12
+ const seed = Number(configuredSeed);
13
+ if (!Number.isSafeInteger(seed) || seed > MAX_TEST_ORDER_SEED) {
14
+ throw new Error('TEST_ORDER_SEED must be a positive 32-bit integer.');
15
+ }
16
+ return seed;
11
17
  }
12
18
  function createSeededRandom(seed) {
13
- let state = seed;
14
- return function nextRandomValue() {
15
- state = (state + 0x6d2b79f5) | 0;
16
- let value = Math.imul(state ^ (state >>> 15), 1 | state);
17
- value = (value + Math.imul(value ^ (value >>> 7), 61 | value)) ^ value;
18
- return ((value ^ (value >>> 14)) >>> 0) / 4_294_967_296;
19
- };
19
+ let state = seed;
20
+ return function nextRandomValue() {
21
+ state = (state + 0x6d2b79f5) | 0;
22
+ let value = Math.imul(state ^ (state >>> 15), 1 | state);
23
+ value = (value + Math.imul(value ^ (value >>> 7), 61 | value)) ^ value;
24
+ return ((value ^ (value >>> 14)) >>> 0) / 4_294_967_296;
25
+ };
20
26
  }
21
27
  function shuffleValues(values, random) {
22
- for (let index = values.length - 1; index > 0; index -= 1) {
23
- const replacementIndex = Math.floor(random() * (index + 1));
24
- [values[index], values[replacementIndex]] = [values[replacementIndex], values[index]];
25
- }
28
+ for (let index = values.length - 1; index > 0; index -= 1) {
29
+ const replacementIndex = Math.floor(random() * (index + 1));
30
+ [values[index], values[replacementIndex]] = [values[replacementIndex], values[index]];
31
+ }
26
32
  }
27
33
  function shuffleSuiteTree(suite, random) {
28
- for (const childSuite of suite.suites) shuffleSuiteTree(childSuite, random);
29
- shuffleValues(suite.tests, random);
30
- shuffleValues(suite.suites, random);
34
+ for (const childSuite of suite.suites) {
35
+ shuffleSuiteTree(childSuite, random);
36
+ }
37
+ shuffleValues(suite.tests, random);
38
+ shuffleValues(suite.suites, random);
31
39
  }
32
40
  const mochaHooks = {
33
- beforeAll() {
34
- const seed = resolveTestOrderSeed(process.env.TEST_ORDER_SEED);
35
- if (seed === undefined) return;
36
- console.log(`Test order seed: ${seed} (reproduce with TEST_ORDER_SEED=${seed})`);
37
- shuffleSuiteTree(this.test.parent, createSeededRandom(seed));
38
- },
41
+ beforeAll() {
42
+ const seed = resolveTestOrderSeed(process.env.TEST_ORDER_SEED);
43
+ if (seed === undefined) {
44
+ return;
45
+ }
46
+ console.log(`Test order seed: ${seed} (reproduce with TEST_ORDER_SEED=${seed})`);
47
+ shuffleSuiteTree(this.test.parent, createSeededRandom(seed));
48
+ },
39
49
  };
40
50
  module.exports = { createSeededRandom, mochaHooks, resolveTestOrderSeed, shuffleSuiteTree };
@@ -6,31 +6,31 @@ import testOrderRandomizer from './randomizeTestOrder.cjs';
6
6
  const { createSeededRandom, resolveTestOrderSeed, shuffleSuiteTree } = testOrderRandomizer;
7
7
 
8
8
  function createSuiteTree() {
9
- return {
10
- suites: [
11
- { title: 'alpha', suites: [], tests: [{ title: 'one' }, { title: 'two' }] },
12
- { title: 'beta', suites: [], tests: [{ title: 'three' }, { title: 'four' }] },
13
- { title: 'gamma', suites: [], tests: [{ title: 'five' }, { title: 'six' }] },
14
- ],
15
- tests: [{ title: 'root one' }, { title: 'root two' }, { title: 'root three' }],
16
- };
9
+ return {
10
+ suites: [
11
+ { title: 'alpha', suites: [], tests: [{ title: 'one' }, { title: 'two' }] },
12
+ { title: 'beta', suites: [], tests: [{ title: 'three' }, { title: 'four' }] },
13
+ { title: 'gamma', suites: [], tests: [{ title: 'five' }, { title: 'six' }] },
14
+ ],
15
+ tests: [{ title: 'root one' }, { title: 'root two' }, { title: 'root three' }],
16
+ };
17
17
  }
18
18
 
19
19
  test('uses ordinary ordering unless TEST_ORDER_SEED is explicitly supplied', () => {
20
- assert.strictEqual(resolveTestOrderSeed(undefined), undefined);
21
- assert.strictEqual(resolveTestOrderSeed('314159'), 314159);
22
- for (const invalidSeed of ['', '0', '-1', '1.5', 'seed', '2147483648']) {
23
- assert.throws(() => resolveTestOrderSeed(invalidSeed), /TEST_ORDER_SEED/);
24
- }
20
+ assert.strictEqual(resolveTestOrderSeed(undefined), undefined);
21
+ assert.strictEqual(resolveTestOrderSeed('314159'), 314159);
22
+ for (const invalidSeed of ['', '0', '-1', '1.5', 'seed', '2147483648']) {
23
+ assert.throws(() => resolveTestOrderSeed(invalidSeed), /TEST_ORDER_SEED/);
24
+ }
25
25
  });
26
26
 
27
27
  test('shuffles nested suites and tests reproducibly', () => {
28
- const firstTree = createSuiteTree();
29
- const secondTree = createSuiteTree();
28
+ const firstTree = createSuiteTree();
29
+ const secondTree = createSuiteTree();
30
30
 
31
- shuffleSuiteTree(firstTree, createSeededRandom(314159));
32
- shuffleSuiteTree(secondTree, createSeededRandom(314159));
31
+ shuffleSuiteTree(firstTree, createSeededRandom(314159));
32
+ shuffleSuiteTree(secondTree, createSeededRandom(314159));
33
33
 
34
- assert.deepStrictEqual(firstTree, secondTree);
35
- assert.notDeepStrictEqual(firstTree, createSuiteTree());
34
+ assert.deepStrictEqual(firstTree, secondTree);
35
+ assert.notDeepStrictEqual(firstTree, createSuiteTree());
36
36
  });
@@ -0,0 +1,54 @@
1
+ import assert from 'node:assert/strict';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { readFileSync } from 'node:fs';
4
+ import { test } from 'node:test';
5
+
6
+ const TEST_ORDER_INVARIANCE_RULE =
7
+ "Non-negotiable test order invariance rule: Every test must pass independently of which tests run before or after it, and the suite must pass in every execution order. Each test must establish the state it needs, isolate mutable state, and clean up state it owns; it must never rely on another test's setup, mutations, or cleanup. Default test, CI, and Husky commands must use the test framework's ordinary ordering and must not force randomized ordering. Random-order execution is an explicit diagnostic only, and every failure it exposes must be fixed at the root cause.";
8
+
9
+ function listRepositoryFiles() {
10
+ return execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard'], {
11
+ encoding: 'utf8',
12
+ })
13
+ .trim()
14
+ .split('\n')
15
+ .filter(Boolean);
16
+ }
17
+
18
+ function isRequiredTestGuidance(filePath) {
19
+ return (
20
+ /^readme\.md$/i.test(filePath) ||
21
+ filePath === '.codex/AGENTS.md' ||
22
+ filePath === '.junie/guidelines.md' ||
23
+ filePath === '.agents/skills/carecard-workspace-standards/SKILL.md' ||
24
+ /^\.agents\/skills\/[^/]*(?:test|testing)[^/]*\/(?:SKILL\.md|references\/[^/]*(?:test|testing|coding-principles)[^/]*\.md)$/i.test(
25
+ filePath,
26
+ )
27
+ );
28
+ }
29
+
30
+ test('keeps the non-negotiable test order rule in repository guidance', () => {
31
+ const guidanceFiles = listRepositoryFiles().filter(isRequiredTestGuidance);
32
+ assert.ok(guidanceFiles.length > 0, 'No repository test guidance was found.');
33
+
34
+ for (const guidanceFile of guidanceFiles) {
35
+ const normalizedGuidance = readFileSync(guidanceFile, 'utf8').replace(/\s+/g, ' ').trim();
36
+ assert.ok(
37
+ normalizedGuidance.includes(TEST_ORDER_INVARIANCE_RULE),
38
+ `${guidanceFile} must document the non-negotiable test order invariance rule.`,
39
+ );
40
+ }
41
+ });
42
+
43
+ test('keeps default package scripts on the test framework ordinary ordering', () => {
44
+ const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
45
+
46
+ for (const [scriptName, command] of Object.entries(packageJson.scripts ?? {})) {
47
+ assert.equal(typeof command, 'string', `${scriptName} must be a string command.`);
48
+ assert.doesNotMatch(
49
+ command,
50
+ /--test-randomize|--test-random-seed/,
51
+ `${scriptName} must not force randomized test ordering.`,
52
+ );
53
+ }
54
+ });
@@ -0,0 +1,63 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readdirSync, readFileSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { join, relative, resolve } from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const repositoryRoot = resolve(import.meta.dirname, '../..');
9
+ const packageJson = JSON.parse(
10
+ readFileSync(new URL('../../package.json', import.meta.url), 'utf8'),
11
+ );
12
+ const packageTaskRunnerSource = readFileSync(
13
+ new URL('../runPackageTask.mjs', import.meta.url),
14
+ 'utf8',
15
+ );
16
+ const testIndexSource = readFileSync(new URL('../../test/index.test.js', import.meta.url), 'utf8');
17
+ const { parallelTestFiles } = require('../../test/index.test.js');
18
+
19
+ function listRuntimeTestFiles(directoryPath) {
20
+ return readdirSync(directoryPath, { withFileTypes: true }).flatMap(entry => {
21
+ const entryPath = join(directoryPath, entry.name);
22
+ if (entry.isDirectory()) {
23
+ return listRuntimeTestFiles(entryPath);
24
+ }
25
+ if (!/\.test\.(?:js|mjs)$/.test(entry.name) || entry.name === 'index.test.js') {
26
+ return [];
27
+ }
28
+ return [relative(repositoryRoot, entryPath)];
29
+ });
30
+ }
31
+
32
+ test('keeps runtime test selection in the index and package scripts short', () => {
33
+ assert.equal(packageJson.scripts.test, 'node scripts/runPackageTask.mjs test');
34
+ assert.equal(
35
+ packageJson.scripts['test:coverage'],
36
+ 'node scripts/runPackageTask.mjs test:coverage',
37
+ );
38
+ assert.match(packageTaskRunnerSource, /arguments: \['run', 'test:order'\]/);
39
+ assert.match(packageTaskRunnerSource, /arguments: \['node', 'test\/index\.test\.js'\]/);
40
+ assert.match(packageTaskRunnerSource, /command: 'nyc'/);
41
+ assert.match(testIndexSource, /parallelTestFiles/);
42
+ assert.match(testIndexSource, /runIndexedMochaTests/);
43
+ assert.match(testIndexSource, /if \(require\.main === module\)/);
44
+ });
45
+
46
+ test('runs static parallel policy in the audit gate and callable runner behavior in the test-order gate', () => {
47
+ assert.equal(
48
+ packageJson.scripts['validate:audits'],
49
+ 'node scripts/runPackageTask.mjs validate:audits',
50
+ );
51
+ assert.match(packageTaskRunnerSource, /scripts\/testParallel\/parallelTestPolicy\.audit\.mjs/);
52
+ assert.match(
53
+ packageJson.scripts['test:order'],
54
+ /scripts\/testParallel\/runIndexedMochaTests\.test\.mjs/,
55
+ );
56
+ });
57
+
58
+ test('selects every runtime test file exactly once', () => {
59
+ assert.deepEqual(
60
+ [...parallelTestFiles].sort(),
61
+ listRuntimeTestFiles(resolve(repositoryRoot, 'test')).sort(),
62
+ );
63
+ });