@abhinav2203/codeflow-canvas 0.1.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/abhinav2203-codeflow-canvas-0.1.0.tgz +0 -0
- package/dist/bin/cli.d.ts +3 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +84 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/components/blueprint-workbench.d.ts +2 -0
- package/dist/components/blueprint-workbench.d.ts.map +1 -0
- package/dist/components/blueprint-workbench.js +144 -0
- package/dist/components/blueprint-workbench.js.map +1 -0
- package/dist/components/code-diff-editor.d.ts +12 -0
- package/dist/components/code-diff-editor.d.ts.map +1 -0
- package/dist/components/code-diff-editor.js +39 -0
- package/dist/components/code-diff-editor.js.map +1 -0
- package/dist/components/code-editor.d.ts +25 -0
- package/dist/components/code-editor.d.ts.map +1 -0
- package/dist/components/code-editor.js +264 -0
- package/dist/components/code-editor.js.map +1 -0
- package/dist/components/file-tabs.d.ts +5 -0
- package/dist/components/file-tabs.d.ts.map +1 -0
- package/dist/components/file-tabs.js +164 -0
- package/dist/components/file-tabs.js.map +1 -0
- package/dist/components/file-tree.d.ts +7 -0
- package/dist/components/file-tree.d.ts.map +1 -0
- package/dist/components/file-tree.js +176 -0
- package/dist/components/file-tree.js.map +1 -0
- package/dist/components/graph-canvas.d.ts +25 -0
- package/dist/components/graph-canvas.d.ts.map +1 -0
- package/dist/components/graph-canvas.js +224 -0
- package/dist/components/graph-canvas.js.map +1 -0
- package/dist/components/ide-layout.d.ts +10 -0
- package/dist/components/ide-layout.d.ts.map +1 -0
- package/dist/components/ide-layout.js +40 -0
- package/dist/components/ide-layout.js.map +1 -0
- package/dist/components/ide-workbench.d.ts +4 -0
- package/dist/components/ide-workbench.d.ts.map +1 -0
- package/dist/components/ide-workbench.js +6 -0
- package/dist/components/ide-workbench.js.map +1 -0
- package/dist/components/index.d.ts +13 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +13 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/monaco-setup.d.ts +4 -0
- package/dist/components/monaco-setup.d.ts.map +1 -0
- package/dist/components/monaco-setup.js +34 -0
- package/dist/components/monaco-setup.js.map +1 -0
- package/dist/components/opencode-settings.d.ts +8 -0
- package/dist/components/opencode-settings.d.ts.map +1 -0
- package/dist/components/opencode-settings.js +33 -0
- package/dist/components/opencode-settings.js.map +1 -0
- package/dist/components/policy-workbench.d.ts +2 -0
- package/dist/components/policy-workbench.d.ts.map +1 -0
- package/dist/components/policy-workbench.js +102 -0
- package/dist/components/policy-workbench.js.map +1 -0
- package/dist/components/ts-language-service.d.ts +14 -0
- package/dist/components/ts-language-service.d.ts.map +1 -0
- package/dist/components/ts-language-service.js +123 -0
- package/dist/components/ts-language-service.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/browser/storage.d.ts +16 -0
- package/dist/lib/browser/storage.d.ts.map +1 -0
- package/dist/lib/browser/storage.js +18 -0
- package/dist/lib/browser/storage.js.map +1 -0
- package/dist/lib/edit.d.ts +14 -0
- package/dist/lib/edit.d.ts.map +1 -0
- package/dist/lib/edit.js +57 -0
- package/dist/lib/edit.js.map +1 -0
- package/dist/lib/flow-view.d.ts +80 -0
- package/dist/lib/flow-view.d.ts.map +1 -0
- package/dist/lib/flow-view.js +850 -0
- package/dist/lib/flow-view.js.map +1 -0
- package/dist/lib/heatmap.d.ts +28 -0
- package/dist/lib/heatmap.d.ts.map +1 -0
- package/dist/lib/heatmap.js +61 -0
- package/dist/lib/heatmap.js.map +1 -0
- package/dist/lib/index.d.ts +9 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/node-navigation.d.ts +36 -0
- package/dist/lib/node-navigation.d.ts.map +1 -0
- package/dist/lib/node-navigation.js +52 -0
- package/dist/lib/node-navigation.js.map +1 -0
- package/dist/lib/traces.d.ts +3 -0
- package/dist/lib/traces.d.ts.map +1 -0
- package/dist/lib/traces.js +64 -0
- package/dist/lib/traces.js.map +1 -0
- package/dist/lib/types.d.ts +57 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +7 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/store/blueprint-store.d.ts +35 -0
- package/dist/store/blueprint-store.d.ts.map +1 -0
- package/dist/store/blueprint-store.js +79 -0
- package/dist/store/blueprint-store.js.map +1 -0
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.d.ts.map +1 -0
- package/dist/store/index.js +2 -0
- package/dist/store/index.js.map +1 -0
- package/package.json +52 -0
- package/scripts/wrap-cli.mjs +15 -0
- package/src/bin/cli.ts +128 -0
- package/src/components/blueprint-workbench.tsx +305 -0
- package/src/components/code-diff-editor.tsx +80 -0
- package/src/components/code-editor.tsx +389 -0
- package/src/components/file-tabs.tsx +288 -0
- package/src/components/file-tree.tsx +301 -0
- package/src/components/graph-canvas.tsx +404 -0
- package/src/components/ide-layout.tsx +104 -0
- package/src/components/ide-workbench.tsx +5 -0
- package/src/components/index.ts +12 -0
- package/src/components/monaco-setup.ts +67 -0
- package/src/components/opencode-settings.tsx +82 -0
- package/src/components/policy-workbench.tsx +233 -0
- package/src/components/ts-language-service.ts +170 -0
- package/src/index.ts +54 -0
- package/src/lib/browser/storage.ts +19 -0
- package/src/lib/edit.ts +74 -0
- package/src/lib/flow-view.ts +1176 -0
- package/src/lib/heatmap.ts +103 -0
- package/src/lib/index.ts +41 -0
- package/src/lib/node-navigation.ts +76 -0
- package/src/lib/traces.ts +79 -0
- package/src/lib/types.ts +79 -0
- package/src/store/blueprint-store.ts +136 -0
- package/src/store/index.ts +2 -0
- package/test-fixtures/minimal-blueprint.json +34 -0
- package/test-fixtures/sample-blueprint.json +184 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.json +22 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { BlueprintGraph, TraceState } from "@abhinav2203/codeflow-core/schema";
|
|
2
|
+
import { idleTraceState } from "@abhinav2203/codeflow-core/schema";
|
|
3
|
+
|
|
4
|
+
export type HeatmapNodeMetric = {
|
|
5
|
+
nodeId: string;
|
|
6
|
+
name: string;
|
|
7
|
+
callCount: number;
|
|
8
|
+
errorCount: number;
|
|
9
|
+
errorRate: number;
|
|
10
|
+
totalDurationMs: number;
|
|
11
|
+
avgDurationMs: number;
|
|
12
|
+
/** 0–1: normalized across all nodes by avg latency */
|
|
13
|
+
latencyIntensity: number;
|
|
14
|
+
/** 0–1: normalized across all nodes by error rate */
|
|
15
|
+
errorIntensity: number;
|
|
16
|
+
/** 0–1: combined heat score (errors weighted most heavily, then latency, then activity) */
|
|
17
|
+
heatIntensity: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type HeatmapData = {
|
|
21
|
+
nodes: HeatmapNodeMetric[];
|
|
22
|
+
maxCallCount: number;
|
|
23
|
+
maxAvgDurationMs: number;
|
|
24
|
+
maxErrorRate: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getTraceState = (raw: BlueprintGraph["nodes"][number]): TraceState =>
|
|
28
|
+
raw.traceState ?? idleTraceState();
|
|
29
|
+
|
|
30
|
+
export const computeHeatmap = (graph: BlueprintGraph): HeatmapData => {
|
|
31
|
+
const raw = graph.nodes.map((node) => {
|
|
32
|
+
const state = getTraceState(node);
|
|
33
|
+
const avgDurationMs = state.count > 0 ? state.totalDurationMs / state.count : 0;
|
|
34
|
+
const errorRate = state.count > 0 ? state.errors / state.count : 0;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
nodeId: node.id,
|
|
38
|
+
name: node.name,
|
|
39
|
+
callCount: state.count,
|
|
40
|
+
errorCount: state.errors,
|
|
41
|
+
errorRate,
|
|
42
|
+
totalDurationMs: state.totalDurationMs,
|
|
43
|
+
avgDurationMs
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const maxCallCount = Math.max(...raw.map((m) => m.callCount), 1);
|
|
48
|
+
const maxAvgDurationMs = Math.max(...raw.map((m) => m.avgDurationMs), 1);
|
|
49
|
+
const maxErrorRate = Math.max(...raw.map((m) => m.errorRate), Number.EPSILON);
|
|
50
|
+
|
|
51
|
+
const nodes: HeatmapNodeMetric[] = raw.map((m) => {
|
|
52
|
+
const latencyIntensity = m.avgDurationMs / maxAvgDurationMs;
|
|
53
|
+
const errorIntensity = m.errorRate / maxErrorRate;
|
|
54
|
+
const activityIntensity = m.callCount / maxCallCount;
|
|
55
|
+
const heatIntensity = Math.min(
|
|
56
|
+
1,
|
|
57
|
+
errorIntensity * 0.5 + latencyIntensity * 0.35 + activityIntensity * 0.15
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return { ...m, latencyIntensity, errorIntensity, heatIntensity };
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return { nodes, maxCallCount, maxAvgDurationMs, maxErrorRate };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Map a 0–1 heat intensity to a CSS rgba colour for heatmap backgrounds */
|
|
67
|
+
export const heatColor = (intensity: number): string => {
|
|
68
|
+
if (intensity <= 0) {
|
|
69
|
+
return "rgba(240,253,244,0.0)";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (intensity < 0.33) {
|
|
73
|
+
const alpha = intensity / 0.33;
|
|
74
|
+
return `rgba(34,197,94,${(alpha * 0.18).toFixed(3)})`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (intensity < 0.66) {
|
|
78
|
+
const alpha = (intensity - 0.33) / 0.33;
|
|
79
|
+
return `rgba(245,158,11,${(0.18 + alpha * 0.2).toFixed(3)})`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const alpha = (intensity - 0.66) / 0.34;
|
|
83
|
+
return `rgba(239,68,68,${(0.22 + alpha * 0.26).toFixed(3)})`;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** Map a 0–1 heat intensity to a CSS box-shadow glow string */
|
|
87
|
+
export const heatGlow = (intensity: number): string => {
|
|
88
|
+
if (intensity <= 0) {
|
|
89
|
+
return "none";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (intensity < 0.33) {
|
|
93
|
+
return `0 0 ${Math.round(8 + intensity * 24)}px rgba(34,197,94,${(intensity * 0.8).toFixed(3)})`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (intensity < 0.66) {
|
|
97
|
+
const scaled = (intensity - 0.33) / 0.33;
|
|
98
|
+
return `0 0 ${Math.round(16 + scaled * 28)}px rgba(245,158,11,${(0.5 + scaled * 0.4).toFixed(3)})`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const scaled = (intensity - 0.66) / 0.34;
|
|
102
|
+
return `0 0 ${Math.round(28 + scaled * 32)}px rgba(239,68,68,${(0.6 + scaled * 0.38).toFixed(3)})`;
|
|
103
|
+
};
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export { computeHeatmap, heatColor, heatGlow } from "./heatmap.js";
|
|
2
|
+
export type { HeatmapData, HeatmapNodeMetric } from "./heatmap.js";
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
applyTraceOverlay
|
|
6
|
+
} from "./traces.js";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
getNavigationTarget,
|
|
10
|
+
getNodesWithNavigation,
|
|
11
|
+
formatNavigationTarget,
|
|
12
|
+
hasNavigationMetadata,
|
|
13
|
+
isValidNavigationTarget
|
|
14
|
+
} from "./node-navigation.js";
|
|
15
|
+
export type { NavigationTarget } from "./node-navigation.js";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
addNodeToGraph,
|
|
19
|
+
addEdgeToGraph,
|
|
20
|
+
deleteNodeFromGraph
|
|
21
|
+
} from "./edit.js";
|
|
22
|
+
|
|
23
|
+
export {
|
|
24
|
+
buildFlowNodes,
|
|
25
|
+
buildFlowEdges,
|
|
26
|
+
buildGhostFlowNodes,
|
|
27
|
+
buildDetailFlow,
|
|
28
|
+
indexRuntimeExecutionResult,
|
|
29
|
+
buildExecutionProjection
|
|
30
|
+
} from "./flow-view.js";
|
|
31
|
+
export type {
|
|
32
|
+
NodeHealthState,
|
|
33
|
+
FlowExecutionStatus,
|
|
34
|
+
FlowExecutionState,
|
|
35
|
+
FlowExecutionIndex,
|
|
36
|
+
FlowExecutionProjection,
|
|
37
|
+
FlowNodeData,
|
|
38
|
+
InspectorSection,
|
|
39
|
+
DetailFlowItem,
|
|
40
|
+
DetailFlowGraph
|
|
41
|
+
} from "./flow-view.js";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { BlueprintNode } from "@abhinav2203/codeflow-core/schema";
|
|
2
|
+
import type { SourceLocation } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Extended BlueprintNode type with source location support.
|
|
6
|
+
* This property exists in codeflow-evolution but not yet in codeflow-core.
|
|
7
|
+
*/
|
|
8
|
+
type BlueprintNodeWithLocation = BlueprintNode & { sourceLocation?: SourceLocation };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Navigate from a graph node to its source location in the editor.
|
|
12
|
+
* This is the critical link between the graph view and Monaco editor.
|
|
13
|
+
*/
|
|
14
|
+
export interface NavigationTarget {
|
|
15
|
+
filePath: string;
|
|
16
|
+
lineNumber: number;
|
|
17
|
+
endLineNumber?: number;
|
|
18
|
+
columnStart?: number;
|
|
19
|
+
columnEnd?: number;
|
|
20
|
+
symbolName?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Extract navigation target from a blueprint node.
|
|
25
|
+
* Returns null if no source location is available.
|
|
26
|
+
*/
|
|
27
|
+
export function getNavigationTarget(node: BlueprintNode): NavigationTarget | null {
|
|
28
|
+
const location = (node as BlueprintNodeWithLocation).sourceLocation;
|
|
29
|
+
|
|
30
|
+
if (!location) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
filePath: location.filePath,
|
|
36
|
+
lineNumber: location.startLine,
|
|
37
|
+
endLineNumber: location.endLine,
|
|
38
|
+
columnStart: location.startColumn,
|
|
39
|
+
columnEnd: location.endColumn,
|
|
40
|
+
symbolName: location.symbolName
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if a node has navigation metadata available.
|
|
46
|
+
*/
|
|
47
|
+
export function hasNavigationMetadata(node: BlueprintNode): boolean {
|
|
48
|
+
return (node as BlueprintNodeWithLocation).sourceLocation !== undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Get all nodes that have navigation metadata from a node list.
|
|
53
|
+
*/
|
|
54
|
+
export function getNodesWithNavigation(nodes: BlueprintNode[]): BlueprintNode[] {
|
|
55
|
+
return nodes.filter(hasNavigationMetadata);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Format a navigation target for display/logging.
|
|
60
|
+
*/
|
|
61
|
+
export function formatNavigationTarget(target: NavigationTarget): string {
|
|
62
|
+
const { filePath, lineNumber, symbolName } = target;
|
|
63
|
+
const symbol = symbolName ? ` (${symbolName})` : "";
|
|
64
|
+
return `${filePath}:${lineNumber}${symbol}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validate that a navigation target points to a valid location.
|
|
69
|
+
* Returns false if the target has invalid or missing data.
|
|
70
|
+
*/
|
|
71
|
+
export function isValidNavigationTarget(target: NavigationTarget | null): target is NavigationTarget {
|
|
72
|
+
if (!target) return false;
|
|
73
|
+
if (!target.filePath) return false;
|
|
74
|
+
if (!target.lineNumber || target.lineNumber < 1) return false;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { BlueprintGraph, BlueprintNode, TraceSpan, TraceStatus } from "@abhinav2203/codeflow-core/schema";
|
|
2
|
+
import { idleTraceState } from "@abhinav2203/codeflow-core/schema";
|
|
3
|
+
|
|
4
|
+
const statusPriority: Record<TraceStatus, number> = {
|
|
5
|
+
idle: 0,
|
|
6
|
+
success: 1,
|
|
7
|
+
warning: 2,
|
|
8
|
+
error: 3
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const resolveNodeId = (graph: BlueprintGraph, span: TraceSpan): string | null => {
|
|
12
|
+
if (span.blueprintNodeId && graph.nodes.some((node) => node.id === span.blueprintNodeId)) {
|
|
13
|
+
return span.blueprintNodeId;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const byName = graph.nodes.find((node) => node.name === span.name);
|
|
17
|
+
if (byName) {
|
|
18
|
+
return byName.id;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (span.path) {
|
|
22
|
+
const byPath = graph.nodes.find((node) => node.path === span.path);
|
|
23
|
+
if (byPath) {
|
|
24
|
+
return byPath.id;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const mergeNodeTrace = (node: BlueprintNode, span: TraceSpan): BlueprintNode => {
|
|
32
|
+
const traceState = node.traceState ?? idleTraceState();
|
|
33
|
+
const nextStatus =
|
|
34
|
+
statusPriority[span.status] > statusPriority[traceState.status] ? span.status : traceState.status;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
...node,
|
|
38
|
+
traceRefs: [...new Set([...(node.traceRefs ?? []), span.spanId])],
|
|
39
|
+
traceState: {
|
|
40
|
+
status: nextStatus,
|
|
41
|
+
count: traceState.count + 1,
|
|
42
|
+
errors: traceState.errors + (span.status === "error" ? 1 : 0),
|
|
43
|
+
totalDurationMs: traceState.totalDurationMs + span.durationMs,
|
|
44
|
+
lastSpanIds: [...new Set([span.spanId, ...traceState.lastSpanIds])].slice(0, 5)
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const applyTraceOverlay = (graph: BlueprintGraph, spans: TraceSpan[]): BlueprintGraph => {
|
|
50
|
+
const nodeMap = new Map<string, BlueprintNode>(
|
|
51
|
+
graph.nodes.map((node) => [
|
|
52
|
+
node.id,
|
|
53
|
+
{
|
|
54
|
+
...node,
|
|
55
|
+
traceRefs: [] as string[],
|
|
56
|
+
traceState: idleTraceState()
|
|
57
|
+
}
|
|
58
|
+
])
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
for (const span of spans) {
|
|
62
|
+
const nodeId = resolveNodeId(graph, span);
|
|
63
|
+
if (!nodeId) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const current = nodeMap.get(nodeId);
|
|
68
|
+
if (!current) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
nodeMap.set(nodeId, mergeNodeTrace(current, span));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
...graph,
|
|
77
|
+
nodes: [...nodeMap.values()]
|
|
78
|
+
};
|
|
79
|
+
};
|
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local type stubs for types referenced from @abhinav2203/codeflow-core
|
|
3
|
+
* that are not yet available in the published package.
|
|
4
|
+
* These should be replaced with proper imports when codeflow-core is updated.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Types from codeflow-analysis (not yet in codeflow-core)
|
|
8
|
+
export type CycleReport = {
|
|
9
|
+
cycles: Array<{
|
|
10
|
+
nodeIds: string[];
|
|
11
|
+
path: string[];
|
|
12
|
+
}>;
|
|
13
|
+
totalCycles: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type SmellReport = {
|
|
17
|
+
smells: Array<{
|
|
18
|
+
nodeId: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
message: string;
|
|
21
|
+
severity: "error" | "warning" | "info";
|
|
22
|
+
}>;
|
|
23
|
+
totalSmells: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type GraphMetrics = {
|
|
27
|
+
totalNodes: number;
|
|
28
|
+
totalEdges: number;
|
|
29
|
+
avgDegree: number;
|
|
30
|
+
maxDegree: number;
|
|
31
|
+
density: number;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Types from codeflow-refactor (not yet in codeflow-core)
|
|
35
|
+
export type RefactorReport = {
|
|
36
|
+
suggestions: Array<{
|
|
37
|
+
nodeId: string;
|
|
38
|
+
kind: string;
|
|
39
|
+
description: string;
|
|
40
|
+
effort: "low" | "medium" | "high";
|
|
41
|
+
}>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type HealResult = {
|
|
45
|
+
healed: boolean;
|
|
46
|
+
nodeId?: string;
|
|
47
|
+
fix?: string;
|
|
48
|
+
error?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Types from codeflow-core/opencode (not yet in codeflow-core)
|
|
52
|
+
export type OpencodeProvider =
|
|
53
|
+
| "anthropic"
|
|
54
|
+
| "openai"
|
|
55
|
+
| "google"
|
|
56
|
+
| "azure"
|
|
57
|
+
| "groq"
|
|
58
|
+
| "mistral"
|
|
59
|
+
| "cohere"
|
|
60
|
+
| "perplexity"
|
|
61
|
+
| "openrouter"
|
|
62
|
+
| "bedrock"
|
|
63
|
+
| "local";
|
|
64
|
+
|
|
65
|
+
export type OpencodeServerInfo = {
|
|
66
|
+
status: "stopped" | "starting" | "running" | "error";
|
|
67
|
+
url?: string;
|
|
68
|
+
error?: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// SourceLocation type for node navigation
|
|
72
|
+
export interface SourceLocation {
|
|
73
|
+
filePath: string;
|
|
74
|
+
startLine: number;
|
|
75
|
+
endLine: number;
|
|
76
|
+
startColumn?: number;
|
|
77
|
+
endColumn?: number;
|
|
78
|
+
symbolName?: string;
|
|
79
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { create } from "zustand";
|
|
4
|
+
|
|
5
|
+
import type { BlueprintGraph, BlueprintNode } from "@abhinav2203/codeflow-core/schema";
|
|
6
|
+
|
|
7
|
+
type GraphStateUpdater = BlueprintGraph | null | ((current: BlueprintGraph | null) => BlueprintGraph | null);
|
|
8
|
+
type NodeUpdater = Partial<BlueprintNode> | ((node: BlueprintNode) => BlueprintNode);
|
|
9
|
+
|
|
10
|
+
export type WorkbenchMode = "graph" | "ide";
|
|
11
|
+
|
|
12
|
+
export interface FloatingGraphPanel {
|
|
13
|
+
visible: boolean;
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface BlueprintStore {
|
|
21
|
+
graph: BlueprintGraph | null;
|
|
22
|
+
setGraph: (next: GraphStateUpdater) => void;
|
|
23
|
+
updateNode: (id: string, patch: NodeUpdater) => void;
|
|
24
|
+
openFiles: string[];
|
|
25
|
+
activeFile: string | null;
|
|
26
|
+
setOpenFiles: (paths: string[]) => void;
|
|
27
|
+
setActiveFile: (path: string | null) => void;
|
|
28
|
+
closeFile: (path: string) => void;
|
|
29
|
+
repoPath: string | null;
|
|
30
|
+
setRepoPath: (path: string | null) => void;
|
|
31
|
+
mode: WorkbenchMode;
|
|
32
|
+
setMode: (mode: WorkbenchMode) => void;
|
|
33
|
+
floatingGraph: FloatingGraphPanel;
|
|
34
|
+
setFloatingGraph: (panel: Partial<FloatingGraphPanel>) => void;
|
|
35
|
+
selectedNodeId: string | null;
|
|
36
|
+
setSelectedNodeId: (id: string | null) => void;
|
|
37
|
+
dirtyFiles: Record<string, boolean>;
|
|
38
|
+
setFileDirty: (path: string, dirty: boolean) => void;
|
|
39
|
+
clearFileDirty: (path: string) => void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const resolveGraphUpdate = (
|
|
43
|
+
current: BlueprintGraph | null,
|
|
44
|
+
next: GraphStateUpdater
|
|
45
|
+
): BlueprintGraph | null => (typeof next === "function" ? next(current) : next);
|
|
46
|
+
|
|
47
|
+
const resolveNodeUpdate = (node: BlueprintNode, patch: NodeUpdater): BlueprintNode =>
|
|
48
|
+
typeof patch === "function" ? patch(node) : { ...node, ...patch };
|
|
49
|
+
|
|
50
|
+
export const useBlueprintStore = create<BlueprintStore>((set) => ({
|
|
51
|
+
graph: null,
|
|
52
|
+
setGraph: (next) =>
|
|
53
|
+
set((state) => ({
|
|
54
|
+
graph: resolveGraphUpdate(state.graph, next)
|
|
55
|
+
})),
|
|
56
|
+
updateNode: (id, patch) =>
|
|
57
|
+
set((state) => {
|
|
58
|
+
if (!state.graph) {
|
|
59
|
+
return state;
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
graph: {
|
|
63
|
+
...state.graph,
|
|
64
|
+
nodes: state.graph.nodes.map((node) =>
|
|
65
|
+
node.id === id ? resolveNodeUpdate(node, patch) : node
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}),
|
|
70
|
+
openFiles: [],
|
|
71
|
+
activeFile: null,
|
|
72
|
+
setOpenFiles: (paths) =>
|
|
73
|
+
set(() => ({
|
|
74
|
+
openFiles: paths
|
|
75
|
+
})),
|
|
76
|
+
setActiveFile: (path) =>
|
|
77
|
+
set((state) => ({
|
|
78
|
+
activeFile: path,
|
|
79
|
+
floatingGraph: {
|
|
80
|
+
...state.floatingGraph,
|
|
81
|
+
visible: path !== null
|
|
82
|
+
}
|
|
83
|
+
})),
|
|
84
|
+
closeFile: (path) =>
|
|
85
|
+
set((state) => {
|
|
86
|
+
const nextOpenFiles = state.openFiles.filter((f) => f !== path);
|
|
87
|
+
const nextActiveFile =
|
|
88
|
+
state.activeFile === path
|
|
89
|
+
? nextOpenFiles[nextOpenFiles.length - 1] ?? null
|
|
90
|
+
: state.activeFile;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
openFiles: nextOpenFiles,
|
|
94
|
+
activeFile: nextActiveFile,
|
|
95
|
+
floatingGraph: {
|
|
96
|
+
...state.floatingGraph,
|
|
97
|
+
visible: nextActiveFile !== null
|
|
98
|
+
},
|
|
99
|
+
dirtyFiles: { ...state.dirtyFiles, [path]: false }
|
|
100
|
+
};
|
|
101
|
+
}),
|
|
102
|
+
repoPath: null,
|
|
103
|
+
setRepoPath: (path) => set(() => ({ repoPath: path })),
|
|
104
|
+
mode: "ide",
|
|
105
|
+
setMode: (mode) =>
|
|
106
|
+
set((state) => ({
|
|
107
|
+
mode,
|
|
108
|
+
floatingGraph: {
|
|
109
|
+
...state.floatingGraph,
|
|
110
|
+
visible: state.activeFile !== null
|
|
111
|
+
}
|
|
112
|
+
})),
|
|
113
|
+
floatingGraph: {
|
|
114
|
+
visible: false,
|
|
115
|
+
x: 0,
|
|
116
|
+
y: 0,
|
|
117
|
+
width: 400,
|
|
118
|
+
height: 350
|
|
119
|
+
},
|
|
120
|
+
setFloatingGraph: (panel) =>
|
|
121
|
+
set((state) => ({
|
|
122
|
+
floatingGraph: { ...state.floatingGraph, ...panel }
|
|
123
|
+
})),
|
|
124
|
+
selectedNodeId: null,
|
|
125
|
+
setSelectedNodeId: (id) => set(() => ({ selectedNodeId: id })),
|
|
126
|
+
dirtyFiles: {},
|
|
127
|
+
setFileDirty: (path, dirty) =>
|
|
128
|
+
set((state) => ({
|
|
129
|
+
dirtyFiles: { ...state.dirtyFiles, [path]: dirty }
|
|
130
|
+
})),
|
|
131
|
+
clearFileDirty: (path) =>
|
|
132
|
+
set((state) => {
|
|
133
|
+
const { [path]: _, ...rest } = state.dirtyFiles;
|
|
134
|
+
return { dirtyFiles: rest };
|
|
135
|
+
})
|
|
136
|
+
}));
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { BlueprintGraph } from "@abhinav2203/codeflow-core/schema";
|
|
2
|
+
|
|
3
|
+
export const minimalBlueprint: BlueprintGraph = {
|
|
4
|
+
projectName: "Minimal Blueprint",
|
|
5
|
+
phase: "spec",
|
|
6
|
+
nodes: [
|
|
7
|
+
{
|
|
8
|
+
id: "node-function-1",
|
|
9
|
+
kind: "function",
|
|
10
|
+
name: "hello",
|
|
11
|
+
summary: "Say hello",
|
|
12
|
+
path: "src/hello.ts",
|
|
13
|
+
signature: "hello(name: string): string",
|
|
14
|
+
contract: {
|
|
15
|
+
inputs: [{ name: "name", type: "string", description: "Name to greet" }],
|
|
16
|
+
outputs: [{ name: "greeting", type: "string", description: "Greeting message" }],
|
|
17
|
+
responsibilities: ["Return a greeting"],
|
|
18
|
+
dependencies: [],
|
|
19
|
+
methods: [],
|
|
20
|
+
attributes: [],
|
|
21
|
+
calls: [],
|
|
22
|
+
sideEffects: [],
|
|
23
|
+
errors: [],
|
|
24
|
+
notes: [],
|
|
25
|
+
summary: "Say hello"
|
|
26
|
+
},
|
|
27
|
+
sourceRefs: [{ kind: "file", path: "src/hello.ts", section: "hello" }],
|
|
28
|
+
generatedRefs: [],
|
|
29
|
+
traceRefs: []
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
edges: [],
|
|
33
|
+
workflows: []
|
|
34
|
+
};
|