Files
trackgo-fe/.agents/skills/archify/test/release-identity.test.mjs
T
shancheas 166e0d40ac feat: introduce archify skill for generating architecture diagrams
- Added a new Archify skill, enabling users to create polished architecture, workflow, sequence, data-flow, and lifecycle diagrams.
- Implemented comprehensive functionality including rendering, validation, and delivery of diagrams in various formats.
- Integrated a user-friendly command-line interface for generating and previewing diagrams.
- Developed supporting files including package.json, LICENSE, and SKILL.md for documentation and licensing.
- Added unit tests to ensure reliability and functionality of the new skill.

These changes enhance the application by providing a structured approach to visualizing system architecture and workflows, improving user experience and data representation.
2026-08-31 11:31:29 +07:00

505 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import test from 'node:test';
import assert from 'node:assert/strict';
const here = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(here, '..', '..');
const checker = path.join(repoRoot, 'scripts', 'check-release-identity.mjs');
function writeFile(root, relativePath, content) {
const target = path.join(root, relativePath);
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, content);
}
function runCheck(root) {
return spawnSync(process.execPath, [checker, '--root', root], {
encoding: 'utf8',
});
}
function stableUpdateManifest(version) {
return JSON.stringify({
schemaVersion: 1,
skillId: 'archify',
channel: 'stable',
version,
publishedAt: '2026-07-29T00:00:00Z',
source: {
repository: 'https://github.com/tt-a1i/archify',
ref: `v${version}`,
treeSha: 'a'.repeat(40),
},
artifact: { sha256: 'b'.repeat(64) },
summary: 'Published stable release.',
releaseNotes: `https://github.com/tt-a1i/archify/releases/tag/v${version}`,
severity: 'normal',
});
}
function writeValidDevelopmentFixture(root, overrides = {}) {
const version = '2.13.0-dev.0';
const english = [
'![Development Version](https://img.shields.io/badge/version-2.13.0--dev.0-blue)',
'',
`Current development version: \`v${version}\``,
'',
'Raven uses manual ZIP installation: extract archify.zip into `~/.raven/workspace/skills`, which yields `~/.raven/workspace/skills/archify`; Raven is not an agent-switcher target.',
].join('\n');
const chinese = [
'![开发版本](https://img.shields.io/badge/version-2.13.0--dev.0-blue)',
'',
`当前开发版本:\`v${version}\``,
'',
'Raven 使用 ZIP 手动安装:将 archify.zip 解压到 `~/.raven/workspace/skills`,解压后会得到 `~/.raven/workspace/skills/archify`Raven 不属于 Agent 切换器目标。',
].join('\n');
const files = {
'archify/package.json': JSON.stringify({ version }),
'archify/package-lock.json': JSON.stringify({ version, packages: { '': { version } } }),
'archify/skill-release.json': JSON.stringify({
schemaVersion: 1,
skillId: 'archify',
channel: 'development',
version,
source: { repository: 'https://github.com/tt-a1i/archify' },
updateManifestUrl: 'https://tt-a1i.github.io/archify/skill-updates/archify/stable.json',
}),
'docs/skill-updates/archify/stable.json': stableUpdateManifest('2.12.0'),
'archify/SKILL.md': '---\nmetadata:\n version: "2.13"\n---\n',
'archify/assets/template.html': '<meta name="generator" content="archify 2.13.0-dev.0">',
'CHANGELOG.md': [
'# Changelog',
'',
'## [Unreleased]',
'',
`> Development identity: \`v${version}\`. Not a stable release.`,
'',
'### Added',
'- Real unreleased work.',
'',
'## [2.12.0] — 2026-07-23',
'',
].join('\n'),
'README.md': english,
'README_EN.md': english,
'README_ZH.md': chinese,
'scripts/start-template.html': 'development · 开发版 · [[ARCHIFY_VERSION]]',
'scripts/guide-template.html': 'development · 开发版 · [[ARCHIFY_VERSION]]',
'scripts/gallery-template.html': 'development · 开发版 · [[ARCHIFY_VERSION]]',
'docs/index.html': `<span>development · v${version} · 开发版 · 9/9 checks</span><p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills,解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>`,
'docs/start.html': `<span>development · v${version} · 开发版</span><p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills,解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>`,
'ROADMAP.md': `The current development line is \`v${version}\`; it contains the work under Changelog Unreleased and is not a stable release.`,
};
for (const [relativePath, content] of Object.entries({ ...files, ...overrides })) {
writeFile(root, relativePath, content);
}
}
function writeValidStableFixture(root, overrides = {}) {
const version = '2.13.0';
const english = [
'![Stable Version](https://img.shields.io/badge/version-2.13.0-blue)',
'',
`Current stable version: \`v${version}\``,
'',
'Raven uses manual ZIP installation: extract archify.zip into `~/.raven/workspace/skills`, which yields `~/.raven/workspace/skills/archify`; Raven is not an agent-switcher target.',
].join('\n');
const chinese = [
'![稳定版本](https://img.shields.io/badge/version-2.13.0-blue)',
'',
`当前稳定版本:\`v${version}\``,
'',
'Raven 使用 ZIP 手动安装:将 archify.zip 解压到 `~/.raven/workspace/skills`,解压后会得到 `~/.raven/workspace/skills/archify`Raven 不属于 Agent 切换器目标。',
].join('\n');
const files = {
'archify/package.json': JSON.stringify({ version }),
'archify/package-lock.json': JSON.stringify({ version, packages: { '': { version } } }),
'archify/skill-release.json': JSON.stringify({
schemaVersion: 1,
skillId: 'archify',
channel: 'stable',
version,
source: { repository: 'https://github.com/tt-a1i/archify' },
updateManifestUrl: 'https://tt-a1i.github.io/archify/skill-updates/archify/stable.json',
}),
'docs/skill-updates/archify/stable.json': stableUpdateManifest(version),
'archify/SKILL.md': '---\nmetadata:\n version: "2.13"\n---\n',
'archify/assets/template.html': '<meta name="generator" content="archify 2.13.0">',
'CHANGELOG.md': [
'# Changelog',
'',
'## [Unreleased]',
'',
'## [2.13.0] — 2026-07-29',
'- Published work.',
'',
].join('\n'),
'README.md': english,
'README_EN.md': english,
'README_ZH.md': chinese,
'scripts/start-template.html': 'stable · 稳定版 · [[ARCHIFY_VERSION]]',
'scripts/guide-template.html': 'stable · 稳定版 · [[ARCHIFY_VERSION]]',
'scripts/gallery-template.html': 'stable · 稳定版 · [[ARCHIFY_VERSION]]',
'docs/index.html': `<span>stable · v${version} · 稳定版 · 9/9 checks</span><p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills,解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>`,
'docs/start.html': `<span>stable · v${version} · 稳定版</span><p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills,解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>`,
'ROADMAP.md': `The current stable version is \`v${version}\`.`,
};
for (const [relativePath, content] of Object.entries({ ...files, ...overrides })) {
writeFile(root, relativePath, content);
}
}
test('an empty Unreleased section accepts a coherent stable release identity', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidStableFixture(fixture);
const result = runCheck(fixture);
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /release identity ok: 2\.13\.0/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('stable release preparation allows only the immediate prior public manifest', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
const changelog = [
'# Changelog',
'',
'## [Unreleased]',
'',
'## [2.13.0] — 2026-07-29',
'- Release being prepared.',
'',
'## [2.12.0] — 2026-07-23',
'- Previously published release.',
'',
].join('\n');
writeValidStableFixture(fixture, {
'CHANGELOG.md': changelog,
'docs/skill-updates/archify/stable.json': stableUpdateManifest('2.12.0'),
});
const prior = runCheck(fixture);
assert.equal(prior.status, 0, prior.stderr);
writeFile(fixture, 'docs/skill-updates/archify/stable.json', stableUpdateManifest('2.11.0'));
const stale = runCheck(fixture);
assert.notEqual(stale.status, 0);
assert.match(stale.stderr, /immediate prior v2\.12\.0/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('the embedded update identity must match the package release exactly', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidDevelopmentFixture(fixture, {
'archify/skill-release.json': JSON.stringify({
schemaVersion: 1,
skillId: 'archify',
channel: 'stable',
version: '2.12.0',
source: { repository: 'https://example.com/untrusted/archify' },
updateManifestUrl: 'https://example.com/latest.json',
}),
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /archify\/skill-release\.json must identify archify 2\.13\.0-dev\.0 as development/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('the published update manifest must track the newest stable changelog release', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidDevelopmentFixture(fixture, {
'docs/skill-updates/archify/stable.json': stableUpdateManifest('2.11.0'),
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /stable\.json must describe the newest published stable v2\.12\.0/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('the published update manifest must use a canonical UTC timestamp', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
const manifest = JSON.parse(stableUpdateManifest('2.12.0'));
manifest.publishedAt = '2026-07-29T08:00:00+08:00';
writeValidDevelopmentFixture(fixture, {
'docs/skill-updates/archify/stable.json': JSON.stringify(manifest),
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /stable\.json must describe the newest published stable v2\.12\.0/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('package identities reject leading-zero core and prerelease identifiers', () => {
for (const version of ['02.13.0', '2.13.0-dev.01']) {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidDevelopmentFixture(fixture, {
'archify/package.json': JSON.stringify({ version }),
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /not a supported SemVer identity/, version);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
}
});
test('the newest stable release is selected by SemVer rather than changelog order', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidDevelopmentFixture(fixture, {
'CHANGELOG.md': [
'# Changelog',
'',
'## [Unreleased]',
'',
'> Development identity: `v2.13.0-dev.0`. Not a stable release.',
'',
'### Added',
'- Real unreleased work.',
'',
'## [2.11.0] — 2026-07-16',
'',
'## [2.12.0] — 2026-07-23',
'',
].join('\n'),
});
const result = runCheck(fixture);
assert.equal(result.status, 0, result.stderr);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('real Unreleased changes cannot reuse a stable published package identity', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeFile(fixture, 'archify/package.json', JSON.stringify({ version: '2.12.0' }));
writeFile(fixture, 'CHANGELOG.md', [
'# Changelog',
'',
'## [Unreleased]',
'',
'### Added',
'- Real unreleased work.',
'',
'## [2.12.0] — 2026-07-23',
'',
].join('\n'));
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /Unreleased changes require a prerelease package version/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('package, lockfile, Skill metadata, escaped Shields badge, and public docs share one development identity', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeFile(fixture, 'archify/package.json', JSON.stringify({ version: '2.13.0-dev.0' }));
writeFile(fixture, 'archify/package-lock.json', JSON.stringify({
version: '2.12.0',
packages: { '': { version: '2.12.0' } },
}));
writeFile(fixture, 'archify/SKILL.md', '---\nmetadata:\n version: "2.12"\n---\n');
writeFile(fixture, 'CHANGELOG.md', [
'# Changelog',
'',
'## [Unreleased]',
'',
'### Added',
'- Real unreleased work.',
'',
'## [2.12.0] — 2026-07-23',
'',
].join('\n'));
const staleEnglish = [
'![Version](https://img.shields.io/badge/version-2.13.0-blue)',
'',
'Archify 2.12 includes unreleased capabilities.',
].join('\n');
writeFile(fixture, 'README.md', staleEnglish);
writeFile(fixture, 'README_EN.md', staleEnglish);
writeFile(fixture, 'README_ZH.md', '![Version](https://img.shields.io/badge/version-2.13.0-blue)\n\nArchify 2.12 包含未发布能力。\n');
writeFile(fixture, 'docs/index.html', '<span>Agent Skill · v2.12.0</span>');
writeFile(fixture, 'docs/start.html', '<span>Archify v2.12.0</span>');
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /package-lock\.json must match 2\.13\.0-dev\.0/);
assert.match(result.stderr, /SKILL\.md metadata version 2\.12 must map to package 2\.13\.0-dev\.0/);
assert.match(result.stderr, /README\.md must advertise development identity v2\.13\.0-dev\.0/);
assert.match(result.stderr, /docs\/index\.html must advertise development identity v2\.13\.0-dev\.0/);
assert.match(result.stderr, /docs\/start\.html must advertise development identity v2\.13\.0-dev\.0/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('landing proof receipt matches the current nine-check artifact contract', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidDevelopmentFixture(fixture, {
'docs/index.html': '<span>development · v2.13.0-dev.0 · 开发版 · 8/8 checks</span><p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills,解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>',
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /docs\/index\.html proof receipt must say 9\/9/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('landing rejects every stale N/N contract count even when 9/9 is also present', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidDevelopmentFixture(fixture, {
'docs/index.html': [
'<span>development · v2.13.0-dev.0 · 开发版 · 9/9 checks</span>',
'<span>legacy receipt · 7/7 checks</span>',
'<p>Raven manual ZIP / ZIP 手动安装: extract archify.zip into ~/.raven/workspace/skills, which yields ~/.raven/workspace/skills/archify; 将 archify.zip 解压到 ~/.raven/workspace/skills,解压后会得到 ~/.raven/workspace/skills/archify; not an agent-switcher target.</p>',
].join('\n'),
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /every N\/N proof receipt must be exactly 9\/9; found 7\/7/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('Raven stays a truthful manual ZIP install and never becomes a generated agent-switcher command', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidDevelopmentFixture(fixture, {
'docs/start.html': [
'<span>development · v2.13.0-dev.0 · 开发版</span>',
'<button data-agent="raven">Raven</button>',
'<pre>npx skills add tt-a1i/archify --agent raven</pre>',
].join('\n'),
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /Raven must remain a manual ZIP installation outside the agent switcher/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('Raven instructions reject extracting the archive into the final Skill directory', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
const nestedEnglish = [
'![Development Version](https://img.shields.io/badge/version-2.13.0--dev.0-blue)',
'',
'Current development version: `v2.13.0-dev.0`',
'',
'Raven is manual ZIP only: extract archify.zip into `~/.raven/workspace/skills/archify`; Raven is not an agent-switcher target.',
].join('\n');
writeValidDevelopmentFixture(fixture, {
'README.md': nestedEnglish,
'README_EN.md': nestedEnglish,
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /extract archify\.zip into ~\/\.raven\/workspace\/skills, yielding ~\/\.raven\/workspace\/skills\/archify/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('renderer template generator carries the complete package prerelease identity', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidDevelopmentFixture(fixture, {
'archify/assets/template.html': '<meta name="generator" content="archify 2.12.0">',
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /archify\/assets\/template\.html generator must be archify 2\.13\.0-dev\.0/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('roadmap current identity follows the package release state', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidDevelopmentFixture(fixture, {
'ROADMAP.md': 'The current development line is `v2.12.0`; it is not a stable release.',
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /ROADMAP\.md must declare the current development line as v2\.13\.0-dev\.0/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('generated public-page templates keep a development marker and version placeholder', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidDevelopmentFixture(fixture, {
'scripts/gallery-template.html': 'Proof Lab / 2.12.0',
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /scripts\/gallery-template\.html must use \[\[ARCHIFY_VERSION\]\] with development and 开发版 labels/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});
test('stable public-page templates reject development labels on version-bearing fallbacks', () => {
const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-release-identity-'));
try {
writeValidStableFixture(fixture, {
'scripts/guide-template.html': [
'<span data-i18n="versionLabel">Scenario guide / development / v[[ARCHIFY_VERSION]]</span>',
"versionLabel:'Scenario guide / stable / v[[ARCHIFY_VERSION]]'",
"versionLabel:'场景指南 / 稳定版 / v[[ARCHIFY_VERSION]]'",
].join('\n'),
});
const result = runCheck(fixture);
assert.notEqual(result.status, 0);
assert.match(result.stderr, /scripts\/guide-template\.html must not label \[\[ARCHIFY_VERSION\]\] as development or 开发版/);
} finally {
fs.rmSync(fixture, { recursive: true, force: true });
}
});