@robotaccomplice/architext 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +497 -0
  3. package/docs/architecture/AGENTS_APPENDIX.md +39 -0
  4. package/docs/architecture/ARCHITECTURE_PLAN.md +520 -0
  5. package/docs/architecture/LLM_ARCHITEXT.md +95 -0
  6. package/docs/architext/AGENTS_APPENDIX.md +39 -0
  7. package/docs/architext/LLM_ARCHITEXT.md +64 -0
  8. package/docs/architext/README.md +120 -0
  9. package/docs/architext/data/data-classification.json +34 -0
  10. package/docs/architext/data/decisions.json +54 -0
  11. package/docs/architext/data/flows.json +114 -0
  12. package/docs/architext/data/glossary.json +24 -0
  13. package/docs/architext/data/manifest.json +23 -0
  14. package/docs/architext/data/nodes.json +194 -0
  15. package/docs/architext/data/risks.json +59 -0
  16. package/docs/architext/data/views.json +91 -0
  17. package/docs/architext/dist/assets/index-BWZ6sEpA.js +51 -0
  18. package/docs/architext/dist/assets/index-iWLms0Pa.css +1 -0
  19. package/docs/architext/dist/compass.svg +9 -0
  20. package/docs/architext/dist/index.html +14 -0
  21. package/docs/architext/index.html +13 -0
  22. package/docs/architext/package-lock.json +1822 -0
  23. package/docs/architext/package.json +28 -0
  24. package/docs/architext/public/compass.svg +9 -0
  25. package/docs/architext/schema/data-classification.schema.json +28 -0
  26. package/docs/architext/schema/decisions.schema.json +33 -0
  27. package/docs/architext/schema/flows.schema.json +72 -0
  28. package/docs/architext/schema/glossary.schema.json +22 -0
  29. package/docs/architext/schema/manifest.schema.json +47 -0
  30. package/docs/architext/schema/nodes.schema.json +69 -0
  31. package/docs/architext/schema/risks.schema.json +34 -0
  32. package/docs/architext/schema/views.schema.json +48 -0
  33. package/docs/architext/src/main.tsx +2133 -0
  34. package/docs/architext/src/styles.css +1475 -0
  35. package/docs/architext/tools/validate-architext.mjs +163 -0
  36. package/docs/architext/tsconfig.json +21 -0
  37. package/docs/architext/vite.config.ts +47 -0
  38. package/docs/assets/screenshots/architext-c4.png +0 -0
  39. package/docs/assets/screenshots/architext-data-risks.png +0 -0
  40. package/docs/assets/screenshots/architext-flows.png +0 -0
  41. package/docs/assets/screenshots/architext-sequence.png +0 -0
  42. package/package.json +81 -0
  43. package/tools/architext-adopt.mjs +874 -0
@@ -0,0 +1,163 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import Ajv2020 from "ajv/dist/2020.js";
5
+ import addFormats from "ajv-formats";
6
+
7
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
8
+
9
+ function parseArgs(argv) {
10
+ const options = {
11
+ dataDir: path.join(root, "data"),
12
+ schemaDir: path.join(root, "schema")
13
+ };
14
+
15
+ for (let index = 0; index < argv.length; index += 1) {
16
+ const arg = argv[index];
17
+ if (arg === "--data-dir") {
18
+ options.dataDir = path.resolve(argv[++index] ?? "");
19
+ } else if (arg === "--schema-dir") {
20
+ options.schemaDir = path.resolve(argv[++index] ?? "");
21
+ } else {
22
+ throw new Error(`Unknown argument: ${arg}`);
23
+ }
24
+ }
25
+
26
+ return options;
27
+ }
28
+
29
+ const options = parseArgs(process.argv.slice(2));
30
+ const dataDir = options.dataDir;
31
+ const schemaDir = options.schemaDir;
32
+
33
+ const schemaFiles = {
34
+ manifest: "manifest.schema.json",
35
+ nodes: "nodes.schema.json",
36
+ flows: "flows.schema.json",
37
+ views: "views.schema.json",
38
+ dataClassification: "data-classification.schema.json",
39
+ decisions: "decisions.schema.json",
40
+ risks: "risks.schema.json",
41
+ glossary: "glossary.schema.json"
42
+ };
43
+
44
+ function readJson(filePath) {
45
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
46
+ }
47
+
48
+ function requireUnique(items, label, errors) {
49
+ const seen = new Set();
50
+ for (const item of items) {
51
+ if (seen.has(item.id)) {
52
+ errors.push(`${label} contains duplicate id "${item.id}"`);
53
+ }
54
+ seen.add(item.id);
55
+ }
56
+ }
57
+
58
+ function requireKnown(id, known, context, errors) {
59
+ if (!known.has(id)) {
60
+ errors.push(`${context} references unknown id "${id}"`);
61
+ }
62
+ }
63
+
64
+ function validateReferences(model, errors) {
65
+ const nodeIds = new Set(model.nodes.nodes.map((node) => node.id));
66
+ const flowIds = new Set(model.flows.flows.map((flow) => flow.id));
67
+ const dataIds = new Set(model.dataClassification.classes.map((item) => item.id));
68
+ const decisionIds = new Set(model.decisions.decisions.map((item) => item.id));
69
+ const riskIds = new Set(model.risks.risks.map((item) => item.id));
70
+ const viewIds = new Set(model.views.views.map((item) => item.id));
71
+
72
+ requireKnown(model.manifest.defaultViewId, viewIds, "manifest.defaultViewId", errors);
73
+
74
+ for (const node of model.nodes.nodes) {
75
+ for (const dependencyId of node.dependencies) requireKnown(dependencyId, nodeIds, `node ${node.id}.dependencies`, errors);
76
+ for (const dataId of node.dataHandled) requireKnown(dataId, dataIds, `node ${node.id}.dataHandled`, errors);
77
+ for (const flowId of node.relatedFlows) requireKnown(flowId, flowIds, `node ${node.id}.relatedFlows`, errors);
78
+ for (const decisionId of node.relatedDecisions) requireKnown(decisionId, decisionIds, `node ${node.id}.relatedDecisions`, errors);
79
+ for (const riskId of node.knownRisks) requireKnown(riskId, riskIds, `node ${node.id}.knownRisks`, errors);
80
+ }
81
+
82
+ for (const flow of model.flows.flows) {
83
+ for (const actorId of flow.actors) requireKnown(actorId, nodeIds, `flow ${flow.id}.actors`, errors);
84
+ const stepIds = new Set();
85
+ for (const step of flow.steps) {
86
+ if (stepIds.has(step.id)) errors.push(`flow ${flow.id} contains duplicate step id "${step.id}"`);
87
+ stepIds.add(step.id);
88
+ requireKnown(step.from, nodeIds, `flow ${flow.id} step ${step.id}.from`, errors);
89
+ requireKnown(step.to, nodeIds, `flow ${flow.id} step ${step.id}.to`, errors);
90
+ for (const dataId of step.data) requireKnown(dataId, dataIds, `flow ${flow.id} step ${step.id}.data`, errors);
91
+ }
92
+ }
93
+
94
+ for (const view of model.views.views) {
95
+ const laneIds = new Set();
96
+ for (const lane of view.lanes) {
97
+ if (laneIds.has(lane.id)) errors.push(`view ${view.id} contains duplicate lane id "${lane.id}"`);
98
+ laneIds.add(lane.id);
99
+ for (const nodeId of lane.nodeIds) requireKnown(nodeId, nodeIds, `view ${view.id} lane ${lane.id}`, errors);
100
+ }
101
+ }
102
+
103
+ for (const decision of model.decisions.decisions) {
104
+ for (const nodeId of decision.relatedNodes) requireKnown(nodeId, nodeIds, `decision ${decision.id}.relatedNodes`, errors);
105
+ for (const flowId of decision.relatedFlows) requireKnown(flowId, flowIds, `decision ${decision.id}.relatedFlows`, errors);
106
+ }
107
+
108
+ for (const risk of model.risks.risks) {
109
+ for (const nodeId of risk.relatedNodes) requireKnown(nodeId, nodeIds, `risk ${risk.id}.relatedNodes`, errors);
110
+ for (const flowId of risk.relatedFlows) requireKnown(flowId, flowIds, `risk ${risk.id}.relatedFlows`, errors);
111
+ }
112
+ }
113
+
114
+ const ajv = new Ajv2020({ allErrors: true, strict: true });
115
+ addFormats(ajv);
116
+
117
+ const schemas = Object.fromEntries(
118
+ Object.entries(schemaFiles).map(([key, file]) => [key, readJson(path.join(schemaDir, file))])
119
+ );
120
+
121
+ for (const schema of Object.values(schemas)) {
122
+ ajv.addSchema(schema);
123
+ }
124
+
125
+ const manifest = readJson(path.join(dataDir, "manifest.json"));
126
+ const model = {
127
+ manifest,
128
+ nodes: readJson(path.join(dataDir, manifest.files.nodes)),
129
+ flows: readJson(path.join(dataDir, manifest.files.flows)),
130
+ views: readJson(path.join(dataDir, manifest.files.views)),
131
+ dataClassification: readJson(path.join(dataDir, manifest.files.dataClassification)),
132
+ decisions: readJson(path.join(dataDir, manifest.files.decisions)),
133
+ risks: readJson(path.join(dataDir, manifest.files.risks)),
134
+ glossary: readJson(path.join(dataDir, manifest.files.glossary))
135
+ };
136
+
137
+ const errors = [];
138
+
139
+ for (const [key, schema] of Object.entries(schemas)) {
140
+ const validate = ajv.getSchema(schema.$id);
141
+ const value = key === "dataClassification" ? model.dataClassification : model[key];
142
+ if (!validate(value)) {
143
+ for (const error of validate.errors ?? []) {
144
+ errors.push(`${key}${error.instancePath}: ${error.message}`);
145
+ }
146
+ }
147
+ }
148
+
149
+ requireUnique(model.nodes.nodes, "nodes", errors);
150
+ requireUnique(model.flows.flows, "flows", errors);
151
+ requireUnique(model.views.views, "views", errors);
152
+ requireUnique(model.dataClassification.classes, "dataClassification.classes", errors);
153
+ requireUnique(model.decisions.decisions, "decisions", errors);
154
+ requireUnique(model.risks.risks, "risks", errors);
155
+ validateReferences(model, errors);
156
+
157
+ if (errors.length > 0) {
158
+ console.error("Architext validation failed:");
159
+ for (const error of errors) console.error(`- ${error}`);
160
+ process.exit(1);
161
+ }
162
+
163
+ console.log("Architext validation passed.");
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "jsx": "react-jsx"
18
+ },
19
+ "include": ["src"]
20
+ }
21
+
@@ -0,0 +1,47 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+
6
+ const dataDir = process.env.ARCHITEXT_DATA_DIR;
7
+
8
+ export default defineConfig({
9
+ plugins: [
10
+ react(),
11
+ {
12
+ name: "architext-target-data",
13
+ configureServer(server) {
14
+ if (!dataDir) return;
15
+ server.middlewares.use(async (request, response, next) => {
16
+ if (!request.url?.startsWith("/data/")) {
17
+ next();
18
+ return;
19
+ }
20
+
21
+ const relativePath = decodeURIComponent(request.url.slice("/data/".length).split("?")[0] ?? "");
22
+ const filePath = path.resolve(dataDir, relativePath);
23
+ const root = path.resolve(dataDir);
24
+
25
+ if (!filePath.startsWith(`${root}${path.sep}`)) {
26
+ response.statusCode = 403;
27
+ response.end("Forbidden");
28
+ return;
29
+ }
30
+
31
+ try {
32
+ const body = await fs.readFile(filePath);
33
+ response.setHeader("Content-Type", "application/json; charset=utf-8");
34
+ response.end(body);
35
+ } catch {
36
+ response.statusCode = 404;
37
+ response.end("Not found");
38
+ }
39
+ });
40
+ }
41
+ }
42
+ ],
43
+ server: {
44
+ host: "127.0.0.1",
45
+ port: 4317
46
+ }
47
+ });
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@robotaccomplice/architext",
3
+ "version": "1.0.0",
4
+ "description": "Local JSON-backed architecture and dataflow viewer for engineering teams and LLM-maintained architecture maps.",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "bin": {
10
+ "architext": "tools/architext-adopt.mjs"
11
+ },
12
+ "files": [
13
+ "docs/architecture",
14
+ "docs/assets/screenshots",
15
+ "docs/architext/data",
16
+ "docs/architext/dist",
17
+ "docs/architext/index.html",
18
+ "docs/architext/AGENTS_APPENDIX.md",
19
+ "docs/architext/LLM_ARCHITEXT.md",
20
+ "docs/architext/package.json",
21
+ "docs/architext/package-lock.json",
22
+ "docs/architext/public",
23
+ "docs/architext/README.md",
24
+ "docs/architext/schema",
25
+ "docs/architext/src",
26
+ "docs/architext/tools",
27
+ "docs/architext/tsconfig.json",
28
+ "docs/architext/vite.config.ts",
29
+ "tools",
30
+ "LICENSE",
31
+ "README.md"
32
+ ],
33
+ "scripts": {
34
+ "architext": "node tools/architext-adopt.mjs serve .",
35
+ "architext:build": "node tools/architext-adopt.mjs build .",
36
+ "architext:clean": "node tools/architext-adopt.mjs clean .",
37
+ "architext:doctor": "node tools/architext-adopt.mjs doctor .",
38
+ "architext:prompt": "node tools/architext-adopt.mjs prompt .",
39
+ "architext:validate": "node tools/architext-adopt.mjs validate .",
40
+ "build": "npm --prefix docs/architext run build",
41
+ "cli": "node tools/architext-adopt.mjs",
42
+ "doctor": "node tools/architext-adopt.mjs doctor --target .",
43
+ "prepack": "npm run build",
44
+ "test": "node --test",
45
+ "validate": "node docs/architext/tools/validate-architext.mjs"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "dependencies": {
51
+ "ajv": "^8.17.1",
52
+ "ajv-formats": "^3.0.1"
53
+ },
54
+ "devDependencies": {
55
+ "@vitejs/plugin-react": "^4.3.4",
56
+ "@types/react": "^19.0.2",
57
+ "@types/react-dom": "^19.0.2",
58
+ "react": "^19.0.0",
59
+ "react-dom": "^19.0.0",
60
+ "typescript": "^5.7.2",
61
+ "vite": "^6.0.7"
62
+ },
63
+ "license": "MIT",
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "git+https://github.com/robot-accomplice/architext.git"
67
+ },
68
+ "keywords": [
69
+ "architecture",
70
+ "c4",
71
+ "dataflow",
72
+ "documentation",
73
+ "llm",
74
+ "schema"
75
+ ],
76
+ "author": "Robot Accomplice",
77
+ "bugs": {
78
+ "url": "https://github.com/robot-accomplice/architext/issues"
79
+ },
80
+ "homepage": "https://github.com/robot-accomplice/architext#readme"
81
+ }