create-project-arch 1.1.0 → 1.2.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 (36) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +536 -43
  4. package/package.json +27 -3
  5. package/templates/arch-ui/.arch/edges/decision_to_domain.json +0 -1
  6. package/templates/arch-ui/.arch/edges/milestone_to_task.json +0 -1
  7. package/templates/arch-ui/.arch/edges/task_to_decision.json +0 -1
  8. package/templates/arch-ui/.arch/edges/task_to_module.json +0 -1
  9. package/templates/arch-ui/.arch/graph.json +0 -1
  10. package/templates/arch-ui/.arch/nodes/decisions.json +0 -1
  11. package/templates/arch-ui/.arch/nodes/domains.json +0 -1
  12. package/templates/arch-ui/.arch/nodes/milestones.json +0 -1
  13. package/templates/arch-ui/.arch/nodes/modules.json +0 -1
  14. package/templates/arch-ui/.arch/nodes/tasks.json +0 -1
  15. package/templates/arch-ui/app/api/health/route.ts +5 -4
  16. package/templates/arch-ui/app/api/node-files/route.ts +6 -1
  17. package/templates/arch-ui/app/api/search/route.ts +0 -1
  18. package/templates/arch-ui/app/work/page.tsx +94 -64
  19. package/templates/arch-ui/components/app-shell.tsx +1 -7
  20. package/templates/arch-ui/components/graph/arch-node.tsx +13 -3
  21. package/templates/arch-ui/components/graph/build-graph-from-dataset.ts +6 -2
  22. package/templates/arch-ui/components/graph/build-initial-graph.ts +215 -221
  23. package/templates/arch-ui/components/graph/graph-types.ts +49 -49
  24. package/templates/arch-ui/components/graph/use-auto-layout.ts +51 -51
  25. package/templates/arch-ui/components/graph/use-connection-validation.ts +48 -48
  26. package/templates/arch-ui/components/graph/use-flow-persistence.ts +38 -38
  27. package/templates/arch-ui/components/graph-canvas.tsx +90 -74
  28. package/templates/arch-ui/components/inspector.tsx +56 -22
  29. package/templates/arch-ui/components/sidebar.tsx +18 -8
  30. package/templates/arch-ui/components/topbar.tsx +8 -11
  31. package/templates/arch-ui/components/work-table.tsx +1 -5
  32. package/templates/arch-ui/components/workspace-context.tsx +2 -1
  33. package/templates/arch-ui/lib/graph-dataset.ts +4 -8
  34. package/templates/arch-ui/lib/graph-schema.ts +1 -4
  35. package/templates/arch-ui/package.json +0 -1
  36. package/templates/arch-ui/tsconfig.json +3 -11
@@ -1,4 +1,3 @@
1
1
  {
2
2
  "edges": []
3
3
  }
4
-
@@ -1,4 +1,3 @@
1
1
  {
2
2
  "edges": []
3
3
  }
4
-
@@ -14,4 +14,3 @@
14
14
  "milestone_to_task": 0
15
15
  }
16
16
  }
17
-
@@ -1,4 +1,3 @@
1
1
  {
2
2
  "decisions": []
3
3
  }
4
-
@@ -1,4 +1,3 @@
1
1
  {
2
2
  "domains": []
3
3
  }
4
-
@@ -1,4 +1,3 @@
1
1
  {
2
2
  "milestones": []
3
3
  }
4
-
@@ -1,4 +1,3 @@
1
1
  {
2
2
  "modules": []
3
3
  }
4
-
@@ -1,4 +1,3 @@
1
1
  {
2
2
  "tasks": []
3
3
  }
4
-
@@ -9,12 +9,13 @@ export const runtime = "nodejs";
9
9
  export async function GET() {
10
10
  const root = getProjectRoot();
11
11
  process.env.PROJECT_ROOT = root;
12
- const [checkResult, architectureMap] = await Promise.all([check.checkRun(), readArchitectureMap(root)]);
12
+ const [checkResult, architectureMap] = await Promise.all([
13
+ check.checkRun(),
14
+ readArchitectureMap(root),
15
+ ]);
13
16
  const { validation } = await buildValidatedGraphDataset(root, architectureMap);
14
17
 
15
- const graphErrors = validation.errors.map(
16
- (issue) => `[graph:${issue.ruleId}] ${issue.message}`,
17
- );
18
+ const graphErrors = validation.errors.map((issue) => `[graph:${issue.ruleId}] ${issue.message}`);
18
19
  const graphWarnings = validation.warnings.map(
19
20
  (issue) => `[graph:${issue.ruleId}] ${issue.message}`,
20
21
  );
@@ -132,7 +132,12 @@ async function resolveFilesForNode(root: string, type: NodeType, id: string): Pr
132
132
  const [phase] = id.split("/");
133
133
  if (!phase || !isSafeSegment(phase)) return [];
134
134
  const phaseDir = path.join(root, "roadmap", "phases", phase);
135
- return collectFiles(phaseDir, root, new Set(["milestones", "tasks", "node_modules", ".git"]), 2);
135
+ return collectFiles(
136
+ phaseDir,
137
+ root,
138
+ new Set(["milestones", "tasks", "node_modules", ".git"]),
139
+ 2,
140
+ );
136
141
  }
137
142
 
138
143
  const [phase, milestone] = id.split("/");
@@ -53,4 +53,3 @@ export async function GET(request: NextRequest) {
53
53
  results: results.slice(0, 30),
54
54
  });
55
55
  }
56
-
@@ -44,14 +44,18 @@ export default function WorkPage() {
44
44
  setPhases(phaseResult.value);
45
45
  } else {
46
46
  setPhases([]);
47
- errors.push(`Phases unavailable: ${phaseResult.reason instanceof Error ? phaseResult.reason.message : "Unknown error"}`);
47
+ errors.push(
48
+ `Phases unavailable: ${phaseResult.reason instanceof Error ? phaseResult.reason.message : "Unknown error"}`,
49
+ );
48
50
  }
49
51
 
50
52
  if (taskResult.status === "fulfilled") {
51
53
  setTasks(taskResult.value.tasks);
52
54
  } else {
53
55
  setTasks([]);
54
- errors.push(`Tasks unavailable: ${taskResult.reason instanceof Error ? taskResult.reason.message : "Unknown error"}`);
56
+ errors.push(
57
+ `Tasks unavailable: ${taskResult.reason instanceof Error ? taskResult.reason.message : "Unknown error"}`,
58
+ );
55
59
  }
56
60
 
57
61
  if (graphResult.status === "fulfilled") {
@@ -59,7 +63,9 @@ export default function WorkPage() {
59
63
  setGraphValidation(graphResult.value.validation);
60
64
  if (!graphResult.value.validation.valid) {
61
65
  errors.push(
62
- ...graphResult.value.validation.errors.map((issue) => `Graph schema error: ${issue.message}`),
66
+ ...graphResult.value.validation.errors.map(
67
+ (issue) => `Graph schema error: ${issue.message}`,
68
+ ),
63
69
  );
64
70
  }
65
71
  } else {
@@ -81,7 +87,8 @@ export default function WorkPage() {
81
87
  }, []);
82
88
 
83
89
  const viewParam = searchParams.get("view");
84
- const view = viewParam === "project" ? "project" : viewParam === "architecture" ? "architecture" : "tasks";
90
+ const view =
91
+ viewParam === "project" ? "project" : viewParam === "architecture" ? "architecture" : "tasks";
85
92
 
86
93
  const milestones = useMemo(() => {
87
94
  const milestoneSet = new Set<string>();
@@ -115,9 +122,11 @@ export default function WorkPage() {
115
122
  );
116
123
  const activeAuthorityFilters = useMemo(
117
124
  () =>
118
- (Object.entries(filters.authorityTypes) as Array<
119
- [keyof typeof filters.authorityTypes, boolean]
120
- >)
125
+ (
126
+ Object.entries(filters.authorityTypes) as Array<
127
+ [keyof typeof filters.authorityTypes, boolean]
128
+ >
129
+ )
121
130
  .filter(([, enabled]) => enabled)
122
131
  .map(([filter]) => filter),
123
132
  [filters.authorityTypes],
@@ -140,19 +149,27 @@ export default function WorkPage() {
140
149
  <Tabs>
141
150
  <TabsList>
142
151
  <TabsTrigger
143
- className={view === "architecture" ? "border border-blue-700 bg-slate-800 text-slate-100" : ""}
152
+ className={
153
+ view === "architecture"
154
+ ? "border border-blue-700 bg-slate-800 text-slate-100"
155
+ : ""
156
+ }
144
157
  onClick={() => router.push("/work?view=architecture")}
145
158
  >
146
159
  Architecture
147
160
  </TabsTrigger>
148
161
  <TabsTrigger
149
- className={view === "tasks" ? "border border-blue-700 bg-slate-800 text-slate-100" : ""}
162
+ className={
163
+ view === "tasks" ? "border border-blue-700 bg-slate-800 text-slate-100" : ""
164
+ }
150
165
  onClick={() => router.push("/work?view=tasks")}
151
166
  >
152
167
  Tasks
153
168
  </TabsTrigger>
154
169
  <TabsTrigger
155
- className={view === "project" ? "border border-blue-700 bg-slate-800 text-slate-100" : ""}
170
+ className={
171
+ view === "project" ? "border border-blue-700 bg-slate-800 text-slate-100" : ""
172
+ }
156
173
  onClick={() => router.push("/work?view=project")}
157
174
  >
158
175
  Project
@@ -166,13 +183,17 @@ export default function WorkPage() {
166
183
  <div className="rounded-xl border border-red-700 bg-red-950/60 p-3 text-sm text-red-200">
167
184
  <p className="font-medium">Graph activation blocked by schema validation errors.</p>
168
185
  {graphValidation.errors.map((issue, index) => (
169
- <p key={`${issue.ruleId}:${index}`}>[{issue.ruleId}] {issue.message}</p>
186
+ <p key={`${issue.ruleId}:${index}`}>
187
+ [{issue.ruleId}] {issue.message}
188
+ </p>
170
189
  ))}
171
190
  </div>
172
191
  ) : graphData ? (
173
192
  <GraphCanvas
174
193
  data={graphData}
175
- viewMode={view === "project" ? "project" : view === "architecture" ? "architecture-map" : "tasks"}
194
+ viewMode={
195
+ view === "project" ? "project" : view === "architecture" ? "architecture-map" : "tasks"
196
+ }
176
197
  enabledFilters={activeFilters}
177
198
  enabledEdgeFilters={activeEdgeFilters}
178
199
  enabledAuthorityFilters={activeAuthorityFilters}
@@ -182,7 +203,14 @@ export default function WorkPage() {
182
203
  hideCompletedTasks={filters.hideCompletedTasks}
183
204
  onNodeSelect={(node) =>
184
205
  setSelection({
185
- type: node.type as "domain" | "decision" | "phase" | "milestone" | "task" | "file" | "health",
206
+ type: node.type as
207
+ | "domain"
208
+ | "decision"
209
+ | "phase"
210
+ | "milestone"
211
+ | "task"
212
+ | "file"
213
+ | "health",
186
214
  title: node.title,
187
215
  id: node.id,
188
216
  metadata: node.metadata,
@@ -199,66 +227,68 @@ export default function WorkPage() {
199
227
  <div className="rounded-xl border border-amber-700 bg-amber-950/60 p-3 text-sm text-amber-200">
200
228
  <p className="font-medium">Graph warnings</p>
201
229
  {graphValidation.warnings.map((issue, index) => (
202
- <p key={`${issue.ruleId}:${index}`}>[{issue.ruleId}] {issue.message}</p>
230
+ <p key={`${issue.ruleId}:${index}`}>
231
+ [{issue.ruleId}] {issue.message}
232
+ </p>
203
233
  ))}
204
234
  </div>
205
235
  ) : null}
206
236
 
207
237
  {view !== "architecture" ? (
208
- <Card>
209
- <CardHeader>
210
- <CardTitle>Roadmap Work</CardTitle>
211
- </CardHeader>
212
- <CardContent className="grid gap-2">
213
- <div className="flex flex-wrap gap-2">
214
- <Select value={phase} onChange={(event) => setPhase(event.target.value)}>
215
- <option value="all">All phases</option>
216
- {phases.map((item) => (
217
- <option key={item.id} value={item.id}>
218
- {item.id} {item.active ? "(active)" : ""}
219
- </option>
220
- ))}
221
- </Select>
222
- <Input
223
- placeholder="Search task..."
224
- value={query}
225
- onChange={(event) => setQuery(event.target.value)}
226
- />
227
- </div>
238
+ <Card>
239
+ <CardHeader>
240
+ <CardTitle>Roadmap Work</CardTitle>
241
+ </CardHeader>
242
+ <CardContent className="grid gap-2">
243
+ <div className="flex flex-wrap gap-2">
244
+ <Select value={phase} onChange={(event) => setPhase(event.target.value)}>
245
+ <option value="all">All phases</option>
246
+ {phases.map((item) => (
247
+ <option key={item.id} value={item.id}>
248
+ {item.id} {item.active ? "(active)" : ""}
249
+ </option>
250
+ ))}
251
+ </Select>
252
+ <Input
253
+ placeholder="Search task..."
254
+ value={query}
255
+ onChange={(event) => setQuery(event.target.value)}
256
+ />
257
+ </div>
228
258
 
229
- <div className="grid gap-1 text-slate-400">
230
- {milestones.map((milestone) => (
231
- <span key={milestone}>{milestone}</span>
232
- ))}
233
- </div>
234
- </CardContent>
235
- </Card>
259
+ <div className="grid gap-1 text-slate-400">
260
+ {milestones.map((milestone) => (
261
+ <span key={milestone}>{milestone}</span>
262
+ ))}
263
+ </div>
264
+ </CardContent>
265
+ </Card>
236
266
  ) : null}
237
267
 
238
268
  {view !== "architecture" ? (
239
- <Card>
240
- <CardHeader>
241
- <CardTitle>Tasks</CardTitle>
242
- </CardHeader>
243
- <CardContent>
244
- <WorkTable
245
- tasks={filteredTasks}
246
- onSelectTask={(task) =>
247
- setSelection({
248
- type: "task",
249
- title: task.title,
250
- id: task.id,
251
- metadata: [
252
- { label: "Phase", value: task.milestone.split("/")[0] ?? "unknown" },
253
- { label: "Milestone", value: task.milestone },
254
- { label: "Status", value: task.lane },
255
- { label: "Domain", value: task.domain ?? "foundation" },
256
- ],
257
- })
258
- }
259
- />
260
- </CardContent>
261
- </Card>
269
+ <Card>
270
+ <CardHeader>
271
+ <CardTitle>Tasks</CardTitle>
272
+ </CardHeader>
273
+ <CardContent>
274
+ <WorkTable
275
+ tasks={filteredTasks}
276
+ onSelectTask={(task) =>
277
+ setSelection({
278
+ type: "task",
279
+ title: task.title,
280
+ id: task.id,
281
+ metadata: [
282
+ { label: "Phase", value: task.milestone.split("/")[0] ?? "unknown" },
283
+ { label: "Milestone", value: task.milestone },
284
+ { label: "Status", value: task.lane },
285
+ { label: "Domain", value: task.domain ?? "foundation" },
286
+ ],
287
+ })
288
+ }
289
+ />
290
+ </CardContent>
291
+ </Card>
262
292
  ) : null}
263
293
  </div>
264
294
  );
@@ -57,13 +57,7 @@ function AppShellContent({ children }: { children: ReactNode }) {
57
57
  }
58
58
  window.addEventListener("keydown", onKeyDown);
59
59
  return () => window.removeEventListener("keydown", onKeyDown);
60
- }, [
61
- leftCollapsed,
62
- resetLayout,
63
- rightCollapsed,
64
- setLeftCollapsed,
65
- setRightCollapsed,
66
- ]);
60
+ }, [leftCollapsed, resetLayout, rightCollapsed, setLeftCollapsed, setRightCollapsed]);
67
61
 
68
62
  const shellStyle = useMemo(() => {
69
63
  const leftResizer = leftCollapsed ? 0 : resizerWidth;
@@ -40,14 +40,20 @@ export function ArchNode({ data, selected }: NodeProps<ArchNodeData>) {
40
40
  const metadataPreviewCount = detailLevel === 0 ? 1 : detailLevel === 1 ? 2 : 4;
41
41
 
42
42
  return (
43
- <div className={`w-[280px] rounded-xl border border-slate-600 px-2.5 py-2 text-slate-100 shadow ${toneClass}`}>
43
+ <div
44
+ className={`w-[280px] rounded-xl border border-slate-600 px-2.5 py-2 text-slate-100 shadow ${toneClass}`}
45
+ >
44
46
  <NodeToolbar isVisible={selected} position={Position.Top}>
45
47
  <div className="flex items-center gap-2 rounded-md border border-slate-600 bg-slate-950 px-2 py-1">
46
48
  <Badge variant="secondary">{data.kind}</Badge>
47
49
  <span className="text-xs text-slate-400">drag, connect</span>
48
50
  </div>
49
51
  </NodeToolbar>
50
- <Handle type="target" position={Position.Left} className="!h-2 !w-2 !border !border-slate-900 !bg-slate-200" />
52
+ <Handle
53
+ type="target"
54
+ position={Position.Left}
55
+ className="!h-2 !w-2 !border !border-slate-900 !bg-slate-200"
56
+ />
51
57
  <div className="mb-1 text-[13px] font-semibold" style={titleStyle}>
52
58
  {data.label}
53
59
  </div>
@@ -69,7 +75,11 @@ export function ArchNode({ data, selected }: NodeProps<ArchNodeData>) {
69
75
  .join(" · ")}
70
76
  </div>
71
77
  ) : null}
72
- <Handle type="source" position={Position.Right} className="!h-2 !w-2 !border !border-slate-900 !bg-slate-200" />
78
+ <Handle
79
+ type="source"
80
+ position={Position.Right}
81
+ className="!h-2 !w-2 !border !border-slate-900 !bg-slate-200"
82
+ />
73
83
  </div>
74
84
  );
75
85
  }
@@ -103,7 +103,10 @@ function estimateNodeWidth(node: GraphDataset["nodes"][number]): number {
103
103
  const normalized = Array.isArray(value) ? value.join(", ") : String(value);
104
104
  return `${key}: ${normalized}`;
105
105
  });
106
- const metadataWidth = metadataStrings.reduce((max, value) => Math.max(max, value.length * 6.8), 0);
106
+ const metadataWidth = metadataStrings.reduce(
107
+ (max, value) => Math.max(max, value.length * 6.8),
108
+ 0,
109
+ );
107
110
  const estimated = base + Math.max(titleWidth, subtitleWidth, metadataWidth) * 0.58;
108
111
  return Math.max(240, Math.min(estimated, 760));
109
112
  }
@@ -136,7 +139,8 @@ export function buildGraphFromDataset(
136
139
  columnX.set(column, cursorX);
137
140
  const currentWidth = columnMaxWidth.get(column) ?? 260;
138
141
  const nextColumn = columns[index + 1];
139
- const nextWidth = nextColumn === undefined ? currentWidth : (columnMaxWidth.get(nextColumn) ?? 260);
142
+ const nextWidth =
143
+ nextColumn === undefined ? currentWidth : (columnMaxWidth.get(nextColumn) ?? 260);
140
144
  const adaptiveGap = Math.max(
141
145
  MIN_HORIZONTAL_GAP,
142
146
  Math.round(Math.max(currentWidth, nextWidth) * 0.24),