Files
trackgo-fe/.agents/skills/archify/test/cli.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

786 lines
33 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { spawn, spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { extractSvgs, parseXml } from './helpers/xml.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const skillRoot = path.resolve(__dirname, '..');
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-cli-'));
const cli = path.join(skillRoot, 'bin/archify.mjs');
function run(args, options = {}) {
return spawnSync(process.execPath, [cli, ...args], {
cwd: options.cwd || skillRoot,
encoding: 'utf8',
env: options.env || process.env,
});
}
function sha256(file) {
return createHash('sha256').update(fs.readFileSync(file)).digest('hex');
}
function makeFakeOpeners(name, { exitCode = 0 } = {}) {
const bin = path.join(tmp, name);
const log = path.join(bin, 'open-log.json');
fs.mkdirSync(bin, { recursive: true });
const source = `#!/usr/bin/env node
const fs = require('node:fs');
const target = process.argv[process.argv.length - 1];
fs.writeFileSync(process.env.ARCHIFY_TEST_OPEN_LOG, JSON.stringify({
argv: process.argv.slice(2),
target,
existed: fs.existsSync(target),
}));
process.exit(${exitCode});
`;
for (const command of ['open', 'xdg-open']) {
const executable = path.join(bin, command);
fs.writeFileSync(executable, source);
fs.chmodSync(executable, 0o755);
}
return {
log,
env: {
...process.env,
PATH: `${bin}${path.delimiter}${process.env.PATH || ''}`,
ARCHIFY_TEST_OPEN_LOG: log,
},
};
}
function copyInstalledSkill(target) {
fs.cpSync(skillRoot, target, {
recursive: true,
filter(source) {
const rel = path.relative(skillRoot, source);
return rel !== 'node_modules' && !rel.startsWith(`node_modules${path.sep}`)
&& rel !== 'test' && !rel.startsWith(`test${path.sep}`)
// Another test creates this short-lived directory under skillRoot so
// Ajv resolves from the checkout. Never copy a concurrently removed
// test fixture into an installed-skill simulation.
&& !rel.startsWith('.validator-check-');
},
});
}
test('cli: help lists commands and diagram types', () => {
const result = run(['--help']);
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /archify render <type>/);
assert.match(result.stdout, /archify compare architecture <base\.json> <head\.json>/);
assert.match(result.stdout, /archify deliver <type>/);
assert.match(result.stdout, /archify preview <type>/);
assert.match(result.stdout, /archify visual-check <output\.html>/);
assert.match(result.stdout, /--open/);
assert.match(result.stdout, /--repo-root path \(architecture only\)/);
assert.match(result.stdout, /archify guide \[scenario or question\]/);
assert.match(result.stdout, /archify doctor/);
assert.match(result.stdout, /archify demo \[output-directory\]/);
assert.match(result.stdout, /architecture, workflow, sequence, dataflow, lifecycle/);
});
test('cli: doctor reports a complete installation is ready', () => {
const result = run(['doctor']);
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /\[ok\] Node\.js v\d+/);
assert.match(result.stdout, /\[ok\] Core template/);
assert.match(result.stdout, /\[ok\] Example renderer/);
assert.match(result.stdout, /\[ok\] Live preview runtime/);
assert.match(result.stdout, /\[ok\] Scenario recipe guide/);
assert.match(result.stdout, /\[ok\] Progressive authoring references/);
assert.match(result.stdout, /\[ok\] Architecture compare runtime and proof fixtures/);
assert.match(result.stdout, /\[ok\] Standalone schema validators/);
assert.match(result.stdout, /\[ok\] architecture renderer, schema, and example/);
assert.match(result.stdout, /\[ok\] lifecycle renderer, schema, and example/);
assert.match(result.stdout, /Archify is ready\./);
});
test('cli: doctor identifies an incomplete installation', () => {
const incompleteRoot = path.join(tmp, 'incomplete-skill');
const incompleteBin = path.join(incompleteRoot, 'bin');
fs.mkdirSync(incompleteBin, { recursive: true });
fs.copyFileSync(cli, path.join(incompleteBin, 'archify.mjs'));
const result = spawnSync(process.execPath, [path.join(incompleteBin, 'archify.mjs'), 'doctor'], {
cwd: incompleteRoot,
encoding: 'utf8',
});
assert.equal(result.status, 1);
assert.match(result.stdout, /\[missing\] Core template/);
assert.match(result.stdout, /\[missing\] Scenario recipe guide/);
assert.match(result.stdout, /\[missing\] workflow renderer, schema, and example/);
assert.match(result.stderr, /Archify is not ready: \d+ required files? missing\./);
});
test('cli: doctor rejects a corrupt standalone validator', () => {
const corruptRoot = path.join(tmp, 'corrupt-skill');
copyInstalledSkill(corruptRoot);
fs.writeFileSync(path.join(corruptRoot, 'renderers/shared/generated-validators.mjs'), 'export const workflow = ;\n');
const result = spawnSync(process.execPath, [path.join(corruptRoot, 'bin/archify.mjs'), 'doctor'], {
cwd: corruptRoot,
encoding: 'utf8',
});
assert.equal(result.status, 1);
assert.match(result.stdout, /\[invalid\] Standalone schema validators/);
assert.match(result.stderr, /Archify is not ready: 1 runtime check failed\./);
});
test('cli: examples renders from an installed skill', () => {
const installedRoot = path.join(tmp, 'installed-skill');
copyInstalledSkill(installedRoot);
const result = spawnSync(process.execPath, [path.join(installedRoot, 'bin/archify.mjs'), 'examples'], {
cwd: installedRoot,
encoding: 'utf8',
});
assert.equal(result.status, 0, result.stderr);
for (const output of [
'workflow-agent-tool-call-rendered.html',
'sequence-cache-miss-request.html',
'dataflow-product-analytics.html',
'lifecycle-agent-run.html',
'web-app-rendered.html',
]) {
assert.equal(fs.existsSync(path.join(installedRoot, 'examples', output)), true, output);
}
});
test('cli: guide lists all scenario recipes by diagram type', () => {
const result = run(['guide']);
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /Archify scenario recipes \(11\)/);
for (const type of ['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']) {
assert.match(result.stdout, new RegExp(`\\[${type}\\]`));
}
});
test('cli: guide recommends a scenario as structured json', () => {
const result = run(['guide', 'Show an API request with Redis cache miss', '--json']);
assert.equal(result.status, 0, result.stderr);
const parsed = JSON.parse(result.stdout);
assert.equal(parsed.ok, true);
assert.equal(parsed.lang, 'en');
assert.equal(parsed.confidence, 'high');
assert.equal(parsed.recommendation.id, 'api-request');
assert.equal(parsed.recommendation.type, 'sequence');
});
test('cli: guide detects Chinese and explains the recommendation boundary', () => {
const result = run(['guide', '展示 Kafka topic 消费者组和死信队列']);
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /推荐: 事件流拓扑 \[dataflow\]/);
assert.match(result.stdout, /不要这样用:/);
assert.match(result.stdout, /必须包含:/);
assert.match(result.stdout, /可直接复制的提示词:/);
});
test('cli: guide works from an installed skill without node_modules', () => {
const installedRoot = path.join(tmp, 'installed-guide-skill');
copyInstalledSkill(installedRoot);
const installedCli = path.join(installedRoot, 'bin/archify.mjs');
const result = spawnSync(process.execPath, [installedCli, 'guide', 'incident-runbook', '--json'], {
cwd: installedRoot,
encoding: 'utf8',
});
assert.equal(result.status, 0, result.stderr);
assert.equal(JSON.parse(result.stdout).recommendation.id, 'incident-runbook');
});
test('cli: demo creates a ready-to-open diagram in a chosen directory', () => {
const outputDirectory = path.join(tmp, 'my-demo');
const output = path.join(outputDirectory, 'archify-demo.html');
const result = run(['demo', outputDirectory]);
assert.equal(result.status, 0, result.stderr);
assert.equal(fs.existsSync(output), true);
assert.match(fs.readFileSync(output, 'utf8'), /Sample Web App Diagram/);
assert.match(result.stdout, new RegExp(`Demo ready: ${output.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
assert.match(result.stdout, /Next: open the HTML in your browser/);
assert.match(result.stdout, /archify render architecture/);
});
test('cli: demo defaults to the current directory', () => {
const workingDirectory = path.join(tmp, 'default-demo');
fs.mkdirSync(workingDirectory);
const result = run(['demo'], { cwd: workingDirectory });
assert.equal(result.status, 0, result.stderr);
assert.equal(fs.existsSync(path.join(workingDirectory, 'archify-demo.html')), true);
});
test('cli: render writes a diagram html file', () => {
const out = path.join(tmp, 'workflow.html');
const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
const result = run(['render', 'workflow', input, out]);
assert.equal(result.status, 0, result.stderr);
assert.equal(fs.existsSync(out), true);
assert.match(fs.readFileSync(out, 'utf8'), /Agent Tool Call Workflow/);
});
test('cli: visual-check returns a skipped receipt with exit 2 when Chrome is unavailable', () => {
const out = path.join(tmp, 'visual-check-skipped.html');
fs.writeFileSync(out, '<!doctype html><html><body>delivered</body></html>');
const missingChrome = path.join(tmp, 'missing-chrome');
const result = run(['visual-check', out, '--json'], {
env: { ...process.env, ARCHIFY_CHROME: missingChrome },
});
assert.equal(result.status, 2, result.stderr);
const receipt = JSON.parse(result.stdout);
assert.equal(receipt.status, 'skipped');
assert.equal(receipt.visualReview, 'pending');
assert.equal(receipt.chrome.status, 'unavailable');
assert.equal(fs.existsSync(out.replace(/\.html$/, '.visual-check.json')), true);
});
test('cli: deliver atomically writes a checked artifact and structured receipt', () => {
const out = path.join(tmp, 'delivered-workflow.html');
const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
const result = run(['deliver', 'workflow', input, out, '--quality', 'showcase', '--json']);
assert.equal(result.status, 0, result.stderr);
assert.equal(fs.existsSync(out), true);
assert.match(fs.readFileSync(out, 'utf8'), /Agent Tool Call Workflow/);
const receipt = JSON.parse(result.stdout);
assert.equal(receipt.schemaVersion, 1);
assert.equal(receipt.ok, true);
assert.equal(receipt.command, 'deliver');
assert.equal(receipt.type, 'workflow');
assert.equal(receipt.input, input);
assert.equal(receipt.output, out);
assert.deepEqual(receipt.specification, {
sha256: sha256(input),
bytes: fs.statSync(input).size,
});
assert.match(receipt.artifact.sha256, /^[a-f0-9]{64}$/);
assert.equal(receipt.artifact.sha256, sha256(out));
assert.equal(receipt.artifact.bytes, fs.statSync(out).size);
assert.deepEqual(receipt.validation, {
checksPassed: 9,
checkCount: 9,
compositionProfile: 'showcase',
compositionStatus: 'pass',
errors: 0,
warnings: 0,
});
assert.equal('open' in receipt, false);
});
test('cli: deliver --open launches only the committed absolute artifact as one argument', {
skip: process.platform === 'win32',
}, () => {
const fake = makeFakeOpeners('successful-open');
const out = path.join(tmp, `-复杂 path 'quoted'`, 'verified diagram.html');
const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
const result = run(['deliver', 'workflow', input, out, '--open', '--json'], { env: fake.env });
assert.equal(result.status, 0, result.stderr);
const receipt = JSON.parse(result.stdout);
assert.deepEqual(receipt.open, {
requested: true,
status: 'opened',
target: out,
method: process.platform === 'darwin' ? 'open' : 'xdg-open',
});
const invocation = JSON.parse(fs.readFileSync(fake.log, 'utf8'));
assert.equal(invocation.existed, true, 'the opener must run after the atomic commit');
assert.deepEqual(invocation.argv, [out]);
assert.equal(invocation.target, out);
assert.equal(fs.existsSync(out), true);
});
test('cli: opener failure does not invalidate a verified delivery or pollute json stdout', {
skip: process.platform === 'win32',
}, () => {
const fake = makeFakeOpeners('failed-open', { exitCode: 17 });
const out = path.join(tmp, 'open-failure-preserves-delivery.html');
const input = path.join(skillRoot, 'examples/web-app.architecture.json');
const result = run(['deliver', 'architecture', input, out, '--open', '--json'], { env: fake.env });
assert.equal(result.status, 0, result.stderr);
const receipt = JSON.parse(result.stdout);
assert.equal(receipt.ok, true);
assert.equal(receipt.open.status, 'failed');
assert.equal(receipt.open.target, out);
assert.match(result.stderr, /Could not open the verified artifact/);
assert.match(result.stderr, new RegExp(out.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
assert.equal(fs.existsSync(out), true);
assert.equal(receipt.artifact.sha256, sha256(out));
});
test('cli: deliver failure never invokes the optional opener', {
skip: process.platform === 'win32',
}, () => {
const fake = makeFakeOpeners('never-open');
const input = path.join(tmp, 'invalid-open-delivery.json');
fs.writeFileSync(input, '{broken json');
const out = path.join(tmp, 'must-not-open.html');
const result = run(['deliver', 'architecture', input, out, '--open', '--json'], { env: fake.env });
assert.equal(result.status, 1);
assert.equal(JSON.parse(result.stdout).stage, 'input');
assert.equal(fs.existsSync(fake.log), false);
assert.equal(fs.existsSync(out), false);
});
test('cli: a missing optional opener module preserves verified delivery with a fallback receipt', () => {
const installedRoot = path.join(tmp, 'missing-open-module-skill');
copyInstalledSkill(installedRoot);
const installedCli = path.join(installedRoot, 'bin/archify.mjs');
fs.rmSync(path.join(installedRoot, 'bin/open-artifact.mjs'));
const input = path.join(installedRoot, 'examples/agent-tool-call.workflow.json');
const out = path.join(tmp, 'missing-open-module-delivery.html');
const result = spawnSync(process.execPath, [installedCli, 'deliver', 'workflow', input, out, '--open', '--json'], {
cwd: installedRoot,
encoding: 'utf8',
});
assert.equal(result.status, 0, result.stderr);
const receipt = JSON.parse(result.stdout);
assert.equal(receipt.ok, true);
assert.deepEqual(receipt.open, {
requested: true,
status: 'unsupported',
target: out,
method: null,
});
assert.match(result.stderr, /Open it manually/);
assert.equal(receipt.artifact.sha256, sha256(out));
});
test('cli: deliver preserves the renderer default output contract', () => {
const workingDirectory = path.join(tmp, 'delivery-default-output');
fs.mkdirSync(workingDirectory, { recursive: true });
const input = path.join(workingDirectory, 'source.architecture.json');
const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
source.meta.output = 'verified-default.html';
fs.writeFileSync(input, JSON.stringify(source));
const result = run(['deliver', 'architecture', input, '--json'], { cwd: workingDirectory });
assert.equal(result.status, 0, result.stderr);
const receipt = JSON.parse(result.stdout);
assert.equal(receipt.output, path.join(fs.realpathSync(workingDirectory), 'verified-default.html'));
assert.equal(fs.existsSync(receipt.output), true);
});
test('cli: deliver works from an installed skill without node_modules', () => {
const installedRoot = path.join(tmp, 'installed-deliver-skill');
copyInstalledSkill(installedRoot);
const installedCli = path.join(installedRoot, 'bin/archify.mjs');
const cases = [
['architecture-boundaries', 'architecture', 'production-deployment.architecture.json'],
['architecture-issue-110', 'architecture', 'brand-aware-delivery.architecture.json'],
['workflow', 'workflow', 'agent-tool-call.workflow.json'],
['sequence', 'sequence', 'cache-miss-request.sequence.json'],
['dataflow', 'dataflow', 'product-analytics.dataflow.json'],
['lifecycle', 'lifecycle', 'agent-run.lifecycle.json'],
];
for (const [label, type, example] of cases) {
const input = path.join(installedRoot, 'examples', example);
const out = path.join(tmp, `installed-${label}-delivery.html`);
const result = spawnSync(process.execPath, [installedCli, 'deliver', type, input, out, '--json'], {
cwd: installedRoot,
encoding: 'utf8',
});
assert.equal(result.status, 0, `${label}: ${result.stderr}`);
assert.equal(JSON.parse(result.stdout).validation.checkCount, 9, label);
assert.equal(fs.existsSync(out), true, label);
const extracted = extractSvgs(fs.readFileSync(out, 'utf8'));
assert.equal(extracted.direct.length, 1, `${label}: expected one delivered SVG`);
assert.doesNotThrow(
() => parseXml(extracted.direct[0]),
`${label}: delivered SVG must be well-formed XML`,
);
}
});
test('cli: deliver XML guard parses markup instead of scanning attribute-like text', () => {
assert.doesNotThrow(() => parseXml(
'<svg xmlns="http://www.w3.org/2000/svg" aria-label="mentions data-node-label safely"/>',
));
assert.throws(
() => parseXml('<svg xmlns="http://www.w3.org/2000/svg" data-node-label></svg>'),
/attribute without value/i,
);
assert.throws(
() => parseXml('<svg xmlns="http://www.w3.org/2000/svg"><g></svg>'),
/unexpected close tag/i,
);
});
test('cli: preview runs from an installed skill without node_modules and exits cleanly', { timeout: 30000 }, async () => {
const installedRoot = path.join(tmp, 'installed-preview-skill');
copyInstalledSkill(installedRoot);
const installedCli = path.join(installedRoot, 'bin/archify.mjs');
const input = path.join(installedRoot, 'examples/web-app.architecture.json');
const output = path.join(tmp, 'installed-preview.html');
const child = spawn(process.execPath, [installedCli, 'preview', 'architecture', input, output, '--quality', 'showcase', '--no-open'], {
cwd: installedRoot,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
});
let stdout = '';
let stderr = '';
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stdout.on('data', (chunk) => { stdout += chunk; });
child.stderr.on('data', (chunk) => { stderr += chunk; });
let previewUrl;
const started = Date.now();
while (!previewUrl && Date.now() - started < 8000) {
previewUrl = stdout.match(/preview (http:\/\/127\.0\.0\.1:\d+\/)/)?.[1];
if (!previewUrl) await new Promise((resolve) => setTimeout(resolve, 40));
}
assert.ok(previewUrl, `preview URL missing; stdout=${stdout}; stderr=${stderr}`);
let state;
while (Date.now() - started < 15000) {
state = await fetch(new URL('/state', previewUrl)).then((response) => response.json());
if (state.status === 'verified') break;
await new Promise((resolve) => setTimeout(resolve, 50));
}
assert.equal(state?.status, 'verified', `preview did not verify; stdout=${stdout}; stderr=${stderr}`);
assert.equal(state.revision, 1);
assert.equal(fs.existsSync(output), true);
child.kill('SIGTERM');
const exit = await new Promise((resolve) => child.once('close', (code, signal) => resolve({ code, signal })));
assert.deepEqual(exit, { code: 0, signal: null });
assert.match(stdout, /stopping preview/);
await assert.rejects(fetch(previewUrl));
assert.deepEqual(fs.readdirSync(path.dirname(output)).filter((name) => name.startsWith('.archify-preview-')), []);
});
test('cli: deliver preserves the previous artifact when the final check fails', () => {
const installedRoot = path.join(tmp, 'broken-deliver-skill');
copyInstalledSkill(installedRoot);
const installedCli = path.join(installedRoot, 'bin/archify.mjs');
const templatePath = path.join(installedRoot, 'assets/template.html');
const template = fs.readFileSync(templatePath, 'utf8');
fs.writeFileSync(templatePath, template.replace('</body>', '<svg aria-label="accidental second svg"></svg>\n</body>'));
const input = path.join(installedRoot, 'examples/web-app.architecture.json');
const out = path.join(tmp, 'preserved-delivery.html');
const trustedPriorArtifact = '<!doctype html><title>trusted prior artifact</title>\n';
fs.writeFileSync(out, trustedPriorArtifact);
const result = spawnSync(process.execPath, [installedCli, 'deliver', 'architecture', input, out, '--json'], {
cwd: installedRoot,
encoding: 'utf8',
});
assert.equal(result.status, 1);
const failure = JSON.parse(result.stdout);
assert.equal(failure.ok, false);
assert.equal(failure.stage, 'check');
assert.equal(failure.diagnostics[0].code, 'artifact/single-svg');
assert.equal(failure.diagnostics[0].subject.check, 'single_svg');
assert.ok(failure.diagnostics[0].supportedFixes.some((fix) => fix.includes('exactly one diagram SVG')));
assert.equal(failure.checker.checks.find((entry) => entry.name === 'single_svg').ok, false);
assert.equal(fs.readFileSync(out, 'utf8'), trustedPriorArtifact);
assert.deepEqual(
fs.readdirSync(path.dirname(out)).filter((name) => name.includes('.archify-delivery-')),
[],
);
});
test('cli: deliver reports renderer failure as json and preserves the previous artifact', () => {
const input = path.join(tmp, 'invalid-delivery.workflow.json');
const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-tool-call.workflow.json'), 'utf8'));
source.nodes[0].unexpected = true;
fs.writeFileSync(input, JSON.stringify(source));
const out = path.join(tmp, 'renderer-failure-preserved.html');
const trustedPriorArtifact = '<!doctype html><title>last known good</title>\n';
fs.writeFileSync(out, trustedPriorArtifact);
const result = run(['deliver', 'workflow', input, out, '--json']);
assert.equal(result.status, 1);
const failure = JSON.parse(result.stdout);
assert.equal(failure.ok, false);
assert.equal(failure.stage, 'render');
assert.match(failure.error, /schema validation failed/i);
assert.equal(fs.readFileSync(out, 'utf8'), trustedPriorArtifact);
});
test('cli: deliver reports unreadable input as json without touching the target', () => {
const input = path.join(tmp, 'malformed-delivery.json');
fs.writeFileSync(input, '{not valid json');
const out = path.join(tmp, 'malformed-input-preserved.html');
const trustedPriorArtifact = '<!doctype html><title>still trusted</title>\n';
fs.writeFileSync(out, trustedPriorArtifact);
const result = run(['deliver', 'architecture', input, out, '--json']);
assert.equal(result.status, 1);
const failure = JSON.parse(result.stdout);
assert.equal(failure.ok, false);
assert.equal(failure.stage, 'input');
assert.match(failure.error, /Could not read delivery input/);
assert.equal(fs.readFileSync(out, 'utf8'), trustedPriorArtifact);
});
test('cli: invalid source output metadata still fails inside the renderer', () => {
const workingDirectory = path.join(tmp, 'invalid-output-metadata');
fs.mkdirSync(workingDirectory, { recursive: true });
const input = path.join(workingDirectory, 'source.architecture.json');
const source = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/web-app.architecture.json'), 'utf8'));
source.meta.output = 17;
fs.writeFileSync(input, JSON.stringify(source));
const out = path.join(workingDirectory, 'architecture.html');
const trustedPriorArtifact = '<!doctype html><title>metadata did not replace me</title>\n';
fs.writeFileSync(out, trustedPriorArtifact);
const result = run(['deliver', 'architecture', input, '--json'], { cwd: workingDirectory });
assert.equal(result.status, 1);
const failure = JSON.parse(result.stdout);
assert.equal(failure.stage, 'render');
assert.match(failure.error, /schema validation failed/i);
assert.equal(fs.readFileSync(out, 'utf8'), trustedPriorArtifact);
});
test('cli: deliver reports commit failure without a false success receipt', () => {
const input = path.join(skillRoot, 'examples/web-app.architecture.json');
const outputDirectory = path.join(tmp, 'commit-target-is-a-directory');
fs.mkdirSync(outputDirectory, { recursive: true });
const result = run(['deliver', 'architecture', input, outputDirectory, '--json']);
assert.equal(result.status, 1);
const failure = JSON.parse(result.stdout);
assert.equal(failure.ok, false);
assert.equal(failure.stage, 'commit');
assert.match(failure.error, /Could not commit verified delivery/);
assert.equal(fs.statSync(outputDirectory).isDirectory(), true);
assert.equal(fs.readdirSync(outputDirectory).length, 0);
});
test('cli: deliver reports preparation failure as json without touching the blocker', () => {
const input = path.join(skillRoot, 'examples/web-app.architecture.json');
const blockingFile = path.join(tmp, 'delivery-parent-is-a-file');
fs.writeFileSync(blockingFile, 'do not replace me');
const out = path.join(blockingFile, 'cannot-write.html');
const result = run(['deliver', 'architecture', input, out, '--json']);
assert.equal(result.status, 1);
const failure = JSON.parse(result.stdout);
assert.equal(failure.ok, false);
assert.equal(failure.stage, 'prepare');
assert.match(failure.error, /Could not create delivery directory/);
assert.equal(fs.readFileSync(blockingFile, 'utf8'), 'do not replace me');
});
test('cli: check validates rendered html', () => {
const out = path.join(tmp, 'workflow-check.html');
const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
assert.equal(run(['render', 'workflow', input, out]).status, 0);
const result = run(['check', out]);
assert.equal(result.status, 0, result.stderr);
assert.match(result.stdout, /"ok": true/);
});
test('cli: validate emits structured json without keeping html output', () => {
const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
const before = new Set(fs.readdirSync(tmp));
const result = run(['validate', 'workflow', input, '--json']);
assert.equal(result.status, 0, result.stderr);
const parsed = JSON.parse(result.stdout);
assert.equal(parsed.ok, true);
assert.equal(parsed.type, 'workflow');
assert.equal(parsed.checks.length, 9);
assert.equal(parsed.composition.profile, 'showcase');
assert.deepEqual(parsed.composition.summary, { errors: 0, warnings: 0 });
assert.equal(parsed.composition.metrics.containerBorderRuns, 0);
assert.equal(parsed.composition.metrics.ambiguousCorridors, 0);
assert.deepEqual(new Set(fs.readdirSync(tmp)), before);
});
test('cli: validate JSON exposes only the primary v1 column-capacity diagnostic', () => {
const input = path.join(tmp, 'pinned-column-capacity.workflow.json');
fs.writeFileSync(input, `${JSON.stringify({
schema_version: 1,
diagram_type: 'workflow',
meta: {
title: 'Pinned issue 126 diagnostic boundary',
viewBox: [720, 400],
legend: { mode: 'hidden' },
},
lanes: [{ id: 'main', label: 'Main' }],
nodes: [
{ id: 'a', lane: 'main', col: 1, type: 'backend', label: 'A' },
{ id: 'b', lane: 'main', col: 2, type: 'backend', label: 'B' },
],
edges: [{
id: 'ab',
from: 'a',
to: 'b',
fromSide: 'top',
toSide: 'top',
via: [[220, 60], [300, 60]],
}],
}, null, 2)}\n`);
const result = run(['validate', 'workflow', input, '--json'], {
env: { ...process.env, ARCHIFY_DIAGNOSTIC_FORMAT: 'json' },
});
assert.equal(result.status, 1, result.stderr || result.stdout);
assert.equal(result.stderr, '');
const failure = JSON.parse(result.stdout);
assert.equal(failure.ok, false);
assert.equal(failure.command, 'validate');
assert.equal(failure.stage, 'render');
assert.equal(failure.type, 'workflow');
assert.equal(failure.diagnostics.length, 1, JSON.stringify(failure.diagnostics, null, 2));
const [primary] = failure.diagnostics;
assert.equal(primary.code, 'workflow/column-capacity');
assert.equal(primary.subject.edge, 'ab');
assert.equal(primary.subject.fromCol, 1);
assert.equal(primary.subject.toCol, 2);
assert.ok(primary.supportedFixes.length > 0);
assert.ok(failure.diagnostics.every(({ code }) => (
code !== 'workflow/explicit-pin-conflict' && code !== 'workflow/viewbox-capacity'
)));
});
test('cli: --quality overrides the source profile for render, validate, and deliver', () => {
const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
const out = path.join(tmp, 'workflow-standard.html');
const rendered = run(['render', 'workflow', input, out, '--quality', 'standard']);
assert.equal(rendered.status, 0, rendered.stderr);
assert.match(fs.readFileSync(out, 'utf8'), /data-quality-profile="standard"/);
const validated = run(['validate', 'workflow', input, '--quality=standard', '--json']);
assert.equal(validated.status, 0, validated.stderr);
assert.equal(JSON.parse(validated.stdout).composition.profile, 'standard');
const deliveredOut = path.join(tmp, 'workflow-delivered-standard.html');
const delivered = run(['deliver', 'workflow', input, deliveredOut, '--quality=standard', '--json']);
assert.equal(delivered.status, 0, delivered.stderr);
assert.equal(JSON.parse(delivered.stdout).validation.compositionProfile, 'standard');
});
test('cli: rejects an unknown quality profile', () => {
const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
const result = run(['validate', 'workflow', input, '--quality', 'hero']);
assert.equal(result.status, 2);
assert.match(result.stderr, /Expected standard or showcase/);
});
test('cli: rejects a quality flag without a value', () => {
const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
for (const args of [
['validate', 'workflow', input, '--json', '--quality'],
['validate', 'workflow', input, '--quality', '--json'],
['validate', 'workflow', input, '--quality='],
]) {
const result = run(args);
assert.equal(result.status, 2);
assert.match(result.stderr, /--quality requires standard or showcase/);
}
});
test('cli: validate rejects unknown flags, layout-json assignment typos, and extra positionals', () => {
const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json');
const cases = [
{
args: ['validate', 'workflow', input, '--layout-json', '--bogus'],
pattern: /Unknown validate option "--bogus"/,
},
{
args: ['validate', 'workflow', input, '--layout-json=true'],
pattern: /Unknown validate option "--layout-json=true"/,
},
{
args: ['validate', 'workflow', input, '--layout-json=true', '--json'],
pattern: /Unknown validate option "--layout-json=true"/,
},
{
args: ['validate', 'workflow', input, 'unexpected-output.html', '--layout-json'],
pattern: /Usage:/,
},
];
for (const { args, pattern } of cases) {
const result = run(args);
assert.equal(result.status, 2, `${args.join(' ')}\n${result.stderr}\n${result.stdout}`);
assert.equal(result.stdout, '');
assert.match(result.stderr, pattern);
}
});
test('cli: inspect emits architecture layout json', () => {
const input = path.resolve(skillRoot, '../examples/archify-repo-grid.architecture.json');
const result = run(['inspect', 'architecture', input]);
assert.equal(result.status, 0, result.stderr);
const parsed = JSON.parse(result.stdout);
assert.equal(parsed.ok, true);
assert.equal(parsed.diagram_type, 'architecture');
assert.equal(parsed.layout.mode, 'grid');
assert.ok(parsed.components.length >= 5);
assert.ok(parsed.connections.length >= 1);
});
test('cli: inspect remains architecture-only while workflow uses validate --layout-json', () => {
const input = path.join(skillRoot, 'examples', 'agent-tool-call.workflow.json');
const result = run(['inspect', 'workflow', input]);
assert.equal(result.status, 2);
assert.match(result.stderr, /inspect is currently supported for architecture diagrams only/);
assert.equal(result.stdout, '');
});
test('cli: validate returns renderer errors for bad input', () => {
const input = path.join(tmp, 'bad.workflow.json');
const validateTmp = path.join(tmp, 'validate-failure-tmp');
const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-tool-call.workflow.json'), 'utf8'));
doc.edges[0].to = 'ghost';
fs.writeFileSync(input, JSON.stringify(doc));
fs.mkdirSync(validateTmp);
const result = run(['validate', 'workflow', input], {
env: { ...process.env, TMPDIR: validateTmp },
});
assert.notEqual(result.status, 0);
assert.match(result.stderr, /unknown target "ghost"/);
assert.deepEqual(fs.readdirSync(validateTmp), []);
});
test('cli: validate rejects an unknown type without leaking a temp directory', () => {
const validateTmp = path.join(tmp, 'validate-unknown-type-tmp');
fs.mkdirSync(validateTmp);
const result = run(['validate', 'unknown', 'ignored.json'], {
env: {
...process.env,
TMPDIR: validateTmp,
TMP: validateTmp,
TEMP: validateTmp,
},
});
assert.equal(result.status, 2);
assert.match(result.stderr, /Unknown diagram type "unknown"/);
assert.deepEqual(fs.readdirSync(validateTmp), []);
});
process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true }));