create-project-arch 1.0.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
@@ -119,7 +119,9 @@ function isTaskLikeType(type: string): type is "task" | "milestone" | "phase" {
119
119
  return type === "task" || type === "milestone" || type === "phase";
120
120
  }
121
121
 
122
- function isFileBackedType(type: string): type is "task" | "milestone" | "phase" | "domain" | "file" {
122
+ function isFileBackedType(
123
+ type: string,
124
+ ): type is "task" | "milestone" | "phase" | "domain" | "file" {
123
125
  return (
124
126
  type === "task" ||
125
127
  type === "milestone" ||
@@ -190,7 +192,11 @@ function humanizeValue(label: string, value: string): string {
190
192
  return value;
191
193
  }
192
194
 
193
- function parseTaskId(taskId: string): { phaseId?: string; milestoneId?: string; taskNumber?: string } {
195
+ function parseTaskId(taskId: string): {
196
+ phaseId?: string;
197
+ milestoneId?: string;
198
+ taskNumber?: string;
199
+ } {
194
200
  const [phaseId, milestoneId, taskNumber] = taskId.split("/");
195
201
  return {
196
202
  phaseId: phaseId || undefined,
@@ -393,23 +399,21 @@ function MetadataDetails({
393
399
  className="rounded-lg border border-slate-700/70 bg-slate-900/30 p-2 [&_summary::-webkit-details-marker]:hidden"
394
400
  open={defaultOpen}
395
401
  >
396
- <summary className="cursor-pointer list-none text-sm font-medium text-slate-200">{title}</summary>
402
+ <summary className="cursor-pointer list-none text-sm font-medium text-slate-200">
403
+ {title}
404
+ </summary>
397
405
  <div className="mt-2 grid gap-2">{children}</div>
398
406
  </details>
399
407
  );
400
408
  }
401
409
 
402
- function MetadataValue({
403
- label,
404
- value,
405
- }: {
406
- label: string;
407
- value: FrontmatterValue;
408
- }) {
410
+ function MetadataValue({ label, value }: { label: string; value: FrontmatterValue }) {
409
411
  if (value === null) {
410
412
  return (
411
413
  <div className="grid gap-1">
412
- <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">{humanizeLabel(label)}</p>
414
+ <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">
415
+ {humanizeLabel(label)}
416
+ </p>
413
417
  <p className="m-0 text-sm text-slate-500">None</p>
414
418
  </div>
415
419
  );
@@ -419,7 +423,9 @@ function MetadataValue({
419
423
  if (value.length === 0) {
420
424
  return (
421
425
  <div className="grid gap-1">
422
- <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">{humanizeLabel(label)}</p>
426
+ <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">
427
+ {humanizeLabel(label)}
428
+ </p>
423
429
  <p className="m-0 text-sm text-slate-500">None</p>
424
430
  </div>
425
431
  );
@@ -428,7 +434,9 @@ function MetadataValue({
428
434
  const listLike = normalizeKey(label).includes("criteria");
429
435
  return (
430
436
  <div className="grid gap-1.5">
431
- <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">{humanizeLabel(label)}</p>
437
+ <p className="m-0 text-xs uppercase tracking-[0.06em] text-slate-400">
438
+ {humanizeLabel(label)}
439
+ </p>
432
440
  {listLike ? (
433
441
  <ol className="m-0 grid list-decimal gap-1 pl-5 text-sm text-slate-200">
434
442
  {value.map((item) => (
@@ -485,7 +493,11 @@ export function InspectorPanel() {
485
493
  keys
486
494
  .filter((key) => sourceEntries.has(key))
487
495
  .map((key) => [key, sourceEntries.get(key) ?? null] as const)
488
- .filter(([, value]) => showEmptyFileMetadata || !(value === null || (Array.isArray(value) && value.length === 0)));
496
+ .filter(
497
+ ([, value]) =>
498
+ showEmptyFileMetadata ||
499
+ !(value === null || (Array.isArray(value) && value.length === 0)),
500
+ );
489
501
 
490
502
  return {
491
503
  identity: pick(keyOrder.identity),
@@ -651,9 +663,15 @@ export function InspectorPanel() {
651
663
  <aside className="h-full min-h-0 overflow-y-auto overflow-x-visible border-l border-slate-800 bg-slate-950/85 p-4">
652
664
  <div className="grid gap-3">
653
665
  <div className="flex flex-wrap items-center gap-2">
654
- <Badge variant="secondary">{humanizeLabel(hasFileDocument ? "file" : selection.type)}</Badge>
655
- {typeof fileLaneValue === "string" ? <Badge variant="secondary">{humanizeValue("lane", fileLaneValue)}</Badge> : null}
656
- {typeof fileStatusValue === "string" ? <Badge variant="secondary">{humanizeValue("status", fileStatusValue)}</Badge> : null}
666
+ <Badge variant="secondary">
667
+ {humanizeLabel(hasFileDocument ? "file" : selection.type)}
668
+ </Badge>
669
+ {typeof fileLaneValue === "string" ? (
670
+ <Badge variant="secondary">{humanizeValue("lane", fileLaneValue)}</Badge>
671
+ ) : null}
672
+ {typeof fileStatusValue === "string" ? (
673
+ <Badge variant="secondary">{humanizeValue("status", fileStatusValue)}</Badge>
674
+ ) : null}
657
675
  {selectedLevelFile?.path ? (
658
676
  <Button
659
677
  variant="outline"
@@ -687,7 +705,9 @@ export function InspectorPanel() {
687
705
  {graphWarnings.length > 0 ? (
688
706
  <Section title="Graph Diagnostics">
689
707
  {graphWarnings.slice(0, 8).map((issue, index) => (
690
- <Code key={`${issue.ruleId}:${index}`}>[{issue.ruleId}] {issue.message}</Code>
708
+ <Code key={`${issue.ruleId}:${index}`}>
709
+ [{issue.ruleId}] {issue.message}
710
+ </Code>
691
711
  ))}
692
712
  </Section>
693
713
  ) : null}
@@ -774,7 +794,11 @@ export function InspectorPanel() {
774
794
  {fileMetadataGroups.references.length > 0 ? (
775
795
  <MetadataDetails title="References">
776
796
  {fileMetadataGroups.references.map(([label, value]) => (
777
- <MetadataValue key={`file-references:${label}`} label={label} value={value} />
797
+ <MetadataValue
798
+ key={`file-references:${label}`}
799
+ label={label}
800
+ value={value}
801
+ />
778
802
  ))}
779
803
  </MetadataDetails>
780
804
  ) : null}
@@ -806,7 +830,11 @@ export function InspectorPanel() {
806
830
  <div className="grid gap-1.5">
807
831
  <p className="text-slate-400">Task Metadata</p>
808
832
  {rawTaskMetadata.map((item) => (
809
- <Field key={`${item.label}:${item.value}`} label={item.label} value={item.value} />
833
+ <Field
834
+ key={`${item.label}:${item.value}`}
835
+ label={item.label}
836
+ value={item.value}
837
+ />
810
838
  ))}
811
839
  </div>
812
840
  ) : null}
@@ -836,7 +864,9 @@ export function InspectorPanel() {
836
864
  {trace?.files?.map((file) => (
837
865
  <Code key={file}>{file}</Code>
838
866
  ))}
839
- {!trace?.decisionRefs?.length && !trace?.moduleRefs?.length && !trace?.files?.length ? (
867
+ {!trace?.decisionRefs?.length &&
868
+ !trace?.moduleRefs?.length &&
869
+ !trace?.files?.length ? (
840
870
  <p className="text-slate-400">No trace links found.</p>
841
871
  ) : null}
842
872
  </div>
@@ -854,7 +884,11 @@ export function InspectorPanel() {
854
884
  <div className="grid gap-2">
855
885
  <p className="text-slate-400">Entity Data</p>
856
886
  {entityDetails.summary.map((item) => (
857
- <Field key={`${item.label}:${item.value}`} label={item.label} value={item.value} />
887
+ <Field
888
+ key={`${item.label}:${item.value}`}
889
+ label={item.label}
890
+ value={item.value}
891
+ />
858
892
  ))}
859
893
  <RelatedList label="Tasks" values={entityDetails.relatedTasks} />
860
894
  <RelatedList label="Milestones" values={entityDetails.relatedMilestones} />
@@ -81,12 +81,17 @@ function sortTree(nodes: TreeNode[]): TreeNode[] {
81
81
  }));
82
82
  }
83
83
 
84
- function buildDocNodes(scope: DomainDocsData["docs"][number]["scope"], docs: DomainDocsData["docs"]): TreeNode[] {
84
+ function buildDocNodes(
85
+ scope: DomainDocsData["docs"][number]["scope"],
86
+ docs: DomainDocsData["docs"],
87
+ ): TreeNode[] {
85
88
  const scopedDocs = docs.filter((doc) => doc.scope === scope);
86
89
  const roots: TreeNode[] = [];
87
90
 
88
91
  for (const doc of scopedDocs) {
89
- const relativePath = doc.path.startsWith(`${scope}/`) ? doc.path.slice(scope.length + 1) : doc.path;
92
+ const relativePath = doc.path.startsWith(`${scope}/`)
93
+ ? doc.path.slice(scope.length + 1)
94
+ : doc.path;
90
95
  const parts = relativePath.split("/").filter(Boolean);
91
96
  if (parts.length === 0) continue;
92
97
 
@@ -162,7 +167,8 @@ export function Sidebar() {
162
167
  }, []);
163
168
 
164
169
  const activeView = useMemo<WorkspaceView>(() => {
165
- if (pathname === "/work" && searchParams.get("view") === "architecture") return "architecture-map";
170
+ if (pathname === "/work" && searchParams.get("view") === "architecture")
171
+ return "architecture-map";
166
172
  if (pathname === "/work" && searchParams.get("view") === "project") return "project-map";
167
173
  if (pathname === "/work") return "tasks-roadmap";
168
174
  const view = searchParams.get("view");
@@ -290,9 +296,7 @@ export function Sidebar() {
290
296
  ) : (
291
297
  <span className="h-4 w-4" />
292
298
  )}
293
- <span className="text-slate-300">
294
- {iconForNode(node, expandedKeys.has(node.key))}
295
- </span>
299
+ <span className="text-slate-300">{iconForNode(node, expandedKeys.has(node.key))}</span>
296
300
  <button
297
301
  type="button"
298
302
  className="min-w-0 flex-1 truncate text-left text-[12px] text-slate-200"
@@ -405,8 +409,14 @@ export function Sidebar() {
405
409
 
406
410
  <NavigationMenu>
407
411
  <section className="mb-4">
408
- <p className="mb-2 text-[11px] uppercase tracking-[0.1em] text-slate-400">Unified Explorer</p>
409
- {tree.length > 0 ? renderTree(tree) : <p className="text-sm text-slate-400">Loading explorer...</p>}
412
+ <p className="mb-2 text-[11px] uppercase tracking-[0.1em] text-slate-400">
413
+ Unified Explorer
414
+ </p>
415
+ {tree.length > 0 ? (
416
+ renderTree(tree)
417
+ ) : (
418
+ <p className="text-sm text-slate-400">Loading explorer...</p>
419
+ )}
410
420
  </section>
411
421
  <section className="mb-4">
412
422
  <p className="mb-2 text-[11px] uppercase tracking-[0.1em] text-slate-400">Node Filters</p>
@@ -32,7 +32,8 @@ export function Topbar({ projectName }: { projectName: string }) {
32
32
  const pathname = usePathname();
33
33
  const searchParams = useSearchParams();
34
34
  const { selection } = useInspector();
35
- const { splitPane, setSplitPane, rightCollapsed, setRightCollapsed, resetLayout } = useWorkspace();
35
+ const { splitPane, setSplitPane, rightCollapsed, setRightCollapsed, resetLayout } =
36
+ useWorkspace();
36
37
 
37
38
  useEffect(() => {
38
39
  function onKeyDown(event: KeyboardEvent) {
@@ -79,11 +80,7 @@ export function Topbar({ projectName }: { projectName: string }) {
79
80
 
80
81
  const breadcrumbs = useMemo(() => {
81
82
  const pathLabel =
82
- pathname === "/work"
83
- ? "Work Graph"
84
- : pathname === "/health"
85
- ? "Health"
86
- : "Workspace";
83
+ pathname === "/work" ? "Work Graph" : pathname === "/health" ? "Health" : "Workspace";
87
84
  const view = searchParams.get("view");
88
85
  const secondary =
89
86
  view === "docs"
@@ -92,11 +89,11 @@ export function Topbar({ projectName }: { projectName: string }) {
92
89
  ? "Decisions"
93
90
  : view === "architecture"
94
91
  ? "Architecture"
95
- : view === "project"
96
- ? "Project"
97
- : view === "tasks"
98
- ? "Tasks"
99
- : "Map";
92
+ : view === "project"
93
+ ? "Project"
94
+ : view === "tasks"
95
+ ? "Tasks"
96
+ : "Map";
100
97
 
101
98
  const parts = [pathLabel];
102
99
  if (pathname === "/work") parts.push(secondary);
@@ -31,11 +31,7 @@ export function WorkTable({ tasks, onSelectTask }: WorkTableProps) {
31
31
  </TableHeader>
32
32
  <TableBody>
33
33
  {tasks.map((task) => (
34
- <TableRow
35
- className="cursor-pointer"
36
- key={task.id}
37
- onClick={() => onSelectTask(task)}
38
- >
34
+ <TableRow className="cursor-pointer" key={task.id} onClick={() => onSelectTask(task)}>
39
35
  <TableCell>
40
36
  <Code>{task.id}</Code>
41
37
  </TableCell>
@@ -84,7 +84,8 @@ function defaultFilters(): WorkspaceFilters {
84
84
 
85
85
  function resolveActiveGraphView(pathname: string, searchParams: URLSearchParams): GraphViewMode {
86
86
  if (pathname === "/work" && searchParams.get("view") === "project") return "project";
87
- if (pathname === "/work" && searchParams.get("view") === "architecture") return "architecture-map";
87
+ if (pathname === "/work" && searchParams.get("view") === "architecture")
88
+ return "architecture-map";
88
89
  if (pathname === "/work") return "tasks";
89
90
  return "architecture-map";
90
91
  }
@@ -114,14 +114,10 @@ async function filterGitIgnoredPaths(root: string, paths: string[]): Promise<Set
114
114
  if (chunk.length === 0) continue;
115
115
 
116
116
  try {
117
- const { stdout } = (await execFile(
118
- "git",
119
- ["check-ignore", ...chunk],
120
- {
121
- cwd: root,
122
- encoding: "utf8",
123
- } as Parameters<typeof execFile>[2],
124
- )) as { stdout: string };
117
+ const { stdout } = (await execFile("git", ["check-ignore", ...chunk], {
118
+ cwd: root,
119
+ encoding: "utf8",
120
+ } as Parameters<typeof execFile>[2])) as { stdout: string };
125
121
  stdout
126
122
  .split("\n")
127
123
  .map((line: string) => line.trim())
@@ -229,10 +229,7 @@ export const GRAPH_VALIDATION_RULES = [
229
229
  },
230
230
  ] as const;
231
231
 
232
- function pushIssue(
233
- output: GraphValidationResult,
234
- issue: GraphValidationIssue,
235
- ) {
232
+ function pushIssue(output: GraphValidationResult, issue: GraphValidationIssue) {
236
233
  if (issue.severity === "error") output.errors.push(issue);
237
234
  else output.warnings.push(issue);
238
235
  }
@@ -35,4 +35,3 @@
35
35
  "typescript": "5.9.2"
36
36
  }
37
37
  }
38
-
@@ -8,14 +8,6 @@
8
8
  ],
9
9
  "strictNullChecks": true
10
10
  },
11
- "include": [
12
- "**/*.ts",
13
- "**/*.tsx",
14
- "**/*.d.ts",
15
- "next-env.d.ts",
16
- "next.config.js"
17
- ],
18
- "exclude": [
19
- "node_modules"
20
- ]
21
- }
11
+ "include": ["**/*.ts", "**/*.tsx", "**/*.d.ts", "next-env.d.ts", "next.config.js"],
12
+ "exclude": ["node_modules"]
13
+ }