import { test } from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const skillRoot = path.resolve(__dirname, '..'); const repoRoot = path.resolve(skillRoot, '..'); const cursorCommand = 'npx -y skills add tt-a1i/archify --skill archify --agent cursor --global --copy --yes'; test('Cursor onboarding stays explicit, bilingual, and backed by the same Skill', () => { const english = fs.readFileSync(path.join(repoRoot, 'README.md'), 'utf8'); const englishMirror = fs.readFileSync(path.join(repoRoot, 'README_EN.md'), 'utf8'); const chinese = fs.readFileSync(path.join(repoRoot, 'README_ZH.md'), 'utf8'); const japanese = fs.readFileSync(path.join(repoRoot, 'README_JA.md'), 'utf8'); const start = fs.readFileSync(path.join(repoRoot, 'docs', 'start.html'), 'utf8'); const landing = fs.readFileSync(path.join(repoRoot, 'docs', 'index.html'), 'utf8'); assert.equal(english, englishMirror, 'English README mirrors must stay synchronized'); for (const agent of ['Cursor', 'Claude Code', 'Codex CLI', 'OpenCode']) { assert.ok(english.includes(agent), `English README must name ${agent}`); assert.ok(chinese.includes(agent), `Chinese README must name ${agent}`); assert.ok(japanese.includes(agent), `Japanese README must name ${agent}`); } for (const surface of [english, chinese, japanese, landing]) assert.ok(surface.includes(cursorCommand)); for (const surface of [english, chinese, japanese, start, landing]) { assert.doesNotMatch(surface, /skills use[^\n<]*--agent cursor/); assert.doesNotMatch(surface, /~\/\.cursor\/skills\/archify/); assert.doesNotMatch(surface, /all Cursor models|every Cursor model/i); } for (const [id, label] of [ ['cursor', 'Cursor'], ['codex', 'Codex'], ['claude-code', 'Claude Code'], ['opencode', 'OpenCode'], ['github-copilot', 'GitHub Copilot'], ]) { assert.match(start, new RegExp(`data-agent="${id}">${label}<\\/button>`)); } assert.match(start, /KNOWN_AGENTS\.has\(requestedAgent\)/); assert.match(start, /same Skill/); assert.match(start, /同一份 Skill/); assert.doesNotMatch(start, /vendor-specific (?:renderer|schema|skill)/i); }); test('the zero-dependency archive works from the canonical Cursor-visible agent path', () => { const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-cursor-package-')); const agentSkills = path.join(tmp, '.agents', 'skills'); try { fs.mkdirSync(agentSkills, { recursive: true }); execFileSync('unzip', ['-q', path.join(repoRoot, 'archify.zip'), '-d', agentSkills]); const installed = path.join(agentSkills, 'archify'); const cli = path.join(installed, 'bin', 'archify.mjs'); const doctor = execFileSync(process.execPath, [cli, 'doctor'], { encoding: 'utf8' }); assert.match(doctor, /Archify is ready\./); const fixtures = { architecture: 'web-app.architecture.json', workflow: 'agent-tool-call.workflow.json', sequence: 'cache-miss-request.sequence.json', dataflow: 'product-analytics.dataflow.json', lifecycle: 'agent-run.lifecycle.json', }; for (const [type, fixture] of Object.entries(fixtures)) { const output = execFileSync(process.execPath, [ cli, 'validate', type, path.join(installed, 'examples', fixture), '--json', ], { encoding: 'utf8' }); const receipt = JSON.parse(output); assert.equal(receipt.ok, true, `${type}: installed package validation failed`); assert.equal(receipt.type, type); } } finally { fs.rmSync(tmp, { recursive: true, force: true }); } });