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.
- package/CHANGELOG.md +43 -0
- package/LICENSE +21 -0
- package/README.md +536 -43
- package/package.json +27 -3
- package/templates/arch-ui/.arch/edges/decision_to_domain.json +0 -1
- package/templates/arch-ui/.arch/edges/milestone_to_task.json +0 -1
- package/templates/arch-ui/.arch/edges/task_to_decision.json +0 -1
- package/templates/arch-ui/.arch/edges/task_to_module.json +0 -1
- package/templates/arch-ui/.arch/graph.json +0 -1
- package/templates/arch-ui/.arch/nodes/decisions.json +0 -1
- package/templates/arch-ui/.arch/nodes/domains.json +0 -1
- package/templates/arch-ui/.arch/nodes/milestones.json +0 -1
- package/templates/arch-ui/.arch/nodes/modules.json +0 -1
- package/templates/arch-ui/.arch/nodes/tasks.json +0 -1
- package/templates/arch-ui/app/api/health/route.ts +5 -4
- package/templates/arch-ui/app/api/node-files/route.ts +6 -1
- package/templates/arch-ui/app/api/search/route.ts +0 -1
- package/templates/arch-ui/app/work/page.tsx +94 -64
- package/templates/arch-ui/components/app-shell.tsx +1 -7
- package/templates/arch-ui/components/graph/arch-node.tsx +13 -3
- package/templates/arch-ui/components/graph/build-graph-from-dataset.ts +6 -2
- package/templates/arch-ui/components/graph/build-initial-graph.ts +215 -221
- package/templates/arch-ui/components/graph/graph-types.ts +49 -49
- package/templates/arch-ui/components/graph/use-auto-layout.ts +51 -51
- package/templates/arch-ui/components/graph/use-connection-validation.ts +48 -48
- package/templates/arch-ui/components/graph/use-flow-persistence.ts +38 -38
- package/templates/arch-ui/components/graph-canvas.tsx +90 -74
- package/templates/arch-ui/components/inspector.tsx +56 -22
- package/templates/arch-ui/components/sidebar.tsx +18 -8
- package/templates/arch-ui/components/topbar.tsx +8 -11
- package/templates/arch-ui/components/work-table.tsx +1 -5
- package/templates/arch-ui/components/workspace-context.tsx +2 -1
- package/templates/arch-ui/lib/graph-dataset.ts +4 -8
- package/templates/arch-ui/lib/graph-schema.ts +1 -4
- package/templates/arch-ui/package.json +0 -1
- 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(
|
|
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): {
|
|
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">
|
|
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">
|
|
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">
|
|
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">
|
|
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(
|
|
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">
|
|
655
|
-
|
|
656
|
-
|
|
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}`}>
|
|
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
|
|
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
|
|
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 &&
|
|
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
|
|
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(
|
|
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}/`)
|
|
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")
|
|
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">
|
|
409
|
-
|
|
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 } =
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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")
|
|
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
|
-
|
|
119
|
-
|
|
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
|
}
|
|
@@ -8,14 +8,6 @@
|
|
|
8
8
|
],
|
|
9
9
|
"strictNullChecks": true
|
|
10
10
|
},
|
|
11
|
-
"include": [
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
}
|