Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d116ba7
feat(import): add Mermaid flowchart importer for typed architecture IR
santhiprakash Aug 27, 2026
6280e42
fix(import): honor Mermaid semantics for edges, declarations, and dir…
santhiprakash Aug 29, 2026
cf6a8ca
build: refresh packaged Archify skill for the flowchart import subset
santhiprakash Aug 29, 2026
4f9bbf5
fix(import): represent nested subgraph membership and same-statement …
santhiprakash Aug 29, 2026
16ad1f9
merge: main (update-awareness #181) into the flowchart import branch
santhiprakash Aug 29, 2026
433a0bf
Merge remote-tracking branch 'upstream/main' into feat/mermaid-flowch…
santhiprakash Aug 30, 2026
c3464cc
fix(import): close round-3 review gaps on head 433a0bfb
santhiprakash Aug 31, 2026
0a8b497
Merge upstream/main (visual-check evidence docs) into feat/mermaid-fl…
santhiprakash Aug 31, 2026
753fec8
fix(import): close round-4 review gaps on head 0a8b497
santhiprakash Sep 1, 2026
e352d70
Merge remote-tracking branch 'origin/main' into feat/mermaid-flowchar…
santhiprakash Sep 1, 2026
bc2fe3d
Merge remote-tracking branch 'origin/main' into feat/mermaid-flowchar…
santhiprakash Sep 4, 2026
104bcb7
Merge origin/main (CLI unknown-option rejection #303) into feat/merma…
santhiprakash Sep 4, 2026
d1626ee
Merge origin/main (sequence label containment #299) into feat/mermaid…
santhiprakash Sep 5, 2026
512386e
Merge origin/main (#311 artifact receipt flush, #322 non-HTML output …
santhiprakash Sep 6, 2026
20e1505
Merge remote-tracking branch 'origin/main' into feat/mermaid-flowchar…
santhiprakash Sep 8, 2026
9a82d0e
Merge remote-tracking branch 'origin/main' into feat/mermaid-flowchar…
santhiprakash Sep 8, 2026
5625772
fix(import): route import outputs through the shared output-path cont…
santhiprakash Sep 8, 2026
6f98d03
Merge origin/main into feat/mermaid-flowchart-import
santhiprakash Sep 8, 2026
3a98c09
Merge origin/main into feat/mermaid-flowchart-import
santhiprakash Sep 10, 2026
99e6f2e
Merge origin/main (viewer modularization #381, sponsors #386) into fe…
santhiprakash Sep 11, 2026
97a7a9f
Merge origin/main (docs badge spacing #393) into feat/mermaid-flowcha…
santhiprakash Sep 11, 2026
589bc81
Merge origin/main (zip Windows-reproducibility #385, CodeRabbit confi…
santhiprakash Sep 12, 2026
34f6524
Merge remote-tracking branch 'origin/main' into feat/mermaid-flowchar…
santhiprakash Sep 13, 2026
027223f
Merge remote-tracking branch 'origin/main' into feat/mermaid-flowchar…
santhiprakash Sep 14, 2026
1dc5c46
Merge remote-tracking branch 'origin/main' into feat/mermaid-flowchar…
santhiprakash Sep 14, 2026
c5fe0c7
Merge remote-tracking branch 'origin/main' into feat/mermaid-flowchar…
santhiprakash Sep 15, 2026
57b645b
Merge remote-tracking branch 'origin/main' into feat/mermaid-flowchar…
santhiprakash Sep 15, 2026
777db1f
Merge remote-tracking branch 'upstream/main' into feat/mermaid-flowch…
santhiprakash Sep 15, 2026
704a435
Merge remote-tracking branch 'origin/main' into feat/mermaid-flowchar…
santhiprakash Sep 16, 2026
1bbd75f
Merge branch 'main' into feat/mermaid-flowchart-import
santhiprakash Sep 16, 2026
b820311
Merge branch 'dev' into feat/mermaid-flowchart-import
santhiprakash Sep 16, 2026
b209c13
Merge remote-tracking branch 'origin/dev' into feat/mermaid-flowchart…
santhiprakash Sep 16, 2026
5925ac1
fix(import/flowchart): reject blank labels and XML disallowed characters
santhiprakash Sep 16, 2026
b04399b
Merge remote-tracking branch 'origin/dev' into feat/mermaid-flowchart…
santhiprakash Sep 16, 2026
86dd883
fix(import/flowchart): keep long edge labels inside the auto viewBox
santhiprakash Sep 16, 2026
5997926
fix(import/flowchart): edge parsing, CLI JSON receipts, and docs
santhiprakash Sep 16, 2026
cdcccb2
merge: integrate current upstream dev (31bfbc8) into feat/mermaid-flo…
santhiprakash Sep 17, 2026
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
Binary file modified archify.zip
Binary file not shown.
2 changes: 1 addition & 1 deletion archify/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ When ambiguous, run `node bin/archify.mjs guide "<scenario>" --json`. Scenario p

Read Mermaid for topology and meaning, then author fresh Archify JSON; do not mechanically render Mermaid styling.

- `flowchart` / `graph` → `workflow`, or `architecture` for a component map.
- `flowchart` / `graph` → `workflow`, or `architecture` for a component map. For the architecture component-map path, `node bin/archify.mjs import flowchart <input.mmd> <output.json> --json` deterministically imports the documented subset; see `references/mermaid-flowchart-import.md` for the supported syntax, target-mode selection, and diagnostic codes.
- `sequenceDiagram` → `sequence`; participants become semantic participants and arrows become messages.
- `stateDiagram` → `lifecycle`; states and transitions retain meaning, not Mermaid style.

Expand Down
264 changes: 264 additions & 0 deletions archify/bin/archify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,7 @@ function invalidProvenance(artifactPath, sidecar, reason) {

function usage() {
return `Usage:
archify import flowchart <input.mmd> [output.json] [--json]
archify render <type> <input.json> [output.html] [--quality standard|showcase] [--repo-root path]
archify compare architecture <base.json> <head.json> [output.html] [--receipt path] [--json] [--quality standard|showcase] [--repo-root path]
archify deliver <type> <input.json> [output.html] [--json] [--open] [--quality standard|showcase] [--repo-root path]
Expand Down Expand Up @@ -3258,6 +3259,266 @@ function commandValidate(args) {
if (exitCode !== 0) process.exitCode = exitCode;
}

function emitImportFailure(json, receipt, exitCode = 1) {
if (json) {
console.log(JSON.stringify(receipt, null, 2));
} else {
console.error(formatDiagnostics(receipt.error, receipt.diagnostics));
}
process.exit(exitCode);
}

async function commandImport(args) {
// The output-commit safety runtime is loaded like the rest of the
// output-path runtime so an installed skill missing it reports a structured
// doctor/diagnostic failure instead of crashing the CLI at startup.
const { commitImportOutput, resolveOutputPath } = await import('../renderers/shared/output-path.mjs');

// Detect --json from the raw argument list before any positional validation,
// so missing/unsupported formats, unknown options, and missing inputs are
// reported through the schema-v1 receipt contract when JSON output is asked.
const json = args.includes('--json');
const positional = [];
for (const arg of args) {
if (arg === '--json') continue;
if (arg.startsWith('--')) {
emitImportFailure(json, {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: `Unknown import option "${arg}".`,
diagnostics: [diagnostic({
code: 'import/unknown-option',
message: `Unknown import option "${arg}".`,
subject: { option: arg },
evidence: { source: { argument: arg } },
supportedFixes: ['use "--json" if you want machine-readable output, otherwise remove the unknown option'],
})],
});
}
positional.push(arg);
}

const [format, inputPath, outputPath, ...extra] = positional;

if (extra.length > 0) {
emitImportFailure(json, {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: `Unexpected argument "${extra[0]}".`,
diagnostics: [diagnostic({
code: 'import/extra-argument',
message: `Unexpected argument "${extra[0]}".`,
subject: { argument: extra[0] },
evidence: { source: { argument: extra[0] } },
supportedFixes: ['use "archify import flowchart <input.mmd> [output.json] [--json]"'],
})],
});
}

if (!format) {
emitImportFailure(json, {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: 'Missing import format.',
diagnostics: [diagnostic({
code: 'import/missing-format',
message: 'Missing import format.',
subject: {},
evidence: { usage: 'archify import flowchart <input.mmd> [output.json] [--json]' },
supportedFixes: ['use "flowchart" as the import format'],
})],
});
}

if (format !== 'flowchart') {
emitImportFailure(json, {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: `Unsupported import format "${format}".`,
diagnostics: [diagnostic({
code: 'import/unsupported-format',
message: `Unsupported import format "${format}".`,
subject: { format },
evidence: { source: { format } },
supportedFixes: ['use "flowchart" as the import format'],
})],
});
}

if (!inputPath) {
emitImportFailure(json, {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: 'Missing input file.',
diagnostics: [diagnostic({
code: 'import/missing-input',
message: 'Missing input file.',
subject: {},
evidence: { usage: 'archify import flowchart <input.mmd> [output.json] [--json]' },
supportedFixes: ['provide a readable .mmd input file'],
})],
});
}

let source;
try {
source = fs.readFileSync(inputPath, 'utf8');
} catch (error) {
const receipt = {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: 'Input could not be read.',
diagnostics: [diagnostic({
code: 'input/read',
message: `Input could not be read: ${error.message}`,
subject: { input: inputPath },
evidence: { reason: error.message },
supportedFixes: ['provide one readable .mmd input file'],
})],
};
if (json) console.log(JSON.stringify(receipt, null, 2));
else console.error(formatDiagnostics(receipt.error, receipt.diagnostics));
process.exit(1);
}

if (outputPath) {
// Resolve the output through the same shared contract as deliver/compare:
// input-alias detection (including future-path aliases and hard links),
// symbolic-link cycle refusal, and the documented [output.json] extension
// contract — before any parsing or writing happens.
try {
resolveOutputPath({
requestedOutput: outputPath,
requiredExtension: '.json',
inputPaths: [inputPath],
inputDescription: 'the Mermaid source',
});
} catch (error) {
const diagnostics = error.archifyDiagnostics ?? [diagnostic({
code: 'output/path-resolution',
message: error.message,
subject: { output: outputPath },
evidence: { reason: error.message },
supportedFixes: ['choose a safe output path and retry'],
})];
const receipt = {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: error.message,
diagnostics,
};
if (json) console.log(JSON.stringify(receipt, null, 2));
else console.error(formatDiagnostics(receipt.error, receipt.diagnostics));
process.exit(1);
}
}

const { importFlowchart } = await import(pathToFileURL(path.join(skillRoot, 'importers', 'flowchart.mjs')).href);
const result = importFlowchart(source);

if (!result.ok) {
const receipt = {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: result.diagnostics[0].message,
diagnostics: result.diagnostics,
};
if (json) console.log(JSON.stringify(receipt, null, 2));
else console.error(formatDiagnostics(receipt.error, receipt.diagnostics));
process.exit(1);
}

const irJson = JSON.stringify(result.ir, null, 2);
if (outputPath) {
try {
const commit = commitImportOutput(inputPath, outputPath, irJson + '\n');
if (!commit.ok) {
// The output began aliasing the input after the preflight (for example
// a symlink swapped while the input parsed). Refuse instead of
// replacing the Mermaid source with the import result.
const receipt = {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: 'Output path aliases the input file.',
diagnostics: [diagnostic({
code: 'input/output-alias',
message: `Output path "${outputPath}" resolves to the input file; writing it would replace the Mermaid source with the import result.`,
subject: { input: inputPath, output: outputPath },
evidence: { input: path.resolve(inputPath), output: path.resolve(outputPath) },
supportedFixes: ['choose a different output path so the Mermaid source is preserved'],
})],
};
if (json) console.log(JSON.stringify(receipt, null, 2));
else console.error(formatDiagnostics(receipt.error, receipt.diagnostics));
process.exit(1);
}
} catch (error) {
if (error.archifyDiagnostics) {
// A commit-time path recheck hit a condition the shared contract
// diagnoses (for example a symbolic-link cycle swapped in after the
// preflight). Report that diagnostic instead of a generic write
// failure so the receipt names the actual contract violation.
const receipt = {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: error.message,
diagnostics: error.archifyDiagnostics,
};
if (json) console.log(JSON.stringify(receipt, null, 2));
else console.error(formatDiagnostics(receipt.error, receipt.diagnostics));
process.exit(1);
}
const receipt = {
schemaVersion: 1,
command: 'import',
source: 'mermaid-flowchart',
ok: false,
error: `Output could not be written: ${error.message}`,
diagnostics: [diagnostic({
code: 'output/write',
message: `Output could not be written: ${error.message}`,
subject: { output: outputPath },
evidence: {
...(error.code ? { systemCode: error.code } : {}),
reason: error.message,
},
supportedFixes: ['choose a writable output file path (the output must not be a directory)'],
})],
};
if (json) console.log(JSON.stringify(receipt, null, 2));
else console.error(formatDiagnostics(receipt.error, receipt.diagnostics));
process.exit(1);
}
if (!json) console.error(`Imported ${result.ir.components.length} components, ${result.ir.connections.length} connections → ${outputPath}`);
} else if (!json) {
console.log(irJson);
}

if (json) {
console.log(JSON.stringify(result.receipt, null, 2));
}
}

const [command, ...args] = process.argv.slice(2);

try {
Expand All @@ -3268,6 +3529,9 @@ try {
case 'help':
console.log(usage());
break;
case 'import':
await commandImport(args);
break;
case 'render':
commandRender(args);
break;
Expand Down
Loading