Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/shared.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: shared files

# The three atlases copy a few terminal primitives byte for byte: catch a copy left behind.
on:
pull_request:
paths: ['src/browser.ts', 'src/ui/mouse.ts', 'src/ui/palette.ts', 'src/ui/render.ts', 'src/ui/width.ts', 'src/ui/ListSelect.tsx', 'scripts/check-shared.ts', '.github/workflows/shared.yml']
# A sibling can change too: check once a week against their main branches.
schedule:
- cron: '23 5 * * 1'
workflow_dispatch:

permissions:
contents: read

jobs:
drift:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run check:shared
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"dev": "tsx src/launch.ts",
"crawl": "tsx scripts/crawl.ts",
"demo": "tsx scripts/build-demo.ts",
"check:shared": "tsx scripts/check-shared.ts",
"sync-agents": "tsx scripts/sync-agents.ts",
"reclassify": "tsx scripts/reclassify.ts",
"typecheck": "tsc --noEmit",
Expand Down
56 changes: 56 additions & 0 deletions scripts/check-shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* skills-atlas, mcp-atlas and plugins-atlas copy a few terminal primitives byte for byte
* instead of sharing a package (the rest of their code has diverged too far for one).
* This checks that the copies still match, so that a fix made in one reaches the others.
*
* npm run check:shared compare with each sibling's main branch on GitHub
* npm run check:shared -- --local compare with sibling checkouts next to this one (../mcp-atlas…)
*
* Exits with 1 and names the files that differ.
*/
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';

/** File under src/ → the sibling repositories that must hold the same bytes. */
export const SHARED: Record<string, string[]> = {
'browser.ts': ['mcp-atlas', 'plugins-atlas'],
'ui/mouse.ts': ['mcp-atlas', 'plugins-atlas'],
'ui/palette.ts': ['mcp-atlas', 'plugins-atlas'],
'ui/render.ts': ['mcp-atlas', 'plugins-atlas'],
'ui/width.ts': ['mcp-atlas', 'plugins-atlas'],
// plugins-atlas reads its strings from its own messages module, not i18n.
'ui/ListSelect.tsx': ['mcp-atlas'],
};

const normalize = (text: string) => text.replace(/\r\n?/g, '\n');

export async function drift(read: (repo: string, file: string) => Promise<string>, own = (file: string) => readFile(join('src', file), 'utf8')) {
const out: { file: string; repo: string; error?: string }[] = [];
for (const [file, repos] of Object.entries(SHARED)) {
const mine = normalize(await own(file));
for (const repo of repos) {
try {
if (normalize(await read(repo, file)) !== mine) out.push({ file, repo });
} catch (err) {
out.push({ file, repo, error: err instanceof Error ? err.message : String(err) });
}
}
}
return out;
}

const fromGitHub = async (repo: string, file: string) => {
const res = await fetch(`https://raw.githubusercontent.com/devohmycode/${repo}/main/src/${file}`, { signal: AbortSignal.timeout(20_000) });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.text();
};
const fromSibling = (repo: string, file: string) => readFile(join('..', repo, 'src', file), 'utf8');

if (process.argv[1]?.endsWith('check-shared.ts')) {
const local = process.argv.includes('--local');
const found = await drift(local ? fromSibling : fromGitHub);
for (const d of found) console.error(`${d.error ? 'unreadable' : 'differs '} src/${d.file} ${d.repo}${d.error ? `: ${d.error}` : ''}`);
const checked = Object.values(SHARED).reduce((n, r) => n + r.length, 0);
console.log(found.length ? `${found.length} of ${checked} shared copies out of step` : `${checked} shared copies match (${local ? 'local checkouts' : 'GitHub main'})`);
process.exitCode = found.length ? 1 : 0;
}
15 changes: 15 additions & 0 deletions test/shared.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, expect, it } from 'vitest';
import { drift, SHARED } from '../scripts/check-shared.js';

describe('shared files check', () => {
it('reports copies that differ or cannot be read, ignoring line endings', async () => {
const own = async (file: string) => `${file}\n`;
const found = await drift(async (repo, file) => {
if (repo === 'plugins-atlas' && file === 'ui/mouse.ts') return 'changed\n';
if (repo === 'mcp-atlas' && file === 'ui/width.ts') throw new Error('HTTP 404');
return `${file}\r\n`;
}, own);
expect(found).toEqual([{ file: 'ui/mouse.ts', repo: 'plugins-atlas' }, { file: 'ui/width.ts', repo: 'mcp-atlas', error: 'HTTP 404' }]);
expect(Object.keys(SHARED)).toContain('browser.ts');
});
});
Loading