create-project-arch 1.1.0 → 1.3.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 (62) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/LICENSE +21 -0
  3. package/README.md +662 -43
  4. package/dist/cli.js +151 -0
  5. package/dist/cli.test.js +191 -0
  6. package/package.json +28 -4
  7. package/templates/arch-ui/.arch/edges/decision_to_domain.json +0 -1
  8. package/templates/arch-ui/.arch/edges/milestone_to_task.json +0 -1
  9. package/templates/arch-ui/.arch/edges/task_to_decision.json +0 -1
  10. package/templates/arch-ui/.arch/edges/task_to_module.json +0 -1
  11. package/templates/arch-ui/.arch/graph.json +0 -1
  12. package/templates/arch-ui/.arch/nodes/decisions.json +0 -1
  13. package/templates/arch-ui/.arch/nodes/domains.json +0 -1
  14. package/templates/arch-ui/.arch/nodes/milestones.json +0 -1
  15. package/templates/arch-ui/.arch/nodes/modules.json +0 -1
  16. package/templates/arch-ui/.arch/nodes/tasks.json +0 -1
  17. package/templates/arch-ui/app/api/health/route.ts +5 -4
  18. package/templates/arch-ui/app/api/node-files/route.ts +6 -1
  19. package/templates/arch-ui/app/api/search/route.ts +0 -1
  20. package/templates/arch-ui/app/work/page.tsx +94 -64
  21. package/templates/arch-ui/components/app-shell.tsx +1 -7
  22. package/templates/arch-ui/components/graph/arch-node.tsx +13 -3
  23. package/templates/arch-ui/components/graph/build-graph-from-dataset.ts +6 -2
  24. package/templates/arch-ui/components/graph/build-initial-graph.ts +215 -221
  25. package/templates/arch-ui/components/graph/graph-types.ts +49 -49
  26. package/templates/arch-ui/components/graph/use-auto-layout.ts +51 -51
  27. package/templates/arch-ui/components/graph/use-connection-validation.ts +48 -48
  28. package/templates/arch-ui/components/graph/use-flow-persistence.ts +38 -38
  29. package/templates/arch-ui/components/graph-canvas.tsx +90 -74
  30. package/templates/arch-ui/components/inspector.tsx +56 -22
  31. package/templates/arch-ui/components/sidebar.tsx +18 -8
  32. package/templates/arch-ui/components/topbar.tsx +8 -11
  33. package/templates/arch-ui/components/work-table.tsx +1 -5
  34. package/templates/arch-ui/components/workspace-context.tsx +2 -1
  35. package/templates/arch-ui/eslint.config.js +2 -2
  36. package/templates/arch-ui/lib/graph-dataset.ts +4 -8
  37. package/templates/arch-ui/lib/graph-schema.ts +1 -4
  38. package/templates/arch-ui/package.json +0 -1
  39. package/templates/arch-ui/tsconfig.json +3 -11
  40. package/templates/architecture-specs/SPEC_TEMPLATE.md +49 -0
  41. package/templates/architecture-specs/example-system.md +42 -0
  42. package/templates/concept-map/concept-map.json +67 -0
  43. package/templates/decisions/DECISION_TEMPLATE.md +53 -0
  44. package/templates/decisions/README.md +19 -0
  45. package/templates/decisions/example-decision.md +45 -0
  46. package/templates/domains/DOMAIN_TEMPLATE.md +43 -0
  47. package/templates/domains/README.md +18 -0
  48. package/templates/domains/api.md +33 -0
  49. package/templates/domains/core.md +33 -0
  50. package/templates/domains/domains.json +19 -0
  51. package/templates/domains/ui.md +34 -0
  52. package/templates/foundation/goals.md +35 -0
  53. package/templates/foundation/project-overview.md +35 -0
  54. package/templates/foundation/prompt.md +23 -0
  55. package/templates/foundation/scope.md +35 -0
  56. package/templates/foundation/user-journey.md +37 -0
  57. package/templates/gap-closure/GAP_CLOSURE_TEMPLATE.md +50 -0
  58. package/templates/gap-closure/README.md +19 -0
  59. package/templates/gap-closure/example-gap-closure.md +43 -0
  60. package/templates/validation-hooks/.githooks/pre-commit +4 -0
  61. package/templates/validation-hooks/README.md +20 -0
  62. package/templates/validation-hooks/scripts/validate.sh +9 -0
@@ -1,245 +1,239 @@
1
1
  import type { Edge, Node } from "reactflow";
2
2
  import type { ArchitectureMapData } from "../../lib/types";
3
- import type {
4
- ArchEdgeData,
5
- ArchNodeData,
6
- GraphFilter,
7
- GraphKind,
8
- GraphTone,
9
- } from "./graph-types";
3
+ import type { ArchEdgeData, ArchNodeData, GraphFilter, GraphKind, GraphTone } from "./graph-types";
10
4
 
11
5
  function createGraphNode(
12
- kind: GraphKind,
13
- id: string,
14
- x: number,
15
- y: number,
16
- label: string,
17
- tone: GraphTone,
18
- subtitle?: string,
19
- metadata: Array<{ label: string; value: string }> = [],
6
+ kind: GraphKind,
7
+ id: string,
8
+ x: number,
9
+ y: number,
10
+ label: string,
11
+ tone: GraphTone,
12
+ subtitle?: string,
13
+ metadata: Array<{ label: string; value: string }> = [],
20
14
  ): Node<ArchNodeData> {
21
- return {
22
- id: `${kind}:${id}`,
23
- type: "archNode",
24
- position: { x, y },
25
- data: { kind, tone, label, subtitle, metadata },
26
- };
15
+ return {
16
+ id: `${kind}:${id}`,
17
+ type: "archNode",
18
+ position: { x, y },
19
+ data: { kind, tone, label, subtitle, metadata },
20
+ };
27
21
  }
28
22
 
29
23
  export function buildInitialGraph(
30
- data: ArchitectureMapData,
31
- enabledFilters: GraphFilter[],
32
- hideCompletedTasks = false,
24
+ data: ArchitectureMapData,
25
+ enabledFilters: GraphFilter[],
26
+ hideCompletedTasks = false,
33
27
  ): { nodes: Node<ArchNodeData>[]; edges: Edge[] } {
34
- const includeDomains = enabledFilters.includes("domains");
35
- const includeModules = enabledFilters.includes("modules");
36
- const includeTasks = enabledFilters.includes("tasks");
37
- const includeDecisions = enabledFilters.includes("decisions");
38
-
39
- const milestoneTaskCount = new Map<string, number>();
40
- data.edges.milestoneToTask.forEach((edge) => {
41
- milestoneTaskCount.set(edge.milestone, (milestoneTaskCount.get(edge.milestone) ?? 0) + 1);
28
+ const includeDomains = enabledFilters.includes("domains");
29
+ const includeModules = enabledFilters.includes("modules");
30
+ const includeTasks = enabledFilters.includes("tasks");
31
+ const includeDecisions = enabledFilters.includes("decisions");
32
+
33
+ const milestoneTaskCount = new Map<string, number>();
34
+ data.edges.milestoneToTask.forEach((edge) => {
35
+ milestoneTaskCount.set(edge.milestone, (milestoneTaskCount.get(edge.milestone) ?? 0) + 1);
36
+ });
37
+
38
+ const decisionTaskCount = new Map<string, number>();
39
+ data.edges.taskToDecision.forEach((edge) => {
40
+ decisionTaskCount.set(edge.decision, (decisionTaskCount.get(edge.decision) ?? 0) + 1);
41
+ });
42
+
43
+ const moduleTaskCount = new Map<string, number>();
44
+ data.edges.taskToModule.forEach((edge) => {
45
+ moduleTaskCount.set(edge.module, (moduleTaskCount.get(edge.module) ?? 0) + 1);
46
+ });
47
+
48
+ const domainDecisionCount = new Map<string, number>();
49
+ data.edges.decisionToDomain.forEach((edge) => {
50
+ domainDecisionCount.set(edge.domain, (domainDecisionCount.get(edge.domain) ?? 0) + 1);
51
+ });
52
+
53
+ const nodes: Node<ArchNodeData>[] = [];
54
+ const edges: Edge<ArchEdgeData>[] = [];
55
+
56
+ if (includeDomains) {
57
+ data.nodes.domains.forEach((domain, index) => {
58
+ nodes.push(
59
+ createGraphNode(
60
+ "domain",
61
+ domain.name,
62
+ 40,
63
+ 40 + index * 140,
64
+ domain.name,
65
+ "domain",
66
+ domain.description,
67
+ [
68
+ { label: "Description", value: domain.description ?? "n/a" },
69
+ { label: "Decisions", value: String(domainDecisionCount.get(domain.name) ?? 0) },
70
+ { label: "Graph Node", value: `domain:${domain.name}` },
71
+ ],
72
+ ),
73
+ );
42
74
  });
43
-
44
- const decisionTaskCount = new Map<string, number>();
45
- data.edges.taskToDecision.forEach((edge) => {
46
- decisionTaskCount.set(edge.decision, (decisionTaskCount.get(edge.decision) ?? 0) + 1);
75
+ }
76
+
77
+ if (includeDecisions) {
78
+ data.nodes.decisions.forEach((decision, index) => {
79
+ nodes.push(
80
+ createGraphNode(
81
+ "decision",
82
+ decision.id,
83
+ 280,
84
+ 40 + index * 140,
85
+ decision.id,
86
+ "decision",
87
+ decision.title,
88
+ [
89
+ { label: "Title", value: decision.title ?? decision.id },
90
+ { label: "Status", value: decision.status ?? "open" },
91
+ { label: "Linked Tasks", value: String(decisionTaskCount.get(decision.id) ?? 0) },
92
+ { label: "Graph Node", value: `decision:${decision.id}` },
93
+ ],
94
+ ),
95
+ );
96
+ });
97
+ }
98
+
99
+ const phases = [...new Set(data.nodes.milestones.map((item) => item.phaseId))];
100
+ phases.forEach((phase, index) => {
101
+ const phaseMilestones = data.nodes.milestones.filter(
102
+ (milestone) => milestone.phaseId === phase,
103
+ ).length;
104
+ nodes.push(
105
+ createGraphNode("phase", phase, 520, 40 + index * 140, phase, "phase", undefined, [
106
+ { label: "Milestones", value: String(phaseMilestones) },
107
+ { label: "Graph Node", value: `phase:${phase}` },
108
+ ]),
109
+ );
110
+ });
111
+
112
+ data.nodes.milestones.forEach((milestone, index) => {
113
+ nodes.push(
114
+ createGraphNode(
115
+ "milestone",
116
+ milestone.id,
117
+ 760,
118
+ 40 + index * 140,
119
+ milestone.id,
120
+ "phase",
121
+ milestone.phaseId,
122
+ [
123
+ { label: "Phase", value: milestone.phaseId },
124
+ { label: "Milestone", value: milestone.milestoneId },
125
+ { label: "Tasks", value: String(milestoneTaskCount.get(milestone.id) ?? 0) },
126
+ { label: "Graph Node", value: `milestone:${milestone.id}` },
127
+ ],
128
+ ),
129
+ );
130
+ edges.push({
131
+ id: `phase-milestone:${milestone.phaseId}-${milestone.id}`,
132
+ source: `phase:${milestone.phaseId}`,
133
+ target: `milestone:${milestone.id}`,
134
+ type: "smoothstep",
135
+ data: { edgeType: "blocking", authority: "authoritative" },
136
+ });
137
+ });
138
+
139
+ const visibleTasks = data.nodes.tasks.filter(
140
+ (task) => !(hideCompletedTasks && task.lane === "complete"),
141
+ );
142
+
143
+ if (includeTasks) {
144
+ visibleTasks.forEach((task, index) => {
145
+ nodes.push(
146
+ createGraphNode("task", task.id, 1020, 40 + index * 120, task.title, "task", task.id, [
147
+ { label: "ID", value: task.id },
148
+ { label: "Milestone", value: task.milestone },
149
+ { label: "Lane", value: task.lane },
150
+ { label: "Status", value: task.status },
151
+ { label: "Domain", value: task.domain ?? "unassigned" },
152
+ { label: "Graph Node", value: `task:${task.id}` },
153
+ ]),
154
+ );
47
155
  });
156
+ }
157
+
158
+ if (includeModules) {
159
+ data.nodes.modules.forEach((moduleRef, index) => {
160
+ nodes.push(
161
+ createGraphNode(
162
+ "file",
163
+ moduleRef.name,
164
+ 1280,
165
+ 40 + index * 100,
166
+ moduleRef.name,
167
+ "file",
168
+ moduleRef.type,
169
+ [
170
+ { label: "Name", value: moduleRef.name },
171
+ { label: "Type", value: moduleRef.type ?? "module" },
172
+ { label: "Description", value: moduleRef.description ?? "n/a" },
173
+ { label: "Linked Tasks", value: String(moduleTaskCount.get(moduleRef.name) ?? 0) },
174
+ { label: "Graph Node", value: `file:${moduleRef.name}` },
175
+ ],
176
+ ),
177
+ );
178
+ });
179
+ }
48
180
 
49
- const moduleTaskCount = new Map<string, number>();
50
- data.edges.taskToModule.forEach((edge) => {
51
- moduleTaskCount.set(edge.module, (moduleTaskCount.get(edge.module) ?? 0) + 1);
181
+ if (includeTasks) {
182
+ data.edges.milestoneToTask.forEach((edge) => {
183
+ if (!visibleTasks.some((task) => task.id === edge.task)) return;
184
+ edges.push({
185
+ id: `milestone-task:${edge.milestone}-${edge.task}`,
186
+ source: `milestone:${edge.milestone}`,
187
+ target: `task:${edge.task}`,
188
+ type: "smoothstep",
189
+ data: { edgeType: "blocking", authority: "authoritative" },
190
+ });
52
191
  });
192
+ }
53
193
 
54
- const domainDecisionCount = new Map<string, number>();
194
+ if (includeDecisions && includeDomains) {
55
195
  data.edges.decisionToDomain.forEach((edge) => {
56
- domainDecisionCount.set(edge.domain, (domainDecisionCount.get(edge.domain) ?? 0) + 1);
196
+ edges.push({
197
+ id: `decision-domain:${edge.decision}-${edge.domain}`,
198
+ source: `decision:${edge.decision}`,
199
+ target: `domain:${edge.domain}`,
200
+ type: "smoothstep",
201
+ data: { edgeType: "data-flow", authority: "authoritative" },
202
+ });
57
203
  });
204
+ }
58
205
 
59
- const nodes: Node<ArchNodeData>[] = [];
60
- const edges: Edge<ArchEdgeData>[] = [];
61
-
62
- if (includeDomains) {
63
- data.nodes.domains.forEach((domain, index) => {
64
- nodes.push(
65
- createGraphNode(
66
- "domain",
67
- domain.name,
68
- 40,
69
- 40 + index * 140,
70
- domain.name,
71
- "domain",
72
- domain.description,
73
- [
74
- { label: "Description", value: domain.description ?? "n/a" },
75
- { label: "Decisions", value: String(domainDecisionCount.get(domain.name) ?? 0) },
76
- { label: "Graph Node", value: `domain:${domain.name}` },
77
- ],
78
- ),
79
- );
80
- });
81
- }
82
-
83
- if (includeDecisions) {
84
- data.nodes.decisions.forEach((decision, index) => {
85
- nodes.push(
86
- createGraphNode(
87
- "decision",
88
- decision.id,
89
- 280,
90
- 40 + index * 140,
91
- decision.id,
92
- "decision",
93
- decision.title,
94
- [
95
- { label: "Title", value: decision.title ?? decision.id },
96
- { label: "Status", value: decision.status ?? "open" },
97
- { label: "Linked Tasks", value: String(decisionTaskCount.get(decision.id) ?? 0) },
98
- { label: "Graph Node", value: `decision:${decision.id}` },
99
- ],
100
- ),
101
- );
102
- });
103
- }
104
-
105
- const phases = [...new Set(data.nodes.milestones.map((item) => item.phaseId))];
106
- phases.forEach((phase, index) => {
107
- const phaseMilestones = data.nodes.milestones.filter(
108
- (milestone) => milestone.phaseId === phase,
109
- ).length;
110
- nodes.push(
111
- createGraphNode("phase", phase, 520, 40 + index * 140, phase, "phase", undefined, [
112
- { label: "Milestones", value: String(phaseMilestones) },
113
- { label: "Graph Node", value: `phase:${phase}` },
114
- ]),
115
- );
206
+ if (includeTasks && includeModules) {
207
+ data.edges.taskToModule.forEach((edge) => {
208
+ if (!visibleTasks.some((task) => task.id === edge.task)) return;
209
+ edges.push({
210
+ id: `task-module:${edge.task}-${edge.module}`,
211
+ source: `task:${edge.task}`,
212
+ target: `file:${edge.module}`,
213
+ type: "smoothstep",
214
+ data: { edgeType: "dependency", authority: "authoritative" },
215
+ });
116
216
  });
217
+ }
117
218
 
118
- data.nodes.milestones.forEach((milestone, index) => {
119
- nodes.push(
120
- createGraphNode(
121
- "milestone",
122
- milestone.id,
123
- 760,
124
- 40 + index * 140,
125
- milestone.id,
126
- "phase",
127
- milestone.phaseId,
128
- [
129
- { label: "Phase", value: milestone.phaseId },
130
- { label: "Milestone", value: milestone.milestoneId },
131
- { label: "Tasks", value: String(milestoneTaskCount.get(milestone.id) ?? 0) },
132
- { label: "Graph Node", value: `milestone:${milestone.id}` },
133
- ],
134
- ),
135
- );
136
- edges.push({
137
- id: `phase-milestone:${milestone.phaseId}-${milestone.id}`,
138
- source: `phase:${milestone.phaseId}`,
139
- target: `milestone:${milestone.id}`,
140
- type: "smoothstep",
141
- data: { edgeType: "blocking", authority: "authoritative" },
142
- });
219
+ if (includeTasks && includeDecisions) {
220
+ data.edges.taskToDecision.forEach((edge) => {
221
+ if (!visibleTasks.some((task) => task.id === edge.task)) return;
222
+ edges.push({
223
+ id: `task-decision:${edge.task}-${edge.decision}`,
224
+ source: `task:${edge.task}`,
225
+ target: `decision:${edge.decision}`,
226
+ type: "smoothstep",
227
+ data: { edgeType: "blocking", authority: "authoritative" },
228
+ });
143
229
  });
230
+ }
144
231
 
145
- const visibleTasks = data.nodes.tasks.filter(
146
- (task) => !(hideCompletedTasks && task.lane === "complete"),
147
- );
148
-
149
- if (includeTasks) {
150
- visibleTasks.forEach((task, index) => {
151
- nodes.push(
152
- createGraphNode("task", task.id, 1020, 40 + index * 120, task.title, "task", task.id, [
153
- { label: "ID", value: task.id },
154
- { label: "Milestone", value: task.milestone },
155
- { label: "Lane", value: task.lane },
156
- { label: "Status", value: task.status },
157
- { label: "Domain", value: task.domain ?? "unassigned" },
158
- { label: "Graph Node", value: `task:${task.id}` },
159
- ]),
160
- );
161
- });
162
- }
163
-
164
- if (includeModules) {
165
- data.nodes.modules.forEach((moduleRef, index) => {
166
- nodes.push(
167
- createGraphNode(
168
- "file",
169
- moduleRef.name,
170
- 1280,
171
- 40 + index * 100,
172
- moduleRef.name,
173
- "file",
174
- moduleRef.type,
175
- [
176
- { label: "Name", value: moduleRef.name },
177
- { label: "Type", value: moduleRef.type ?? "module" },
178
- { label: "Description", value: moduleRef.description ?? "n/a" },
179
- { label: "Linked Tasks", value: String(moduleTaskCount.get(moduleRef.name) ?? 0) },
180
- { label: "Graph Node", value: `file:${moduleRef.name}` },
181
- ],
182
- ),
183
- );
184
- });
185
- }
186
-
187
- if (includeTasks) {
188
- data.edges.milestoneToTask.forEach((edge) => {
189
- if (!visibleTasks.some((task) => task.id === edge.task)) return;
190
- edges.push({
191
- id: `milestone-task:${edge.milestone}-${edge.task}`,
192
- source: `milestone:${edge.milestone}`,
193
- target: `task:${edge.task}`,
194
- type: "smoothstep",
195
- data: { edgeType: "blocking", authority: "authoritative" },
196
- });
197
- });
232
+ edges.forEach((edge) => {
233
+ if (!edge.data) {
234
+ edge.data = { edgeType: "dependency", authority: "authoritative" };
198
235
  }
236
+ });
199
237
 
200
- if (includeDecisions && includeDomains) {
201
- data.edges.decisionToDomain.forEach((edge) => {
202
- edges.push({
203
- id: `decision-domain:${edge.decision}-${edge.domain}`,
204
- source: `decision:${edge.decision}`,
205
- target: `domain:${edge.domain}`,
206
- type: "smoothstep",
207
- data: { edgeType: "data-flow", authority: "authoritative" },
208
- });
209
- });
210
- }
211
-
212
- if (includeTasks && includeModules) {
213
- data.edges.taskToModule.forEach((edge) => {
214
- if (!visibleTasks.some((task) => task.id === edge.task)) return;
215
- edges.push({
216
- id: `task-module:${edge.task}-${edge.module}`,
217
- source: `task:${edge.task}`,
218
- target: `file:${edge.module}`,
219
- type: "smoothstep",
220
- data: { edgeType: "dependency", authority: "authoritative" },
221
- });
222
- });
223
- }
224
-
225
- if (includeTasks && includeDecisions) {
226
- data.edges.taskToDecision.forEach((edge) => {
227
- if (!visibleTasks.some((task) => task.id === edge.task)) return;
228
- edges.push({
229
- id: `task-decision:${edge.task}-${edge.decision}`,
230
- source: `task:${edge.task}`,
231
- target: `decision:${edge.decision}`,
232
- type: "smoothstep",
233
- data: { edgeType: "blocking", authority: "authoritative" },
234
- });
235
- });
236
- }
237
-
238
- edges.forEach((edge) => {
239
- if (!edge.data) {
240
- edge.data = { edgeType: "dependency", authority: "authoritative" };
241
- }
242
- });
243
-
244
- return { nodes, edges };
238
+ return { nodes, edges };
245
239
  }
@@ -8,75 +8,75 @@ export type GraphKind = "domain" | "decision" | "phase" | "milestone" | "task" |
8
8
  export type GraphTone = "domain" | "decision" | "phase" | "task" | "file";
9
9
 
10
10
  export type InspectorNode = {
11
- type: string;
12
- id: string;
13
- title: string;
14
- metadata: Array<{ label: string; value: string }>;
15
- markdown?: string;
11
+ type: string;
12
+ id: string;
13
+ title: string;
14
+ metadata: Array<{ label: string; value: string }>;
15
+ markdown?: string;
16
16
  };
17
17
 
18
18
  export type ArchNodeData = {
19
- kind: GraphKind;
20
- tone: GraphTone;
21
- label: string;
22
- subtitle?: string;
23
- canonicalType?: string;
24
- metadata: Array<{ label: string; value: string }>;
19
+ kind: GraphKind;
20
+ tone: GraphTone;
21
+ label: string;
22
+ subtitle?: string;
23
+ canonicalType?: string;
24
+ metadata: Array<{ label: string; value: string }>;
25
25
  };
26
26
 
27
27
  export type ArchEdgeData = {
28
- edgeType: GraphEdgeFilter;
29
- authority: GraphEdgeAuthority;
28
+ edgeType: GraphEdgeFilter;
29
+ authority: GraphEdgeAuthority;
30
30
  };
31
31
 
32
32
  export type ContextMenuState = {
33
- nodeId: string;
34
- x: number;
35
- y: number;
33
+ nodeId: string;
34
+ x: number;
35
+ y: number;
36
36
  };
37
37
 
38
38
  export const NODE_WIDTH = 220;
39
39
  export const NODE_HEIGHT = 100;
40
40
 
41
41
  export const toneColor: Record<GraphTone, string> = {
42
- domain: "#3b82f6",
43
- decision: "#7c3aed",
44
- phase: "#22c55e",
45
- task: "#f59e0b",
46
- file: "#6b7280",
42
+ domain: "#3b82f6",
43
+ decision: "#7c3aed",
44
+ phase: "#22c55e",
45
+ task: "#f59e0b",
46
+ file: "#6b7280",
47
47
  };
48
48
 
49
49
  export function parseNodeId(nodeId: string): { kind: GraphKind; id: string } {
50
- const [scope = "file", type = "file", ...rest] = nodeId.split(":");
51
- if (scope === "roadmap" && type === "task") return { kind: "task", id: rest.join(":") };
52
- if (scope === "roadmap" && type === "story") return { kind: "milestone", id: rest.join(":") };
53
- if (scope === "roadmap" && type === "epic") return { kind: "phase", id: rest.join(":") };
54
- if (scope === "arch" && type === "domain") return { kind: "domain", id: rest.join(":") };
55
- if (scope === "arch" && type === "doc") return { kind: "domain", id: rest.join(":") };
56
- if (scope === "arch" && type === "model") return { kind: "decision", id: rest.join(":") };
57
- if (scope === "project") return { kind: "file", id: rest.join(":") };
58
- const kind = (scope as GraphKind) || "file";
59
- return { kind, id: [type, ...rest].filter(Boolean).join(":") };
50
+ const [scope = "file", type = "file", ...rest] = nodeId.split(":");
51
+ if (scope === "roadmap" && type === "task") return { kind: "task", id: rest.join(":") };
52
+ if (scope === "roadmap" && type === "story") return { kind: "milestone", id: rest.join(":") };
53
+ if (scope === "roadmap" && type === "epic") return { kind: "phase", id: rest.join(":") };
54
+ if (scope === "arch" && type === "domain") return { kind: "domain", id: rest.join(":") };
55
+ if (scope === "arch" && type === "doc") return { kind: "domain", id: rest.join(":") };
56
+ if (scope === "arch" && type === "model") return { kind: "decision", id: rest.join(":") };
57
+ if (scope === "project") return { kind: "file", id: rest.join(":") };
58
+ const kind = (scope as GraphKind) || "file";
59
+ return { kind, id: [type, ...rest].filter(Boolean).join(":") };
60
60
  }
61
61
 
62
62
  export function buildNodeMarkdown(node: Node<ArchNodeData>): string {
63
- const { kind, label, subtitle, metadata } = node.data;
64
- const identity = parseNodeId(node.id);
65
- const metadataMarkdown =
66
- metadata.length > 0
67
- ? metadata.map((item) => `- **${item.label}**: ${item.value}`).join("\n")
68
- : "- No metadata available";
63
+ const { kind, label, subtitle, metadata } = node.data;
64
+ const identity = parseNodeId(node.id);
65
+ const metadataMarkdown =
66
+ metadata.length > 0
67
+ ? metadata.map((item) => `- **${item.label}**: ${item.value}`).join("\n")
68
+ : "- No metadata available";
69
69
 
70
- return [
71
- `## ${label}`,
72
- "",
73
- `- **Type**: ${kind}`,
74
- `- **ID**: \`${identity.id}\``,
75
- subtitle ? `- **Subtitle**: ${subtitle}` : "",
76
- "",
77
- "### Metadata",
78
- metadataMarkdown,
79
- ]
80
- .filter(Boolean)
81
- .join("\n");
70
+ return [
71
+ `## ${label}`,
72
+ "",
73
+ `- **Type**: ${kind}`,
74
+ `- **ID**: \`${identity.id}\``,
75
+ subtitle ? `- **Subtitle**: ${subtitle}` : "",
76
+ "",
77
+ "### Metadata",
78
+ metadataMarkdown,
79
+ ]
80
+ .filter(Boolean)
81
+ .join("\n");
82
82
  }