Browse Source

feat(35-02): implement extractSubgraph for selection-aware context

- Implement extractSubgraph function with full selection logic
- Early return for no selection (return all nodes/edges, isScoped=false)
- Filter nodes into selected vs unselected with O(1) Set lookup
- Classify edges: fully within selection, boundary, or outside
- Build type breakdown from unselected nodes
- Identify boundary connections with direction (incoming/outgoing)
- All 8 tests passing (GREEN phase)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
e44bdfab51
  1. 86
      src/lib/chat/subgraphExtractor.ts

86
src/lib/chat/subgraphExtractor.ts

@ -49,6 +49,88 @@ export function extractSubgraph(
edges: WorkflowEdge[],
selectedNodeIds: string[]
): SubgraphResult {
// TODO: Implement
throw new Error("Not implemented");
// Early return for no selection - return everything
if (selectedNodeIds.length === 0) {
return {
selectedNodes: nodes,
selectedEdges: edges,
restSummary: null,
isScoped: false,
};
}
// Build selectedSet for O(1) lookup
const selectedSet = new Set(selectedNodeIds);
// Filter nodes into selected vs unselected
const selectedNodes: WorkflowNode[] = [];
const unselectedNodes: WorkflowNode[] = [];
for (const node of nodes) {
if (selectedSet.has(node.id)) {
selectedNodes.push(node);
} else {
unselectedNodes.push(node);
}
}
// Filter edges: both endpoints in selectedSet -> selectedEdges
const selectedEdges: WorkflowEdge[] = [];
const boundaryEdges: WorkflowEdge[] = [];
for (const edge of edges) {
const sourceSelected = selectedSet.has(edge.source);
const targetSelected = selectedSet.has(edge.target);
if (sourceSelected && targetSelected) {
// Both endpoints selected -> fully within selection
selectedEdges.push(edge);
} else if (sourceSelected || targetSelected) {
// Exactly one endpoint selected -> boundary edge
boundaryEdges.push(edge);
}
// else: neither selected -> ignore
}
// Build type breakdown from unselected nodes
const typeBreakdown: Record<string, number> = {};
for (const node of unselectedNodes) {
const type = node.type;
typeBreakdown[type] = (typeBreakdown[type] || 0) + 1;
}
// Build boundaryConnections from boundary edges with direction
const boundaryConnections: BoundaryConnection[] = boundaryEdges.map((edge) => {
const sourceSelected = selectedSet.has(edge.source);
const targetSelected = selectedSet.has(edge.target);
if (targetSelected) {
// Target is selected, source is not -> incoming
return {
direction: "incoming" as const,
selectedNodeId: edge.target,
otherNodeId: edge.source,
handleType: edge.targetHandle || "unknown",
};
} else {
// Source is selected, target is not -> outgoing
return {
direction: "outgoing" as const,
selectedNodeId: edge.source,
otherNodeId: edge.target,
handleType: edge.sourceHandle || "unknown",
};
}
});
return {
selectedNodes,
selectedEdges,
restSummary: {
nodeCount: unselectedNodes.length,
typeBreakdown,
boundaryConnections,
},
isScoped: true,
};
}

Loading…
Cancel
Save