archrip 0.1.2

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 (103) hide show
  1. package/dist/commands/build.d.ts +2 -0
  2. package/dist/commands/build.d.ts.map +1 -0
  3. package/dist/commands/build.js +88 -0
  4. package/dist/commands/build.js.map +1 -0
  5. package/dist/commands/init.d.ts +2 -0
  6. package/dist/commands/init.d.ts.map +1 -0
  7. package/dist/commands/init.js +58 -0
  8. package/dist/commands/init.js.map +1 -0
  9. package/dist/commands/serve.d.ts +2 -0
  10. package/dist/commands/serve.d.ts.map +1 -0
  11. package/dist/commands/serve.js +27 -0
  12. package/dist/commands/serve.js.map +1 -0
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +64 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/install/slash-commands.d.ts +6 -0
  18. package/dist/install/slash-commands.d.ts.map +1 -0
  19. package/dist/install/slash-commands.js +31 -0
  20. package/dist/install/slash-commands.js.map +1 -0
  21. package/dist/install/viewer.d.ts +5 -0
  22. package/dist/install/viewer.d.ts.map +1 -0
  23. package/dist/install/viewer.js +40 -0
  24. package/dist/install/viewer.js.map +1 -0
  25. package/dist/schema/architecture.schema.json +144 -0
  26. package/dist/templates/skeleton.json +15 -0
  27. package/dist/templates/slash-commands/claude/archrip-refine.md +17 -0
  28. package/dist/templates/slash-commands/claude/archrip-scan.md +140 -0
  29. package/dist/templates/slash-commands/claude/archrip-update.md +34 -0
  30. package/dist/templates/slash-commands/claude/archrips-refine.md +17 -0
  31. package/dist/templates/slash-commands/claude/archrips-scan.md +97 -0
  32. package/dist/templates/slash-commands/claude/archrips-update.md +18 -0
  33. package/dist/templates/slash-commands/codex/archrip-refine.md +17 -0
  34. package/dist/templates/slash-commands/codex/archrip-scan.md +140 -0
  35. package/dist/templates/slash-commands/codex/archrip-update.md +34 -0
  36. package/dist/templates/slash-commands/codex/archrips-refine.md +17 -0
  37. package/dist/templates/slash-commands/codex/archrips-scan.md +97 -0
  38. package/dist/templates/slash-commands/codex/archrips-update.md +18 -0
  39. package/dist/templates/slash-commands/gemini/archrip-refine.md +17 -0
  40. package/dist/templates/slash-commands/gemini/archrip-scan.md +140 -0
  41. package/dist/templates/slash-commands/gemini/archrip-update.md +34 -0
  42. package/dist/templates/slash-commands/gemini/archrips-refine.md +17 -0
  43. package/dist/templates/slash-commands/gemini/archrips-scan.md +97 -0
  44. package/dist/templates/slash-commands/gemini/archrips-update.md +18 -0
  45. package/dist/utils/detect-agents.d.ts +15 -0
  46. package/dist/utils/detect-agents.d.ts.map +1 -0
  47. package/dist/utils/detect-agents.js +29 -0
  48. package/dist/utils/detect-agents.js.map +1 -0
  49. package/dist/utils/gitignore.d.ts +5 -0
  50. package/dist/utils/gitignore.d.ts.map +1 -0
  51. package/dist/utils/gitignore.js +21 -0
  52. package/dist/utils/gitignore.js.map +1 -0
  53. package/dist/utils/layout.d.ts +17 -0
  54. package/dist/utils/layout.d.ts.map +1 -0
  55. package/dist/utils/layout.js +121 -0
  56. package/dist/utils/layout.js.map +1 -0
  57. package/dist/utils/layout.spec.d.ts +2 -0
  58. package/dist/utils/layout.spec.d.ts.map +1 -0
  59. package/dist/utils/layout.spec.js +176 -0
  60. package/dist/utils/layout.spec.js.map +1 -0
  61. package/dist/utils/paths.d.ts +3 -0
  62. package/dist/utils/paths.d.ts.map +1 -0
  63. package/dist/utils/paths.js +28 -0
  64. package/dist/utils/paths.js.map +1 -0
  65. package/dist/utils/project-info.d.ts +9 -0
  66. package/dist/utils/project-info.d.ts.map +1 -0
  67. package/dist/utils/project-info.js +86 -0
  68. package/dist/utils/project-info.js.map +1 -0
  69. package/dist/utils/validate.d.ts +88 -0
  70. package/dist/utils/validate.d.ts.map +1 -0
  71. package/dist/utils/validate.js +238 -0
  72. package/dist/utils/validate.js.map +1 -0
  73. package/dist/utils/validate.spec.d.ts +2 -0
  74. package/dist/utils/validate.spec.d.ts.map +1 -0
  75. package/dist/utils/validate.spec.js +424 -0
  76. package/dist/utils/validate.spec.js.map +1 -0
  77. package/dist/utils/verbose.d.ts +3 -0
  78. package/dist/utils/verbose.d.ts.map +1 -0
  79. package/dist/utils/verbose.js +8 -0
  80. package/dist/utils/verbose.js.map +1 -0
  81. package/dist/viewer-template/index.html +15 -0
  82. package/dist/viewer-template/package-lock.json +2714 -0
  83. package/dist/viewer-template/package.json +26 -0
  84. package/dist/viewer-template/src/App.tsx +168 -0
  85. package/dist/viewer-template/src/components/DepthFilter.tsx +43 -0
  86. package/dist/viewer-template/src/components/DetailPanel.tsx +261 -0
  87. package/dist/viewer-template/src/components/Legend.tsx +41 -0
  88. package/dist/viewer-template/src/components/ThemeToggle.tsx +37 -0
  89. package/dist/viewer-template/src/components/UseCaseFilter.tsx +59 -0
  90. package/dist/viewer-template/src/components/nodes/ArchNode.tsx +37 -0
  91. package/dist/viewer-template/src/data/loader.ts +140 -0
  92. package/dist/viewer-template/src/hooks/useArchitecture.ts +32 -0
  93. package/dist/viewer-template/src/hooks/useDepthFilter.ts +37 -0
  94. package/dist/viewer-template/src/hooks/useTheme.ts +39 -0
  95. package/dist/viewer-template/src/hooks/useUseCaseFilter.ts +56 -0
  96. package/dist/viewer-template/src/index.css +130 -0
  97. package/dist/viewer-template/src/main.tsx +13 -0
  98. package/dist/viewer-template/src/types.ts +125 -0
  99. package/dist/viewer-template/tsconfig.app.json +24 -0
  100. package/dist/viewer-template/tsconfig.json +7 -0
  101. package/dist/viewer-template/tsconfig.node.json +22 -0
  102. package/dist/viewer-template/vite.config.ts +7 -0
  103. package/package.json +45 -0
@@ -0,0 +1,140 @@
1
+ import type { Edge } from '@xyflow/react';
2
+ import type { ArchFlowNode, ArchNodeData, UseCase, TableSchema, DepthLevel } from '../types.ts';
3
+ import { getDefaultDepth } from '../types.ts';
4
+
5
+ interface RawArchData {
6
+ version: string;
7
+ project: {
8
+ name: string;
9
+ description?: string;
10
+ language?: string;
11
+ framework?: string;
12
+ sourceUrl?: string;
13
+ };
14
+ nodes: RawNode[];
15
+ edges: RawEdge[];
16
+ useCases?: RawUseCase[];
17
+ schemas?: Record<string, TableSchema>;
18
+ _layout?: Record<string, { x: number; y: number }>;
19
+ }
20
+
21
+ interface RawNode {
22
+ id: string;
23
+ category: string;
24
+ label: string;
25
+ description?: string;
26
+ filePath?: string;
27
+ layer: number;
28
+ methods?: string[];
29
+ routes?: string[];
30
+ useCases?: string[];
31
+ schema?: string;
32
+ implements?: string;
33
+ externalService?: string;
34
+ sqlExamples?: string[];
35
+ depth?: number;
36
+ }
37
+
38
+ interface RawEdge {
39
+ source: string;
40
+ target: string;
41
+ label?: string | null;
42
+ type?: string;
43
+ }
44
+
45
+ interface RawUseCase {
46
+ id: string;
47
+ name: string;
48
+ description?: string;
49
+ nodeIds: string[];
50
+ flow?: string[];
51
+ }
52
+
53
+ export interface LoadedArchitecture {
54
+ projectName: string;
55
+ nodes: ArchFlowNode[];
56
+ edges: Edge[];
57
+ useCases: UseCase[];
58
+ }
59
+
60
+ function resolveSourceUrl(template: string | undefined, filePath: string): string {
61
+ if (!template || !filePath) return '';
62
+
63
+ const encoded = filePath.split('/').map(encodeURIComponent).join('/');
64
+ const resolved = template.replace('{filePath}', encoded);
65
+
66
+ try {
67
+ const url = new URL(resolved);
68
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return '';
69
+ return url.href;
70
+ } catch {
71
+ return '';
72
+ }
73
+ }
74
+
75
+ export async function loadArchitecture(): Promise<LoadedArchitecture> {
76
+ const res = await fetch('/architecture.json');
77
+ if (!res.ok) {
78
+ throw new Error(`Failed to fetch architecture.json: ${String(res.status)} ${res.statusText}`);
79
+ }
80
+ const raw: RawArchData = await res.json();
81
+
82
+ const schemas = raw.schemas ?? {};
83
+ const layout = raw._layout ?? {};
84
+ const sourceUrlTemplate = raw.project.sourceUrl;
85
+
86
+ // Convert raw nodes to React Flow nodes
87
+ const nodes: ArchFlowNode[] = raw.nodes.map((n) => {
88
+ const pos = layout[n.id] ?? { x: 0, y: 0 };
89
+ const resolvedSchema = n.schema ? schemas[n.schema] : undefined;
90
+
91
+ const data: ArchNodeData = {
92
+ label: n.label,
93
+ category: n.category,
94
+ depth: (n.depth ?? getDefaultDepth(n.category)) as DepthLevel,
95
+ description: n.description ?? '',
96
+ filePath: n.filePath ?? '',
97
+ sourceUrl: resolveSourceUrl(sourceUrlTemplate, n.filePath ?? ''),
98
+ methods: n.methods,
99
+ routes: n.routes,
100
+ useCases: n.useCases ?? [],
101
+ schema: resolvedSchema,
102
+ implements: n.implements,
103
+ externalService: n.externalService,
104
+ sqlExamples: n.sqlExamples,
105
+ };
106
+
107
+ return {
108
+ id: n.id,
109
+ type: 'archNode',
110
+ position: { x: pos.x, y: pos.y },
111
+ data,
112
+ };
113
+ });
114
+
115
+ // Convert raw edges to React Flow edges
116
+ const edges: Edge[] = raw.edges.map((e) => ({
117
+ id: `${e.source}->${e.target}`,
118
+ source: e.source,
119
+ target: e.target,
120
+ label: e.label ?? undefined,
121
+ style: { stroke: 'var(--color-edge-stroke)', strokeWidth: 1.5 },
122
+ type: 'smoothstep',
123
+ }));
124
+
125
+ // Convert use cases
126
+ const useCases: UseCase[] = (raw.useCases ?? []).map((uc) => ({
127
+ id: uc.id,
128
+ name: uc.name,
129
+ description: uc.description ?? '',
130
+ nodeIds: uc.nodeIds,
131
+ flow: uc.flow,
132
+ }));
133
+
134
+ return {
135
+ projectName: raw.project.name,
136
+ nodes,
137
+ edges,
138
+ useCases,
139
+ };
140
+ }
@@ -0,0 +1,32 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useNodesState, useEdgesState } from '@xyflow/react';
3
+ import type { Edge } from '@xyflow/react';
4
+
5
+ import type { ArchFlowNode, UseCase } from '../types.ts';
6
+ import { loadArchitecture } from '../data/loader.ts';
7
+
8
+ export function useArchitecture() {
9
+ const [nodes, setNodes, onNodesChange] = useNodesState<ArchFlowNode>([]);
10
+ const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
11
+ const [useCases, setUseCases] = useState<UseCase[]>([]);
12
+ const [projectName, setProjectName] = useState('Architecture Viewer');
13
+ const [loading, setLoading] = useState(true);
14
+ const [error, setError] = useState<string | null>(null);
15
+
16
+ useEffect(() => {
17
+ loadArchitecture()
18
+ .then((arch) => {
19
+ setNodes(arch.nodes);
20
+ setEdges(arch.edges);
21
+ setUseCases(arch.useCases);
22
+ setProjectName(arch.projectName);
23
+ setLoading(false);
24
+ })
25
+ .catch((err: unknown) => {
26
+ setError(err instanceof Error ? err.message : 'Failed to load architecture.json');
27
+ setLoading(false);
28
+ });
29
+ }, [setNodes, setEdges]);
30
+
31
+ return { nodes, edges, useCases, projectName, loading, error, setNodes, setEdges, onNodesChange, onEdgesChange };
32
+ }
@@ -0,0 +1,37 @@
1
+ import { useMemo } from 'react';
2
+ import { useQueryState, parseAsInteger } from 'nuqs';
3
+ import type { Edge } from '@xyflow/react';
4
+
5
+ import type { ArchFlowNode, DepthLevel } from '../types.ts';
6
+
7
+ function clampDepth(value: number): DepthLevel {
8
+ if (value <= 0) return 0;
9
+ if (value >= 2) return 2;
10
+ return value as DepthLevel;
11
+ }
12
+
13
+ export function useDepthFilter(nodes: ArchFlowNode[], edges: Edge[]) {
14
+ const [rawDepth, setRawDepth] = useQueryState('depth', parseAsInteger.withDefault(2).withOptions({ history: 'replace' }));
15
+ const depthLevel = clampDepth(rawDepth);
16
+
17
+ const setDepthLevel = (level: DepthLevel) => {
18
+ void setRawDepth(level);
19
+ };
20
+
21
+ const visibleNodes = useMemo(
22
+ () => nodes.map((node) => ({ ...node, hidden: node.data.depth > depthLevel })),
23
+ [nodes, depthLevel],
24
+ );
25
+
26
+ const visibleEdges = useMemo(() => {
27
+ const hiddenIds = new Set(
28
+ visibleNodes.filter((n) => n.hidden).map((n) => n.id),
29
+ );
30
+ return edges.map((edge) => ({
31
+ ...edge,
32
+ hidden: hiddenIds.has(edge.source) || hiddenIds.has(edge.target),
33
+ }));
34
+ }, [visibleNodes, edges]);
35
+
36
+ return { depthLevel, setDepthLevel, visibleNodes, visibleEdges };
37
+ }
@@ -0,0 +1,39 @@
1
+ import { useCallback, useEffect } from 'react';
2
+ import { useQueryState, parseAsStringLiteral } from 'nuqs';
3
+
4
+ type Theme = 'light' | 'dark';
5
+
6
+ const STORAGE_KEY = 'archrip-theme';
7
+ const THEME_VALUES = ['light', 'dark'] as const;
8
+
9
+ function resolveTheme(urlTheme: string | null): Theme {
10
+ // 1. URL query parameter — highest priority
11
+ if (urlTheme === 'light' || urlTheme === 'dark') return urlTheme;
12
+ // 2. localStorage
13
+ const stored = localStorage.getItem(STORAGE_KEY);
14
+ if (stored === 'dark' || stored === 'light') return stored;
15
+ // 3. System preference
16
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
17
+ }
18
+
19
+ export function useTheme() {
20
+ const [urlTheme, setUrlTheme] = useQueryState('theme', parseAsStringLiteral(THEME_VALUES).withOptions({ history: 'replace' }));
21
+ const theme = resolveTheme(urlTheme);
22
+
23
+ useEffect(() => {
24
+ const root = document.documentElement;
25
+ if (theme === 'dark') {
26
+ root.classList.add('dark');
27
+ } else {
28
+ root.classList.remove('dark');
29
+ }
30
+ localStorage.setItem(STORAGE_KEY, theme);
31
+ }, [theme]);
32
+
33
+ const toggleTheme = useCallback(() => {
34
+ const next: Theme = theme === 'dark' ? 'light' : 'dark';
35
+ void setUrlTheme(next);
36
+ }, [theme, setUrlTheme]);
37
+
38
+ return { theme, toggleTheme } as const;
39
+ }
@@ -0,0 +1,56 @@
1
+ import { useMemo } from 'react';
2
+ import { useQueryState, parseAsString } from 'nuqs';
3
+ import type { Edge } from '@xyflow/react';
4
+
5
+ import type { ArchFlowNode, UseCase } from '../types.ts';
6
+
7
+ export function useUseCaseFilter(nodes: ArchFlowNode[], edges: Edge[], useCases: UseCase[]) {
8
+ const [selectedUseCase, setSelectedUseCase] = useQueryState('uc', parseAsString.withOptions({ history: 'replace' }));
9
+
10
+ const categories = useMemo(() => {
11
+ const set = new Set<string>();
12
+ for (const node of nodes) {
13
+ if (!node.hidden) set.add(node.data.category);
14
+ }
15
+ return Array.from(set);
16
+ }, [nodes]);
17
+
18
+ const filteredNodes = useMemo(() => {
19
+ if (!selectedUseCase) return nodes;
20
+ const uc = useCases.find((u) => u.id === selectedUseCase);
21
+ if (!uc) return nodes;
22
+ const activeIds = new Set(uc.nodeIds);
23
+ return nodes.map((node) => {
24
+ if (node.hidden) return node;
25
+ return {
26
+ ...node,
27
+ style: {
28
+ ...node.style,
29
+ opacity: activeIds.has(node.id) ? 1 : 0.15,
30
+ transition: 'opacity 0.3s',
31
+ },
32
+ };
33
+ });
34
+ }, [nodes, selectedUseCase, useCases]);
35
+
36
+ const filteredEdges = useMemo(() => {
37
+ if (!selectedUseCase) return edges;
38
+ const uc = useCases.find((u) => u.id === selectedUseCase);
39
+ if (!uc) return edges;
40
+ const activeIds = new Set(uc.nodeIds);
41
+ return edges.map((edge) => ({
42
+ ...edge,
43
+ style: {
44
+ ...edge.style,
45
+ opacity: activeIds.has(edge.source) && activeIds.has(edge.target) ? 1 : 0.08,
46
+ transition: 'opacity 0.3s',
47
+ },
48
+ labelStyle: {
49
+ ...((edge.labelStyle as Record<string, unknown>) ?? {}),
50
+ opacity: activeIds.has(edge.source) && activeIds.has(edge.target) ? 1 : 0,
51
+ },
52
+ }));
53
+ }, [edges, selectedUseCase, useCases]);
54
+
55
+ return { selectedUseCase, setSelectedUseCase, categories, filteredNodes, filteredEdges };
56
+ }
@@ -0,0 +1,130 @@
1
+ @import "tailwindcss";
2
+
3
+ /* Class-based dark mode (instead of media query) */
4
+ @custom-variant dark (&:where(.dark, .dark *));
5
+
6
+ /* ─── Design Tokens (Light) ─── */
7
+ :root {
8
+ /* Surface */
9
+ --color-surface-canvas: #f8fafc;
10
+ --color-surface-primary: #ffffff;
11
+ --color-surface-secondary: #f1f5f9;
12
+ --color-surface-tertiary: #e2e8f0;
13
+
14
+ /* Content */
15
+ --color-content-primary: #0f172a;
16
+ --color-content-secondary: #475569;
17
+ --color-content-tertiary: #94a3b8;
18
+ --color-content-inverse: #ffffff;
19
+
20
+ /* Border */
21
+ --color-border-primary: #e2e8f0;
22
+ --color-border-secondary: #cbd5e1;
23
+ --color-border-focus: #3b82f6;
24
+
25
+ /* Interactive */
26
+ --color-interactive-primary: #2563eb;
27
+ --color-interactive-hover: #1d4ed8;
28
+
29
+ /* Edge */
30
+ --color-edge-stroke: #94a3b8;
31
+
32
+ /* Shadows */
33
+ --shadow-panel: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
34
+ --shadow-node: 0 1px 3px rgba(0, 0, 0, 0.1);
35
+ --shadow-node-selected: 0 0 0 2px rgba(37, 99, 235, 0.25);
36
+
37
+ /* Category colors (light) */
38
+ --cat-controller-bg: #dbeafe;
39
+ --cat-controller-border: #3b82f6;
40
+ --cat-controller-text: #1e40af;
41
+ --cat-service-bg: #dcfce7;
42
+ --cat-service-border: #22c55e;
43
+ --cat-service-text: #166534;
44
+ --cat-port-bg: #ede9fe;
45
+ --cat-port-border: #8b5cf6;
46
+ --cat-port-text: #5b21b6;
47
+ --cat-adapter-bg: #ffedd5;
48
+ --cat-adapter-border: #f97316;
49
+ --cat-adapter-text: #9a3412;
50
+ --cat-model-bg: #fee2e2;
51
+ --cat-model-border: #ef4444;
52
+ --cat-model-text: #991b1b;
53
+ --cat-external-bg: #f3f4f6;
54
+ --cat-external-border: #6b7280;
55
+ --cat-external-text: #374151;
56
+ --cat-job-bg: #fef9c3;
57
+ --cat-job-border: #eab308;
58
+ --cat-job-text: #854d0e;
59
+ --cat-dto-bg: #cffafe;
60
+ --cat-dto-border: #06b6d4;
61
+ --cat-dto-text: #155e75;
62
+ --cat-fallback-bg: #f5f5f4;
63
+ --cat-fallback-border: #a8a29e;
64
+ --cat-fallback-text: #44403c;
65
+ }
66
+
67
+ /* ─── Design Tokens (Dark) ─── */
68
+ .dark {
69
+ --color-surface-canvas: #0c0f14;
70
+ --color-surface-primary: #151921;
71
+ --color-surface-secondary: #1e2330;
72
+ --color-surface-tertiary: #2a3040;
73
+
74
+ --color-content-primary: #f1f5f9;
75
+ --color-content-secondary: #94a3b8;
76
+ --color-content-tertiary: #64748b;
77
+ --color-content-inverse: #0f172a;
78
+
79
+ --color-border-primary: #2a3040;
80
+ --color-border-secondary: #334155;
81
+ --color-border-focus: #60a5fa;
82
+
83
+ --color-interactive-primary: #3b82f6;
84
+ --color-interactive-hover: #60a5fa;
85
+
86
+ --color-edge-stroke: #475569;
87
+
88
+ --shadow-panel: 0 1px 3px 0 rgba(0, 0, 0, 0.4), 0 1px 2px -1px rgba(0, 0, 0, 0.4);
89
+ --shadow-node: 0 1px 3px rgba(0, 0, 0, 0.3);
90
+ --shadow-node-selected: 0 0 0 2px rgba(96, 165, 250, 0.3);
91
+
92
+ /* Category colors (dark) */
93
+ --cat-controller-bg: #1e293b;
94
+ --cat-controller-border: #60a5fa;
95
+ --cat-controller-text: #93c5fd;
96
+ --cat-service-bg: #14291a;
97
+ --cat-service-border: #4ade80;
98
+ --cat-service-text: #86efac;
99
+ --cat-port-bg: #1e1533;
100
+ --cat-port-border: #a78bfa;
101
+ --cat-port-text: #c4b5fd;
102
+ --cat-adapter-bg: #2b1a0a;
103
+ --cat-adapter-border: #fb923c;
104
+ --cat-adapter-text: #fdba74;
105
+ --cat-model-bg: #2b1111;
106
+ --cat-model-border: #f87171;
107
+ --cat-model-text: #fca5a5;
108
+ --cat-external-bg: #1e2330;
109
+ --cat-external-border: #9ca3af;
110
+ --cat-external-text: #d1d5db;
111
+ --cat-job-bg: #2b2506;
112
+ --cat-job-border: #facc15;
113
+ --cat-job-text: #fde68a;
114
+ --cat-dto-bg: #0b2528;
115
+ --cat-dto-border: #22d3ee;
116
+ --cat-dto-text: #67e8f9;
117
+ --cat-fallback-bg: #1c1917;
118
+ --cat-fallback-border: #a8a29e;
119
+ --cat-fallback-text: #d6d3d1;
120
+ }
121
+
122
+ html,
123
+ body,
124
+ #root {
125
+ margin: 0;
126
+ padding: 0;
127
+ width: 100%;
128
+ height: 100vh;
129
+ font-family: system-ui, -apple-system, sans-serif;
130
+ }
@@ -0,0 +1,13 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { NuqsAdapter } from 'nuqs/adapters/react'
4
+ import './index.css'
5
+ import App from './App.tsx'
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <StrictMode>
9
+ <NuqsAdapter>
10
+ <App />
11
+ </NuqsAdapter>
12
+ </StrictMode>,
13
+ )
@@ -0,0 +1,125 @@
1
+ // ─── Depth system ───
2
+
3
+ export type DepthLevel = 0 | 1 | 2;
4
+
5
+ const CATEGORY_DEPTH: Record<string, DepthLevel> = {
6
+ controller: 0, external: 0,
7
+ service: 1, port: 1, job: 1,
8
+ adapter: 2, model: 2, dto: 2,
9
+ };
10
+
11
+ export function getDefaultDepth(category: string): DepthLevel {
12
+ return CATEGORY_DEPTH[category] ?? 1;
13
+ }
14
+
15
+ export const DEPTH_LEVELS = [
16
+ { level: 0 as const, label: 'Overview' },
17
+ { level: 1 as const, label: 'Structure' },
18
+ { level: 2 as const, label: 'Detail' },
19
+ ] as const;
20
+
21
+ // ─── Category system ───
22
+ // Standard 8 categories. Custom categories are allowed — they get a fallback color.
23
+
24
+ export interface CategoryStyle {
25
+ bg: string;
26
+ border: string;
27
+ text: string;
28
+ }
29
+
30
+ interface CategoryMeta {
31
+ label: string;
32
+ icon: string;
33
+ color: CategoryStyle;
34
+ }
35
+
36
+ const CATEGORY_META = {
37
+ controller: { label: 'Controller', icon: '\u{1F310}', color: { bg: '#dbeafe', border: '#3b82f6', text: '#1e40af' } },
38
+ service: { label: 'Service', icon: '\u{2699}\u{FE0F}', color: { bg: '#dcfce7', border: '#22c55e', text: '#166534' } },
39
+ port: { label: 'Port', icon: '\u{1F50C}', color: { bg: '#ede9fe', border: '#8b5cf6', text: '#5b21b6' } },
40
+ adapter: { label: 'Adapter', icon: '\u{1F527}', color: { bg: '#ffedd5', border: '#f97316', text: '#9a3412' } },
41
+ model: { label: 'Model / DB', icon: '\u{1F4BE}', color: { bg: '#fee2e2', border: '#ef4444', text: '#991b1b' } },
42
+ external: { label: 'External', icon: '\u{2601}\u{FE0F}', color: { bg: '#f3f4f6', border: '#6b7280', text: '#374151' } },
43
+ job: { label: 'Job', icon: '\u{23F0}', color: { bg: '#fef9c3', border: '#eab308', text: '#854d0e' } },
44
+ dto: { label: 'DTO', icon: '\u{1F4E6}', color: { bg: '#cffafe', border: '#06b6d4', text: '#155e75' } },
45
+ } as const satisfies Record<string, CategoryMeta>;
46
+
47
+ export type StandardCategory = keyof typeof CATEGORY_META;
48
+
49
+ const FALLBACK_META: CategoryMeta = { label: '', icon: '\u{1F4C4}', color: { bg: '#f5f5f4', border: '#a8a29e', text: '#44403c' } };
50
+
51
+ function getMeta(category: string): CategoryMeta {
52
+ return CATEGORY_META[category as StandardCategory] ?? FALLBACK_META;
53
+ }
54
+
55
+ const STANDARD_CATEGORIES = ['controller', 'service', 'port', 'adapter', 'model', 'external', 'job', 'dto'] as const;
56
+
57
+ export function getCategoryColors(category: string): CategoryStyle {
58
+ const key = (STANDARD_CATEGORIES as readonly string[]).includes(category) ? category : 'fallback';
59
+ return {
60
+ bg: `var(--cat-${key}-bg)`,
61
+ border: `var(--cat-${key}-border)`,
62
+ text: `var(--cat-${key}-text)`,
63
+ };
64
+ }
65
+
66
+ export function getCategoryLabel(category: string): string {
67
+ const meta = getMeta(category);
68
+ return meta.label || category.charAt(0).toUpperCase() + category.slice(1);
69
+ }
70
+
71
+ export function getCategoryIcon(category: string): string {
72
+ return getMeta(category).icon;
73
+ }
74
+
75
+ // ─── React Flow node type alias ───
76
+
77
+ import type { Node } from '@xyflow/react';
78
+
79
+ // ─── Data types ───
80
+ // Keep in sync with packages/cli/src/utils/validate.ts (ColumnSchema, TableSchema)
81
+
82
+ export interface ColumnSchema {
83
+ name: string;
84
+ type: string;
85
+ nullable?: boolean;
86
+ default?: string;
87
+ index?: string;
88
+ foreignKey?: { table: string; column: string; onDelete?: string };
89
+ }
90
+
91
+ export interface TableSchema {
92
+ tableName: string;
93
+ columns: ColumnSchema[];
94
+ indexes?: string[];
95
+ enumValues?: Record<string, Record<string, string>>;
96
+ }
97
+
98
+ export interface ArchNodeData {
99
+ [key: string]: unknown;
100
+ label: string;
101
+ category: string;
102
+ depth: DepthLevel;
103
+ description: string;
104
+ filePath: string;
105
+ sourceUrl: string;
106
+ methods?: string[];
107
+ useCases: string[];
108
+ schema?: TableSchema;
109
+ sqlExamples?: string[];
110
+ routes?: string[];
111
+ implements?: string;
112
+ externalService?: string;
113
+ }
114
+
115
+ export interface UseCase {
116
+ id: string;
117
+ name: string;
118
+ description: string;
119
+ nodeIds: string[];
120
+ flow?: string[];
121
+ }
122
+
123
+ // ─── React Flow typed node ───
124
+
125
+ export type ArchFlowNode = Node<ArchNodeData>;
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+ "jsx": "react-jsx",
16
+ "strict": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "erasableSyntaxOnly": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedSideEffectImports": true
22
+ },
23
+ "include": ["src"]
24
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "moduleDetection": "force",
13
+ "noEmit": true,
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "erasableSyntaxOnly": true,
18
+ "noFallthroughCasesInSwitch": true,
19
+ "noUncheckedSideEffectImports": true
20
+ },
21
+ "include": ["vite.config.ts"]
22
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+
5
+ export default defineConfig({
6
+ plugins: [react(), tailwindcss()],
7
+ })
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "archrip",
3
+ "version": "0.1.2",
4
+ "description": "Generate interactive architecture diagrams from your codebase using AI agents",
5
+ "type": "module",
6
+ "bin": {
7
+ "archrip": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc && npm run copy-templates",
14
+ "copy-templates": "cp -r src/templates dist/ && cp -r src/schema dist/ && node scripts/copy-viewer.js",
15
+ "typecheck": "tsc --noEmit",
16
+ "dev": "tsc --watch",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest"
19
+ },
20
+ "keywords": [
21
+ "architecture",
22
+ "diagram",
23
+ "code-analysis",
24
+ "react-flow",
25
+ "cli",
26
+ "ai"
27
+ ],
28
+ "author": "Yuto Ida",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/yida29/archrip"
33
+ },
34
+ "dependencies": {
35
+ "@dagrejs/dagre": "^1.1.4"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.0.0",
39
+ "typescript": "~5.8.0",
40
+ "vitest": "^3.0.0"
41
+ },
42
+ "engines": {
43
+ "node": ">=20"
44
+ }
45
+ }