create-project-arch 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 (90) hide show
  1. package/README.md +58 -0
  2. package/dist/cli.js +232 -0
  3. package/dist/cli.test.js +8 -0
  4. package/package.json +29 -0
  5. package/templates/arch-ui/.arch/edges/decision_to_domain.json +4 -0
  6. package/templates/arch-ui/.arch/edges/milestone_to_task.json +4 -0
  7. package/templates/arch-ui/.arch/edges/task_to_decision.json +4 -0
  8. package/templates/arch-ui/.arch/edges/task_to_module.json +4 -0
  9. package/templates/arch-ui/.arch/graph.json +17 -0
  10. package/templates/arch-ui/.arch/nodes/decisions.json +4 -0
  11. package/templates/arch-ui/.arch/nodes/domains.json +4 -0
  12. package/templates/arch-ui/.arch/nodes/milestones.json +4 -0
  13. package/templates/arch-ui/.arch/nodes/modules.json +4 -0
  14. package/templates/arch-ui/.arch/nodes/tasks.json +4 -0
  15. package/templates/arch-ui/app/api/architecture/map/route.ts +13 -0
  16. package/templates/arch-ui/app/api/decisions/route.ts +23 -0
  17. package/templates/arch-ui/app/api/domain-docs/route.ts +89 -0
  18. package/templates/arch-ui/app/api/domains/route.ts +10 -0
  19. package/templates/arch-ui/app/api/graph/route.ts +16 -0
  20. package/templates/arch-ui/app/api/health/route.ts +44 -0
  21. package/templates/arch-ui/app/api/node-files/route.ts +173 -0
  22. package/templates/arch-ui/app/api/phases/route.ts +10 -0
  23. package/templates/arch-ui/app/api/route.ts +22 -0
  24. package/templates/arch-ui/app/api/search/route.ts +56 -0
  25. package/templates/arch-ui/app/api/task-doc/[taskId]/route.ts +60 -0
  26. package/templates/arch-ui/app/api/tasks/route.ts +36 -0
  27. package/templates/arch-ui/app/api/trace/file/route.ts +40 -0
  28. package/templates/arch-ui/app/api/trace/task/[taskId]/route.ts +12 -0
  29. package/templates/arch-ui/app/architecture/page.tsx +5 -0
  30. package/templates/arch-ui/app/globals.css +240 -0
  31. package/templates/arch-ui/app/health/page.tsx +48 -0
  32. package/templates/arch-ui/app/layout.tsx +19 -0
  33. package/templates/arch-ui/app/page.tsx +5 -0
  34. package/templates/arch-ui/app/work/page.tsx +265 -0
  35. package/templates/arch-ui/components/app-shell.tsx +171 -0
  36. package/templates/arch-ui/components/error-boundary.tsx +53 -0
  37. package/templates/arch-ui/components/graph/arch-node.tsx +77 -0
  38. package/templates/arch-ui/components/graph/build-graph-from-dataset.ts +196 -0
  39. package/templates/arch-ui/components/graph/build-initial-graph.ts +245 -0
  40. package/templates/arch-ui/components/graph/graph-context-menu.tsx +84 -0
  41. package/templates/arch-ui/components/graph/graph-doc-panel.tsx +46 -0
  42. package/templates/arch-ui/components/graph/graph-types.ts +82 -0
  43. package/templates/arch-ui/components/graph/use-auto-layout.ts +65 -0
  44. package/templates/arch-ui/components/graph/use-connection-validation.ts +62 -0
  45. package/templates/arch-ui/components/graph/use-flow-persistence.ts +48 -0
  46. package/templates/arch-ui/components/graph-canvas.tsx +670 -0
  47. package/templates/arch-ui/components/health-panel.tsx +49 -0
  48. package/templates/arch-ui/components/inspector-context.tsx +35 -0
  49. package/templates/arch-ui/components/inspector.tsx +895 -0
  50. package/templates/arch-ui/components/markdown-viewer.tsx +74 -0
  51. package/templates/arch-ui/components/sidebar.tsx +531 -0
  52. package/templates/arch-ui/components/topbar.tsx +187 -0
  53. package/templates/arch-ui/components/work-table.tsx +57 -0
  54. package/templates/arch-ui/components/workspace-context.tsx +274 -0
  55. package/templates/arch-ui/eslint.config.js +2 -0
  56. package/templates/arch-ui/global.d.ts +1 -0
  57. package/templates/arch-ui/lib/api.ts +93 -0
  58. package/templates/arch-ui/lib/arch-model.ts +113 -0
  59. package/templates/arch-ui/lib/graph-dataset.ts +756 -0
  60. package/templates/arch-ui/lib/graph-schema.ts +408 -0
  61. package/templates/arch-ui/lib/project-root.ts +52 -0
  62. package/templates/arch-ui/lib/types.ts +116 -0
  63. package/templates/arch-ui/next-env.d.ts +6 -0
  64. package/templates/arch-ui/next.config.js +17 -0
  65. package/templates/arch-ui/package.json +38 -0
  66. package/templates/arch-ui/postcss.config.mjs +6 -0
  67. package/templates/arch-ui/tailwind.config.ts +11 -0
  68. package/templates/arch-ui/tsconfig.json +21 -0
  69. package/templates/ui-package/eslint.config.mjs +4 -0
  70. package/templates/ui-package/package.json +26 -0
  71. package/templates/ui-package/src/accordion.tsx +10 -0
  72. package/templates/ui-package/src/badge.tsx +12 -0
  73. package/templates/ui-package/src/button.tsx +32 -0
  74. package/templates/ui-package/src/card.tsx +22 -0
  75. package/templates/ui-package/src/code.tsx +6 -0
  76. package/templates/ui-package/src/command.tsx +18 -0
  77. package/templates/ui-package/src/dialog.tsx +6 -0
  78. package/templates/ui-package/src/dropdown-menu.tsx +10 -0
  79. package/templates/ui-package/src/input.tsx +6 -0
  80. package/templates/ui-package/src/navigation-menu.tsx +6 -0
  81. package/templates/ui-package/src/scroll-area.tsx +6 -0
  82. package/templates/ui-package/src/select.tsx +6 -0
  83. package/templates/ui-package/src/separator.tsx +6 -0
  84. package/templates/ui-package/src/sheet.tsx +6 -0
  85. package/templates/ui-package/src/skeleton.tsx +6 -0
  86. package/templates/ui-package/src/table.tsx +26 -0
  87. package/templates/ui-package/src/tabs.tsx +14 -0
  88. package/templates/ui-package/src/toggle-group.tsx +10 -0
  89. package/templates/ui-package/src/utils.ts +3 -0
  90. package/templates/ui-package/tsconfig.json +10 -0
@@ -0,0 +1,65 @@
1
+ import dagre from "dagre";
2
+ import { useCallback } from "react";
3
+ import type { Edge, Node, ReactFlowInstance } from "reactflow";
4
+ import { ArchNodeData, NODE_HEIGHT, NODE_WIDTH } from "./graph-types";
5
+
6
+ export function useAutoLayout(
7
+ setNodes: React.Dispatch<React.SetStateAction<Node<ArchNodeData>[]>>,
8
+ edges: Edge[],
9
+ hiddenNodeIds: Set<string>,
10
+ flowInstance: ReactFlowInstance<ArchNodeData, Edge> | null,
11
+ ) {
12
+ return useCallback(() => {
13
+ const currentViewport = flowInstance?.getViewport();
14
+
15
+ setNodes((currentNodes) => {
16
+ const graph = new dagre.graphlib.Graph();
17
+ graph.setDefaultEdgeLabel(() => ({}));
18
+ graph.setGraph({
19
+ rankdir: "LR",
20
+ nodesep: 40,
21
+ ranksep: 100,
22
+ marginx: 20,
23
+ marginy: 20,
24
+ });
25
+
26
+ const visibleNodeSet = new Set(
27
+ currentNodes.filter((node) => !hiddenNodeIds.has(node.id)).map((node) => node.id),
28
+ );
29
+ currentNodes
30
+ .filter((node) => visibleNodeSet.has(node.id))
31
+ .forEach((node) => {
32
+ graph.setNode(node.id, { width: NODE_WIDTH, height: NODE_HEIGHT });
33
+ });
34
+
35
+ edges
36
+ .filter((edge) => visibleNodeSet.has(edge.source) && visibleNodeSet.has(edge.target))
37
+ .forEach((edge) => {
38
+ graph.setEdge(edge.source, edge.target);
39
+ });
40
+
41
+ dagre.layout(graph);
42
+
43
+ return currentNodes.map((node) => {
44
+ if (!visibleNodeSet.has(node.id)) {
45
+ return node;
46
+ }
47
+ const position = graph.node(node.id) as { x: number; y: number } | undefined;
48
+ if (!position) {
49
+ return node;
50
+ }
51
+ return {
52
+ ...node,
53
+ position: {
54
+ x: position.x - NODE_WIDTH / 2,
55
+ y: position.y - NODE_HEIGHT / 2,
56
+ },
57
+ };
58
+ });
59
+ });
60
+ if (flowInstance && currentViewport) {
61
+ // Preserve user zoom/pan while applying new node positions.
62
+ flowInstance.setViewport(currentViewport, { duration: 0 });
63
+ }
64
+ }, [edges, flowInstance, hiddenNodeIds, setNodes]);
65
+ }
@@ -0,0 +1,62 @@
1
+ import { addEdge, Connection, Edge, getOutgoers, Node } from "reactflow";
2
+ import type { ArchNodeData, GraphKind } from "./graph-types";
3
+
4
+ function nodeAllowsOutgoing(kind: GraphKind): GraphKind[] {
5
+ switch (kind) {
6
+ case "decision":
7
+ return ["domain"];
8
+ case "phase":
9
+ return ["milestone"];
10
+ case "milestone":
11
+ return ["task"];
12
+ case "task":
13
+ return ["file", "decision"];
14
+ case "domain":
15
+ return ["file"];
16
+ case "file":
17
+ default:
18
+ return [];
19
+ }
20
+ }
21
+
22
+ export function isValidArchitectureConnection(
23
+ connection: Connection,
24
+ nodes: Node<ArchNodeData>[],
25
+ edges: Edge[],
26
+ ) {
27
+ if (!connection.source || !connection.target || connection.source === connection.target) {
28
+ return false;
29
+ }
30
+
31
+ const sourceNode = nodes.find((node) => node.id === connection.source);
32
+ const targetNode = nodes.find((node) => node.id === connection.target);
33
+ if (!sourceNode || !targetNode) {
34
+ return false;
35
+ }
36
+
37
+ const allowedTargets = nodeAllowsOutgoing(sourceNode.data.kind);
38
+ if (!allowedTargets.includes(targetNode.data.kind)) {
39
+ return false;
40
+ }
41
+
42
+ const nextEdges = addEdge({ ...connection, type: "smoothstep" }, edges);
43
+ const hasCycle = (node: Node<ArchNodeData>, visited = new Set<string>()): boolean => {
44
+ if (visited.has(node.id)) {
45
+ return false;
46
+ }
47
+ visited.add(node.id);
48
+
49
+ const outgoers = getOutgoers(node, nodes, nextEdges);
50
+ for (const outgoer of outgoers) {
51
+ if (outgoer.id === connection.source) {
52
+ return true;
53
+ }
54
+ if (hasCycle(outgoer as Node<ArchNodeData>, visited)) {
55
+ return true;
56
+ }
57
+ }
58
+ return false;
59
+ };
60
+
61
+ return !hasCycle(targetNode);
62
+ }
@@ -0,0 +1,48 @@
1
+ import { useCallback } from "react";
2
+ import type { Edge, Node, ReactFlowInstance } from "reactflow";
3
+ import { ArchNodeData } from "./graph-types";
4
+
5
+ export function useFlowPersistence(
6
+ setNodes: React.Dispatch<React.SetStateAction<Node<ArchNodeData>[]>>,
7
+ setEdges: React.Dispatch<React.SetStateAction<Edge[]>>,
8
+ setHiddenNodeIds: React.Dispatch<React.SetStateAction<Set<string>>>,
9
+ flowInstance: ReactFlowInstance<ArchNodeData, Edge> | null,
10
+ storageKey: string,
11
+ ) {
12
+ const saveFlow = useCallback(() => {
13
+ if (typeof window === "undefined" || !flowInstance) return;
14
+ const flow = flowInstance.toObject();
15
+ window.localStorage.setItem(
16
+ storageKey,
17
+ JSON.stringify({
18
+ nodes: flow.nodes,
19
+ edges: flow.edges,
20
+ viewport: flow.viewport,
21
+ }),
22
+ );
23
+ }, [flowInstance, storageKey]);
24
+
25
+ const restoreFlow = useCallback(() => {
26
+ if (typeof window === "undefined") return;
27
+ const payload = window.localStorage.getItem(storageKey);
28
+ if (!payload) return;
29
+
30
+ try {
31
+ const parsed = JSON.parse(payload) as {
32
+ nodes: Node<ArchNodeData>[];
33
+ edges: Edge[];
34
+ viewport?: { x: number; y: number; zoom: number };
35
+ };
36
+ setNodes(parsed.nodes);
37
+ setEdges(parsed.edges);
38
+ if (parsed.viewport && flowInstance) {
39
+ flowInstance.setViewport(parsed.viewport);
40
+ }
41
+ setHiddenNodeIds(new Set());
42
+ } catch {
43
+ // Ignore invalid state.
44
+ }
45
+ }, [flowInstance, setEdges, setNodes, setHiddenNodeIds, storageKey]);
46
+
47
+ return { saveFlow, restoreFlow };
48
+ }