@onlooker-community/ecosystem 0.43.2 → 0.43.3

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.
@@ -0,0 +1,160 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import { describe, it } from 'node:test';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { ALL_EVENT_TYPES } from '@onlooker-community/schema';
9
+
10
+ const HERE = dirname(fileURLToPath(import.meta.url));
11
+ const REPO_ROOT = resolve(HERE, '..', '..');
12
+ const GATE = join(REPO_ROOT, 'scripts', 'lint', 'check-bus-coverage.mjs');
13
+
14
+ function reportDir(lines) {
15
+ const dir = mkdtempSync(join(tmpdir(), 'bus-report-'));
16
+ mkdirSync(dir, { recursive: true });
17
+ writeFileSync(
18
+ join(dir, 'emissions.jsonl'),
19
+ lines.map((l) => JSON.stringify(l)).join('\n') + (lines.length ? '\n' : ''),
20
+ );
21
+ return dir;
22
+ }
23
+
24
+ function manifestFile(manifest) {
25
+ const dir = mkdtempSync(join(tmpdir(), 'bus-manifest-'));
26
+ const p = join(dir, 'bus-coverage.json');
27
+ writeFileSync(p, JSON.stringify(manifest, null, 2));
28
+ return p;
29
+ }
30
+
31
+ // Gate A's own tests exercise Gate A only. They must stay hermetic — immune
32
+ // to whatever the real committed manifest expects — so they run against a
33
+ // fixture that expects only session.start (the type the OK fixture below
34
+ // actually emits) and excludes every other registered type. session.start
35
+ // has to sit in `expected`, not `excluded`: Gate B's fourth assertion fails
36
+ // an excluded type that turns up emitted-and-valid, and every Gate A test
37
+ // using OK does emit it.
38
+ function gateAManifest() {
39
+ const excluded = {};
40
+ for (const t of ALL_EVENT_TYPES) {
41
+ if (t !== 'session.start') excluded[t] = 'not exercised by this Gate A fixture';
42
+ }
43
+ return { expected: ['session.start'], excluded };
44
+ }
45
+
46
+ const GATE_A_MANIFEST = manifestFile(gateAManifest());
47
+
48
+ function run(dir) {
49
+ const r = spawnSync('node', [GATE, '--report', dir, '--manifest', GATE_A_MANIFEST], {
50
+ encoding: 'utf8',
51
+ });
52
+ return { code: r.status, stdout: r.stdout, stderr: r.stderr };
53
+ }
54
+
55
+ const OK = { event_type: 'session.start', validated: true, valid: true };
56
+
57
+ describe('check-bus-coverage gate A', () => {
58
+ it('passes when every emission validated', () => {
59
+ const r = run(reportDir([OK, OK]));
60
+ assert.equal(r.code, 0, r.stderr);
61
+ });
62
+
63
+ it('fails on a rejected emission and names the type', () => {
64
+ const bad = {
65
+ event_type: 'librarian.scan.complete',
66
+ validated: true,
67
+ valid: false,
68
+ errors: [{ path: '/outcome', message: 'must be equal to one of the allowed values' }],
69
+ };
70
+ const r = run(reportDir([OK, bad]));
71
+ assert.equal(r.code, 1);
72
+ assert.match(r.stderr, /librarian\.scan\.complete/);
73
+ });
74
+
75
+ it('fails when validation never ran, rather than passing vacuously', () => {
76
+ const unvalidated = { event_type: 'session.start', validated: false, valid: null };
77
+ const r = run(reportDir([unvalidated, unvalidated]));
78
+ assert.equal(r.code, 1);
79
+ assert.match(r.stderr, /did not resolve|never validated|no emission was validated/i);
80
+ });
81
+
82
+ it('fails when the report is missing entirely', () => {
83
+ const r = run(mkdtempSync(join(tmpdir(), 'bus-empty-')));
84
+ assert.equal(r.code, 1);
85
+ assert.match(r.stderr, /no emissions recorded/i);
86
+ });
87
+ });
88
+
89
+ function runWith(dir, manifestPath) {
90
+ const r = spawnSync('node', [GATE, '--report', dir, '--manifest', manifestPath], {
91
+ encoding: 'utf8',
92
+ });
93
+ return { code: r.status, stdout: r.stdout, stderr: r.stderr };
94
+ }
95
+
96
+ // A manifest that accounts for all 125 types, expecting only session.start.
97
+ function fullManifest(expected = ['session.start']) {
98
+ const excluded = {};
99
+ for (const t of ALL_EVENT_TYPES) {
100
+ if (!expected.includes(t)) excluded[t] = 'not emitted in tests';
101
+ }
102
+ return { expected, excluded };
103
+ }
104
+
105
+ describe('check-bus-coverage gate B', () => {
106
+ it('passes when every expected type has a validated emission', () => {
107
+ const r = runWith(reportDir([OK]), manifestFile(fullManifest()));
108
+ assert.equal(r.code, 0, r.stderr);
109
+ });
110
+
111
+ it('fails when an expected type never appeared', () => {
112
+ const m = fullManifest(['session.start', 'session.end']);
113
+ const r = runWith(reportDir([OK]), manifestFile(m));
114
+ assert.equal(r.code, 1);
115
+ assert.match(r.stderr, /session\.end/);
116
+ });
117
+
118
+ it('fails when a registered type is in neither list', () => {
119
+ const m = fullManifest();
120
+ delete m.excluded[ALL_EVENT_TYPES.find((t) => t !== 'session.start')];
121
+ const r = runWith(reportDir([OK]), manifestFile(m));
122
+ assert.equal(r.code, 1);
123
+ assert.match(r.stderr, /accounted for|neither/i);
124
+ });
125
+
126
+ it('fails when the manifest names a type the schema does not register', () => {
127
+ const m = fullManifest();
128
+ m.excluded['not.a.real.type'] = 'bogus';
129
+ const r = runWith(reportDir([OK]), manifestFile(m));
130
+ assert.equal(r.code, 1);
131
+ assert.match(r.stderr, /not\.a\.real\.type/);
132
+ });
133
+
134
+ it('fails when an exclusion has an empty reason', () => {
135
+ const m = fullManifest();
136
+ m.excluded[Object.keys(m.excluded)[0]] = '';
137
+ const r = runWith(reportDir([OK]), manifestFile(m));
138
+ assert.equal(r.code, 1);
139
+ assert.match(r.stderr, /reason/i);
140
+ });
141
+
142
+ it('fails when an excluded type is actually emitted and valid', () => {
143
+ // expected: [] puts session.start in excluded, but the fixture report
144
+ // still emits it as valid — coverage would be silently under-claimed.
145
+ const m = fullManifest([]);
146
+ const r = runWith(reportDir([OK]), manifestFile(m));
147
+ assert.equal(r.code, 1);
148
+ assert.match(r.stderr, /session\.start/);
149
+ });
150
+
151
+ it('the committed manifest accounts for every registered type', () => {
152
+ const committed = JSON.parse(readFileSync(join(REPO_ROOT, 'test', 'bus-coverage.json'), 'utf8'));
153
+ const accounted = new Set([...committed.expected, ...Object.keys(committed.excluded)]);
154
+ const missing = ALL_EVENT_TYPES.filter((t) => !accounted.has(t));
155
+ assert.deepEqual(missing, [], `unaccounted event types: ${missing.join(', ')}`);
156
+ for (const [type, reason] of Object.entries(committed.excluded)) {
157
+ assert.ok(reason?.trim() && reason !== 'FILL IN', `${type} needs a real reason`);
158
+ }
159
+ });
160
+ });
@@ -0,0 +1,179 @@
1
+ // Tests for scripts/lint/check-managed-blocks.mjs. Each test stands up a
2
+ // scratch git repo in a temp dir, writes markdown into it, runs the linter as a
3
+ // subprocess, and asserts on exit code + emitted output.
4
+ //
5
+ // The linter reads its file list from `git ls-files`, so every fixture has to be
6
+ // staged -- an unstaged file is invisible to it, same as in a real checkout.
7
+
8
+ import assert from 'node:assert/strict';
9
+ import { execFileSync, spawnSync } from 'node:child_process';
10
+ import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { dirname, join, resolve } from 'node:path';
13
+ import { describe, it } from 'node:test';
14
+ import { fileURLToPath } from 'node:url';
15
+
16
+ const HERE = dirname(fileURLToPath(import.meta.url));
17
+ const REPO_ROOT = resolve(HERE, '..', '..');
18
+ const LINTER = join(REPO_ROOT, 'scripts', 'lint', 'check-managed-blocks.mjs');
19
+
20
+ const RULES = 'MD012 MD024 MD034';
21
+ const DISABLE = `<!-- markdownlint-disable ${RULES} -->`;
22
+ const ENABLE = `<!-- markdownlint-enable ${RULES} -->`;
23
+
24
+ function scaffold() {
25
+ const root = mkdtempSync(join(tmpdir(), 'check-blocks-'));
26
+ execFileSync('git', ['init', '-q'], { cwd: root });
27
+ execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: root });
28
+ execFileSync('git', ['config', 'user.name', 'test'], { cwd: root });
29
+ return root;
30
+ }
31
+
32
+ function writeFile(root, relPath, text) {
33
+ const p = join(root, relPath);
34
+ mkdirSync(dirname(p), { recursive: true });
35
+ writeFileSync(p, text);
36
+ }
37
+
38
+ function run(root) {
39
+ execFileSync('git', ['add', '-A'], { cwd: root });
40
+ const r = spawnSync('node', [LINTER, '--root', root], { encoding: 'utf8' });
41
+ return { code: r.status, stdout: r.stdout, stderr: r.stderr };
42
+ }
43
+
44
+ // A correctly fenced managed block, the shape this linter exists to enforce.
45
+ function fencedBlock(name, body) {
46
+ return [DISABLE, `<!-- BEGIN ${name} -->`, body, `<!-- END ${name} -->`, ENABLE].join('\n');
47
+ }
48
+
49
+ describe('check-managed-blocks', () => {
50
+ it('passes on a repo with no markdown at all', () => {
51
+ const root = scaffold();
52
+ writeFile(root, 'README.txt', 'not markdown\n');
53
+ const r = run(root);
54
+ assert.equal(r.code, 0, r.stderr);
55
+ assert.match(r.stdout, /0 managed block\(s\)/);
56
+ });
57
+
58
+ it('passes on markdown with no managed blocks', () => {
59
+ const root = scaffold();
60
+ writeFile(root, 'doc.md', '# Doc\n\n<!-- an ordinary comment -->\n\nBody.\n');
61
+ const r = run(root);
62
+ assert.equal(r.code, 0, r.stderr);
63
+ });
64
+
65
+ it('passes on a correctly fenced managed block', () => {
66
+ const root = scaffold();
67
+ writeFile(root, 'AGENTS.md', `# Doc\n\n${fencedBlock('BEADS INTEGRATION v:1', 'See https://example.com/x')}\n`);
68
+ const r = run(root);
69
+ assert.equal(r.code, 0, r.stderr);
70
+ assert.match(r.stdout, /1 managed block\(s\) in 1 file\(s\)/);
71
+ });
72
+
73
+ it('counts multiple fenced blocks in one file', () => {
74
+ const root = scaffold();
75
+ const body = [
76
+ '# Doc',
77
+ '',
78
+ fencedBlock('BEADS INTEGRATION', 'a'),
79
+ '',
80
+ fencedBlock('BEADS CODEX SETUP: generated by bd setup codex', 'b'),
81
+ '',
82
+ ].join('\n');
83
+ writeFile(root, 'AGENTS.md', body);
84
+ const r = run(root);
85
+ assert.equal(r.code, 0, r.stderr);
86
+ assert.match(r.stdout, /2 managed block\(s\)/);
87
+ });
88
+
89
+ it('fails when a managed block has no fence -- the ecosystem-55g regression', () => {
90
+ const root = scaffold();
91
+ writeFile(root, 'AGENTS.md', '# Doc\n\n<!-- BEGIN BEADS INTEGRATION -->\nx\n<!-- END BEADS INTEGRATION -->\n');
92
+ const r = run(root);
93
+ assert.equal(r.code, 1);
94
+ assert.match(r.stderr, /AGENTS\.md:3: managed block is not fenced/);
95
+ assert.match(r.stderr, /AGENTS\.md:5: managed block is not closed/);
96
+ });
97
+
98
+ it('fails when the fence omits a required rule', () => {
99
+ const root = scaffold();
100
+ const body = [
101
+ '# Doc',
102
+ '',
103
+ '<!-- markdownlint-disable MD024 -->',
104
+ '<!-- BEGIN BEADS CODEX SETUP -->',
105
+ 'See https://example.com/y',
106
+ '<!-- END BEADS CODEX SETUP -->',
107
+ '<!-- markdownlint-enable MD024 -->',
108
+ '',
109
+ ].join('\n');
110
+ writeFile(root, 'CLAUDE.md', body);
111
+ const r = run(root);
112
+ assert.equal(r.code, 1);
113
+ assert.match(r.stderr, /markdownlint-disable above this block omits MD012, MD034/);
114
+ assert.match(r.stderr, /markdownlint-enable below this block omits MD012, MD034/);
115
+ });
116
+
117
+ it('fails when only the opening half of the fence is present', () => {
118
+ const root = scaffold();
119
+ writeFile(root, 'CLAUDE.md', `# Doc\n\n${DISABLE}\n<!-- BEGIN X -->\nx\n<!-- END X -->\n`);
120
+ const r = run(root);
121
+ assert.equal(r.code, 1);
122
+ assert.match(r.stderr, /managed block is not closed/);
123
+ assert.doesNotMatch(r.stderr, /is not fenced/);
124
+ });
125
+
126
+ it('reports an unterminated block once, without cascading fence errors', () => {
127
+ const root = scaffold();
128
+ writeFile(root, 'doc.md', `# Doc\n\n${DISABLE}\n<!-- BEGIN THING -->\nbody with no end\n`);
129
+ const r = run(root);
130
+ assert.equal(r.code, 1);
131
+ assert.match(r.stderr, /doc\.md:4: managed block "<!-- BEGIN THING -->" has no matching/);
132
+ assert.match(r.stderr, /1 error\(s\)/);
133
+ });
134
+
135
+ it('skips files listed in .markdownlintignore', () => {
136
+ const root = scaffold();
137
+ writeFile(root, '.markdownlintignore', 'node_modules/\n**/CHANGELOG.md\n');
138
+ // Generated by release-please and never hand-authored, so an unfenced
139
+ // block inside it is not ours to fix.
140
+ writeFile(root, 'CHANGELOG.md', '<!-- BEGIN GENERATED -->\nx\n<!-- END GENERATED -->\n');
141
+ writeFile(root, 'packages/a/CHANGELOG.md', '<!-- BEGIN GENERATED -->\nx\n<!-- END GENERATED -->\n');
142
+ const r = run(root);
143
+ assert.equal(r.code, 0, r.stderr);
144
+ assert.match(r.stdout, /0 managed block\(s\)/);
145
+ });
146
+
147
+ it('still checks non-ignored files when an ignore file is present', () => {
148
+ const root = scaffold();
149
+ writeFile(root, '.markdownlintignore', '**/CHANGELOG.md\n');
150
+ writeFile(root, 'CHANGELOG.md', '<!-- BEGIN GENERATED -->\nx\n<!-- END GENERATED -->\n');
151
+ writeFile(root, 'AGENTS.md', '<!-- BEGIN BEADS INTEGRATION -->\nx\n<!-- END BEADS INTEGRATION -->\n');
152
+ const r = run(root);
153
+ assert.equal(r.code, 1);
154
+ assert.match(r.stderr, /AGENTS\.md:1: managed block is not fenced/);
155
+ assert.doesNotMatch(r.stderr, /CHANGELOG/);
156
+ });
157
+
158
+ it('accepts a fence that disables more rules than required', () => {
159
+ const root = scaffold();
160
+ const body = [
161
+ '# Doc',
162
+ '',
163
+ '<!-- markdownlint-disable MD012 MD024 MD034 MD041 -->',
164
+ '<!-- BEGIN X -->',
165
+ 'x',
166
+ '<!-- END X -->',
167
+ '<!-- markdownlint-enable MD012 MD024 MD034 MD041 -->',
168
+ '',
169
+ ].join('\n');
170
+ writeFile(root, 'doc.md', body);
171
+ const r = run(root);
172
+ assert.equal(r.code, 0, r.stderr);
173
+ });
174
+
175
+ it('checks the real repository', () => {
176
+ const r = spawnSync('node', [LINTER, '--root', REPO_ROOT], { encoding: 'utf8' });
177
+ assert.equal(r.status, 0, r.stderr);
178
+ });
179
+ });
@@ -0,0 +1,97 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { existsSync, mkdtempSync, readFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import { describe, it } from 'node:test';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const HERE = dirname(fileURLToPath(import.meta.url));
10
+ const REPO_ROOT = resolve(HERE, '..', '..');
11
+ const EMITTER = join(REPO_ROOT, 'scripts', 'lib', 'onlooker-event.mjs');
12
+
13
+ // session.start requires only working_directory and forbids extra properties.
14
+ // Verified against node_modules/@onlooker-community/schema/schemas/payload/session.json
15
+ const VALID = {
16
+ plugin: 'onlooker',
17
+ session_id: '01JZZZZZZZZZZZZZZZZZZZZZZZ',
18
+ event_type: 'session.start',
19
+ payload: { working_directory: '/tmp/x' },
20
+ };
21
+ const INVALID = { ...VALID, payload: { working_directory: 42 } };
22
+
23
+ function emit(params, { reportDir } = {}) {
24
+ const env = {
25
+ ...process.env,
26
+ ONLOOKER_DIR: mkdtempSync(join(tmpdir(), 'emit-onlooker-')),
27
+ };
28
+ if (reportDir) env.ONLOOKER_TEST_REPORT_DIR = reportDir;
29
+ else delete env.ONLOOKER_TEST_REPORT_DIR;
30
+ return spawnSync('node', [EMITTER, 'emit'], {
31
+ input: JSON.stringify(params),
32
+ encoding: 'utf8',
33
+ env,
34
+ });
35
+ }
36
+
37
+ function readReport(dir) {
38
+ const p = join(dir, 'emissions.jsonl');
39
+ if (!existsSync(p)) return null;
40
+ return readFileSync(p, 'utf8')
41
+ .trim()
42
+ .split('\n')
43
+ .filter(Boolean)
44
+ .map((l) => JSON.parse(l));
45
+ }
46
+
47
+ describe('emission report', () => {
48
+ it('records a valid emission as validated and valid', () => {
49
+ const dir = mkdtempSync(join(tmpdir(), 'emit-report-'));
50
+ const r = emit(VALID, { reportDir: dir });
51
+ assert.equal(r.status, 0, r.stderr);
52
+ const lines = readReport(dir);
53
+ assert.equal(lines.length, 1);
54
+ assert.equal(lines[0].event_type, 'session.start');
55
+ assert.equal(lines[0].validated, true);
56
+ assert.equal(lines[0].valid, true);
57
+ });
58
+
59
+ it('records a rejected emission as invalid, with its errors', () => {
60
+ const dir = mkdtempSync(join(tmpdir(), 'emit-report-'));
61
+ const r = emit(INVALID, { reportDir: dir });
62
+ assert.equal(r.status, 1);
63
+ const lines = readReport(dir);
64
+ assert.equal(lines.length, 1);
65
+ assert.equal(lines[0].validated, true);
66
+ assert.equal(lines[0].valid, false);
67
+ assert.ok(Array.isArray(lines[0].errors) && lines[0].errors.length > 0);
68
+ });
69
+
70
+ it('writes nothing when the report dir is unset', () => {
71
+ const dir = mkdtempSync(join(tmpdir(), 'emit-report-'));
72
+ emit(VALID, { reportDir: dir });
73
+ assert.equal(readReport(dir).length, 1);
74
+ // Same directory, but this emission is never told about it. The count must
75
+ // not move. Asserting on an untouched temp dir would pass either way.
76
+ const r = emit(VALID);
77
+ assert.equal(r.status, 0, r.stderr);
78
+ assert.equal(readReport(dir).length, 1);
79
+ });
80
+
81
+ it('appends across emissions rather than truncating', () => {
82
+ const dir = mkdtempSync(join(tmpdir(), 'emit-report-'));
83
+ emit(VALID, { reportDir: dir });
84
+ emit(VALID, { reportDir: dir });
85
+ assert.equal(readReport(dir).length, 2);
86
+ });
87
+
88
+ it('does not record for the validate subcommand', () => {
89
+ const dir = mkdtempSync(join(tmpdir(), 'emit-report-'));
90
+ spawnSync('node', [EMITTER, 'validate'], {
91
+ input: JSON.stringify({ nonsense: true }),
92
+ encoding: 'utf8',
93
+ env: { ...process.env, ONLOOKER_TEST_REPORT_DIR: dir },
94
+ });
95
+ assert.equal(readReport(dir), null);
96
+ });
97
+ });