Architecture decisions: files, diagram references, views and panels

- *.archify-decision files: id (unique, words), title, status, date,
  elements, diagrams, supersedes, and prose; parsed, merged and link-checked
  in the model index; diagnostics on the file
- diagrams point at decisions with decisions: [...] on the diagram, nodes
  and edges; stripped before Archify, unknown ids reported, completion,
  hover and go-to-definition for the ids and for elements/diagrams/
  supersedes in decision files
- Decisions panel on the main.archify page; Decisions rail panel under a
  diagram (hover lights up the node or connection); decisions on element
  cards and in the element dialog; decision view for the file, with a
  lost-model note outside a model
- Archify | New Decision… command and template; editor schemas; the three
  New Model examples carry decisions; docs, changelog, 0.8.0
This commit is contained in:
2026-10-19 23:13:05 +03:00
parent 33ebdd6328
commit ebf320a023
49 changed files with 1637 additions and 55 deletions
+11 -1
View File
@@ -7,6 +7,7 @@ import { parse } from 'yaml';
import { validate } from '../src/archify/cli';
import { MODEL_EXAMPLES, mainFromExample } from '../src/language/examples';
import { MAIN_FILE, REGISTRY_EXT, hasDiagramBody, mergeRegistries, parseRegistry, readModelMeta, resolveRefs } from '../src/model/format';
import { DECISION_EXT, mergeDecisions, parseDecision, validateDecisions } from '../src/model/decisions';
import { readDiagramSource } from '../src/sources/detect';
const root = path.resolve(__dirname, '..', '..');
@@ -58,6 +59,15 @@ for (const example of MODEL_EXAMPLES) {
if (element.diagram) assert.ok(files.includes(element.diagram), `${element.id} links to missing ${element.diagram}`);
}
// Decisions: at least one, all links resolve.
const decisionFiles = files.filter((f) => f.endsWith(DECISION_EXT));
assert.ok(decisionFiles.length >= 1, 'an example carries at least one decision');
const decisions = mergeDecisions(decisionFiles.map((file) => ({ file, ...parseDecision(read(file), file) })));
assert.deepEqual(decisions.problems, []);
const diagramPaths = new Set(files.filter((f) => f.endsWith('.archify') && f !== MAIN_FILE));
assert.deepEqual(validateDecisions(decisions, registry, diagramPaths), []);
for (const d of decisions.decisions.values()) assert.ok(d.title && d.status && d.context && d.decision && d.consequences, `${d.id} is complete`);
// Diagrams: the three C4 levels, each resolving without problems and valid for Archify.
const diagrams = files.filter((f) => f.endsWith('.archify')).sort();
for (const level of ['0100-context', '0200-containers', '0300-components']) {
@@ -67,7 +77,7 @@ for (const example of MODEL_EXAMPLES) {
for (const file of diagrams) {
const source = readDiagramSource(read(file), file);
assert.ok(source, `${file} is not a diagram`);
const resolved = resolveRefs(source.diagram, { registry, fileName: file });
const resolved = resolveRefs(source.diagram, { registry, decisions: new Set(decisions.decisions.keys()), fileName: file });
assert.deepEqual(resolved.problems, [], file);
for (const link of resolved.links.values()) assert.ok(files.includes(link), `${file} links to missing ${link}`);
for (const id of resolved.used) used.add(id);